Виявлення повернень у StoreKit 2 зводиться до однієї властивості транзакції, і це revocationDate
Коли Apple повертає гроші одному з ваших клієнтів, повернення вже перебуває всередині вашого застосунку, у властивості revocationDate транзакції, ще до того як спрацює серверне завдання. Ось де виявлення повернень StoreKit 2 проявляється на пристрої, що вам кажуть revocationDate і revocationReason, і чому клієнт потрібен для швидкості, а сервер для істини.

Головне
- У StoreKit 2 повернена покупка несе непорожній revocationDate у своїй Transaction, тож ваш застосунок може виявити повернення самостійно, без звернення до сервера.
- revocationDate встановлюється, коли App Store повертає гроші за транзакцію або коли клієнт втрачає її через Family Sharing, тож непорожня дата не завжди означає повернення.
- revocationReason повідомляє причину: developerIssue означає, що клієнт вказав на проблему у вашому застосунку, а other охоплює всі інші причини повернення.
- Transaction.currentEntitlements уже виключає повернені та відкликані покупки, тож найчистіший клієнтський спосіб перевірити доступ, це просто наявність продукту в цьому списку.
- Transaction.updates доставляє повернення, що сталося при закритому застосунку, лише якщо ви починаєте слухати під час запуску, тож відсутній Task означає пропущене повернення.
- Клієнтське виявлення спрацьовує лише поки застосунок відкритий, тому REFUND з App Store Server Notifications V2 залишається авторитетним сигналом, який не дає вам платити за обслуговування клієнта з поверненням.
- Повернення можна скасувати, і коли це стається, поля відкликання видаляються з транзакції, і ви маєте відновити доступ, який відключили.
Більшість застосунків дізнаються про повернення Apple від свого сервера, через App Store Server Notification, і не помічають, що те саме повернення вже перебуває всередині застосунку. Воно в транзакції, у властивості під назвою revocationDate, і читання його дозволяє вашому застосунку відключити доступ клієнту з поверненням при наступному відкритті, замість очікування серверного завдання. Виявлення повернень StoreKit 2, це клієнтський сигнал, який більшість команд пропускає. Ось де саме повернення проявляється на пристрої, що воно вам каже, чого не каже, і чому воно має бути поруч із серверними сповіщеннями, а не замість них.
Де повернення з'являється всередині StoreKit 2
StoreKit 2 передає вам транзакції як підписані значення, і повернення не видаляє транзакцію. Воно позначає її. Дві властивості в Transaction несуть цю позначку, і обидві залишаються nil протягом усього життя здорової покупки. Коли одна з них стає непорожньою, App Store забрав покупку назад.
revocationDate, це поле, яке змінюється
revocationDate, це опціональна Date. Власний опис Apple точний: це дата, коли App Store повернув гроші за транзакцію або відкликав її з Family Sharing. Для покупки, яка все ще в порядку, це nil. У момент обробки повернення воно зберігає позначку часу цього повернення. Ця єдина перевірка, чи непорожній revocationDate, і є все клієнтське виявлення повернень. Усе інше, це нюанси поверх неї.
revocationReason повідомляє, чому Apple відкликав покупку
revocationReason розташований поруч із датою і пояснює причину. StoreKit дає йому два значення, важливі для повернень. developerIssue означає, що клієнт повідомив Apple, що повернення було спричинене реальною або уявною проблемою у вашому застосунку. other охоплює всі інші причини. Третє значення, upgradedToBundle, зовсім не повернення; воно позначає транзакцію, яку App Store відкликав, тому що клієнт перейшов на пакет підписки. Читайте причину, перш ніж діяти, тому що developerIssue, це та, яку варто рахувати: їх скупчення, це ваш власний продукт, що каже вам, де він зламався.
| Властивість | Тип | Що означає непорожнє значення |
|---|---|---|
revocationDate | Date? | App Store повернув гроші за цю транзакцію або відкликав її через Family Sharing у цю дату |
revocationReason дорівнює developerIssue | причина | Клієнт вказав на реальну або уявну проблему у вашому застосунку |
revocationReason дорівнює other | причина | Повернення сталося з якоїсь іншої причини, яку Apple не деталізує |
revocationReason дорівнює upgradedToBundle | причина | Не повернення; транзакцію відкликано, тому що клієнт перейшов на пакет підписки |
currentEntitlements уже виключає повернену покупку
Вам не завжди потрібно читати поля відкликання самостійно. Transaction.currentEntitlements, це послідовність покупок, на які клієнт усе ще має право просто зараз, і Apple будує її так, щоб залишити за бортом ті, які ви не повинні враховувати. Продукт, за який App Store повернув гроші або який відкликав, у ній не з'являється. Як і застарілі підписки чи витратні товари, які зникають у момент, коли їх використано.
Це робить currentEntitlements найчистішим способом контролю доступу. Запитайте його, чим володіє клієнт, надайте рівно це, і повернення прибере право за вас без жодної перевірки revocationDate. Поля відкликання потрібні, коли вам потрібні деталі, дата і причина, щоб записати подію або відреагувати на неї. Список прав потрібен для простого питання, чи залишати світло увімкненим.
Виявлення повернень StoreKit 2 на практиці, під час запуску і поки застосунок працює
Є два моменти, коли ваш застосунок може зловити повернення на пристрої, і їм потрібен різний код. Один, поки застосунок відкритий і повернення відбувається наживо або на іншому пристрої. Інший, під час запуску, наздоганяючи все, що змінилося, поки ви були закриті. Пропустіть другий, і у вашому виявленні повернень StoreKit 2 буде діра саме там, куди потрапляє більшість повернень, тому що у клієнтів рідко відкритий ваш застосунок, коли вони просять повернення.
Почніть слухати під час запуску, інакше ви пропустите повернення, що сталися при закритому застосунку
Transaction.updates, це асинхронна послідовність, яка видає транзакцію щоразу, коли система створює або оновлює її поза вашим застосунком чи на іншому пристрої, включно з поверненням. Інструкція Apple пряма: запустіть Task, який перебирає її, щойно ваш застосунок запускається, інакше ви можете пропустити транзакції, які вона доставляє лише один раз при старті. Повернення, що сталося вночі, приходить через updates при наступному відкритті застосунку, але лише якщо слухач уже працює, щоб його прийняти. Немає слухача, немає події, і повернення залишається невидимим, поки щось інше його не звірить.
Покупка на тому самому пристрої не приходить через updates
Одна пастка ловить тих, хто тестує повернення вручну. Звичайна покупка, зроблена на тому самому пристрої, не приходить через updates; StoreKit повертає її прямо з результату виклику покупки. updates потрібен для позасмугових змін: повернень, схвалень Ask to Buy, погашень промокодів і покупок, зроблених в іншому місці. Тож будуйте обробку повернень навколо updates і currentEntitlements, а не навколо процесу покупки, тому що повернення ніколи не повернеться тим шляхом, яким пройшов продаж.

