HarmonyOS开发:支付服务Payment Kit

举报
Jack20 发表于 2026/06/25 21:00:19 2026/06/25
【摘要】 HarmonyOS开发:支付服务Payment Kit📌 核心要点:Payment Kit让HarmonyOS应用接入华为应用内支付,IAP支付流程、订单安全校验、订单管理与对账,每一环都关乎钱,容不得半点马虎。 背景与动机你的App要收费了。虚拟商品、会员订阅、增值服务——用户得掏钱,你得收钱。最简单的做法?让用户转账,你手动确认。一个用户还行,一百个呢?一千个呢?你每天光确认转账就够...

HarmonyOS开发:支付服务Payment Kit

📌 核心要点:Payment Kit让HarmonyOS应用接入华为应用内支付,IAP支付流程、订单安全校验、订单管理与对账,每一环都关乎钱,容不得半点马虎。

背景与动机

你的App要收费了。虚拟商品、会员订阅、增值服务——用户得掏钱,你得收钱。

最简单的做法?让用户转账,你手动确认。一个用户还行,一百个呢?一千个呢?你每天光确认转账就够忙的了,而且用户等不起——付了钱不能立刻用,体验极差。

接入第三方支付?微信支付、支付宝——但它们不支持虚拟商品的支付。苹果和谷歌都要求虚拟商品必须走应用内支付(IAP),华为也一样。

Payment Kit就是华为的IAP方案。用户在App内购买虚拟商品,通过华为支付完成交易,你收到订单回调和货款。整个流程闭环,安全合规。

但支付这东西,每一行代码都关乎钱。支付流程少一步,用户付了钱收不到货;订单校验少一步,黑客能绕过支付直接拿商品;对账少一步,钱少了你都不知道。所以Payment Kit的集成,容不得半点马虎。

核心原理

Payment Kit的IAP支付流程:客户端发起支付 → 华为支付系统处理 → 服务端校验订单 → 发放商品。

flowchart TD
    A[用户点击购买] --> B[客户端创建订单]
    B --> C[调用Payment Kit支付]
    
    C --> D[华为支付系统]
    D --> E{用户确认支付}
    E -->|取消| F[返回支付取消]
    E -->|确认| G[处理支付]
    
    G --> H{支付结果}
    H -->|成功| I[返回PurchaseInfo]
    H -->|失败| J[返回支付失败]
    
    I --> K[客户端发送订单到服务端]
    K --> L[服务端调用华为校验API]
    
    L --> M{校验结果}
    M -->|合法| N[发放商品]
    M -->|非法| O[拒绝发放,记录异常]
    
    N --> P[服务端确认消费]
    P --> Q[华为标记订单已消费]
    Q --> R[客户端更新UI]
    
    classDef client fill:#1565C0,color:#fff,stroke:#0D47A1
    classDef pay fill:#E65100,color:#fff,stroke:#BF360C
    classDef server fill:#2E7D32,color:#fff,stroke:#1B5E20
    classDef result fill:#6A1B9A,color:#fff,stroke:#4A148C
    classDef error fill:#C62828,color:#fff,stroke:#B71C1C

    class A,B,C,K,R,client
    class D,E,F,G,H,I,J,pay
    class L,M,N,P,Q,server
    class O,error

商品类型

Payment Kit支持两种商品类型:

商品类型 说明 示例
消耗型(Consumable) 购买后消耗,可重复购买 游戏金币、虚拟币、次数卡
非消耗型(Non-consumable) 购买后永久拥有 去广告、解锁功能、主题

注意:HarmonyOS 6之前不支持订阅型商品(Subscription),HarmonyOS 6新增了订阅支持。

订单状态

一个订单从创建到完成,经历以下状态:

  1. 已创建:用户发起支付,订单创建成功
  2. 已支付:用户完成支付,但商品尚未发放
  3. 已消费:服务端确认发放商品,订单完成
  4. 已退款:用户申请退款,华为审核通过

关键点:已支付 ≠ 已消费。 用户付了钱,但你还没确认发放商品,订单处于"已支付"状态。你必须调用服务端API确认消费,订单才算真正完成。

