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

اكتشاف الاسترداد في StoreKit 2 يتلخص في خاصية واحدة على الـ transaction، وهي revocationDate

عندما يعيد Apple المال لأحد عملائك، فإن الاسترداد يكون موجوداً بالفعل داخل تطبيقك على revocationDate الخاص بالـ transaction، قبل أن تعمل مهمة الخادم لديك. هنا نوضح أين يظهر اكتشاف الاسترداد في StoreKit 2 على الجهاز، وما الذي يخبرك به revocationDate و revocationReason، ولماذا العميل للسرعة والخادم للحقيقة.

هاتف ذكي يتوهج على مكتب مطور مظلم بجانب ساعة ميكانيكية، يوضح كيف يظهر اكتشاف الاسترداد في StoreKit 2 داخل التطبيق

أهم الخلاصات

  • في StoreKit 2 يحمل الـ purchase المُسترد قيمة revocationDate غير nil على الـ Transaction الخاص به، لذا يمكن لتطبيقك اكتشاف الاسترداد بنفسه، دون استدعاء خادمك.
  • يتم تعيين revocationDate عندما يعيد App Store المال عن transaction أو عندما يفقده عميل عبر Family Sharing، لذا فإن التاريخ غير nil لا يعني دائماً استرداداً.
  • يخبرك revocationReason بالسبب: developerIssue يعني أن العميل ذكر مشكلة في تطبيقك، و other يغطي كل سبب استرداد آخر متبقٍ.
  • يستبعد Transaction.currentEntitlements بالفعل المشتريات المُستردة والملغاة، لذا فإن أنظف بوابة من جانب العميل للوصول هي ببساطة ما إذا كان الـ product لا يزال يظهر هناك.
  • لا يسلّم Transaction.updates استرداداً حدث بينما كان تطبيقك مغلقاً إلا إذا بدأت الاستماع عند التشغيل، لذا فإن غياب Task يعني استرداداً فائتاً.
  • الاكتشاف من جانب العميل يعمل فقط بينما التطبيق مفتوح، ولهذا يبقى REFUND في App Store Server Notifications V2 هو الإشارة الموثوقة التي توقفك عن الدفع لخدمة مستخدم مُسترد.
  • يمكن عكس الاسترداد، وعندما يحدث ذلك، تُزال حقول الـ revocation من الـ transaction ويُتوقع منك استعادة الوصول الذي قطعته.

تعرف معظم التطبيقات عن استرداد Apple من خادمها، عبر App Store Server Notification، ولا تلاحظ أبداً أن الاسترداد نفسه موجود بالفعل داخل التطبيق. إنه على الـ transaction، في خاصية اسمها revocationDate، وقراءتها تتيح لتطبيقك قطع وصول عميل مُسترد في المرة التالية التي يفتحه فيها، بدلاً من انتظار مهمة خلفية. اكتشاف الاسترداد في StoreKit 2 هو إشارة من جانب العميل تتخطاها معظم الفرق. هنا نوضح بالضبط أين يظهر الاسترداد على الجهاز، وما الذي يخبرك به، وما لا يخبرك به، ولماذا يجب أن يكون بجانب إشعارات خادمك وليس بديلاً عنها.

أين يظهر الاسترداد داخل StoreKit 2

يسلّمك StoreKit 2 المعاملات كقيم موقّعة، والاسترداد لا يحذف الـ transaction. إنه يضع عليه علامة. خاصيتان على الـ Transaction تحملان العلامة، وكلاهما يبقى nil طوال حياة الـ purchase السليم. عندما تصبح إحداهما غير nil، يكون App Store قد استعاد الـ purchase.

revocationDate هو الحقل الذي ينقلب

revocationDate هو Date اختياري. وصف Apple نفسه دقيق: إنه التاريخ الذي أعاد فيه App Store المال عن الـ transaction أو ألغاه من Family Sharing. بالنسبة لـ purchase لا يزال جيداً، يكون nil. في اللحظة التي تتم فيها معالجة استرداد، يحمل الطابع الزمني لذلك الاسترداد. هذا الفحص الوحيد، هل revocationDate غير nil، هو كامل اكتشاف الاسترداد من جانب العميل. كل ما عدا ذلك دقائق مكدسة فوقه.

revocationReason يخبرك لماذا سحبه Apple

revocationReason يقع بجانب التاريخ ويشرح السبب. يمنحه StoreKit قيمتين مهمتين للاسترداد. developerIssue يعني أن العميل أخبر Apple أن الاسترداد كان بسبب مشكلة فعلية أو متصوَّرة في تطبيقك. other يغطي كل سبب متبقٍ. قيمة ثالثة، upgradedToBundle، ليست استرداداً على الإطلاق؛ إنها تشير إلى transaction ألغاه App Store لأن العميل انتقل إلى subscription bundle. اقرأ السبب قبل أن تتصرف، لأن developerIssue هو ما يستحق العدّ: مجموعة منها هي منتجك نفسه يخبرك أين انكسر.

