HarmonyOS IAP Kit:让你的应用支持应用内购买

什么是 IAP Kit

你玩游戏的时候,是不是经常会看到"购买钻石"、"购买皮肤"之类的按钮?你点一下,弹出一个支付界面,付完钱东西就到手了。这就是应用内购买(In-App Purchase),简称 IAP。

IAP Kit 就是 HarmonyOS 提供的应用内购买工具。你不需要自己搭支付系统,不需要对接银行,只需要用 IAP Kit 提供的接口,就能让你的 App 支持用户在里面买东西。

买什么呢?比如游戏里买道具、买钻石、买皮肤;比如视频 App 里买会员;比如工具 App 里解锁高级功能。只要是在 App 里面发生的购买行为,都可以用 IAP Kit 来实现。

商品类型

IAP Kit 支持三种商品类型,每种类型的用途不一样:

消耗型商品:用完了可以再买的那种。最典型的例子就是游戏里的钻石、金币。你买了 100 钻石,用完了,可以再买 100 钻石。这种商品的特点是"用完即消",所以叫消耗型。

非消耗型商品:买一次就永久拥有的那种。比如你买了一个游戏的额外关卡,买了就是你的,不用再花钱。再比如你买了一个 App 的永久会员,一次付费终身使用。这种商品买了之后就不会再扣费了。

自动续期订阅商品:按月或者按年自动续费的那种。最典型的就是视频会员、音乐会员。你买了月度会员,每个月自动扣费,直到你取消为止。这种商品的好处是能给开发者带来持续的收入。

环境搭建

硬件要求

  • 设备类型:华为手机
  • HarmonyOS 系统:HarmonyOS 5.0.5 Release 及以上

软件要求

  • DevEco Studio 版本:DevEco Studio 6.1.0 Release 及以上
  • HarmonyOS SDK 版本:HarmonyOS 6.1.0 Release SDK 及以上

前置准备

在写代码之前,你需要先在华为开发者后台做一些配置。这些配置不做的话,代码写了也跑不起来。

开通商户服务

首先,你得去 AppGallery Connect(华为开发者后台)开通商户服务。这一步是告诉华为:“我要开始收钱了,请帮我把钱打到我的银行卡里。”

具体操作是:登录 AppGallery Connect → 找到你的应用 → 进入"应用内购买服务" → 开通商户服务。开通过程中需要填写你的收款银行卡信息,华为会把用户付的钱打到这张卡上。

开启应用内购买服务

商户服务开通之后,还需要开启应用内购买服务。这一步是告诉华为:“我的 App 要用 IAP 功能了,请给我开通。”

操作步骤:

  1. 打开"应用内购买服务(HarmonyOS NEXT)"开关。这个开关在 AppGallery Connect 里你的应用设置页面。
  2. 激活应用内购买服务。激活之后,你的 App 才能调用 IAP 的接口。

配置商品

商品不是在代码里定义的,而是在 AppGallery Connect 的"商品管理"页面里配置的。你需要把你打算卖的东西一条一条添加进去。

具体操作是这样的:

  1. 选择商品类型:你要卖的是消耗型(比如钻石)、非消耗型(比如永久会员)还是订阅型(比如月度会员)?不同类型的商品,购买流程和发货逻辑会有些差别。

  2. 配置商品信息:给商品起个名字、写个描述。比如商品名叫"100 钻石",描述写"购买后立即到账 100 钻石,可用于游戏内消费"。这些信息会展示给用户看,所以写得清楚一点。

  3. 配置商品价格:设置这个商品卖多少钱。华为支持的价格档位是预设的,你从里面选一个就行,不能自己随便填一个价格。

  4. 激活商品:配置完之后,商品默认是"未激活"状态,你需要手动激活它。只有激活了的商品,用户才能在 App 里看到和购买。如果你配置了商品但用户在 App 里看不到,先检查一下是不是忘了激活。

每种商品类型都有一个"商品 ID",这个 ID 是你自己定义的,比如 ohos_consume_001。在代码里查询和购买商品时,用的就是这个 ID。商品 ID 一旦创建就不能改了,所以取名的时候想清楚。

项目结构

├── entry/src/main/ets
│  ├── common
│  │  ├── JWSUtil.ets         // JWS 工具组件,用来解析购买结果
│  │  └── Logger.ets          // 日志打印组件
│  ├── entryability
│  │  └── EntryAbility.ets    // 应用首页 ability
│  └── pages
│     └── ConsumablesPage.ets // 商品展示页面

项目结构比较简单。JWSUtil 是一个工具类,用来解析购买结果里的 JWS 数据。你不需要自己写这个工具类,直接用就行。

第一步:导入模块

import { iap } from '@kit.IAPKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { common } from '@kit.AbilityKit';
import { JWSUtil } from '../common/JWSUtil';