安全校验

支付安全的核心原则:永远不要信任客户端。

客户端返回的支付结果可以被篡改。黑客可以hook支付回调,伪造支付成功的消息。所以,支付结果的校验必须在服务端完成——服务端调用华为的订单校验API,确认订单真实有效后,再发放商品。

代码实战

基础用法:集成IAP并发起支付

先在AGC控制台配置商品,然后在客户端发起支付。

// PaymentManager.ets
import { iap } from '@kit.PaymentKit';

// 商品定义(和AGC控制台配置一致)
interface ProductInfo {
  productId: string;    // 商品ID
  productType: iap.ProductType;  // 商品类型
  name: string;         // 商品名称
  price: string;        // 展示价格
}

// 预定义的商品列表
const PRODUCTS: Record<string, ProductInfo> = {
  'coin_100': {
    productId: 'coin_100',
    productType: iap.ProductType.CONSUMABLE,
    name: '100金币',
    price: '¥6'
  },
  'coin_500': {
    productId: 'coin_500',
    productType: iap.ProductType.CONSUMABLE,
    name: '500金币',
    price: '¥25'
  },
  'remove_ads': {
    productId: 'remove_ads',
    productType: iap.ProductType.NONCONSUMABLE,
    name: '去广告',
    price: '¥18'
  }
};

export class PaymentManager {
  private static instance: PaymentManager;
  private iapService: iap.IAPService | null = null;

  private constructor() {}

  static getInstance(): PaymentManager {
    if (!PaymentManager.instance) {
      PaymentManager.instance = new PaymentManager();
    }
    return PaymentManager.instance;
  }

  /**
   * 初始化IAP服务
   */
  async init(): Promise<boolean> {
    try {
      this.iapService = iap.getService();
      console.info('[Payment] IAP服务初始化完成');
      return true;
    } catch (error) {
      console.error(`[Payment] 初始化失败: ${JSON.stringify(error)}`);
      return false;
    }
  }

  /**
   * 查询商品信息
   * 从华为服务端获取最新的商品信息(名称、价格等)
   */
  async queryProducts(productIds: string[]): Promise<iap.Product[]> {
    if (!this.iapService) return [];

    try {
      const products = await this.iapService.queryProducts({
        productIds: productIds,
        productType: iap.ProductType.CONSUMABLE  // 查询消耗型商品
      });

      console.info(`[Payment] 查询到${products.length}个商品`);
      return products;
    } catch (error) {
      console.error(`[Payment] 查询商品失败: ${JSON.stringify(error)}`);
      return [];
    }
  }

  /**
   * 发起支付
   * 消耗型商品支付流程
   */
  async purchaseConsumable(productId: string): Promise<iap PurchaseResult | null> {
    if (!this.iapService) return null;

    try {
      // 创建支付请求
      const purchaseRequest: iap.PurchaseRequest = {
        productId: productId,
        productType: iap.ProductType.CONSUMABLE,
        developerPayload: `order_${Date.now()}`  // 开发者自定义数据,校验时回传
      };

      // 发起支付(会拉起华为支付页面)
      const result = await this.iapService.createPurchase(purchaseRequest);

      console.info(`[Payment] 支付成功: orderId=${result.getOrderId()}`);
      return result;
    } catch (error) {
      const payError = error as iap.IAPError;
      switch (payError.code) {
        case iap.IAPErrorCode.ORDER_CANCELLED:
          console.info('[Payment] 用户取消支付');
          break;
        case iap.IAPErrorCode.ORDER_FAILED:
          console.error('[Payment] 支付失败');
          break;
        case iap.IAPErrorCode.PRODUCT_NOT_EXIST:
          console.error('[Payment] 商品不存在');
          break;
        default:
          console.error(`[Payment] 支付异常: ${JSON.stringify(error)}`);
      }
      return null;
    }
  }

