إلغاء الوصول بعد استرداد الأموال: الخطوة التي لا تقوم بها Apple و Google نيابة عنك
يمكن لكل من Apple و Google إتمام عملية استرداد للأموال بينما يحتفظ العميل بمشترياته. إليك بالضبط متى يُزال الوصول تلقائيا، ومتى يجب على خادمك القيام بذلك، والإشعار الوحيد الذي لا تتعامل معه معظم عمليات التكامل أبدا.

أهم الخلاصات
- في Google Play، استرداد أموال طلب ما لا يُلغي وصول المستخدم من تلقاء نفسه. لا يُلغى الوصول إلا عند تحديد خيار "Remove entitlement" في Play Console، أو عندما يتضمن استدعاء API القيمة revoke=true، وهذا ليس الإعداد الافتراضي.
- الاسترداد الصادر دون خيار الإلغاء لا يظهر إطلاقا في Voided Purchases API. أي مهمة إلغاء تعتمد فقط على قراءة هذا المصدر ستفوت بصمت كل طلب من هذا النوع.
- يمكن لـ Apple إلغاء الترخيص تلقائيا للعناصر غير الاستهلاكية والاشتراكات، لكن العنصر الاستهلاكي يُعتبر مُسلَّما بمجرد إنفاقه، لذا لا تملك StoreKit أي آلية لإلغائه. يجب على خادمك خصم الرصيد بنفسه.
- يمكن لـ Apple أيضا التراجع عن استردادها الخاص. إشعار REFUND_REVERSED يعني أن استردادا سبق منحه قد تم التراجع عنه، وتعود قيمة revocationDate إلى null، ويجب استعادة أي وصول أو رصيد قمت بإزالته.
- يحمل حقل voidedReason في Google تسع قيم، من remorse و accidental_purchase إلى fraud و friendly_fraud و unacknowledged_purchase، لذا فإن المصدر نفسه الذي يخبرك بإلغاء الوصول يخبرك أيضا أي إلغاء ناتج عن إساءة استخدام وأيها ناتج عن خلل في تكاملك الخاص.
- كل يوم تحتفظ فيه عملية شراء تم استرداد أموالها بوصولها، هو يوم تدفع فيه ثمن الحوسبة والتخزين واستدعاءات API الكامنة خلفها، من أموال أعدتها بالفعل.
الموافقة على استرداد الأموال وإلغاء الوصول خطوتان منفصلتان، ولا تحدث تلقائيا سوى واحدة منهما. المطورون الذين يفترضون أن استردادا تمت معالجته يساوي إلغاء الشراء مخطئون في كلا المتجرين الرئيسيين، بطرق مختلفة ولأسباب مختلفة. معرفة أين تتوقف الأتمتة بالضبط هي ما يلزم لإلغاء الوصول بعد الاسترداد، بدلا من اكتشاف بعد أشهر أن ذلك لم يحدث أبدا.
لماذا لا يُلغي الاسترداد الوصول افتراضيا
تتعامل كل من Apple و Google مع الأموال والوصول باعتبارهما عمليتين مختلفتين. جانب الأموال هو سياسة المتجر: بمجرد الموافقة على الاسترداد، يصبح نهائيا ويتحدث السجل. جانب الوصول اختياري، أو من مسؤولية المطور، أو كلاهما، حسب المنصة ونوع الشراء. لا يفترض أي من المتجرين أنك تريد زوال الوصول فور اكتمال الاسترداد، لأن كثيرا من الحالات المشروعة (استرداد جزئي، رصيد كبادرة حسن نية، لفتة دعم) لا يُفترض أصلا أن تسحب المنتج.
Google Play: خانة الاختيار التي لا يلاحظها أحد
في Play Console، استرداد أموال طلب ما هو نموذج من جزأين: تختار مبلغ الاسترداد، ثم تقرر بشكل منفصل ما إذا كنت ستحدد "Remove entitlement". ترك الخيار غير محدد هو المسار الافتراضي الذي يسلكه معظم موظفي الدعم، لأن معظم طلبات الاسترداد هي لفتات حسن نية يحتفظ فيها العميل بما اشتراه. المشكلة أن شاشة تأكيد الاسترداد لا تخبرك بأن هذه هي أيضا الطريقة التي يحتفظ بها العميل باشتراك أو عنصر داخل التطبيق بعد استرداد كنت تنويه نهائيا.
يحمل مسار API الفخ نفسه
الخيار نفسه موجود في الكود. تقبل نقطة النهاية orders.refund معامل استعلام اختياريا هو revoke؛ عندما تكون قيمته true، يُنهى الوصول إلى العنصر فورا، وبالنسبة للاشتراك المتكرر تُلغى معه كل الدفعات المستقبلية. أما إذا حُذف المعامل فقيمته الافتراضية false: يُسجَّل الاسترداد، ويُحدَّث سجل الشراء، ويستمر المستخدم في استخدام المنتج تماما كما كان من قبل.
| الإجراء | هل أُزيل الوصول؟ | هل يظهر في Voided Purchases API؟ |
|---|---|---|
| استرداد عبر Play Console، دون تحديد "Remove entitlement" | لا | لا |
| استرداد عبر Play Console، مع تحديد "Remove entitlement" | نعم، فورا | نعم |
| orders.refund API، مع حذف معامل revoke (الافتراضي false) | لا | لا |
| orders.refund API، مع revoke=true | نعم، فورا | نعم |
ذلك الصف الأخير هو ما يوقع عمليات التكامل في الفخ. توجد Voided Purchases API لتتيح لك بناء مسار إلغاء دون تفقد Play Console يدويا، لكنها تسرد فقط الطلبات التي أُلغيت فعليا مع الإلغاء. الاسترداد الذي تمت معالجته دون تحديد الخانة، أو دون revoke=true، غير مرئي لذلك المصدر بحكم التصميم، لا بسبب خلل.

