华为支付作为 HarmonyOS 生态中的核心支付能力,为开发者提供了安全、便捷的支付解决方案。本文将系统解析华为支付的商户模型、支付能力、接入流程及代码实现,通过图表、流程图和实战代码,帮助开发者快速完成支付功能集成。

一、华为支付能力全景概览

华为支付收银台是一个集订单管理、支付渠道整合、安全验证于一体的综合支付解决方案,其核心优势体现在:

  • 多场景适配:支持电商、娱乐、零售、交通等全品类业务
  • 灵活的商户模型:满足直连商户、平台类商户、服务商等不同业务模式
  • 全链路安全保障:基于 SM2 加密算法和华为终端安全能力,确保支付信息安全
  • 极简接入流程:提供标准化 API,减少开发成本,加快上线速度

1.1 支付能力矩阵

华为支付提供四类核心支付能力,覆盖不同业务场景:

支付类型 核心功能 支持商户类型 典型应用场景
单次支付 完成单一订单的支付流程 直连商户、平台类商户、服务商 商品购买、服务付费
合单支付 合并多个商户的订单为一个支付单 平台类商户 电商平台多店铺联合结算
支付并签约 支付完成后自动签订代扣协议 直连商户、服务商 会员订阅、服务套餐购买
签约代扣 预先签约后自动完成扣款 直连商户、服务商 水电费代缴、自动充值

说明:"签约代扣" 需用户预先授权,适用于周期性扣款场景,可大幅减少用户操作步骤。

二、商户模型深度解析

华为支付支持三种商户模型,开发者需根据自身业务模式选择适配的接入方式:

2.1 商户模型对比表

模型类型 定义 核心能力 接入主体 典型案例
直连商户 直接与华为支付签约的商户 支持单次支付、支付并签约、签约代扣 企业或个体工商户 独立电商 APP、品牌官网
平台类商户 聚合多个子商户的平台型企业 支持合单支付,可配置分账规则 电商平台、服务聚合平台 综合电商平台、生活服务平台
服务商 为子商户提供支付技术服务的企业 代子商户发起支付,支持分账 支付解决方案提供商 收银系统服务商、SaaS 平台

2.2 分账能力说明

平台类商户和合单支付支持灵活分账,可通过 API 设置分账接收方及比例:

  • 支持最多 10 个分账接收方
  • 分账比例精确到 0.01%
  • 支持实时分账和延迟分账两种模式

三、支付全流程解析(含流程图)

华为支付收银台的接入流程涉及商户客户端、商户服务器、华为支付服务器三方交互,核心步骤包括订单创建、预下单、拉起收银台、支付结果处理等。

3.1 完整业务流程图

 

3.2 关键步骤说明

  1. 订单创建:商户客户端向自身服务器提交商品信息,生成商户侧订单号(需确保唯一性)。
  2. 预下单:商户服务器调用华为支付预下单接口,获取prepayId(华为支付侧唯一订单标识)。
  3. 订单字符串组装:商户服务器将prepayIdapp_idtimestamp等信息签名后,生成orderStr返回给客户端。
  4. 拉起收银台:客户端调用requestPayment接口,传入orderStr,调起华为支付客户端。
  5. 支付结果处理
    • 同步结果:华为支付客户端直接返回支付状态(适用于 UI 展示)。
    • 异步回调:华为支付服务器向商户服务器发送带签名的支付结果(用于业务最终确认)。
  6. 签名验证:商户服务器必须验证回调结果的签名,防止数据篡改。

四、客户端集成实战(ArkTS 代码)

4.1 环境准备

  • 依赖配置:在module.json5中添加支付权限和依赖:

{
  "module": {
    "reqPermissions": [
      { "name": "ohos.permission.PAYMENT" }
    ],
    "dependencies": {
      "@kit.PaymentKit": { "version": "1.0.0" }
    }
  }
}

 

  • 参数说明orderStr需包含以下核心字段(由商户服务器生成):

    • app_id:应用在华为开发者联盟的唯一标识
    • merc_no:商户号(华为支付签约后分配)
    • prepay_id:预下单接口返回的预订单号
    • timestamp:时间戳(秒级)
    • noncestr:随机字符串(32 位以内)
    • sign:签名结果(SM2 算法)