  /**
   * 购买非消耗型商品
   */
  async purchaseNonConsumable(productId: string): Promise<iap.PurchaseResult | null> {
    if (!this.iapService) return null;

    try {
      const purchaseRequest: iap.PurchaseRequest = {
        productId: productId,
        productType: iap.ProductType.NONCONSUMABLE,
        developerPayload: `order_${Date.now()}`
      };

      const result = await this.iapService.createPurchase(purchaseRequest);
      console.info(`[Payment] 非消耗型商品购买成功: ${productId}`);
      return result;
    } catch (error) {
      console.error(`[Payment] 购买失败: ${JSON.stringify(error)}`);
      return null;
    }
  }

  /**
   * 查询已购买的非消耗型商品
   * 用于恢复购买(用户换设备后恢复已购商品)
   */
  async queryPurchasedProducts(): Promise<iap.PurchaseResult[]> {
    if (!this.iapService) return [];

    try {
      const purchases = await this.iapService.queryPurchases({
        productType: iap.ProductType.NONCONSUMABLE
      });

      console.info(`[Payment] 已购买${purchases.length}个非消耗型商品`);
      return purchases;
    } catch (error) {
      console.error(`[Payment] 查询已购商品失败: ${JSON.stringify(error)}`);
      return [];
    }
  }
}

进阶用法:服务端订单校验与消费确认

客户端支付成功后,必须到服务端校验订单真实性,然后确认消费。

// === 服务端订单校验代码(Node.js) ===
// order-verify-server.js

const axios = require('axios');

const IAP_CONFIG = {
  appId: 'your_app_id',
  appSecret: 'your_app_secret',
  verifyUrl: 'https://subscr-drcn.iap.cloud.huawei.com/v2/applications/purchases/verify',
  consumeUrl: 'https://subscr-drcn.iap.cloud.huawei.com/v2/applications/purchases/consume'
};

/**
 * 获取IAP AccessToken
 */
async function getIAPAccessToken(): Promise<string> {
  const response = await axios.post(
    'https://oauth-login.cloud.huawei.com/oauth2/v3/token',
    new URLSearchParams({
      grant_type: 'client_credentials',
      client_id: IAP_CONFIG.appId,
      client_secret: IAP_CONFIG.appSecret
    })
  );
  return response.data.access_token;
}

/**
 * 校验订单
 * 这是支付安全的核心——必须在服务端完成
 */
async function verifyPurchase(
  purchaseToken: string,
  productId: string,
  orderId: string
): Promise<{ valid: boolean; data?: any; error?: string }> {
  try {
    const token = await getIAPAccessToken();

    const response = await axios.post(
      IAP_CONFIG.verifyUrl,
      {
        purchaseToken: purchaseToken,
        productId: productId,
        orderId: orderId
      },
      {
        headers: {
          'Authorization': `Bearer ${token}`,
          'Content-Type': 'application/json'
        }
      }
    );

    const result = response.data;

    if (result.responseCode === '0') {
      // 校验通过
      console.info(`订单校验通过: orderId=${orderId}, productId=${productId}`);
      return {
        valid: true,
        data: {
          orderId: result.orderId,
          productId: result.productId,
          purchaseTime: result.purchaseTime,
          purchaseState: result.purchaseState,  // 0=已创建, 1=已取消
          consumptionState: result.consumptionState  // 0=未消费, 1=已消费
        }
      };
    } else {
      console.error(`订单校验失败: ${result.responseCode} - ${result.responseMessage}`);
      return { valid: false, error: result.responseMessage };
    }
  } catch (error) {
    console.error(`订单校验异常: ${JSON.stringify(error)}`);
    return { valid: false, error: '校验服务异常' };
  }
}

/**
 * 确认消费
 * 商品发放后必须调用,否则订单一直处于"已支付未消费"状态
 */
async function consumePurchase(
  purchaseToken: string,
  orderId: string
): Promise<boolean> {
  try {
    const token = await getIAPAccessToken();

    const response = await axios.post(
      IAP_CONFIG.consumeUrl,
      {
        purchaseToken: purchaseToken,
        orderId: orderId
      },
      {
        headers: {
          'Authorization': `Bearer ${token}`,
          'Content-Type': 'application/json'
        }
      }
    );

    if (response.data.responseCode === '0') {
      console.info(`订单已消费: orderId=${orderId}`);
      return true;
    } else {
      console.error(`订单消费失败: ${response.data.responseMessage}`);
      return false;
    }
  } catch (error) {
    console.error(`订单消费异常: ${JSON.stringify(error)}`);
    return false;
  }
}

