لماذا يحدث هذا: التقسيط ليس زر دفع إضافيًا
أدوات الذكاء الاصطناعي تتعامل مع «أضف تابي وتمارا» كأنه شعاران تحت زر الدفع، فتضع صورتين وتربطهما برابط عام أو بنموذج بطاقة. لكن الشراء الآن والدفع لاحقًا (BNPL) تدفق مختلف عن البطاقة: المزوّد يقيّم العميل لحظيًا، وقد يوافق أو يرفض، ويدفع للتاجر بحسب العقد بينما يقسّط العميل على دفعات. لذلك يحتاج المتجر بيانات أكثر عن الطلب والعميل، ومراحل حالة أكثر من «مدفوع» و«غير مدفوع».
الخطأ الثاني شائع في المتاجر المولّدة: عرض «قسّطها على 4 دفعات» على كل منتج، حتى على قطعة بسعر يقل عن الحد الأدنى الذي يقبله المزوّد، أو على عميل خارج البلدان المدعومة. يضغط العميل، فيُرفض، ويترك السلة. أما الخطأ الثالث فهو نسيان خطوة الالتقاط (Capture) أو الإلغاء، فيبقى الطلب معلّقًا في لوحة المزوّد ولا تتم التسوية كما تتوقع.
ما الذي تحتاجه
- حساب تاجر معتمد لدى Tabby أو Tamara أو كليهما. التسجيل يتطلب عادة سجلًا تجاريًا أو وثيقة عمل حر ومراجعة لمتجرك قبل التفعيل، والتفاصيل التجارية في دليل تابي وتمارا.
- مفاتيح بيئة الاختبار والإنتاج من لوحة كل مزوّد، المفتاح السري يبقى في الخادم.
- بيانات طلب كاملة: المنتجات وكمياتها وأسعارها، ورسوم الشحن، والضريبة، وعنوان التوصيل، وجوال العميل وبريده. المزوّد يستخدمها في التقييم وفي إرسال تفاصيل الأقساط للعميل.
- روابط عودة ثلاثة: نجاح، وإلغاء، ورفض، مع رسالة مناسبة لكل حالة.
- مسار Webhook عام يستقبل تغيّر حالة الطلب.
- قواعد العرض: الحد الأدنى والأعلى للمبلغ والعملات والبلدان المدعومة كما تظهر في حسابك لدى المزوّد، لا كما تتوقعها.
الحل خطوة بخطوة
تحقق من الأهلية قبل عرض الخيار
اقرأ حدود المبلغ من إعدادات حسابك أو من واجهة المزوّد الخاصة بالأهلية إن وفّرها، واحفظها في الإعدادات. اعرض شارة التقسيط في صفحة المنتج والسلة فقط حين يقع الإجمالي ضمنها والعملة SAR.
أنشئ جلسة الدفع من الخادم
الواجهة ترسل رقم السلة، والخادم يبني جسم الطلب من قاعدة البيانات ويرسله بالمفتاح السري. الرد يحتوي رابطًا تحوّل إليه العميل.
خزّن رقم الجلسة مع الطلب
احفظ معرّف الجلسة أو الطلب لدى المزوّد في جدول الطلبات بحالة
bnpl_pending، فهو ما تطابق به إشعارات الـ Webhook لاحقًا.أكّد عبر Webhook ثم استعلم عن الحالة
عند وصول إشعار الموافقة، استعلم من المزوّد عن الطلب برقمه، ثم اعتمده أو التقطه بحسب ما يطلبه توثيقه. بعض المزوّدين يطلب خطوة اعتماد أو التقاط صريحة، وإن لم تتم قد يُلغى الطلب تلقائيًا.
عالج الإلغاء والاسترجاع
إذا ألغى العميل أو أرجع قطعة، أرسل الإلغاء أو الاسترجاع الجزئي إلى المزوّد من لوحة متجرك، حتى تتعدّل الأقساط المتبقية عند العميل.
import express from "express";
import { getCart, saveBnplSession } from "./db.js";
const router = express.Router();
const PROVIDERS = {
tabby: { base: process.env.TABBY_API_BASE, key: process.env.TABBY_SECRET_KEY },
tamara: { base: process.env.TAMARA_API_BASE, key: process.env.TAMARA_SECRET_KEY },
};
router.post("/api/bnpl/:provider/session", express.json(), async (req, res) => {
const p = PROVIDERS[req.params.provider];
const cart = await getCart(req.body.cartId);
if (!p || !cart) return res.status(400).json({ error: "bad request" });
const { min, max } = cart.bnplLimits[req.params.provider]; // from your merchant settings
if (cart.totalSar < min || cart.totalSar > max) return res.status(422).json({ error: "not eligible" });
const body = {
amount: cart.totalSar.toFixed(2),
currency: "SAR",
order_reference: cart.orderId,
buyer: { name: cart.customer.name, phone: cart.customer.phone, email: cart.customer.email },
shipping_address: { city: cart.address.city, line1: cart.address.line1, country: "SA" },
items: cart.items.map(i => ({ title: i.title, quantity: i.qty, unit_price: i.priceSar.toFixed(2) })),
tax_amount: cart.vatSar.toFixed(2),
shipping_amount: cart.shippingSar.toFixed(2),
urls: {
success: `${process.env.PUBLIC_BASE_URL}/checkout/success`,
cancel: `${process.env.PUBLIC_BASE_URL}/checkout/cancel`,
failure: `${process.env.PUBLIC_BASE_URL}/checkout/declined`,
},
};
const r = await fetch(`${p.base}/checkout`, {
method: "POST",
headers: { Authorization: `Bearer ${p.key}`, "Content-Type": "application/json" },
body: JSON.stringify(body),
});
if (!r.ok) return res.status(502).json({ error: "provider error" });
const session = await r.json();
await saveBnplSession(cart.orderId, req.params.provider, session.id, "bnpl_pending");
res.json({ redirectUrl: session.checkout_url });
});
export default router;<!-- product.html -->
<div id="bnpl-badge" hidden>
أو قسّطها مع تابي أو تمارا حسب شروط المزوّد
</div>
<script>
const total = Number(document.querySelector("[data-price]").dataset.price);
fetch("/api/bnpl/limits").then(r => r.json()).then(l => {
if (total >= l.min && total <= l.max) document.getElementById("bnpl-badge").hidden = false;
});
</script>المزوّدون يفرضون صيغًا معيّنة لنص الترويج وشعاراتهم، وبعضهم يوفّر عنصرًا جاهزًا (Widget) لصفحة المنتج. استخدم ما يقدّمه المزوّد نفسه بدل نص من تأليفك عن «بدون فوائد» أو عدد الأقساط، لأن الشروط تختلف بين العملاء والمبالغ.
حالات الطلب التي يجب أن يفهمها متجرك
| الحالة عندك | متى تحدث | ماذا تفعل |
|---|---|---|
| bnpl_pending | بعد إنشاء الجلسة وتحويل العميل | لا تحجز المخزون نهائيًا ولا تشحن |
| approved | وافق المزوّد وأكّد الـ Webhook | اعتمد الطلب أو التقطه حسب التوثيق، ثم جهّز الشحن |
| declined | رفض المزوّد العميل | اعرض مدى أو البطاقة كبديل فورًا |
| canceled | ألغى العميل أو انتهت الجلسة | أعد المنتجات للمخزون |
| refunded | استرجاع كلي أو جزئي | أرسل الاسترجاع للمزوّد وحدّث الفاتورة |
مثال عملي: متجر عبايات في جدة سعر القطعة فيه 450 ر.س. العميلة تختار تمارا، فيُنشئ الخادم الجلسة ويحفظ الطلب بحالة bnpl_pending. تُرفض العميلة لسبب يخص تقييمها، فتعود إلى صفحة declined التي تعرض عليها الدفع بمدى مع السلة نفسها، بدل رسالة خطأ تجعلها تغادر. هذا التفصيل الصغير يحفظ كثيرًا من الطلبات.
أصلح خيار تابي وتمارا في متجري: أنشئ جلسة الدفع من الخادم ببيانات السلة والعميل من قاعدة البيانات، واحفظ رقم الجلسة مع الطلب بحالة bnpl_pending، وأضف Webhook يؤكد الحالة من المزوّد قبل الشحن. اعرض شارة التقسيط فقط إذا كان الإجمالي ضمن حدود حسابي، وفي حالة الرفض اعرض الدفع بمدى مباشرة.
قائمة اختبار قبل التفعيل
قبل أن تطلب من المزوّد نقل حسابك إلى الإنتاج، مرّ على هذه الحالات في بيئة الاختبار واحدة واحدة، وسجّل نتيجة كل حالة في جدول بسيط. فرق المراجعة لدى مزوّدي التقسيط تطلب أحيانًا أن ترى هذه السيناريوهات تعمل قبل التفعيل.
- طلب ناجح يصل فيه الـ Webhook ويتحول الطلب إلى approved ثم يُلتقط عند الشحن.
- طلب مرفوض يعود فيه العميل إلى صفحة declined ويرى بديل الدفع بمدى.
- عميل يغلق الصفحة قبل الموافقة، ثم تنتهي الجلسة ويعود المخزون.
- استرجاع جزئي لقطعة واحدة من طلب فيه ثلاث قطع، والتأكد أن المبلغ المتبقي صحيح عند المزوّد.
- وصول الإشعار نفسه مرتين، والتأكد أن الطلب لا يُشحن مرتين ولا يتكرر في التقارير.
- سلة إجماليها أقل من الحد الأدنى، والتأكد أن الشارة لا تظهر أصلًا.
الحل الأسرع: أوترا
في أوترا يعيش المتجر وخادمه وقاعدة بياناته في مشروع واحد، فتطلب تدفق التقسيط بالمحادثة وتتابع الطلبات وحالاتها في معاينة «قاعدة البيانات» دون أداة إضافية. مفاتيح Tabby و Tamara تضعها في «البيانات السرية»، فتصل إلى الخادم كمتغيّرات بيئة ولا تظهر في المحادثة.
لا نعرض هنا تكاملًا جاهزًا باسم تابي أو تمارا داخل أوترا، فالربط يُبنى لمشروعك حسب توثيق المزوّد الذي فتحت حسابك لديه. وإن أردت أن تخطط قبل أي تعديل، ابدأ بوضع Plan لترى الخطوات، ثم نفّذها.
أضف لمتجري خيار الدفع بالتقسيط عبر Tabby و Tamara إلى جانب مدى: جلسة دفع من الخادم، صفحات نجاح وإلغاء ورفض، Webhook لتأكيد الحالة، وشارة تظهر في صفحة المنتج فقط ضمن حدود المبلغ. المفاتيح سأضيفها باسم TABBY_SECRET_KEY و TAMARA_SECRET_KEY.