4.2 拉起收银台代码实现

import { BusinessError } from '@kit.BasicServicesKit';
import { paymentService } from '@kit.PaymentKit';
import { common } from '@kit.AbilityKit';
import promptAction from '@ohos.promptAction';

@Entry
@Component
struct PaymentPage {
  // 获取应用上下文
  private context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
  // 商户订单号(从商户服务器获取)
  @State merchantOrderNo: string = '';
  // 支付状态
  @State payStatus: string = '未支付';

  build() {
    Column() {
      Text('商品:华为Mate 60 Pro')
        .fontSize(18)
        .margin(10)
      Text('金额:5999.00元')
        .fontSize(16)
        .fontColor('#ff4d4f')
        .margin(10)
      Text(`支付状态:${this.payStatus}`)
        .fontSize(14)
        .margin(20)
      Button('确认支付')
        .type(ButtonType.Capsule)
        .width('60%')
        .height(48)
        .backgroundColor('#1677ff')
        .onClick(() => this.startPayment())
    }
    .width('100%')
    .height('100%')
    .padding(20)
  }

  /**
   * 发起支付流程
   */
  private async startPayment() {
    try {
      // 1. 向商户服务器请求创建订单
      this.merchantOrderNo = await this.createMerchantOrder();
      if (!this.merchantOrderNo) {
        promptAction.showToast({ message: '创建订单失败' });
        return;
      }

      // 2. 获取orderStr(由商户服务器生成)
      const orderStr = await this.getOrderStr();
      if (!orderStr) {
        promptAction.showToast({ message: '获取支付参数失败' });
        return;
      }

      // 3. 调用华为支付收银台
      this.payStatus = '支付中...';
      await this.requestPayment(orderStr);
      
      // 4. 支付成功后查询最终结果(可选,建议依赖服务端回调)
      const result = await this.queryPaymentResult();
      if (result === 'SUCCESS') {
        this.payStatus = '支付成功';
        promptAction.showToast({ message: '支付成功' });
      }
    } catch (error) {
      const err = error as BusinessError;
      this.payStatus = `支付失败:${err.message}`;
      promptAction.showToast({ message: `支付失败:${err.code}-${err.message}` });
    }
  }

  /**
   * 调用华为支付API拉起收银台
   */
  private requestPayment(orderStr: string): Promise<void> {
    return new Promise((resolve, reject) => {
      paymentService.requestPayment(this.context, orderStr)
        .then(() => {
          // 同步结果:支付流程完成(需以服务端回调为准)
          resolve();
        })
        .catch((error: BusinessError) => {
          reject(error);
        });
    });
  }

  /**
   * 向商户服务器请求创建订单
   */
  private createMerchantOrder(): Promise<string> {
    // 实际开发中替换为商户服务器接口
    return new Promise(resolve => {
      setTimeout(() => {
        // 生成模拟订单号
        resolve(`MERCH_${Date.now()}`);
      }, 500);
    });
  }

  /**
   * 从商户服务器获取orderStr
   */
  private getOrderStr(): Promise<string> {
    // 实际开发中替换为商户服务器接口
    return new Promise(resolve => {
      setTimeout(() => {
        // 注意:以下为模拟数据,实际需由商户服务器生成并签名
        const mockOrderStr = JSON.stringify({
          app_id: '1000000001',
          merc_no: '202405120001',
          prepay_id: `PREPAY_${Date.now()}`,
          timestamp: Math.floor(Date.now() / 1000).toString(),
          noncestr: 'a1b2c3d4e5f6g7h8i9j0',
          sign: '模拟签名(实际为SM2加密结果)'
        });
        resolve(mockOrderStr);
      }, 800);
    });
  }

  /**
   * 查询支付最终结果(向商户服务器查询)
   */
  private queryPaymentResult(): Promise<string> {
    // 实际开发中替换为商户服务器查询接口
    return new Promise(resolve => {
      setTimeout(() => {
        resolve('SUCCESS'); // 模拟支付成功
      }, 1000);
    });
  }
}

4.3 错误码处理机制

支付过程中可能遇到多种异常,需针对性处理:

错误码 含义 处理建议
6001 用户取消支付 提示用户 "已取消支付,可重新发起"
6002 支付参数错误 检查 orderStr 格式及签名有效性
6003 网络异常 提示用户检查网络,并重试支付
6004 支付渠道不可用 切换其他支付方式(如支持)
6005 订单已支付 跳转到支付成功页面,避免重复支付

五、服务端集成关键步骤(Java 示例)

商户服务端需完成预下单、签名生成、支付结果验签三个核心步骤,确保支付流程的安全性和准确性。

5.1 预下单接口调用

import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.client.RestTemplate;

public class HuaweiPayService {
    // 华为支付预下单接口地址(正式环境)
    private static final String PREPAY_URL = "https://pay.cloud.huawei.com/pay/v1/prepay";
    // 商户号(华为支付签约后分配)
    private String merchantNo;
    // 应用ID(华为开发者联盟申请)
    private String appId;
    // 商户私钥(用于签名)
    private String privateKey;

    /**
     * 调用华为支付预下单接口
     */
    public PrepayResponse createPrepay(PrepayRequest request) {
        // 1. 组装请求参数
        HuaweiPrepayRequest huaweiRequest = new HuaweiPrepayRequest();
        huaweiRequest.setAppId(appId);
        huaweiRequest.setMercNo(merchantNo);
        huaweiRequest.setOutTradeNo(request.getMerchantOrderNo());
        huaweiRequest.setTotalAmount(request.getTotalAmount()); // 单位:分
        huaweiRequest.setCurrency("CNY");
        huaweiRequest.setSubject(request.getProductName());
        huaweiRequest.setNotifyUrl("https://商户域名/pay/callback"); // 支付结果回调地址
        huaweiRequest.setTimestamp(String.valueOf(System.currentTimeMillis() / 1000));
        huaweiRequest.setNonceStr(generateNonceStr());

        // 2. 生成签名
        String sign = Sm2Utils.sign(privateKey, buildSignStr(huaweiRequest));
        huaweiRequest.setSign(sign);

        // 3. 发送请求
        RestTemplate restTemplate = new RestTemplate();
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<HuaweiPrepayRequest> httpRequest = new HttpEntity<>(huaweiRequest, headers);
        
        return restTemplate.postForObject(PREPAY_URL, httpRequest, PrepayResponse.class);
    }

    /**
     * 生成随机字符串
     */
    private String generateNonceStr() {
        return java.util.UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
    }

    /**
     * 构建签名原文(按ASCII排序)
     */
    private String buildSignStr(HuaweiPrepayRequest request) {
        // 签名参数需按key的ASCII顺序排序,拼接格式:key=value&key=value
        return String.format("appId=%s&currency=%s&mercNo=%s&nonceStr=%s&notifyUrl=%s&outTradeNo=%s&subject=%s&timestamp=%s&totalAmount=%s",
                request.getAppId(),
                request.getCurrency(),
                request.getMercNo(),
                request.getNonceStr(),
                request.getNotifyUrl(),
                request.getOutTradeNo(),
                request.getSubject(),
                request.getTimestamp(),
                request.getTotalAmount()
        );
    }
}

5.2 支付结果回调与验签

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class PaymentCallbackController {
    // 华为支付公钥(用于验签,从华为商户平台获取)
    private static final String HUAWEI_PUBLIC_KEY = "华为支付公钥";

    /**
     * 华为支付结果回调接口
     */
    @PostMapping("/pay/callback")
    public String handleCallback(@RequestBody PaymentCallback callback) {
        try {
            // 1. 验证签名
            boolean verifySuccess = verifySign(callback);
            if (!verifySuccess) {
                // 签名验证失败,返回错误
                return "fail";
            }

            // 2. 处理支付结果
            String tradeStatus = callback.getTradeStatus();
            if ("SUCCESS".equals(tradeStatus)) {
                // 支付成功:更新商户订单状态
                updateOrderStatus(callback.getOutTradeNo(), "PAID");
                // 处理分账(如需要)
                if (needSplit(callback)) {
                    processSplit(callback);
                }
            } else {
                // 支付失败:记录失败原因
                logPaymentFailure(callback.getOutTradeNo(), callback.getFailReason());
            }

            // 3. 返回处理结果(必须返回"success",否则华为支付会重试回调)
            return "success";
        } catch (Exception e) {
            // 异常处理:记录日志,返回失败(华为支付会重试)
            return "fail";
        }
    }

    /**
     * 验证回调签名
     */
    private boolean verifySign(PaymentCallback callback) {
        // 1. 构建签名原文(按ASCII排序)
        String signStr = buildCallbackSignStr(callback);
        // 2. 使用华为公钥验证签名
        return Sm2Utils.verify(HUAWEI_PUBLIC_KEY, signStr, callback.getSign());
    }

    /**
     * 构建回调签名原文
     */
    private String buildCallbackSignStr(PaymentCallback callback) {
        // 按华为支付文档要求的字段顺序拼接
        return String.format("appId=%s&mercNo=%s&outTradeNo=%s&tradeNo=%s&tradeStatus=%s&totalAmount=%s&timestamp=%s",
                callback.getAppId(),
                callback.getMercNo(),
                callback.getOutTradeNo(),
                callback.getTradeNo(),
                callback.getTradeStatus(),
                callback.getTotalAmount(),
                callback.getTimestamp()
        );
    }
}