/**
 * 完整的支付处理流程
 */
async function handlePurchase(
  purchaseToken: string,
  productId: string,
  orderId: string,
  userId: string
): Promise<{ success: boolean; error?: string }> {
  // 1. 校验订单
  const verifyResult = await verifyPurchase(purchaseToken, productId, orderId);
  if (!verifyResult.valid) {
    return { success: false, error: verifyResult.error || '订单校验失败' };
  }

  // 2. 检查订单是否已消费(防止重复发放)
  if (verifyResult.data.consumptionState === 1) {
    console.warn(`订单已消费,跳过: orderId=${orderId}`);
    return { success: true };  // 已消费,视为成功
  }

  // 3. 检查订单状态
  if (verifyResult.data.purchaseState !== 0) {
    return { success: false, error: '订单状态异常' };
  }

  // 4. 发放商品
  try {
    await deliverProduct(userId, productId);
    console.info(`商品已发放: userId=${userId}, productId=${productId}`);
  } catch (error) {
    console.error(`商品发放失败: ${JSON.stringify(error)}`);
    return { success: false, error: '商品发放失败' };
  }

  // 5. 确认消费
  const consumed = await consumePurchase(purchaseToken, orderId);
  if (!consumed) {
    // 商品已发放但消费确认失败——记录日志,人工处理
    console.error(`商品已发放但消费确认失败: orderId=${orderId}`);
    // 不返回失败,因为商品已经发了
  }

  return { success: true };
}

/**
 * 发放商品
 */
async function deliverProduct(userId: string, productId: string): Promise<void> {
  switch (productId) {
    case 'coin_100':
      // 增加100金币
      await addCoins(userId, 100);
      break;
    case 'coin_500':
      // 增加500金币
      await addCoins(userId, 500);
      break;
    case 'remove_ads':
      // 开通去广告
      await enableRemoveAds(userId);
      break;
    default:
      throw new Error(`未知商品: ${productId}`);
  }
}

module.exports = { verifyPurchase, consumePurchase, handlePurchase };

完整示例:生产级支付流程

把客户端支付、服务端校验、订单管理、异常处理串成完整链路。

// ProductionPaymentService.ets
import { iap } from '@kit.PaymentKit';

// 支付状态
enum PaymentState {
  IDLE = 'idle',
  PAYING = 'paying',
  VERIFYING = 'verifying',
  DELIVERING = 'delivering',
  COMPLETED = 'completed',
  FAILED = 'failed'
}

// 支付结果
interface PaymentResult {
  success: boolean;
  orderId: string;
  productId: string;
  error?: string;
  state: PaymentState;
}

export class ProductionPaymentService {
  private static instance: ProductionPaymentService;
  private iapService: iap.IAPService | null = null;
  private paymentState: PaymentState = PaymentState.IDLE;
  private stateListeners: Array<(state: PaymentState) => void> = [];

  private constructor() {}

  static getInstance(): ProductionPaymentService {
    if (!ProductionPaymentService.instance) {
      ProductionPaymentService.instance = new ProductionPaymentService();
    }
    return ProductionPaymentService.instance;
  }

  /**
   * 初始化
   */
  async init(): Promise<void> {
    try {
      this.iapService = iap.getService();

      // 检查是否有未完成的订单(App崩溃或网络中断导致)
      await this.checkPendingPurchases();

      console.info('[ProdPayment] 初始化完成');
    } catch (error) {
      console.error(`[ProdPayment] 初始化失败: ${JSON.stringify(error)}`);
    }
  }

