系列生态共建篇·第53篇。跨端篇后,有开源爱好者问:“我在电商Demo里写了很多通用组件(如SKU选择器、地址联动),能不能抽离出来给社区用?怎么做成标准的OpenHarmony三方库?” 这正是开源生态的魅力。今天我们将电商Demo中的通用支付模块SKU选择组件抽离、封装,发布为一个标准的OpenHarmony三方库(HAR包),并上架到OHPM(OpenHarmony Package Manager)仓库。我们将覆盖库工程搭建、API设计、文档撰写、单元测试、CI发布全流程。全程基于API23,含官方文档未涉及的“多目标构建”和“语义化版本控制”技巧。

一、前言:为什么“造轮子”也要讲姿势?

很多开发者写过“工具类”,但那只是“代码片段”。真正的三方库需要具备:

  1. 独立性:不依赖具体业务(如电商Demo),可独立编译和运行。

  2. 通用性:API设计抽象,能适应多种场景(如支付模块支持支付宝、微信、银联)。

  3. 稳定性:经过充分测试,版本迭代不破坏兼容性。

  4. 易用性:文档齐全,示例清晰,一键集成。

OHPM是OpenHarmony的官方包管理器,类似于npm(Node.js)或Maven(Android)。今天,我们将把电商Demo中的“支付功能”提炼成一个名为@harmony/payment-kit的高质量三方库,并贡献给开源社区。

二、核心概念辨析(代码片段 vs 三方库)

维度

代码片段 (Utils/Snippets)

三方库 (Library/HAR)

复用性

低,需复制粘贴修改

高,一键集成 (ohpm install)

维护性

差,分散在各项目中

好,集中维护,版本化管理

测试

无或简陋

完善,包含单元测试、集成测试

文档

注释为主

独立文档、API参考、示例工程

依赖

隐式依赖项目环境

显式声明依赖,自动解决

发布

口头分享

OHPM中央仓库,可检索

三、代码实现:从“业务代码”到“开源库”

3.1 创建HAR库工程

步骤1:新建Library Module

在DevEco Studio中:File -> New -> Module -> Static Library (HAR)

命名为payment-kit

步骤2:工程结构规划



payment-kit/
├── src/main/ets/
│   ├── components/      # UI组件(如支付密码弹窗)
│   │   └── PayPasswordDialog.ets
│   ├── core/            # 核心逻辑
│   │   ├── PaymentManager.ets
│   │   └── ChannelAdapter.ets
│   ├── models/          # 数据模型
│   │   └── PaymentInfo.ets
│   ├── utils/           # 工具类
│   │   └── SignUtil.ets
│   ├── index.ets        # 对外暴露的API入口(关键!)
│   └── resources/       # 资源文件
├── src/test/ets/        # 单元测试
├── oh-package.json5     # 库配置文件(类似package.json)
└── README.md            # 项目说明文档

3.2 抽离核心逻辑:支付管理器

创建src/main/ets/core/PaymentManager.ets



// 定义支付渠道枚举
export enum PayChannel {
  ALIPAY = 'alipay',
  WECHAT = 'wechat',
  UNIONPAY = 'unionpay',
  HUAWEI_IAP = 'huawei_iap' // 华为IAP
}

// 定义支付结果回调
export interface PaymentCallback {
  onSuccess?(result: PaymentResult): void
  onFailed?(code: number, msg: string): void
  onCancel?(): void
}

// 支付管理器(单例)
export class PaymentManager {
  private static instance: PaymentManager
  private channels: Map<PayChannel, ChannelAdapter> = new Map()
  private currentCallback: PaymentCallback | null = null

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

  /**
   * 注册支付渠道适配器
   */
  registerChannel(channel: PayChannel, adapter: ChannelAdapter): void {
    this.channels.set(channel, adapter)
    console.log(`支付渠道注册成功: ${channel}`)
  }