六、安全机制详解

华为支付采用多层次安全保障机制,确保支付全流程的安全性:

6.1 数据传输安全

  • 所有接口通信强制使用 HTTPS 加密通道
  • 敏感信息(如银行卡号)传输时额外加密

6.2 签名机制(SM2 非对称加密)

  • 商户侧使用私钥对请求参数签名
  • 华为支付侧使用商户公钥验证签名
  • 回调结果使用华为私钥签名,商户使用华为公钥验签

6.3 订单安全

  • prepayId有效期为 30 分钟,超时后需重新预下单
  • 订单号(outTradeNo)唯一,防止重复支付
  • 支付金额与预下单金额严格校验,防止金额篡改

6.4 终端安全

  • 依赖华为终端的 TEE(可信执行环境)存储支付密码
  • 支持指纹、人脸等生物识别验证,减少密码泄露风险

七、接入 Checklist 与最佳实践

7.1 接入前检查项

  •  已在华为开发者联盟完成应用注册并获取app_id
  •  已完成华为支付商户签约,获取merc_no和密钥
  •  服务端已部署 SM2 加解密算法(推荐使用华为提供的 SDK)
  •  回调接口已部署并能正确返回 "success"(区分大小写)
  •  已准备测试环境的商户账号和测试金额(支持 0.01 元测试)

7.2 最佳实践建议

  1. 支付结果确认:客户端同步结果仅用于 UI 展示,最终以服务端回调为准
  2. 订单状态管理:建议设置订单超时时间(如 15 分钟),超时后自动取消
  3. 异常处理:实现支付结果查询接口,用于解决支付状态不一致问题
  4. 用户体验:支付过程中显示明确的加载状态,支付完成后提供清晰的结果页和后续操作指引
  5. 日志记录:详细记录支付各环节日志,便于问题排查

八、常见问题与解决方案

问题场景 可能原因 解决方案
预下单接口返回 "签名错误" 1. 签名参数顺序错误
2. 私钥与商户号不匹配
3. 包含空值参数
1. 按 ASCII 顺序排序参数
2. 核对商户号与密钥是否匹配
3. 移除值为 null 的参数
收银台调起失败 1. orderStr格式错误
2. 应用未声明支付权限
3. 设备未安装华为支付客户端
1. 检查orderStr是否为标准 JSON
2. 确认已添加ohos.permission.PAYMENT权限
3. 提示用户安装华为支付
回调接口未收到通知 1. 回调 URL 不可达
2. 接口返回非 "success"
3. 网络防火墙拦截
1. 确保 URL 公网可访问
2. 验证通过后返回 "success"
3. 开放华为支付服务器 IP 白名单
支付成功但订单状态未更新 1. 回调处理失败
2. 商户服务器宕机
1. 实现定时任务查询订单状态
2. 部署高可用服务架构

通过本文的指南,开发者可系统掌握华为支付收银台的集成要点,从商户模型选择到支付流程落地,再到安全机制保障,全方位覆盖支付功能开发需求。华为支付的标准化接口和完善的安全机制,将帮助开发者快速构建稳定、安全的支付体验,助力业务增长。

Logo

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

更多推荐