  /**
   * 发起支付(完整流程)
   */
  async purchase(productId: string, productType: iap.ProductType): Promise<PaymentResult> {
    if (!this.iapService) {
      return this.failResult(productId, '支付服务未初始化');
    }

    if (this.paymentState === PaymentState.PAYING) {
      return this.failResult(productId, '正在支付中,请稍候');
    }

    const orderId = `order_${Date.now()}`;

    try {
      // 1. 发起支付
      this.updateState(PaymentState.PAYING);

      const purchaseRequest: iap.PurchaseRequest = {
        productId: productId,
        productType: productType,
        developerPayload: orderId
      };

      const purchaseResult = await this.iapService.createPurchase(purchaseRequest);

      if (!purchaseResult) {
        return this.failResult(productId, '支付结果为空');
      }

      // 2. 服务端校验
      this.updateState(PaymentState.VERIFYING);

      const verifyResult = await this.verifyOnServer(
        purchaseResult.getPurchaseToken(),
        productId,
        purchaseResult.getOrderId()
      );

      if (!verifyResult.success) {
        return this.failResult(productId, verifyResult.error || '订单校验失败');
      }

      // 3. 发放商品
      this.updateState(PaymentState.DELIVERING);

      const deliverResult = await this.deliverProduct(productId);
      if (!deliverResult) {
        return this.failResult(productId, '商品发放失败');
      }

      // 4. 确认消费(消耗型商品)
      if (productType === iap.ProductType.CONSUMABLE) {
        await this.confirmConsumption(
          purchaseResult.getPurchaseToken(),
          purchaseResult.getOrderId()
        );
      }

      // 5. 完成
      this.updateState(PaymentState.COMPLETED);

      return {
        success: true,
        orderId: purchaseResult.getOrderId(),
        productId: productId,
        state: PaymentState.COMPLETED
      };
    } catch (error) {
      this.updateState(PaymentState.FAILED);

      const payError = error as iap.IAPError;
      let errorMsg = '支付失败';
      if (payError.code === iap.IAPErrorCode.ORDER_CANCELLED) {
        errorMsg = '用户取消支付';
      }

      return this.failResult(productId, errorMsg);
    }
  }

  /**
   * 检查未完成的订单
   * App崩溃或网络中断时,可能有已支付但未消费的订单
   */
  private async checkPendingPurchases(): Promise<void> {
    if (!this.iapService) return;

    try {
      // 查询消耗型商品的未消费订单
      const consumablePurchases = await this.iapService.queryPurchases({
        productType: iap.ProductType.CONSUMABLE
      });

      for (const purchase of consumablePurchases) {
        console.info(`[ProdPayment] 发现未消费订单: ${purchase.getOrderId()}`);

        // 重新走校验和消费流程
        const verifyResult = await this.verifyOnServer(
          purchase.getPurchaseToken(),
          purchase.getProductId(),
          purchase.getOrderId()
        );

        if (verifyResult.success) {
          await this.deliverProduct(purchase.getProductId());
          await this.confirmConsumption(
            purchase.getPurchaseToken(),
            purchase.getOrderId()
          );
          console.info(`[ProdPayment] 未消费订单已处理: ${purchase.getOrderId()}`);
        }
      }

      // 查询非消耗型商品的已购记录
      const nonConsumablePurchases = await this.iapService.queryPurchases({
        productType: iap.ProductType.NONCONSUMABLE
      });

      // 恢复已购的非消耗型商品
      for (const purchase of nonConsumablePurchases) {
        await this.deliverProduct(purchase.getProductId());
      }
    } catch (error) {
      console.error(`[ProdPayment] 检查未完成订单失败: ${JSON.stringify(error)}`);
    }
  }

  /**
   * 服务端校验
   */
  private async verifyOnServer(
    purchaseToken: string,
    productId: string,
    orderId: string
  ): Promise<{ success: boolean; error?: string }> {
    try {
      // 调用你的服务端接口校验
      // const response = await apiClient.post('/payment/verify', {
      //   purchaseToken, productId, orderId
      // });
      // return response.data;

      console.info(`[ProdPayment] 服务端校验: orderId=${orderId}`);
      return { success: true }; // 简化实现
    } catch (error) {
      console.error(`[ProdPayment] 服务端校验失败: ${JSON.stringify(error)}`);
      return { success: false, error: '校验服务异常' };
    }
  }

