文档管理中心

云侧服务准备

Payment Kit服务提供了支付系统相关的一系列API接口。为减少API接口的接入工作量,提供了相应java版本的Maven依赖用于云侧服务对接。

开发者可通过Maven集成来完成服务器开发环境的构建,以此来快速使用华为支付提供的API接口。

可下载示例代码-服务端用以快速完成商户服务器接入。

说明

因业务发展需要,API接口响应、通知回调请求字段可能会发生变动,如开发者自行实现验签逻辑,一定要使用原始的字符串转为Map对象进行验签处理后再转换成自定义的业务对象去使用,以确保验签正常通过。

约束与限制

商户使用提供的示例代码接入华为支付前请确保网络已正常连接,可以从华为支付仓库地址正常拉取Maven依赖。

开发环境:JDK 1.8及以上。

集成Maven依赖

在示例代码pom.xml文件中已配置仓库地址,如无法正常拉取依赖,可在Maven配置文件“settings.xml”中添加华为支付的Maven仓库地址。

<mirror>
  <id>central</id>
  <mirrorOf>central</mirrorOf>
  <url>https://developer.huawei.com/repo/</url>
</mirror>

在示例代码pom.xml文件中已引入jar包的Maven坐标。如商户自己构建工程则需在pom.xml文件中引入如下坐标:

<dependency>
  <groupId>com.huawei.petalpay</groupId>
  <artifactId>pay-java</artifactId>
  <version>1.0.0.331</version>
</dependency>

配置初始化

将以下商户相关配置添加到示例代码配置文件src/main/resources/petalpayconfig.properties。

  1. 商户号,获取方式请参见查询商户号信息。

  2. 商户私钥,获取途径请参考准备证书。

  3. 商户私钥签名类型,RSA或SM2。

  4. 商户证书ID,获取方式请参见上传商户证书。

  5. 鸿蒙支付服务验签公钥,获取方式请参见下载华为支付证书。

  6. 鸿蒙支付服务加密公钥(非必选),涉及接口入参敏感字段(接口会做标注)加密时需配置,参见敏感信息处理。

  7. 商户号关联的APPID,获取方式请参见AppID管理及关联。

配置内容示例如下:

# 商户号
PETALPAY.MERC_NO=121540000***
# 商户私钥
PETALPAY.MERC_PRIVATE_KEY=MIIJQwIBADAN=9w0BAQEFAASCCS0wg******************************CldcDlDCSsdfDceCSDr+RyvJdfcXssdEA=
# 商户证书ID
PETALPAY.MERC_AUTH_ID=101540200089***
# 商户私钥签名类型
PETALPAY.SIGN_TYPE=RSA
# 鸿蒙支付服务域名
PETALPAY.SERVER_HOST=https://petalpay-developer.cloud.huawei.com.cn
# 鸿蒙支付服务验签公钥
PETALPAY.HW_PAY_PUBLIC_KEY_FOR_CALLBACK=6D015316F09CB747E4467******************************DB46DA4BD0960ADD500D84912
# 鸿蒙支付服务加密公钥(非必选)
PETALPAY.HW_PUBLIC_KEY_FOR_SESSIONKEY=042A7D32FA19C29D3E722D6C4ACAC0B******************************E5A5B1C8120DAC9882E4B093B9CE7A38296F87F41747D319A
# 商户号关联的APPID
PETALPAY.APPID=111831***

