JAVA云侧SDK设计开发实践
云侧接口SDK设计思路
SDK设计的初衷,是为了简化商户接入的过程。在此前的支撑沟通中,发现主要的问题在于签名验签等固有机制的处理上,因此需要将该部分逻辑封装,使开发者聚焦于接口业务出入参的处理上。
然而业务当前仍处于快速迭代发展过程中,不断存在新的产品能力及开放接口上线,为了便于后续快速更迭,sdk内接口的新增需要能做到流程体系化——依托于云侧接口的原始设计,可以自动生成新的sdk接口及配套验证demo。
- 整个业务对接过程的配置,涉及到全局的对接配置放到一个类中,涉及到单个请求的配置,放到一个类中。
- 使用全局配置类初始化client对象,在调用方法时单接口的配置可选传入。
- 为了便于商户使用自定义的http client。对外定义了doPost和doGet接口使业务开发者自由实现。
- 最终对外暴露一个execute(String httpMethod, String apiUrl, Class rspType, RequestConfig requestConfig, Object requestObj)方法。这里除了requestConfig和requestObj,其他的接口信息,都可以在接口定义中提取到。
- 最后使用swagger-codegen插件,配合自定义的mustache模板,生成GenerateApiService.generateFunc方法,该方法内,入参就仅需传入入参及请求配置了。最终暴露到开发者层面的使用也将非常简洁。
- 同理,也可以模板化自动生成test用例,使用接口设计时定义的示例入参做连通性访问的测试。
uml: 核心类关系
@startuml
package "customer domain" #DDDDDD {
class BaseClient {
<<abstract>>
- GlobalConfig config
+BaseClient(config)
+execute(String httpMethod, String apiUrl, Class<T> rspType, RequestConfig requestConfig,
Object requestObj)
+execute(String httpMethod, String apiUrl, Class<T> rspType, Object requestObj)
+executeexecute(String httpMethod, String apiUrl, Class<T> rspType)
#doPost(String url, Map<String, String> headers, String requestBody)
#doGet(String url, Map<String, String> headers)
}
class DefaultClient {
#doPost(String url, Map<String, String> headers, String requestBody)
#doGet(String url, Map<String, String> headers)
}
BaseClient <|-- DefaultClient
class BaseApiService {
# BaseClient baseClient
+ BaseApiService(baseClient)
}
class GenerateApiService {
+ generateFunc1(requestBody, requestConfig)
+ generateFunc1(param1, param2, requestConfig)
}
BaseApiService <|-- GenerateApiService
class GlobeConfig {
+String mercNo
+String priKey
}
class RequestConfig {
+String requestId
+String sessionKey
}
}
@enduml
mustache生成示例代码
return baseClient.execute("{{httpMethod}}",
"{{path}}"
{{#pathParams}}.replace("{"+"{{paramName}}"+"}",{{paramName}}){{/pathParams}}
{{#queryParams}}.replace("{"+"{{paramName}}"+"}",{{paramName}}){{/queryParams}},
{{returnType}}.class,
requestConfig
{{#allParams}}{{#isBodyParam}},{{paramName}} {{/isBodyParam}}{{/allParams}}
);
SDK使用说明
安装
Maven
加入以下依赖
<dependency>
<groupId>com.huawei.petalpay</groupId>
<artifactId>pay-java</artifactId>
<version>1.0.0</version>
</dependency>
调用业务请求接口
以 App 支付预下单为例,先补充商户号等必要参数以构建 config
,初始化payClient。 再构建 service
即可调用 aggrPreOrderForApp
发送请求。
/** Native 支付下单为例 */
public class QuickStart {
// 商户配置
public static PetalPayConfig getMercConfig() {
return PetalPayConfig.builder().callerId(MERC_NO) // (必填)商户号
.privateKey(MERC_PRIVATE_KEY) // (必填) 商户秘钥
.authId(MERC_AUTH_ID) // (必填) 商户证书序列号
.signType(SIGN_TYPE) // (选填) 商户公私钥类型,默认RSA加密
.petalpayPublicKey(HW_PAY_PUBLIC_KEY_FOR_CALLBACK) // (非必填) 验签公钥(和接口级配置needVerifyRsp对应,公钥和商户通知回调验签公钥同一个)
.domainHost(SERVER_HOST).build();
}
public static PreOrderCreateRequest getRequest() {
return PreOrderCreateRequest.builder()
.mercOrderNo("20230328" + System.currentTimeMillis()) // 每次订单号都要变
.mercNo(MERC_NO) // 商户的商户号
.tradeSummary("Mate50 手机")
.bizType(
"100002") // (100001:虚拟商品购买,100002:实物商品购买,100003:预付类账号充值,100004:航旅交通服务,100005:活动票务订购,100006:商业服务消费,100007:生活服务消费,100008:租金缴纳,100009:会员费缴纳,100011:其他商家消费,100037:公共便民服务)
.currency("CNY")
.totalAmount(2L)
.callbackUrl("http://www.abc.com") // 回调通知地址,通知URL必须为直接可访问的URL,要求为http或https(建议)地址。最大长度为512
.payload("1000222") // 商户预留信息,在查询和回调通知时会原样返回。最大长度255。
.expireTime(
"2023-03-28T17:50:12.000+0800") // string 交易过期时间。格式要求:"yyyy-MM-dd'T'HH:mm:ss.SSSZ" 注意:要使用必须传准确的UTC时间
.allocationType("DELAY_ORDER_ALLOCATION") // 分账类型,NO_ALLOCATION—不分账,DELAY_ORDER_ALLOCATION—延时分账
.build();
}
public static void main(String[] args) {
DefaultPetalPayClient payClient = new DefaultPetalPayClient(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));
}
}
从示例可见,使用 SDK 不需要计算请求签名和验证应答签名。
回调通知
首先,你需要在你的服务器上创建一个公开的 HTTP 端点,接受来自华为支付的回调通知。 当接收到回调通知,使用VerifyTools.getCallbackResult方法来验证回调通知。 并实现CallBackHandleInterface接口来处理回调结果。 使用示例如下:
public class PayDemoMerchantController {
/**
* 华为支付通知回调签名公钥
*/
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接口文档中标注。 对应的接口调用方法如下:
// 组装对象
String sessionKey=SM4Util.getSM4GCMSessionKey();
String message="plainText";
RegisterSubmercReq req=RegisterSubmercReq.builder().message(SM4Util.getSM4GCMContent(sessionKey,message)).build();
RequestConfig config=RequestConfig.builder().publicKeyForSessionKey(HW_PUBLIC_KEY_FOR_SESSIONKEY).sessionKey(sessionKey).build();
MgmtSubmercRsp response=null;
try{
response=partnerMerchantMgmt.partnerMgmtSubmercRegister(req,config);
}catch(Exception e){
System.out.println(e);
}
System.out.println(JsonUtils.obj2Json(response));
自定义httpClient
SDK 使用 [HttpClient] 作为默认的 HTTP 客户端。 开发者可以直接使用 DefaultPetalPayClient来发起http请求。 开发者如果需要自定义接口请求的client,以做请求中的日志打印等操作,可以通过继承PetalpayClient来实现:
public class DefaultPetalPayClient extends PetalPayClient {
public DefaultPetalPayClient(PetalPayConfig petalPayConfig) {
super(petalPayConfig);
}
@Override
public String doPost(String url, Map<String, String> headers, String requestBody) throws Exception {
// todo
}
@Override
public String doGet(String url, Map<String, String> headers, String requestBody) throws Exception {
// todo
}
}
新增或拓展业务接口
开发者如果未及时更新SDK,需要使用最新的http接口,可直接调用petalpayClient的execute方法,进行接口请求。 对应的方法签名如下:
execute(String httpMethod, String apiUrl,Class<T> rspType);
execute(String httpMethod, String apiUrl, Class<T> rspType, Object requestObj);
execute(String httpMethod, String apiUrl, Class<T> rspType, RequestConfig requestConfig, Object requestObj);