  /**
   * 发放商品
   */
  private async deliverProduct(productId: string): Promise<boolean> {
    try {
      // 根据商品ID发放对应商品
      // 实际项目中调用你的业务服务
      console.info(`[ProdPayment] 商品发放: ${productId}`);
      return true;
    } catch (error) {
      console.error(`[ProdPayment] 商品发放失败: ${JSON.stringify(error)}`);
      return false;
    }
  }

  /**
   * 确认消费
   */
  private async confirmConsumption(
    purchaseToken: string,
    orderId: string
  ): Promise<void> {
    try {
      // 调用服务端确认消费API
      // await apiClient.post('/payment/consume', { purchaseToken, orderId });
      console.info(`[ProdPayment] 确认消费: orderId=${orderId}`);
    } catch (error) {
      // 消费确认失败不影响商品发放,记录日志后续处理
      console.error(`[ProdPayment] 消费确认失败: ${JSON.stringify(error)}`);
    }
  }

  /**
   * 注册状态监听
   */
  addStateListener(listener: (state: PaymentState) => void): void {
    this.stateListeners.push(listener);
  }

  private updateState(state: PaymentState): void {
    this.paymentState = state;
    this.stateListeners.forEach(l => {
      try { l(state); } catch (e) { /* 忽略 */ }
    });
  }

  private failResult(productId: string, error: string): PaymentResult {
    return {
      success: false,
      orderId: '',
      productId: productId,
      error: error,
      state: PaymentState.FAILED
    };
  }
}

// 页面中使用
@Entry
@Component
struct ShopPage {
  @State paymentState: PaymentState = PaymentState.IDLE;
  @State message: string = '';

  async aboutToAppear() {
    const service = ProductionPaymentService.getInstance();
    await service.init();
    service.addStateListener((state) => {
      this.paymentState = state;
    });
  }

  async buyCoins(productId: string) {
    this.message = '';
    const service = ProductionPaymentService.getInstance();
    const result = await service.purchase(productId, iap.ProductType.CONSUMABLE);

    if (result.success) {
      this.message = '购买成功!';
    } else {
      this.message = result.error || '购买失败';
    }
  }

  build() {
    Column() {
      Text('金币商城')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 24 })

      // 商品列表
      Row() {
        Column() {
          Text('100金币')
            .fontSize(18)
            .fontWeight(FontWeight.Medium)
          Text('¥6')
            .fontSize(16)
            .fontColor('#E65100')
            .margin({ top: 8 })
          Button('购买')
            .margin({ top: 12 })
            .enabled(this.paymentState !== PaymentState.PAYING)
            .onClick(() => this.buyCoins('coin_100'))
        }
        .padding(16)
        .borderRadius(12)
        .backgroundColor('#F5F5F5')
        .margin({ right: 12 })

        Column() {
          Text('500金币')
            .fontSize(18)
            .fontWeight(FontWeight.Medium)
          Text('¥25')
            .fontSize(16)
            .fontColor('#E65100')
            .margin({ top: 8 })
          Button('购买')
            .margin({ top: 12 })
            .enabled(this.paymentState !== PaymentState.PAYING)
            .onClick(() => this.buyCoins('coin_500'))
        }
        .padding(16)
        .borderRadius(12)
        .backgroundColor('#F5F5F5')
      }

      // 状态提示
      if (this.paymentState === PaymentState.PAYING) {
        LoadingProgress().width(32).height(32).margin({ top: 24 })
        Text('支付中...').fontSize(14).fontColor(Color.Gray).margin({ top: 8 })
      }

      if (this.message) {
        Text(this.message)
          .fontSize(16)
          .fontColor(this.paymentState === PaymentState.COMPLETED ? '#2E7D32' : '#C62828')
          .margin({ top: 16 })
      }
    }
    .padding(24)
    .width('100%')
    .height('100%')
  }
}

踩坑与注意事项

坑1:消耗型商品必须确认消费

消耗型商品支付成功后,如果你不调用消费确认API,订单一直处于"已支付未消费"状态。下次用户再买同一个商品,会收到"您已拥有此商品"的错误。

