Реализация серверной верификации покупок (Receipt Validation)

TRUETECH занимается разработкой, поддержкой и обслуживанием мобильных приложений iOS, Android, PWA. Имеем большой опыт и экспертизу для публикации мобильных приложений в популярные маркеты Google Play, App Store, Amazon, AppGallery и другие.
Разработка и поддержка любых видов мобильных приложений:
Информационные и развлекательные мобильные приложения
Новостные приложения, игры, справочники, онлайн-каталоги, погодные, фитнес и здоровье, туристические, образовательные, социальные сети и мессенджеры, квиз, блоги и подкасты, форумы, агрегаторы
Мобильные приложения электронной коммерции
Интернет-магазины, B2B-приложения, маркетплейсы, онлайн-обменники, кэшбэк-сервисы, биржи, дропшиппинг-платформы, программы лояльности, доставка еды и товаров, платежные системы
Мобильные приложения для управления бизнес-процессами
CRM-системы, ERP-системы, управление проектами, инструменты для команды продаж, учет финансов, управление производством, логистика и доставка, управление персоналом, системы мониторинга данных
Мобильные приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, платформы предоставления электронных услуг, платформы кешбека, видеохостинги, тематические порталы, платформы онлайн-бронирования и записи, платформы онлайн-торговли

Это лишь некоторые из типы мобильных приложений, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента.

Предлагаемые услуги
Показано 1 из 1 услугВсе 1735 услуг
Реализация серверной верификации покупок (Receipt Validation)
Сложная
~2-3 рабочих дня
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_mobile-applications_feedme_467_0.webp
    Разработка мобильного приложения для компании FEEDME
    756
  • image_mobile-applications_xoomer_471_0.webp
    Разработка мобильного приложения для компании XOOMER
    624
  • image_mobile-applications_rhl_428_0.webp
    Разработка мобильного приложения для компании RHL
    1052
  • image_mobile-applications_zippy_411_0.webp
    Разработка мобильного приложения для компании ZIPPY
    947
  • image_mobile-applications_affhome_429_0.webp
    Разработка мобильного приложения для компании Affhome
    862
  • image_mobile-applications_flavors_409_0.webp
    Разработка мобильного приложения для компании FLAVORS
    445

Реализация серверной верификации покупок (Receipt Validation)

Клиент прислал баг-репорт: пользователь купил Premium, получил токен транзакции, потом восстановил приложение с резервной копии на другом устройстве — и Premium снова активен без повторной оплаты. Классика. Причина — валидация только на клиенте: приложение проверяет локальный receipt или trust-флаг от StoreKit, не сверяясь с сервером.

Почему клиентская валидация — это не валидация

На iOS StoreKit 2 возвращает Transaction с подписью Apple. Можно верифицировать подпись локально через Transaction.verificationResult, но это не защищает от replay-атак: злоумышленник перехватывает валидный receipt одного пользователя и подставляет его в другой аккаунт. На Android ситуация аналогична — BillingClient.queryPurchasesAsync() возвращает Purchase объекты, которые клиент не должен трактовать как подтверждение без серверной проверки purchaseToken.

Самая частая схема мошенничества — «receipt sharing»: один receipt распространяется между пользователями через форумы. Без серверной базы, фиксирующей какой originalTransactionId (iOS) или orderId (Android) уже использован, это не поймать.

Как устроена нормальная серверная верификация

Сторона iOS (App Store Server API). Старый подход — POST на https://buy.itunes.apple.com/verifyReceipt с base64-encoded receipt-data — устарел. Apple продвигает App Store Server API v1: клиент передаёт серверу transactionId из Transaction.id (StoreKit 2), сервер делает GET /inApps/v1/history/{transactionId} с JWT-токеном (подписанным ES256 ключом из App Store Connect). Ответ — JWSTransaction, который нужно декодировать и верифицировать подпись через Apple Root CA.

Параллельно нужно подписаться на App Store Server Notifications V2: Apple пушит события (DID_RENEW, EXPIRED, REFUND, GRACE_PERIOD_EXPIRED) на ваш endpoint. Без этого статус подписки на сервере устаревает — пользователь отменил подписку, а у вас он ещё числится Premium.

Сторона Android (Google Play Developer API). Для разовых покупок — purchases.products.get с packageName, productId, purchaseToken. Для подписок — purchases.subscriptions.v2.get. Авторизация через Service Account с ролью Financial data viewer — это минимально необходимые права, не давайте Editor на весь проект. Ответ содержит purchaseState (0 = Purchased, 1 = Canceled, 2 = Pending) и acknowledgementState — если 0, нужно вызвать purchases.products.acknowledge, иначе Google вернёт деньги автоматически через 3 дня.

Идемпотентность и защита от replay. В базе храним таблицу purchase_receipts с уникальным индексом по original_transaction_id + product_id. При каждом запросе на верификацию сначала проверяем наличие записи — если уже верифицировали с другим user_id, отвечаем ошибкой. Это и есть защита от receipt sharing.

purchase_receipts
  id                    uuid PK
  user_id               uuid FK
  platform              enum('ios','android')
  original_transaction_id varchar UNIQUE (per product)
  product_id            varchar
  purchase_state        smallint
  expires_at            timestamptz  -- для подписок
  raw_payload           jsonb        -- оригинальный ответ от Apple/Google
  verified_at           timestamptz

Стек и интеграция

Серверная часть чаще всего Node.js (библиотека app-store-server-api) или Python (google-auth + googleapiclient). Для Node удобен пакет node-apple-receipt-verify для легаси-endpoint, но лучше сразу брать app-store-server-api от Apple — поддерживает JWT-авторизацию и верификацию JWS из коробки.

На стороне клиента iOS — минимум кода: получить Transaction.id из Transaction.all или из updates потока, отправить на бэкенд. Не передавайте весь appStoreReceiptURL — это легаси, и файл может быть невалидным на симуляторе.

На Android клиент передаёт purchaseToken и productId из Purchase.purchaseToken. Важно: токен может быть одним для нескольких productId при апгрейде подписки — учитывайте это в логике.

Процесс работы

Начинаем с аудита текущей схемы валидации — где именно проверяется receipt, есть ли серверная база покупок, обрабатываются ли Server Notifications. Дальше проектируем схему БД и API-эндпоинты, реализуем верификацию для каждой платформы, настраиваем webhook-обработчик для Server Notifications, покрываем тестами с моковыми ответами от Apple/Google. Отдельный этап — нагрузочное тестирование эндпоинта верификации, потому что при пиковых запусках (акция, фича в топе App Store) он получает всё разом.

Сроки — от 2 до 5 дней, зависит от наличия серверной инфраструктуры и количества типов покупок (разовые, подписки, consumable, non-consumable). Если сервер уже есть и нужно только добавить верификацию — ближе к 2 дням. Полная архитектура с нуля плюс миграция существующих пользователей — до 5.