diff --git a/README.en.md b/README.en.md deleted file mode 100644 index 6287907df297cf7d612e8218c03e4e9bb084cc2b..0000000000000000000000000000000000000000 --- a/README.en.md +++ /dev/null @@ -1,36 +0,0 @@ -# Paymentkit-Codelab-Serverdemo-Java - -#### Description -{**When you're done, you can delete the content in this README and update the file with details for others getting started with your repository**} - -#### Software Architecture -Software architecture description - -#### Installation - -1. xxxx -2. xxxx -3. xxxx - -#### Instructions - -1. xxxx -2. xxxx -3. xxxx - -#### Contribution - -1. Fork the repository -2. Create Feat_xxx branch -3. Commit your code -4. Create Pull Request - - -#### Gitee Feature - -1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md -2. Gitee blog [blog.gitee.com](https://blog.gitee.com) -3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore) -4. The most valuable open source project [GVP](https://gitee.com/gvp) -5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help) -6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) diff --git a/README.md b/README.md index 22eb4ef568cfcf324d6ebbbc8154480481ca4420..7fe7e76598216e5573ef0a92aa9c14ba9ffdf33f 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,287 @@ -# Paymentkit-Codelab-Serverdemo-Java +# 华为支付服务 -#### 介绍 -{**以下是 Gitee 平台说明,您可以替换此简介** -Gitee 是 OSCHINA 推出的基于 Git 的代码托管平台(同时支持 SVN)。专为开发者提供稳定、高效、安全的云端软件开发协作平台 -无论是个人、团队、或是企业,都能够用 Gitee 实现代码托管、项目管理、协作开发。企业项目请看 [https://gitee.com/enterprises](https://gitee.com/enterprises)} +## 介绍 +华为支付云侧接口 Java SDK Sample。 -#### 软件架构 -软件架构说明 +官方 Java 语言开发库`pay-java`由 `core` 和 `service` 组成: +- `core` 为基础库。包含自动签名和验签的 HTTP 客户端、回调处理、加解密库。 +- `service` 为业务服务。基于业务场景提供不同的业务类,其下的方法为对应的http接口。 +本示例展示通过`pay-java`使用华为支付服务提供的`预下单`,`预签约`API接口并对`回调通知`和`敏感信息加解密`等其他内容进行说明。 -#### 安装教程 +需要使用华为支付服务接口: +1. 预下单:/api/v2/aggr/preorder/create/app (参考[直连商户-预下单](https://developer.huawei.com/consumer/cn/doc/HMSCore-References/api-android-pre-pay-order-0000001589121249)) +2. 预签约:/api/v2/contract/presign/app (参考[直连商户-预签约](https://developer.huawei.com/consumer/cn/doc/HMSCore-References/api-app-presign-0000001680178917)) +3. 更多接口详细查看:[华为支付服务API参考](https://developer.huawei.com/consumer/cn/doc/HMSCore-References/payment-service-api-overview-0000001535983144) -1. xxxx -2. xxxx -3. xxxx +## 效果预览 -#### 使用说明 +| **运行成功日志输出** | +|------------------------| +| ![run_log.png](run_log.png) | -1. xxxx -2. xxxx -3. xxxx +## demo工程的配置与使用 -#### 参与贡献 +### 配置demo工程的步骤如下 -1. Fork 本仓库 -2. 新建 Feat_xxx 分支 -3. 提交代码 -4. 新建 Pull Request +1. 配置好java环境:JDK 1.8及以上。 +2. 配置好Maven集成环境。 +3. 获取商户配置后将配置添加到配置文件petalpayconfig.properties。 +4. 启动demo工程:src/main/java/com.huawei.petalpay.paymentservice.example/ExampleApplication.java +5. 详细可参见指南[开发准备](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/payment-preparations-0000001716034257-V5)。 +## 工程目录 +``` +pay-example + └─src + ├─main + │ ├─java + │ │ └─com + │ │ └─huawei + │ │ └─example + │ │ ├─common // 通用类包 + │ │ └─controller // 程序接口类包 + │ └─resources // 资源目录 + └─test // 测试类目录 +``` -#### 特技 +## 具体实现 +### 预下单: +- 商户服务器按照商户模型调用华为支付提供的[直连商户预下单](https://developer.huawei.com/consumer/cn/doc/HMSCore-References/api-android-pre-pay-order-0000001589121249)或[服务商/平台类商户预下单](https://developer.huawei.com/consumer/cn/doc/HMSCore-References/api-partner-base-payment-app-prepay-0000001644063453)接口。 +- 获取预下单号(prepayId),然后组建orderStr返回给客户端。 + +使用示例如下: +```java +/** Native 支付下单为例 */ +public class PrepayQuickStart { + public static PreOrderCreateRequest getRequest() { + return PreOrderCreateRequestV2.builder() + .mercOrderNo("pay-example-" + System.currentTimeMillis()) // 每次订单号都要变,请将pay-example-修改为商户自己的订单前缀 + .appId(MercConfigUtil.APP_ID) // appId,需要配置为与商户绑定的正确的appId + .mercNo(MercConfigUtil.MERC_NO) // 商户的商户号 + .tradeSummary("请修改为对应的商品简称") // 请修改为商品简称 + .bizType("100002") // (100001:虚拟商品购买,100002:实物商品购买,100003:预付类账号充值,100004:航旅交通服务,100005:活动票务订购,100006:商业服务消费,100007:生活服务消费,100008:租金缴纳,100009:会员费缴纳,100011:其他商家消费,100037:公共便民服务) + .totalAmount(2L) + .callbackUrl("https://www.xxxxxx.com/hw/pay/callback") //回调通知地址,通知URL必须为直接可访问的URL,要求为https地址。最大长度为512。请替换为格式正确的结果通知回调地址。 + .build(); + } + + public static void main(String[] args) { + DefaultPetalPayClient payClient = new DefaultPetalPayClient(MercConfigUtil.getMercConfig()); + // 配置请求参数 + AggrPay aggrPay = new AggrPay(payClient); + // 组装对象 + PreOrderCreateRequest preOrderReq = getRequest(); + PreOrderCreateResponse response = null; + try { + response = aggrPay.aggrPreOrderForApp(preOrderReq); + } catch (Exception e) { + System.out.println(e); + } + System.out.println(JsonUtils.obj2Json(response)); + } +} +``` + +### 预签约: +- 商户服务器按照商户模型调用华为支付提供的[直连商户预签约](https://developer.huawei.com/consumer/cn/doc/HMSCore-References/api-app-presign-0000001680178917)接口。 +- 获取预下单号(preSignNo),构建contractStr参数,然后返回给客户端。 + +使用示例如下: +```java +import com.huawei.petalpay.paymentservice.core.tools.JsonUtils; + +public class PrepayQuickStart { + public static void main(String[] args) { + System.out.println(JsonUtils.obj2Json(contractPreSignAppV2())); + } + public CommonResponse contractPreSignAppV2() { + DefaultPetalPayClient payClient = new DefaultPetalPayClient(MercConfigUtil.getMercConfig()); + // 组装对象 + PreSignRequestV2 preSignReq = getPreSignRequestV2(); + PreSignResponse response = null; + try { + response = payClient.execute("POST", "/api/v2/contract/presign/app", PreSignResponse.class, preSignReq); + } 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); + } + return CommonResponse.buildSuccessRsp(payClient.buildContractStr(response.getPreSignNo())); + } + + public static boolean validResponse(BaseGwRspWithSign rsp) { + return rsp != null || "000000".equals(rsp.getResultCode()); + } + + private PreSignRequestV2 getPreSignRequestV2() { + return PreSignRequestV2.builder().appId(MercConfigUtil.APP_ID) // appId,需要配置为与商户绑定的正确的appId + .mercContractCode("pay-example-" + System.currentTimeMillis()) // 签约协议号,每次请求都要变,请将pay-example-修改为商户自己的订单前缀 + .mercNo(MercConfigUtil.MERC_NO) // 商户号 + .planId("100") // 协议模板ID,该模板ID是商户在向华为支付提交代扣权限申请时由华为支付生成。请填写正确的协议模板ID。 + .callbackUrl("https://www.xxxxxx.com/hw/sign/callback") //回调通知地址,通知URL必须为直接可访问的URL,要求为https地址。最大长度为512。请替换为格式正确的结果通知回调地址。 + .build(); + } +} +``` +> 从示例可见,使用 SDK 不需要计算请求签名和验证应答签名。 + +参考: +1. pay-example/src/main/java/com.huawei.petalpay.paymentservice.example/controller/MercApiController.java + +### 回调通知 +- 开发者需要在开发者的服务器上创建一个公开的 HTTP 端点,接受来自华为支付的回调通知。 +- 接收到回调通知,使用VerifyTools.getCallbackResult方法来验证回调通知并实现CallBackHandleInterface接口来处理回调结果。 + +使用示例如下: +```java +public class CallbackController { + /** + * 华为支付通知回调签名公钥 + */ + public static final String HW_PAY_PUBLIC_KEY_FOR_CALLBACK = ""; + + /** + * 支付回调模拟接口(不同的场景,用不同的接口处理) + * + * @param request 入参 + * @return CallBackBaseResponse + */ + @PostMapping(value = "/v1/transation/result", produces = MediaType.APPLICATION_JSON_VALUE) + public CallBackBaseResponse transationResultNotify(HttpServletRequest request) { + // TransResultCallbackReq-支付回调实体类 + return VerifyTools.getCallbackResult(request, HW_PAY_PUBLIC_KEY_FOR_CALLBACK, reqString -> { + NotifyPaymentReq callbackReq = JSONObject.parseObject(reqString, NotifyPaymentReq.class); + // 商户自行业务处理 + doProcess(callbackReq); + }); + } + + /** + * 业务处理 + * + * @param reqBody + */ + private void doProcess(Object reqBody) { + log.info("Please write merchant business process here"); + } +} +``` +目前不同通知业务结果的通知类存在差异,对应的映射关系如下: +- NotifyPaymentReq: 支付及代扣结果回调 +- NotifyRefundReq: 退款结果回调 +- NotifyContractReq: 签约结果回调 +- NotifyAllocReq: 分账结果回调 +- NotifyReclaimAllocReq: 分账回收结果回调 +- NotifyCombinedTransactionReq: 合单支付结果回调 + +### 敏感信息加解密 + +为了保证通信过程中敏感信息字段(如用户的住址、银行卡号、手机号码等)的机密性, 华为支付要求加密上送的敏感信息。对应的字段在api接口文档中标注。 +使用示例如下: +```java +static class RegisterSubmercReq { + private String message; + public RegisterSubmercReq(String message) { + this.message = message; + } +} +public static void main(String[] args) { + PetalPayClient payClient = new DefaultPetalPayClient(MercConfigUtil.getMercConfig()); + String sessionKey = SM4Util.getSM4GCMSessionKey(); + String message = "xxxx"; + 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) { + System.out.println(e); + } + System.out.println(JsonUtils.obj2Json(response)); +} +``` + +### 自定义httpClient + +SDK 使用 [HttpClient] 作为默认的 HTTP 客户端。 开发者可以直接使用 DefaultPetalPayClient来发起http请求。 + +开发者如果需要自定义接口请求的client以做请求中的日志打印等操作,可以通过继承PetalpayClient来实现: +```java +public class MercPetalPayClient extends PetalPayClient { + public MercPetalPayClient(PetalPayConfig petalPayConfig) { + super(petalPayConfig); + } + + @Override + public String doPost(String url, Map headers, String requestBody) throws Exception { + // todo + } + + @Override + public String doGet(String url, Map headers, String requestBody) throws Exception { + // todo + } +} +``` + +### 新增或拓展业务接口 +开发者如果未及时更新SDK,需要使用最新的http接口,可直接调用petalpayClient的execute方法,进行接口请求。 + +对应的方法示例如下: +```java + execute(String httpMethod,String apiUrl,Class rspType); + execute(String httpMethod,String apiUrl,Class rspType, Object requestObj); + execute(String httpMethod,String apiUrl,Class rspType, RequestConfig requestConfig, Object requestObj); +``` + +## 依赖 +- 仓库地址 +```xml + + central + central + https://developer.huawei.com/repo/ + +``` +- Maven依赖 +```xml + + com.huawei.petalpay + pay-java + 1.0.0.295 + +``` + +## 约束与限制 +**通知回调接口** +- 对于回调通知,如果华为支付未收到application/json类型响应的数据,或收到应答数据不是{"resultCode":"000000","resultDesc":"Success."} ,华为支付会通过一定的周期定期重新发起通知,但不保证通知最终能成功。 +- 相同通知可能多次重复发送给商户服务器,商户服务器需要正确实现以应对重复请求,处理建议: + - 在商户服务器收到通知进行业务处理前先检查对应业务状态,对于未处理过的场景才进行业务处理。已处理的场景则直接返回成功。 + - 在业务处理时,合理设计同步机制防止并发问题。 +- 如果在预期时间内未收到Payment Kit的回调请求,请排查提供的callbackUrl网络是否连通。如排除网络连通性问题,请调用同步查询接口确认订单状态。排查建议: + - 确认callbackUrl为商户系统真实地址,保证url中的域名或IP是外网可以正常访问的。不能填写localhost、127.0.0.1、192.168.x.x、10.xx.xx.xx等。 + - callbackUrl必须为https://开头的完整地址。 +- 对于收到的异步回调请求,请务必进行验签处理并在验签通过后进行后续业务流程。否则可能因为信息泄露导致对商户潜在的攻击,造成资金损失。 +- 因商户自身系统实现问题导致的业务异常,资金损失,由商户自行承担。 +- 如商户对支付回调地址有IP防火墙策略限制,需要对以下网段开通允许名单,后续有变动时会在此处更新。 + - 124.70.118.0/24 + - 139.159.166.0/24 +- 商户系统收到回调通知时,需要在3秒内返回应答响应,否则华为支付会认为通知失败,会触发重试机制。 +- 商户系统收到异步通知并返回{"resultCode":"000000","resultDesc":"Success."} 时,服务器异步通知参数 callbackId 才会失效。同一个异步通知请求的多次重试callbackId是不变的。 + +**接口约束** +- 请勿将开发者的服务器的IP允许清单设置成用于限制华为的出口IP地址。IP允许清单本身并不能提高安全性且会给业务发展带来约束,在消息层面已有更安全的RSA签名机制条件下,没有存在价值。若开发者不遵守此约定带来的后果将由开发者自行承担。 +- 地址必须支持HTTPS协议且具有合法商用证书,否则无法正常接收通知消息。 +- 支持的TLS协议版本:1.2 / 1.3。 +- 支持的加密套件列表: + > TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,TLS_DHE_RSA_WITH_AES_256_GCM_SHA384,TLS_DHE_DSS_WITH_AES_128_GCM_SHA256,TLS_DHE_DSS_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_AES_128_GCM_SHA256 -1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md -2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com) -3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目 -4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目 -5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help) -6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..d52c8b2d3b897d30cf61a465686bf6f43ef4effb --- /dev/null +++ b/pom.xml @@ -0,0 +1,75 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.5.4 + + + com.huawei.petalpay + pay-example + 1.0.0 + pay-example + + UTF-8 + 1.8 + yyyyMMddHHmmss + ${project.version}.${maven.build.timestamp} + + + + + + com.huawei.petalpay + pay-java + 1.0.0.295 + compile + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.projectlombok + lombok + + + + com.alibaba + fastjson + 1.2.76 + + + junit + junit + test + + + + + + central + central + https://developer.huawei.com/repo/ + + true + + + true + always + + + + + diff --git a/run_log.png b/run_log.png new file mode 100644 index 0000000000000000000000000000000000000000..9701c3cdf3f6d32a5835904499f0f4a805c26457 Binary files /dev/null and b/run_log.png differ diff --git a/src/main/java/com/huawei/petalpay/paymentservice/example/ExampleApplication.java b/src/main/java/com/huawei/petalpay/paymentservice/example/ExampleApplication.java new file mode 100644 index 0000000000000000000000000000000000000000..d9687fb6ee3bb416c64aad3cbd04ea35b364e2b8 --- /dev/null +++ b/src/main/java/com/huawei/petalpay/paymentservice/example/ExampleApplication.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2024-2024. All rights reserved. + */ + +package com.huawei.petalpay.paymentservice.example; + +import com.huawei.petalpay.paymentservice.core.tools.JsonUtils; +import com.huawei.petalpay.paymentservice.example.common.CommonResponse; +import com.huawei.petalpay.paymentservice.example.controller.MercApiController; + +import lombok.extern.slf4j.Slf4j; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * 服务启动类 + * + * @author 华为支付 + * @since 2024-02-22 + */ +@Slf4j +@SpringBootApplication +public class ExampleApplication { + public static void main(String[] args) { + SpringApplication.run(ExampleApplication.class, args); + test(); + } + + /** + * 验证接口调用 + */ + public static void test() { + MercApiController mercApiController = new MercApiController(); + CommonResponse rsp = null; + rsp = mercApiController.aggrPreOrderForAppV2(); + log.info("aggrPreOrderForAppV2 rsp: " + JsonUtils.obj2Json(rsp)); + + rsp = mercApiController.contractPreSignAppV2(); + log.info("contractPreSignAppV2 rsp: " + JsonUtils.obj2Json(rsp)); + } +} diff --git a/src/main/java/com/huawei/petalpay/paymentservice/example/common/CommonResponse.java b/src/main/java/com/huawei/petalpay/paymentservice/example/common/CommonResponse.java new file mode 100644 index 0000000000000000000000000000000000000000..e78560a42805e9627cef3ebad76859e2f1a61b5b --- /dev/null +++ b/src/main/java/com/huawei/petalpay/paymentservice/example/common/CommonResponse.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2024-2024. All rights reserved. + */ + +package com.huawei.petalpay.paymentservice.example.common; + +import lombok.Getter; +import lombok.Setter; + +/** + * 通用响应结构体,商户请根据自己业务定义 + * + * @author 华为支付 + * @since 2024-02-22 + */ +@Getter +@Setter +public class CommonResponse { + private String resultCode; + + private String subDesc; + + private Object result; + + public static CommonResponse buildSuccessRsp(Object result) { + CommonResponse rsp = new CommonResponse(); + rsp.setResultCode("000000"); + rsp.setSubDesc("SUCCESS"); + rsp.setResult(result); + return rsp; + } + + public static CommonResponse buildFailRsp(Object result) { + CommonResponse rsp = new CommonResponse(); + rsp.setResultCode("-1"); + rsp.setSubDesc("FAIL"); + rsp.setResult(result); + return rsp; + } + + public static CommonResponse buildErrorRsp(String errMsg) { + CommonResponse rsp = new CommonResponse(); + rsp.setResultCode("-1"); + rsp.setSubDesc("ERROR"); + rsp.setResult(errMsg); + return rsp; + } +} diff --git a/src/main/java/com/huawei/petalpay/paymentservice/example/common/MercConfigUtil.java b/src/main/java/com/huawei/petalpay/paymentservice/example/common/MercConfigUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..16bedc666f89e170e4cbb70b1e9159526ca579aa --- /dev/null +++ b/src/main/java/com/huawei/petalpay/paymentservice/example/common/MercConfigUtil.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2024-2024. All rights reserved. + */ + +package com.huawei.petalpay.paymentservice.example.common; + +import com.huawei.petalpay.paymentservice.core.config.PetalPayConfig; +import com.huawei.petalpay.paymentservice.core.constant.SignType; + +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.util.Properties; + +/** + * 获取商户配置信息的工具类 + * + * @author 华为支付 + * @since 2024-02-22 + */ +@Slf4j +public class MercConfigUtil { + /** + * 商户秘钥 + */ + public static final String MERC_PRIVATE_KEY; + + /** + * 应用id + */ + public static final String APP_ID; + + /** + * 商户号 + */ + public static final String MERC_NO; + + /** + * 商户证书序列号 + */ + public static final String MERC_AUTH_ID; + + /** + * 商户公私钥类型 + */ + public static final SignType SIGN_TYPE; + + /** + * 请求域名 + */ + public static final String SERVER_HOST; + + /** + * rsp和通知回调验签公钥 + */ + public static final String HW_PAY_PUBLIC_KEY_FOR_CALLBACK; + + /** + * SESSSIONKEY加密公钥 + */ + public static final String HW_PUBLIC_KEY_FOR_SESSIONKEY; + + static { + Properties p2 = new Properties(); + try { + p2.load(MercConfigUtil.class.getClassLoader().getResourceAsStream("petalpayconfig.properties")); + } catch (IOException e) { + log.error("get petalpayconfig.properties error: {}", e.getMessage()); + } + MERC_PRIVATE_KEY = p2.getProperty("PETALPAY.MERC_PRIVATE_KEY"); + MERC_NO = p2.getProperty("PETALPAY.MERC_NO"); + APP_ID = p2.getProperty("PETALPAY.APPID"); + MERC_AUTH_ID = p2.getProperty("PETALPAY.MERC_AUTH_ID"); + SIGN_TYPE = "RSA".equals(p2.getProperty("PETALPAY.SIGN_TYPE")) ? SignType.RSA : SignType.SM2; + SERVER_HOST = p2.getProperty("PETALPAY.SERVER_HOST"); + HW_PAY_PUBLIC_KEY_FOR_CALLBACK = p2.getProperty("PETALPAY.HW_PAY_PUBLIC_KEY_FOR_CALLBACK"); + HW_PUBLIC_KEY_FOR_SESSIONKEY = p2.getProperty("PETALPAY.HW_PUBLIC_KEY_FOR_SESSIONKEY"); + } + + // 商户配置 + public static PetalPayConfig getMercConfig() { + return PetalPayConfig.builder().callerId(MERC_NO) // (必填)商户号 + .appId(APP_ID).privateKey(MERC_PRIVATE_KEY) // (必填) 商户秘钥 + .authId(MERC_AUTH_ID) // (必填) 商户证书序列号 + .signType(SIGN_TYPE) // (选填) 商户公私钥类型,默认RSA加密 + .petalpayPublicKey(HW_PAY_PUBLIC_KEY_FOR_CALLBACK) // (非必填) 验签公钥(和接口级配置needVerifyRsp对应,公钥和商户通知回调验签公钥同一个) + .domainHost(SERVER_HOST).build(); + } + +} diff --git a/src/main/java/com/huawei/petalpay/paymentservice/example/controller/CallbackController.java b/src/main/java/com/huawei/petalpay/paymentservice/example/controller/CallbackController.java new file mode 100644 index 0000000000000000000000000000000000000000..182398b6d4cb982c3e5fd192dcc9d389cd2948ea --- /dev/null +++ b/src/main/java/com/huawei/petalpay/paymentservice/example/controller/CallbackController.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2024-2024. All rights reserved. + */ + +package com.huawei.petalpay.paymentservice.example.controller; + +import com.huawei.petalpay.paymentservice.apiservice.client.model.NotifyPaymentReq; +import com.huawei.petalpay.paymentservice.core.callback.CallBackBaseResponse; +import com.huawei.petalpay.paymentservice.core.tools.VerifyTools; + +import com.alibaba.fastjson.JSONObject; + +import lombok.extern.slf4j.Slf4j; + +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 华为支付通知回调商户的接口 + * + * @author 华为支付 + * @since 2023-10-28 + */ +@Slf4j +@RestController +@RequestMapping(path = "/test/merc/callback", produces = MediaType.APPLICATION_JSON_VALUE) +public class CallbackController { + /** + * 华为支付通知回调签名公钥 + */ + public static final String HW_PAY_PUBLIC_KEY_FOR_CALLBACK = ""; + + /** + * 支付回调模拟接口(不同的场景,用不同的接口处理) + * + * @param callbackRequest 入参 + * @return CallBackBaseResponse + */ + @PostMapping(value = "/v1/transation/result", produces = MediaType.APPLICATION_JSON_VALUE) + public CallBackBaseResponse transationResultNotify(@RequestBody Object callbackRequest) { + String callbackStr = JSONObject.toJSONString(callbackRequest); + // TransResultCallbackReq-支付回调实体类 + return VerifyTools.getCallbackResult(callbackStr, HW_PAY_PUBLIC_KEY_FOR_CALLBACK, reqString -> { + NotifyPaymentReq callbackReq = JSONObject.parseObject(reqString, NotifyPaymentReq.class); + // 商户自行业务处理 + doProcess(callbackReq); + }); + } + + /** + * 业务处理 + * + * @param reqBody + */ + private void doProcess(Object reqBody) { + log.info("Please write merchant business process here"); + } +} diff --git a/src/main/java/com/huawei/petalpay/paymentservice/example/controller/MercApiController.java b/src/main/java/com/huawei/petalpay/paymentservice/example/controller/MercApiController.java new file mode 100644 index 0000000000000000000000000000000000000000..42a3c60e4d364ada922147feb11ab6fe76f4852f --- /dev/null +++ b/src/main/java/com/huawei/petalpay/paymentservice/example/controller/MercApiController.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2024-2024. All rights reserved. + */ + +package com.huawei.petalpay.paymentservice.example.controller; + +import com.huawei.petalpay.paymentservice.apiservice.client.model.BaseGwRspWithSign; +import com.huawei.petalpay.paymentservice.apiservice.client.model.PreOrderCreateRequestV2; +import com.huawei.petalpay.paymentservice.apiservice.client.model.PreOrderCreateResponse; +import com.huawei.petalpay.paymentservice.apiservice.client.model.PreSignRequestV2; +import com.huawei.petalpay.paymentservice.apiservice.client.model.PreSignResponse; +import com.huawei.petalpay.paymentservice.core.client.DefaultPetalPayClient; +import com.huawei.petalpay.paymentservice.core.client.PetalPayClient; +import com.huawei.petalpay.paymentservice.example.common.CommonResponse; +import com.huawei.petalpay.paymentservice.example.common.MercConfigUtil; + +import lombok.extern.slf4j.Slf4j; + +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 商户对华为支付相关接口的调用 + * + * @author 华为支付 + * @since 2023-10-28 + */ +@Slf4j +@RestController +@RequestMapping(path = "/test/merc/api", produces = MediaType.APPLICATION_JSON_VALUE) +public class MercApiController { + + private static PetalPayClient payClient = new DefaultPetalPayClient(MercConfigUtil.getMercConfig()); + + /** + * 预下单接口调用 + */ + @PostMapping(value = "/preorder", produces = MediaType.APPLICATION_JSON_VALUE) + 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); + } + return CommonResponse.buildSuccessRsp(payClient.buildOrderStr(response.getPrepayId())); + } + + /** + * 预签约接口调用 + */ + @PostMapping(value = "/presign", produces = MediaType.APPLICATION_JSON_VALUE) + public CommonResponse contractPreSignAppV2() { + // 组装对象 + PreSignRequestV2 preSignReq = getPreSignRequestV2(); + PreSignResponse response = null; + try { + response = payClient.execute("POST", "/api/v2/contract/presign/app", PreSignResponse.class, preSignReq); + } 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); + } + return CommonResponse.buildSuccessRsp(payClient.buildContractStr(response.getPreSignNo())); + } + + public static boolean validResponse(BaseGwRspWithSign rsp) { + return rsp != null && "000000".equals(rsp.getResultCode()); + } + + /** + * 预下单接口请求参数组装,商户请根据业务自行实现 + */ + 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("请修改为对应的商品简称") // 请修改为商品简称 + .bizType("100002") // (100001:虚拟商品购买,100002:实物商品购买,100003:预付类账号充值,100004:航旅交通服务,100005:活动票务订购,100006:商业服务消费,100007:生活服务消费,100008:租金缴纳,100009:会员费缴纳,100011:其他商家消费,100037:公共便民服务) + .totalAmount(2L) + .callbackUrl("https://www.xxxxxx.com/hw/pay/callback") //回调通知地址,通知URL必须为直接可访问的URL,要求为https地址。最大长度为512。请替换为格式正确的结果通知回调地址。 + .build(); + } + + /** + * 预签约接口请求参数组装,商户请根据业务自行实现 + */ + private PreSignRequestV2 getPreSignRequestV2() { + return PreSignRequestV2.builder() + .appId(MercConfigUtil.APP_ID) // appId,需要配置为与商户绑定的正确的appId + .mercContractCode("pay-example-" + System.currentTimeMillis()) // 签约协议号,每次请求都要变,请将pay-example-修改为商户自己的订单前缀 + .mercNo(MercConfigUtil.MERC_NO) // 商户号 + .planId("100") // 协议模板ID,该模板ID是商户在向华为支付提交代扣权限申请时由华为支付生成。请填写正确的协议模板ID。 + .callbackUrl("https://www.xxxxxx.com/hw/sign/callback") //回调通知地址,通知URL必须为直接可访问的URL,要求为https地址。最大长度为512。请替换为格式正确的结果通知回调地址。 + .build(); + } +} diff --git a/src/main/resources/petalpayconfig.properties b/src/main/resources/petalpayconfig.properties new file mode 100644 index 0000000000000000000000000000000000000000..67a31c50d57e90a3c7fc32c421907b12ee6172e9 --- /dev/null +++ b/src/main/resources/petalpayconfig.properties @@ -0,0 +1,16 @@ +# 商户私钥 +PETALPAY.MERC_PRIVATE_KEY= +# 商户号 +PETALPAY.MERC_NO= +# 商户证书id +PETALPAY.MERC_AUTH_ID= +# 商户私钥签名类型 +PETALPAY.SIGN_TYPE= +# 华为支付服务域名 +PETALPAY.SERVER_HOST=https://petalpay-developer.cloud.huawei.com.cn +# 华为支付服务回调验签公钥 +PETALPAY.HW_PAY_PUBLIC_KEY_FOR_CALLBACK= +# 华为支付服务验签公钥 sessionkey +PETALPAY.HW_PUBLIC_KEY_FOR_SESSIONKEY= +# 商户号关联的APPID +PETALPAY.APPID= \ No newline at end of file diff --git a/src/test/java/com/huawei/petalpay/paymentservice/example/QuickRequestApiTest.java b/src/test/java/com/huawei/petalpay/paymentservice/example/QuickRequestApiTest.java new file mode 100644 index 0000000000000000000000000000000000000000..34197df8403d8f1034cf523b5597162b4326daa1 --- /dev/null +++ b/src/test/java/com/huawei/petalpay/paymentservice/example/QuickRequestApiTest.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2024-2024. All rights reserved. + */ + +package com.huawei.petalpay.paymentservice.example; + +import com.huawei.petalpay.paymentservice.core.client.DefaultPetalPayClient; +import com.huawei.petalpay.paymentservice.core.client.PetalPayClient; +import com.huawei.petalpay.paymentservice.core.tools.JsonUtils; +import com.huawei.petalpay.paymentservice.example.common.CommonResponse; +import com.huawei.petalpay.paymentservice.example.common.MercConfigUtil; + +import lombok.extern.slf4j.Slf4j; + +import org.apache.commons.lang3.StringUtils; +import org.junit.Test; + +import java.util.Map; + +/** + * 快速发起一个请求 + * + * @author 华为支付 + * @since 2024-04-19 + */ +@Slf4j +public class QuickRequestApiTest { + private static PetalPayClient payClient = new DefaultPetalPayClient(MercConfigUtil.getMercConfig()); + + private Object request(String httpMethod, String apiUrl, String bodyStr) { + try { + Map body = null; + if (StringUtils.isNotBlank(bodyStr)){ + body = JsonUtils.json2Obj(bodyStr, Map.class); + } + return payClient.execute(httpMethod, apiUrl, Object.class, body); + } catch (Exception e) { + log.error("request error ", e); + return CommonResponse.buildErrorRsp(e.getMessage()); + } + } + + @Test + public void quick_send_request_by_post_test() { + log.info("RESPONSE: " + JsonUtils.obj2Json(request( + "POST", + "/api/v2/aggr/preorder/create/app", + "{}" + ))); + } + + @Test + public void quick_send_request_by_get_test() { + log.info("RESPONSE: " + JsonUtils.obj2Json(request( + "GET", + "/api/v2/aggr/transactions/merc-orders/{mercOrderNo}", + null + ))); + } +} \ No newline at end of file