业务接口请求

  1. 获取发起请求客户端对象

    可通过工具类MercConfigUtil提供的方法getMercConfig快速获取PetalPayConfig对象来构建请求客户端,对应配置项获取及配置参见配置初始化。

    // 商户配置
    public static PetalPayConfig getMercConfig() {
        return PetalPayConfig.builder().callerId(MERC_NO) // (必填)商户号
            .appId(APP_ID) // (必填)商户号关联的APPID
            .privateKey(MERC_PRIVATE_KEY) // (必填)商户私钥
            .authId(MERC_AUTH_ID) // (必填)商户证书ID
            .signType(SIGN_TYPE) // (选填)商户公私钥类型,默认RSA加密
            .petalpayPublicKey(HW_PAY_PUBLIC_KEY_FOR_CALLBACK) // (非必填)验签公钥(和接口级配置needVerifyRsp对应,公钥和商户通知回调验签公钥同一个)
            .domainHost(SERVER_HOST).build();
    }

    方式一:使用默认实现的请求客户端工具类。通过PetalPayConfig构建请求客户端对象示例如下:

    private static PetalPayClient payClient = new DefaultPetalPayClient(MercConfigUtil.getMercConfig());

    方式二:自定义实现请求客户端工具类。开发者如果需要自定义接口请求的client,用于处理请求中的日志打印等操作,可以通过继承PetalPayClient 来实现。示例如下:

    public class MercPetalPayClient extends PetalPayClient {
        public MercPetalPayClient(PetalPayConfig petalPayConfig) {
            super(petalPayConfig);
        }
        @Override
        public String doPost(String url, Map<String, String> headers, String requestBody) throws Exception {
            // 开发者业务侧post请求实现
        }
        @Override
        public String doGet(String url, Map<String, String> headers, String requestBody) throws Exception {
            // 开发者业务侧get请求实现
        }
    }
    说明
    • 需要使用最新开放的API接口,如示例代码未及时更新,未找到默认提供用于接口请求的对象信息,可直接调用PetalPayClient的execute方法构建接口请求。

    • 由于PetalPayClient.execute()方法及DefaultPetalPayClient实现涉及通过SecureRandom.getInstanceStrong()获取安全随机数,如果服务器熵值不足,可能会导致请求阻塞,以下处理方式可供参考:

      方式一:通过三方服务补充服务器熵值。以下为通过haveged服务补充熵值参考。

      # 查看熵值
      cat /proc/sys/kernel/random/entropy_avail
      # 安装haveged
      yum install haveged
      # 启动haveged
      systemctl start haveged.service
      # 开启haveged服务开机自启动
      systemctl enable haveged.service

      方式二:开发者参照DefaultPetalPayClient自定义实现请求客户端工具类并通过RequestConfig对象tranceId字段(建议每次请求都更新该字段)来主动传递tranceId,避免自动通过SecureRandom.getInstanceStrong()生成安全随机数导致请求阻塞。

      public <T> T execute(String httpMethod, String apiUrl, Class<T> rspType, RequestConfig requestConfig, Object requestObj) throws Exception;
  2. 组装请求参数

    预下单请求参数组装示例如下:

    public static PreOrderCreateRequestV2 getPreOrderCreateRequestV2() {
        return PreOrderCreateRequestV2.builder()
            .mercOrderNo("pay-example-" + System.currentTimeMillis()) // 每次订单号都要变,请将pay-example-修改为商户自己的订单前缀
            .appId(MercConfigUtil.APP_ID)  // appId,需要配置为与商户绑定的正确的appId
            .mercNo(MercConfigUtil.MERC_NO) // 商户的商户号
            .tradeSummary("请修改为对应的商品简称") // 请修改为商品简称
            .totalAmount(2L)
            .callbackUrl("https://www.xxxxxx.com/hw/pay/callback") //回调通知地址,通知URL必须为直接可访问的URL,要求为https地址。最大长度为512。请替换为格式正确的结果通知回调地址。
            .build();
    }
  3. 请求业务接口

    不同API接口调用URI不一样,详情请参见API参考文档。

    APP预下单请求示例如下:

    public CommonResponse aggrPreOrderForAppV2() {
        // 组装对象
        PreOrderCreateRequestV2 preOrderReq = getPreOrderCreateRequestV2();
        PreOrderCreateResponse response = null;
        try {
            response = payClient.execute("POST", "/api/v2/aggr/preorder/create/app", PreOrderCreateResponse.class,
                preOrderReq);
        } catch (Exception e) {
            // todo 异常处理
            log.error("request error ", e);
            return CommonResponse.buildErrorRsp(e.getMessage());
        }
        if (!validResponse(response)) {
            // todo 异常处理
            log.error("response is invalid ", response);
            return CommonResponse.buildFailRsp(response);
        }
        // 获取prepayId构建orderStr返回
        return CommonResponse.buildSuccessRsp(payClient.buildOrderStr(response.getPrepayId()));
    }
    public static boolean validResponse(BaseGwRspWithSign rsp) {
        return rsp != null && "000000".equals(rsp.getResultCode());
    }

