معالجة رابط العودة

ابنِ الصفحة التي يعود إليها العملاء بعد الدفع، وتحقّق من النتيجة على خادمك قبل تنفيذ الطلب.

بعد الدفع، توجّه تيرا باي العميل إلى return_url ومعه معرّف عملية الدفع وحالتها:

https://shop.example.com/thanks?payment_id=pay_01JBX4T9W2&status=succeeded
المعاملالقيم
payment_idمعرّف عملية الدفع pay_….
statussucceeded أو failed أو processing. تشمل failed أيضًا العمليات الملغاة (canceled) والمنتهية (expired).

لا تثق بإعادة التوجيه وحدها

قيم الرابط ليست دليلًا على الدفع

يستطيع أي شخص فتح https://shop.example.com/thanks?payment_id=pay_123&status=succeeded في المتصفح. لا تنفّذ طلبًا ولا تشحن بضاعة ولا تفتح محتوى بناءً على معاملات إعادة التوجيه. تحقّق دائمًا عبر الـ API أو عبر Webhook تم التحقق من توقيعه.

هناك مصدران موثوقان للحقيقة:

  1. GET /v1/payments/:id بمفتاحك السري، من خادمك.
  2. Webhook يحمل Teyra-Signature صحيحًا.

استخدم الاثنين معًا. صفحة العودة تعطي العميل إجابة فورية، والـ Webhook يغطي العملاء الذين يغلقون المتصفح أو ينقطع اتصالهم أو لا يعودون أبدًا.

معالج عودة صحيح

في صفحة العودة:

  1. اقرأ payment_id من الرابط.
  2. اجلب عملية الدفع من خادمك عبر GET /v1/payments/:id.
  3. تأكد أن العملية تخص هذا الطلب (قارن reference أو الـ id الذي حفظته) وأن amount وcurrency متطابقان.
  4. اعتمد على 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'.