## 恢复京东支付渠道;
This commit is contained in:
parent
a33e55e0f4
commit
01a24c45cd
|
@ -5,12 +5,6 @@ import com.hzs.third.bankcard.dto.BankCardParam;
|
|||
|
||||
/**
|
||||
* 银行卡验证接口
|
||||
*
|
||||
* @Description:
|
||||
* @Author: ljc
|
||||
* @Time: 2022/11/24 18:03
|
||||
* @Classname: BankCardService
|
||||
* @Package_name: com.hz.bankCard.service.impl
|
||||
*/
|
||||
public interface BankCardService {
|
||||
|
||||
|
|
|
@ -19,12 +19,6 @@ import java.util.Map;
|
|||
|
||||
/**
|
||||
* 银行卡验证接口
|
||||
*
|
||||
* @Description:
|
||||
* @Author: ljc
|
||||
* @Time: 2022/11/24 18:14
|
||||
* @Classname: BankCardServiceImpl
|
||||
* @Package_name: com.hz.bankCard.service.impl
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
package com.hzs.third.pay.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 京东支付配置
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "jdpay")
|
||||
public class JdPayConfig {
|
||||
|
||||
/**
|
||||
* 商户编号
|
||||
*/
|
||||
private String customerNum;
|
||||
|
||||
/**
|
||||
* 店铺编号
|
||||
*/
|
||||
private String shopNum;
|
||||
|
||||
/**
|
||||
* 公钥
|
||||
*/
|
||||
private String accessKey;
|
||||
|
||||
/**
|
||||
* 私钥
|
||||
*/
|
||||
private String secretKey;
|
||||
|
||||
/**
|
||||
* 回调地址
|
||||
*/
|
||||
private String callbackUrl;
|
||||
|
||||
/**
|
||||
* 代付回调地址
|
||||
*/
|
||||
private String agentCallbackUrl;
|
||||
|
||||
/**
|
||||
* 退款回调地址
|
||||
*/
|
||||
private String refundCallbackUrl;
|
||||
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
package com.hzs.third.pay.constants;
|
||||
|
||||
/**
|
||||
* 京东支付常量类
|
||||
*/
|
||||
public class JdPayConstants {
|
||||
|
||||
/**
|
||||
* 终端标记(暂用京东注册手机号)
|
||||
*/
|
||||
public static final String TERMINAL_ID = "18246334501";
|
||||
/**
|
||||
* 用户注册账号(暂用京东注册手机号)
|
||||
*/
|
||||
public static final String USER_ACCOUNT = "18246334501";
|
||||
/**
|
||||
* 应用名称
|
||||
*/
|
||||
public static final String APP_NAME = "盛美源";
|
||||
|
||||
// TODO 上面3项考试动态化配置
|
||||
|
||||
/**
|
||||
* 订单状态 - 交易处理中
|
||||
*/
|
||||
public static final String ORDER_INT = "INT";
|
||||
/**
|
||||
* 订单状态 - 成功
|
||||
*/
|
||||
public static final String ORDER_SUCCESS = "SUCCESS";
|
||||
|
||||
/**
|
||||
* 查询状态 - 交易处理中
|
||||
*/
|
||||
public static final String QUERY_INIT = "INIT";
|
||||
/**
|
||||
* 查询状态 - 成功
|
||||
*/
|
||||
public static final String QUERY_SUCCESS = "SUCCESS";
|
||||
/**
|
||||
* 查询状态 - 失败
|
||||
*/
|
||||
public static final String QUERY_FAIL = "FAIL";
|
||||
|
||||
/**
|
||||
* 处理成功
|
||||
*/
|
||||
public static final String RESULT_SUCCESS = "success";
|
||||
|
||||
/**
|
||||
* 处理成功业务响应码
|
||||
*/
|
||||
public static final String RESULT_SUCCESS_CODE = "C000000";
|
||||
|
||||
/**
|
||||
* 银行卡交易不支持错误编码
|
||||
*/
|
||||
public static final String RESULT_FAIL_CODE_6 = "C000006";
|
||||
|
||||
/**
|
||||
* 请求成功
|
||||
*/
|
||||
public static final String SUCCESS = "true";
|
||||
|
||||
/**
|
||||
* 返回成功
|
||||
*/
|
||||
public static final String RETURN_SUCCESS = "200";
|
||||
/**
|
||||
* 返回失败
|
||||
*/
|
||||
public static final String RETURN_FAIL = "500";
|
||||
|
||||
/**
|
||||
* 支付接口版本
|
||||
*/
|
||||
public static final String PAY_VERSION = "V4.0";
|
||||
|
||||
/**
|
||||
* 绑卡类型: 身份证
|
||||
*/
|
||||
public static final String ID_CARD_TYPE = "IDCARD";
|
||||
|
||||
/**
|
||||
* 快捷支付标准业务场景
|
||||
*/
|
||||
public static final String FAST_TRADESCENE_QUICKPAY = "QUICKPAY";
|
||||
|
||||
/**
|
||||
* 代付业务类型
|
||||
*/
|
||||
public static final String AGENT_BUSINESS_TYPE = "DEFY";
|
||||
|
||||
/**
|
||||
* 代付支付方式
|
||||
*/
|
||||
public static final String AGENT_BANK_TYPE = "DEFY";
|
||||
|
||||
/**
|
||||
* 支付地址
|
||||
*/
|
||||
public static final String PAY_URL = "https://openapi.duolabao.com";
|
||||
|
||||
/**
|
||||
* 扫码支付调用方法
|
||||
*/
|
||||
public static final String METHOD_SCAN = "/v3/order/payurl/create";
|
||||
|
||||
/**
|
||||
* 快捷支付绑卡方法
|
||||
*/
|
||||
public static final String METHOD_BIND_CARD = "/api/applyBindCard";
|
||||
|
||||
/**
|
||||
* 快捷支付绑卡确认方法
|
||||
*/
|
||||
public static final String METHOD_CONFIRM_BIND = "/api/confirmBindCard";
|
||||
|
||||
/**
|
||||
* 快捷支付解绑方法
|
||||
*/
|
||||
public static final String METHOD_UNBIND = "/api/unBindCard";
|
||||
|
||||
/**
|
||||
* 快捷支付预下单
|
||||
*/
|
||||
public static final String METHOD_PRE_ORDER = "/api/applyQuickPayWithCheck";
|
||||
|
||||
/**
|
||||
* 快捷支付确认订单
|
||||
*/
|
||||
public static final String METHOD_CONFIRM_ORDER = "/api/confirmQuickPay";
|
||||
|
||||
/**
|
||||
* 京东代付余额查询方法
|
||||
*/
|
||||
public static final String METHOD_QUERY_BALANCE = "/api/queryBalance";
|
||||
|
||||
/**
|
||||
* 京东代付调用方法
|
||||
*/
|
||||
public static final String METHOD_AGENT = "/api/payWithCheck";
|
||||
|
||||
/**
|
||||
* 京东查询订单路径
|
||||
*/
|
||||
public static final String QUERY_ORDER = "/v3/order/query";
|
||||
|
||||
/**
|
||||
* 京东退款路径
|
||||
*/
|
||||
public static final String REFUND_ORDER = "/v3/order/refund/part";
|
||||
|
||||
}
|
|
@ -7,6 +7,11 @@ import com.hzs.common.core.constant.CacheConstants;
|
|||
*/
|
||||
public class PayRedisConstants {
|
||||
|
||||
/**
|
||||
* jd扫码KEY
|
||||
*/
|
||||
public static final String JD_SCAN_KEY = CacheConstants.CACHE_PREFIX + "JDPAY:SCAN:%s:%s";
|
||||
|
||||
/**
|
||||
* sand扫码KEY
|
||||
*/
|
||||
|
|
|
@ -40,6 +40,8 @@ public class PayController {
|
|||
@Autowired
|
||||
private IAllInPayService iAllInPayService;
|
||||
@Autowired
|
||||
private IJdPayService iJdPayService;
|
||||
@Autowired
|
||||
private IHuiFuPayService iHuiFuPayService;
|
||||
|
||||
@Autowired
|
||||
|
@ -200,6 +202,28 @@ public class PayController {
|
|||
default:
|
||||
}
|
||||
break;
|
||||
case JD:
|
||||
// 京东
|
||||
switch (payType) {
|
||||
case WECHAT:
|
||||
case ALIPAY:
|
||||
// 微信、支付宝 扫码支付
|
||||
payResult = iJdPayService.scanPay(onlinePayment);
|
||||
break;
|
||||
case BANK_CARD:
|
||||
// 银行卡支付
|
||||
if (StringUtils.isEmpty(payParam.getBindCode())) {
|
||||
return AjaxResult.error("银行绑卡编号不能为空");
|
||||
}
|
||||
payResult = iJdPayService.bankPay(onlinePayment, payParam.getBindCode());
|
||||
break;
|
||||
case BANK_ONLINE:
|
||||
// 网上银行(收银台)
|
||||
payResult = iJdPayService.cashRegister(onlinePayment, dataSource);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
break;
|
||||
case HUIFU:
|
||||
// 汇付新
|
||||
switch (payType) {
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
package com.hzs.third.pay.dto.jd;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 京东绑卡确认DTO
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Builder
|
||||
@Data
|
||||
public class JdFastBindConfirmDTO {
|
||||
|
||||
/**
|
||||
* 商户编号
|
||||
*/
|
||||
private String customerNum;
|
||||
|
||||
/**
|
||||
* 商户订单号
|
||||
*/
|
||||
private String requestNum;
|
||||
|
||||
/**
|
||||
* 验证短信
|
||||
*/
|
||||
private String validateCode;
|
||||
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package com.hzs.third.pay.dto.jd;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 京东绑卡确认返回结果
|
||||
*/
|
||||
@Data
|
||||
public class JdFastBindConfirmResult {
|
||||
|
||||
/**
|
||||
* 请求状态
|
||||
*/
|
||||
private boolean success;
|
||||
|
||||
/**
|
||||
* 业务响应码
|
||||
*/
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 业务响应描述
|
||||
*/
|
||||
private String msg;
|
||||
|
||||
/**
|
||||
* 商户订单号
|
||||
*/
|
||||
private String requestNum;
|
||||
|
||||
/**
|
||||
* 绑卡状态
|
||||
*/
|
||||
private String bindStatus;
|
||||
/**
|
||||
* 绑卡ID
|
||||
*/
|
||||
private String bindId;
|
||||
|
||||
/**
|
||||
* 银行编号
|
||||
*/
|
||||
private String bankCode;
|
||||
/**
|
||||
* 银行卡后4位
|
||||
*/
|
||||
private String cardAfterFour;
|
||||
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
package com.hzs.third.pay.dto.jd;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 京东快捷预下单DTO
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Builder
|
||||
@Data
|
||||
public class JdFastDTO {
|
||||
|
||||
/**
|
||||
* 版本号(仅支持:V4.0)
|
||||
*/
|
||||
private String version;
|
||||
|
||||
/**
|
||||
* 商户编号
|
||||
*/
|
||||
private String customerNum;
|
||||
|
||||
/**
|
||||
* 店铺编号
|
||||
*/
|
||||
private String shopNum;
|
||||
|
||||
/**
|
||||
* 商户订单号
|
||||
*/
|
||||
private String requestNum;
|
||||
|
||||
/**
|
||||
* 交易金额
|
||||
*/
|
||||
private String orderAmount;
|
||||
|
||||
/**
|
||||
* 客户端IP
|
||||
*/
|
||||
private String clientIp;
|
||||
|
||||
/**
|
||||
* 通知地址
|
||||
*/
|
||||
private String callbackUrl;
|
||||
|
||||
/**
|
||||
* 用户编号
|
||||
*/
|
||||
private String userId;
|
||||
|
||||
/**
|
||||
* 绑卡ID
|
||||
*/
|
||||
private String bindId;
|
||||
|
||||
/**
|
||||
* 商品名称
|
||||
*/
|
||||
private String goodsName;
|
||||
/**
|
||||
* 商品数量
|
||||
*/
|
||||
private String goodsQuantity;
|
||||
|
||||
/**
|
||||
* 终端类型(OTHER)
|
||||
*/
|
||||
private String terminalType;
|
||||
/**
|
||||
* 终端标识
|
||||
*/
|
||||
private String terminalId;
|
||||
|
||||
/**
|
||||
* 用户注册账号
|
||||
*/
|
||||
private String userAccount;
|
||||
|
||||
/**
|
||||
* 应用类型(H5 网页)
|
||||
*/
|
||||
private String appType;
|
||||
/**
|
||||
* 应用名称
|
||||
*/
|
||||
private String appName;
|
||||
|
||||
/**
|
||||
* 业务场景(QUICKPAY 标准场景)
|
||||
*/
|
||||
private String tradeScene;
|
||||
|
||||
/**
|
||||
* 来源(API)
|
||||
*/
|
||||
private String source;
|
||||
|
||||
/**
|
||||
* 订单有效时间
|
||||
*/
|
||||
private String period;
|
||||
/**
|
||||
* 订单有效单位(Day:天,Hour:时,Minute:分)
|
||||
*/
|
||||
private String periodUnit;
|
||||
|
||||
/**
|
||||
* 扩展字段(长度:32)
|
||||
*/
|
||||
private String extraInfo;
|
||||
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package com.hzs.third.pay.dto.jd;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 京东快捷返回结果
|
||||
*/
|
||||
@Data
|
||||
public class JdFastResult {
|
||||
|
||||
/**
|
||||
* 请求状态
|
||||
*/
|
||||
private boolean success;
|
||||
|
||||
/**
|
||||
* 业务响应码
|
||||
*/
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 业务响应描述
|
||||
*/
|
||||
private String msg;
|
||||
|
||||
/**
|
||||
* 商户编号
|
||||
*/
|
||||
private String customerNum;
|
||||
|
||||
/**
|
||||
* 商户订单号
|
||||
*/
|
||||
private String requestNum;
|
||||
|
||||
/**
|
||||
* 订单编号
|
||||
*/
|
||||
private String orderNum;
|
||||
|
||||
/**
|
||||
* 银行流水号
|
||||
*/
|
||||
private String bankRequestNum;
|
||||
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
package com.hzs.third.pay.dto.jd;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 京东支付回调body体
|
||||
*/
|
||||
@Data
|
||||
public class JdPayNotifyBody {
|
||||
|
||||
/**
|
||||
* 处理状态
|
||||
*/
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 商户订单编号
|
||||
*/
|
||||
private String requestNum;
|
||||
/**
|
||||
* 订单金额(元)
|
||||
*/
|
||||
private String orderAmount;
|
||||
/**
|
||||
* 订单完成时间
|
||||
*/
|
||||
private String completeTime;
|
||||
/**
|
||||
* 支付类型(支付宝、微信等)
|
||||
*/
|
||||
private String payWay;
|
||||
/**
|
||||
* 扩展内容
|
||||
*/
|
||||
private String extraInfo;
|
||||
|
||||
/**
|
||||
* 订单编号(京东系统订单号)
|
||||
*/
|
||||
private String orderNum;
|
||||
|
||||
/**
|
||||
* 银行订单号(银行流水号)
|
||||
*/
|
||||
private String bankOutTradeNum;
|
||||
/**
|
||||
* 银行流水号(渠道订单流水号)
|
||||
*/
|
||||
private String bankRequestNum;
|
||||
|
||||
/**
|
||||
* 订单类型
|
||||
*/
|
||||
private String orderType;
|
||||
/**
|
||||
* 订单子类型
|
||||
*/
|
||||
private String subOrderType;
|
||||
/**
|
||||
* 订单失败原因
|
||||
*/
|
||||
private String failReason;
|
||||
/**
|
||||
* 订单失败编码
|
||||
*/
|
||||
private String faiCode;
|
||||
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package com.hzs.third.pay.dto.jd;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 京东扫码DTO
|
||||
*/
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@Data
|
||||
public class JdScanDTO {
|
||||
|
||||
/**
|
||||
* 商户编号
|
||||
*/
|
||||
private String customerNum;
|
||||
|
||||
/**
|
||||
* 店铺编号
|
||||
*/
|
||||
private String shopNum;
|
||||
|
||||
/**
|
||||
* 商户订单号
|
||||
*/
|
||||
private String requestNum;
|
||||
|
||||
/**
|
||||
* 金额(单元:元)
|
||||
*/
|
||||
private String amount;
|
||||
|
||||
/**
|
||||
* 回调地址
|
||||
*/
|
||||
private String callbackUrl;
|
||||
|
||||
/**
|
||||
* 来源(固定值:API)
|
||||
*/
|
||||
private String source;
|
||||
|
||||
/**
|
||||
* 扩展字段(长度:32)
|
||||
*/
|
||||
private String extraInfo;
|
||||
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package com.hzs.third.pay.dto.jd;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 京东扫码返回
|
||||
*/
|
||||
@Data
|
||||
public class JdScanResult {
|
||||
|
||||
/**
|
||||
* 响应状态(success表示成功,fail表示失败,error表示异常)
|
||||
*/
|
||||
private String result;
|
||||
/**
|
||||
* 返回实际内容
|
||||
*/
|
||||
private JdScanResultData data;
|
||||
|
||||
/**
|
||||
* 错误代码(https://mer.jd.com/open/?agg_recpt&APIDocument&30&153)
|
||||
*/
|
||||
private String errorCode;
|
||||
/**
|
||||
* 错误描述
|
||||
*/
|
||||
private String errorMsg;
|
||||
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package com.hzs.third.pay.dto.jd;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 京东扫码返回Data
|
||||
*/
|
||||
@Data
|
||||
public class JdScanResultData {
|
||||
|
||||
/**
|
||||
* 扫码URL
|
||||
*/
|
||||
private String url;
|
||||
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package com.hzs.third.pay.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 京东应用类型
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum EJdAppType {
|
||||
|
||||
IOS("IOS", "IOS"),
|
||||
|
||||
AND("AND", "AND"),
|
||||
|
||||
H5("H5", "H5"),
|
||||
|
||||
WX("WX", "WX"),
|
||||
|
||||
OTHER("OTHER", "OTHER"),
|
||||
|
||||
;
|
||||
|
||||
private final String value;
|
||||
private final String label;
|
||||
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package com.hzs.third.pay.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 京东终端类型
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum EJdTerminalType {
|
||||
|
||||
IMEI("IMEI", "IMEI"),
|
||||
|
||||
MAC("MAC", "MAC"),
|
||||
|
||||
// 针对IOS系统
|
||||
UUID("UUID", "UUID"),
|
||||
|
||||
OTHER("OTHER", "OTHER"),
|
||||
|
||||
;
|
||||
|
||||
private final String value;
|
||||
private final String label;
|
||||
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
package com.hzs.third.pay.jdpay.config;
|
||||
|
||||
import com.hzs.third.pay.jdpay.sdk.JdPay;
|
||||
import com.hzs.third.pay.jdpay.sdk.JdPayNewConfig;
|
||||
import com.hzs.third.pay.jdpay.sdk.JdPayDefaultNewConfig;
|
||||
import com.hzs.third.pay.jdpay.util.FileUtil;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/*************************************************
|
||||
*
|
||||
* 京东支付sdk初始化
|
||||
*
|
||||
*************************************************/
|
||||
@Configuration
|
||||
public class JdPayAutoConfiguration {
|
||||
|
||||
@Value("${jd.pay.merchantNo}")
|
||||
private String merchantNo;
|
||||
@Value("${jd.pay.signKey}")
|
||||
private String signKey;
|
||||
@Value("${jd.pay.priCertPwd}")
|
||||
private String priCertPwd;
|
||||
@Value("${jd.pay.priCert}")
|
||||
private String priCert;
|
||||
@Value("${jd.pay.pubCert}")
|
||||
private String pubCert;
|
||||
@Value("${jd.pay.apiDomain}")
|
||||
private String apiDomain;
|
||||
|
||||
@Bean(name = "jdPay")
|
||||
public JdPay initJddPay() {
|
||||
// 加载商户私钥证书
|
||||
byte[] privateCert = FileUtil.readFile(priCert);
|
||||
// 加载商户公钥证书
|
||||
byte[] publicCert = FileUtil.readFile(pubCert);
|
||||
// 检查商户证书
|
||||
checkCert(privateCert, publicCert);
|
||||
// 初始化京东支付配置对象
|
||||
JdPayNewConfig myConfig = new JdPayDefaultNewConfig(merchantNo, signKey, privateCert, priCertPwd, publicCert, apiDomain);
|
||||
return new JdPay(myConfig);
|
||||
}
|
||||
|
||||
private void checkCert(byte[] privateCert, byte[] publicCert) {
|
||||
if (privateCert == null) {
|
||||
throw new RuntimeException("读取京东支付商户私钥证书为空");
|
||||
}
|
||||
if (publicCert == null) {
|
||||
throw new RuntimeException("读取京东支付商户公钥证书为空");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
package com.hzs.third.pay.jdpay.dto;
|
||||
|
||||
import lombok.*;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 京东聚合下单请求
|
||||
*/
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Data
|
||||
public class JdPayAggregateCreateOrderRequest implements Serializable {
|
||||
/**
|
||||
* 商户订单号
|
||||
*/
|
||||
private String outTradeNo;
|
||||
/**
|
||||
* 订单总金额
|
||||
*/
|
||||
private String tradeAmount;
|
||||
/**
|
||||
* 订单创建时间
|
||||
*/
|
||||
private String createDate;
|
||||
/**
|
||||
* 订单时效时间 分钟
|
||||
*/
|
||||
private String tradeExpiryTime;
|
||||
/**
|
||||
* 交易名称
|
||||
*/
|
||||
private String tradeSubject;
|
||||
/**
|
||||
* 交易类型
|
||||
*/
|
||||
private String tradeType;
|
||||
/**
|
||||
* 交易描述
|
||||
*/
|
||||
private String tradeRemark;
|
||||
/**
|
||||
* 币种
|
||||
*/
|
||||
private String currency;
|
||||
/**
|
||||
* 用户ip
|
||||
*/
|
||||
private String userIp;
|
||||
/**
|
||||
* 回传字段
|
||||
*/
|
||||
private String returnParams;
|
||||
/**
|
||||
* 商品信息list-- json串
|
||||
*/
|
||||
private String goodsInfo;
|
||||
/**
|
||||
* 商户用户标识
|
||||
*/
|
||||
private String userId;
|
||||
|
||||
/**
|
||||
* 交易异步通知url
|
||||
*/
|
||||
private String notifyUrl;
|
||||
/**
|
||||
* 同步通知页面url
|
||||
*/
|
||||
private String pageBackUrl;
|
||||
/**
|
||||
* 风控信息map-- json串
|
||||
*/
|
||||
private String riskInfo;
|
||||
/**
|
||||
* 行业分类码
|
||||
*/
|
||||
private String categoryCode;
|
||||
/**
|
||||
* 业务类型
|
||||
*/
|
||||
private String bizTp;
|
||||
/**
|
||||
* 订单类型
|
||||
*/
|
||||
private String orderType;
|
||||
/**
|
||||
* 报文格式
|
||||
*/
|
||||
private String messageFormat;
|
||||
/**
|
||||
* 收货信息
|
||||
*/
|
||||
private String receiverInfo;
|
||||
/**
|
||||
* 交易场景
|
||||
*/
|
||||
private String sceneType;
|
||||
/**
|
||||
* 指定支付信息
|
||||
*/
|
||||
private String identity;
|
||||
/**
|
||||
* 接入方式
|
||||
*/
|
||||
private String accessType;
|
||||
/**
|
||||
* openId
|
||||
*/
|
||||
private String subOpenId;
|
||||
/**
|
||||
* appId
|
||||
*/
|
||||
private String subAppId;
|
||||
/**
|
||||
* 分帐信息
|
||||
*/
|
||||
private String divisionAccount;
|
||||
/**
|
||||
* 门店号
|
||||
*/
|
||||
private String storeNum;
|
||||
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package com.hzs.third.pay.jdpay.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 京东聚合下单响应
|
||||
*/
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Data
|
||||
public class JdPayAggregateCreateOrderResponse implements Serializable {
|
||||
|
||||
/**
|
||||
* 业务结果
|
||||
*/
|
||||
private String resultCode;
|
||||
/**
|
||||
* 响应描述
|
||||
*/
|
||||
private String resultDesc;
|
||||
/**
|
||||
* 商户号
|
||||
*/
|
||||
private String merchantNo;
|
||||
/**
|
||||
* 京东交易订单号
|
||||
*/
|
||||
private String tradeNo;
|
||||
/**
|
||||
* 商户订单号
|
||||
*/
|
||||
private String outTradeNo;
|
||||
/**
|
||||
* 跳转收银台链接
|
||||
*/
|
||||
private String webUrl;
|
||||
/**
|
||||
* PC扫码支付
|
||||
*/
|
||||
private String qrCode;
|
||||
/**
|
||||
* 唤起通道参数
|
||||
*/
|
||||
private String payInfo;
|
||||
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package com.hzs.third.pay.jdpay.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 京东退款响应
|
||||
*/
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Data
|
||||
public class JdPayRefundRequest implements Serializable {
|
||||
|
||||
/**
|
||||
* 商户原交易号
|
||||
*/
|
||||
private String originalOutTradeNo;
|
||||
/**
|
||||
* 商户退款单号
|
||||
*/
|
||||
private String outTradeNo;
|
||||
/**
|
||||
* 退款金额
|
||||
*/
|
||||
private String tradeAmount;
|
||||
/**
|
||||
* 异步通知URL
|
||||
*/
|
||||
private String notifyUrl;
|
||||
/**
|
||||
* 回传字段
|
||||
*/
|
||||
private String returnParams;
|
||||
/**
|
||||
* 币种
|
||||
*/
|
||||
private String currency;
|
||||
/**
|
||||
* 交易时间
|
||||
*/
|
||||
private String tradeDate;
|
||||
/**
|
||||
* 退款分账信息
|
||||
*
|
||||
* @see
|
||||
*/
|
||||
private String divisionAccountRefund;
|
||||
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package com.hzs.third.pay.jdpay.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 京东退款请求
|
||||
*/
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Data
|
||||
public class JdPayRefundResponse implements Serializable {
|
||||
|
||||
/**
|
||||
* 业务结果
|
||||
*/
|
||||
private String resultCode;
|
||||
/**
|
||||
* 响应描述
|
||||
*/
|
||||
private String resultDesc;
|
||||
|
||||
/**
|
||||
* 京东退款订单号
|
||||
*/
|
||||
private String tradeNo;
|
||||
/**
|
||||
* 商户退款订单号
|
||||
*/
|
||||
private String outTradeNo;
|
||||
/**
|
||||
* 商户原正单订单号
|
||||
*/
|
||||
private String originalOutTradeNo;
|
||||
/**
|
||||
* 订单总金额
|
||||
*/
|
||||
private String tradeAmount;
|
||||
|
||||
/**
|
||||
* 交易状态
|
||||
*/
|
||||
private String tradeStatus;
|
||||
/**
|
||||
* 币种
|
||||
*/
|
||||
private String currency;
|
||||
/**
|
||||
* 退款完成时间
|
||||
*/
|
||||
private String finishDate;
|
||||
|
||||
}
|
|
@ -0,0 +1,175 @@
|
|||
package com.hzs.third.pay.jdpay.sdk;
|
||||
|
||||
import com.hzs.third.pay.jdpay.dto.JdPayAggregateCreateOrderRequest;
|
||||
import com.hzs.third.pay.jdpay.dto.JdPayAggregateCreateOrderResponse;
|
||||
import com.hzs.third.pay.jdpay.dto.JdPayRefundRequest;
|
||||
import com.hzs.third.pay.jdpay.dto.JdPayRefundResponse;
|
||||
import com.hzs.third.pay.jdpay.util.GsonUtil;
|
||||
import com.hzs.third.pay.jdpay.util.JdPayApiUtil;
|
||||
import com.hzs.third.pay.jdpay.util.SignUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/*************************************************
|
||||
*
|
||||
* 京东支付api实现类
|
||||
*
|
||||
*************************************************/
|
||||
@Slf4j
|
||||
public class JdPay {
|
||||
|
||||
private JdPayNewConfig jdPayNewConfig;
|
||||
|
||||
private JdPayHttpClientProxy jdPayHttpClientProxy;
|
||||
|
||||
public JdPay(JdPayNewConfig jdPayNewConfig) {
|
||||
this.jdPayNewConfig = jdPayNewConfig;
|
||||
this.jdPayHttpClientProxy = new JdPayHttpClientProxy(jdPayNewConfig, new JdPayHttpClient());
|
||||
}
|
||||
|
||||
// /**
|
||||
// * 作用:统一下单
|
||||
// * 场景:京东支付
|
||||
// *
|
||||
// * @param request 向jdPay post的请求数据
|
||||
// * @return JdPayCreateOrderResponse 返回数据
|
||||
// * @throws Exception
|
||||
// */
|
||||
// public JdPayCreateOrderResponse createOrder(JdPayCreateOrderRequest request) throws Exception {
|
||||
// return this.baseExecute(JdPayConstant.CREATE_ORDER_URL, request, JdPayCreateOrderResponse.class);
|
||||
// }
|
||||
|
||||
/**
|
||||
* 作用:三方聚合统一下单
|
||||
* 场景:三方聚合
|
||||
*
|
||||
* @param request 向jdPay post的请求数据
|
||||
* @return JdPayAggregateCreateOrderResponse 返回数据
|
||||
* @throws Exception
|
||||
*/
|
||||
public JdPayAggregateCreateOrderResponse aggregateCreateOrder(JdPayAggregateCreateOrderRequest request) throws Exception {
|
||||
return this.baseExecute(JdPayConstant.AGGREGATE_CREATE_ORDER_URL, request, JdPayAggregateCreateOrderResponse.class);
|
||||
}
|
||||
// /**
|
||||
// * 作用:订单查询
|
||||
// * 场景:查询订单信息 - 包括首次支付订单与代扣订单
|
||||
// *
|
||||
// * @param request 向jdPay post的请求数据
|
||||
// * @return JdPayQueryOrderResponse 返回数据
|
||||
// * @throws Exception
|
||||
// */
|
||||
// public JdPayQueryOrderResponse queryOrder(JdPayQueryOrderRequest request) throws Exception {
|
||||
// return this.baseExecute(JdPayConstant.TRADE_QUERY_URL, request, JdPayQueryOrderResponse.class);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 作用:代扣
|
||||
// * 场景:代扣交易场景
|
||||
// *
|
||||
// * @param request 向jdPay post的请求数据
|
||||
// * @return JdPayAgreementPayResponse 返回数据
|
||||
// * @throws Exception
|
||||
// */
|
||||
// public JdPayAgreementPayResponse agreementPay(JdPayAgreementPayRequest request) throws Exception {
|
||||
// return this.baseExecute(JdPayConstant.AGREEMENT_PAY_URL, request, JdPayAgreementPayResponse.class);
|
||||
// }
|
||||
|
||||
/**
|
||||
* 作用:申请退款
|
||||
* 场景:退款
|
||||
*
|
||||
* @param request 向jdPay post的请求数据
|
||||
* @return JdPayRefundResponse 返回数据
|
||||
* @throws Exception
|
||||
*/
|
||||
public JdPayRefundResponse refund(JdPayRefundRequest request) throws Exception {
|
||||
return this.baseExecute(JdPayConstant.REFUND_URL, request, JdPayRefundResponse.class);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * 作用:退款查询
|
||||
// * 场景:查询退款信息
|
||||
// *
|
||||
// * @param request 向jdPay post的请求数据
|
||||
// * @return JdPayQueryOrderResponse 返回数据
|
||||
// * @throws Exception
|
||||
// */
|
||||
// public JdPayRefundQueryResponse refundQuery(JdPayRefundQueryRequest request) throws Exception {
|
||||
// return this.baseExecute(JdPayConstant.REFUND_QUERY_URL, request, JdPayRefundQueryResponse.class);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 作用:解约
|
||||
// * 场景:接触签约关系
|
||||
// *
|
||||
// * @param request 向jdPay post的请求数据
|
||||
// * @return JdPayAgreementCancelRequest 返回数据
|
||||
// * @throws Exception
|
||||
// */
|
||||
// public JdPayAgreementCancelResponse agreementCancel(JdPayAgreementCancelRequest request) throws Exception {
|
||||
// return this.baseExecute(JdPayConstant.AGREEMENT_CANCEL_URL, request, JdPayAgreementCancelResponse.class);
|
||||
// }
|
||||
//
|
||||
//
|
||||
// /**
|
||||
// * 作用:解约查询
|
||||
// * 场景:查询签约关系
|
||||
// *
|
||||
// * @param request 向jdPay post的请求数据
|
||||
// * @return JdPayAgreementCancelRequest 返回数据
|
||||
// * @throws Exception
|
||||
// */
|
||||
// public JdPayAgreementQueryResponse agreementQuery(JdPayAgreementQueryRequest request) throws Exception {
|
||||
// return this.baseExecute(JdPayConstant.AGREEMENT_QUERY_URL, request, JdPayAgreementQueryResponse.class);
|
||||
// }
|
||||
//
|
||||
// public JdPayAgreementSignResponse agreementNewSign(JdPayAgreementSignRequest request) throws Exception{
|
||||
// return this.baseExecute(JdPayConstant.AGREEMENT_NEW_SIGN_URL, request, JdPayAgreementSignResponse.class);
|
||||
// }
|
||||
|
||||
/**
|
||||
* 验证接口参数签名
|
||||
* 场景:api接口返回参数,异步回调请求参数
|
||||
*/
|
||||
public String verifyResponse(String respText) throws Exception {
|
||||
String interData = JdPayApiUtil.decryptAndVerifySign(jdPayNewConfig, respText);
|
||||
log.info("京东支付异步通知-解析数据:{}", interData);
|
||||
return interData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证页面回调参数
|
||||
*
|
||||
* @param respMap 页面回调参数
|
||||
* @return 验证结果
|
||||
* @throws Exception 异常
|
||||
*/
|
||||
public boolean verifyPageCallBack(Map<String, String> respMap) throws Exception {
|
||||
return SignUtil.verifyPageCallBackSign(respMap, jdPayNewConfig.getSignKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行接口调用
|
||||
*
|
||||
* @param request 请求对象
|
||||
* @param clazz 返回对象类型
|
||||
* @return 返回对象
|
||||
* @throws Exception 异常
|
||||
*/
|
||||
public <REQ, RES> RES baseExecute(String urlSuffix, REQ request, Class<RES> clazz) throws Exception {
|
||||
String reqJson = GsonUtil.toJson(request);
|
||||
String respJson = jdPayHttpClientProxy.execute(urlSuffix, reqJson);
|
||||
return GsonUtil.fromJson(respJson, clazz);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * 账户签约
|
||||
// * @param request
|
||||
// * @return
|
||||
// * @throws Exception
|
||||
// */
|
||||
// public JdPayAgreementSignApplyResponse agreementSignApply(JdPayAgreementSignApplyRequest request) throws Exception{
|
||||
// return this.baseExecute(JdPayConstant.AGREEMENT_SIGN_APPLY_URL, request, JdPayAgreementSignApplyResponse.class);
|
||||
// }
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
package com.hzs.third.pay.jdpay.sdk;
|
||||
|
||||
import org.apache.http.client.HttpClient;
|
||||
|
||||
public class JdPayConstant {
|
||||
|
||||
/**
|
||||
* 京东支付统一收单 url
|
||||
**/
|
||||
public static final String CREATE_ORDER_URL = "/api/createOrder";
|
||||
/**
|
||||
* 三方聚合统一收单 url
|
||||
**/
|
||||
public static final String AGGREGATE_CREATE_ORDER_URL = "/api/createAggregateOrder";
|
||||
/**
|
||||
* 交易查询 url
|
||||
**/
|
||||
public static final String TRADE_QUERY_URL = "/api/queryOrder";
|
||||
/**
|
||||
* 退款申请 url
|
||||
**/
|
||||
public static final String REFUND_URL = "/api/refund";
|
||||
/**
|
||||
* 退款查询 url
|
||||
**/
|
||||
public static final String REFUND_QUERY_URL = "/api/refundQuery";
|
||||
/**
|
||||
* 代扣 url
|
||||
**/
|
||||
public static final String AGREEMENT_PAY_URL = "/api/agreementPay";
|
||||
/**
|
||||
* 签约关系查询 url
|
||||
**/
|
||||
public static final String AGREEMENT_QUERY_URL = "/api/agreementQuery";
|
||||
/**
|
||||
* 解约申请 url
|
||||
**/
|
||||
public static final String AGREEMENT_CANCEL_URL = "/api/agreementCancel";
|
||||
|
||||
/**
|
||||
* 无卡号签约
|
||||
*/
|
||||
public static final String AGREEMENT_NEW_SIGN_URL = "/api/agreementNewSign";
|
||||
|
||||
/**
|
||||
* 账户签约
|
||||
*/
|
||||
public static final String AGREEMENT_SIGN_APPLY_URL = "/api/agreementSignApply";
|
||||
|
||||
/**
|
||||
* 随机字符常量
|
||||
*/
|
||||
public static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
|
||||
/**
|
||||
* 公共字段常量 start
|
||||
**/
|
||||
public static final String CCM = "CCM";
|
||||
public static final String MERCHANT_NO = "merchantNo";
|
||||
public static final String REQ_NO = "reqNo";
|
||||
public static final String CHARSET = "charset";
|
||||
public static final String FORMAT_TYPE = "formatType";
|
||||
public static final String SIGN_TYPE = "signType";
|
||||
public static final String ENC_TYPE = "encType";
|
||||
public static final String UTF8 = "UTF-8";
|
||||
public static final String JSON = "JSON";
|
||||
public static final String SHA256 = "SHA-256";
|
||||
public static final String AP7 = "AP7";
|
||||
public static final String YYYYMMDDHHMMSS = "yyyyMMddHHmmss";
|
||||
public static final String SIGN_DATA = "signData";
|
||||
public static final String ENC_DATA = "encData";
|
||||
public static final String RESP_DATA = "respData";
|
||||
public static final String CODE = "code";
|
||||
public static final String DESC = "desc";
|
||||
public static final String SUCCESS_CODE = "00000";
|
||||
/* 公共字段常量 end */
|
||||
|
||||
|
||||
/**
|
||||
* http请求常量 start
|
||||
**/
|
||||
public static final String HTTP = "http";
|
||||
public static final String HTTPS = "https";
|
||||
public static final String CONTENT_TYPE = "Content-Type";
|
||||
public static final String UA = "User-Agent";
|
||||
public static final String PKCS12 = "PKCS12";
|
||||
public static final String TLS = "TLS";
|
||||
public static final String APPLICATION_JSON = "application/json";
|
||||
public static final String USER_AGENT = "jdPay" +
|
||||
" (" + System.getProperty("os.arch") + " " + System.getProperty("os.name") + " " + System.getProperty("os.version") +
|
||||
") Java/" + System.getProperty("java.version") + " HttpClient/" + HttpClient.class.getPackage().getImplementationVersion();
|
||||
/* http请求常量 end */
|
||||
|
||||
public static final String URL_PATH = "/api/";
|
||||
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
package com.hzs.third.pay.jdpay.sdk;
|
||||
|
||||
import com.hzs.third.pay.jdpay.util.FileUtil;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
|
||||
/*************************************************
|
||||
*
|
||||
* 商户默认配置类
|
||||
* 商户也可以自行实现JdPayConfig,调整配置属性
|
||||
*
|
||||
*************************************************/
|
||||
public class JdPayDefaultNewConfig extends JdPayNewConfig {
|
||||
|
||||
private String merchantNo;
|
||||
private String signKey;
|
||||
private byte[] priCert;
|
||||
private String priCertPwd;
|
||||
private byte[] pubCert;
|
||||
private String apiDomain;
|
||||
|
||||
public JdPayDefaultNewConfig(String merchantNo, String signKey, byte[] priCert, String priCertPwd, byte[] pubCert, String apiDomain) {
|
||||
this.merchantNo = merchantNo;
|
||||
this.signKey = signKey;
|
||||
this.priCert = priCert;
|
||||
this.priCertPwd = priCertPwd;
|
||||
this.pubCert = pubCert;
|
||||
this.apiDomain = apiDomain;
|
||||
}
|
||||
|
||||
public JdPayDefaultNewConfig(String merchantNo, String signKey, String priCertPwd, String apiDomain, String priCertPath, String pubCertPath) {
|
||||
this.merchantNo = merchantNo;
|
||||
this.signKey = signKey;
|
||||
this.priCertPwd = priCertPwd;
|
||||
this.apiDomain = apiDomain;
|
||||
|
||||
//加载商户私钥证书
|
||||
this.priCert = FileUtil.readFile(priCertPath);
|
||||
//加载商户公钥证书
|
||||
this.pubCert = FileUtil.readFile(pubCertPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMerchantNo() {
|
||||
return this.merchantNo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSignKey() {
|
||||
return this.signKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getPriCert() {
|
||||
return new ByteArrayInputStream(this.priCert);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPriCertPwd() {
|
||||
return this.priCertPwd;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getPubCert() {
|
||||
return new ByteArrayInputStream(this.pubCert);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getApiDomain() {
|
||||
return this.apiDomain;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,173 @@
|
|||
package com.hzs.third.pay.jdpay.sdk;
|
||||
|
||||
import com.hzs.common.core.exception.ServiceException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.http.*;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.config.RequestConfig;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.config.RegistryBuilder;
|
||||
import org.apache.http.conn.ConnectionKeepAliveStrategy;
|
||||
import org.apache.http.conn.HttpClientConnectionManager;
|
||||
import org.apache.http.conn.socket.ConnectionSocketFactory;
|
||||
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
|
||||
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
|
||||
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
|
||||
import org.apache.http.message.BasicHeaderElementIterator;
|
||||
import org.apache.http.protocol.HTTP;
|
||||
import org.apache.http.protocol.HttpContext;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@Slf4j
|
||||
public class JdPayHttpClient {
|
||||
|
||||
private CloseableHttpClient httpClient = null;
|
||||
|
||||
public JdPayHttpClient() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 远程调用
|
||||
*
|
||||
* @param jdPayNewConfig 京东支付配置
|
||||
* @param urlSuffix 接口url后缀
|
||||
* @param request 请求参数
|
||||
* @return 返回参数看
|
||||
* @throws Exception 远程调用异常信息
|
||||
*/
|
||||
public String execute(JdPayNewConfig jdPayNewConfig, String urlSuffix, String request) {
|
||||
HttpClient httpClient = buildHttpClient(jdPayNewConfig);
|
||||
String url = jdPayNewConfig.getApiDomain() + urlSuffix;
|
||||
int connectTimeoutMs = jdPayNewConfig.getHttpConnectTimeoutMs();
|
||||
int readTimeoutMs = jdPayNewConfig.getHttpReadTimeoutMs(urlSuffix);
|
||||
try {
|
||||
return this.sendRequest(httpClient, url, request, connectTimeoutMs, readTimeoutMs);
|
||||
} catch (IOException e) {
|
||||
throw new ServiceException("HTTP read timeout");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取httpClient
|
||||
*
|
||||
* @param jdPayNewConfig 京东支付配置
|
||||
* @return httpClient
|
||||
*/
|
||||
private HttpClient buildHttpClient(JdPayNewConfig jdPayNewConfig) {
|
||||
if (jdPayNewConfig.useHttpConnectPool()) {
|
||||
return getHttpClientByPoolingConnectionManager(jdPayNewConfig);
|
||||
} else {
|
||||
return getHttpClientByBasicConnectionManager();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送请求
|
||||
*
|
||||
* @param httpClient httpClient
|
||||
* @param url 请求地址
|
||||
* @param data 请求数据
|
||||
* @param connectTimeoutMs 连接超时时间,单位是毫秒
|
||||
* @param readTimeoutMs 读超时时间,单位是毫秒
|
||||
* @return api接口返回参数
|
||||
*/
|
||||
private String sendRequest(HttpClient httpClient, String url, String data, int connectTimeoutMs, int readTimeoutMs) throws IOException {
|
||||
HttpPost httpPost = new HttpPost(url);
|
||||
RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(readTimeoutMs).setConnectTimeout(connectTimeoutMs).build();
|
||||
httpPost.setConfig(requestConfig);
|
||||
|
||||
StringEntity postEntity = new StringEntity(data, JdPayConstant.UTF8);
|
||||
httpPost.addHeader(JdPayConstant.CONTENT_TYPE, JdPayConstant.APPLICATION_JSON);
|
||||
httpPost.addHeader(JdPayConstant.UA, JdPayConstant.USER_AGENT);
|
||||
httpPost.setEntity(postEntity);
|
||||
HttpResponse httpResponse = httpClient.execute(httpPost);
|
||||
int statusCode = httpResponse.getStatusLine().getStatusCode();
|
||||
if (HttpStatus.SC_OK != statusCode) {
|
||||
throw new ServiceException(String.format("httpStatusCode: %s", statusCode));
|
||||
}
|
||||
HttpEntity httpEntity = httpResponse.getEntity();
|
||||
return EntityUtils.toString(httpEntity, JdPayConstant.UTF8);
|
||||
}
|
||||
|
||||
private HttpClient getHttpClientByBasicConnectionManager() {
|
||||
HttpClientConnectionManager connManager = new BasicHttpClientConnectionManager(
|
||||
RegistryBuilder.<ConnectionSocketFactory>create()
|
||||
.register(JdPayConstant.HTTP, PlainConnectionSocketFactory.getSocketFactory())
|
||||
.register(JdPayConstant.HTTPS, SSLConnectionSocketFactory.getSocketFactory())
|
||||
.build(),
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
return HttpClientBuilder.create()
|
||||
// 连接池
|
||||
.setConnectionManager(connManager)
|
||||
// 重试策略
|
||||
.setRetryHandler(new DefaultHttpRequestRetryHandler(0, false)).build();
|
||||
}
|
||||
|
||||
private HttpClient getHttpClientByPoolingConnectionManager(JdPayNewConfig jdPayNewConfig) {
|
||||
if (httpClient != null) {
|
||||
return httpClient;
|
||||
}
|
||||
synchronized (this) {
|
||||
if (httpClient == null) {
|
||||
long start = System.currentTimeMillis();
|
||||
PoolingHttpClientConnectionManager httpClientConnectionManager = new PoolingHttpClientConnectionManager(
|
||||
RegistryBuilder.<ConnectionSocketFactory>create()
|
||||
.register(JdPayConstant.HTTP, PlainConnectionSocketFactory.getSocketFactory())
|
||||
.register(JdPayConstant.HTTPS, SSLConnectionSocketFactory.getSocketFactory())
|
||||
.build()
|
||||
);
|
||||
// 设置最大连接数
|
||||
httpClientConnectionManager.setMaxTotal(jdPayNewConfig.getHttpConnectMaxTotal());
|
||||
// 将每个路由默认最大连接数
|
||||
httpClientConnectionManager.setDefaultMaxPerRoute(jdPayNewConfig.getHttpConnectDefaultTotal());
|
||||
httpClient = HttpClients.custom()
|
||||
// 设置连接池
|
||||
.setConnectionManager(httpClientConnectionManager)
|
||||
// 连接存活策略
|
||||
.setKeepAliveStrategy(getConnectionKeepAliveStrategy())
|
||||
// 重试策略
|
||||
.setRetryHandler(new DefaultHttpRequestRetryHandler(0, false)).build();
|
||||
// 连接回收策略
|
||||
JdPayHttpConnectionMonitor idleConnectionMonitor = new JdPayHttpConnectionMonitor(httpClientConnectionManager, jdPayNewConfig.getHttpConnectIdleAliveMs());
|
||||
idleConnectionMonitor.start();
|
||||
log.info("初始化http连接池耗时:{}", System.currentTimeMillis() - start);
|
||||
}
|
||||
}
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
private ConnectionKeepAliveStrategy getConnectionKeepAliveStrategy() {
|
||||
return new ConnectionKeepAliveStrategy() {
|
||||
@Override
|
||||
public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
|
||||
// Honor 'keep-alive' header
|
||||
HeaderElementIterator it = new BasicHeaderElementIterator(response.headerIterator(HTTP.CONN_KEEP_ALIVE));
|
||||
while (it.hasNext()) {
|
||||
HeaderElement he = it.nextElement();
|
||||
String param = he.getName();
|
||||
String value = he.getValue();
|
||||
if (value != null && "timeout".equalsIgnoreCase(param)) {
|
||||
try {
|
||||
log.info("Keep-Alive指定时长:{}", value);
|
||||
return Long.parseLong(value) * 1000;
|
||||
} catch (NumberFormatException ignore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Keep alive for 300 seconds only
|
||||
return 300 * 1000;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package com.hzs.third.pay.jdpay.sdk;
|
||||
|
||||
import com.hzs.third.pay.jdpay.util.JdPayApiUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Random;
|
||||
|
||||
@Slf4j
|
||||
public class JdPayHttpClientProxy {
|
||||
|
||||
private static final Random RANDOM = new SecureRandom();
|
||||
|
||||
private final JdPayNewConfig jdPayNewConfig;
|
||||
|
||||
private final JdPayHttpClient jdPayHttpClient;
|
||||
|
||||
public JdPayHttpClientProxy(JdPayNewConfig jdPayNewConfig, JdPayHttpClient jdPayHttpClient) {
|
||||
this.jdPayNewConfig = jdPayNewConfig;
|
||||
this.jdPayHttpClient = jdPayHttpClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取随机字符串,含数字和大小写英文字母
|
||||
*/
|
||||
private static String genNonceStr() {
|
||||
char[] nonceChars = new char[32];
|
||||
for (int index = 0; index < nonceChars.length; ++index) {
|
||||
nonceChars[index] = JdPayConstant.SYMBOLS.charAt(RANDOM.nextInt(JdPayConstant.SYMBOLS.length()));
|
||||
}
|
||||
return new String(nonceChars);
|
||||
}
|
||||
|
||||
public String execute(String urlSuffix, String request) throws Exception {
|
||||
long startTimestampMs = System.currentTimeMillis();
|
||||
String response;
|
||||
// 接口名称
|
||||
String apiName = urlSuffix.replaceFirst(JdPayConstant.URL_PATH, "");
|
||||
// 唯一请求号
|
||||
String reqNo = genNonceStr();
|
||||
try {
|
||||
log.info("1.{}接口请求参数:{}", apiName, request);
|
||||
// 请求参数加密和签名
|
||||
String httpRequest = JdPayApiUtil.encryptAndSignature(jdPayNewConfig, reqNo, request);
|
||||
log.info("2.{}远程调用请求参数:{}", apiName, httpRequest);
|
||||
String httpResponse = jdPayHttpClient.execute(jdPayNewConfig, urlSuffix, httpRequest);
|
||||
log.info("3.{}远程调用返回参数:{}", apiName, httpResponse);
|
||||
// 验证和解析返回参数
|
||||
response = JdPayApiUtil.decryptAndVerifySign(jdPayNewConfig, httpResponse);
|
||||
log.info("4.{}耗时:{},接口返回参数:{}", apiName, (System.currentTimeMillis() - startTimestampMs), response);
|
||||
} catch (Exception e) {
|
||||
log.error("{}远程调用异常,接口参数:{}", apiName, request, e);
|
||||
throw e;
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
package com.hzs.third.pay.jdpay.sdk;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.http.conn.HttpClientConnectionManager;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 用于监控空闲的连接池连接
|
||||
*/
|
||||
@Slf4j
|
||||
public class JdPayHttpConnectionMonitor extends Thread {
|
||||
|
||||
// 轮询检查时间间隔,单位毫秒
|
||||
private static final int MONITOR_INTERVAL_MS = 5000;
|
||||
// 连接最大空闲时间,单位毫秒
|
||||
private static int IDLE_ALIVE_MS = 20000;
|
||||
|
||||
private final HttpClientConnectionManager httpClientConnectionManager;
|
||||
|
||||
private volatile boolean shutdown;
|
||||
|
||||
JdPayHttpConnectionMonitor(HttpClientConnectionManager httpClientConnectionManager, int idleAliveMs) {
|
||||
super();
|
||||
this.httpClientConnectionManager = httpClientConnectionManager;
|
||||
IDLE_ALIVE_MS = idleAliveMs;
|
||||
this.shutdown = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
while (!shutdown) {
|
||||
synchronized (this) {
|
||||
wait(MONITOR_INTERVAL_MS);
|
||||
// 关闭无效的连接
|
||||
httpClientConnectionManager.closeExpiredConnections();
|
||||
// 关闭空闲时间超过IDLE_ALIVE_MS的连接
|
||||
httpClientConnectionManager.closeIdleConnections(IDLE_ALIVE_MS, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
log.error("连接池管理任务异常:", e);
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭后台连接
|
||||
public void shutdown() {
|
||||
shutdown = true;
|
||||
synchronized (this) {
|
||||
notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
package com.hzs.third.pay.jdpay.sdk;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* 京东支付配置类
|
||||
*/
|
||||
public abstract class JdPayNewConfig {
|
||||
|
||||
/**
|
||||
* 接口超时时间全局配置
|
||||
*/
|
||||
private static final HashMap<String, Integer> HTTP_READ_TIMEOUT_CONFIG = new HashMap<String, Integer>() {{
|
||||
put(JdPayConstant.CREATE_ORDER_URL, 15000);
|
||||
put(JdPayConstant.TRADE_QUERY_URL, 5000);
|
||||
put(JdPayConstant.REFUND_URL, 5000);
|
||||
put(JdPayConstant.REFUND_QUERY_URL, 5000);
|
||||
put(JdPayConstant.AGREEMENT_PAY_URL, 10000);
|
||||
put(JdPayConstant.AGREEMENT_QUERY_URL, 5000);
|
||||
put(JdPayConstant.AGREEMENT_CANCEL_URL, 5000);
|
||||
}};
|
||||
|
||||
/**
|
||||
* 获取merchantNo
|
||||
*
|
||||
* @return merchantNo
|
||||
*/
|
||||
public abstract String getMerchantNo();
|
||||
|
||||
/**
|
||||
* 获取signKey
|
||||
*
|
||||
* @return signKey
|
||||
*/
|
||||
public abstract String getSignKey();
|
||||
|
||||
/**
|
||||
* 获取 私钥证书
|
||||
*
|
||||
* @return 私钥证书
|
||||
*/
|
||||
public abstract InputStream getPriCert();
|
||||
|
||||
/**
|
||||
* 获取 私钥证书密钥
|
||||
*
|
||||
* @return 私钥证书密钥
|
||||
*/
|
||||
public abstract String getPriCertPwd();
|
||||
|
||||
/**
|
||||
* 获取 公钥证书
|
||||
*
|
||||
* @return 公钥证书
|
||||
*/
|
||||
public abstract InputStream getPubCert();
|
||||
|
||||
/**
|
||||
* 获取域名-新api接口
|
||||
*
|
||||
* @return 域名-新api接口
|
||||
*/
|
||||
public abstract String getApiDomain();
|
||||
|
||||
/**
|
||||
* HTTP(S) 连接超时时间,单位毫秒
|
||||
*
|
||||
* @return 连接时间
|
||||
*/
|
||||
public int getHttpConnectTimeoutMs() {
|
||||
return 6 * 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置HTTP(S) 读数据超时时间,单位毫秒
|
||||
*
|
||||
* @param urlSuffix api地址除去域名后的路径
|
||||
* @param httpReadTimeoutMs 读超时时间,单位毫秒
|
||||
*/
|
||||
public void setHttpReadTimeoutMs(String urlSuffix, int httpReadTimeoutMs) {
|
||||
HTTP_READ_TIMEOUT_CONFIG.put(urlSuffix, httpReadTimeoutMs);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询HTTP(S) 读数据超时时间,单位毫秒
|
||||
*
|
||||
* @return 读超时时间,单位毫秒
|
||||
*/
|
||||
public int getHttpReadTimeoutMs(String requestPath) {
|
||||
if (!HTTP_READ_TIMEOUT_CONFIG.containsKey(requestPath)) {
|
||||
return 15000;
|
||||
}
|
||||
return HTTP_READ_TIMEOUT_CONFIG.get(requestPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否自动上报异常请求。默认为 true
|
||||
* 若要关闭,子类中实现该函数返回 false 即可。
|
||||
*/
|
||||
public boolean shouldAutoReport() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 进行异常上报的线程的数量
|
||||
*/
|
||||
public int getReportWorkerNum() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量上报,一次报多条异常数据
|
||||
*/
|
||||
public int getReportBatchSize() {
|
||||
return 5;
|
||||
}
|
||||
|
||||
/**
|
||||
* 异常上报缓存消息队列最大数量。
|
||||
* 队列满后,不会上报新增的异常信息
|
||||
*/
|
||||
public int getReportQueueMaxSize() {
|
||||
return 50;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否使用http连接池
|
||||
*/
|
||||
public boolean useHttpConnectPool() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* http连接池最大连接数量
|
||||
*/
|
||||
public int getHttpConnectMaxTotal() {
|
||||
return 800;
|
||||
}
|
||||
|
||||
/**
|
||||
* http连接池默认连接数量
|
||||
*/
|
||||
public int getHttpConnectDefaultTotal() {
|
||||
return 100;
|
||||
}
|
||||
|
||||
/**
|
||||
* http连接最大闲置时长,单位毫秒
|
||||
*/
|
||||
public int getHttpConnectIdleAliveMs() {
|
||||
return 20000;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package com.hzs.third.pay.jdpay.sdk;
|
||||
|
||||
import org.bouncycastle.util.encoders.Base64;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
public class JdPaySecurity {
|
||||
|
||||
public String signEnvelop(InputStream signCert, String password, InputStream envelopCert, byte[] orgData) {
|
||||
byte[] signData = JdPaySign.getInstance().attachSign(signCert, password, orgData);
|
||||
byte[] envelop = JdPaySign.getInstance().encryptEnvelop(envelopCert, signData);
|
||||
return new String(Base64.encode(envelop));
|
||||
}
|
||||
|
||||
public byte[] verifyEnvelop(InputStream envelopCert, String password, byte[] envelopData) {
|
||||
byte[] signData = JdPaySign.getInstance().decryptEnvelop(envelopCert, password, envelopData);
|
||||
return JdPaySign.getInstance().verifyAttachSign(signData);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,200 @@
|
|||
package com.hzs.third.pay.jdpay.sdk;
|
||||
|
||||
import com.hzs.common.core.exception.ServiceException;
|
||||
import com.hzs.third.pay.jdpay.util.CertUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.bouncycastle.asn1.DEROutputStream;
|
||||
import org.bouncycastle.cert.X509CertificateHolder;
|
||||
import org.bouncycastle.cert.jcajce.JcaCertStore;
|
||||
import org.bouncycastle.cms.*;
|
||||
import org.bouncycastle.cms.jcajce.*;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.operator.ContentSigner;
|
||||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
|
||||
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
|
||||
import org.bouncycastle.util.Store;
|
||||
|
||||
import javax.security.auth.x500.X500PrivateCredential;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.Security;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
public class JdPaySign {
|
||||
|
||||
private static final String SIGN_ALGORITHMS = "SHA1WITHRSA";
|
||||
private static final String BC = "BC";
|
||||
|
||||
private static JdPaySign INSTANCE = null;
|
||||
|
||||
static {
|
||||
BouncyCastleProvider provider = new BouncyCastleProvider();
|
||||
Security.addProvider(provider);
|
||||
}
|
||||
|
||||
private JdPaySign() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 单例,双重校验
|
||||
*/
|
||||
public static JdPaySign getInstance() {
|
||||
if (INSTANCE == null) {
|
||||
synchronized (JdPaySign.class) {
|
||||
if (INSTANCE == null) {
|
||||
INSTANCE = new JdPaySign();
|
||||
}
|
||||
}
|
||||
}
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
|
||||
public byte[] attachSign(InputStream priCert, String password, byte[] data) {
|
||||
return this.sign(priCert, password, data, true);
|
||||
}
|
||||
|
||||
public byte[] detachSign(InputStream priCert, String password, byte[] data) {
|
||||
return this.sign(priCert, password, data, false);
|
||||
}
|
||||
|
||||
private byte[] sign(InputStream priCert, String password, byte[] data, boolean isDetach) {
|
||||
try {
|
||||
X500PrivateCredential privateCert = CertUtil.getPrivateCert(priCert, password.toCharArray());
|
||||
X509Certificate x509Certificate = privateCert.getCertificate();
|
||||
PrivateKey privateKey = privateCert.getPrivateKey();
|
||||
|
||||
List<X509Certificate> certList = new ArrayList<>();
|
||||
certList.add(x509Certificate);
|
||||
Store certs = new JcaCertStore(certList);
|
||||
|
||||
CMSSignedDataGenerator generator = new CMSSignedDataGenerator();
|
||||
ContentSigner sha1Signer = new JcaContentSignerBuilder(SIGN_ALGORITHMS).setProvider(BC).build(privateKey);
|
||||
generator.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build()).build(sha1Signer, x509Certificate));
|
||||
generator.addCertificates(certs);
|
||||
|
||||
CMSTypedData msg = new CMSProcessableByteArray(data);
|
||||
return generator.generate(msg, isDetach).getEncoded();
|
||||
} catch (CertificateException e) {
|
||||
log.error("=====", e);
|
||||
log.error(e.getMessage());
|
||||
throw new ServiceException(e.getMessage());
|
||||
} catch (Exception e) {
|
||||
log.error("-----", e);
|
||||
log.error(e.getMessage());
|
||||
throw new ServiceException("签名异常");
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
public int verifyDetachSign(byte[] data, byte[] signData) {
|
||||
try {
|
||||
CMSProcessable content = new CMSProcessableByteArray(data);
|
||||
CMSSignedData s = new CMSSignedData(content, signData);
|
||||
Store certStore = s.getCertificates();
|
||||
SignerInformationStore signers = s.getSignerInfos();
|
||||
Collection c = signers.getSigners();
|
||||
Iterator it = c.iterator();
|
||||
int verified = 0, size = 0;
|
||||
while (it.hasNext()) {
|
||||
size++;
|
||||
SignerInformation signer = (SignerInformation) it.next();
|
||||
Collection certCollection = certStore.getMatches(signer.getSID());
|
||||
Iterator certIt = certCollection.iterator();
|
||||
X509CertificateHolder cert = (X509CertificateHolder) certIt.next();
|
||||
if (signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert))) {
|
||||
verified++;
|
||||
}
|
||||
}
|
||||
if (size == verified) {
|
||||
return 1;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"rawtypes"})
|
||||
public byte[] verifyAttachSign(byte[] signData) {
|
||||
try {
|
||||
byte[] data = null;
|
||||
CMSSignedData s = new CMSSignedData(signData);
|
||||
Store certStore = s.getCertificates();
|
||||
SignerInformationStore signers = s.getSignerInfos();
|
||||
Collection c = signers.getSigners();
|
||||
Iterator it = c.iterator();
|
||||
int verified = 0, size = 0;
|
||||
|
||||
while (it.hasNext()) {
|
||||
size++;
|
||||
SignerInformation signer = (SignerInformation) it.next();
|
||||
Collection certCollection = certStore.getMatches(signer.getSID());
|
||||
Iterator certIt = certCollection.iterator();
|
||||
X509CertificateHolder cert = (X509CertificateHolder) certIt.next();
|
||||
|
||||
if (signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert))) {
|
||||
verified++;
|
||||
CMSTypedData cmsData = s.getSignedContent();
|
||||
data = (byte[]) cmsData.getContent();
|
||||
}
|
||||
}
|
||||
if (size == verified) {
|
||||
return data;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public byte[] encryptEnvelop(InputStream pubCert, byte[] bOrgData) {
|
||||
try {
|
||||
X509Certificate publicCert = CertUtil.getPublicCert(pubCert);
|
||||
CMSEnvelopedDataGenerator generator = new CMSEnvelopedDataGenerator();
|
||||
generator.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(publicCert).setProvider(BC));
|
||||
CMSEnvelopedData enveloped = generator.generate(new CMSProcessableByteArray(bOrgData), new JceCMSContentEncryptorBuilder(CMSAlgorithm.DES_EDE3_CBC).setProvider(BC).build());
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
new DEROutputStream(out).writeObject(enveloped.toASN1Structure());
|
||||
byte[] result = out.toByteArray();
|
||||
out.close();
|
||||
return result;
|
||||
} catch (CertificateException | CMSException | IOException e) {
|
||||
log.error("加密异常:{}", e.getMessage());
|
||||
throw new ServiceException("加密异常");
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] decryptEnvelop(InputStream priCert, String privateKeyPassword, byte[] bEnvelop) {
|
||||
try {
|
||||
CMSEnvelopedData enveloped = new CMSEnvelopedData(bEnvelop);
|
||||
RecipientInformationStore ris = enveloped.getRecipientInfos();
|
||||
if (ris == null) {
|
||||
log.error("数字信封格式不对:{}", new String(bEnvelop));
|
||||
throw new ServiceException("验签异常");
|
||||
}
|
||||
|
||||
X500PrivateCredential privateCert = CertUtil.getPrivateCert(priCert, privateKeyPassword.toCharArray());
|
||||
PrivateKey privateKey = privateCert.getPrivateKey();
|
||||
|
||||
byte[] sign = null;
|
||||
Collection recipients = ris.getRecipients();
|
||||
for (Object object : recipients) {
|
||||
RecipientInformation recipient = (RecipientInformation) object;
|
||||
sign = recipient.getContent(new JceKeyTransEnvelopedRecipient(privateKey).setProvider(BC));
|
||||
}
|
||||
return sign;
|
||||
} catch (CMSException | CertificateException e) {
|
||||
log.error("验签异常:{}", e.getMessage());
|
||||
throw new ServiceException("验签异常");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
package com.hzs.third.pay.jdpay.util;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.security.auth.x500.X500PrivateCredential;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.*;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Enumeration;
|
||||
|
||||
@Slf4j
|
||||
public class CertUtil {
|
||||
|
||||
private static final String PKCS12 = "PKCS12";
|
||||
|
||||
public static X500PrivateCredential getPrivateCert(InputStream pfxCert, char[] privateKeyPassword) throws CertificateException {
|
||||
KeyStore keyStore;
|
||||
String keyStoreAlias = null;
|
||||
|
||||
/* Load KeyStore contents from file */
|
||||
try {
|
||||
keyStore = KeyStore.getInstance(CertUtil.PKCS12);
|
||||
keyStore.load(pfxCert, privateKeyPassword);
|
||||
|
||||
/* Get aliases */
|
||||
Enumeration aliases = keyStore.aliases();
|
||||
if (aliases != null) {
|
||||
while (aliases.hasMoreElements()) {
|
||||
keyStoreAlias = (String) aliases.nextElement();
|
||||
Certificate[] certs = keyStore.getCertificateChain(keyStoreAlias);
|
||||
if (certs == null || certs.length == 0) {
|
||||
continue;
|
||||
}
|
||||
X509Certificate cert = (X509Certificate) certs[0];
|
||||
if (matchUsage(cert.getKeyUsage(), 1)) {
|
||||
try {
|
||||
cert.checkValidity();
|
||||
} catch (CertificateException e) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (GeneralSecurityException | IOException e) {
|
||||
log.error("===============", e);
|
||||
throw new CertificateException("Error initializing keystore");
|
||||
}
|
||||
|
||||
if (keyStoreAlias == null) {
|
||||
throw new CertificateException("None certificate for sign in this keystore");
|
||||
}
|
||||
|
||||
/* Get certificate chain and create a certificate path */
|
||||
Certificate[] fromKeyStore;
|
||||
try {
|
||||
fromKeyStore = keyStore.getCertificateChain(keyStoreAlias);
|
||||
if (fromKeyStore == null
|
||||
|| fromKeyStore.length == 0
|
||||
|| !(fromKeyStore[0] instanceof X509Certificate)) {
|
||||
throw new CertificateException("Unable to find X.509 certificate chain in keystore");
|
||||
}
|
||||
} catch (KeyStoreException e) {
|
||||
throw new CertificateException("Error using keystore");
|
||||
}
|
||||
|
||||
/* Get PrivateKey */
|
||||
Key privateKey;
|
||||
try {
|
||||
privateKey = keyStore.getKey(keyStoreAlias, privateKeyPassword);
|
||||
if (!(privateKey instanceof PrivateKey)) {
|
||||
throw new CertificateException("Unable to recover key from keystore");
|
||||
}
|
||||
} catch (KeyStoreException | NoSuchAlgorithmException e) {
|
||||
throw new CertificateException("Error using keystore");
|
||||
} catch (UnrecoverableKeyException e) {
|
||||
throw new CertificateException("Unable to recover key from keystore");
|
||||
}
|
||||
|
||||
X509Certificate certificate = (X509Certificate) fromKeyStore[0];
|
||||
return new X500PrivateCredential(certificate, (PrivateKey) privateKey, keyStoreAlias);
|
||||
}
|
||||
|
||||
public static X509Certificate getPublicCert(InputStream publicCert) throws CertificateException {
|
||||
try {
|
||||
CertificateFactory cf = CertificateFactory.getInstance("X509");
|
||||
Certificate certificate = cf.generateCertificate(publicCert);
|
||||
return (X509Certificate) certificate;
|
||||
} catch (CertificateException e) {
|
||||
throw new CertificateException("Error loading public key certificate");
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean matchUsage(boolean[] keyUsage, int usage) {
|
||||
if (usage == 0 || keyUsage == null) {
|
||||
return true;
|
||||
}
|
||||
for (int i = 0; i < Math.min(keyUsage.length, 32); i++) {
|
||||
if ((usage & (1 << i)) != 0 && !keyUsage[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package com.hzs.third.pay.jdpay.util;
|
||||
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
public class FileUtil {
|
||||
|
||||
public FileUtil() {
|
||||
}
|
||||
|
||||
public static byte[] readFile(String filename) {
|
||||
ClassPathResource classPathResource = new ClassPathResource(filename);
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
BufferedInputStream in = null;
|
||||
try {
|
||||
in = new BufferedInputStream(classPathResource.getInputStream());
|
||||
byte[] buffer = new byte[1024];
|
||||
int len;
|
||||
while (-1 != (len = in.read(buffer, 0, 1024))) {
|
||||
bos.write(buffer, 0, len);
|
||||
}
|
||||
return bos.toByteArray();
|
||||
} catch (IOException var21) {
|
||||
var21.printStackTrace();
|
||||
} finally {
|
||||
try {
|
||||
if (null != in) {
|
||||
in.close();
|
||||
}
|
||||
} catch (IOException var20) {
|
||||
var20.printStackTrace();
|
||||
}
|
||||
try {
|
||||
bos.close();
|
||||
} catch (IOException var19) {
|
||||
var19.printStackTrace();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,171 @@
|
|||
package com.hzs.third.pay.jdpay.util;
|
||||
|
||||
import com.google.gson.ExclusionStrategy;
|
||||
import com.google.gson.FieldAttributes;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.internal.Primitives;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Collection;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/*************************************************
|
||||
*
|
||||
* Gson工具类,对Google的gson工具进行封装
|
||||
* (1)日期格式yyyyMMddHHmmss
|
||||
* (2)toJson时支持掩码
|
||||
* (3)toJson时支持跳过指定属性
|
||||
*************************************************/
|
||||
public class GsonUtil {
|
||||
private static final String EMPTY_JSON = "{}";
|
||||
private static final String EMPTY_JSON_ARRAY = "[]";
|
||||
private static final String DEFAULT_DATE_PATTERN = "yyyyMMddHHmmss";
|
||||
private static final Gson DEFAULT_GSON;
|
||||
|
||||
static {
|
||||
GsonBuilder builder = new GsonBuilder();
|
||||
builder.setDateFormat(DEFAULT_DATE_PATTERN);
|
||||
DEFAULT_GSON = builder.create();
|
||||
DEFAULT_GSON.toJson(null);
|
||||
}
|
||||
|
||||
public static String toJson(Object target) {
|
||||
return toJson(target, null, null, null);
|
||||
}
|
||||
|
||||
public static String toJson(Object target, Type targetType) {
|
||||
return toJson(target, targetType, null, null);
|
||||
}
|
||||
|
||||
public static String toMaskJson(Object target) {
|
||||
return toJson(target, null, null, null);
|
||||
}
|
||||
|
||||
public static String toMaskJson(Object target, List<String> excludeFields) {
|
||||
return toJson(target, null, null, excludeFields);
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印目标对象的json串
|
||||
*
|
||||
* @param target 目标对象
|
||||
* @param targetType 对象类型,可为null
|
||||
* @param datePattern 日期格式,可为null,若为null则按 DEFAULT_DATE_PATTERN格式输出
|
||||
* @param excludeFields 不打印的字段,可为null
|
||||
* @return 目标对象的标准json串
|
||||
*/
|
||||
public static String toJson(Object target, Type targetType, String datePattern, final List<String> excludeFields) {
|
||||
if (target == null) {
|
||||
return EMPTY_JSON;
|
||||
}
|
||||
|
||||
Gson gson = DEFAULT_GSON;
|
||||
if (null != datePattern && !"".equals(datePattern)) {
|
||||
gson = getGson(datePattern);
|
||||
}
|
||||
if (isNotEmpty(excludeFields)) {
|
||||
gson = getStrategyGson(excludeFields);
|
||||
}
|
||||
|
||||
String result = emptyResult(target);
|
||||
try {
|
||||
if (targetType == null) {
|
||||
targetType = target.getClass();
|
||||
}
|
||||
result = gson.toJson(target, targetType);
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <T> T fromJson(String json, TypeToken<T> token) {
|
||||
return fromJson(json, token, null);
|
||||
}
|
||||
|
||||
public static <T> T fromJson(String json, TypeToken<T> token, String datePattern) {
|
||||
return fromJson(json, token.getType(), datePattern);
|
||||
}
|
||||
|
||||
public static <T> T fromJson(String json, Class<T> classOfT) {
|
||||
Object object = fromJson(json, (Type) classOfT, null);
|
||||
return Primitives.wrap(classOfT).cast(object);
|
||||
}
|
||||
|
||||
public static <T> T fromJson(String json, Class<T> classOfT, String datePattern) {
|
||||
Object object = fromJson(json, (Type) classOfT, datePattern);
|
||||
return Primitives.wrap(classOfT).cast(object);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将json串转化为目标对象
|
||||
*
|
||||
* @param json json串
|
||||
* @param type 目标对象类型
|
||||
* @param datePattern json串中日期格式,若为null,则按两个标准日期尝试解析:DEFAULT_DATE_PATTERN和DEFAULT_DATE_PATTERN_1
|
||||
* @param <T>
|
||||
* @return 目标对象
|
||||
*/
|
||||
public static <T> T fromJson(String json, Type type, String datePattern) {
|
||||
if (null == json || "".equals(json)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (null == datePattern || "".equals(datePattern)) {
|
||||
try {
|
||||
return DEFAULT_GSON.fromJson(json, type);
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
Gson gson = getGson(datePattern);
|
||||
return gson.fromJson(json, type);
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Gson getGson(String datePattern) {
|
||||
GsonBuilder builder = new GsonBuilder();
|
||||
builder.setDateFormat(datePattern);
|
||||
return builder.create();
|
||||
}
|
||||
|
||||
private static Gson getStrategyGson(final List<String> excludeFields) {
|
||||
ExclusionStrategy myExclusionStrategy = new ExclusionStrategy() {
|
||||
@Override
|
||||
public boolean shouldSkipField(FieldAttributes fa) {
|
||||
return excludeFields != null && excludeFields.contains(fa.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldSkipClass(Class<?> clazz) {
|
||||
return false;
|
||||
}
|
||||
|
||||
};
|
||||
return new GsonBuilder().setExclusionStrategies(myExclusionStrategy).create();
|
||||
}
|
||||
|
||||
private static boolean isNotEmpty(final List<String> fieldNames) {
|
||||
return fieldNames != null && !fieldNames.isEmpty();
|
||||
}
|
||||
|
||||
private static String emptyResult(Object target) {
|
||||
if (target == null) {
|
||||
return EMPTY_JSON;
|
||||
}
|
||||
if (target instanceof Collection
|
||||
|| target instanceof Iterator
|
||||
|| target instanceof Enumeration
|
||||
|| target.getClass().isArray()) {
|
||||
return EMPTY_JSON_ARRAY;
|
||||
}
|
||||
return EMPTY_JSON;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
package com.hzs.third.pay.jdpay.util;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.hzs.third.pay.jdpay.sdk.JdPayNewConfig;
|
||||
import com.hzs.third.pay.jdpay.sdk.JdPayConstant;
|
||||
import com.hzs.third.pay.jdpay.sdk.JdPaySecurity;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/*************************************************
|
||||
*
|
||||
* 京东支付接口工具类
|
||||
*
|
||||
*************************************************/
|
||||
public class JdPayApiUtil {
|
||||
|
||||
/**
|
||||
* 加密和签名
|
||||
*/
|
||||
public static String encryptAndSignature(JdPayNewConfig jdPayNewConfig, String reqNo, String jsonParam) throws IOException {
|
||||
// 组装公共请求参数
|
||||
Map<String, String> commonParam = fillCommonParam(jdPayNewConfig.getMerchantNo(), reqNo);
|
||||
// 加密
|
||||
byte[] dataBytes = jsonParam.getBytes(StandardCharsets.UTF_8);
|
||||
JdPaySecurity se = new JdPaySecurity();
|
||||
String encData = se.signEnvelop(jdPayNewConfig.getPriCert(), jdPayNewConfig.getPriCertPwd(), jdPayNewConfig.getPubCert(), dataBytes);
|
||||
commonParam.put(JdPayConstant.ENC_DATA, encData);
|
||||
// 签名
|
||||
String sign = SignUtil.sign(commonParam, JdPayConstant.SHA256, jdPayNewConfig.getSignKey(), JdPayConstant.UTF8);
|
||||
commonParam.put(JdPayConstant.SIGN_DATA, sign);
|
||||
return GsonUtil.toJson(commonParam);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密和验签
|
||||
*/
|
||||
public static String decryptAndVerifySign(JdPayNewConfig jdPayNewConfig, String respText) throws Exception {
|
||||
Map<String, String> respMap = GsonUtil.fromJson(respText, new TypeToken<Map<String, String>>() {
|
||||
});
|
||||
String code = respMap.get(JdPayConstant.CODE);
|
||||
if (!JdPayConstant.SUCCESS_CODE.equals(code)) {
|
||||
return respText;
|
||||
}
|
||||
if (!respMap.containsKey(JdPayConstant.SIGN_DATA)) {
|
||||
throw new Exception(String.format("No sign field in response: %s", respText));
|
||||
}
|
||||
String sign = respMap.remove(JdPayConstant.SIGN_DATA);
|
||||
String signType = respMap.get(JdPayConstant.SIGN_TYPE);
|
||||
String charset = respMap.get(JdPayConstant.CHARSET);
|
||||
boolean isRespSignValid = SignUtil.verify(sign, respMap, signType, jdPayNewConfig.getSignKey(), charset);
|
||||
if (!isRespSignValid) {
|
||||
throw new Exception(String.format("Invalid sign value in response: %s", respText));
|
||||
}
|
||||
return SignUtil.decodeBase64(respMap.get(JdPayConstant.RESP_DATA), respMap.get(JdPayConstant.CHARSET), false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 组装api公共参数赋值
|
||||
*/
|
||||
private static Map<String, String> fillCommonParam(String merchantNo, String reqNo) {
|
||||
|
||||
Map<String, String> reqMap = new HashMap<>();
|
||||
// 二级商户号
|
||||
reqMap.put(JdPayConstant.MERCHANT_NO, merchantNo);
|
||||
//商户生成的唯一标识,可以与outTradeNo一致
|
||||
reqMap.put(JdPayConstant.REQ_NO, reqNo);
|
||||
//字符集
|
||||
reqMap.put(JdPayConstant.CHARSET, JdPayConstant.UTF8);
|
||||
//固定值
|
||||
reqMap.put(JdPayConstant.FORMAT_TYPE, JdPayConstant.JSON);
|
||||
//签名类型
|
||||
reqMap.put(JdPayConstant.SIGN_TYPE, JdPayConstant.SHA256);
|
||||
//固定值,证书加密
|
||||
reqMap.put(JdPayConstant.ENC_TYPE, JdPayConstant.AP7);
|
||||
return reqMap;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
package com.hzs.third.pay.jdpay.util;
|
||||
|
||||
import com.hzs.third.pay.jdpay.sdk.JdPayConstant;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.commons.codec.binary.BaseNCodec;
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
public class SignUtil {
|
||||
|
||||
private static final String[] HEX_STRINGS;
|
||||
|
||||
static {
|
||||
HEX_STRINGS = new String[256];
|
||||
for (int i = 0; i < 256; i++) {
|
||||
StringBuilder d = new StringBuilder(2);
|
||||
char ch = Character.forDigit(((byte) i >> 4) & 0x0F, 16);
|
||||
d.append(Character.toUpperCase(ch));
|
||||
ch = Character.forDigit((byte) i & 0x0F, 16);
|
||||
d.append(Character.toUpperCase(ch));
|
||||
HEX_STRINGS[i] = d.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算签名
|
||||
*
|
||||
* @param map 有key和value的map,使用=和&拼接所有参数,
|
||||
* "sign_type", "sign_data", "encrypt_type", "encrypt_data"不参加计算
|
||||
* @param algorithm 签名算法 MD5, SHA-1, SHA-256
|
||||
* @param salt 签名密钥
|
||||
* @param charset 字符串编码
|
||||
* @return 签名
|
||||
*/
|
||||
public static String sign(Map<String, String> map, String algorithm, String salt, String charset) throws UnsupportedEncodingException {
|
||||
String linkString = map2LinkString(map);
|
||||
String data = linkString + salt;
|
||||
return digestHex(algorithm, data, charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证签名正确性。
|
||||
*
|
||||
* @param sign 签名数据
|
||||
* @param map 数据
|
||||
* @param algorithm 签名算法 MD5, SHA-1, SHA-256
|
||||
* @param salt 签名密钥
|
||||
* @param charset 字符串
|
||||
* @return 验证结果
|
||||
*/
|
||||
public static boolean verify(String sign,
|
||||
Map<String, String> map,
|
||||
String algorithm,
|
||||
String salt,
|
||||
String charset) throws UnsupportedEncodingException {
|
||||
if (sign == null || "".equals(sign.trim()) || map.size() == 0) {
|
||||
return false;
|
||||
}
|
||||
String newSign = sign(map, algorithm, salt, charset);
|
||||
return newSign.equals(sign);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证页面回调
|
||||
*
|
||||
* @param respMap 页面回调参数
|
||||
* @param signKey signKey
|
||||
* @return 验证结果
|
||||
* @throws NoSuchAlgorithmException
|
||||
*/
|
||||
public static boolean verifyPageCallBackSign(Map<String, String> respMap, String signKey) throws UnsupportedEncodingException {
|
||||
String sign = respMap.remove("sign");
|
||||
String newSign = sign(respMap, JdPayConstant.SHA256, signKey, JdPayConstant.UTF8);
|
||||
return newSign.equals(sign);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将MAP数据用=和&拼接成String
|
||||
*
|
||||
* @param map 数据
|
||||
* @return 字符串
|
||||
*/
|
||||
public static String map2LinkString(Map<String, String> map) {
|
||||
ArrayList<String> mapKeys = new ArrayList<String>(map.keySet());
|
||||
Collections.sort(mapKeys);
|
||||
StringBuilder link = new StringBuilder(2048);
|
||||
for (String key : mapKeys) {
|
||||
String value = map.get(key);
|
||||
// 属性为空不参与签名
|
||||
if (value == null || "".equals(value.trim())) {
|
||||
continue;
|
||||
}
|
||||
link.append(key).append("=").append(value).append("&");
|
||||
}
|
||||
// 删除末尾的&
|
||||
link.deleteCharAt(link.length() - 1);
|
||||
return link.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 对数据进行指定算法的数据摘要
|
||||
*
|
||||
* @param algorithm 算法名,如MD2, MD5, SHA-1, SHA-256, SHA-512
|
||||
* @param data 待计算的数据
|
||||
* @param charset 字符串的编码
|
||||
* @return 摘要结果
|
||||
*/
|
||||
public static String digestHex(String algorithm, String data, String charset) throws UnsupportedEncodingException {
|
||||
byte[] digest = DigestUtils.getDigest(algorithm).digest(data.getBytes(charset));
|
||||
return hexString(digest);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将字节数组转换成HEX String
|
||||
*
|
||||
* @param b
|
||||
* @return HEX String
|
||||
*/
|
||||
public static String hexString(byte[] b) {
|
||||
StringBuilder d = new StringBuilder(b.length * 2);
|
||||
for (byte aB : b) {
|
||||
d.append(HEX_STRINGS[(int) aB & 0xFF]);
|
||||
}
|
||||
return d.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 对数据进行BASE64解码
|
||||
*
|
||||
* @param base64Data Base64数据
|
||||
* @param charset 解码的编码格式
|
||||
* @param urlSafe 是否是URL安全的,如果为true,则将会被URL编码的'+', '/'转成'-', '_'
|
||||
* @param oneLine 是否是一行
|
||||
* @return 解码后数据
|
||||
*/
|
||||
public static String decodeBase64(String base64Data, String charset, boolean urlSafe, boolean oneLine) throws UnsupportedEncodingException {
|
||||
Base64 base64 = oneLine ? new Base64(BaseNCodec.MIME_CHUNK_SIZE, null, urlSafe) : new Base64(urlSafe);
|
||||
byte[] binaryData = base64.decode(base64Data);
|
||||
return new String(binaryData, charset);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package com.hzs.third.pay.service;
|
||||
|
||||
import com.hzs.common.core.domain.R;
|
||||
import com.hzs.common.core.enums.EDataSource;
|
||||
import com.hzs.common.domain.third.pay.TOnlinePayment;
|
||||
|
||||
/**
|
||||
* 京东支付服务
|
||||
*/
|
||||
public interface IJdPayService {
|
||||
|
||||
/**
|
||||
* 扫码支付
|
||||
*
|
||||
* @param onlinePayment
|
||||
* @return
|
||||
*/
|
||||
R<String> scanPay(TOnlinePayment onlinePayment);
|
||||
|
||||
/**
|
||||
* 银行卡支付
|
||||
*
|
||||
* @param onlinePayment
|
||||
* @param bindCode
|
||||
* @return
|
||||
*/
|
||||
R<String> bankPay(TOnlinePayment onlinePayment, String bindCode);
|
||||
|
||||
/**
|
||||
* 收银台支付
|
||||
*
|
||||
* @param onlinePayment
|
||||
* @param dataSource
|
||||
* @return
|
||||
*/
|
||||
R<String> cashRegister(TOnlinePayment onlinePayment, EDataSource dataSource);
|
||||
|
||||
}
|
|
@ -0,0 +1,264 @@
|
|||
package com.hzs.third.pay.service.impl;
|
||||
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.hzs.common.core.config.BdConfig;
|
||||
import com.hzs.common.core.domain.R;
|
||||
import com.hzs.common.core.enums.EBindStatus;
|
||||
import com.hzs.common.core.enums.EDataSource;
|
||||
import com.hzs.common.core.enums.EEnv;
|
||||
import com.hzs.common.core.utils.DateUtils;
|
||||
import com.hzs.common.domain.third.pay.TOnlineCard;
|
||||
import com.hzs.common.domain.third.pay.TOnlinePayment;
|
||||
import com.hzs.third.pay.config.JdPayConfig;
|
||||
import com.hzs.third.pay.constants.JdPayConstants;
|
||||
import com.hzs.third.pay.constants.PayRedisConstants;
|
||||
import com.hzs.third.pay.dto.jd.JdFastDTO;
|
||||
import com.hzs.third.pay.dto.jd.JdFastResult;
|
||||
import com.hzs.third.pay.dto.jd.JdScanDTO;
|
||||
import com.hzs.third.pay.dto.jd.JdScanResult;
|
||||
import com.hzs.third.pay.enums.EJdAppType;
|
||||
import com.hzs.third.pay.enums.EJdTerminalType;
|
||||
import com.hzs.third.pay.jdpay.dto.JdPayAggregateCreateOrderRequest;
|
||||
import com.hzs.third.pay.jdpay.dto.JdPayAggregateCreateOrderResponse;
|
||||
import com.hzs.third.pay.jdpay.sdk.JdPay;
|
||||
import com.hzs.third.pay.service.IJdPayService;
|
||||
import com.hzs.third.pay.service.ITOnlineCardService;
|
||||
import com.hzs.third.pay.util.JdPayUtil;
|
||||
import com.hzs.third.pay.util.PayUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigDecimal;
|
||||
import java.net.InetAddress;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 京东支付服务
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class JdPayServiceImpl implements IJdPayService {
|
||||
|
||||
@Autowired
|
||||
private JdPayConfig jdPayConfig;
|
||||
@Autowired
|
||||
private ITOnlineCardService itOnlineCardService;
|
||||
@Resource
|
||||
private JdPay jdPay;
|
||||
|
||||
@Autowired
|
||||
private RedisTemplate redisTemplate;
|
||||
|
||||
/**
|
||||
* 页面回调地址
|
||||
*/
|
||||
@Value("${jd.pay.pageBackUrl}")
|
||||
private String pageBackUrl;
|
||||
/**
|
||||
* 支付回调地址
|
||||
*/
|
||||
@Value("${jd.pay.notifyUrl}")
|
||||
private String notifyUrl;
|
||||
|
||||
|
||||
@Override
|
||||
public R<String> scanPay(TOnlinePayment onlinePayment) {
|
||||
try {
|
||||
// 京东付款码缓存KEY
|
||||
String scanKey = String.format(PayRedisConstants.JD_SCAN_KEY, onlinePayment.getBusinessType(), onlinePayment.getBusinessCode());
|
||||
if (redisTemplate.hasKey(scanKey)) {
|
||||
Object payInfo = redisTemplate.opsForValue().get(scanKey);
|
||||
if (null != payInfo) {
|
||||
return R.ok(payInfo.toString());
|
||||
}
|
||||
}
|
||||
|
||||
// 生成请求实体
|
||||
JdScanDTO jdScan = JdScanDTO.builder()
|
||||
// 商户编号
|
||||
.customerNum(jdPayConfig.getCustomerNum())
|
||||
// 店铺编号
|
||||
.shopNum(jdPayConfig.getShopNum())
|
||||
// 回调地址
|
||||
.callbackUrl(jdPayConfig.getCallbackUrl())
|
||||
// 商户订单号
|
||||
.requestNum(onlinePayment.getBusinessCode())
|
||||
// 订单金额(元)
|
||||
.amount(onlinePayment.getPayMoney().toString())
|
||||
// 固定传入:API
|
||||
.source("API")
|
||||
// 扩展信息
|
||||
.extraInfo(onlinePayment.getBusinessType().toString())
|
||||
.build();
|
||||
String body = JSONUtil.toJsonStr(jdScan);
|
||||
|
||||
log.info("京东扫码支付请求参数: {}", body);
|
||||
|
||||
String postResult = JdPayUtil.requestOrder(JdPayConstants.METHOD_SCAN, body, jdPayConfig.getSecretKey(), jdPayConfig.getAccessKey());
|
||||
|
||||
log.info("京东扫码支付返回数据: {}", postResult);
|
||||
|
||||
// 返回结果
|
||||
JdScanResult scanResult = JSONUtil.toBean(postResult, JdScanResult.class);
|
||||
|
||||
if (JdPayConstants.RESULT_SUCCESS.equals(scanResult.getResult())) {
|
||||
// 请求成功,返回二维码
|
||||
String qrCode = scanResult.getData().getUrl();
|
||||
|
||||
// 付款码redis保存24小时
|
||||
redisTemplate.opsForValue().set(scanKey, qrCode, 24, TimeUnit.HOURS);
|
||||
|
||||
return R.ok(qrCode);
|
||||
}
|
||||
log.info("京东扫码支付返回失败");
|
||||
} catch (Exception e) {
|
||||
log.error("京东扫码支付返回异常", e);
|
||||
}
|
||||
return R.fail("京东扫码支付返回失败");
|
||||
}
|
||||
|
||||
@Override
|
||||
public R<String> bankPay(TOnlinePayment onlinePayment, String bindCode) {
|
||||
try {
|
||||
// 与三方对接,需要扩展支付订单号,用于标记业务
|
||||
String thirdOrderCode = PayUtil.getThirdOrderCode(onlinePayment.getBusinessCode(), null);
|
||||
onlinePayment.setOriginalOrder(thirdOrderCode);
|
||||
|
||||
TOnlineCard tOnlineCard = itOnlineCardService.queryCardByCode(bindCode, onlinePayment.getPkModified(), jdPayConfig.getCustomerNum(), onlinePayment.getPkCountry());
|
||||
if (null == tOnlineCard || EBindStatus.BIND.getValue() != tOnlineCard.getBindStatus()) {
|
||||
return R.fail("用户未绑定该银行卡");
|
||||
}
|
||||
|
||||
JdFastDTO jdFastDTO = JdFastDTO.builder()
|
||||
.version(JdPayConstants.PAY_VERSION)
|
||||
// 商户编号
|
||||
.customerNum(jdPayConfig.getCustomerNum())
|
||||
// 店铺编号
|
||||
.shopNum(jdPayConfig.getShopNum())
|
||||
// 回调地址
|
||||
.callbackUrl(jdPayConfig.getCallbackUrl())
|
||||
.clientIp(InetAddress.getLocalHost().getHostAddress())
|
||||
// 商户订单号
|
||||
.requestNum(thirdOrderCode)
|
||||
// 订单金额(元)
|
||||
.orderAmount(onlinePayment.getPayMoney().toString())
|
||||
// 用户编号
|
||||
.userId(onlinePayment.getPkModified().toString())
|
||||
// 绑卡ID
|
||||
.bindId(tOnlineCard.getBindId())
|
||||
.goodsName("购买商品")
|
||||
.goodsQuantity("1")
|
||||
// 终端类型
|
||||
.terminalType(EJdTerminalType.OTHER.getLabel())
|
||||
// 终端标记
|
||||
.terminalId(JdPayConstants.TERMINAL_ID)
|
||||
// 用户注册账号
|
||||
.userAccount(JdPayConstants.USER_ACCOUNT)
|
||||
.appType(EJdAppType.H5.getLabel())
|
||||
.appName(JdPayConstants.APP_NAME)
|
||||
.tradeScene(JdPayConstants.FAST_TRADESCENE_QUICKPAY)
|
||||
.source("API")
|
||||
// 设置订单24小时失效
|
||||
.period("24")
|
||||
.periodUnit("Hour")
|
||||
.extraInfo(onlinePayment.getBusinessType().toString())
|
||||
.build();
|
||||
String body = JSONUtil.toJsonStr(jdFastDTO);
|
||||
|
||||
log.info("京东银行卡下单请求参数: {}", body);
|
||||
|
||||
String postResult = JdPayUtil.requestOrder(JdPayConstants.METHOD_PRE_ORDER, body, jdPayConfig.getSecretKey(), jdPayConfig.getAccessKey());
|
||||
|
||||
log.info("京东银行卡下单返回数据: {}", postResult);
|
||||
|
||||
// 返回结果
|
||||
JdFastResult fastResult = JSONUtil.toBean(postResult, JdFastResult.class);
|
||||
|
||||
if (fastResult.isSuccess() && JdPayConstants.RESULT_SUCCESS.equals(fastResult.getCode())) {
|
||||
// 返回成功
|
||||
return R.ok();
|
||||
}
|
||||
log.error("京东银行卡下单返回失败: {}", fastResult.getMsg());
|
||||
} catch (Exception e) {
|
||||
log.error("京东银行卡下单异常!", e);
|
||||
}
|
||||
return R.fail("京东银行卡下单返回失败");
|
||||
}
|
||||
|
||||
@Override
|
||||
public R<String> cashRegister(TOnlinePayment onlinePayment, EDataSource dataSource) {
|
||||
try {
|
||||
// 交易场景(ONLINE_APP:线上移动端 ONLINE_PC:线上PC)
|
||||
String sceneType = "ONLINE_APP";
|
||||
// 交易类型(AGGRE:聚合收银台 AGGRE_QR:PC扫码)
|
||||
String tradeType = "AGGRE";
|
||||
if (null != dataSource && EDataSource.PC.getValue().equals(dataSource.getValue())) {
|
||||
sceneType = "ONLINE_PC";
|
||||
tradeType = "AGGRE_QR";
|
||||
}
|
||||
|
||||
String userId = onlinePayment.getPkCreator().toString();
|
||||
if (EEnv.TEST.getValue().equals(BdConfig.getEnv())) {
|
||||
userId = "T_" + userId;
|
||||
}
|
||||
|
||||
JdPayAggregateCreateOrderRequest request = JdPayAggregateCreateOrderRequest.builder()
|
||||
// 商户订单号(最大32位)
|
||||
.outTradeNo(onlinePayment.getBusinessCode())
|
||||
// 订单总金额(单位:分)
|
||||
.tradeAmount(onlinePayment.getPayMoney().multiply(new BigDecimal("100")).intValue() + "")
|
||||
// 订单创建时间(最大14位:yyyyMMddHHmmss)
|
||||
.createDate(DateUtils.parseDateToFormat(DateUtils.YAMMERERS, onlinePayment.getCreationTime()))
|
||||
// 订单有效时长(分钟)
|
||||
.tradeExpiryTime("1440")
|
||||
// 交易名称
|
||||
.tradeSubject("支付:" + onlinePayment.getBusinessCode())
|
||||
// 交易描述
|
||||
.tradeRemark("支付:" + onlinePayment.getBusinessCode())
|
||||
// 币种
|
||||
.currency("CNY")
|
||||
// 用户IP
|
||||
.userIp(InetAddress.getLocalHost().getHostAddress())
|
||||
// 通道业务类型
|
||||
.bizTp("100001")
|
||||
// 回传字段
|
||||
.returnParams(onlinePayment.getBusinessType().toString())
|
||||
// 用户标识(收银台必传)
|
||||
.userId(userId)
|
||||
// 同步通知URL(收银台必传,页面回调地址)
|
||||
.pageBackUrl(this.pageBackUrl)
|
||||
// 支付回调地址
|
||||
.notifyUrl(this.notifyUrl)
|
||||
// 交易类型
|
||||
.tradeType(tradeType)
|
||||
// 交易场景(ONLINE_APP:线上移动端 ONLINE_PC:线上PC)
|
||||
.sceneType(sceneType)
|
||||
.build();
|
||||
|
||||
// 请求京东支付接口
|
||||
JdPayAggregateCreateOrderResponse response = jdPay.aggregateCreateOrder(request);
|
||||
|
||||
if ("0000".equals(response.getResultCode())) {
|
||||
// 请求响应成功
|
||||
// 京东唯一订单号
|
||||
onlinePayment.setPayNumber(response.getTradeNo());
|
||||
|
||||
if ("AGGRE_QR".equals(tradeType)) {
|
||||
return R.ok(response.getQrCode());
|
||||
}
|
||||
return R.ok(response.getWebUrl());
|
||||
} else {
|
||||
log.error("京东收银台返回失败,resultDesc: {}", response.getResultDesc());
|
||||
return R.fail("调用京东收银台返回失败");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("京东收银台处理异常!", e);
|
||||
return R.fail("京东收银台处理异常");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,150 @@
|
|||
package com.hzs.third.pay.util;
|
||||
|
||||
import cn.hutool.crypto.SecureUtil;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import com.hzs.third.pay.constants.JdPayConstants;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
|
||||
/**
|
||||
* 京东加密工具类
|
||||
*/
|
||||
@Slf4j
|
||||
public class JdPayUtil {
|
||||
|
||||
/**
|
||||
* 请求订单
|
||||
*
|
||||
* @param path 访问路径
|
||||
* @param body 请求体
|
||||
* @param secretKey 私钥
|
||||
* @param accessKey 公钥
|
||||
* @return
|
||||
*/
|
||||
public static String requestOrder(String path, String body, String secretKey, String accessKey) {
|
||||
// 时间戳(秒)
|
||||
long timestamp = System.currentTimeMillis() / 1000;
|
||||
|
||||
// SHA1 加密串
|
||||
String sha1Str = "secretKey=" + secretKey +
|
||||
"×tamp=" + timestamp +
|
||||
"&path=" + path +
|
||||
"&body=" + body;
|
||||
String token = SecureUtil.sha1(sha1Str).toUpperCase();
|
||||
|
||||
// 请求京东获取扫码地址
|
||||
return HttpUtil.createPost(JdPayConstants.PAY_URL + path)
|
||||
.body(body)
|
||||
.header("Content-type", "application/json")
|
||||
.header("accessKey", accessKey)
|
||||
.header("timestamp", Long.toString(timestamp))
|
||||
.header("token", token)
|
||||
.execute()
|
||||
.body();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param encryptSrc 需要加密的字符串
|
||||
* @param encryptKey 加密的aes key
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
public static String encrypt(String encryptSrc, String encryptKey) throws Exception {
|
||||
// 判断encryptKey是否为空
|
||||
if (StringUtils.isBlank(encryptKey)) {
|
||||
log.error("encryptKey 加密key为null");
|
||||
return null;
|
||||
}
|
||||
// 判断encryptKey是否为16位
|
||||
if (encryptKey.length() != 16) {
|
||||
log.error("encryptKey长度小于16位");
|
||||
return null;
|
||||
}
|
||||
byte[] raw = encryptKey.getBytes(StandardCharsets.UTF_8);
|
||||
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
|
||||
// "算法/模式/补码方式"
|
||||
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
|
||||
byte[] encrypted = cipher.doFinal(encryptSrc.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
// 此处使用BASE64做转码功能,同时能起到2次加密的作用。
|
||||
return new Base64().encodeToString(encrypted);
|
||||
}
|
||||
|
||||
// 解密
|
||||
public static String decrypt(String encryptSrc, String encryptKey) throws Exception {
|
||||
try {
|
||||
// 判断encryptKey是否为null
|
||||
if (StringUtils.isBlank(encryptKey)) {
|
||||
log.error("encryptKey 加密key为null");
|
||||
return null;
|
||||
}
|
||||
// 判断encryptKey是否为16位
|
||||
if (encryptKey.length() != 16) {
|
||||
log.error("encryptKey长度小于16位");
|
||||
return null;
|
||||
}
|
||||
byte[] raw = encryptKey.getBytes(StandardCharsets.UTF_8);
|
||||
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
|
||||
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
|
||||
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
|
||||
// 先用base64解密
|
||||
byte[] encrypted1 = new Base64().decode(encryptSrc);
|
||||
try {
|
||||
byte[] original = cipher.doFinal(encrypted1);
|
||||
return new String(original, StandardCharsets.UTF_8);
|
||||
} catch (Exception e) {
|
||||
log.info("加密异常", e);
|
||||
return null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.info("加密异常:", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 通过access_token的40位数字获取16位的aes key
|
||||
*
|
||||
* @param pwd 需要加密的字符串
|
||||
* @return
|
||||
*/
|
||||
public static String getAesKey(String pwd) {
|
||||
String md5 = "";
|
||||
try {
|
||||
// 创建加密对象
|
||||
MessageDigest md = MessageDigest.getInstance("md5");
|
||||
// 计算MD5函数
|
||||
md.update(pwd.getBytes());
|
||||
byte[] bytes = md.digest();
|
||||
int i;
|
||||
StringBuilder sb = new StringBuilder("");
|
||||
for (byte aByte : bytes) {
|
||||
i = aByte;
|
||||
if (i < 0) {
|
||||
i += 256;
|
||||
}
|
||||
if (i < 16) {
|
||||
sb.append("0");
|
||||
}
|
||||
sb.append(Integer.toHexString(i));
|
||||
}
|
||||
md5 = sb.toString();
|
||||
// 截取32位md5为16位
|
||||
md5 = md5.substring(8, 24);
|
||||
return md5;
|
||||
} catch (Exception e) {
|
||||
log.error("md5加密异常:", e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return md5;
|
||||
}
|
||||
}
|
|
@ -85,6 +85,28 @@ wechat:
|
|||
publicAppSecret: 896bf6b984b44b192e3f71e96002b800
|
||||
|
||||
|
||||
## 京东收银台支付配置
|
||||
jd:
|
||||
pay:
|
||||
## 二级商户号
|
||||
merchantNo: 146394993003
|
||||
## 签名密钥
|
||||
signKey: 9c4b9837e13dfcb163a87cbba521f6357b581bff49b0d6d0b503cd563238c1e9
|
||||
## 证书加密密码
|
||||
priCertPwd: hzs20253003
|
||||
## 私钥文件名
|
||||
priCert: jd/merchantCert.pfx
|
||||
## 公钥文件名
|
||||
pubCert: jd/npp_11_API2_pro.cer
|
||||
## 生产环境api接口域名
|
||||
apiDomain: http://wapi.jd.com
|
||||
## 页面回调地址
|
||||
pageBackUrl: https://p1.hzs413.com/inter-api/pay/jd/sync-notify
|
||||
## 支付回调地址
|
||||
notifyUrl: https://p1.hzs413.com/inter-api/pay/jd/trade-notify
|
||||
## 退款回调地下
|
||||
refundNotifyUrl: https://p1.hzs413.com/inter-api/pay/jd-refund/trade-notify
|
||||
|
||||
## 通联支付配置
|
||||
allinpay:
|
||||
# #################### 基础支付相关(正式) ####################
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
|
||||
根据文档浏览器导出证书:
|
||||
merchantCert.pfx
|
||||
|
||||
DEMO中的公钥证书:
|
||||
npp_11_API2_pro.cer
|
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue