بوابات الدفع

عقد البوابة، الدفع/التحقق، والتعامل مع الـwebhook.

عقد البوابة

بوابات الدفع تتوصّل بالمنصة عبر عقد Nafezly Payments. البوابة كلاس PHP في namespace App\Payments يورّث Nafezly\Payments\Classes\BaseController وينفّذ Nafezly\Payments\Interfaces\PaymentInterface.

العقد ميثودين:

  • pay(...) — ابدأ الشحن. رجّع فين تبعت المشتري (checkout مستضاف redirect_url، أو html inline).
  • verify(Request $request) — أكّد النتيجة لما المشتري يرجع (أو webhook يشتغل).

المنصة تتولّى الباقي: إنشاء سجل Payment، توجيه المشتري، التقاط الـcallback، وعند نجاح مُتحقَّق — إتمام الشراء و(للعناصر السوقية) تسجيل ربح المطور.

pay() — بدء الشحن

pay() تستقبل المبلغ وبيانات المشتري، تنادي API المزوّد لإنشاء جلسة دفع، وترجّع مصفوفة موحّدة. الشكل ماخوذ من TabbyPayment الحقيقي:

public function pay(
    $amount = null, $user_id = null,
    $user_first_name = null, $user_last_name = null,
    $user_email = null, $user_phone = null, $source = null
) {
    $this->setPassedVariablesToGlobal($amount, $user_id, $user_first_name,
        $user_last_name, $user_email, $user_phone, $source);
    $this->checkRequiredFields(
        ['amount', 'user_first_name', 'user_last_name', 'user_email', 'user_phone'],
        'Tabby'
    );

    $reference   = (string) ($this->payment_id ?: uniqid().rand(100000, 999999));
    $callbackUrl = route($this->verifyRouteName, ['payment' => 'tabby', 'payment_id' => $reference]);

    $response = Http::withToken($this->secretKey)->acceptJson()->asJson()->timeout(20)
        ->post($this->baseUrl.'/api/v2/checkout', [ /* حمولة المزوّد */ ]);

    $payload     = $response->json() ?? [];
    $paymentId   = (string) data_get($payload, 'payment.id', $reference);
    $checkoutUrl = $this->checkoutUrl($payload);

    if ($response->successful() && $paymentId !== '' && $checkoutUrl !== '') {
        return [
            'payment_id'   => $paymentId,
            'html'         => '',            // أو رجّع html inline واترك redirect_url فاضي
            'redirect_url' => $checkoutUrl,  // checkout مستضاف
            'process_data' => $payload,
        ];
    }

    return $this->failure($paymentId ?: $reference, $payload ?: $response->body());
}

مفاتيح الإرجاع:

المفتاح المعنى
payment_id معرّف الشحن عند المزوّد — يُحفَظ ويُستخدم وقت التحقق
redirect_url رابط checkout المستضاف لتوجيه المشتري (اترك html فاضي)
html iframe/form inline يُرندَر بدل التوجيه (اترك redirect_url فاضي)
process_data رد المزوّد الخام، يُحفَظ للتدقيق

ابنِ الـcallback دائمًا براوت التحقق بتاع المنصة: route($this->verifyRouteName, ['payment' => '{slug}', 'payment_id' => $reference]).

verify() — تأكيد النتيجة

لما المشتري يرجع، verify() تعيد فحص الشحن بشكل موثوق مقابل API المزوّد — ما تثقش في حالة الـquery-string لوحدها أبدًا:

public function verify(Request $request): array
{
    $paymentId = (string) ($request->input('payment_id') ?: $request->route('payment_id'));
    if ($paymentId === '') {
        return $this->verificationResult(false, '', ['error' => 'Missing payment id.']);
    }

    $response = Http::withToken($this->secretKey)->acceptJson()->timeout(20)
        ->get($this->baseUrl.'/api/v2/payments/'.$paymentId);

    $payload = $response->json() ?? [];
    $status  = strtolower((string) data_get($payload, 'status', ''));
    $success = $response->successful()
        && in_array($status, ['authorized', 'closed', 'paid', 'captured'], true);

    return $this->verificationResult($success, $paymentId, $payload ?: $response->body());
}

استخدم المساعدات اللي يديهالك الـbase controller — verificationResult($success, $paymentId, $data) وfailure($paymentId, $data) — عشان شكل الإرجاع يطابق اللي المنصة متوقعاه.

الإعداد وبيانات الاعتماد

اقرأ بيانات الاعتماد من الإعداد، ما تحطّهاش ثابتة أبدًا. البوابات تستخدم namespace nafezly-payments.*:

$this->secretKey    = (string) config('nafezly-payments.TABBY_SECRET_KEY', '');
$this->merchantCode = (string) config('nafezly-payments.TABBY_MERCHANT_CODE', '');
$this->baseUrl      = rtrim((string) config('nafezly-payments.TABBY_BASE_URL', 'https://api.tabby.ai'), '/');
$this->verifyRouteName = (string) config('nafezly-payments.VERIFY_ROUTE_NAME');

التجار/الأدمن يدخلوا دي في شاشة إعدادات بوابة الدفع؛ بلاجنك يسجّل البوابة وحقول إعداداتها.

اشحن بعملة المشتري

المنصة تشحن المشتري بعملته المعروضة وتفصل المبلغ المشحون عن مبلغ المحاسبة الداخلية. ابعت العملة اللي المشتري يشوفها للمزوّد ($this->currency ?: $defaultCurrency) بدل افتراض عملة ثابتة.

الـWebhooks

لو المزوّد بيبعت webhooks خادم-لخادم كمان، اعرض راوت بصلاحية webhook_listener وافشل مغلق: تحقق من توقيع/سر المزوّد قبل أي شغل، وارفض لما يبقى ناقص. وبعدين أعِد التحقق من الشحن عبر نداء API الموثوق في verify() عشان webhook مزوّر ما يقدرش يعلّم دفعة كمدفوعة. راجع تكاملات الموردين لنمط الـwebhook فاشل-مغلق.

قائمة المراجعة

  • ورّث BaseController، نفّذ PaymentInterface.
  • pay() ترجّع payment_id + redirect_url أو html، بالإضافة لـprocess_data.
  • verify() تعيد الفحص من API المزوّد بشكل موثوق.
  • بيانات الاعتماد من config('nafezly-payments.*')، مش في الكود.
  • رابط الـcallback مبني من VERIFY_ROUTE_NAME.
  • HTTP الخارجي متوقع هنا — لكنه معلَن ومراجَع.