在这里插入图片描述

1 -> 概述

1.1 -> 什么是IAP Kit

IAP Kit(应用内支付服务)是华为鸿蒙系统为开发者提供的系统级支付能力解决方案。通过IAP Kit,开发者可以在应用中快速集成支付功能,让用户便捷地购买各类虚拟商品,从而完成商业变现。IAP Kit的核心优势在于其统一的API接口设计——开发者无需关心底层支付渠道的差异,IAP Kit会自动处理华为支付、支付宝、微信等支付方式的调用与路由,大大降低了支付接入的复杂度。

IAP Kit支持的交易模式覆盖了主流应用场景,主要包括以下三种商品类型:

  • 消耗型商品CONSUMABLE,值为0):可以重复购买、多次使用的虚拟物品,典型场景如游戏币、体力值、道具等。
  • 非消耗型商品NONCONSUMABLE,值为1):一次购买、永久拥有的虚拟权益,典型场景如付费解锁关卡、永久会员等。
  • 自动续期订阅商品AUTORENEWABLE,值为2):用户购买后在一段时间内可访问增值内容,到期自动续费,典型场景如VIP月卡、会员订阅等。

1.2 -> HarmonyOS 6.0 IAP Kit的核心改进

在之前的IAP Kit版本中,开发者调用商品查询接口时,需要同时传递商品ID列表和商品类型等多个参数。这种设计虽然有助于后端进行针对性的数据查询,但在实际开发中存在一些不便之处——商品类型是开发者事先已知的信息,将其作为查询参数反复传递,既增加了参数维护的负担,也使得接口调用略显冗余。

HarmonyOS 6.0对IAP Kit的商品查询接口进行了重要优化:QueryProductsParameter对象中新增了productIds属性,支持仅传递商品ID列表即可查询商品信息,商品类型参数变成了可选参数。这一改动看似微小,实则简化了调用链路上的参数管理,使接口语义更加清晰——只需要告诉IAP Kit“我想查哪些商品”,剩下的信息由系统自动补全。

需要特别说明的是:在HarmonyOS 6.0中,productIds参数无论在调用queryProducts时是否被单独传入,它都是QueryProductsParameter中唯一必填的参数。这意味着开发者必须提供至少一个商品ID才能发起查询请求,不能只传商品类型而不传商品ID。IAP Kit会根据传入的商品ID列表,自动定位对应的商品信息并返回。

这一改进最核心的价值在于参数的精简化和语义的清晰化,尤其对于包含多类型商品的复杂应用场景,开发者不再需要人为区分哪些商品属于哪一类,只需传入商品ID列表即可。

1.3 -> 本文内容概览

本文将围绕HarmonyOS 6.0 IAP Kit的这项核心改进展开,首先介绍前置准备工作,包括开通商户服务、配置商品信息等环节;随后详细讲解商品查询接口的使用方法,涵盖接口参数详解、Promise和Callback两种调用方式,并提供完整的代码示例;接着讲解如何基于查询到的商品信息构建商品列表界面;最后总结实际开发中的最佳实践和常见问题,帮助开发者高效、安全地接入IAP Kit。

2 -> 前置准备工作

2.1 -> 开通商户服务

在正式接入IAP Kit之前,开发者需要先在AppGallery Connect中开通商户服务。这是因为商户服务决定了收益分成的收款方式——你需要配置用于接收华为分成收益的银行卡账号和币种。

开通商户服务所需的基本信息包括:收款银行卡信息(开户行国家、开户银行、开户行支行、开户名等)、税务信息(税务地点、税务注册地址、税票类型)、以及市场、财务、法务等联系人信息。

2.2 -> 在AppGallery Connect中配置商品

这是IAP Kit接入中最重要的一步。所有商品信息——包括商品ID、商品类型、各国家/地区的价格和商品名称——都需要在AppGallery Connect中提前录入。客户端调用购买接口时只需传入商品ID和商品类型,IAP Kit会根据用户当前账号的服务地自动展示对应的商品信息,开发者无需自行处理多国家/地区的价格适配问题。

商品ID的命名规则如下:

  • 必须以大小写字母或数字开头;
  • 只能由大小写字母(A-Z、a-z)、数字(0-9)、下划线(_)或句点(.)组成;
  • 最多148个字符;
  • 同一应用内商品ID不能重复,且保存后无法修改,删除后也无法再次使用。

