智能客服
你问我答,随时在线为你解决问题

























用户购买自动续期订阅商品后,若订阅处于生效状态,开发者需要及时给用户发放对应权益。
在应用启动时,获取用户当前处于生效状态的订阅列表,处理此部分订阅的权益发放。建议先检查当前订阅对应权益的发放状态,未发放再补充发放权益。在权益发放成功后,向IAP确认发货,完成购买。
建议单机应用将用户权益和订阅状态关联。如果订阅处于生效状态,始终为用户发放权益。

应用客户端向IAP Kit发起queryPurchases请求,查询用户生效中的订阅列表。
IAP Kit返回PurchaseData列表。PurchaseData为JWS格式的字符串,承载了相关的订阅信息。
应用客户端向应用服务器上报PurchaseData列表。
应用服务器需对每个PurchaseData.jwsSubscriptionStatus进行解码验签,验证成功可得到对应的SubGroupStatusPayload的JSON字符串。
处理权益发放。检查SubGroupStatusPayload.lastSubscriptionStatus.lastPurchaseOrder是否已发放权益,未发放则需发放相关权益,并记录对应的订单信息(PurchaseOrderPayload)。
建议单机应用将用户权益和订阅状态关联。如果订阅处于生效状态,始终为用户发放权益。
应用客户端向应用服务器查询订单的发货状态。
应用服务器返回对应的发货状态以及订单信息(PurchaseOrderPayload)。
发放权益后应用客户端向IAP Kit发送finishPurchase请求,以此通知IAP服务器更新商品的发货状态,完成购买流程。应用成功执行finishPurchase之后,IAP服务器会将相应商品标记为已发货状态。此步骤也可放到应用服务器处理。应用服务器可通过请求服务端订阅确认发货接口来确认发货,完成购买流程。
对于自动续期订阅商品,如果不执行此步骤,会导致后续自动续期无法扣费 ,以及同一个订阅组不同自动续期订阅商品无法切换等问题。
应用客户端向IAP Kit发起queryPurchases请求,获取生效中的订阅列表。
在请求参数QueryPurchasesParameter中指定productType为iap.ProductType.AUTORENEWABLE,同时指定queryType为iap.PurchaseQueryType.CURRENT_ENTITLEMENT。当接口请求成功时,IAP Kit将返回一个QueryPurchaseResult对象,该对象包含承载了订阅信息的PurchaseData的列表。
验证订单信息。对每个purchaseData.jwsSubscriptionStatus进行解码验签,验证成功可得到SubGroupStatusPayload的JSON字符串。建议应用客户端将purchaseData发送至应用服务器,在应用服务器执行此操作。
为了提高安全性,可从SubGroupStatusPayload.lastSubscriptionStatus.lastPurchaseOrder中解析出purchaseToken和purchaseOrderId信息,并通过服务端订阅状态查询接口向IAP服务器查询最新的订阅状态信息,进一步确认订阅信息的准确性。
展示订阅状态。
权益发放。获取SubGroupStatusPayload.lastSubscriptionStatus.lastPurchaseOrder(下文标记为PurchaseOrderPayload),处理权益发放。
可先检查此笔订单权益的发放状态,未发放则补充发放权益,成功后记录PurchaseOrderPayload等信息,用于后续检查权益发放状态。
建议单机应用将用户权益和订阅状态关联。如果订阅处于生效状态,始终为用户发放权益。
在发放权益后,如果PurchaseOrderPayload.finishStatus不为1,应用需调用finishPurchase接口确认发货,完成购买流程。
发起请求时,需在请求参数FinishPurchaseParameter中携带PurchaseOrderPayload中的productType、purchaseToken、purchaseOrderId。请求成功后,IAP服务器会将相应商品标记为已发货。
此步骤也可放到应用服务器处理。应用服务器可通过请求服务端订阅确认发货接口来确认发货,完成购买流程。
JWSUtil为自定义类,可参见示例代码。
- import { iap } from '@kit.IAPKit';
- import { BusinessError } from '@kit.BasicServicesKit';
- import { common } from '@kit.AbilityKit';
- import Logger from '../common/Logger';
- import { JWSUtil } from '../common/JWSUtil';
- import {
- FinishStatus,
- PurchaseData,
- PurchaseOrderPayload,
- SubGroupStatusPayload,
- SubStatus,
- } from '../common/IapDataModel';
- // ...
- await this.queryPurchases(iap.PurchaseQueryType.CURRENT_ENTITLEMENT);
- // ...
- queryPurchases(queryType: iap.PurchaseQueryType): Promise<void> {
- return new Promise<void>((resolve) => {
- const param: iap.QueryPurchasesParameter = {
- productType: iap.ProductType.AUTORENEWABLE,
- queryType: queryType,
- };
- iap.queryPurchases(this.context, param).then((res: iap.QueryPurchaseResult) => {
- Logger.info(TAG, 'Succeeded in querying purchases.');
- const purchaseDataList: string[] = res.purchaseDataList;
- if (purchaseDataList === undefined || purchaseDataList.length <= 0) {
- Logger.info(TAG, 'queryPurchases, purchaseDataList empty');
- resolve();
- return;
- }
- for (let i = 0; i < purchaseDataList.length; i++) {
- this.dealPurchaseData(purchaseDataList[i]);
- }
- resolve();
- }).catch((err: BusinessError) => {
- Logger.error(TAG, `Failed to query purchases. Code is ${err.code}, message is ${err.message}`);
- resolve();
- }).finally(() => {
- this.showNormalPage();
- });
- });
- }
-
- dealPurchaseData(purchaseData: string) {
- try {
- // 建议您将 purchaseData 发送到应用服务器进行签名验证。
- const jwsSubscriptionStatus = (JSON.parse(purchaseData) as PurchaseData).jwsSubscriptionStatus;
- if (!jwsSubscriptionStatus) {
- Logger.error(TAG, 'dealPurchaseData, jwsSubscriptionStatus invalid');
- return;
- }
- // 解码 jwsPurchaseOrder 并执行签名验证。
- const subscriptionStatus = JWSUtil.decodeJwsObj(jwsSubscriptionStatus);
- if (!subscriptionStatus) {
- Logger.error(TAG, 'dealPurchaseData, subscriptionStatus invalid');
- return;
- }
- // 需自定义SubGroupStatusPayload类,包含的信息请参见SubGroupStatusPayload
- const subGroupStatusPayload = JSON.parse(subscriptionStatus) as SubGroupStatusPayload;
- const lastSubscriptionStatus = subGroupStatusPayload.lastSubscriptionStatus;
- if (!lastSubscriptionStatus) {
- Logger.error(TAG, 'dealPurchaseData, lastSubscriptionStatus is invalid');
- return;
- }
- if (lastSubscriptionStatus.status === SubStatus.ACTIVE) {
- // 订阅已生效,您需要发货。
- const productId = lastSubscriptionStatus.renewalInfo?.productId;
- if (productId) {
- this.setProductInfoStatus(subGroupStatusPayload.subGroupId, productId, lastSubscriptionStatus.status);
- }
- // 在执行以下步骤之前,请确保发货成功。
- }
- const purchaseOrderPayload = lastSubscriptionStatus.lastPurchaseOrder;
- if (purchaseOrderPayload && purchaseOrderPayload.finishStatus !== FinishStatus.FINISHED) {
- // 向IAP Kit发送finishPurchase请求,以确认商品已发货并完成购买。
- this.finishPurchase(purchaseOrderPayload);
- }
- } catch (e) {
- Logger.error(TAG, 'dealPurchaseData json error');
- }
- }
-
- finishPurchase(purchaseOrder: PurchaseOrderPayload) {
- if (!purchaseOrder.productType) {
- Logger.error(TAG, 'finishPurchase but productType is empty');
- return;
- }
- const finishPurchaseParam: iap.FinishPurchaseParameter = {
- productType: Number(purchaseOrder.productType),
- purchaseToken: purchaseOrder.purchaseToken,
- purchaseOrderId: purchaseOrder.purchaseOrderId,
- };
- iap.finishPurchase(this.context, finishPurchaseParam).then(() => {
- Logger.info(TAG, 'Succeeded in finishing purchase.');
- }).catch((err: BusinessError) => {
- Logger.error(TAG, `Failed to finish purchase. Code is ${err.code}, message is ${err.message}`);
- });
- }
用户购买自动续期订阅成功或者自动续期成功后,开发者需要及时给用户发放相关权益。但实际应用场景中,若出现异常(网络错误等)将导致应用无法知道用户实际是否支付成功,从而无法及时发放权益,即出现掉单情况。
为了确保权益发放,需要在createPurchase请求返回iap.IAPErrorCode.PRODUCT_OWNED或iap.IAPErrorCode.SYSTEM_ERROR时检查用户是否存在已购但未确认发货的商品,如果存在则发放相关权益,然后向IAP Kit确认发货,完成购买。