必须在校验通过并发放商品后,立刻调用消费确认API。 如果确认失败,记录日志,后续补确认。

坑2:未消费订单的恢复

App崩溃、网络中断、用户杀进程——这些情况下,可能存在"已支付但未消费"的订单。你必须在App启动时检查未消费订单,完成校验和消费流程。

否则用户付了钱,商品没到账,客服投诉就来了。

坑3:服务端校验是必须的

客户端的支付结果可以被篡改。如果你只在客户端判断支付是否成功,黑客可以绕过支付直接拿到商品。

所有支付结果的校验必须在服务端完成。 客户端收到支付成功回调后,把订单信息发给服务端,服务端调用华为校验API确认真实性。

坑4:商品ID必须和AGC控制台一致

客户端代码中的商品ID(如coin_100)必须和AGC控制台配置的完全一致,大小写敏感。你在控制台建的是Coin_100,代码里写coin_100,查询商品时直接返回空列表。

坑5:测试环境用测试账号

开发阶段不要用真实账号支付。在AGC控制台添加测试账号,测试账号支付不会真正扣钱。如果你用真实账号测试,每次支付都是真金白银——测试完了还得退款,麻烦。

坑6:对账不能少

华为的结算周期通常是月结。每月你需要在AGC控制台下载交易明细,和你的服务端订单数据对账。如果发现差异(比如华为记录了100笔订单,你只有99笔),需要排查原因。

建议:每天自动对账,发现差异及时处理。别等到月底才发现问题,那时候可能已经无法追溯了。

HarmonyOS 6适配说明

HarmonyOS 6对Payment Kit做了以下重要更新:

  1. 订阅型商品支持:这是最大的更新。HarmonyOS 6新增了订阅型商品(Subscription),支持月度/年度自动续费订阅。之前只支持一次性购买,现在会员订阅也能走IAP了。
// HarmonyOS 6订阅型商品
const subscriptionRequest: iap.PurchaseRequest = {
  productId: 'vip_monthly',
  productType: iap.ProductType.SUBSCRIPTION,
  developerPayload: `sub_${Date.now()}`
};

const result = await iapService.createPurchase(subscriptionRequest);
  1. 订阅管理:新增订阅管理API,用户可以查看当前订阅状态、取消订阅、修改订阅计划。
// 查询用户订阅状态
const subscriptions = await iapService.querySubscriptions({
  productId: 'vip_monthly'
});

// 取消订阅
await iapService.cancelSubscription(subscriptionId);
  1. 优惠码支持:新增优惠码(Promo Code)能力。运营可以生成优惠码,用户输入优惠码后以折扣价购买商品或免费试用订阅。

  2. 沙盒测试增强:沙盒测试环境新增了多种支付场景模拟,包括支付成功、支付失败、退款、订阅续费等,不需要真实支付就能测试所有流程。

  3. 订单通知推送:新增订单状态变更推送。当订单状态变化(如用户申请退款、订阅续费成功)时,服务端会收到Webhook通知,不需要轮询查询。

总结

Payment Kit让HarmonyOS应用接入IAP变得简单,但"简单"不等于"可以掉以轻心"。支付流程的每一步都关乎钱——校验少了,黑客能白嫖;消费确认忘了,用户买不了第二次;未完成订单不处理,用户付了钱收不到货。

核心记住三点:

  • 服务端校验是底线,客户端的支付结果不可信
  • 消耗型商品必须确认消费,不确认就买不了第二次
  • 启动时检查未完成订单,崩溃和网络中断是常态
评估维度 说明
学习难度 ⭐⭐⭐⭐ 基础支付简单,服务端校验和订单管理需要严谨的逻辑
使用频率 ⭐⭐⭐⭐ 有付费需求的应用必备
重要程度 ⭐⭐⭐⭐⭐ 支付出问题就是钱的问题,没有比这更重要的了

Payment Kit不是"可选项"——如果你的App有虚拟商品,不走IAP就上不了架。而且支付这东西,宁可多写十行校验代码,也不能少写一行。每一行代码都关乎钱。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。