Чого клієнтське виявлення не може зробити за вас
Читання повернень на пристрої швидке і безкоштовне, але воно має стелю, і вдавати, що її немає, це спосіб втрачати дохід. Пристрій знає лише те, що йому сказав StoreKit, а StoreKit говорить лише поки ваш застосунок працює. Клієнт, який отримав повернення і більше ніколи не відкрив ваш застосунок, це клієнт, якого ваша клієнтська перевірка ніколи не побачить.
revocationDate не завжди повернення
Те саме поле змінюється через Family Sharing. Коли клієнт втрачає доступ до спільної покупки, тому що організатор видалив його або обмін закінчився, ця транзакція теж отримує revocationDate. Тож непорожня дата означає, що клієнт більше не має цієї покупки, що якраз те, що вам потрібно для контролю доступу, але це не завжди означає, що гроші повернулися. Якщо ви рахуєте повернення для доходу, відокремте відкликання Family Sharing від справжніх, перш ніж довіряти цифрі.
Повернення можна скасувати
Повернення не завжди остаточне. Apple може його скасувати, і коли це стається, поля відкликання видаляються з транзакції, і покупка знову дійсна. Якщо ви відключили доступ при поверненні, ви маєте відновити його при скасуванні. На пристрої це проявляється як ще одна подія updates з чистою транзакцією; на вашому сервері це окреме сповіщення REFUND_REVERSED. Обробіть лише повернення, і ви залишите платящого клієнта без доступу, але з робочим чеком.
У що насправді обходиться пізнє відкликання
Повернення рідко буває просто ціною продажу, що йде з вашого рахунку. На той час, коли воно проходить, ви зазвичай уже витратили реальні гроші на обслуговування цієї покупки, і ці витрати не повертаються. Згенеровані зображення коштують хвилин роботи GPU. Відповіді чату коштують викликів API моделі, за які вам виставили рахунок за кожен токен. Завантаження коштують сховища, за яке ви все ще платите. Якщо покупка профінансувала виплату автору, ці гроші вже пішли. Нічого з цього не повертається з поверненням.
Клієнтське виявлення скорочує вікно на тій єдиній частині, яку ви все ще можете контролювати, це майбутні витрати. Що раніше ви дізнаєтеся, що за покупку повернули гроші, то раніше ви перестанете її обслуговувати. Але пристрій повідомляє вам лише поки застосунок відкритий, тож клієнт з поверненням, який ніколи не повернеться, зберігає будь-який серверний доступ, який ви надали, тихо обходячись вам у копійчину щоразу, коли фонове завдання чи синхронізований пристрій діють від його імені. Клієнт робить відкликання швидким. Він не робить його гарантованим.
Використовуйте клієнт для швидкості, а сервер для істини
Чиста архітектура використовує обидва сигнали для того, у чому кожен добрий. На пристрої Transaction.updates і currentEntitlements дають вам миттєву локальну реакцію в момент, коли клієнт з поверненням відкриває застосунок, що добре для інтерфейсу і для завершення змін прав без звернення до сервера. На сервері App Store Server Notifications V2 надсилають повідомлення REFUND, яке приходить незалежно від того, чи відкриють застосунок знову, і це єдиний сигнал, який надійно зупиняє витрати вашого бекенду на акаунт з поверненням.
| Сигнал | Де живе | Спрацьовує коли | Довіряйте йому |
|---|---|---|---|
revocationDate у транзакції | Пристрій, StoreKit 2 | Ваш застосунок читає транзакцію | Повідомити, що конкретну покупку повернено або відкликано |
Transaction.updates | Пристрій, StoreKit 2 | Повернення приходить, поки застосунок працює, або під час запуску, якщо ви слухаєте | Миттєво відреагувати для присутнього клієнта |
currentEntitlements | Пристрій, StoreKit 2 | Ви перевіряєте, чим клієнт володіє зараз | Контролювати доступ, не відстежуючи повернення самостійно |
REFUND сповіщення | Ваш сервер, App Store Server Notifications V2 | Apple обробляє повернення, відкритий застосунок чи ні | Зупинити серверні витрати на клієнта, який ніколи не повернеться |
Налаштуйте сигнали пристрою для клієнта, який тримає телефон, і серверне сповіщення для того, хто не тримає. Повернення з'являється в обох місцях навмисно. Читати лише один з них, це спосіб, яким акаунт з поверненням продовжує обходитися вам після того, як продаж уже пішов.
Поширені запитання
- Як виявити повернення у StoreKit 2?
- Перевірте revocationDate транзакції. Він дорівнює nil для дійсної покупки і зберігає дату, щойно App Store повертає гроші за транзакцію, тож непорожній revocationDate, це сигнал, що за покупку повернули гроші або її відкликали.
- У чому різниця між revocationDate і revocationReason?
- revocationDate, це коли App Store забрав покупку назад, а revocationReason, це чому. Причина, це developerIssue, коли клієнт вказав на проблему у вашому застосунку, і other для всього іншого.
- Чи з'являється повернена покупка в currentEntitlements?
- Ні. Transaction.currentEntitlements виключає покупки, за які App Store повернув гроші або які відкликав, тож повернений продукт сам випадає з прав клієнта, що робить його безпечним способом контролю доступу.
- Чи повідомить StoreKit мій застосунок про повернення, що сталося при закритому застосунку?
- Лише якщо ви слухаєте з запуску. Transaction.updates доставляє ці зміни один раз при старті, тож ви маєте запустити Task, що перебирає її при запуску застосунку, інакше повернення буде пропущено, поки щось інше його не звірить.
- Чи достатньо клієнтського виявлення повернень самого по собі?
- Ні. Пристрій дізнається про повернення лише поки ваш застосунок працює, тож клієнт, який ніколи не відкриє застосунок знову, для нього невидимий. REFUND з App Store Server Notifications V2, це сигнал, який доходить до вас у будь-якому разі.
- Чи завжди revocationDate означає, що клієнту повернули гроші?
- Ні. revocationDate також встановлюється, коли клієнт втрачає покупку через Family Sharing, тож непорожня дата означає, що він більше не має покупки, але не завжди що гроші повернулися.
Джерела та додаткове читання
- Apple Developer: Transaction.revocationDate (StoreKit)
- Apple Developer: Transaction.RevocationReason (StoreKit)
- Apple Developer: Transaction.currentEntitlements (StoreKit)
- Apple Developer: Transaction.updates (StoreKit)
- Apple Developer: App Store Server Notifications V2 notificationType (REFUND, REFUND_REVERSED)
- Apple Developer Tech Talk: Support customers with StoreKit 2 and App Store Server API
RefundHalt
Автопілот повернень для App Store і Google Play
Читайте далі
Кожне вікно повернення коштів в App Store і Google Play це зворотний відлік, і ось скільки годин дає кожне з них
Кожне повернення коштів в App Store і Google Play запускає годинник, і більшість із них ідуть без вас. Найкоротше вікно повернення в Apple становить 12 годин, вікно для оскарження чарджбеку в Google це 24, а з 3 серпня 2026 року пропущене вікно чарджбеку це рахунок, а не просто втрачений продаж. Ось кожен дедлайн, що торкається вашого акаунта.
Сповіщення про анульовану покупку дозволяє вашому серверу Google Play відкликати доступ у ту саму мить, коли надходить повернення коштів
Google Play може надіслати вашому серверу сповіщення про анульовану покупку в ту мить, коли покупку повертають, оскаржують через чарджбек або анулюють. Воно містить purchaseToken, orderId, productType та refundType і означає одне, відкликати доступ. Ось як його читати та підключити.