导入 IAP Kit 相关的模块:

  • iap:IAP Kit 的核心接口,所有的购买、查询操作都靠它
  • BusinessError:错误处理用的,华为的 Kit 接口基本都用这个来返回错误
  • common:获取上下文用的,调用 IAP 接口时需要传入上下文
  • JWSUtil:JWS 工具类,用来解析购买结果里的加密数据

第二步:检查支付环境

在用户购买之前,先检查一下当前环境是否支持应用内支付。这一步很重要,如果不支持的话,后面的流程都不用走了。

iap.queryEnvironmentStatus(this.getUIContext().getHostContext() as common.UIAbilityContext).then(() => {
  // 支持应用内支付,可以继续后面的流程
}).catch((error: BusinessError) => {
  // 不支持,可能是地区不对,或者 HMS Core 版本太低
});

queryEnvironmentStatus 这个接口会检查用户当前登录的华为账号所在的服务地是否支持应用内支付。目前只有中国大陆支持,其他地区调用会返回错误。

什么时候调用这个接口呢?建议在用户进入商品页面的时候就调用,这样如果不支持,你可以提前给用户一个提示,而不是等用户点了购买按钮才发现不行。

第三步:查询商品信息

用户进入商品页面后,你需要从华为服务器查询商品的最新信息,包括价格、名称、描述等。

queryProducts() {
  let queryProductParam: iap.QueryProductsParameter = {
    productType: iap.ProductType.CONSUMABLE,
    productIds: ['ohos_consume_001']
  };
  
  iap.queryProducts(this.context, queryProductParam).then((result) => {
    // 查询成功,result.productList 里就是商品信息
    // 你可以把这些信息展示在页面上
  }).catch((error: BusinessError) => {
    // 查询失败,可能是商品 ID 写错了,或者商品没有激活
  });
}

queryProducts 传入商品类型和商品 ID 列表,返回商品的详细信息。注意:

  • productType 要和你在 AppGallery Connect 里配置的商品类型一致。你配置的是消耗型,这里就填 CONSUMABLE
  • productIds 是一个数组,可以一次查询多个商品。里面的 ID 要和你在后台配置的商品 ID 完全一致,差一个字母都不行。
  • 返回的 productList 里包含了每个商品的价格、名称、描述等信息,你可以直接展示给用户。

查询商品这一步不要跳过。虽然你在后台配置了商品信息,但价格可能会调整,而且你需要用返回的价格来展示给用户,而不是自己硬编码一个价格。

第四步:发起购买

用户点击"购买"按钮后,调用 createPurchase 拉起华为的收银台。

async buy(id: string, type: iap.ProductType) {
  try {
    let createPurchaseParam: iap.PurchaseParameter = {
      productId: id,
      productType: type,
    };
    
    iap.createPurchase(this.context, createPurchaseParam).then(async (result) => {
      // 购买成功,解析购买结果
      let jwsPurchaseOrder: string = JSON.parse(result.purchaseData).jwsPurchaseOrder;
      let purchaseStr = JWSUtil.decodeJwtObj(jwsPurchaseOrder);
      let purchaseOrderPayload = JSON.parse(purchaseStr) as PurchaseOrderPayload;
      this.finishPurchase(purchaseOrderPayload);
    }).catch((error: BusinessError) => {
      // 购买失败或用户取消
      // error.code 1001860002 表示用户取消了购买
      // 其他错误码表示购买过程中出了问题
    });
  } catch (error) {
    // 参数错误等异常情况
  }
}

createPurchase 会弹出华为的收银台界面,用户在里面确认支付。这个过程是华为控制的,你不需要自己做支付界面。

用户支付成功后,result.purchaseData 里会包含购买结果。但这个结果是 JWS 格式的(一种加密签名格式),你需要用 JWSUtil 来解析它。解析之后,你会得到一个 PurchaseOrderPayload 对象,里面包含了商品 ID、购买订单号、购买令牌等信息。

拿到这些信息后,你需要做两件事:

  1. 发放权益:比如给用户充钻石、解锁会员功能
  2. 调用 finishPurchase:告诉华为"我已经发货了"

这两件事做完,一次购买流程才算完整。

第五步:完成购买

发放权益之后,一定要调用 finishPurchase 告诉华为"我发货了"。

finishPurchase(purchaseOrder: PurchaseOrderPayload) {
  let finishPurchaseParam: iap.FinishPurchaseParameter = {
    productType: purchaseOrder.productType,
    purchaseToken: purchaseOrder.purchaseToken,
    purchaseOrderId: purchaseOrder.purchaseOrderId
  };
  
  iap.finishPurchase(this.context, finishPurchaseParam).then((result) => {
    // 购买完成,整个流程结束
    // 用户可以再次购买该商品了
  }).catch((error: BusinessError) => {
    // 完成购买失败,需要重试
  });
}

