10.5 Apple IAP 小票确认【未授权拦截通过,待登录态联调】

POST /tieup/api/v1/payment-orders/apple-iap-receipts

请求体:

{
  "receipt_data": "base64_receipt"
}

请求参数:

参数 类型 必填 说明
receipt_data string 客户端购买成功后提交的完整 base64 App Receipt;客户端不提交订单号、交易号、商品 ID 或用户标识

响应字段:

字段 类型 说明
ack boolean 小票已通过至少一个启用 Apple 支付账号验证,并匹配到当前用户订单时为 true
matched_count integer 当前小票匹配到的当前 Tieup 用户订单数
success_count integer successduplicate 结果总数,两者均按支付成功处理
failed_count integer 单订单确认失败数量;失败不回滚同一小票中其他已成功订单
results array 每张匹配订单的独立处理结果
results[].status string success=首次确认成功,duplicate=此前已幂等处理,failed=该订单确认失败
results[].order_no string 平台订单号;重复交易已由服务端通知处理时可能指向真实承载交易的续订订单
results[].transaction_no string 平台支付流水号;failed 时可能不返回
results[].business_type string subscriptionauto_subscriptioncandy_recharge
results[].error_code string 失败稳定错误码,仅 failed 返回
results[].message string 失败原因,仅 failed 返回
results[].retryable boolean 是否建议稍后重交同一完整小票;不得据此重新拉起购买
results[].delivery object 业务发放结果;订阅为权益发放结果,糖果充值为糖果到账结果
results[].delivery.delivered boolean 是否已完成权益或糖果到账
results[].delivery.duplicate boolean 发放动作是否为幂等重复;首次订阅发放可能不返回该字段
results[].delivery.reason string 未发放原因,仅未发放时返回
results[].delivery.subscription_no string 订阅发放对应订阅编号,仅订阅业务返回
results[].delivery.expires_at string 订阅到期时间,仅订阅业务返回
results[].delivery.im_sync object 订阅生效后的 IM 成员同步结果,仅首次实际发放返回
results[].delivery.im_sync.artist_id integer 同步目标艺人 ID
results[].delivery.im_sync.synced_count integer 成功同步成员数
results[].delivery.im_sync.failed_count integer 失败同步成员数
results[].delivery.subscription_message object 订阅成功消息登记与异步发送结果,仅首次实际发放返回
results[].delivery.subscription_message.queued boolean 是否已投递异步发送任务
results[].delivery.subscription_message.message_no string/null 已登记的消息编号
results[].delivery.subscription_message.registration_error string/null 消息登记失败原因
results[].delivery.income object 订阅收入入账结果,仅首次实际发放返回
results[].delivery.income.duplicate boolean 收入是否为幂等复用
results[].delivery.income.income_no string 收入记录编号
results[].delivery.income.settlement_base_amount string 艺人分成结算基数,单位元,固定两位小数
results[].delivery.income.artist_income_amount string 艺人收入金额,单位元,固定两位小数
results[].delivery.wallet_transaction_no string 糖果充值钱包流水号,仅糖果充值返回
results[].delivery.user_id integer 糖果到账用户 ID,仅糖果充值返回
results[].delivery.change_type integer 糖果变动类型,仅糖果充值返回
results[].delivery.change_amount string 糖果到账数量,仅糖果充值返回
results[].delivery.balance_before string 到账前糖果余额,仅糖果充值返回
results[].delivery.balance_after string 到账后糖果余额,仅糖果充值返回
results[].delivery.business_type string 糖果流水业务类型,仅糖果充值返回
results[].delivery.business_id integer/null 糖果流水业务主键,仅糖果充值返回
results[].delivery.order_no string/null 糖果流水关联订单号,仅糖果充值返回
results[].delivery.recharge_no string/null 糖果流水关联充值业务单号,仅糖果充值返回
results[].subscription object/null 当前用户对本订单艺人的有效订阅;糖果充值或当前无有效订阅时为 null
results[].subscription.subscription_no string 当前有效订阅编号
results[].subscription.fan_user_id integer 当前粉丝用户 ID
results[].subscription.artist_id integer 艺人 ID
results[].subscription.artist_user_id integer 艺人绑定 APP 用户 ID
results[].subscription.artist_name string 艺人名称
results[].subscription.artist_avatar string 艺人头像,严格读取独立维护的 tieup_artist.avatar;未配置时返回空字符串,不回退绑定账号的用户头像
results[].subscription.order_no string 当前有效订阅对应平台订单号
results[].subscription.recharge_no string/null 当前有效订阅对应业务单号
results[].subscription.subscription_plan_code string 订阅档位编码
results[].subscription.plan_name_snapshot string 订阅档位名称快照
results[].subscription.sub_type string 订阅周期:month/year
results[].subscription.subscription_action string 订阅动作:initial/renewal/legacy
results[].subscription.pay_amount string 本周期支付金额,单位元,固定两位小数
results[].subscription.duration_days integer 本周期权益天数
results[].subscription.started_at string 本周期开始时间
results[].subscription.expires_at string 当前有效周期到期时间
results[].subscription.is_auto_renew integer 业务自动续费状态:1=是,2=否
results[].subscription.status integer 订阅展示状态:1=生效中,2=已过期,3=已取消,4=即将过期
results[].subscription.cancelled_at string/null 取消时间
results[].subscription.original_transaction_id string/null Apple 自动订阅链原始交易 ID
results[].subscription.latest_transaction_id string/null 最近 Apple 交易 ID
results[].subscription.latest_apple_notification_type string/null 最近 Apple 通知类型
results[].subscription.latest_apple_subtype string/null 最近 Apple 通知 subtype
results[].subscription.auto_renew_status integer/null Apple 原始自动续费状态:0=关闭,1=开启,null=未知

