تخطَّ إلى المحتوى
Uttra

ربط الدفع بمدى في تطبيق مبني بالذكاء الاصطناعي

مدى لا تُربط مباشرة بتطبيقك، بل عبر بوابة دفع سعودية مرخّصة مثل Moyasar أو HyperPay أو Tap أو PayTabs. تنشئ الدفعة من الخادم بالمفتاح السري، وتترك صفحة البطاقة و 3-D Secure للبوابة، ثم تؤكد نجاح الدفع عبر Webhook قبل تسليم الطلب. لا تضع المفتاح السري في الواجهة أبدًا.

آخر تحديث:

لماذا يحدث هذا: لماذا يفشل ربط مدى في التطبيقات المولّدة

حين تطلب من أداة ذكاء اصطناعي «أضف الدفع بمدى»، كثيرًا ما تحصل على نموذج بطاقة جميل يرسل الرقم إلى مكان غير موجود، أو على كود يستدعي البوابة من المتصفح مباشرة ويضع المفتاح السري داخل ملف JavaScript يراه أي زائر. السبب أن مدى شبكة مدفوعات محلية، وليست مكتبة تثبّتها: التاجر لا يتعامل معها مباشرة، بل عبر بوابة دفع مرخّصة تتولى استقبال البطاقة والتحقق منها وتسوية المبلغ.

الأخطاء التي نراها تتكرر في المشاريع المولّدة ثلاثة: إنشاء الدفعة من الواجهة الأمامية، والاعتماد على صفحة «تم الدفع» التي يعود إليها العميل كدليل نجاح، وتجاهل خطوة 3-D Secure التي تطلب رمز التحقق من البنك. النتيجة طلبات تُسجَّل مدفوعة وهي لم تُدفع، أو مفاتيح مسرّبة تُستخدم لاسترداد مبالغ أو إنشاء عمليات وهمية.

ما الذي تحتاجه قبل كتابة سطر كود

  • حساب تاجر لدى بوابة سعودية مرخّصة تدعم مدى، مثل Moyasar أو HyperPay أو Tap أو PayTabs. قارن بينها في دليل بوابات الدفع السعودية.
  • مفتاحان من لوحة البوابة: مفتاح عام (Publishable) يمكن أن يظهر في الواجهة إن كانت البوابة تستخدمه، ومفتاح سري (Secret) يبقى في الخادم فقط.
  • خادم (Backend) ولو كان صغيرًا: Node و Express يكفيان. بدون خادم لا يوجد مكان آمن للمفتاح السري.
  • رابط Webhook عام تستطيع البوابة الوصول إليه، أي أن تطبيقك منشور على رابط حقيقي أو رابط معاينة يعمل من الخارج.
  • بيئة الاختبار (Sandbox): كل بوابة تنشر بطاقات اختبار خاصة بها لمدى وفيزا، خذها من توثيق البوابة نفسها ولا تنسخ أرقامًا من مدونات قديمة.

إذا كنت تريد الصورة الكاملة لتجربة العميل وخطوات التسجيل لدى البوابة، فهذا موجود في دليل ربط مدى. هذه الصفحة تركّز على الكود وعلى إصلاح ما تكتبه أدوات الذكاء الاصطناعي خطأً.

