iOS内购必须通过原生插件(如uni-app-iap)桥接StoreKit,uni.pay和uni.requestPayment对IAP无效;商品查询、购买、验签等全流程需原生支持,服务端必须向Apple接口验证receipt,沙盒与正式环境验签地址及逻辑须严格区分。

iOS内购必须用原生模块,uni-app不能直接调用StoreKit
uni-app 的 uni.pay 只支持微信/支付宝,对 iOS 应用内购买(IAP)完全不生效。想做 App 端内购,必须通过原生插件桥接,否则打包后点击购买会静默失败或报错 undefined is not an object (evaluating 'uni.requestPayment')。
常见错误现象:在 HBuilderX 里点“运行到手机”后调用 uni.requestPayment,控制台没报错但什么也不弹——因为该 API 在 iOS 真机上对 IAP 返回空实现,连回调都不会触发。
- 所有商品查询、购买、验证、恢复购买,都得走原生层,JS 层只负责发指令和收结果
- 不能依赖
uni.getProvider检测 IAP 支持,它返回的appleiap是假的,实际不可用 - HBuilderX 自带的“苹果应用内支付”插件(
uni-pay)已多年未更新,iOS 16+ 上大概率闪退,别碰
必须自己封装或选用可靠原生插件,推荐 DCloud 官方 uni-app-iap
DCloud 社区维护的 uni-app-iap 插件(非官方但被广泛验证)目前是少数能在 iOS 17 下稳定工作的方案。它把 StoreKit 封装成 JS 可调用的 Promise 接口,关键能力都覆盖了。
使用场景:你需要展示商品列表、发起购买、监听交易完成、处理续订/降级、恢复已购项目——这些都不能靠 JS 模拟,必须和原生 transaction 状态严格同步。
- 安装方式:通过 HBuilderX 的“原生插件管理”添加,插件 ID 是
uni-app-iap,不是 npm install - 初始化必须在
onLaunch里调用iap.init(),否则后续所有方法都返回not initialized - 商品 ID 必须和 App Store Connect 后台完全一致,大小写、下划线都不能错,否则
iap.getProducts返回空数组 - 调试时开启 Xcode 控制台,看是否出现
SKErrorDomain Code=0(沙盒环境未配置)或SKErrorDomain Code=2(用户未登录测试账号)
服务端验签不能省,客户端验签等于没验
iOS 返回的 receipt data 是 base64 字符串,但直接在前端解码校验毫无意义——攻击者可以伪造任意 receipt 并绕过。真正有效的验签必须由你自己的服务器向 Apple 的 https://buy.itunes.apple.com/verifyReceipt 或沙盒地址发起 POST 请求。
参数差异:沙盒验签用 https://sandbox.itunes.apple.com/verifyReceipt,上线后要切回正式地址;两者返回结构一致,但沙盒环境不接受正式 receipt,反之亦然。
- receipt data 必须原样透传,不要 base64 decode 再 encode,不要 trim 空格,否则 Apple 返回
status: 21002 - 服务器响应必须包含
status: 0和有效latest_receipt_info,前端才应解锁功能;仅检查 status 不为 0 不够,得确认对应 product_id 和 expires_date_ms(订阅类) - Apple 的验签接口可能返回 503,需加重试逻辑;单次请求超时建议设为 10s,避免前端卡死
订阅类产品必须处理续订、降级、跨档迁移,别只写一次购买逻辑
用户买的是“订阅”,不是“一次付款”。如果只按普通商品处理,用户续订失败、手动取消、换档位(比如月付切年付)时,你的服务端和客户端状态就会彻底脱节。
性能影响:每次打开 App 都该调用 iap.refreshReceipt() + 服务端验签,但别在首页 onLoad 里同步阻塞执行——应该用 background fetch 或静默触发,避免白屏等待。
- 用户在 App Store 手动取消订阅后,Apple 不会主动通知你;你只能靠下次验签时发现
latest_receipt_info中的expires_date_ms已过期 - 降级(如年付→月付)会在当前周期结束前生效,
pending_renewal_info字段会体现新计划,但旧订单仍有效直到到期 - 客户端恢复购买(restore)不是“重新扣款”,而是拉取历史 receipt 并交服务端验签,必须和正常购买走同一套验签流程
最易被忽略的是沙盒测试账号的生命周期:每个测试账号有 3 次订阅试用机会,用完就得换号,否则所有 purchase 请求返回 SKErrorDomain Code=2 却不提示原因。

















