أهدرت معظم يوم كامل بسبب خطأ Undelivered Consumption Percentage Non Zero Error. ليس لأنه خطأ معقد. بل لأنني ضبطت delivery Status على قيمة غير DELIVERED وتركت consumption Percentage عند 60000، وApple ترفض هذا التركيب دون أي تساهل.
وهذه طريقة غير مباشرة للوصول إلى السؤال الحقيقي.
إذا كنت تحاول معرفة كيفية الرد تلقائيًا على طلبات استرداد الأموال من Apple، فالمهمة أصغر مما تتخيله معظم الفرق. الجزء الصعب لم يكن أبدًا استدعاء API. الصعوبة تكمن في توفر البيانات التي تضعها فيه. يمكنك الاطلاع على الشرح التفصيلي لكل حقل في شرح CONSUMPTION_REQUEST من Apple. هذا المقال يتناول كيفية ربط الأمر بأكمله حتى لا يحتاج أحد للبقاء مستيقظًا.
أهم النقاط
أتمتة استرداد الأموال في Apple هي webhook واحد بالإضافة إلى طلب PUT واحد. تُرسل Apple إشعار CONSUMPTION_REQUEST، وعليك الرد خلال 12 ساعة.
تحتاج إلى مفتاح In-App Purchase، وليس مفتاح App Store Connect API الخاص بك. فهما غير قابلين للتبادل.
ثلاثة من الحقول الخمسة مطلوبة، واثنان منها لهما قيود لا تظهر إلا في شكل أخطاء.
بدون موافقة العميل لا ينبغي أن تردّ على الإطلاق، ولا يمكنك جمع الموافقة بأثر رجعي.
انتقلت نقطة النهاية إلى الإصدار v2. بيئة الإنتاج هي api.storekit.apple.com، وتعطيك بيئة sandbox 5 دقائق فقط بدلًا من 12 ساعة.
كيف تعمل طلبات استرداد الأموال من Apple للمطورين فعليًا
يفتح العميل reportaproblem.apple.com، ويجد عملية الشراء خلال آخر 90 يومًا، ويختار سببًا، ثم يُرسل الطلب. إذا كنت قد هيّأت App Store Server Notifications V2، فستحصل نقطة النهاية الخاصة بك على إشعار CONSUMPTION_REQUEST في شكل حمولة JWS موقّعة تحمل المعاملة والمنتج والسبب الذي اختاره العميل.
هذا الـwebhook هو المُحرّك الوحيد لاسترداد أموال عملية شراء داخل تطبيق في App Store. لا يظهر شيء في لوحة التحكم (dashboard) الخاصة بك، ولا توجد قائمة انتظار للتحقق منها. ولهذا تكتشف الفرق وجود هذا النظام بعد أسابيع من بدء خسارتها للمال بسببه.
ثم يكون أمامك 12 ساعة.
هناك أمر يستحق التصحيح، لأنه يتكرر في كل مكان. هذا الأمر لا يقتصر على المنتجات القابلة للاستهلاك (consumables). توثيق Apple الحالي يذكر أن إشعار CONSUMPTION_REQUEST يصل لأي نوع من المنتجات: consumable، وnon-consumable، والاشتراك غير المتجدد (non-renewing subscription)، والاشتراك التلقائي المتجدد (auto-renewable subscription). لا تزال الأدلة القديمة تذكر أنه يقتصر على consumables والاشتراكات فقط. وهي معلومات قديمة لم تعد صحيحة.
ما الذي يعنيه "الرد" بشكل ملموس
ترسل طلب PUT يحتوي على نص JSON إلى نقطة نهاية واحدة.
PUT https://api.storekit.apple.com/inApps/v2/transactions/consumption/{transactionId}
بيئة sandbox هي api.storekit-sandbox.apple.com، بنفس المسار. الاستجابة الناجحة تُرجع 202 Accepted، لا 200، وهو ما يُوقع الكثيرين في الخطأ عند التحقق من رموز الحالة (status codes).
المصادقة تتم عبر رمز ES256 JWT قصير الأجل، موقّع بمفتاح In-App Purchase، يتم إنشاؤه في App Store Connect تحت Users and Access، في قسم Integrations. وهو ليس مفتاح App Store Connect API الخاص بك. ملف .p8 يُتاح للتنزيل مرة واحدة فقط، عند إنشائه، فإذا فقدته فستحتاج إلى إنشاء مفتاح جديد وتدوير كل ما يستخدمه. ولا تسألني كيف أعرف ذلك.
الحقول الخمسة، والاثنان اللذان يُسببان المتاعب
نص الطلب هو Consumption Request. ثلاثة حقول مطلوبة، واثنان اختياريان.
Customer Consented (مطلوب). تكون قيمته true فقط إذا كان لديك موافقة حقيقية. مزيد من التفاصيل أدناه، لأن هذا الحقل يتحكم في العملية بأكملها.
Delivery Status (مطلوب). يحدد ما إذا كانت عملية الشراء قد وصلت وعملت بشكل صحيح.
Sample Content Provided (مطلوب). يحدد ما إذا كانت هناك نسخة تجريبية مجانية، أو فترة تجربة، أو معلومات عن الوظائف متاحة قبل الشراء.
Consumption Percentage (اختياري). مقدار ما استهلكه العميل، بوحدة الألفية (milliunits). من 0 إلى 100000، فنسبة 45.2 بالمئة تُكتب 45200.
Refund Preference (اختياري). GRANT_FULL، أو GRANT_PRORATED، أو DECLINE، أو NO_PREFERENCE.
قيدان سيكلّف كل منهما نصف يوم من وقتك.
إذا لم تكن Delivery Status تساوي DELIVERED، فيجب أن تكون Consumption Percentage تساوي 0. وأي قيمة أخرى تفشل بخطأ Undelivered Consumption Percentage Non Zero Error. وهذا ما أضاع يومي.
وإذا أرسلت GRANT_PRORATED لاشتراك تلقائي متجدد، فلا تُضمّن consumptionPercentage على الإطلاق. فـApple تحسبها بناءً على الوقت المنقضي في فترة الفوترة، وترفض قيمتك بخطأ Consumption Percentage Auto Renewable Subscription Error. أسماء الأخطاء على الأقل واضحة ومعبّرة، وهذا أكثر مما كنت أتوقعه.
الموافقة هي البوابة، لا مجرد إجراء شكلي
توثيق Apple واضح وصريح بشكل غير معتاد في هذه النقطة: إذا لم يوافق العميل، فلا تردّ على الإشعار. ليس أن تردّ مع ضبط Customer Consented على false. بل لا تردّ أصلًا.
كما لا يمكنك الحصول على الموافقة بعد وصول الطلب. يجب أن تكون موجودة مسبقًا، وهذا يعني أن الصياغة يجب أن تكون في شروط الاستخدام أو سياسة الخصوصية الخاصة بك قبل إطلاق أي من هذا، لا بعد أول طلب استرداد متنازع عليه. وتوجيهات Apple تنص على أن الموافقة يجب أن تكون طوعية، ومحددة، ومستنيرة، وغير غامضة، وأن الاختيار الصريح (opt-in) أفضل من الاختيار الضمني (opt-out)، وأن مسؤولية الحصول عليها تقع عليك وحدك.
من الناحية العملية: سطر يوضّح أنك قد تشارك بيانات الشراء والاستخدام مع Apple للمساعدة في مراجعة طلبات الاسترداد، بالإضافة إلى طريقة للانسحاب. احصل على مراجعة قانونية خاصة بك لهذا الأمر. أنا لست محاميًا وهذا ليس نصيحة قانونية.
كيف يؤتمت المطورون طلبات استرداد الأموال في App Store
سلسلة العمل، بالترتيب الذي سأبنيها به:
هيّئ Production Server URL في App Store Connect، تحت App Information، واختر Version 2. فطلبات الاستهلاك (consumption requests) لا تُرسل عبر V1.
تحقق من سلسلة توقيع JWS مقابل شهادات الجذر (root certificates) الخاصة بـApple قبل الوثوق بأي حقل.
طابق المعاملة بالمستخدم. الـappAccountToken الذي أرفقته عند الشراء هو الطريق الأسرع. وبدونه ستضطر إلى البحث العكسي عبر المنتج والتاريخ، وهو أمر يصبح معقدًا بسرعة.
تحقق من الموافقة. بلا موافقة، لا رد. سجّل ذلك واستمر.
استخرج بيانات الاستخدام الفعلية. ليس تقديرًا، وليست قيمة افتراضية. إذا لم تستطع الإجابة عن "كم استخدم العميل من هذا؟"، فتلك هي الفجوة الحقيقية في أتمتتك.
بناء ConsumptionRequest، مع تطبيق القيدين المذكورين أعلاه.
أرسله بطلب PUT، وتأكد من رمز 202، واحفظ الحمولة (payload) التي أرسلتها.
الخطوة 5 هي حيث تتعثر معظم المحاولات. استدعاء API هو عمل نصف يوم. أما توفير بيانات استهلاك دقيقة لكل مستخدم عن عملية شراء تمت منذ سبعة أسابيع، فهي مشكلة في نمذجة البيانات قد لا تكون قد حللتها بعد.
ما الذي يجعله يصمد في بيئة الإنتاج
بعيدًا عن السيناريو المثالي، هناك أربعة أمور:
منع التكرار (Idempotency). تُعيد Apple إرسال الإشعارات، ومُعالِجك سيرى نفس الإشعار مرتين. أزل التكرار بالاستناد إلى UUID الإشعار، وإلا فستسجّل ردودًا مكررة.
نظام تنبيهات يُنبّه شخصًا فعليًا. فمعالج يفشل بصمت أسوأ من عدم وجود معالج على الإطلاق، لأنك ستظن أنك محمي.
بنية اختبار في بيئة sandbox. فبيئة sandbox تمنحك 5 دقائق فقط بدلًا من 12 ساعة، لذا لا يمكنك اختبار هذا يدويًا بشكل عشوائي. برمجها كسكريبت.
وسجلّ بما أرسلته، لكل حالة على حدة. فعندما يُرفض طلب استرداد، ستحتاج إلى معرفة أي تفضيل (preference) قدّمته ولماذا. وعندما يُمنح استرداد رغم أنك اخترت DECLINE، ستحتاج إلى معرفة ذلك أيضًا.
تقدير صادق: بضعة أيام من الربط إذا كنت قد بنيت أنابيب webhook من قبل، وبضعة أسابيع لتثق فيه فعليًا. ومعظم هذا الوقت لا يتعلق بجانب Apple.
أو اترك شيئًا آخر يقوم بذلك
إذا كنت تستخدم RevenueCat أو Adapty بالفعل، تحقق أولًا مما يقدّمانه. فكلاهما أضاف ميزات للرد على طلبات الاسترداد، وإيصالاتك وربط العملاء موجودة فيهما بالفعل.
برامج إدارة استرداد الأموال في Apple المصمَّمة خصيصًا لهذا الغرض تذهب إلى مدى أبعد. Refund Sensor يعمل باستخدام مفاتيح App Store Connect التي تملكها بالفعل، ويردّ على كل طلب بأدلة من المعاملة خلال المهلة المحددة، ويسجّل النتيجة لكل حالة. بدون SDK، وبدون تغييرات في الكود، وخلال نحو خمس دقائق للربط. وقد قارنّا بقية الحلول المتاحة في أدوات إدارة استرداد الأموال في Apple، إذا كنت تفضّل المقارنة بين الخيارات.
جانب العميل، بما أن الكثيرين يسألون عنه
بخصوص كيفية طلب استرداد أموال عمليات شراء في App Store، من منظور المستخدم: توجّه إلى reportaproblem.apple.com، وسجّل الدخول، وابحث عن عملية الشراء خلال آخر 90 يومًا، واختر سببًا. وتخبر Apple العملاء بأنهم سيحصلون على رد خلال 48 ساعة.
يمكنك أيضًا إظهار هذا النموذج نفسه داخل تطبيقك باستخدام refund request sheet الخاصة بـStoreKit، والتي توجّه المستخدمين غير الراضين إلى Apple بدلًا من تقييم بنجمة واحدة. ولا يمكنها منح الاسترداد. Apple وحدها القادرة على ذلك. دليل Apple Support لطلب استرداد الأموال يوضّح ما يراه العميل.
يستحق الأمر أن يُقال بوضوح، لأن إدارة استرداد الأموال في App Store كثيرًا ما يُضخَّم تسويقها: لا يمكنك قبول أو رفض طلب استرداد. يمكنك فقط تقديم أدلة لـApple بينما تتخذ قرارها، وتحديد تفضيلك. وهذا هو كل التأثير الذي تملكه، وهو أكثر مما يستخدمه معظم المطورين.
المصادر
الأسئلة الشائعة
نعم. هذا هو الغرض من إشعار CONSUMPTION_REQUEST ونقطة النهاية Send Consumption Information. لا يمكنك منح الاسترداد أو رفضه، لكن Apple تستخدم ما ترسله كمُدخل ضمن عملية اتخاذ القرار.
استقبل إشعارات App Store Server Notifications V2، وتحقق من توقيع JWS، وطابق المعاملة بالمستخدم، وتحقق من الموافقة، وجمّع بيانات استهلاك حقيقية، وأرسلها بطلب PUT إلى نقطة نهاية الاستهلاك في الإصدار v2 خلال 12 ساعة. كل ما عدا ذلك يتعلق بمنع التكرار (idempotency) والتنبيهات وحفظ السجلات.
مفتاح In-App Purchase من قسم Users and Access، تحت Integrations. مفتاح App Store Connect API الخاص بك لن يُصادق على هذه الطلبات، وستحصل على خطأ 401 يبدو كمشكلة في التوقيت.
لا، هذا الحقل اختياري. لكن إذا لم تكن قيمة deliveryStatus هي DELIVERED، فيجب أن تكون القيمة 0، وفي حالة الاشتراك التلقائي المتجدد مع التناسب الزمني (prorated)، يجب حذف هذا الحقل كليًا.
لا تَرُدّ. توجيهات Apple واضحة في هذا الشأن، ولا يمكن الحصول على الموافقة بعد وصول الطلب.
إنه مُدخل، لا ضمانة. تقول Apple إنها تستخدم مجموعة متنوعة من العوامل. عمليًا، فإن رد DECLINE المدعوم بأدلة قوية على عملية شراء استُهلكت بشكل كبير يُرفض في أغلب الأحيان، على عكس الطلب الذي لم يرد عليه أحد.



