كل المقالات
Deep diveمدة القراءة 7 دقائق

هناك endpoint واحد يعيد سجل استرداد App Store الكامل للعميل، وإليك ما يقدمه لك

يعيد endpoint المسمى Get Refund History من Apple سجل استرداد App Store الكامل للعميل على هيئة signed transactions. إليك كل حقل، وكيف يقوم رمز revision بالتقسيم إلى صفحات، ولماذا هو لكل عميل وليس لكل تطبيق، وكم يكلفك استرداد تفوّته.

مشهد مكتبي من الأعلى يضم هاتفاً ذكياً ودفتر معاملات ورقياً وعدسة مكبّرة، يوضّح سحب سجل استرداد App Store لعميل من endpoint المسمى Get Refund History لدى Apple

أهم الخلاصات

  • Get Refund History هو endpoint ضمن App Store Server API يعيد مشتريات in-app المستردة لعميل في تطبيقك على هيئة قائمة من signed transactions، حتى تتمكن من مطابقة عمليات الاسترداد وإلغاء الوصول حتى لو لم يصلك أي notification أبداً.
  • تستدعي GET /inApps/v2/refund/lookup/{transactionId} بأي transaction id لذلك العميل، فتعيد Apple عمليات الاسترداد الخاصة به عبر كل أنواع الشراء في تطبيقك، وليس فقط النوع الذي سألت عنه.
  • تحتوي الاستجابة على ثلاثة حقول: signedTransactions، حتى 20 من JWS transactions لكل صفحة مرتبة بأقدم استرداد أولاً، إضافة إلى رمز revision وقيمة hasMore منطقية للتقسيم إلى صفحات.
  • خزّن رمز revision الأخير. مرّره مرة أخرى في المرة القادمة فتعيد Apple فقط عمليات الاسترداد الأحدث من تلك النقطة، مما يحوّل تفريغ السجل الكامل إلى قائمة قصيرة من الصفوف الجديدة في كل تشغيل.
  • يحمل كل transaction مفكوك التشفير حقلي revocationDate و revocationReason. قيمة revocationReason تساوي 1 تعني أن العميل استرد بسبب مشكلة فعلية أو متصوَّرة في تطبيقك، و 0 تعني سبباً آخر مثل شراء عن طريق الخطأ.
  • هذا الـ endpoint لكل عميل، وليس لكل تطبيق. لا توجد استدعاءة واحدة تسرد كل عمليات الاسترداد عبر تطبيقك بأكمله، لذا تُجري المطابقة لكل حساب انطلاقاً من transaction id، أو تقرأ تدفق notification من نوع REFUND للحصول على العرض الشامل للتطبيق.
  • السبب في ربطه هو المال. الاسترداد الذي لا تلتقطه أبداً يبقي حساباً نشطاً، وتظل تدفع مقابل compute، و model API calls، و storage، و payouts لعميل عوّضته App Store بالفعل.

تحتفظ Apple بسجل قابل للاستعلام لكل استرداد منحته على حساب عميل في تطبيقك، واستدعاءة واحدة تعيده. الـ endpoint هو Get Refund History، وهو جزء من App Store Server API، ويسلّمك سجل استرداد App Store الكامل لذلك العميل على هيئة قائمة من signed transactions. تمرّر transaction id، فتستعيد ما استردته Apple، وتطابقه مع ما لا يزال مفعّلاً لديك.

إليك لماذا يستحق العناء. الاسترداد الذي لا تراه أبداً هو استرداد تظل تدفع مقابله. المال ذهب بالفعل، لكن الحساب يبقى نشطاً، وكل ساعة يبقى فيها كذلك تظل تنفق على compute، و model API calls، و storage، وأي payout مرتبط بذلك العميل. إشعارات الاسترداد لديك مصمّمة لالتقاط هذا لحظة حدوثه. Get Refund History هو خط الدفاع الأخير عندما لا تلتقطها، بعد انقطاع، أو deploy أسقط webhook، أو حالة دعم تحتاج فيها الصورة الكاملة في استدعاءة واحدة.

ما الذي يعيده endpoint سجل استرداد App Store

