Files
eryao/docs/protocols/payments/apple-iap-protocol.md
T
ZL-Q 86062d5e78 fix: 修复 packages 接口访问不存在字段导致的运行时错误
- 移除 router 中对 result.region、result.currency、pkg.price 的访问
- 修正 pkg.type.value 为 pkg.type (type 是 Literal 不是 Enum)
- 更新协议文档以反映实际实现
- 新增 Apple IAP 协议文档
- 标记未使用的错误码为 RESERVED
2026-04-28 17:31:24 +08:00

4.6 KiB

Apple IAP Protocol (Frontend <-> Backend)

This document defines the Apple In-App Purchase verification and grant contract for Eryao Flutter app.

Protocol verification status:

  • Backend route source: backend/src/v1/payments/router.py
  • Backend service source: backend/src/v1/payments/service.py
  • Backend verifier source: backend/src/v1/payments/apple_verifier.py
  • Backend schema source: backend/src/v1/payments/schemas.py
  • Current status: aligned

Compatibility strategy

  • Current strategy: additive evolution (backward-compatible).
  • Breaking change requires explicit migration + rollback notes (requires-migration).

Route overview

  • Verify transaction: POST /api/v1/payments/apple/transactions/verify
  • Apple server notification: POST /api/v1/payments/apple/notifications (server-to-server)

Verify transaction

POST /api/v1/payments/apple/transactions/verify

Verify and grant credits for an Apple IAP transaction.

Authorization: Requires authenticated session. User identity from JWT sub.

Request:

{
  "productCode": "new_user_pack",
  "appStoreProductId": "com.meeyao.qianwen.new_user_pack",
  "transactionId": "2000000123456789",
  "signedTransactionInfo": "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsIng1YyI6WyJNSUlFTURDQ0E3YWdBd0lCQWdJUWFQeF...",
  "appAccountToken": "uuid-or-null"
}

Fields:

  • productCode (required): string, max 32 chars, product code from packages API
  • appStoreProductId (required): string, max 128 chars, Apple product ID
  • transactionId (required): string, max 64 chars, Apple transaction ID
  • signedTransactionInfo (required): string, JWS signed transaction info from Apple
  • appAccountToken (optional): UUID, app account token for user association

Response (200):

{
  "status": "granted",
  "productCode": "new_user_pack",
  "transactionId": "2000000123456789",
  "creditsAdded": 60,
  "newBalance": 160,
  "ledgerEventId": "payment.apple_iap:2000000123456789"
}

Status values:

  • granted: Transaction verified and credits added
  • already_granted: Transaction already processed for this user

Error codes:

code status meaning
PAYMENT_PRODUCT_NOT_FOUND 404 productCode does not exist or is not enabled
PAYMENT_PRODUCT_MISMATCH 422 Client product ID does not match backend/Apple verification result
PAYMENT_ENVIRONMENT_MISMATCH 422 Transaction environment (Sandbox/Production) does not match server environment
PAYMENT_TRANSACTION_INVALID 422 Apple signed transaction invalid, signature verification failed, or payload malformed
PAYMENT_TRANSACTION_REVOKED 409 Transaction has been revoked or refunded, grant not allowed
PAYMENT_TRANSACTION_CONFLICT 409 Transaction already processed by another user or in conflicting state
PAYMENT_STARTER_PACK_INELIGIBLE 409 Current email identity has already purchased starter pack

Apple server notification

POST /api/v1/payments/apple/notifications

Server-to-server notification from Apple for refund/revoke events.

Authorization: None (Apple server origin).

Request:

{
  "signedPayload": "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsIng1YyI6WyJNSUlFTURDQ0E3YWdBd0lCQWdJUWFQeF..."
}

Response (200): Empty success response.

Behavior:

  • Parses notification type and transaction info
  • For REFUND, REVOKE, DID_FAIL_TO_RENEW notifications, processes refund clawback
  • Refund reduces user balance by original purchase amount (or remaining balance if insufficient)

Product mapping

Products are configured in backend/src/core/config/static/packages/mapping.yaml:

product_mappings:
  new_user_pack:
    app_store_product_id: com.meeyao.qianwen.new_user_pack
    credits: 60
    type: starter
    sort_order: 0
    enabled: true

Starter pack eligibility

  • Starter pack (type: starter) can only be purchased once per email identity
  • Backend tracks purchase via register_bonus_claims.has_purchased_starter_pack
  • If already purchased, returns PAYMENT_STARTER_PACK_INELIGIBLE (409)

Ledger integration

  • Successful purchases create a ledger entry with:

    • change_type: purchase
    • biz_type: payment
    • biz_id: apple_iap_transactions.id
    • metadata.ext containing Apple IAP details
  • Refunds create a ledger entry with:

    • change_type: refund
    • biz_type: payment
    • metadata.ext.original_event_id referencing original purchase event

Error contract linkage

  • All errors use RFC7807 with extension code and optional params.
  • Error code registry source: docs/protocols/common/http-error-codes.md.