配置商品的步骤:登录AppGallery Connect,选择“APP与元服务”,在应用列表中点击需要新增商品的应用,在“运营”页签下选择“产品运营 > 商品管理”,然后选择“商品列表”页签并点击“添加商品”。如果应用还未设置分发国家/地区,系统会弹出警告提示,需要先完成设置。

2.3 -> 配置应用签名

在HarmonyOS应用开发中,需要在工程中配置bundleName和Client ID。如果应用的compatibleSdkVersion >= 14,接入IAP Kit不再需要开发者额外添加公钥指纹和配置应用身份信息。

2.4 -> 导入IAP Kit模块

在所有准备工作完成后,在需要调用IAP Kit能力的ArkTS文件中导入模块:

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

@kit.IAPKit是鸿蒙官方提供的应用内支付服务Kit包,包含所有商品查询、购买创建、订单管理等核心API。

2.5 -> 商品类型定义

在使用IAP Kit的过程中,开发者需要了解商品类型的枚举定义:

名称 说明
CONSUMABLE 0 消耗型商品
NONCONSUMABLE 1 非消耗型商品
AUTORENEWABLE 2 自动续期订阅商品(不支持可穿戴设备)
NONRENEWABLE 3 非续期订阅商品(不支持可穿戴设备)

3 -> 核心API详解:商品查询接口

3.1 -> 接口签名说明

HarmonyOS 6.0 IAP Kit提供了两种调用方式的商品查询接口:

Promise方式

function queryProducts(context: common.UIAbilityContext, parameter: QueryProductsParameter): Promise<Array<Product>>

Callback方式

function queryProducts(context: common.UIAbilityContext, parameter: QueryProductsParameter, callback: AsyncCallback<Array<Product>>): void

两种方式的区别在于异步结果的处理方式——Promise方式更适合现代化的async/await编程模式,代码可读性更好;Callback方式则保持了与传统回调风格的兼容性。

3.2 -> QueryProductsParameter参数详解

QueryProductsParameter是商品查询请求的参数对象。在HarmonyOS 6.0中,该对象的参数设计发生了重要变化:

  • productIds:字符串数组类型,必填。需要查询的商品ID列表,商品ID必须已经在AppGallery Connect中创建且唯一。一次最多可查询200个商品,如果商品数量较多建议分批查询。
  • productType:数值类型,变为可选。用于指定要查询的商品类型,与productIds共同约束返回结果。

新旧版本对比:早期版本的QueryProductsParameter要求必须同时提供productIdproductType,且缺少其中一个参数都会导致查询失败。而在HarmonyOS 6.0中,通过productIds参数仅传递商品ID即可完成查询,productType参数是可选的。这一改进的关键优势在于:当应用需要同时展示消耗型和非消耗型商品时,开发者不再需要分两次调用接口(一次传消耗型+商品ID、一次传非消耗型+商品ID),只需将所有商品ID一次性传入即可。

但在实际开发场景中,仍然有几种情况下建议使用productType参数:其一,当调用方需要明确限定查询结果的类型时,通过productType可以起到过滤作用;其二,当应用需要统计不同类型商品的查询成功率或展示数据时,productType的区分具有重要的业务意义。

3.3 -> 返回值详解

接口成功调用后,返回Array<Product>,即Product对象的数组。Product对象中包含商品的完整信息,开发者可以利用这些信息在UI上展示商品。主要字段包括:

  • productId:商品ID,与请求中的ID一一对应;
  • productName:商品名称,支持多语言,IAP Kit会自动根据用户当前语言环境返回对应语言的名称;
  • productType:商品类型,反映该商品属于消耗型、非消耗型还是订阅型;
  • price:商品价格(含税),已根据用户账号所在地自动换算;
  • currencyCode:货币代码(如CNY、USD);
  • localPrice:本地化价格字符串,可直接用于UI展示;
  • description:商品简介;
  • iconUrl:商品图标URL。

3.4 -> 错误码说明

商品查询过程中可能会遇到的常见错误码:

错误码 说明 处理建议
1001860001 内部错误 检查网络连接,稍后重试
1001860004 请求频率过高 降低调用频率,避免1秒内超过3次
商品未审核通过 商品状态无效 确认商品已在AGC中提交并通过审核

其中错误码1001860001——内部错误,是在查询商品信息时较为常见的问题之一,通常表现为商品ID尚未在AGC中通过审核,或者网络层面出现异常导致请求失败。在开发调试阶段,可通过沙盒测试来排除这些问题。

4 -> 代码实现示例

4.1 -> 导入依赖模块

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

4.2 -> 基础示例:仅使用productIds查询(鸿蒙6.0推荐方式)

这是鸿蒙6.0推荐的查询方式,仅传入商品ID列表即可:

// 在UIAbility或自定义组件中调用
async queryProductList(): Promise<void> {
  // 获取UIContext并转换为UIAbilityContext
  const context: common.UIAbilityContext = this.getUIContext().getHostContext() as common.UIAbilityContext;
  
  // 构建参数:仅传入商品ID列表
  const parameter: iap.QueryProductsParameter = {
    productIds: ['premium_monthly', 'coin_pack_100', 'ad_remove']  // 直接提供商品ID列表
    // productType 为可选参数,此处不传
  };
  
  try {
    const products: Array<iap.Product> = await iap.queryProducts(context, parameter);
    console.info(`查询成功,共获取到 ${products.length} 个商品`);
    
    // 遍历处理商品信息
    products.forEach((product: iap.Product) => {
      console.info(`商品ID: ${product.productId}`);
      console.info(`商品名称: ${product.productName}`);
      console.info(`商品价格: ${product.localPrice}`);
      console.info(`商品类型: ${product.productType}`);
    });
  } catch (err) {
    const error = err as BusinessError;
    console.error(`查询失败: code=${error.code}, message=${error.message}`);
  }
}

这段代码中的parameter参数并未包含productType字段,queryProducts接口会根据productIds中的商品ID自动匹配对应的商品类型。当应用需要展示混合类型的商品时,这个特性可以大幅简化调用逻辑——开发者无需再根据商品类型拆分成多次查询请求。

4.3 -> 同时使用productIds和productType(带类型过滤)

如果开发者仍然需要按商品类型过滤查询结果,可以同时传递productType参数:

async queryProductsByType(): Promise<void> {
  const context = this.getUIContext().getHostContext() as common.UIAbilityContext;
  
  const parameter: iap.QueryProductsParameter = {
    productType: iap.ProductType.CONSUMABLE,    // 限定只查询消耗型商品
    productIds: ['coin_pack_100', 'coin_pack_500']  // 指定要查询的商品ID
  };
  
  try {
    const products = await iap.queryProducts(context, parameter);
    console.info(`找到 ${products.length} 个消耗型商品`);
  } catch (err) {
    console.error(`查询失败: ${(err as BusinessError).message}`);
  }
}

productTypeproductIds同时指定时,接口会返回两者交集的结果——即传入的商品ID中,只有类型匹配的那些才会被返回。例如,如果某个商品ID实际配置为非消耗型商品,但在请求中指定了productType: CONSUMABLE,则该商品不会被返回。

4.4 -> 使用Callback方式

对于偏好回调风格的开发者,也可以使用Callback方式调用:

queryProductsWithCallback(): void {
  const context = this.getUIContext().getHostContext() as common.UIAbilityContext;
  
  const parameter: iap.QueryProductsParameter = {
    productIds: ['premium_monthly', 'coin_pack_100']
  };
  
  iap.queryProducts(context, parameter, (err: BusinessError, data: Array<iap.Product>) => {
    if (err) {
      console.error(`查询失败: code=${err.code}, message=${err.message}`);
      return;
    }
    console.info(`查询成功,共获取到 ${data.length} 个商品`);
    // 处理商品数据
    data.forEach((product: iap.Product) => {
      console.info(`${product.productName}: ${product.localPrice}`);
    });
  });
}

两种调用方式各有适用场景:Promise方式更加现代化,便于使用async/await进行错误处理,代码逻辑更清晰;Callback方式则在不支持Promise的环境下使用,或者在与旧代码保持一致的场景下选择。

4.5 -> 完整示例:带环境检查的商品查询

在实际生产环境中,建议在执行商品查询前先检查环境状态:

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