تستدعي GET /inApps/v2/refund/lookup/{transactionId} مقابل App Store Server API، موقّعة بنفس JWT الذي تستخدمه لكل استدعاءة أخرى إليه. الـ transaction id في المسار يمكن أن يكون أي transaction لذلك العميل. تقرؤه Apple كهوية، وليس كمرشِّح، وتعيد مشتريات ذلك العميل المستردة عبر تطبيقك بأكمله: consumables و non-consumables و auto-renewable و non-renewing subscriptions على حد سواء. كان الإصدار V1 الأقدم من هذا الـ endpoint يعيد حتى 50 استرداداً في استجابة واحدة وهو الآن متوقف. الإصدار الحالي يقسّم إلى صفحات، حتى تتعامل مع العملاء ذوي السجلات الطويلة دون payload ضخم.

الاستجابة ثلاثة حقول

الحقلما الذي يحمله
signedTransactionsحتى 20 transaction مستردة لهذا العميل، كل منها JWS موقّع تتحقق منه وتفكّ تشفيره. مرتبة بأقدم استرداد أولاً حسب revocationDate. المصفوفة الفارغة تعني أن العميل ليس لديه أي استردادات في تطبيقك
revisionرمز تقسيم إلى صفحات. مرّره مرة أخرى للحصول على الصفحة التالية، واحتفظ بالأخير لجلب عمليات الاسترداد الجديدة فقط في المرة القادمة
hasMoreTrue عندما تحتفظ Apple بعدد من transactions المستردة أكبر مما أعادته هذه الصفحة، فتستدعي مجدداً باستخدام revision

ما الذي تخبرك به transaction مستردة واحدة

كل مدخل في signedTransactions هو JWS. تحقق منه مقابل سلسلة شهادات Apple، وفكّ تشفيره، فيكون لديك payload اعتيادي لـ transaction مع تعبئة حقول الاسترداد. هذه هي الحقول المهمة هنا.

الحقلما الذي يخبرك به
transactionIdمعرّف الـ transaction المستردة، مفتاح الربط لديك بالشراء الذي سجّلته
originalTransactionIdمعرّف أول شراء في السلسلة، وسيلتك لربط تجديدات subscription معاً
productIdالمنتج الذي استُرد، حتى تلغي الـ entitlement الصحيح دون سواه
revocationDateوقت UNIX، بالمللي ثانية، الذي استردت فيه Apple الـ transaction
revocationReasonلماذا استردتها Apple. 1 يعني مشكلة فعلية أو متصوَّرة في تطبيقك، و 0 يعني سبباً آخر مثل شراء عن طريق الخطأ
price, currencyالمبلغ، بوحدات milliunits، ورمز عملته وفق ISO 4217، حتى تحسب إجمالي المال المُعاد
appAccountTokenالـ UUID الذي أرفقته عند الشراء، أنظف طريقة لربط استرداد بمستخدمك الخاص

رمز revision هو كيف تتوقف عن إعادة قراءة القائمة بأكملها

الطريقة الساذجة لاستخدام هذا الـ endpoint هي البحث عن عميل والمرور بكل صفحة في كل مرة. هذا يعمل، وعلى عميل لديه خمسون استرداداً فهي خمسون صفاً تعرفها بالفعل زائد الصف الجديد الواحد. رمز revision موجود ليقضي على ذلك الهدر. تحمل كل استجابة revision. عندما تكون hasMore بقيمة true، تمرّره مرة أخرى للحصول على الصفحة التالية. عندما تصل إلى النهاية، تحتفظ بآخر revision رأيته.

ما الذي لن يفعله هذا الـ endpoint

هناك توقّع واحد ينبغي التخلي عنه قبل أن تبني عليه. Get Refund History لكل عميل، وليس لكل تطبيق. لا يمكنك أن تطلب منه كل استرداد تلقاه تطبيقك الأسبوع الماضي. إنه يجيب على سؤال واحد، أي عمليات استرداد يملكها هذا الحساب، وعليك أن تأتي بـ transaction id لذلك الحساب كي تسأله. يصطدم المطورون بهذا الجدار باستمرار ويذهبون بحثاً عن endpoint لاستردادات التطبيق بأكمله لا وجود له.

