Все статьи
Deep dive7 мин чтения

Прикрепляйте appAccountToken к каждой покупке в App Store, иначе вы не сможете защитить возврат

Apple присылает вашему серверу CONSUMPTION_REQUEST, когда клиент просит возврат, но в транзакции нет данных о том, кто он. appAccountToken это UUID, который связывает покупку с вашим пользователем. Установите его, и вы сможете ответить Apple реальными данными. Пропустите, и вам останется только гадать.

Пронумерованный латунный ключ лежит на тёмной бухгалтерской книге рядом со смартфоном, где показан чек о покупке, иллюстрируя, как appAccountToken связывает покупку в App Store с учётной записью пользователя

Главное

  • appAccountToken это UUID, который вы прикрепляете к покупке в App Store, чтобы получившаяся транзакция указывала на конкретного пользователя в вашей собственной системе. Apple хранит его в транзакции и возвращает везде, где эта транзакция появляется.
  • Единственное правило форматирования, которое требует Apple, это чтобы значение было корректным UUID. Передайте что-то другое, id, email, склеенную строку, и StoreKit тихо отбросит это и вернёт appAccountToken как nil.
  • В StoreKit 2 вы задаёте его одним параметром покупки, Product.PurchaseOption.appAccountToken(_:), используя стабильный UUID, который вы сгенерировали и сохранили для этой учётной записи.
  • Установите его один раз на исходной покупке, и Apple переносит тот же токен через каждое продление, повторную попытку списания и апгрейд в цепочке подписки.
  • С 2025 года endpoint Set App Account Token позволяет вашему серверу прикрепить токен к покупкам, сделанным вне вашего приложения, таким как погашение промокодов и продвигаемые покупки, до которых внутренний процесс никогда не мог дотянуться.
  • appAccountToken это то, что делает CONSUMPTION_REQUEST от Apple разрешимым. Без него вы не сможете сопоставить возврат с клиентом, чьё использование вы должны описать в течение 12-часового окна.
  • Возврат, который вы не можете идентифицировать, это возврат, который вы не можете защитить. Вы отдаёте деньги за покупки, которые у вас были доказательства оставить, плюс вычисления, вызовы API и выплаты, которые вы уже потратили на их доставку.

Клиент просит у Apple возврат, Apple присылает вашему серверу CONSUMPTION_REQUEST, и у вас есть двенадцать часов, чтобы ответить реальными данными о том, как этот человек пользовался продуктом. Затем вы открываете уведомление и понимаете, что понятия не имеете, кто он. Транзакция несёт originalTransactionId и id продукта, но ничего, что указывало бы на учётную запись в вашей собственной базе данных. Именно этот разрыв закрывает appAccountToken, и если вы не установили его в момент покупки, вы не сможете закрыть его задним числом для этой продажи.

appAccountToken это UUID, который вы прикрепляете к покупке, чтобы получившаяся транзакция в App Store несла указатель на конкретного пользователя в вашей системе. Установите его, и каждый вопрос о возврате, который Apple когда-либо задаст об этом клиенте, придёт с прикреплённой личностью. Пропустите, и вам придётся гадать. Вот что это за поле, как его установить, новый endpoint, который спасает покупки, сделанные вне вашего приложения, и во что реально обходится отсутствующая связь, когда приходит возврат.

Что такое appAccountToken на самом деле

appAccountToken это непрозрачный UUID, который вы генерируете и передаёте StoreKit в момент покупки. Apple хранит его в транзакции и возвращает в информации о транзакции для этой покупки, и он там остаётся. Словами Apple, это "UUID, который связывает транзакцию с учётной записью пользователя в вашем собственном сервисе". Единственное правило форматирования это то, что он должен быть UUID. Apple не читает его, не проверяет, на что он ссылается, и её не волнует, что он означает на вашей стороне. Это связь, которой управляете вы.

Поскольку он живёт в транзакции, он возвращается везде, где появляется транзакция. Подписанная транзакция в серверном уведомлении, ответ App Store Server API Get Transaction Info и каждое продление в цепочке подписки несут один и тот же токен, если вы установили его на исходной покупке. Один UUID, прикреплённый однажды, сопровождает биллинг клиента на протяжении всей его жизни.

Это должен быть настоящий UUID, иначе он тихо исчезнет

Единственное правило, которое требует Apple, это формат. StoreKit 2 требует UUID по RFC 4122. Если вы передадите склеенную строку, целочисленный id или email, StoreKit не выбросит ошибку. Он отбросит значение, и транзакция вернётся с appAccountToken равным nil. Разработчики постоянно на это натыкаются, и симптом всегда один и тот же, тот или иной вариант "appAccountToken отсутствует в полезной нагрузке транзакции" на собственных форумах Apple, почти всегда потому что переданное значение не было корректным UUID. Сгенерируйте настоящий UUID на стороне сервера, сохраните его для учётной записи и никогда не передавайте StoreKit ничего другого.

Как установить его в момент покупки

В StoreKit 2 это единственный параметр покупки. Сгенерируйте UUID на своём сервере, когда пользователь регистрируется или впервые доходит до оформления, сохраните его в записи учётной записи и передайте это же значение в вызов покупки.