其中 results[].error_code=apple_transaction_not_current 表示该 token 的小票仅包含已处理历史首购交易或续订交易,服务端不会再次入账,客户端不应因此重新发起购买。

响应示例:

{
  "code": 200,
  "message": "成功",
  "data": {
    "ack": true,
    "matched_count": 2,
    "success_count": 1,
    "failed_count": 1,
    "results": [
      {
        "status": "success",
        "order_no": "TO202608120001",
        "transaction_no": "TP202608120001",
        "business_type": "subscription",
        "delivery": {
          "delivered": true,
          "subscription_no": "SUB202608120001",
          "expires_at": "2026-09-12 10:01:00"
        },
        "subscription": {
          "subscription_no": "SUB202608120001",
          "fan_user_id": 5,
          "artist_id": 1,
          "artist_user_id": 10001,
          "artist_name": "示例艺人",
          "artist_avatar": "https://cdn.example.com/artist.png",
          "order_no": "TO202608120001",
          "recharge_no": "RC202608120001",
          "subscription_plan_code": "month_30",
          "plan_name_snapshot": "月度订阅",
          "sub_type": "month",
          "subscription_action": "initial",
          "pay_amount": "30.00",
          "duration_days": 30,
          "started_at": "2026-08-12 10:01:00",
          "expires_at": "2026-09-12 10:01:00",
          "is_auto_renew": 1,
          "status": 1,
          "cancelled_at": null,
          "original_transaction_id": "2000001208186444",
          "latest_transaction_id": "2000001208186444",
          "latest_apple_notification_type": null,
          "latest_apple_subtype": null,
          "auto_renew_status": null
        }
      },
      {
        "status": "failed",
        "order_no": "TO202608120002",
        "business_type": "candy_recharge",
        "error_code": "apple_product_mismatch",
        "message": "Apple小票商品与订单商品不一致",
        "retryable": false
      }
    ]
  }
}

Apple 小票验证失败时返回 422,完整示例:

{
  "code": 422,
  "message": "Apple小票验证失败:共享密钥与账号不匹配",
  "data": {
    "failure_stage": "apple_response",
    "apple_status": 21004,
    "reason": "共享密钥与账号不匹配",
    "retryable": false,
    "environment": "production"
  }
}

失败响应字段:

