تجربتي مع Laravel Cashier: ربط الدفع الإلكتروني بين التحديات والحلول
عندما بدأت رحلتي في بناء تطبيقات ويب تتطلب اشتراكات شهرية أو مدفوعات متكررة، كنت أبحث عن حلٍّ يوازن بين البساطة والاحترافية. لم أكن أريد أن أخترع العجلة من جديد، ولا أن أغرق في كود معقد يُعقّد الصيانة ويُبطئ التطوير. هنا ظهر لي Laravel Cashier كمنقذ حقيقي. لكن كما في كل رحلة تقنية، لم تكن الأمور سلسة من البداية. واجهت تحديات حقيقية متعلقة بالعملات، والبلدان، وتكامل بوابات الدفع مثل Stripe وPayPal. في هذا المقال، سأشاركك تجربتي العملية مع Laravel Cashier، من أول خطوة حتى التعامل مع المشاكل التي لا يتحدث عنها أحد عادةً.
لماذا اخترت Laravel Cashier؟
قبل أن أتعمق في التفاصيل، دعني أوضح لماذا وقعت على Laravel Cashier تحديدًا. كنت أستخدم Laravel كإطار عمل رئيسي لمشاريعي، ولاحظت أن الفريق خلفه قدّم أدوات مدمجة تُبسّط المهام الشائعة دون التضحية بالمرونة. Laravel Cashier ليس مجرد مكتبة لربط بوابات الدفع، بل هو طبقة ذكية فوق واجهات برمجة تطبيقات (APIs) مثل Stripe وPaddle، تُدير لك الاشتراكات، الفواتير، المحاولات الفاشلة، وحتى إشعارات الدفع التلقائية.
الميزة الأكبر بالنسبة لي كانت في الطريقة التي يتعامل بها Cashier مع العلاقات بين المستخدمين والاشتراكات. بدلًا من أن أكتب كودًا يدويًا لإنشاء اشتراك جديد، تحديثه، أو إلغائه، وجدت أن كل هذه العمليات مغطاة بواجهة برمجية نظيفة وبسيطة. مثلاً، بدلاً من التعامل مع عشرات السطور لمعالجة اشتراك شهري عبر Stripe، كل ما احتجته كان:
$user->newSubscription('default', 'premium-monthly')->create($paymentMethod);
بسهولة كهذه، يُنشئ Cashier اشتراكًا جديدًا، يربطه بوسيلة الدفع، ويبدأ دورة الفوترة تلقائيًا. هذا النوع من البساطة كان كافيًا لإقناعي بالاعتماد عليه.
البداية مع Stripe: سهولة التكامل لكن...
الخطوات الأولى: إعداد الحساب والتكامل
بدأت رحلتي مع Stripe لأنها البوابة الأكثر دعماً في Laravel Cashier، والأكثر انتشارًا في الوثائق الرسمية. إعداد الحساب كان سهلًا: أنشأت حسابًا تجريبيًا، واستخرجت مفاتيح API (secret وpublishable)، ثم أضفتها إلى ملف .env. بعد ذلك، نفّذت أمر التثبيت عبر Composer:
composer require laravel/cashier
ثم قمت بتشغيل عمليات الهجرة (migrations) التي يوفرها Cashier، والتي تضيف جداول مثل subscriptions وsubscription_items وcustomer إلى قاعدة البيانات. كل شيء بدا مثاليًا في البيئة التجريبية. المستخدم يضيف بطاقة ائتمان، يشترك في خطة، ويبدأ الدفع التلقائي دون أي تدخل يدوي.
المفاجأة الأولى: العملات والمنطقة الجغرافية
لكن الأمور بدأت تتعقد عندما قررت توسيع التطبيق ليشمل مستخدمين من خارج أمريكا الشمالية. المشكلة لم تكن في الكود، بل في الطريقة التي يتعامل بها Stripe مع العملات والبلدان. اكتشفت أن بعض العملات لا تُدعم في بعض الدول، أو أن الحد الأدنى للمبلغ يختلف حسب العملة. مثلاً، في اليابان، لا يمكنك فرض اشتراك أقل من 50 ين ياباني، بينما في أوروبا قد يكون الحد 0.50 يورو.
في البداية، لم أنتبه لهذا التفصيل، فعندما حاول مستخدم من اليابان الاشتراك بخطة بقيمة 1 دولار أمريكي (حوالي 150 ين)، نجح الأمر. لكن عندما جربت خطة بقيمة 0.99 دولار (أقل من 150 ين)، رفض Stripe المعاملة تمامًا دون رسالة خطأ واضحة في الواجهة الأمامية. استغرق الأمر وقتًا حتى أكتشف أن المشكلة في الحد الأدنى للعملة، وليس في بيانات البطاقة أو الكود.
التحول إلى PayPal: هل كان الحل الأفضل؟
لماذا فكرت في PayPal؟
بسبب بعض القيود الجغرافية في Stripe، خاصة في مناطق الشرق الأوسط وأفريقيا، قررت اختبار دعم PayPal عبر Laravel Cashier. في البداية، بدت فكرة جيدة: PayPal مقبول على نطاق واسع، ويدعم طرق دفع متعددة (مثل الحسابات البنكية والمحافظ الإلكترونية)، ويُفضّله كثير من المستخدمين الذين لا يمتلكون بطاقات ائتمان.
لكن سرعان ما اكتشفت أن دعم PayPal في Laravel Cashier ليس بنفس عمق دعم Stripe. في الواقع، النسخة الأساسية من Cashier لا تدعم PayPal إطلاقًا. هناك حزم طرف ثالث مثل laravel/cashier-paddle أو مكتبات مجتمعية، لكنها أقل استقرارًا وأقل توثيقًا. اضطررت إلى استخدام مكتبة خارجية، وتعديل جزء كبير من منطق الدفع ليناسب هيكل PayPal المختلف.
الاختلاف الجوهري في نموذج الدفع
الفرق الأكبر الذي لاحظته بين Stripe وPayPal يتمثل في كيفية إدارة الدورات التلقائية. في Stripe، يمكنك إنشاء "Price" مرتبط بـ "Product"، ثم ربطه باشتراك. أما في PayPal، فالنظام يعتمد على "Billing Plans" و"Billing Agreements"، وهي مفاهيم أبطأ في التنفيذ وأقل مرونة عند التعديل.
بالإضافة إلى ذلك، واجهت مشكلة كبيرة في معالجة الإشعارات (webhooks). في Stripe، تأتي أحداث مثل invoice.payment_succeeded أو customer.subscription.deleted بشكل منظم وواضح. أما في PayPal، فكانت بعض الأحداث تصل متأخرة، أو تتكرر، أو تفتقر إلى البيانات الكافية لتحديد المستخدم المعني. اضطررت إلى بناء طبقة تحقق إضافية لضمان أن كل إشعار يُعالج مرة واحدة فقط، وأن البيانات متوافقة مع قاعدة بياناتي.
المشاكل الحقيقية: العملات، الضرائب، والاختلافات الإقليمية
التعامل مع تحويل العملات تلقائيًا
من أكبر التحديات التي واجهتها كانت في عرض الأسعار للمستخدمين من دول مختلفة. في البداية، كنت أعرض السعر بالدولار الأمريكي للجميع، ثم يُحوّله Stripe تلقائيًا إلى العملة المحلية عند الدفع. لكن هذا أدى إلى ارتباك كبير: المستخدم يرى سعرًا، ثم يُخصم مبلغًا مختلفًا من حسابه بسبب سعر الصرف أو الرسوم البنكية.
في رأيي، الحل الأمثل هو عرض السعر بالعملة المحلية من البداية. لكن هذا يتطلب منك تحديد أسعار يدوية لكل عملة، أو استخدام خدمة خارجية لتحويل العملات بشكل ديناميكي. جربت الخيار الأول، ووضعت جدولًا في قاعدة البيانات يحتوي على سعر كل خطة حسب العملة (USD، EUR، JPY، SAR... إلخ). ثم استخدمت مكتبة مثل moneyphp/money لعرض المبلغ بشكل منسق حسب لغة المستخدم.
النتيجة؟ رضا أعلى من المستخدمين، وانخفاض كبير في طلبات الدعم المتعلقة بـ "لماذا خُصمت مني مبلغ مختلف؟".
الضرائب واللوائح المحلية
لم أكن أتوقع أن الضرائب ستكون جزءًا من معادلة الدفع الإلكتروني، لكن الواقع كان مختلفًا تمامًا. في أوروبا، مثلاً، يجب تطبيق ضريبة القيمة المضافة (VAT) على الخدمات الرقمية، ومعدل الضريبة يختلف من دولة إلى أخرى. في السعودية، هناك ضريبة القيمة المضافة بنسبة 15٪. أما في دول أخرى، فقد لا توجد ضرائب على الإطلاق.
Laravel Cashier لا يتعامل مع الضرائب بشكل افتراضي، لكنه يسمح لك بدمجها بسهولة. استخدمت خدمة خارجية مثل TaxJar أو Quaderno لحساب الضريبة تلقائيًا بناءً على عنوان المستخدم. ثم مرّرت القيمة إلى Stripe عند إنشاء الفاتورة:
$user->newSubscription('default', 'premium')
->withTaxRates(['txr_example123'])
->create($paymentMethod);
بدون هذه الخطوة، كنت سأتحمل مسؤولية قانونية كبيرة، خاصة مع تزايد التشريعات حول الضرائب الرقمية عالميًا.
التعامل مع الفشل: محاولات الدفع الفاشلة وإدارة الإيقاف المؤقت
ليس كل مستخدم يمتلك بطاقة صالحة طوال الوقت. قد تنتهي صلاحية البطاقة، أو يتجاوز الحد الائتماني، أو يرفض البنك المعاملة لأسباب داخلية. هنا تظهر قوة Laravel Cashier الحقيقية: قدرته على التعامل مع حالات الفشل بشكل ذكي.
عندما يفشل دفع اشتراك، لا يلغي Cashier الاشتراك فورًا. بل يمنح فترة سماح (grace period) يمكن تخصيصها في الإعدادات. خلال هذه الفترة، يظل المستخدم قادرًا على استخدام الخدمة، مع إرسال إشعارات تلقائية لحثه على تحديث وسيلة الدفع. إذا لم يُصلح المشكلة خلال الفترة المحددة، يتم إيقاف الاشتراك تلقائيًا.
في تجربتي، وجدت أن 30٪ من محاولات الدفع الفاشلة تُحل تلقائيًا خلال فترة السماح، خاصة عندما يكون السبب مجرد انتهاء صلاحية البطاقة. لكن المشكلة كانت في كيفية إعلام المستخدم. في البداية، كنت أعتمد فقط على البريد الإلكتروني، لكن نسبة الاستجابة كانت منخفضة. لاحقًا، أضفت تنبيهات داخل التطبيق (in-app notifications) تظهر بمجرد تسجيل الدخول، مما رفع معدل التحديث إلى أكثر من 60٪.
أفضل الممارسات التي تعلمتها من التجربة
1. لا تعتمد على بيئة الاختبار فقط
في البداية، اعتقدت أن كل شيء يعمل بشكل مثالي لأنني اختبرته في وضع "sandbox". لكن بمجرد الانتقال إلى الإنتاج، ظهرت مشاكل غير متوقعة: بطاقات حقيقية تُرفض لأسباب لم تظهر في الاختبار، أو اختلاف في سلوك webhooks. الآن، أخصص دائمًا فترة اختبار قصيرة مع مستخدمين حقيقيين (beta testers) قبل الإطلاق الكامل.
2. سجّل كل شيء
أنشأت جدولًا خاصًا باسم payment_logs لتسجيل كل محاولة دفع، سواء نجحت أو فشلت، مع تفاصيل مثل: المستخدم، المبلغ، العملة، بوابة الدفع، والرسالة المرتدة من API. هذا السجل كان كنزًا عند حل المشكلات أو الرد على استفسارات الدعم.
3. اجعل واجهة المستخدم مرنة
لا تفرض على المستخدم طريقة دفع واحدة. قدّم خيارات متعددة (بطاقة، PayPal، Apple Pay...) إذا سمحت البوابة بذلك. وتأكد من أن رسائل الخطأ واضحة وودية، لا تقنية. بدلًا من "Error 402: Card declined"، اكتب "عذرًا، لم نتمكن من تأكيد بطاقتك. تأكد من صلاحيتها أو جرّب وسيلة دفع أخرى".
الخلاصة: Laravel Cashier ليس سحرًا، لكنه أداة قوية بيد ماهرة
بعد كل هذه التجارب، أستطيع القول إن Laravel Cashier هو أحد أفضل القرارات التي اتخذتها في مسيرتي كمطور. لكنه ليس "حلًا سحريًا" يُنفّذ كل شيء نيابة عنك. النجاح في ربط الدفع الإلكتروني يعتمد على فهمك العميق لبوابات الدفع، والاختلافات الجغرافية، واحتياجات جمهورك.
المشاكل المتعلقة بالعملات، والضرائب، والاختلافات الإقليمية ليست عيوبًا في Laravel Cashier، بل واقع معقد للتجارة الإلكترونية العالمية. المهم هو كيف تتعامل معها. بالنسبة لي، كانت التجربة مليئة بالتحديات، لكن كل مشكلة حلت علّمتني درسًا جديدًا جعل النظام أكثر مرونة واحترافية.
إذا كنت تفكر في استخدام Laravel Cashier لتطبيقك، فلا تتردد. ابدأ بـ Stripe إذا أمكن، وافهم حدود كل بوابة دفع. اختبر مع مستخدمين من خلفيات مختلفة، وكن مستعدًا لتعديل خطط التسعير حسب المنطقة. الأهم من كل ذلك: لا تهمل الجانب البشري من تجربة الدفع. المستخدم لا يريد أن يفهم كيف يعمل API، بل يريد أن يدفع بسلاسة ويواصل استخدام خدمتك دون انقطاع.
الخطوة التالية؟ جرّب إنشاء مشروع تجريبي صغير باستخدام Laravel Cashier وStripe. شغّل بيئة الاختبار، وأضف اشتراكًا بسيطًا، ثم غيّر العملة، وحاول محاكاة فشل الدفع. ستعلمك هذه التجربة الصغيرة أكثر مما تقرأه في عشرات المقالات. لأن في النهاية، البرمجة ليست فقط عن الكود، بل عن فهم العالم الذي يعمل فيه هذا الكود.
