
045 — IAP Kit 应用内支付会员订阅全流程实现简介付费会员是记账应用实现商业闭环的重要途径。MoneyTrack 通过 IAP Kit 实现了完整的应用内购买功能支持自动续费订阅AUTORENEWABLE和非续费订阅NONRENEWABLE两种商品类型。从商品信息配置、购买流程发起、收银台拉起、收据验签到会员状态管理形成了一条完整的支付链路。MemberSubscriptionPage 作为订阅入口页面通过 ProductInfo 配置商品列表结合 JWSUtil 对支付平台返回的 JWS 收据进行解码验证。完整购买流程IAP Kit 的完整购买流程分为五个关键步骤自有服务端应用市场IAP KitMoneyTrack用户自有服务端应用市场IAP KitMoneyTrack用户进入会员订阅页1. queryProductInfo() 查询商品返回 ProductInfo 列表展示商品月度/年度选择商品点击购买2. createPurchase() 创建订单返回 PurchaseResult3. purchase() 拉起收银台调用系统支付能力支付完成返回收据返回 JWS 收据4. JWSUtil 解码验证5. 发送收据到服务端验签验证签名 解析 payload返回激活结果提示会员已激活商品配置ProductInfo 完整定义商品信息通过iap.ProductInfo对象定义在查询商品时由系统返回import{iap}fromkit.IAPKit;// 查询商品信息asyncfunctionqueryProducts():Promiseiap.ProductInfo[]{constproductIds[moneytrack_monthly,moneytrack_yearly];constresultawaitiap.queryProductInfo({productIds:productIds,type:iap.ProductType.AUTORENEWABLE});returnresult.productInfoList;}// ProductInfo 包含以下完整字段interfaceProductInfo{productId:string;// 商品唯一 ID在 AGC 控制台配置type:iap.ProductType;// 商品类型title:string;// 商品名称本地化description:string;// 商品描述price:string;// 显示价格如 ¥12.00microPrice:number;// 微单位价格如 1200单位分currency:string;// 货币代码如 CNYstatus:number;// 商品状态0正常1下架subscriptionPeriod:string;// 订阅周期如 P1M 表示每月freeTrialPeriod:string;// 免费试用期如 P7D 表示 7 天}订阅状态管理会员订阅不是一次性操作需要持续管理会员状态、检测过期、处理续费import{iap}fromkit.IAPKit;exportclassMembershipManager{// 检测当前会员状态staticasynccheckMembershipStatus():PromiseMembershipStatus{try{constreceiptsawaitiap.getPurchaseRecords({type:iap.ProductType.AUTORENEWABLE});if(receipts.length0){return{isActive:false,level:none,expireDate:null};}// 取最新的收据constlatestReceiptreceipts[0];constdecodedJWSUtil.decode(latestReceipt.receipt);constpayloaddecoded.payload;constexpireDatenewDate(payload.expirationDate);constnownewDate();return{isActive:expireDatenow,level:this.getProductLevel(payload.productId),expireDate:expireDate,autoRenew:payload.autoRenewStatus1,daysRemaining:Math.ceil((expireDate.getTime()-now.getTime())/86400000)};}catch(error){console.error(Check membership failed:,error);return{isActive:false,level:none,expireDate:null};}}// 处理续费提醒staticasynchandleRenewReminder(){conststatusawaitthis.checkMembershipStatus();if(status.isActivestatus.daysRemaining7status.daysRemaining0){// 会员即将过期推送续费提醒this.showRenewPrompt(status.daysRemaining);}elseif(!status.isActivestatus.level!none){// 会员已过期引导重新订阅this.showExpiredPrompt();}}// 恢复购买跨设备同步staticasyncrestorePurchases(){constreceiptsawaitiap.getPurchaseRecords({type:iap.ProductType.AUTORENEWABLE});// 将收据列表发送到服务端验证并激活for(constreceiptofreceipts){awaitthis.verifyOnServer(receipt.receipt);}}}支付安全JWS 解码验签与服务端验证支付安全是应用内购买的核心。IAP Kit 返回的收据采用 JWSJSON Web Signature格式需要在服务端完成签名验证后才能激活会员权益// 客户端解析 JWS 收据exportclassJWSUtil{// JWS 格式为header.payload.signaturestaticdecode(jwsString:string):DecodedJWS{constpartsjwsString.split(.);if(parts.length!3){thrownewError(Invalid JWS format);}return{header:JSON.parse(this.base64UrlDecode(parts[0])),payload:JSON.parse(this.base64UrlDecode(parts[1])),signature:parts[2],// 客户端仅解析验证由服务端完成verify():boolean{returnfalse;// 客户端不做签名验证}};}privatestaticbase64UrlDecode(str:string):string{// Base64 URL 解码实现constbase64str.replace(/-/g,).replace(/_/g,/);returndecodeURIComponent(Array.from(atob(base64),c%(00c.charCodeAt(0).toString(16)).slice(-2)).join());}}// 服务端验证逻辑Node.js 示例/* async function verifyReceipt(receipt: string) { // 1. 获取 IAP Kit 公钥从华为 IAP 服务端获取 const publicKey await getIAPPublicKey(); // 2. 使用 JWS 库验证签名 const jws jose.generalVerify(receipt, publicKey); // 3. 验证 payload 中的关键字段 const payload jws.payload; if (payload.productId ! expectedProductId) throw new Error(Product mismatch); if (payload.purchaseTime 3600000 Date.now()) throw new Error(Receipt expired); // 4. 检查 purchaseToken 是否已被使用防止重放攻击 if (await isTokenUsed(payload.purchaseToken)) throw new Error(Token already used); // 5. 标记 token 已使用并激活会员 await markTokenUsed(payload.purchaseToken); await activateMembership(payload); } */安全要点说明客户端可以解析 JWS 查看收据内容但绝不能仅凭客户端解析结果就激活会员。签名验证必须在自有服务端完成以防止伪造收据。服务端还需校验purchaseToken的唯一性防止重放攻击。最佳实践服务端强制验签所有会员激活逻辑必须在服务端完成客户端仅做展示和收据透传。支持恢复购买提供恢复购买按钮通过getPurchaseRecords获取历史收据确保用户在不同设备间已购权益同步。处理收据重复回调purchase操作可能因网络原因收到重复回调需通过purchaseToken去重。订阅状态本地缓存定期检测会员状态并缓存到本地避免每次打开应用都调用网络接口。提前 7 天续费提醒在会员到期前 7 天开始提示续费降低流失率。测试商品与正式商品隔离开发阶段使用 AGC 控制台配置的测试商品价格为零上架前切换为正式商品。项目代码案例文件路径feature_membership/ProductInfo.etsimport{iap}fromkit.IAPKit;exportconstPRODUCTS:iap.ProductInfo[][{productId:moneytrack_monthly,type:iap.ProductType.AUTORENEWABLE,title:月度会员,description:解锁所有高级功能按月订阅,price:¥12.00,microPrice:1200,currency:CNY,status:0,subscriptionPeriod:P1M,freeTrialPeriod:P7D},{productId:moneytrack_yearly,type:iap.ProductType.AUTORENEWABLE,title:年度会员,description:解锁所有高级功能按年订阅享优惠,price:¥98.00,microPrice:9800,currency:CNY,status:0,subscriptionPeriod:P1Y,freeTrialPeriod:P7D}];文件路径feature_membership/MemberSubscriptionPage.etsimport{iap}fromkit.IAPKit;asyncfunctionstartPurchase(productId:string){try{// 1. 创建订单constpurchaseResultawaitiap.createPurchase({productId});// 2. 拉起收银台awaitiap.purchase(purchaseResult);// 3. 获取 JWS 收据constjwsReceiptpurchaseResult.inAppPurchaseResponse.receipt;// 4. 客户端解码查看constdecodedJWSUtil.decode(jwsReceipt);console.info(Purchase payload:,decoded.payload);// 5. 发送到服务端验证constverifiedawaitverifyOnServer(jwsReceipt);if(verified){awaitactivateLocalMembership(decoded.payload);showSuccessPrompt();}}catch(error){console.error(Purchase failed:,error);showErrorPrompt(error.message);}}推荐参考文档IAP Kit 应用内支付指南IAP Kit 服务端验签指南JWS 解码与验签规范