字段 类型 说明
code integer 固定为 422
message string Apple小票验证失败:{具体原因}
data.failure_stage string 失败阶段:apple_response=Apple 返回非成功状态,bundle_match=Bundle ID 不匹配,transaction_match=未找到交易,receipt_item_validation=商品等交易字段校验失败,transaction_state=交易已撤销,http_request=请求 Apple 失败,response_decode=Apple 响应格式异常,payload_decode/payload_type=载荷错误,gateway_exception=其他安全降级错误
data.apple_status integer/null Apple verifyReceipt 状态码;本地校验、网络或响应格式异常时为 null
data.reason string 服务端白名单映射后的具体原因,不包含小票、密钥或第三方原始响应
data.retryable boolean 是否建议稍后使用同一订单、同一 Apple 交易重新提交;true 不代表客户端可以重复发起购买
data.environment string/null 实际验票环境:productionsandbox;尚未获得有效 Apple 响应时可能为 null

常见 Apple 状态:

状态码 原因 是否建议重试
21002 小票数据格式错误或缺失 否,应重新获取有效 App Receipt
21003 小票无法通过认证
21004 共享密钥与账号不匹配 否,由服务端检查支付账号配置
21005 Apple 小票验证服务暂时不可用 是,稍后重交同一交易
21006 订阅已过期,但小票有效
21100-21199 Apple 小票服务内部错误 是,稍后重交同一交易

说明:

  • 客户端使用 StoreKit 1 / SwiftyStoreKit 购买;创建订单返回的 pay_params.application_username 是每张平台订单唯一的 UUID,必须原样传入 StoreKit,不能跨订单复用。
  • Apple 会把 UUID applicationUsername 作为交易条目的 app_account_token 返回。服务端只信任验票结果中的 token,并通过它定位订单和用户;响应不会回传 token。
  • 服务端调用 Apple verifyReceipt 校验小票;生产环境返回 21007 时自动切到沙盒,沙盒返回 21008 时自动切到生产。
  • 验票失败时 APP 应展示 message,并依据 data.retryable 决定是否允许稍后重交同一订单和同一 Apple 交易;不得因 retryable=true 再次拉起购买或生成新订单。
  • 失败响应仅提供稳定诊断字段。receipt_data、共享密钥、异常堆栈、Apple 请求地址和完整响应不会返回给 APP。
  • 客户端只提交 receipt_data。Bundle ID、环境、交易号、原始交易号、商品 ID 和 token 均读取 Apple 验票结果;商品必须匹配订单创建时的服务端快照。
  • 完整 App Receipt 可能包含多张订单;服务端按 token 逐单独立事务处理。只要至少匹配一张当前用户订单就返回 200,客户端必须遍历 results,把 successduplicate 都按成功处理,并仅按失败项的 retryable 决定是否重交同一小票。
  • 同一个订阅 token 可能包含历史首购及多次续订交易;小票确认只检查首购交易,并按 purchase_date_ms 从新到旧选择。已经存在于其他订单支付流水中的首购交易会作为历史交易跳过,不会再次入账;只有当前订单已有交易或尚未入账的首购交易才会返回幂等或进入确认。续订交易继续由 Apple 服务端通知处理。
  • 当某个 token 仅包含已处理历史交易或续订交易时,该 token 返回 status=failederror_code=apple_transaction_not_currentretryable=false,不会影响同一小票中其他 token 的独立处理。
  • Apple 自动订阅链首次验票时绑定当前 Tieup 用户和艺人;同一用户换设备或恢复购买允许继续,其他 Tieup 账号或其他艺人提交相同订阅链时返回 422,订单保持未支付且不发放权益。
  • Apple 自动续订服务端通知可能先于 APP 小票确认处理交易。当前用户和艺人与订阅链归属一致时,相应 results[].status=duplicate,服务端不会重复续期、发送消息或记账。
  • 自动续费展示只使用服务端返回的 statusis_auto_renewexpires_at:有效订阅且 is_auto_renew=2 时显示“自动续费已关闭,到期后失效”;status=2 时显示“已到期”。通知 type/subtype 仅用于诊断。
  • 小票中属于其他 Tieup 用户的 token 会被忽略并记录脱敏审计,不返回其订单信息;如果没有任何当前登录用户订单匹配则返回 422
  • 小票校验成功后更新 tieup_ordertieup_payment_transactionbusiness_type=subscription/auto_subscription 时发放或续期 tieup_subscriptionbusiness_type=candy_recharge 时锁定用户余额并增加糖果,写入 tieup_user_transaction,重复提交同一 Apple 交易不会重复入账。