通知回调处理

可使用VerifyTools.getCallbackResult方法自动处理回调结果验签并返回响应给华为支付服务器以及实现CallBackHandleInterface接口来处理回调结果。

通知回调处理示例如下:

public CallBackBaseResponse transactionResultNotify(@RequestBody Object callbackRequest) {
    String callbackStr = JSONObject.toJSONString(callbackRequest);
    return VerifyTools.getCallbackResult(callbackStr, MercConfigUtil.HW_PAY_PUBLIC_KEY_FOR_CALLBACK, new CallBackHandleInterface() {
        @Override
         public void fail(CallBackBaseResponse response, String reqString) {
              // 未获取到字节流或者验签失败
              // 商户自己业务处理
              log.error("CallBack failed: ", response != null ? response.getResultCode() : null);
         }
         @Override
         public void success(String reqString) {
              NotifyPaymentReq callbackReq = JSONObject.parseObject(reqString, NotifyPaymentReq.class);
              // 验签成功,商户自己业务处理
         }
    });
}

关于通知回调更多具体要求可参考通知回调接口说明。

敏感信息处理

为了保证API接口请求通信过程中敏感信息字段(如用户的住址、银行卡号、手机号码等,涉及加密字段会在具体API接口中标注)的机密性,鸿蒙支付服务要求加密上送。 如开发者使用对应业务接口涉及字段加密,请联系华为侧工程师获取对应的SM2加密公钥(合作咨询可点击此处)。

涉及密钥:

展开
密钥类型 来源 作用
SM2 华为支付提供。 加密开发者生成的SM4密钥。
SM4 开发者生成。 加密敏感信息字段。

处理逻辑:

1、开发者生成SM4对称密钥,用于加密敏感数据内容并作为入参传递给华为支付。

2、开发者使用华为支付提供的SM2密钥加密生成的SM4对称密钥并通过PayMercAuth.sessionKey字段传递给华为支付用于解密敏感信息。

本质为SM4对称密钥加密,示例代码如下:

import com.huawei.petalpay.paymentservice.apiservice.client.model.MgmtSubmercRsp;
import com.huawei.petalpay.paymentservice.core.client.DefaultPetalPayClient;
import com.huawei.petalpay.paymentservice.core.client.PetalPayClient;
import com.huawei.petalpay.paymentservice.core.config.RequestConfig;
import com.huawei.petalpay.paymentservice.core.tools.SM4Util;
import com.huawei.petalpay.paymentservice.example.common.MercConfigUtil;

public class SignRegister {
    public static void main(String[] args) {
        PetalPayClient payClient = new DefaultPetalPayClient(MercConfigUtil.getMercConfig());
        String sessionKey = SM4Util.getSM4GCMSessionKey();
        String message = "xxxxxx";
        RegisterSubmercReq req = new RegisterSubmercReq(SM4Util.getSM4GCMContent(sessionKey, message));
        RequestConfig config = RequestConfig.builder()
            .publicKeyForSessionKey(MercConfigUtil.HW_PUBLIC_KEY_FOR_SESSIONKEY)
            .sessionKey(sessionKey)
            .build();
        try {
            MgmtSubmercRsp response = payClient.execute("POST", "/api/v1/partner/mgmt/submerc/register",
                MgmtSubmercRsp.class, config, req);
        } catch (Exception e) {
            // todo 异常处理
        }
    }
    static class RegisterSubmercReq {
        private String message;
        public RegisterSubmercReq(String message) {
            this.message = message;
        }
    }
}