قراءة حقل voidedReason عندما تظهر عملية شراء فعلا كملغاة، يخبرك حقل voidedReason بالسبب، على مقياس من تسع قيم: other و remorse و not_received و defective و accidental_purchase و fraud و friendly_fraud و chargeback و unacknowledged_purchase. هذا ليس مجرد قيد محاسبي. remorse و accidental_purchase استردادات عادية. أما fraud و friendly_fraud و chargeback فهي الحالات التي تستحق التوجيه إلى قائمة مراجعة الاحتيال بدلا من مسار إلغاء ونسيان بسيط، لأن الحساب نفسه أكثر عرضة إحصائيا لتكرار ذلك.
هناك تفصيل تشغيلي مهم إذا كنت تبني نظاما يعتمد مباشرة على هذا المصدر: purchases.voidedpurchases.list محدد بسقف 6,000 استعلام في اليوم و30 استعلاما في أي نافذة مدتها 30 ثانية. أي تصميم يقوم بالاستطلاع في حلقة ضيقة يصل إلى هذا السقف بسرعة؛ النمط المقصود هو سحب مجدول باستخدام رموز ترقيم الصفحات، لا استطلاع مستمر.
Apple: يمكن لـ App Store إلغاء الترخيص، لكن ليس العنصر الاستهلاكي
ترسل App Store Server Notifications V2 من Apple إشعار REFUND فور اكتمال الاسترداد، سواء للعناصر الاستهلاكية أو غير الاستهلاكية أو الاشتراكات المتجددة تلقائيا أو الاشتراكات غير المتجددة على حد سواء. ما يحدث بعد ذلك الإشعار يعتمد كليا على نوع الشراء، لأن آلية الإلغاء الخاصة بـ StoreKit لا تغطي سوى نوعين من أصل أربعة.
لماذا تُعد العناصر الاستهلاكية استثناء
يملك العنصر غير الاستهلاكي أو الاشتراك حقا قائما يمكن لأنظمة Apple إيقافه. أما العنصر الاستهلاكي فلا: بمجرد استخدامه، يُعامل على أنه مُسلَّم، ولا يبقى في StoreKit شيء لإلغائه. إذا اشترى عميل 500 عملة، أنفقها، ثم حصل على استرداد، فإن العملات من وجهة نظر Apple قد اختفت بالفعل. السجل الوحيد الذي لا يزال يحتفظ بها هو سجلك أنت. إشعار REFUND هو إشارتك الوحيدة للعثور على معرف المعاملة الأصلية المطابق وخصم ذلك الرصيد من سجلاتك الخاصة؛ لن يقوم أي شيء من جانب Apple بذلك نيابة عنك.
| الإشعار | ماذا يعني | ما الذي يجب أن يفعله خادمك |
|---|---|---|
| REFUND | استردت Apple أموال شراء استهلاكي أو غير استهلاكي أو متجدد تلقائيا أو غير متجدد | ألغِ فورا وصول العناصر غير الاستهلاكية والاشتراكات؛ اخصم رصيد العنصر الاستهلاكي بنفسك |
| REFUND_DECLINED | رفضت Apple طلب استرداد من عميل | لا شيء. لم يكن الوصول معرضا للخطر مطلقا |
| REFUND_REVERSED | تراجعت Apple عن استرداد سبق أن منحته | استعد أي وصول أو رصيد تسبب إشعار REFUND في إزالته |
| CONSUMPTION_REQUEST | تريد Apple دليلك قبل أن تقرر بشأن الاسترداد | استجب خلال 12 ساعة؛ يُطلق هذا قبل REFUND، لا بعده |
الإشعار الذي لا يتعامل معه أحد تقريبا
REFUND_REVERSED هو الإشعار الذي تتجاهله معظم عمليات التكامل تماما، لأنه يصف تراجع Apple عن قرارها الخاص، وهي حالة قلة من المطورين يصممون لها من المحاولة الأولى. عند وصوله، تعود قيمة revocationDate على المعاملة إلى null: فمن منظور Apple، أصبح الشراء صالحا من جديد. إذا كان خادمك قد حذف حقا، أو ألغى سجل اشتراك، أو صفّر رصيد عنصر استهلاكي عند وصول REFUND الأصلي، فإن REFUND_REVERSED هو الإشارة لإعادة كل ذلك.
كيفية سد الفجوة، خطوة بخطوة
- في Google Play، اجعل الإعداد الافتراضي لكل مسار استرداد هو revoke=true، سواء تم عبر Play Console أو API، ما لم تستدعِ حالة دعم محددة احتفاظ العميل بالوصول.
- استطلع purchases.voidedpurchases.list وفق جدول باستخدام رموز ترقيم الصفحات، لا في حلقة ضيقة، وابقَ تحت 6,000 طلب في اليوم و30 طلبا كل 30 ثانية.
- وجّه قيم voidedReason الخاصة بـ fraud و friendly_fraud و chargeback إلى قائمة مراجعة بدلا من إلغاء وإغلاق تلقائيين.
- على جانب Apple، اشترك في App Store Server Notifications V2 وألغِ وصول العناصر غير الاستهلاكية والاشتراكات في اللحظة التي يذكرها فيها إشعار REFUND.
- احفظ معرف المعاملة الأصلية مع كل عملية شراء استهلاكية، حتى يتمكن إشعار REFUND من إيجاد الرصيد الدقيق الواجب خصمه دون بحث يدوي.
- تعامل مع REFUND_REVERSED صراحة وسجّل كل عملية إلغاء مرتبطة بمعرف المعاملة أو الطلب الخاص بها، حتى يتمكن التراجع من استعادة ما أُزيل بالضبط.
ما تكلفه الفجوة فعليا
لا علاقة لكل هذا بالإنصاف تجاه العميل؛ إنه يتعلق بما يظل يعمل بعد رحيل الأموال. اشتراك تم استرداد أمواله لكنه لا يزال يستدعي API الخاص بك، ويخزن بيانات، ويستهلك حوسبة، ليس مشكلة أخلاقية، بل بند تكلفة: لقد دفعت فاتورة البنية التحتية لشراء أعدت إيراده بالفعل. لا تتتبع المتاجر هذه التكلفة نيابة عنك، لأن الاسترداد من جانبها يكتمل في اللحظة التي تتحرك فيها الأموال. جانب الوصول هو بالكامل فاتورتك أنت التي عليك التحكم بها.
لهذا السبب نفسه تتعامل RefundHalt مع الإلغاء باعتباره جزءا من مسار الاسترداد، لا فكرة لاحقة: يُسجَّل حدث REFUND وتغيير الوصول كإجراء واحد، ويعيد حدث REFUND_REVERSED الحالة تلقائيا بدلا من انتظار أن يلاحظ أحدهم أن العميل ما زال محروما من شيء أعادته Apple بالفعل.
الأسئلة الشائعة
- هل يؤدي استرداد أموال شراء على Google Play إلى إلغاء الوصول تلقائيا؟
- لا. لا يُزال الوصول إلا إذا حُدد خيار "Remove entitlement" في Play Console أو تضمن استدعاء API الخاص بـ orders.refund القيمة revoke=true. لا يُعد أي منهما الإعداد الافتراضي، لذا يمكن أن يُسجَّل الاسترداد بينما يستمر العميل في استخدام المنتج.
- ماذا يحدث عند استرداد أموال عملية شراء استهلاكية داخل التطبيق على App Store؟
- ترسل Apple إشعار REFUND، لكن لا تملك StoreKit وسيلة لإلغاء عنصر استهلاكي لأنه يُعتبر مُسلَّما بمجرد إنفاقه. يجب على خادم المطور إيجاد المعاملة الأصلية وخصم الرصيد يدويا.
- ما هو إشعار REFUND_REVERSED وهل يحتاج إلى تعامل خاص؟
- يعني REFUND_REVERSED أن Apple تراجعت عن استرداد سبق أن منحته؛ تعود قيمة revocationDate إلى null ويصبح الشراء صالحا من جديد. يجب استعادة أي وصول أو رصيد أزاله REFUND الأصلي، ومعظم عمليات التكامل لا تستمع لهذا الإشعار إطلاقا.
- لماذا قد لا يظهر طلب مسترد من Google Play في Voided Purchases API؟
- لا تسرد API سوى الطلبات التي أُلغيت مع الإلغاء. إذا تمت معالجة الاسترداد دون خيار الإلغاء، فلن تُعيد نقطة النهاية تلك الطلب إطلاقا، لذا فإن أي مسار يعتمد عليها وحدها سيفوت تلك الاستردادات تماما.
- كم مرة يمكنني استطلاع Voided Purchases API الخاصة بـ Google؟
- نقطة النهاية محددة بمعدل 6,000 استعلام في اليوم و30 استعلاما في أي نافذة مدتها 30 ثانية، لذا فهي مخصصة لسحب مجدول باستخدام رموز ترقيم الصفحات، لا لاستطلاع مستمر.
المصادر وقراءات إضافية
- Apple: Handling refund notifications
- Apple: notificationType (App Store Server Notifications)
- Apple: revocationReason (App Store Server Notifications)
- Google: Voided Purchases API overview
- Google: purchases.voidedpurchases REST resource
- Google: orders.refund API reference (revoke parameter)
- Google Play Console Help: manage your app's orders and issue refunds
RefundHalt
الطيار الآلي للاستردادات في App Store وGoogle Play
تابع القراءة
The refunds you cannot contest: what Apple and Google decide without asking you
Only two refund flows ever ask for your evidence: Apple's consumption request and Google Play's chargeback review. Everything else, the 48-hour Play refund, support refunds, voided purchases, unacknowledged trials, is decided without you. Here is the full map, what you can prevent, and what you can only record.
Apple's CONSUMPTION_REQUEST, explained: the 12 hours that decide your refund
When a customer asks Apple for a refund, Apple asks you first. Here's exactly what the CONSUMPTION_REQUEST notification contains, what Apple expects back, how to answer one end to end, and why most developers silently forfeit.