为什么要调用这个接口?因为如果你不调用,华为会认为你还没发货,用户就没办法再次购买同一个消耗型商品。比如用户买了 100 钻石,你给充了,但没调 finishPurchase,那用户下次想再买 100 钻石就买不了了,华为会提示"你已经购买过该商品"。

所以这个接口一定要调,而且要在确认发货成功之后再调。如果你先调了 finishPurchase 再发货,万一发货失败了,用户钱付了但东西没到手,那就麻烦了。

第六步:处理掉单

"掉单"是支付场景里一个很常见的问题。简单说就是:用户付了钱,但因为网络问题、App 闪退等原因,你没收到支付成功的通知,导致你没给用户发货。

这种情况如果不管的话,用户会投诉:"我钱扣了,但东西没到!"所以你必须处理掉单。

怎么处理呢?在以下场景调用 queryPurchases 查询有没有未发货的购买:

async queryPurchases() {
  let param: iap.QueryPurchasesParameter = {
    productType: iap.ProductType.CONSUMABLE,
    queryType: iap.PurchaseQueryType.UNFINISHED
  };
  
  iap.queryPurchases(this.context, param).then((res: iap.QueryPurchaseResult) => {
    let purchaseDataList: string[] = res.purchaseDataList;
    if (purchaseDataList === undefined || purchaseDataList.length <= 0) {
      // 没有未完成的购买,正常情况
      return;
    }
    // 有未完成的购买,逐个处理
    for (let i = 0; i < purchaseDataList.length; i++) {
      let purchaseData = purchaseDataList[i];
      let purchaseStr = JWSUtil.decodeJwsObj(purchaseData);
      let purchaseOrderPayload = JSON.parse(purchaseStr) as PurchaseOrderPayload;
      this.finishPurchase(purchaseOrderPayload);
    }
  }).catch((error: BusinessError) => {
    // 查询失败,稍后重试
  });
}

queryPurchases 会返回所有已购但未发货的商品列表。如果有,你就发放权益并调用 finishPurchase

你需要在以下场景检查掉单:

  • 应用启动时:用户可能昨天买了东西但 App 闪退了,今天打开 App 时要补发
  • 购买请求返回错误码 1001860001:这个错误码表示有未完成的购买
  • 购买请求返回错误码 1001860051:这个错误码也表示有未完成的购买

掉单处理是支付功能里非常重要的一环,千万不要忽略。很多开发者上线后收到用户投诉"付了钱没到账",基本都是因为没做好掉单处理。

完整购买流程

把上面的步骤串起来,整个购买流程是这样的:

  1. 检查环境:确认用户支持应用内支付
  2. 查询商品:从华为服务器获取商品信息,展示给用户
  3. 用户点击购买:调用 createPurchase 拉起收银台
  4. 用户确认支付:用户在收银台完成支付
  5. 发放权益:给用户充钻石、解锁功能等
  6. 确认发货:调用 finishPurchase 告诉华为发货完成
  7. 处理掉单:检查是否有未完成的购买,补发权益

适用场景

IAP Kit 适合以下场景:

  • 游戏应用:购买游戏道具、钻石、皮肤
  • 内容应用:购买课程、电子书
  • 工具应用:购买高级功能
  • 会员服务:购买月度/年度会员

注意事项

  1. 商品配置:商品必须在 AppGallery Connect 上配置并激活,代码里不能凭空创建商品
  2. JWS 验签:购买结果是 JWS 格式的,要验签确保来自华为服务器,防止伪造
  3. 掉单处理:一定要处理掉单情况,否则用户会投诉"付了钱没到账"
  4. 发货确认:发货后一定要调用 finishPurchase,否则用户无法再次购买消耗型商品
  5. 测试环境:开发时使用测试环境,上线前切换到正式环境

核心流程图

IAP 应用内购买的完整流程:

在 AppGallery Connect 开通商户服务

配置商品信息并激活

应用启动: 检查支付环境

是否支持应用内支付?

提示用户当前地区不支持

查询商品信息展示给用户

用户点击购买

调用 createPurchase 拉起收银台

用户是否完成支付?

用户取消或支付失败

解析 JWS 购买结果

发放权益给用户

调用 finishPurchase 确认发货

掉单处理流程:

应用启动

调用 queryPurchases 查询未完成购买

是否有未发货的购买?

正常流程继续

遍历未完成购买列表

解析购买数据

发放权益给用户

调用 finishPurchase 确认发货

还有更多未完成购买?

掉单处理完成

总结

IAP Kit 让你的应用支持应用内购买,核心流程:

  1. 在 AppGallery Connect 开通商户服务
  2. 配置商品信息(类型、价格、激活)
  3. 检查支付环境
  4. 查询商品信息展示给用户
  5. 发起购买,拉起收银台
  6. 完成购买,发放权益并确认发货
  7. 处理掉单,确保用户权益

掌握了这些,你就能让你的应用支持应用内购买,开始赚钱了。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