أثناء اختبار IAP قبل الإطلاق، واجهنا مشكلة شائعة لكنها شديدة الإرباك:
- المستخدم يضغط “متابعة الشراء”
- الزر ينتقل إلى حالة “جارٍ المعالجة”
- بعد ثوانٍ يعود إلى “متابعة الشراء”
- لا تظهر نافذة تأكيد الشراء من App Store
في هذه المقالة نوثّق مسار التحقيق كاملًا، والسجلات الحاسمة، والسبب الجذري، وخطة إصلاح قابلة للتنفيذ قبل الإطلاق.
1. وصف المشكلة
كانت قائمة المنتجات في صفحة الشراء تُحمَّل بشكل طبيعي، لكن بعد الضغط على زر الشراء:
- تم استدعاء
purchase() - لم تظهر أي نافذة نظام
- الواجهة عادت في النهاية إلى حالة “غير مشتَرٍ”
ظاهريًا يبدو أن عملية الشراء لم تبدأ، لكن السجلات أظهرت عكس ذلك.
2. أول جولة تحقيق: هل مسار الشراء يعمل فعلاً؟
بدأنا بإضافة سجلات تفصيلية لكل النقاط الحرجة في المسار:
- معاملات الضغط على الزر
- نقطة دخول
purchaseSelectedProduct()وفروعguard - قبل/بعد
product.purchase() Transaction.updatesrefreshEntitlements()Transaction.currentEntitlementsTransaction.latest(for:)
النتيجة:
- استدعاء
purchase()أعاد.success - التحقق من المعاملة نجح، وتم تنفيذ
transaction.finish() - لكن
entitledIDsبقيت فارغة
هذا يعني: المشكلة ليست «عدم بدء الشراء»، بل «عدم التعرف على الصلاحيات بعد الشراء».
3. حقائق حاسمة ظهرت بعد التعمق
بعد توسيع السجلات ظهرت مؤشرات حاسمة:
- عدد التكرارات في
Transaction.currentEntitlementsكان0 Transaction.latest(for: monthly)أعادت سجلًا صحيحًا لكنه منتهي الصلاحية- الأهم: قبل الشراء كانت هناك معاملات قديمة داخل
Transaction.unfinished - نتيجة
purchase()الحالية كانت من سلسلة معاملات قديمة، وليست من مسار شراء تفاعلي جديد
وهذا يفسّر سبب «عدم ظهور النافذة»:
- StoreKit أعادت أولًا معاملة قديمة قابلة للمعالجة (unfinished / historical flow)
- الكود قام بعمل
finish()لها - العملية بدت «ناجحة»، لكنها لم تمر عبر نافذة تأكيد شراء جديدة كما يتوقع المستخدم
4. ملخص السبب الجذري
لم يكن السبب عاملًا واحدًا، بل تراكب عاملين:
- معاملات غير مكتملة (
unfinished transaction) تتداخل مع مسار الشراء - مسار اشتراك Sandbox في حالة منتهية (حتى مع وجود
latest transactionفهي ليست بالضرورة entitlement فعّالًا)
لذلك كانت النتيجة: لا نافذة تأكيد، ونجاح شكلي، وصلاحيات فارغة.
5. استراتيجية الإصلاح (مجرّبة وفعّالة)
5.1 تنظيف معاملات unfinished قبل الشراء
عند بدء التطبيق وقبل الضغط على الشراء، افحص Transaction.unfinished ونفّذ finish() لكل معاملة verified، حتى لا «تخطف» المعاملات القديمة مسار الشراء الجديد.
5.2 الإبقاء على سجلات التشخيص وتعزيزها (موصى به قبل الإطلاق)
سجلات أساسية يُفضَّل إبقاؤها مفعّلة حتى الإطلاق:
- لقطة عن
unfinished transaction subscription status(state, renewal info)- عدد عناصر
currentEntitlements transaction/expirationفيlatest(for:)
5.3 اعتماد مسارين لتحديد الصلاحيات (اختياري)
إذا أصبحت currentEntitlements فارغة مؤقتًا في بعض البيئات، يمكن استخدام latest(for:) كمسار احتياطي (مع التحقق من أن الصلاحية ما زالت active).
6. قائمة فحص IAP قبل الإطلاق
- قبل كل اختبار، تأكد من حالة اشتراك حساب Sandbox (هل انتهت صلاحيته أم لا)
- اطبع عدد عناصر
unfinishedفي المسار الحرج - اختبر 3 حالات: “شراء أول مرة / تجديد بعد الانتهاء / استعادة الشراء”
- تحقق من تطابق
currentEntitlementsمعlatest - عند ظهور «لا نافذة تأكيد»، افحص أولًا ما إذا كان التنفيذ قد دخل مسار معاملات قديم
7. الخلاصة
«الضغط على الشراء بلا ظهور نافذة» لا يعني بالضرورة أن المشكلة في UI أو في توقيت الاستدعاء فقط.
في StoreKit 2، حالة المعاملات، قائمة unfinished، ودورة حياة الاشتراك كلها تؤثر على النتيجة النهائية.
إضافة سجلات التشخيص وخطوة تنظيف المعاملات قبل الإطلاق تقلّل كثيرًا من مشاكل IAP التي يصعب إعادة إنتاجها في بيئة الإنتاج.