应用客户端向IAP Kit发起queryPurchases请求,查询用户已购买但未确认发货的订阅列表。
IAP Kit返回PurchaseData列表。PurchaseData为JWS格式的字符串,承载了相关的订阅信息。
应用客户端向应用服务器上报PurchaseData列表。
应用服务器需对每个PurchaseData.jwsSubscriptionStatus进行解码验签,验证成功可得到对应的SubGroupStatusPayload的JSON字符串。
处理权益发放。检查SubGroupStatusPayload.lastSubscriptionStatus.lastPurchaseOrder是否已发放权益,未发放则需发放相关权益,并记录对应的订单信息(PurchaseOrderPayload)。
建议单机应用将用户权益和订阅状态关联。如果订阅处于生效状态,始终为用户发放权益。
应用客户端向应用服务器查询订单的发货状态。
应用服务器返回对应的发货状态以及订单信息(PurchaseOrderPayload)。
发放权益后应用客户端向IAP Kit发送finishPurchase请求,以此通知IAP服务器更新商品的发货状态,完成购买流程。应用成功执行finishPurchase之后,IAP服务器会将相应商品标记为已发货状态。此步骤也可放到应用服务器处理。应用服务器可通过请求服务端订阅确认发货接口来确认发货,完成购买流程。
对于自动续期订阅商品,如果不执行此步骤,会导致后续自动续期无法扣费 ,以及同一个订阅组不同自动续期订阅商品无法切换等问题。
应用客户端向IAP Kit发起queryPurchases请求,获取用户已购但未确认发货的订阅列表。
在请求参数QueryPurchasesParameter中指定productType为iap.ProductType.AUTORENEWABLE,同时指定queryType为iap.PurchaseQueryType.UNFINISHED。当接口请求成功时,IAP Kit将返回一个QueryPurchaseResult对象,该对象包含承载了订阅信息的PurchaseData的列表。
验证订单信息。对每个purchaseData.jwsSubscriptionStatus进行解码验签,验证成功可得到SubGroupStatusPayload的JSON字符串。建议应用客户端将purchaseData发送至应用服务器,在应用服务器执行此操作。
为了提高安全性,可从SubGroupStatusPayload.lastSubscriptionStatus.lastPurchaseOrder中解析出purchaseToken和purchaseOrderId信息,并通过服务端订阅状态查询接口向IAP服务器查询最新的订阅状态信息,进一步确认订阅信息的准确性。
处理权益发放。
如果SubGroupStatusPayload.lastSubscriptionStatus.status=1,表示订阅处于生效状态。需要对生效状态的订阅处理权益发放。建议先检查此笔订单权益的发放状态,未发放则补充发放权益,成功后记录PurchaseOrderPayload等信息,用于后续检查权益发放状态。
建议单机应用将用户权益和订阅状态关联。如果订阅处于生效状态,始终为用户发放权益。
在发放权益后,如果PurchaseOrderPayload.finishStatus不为1,应用需调用finishPurchase接口确认发货,完成购买流程。
发起请求时,需在请求参数FinishPurchaseParameter中携带PurchaseOrderPayload中的productType、purchaseToken、purchaseOrderId。请求成功后,IAP服务器会将相应商品标记为已发货。
此步骤也可放到应用服务器处理。应用服务器可通过请求服务端订阅确认发货接口来确认发货,完成购买流程。
JWSUtil为自定义类,可参见示例代码。
- import { iap } from '@kit.IAPKit';
- import { BusinessError } from '@kit.BasicServicesKit';
- import { common } from '@kit.AbilityKit';
- import Logger from '../common/Logger';
- import { JWSUtil } from '../common/JWSUtil';
- import {
- FinishStatus,
- PurchaseData,
- PurchaseOrderPayload,
- SubGroupStatusPayload,
- SubStatus,
- } from '../common/IapDataModel';
- // ...
- queryPurchases(queryType: iap.PurchaseQueryType): Promise<void> {
- return new Promise<void>((resolve) => {
- const param: iap.QueryPurchasesParameter = {
- productType: iap.ProductType.AUTORENEWABLE,
- queryType: queryType,
- };
- iap.queryPurchases(this.context, param).then((res: iap.QueryPurchaseResult) => {
- Logger.info(TAG, 'Succeeded in querying purchases.');
- const purchaseDataList: string[] = res.purchaseDataList;
- if (purchaseDataList === undefined || purchaseDataList.length <= 0) {
- Logger.info(TAG, 'queryPurchases, purchaseDataList empty');
- resolve();
- return;
- }
- for (let i = 0; i < purchaseDataList.length; i++) {
- this.dealPurchaseData(purchaseDataList[i]);
- }
- resolve();
- }).catch((err: BusinessError) => {
- Logger.error(TAG, `Failed to query purchases. Code is ${err.code}, message is ${err.message}`);
- resolve();
- }).finally(() => {
- this.showNormalPage();
- });
- });
- }
-
- dealPurchaseData(purchaseData: string) {
- try {
- // 建议您将 purchaseData 发送到应用服务器进行签名验证。
- const jwsSubscriptionStatus = (JSON.parse(purchaseData) as PurchaseData).jwsSubscriptionStatus;
- if (!jwsSubscriptionStatus) {
- Logger.error(TAG, 'dealPurchaseData, jwsSubscriptionStatus invalid');
- return;
- }
- // 解码 jwsPurchaseOrder 并执行签名验证。
- const subscriptionStatus = JWSUtil.decodeJwsObj(jwsSubscriptionStatus);
- if (!subscriptionStatus) {
- Logger.error(TAG, 'dealPurchaseData, subscriptionStatus invalid');
- return;
- }
- // 需自定义SubGroupStatusPayload类,包含的信息请参见SubGroupStatusPayload
- const subGroupStatusPayload = JSON.parse(subscriptionStatus) as SubGroupStatusPayload;
- const lastSubscriptionStatus = subGroupStatusPayload.lastSubscriptionStatus;
- if (!lastSubscriptionStatus) {
- Logger.error(TAG, 'dealPurchaseData, lastSubscriptionStatus is invalid');
- return;
- }
- if (lastSubscriptionStatus.status === SubStatus.ACTIVE) {
- // 订阅已生效,您需要发货。
- const productId = lastSubscriptionStatus.renewalInfo?.productId;
- if (productId) {
- this.setProductInfoStatus(subGroupStatusPayload.subGroupId, productId, lastSubscriptionStatus.status);
- }
- // 在执行以下步骤之前,请确保发货成功。
- }
- const purchaseOrderPayload = lastSubscriptionStatus.lastPurchaseOrder;
- if (purchaseOrderPayload && purchaseOrderPayload.finishStatus !== FinishStatus.FINISHED) {
- // 向IAP Kit发送finishPurchase请求,以确认商品已发货并完成购买。
- this.finishPurchase(purchaseOrderPayload);
- }
- } catch (e) {
- Logger.error(TAG, 'dealPurchaseData json error');
- }
- }
-
- finishPurchase(purchaseOrder: PurchaseOrderPayload) {
- if (!purchaseOrder.productType) {
- Logger.error(TAG, 'finishPurchase but productType is empty');
- return;
- }
- const finishPurchaseParam: iap.FinishPurchaseParameter = {
- productType: Number(purchaseOrder.productType),
- purchaseToken: purchaseOrder.purchaseToken,
- purchaseOrderId: purchaseOrder.purchaseOrderId,
- };
- iap.finishPurchase(this.context, finishPurchaseParam).then(() => {
- Logger.info(TAG, 'Succeeded in finishing purchase.');
- }).catch((err: BusinessError) => {
- Logger.error(TAG, `Failed to finish purchase. Code is ${err.code}, message is ${err.message}`);
- });
- }
- // ...
- this.queryPurchases(iap.PurchaseQueryType.UNFINISHED);
智能客服
你问我答,随时在线为你解决问题
合作咨询
我们的专家服务团队将竭诚为您提供专业的合作咨询服务
解决方案
精准高效的一站式服务支持,助力开发者商业成功