PropertyTypeماذا تعني قيمة غير nil
revocationDateDate?أعاد App Store المال عن هذا الـ transaction، أو ألغاه عبر Family Sharing، في هذا التاريخ
revocationReason هو developerIssuereasonذكر العميل مشكلة فعلية أو متصوَّرة في تطبيقك
revocationReason هو otherreasonحدث الاسترداد لسبب آخر لا يفصّله Apple
revocationReason هو upgradedToBundlereasonليس استرداداً؛ أُلغي الـ transaction لأن العميل انتقل إلى subscription bundle

currentEntitlements يُسقط بالفعل الـ purchase المُسترد

لست مضطراً دائماً لقراءة حقول الـ revocation بنفسك. Transaction.currentEntitlements هو تسلسل المشتريات التي لا يزال العميل مستحقاً لها الآن، ويبنيه Apple ليستبعد تلك التي لا ينبغي أن تكرمها. الـ product الذي أعاد App Store المال عنه أو ألغاه لا يظهر فيه. ولا الاشتراكات المنتهية، ولا الـ consumables، التي تختفي لحظة انتهائها.

هذا يجعل currentEntitlements أنظف بوابة للوصول. اسأله عمّا يملكه العميل، امنح ذلك بالضبط، والاسترداد يزيل الـ entitlement نيابة عنك دون فحص revocationDate واحد. حقول الـ revocation لأجل عندما تريد التفاصيل، التاريخ والسبب، لتسجيل الحدث أو التفاعل معه. قائمة الـ entitlement لأجل السؤال البسيط عمّا إذا كان يجب إبقاء الأضواء مضاءة.

اكتشاف الاسترداد في StoreKit 2 عملياً، من التشغيل وأثناء عمل التطبيق

هناك لحظتان يمكن لتطبيقك أن يلتقط فيهما استرداداً على الجهاز، وتحتاجان إلى كود مختلف. الأولى بينما التطبيق مفتوح ويحدث استرداد مباشرة أو على جهاز آخر. الأخرى عند التشغيل، للحاق بكل ما تغيّر بينما كنت مغلقاً. أخفق في الثانية وسيكون في اكتشاف الاسترداد في StoreKit 2 لديك ثغرة بالضبط حيث تسقط معظم عمليات الاسترداد، لأن العملاء نادراً ما يكون تطبيقك مفتوحاً لديهم عندما يطلبون استرداداً.

ابدأ الاستماع عند التشغيل وإلا فاتتك عمليات الاسترداد التي حدثت وأنت مغلق

Transaction.updates هو التسلسل غير المتزامن الذي يصدر transaction كلما أنشأ النظام أو حدّث واحداً خارج تطبيقك أو على جهاز آخر، بما في ذلك الاسترداد. تعليمات Apple صريحة: ابدأ Task يتكرر عليه بمجرد تشغيل تطبيقك، وإلا فقد تفوتك المعاملات التي يسلّمها مرة واحدة فقط عند البدء. استرداد وصل أثناء الليل يصل عبر updates في المرة التالية التي يُفتح فيها التطبيق، لكن فقط إذا كان هناك listener يعمل بالفعل لاستقباله. لا listener، لا event، ويبقى الاسترداد غير مرئي حتى يوفّقه شيء آخر.

الـ purchase على الجهاز نفسه لا يأتي عبر updates

فخّ واحد يوقع من يختبرون الاسترداد يدوياً. الـ purchase العادي الذي يتم على الجهاز نفسه لا يصل عبر updates؛ يعيده StoreKit مباشرة من نتيجة استدعاء الـ purchase. updates للتغييرات خارج النطاق: عمليات الاسترداد، موافقات Ask to Buy، استبدالات offer-code، والمشتريات التي تتم في مكان آخر. لذا ابنِ معالجة الاسترداد لديك حول updates و currentEntitlements، وليس حول تدفق الـ purchase، لأن الاسترداد لن يعود أبداً عبر المسار الذي سلكته عملية البيع.

إيصال ورقي مجعّد على سطح مظلم مع ختم أحمر باهت مضغوط عبره، يمثّل transaction مُسترداً يضع StoreKit 2 عليه علامة بـ revocation date

ما لا يستطيع الاكتشاف من جانب العميل فعله لك

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

الـ revocationDate ليس دائماً استرداداً

الحقل نفسه ينقلب لأجل Family Sharing. عندما يفقد عميل الوصول إلى purchase مشترك، لأن المنظّم أزاله أو انتهت المشاركة، يحصل ذلك الـ transaction على revocationDate أيضاً. لذا فإن تاريخاً غير nil يعني أن العميل لم يعد يملك هذا الـ purchase، وهو بالضبط ما تحتاجه للتحكم في الوصول، لكنه لا يعني دائماً أن المال عاد. إذا كنت تعدّ عمليات الاسترداد للإيرادات، افصل إلغاءات Family Sharing عن الحقيقية قبل أن تثق بالرقم.