class IAPManager {
  private context: common.UIAbilityContext;

  constructor(context: common.UIAbilityContext) {
    this.context = context;
  }

  // 1. 首先检查环境是否支持支付
  async checkEnvironmentAndQuery(): Promise<Array<iap.Product> | null> {
    try {
      // 检查环境状态
      await iap.queryEnvironmentStatus(this.context);
      console.info('环境检查通过,支持应用内支付');
      
      // 环境通过后再查询商品
      return await this.queryProducts();
    } catch (err) {
      const error = err as BusinessError;
      console.error(`环境检查失败或支付环境不支持: ${error.message}`);
      // 环境不支持时,可以考虑隐藏支付相关按钮
      return null;
    }
  }

  // 2. 查询商品信息(鸿蒙6.0简化方式)
  async queryProducts(): Promise<Array<iap.Product>> {
    const parameter: iap.QueryProductsParameter = {
      productIds: [
        'consumable_gem_pack',   // 消耗型商品
        'nonconsumable_ad_free', // 非消耗型商品
        'subscription_vip'       // 自动续期订阅商品
      ]
      // productType 为可选参数,此处不传即可一次性获取所有类型商品
    };
    
    try {
      const products = await iap.queryProducts(this.context, parameter);
      console.info(`查询成功,返回商品数量: ${products.length}`);
      return products;
    } catch (err) {
      const error = err as BusinessError;
      console.error(`查询商品失败: code=${error.code}, message=${error.message}`);
      throw error;
    }
  }
  
  // 批量查询场景:当商品数量超过200时需要分批
  async queryProductsBatch(productIds: string[], batchSize: number = 200): Promise<Array<iap.Product>> {
    const allProducts: Array<iap.Product> = [];
    
    for (let i = 0; i < productIds.length; i += batchSize) {
      const batch = productIds.slice(i, i + batchSize);
      const parameter: iap.QueryProductsParameter = { productIds: batch };
      
      try {
        const products = await iap.queryProducts(this.context, parameter);
        allProducts.push(...products);
      } catch (err) {
        console.error(`${Math.floor(i / batchSize) + 1}批查询失败: ${(err as BusinessError).message}`);
        // 可以根据业务需求决定是中断还是继续
      }
    }
    
    return allProducts;
  }
}

4.6 -> 构建商品列表界面

查询到商品信息后,可以使用这些数据构建商品列表UI:

import { Product } from '@kit.IAPKit';

@Entry
@Component
struct ShopPage {
  @State productList: Product[] = [];
  @State loading: boolean = true;
  private iapManager: IAPManager = new IAPManager(getContext(this) as common.UIAbilityContext);

  async aboutToAppear(): Promise<void> {
    await this.loadProducts();
  }

  async loadProducts(): Promise<void> {
    this.loading = true;
    const result = await this.iapManager.checkEnvironmentAndQuery();
    if (result) {
      this.productList = result;
    }
    this.loading = false;
  }

  build() {
    Column() {
      if (this.loading) {
        LoadingProgress().width(48).height(48);
      } else {
        List() {
          ForEach(this.productList, (product: Product) => {
            ListItem() {
              Row() {
                Image(product.iconUrl)
                  .width(60).height(60)
                  .borderRadius(8)
                Column({ space: 4 }) {
                  Text(product.productName)
                    .fontSize(16).fontWeight(FontWeight.Medium)
                  Text(product.description)
                    .fontSize(12).fontColor('#666')
                  Text(product.localPrice)
                    .fontSize(18).fontColor('#FF6B6B')
                    .fontWeight(FontWeight.Bold)
                }
                .alignItems(HorizontalAlign.Start)
                .margin({ left: 12 })
                .layoutWeight(1)
              }
              .width('100%')
              .padding(16)
              .onClick(() => this.purchaseProduct(product))
            }
          })
        }
      }
    }
  }

  purchaseProduct(product: Product): void {
    // 跳转到购买逻辑
    console.info(`开始购买: ${product.productId}`);
  }
}

4.7 -> 购买商品的基础示例

查询到商品信息后,用户在商品列表中选择某个商品时,可以调用购买接口:

