Table of Contents
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 | success 与 duplicate 结果总数,两者均按支付成功处理 |
failed_count |
integer | 单订单确认失败数量;失败不回滚同一小票中其他已成功订单 |
results |
array | 每张匹配订单的独立处理结果 |
results[].status |
string | success=首次确认成功,duplicate=此前已幂等处理,failed=该订单确认失败 |
results[].order_no |
string | 平台订单号;重复交易已由服务端通知处理时可能指向真实承载交易的续订订单 |
results[].transaction_no |
string | 平台支付流水号;failed 时可能不返回 |
results[].business_type |
string | subscription、auto_subscription 或 candy_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 | 实际验票环境:production、sandbox;尚未获得有效 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,把success和duplicate都按成功处理,并仅按失败项的retryable决定是否重交同一小票。 - 同一个订阅 token 可能包含历史首购及多次续订交易;小票确认只检查首购交易,并按
purchase_date_ms从新到旧选择。已经存在于其他订单支付流水中的首购交易会作为历史交易跳过,不会再次入账;只有当前订单已有交易或尚未入账的首购交易才会返回幂等或进入确认。续订交易继续由 Apple 服务端通知处理。 - 当某个 token 仅包含已处理历史交易或续订交易时,该 token 返回
status=failed、error_code=apple_transaction_not_current、retryable=false,不会影响同一小票中其他 token 的独立处理。 - Apple 自动订阅链首次验票时绑定当前 Tieup 用户和艺人;同一用户换设备或恢复购买允许继续,其他 Tieup 账号或其他艺人提交相同订阅链时返回
422,订单保持未支付且不发放权益。 - Apple 自动续订服务端通知可能先于 APP 小票确认处理交易。当前用户和艺人与订阅链归属一致时,相应
results[].status=duplicate,服务端不会重复续期、发送消息或记账。 - 自动续费展示只使用服务端返回的
status、is_auto_renew和expires_at:有效订阅且is_auto_renew=2时显示“自动续费已关闭,到期后失效”;status=2时显示“已到期”。通知 type/subtype 仅用于诊断。 - 小票中属于其他 Tieup 用户的 token 会被忽略并记录脱敏审计,不返回其订单信息;如果没有任何当前登录用户订单匹配则返回
422。 - 小票校验成功后更新
tieup_order、tieup_payment_transaction。business_type=subscription/auto_subscription时发放或续期tieup_subscription;business_type=candy_recharge时锁定用户余额并增加糖果,写入tieup_user_transaction,重复提交同一 Apple 交易不会重复入账。