Files
nobat724_front/.claude/prompt/payment-flow-frontend.md
T

6.6 KiB

جریان پرداخت سمت کلاینت — انتقال از طریق بک‌اند + صفحات نتیجه (Frontend)

پروژه

nobat724_front (سایت عمومی). cross-repo — پرامپت همتا (Backend، اول اجرا شود): clinicpro/.claude/prompt/payment-flow-architecture.md. قرارداد API توسط این کلاینت مصرف می‌شود.

زمینه

طبق معمار: هیچ Frontend نباید مستقیم با بانک صحبت کند؛ Backend مرجع است و انتقال به بانک و بازگشت از طریق Backend انجام می‌شود. بخش زیادی قبلاً پیاده شده:

  • components/appointment/paying/index.js: درگاه‌ها از GET /api/v1/payment/config (data.gateways فعال) خوانده می‌شوند؛ کلیک پرداخت → POST /api/v1/payment/appointment (authenticated XHR) → دریافت pay_urlwindow.location.href = pay_url (انتقال full-page به بک‌اند؛ بک‌اند خودش به بانک می‌رود).
  • frontend_address = ${window.location.origin}/payment/result → بک‌اند بعد از callback به همین دامنهٔ مبدأ برمی‌گردد با ?payment_uuid=..&status=...
  • app/payment/result/page.js وضعیت را می‌خواند و به /payment/${uuid} هدایت می‌کند.

مشکل / هدف

طبق spec باید این موارد دقیق و کامل باشند:

  1. کلاینت هرگز مستقیم به بانک نرود — فقط به pay_url (بک‌اند). (عمدتاً انجام شده؛ باید تأیید و تثبیت شود.)
  2. صفحات نتیجهٔ استانداردsuccess / failed با نمایش وضعیت پرداخت و لینک‌های اقدام، به‌جای هدایت خام.
  3. سازگاری با تغییر قرارداد Backend — اگر Backend پاسخ GET /api/v1/payment/{uuid} را از double-nested به flat تغییر داد (وظیفهٔ backend)، خواندن در app/payment/[uuid]/page.js باید هماهنگ شود.
  4. حالت انصراف/شکست — وقتی status برابر canceled/failed است، پیام مناسب و امکان تلاش مجدد.

فایل‌های مرتبط

فایل نقش
components/appointment/paying/index.js دکمهٔ پرداخت + انتخاب درگاه + هدایت به pay_url
app/payment/result/page.js صفحهٔ بازگشت از بک‌اند (?payment_uuid&status)
app/payment/[uuid]/page.js نمایش نتیجهٔ پرداخت بر اساس getPayment
services/response.js postAppointmentPayment, getPaymentConfig, getPayment

وضعیت فعلی

// components/appointment/paying/index.js  (کلیک پرداخت)
const res = await request.postAppointmentPayment({
  appointment_uuid: appointmentId,
  gateway: testMode ? "mellat" : selectedBank,
  frontend_address: `${window.location.origin}/payment/result`,
});
const payUrl = res?.data?.pay_url || res?.data?.redirect_url;
if (payUrl) window.location.href = payUrl;   // انتقال full-page به بک‌اند ✅
// app/payment/result/page.js  (بازگشت از بک‌اند)
const paymentUuid = searchParams.get("payment_uuid");
if (paymentUuid) router.replace(`/payment/${paymentUuid}`);
else router.replace("/dashboard?sidebar=2");

وظایف

۱. تثبیت انتقال از طریق بک‌اند (بدون تماس مستقیم با بانک)

مطمئن شو در handlePayment هیچ URL بانکی مستقیم باز نمی‌شود؛ فقط pay_url. اگر pay_url نبود (پاسخ ناقص)، به‌جای رفتن به مرحلهٔ بعد، خطای کاربرپسند نشان بده:

const payUrl = res?.data?.pay_url;
if (payUrl) { window.location.href = payUrl; return; }
toast/alert("خطا در شروع پرداخت. دوباره تلاش کنید.");
setLoading(false);

(اتکا به redirect_url بانک را حذف کن؛ منبع درست فقط pay_url است.)

۲. صفحهٔ نتیجه بر اساس status

app/payment/result/page.js علاوه بر payment_uuid، پارامتر status را هم بخواند و بر اساس آن رفتار کند:

const status = searchParams.get("status"); // success | failed | canceled | pending
const paymentUuid = searchParams.get("payment_uuid");
if (status === "success" && paymentUuid) router.replace(`/payment/${paymentUuid}`);
else router.replace(`/payment/${paymentUuid ?? ""}?status=${status ?? "failed"}`);

در app/payment/[uuid]/page.js وضعیت را از getPayment(uuid) بگیر (source of truth بک‌اند، نه فقط query) و کارت نتیجه را نشان بده: موفق (سبز، جزئیات نوبت/کدرهگیری)، ناموفق/لغو (قرمز، دکمهٔ «تلاش مجدد» → بازگشت به صفحهٔ رزرو پزشک).

۳. سازگاری با شکل پاسخ getPayment

اگر Backend (وظیفهٔ همتا) پاسخ GET /api/v1/payment/{uuid} را flat کرد، خواندن را طوری بنویس که هر دو حالت کار کند:

const data = await request.getPayment(uuid);
const payment = data?.data?.data ?? data?.data; // سازگار با flat و nested

۴. UX انصراف/شکست

اگر status !== success: پیام «پرداخت انجام نشد یا لغو شد» + وضعیت واقعی از payment.status + دکمهٔ تلاش مجدد. هیچ اطلاعات حساسی نمایش نده.

نکات مهم

  • قرارداد API را از Backend بگیر: POST /api/v1/payment/appointment{ payment_uuid, pay_url, order_id }؛ بازگشت callback → frontend_address?payment_uuid&status. (منبع: clinicpro/docs/api/payment.md.)
  • چرا یک XHR لازم است: ساخت Payment نیازمند احراز مالکیت سفارش است و JWT در کوکیِ همین دامنه است؛ redirect full-page کوکی cross-domain نمی‌برد. پس الگوی «XHR authenticated برای ساخت + سپس redirect full-page به pay_url» درست و امن است — این را حفظ کن (نه fetch مستقیم به بانک).
  • App Router؛ صفحات نتیجه generateMetadata با noindex داشته باشند (صفحهٔ تراکنش نباید ایندکس شود).
  • استایل MUI v5 + Tailwind، RTL، فونت Vazir، Jalali؛ کلاس‌های موجود.
  • API client فقط از طریق services/response.jsrequest.*؛ getPayment با { requireAuth: true }.
  • بعد از تغییر: npm run build در nobat724_front.