Сигнатура это Product.PurchaseOption.appAccountToken(_ token: UUID), а покупка выглядит как try await product.purchase(options: [.appAccountToken(token)]). Когда транзакция возвращается, проверенная через App Store Server API или доставленная серверным уведомлением, она несёт этот UUID, и ваш сервер находит клиента одним запросом.

Используйте один стабильный токен на учётную запись

Не генерируйте новый токен для каждой покупки одного и того же пользователя. Apple возвращает токен при продлениях, повторных попытках списания и апгрейдах в той же цепочке, поэтому стабильный UUID на учётную запись даёт вам чистую нить от первой покупки через каждое будущее событие. Токен, который меняется от покупки к покупке, рвёт эту нить и сводит на нет весь смысл. Одна учётная запись, один токен, повторно используемый каждый раз, когда эта учётная запись покупает.

Endpoint, который спасает покупки вне приложения

До 2025 года была дыра. Если клиент погашал промокод или покупал продвигаемую внутреннюю покупку прямо из App Store, ваше приложение никогда не запускало процесс покупки, поэтому appAccountToken негде было установить. Эти транзакции приходили анонимными и такими и оставались.

WWDC 2025 закрыл этот пробел с помощью endpoint Set App Account Token. Ваш сервер вызывает PUT /inApps/v1/transactions/{originalTransactionId}/appAccountToken в App Store Server API с UUID в теле, и Apple устанавливает токен на этой транзакции. Это работает для каждого типа продукта, охватывает погашение промокодов и продвигаемые покупки, а значение, которое вы отправляете, перезаписывает любой токен, уже находящийся в транзакции. Теперь вы можете связать покупку задним числом, со своего сервера, без того чтобы она вообще проходила через ваше приложение.

Стопка бумажных чеков о продаже на тёмном столе с пустой строкой имени клиента, один освещён тёплым прожектором, иллюстрируя покупку в App Store, которая приходит без appAccountToken для идентификации покупателя
Канал покупкиГде вы устанавливаете appAccountTokenПримечания
Внутренняя покупкаПараметр покупки StoreKit в момент оплатыProduct.PurchaseOption.appAccountToken(UUID)
Погашение промокодаEndpoint Set App Account Token, на стороне сервераНет внутреннего процесса, за который зацепиться, поэтому установите его после
Продвигаемая внутренняя покупка из App StoreEndpoint Set App Account Token, на стороне сервераПокупка происходит вне вашего приложения
Продление подпискиНичего делать не нужноАвтоматически переносится с исходной покупки

Где отсутствующая связь стоит вам денег

Смысл токена не в аккуратных записях. Смысл в том, что единственный вопрос Apple о возврате для разработчиков, CONSUMPTION_REQUEST, разрешим только если вы можете найти клиента, о котором он идёт.

Когда покупатель запрашивает возврат за расходуемый товар или невозобновляемую подписку, Apple присылает вашему серверу уведомление CONSUMPTION_REQUEST и даёт вам двенадцать часов, чтобы ответить через Send Consumption Information. Ваш ответ это данные о конкретном клиенте: сколько продукта он потребил, стаж его учётной записи, его совокупные траты, статус доставки. appAccountToken сам по себе является одним из полей в этом запросе, и, что важнее, именно так транзакция уведомления сопоставляется с учётной записью, чьё использование вы собираетесь описать. Нет токена, нет поиска, нет точного ответа.

Во что реально обходится пустой ответ

Неидентифицированный возврат вынуждает к плохому выбору. Вы можете ответить на запрос о потреблении ничем, что читается как низкое потребление и подталкивает Apple к одобрению возврата, в том числе клиентам, которые активно пользовались продуктом. Или вы можете гадать. В любом случае вы возвращаете деньги за покупки, которые у вас были доказательства защитить, и вы уже заплатили реальную стоимость их обслуживания.

Эта стоимость это не цена продажи. Расходуемый товар, который запустил пакет вызовов API модели, сгенерировал изображения, экспортировал видео или инициировал выплату автору, потратил реальные деньги в момент доставки. Возврат возвращает платёж клиента. Он не возвращает счёт от провайдера. Умножьте одного неидентифицируемого клиента на каждый возврат, который он подаёт, и на каждого серийного возвращателя, который рассчитывает на то, что вы не знаете, кто он, и токен, который вы пропустили, становится самой дорогой строкой кода, которую вы так и не написали.

Та же идея существует на Android, под другим именем

Google Play решает идентичную проблему с помощью setObfuscatedAccountId, который прикрепляет идентификатор учётной записи к покупке, чтобы на разбор возвратного платежа Google через orders.reviewrefund можно было ответить с привязкой к реальному пользователю. Другой магазин, другая механика, тот же урок: прикрепите личность в момент покупки, иначе вы не сможете защитить спор позже. В App Store этот инструмент это appAccountToken, и он должен быть UUID.

