会员订阅的全流程实现 — 购买→支付→服务端验证→权益激活
文章简介
会员订阅是付费应用的核心商业闭环。在 MoneyTrack 中,用户从会员页面选择商品到最终解锁会员权益,经历了一个包含客户端支付、服务端收据验证、本地权益激活在内的完整流程。本文详细拆解这一流程的各个阶段,涵盖 createPurchase 购买发起、支付回调处理、finishStatus 状态判断、服务端同步以及到期提醒等关键环节。
核心知识点
1. 完整购买流程时序图
以下时序图清晰展示了从用户点击购买到权益激活的完整交互过程:
2. FinishStatus 完整枚举说明
FinishStatus枚举定义在membership/constant/Enum.ets中,用于标识支付完成的三种状态:
export enum FinishStatus { /** 支付成功,收据有效,可发起服务端验证 */ FINISHED = 0, /** 用户主动取消支付 */ CANCELED = 1, /** 支付过程发生错误(网络异常、余额不足等) */ ERROR = 2, }应用需根据不同状态执行不同的 UI 反馈逻辑:FINISHED触发权益激活流程,CANCELED展示友好提示,ERROR提供重试入口。
3. 支付失败处理
支付失败是移动支付场景中的高频问题,MoneyTrack 设计了三级处理策略:
- 自动重试:对于网络超时等可恢复错误,自动重试 1 次,重试间隔 3 秒。
- 用户引导:重试仍失败时,弹窗提示用户检查网络或切换支付方式,并提供「重新购买」按钮。
- 优雅降级:支付取消不视为失败,不弹错误提示,仅关闭加载状态。会员页面保持原样,用户可以稍后重新尝试。
async function handlePaymentError(err: Error, productId: string): Promise<void> { if (isRetryableError(err) && retryCount < 1) { retryCount++; await delay(3000); return buyProduct(productId, iap.ProductType.AUTORENEWABLE); } // 不可恢复错误,提示用户 AlertDialog.show({ message: '支付失败,请检查网络后重试' }); }4. 服务端同步完整代码
支付成功后,客户端需要将解码后的收据发送到服务端。服务端验证通过后,更新用户会员状态并返回最新信息:
// 客户端:服务端同步 async function syncReceiptToServer(params: iap.PurchaseParams): Promise<void> { try { // 1. 解码 JWS 收据 const decodedReceipt = JWSUtil.decodeJwsObj(params.receipt); // 2. 组装请求体 const body = { receipt: decodedReceipt, productId: params.productId, purchaseToken: params.purchaseToken, deviceId: await getDeviceId(), }; // 3. 发送到服务端 const response = await UserApis.subscribeMembership(body); // 4. 更新本地会员状态 if (response.code === 200) { memberShipVM.isMember = true; memberShipVM.expiresTime = response.data.expiresTime; memberShipVM.isRenewMember = true; } } catch (err) { hilog.error(0xFF00, 'SyncReceipt', `sync failed: ${JSON.stringify(err)}`); // 收据上传失败,存入本地队列等待下次重试 await enqueuePendingReceipt(params); } }5. 订阅到期提醒
为了防止用户因忘记续费而失去会员权益,MoneyTrack 在会员到期前通过本地通知进行提醒:
function scheduleExpiryReminder(expiresTime: number): void { const now = Date.now(); const daysUntilExpiry = Math.floor((expiresTime - now) / 86400000); if (daysUntilExpiry <= 7 && daysUntilExpiry > 0) { notificationManager.publish({ id: 2001, content: { notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT, normal: { title: '会员即将到期', text: `您的会员将在 ${daysUntilExpiry} 天后到期,请及时续费`, }, }, }); } }项目代码案例
MemberShipPage 中 subscribeCallBack 回调链
文件路径:components/membership/src/main/ets/pages/MemberShipPage.ets
@Event subscribeCallBack: (params: MemberParams) => Promise<void> = () => new Promise(() => {}); // 在 aboutToAppear 中注册回调 this.vm.setEvent(this.subscribeCallBack, this.queryExpireTime, this.ownedMemberCallBack);UserApis.subscribeMembership()
文件路径:commons/lib_network/src/main/ets/https/apis/User.ets
public subscribeMembership(body: Record<string, object>): Promise<BaseResponse> { return request.post(RequestUrlMap.USER_MEMBERSHIP, body); }最佳实践
- 收据上传失败处理:使用本地队列暂存未上传成功的收据,应用下次启动时重试,确保不丢失任何有效订单。
- 幂等性设计:服务端接口需做幂等处理,防止同一笔订单多次验证导致重复激活。
- 到期提醒时机:分别在到期前 7 天、3 天、1 天发送三级提醒,提醒频率不宜过高避免用户反感。
- 回调超时保护:subscribeCallBack 内部设置 10 秒超时,超时后释放资源防止内存泄漏。
推荐参考文档
- HarmonyOS IAP Kit 购买与订阅开发指南
- AppGallery Connect 服务端收据验证文档
- @kit.IAPKit createPurchase API
- HarmonyOS notificationManager 通知开发指南