يمكن عكس الاسترداد

الاسترداد ليس دائماً نهائياً. يمكن لـ Apple عكسه، وعندما يفعل، تُزال حقول الـ revocation من الـ transaction ويصبح الـ purchase صالحاً مجدداً. إذا قطعت الوصول عند الاسترداد، يُتوقع منك استعادته عند العكس. على الجهاز يظهر ذلك كـ event آخر من updates بـ transaction نظيف؛ على خادمك هو notification متميز اسمه REFUND_REVERSED. عالج الاسترداد فقط وستترك عميلاً يدفع عالقاً بلا وصول وبإيصال يعمل.

ما يكلّفه فعلاً الإلغاء المتأخر

نادراً ما يكون الاسترداد مجرد خروج سعر البيع من حسابك. بحلول وقت تسويته تكون عادة قد أنفقت بالفعل مالاً حقيقياً لخدمة ذلك الـ purchase، وذلك الإنفاق لا يعود. الصور المُنشأة تكلّف دقائق GPU. إجابات الدردشة تكلّف استدعاءات model API فُوترت عليك لكل token. عمليات الرفع تكلّف تخزيناً لا تزال تدفع للاحتفاظ به. إذا موّل الـ purchase دفعة لمُنشئ محتوى، فذلك المال خرج بالفعل. لا شيء من ذلك ينعكس مع الاسترداد.

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

استخدم العميل للسرعة والخادم للحقيقة

التصميم النظيف يستخدم الإشارتين لما تجيده كل منهما. على الجهاز، يمنحك Transaction.updates و currentEntitlements رد فعل فورياً محلياً في اللحظة التي يفتح فيها عميل مُسترد التطبيق، جيد للواجهة ولإنهاء تغييرات الـ entitlement دون رحلة ذهاب وإياب. على الخادم، ترسل App Store Server Notifications V2 رسالة REFUND تصل سواء فُتح التطبيق مجدداً أم لا، وهي الإشارة الوحيدة التي توقف خلفيتك بشكل موثوق عن الإنفاق على حساب مُسترد.

Signalأين يعيشيعمل عندماثِق به لـ
revocationDate على transactionDevice، StoreKit 2يقرأ تطبيقك الـ transactionيخبرك أن purchase محدداً استُرد أو أُلغي
Transaction.updatesDevice، StoreKit 2يصل استرداد بينما التطبيق يعمل، أو عند التشغيل إن استمعتالتفاعل فوراً لعميل حاضر
currentEntitlementsDevice، StoreKit 2تفحص ما يملكه العميل الآنبوابة الوصول دون تتبّع عمليات الاسترداد بنفسك
REFUND notificationخادمك، App Store Server Notifications V2يعالج Apple الاسترداد، سواء فُتح التطبيق أم لاإيقاف الإنفاق من جانب الخادم على عميل لا يعود أبداً

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

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

كيف أكتشف استرداداً في StoreKit 2؟
افحص revocationDate الخاص بالـ transaction. يكون nil لـ purchase صالح ويحمل تاريخاً بمجرد أن يعيد App Store المال عن الـ transaction، لذا فإن revocationDate غير nil هو الإشارة إلى أن purchase قد استُرد أو أُلغي.
ما الفرق بين revocationDate و revocationReason؟
revocationDate هو متى استعاد App Store الـ purchase، و revocationReason هو لماذا. السبب هو developerIssue عندما يذكر العميل مشكلة في تطبيقك و other لأي شيء آخر.
هل يظهر الـ purchase المُسترد في currentEntitlements؟
لا. يستبعد Transaction.currentEntitlements المشتريات التي أعاد App Store المال عنها أو ألغاها، لذا فإن product مُسترداً يسقط من entitlements العميل من تلقاء نفسه، مما يجعله بوابة آمنة للوصول.
هل سيخبر StoreKit تطبيقي عن استرداد حدث بينما كان التطبيق مغلقاً؟
فقط إذا استمعت من التشغيل. يسلّم Transaction.updates تلك التغييرات مرة واحدة عند البدء، لذا يجب أن تبدأ Task يتكرر عليه بمجرد تشغيل تطبيقك وإلا فُقد الاسترداد حتى يوفّقه شيء آخر.
هل الاكتشاف من جانب العميل كافٍ بمفرده؟
لا. يعرف الجهاز عن استرداد فقط بينما تطبيقك يعمل، لذا فإن عميلاً لا يعيد فتح التطبيق أبداً يكون غير مرئي له. REFUND في App Store Server Notifications V2 هو الإشارة التي تصلك بغض النظر عن ذلك.
هل revocationDate يعني دائماً أن العميل حصل على استرداد؟
لا. يُعيَّن revocationDate أيضاً عندما يفقد عميل purchase عبر Family Sharing، لذا فإن تاريخاً غير nil يعني أنه لم يعد يملك الـ purchase لكن ليس دائماً أن المال أُعيد.

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

RefundHalt

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

تابع القراءة

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

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