  /**
   * 发起支付
   */
  pay(info: PaymentInfo, callback: PaymentCallback): void {
    this.currentCallback = callback
    const adapter = this.channels.get(info.channel)
    
    if (!adapter) {
      callback.onFailed?.(-1, `支付渠道 ${info.channel} 未注册`)
      return
    }

    // 参数校验
    if (!this.validateParams(info)) {
      callback.onFailed?.(-2, '支付参数校验失败')
      return
    }

    // 调用具体渠道的支付逻辑
    adapter.pay(info, {
      onSuccess: (result) => {
        this.handleSuccess(result)
      },
      onFailed: (code, msg) => {
        this.handleFailed(code, msg)
      },
      onCancel: () => {
        this.handleCancel()
      }
    })
  }

  /**
   * 参数校验
   */
  private validateParams(info: PaymentInfo): boolean {
    if (!info.orderId || !info.amount || info.amount <= 0) {
      return false
    }
    return true
  }

  private handleSuccess(result: PaymentResult): void {
    console.log('支付成功:', result)
    this.currentCallback?.onSuccess?.(result)
  }

  private handleFailed(code: number, msg: string): void {
    console.error('支付失败:', code, msg)
    this.currentCallback?.onFailed?.(code, msg)
  }

  private handleCancel(): void {
    console.log('支付取消')
    this.currentCallback?.onCancel?.()
  }
}

// 渠道适配器接口(策略模式)
export interface ChannelAdapter {
  pay(info: PaymentInfo, callback: PaymentCallback): void
}

3.3 实现具体渠道:华为IAP适配器

创建src/main/ets/core/adapters/HuaweiIAPAdapter.ets



import { iap } from '@kit.IAPKit'
import { PaymentCallback, ChannelAdapter, PaymentInfo, PaymentResult } from '../PaymentManager'

export class HuaweiIAPAdapter implements ChannelAdapter {
  async pay(info: PaymentInfo, callback: PaymentCallback): Promise<void> {
    try {
      // 1. 创建订单
      const order = await iap.createPurchaseOrder({
        productId: info.productId!,
        quantity: info.quantity || 1
      })

      // 2. 发起支付
      const payResult = await iap.pay(order)

      // 3. 处理支付结果
      if (payResult.returnCode === 0) {
        const result: PaymentResult = {
          orderId: info.orderId,
          transactionId: payResult.inAppPurchaseData?.inAppPurchaseData?.orderId || '',
          channel: 'huawei_iap',
          rawData: JSON.stringify(payResult)
        }
        callback.onSuccess?.(result)
      } else {
        callback.onFailed?.(payResult.returnCode, payResult.errMsg || '支付失败')
      }
    } catch (err) {
      console.error('华为IAP支付异常:', err)
      callback.onFailed?.(-3, '支付过程发生异常')
    }
  }
}

3.4 定义对外API(入口文件)

关键src/main/ets/index.ets 是库的“脸面”,必须清晰、简洁。



// 核心类
export { PaymentManager } from './core/PaymentManager'
export { HuaweiIAPAdapter } from './core/adapters/HuaweiIAPAdapter'
// 导出枚举和接口,方便使用者
export { PayChannel } from './core/PaymentManager'
export type { PaymentCallback, PaymentResult } from './core/PaymentManager'
export type { PaymentInfo } from './models/PaymentInfo'

// 提供便捷的初始化函数
import { PaymentManager } from './core/PaymentManager'
import { HuaweiIAPAdapter } from './core/adapters/HuaweiIAPAdapter'

export function initPaymentKit(): PaymentManager {
  const manager = PaymentManager.getInstance()
  // 默认注册华为IAP渠道
  manager.registerChannel(PayChannel.HUAWEI_IAP, new HuaweiIAPAdapter())
  return manager
}

3.5 配置库信息(oh-package.json5)