الحل خطوة بخطوة

  1. خزّن المفتاح السري كمتغيّر بيئة

    لا تكتبه في الكود ولا في ملف يُرفع إلى GitHub. ضعه في ملف .env محليًا (وأضف الملف إلى .gitignore) أو في مخزن الأسرار في منصتك.

    code
    # .env (لا يُرفع إلى المستودع)
    GATEWAY_SECRET_KEY=sk_test_xxxxxxxxxxxxxxxx
    GATEWAY_WEBHOOK_SECRET=whsec_xxxxxxxxxxxx
    PUBLIC_BASE_URL=https://your-app.example.sa
  2. أنشئ الدفعة من الخادم بالمبلغ المحسوب عندك

    الواجهة ترسل رقم الطلب فقط، والخادم يقرأ المبلغ من قاعدة البيانات. لا تقبل المبلغ من المتصفح أبدًا، وإلا غيّره أي شخص إلى ريال واحد.

  3. حوّل العميل إلى صفحة البوابة

    البوابة تعيد رابط دفع أو جلسة دفع. صفحة البطاقة و 3-D Secure تعملان داخل البوابة، فلا تمر بيانات البطاقة على خادمك، وهذا يخفف عنك كثيرًا من متطلبات أمان البطاقات.

  4. استقبل الـ Webhook وتحقق من توقيعه

    البوابة ترسل إشعارًا إلى خادمك عند نجاح الدفع أو فشله. هذا الإشعار، لا صفحة العودة، هو مصدر الحقيقة. تحقق من التوقيع، ثم اسأل البوابة عن حالة الدفعة برقمها قبل تعليم الطلب «مدفوع».

  5. اختبر بالبطاقات التجريبية ثم بدّل المفاتيح

    جرّب نجاح الدفع، ورفض البطاقة، وإلغاء 3-D Secure، وانقطاع الاتصال قبل العودة. بعد ذلك فقط استبدل مفاتيح الاختبار بمفاتيح الإنتاج.

server.js: إنشاء دفعة عبر بوابة عامة (مثال توضيحي، أسماء الحقول تختلف حسب البوابة)
import express from "express";
import crypto from "node:crypto";
import { getOrder, markOrderPaid } from "./db.js";

const app = express();
const GATEWAY = "https://api.example-gateway.sa/v1"; // illustrative endpoint

app.post("/api/checkout", express.json(), async (req, res) => {
  const order = await getOrder(req.body.orderId);
  if (!order || order.status !== "pending") return res.status(400).json({ error: "invalid order" });

  const r = await fetch(`${GATEWAY}/payments`, {
    method: "POST",
    headers: {
      "Authorization": "Basic " + Buffer.from(process.env.GATEWAY_SECRET_KEY + ":").toString("base64"),
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      amount: Math.round(order.totalSar * 100), // halalas
      currency: "SAR",
      description: `Order ${order.id}`,
      callback_url: `${process.env.PUBLIC_BASE_URL}/thanks?order=${order.id}`,
      metadata: { order_id: order.id },
    }),
  });
  if (!r.ok) return res.status(502).json({ error: "gateway error" });
  const payment = await r.json();
  res.json({ redirectUrl: payment.redirect_url }); // the gateway hosts the card form and 3-D Secure
});

app.listen(process.env.PORT || 3000);
webhook.js: التحقق من التوقيع ثم من حالة الدفعة (مثال توضيحي)
app.post("/api/payments/webhook", express.raw({ type: "application/json" }), async (req, res) => {
  const expected = crypto
    .createHmac("sha256", process.env.GATEWAY_WEBHOOK_SECRET)
    .update(req.body)
    .digest("hex");
  const given = String(req.get("X-Signature") || "");
  if (given.length !== expected.length || !crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected))) {
    return res.sendStatus(401);
  }

  const event = JSON.parse(req.body.toString());
  // Never trust the event alone: ask the gateway for the payment status.
  const r = await fetch(`${GATEWAY}/payments/${event.data.id}`, {
    headers: { Authorization: "Basic " + Buffer.from(process.env.GATEWAY_SECRET_KEY + ":").toString("base64") },
  });
  const payment = await r.json();
  if (payment.status === "paid") await markOrderPaid(payment.metadata.order_id, payment.id); // idempotent
  res.sendStatus(200);
});

سجّل مسار الـ Webhook قبل app.use(express.json()) العام، لأن التحقق من التوقيع يحتاج جسم الطلب الخام كما وصل. ولتكن دالة markOrderPaid آمنة للتكرار، فالبوابات تعيد إرسال الإشعار أحيانًا.

