diff --git a/bd-third/src/main/java/com/hzs/third/bankcard/service/BankCardService.java b/bd-third/src/main/java/com/hzs/third/bankcard/service/BankCardService.java index 1821baed..23c350e2 100644 --- a/bd-third/src/main/java/com/hzs/third/bankcard/service/BankCardService.java +++ b/bd-third/src/main/java/com/hzs/third/bankcard/service/BankCardService.java @@ -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 { diff --git a/bd-third/src/main/java/com/hzs/third/bankcard/service/impl/BankCardServiceImpl.java b/bd-third/src/main/java/com/hzs/third/bankcard/service/impl/BankCardServiceImpl.java index 19cb8d2f..92ce2c8a 100644 --- a/bd-third/src/main/java/com/hzs/third/bankcard/service/impl/BankCardServiceImpl.java +++ b/bd-third/src/main/java/com/hzs/third/bankcard/service/impl/BankCardServiceImpl.java @@ -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 diff --git a/bd-third/src/main/java/com/hzs/third/pay/config/JdPayConfig.java b/bd-third/src/main/java/com/hzs/third/pay/config/JdPayConfig.java new file mode 100644 index 00000000..995a9f80 --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/config/JdPayConfig.java @@ -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; + +} diff --git a/bd-third/src/main/java/com/hzs/third/pay/constants/JdPayConstants.java b/bd-third/src/main/java/com/hzs/third/pay/constants/JdPayConstants.java new file mode 100644 index 00000000..e2223623 --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/constants/JdPayConstants.java @@ -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"; + +} diff --git a/bd-third/src/main/java/com/hzs/third/pay/constants/PayRedisConstants.java b/bd-third/src/main/java/com/hzs/third/pay/constants/PayRedisConstants.java index 73a5e468..c7869983 100644 --- a/bd-third/src/main/java/com/hzs/third/pay/constants/PayRedisConstants.java +++ b/bd-third/src/main/java/com/hzs/third/pay/constants/PayRedisConstants.java @@ -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 */ diff --git a/bd-third/src/main/java/com/hzs/third/pay/controller/api/PayController.java b/bd-third/src/main/java/com/hzs/third/pay/controller/api/PayController.java index 4f5b9b2f..46471d4c 100644 --- a/bd-third/src/main/java/com/hzs/third/pay/controller/api/PayController.java +++ b/bd-third/src/main/java/com/hzs/third/pay/controller/api/PayController.java @@ -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) { diff --git a/bd-third/src/main/java/com/hzs/third/pay/dto/jd/JdFastBindConfirmDTO.java b/bd-third/src/main/java/com/hzs/third/pay/dto/jd/JdFastBindConfirmDTO.java new file mode 100644 index 00000000..46fa888d --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/dto/jd/JdFastBindConfirmDTO.java @@ -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; + +} diff --git a/bd-third/src/main/java/com/hzs/third/pay/dto/jd/JdFastBindConfirmResult.java b/bd-third/src/main/java/com/hzs/third/pay/dto/jd/JdFastBindConfirmResult.java new file mode 100644 index 00000000..e9beec79 --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/dto/jd/JdFastBindConfirmResult.java @@ -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; + +} diff --git a/bd-third/src/main/java/com/hzs/third/pay/dto/jd/JdFastDTO.java b/bd-third/src/main/java/com/hzs/third/pay/dto/jd/JdFastDTO.java new file mode 100644 index 00000000..ee653518 --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/dto/jd/JdFastDTO.java @@ -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; + +} diff --git a/bd-third/src/main/java/com/hzs/third/pay/dto/jd/JdFastResult.java b/bd-third/src/main/java/com/hzs/third/pay/dto/jd/JdFastResult.java new file mode 100644 index 00000000..771bcaad --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/dto/jd/JdFastResult.java @@ -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; + +} diff --git a/bd-third/src/main/java/com/hzs/third/pay/dto/jd/JdPayNotifyBody.java b/bd-third/src/main/java/com/hzs/third/pay/dto/jd/JdPayNotifyBody.java new file mode 100644 index 00000000..cbbc5bc7 --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/dto/jd/JdPayNotifyBody.java @@ -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; + +} diff --git a/bd-third/src/main/java/com/hzs/third/pay/dto/jd/JdScanDTO.java b/bd-third/src/main/java/com/hzs/third/pay/dto/jd/JdScanDTO.java new file mode 100644 index 00000000..9c776cd6 --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/dto/jd/JdScanDTO.java @@ -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; + +} diff --git a/bd-third/src/main/java/com/hzs/third/pay/dto/jd/JdScanResult.java b/bd-third/src/main/java/com/hzs/third/pay/dto/jd/JdScanResult.java new file mode 100644 index 00000000..d3ab41b7 --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/dto/jd/JdScanResult.java @@ -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; + +} diff --git a/bd-third/src/main/java/com/hzs/third/pay/dto/jd/JdScanResultData.java b/bd-third/src/main/java/com/hzs/third/pay/dto/jd/JdScanResultData.java new file mode 100644 index 00000000..d39a3b9d --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/dto/jd/JdScanResultData.java @@ -0,0 +1,16 @@ +package com.hzs.third.pay.dto.jd; + +import lombok.Data; + +/** + * 京东扫码返回Data + */ +@Data +public class JdScanResultData { + + /** + * 扫码URL + */ + private String url; + +} diff --git a/bd-third/src/main/java/com/hzs/third/pay/enums/EJdAppType.java b/bd-third/src/main/java/com/hzs/third/pay/enums/EJdAppType.java new file mode 100644 index 00000000..7006e175 --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/enums/EJdAppType.java @@ -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; + +} diff --git a/bd-third/src/main/java/com/hzs/third/pay/enums/EJdTerminalType.java b/bd-third/src/main/java/com/hzs/third/pay/enums/EJdTerminalType.java new file mode 100644 index 00000000..6e5bb6ca --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/enums/EJdTerminalType.java @@ -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; + +} diff --git a/bd-third/src/main/java/com/hzs/third/pay/jdpay/config/JdPayAutoConfiguration.java b/bd-third/src/main/java/com/hzs/third/pay/jdpay/config/JdPayAutoConfiguration.java new file mode 100644 index 00000000..3cbf854d --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/jdpay/config/JdPayAutoConfiguration.java @@ -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("读取京东支付商户公钥证书为空"); + } + } + +} + diff --git a/bd-third/src/main/java/com/hzs/third/pay/jdpay/dto/JdPayAggregateCreateOrderRequest.java b/bd-third/src/main/java/com/hzs/third/pay/jdpay/dto/JdPayAggregateCreateOrderRequest.java new file mode 100644 index 00000000..ec2e5118 --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/jdpay/dto/JdPayAggregateCreateOrderRequest.java @@ -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; + +} diff --git a/bd-third/src/main/java/com/hzs/third/pay/jdpay/dto/JdPayAggregateCreateOrderResponse.java b/bd-third/src/main/java/com/hzs/third/pay/jdpay/dto/JdPayAggregateCreateOrderResponse.java new file mode 100644 index 00000000..d42a1ed8 --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/jdpay/dto/JdPayAggregateCreateOrderResponse.java @@ -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; + +} diff --git a/bd-third/src/main/java/com/hzs/third/pay/jdpay/dto/JdPayRefundRequest.java b/bd-third/src/main/java/com/hzs/third/pay/jdpay/dto/JdPayRefundRequest.java new file mode 100644 index 00000000..801ce0bd --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/jdpay/dto/JdPayRefundRequest.java @@ -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; + +} diff --git a/bd-third/src/main/java/com/hzs/third/pay/jdpay/dto/JdPayRefundResponse.java b/bd-third/src/main/java/com/hzs/third/pay/jdpay/dto/JdPayRefundResponse.java new file mode 100644 index 00000000..3a95f02a --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/jdpay/dto/JdPayRefundResponse.java @@ -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; + +} diff --git a/bd-third/src/main/java/com/hzs/third/pay/jdpay/sdk/JdPay.java b/bd-third/src/main/java/com/hzs/third/pay/jdpay/sdk/JdPay.java new file mode 100644 index 00000000..7da33e06 --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/jdpay/sdk/JdPay.java @@ -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 respMap) throws Exception { + return SignUtil.verifyPageCallBackSign(respMap, jdPayNewConfig.getSignKey()); + } + + /** + * 执行接口调用 + * + * @param request 请求对象 + * @param clazz 返回对象类型 + * @return 返回对象 + * @throws Exception 异常 + */ + public RES baseExecute(String urlSuffix, REQ request, Class 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); +// } +} diff --git a/bd-third/src/main/java/com/hzs/third/pay/jdpay/sdk/JdPayConstant.java b/bd-third/src/main/java/com/hzs/third/pay/jdpay/sdk/JdPayConstant.java new file mode 100644 index 00000000..b92e78dd --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/jdpay/sdk/JdPayConstant.java @@ -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/"; + +} diff --git a/bd-third/src/main/java/com/hzs/third/pay/jdpay/sdk/JdPayDefaultNewConfig.java b/bd-third/src/main/java/com/hzs/third/pay/jdpay/sdk/JdPayDefaultNewConfig.java new file mode 100644 index 00000000..960aab09 --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/jdpay/sdk/JdPayDefaultNewConfig.java @@ -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; + } +} diff --git a/bd-third/src/main/java/com/hzs/third/pay/jdpay/sdk/JdPayHttpClient.java b/bd-third/src/main/java/com/hzs/third/pay/jdpay/sdk/JdPayHttpClient.java new file mode 100644 index 00000000..2d4be9b5 --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/jdpay/sdk/JdPayHttpClient.java @@ -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.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.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; + } + }; + } +} diff --git a/bd-third/src/main/java/com/hzs/third/pay/jdpay/sdk/JdPayHttpClientProxy.java b/bd-third/src/main/java/com/hzs/third/pay/jdpay/sdk/JdPayHttpClientProxy.java new file mode 100644 index 00000000..23863944 --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/jdpay/sdk/JdPayHttpClientProxy.java @@ -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; + } + +} diff --git a/bd-third/src/main/java/com/hzs/third/pay/jdpay/sdk/JdPayHttpConnectionMonitor.java b/bd-third/src/main/java/com/hzs/third/pay/jdpay/sdk/JdPayHttpConnectionMonitor.java new file mode 100644 index 00000000..8d5ea29c --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/jdpay/sdk/JdPayHttpConnectionMonitor.java @@ -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(); + } + } + +} diff --git a/bd-third/src/main/java/com/hzs/third/pay/jdpay/sdk/JdPayNewConfig.java b/bd-third/src/main/java/com/hzs/third/pay/jdpay/sdk/JdPayNewConfig.java new file mode 100644 index 00000000..b97f01fa --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/jdpay/sdk/JdPayNewConfig.java @@ -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 HTTP_READ_TIMEOUT_CONFIG = new HashMap() {{ + 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; + } +} diff --git a/bd-third/src/main/java/com/hzs/third/pay/jdpay/sdk/JdPaySecurity.java b/bd-third/src/main/java/com/hzs/third/pay/jdpay/sdk/JdPaySecurity.java new file mode 100644 index 00000000..c2972306 --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/jdpay/sdk/JdPaySecurity.java @@ -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); + } +} diff --git a/bd-third/src/main/java/com/hzs/third/pay/jdpay/sdk/JdPaySign.java b/bd-third/src/main/java/com/hzs/third/pay/jdpay/sdk/JdPaySign.java new file mode 100644 index 00000000..488ae041 --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/jdpay/sdk/JdPaySign.java @@ -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 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("验签异常"); + } + } +} diff --git a/bd-third/src/main/java/com/hzs/third/pay/jdpay/util/CertUtil.java b/bd-third/src/main/java/com/hzs/third/pay/jdpay/util/CertUtil.java new file mode 100644 index 00000000..36fd0ff5 --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/jdpay/util/CertUtil.java @@ -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; + } + +} diff --git a/bd-third/src/main/java/com/hzs/third/pay/jdpay/util/FileUtil.java b/bd-third/src/main/java/com/hzs/third/pay/jdpay/util/FileUtil.java new file mode 100644 index 00000000..76bbd290 --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/jdpay/util/FileUtil.java @@ -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; + } + +} diff --git a/bd-third/src/main/java/com/hzs/third/pay/jdpay/util/GsonUtil.java b/bd-third/src/main/java/com/hzs/third/pay/jdpay/util/GsonUtil.java new file mode 100644 index 00000000..e54c13df --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/jdpay/util/GsonUtil.java @@ -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 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 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 fromJson(String json, TypeToken token) { + return fromJson(json, token, null); + } + + public static T fromJson(String json, TypeToken token, String datePattern) { + return fromJson(json, token.getType(), datePattern); + } + + public static T fromJson(String json, Class classOfT) { + Object object = fromJson(json, (Type) classOfT, null); + return Primitives.wrap(classOfT).cast(object); + } + + public static T fromJson(String json, Class 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 + * @return 目标对象 + */ + public static 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 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 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; + } +} diff --git a/bd-third/src/main/java/com/hzs/third/pay/jdpay/util/JdPayApiUtil.java b/bd-third/src/main/java/com/hzs/third/pay/jdpay/util/JdPayApiUtil.java new file mode 100644 index 00000000..133ca9b7 --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/jdpay/util/JdPayApiUtil.java @@ -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 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 respMap = GsonUtil.fromJson(respText, new TypeToken>() { + }); + 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 fillCommonParam(String merchantNo, String reqNo) { + + Map 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; + } + +} diff --git a/bd-third/src/main/java/com/hzs/third/pay/jdpay/util/SignUtil.java b/bd-third/src/main/java/com/hzs/third/pay/jdpay/util/SignUtil.java new file mode 100644 index 00000000..ef3b0ec1 --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/jdpay/util/SignUtil.java @@ -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 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 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 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 map) { + ArrayList mapKeys = new ArrayList(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); + } + +} diff --git a/bd-third/src/main/java/com/hzs/third/pay/service/IJdPayService.java b/bd-third/src/main/java/com/hzs/third/pay/service/IJdPayService.java new file mode 100644 index 00000000..15b633c2 --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/service/IJdPayService.java @@ -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 scanPay(TOnlinePayment onlinePayment); + + /** + * 银行卡支付 + * + * @param onlinePayment + * @param bindCode + * @return + */ + R bankPay(TOnlinePayment onlinePayment, String bindCode); + + /** + * 收银台支付 + * + * @param onlinePayment + * @param dataSource + * @return + */ + R cashRegister(TOnlinePayment onlinePayment, EDataSource dataSource); + +} diff --git a/bd-third/src/main/java/com/hzs/third/pay/service/impl/JdPayServiceImpl.java b/bd-third/src/main/java/com/hzs/third/pay/service/impl/JdPayServiceImpl.java new file mode 100644 index 00000000..b6e58684 --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/service/impl/JdPayServiceImpl.java @@ -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 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 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 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("京东收银台处理异常"); + } + } + +} diff --git a/bd-third/src/main/java/com/hzs/third/pay/util/JdPayUtil.java b/bd-third/src/main/java/com/hzs/third/pay/util/JdPayUtil.java new file mode 100644 index 00000000..bd9e6c09 --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/util/JdPayUtil.java @@ -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; + } +} diff --git a/bd-third/src/main/resources/bootstrap.yml b/bd-third/src/main/resources/bootstrap.yml index 95d8f366..29f5fe42 100644 --- a/bd-third/src/main/resources/bootstrap.yml +++ b/bd-third/src/main/resources/bootstrap.yml @@ -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: # #################### 基础支付相关(正式) #################### diff --git a/bd-third/src/main/resources/jd/bak.txt b/bd-third/src/main/resources/jd/bak.txt new file mode 100644 index 00000000..d930d8ab --- /dev/null +++ b/bd-third/src/main/resources/jd/bak.txt @@ -0,0 +1,6 @@ + +根据文档浏览器导出证书: +merchantCert.pfx + +DEMO中的公钥证书: +npp_11_API2_pro.cer diff --git a/bd-third/src/main/resources/jd/merchantCert.pfx b/bd-third/src/main/resources/jd/merchantCert.pfx new file mode 100644 index 00000000..d2069bfe Binary files /dev/null and b/bd-third/src/main/resources/jd/merchantCert.pfx differ diff --git a/bd-third/src/main/resources/jd/npp_11_API2_pro.cer b/bd-third/src/main/resources/jd/npp_11_API2_pro.cer new file mode 100644 index 00000000..51bca055 Binary files /dev/null and b/bd-third/src/main/resources/jd/npp_11_API2_pro.cer differ