العرض الشامل للتطبيق موجود في مكان آخر. يرسل تدفق App Store Server Notifications لديك notification من نوع REFUND لحظة منح Apple كل استرداد، ويتيح لك Get Notification History إعادة تشغيل ذلك التدفق مصفّى على أنواع الاسترداد ضمن نطاق تاريخي. فالتقسيم واضح. الإشعارات وسجلها يمنحانك التدفق الشامل للتطبيق. Get Refund History يمنحك القائمة الموثوقة لحساب واحد، عند الطلب، وهو ما تريده عند مكتب دعم أو بعد انقطاع.

عدسة مكبّرة تحوم فوق صف واحد مُبرَز من دفتر معاملات ورقي بجوار هاتف ذكي، توضّح البحث عن استردادات عميل واحد في سجل استرداد App Store

كم يكلفك استرداد فائت من المال

الـ endpoint هو السباكة. الفاتورة هي السبب في مدّ الأنبوب. كل استرداد في تلك القائمة هو مال أُعيد بالفعل، والمتغير الوحيد المتبقي تحت سيطرتك هو كم من الوقت تظل تنفق على حساب لم يعد يدفع.

تظل تدفع لخدمة حساب مسترد

سعر الشراء يذهب لحظة منح Apple الاسترداد. ما يبقى قيد التشغيل هو تكلفة التسليم. بالنسبة لتطبيق يؤدي عملاً حقيقياً لكل مستخدم، فذلك يعني compute، و model API calls، و storage، وأي payout لمنشئ أو شريك مرتبط باستخدامه. العميل المسترد الذي لا تقطع وصوله أبداً هو subscription تموّله من جيبك. المطابقة مقابل Get Refund History والإلغاء بناءً على ما تجده هو كيف توقف ذلك العدّاد عندما تفلت notification.

سبب استرداد قيمته 1 هو تقرير عيب متنكّر

revocationReason يكلفك مرتين إن تجاهلته. التكلفة الأولى هي الاسترداد نفسه. الثانية هي كل استرداد مستقبلي من السبب ذاته. عندما يظل منتج يعود بقيمة revocationReason تساوي 1، أي مشكلة فعلية أو متصوَّرة في تطبيقك، فإن Apple تسلّمك عيّنة موسومة لما يدفع العملاء إلى طلب استرداد أموالهم. تتبّع اتجاهها حسب المنتج فتتمكن من إصلاح التسرّب بدلاً من دفعه استرداداً تلو الآخر.

التقاطه متأخراً لا يزال أفضل من عدم التقاطه

الـ chargeback نهائي مع البنك، وعلى المتجر الآخر صار الآن يحمل رسماً يتحمّله المطور. استرداد App Store ليس كذلك. إنه مُسوّى، لكن الـ entitlement ملكك لتلغيه لحظة معرفتك. لذا حتى الاسترداد الذي تجده متأخراً بأيام عبر هذا الـ endpoint يستحق أن يُوجد. لا يمكنك استرجاع المال، لكن يمكنك إيقاف الإنفاق الذي كان لا يزال يجري خلفه.

كيف يتناسب هذا مع الإشعارات، ومع Google

فكّر في القطع كنظام واحد. الـ notification من نوع REFUND هو الإشارة الحية، تُدفع إلى خادمك بمجرد أن تقرر Apple. Get Refund History هو مصدر الحقيقة القائم على السحب لعميل واحد، الاستدعاءة التي تجريها عندما تفشل عملية الدفع أو عندما يحتاج إنسان الحساب الكامل أمامه. على جانب Google Play الشكل هو الفكرة ذاتها بأسماء مختلفة: يدفع VoidedPurchaseNotification في الوقت الفعلي، و Voided Purchases API هي القائمة التي تسحبها. كلا المتجرين يمنحانك تدفقاً ودفتراً. الخطأ هو الوثوق بالتدفق وحده، لأن التدفقات تسقط.

ربطه بطريقة RefundHalt