{
  "name": "@harmony/payment-kit",
  "version": "1.0.0",
  "description": "A universal payment kit for HarmonyOS, supporting multiple channels.",
  "main": "src/main/ets/index.ets",
  "author": "listening777",
  "license": "Apache-2.0",
  "keywords": ["harmonyos", "payment", "iap", "alipay", "wechat"],
  "repository": {
    "type": "git",
    "url": "https://gitee.com/your_repo/payment-kit.git"
  },
  "dependencies": {
    "@ohos/iap": "^1.0.0" // 声明对IAP Kit的依赖
  },
  "devDependencies": {
    "@ohos/hypium": "^1.0.0" // 单元测试框架
  },
  "ohos": {
    "minAPIVersion": 11, // 支持的最低API版本
    "targetAPIVersion": 12 // 目标API版本
  }
}

3.6 编写README.md(门面担当)



# @harmony/payment-kit

一个用于HarmonyOS的通用支付聚合库,旨在简化多支付渠道的集成流程。

## 特性
- 🚀 **一键集成**:一行代码初始化,支持链式调用。
- 🔌 **可扩展**:通过适配器模式轻松接入新支付渠道。
- 🛡️ **类型安全**:完整的TypeScript类型定义。
- 📱 **跨端支持**:基于ArkUI-X,支持HarmonyOS、Android、iOS。

## 安装

bash

ohpm install @harmony/payment-kit



## 快速开始

typescript

import { initPaymentKit, PayChannel, PaymentInfo } from '@harmony/payment-kit'

// 1. 初始化

const paymentKit = initPaymentKit()

// 2. 构建支付信息

const info: PaymentInfo = {

orderId: 'ORDER_123456',

amount: 99.8,

currency: 'CNY',

channel: PayChannel.HUAWEI_IAP,

productId: 'product_001', // 华为IAP商品ID

subject: '测试商品'

}

// 3. 发起支付

paymentKit.pay(info, {

onSuccess: (result) => {

console.log('支付成功:', result.transactionId)

},

onFailed: (code, msg) => {

console.error('支付失败:', code, msg)

},

onCancel: () => {

console.log('用户取消支付')

}

})



## API文档
### PaymentManager
- `registerChannel(channel: PayChannel, adapter: ChannelAdapter)`: 注册支付渠道。
- `pay(info: PaymentInfo, callback: PaymentCallback)`: 发起支付。

### PaymentInfo
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| orderId | string | 是 | 商户订单号 |
| amount | number | 是 | 支付金额 |
| channel | PayChannel | 是 | 支付渠道 |
| productId | string | 否 | 商品ID(IAP需要) |

## 贡献指南
欢迎PR!请确保:
1. 代码通过`ohpm run lint`检查。
2. 新增功能包含单元测试。
3. 更新README文档。

## 许可证
Apache License 2.0

四、踩坑记录(官方文档没写的开源细节)

  1. API设计的“洁癖”:三方库的API一旦发布,修改成本极高。原则:宁缺毋滥。不要在1.0.0版本暴露过多的内部方法。使用export严格控制对外API,内部类使用internal或文件夹隔离。

  2. 资源命名的“隔离”:如果库中使用了图片、字符串等资源,务必添加前缀(如pk_),防止与主工程资源冲突。例如$r('app.media.pk_pay_icon')

  3. 多目标构建(Multi-target Build):如果库需要支持HarmonyOS和OpenHarmony(社区版),需要注意API差异。使用条件编译:

    
      
    
      
    // 条件编译:仅HarmonyOS支持
    // @ts-ignore
    if (canIUse('SystemCapability.ArkUI.ArkUI.Full')) {
      // HarmonyOS特有逻辑
    }
  4. 版本号的“敬畏”:严格遵守语义化版本(SemVer)主版本.次版本.修订号

    • 主版本:不兼容的API修改(如重构了支付流程)。

    • 次版本:向后兼容的功能新增(如增加了新的支付渠道)。

    • 修订号:向后兼容的问题修正(如修复了某个NullPointerException)。

  5. OHPM发布的“门槛”:首次发布需要实名认证(个人或企业)。包名(name)必须全局唯一,且不能以@ohos/开头(那是官方包)。建议使用@组织名/包名的格式。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