Три привычки, которые удерживают токен на месте

  • Генерируйте UUID на учётную запись и сохраняйте его. Один стабильный токен на клиента, созданный при регистрации или при первом оформлении, сохранённый в его записи и повторно используемый для каждой покупки.
  • Проверяйте перед передачей. Убедитесь, что значение является настоящим UUID в вашем коде покупки, чтобы некорректный id никогда не мог тихо превратиться в nil токен в транзакции.
  • Дозаполняйте покупки вне приложения. Когда приходит серверное уведомление о погашении промокода или о продвигаемой покупке без токена, вызовите endpoint Set App Account Token, чтобы прикрепить нужный.

Сделайте эти три вещи, и каждая транзакция, которую Apple вам когда-либо пришлёт, включая запросы на возврат, придёт уже привязанной к клиенту, которому она принадлежит.

Это фундамент, на который опирается RefundHalt. Мы считываем appAccountToken из каждой транзакции и серверного уведомления, привязываем его к использованию, которое мы уже отслеживаем для этой учётной записи, и отвечаем на запрос о потреблении от Apple в течение двенадцатичасового окна реальными цифрами клиента. Токен это нить. Установите его один раз, и вашей защите от возвратов будет за что держаться.

Частые вопросы

Что такое appAccountToken в App Store?
appAccountToken это UUID, который вы генерируете и прикрепляете к покупке через StoreKit, чтобы получившаяся транзакция в App Store указывала на конкретную учётную запись пользователя в вашей собственной системе. Apple хранит его в транзакции и возвращает в информации о транзакции, серверных уведомлениях и каждом продлении в той же цепочке, что позволяет вам связать любое будущее событие, включая запрос на возврат, с нужным клиентом.
Почему мой appAccountToken равен nil или отсутствует?
Почти всегда потому, что переданное вами значение не было корректным UUID. StoreKit 2 требует UUID по RFC 4122 и тихо отбрасывает всё остальное, поэтому склеенная строка, целочисленный id или email возвращаются как nil appAccountToken, хотя покупка всё равно проходит успешно. Другая распространённая причина это покупка, сделанная вне вашего приложения, например погашение промокода, где не запускался внутренний процесс для установки токена.
Могу ли я установить appAccountToken после покупки, для промокодов?
Да, с 2025 года. Endpoint Set App Account Token в App Store Server API позволяет вашему серверу прикрепить или перезаписать токен на существующей транзакции, вызвав PUT /inApps/v1/transactions/{originalTransactionId}/appAccountToken с UUID в теле. Это работает для каждого типа продукта и создано для покупок, сделанных вне вашего приложения, таких как погашение промокодов и продвигаемые внутренние покупки.
Обязательно ли appAccountToken должен быть UUID?
Да. Единственное правило форматирования, которое требует Apple, это чтобы значение было корректным UUID. В остальном он непрозрачен, поэтому может ссылаться на любой ключ учётной записи, который вам нравится, на вашей стороне, но если это не UUID, StoreKit не сохранит его, и транзакция вернётся с appAccountToken равным nil.
Как appAccountToken помогает с возвратами?
Когда клиент запрашивает возврат, Apple присылает CONSUMPTION_REQUEST и даёт вам двенадцать часов, чтобы ответить данными об этом конкретном покупателе. appAccountToken это то, как вы сопоставляете транзакцию уведомления с учётной записью, чьё использование вам нужно сообщить, и это одно из полей в самом запросе о потреблении. Без него вы не сможете ответить реальным использованием, поэтому Apple склоняется к одобрению возвратов, которые у вас были доказательства оспорить.
Должен ли appAccountToken быть разным для каждой покупки?
Нет. Используйте один стабильный UUID на учётную запись и повторно используйте его для каждой покупки, которую делает этот пользователь. Apple переносит токен через продления и апгрейды в цепочке подписки, поэтому стабильный токен даёт вам чистую связь во времени. Токен, который меняется от покупки к покупке, рвёт эту связь и усложняет привязку транзакций к одному и тому же клиенту.

Источники и дополнительное чтение

RefundHalt

Автопилот возвратов для App Store и Google Play

Читайте дальше

Deep dive8 мин чтения

Не подтвердите покупку в Google Play в течение трёх дней, и Google её вернёт, вот во что это вам обходится

Google Play автоматически возвращает деньги и отзывает любую покупку, которую ваш сервер не подтвердил в течение трёх дней. Это сбой интеграции, а не решение клиента, и его полностью можно предотвратить. Вот точное правило, почему оно срабатывает и во что на самом деле обходится каждая потерянная продажа.

Playbook8 мин чтения

Серийные злоупотребления возвратами обходятся вам дважды, вот как магазины позволяют дать отпор

Клиент, который снова и снова оформляет возврат, это не случайность. Злоупотребление возвратами стоит вам возвращённых денег плюс тех вычислений, которые вы уже потратили, и оба магазина дают вам сигнал идентичности, appAccountToken от Apple и обфусцированный идентификатор аккаунта от Google, чтобы связать этот паттерн воедино.

Следующий запрос на возврат уже в пути.

Настройте RefundHalt за то время, что уходит на чтение очередного письма в поддержку о возврате, который вы не успели оспорить.