إذا كان تطبيقك مولّدًا بأداة ذكاء اصطناعي وتريد أن تصلحه بدل أن تعيد كتابته، أعطها هذا الطلب كما هو:

برومبت لإصلاح ربط مدى في مشروع قائمجرّبه في أوترا

راجع كود الدفع في المشروع: انقل إنشاء الدفعة إلى الخادم في مسار POST /api/checkout يقرأ المبلغ من قاعدة البيانات لا من المتصفح، واقرأ المفتاح السري من متغيّر البيئة GATEWAY_SECRET_KEY فقط. أضف مسار Webhook يتحقق من التوقيع ثم يستعلم من البوابة عن حالة الدفعة قبل تعليم الطلب مدفوعًا، واحذف أي مفتاح سري من ملفات الواجهة.

3-D Secure وصفحة العودة: أين يخطئ الأغلب

بطاقات مدى تمر غالبًا بخطوة 3-D Secure: يرى العميل صفحة بنكه ويُدخل رمزًا يصله برسالة. هذه الخطوة تعمل داخل البوابة، ودورك أن تتعامل مع نتيجتها بصدق. العميل قد يغلق المتصفح بعد إدخال الرمز وقبل العودة لموقعك، فإن كنت تعتمد على صفحة العودة سيبقى طلبه «معلّقًا» رغم أن المبلغ خُصم، وهذا أكثر ما يولّد شكاوى في متجر عبايات أو محمصة تبيع أونلاين.

التعامل مع نتيجة الدفع بحسب مصدرها
الحدثما تعرضه للعميلما تفعله في الخادم
العودة إلى صفحة الشكر«نراجع عملية الدفع» مع رقم الطلبلا شيء، انتظر الـ Webhook أو استعلم عن الحالة
Webhook بحالة paidرسالة تأكيد وبريد أو رسالة نصيةتعليم الطلب مدفوعًا وحفظ رقم العملية
Webhook بحالة failedزر «حاول مرة أخرى»إبقاء الطلب pending وتسجيل السبب
لا إشعار خلال دقائقرسالة انتظارمهمة دورية تستعلم عن الدفعات المعلّقة

الحل الأسرع: أوترا

في أوترا تبني الخادم والواجهة معًا في المشروع نفسه، فلا تحتاج أن تجهّز بيئة منفصلة للباك-إند. حين يحتاج الربط مفتاح البوابة، تلصقه في نموذج مخفي داخل المحادثة أو في «البيانات السرية»، فيُحفظ كمتغيّر بيئة ولا يظهر نصّه في المحادثة أو في الكود. ثم تطلب الربط بالمحادثة وتختبره على رابط المعاينة قبل النشر.

لا يوجد في أوترا اليوم مكوّن جاهز باسم «دفع مدى» تضغطه فيعمل، بل تطلبه كما تطلب أي ميزة: تذكر البوابة التي فتحت حسابًا لديها، وتبني أوترا مسار الدفع والـ Webhook حسب توثيقها. تبقى مراجعتك للتدفق قبل استقبال أموال حقيقية ضرورية.

جرّبه في أوتراجرّبه في أوترا

أضف الدفع بمدى إلى متجري عبر بوابة Moyasar: إنشاء الدفعة من الخادم بالمبلغ من قاعدة البيانات، صفحة الدفع من البوابة مع 3-D Secure، ومسار Webhook يؤكد الدفع قبل تعليم الطلب مدفوعًا. سأضيف المفتاح السري في البيانات السرية باسم GATEWAY_SECRET_KEY.

أسئلة شائعة

لا عمليًا. التاجر الصغير والمتوسط يقبل مدى عبر بوابة دفع مرخّصة أو مزوّد خدمات دفع يتولى التعاقد مع الشبكة والبنوك. البوابة تعطيك واجهة برمجية ولوحة تحكم وتسوية للمبالغ، وأنت تربط تطبيقك بها لا بمدى مباشرة.

صفحات ذات صلة