معالجة رابط العودة
ابنِ الصفحة التي يعود إليها العملاء بعد الدفع، وتحقّق من النتيجة على خادمك قبل تنفيذ الطلب.
بعد الدفع، توجّه تيرا باي العميل إلى return_url ومعه معرّف عملية الدفع وحالتها:
https://shop.example.com/thanks?payment_id=pay_01JBX4T9W2&status=succeeded| المعامل | القيم |
|---|---|
payment_id | معرّف عملية الدفع pay_…. |
status | succeeded أو failed أو processing. تشمل failed أيضًا العمليات الملغاة (canceled) والمنتهية (expired). |
لا تثق بإعادة التوجيه وحدها
قيم الرابط ليست دليلًا على الدفع
يستطيع أي شخص فتح https://shop.example.com/thanks?payment_id=pay_123&status=succeeded في المتصفح. لا تنفّذ طلبًا ولا تشحن بضاعة ولا تفتح محتوى بناءً على معاملات إعادة التوجيه. تحقّق دائمًا عبر الـ API أو عبر Webhook تم التحقق من توقيعه.
هناك مصدران موثوقان للحقيقة:
GET /v1/payments/:idبمفتاحك السري، من خادمك.- Webhook يحمل
Teyra-Signatureصحيحًا.
استخدم الاثنين معًا. صفحة العودة تعطي العميل إجابة فورية، والـ Webhook يغطي العملاء الذين يغلقون المتصفح أو ينقطع اتصالهم أو لا يعودون أبدًا.
معالج عودة صحيح
في صفحة العودة:
- اقرأ
payment_idمن الرابط. - اجلب عملية الدفع من خادمك عبر
GET /v1/payments/:id. - تأكد أن العملية تخص هذا الطلب (قارن
referenceأو الـidالذي حفظته) وأنamountوcurrencyمتطابقان. - اعتمد على
statusالمجلوبة من الـ API، لا على المعامل في الرابط.
app.get('/orders/:id/return', async (req, res) => {
const order = await db.orders.find(req.params.id);
const paymentId = String(req.query.payment_id || '');
// The payment must be the one we created for this order.
if (!paymentId || paymentId !== order.paymentId) return res.status(400).render('payment-error');
const response = await fetch(`https://api.teyra.co/v1/payments/${encodeURIComponent(paymentId)}`, {
headers: { Authorization: `Bearer ${process.env.TEYRA_SECRET_KEY}` },
});
const payment = await response.json();
if (payment.status === 'succeeded' && payment.amount === order.totalMinor && payment.currency === order.currency) {
await markOrderPaid(order.id, payment.id); // must be idempotent: the webhook may get there first
return res.render('thank-you', { order });
}
if (payment.status === 'processing' || payment.status === 'requires_payment') {
return res.render('payment-pending', { order }); // the webhook will finish the job
}
return res.render('payment-failed', { order, reason: payment.failure_message });
});في صفحة الفشل، اعرض زر حاول مجددًا يعيد العميل إلى checkout_url الخاص بعملية الدفع، إذ يظل بالإمكان دفع العملية failed أو canceled هناك حتى expires_at.
عندما تكون الحالة processing
بعض الوسائل تؤكد الدفع بعد ثوانٍ من عودة العميل. اعرض صفحة "نتحقق من دفعتك"، ثم إما أن تحدّثها كل بضع ثوانٍ (بسؤال خادمك الذي يستدعي GET /v1/payments/:id) أو تخبر العميل بأنك سترسل له بريدًا إلكترونيًا. يصل إشعار payment.succeeded أو payment.failed فور أن تصبح النتيجة نهائية.
اجعل تنفيذ الطلب آمنًا من التكرار
قد تصل صفحة العودة والـ Webhook بأي ترتيب، وقد يُسلَّم الـ Webhook أكثر من مرة. اكتب markOrderPaid بحيث لا يكون لاستدعائها مرتين أي أثر إضافي، مثلًا بتحديث الطلب فقط WHERE status = 'pending'.