# 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:** ```json { "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):** ```json { "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:** ```json { "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`: ```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`.