الحلقة صغيرة بمجرد أن تكون كل قطعة في مكانها. خذ notification من نوع REFUND كمُشغِّل. طابق مقابل Get Refund History حتى لا يترك webhook ساقط أبداً حساباً مستردّاً نشطاً. فكّ تشفير كل transaction، اربطه على appAccountToken أو transactionId رجوعاً إلى مستخدمك، اقرأ revocationReason حتى يُوسَم استرداد العيب لا أن يُحفظ فحسب، وألغِ الـ entitlement بالضبط بدلاً من الحساب بأكمله. قسّم إلى صفحات برمز revision حتى تقرأ عمليات الاسترداد الجديدة، لا القديمة.

هذا هو الجزء الذي يديره RefundHalt نيابة عنك. يستمع إلى إشعارات الاسترداد، ويرجع إلى Get Refund History عندما يحتاج القائمة الموثوقة، ويتحقق من كل signed transaction، ويلغي الشراء المحدد بدقة، ويحتفظ بـ revision حتى يقرأ كل تمرير ما تغيّر فقط. تحصل على قطع الوصول في ثوانٍ وسجل نظيف لمن استُرد له، ومقابل ماذا، ولماذا، دون أن تقيم عملية polling والتحقق من JWS بنفسك.

الأسئلة الشائعة

كيف أرى كل استرداد عبر تطبيقي بأكمله، وليس عميلاً واحداً فقط؟
لا يمكنك عبر Get Refund History، لأنه لكل عميل ويحتاج transaction id للحساب الذي تسأل عنه. للعرض الشامل للتطبيق، استخدم تدفق App Store Server Notifications لديك، الذي يرسل notification من نوع REFUND لكل استرداد بمجرد أن تمنحه Apple، و Get Notification History لإعادة تشغيل ذلك التدفق مصفّى على أنواع الاسترداد ضمن نطاق تاريخي.
كم عدد عمليات الاسترداد التي يعيدها endpoint المسمى Get Refund History؟
يعيد الإصدار الحالي حتى 20 transaction مستردة لكل صفحة، مرتبة بأقدم استرداد أولاً، ويقسّم البقية إلى صفحات برمز revision عندما تكون hasMore بقيمة true. كان endpoint المتوقف V1 يعيد حتى 50 في استجابة واحدة. لا يوجد حد أقصى للإجمالي، لذا العميل ذو السجل الطويل يمتد ببساطة عبر صفحات أكثر.
ما الغرض من رمز revision؟
إنه كيف تقسّم إلى صفحات وكيف تتفادى إعادة قراءة سجل العميل بأكمله في كل مرة. تتضمن كل استجابة revision. تمرّره مرة أخرى لجلب الصفحة التالية، وتخزّن الأخير حتى تعيد عملية بحثك التالية فقط عمليات الاسترداد الأحدث من تلك النقطة. هذا يبقي المطابقة المجدولة ضمن قائمة قصيرة من الصفوف الجديدة.
ماذا يعني revocationReason في transaction مستردة؟
إنه لماذا استردت Apple الـ transaction. القيمة 1 تعني أن العميل استرد بسبب مشكلة فعلية أو متصوَّرة داخل تطبيقك، و 0 تعني سبباً آخر، مثل شراء عن طريق الخطأ. يخبرك revocationDate بموعد حدوث الاسترداد، بوحدات UNIX بالمللي ثانية. قراءة revocationReason تتيح لك فصل عيب المنتج عن استرداد ندم لمرة واحدة.
هل ما زلت بحاجة إلى هذا إن كنت أتعامل بالفعل مع إشعارات REFUND؟
نعم، كخط دفاع أخير. الإشعارات هي الإشارة الحية، لكن عملية الدفع قد تفشل في الوصول أثناء انقطاع، أو deploy سيئ، أو تغيير في webhook، والاسترداد الفائت يترك حساباً مستردّاً نشطاً ويكلفك مالاً. Get Refund History هو مصدر الحقيقة القائم على السحب الذي تطابق مقابله حتى لا يبقى شيء مفعّلاً استردته Apple بالفعل.

المصادر وقراءات إضافية

RefundHalt

الطيار الآلي للاستردادات في App Store وGoogle Play

تابع القراءة

طلب الاسترداد التالي في طريقه إليك بالفعل.

أعد RefundHalt في الوقت الذي تستغرقه لقراءة رسالة دعم أخرى عن استرداد لم تتمكن من الاعتراض عليه.