async purchaseProduct(productId: string, productType: number): Promise<void> {
  const context = this.getUIContext().getHostContext() as common.UIAbilityContext;
  
  try {
    const result = await iap.createPurchase(context, {
      productId: productId,
      productType: productType
    });
    console.info(`购买成功: orderId=${result.orderId}`);
    // 处理购买成功后的发货逻辑
    await this.deliverProduct(result);
  } catch (err) {
    const error = err as BusinessError;
    console.error(`购买失败: ${error.message}`);
  }
}

createPurchase请求中只需要携带商品ID和商品类型,IAP Kit会自动创建订单并展示收银台。

5 -> 最佳实践与避坑指南

5.1 -> 查询商品数量的限制

queryProducts接口一次最多可查询200个商品。如果应用的商品数量较多(例如游戏有数百种道具),应当在客户端实现分批查询逻辑,每批不超过200个商品。上面4.5节的queryProductsBatch方法已经提供了分批查询的参考实现。

5.2 -> 商品配置与查询失败的常见原因

查询商品信息时接口报错是开发过程中最常见的问题。即便后续购买逻辑正常,查询商品信息这一步也可能先失败。根据社区中开发者遇到的实际情况,商品未审核通过是导致查询失败的最常见原因——即便沙盒测试环境下支付功能可以正常调起,商品信息仍然可能因为尚未通过审核而查不到。

因此在开发调试阶段,务必确认以下几点:商品是否已在AppGallery Connect中提交并审核通过;商品销售范围是否已配置至少一个国家/地区;商品状态是否为“已上架”。

5.3 -> 支付频率控制

调用购买接口时需要注意频率控制。IAP Kit系统侧存在风控机制,如果调用过于频繁会触发风控限制,导致接口报错。实际生产环境中,应当避免在短时间内连续发起购买请求。例如,用户连续点击购买按钮时应当进行防抖处理,通过节流或增加冷却时间来控制请求频率。

5.4 -> 发货确认流程

购买成功后,有一个极其重要但容易被忽视的环节——发货确认。finishPurchase方法必须在权益成功发放后才能调用,调用顺序绝对不能颠倒。错误的做法是先调用finishPurchase再发货,这样会造成用户未收到货但钱已经扣了的严重后果。正确的做法是在服务器端或客户端确认权益发放成功后,再调用finishPurchase完成订单的最终确认。

5.5 -> 补货机制

在支付过程中可能遇到一些边界场景,例如用户支付成功后应用意外闪退、网络异常导致发货确认未完成等。针对这种情况,建议在应用启动时调用queryPurchases方法查询未完成发货的订单,并进行补发处理。这种机制可以有效防止掉单,确保用户权益在任何情况下都能得到保障。

5.6 -> 数据验证

支付数据的安全性至关重要。建议采用服务端通知的方式接收购买结果,而非完全依赖客户端回调。服务端收到华为发送的支付通知后,应当进行签名验签,确认订单的真实性和完整性后再发放权益。这种方案比纯客户端的可靠性更高,适用于对安全性要求较高的场景。

6 -> 总结

HarmonyOS 6.0 IAP Kit在商品查询接口上的优化,表面上看只是一个参数的调整,但其背后反映的是一种简化开发者体验的设计哲学。productIds参数的引入使查询逻辑更加直观自然,尤其对于需要混合展示多种类型商品的应用场景,原本需要多次调用的代码可以简化为单次调用。这正是IAP Kit持续演进的方向——让支付能力的接入成本不断降低,让开发者能够将更多精力聚焦于应用的核心业务本身。

从整体接入流程来看,IAP Kit的核心环节可以归纳为五个步骤:开通商户服务 → 配置商品信息 → 查询商品展示 → 发起购买 → 确认发货。其中商品查询是连接配置与交易的关键桥梁。鸿蒙6.0对商品查询接口的精简,使得这座桥梁的搭建更加顺畅。

对于正在接入或计划接入IAP Kit的开发者而言,本文介绍的最佳实践和避坑指南尤其值得关注——查询失败排查、支付频率控制、发货顺序确认以及补发机制的设计,往往是项目上线后最容易出现问题的环节。提前在这些方面做好防御性设计,可以大幅提升应用的支付稳定性和用户体验。


感谢各位大佬支持!!!

互三啦!!!
Logo

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

更多推荐