如下业务流程对于单机应用同样适用。在单机应用中,应用服务器和应用客户端的交互放在应用客户端完成,应用服务器和IAP服务器交互的部分可不处理。
自动续期订阅能力支持Phone、Tablet、PC/2in1设备,并且从5.1.1(19)版本开始,新增支持TV设备,从26.0.0版本开始,新增支持Car设备。
如下业务流程对于单机应用同样适用。在单机应用中,应用服务器和应用客户端的交互放在应用客户端完成,应用服务器和IAP服务器交互的部分可不处理。

展示商品
应用客户端向IAP Kit发起queryEnvironmentStatus请求,检查当前用户登录的华为账号所在的服务地是否在IAP Kit支持结算的国家/地区中。
如果接口返回错误码“1001860054:用户账号所在服务地不在IAP Kit支持结算的国家/地区中”,应用需隐藏相关IAP功能入口。
应用客户端向IAP Kit发起queryProducts请求来获取在AppGallery Connect上配置的商品信息。
应用客户端根据返回的商品信息展示可供购买的商品列表,包含商品名称、价格等信息。
检查权益发放状态
应用客户端向IAP Kit发起queryPurchases请求,获取当前生效中的订阅列表。IAP Kit返回PurchaseData列表。PurchaseData为JWS格式的字符串,承载了相关的订阅信息。
应用客户端展示商品的订阅状态,需要屏蔽处于自动续期状态的商品的购买入口。同时处理商品的权益发放。
若商品未确认发货,需要在权益发放后,向IAP Kit发送finishPurchase请求,以此通知IAP服务器更新商品的发货状态,完成购买流程。应用成功执行finishPurchase之后,IAP服务器会将相应商品标记为已发货状态。
此步骤也可放到应用服务器处理。应用服务器可通过请求服务端订阅确认发货接口来确认发货,完成购买流程。
具体请参见对生效中的订阅发放权益。
购买及结果确认
用户发起购买后,应用客户端向IAP Kit发起createPurchase购买请求或通过IAP嵌入式收银台组件发起购买请求(只支持TV),请求中携带商品ID、商品类型等信息。IAP Kit创建订单并展示收银台。
购买结果确认。如购买成功,可通过应用客户端或应用服务器接收购买结果,建议通过应用服务器接收购买结果。
方式一:通过客户端接收购买结果
(建议)方式二:通过服务器接收购买结果
为了提高安全性,开发者可以接入服务端关键事件通知,在用户购买成功时,IAP服务器将发送订单关键事件通知。
应用服务器可从NotificationPayload.NotificationMetaData中解析出purchaseToken和purchaseOrderId信息,并通过服务端订阅状态查询接口向IAP服务器查询最新的订阅状态信息,进一步确认订阅信息的准确性。
IAP服务器返回订阅组相关订阅状态数据jwsSubGroupStatus。
应用服务器对jwsSubGroupStatus进行解码验签,成功后可得到SubGroupStatusPayload的JSON字符串。
如果购买失败,请参见确保权益发放处理,及时发放权益。
发放权益
确认购买成功后,需要处理权益发放。检查SubGroupStatusPayload.lastSubscriptionStatus.lastPurchaseOrder是否已发放权益,未发放则需发放相关权益,并记录对应的订单信息(PurchaseOrderPayload),用于后续检查权益发放状态。
建议单机应用将用户权益和订阅状态关联。如果订阅处于生效状态,始终为用户发放权益。
应用客户端向应用服务器查询订单的发货状态。
应用服务器返回对应的发货状态以及订单信息(PurchaseOrderPayload)。
发货成功后应用客户端向IAP Kit发送finishPurchase请求,以此通知IAP服务器更新商品的发货状态,完成购买流程。应用成功执行finishPurchase之后,IAP服务器会将相应商品标记为已发货状态,后续该商品即可正常续期。
此步骤也可放到应用服务器处理。应用服务器可通过请求服务端订阅确认发货接口来确认发货,完成购买流程。
对于自动续期订阅商品,如果不执行此步骤,会导致后续自动续期无法扣费,以及同一个订阅组不同自动续期订阅商品无法切换等问题。
检查应用引入IAP Kit的可用性。
在使用应用内支付之前,应用客户端需要向IAP Kit发送queryEnvironmentStatus请求,以此判断用户当前登录的华为账号所在的服务地是否在IAP Kit支持结算的国家/地区中。
当前IAP Kit支持结算的国家/地区仅有中国境内(香港特别行政区、澳门特别行政区、中国台湾除外)。
import { iap } from '@kit.IAPKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { common } from '@kit.AbilityKit';
import Logger from '../common/Logger';
// ...
const queryEnvCode = await this.queryEnv();
if (queryEnvCode !== 0) {
let queryEnvFailedText = 'This app does not support iap';
if (queryEnvCode === iap.IAPErrorCode.ACCOUNT_TERRITORY_NOT_SUPPORTED) {
// 如果接口返回错误码“1001860054 用户账号所在服务地不在IAP Kit支持结算的国家/地区中”,应用需隐藏相关IAP功能入口
queryEnvFailedText = 'The country or region of the signed-in HUAWEI ID does not support IAP.';
}
this.showFailedPage(queryEnvFailedText);
return;
}
// ...
async queryEnv(): Promise<number> {
return new Promise<number>((resolve) => {
iap.queryEnvironmentStatus(this.context).then(() => {
Logger.info(TAG, 'Succeeded in querying environment status.');
resolve(0);
}).catch((err: BusinessError) => {
Logger.error(TAG, `Failed to query environment status. Code is ${err.code}, message is ${err.message}`);
resolve(err.code);
});
});
}展示商品列表。
应用客户端通过queryProducts来获取在AppGallery Connect上配置的商品信息。发起请求时,需在请求参数QueryProductsParameter中携带相关的商品ID,并指定其productType为iap.ProductType.AUTORENEWABLE。
当接口请求成功时,IAP Kit将返回商品信息Product的列表。 应用可以使用Product包含的商品价格、名称和描述等信息,向用户展示可供购买的商品列表。
queryProducts每次只能查询一种商品类型的商品,每次最多查询200个商品,否则请求将报错。
import { iap } from '@kit.IAPKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { common } from '@kit.AbilityKit';
import Logger from '../common/Logger';
// ...
async queryProducts(): Promise<number> {
const productIds: string[] = ['Sub001', 'KH003', 'FH301'];
return new Promise<number>((resolve) => {
iap.queryProducts(this.getUIContext().getHostContext() as common.UIAbilityContext, productIds).then((result) => {
Logger.info(TAG, 'Succeeded in querying products.');
// 展示产品详情
this.productInfoArray = result;
resolve(0);
}).catch((err: BusinessError) => {
// 查询商品报错
Logger.error(TAG, `Failed to query products. Code is ${err.code}, message is ${err.message}`);
this.showFailedPage();
resolve(err.code);
});
});
}应用获取用户当前生效中的订阅列表。
应用客户端展示对应商品的订阅状态。此处需要屏蔽处于自动续期状态的商品的购买入口。
处理生效中的订阅的权益发放。
具体可参见对生效中的订阅发放权益。
用户发起购买时,应用可通过向IAP Kit发送createPurchase请求来拉起IAP Kit收银台或通过IAP嵌入式收银台组件发起购买请求(只支持TV)。发起请求时,应用需在请求参数PurchaseParameter中携带此前已在华为AppGallery Connect网站上配置并生效的自动续期订阅的商品ID,并指定其productType为iap.ProductType.AUTORENEWABLE。
开发过程中易出现频繁调用接口的现象,建议控制接口调用频度,具体可参见1001860004 接口访问过频。
import { iap } from '@kit.IAPKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { common } from '@kit.AbilityKit';
import Logger from '../common/Logger';
// ...
subscribe(id: string, type: iap.ProductType) {
try {
const parameter: iap.PurchaseParameter = {
productId: id,
productType: type,
developerPayload: 'test developer payload string.',
};
iap.createPurchase(this.getUIContext().getHostContext() as common.UIAbilityContext, parameter,
(err: BusinessError, data: iap.CreatePurchaseResult) => {
if (err) {
// 请求失败
const msg: string = `Failed to create purchase. Code is ${err.code}, message is ${err.message}`;
Logger.error(TAG, msg);
// 购买失败
// ...
return;
}
// 请求成功
const msg: string = 'Succeeded in creating purchase.';
Logger.info(TAG, msg);
// 购买成功,处理购买结果
this.dealPurchaseData(data.purchaseData);
});
} catch (err) {
const e: BusinessError = err as BusinessError;
const msg: string = `Failed to create purchase. Code is ${e.code}, message is ${e.message}`;
Logger.error(TAG, msg);
}
}【结果1:购买成功】
以下内容为通过客户端接收购买结果及处理的步骤说明。
当用户购买成功时,应用将接收到一个CreatePurchaseResult对象,其purchaseData字段包括了此次购买的结果信息。
对purchaseData.jwsSubscriptionStatus进行解码验签,验证成功可得到SubGroupStatusPayload的JSON字符串。建议应用客户端将purchaseData发送至应用服务器,在应用服务器执行此操作。
验签成功后,检查SubGroupStatusPayload.lastSubscriptionStatus.status是否为1(生效中),是则发放相关权益。
建议先检查此笔订单权益的发放状态,未发放则发放权益,成功后记录SubGroupStatusPayload.lastSubscriptionStatus.lastPurchaseOrder等信息,用于后续检查权益发放状态。
完成购买。
发放权益后,应用需发送finishPurchase请求确认发货,以此通知IAP服务器更新商品的发货状态,完成购买流程。发送finishPurchase请求时,需在请求参数FinishPurchaseParameter中携带PurchaseOrderPayload中的productType、purchaseToken、purchaseOrderId,其中PurchaseOrderPayload为SubGroupStatusPayload.lastSubscriptionStatus.lastPurchaseOrder。请求成功后,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';
// ...
dealPurchaseData(purchaseData: string) {
try {
// 建议您将purchaseData发送到应用服务器进行签名验证。
const jwsSubscriptionStatus = (JSON.parse(purchaseData) as PurchaseData).jwsSubscriptionStatus;
if (!jwsSubscriptionStatus) {
Logger.error(TAG, 'dealPurchaseData, jwsSubscriptionStatus invalid');
return;
}
// 解码jwsSubscriptionStatus并执行签名验证。
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}`);
});
}【结果2:购买失败】
当用户购买失败时,需要针对code为iap.IAPErrorCode.PRODUCT_OWNED和iap.IAPErrorCode.SYSTEM_ERROR的场景,检查是否需要补发货,确保权益发放,具体请参见确保权益发放。
if (err.code === iap.IAPErrorCode.PRODUCT_OWNED || err.code === iap.IAPErrorCode.SYSTEM_ERROR) {
// 参见确保权益发放检查是否需要补发货,确保权益发放
// ...
// ...
}