## 恢复京东支付渠道;

This commit is contained in:
cabbage 2025-06-06 16:05:48 +08:00
parent a33e55e0f4
commit 01a24c45cd
42 changed files with 3077 additions and 12 deletions

View File

@ -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 {

View File

@ -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

View File

@ -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;
}

View File

@ -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";
}

View File

@ -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
*/

View File

@ -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) {

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
/**
* 订单有效单位DayHourMinute
*/
private String periodUnit;
/**
* 扩展字段长度32
*/
private String extraInfo;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -0,0 +1,16 @@
package com.hzs.third.pay.dto.jd;
import lombok.Data;
/**
* 京东扫码返回Data
*/
@Data
public class JdScanResultData {
/**
* 扫码URL
*/
private String url;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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("读取京东支付商户公钥证书为空");
}
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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);
// }
}

View File

@ -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/";
}

View File

@ -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;
}
}

View File

@ -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;
}
};
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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("验签异常");
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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_QRPC扫码
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("京东收银台处理异常");
}
}
}

View File

@ -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 +
"&timestamp=" + 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;
}
}

View File

@ -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:
# #################### 基础支付相关(正式) ####################

View File

@ -0,0 +1,6 @@
根据文档浏览器导出证书:
merchantCert.pfx
DEMO中的公钥证书
npp_11_API2_pro.cer

Binary file not shown.

Binary file not shown.