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

إشعارات الدفع تصل متأخرة ومكررة وخارج الترتيب

العودة من صفحة الدفع ليست تأكيدًا، ولا إشعار واحد كذلك. ما الذي يَعِد به مزوّد الدفع فعلًا بشأن التسليم، وكيف تبني فوقه شيئًا صحيحًا.

قراءة 4 دقيقة

سيخبرك مزوّد الدفع أنه يسلّم الأحداث بموثوقية. وهذا صحيح، وهو وعد أضيق مما تقرؤه فيه أغلب الفرق. معناه أن الحدث سيصل في وقت ما، وغالبًا أكثر من مرة، وربما بعد ساعات مما يصفه، وليس بالضرورة بترتيب وقوع الأشياء.

وكل قرار تصميمي أدناه ينبع من أخذ تلك الجملة حرفيًا.

العودة واجهة لا نتيجة

حين يعود العميل من صفحة الدفع، يستقبل تطبيقك طلبًا يقول في جوهره إن العميل هنا مجددًا. لا يقول إن الدفعة نجحت، ولا يرسله المزوّد - يرسله متصفح العميل، وهو شيء يُغلَق ويُحدَّث ويُستبدَل بهاتف ينفد شحنه في مصعد.

استعمل العودة لمهمة واحدة بالضبط: عرض شاشة. اقرأ حالة الدفعة الحالية من المزوّد، واعرض ما تعرفه، وإن كنت لا تعرف بعدُ فقل ذلك. أما ما يجب ألا تفعله العودة أبدًا فهو كتابة "مدفوع" في قاعدة بياناتك.

الذي يؤكّد الدفعة هو الحدث، والحدث يصل عبر قناة لا يستطيع العميل التأثير فيها.

تحقّق من البايتات لا من الكائن

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

public function handle(Request $request): Response
{
    $payload = $request->getContent();   // خام، لا $request->all()
 
    try {
        $event = Webhook::constructEvent(
            $payload,
            $request->header('Stripe-Signature'),
            config('services.stripe.webhook_secret'),
        );
    } catch (SignatureVerificationException) {
        return response()->noContent(400);
    }
 
    ProcessPaymentEvent::dispatch($event->id, $payload);
 
    return response()->noContent(200);
}

الأول التحليل قبل التجزئة. فـ $request->all() يعطيك مصفوفة، وإعادة ترميزها تُنتج بايتات غير التي وصلت - ترتيب مفاتيح مختلف، وتهريب يونيكود مختلف، وتنسيق أرقام مختلف. جزّئ النص.

والثاني وسيط CSRF، الذي سيرفض الطلب قبل أن يعمل أي مما سبق، لأن مزوّد الدفع بلا جلسة وبلا رمز. فالمسار مكانه خارج مجموعة وسطاء الويب، وإن كان داخلها كان الفشل 419 يعيده المزوّد ثلاثة أيام.

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

الخروج عن الترتيب هو الحالة الطبيعية

قد يصل حدثان عن دفعة واحدة بتسلسل خاطئ. تنجح عملية ثم تُرتجَع بعد تسعين ثانية؛ فيسبق حدث الارتجاع حدث النجاح؛ فيعالج معالجك الارتجاع على طلب لم يُدفَع بعدُ، فيرى ذلك غير منطقي، فلا يفعل شيئًا. والآن صار الطلب مدفوعًا إلى الأبد.

لا تعالج هذا بالترتيب. عالجه بأن يصف كل معالج العالم بدل أن يصف انتقالًا:

  • خطأ: "عند الارتجاع، غيّر الحالة من محصَّل إلى مرتجَع."
  • صواب: "عند أي حدث عن هذه الدفعة، اجلب حالتها الحالية من المزوّد واجعل صفّي مطابقًا لها."

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

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

والتكرار حالة طبيعية أيضًا

كل مزوّد يعيد المحاولة على أي شيء ليس 2xx، والتسليم الذي انتهت مهلته بعد نجاح شيفرتك يُعاد كذلك. الحدث نفسه سيصل مرتين.

خزّن معرّف الحدث من المزوّد بفهرس فريد، وأدخله قبل المعالجة، ودع قاعدة البيانات ترفض المكرر. هذه هي الآلية كلها، وهي أوثق من فحص وجود الصف، لأن فحصًا يتبعه إدخال سباقٌ بين عاملين، وفي عواصف إعادة المحاولة يكون العاملان حقيقيين.

ثم لا تثق بالقناة إطلاقًا

كل ما سبق يجعل مسار الـ webhook صحيحًا. ولا يجعله كاملًا، لأن نقطةً تعطّلت أربع ساعات أثناء نشرٍ هي نقطة فاتتها أحداث، وبعد انتهاء نافذة إعادة المحاولة تكون قد ذهبت.

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

وهذا شكل التكاملات المحاسبية التي نبنيها نفسه: تدفق أحداث للحداثة، وسحب دوري للصحة، والسحب هو ما يمكنك الاعتماد عليه فعلًا. وفي المدفوعات سبب ثانٍ: سيسأل أحدهم في المالية لماذا يختلف إجمالي المزوّد الشهري عن قاعدة بياناتك، و"لا يختلفان" جواب أفضل بكثير من تحقيق.

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

أسئلة ذات صلة

هل العودة من صفحة الدفع لا تكفي حقًا؟
لا تكفي، والسبب أنها معلّقة بمتصفح العميل. يغلق اللسان، أو يفقد هاتفه الإشارة في القطار، أو تأخذه صفحة مصادقة البنك إلى مكان آخر، أو ببساطة لا ينتظر. وتنجح الدفعة رغم ذلك. فإن كان معالج العودة هو ما يعلّم الطلب مدفوعًا، فكل حالة من هذه مال مقبوض بلا طلب تعرضه.
لماذا يفشل فحص التوقيع لدينا والحمولة تبدو سليمة؟
غالبًا لأن شيئًا حلّل الطلب قبل أن تحسب التجزئة. يُحسب التوقيع على البايتات المرسلة بعينها، فإطارٌ يفكّ ترميز JSON ثم يعيد ترميزه، أو وسيطٌ يعيد تنسيق الجسم، يُنتج سلسلة مختلفة ومن ثم تجزئة مختلفة. اقرأ الجسم الخام وتحقق منه، ثم حلّل بعد ذلك.
هل نعالج كل أنواع الأحداث؟
لا، والاشتراك في كل شيء هو كيف تصير النقطة بطيئة وصاخبة. اشترك في القليل الذي يغيّر حالتك - نجح، فشل، مرتجع، متنازع عليه، وما تحتاجه دورة الاشتراك - وتجاهل الباقي صراحةً لا مصادفةً. وأكّد المتجاهَلة أيضًا بـ 200.
كم نحتفظ بالأحداث الخام؟
أطول مما يبدو معقولًا. إنها صغيرة، وهي السجل الوحيد لما قاله لك المزوّد ومتى. فأول مرة ينازع فيها عميل عملية عمرها ثمانية أشهر، أو يعجز فريق المالية عن مطابقة شهر، يكون ذلك الجدول هو ما يجيب. والاحتفاظ بسنة منها لا يكاد يكلّف شيئًا.

← العودة إلى كل المقالات

اتصل بنا+1 848 272 7583واتساب+90 850 308 5436البريدinfo@codefacture.comصفحة التواصل