منذ عامين، كنت متأخراً بأسبوعين عن الموعد النهائي لتسليم منصة تعتمد على Laravel لخدمة سيارات ليموزين فاخرة في دبي. كان دمج بوابة الدفع الخاصة بهم يعطل كل شيء - كانت وثائق API لمزود الخدمة المحلي مترجمة جزئياً من اللغة العربية، وكانت نقاط النهاية تستغرق وقتاً طويلاً، ولم أستطع معرفة سبب استمرار مولد التوقيع في رفض JSON صالح. مرحباً بكم في عالم عمليات تكامل الدفع الخاصة بدولة الإمارات العربية المتحدة.
لن تعدك هذه المقالة بخطوات "سلسة" أو حلول "شاملة". إنه نهج واقعي يعتمد على المعارك التي خضتها - وفزت بها في بعض الأحيان - مع بوابات الدفع المحلية في الإمارات العربية المتحدة ومنطقة الخليج. لنعمل من خلال تطبيق أساسي باستخدام أمثلة التعليمات البرمجية الفعلية والإحباطات الحقيقية التي قد تواجهها.
**البدء: ما ستحتاج إليه**
عندما يقول أحد العملاء في أبو ظبي "إضافة مدفوعات"، فإنهم عادةً ما يعنون مجموعة من:
- •معالجات بطاقات البنوك الإماراتية (مدى، فوري باي)
- •محافظ الهاتف المحمول الإقليمية (ثواني، ويب كاش)
- •في بعض الأحيان حتى الدفع نقداً عند الاستلام للمؤسسات الصغيرة والمتوسطة
لقد اخترت Thawani كحالة اختبار لهذا الدليل - فهي تكتسب قوة جذب في الأوساط التجارية الإلكترونية الخليجية، وكان لدي مشروع حقيقي (المزيد حول ذلك لاحقاً) حيث تسببت في بعض جلسات الترميز المتأخرة.
ستحتاج إلى:
- •مشروع Laravel 9+ مع PHP 8.1
- •بيئة اختبار (استخدم ngrok.io لعناوين URL معاودة الاتصال)
- •بيانات اعتمادThawani الخاصة بـ sandbox (أو بوابة مكافئة إذا كان لديك مزود محلي مفضل)
**إعداد البوابة**
أبدأ بإنشاء PaymentServiceProvider للحفاظ على الجنون محتوياً. قم بتشغيل هذا في جهازك الطرفي:
php artisan make:service-provider PaymentServiceProviderثم في config/services.php، أضف:
'thawani' => [
'public_key' => env('THAWANI_PUBLIC_KEY'),
'secret_key' => env('THAWANI_SECRET_KEY'),
'base_url' => env('THAWANI_BASE_URL', 'https://uatcheckout.thawani.om/api'),
'currency' => 'OMR'
];نصيحة للمحترفين: لا تقم بترميز العملة كـ AED - فالعديد من البوابات الموجودة في الخليج لديها OMR (الريال العماني) كقيمة افتراضية. تعلمت هذا بالطريقة الصعبة عندما ظهر 10 آلاف درهم إماراتي على شكل 3000 ريال في معاملة اختبارية.
**التعامل مع المصادقة**
هذا الجزء كان سيئاً. يتوقع API الخاص بهم مفتاحك السري في رأس مخصص يسمى X-Thawani-Secret. لا يتعامل عميل Laravel الافتراضي Guzzle مع ذلك بشكل أنيق.
إليك كيف قمت بتغليفه في فئة خدمة:
class ThawaniPaymentHandler {
protected $client;
public function __construct() {
$this->client = new \GuzzleHttp\Client([
'base_uri' => config('services.thawani.base_url'),
'headers' => [
'X-Thawani-Secret' => config('services.thawani.secret_key')
]
]);
}
}الآن في ذلك الوقت استمر رفض شهادة SSL الخاصة بي. كان لدى خادم sandbox شهادة وسيطة لم يثق بها Ubuntu - وهي مشكلة شائعة في بيئات التطوير المحلية ولكنها كابوس في الإنتاج. لقد انتهى بي الأمر بتعطيل التحقق من الشهادة في تكوين التطوير (لا تفعل هذا في PROD):
'verify' => app()->isLocal() ? false : trueأجل. ليس قراري الأكثر فخراً، لكن العملاء لا يهتمون بسلاسل الشهادات عندما يكون موقعهم معطلاً.
**بدء الدفع**
لنفترض أنك تقوم ببناء منصة حجز سيارات فاخرة - لقد كنت هناك، وفعلت ذلك لعميل في الشارقة. إليك كيفية إنشاء رابط دفع:
public function createCheckout(Request $request): JsonResponse {
$payload = [
'client_reference_id' => $booking->id,
'products' => [[
'name' => 'Toyota Limousine',
'quantity' => 1,
'unit_amount' => $booking->total_amount * 1000 // they use 3 decimal places
]]
];
$response = $this->client->post('/v1/charges/create', [
'json' => $payload,
'headers' => [
'Accept' => 'application/json'
]
]);
$data = json_decode($response->getBody(), true);
return redirect()->away($data['session']['checkout_url']);
}إنهم يطلبون جميع المبالغ بـ الفلس، حتى إذا قمت بتعيين العملة على OMR. اضرب جميع أرقامك في 1000 أو تعامل مع 0.5 ريال تتحول بشكل سحري إلى نصف سنت (نعم، حدث ذلك في معاملة اختبارية).
**التعامل مع رد الاتصال**
لقد تعثرت في هذا الجزء عندما أصر أحد العملاء على تحديثات الطلبات في الوقت الفعلي باللغة العربية. لقد أرادوا إشعارات فورية من خلال خدمة الدفع - انتهى بي الأمر باستخدام Firebase FCM مع خادم Laravel echo لـ webhooks.
public function handleWebhook(Request $request): \Illuminate\Http\Response {
$payload = json_decode($request->getContent(), true);
if ($payload['type'] === 'charge.succeeded') {
// Update database
Booking::findOrFail($payload['data']['client_reference_id'])->update([
'payment_status' => 'paid',
'receipt_url' => $payload['data']['receipt_url']
]);
}
return response()->noContent(200);
}تأكد من أن عنوان URL الخاص بـ webhook هو HTTPS (سوف يرفضون HTTP بـ 403)، واختبر نفق ngrok الخاص بك عن طريق إرسال ping إليه من الخادم الخاص بك عبر curl:
curl https://your-ngrok-url.onrender.com/webhook**مثال واقعي: حجز تواصُل ليمو**
لقد استخدمت هذا التنفيذ الدقيق لـ Tawasul Limo - وهي منصة لحجز السيارات الفاخرة كانت بحاجة إلى مدفوعات في الوقت الفعلي بالدرهم الإماراتي والريال العماني. فضل عملاؤهم الإماراتيون محافظ الهاتف المحمول المحلية، لكن الشركة تعمل في 5 دول خليجية. كان علينا التعامل مع تنسيق الأرقام العربية، والمناطق الزمنية لمدة 12 ساعة لتأكيدات الرسائل النصية القصيرة، ودعم المبالغ المستردة الجزئية - وهو ما سمحت به البوابة فقط من خلال تحميل CSV حتى إصدار API v2 الخاص بهم.
كان على وظيفة الاسترداد أن تنتظر حتى منتصف الليل بتوقيت الإمارات العربية المتحدة لأن وظائف cron الخاصة بهم كانت تعمل في الساعة 4 صباحاً. نعم، ليس مثالياً، لكن العملاء لا يدفعون مقابل ما هو مثالي.
**اختبار كل شيء**
قم بتشغيل اختباراتك باستخدام Laravel Dusk لتدفقات المتصفح واستخدم أمر Artisan هذا لعمليات التحقق من الواجهة الخلفية:
php artisan make:test PaymentIntegrationTest --unitثم في الاختبار:
public function test_thawani_returns_charge_id() {
$handler = new ThawaniPaymentHandler();
$payload = require 'your-payment-data.php';
$response = $handler->createCharge($payload);
$this->assertStringStartsWith('chg_', $response['id']);
}تذكر التحقق من معرفات المعاملات المكررة - API الخاص بهم يعطي خطأ إذا حاولت طلبين دفع متطابقين في غضون 15 دقيقة. انتهى بنا الأمر بتخزين تجزئات الطلبات الحديثة في Redis بانتهاء صلاحية مدته 20 دقيقة.
بصراحة، هذه عمليات التكامل المحلية لن تكون أبداً نظيفة مثل Stripe. ولكن عندما يحتاج مطعم مملوك لعائلة في جدة إلى قبول المدفوعات من خلال محفظة رقمية سعودية معينة، فإنك تجعلها تعمل - حتى لو كانت الوثائق تحتوي على صور معطلة و 3 إصدارات من الرؤوس العربية.
إذا كنت عالقاً في جزء معين من هذه العملية، فلا تتردد في التواصل عبر نموذج الاتصال الخاص بي. أنا دائماً على استعداد لتناول القهوة وقصص حروب API.