المدفوعات غير المتزامنة
بعض طرق الدفع بتتدفع بعد الـ checkout. الجلسة بتكتمل الأول والفلوس بتوصل بعدين، فنفّذ الطلب بناءً على paymentStatus مش على الاكتمال.
فوري متاحة على كل حساب وفي كل طريقة تكامل، بما فيها الدفع المستضاف وروابط الدفع، فكل معالِج webhook لازم يتعامل مع ده. مع فوري، العميل بياخد رقم مرجعي عند الدفع وبيدفعه بعدين في أي منفذ أو من تطبيق فوري. الـ checkout بيخلص قبل ما الفلوس توصل.
قاعدة واحدة بتغطي الموضوع: status بتقول إذا كان العميل خلّص الـ checkout، وpaymentStatus بتقول إذا كانت الفلوس وصلت. نفّذ الطلب بناءً على paymentStatus، ومتعتمدش أبدًا على status لوحدها.
checkout.session.completed بتوصل بـ paymentStatus: "unpaid" لدفعة فوري. المعالِج اللي بينفّذ
الطلب على الحدث من غير ما يقرا paymentStatus هيشحن طلبات ماتدفعتش.
الأحداث التلاتة
| الحدث | paymentStatus | اللي تعمله |
|---|---|---|
checkout.session.completed | unpaid | سجّل الطلب على إنه في انتظار الدفع. متنفّذش. |
checkout.session.async_payment_succeeded | paid | نفّذ الطلب. |
checkout.session.async_payment_failed | unpaid | اقفل الطلب. العميل محتاج جلسة جديدة. |
البطاقات وValU بتطلّق checkout.session.completed بـ paymentStatus: "paid" ومفيش غيره.
الـ data.object في الأحداث التلاتة هو Checkout SessionAPI كاملة، فمعالِج واحد بيكفي لكل الأحداث. الرقم المرجعي اللي العميل بيدفعه موجود في paymentIntent.nextAction.displayVoucherDetails (reference وexpiresAt وinstructions).
دالة تنفيذ واحدة
خلّي التنفيذ مشروط بـ paymentStatus واستدعيه من حدثَي النجاح الاتنين. خلّيه آمن لو اتنفّذ مرتين لنفس الجلسة: التسليم بيتعاد.
async function fulfillCheckout(session: CheckoutSession) {
if (session.paymentStatus !== "paid") return;
if (await orders.isFulfilled(session.id)) return;
await orders.fulfill(session.id, session.lineItems);
}
app.post("/webhooks/xpay", async (req, res) => {
const event = verifyAndParse(req);
const session = event.data.object;
switch (event.type) {
case "checkout.session.completed":
case "checkout.session.async_payment_succeeded":
await fulfillCheckout(session);
break;
case "checkout.session.async_payment_failed":
await orders.markUnpaid(session.id);
break;
}
res.sendStatus(200);
});اللي العميل بيشوفه
في الـ Hosted Checkout، العميل بيشوف الرقم المرجعي، بيدوس Done، وبيوصل لصفحة التأكيد في حالة انتظار الدفع أو لـ afterCompletion.redirect.url بتاعك. فتح الجلسة تاني بيعرض الرقم المرجعي لحد ما يتدفع.
مع Drop-in وElements، الرقم المرجعي بيتفتح في نفس الطبقة اللي بيتفتح فيها تحدي 3D Secure. عند Done، confirm() بتتحلّ بـ { type: "success", session } والـ session.status.paymentStatus بتبقى "unpaid"، والـ onComplete بتاعة Drop-in بتتطلّق بـ paymentStatus: "unpaid". اعرض صفحة انتظار دفع، مش صفحة شكر.
الجلسة المكتملة مينفعش تتدفع بطريقة تانية. العميل اللي غيّر رأيه بيبدأ جلسة جديدة.