权益发放
场景介绍
应用在收到用户购买消耗型/非消耗型商品成功的结果后,需要发放相关权益,并在权益发放后,向IAP Kit确认发货,完成购买流程,具体实现请参见接入购买。此外,还需要补充如下处理,确保权益发放:
- 若应用提供消耗型商品,需要按照确保权益发放处理消耗型商品的权益发放。
- 若应用提供非消耗型商品,且为单机应用,则需要按照单机应用权益发放(非消耗型商品)处理非消耗型商品的权益发放。其他场景需要按照确保权益发放处理非消耗型商品的权益发放。
确保权益发放
用户购买商品后,开发者需要及时发放相关权益。但实际应用场景中,若出现异常(网络错误、进程被中止等)将导致应用无法知道用户实际是否支付成功,从而无法及时发放权益,即出现掉单情况。为了确保权益发放,需要在以下场景检查用户是否存在已购未发货的商品:
如果存在已购未发货商品,则发放相关权益,然后向IAP Kit确认发货,完成购买。
业务流程

-
应用客户端向IAP Kit发起queryPurchases请求,查询用户已购买但未确认发货的订单信息。
-
IAP Kit返回PurchaseData列表。PurchaseData为JWS格式的字符串,承载了相关的订单信息。
-
应用客户端向应用服务器上报PurchaseData列表。
-
应用服务器需对每个PurchaseData.jwsPurchaseOrder进行解码验签,验证成功可得到对应的PurchaseOrderPayload的JSON字符串。
-
处理权益发放。检查当前PurchaseOrderPayload是否已发放权益,未发放则发放相关权益,并记录对应的订单信息(PurchaseOrderPayload)。
-
应用客户端向应用服务器查询订单的发货状态。
-
应用服务器返回对应的发货状态以及订单信息(PurchaseOrderPayload)。
-
发货成功后应用客户端向IAP Kit发送finishPurchase请求,以此通知IAP服务器更新商品的发货状态,完成购买流程。应用成功执行finishPurchase之后,IAP服务器会将相应商品标记为已发货状态。此步骤也可放到应用服务器处理。应用服务器可通过请求服务端订单确认发货接口来确认发货,完成购买流程。
- 对于消耗型商品,IAP服务器会将相应商品重新设置为可购买状态,用户即可再次购买该商品。如果不执行此步骤,会导致用户无法再次购买该商品。
- 确保在发货成功之后再执行此步骤,否则可能导致IAP服务器已经确认发货但是应用没有发货的问题。
开发步骤
-
应用客户端向IAP Kit发起queryPurchases请求,获取用户已购买但未确认发货的订单信息。
在请求参数QueryPurchasesParameter中指定对应的productType,同时指定queryType为iap.PurchaseQueryType.UNFINISHED。当接口请求成功时,IAP Kit将返回一个QueryPurchaseResult对象,该对象包含承载了订单信息的PurchaseData的列表。
-
对purchaseData.jwsPurchaseOrder进行解码验签。建议应用客户端将purchaseData发送至应用服务器,在应用服务器执行此操作。
-
验证成功可得到对应的PurchaseOrderPayload的JSON字符串,如果PurchaseOrderPayload.purchaseOrderRevocationReasonCode为空,则代表购买成功,需要进行补发货处理。
建议先检查此笔订单权益的发放状态,未发放则发放权益,成功后记录PurchaseOrderPayload等信息,用于后续检查权益发放状态。
如果开发者在发起购买时支持消耗型商品的批量购买,则需要在发货时校验下单的商品数量和PurchaseOrderPayload.quantity是否一致,避免造成漏发、多发的情况。
-
发货成功后,应用需调用finishPurchase接口确认发货,以此通知IAP服务器更新商品的发货状态,完成购买流程。
发起请求时,需在请求参数FinishPurchaseParameter中携带PurchaseOrderPayload中的productType、purchaseToken、purchaseOrderId。
请求成功后,IAP服务器会将相应商品标记为已发货状态。对于消耗型商品,IAP服务器会将相应商品重新设置为可购买状态,用户即可再次购买该商品。对于非消耗型商品,用户购买后永久拥有,无法再次购买该商品。
JWSUtil为自定义类,可参见示例代码。
import { iap } from '@kit.IAPKit';import { common } from '@kit.AbilityKit';import { BusinessError } from '@kit.BasicServicesKit';// JWSUtil为自定义类import { JWSUtil } from '../common/JWSUtil';@Entry@Componentstruct Index {queryPurchases(context: common.UIAbilityContext,) {const param: iap.QueryPurchasesParameter = {// iap.ProductType.CONSUMABLE:消耗型商品// iap.ProductType.NONCONSUMABLE:非消耗型商品productType: iap.ProductType.CONSUMABLE,queryType: iap.PurchaseQueryType.UNFINISHED};iap.queryPurchases(context, param).then((res: iap.QueryPurchaseResult) => {console.info('Succeeded in querying purchases.');const purchaseDataList: string[] = res.purchaseDataList;if (purchaseDataList === undefined || purchaseDataList.length <= 0) {return;}for (let i = 0; i < purchaseDataList.length; i++) {const jwsPurchaseOrder: string = JSON.parse(purchaseDataList[i]).jwsPurchaseOrder;if (!jwsPurchaseOrder) {continue;}const purchaseStr = JWSUtil.decodeJwsObj(jwsPurchaseOrder);// 需自定义PurchaseOrderPayload类,包含的信息请参见PurchaseOrderPayloadconst purchaseOrderPayload = JSON.parse(purchaseStr) as PurchaseOrderPayload;// 处理发货// ...// 发货成功后向IAP Kit发送finishPurchase请求,确认发货,完成购买this.finishPurchase(context, purchaseOrderPayload);}}).catch((err: BusinessError) => {// 请求失败console.error(`Failed to query purchases. Code is ${err.code}, message is ${err.message}`);});}finishPurchase(context: common.UIAbilityContext, purchaseOrder: PurchaseOrderPayload) {const finishPurchaseParam: iap.FinishPurchaseParameter = {productType: Number(purchaseOrder.productType),purchaseToken: purchaseOrder.purchaseToken,purchaseOrderId: purchaseOrder.purchaseOrderId};iap.finishPurchase(context, finishPurchaseParam).then(() => {// 请求成功console.info('Succeeded in finishing purchase.');}).catch((err: BusinessError) => {// 请求失败console.error(`Failed to finish purchase. Code is ${err.code}, message is ${err.message}`);});}build() {}}
单机应用权益发放(非消耗型商品)
用户在购买非消耗型商品后,将永久拥有该商品的权益。应用需要在用户购买非消耗型商品后,始终为其发放相关权益。
请在以下场景获取用户已购非消耗型商品的信息,并发放相关权益。
为了在卸载重装、更换设备安装等场景下保障用户权益,需要在应用首次打开时,应用客户端向IAP Kit发起queryPurchases请求,查询用户已购非消耗型商品,完成权益恢复。
开发步骤
-
应用客户端向IAP Kit发起queryPurchases请求,获取用户已购非消耗型商品的订单状态信息。
在请求参数QueryPurchasesParameter中指定productType为iap.ProductType.NONCONSUMABLE,同时指定queryType为iap.PurchaseQueryType.CURRENT_ENTITLEMENT。当接口请求成功时,IAP Kit将返回一个QueryPurchaseResult对象,该对象包含承载了订单信息的PurchaseData的列表。
-
对每个PurchaseData.jwsPurchaseOrder进行解码验签。
-
验证成功可得到对应的PurchaseOrderPayload的JSON字符串,此时需要发放相关权益。
-
发放权益后,应用需调用finishPurchase接口确认发货,以此通知IAP服务器更新商品的发货状态,完成购买流程。
发起请求时,需在请求参数FinishPurchaseParameter中携带PurchaseOrderPayload中的productType、purchaseToken、purchaseOrderId。
请求成功后,IAP服务器会将相应商品标记为已发货。对于非消耗型商品,用户购买后永久拥有,无法再次购买该商品。
JWSUtil为自定义类,可参见示例代码。
import { iap } from '@kit.IAPKit';
import { common } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
// JWSUtil为自定义类
import { JWSUtil } from '../common/JWSUtil';
@Entry
@Component
struct Index {
queryPurchases(context: common.UIAbilityContext) {
const param: iap.QueryPurchasesParameter = {
productType: iap.ProductType.NONCONSUMABLE,
queryType: iap.PurchaseQueryType.CURRENT_ENTITLEMENT
};
iap.queryPurchases(context, param).then((res: iap.QueryPurchaseResult) => {
console.info('Succeeded in querying purchases.');
const purchaseDataList: string[] = res.purchaseDataList;
if (purchaseDataList === undefined || purchaseDataList.length <= 0) {
return;
}
for (let i = 0; i < purchaseDataList.length; i++) {
const jwsPurchaseOrder: string = JSON.parse(purchaseDataList[i]).jwsPurchaseOrder;
if (!jwsPurchaseOrder) {
continue;
}
// 对jwsPurchaseOrder进行解码验签
const purchaseStr = JWSUtil.decodeJwsObj(jwsPurchaseOrder);
// 需自定义PurchaseOrderPayload类,包含的信息请参见PurchaseOrderPayload
const purchaseOrderPayload = JSON.parse(purchaseStr) as PurchaseOrderPayload;
// 处理权益发放
// ...
// 发放权益后向IAP Kit发送finishPurchase请求,确认发货,完成购买
if (purchaseOrderPayload && purchaseOrderPayload.finishStatus !== '1') {
this.finishPurchase(context, purchaseOrderPayload);
}
}
}).catch((err: BusinessError) => {
// 请求失败
console.error(`Failed to query purchases. Code is ${err.code}, message is ${err.message}`);
});
}
finishPurchase(context: common.UIAbilityContext, purchaseOrder: PurchaseOrderPayload) {
const finishPurchaseParam: iap.FinishPurchaseParameter = {
productType: Number(purchaseOrder.productType),
purchaseToken: purchaseOrder.purchaseToken,
purchaseOrderId: purchaseOrder.purchaseOrderId
};
iap.finishPurchase(context, finishPurchaseParam).then(() => {
// 请求成功
console.info('Succeeded in finishing purchase.');
}).catch((err: BusinessError) => {
// 请求失败
console.error(`Failed to finish purchase. Code is ${err.code}, message is ${err.message}`);
});
}
build() {}
}