diff --git a/bd-third/src/main/java/com/hzs/third/mqcall/listener/PayRefundListener.java b/bd-third/src/main/java/com/hzs/third/mqcall/listener/RefundOrderListener.java similarity index 70% rename from bd-third/src/main/java/com/hzs/third/mqcall/listener/PayRefundListener.java rename to bd-third/src/main/java/com/hzs/third/mqcall/listener/RefundOrderListener.java index 3592490c..df30e4c2 100644 --- a/bd-third/src/main/java/com/hzs/third/mqcall/listener/PayRefundListener.java +++ b/bd-third/src/main/java/com/hzs/third/mqcall/listener/RefundOrderListener.java @@ -1,15 +1,22 @@ package com.hzs.third.mqcall.listener; import com.hzs.common.core.constant.RabbitMqConstants; -import com.hzs.common.core.enums.*; +import com.hzs.common.core.enums.ECallbackStatus; +import com.hzs.common.core.enums.EPayBusinessType; +import com.hzs.common.core.enums.EPayChannel; +import com.hzs.common.core.enums.EPayStatus; +import com.hzs.common.domain.sale.ext.SaOrderExt; import com.hzs.common.domain.third.pay.TOnlinePayment; import com.hzs.common.security.utils.SecurityUtils; +import com.hzs.sale.order.ISaOrderServiceApi; import com.hzs.third.mq.dto.RefundOrderDTO; +import com.hzs.third.pay.dto.RefundDTO; import com.hzs.third.pay.service.IPayService; import com.hzs.third.pay.service.IRefundService; import com.hzs.third.pay.service.ITOnlinePaymentService; import com.rabbitmq.client.Channel; import lombok.extern.slf4j.Slf4j; +import org.apache.dubbo.config.annotation.DubboReference; import org.springframework.amqp.rabbit.annotation.*; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.amqp.support.AmqpHeaders; @@ -24,18 +31,21 @@ import java.util.Date; */ @Slf4j @Component -public class PayRefundListener { +public class RefundOrderListener { @Autowired private ITOnlinePaymentService itOnlinePaymentService; @Autowired - private IPayService iPayService; - @Autowired private IRefundService iRefundService; + @Autowired + private IPayService iPayService; @Autowired private RabbitTemplate rabbitTemplate; + @DubboReference + ISaOrderServiceApi iSaOrderServiceApi; + /** * 此监听只处理在线支付,支付成功,但是订单未成功的处理 * @@ -72,7 +82,7 @@ public class PayRefundListener { // 支付业务类型 Integer payBusinessType = refundOrderDTO.getPayBusinessType(); - // 业务类型(默认为会员订单) + // 业务类型(默认为直销订单) EPayBusinessType eBusinessType = EPayBusinessType.MEMBER_ORDER; if (null != payBusinessType) { // 传入支付业务类型不为空,则进行转换 @@ -96,15 +106,30 @@ public class PayRefundListener { // 业务重试次数 int businessRetryNum = null != refundOrderDTO.getBusinessRetryNum() ? refundOrderDTO.getBusinessRetryNum() : 0; if (businessRetryNum > 2) { - log.error("订单退款MQ处理. 会员订单业务已重试3次,仍未成功,不进行再次重试。开始进行退款"); + log.error("订单退款MQ处理. 直销订单业务已重试3次,仍未成功,不进行再次重试。开始进行退款"); + + if (EPayBusinessType.MEMBER_ORDER.equals(eBusinessType)) { + // 直销订单 + // 业务处理重试三次都失败,则进行退款流程,进行退款处理 + SaOrderExt saOrderExt = iSaOrderServiceApi.queryOrderByCode(orderCode, pkMember).getData(); + if (null == saOrderExt) { + log.error("订单退款MQ处理. 直销订单不存在"); + return; + } + log.info("订单退款MQ处理,订单数据:{}", saOrderExt); + if (null != saOrderExt.getOrderStatus() && EPayStatus.PAID.getValue() == saOrderExt.getOrderStatus()) { + log.error("订单退款MQ处理. 直销订单已支付成功,不能进行退款"); + return; + } + } // 退款处理 String str = this.handleRefund(tOnlinePayment, eBusinessType, orderCode, pkMember); if (null == str) { - log.info("退款MQ处理退款成功"); + log.info("订单退款MQ处理. 申请三方支付退款成功"); return; } else { - log.error("退款MQ处理. 申请三方支付退款失败:{}", str); + log.error("订单退款MQ处理. 申请三方支付退款失败:{}", str); } // 退款失败,重试次数+1,推送MQ @@ -113,8 +138,8 @@ public class PayRefundListener { return; } - // 会员订单,缓冲2秒后,进行重试 - Thread.sleep(2000); + // 直销订单,缓冲5秒后,进行重试 + Thread.sleep(5000); tOnlinePayment.setPkModified(SecurityUtils.getUserId()); tOnlinePayment.setModifiedTime(new Date()); @@ -144,11 +169,23 @@ public class PayRefundListener { private String handleRefund(TOnlinePayment tOnlinePayment, EPayBusinessType eBusinessType, String orderCode, Long pkMember) { String str = null; + RefundDTO refundDTO = RefundDTO.builder() + .businessType(eBusinessType.getValue()) + .businessCode(orderCode) + .refundAmount(tOnlinePayment.getPayMoney()) + .userId(pkMember) + .pkCountry(tOnlinePayment.getPkCountry()) + .build(); + // 订单存在并且没有支付成功,代表着钱收到了,但是单没有下成,所以需要进行退款 switch (EPayChannel.getEnumByValue(tOnlinePayment.getPayChannel())) { - case HUIFU: - // 新汇付 - str = iRefundService.huifuRefundHandle(tOnlinePayment); + case ALLIN: + // 通联 + str = iRefundService.allInRefundHandle(refundDTO, tOnlinePayment); + break; + case JD: + // 京东 + str = iRefundService.jdRefundDivision(tOnlinePayment.getBusinessCode(), tOnlinePayment.getPkCreator()); break; default: } diff --git a/bd-third/src/main/java/com/hzs/third/pay/config/JdPayBankProperties.java b/bd-third/src/main/java/com/hzs/third/pay/config/JdPayBankProperties.java new file mode 100644 index 00000000..e04fda30 --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/config/JdPayBankProperties.java @@ -0,0 +1,27 @@ +package com.hzs.third.pay.config; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Setter +@Getter +@Component +@ConfigurationProperties(prefix = "jd.bank") // 绑定jd.pay前缀的配置 +public class JdPayBankProperties { + + private String merchantNo; + private String signKey; + private String priCertPwd; + private String priCert; + private String pubCert; + private String apiDomain; + private String pageBackUrl; + private String notifyUrl; + private String refundNotifyUrl; + + private List separateAccounts; +} 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/config/JdPaySeparateAccountConfig.java b/bd-third/src/main/java/com/hzs/third/pay/config/JdPaySeparateAccountConfig.java new file mode 100644 index 00000000..d4d05f2f --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/config/JdPaySeparateAccountConfig.java @@ -0,0 +1,14 @@ +package com.hzs.third.pay.config; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; + +@Data +public class JdPaySeparateAccountConfig implements Serializable { + private String account; + @JsonFormat(shape = JsonFormat.Shape.STRING) + private BigDecimal proportion; +} diff --git a/bd-third/src/main/java/com/hzs/third/pay/config/JdPayWechatAlipayProperties.java b/bd-third/src/main/java/com/hzs/third/pay/config/JdPayWechatAlipayProperties.java new file mode 100644 index 00000000..94c814d3 --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/config/JdPayWechatAlipayProperties.java @@ -0,0 +1,27 @@ +package com.hzs.third.pay.config; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Setter +@Getter +@Component +@ConfigurationProperties(prefix = "jd.wechat-alipay") // 绑定jd.pay前缀的配置 +public class JdPayWechatAlipayProperties { + + private String merchantNo; + private String signKey; + private String priCertPwd; + private String priCert; + private String pubCert; + private String apiDomain; + private String pageBackUrl; + private String notifyUrl; + private String refundNotifyUrl; + + private List separateAccounts; +} 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/PayConfigConstants.java b/bd-third/src/main/java/com/hzs/third/pay/constants/PayConfigConstants.java index a29c4357..fc75d39f 100644 --- a/bd-third/src/main/java/com/hzs/third/pay/constants/PayConfigConstants.java +++ b/bd-third/src/main/java/com/hzs/third/pay/constants/PayConfigConstants.java @@ -5,13 +5,19 @@ package com.hzs.third.pay.constants; */ public class PayConfigConstants { - // 新汇付PC微信扫码支付 + // 京东收银台(H5) + public static final String PAY_CONFIG_5 = "PAY:CONFIG:%s:5"; + // 京东收银台(PC) + public static final String PAY_CONFIG_6 = "PAY:CONFIG:%s:6"; + + // 通联微信 + public static final String PAY_CONFIG_32 = "PAY:CONFIG:%s:32"; + // 通联银行卡 + public static final String PAY_CONFIG_33 = "PAY:CONFIG:%s:33"; + + // 新汇付PC微信扫码 public static final String PAY_CONFIG_81 = "PAY:CONFIG:%s:81"; - // 新汇付PC支付宝扫码支付 + // 新汇付PC支付宝扫码 public static final String PAY_CONFIG_82 = "PAY:CONFIG:%s:82"; - // 新汇付H5微信支付 - public static final String PAY_CONFIG_83 = "PAY:CONFIG:%s:83"; - // 新汇付H5支付宝支付 - public static final String PAY_CONFIG_84 = "PAY:CONFIG:%s:84"; } diff --git a/bd-third/src/main/java/com/hzs/third/pay/controller/api/JdRefundController.java b/bd-third/src/main/java/com/hzs/third/pay/controller/api/JdRefundController.java new file mode 100644 index 00000000..d634df91 --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/controller/api/JdRefundController.java @@ -0,0 +1,100 @@ +package com.hzs.third.pay.controller.api; + +import com.hzs.common.core.enums.EPayChannel; +import com.hzs.common.core.enums.EPayStatus; +import com.hzs.common.core.utils.StringUtils; +import com.hzs.common.core.web.domain.AjaxResult; +import com.hzs.common.domain.third.pay.TOnlinePayment; +import com.hzs.common.security.utils.SecurityUtils; +import com.hzs.third.pay.controller.base.PayBaseController; +import com.hzs.third.pay.dto.RefundDTO; +import com.hzs.third.pay.param.PayParam; +import com.hzs.third.pay.service.IRefundService; +import com.hzs.third.pay.service.ITOnlinePaymentService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +/** + * @Description: 京东退款控制器 + * @Author: jiang chao + * @Time: 2023/8/17 15:11 + * @Classname: JdRefundController + * @PackageName: com.hzs.third.pay.controller.api + */ +@Slf4j +@RestController +@RequestMapping("/jd-refund") +public class JdRefundController extends PayBaseController { + + @Autowired + private IRefundService iRefundService; + @Autowired + private ITOnlinePaymentService itOnlinePaymentService; + + @GetMapping("/test/{orderCode}") + public AjaxResult test(@PathVariable String orderCode) { + iRefundService.jdRefundDivision(orderCode, SecurityUtils.getUserId()); + return AjaxResult.success(); + } + + @GetMapping("/test2") + public AjaxResult test2() { + iRefundService.jdQueryEnterpriseWalletBalance(); + return AjaxResult.success(); + } + + /** + * 京东退款接口 + * + * @param param + * @return + */ + @PostMapping("/refund") + public AjaxResult refund(@RequestBody PayParam param) { + if (null == param.getBusinessType() || StringUtils.isEmpty(param.getBusinessCode())) { + // 缺少参数 + return AjaxResult.error("缺少参数"); + } + // 查询在线支付明细 + TOnlinePayment tOnlinePayment = itOnlinePaymentService.queryByBusiness(param.getBusinessType(), param.getBusinessCode(), SecurityUtils.getPkCountry()); + if (null == tOnlinePayment || EPayStatus.UNPAID.getValue() == tOnlinePayment.getPayStatus()) { + // 在线支付明细为空 或者 未支付成功,则不能进行退款 + return AjaxResult.error("订单不存在或未支付成功,不能进行退款"); + } + // 获取支付渠道 + EPayChannel ePayChannel = EPayChannel.getEnumByValue(tOnlinePayment.getPayChannel()); + if (null == ePayChannel) { + return AjaxResult.error("订单支付信息有误,不能进行退款"); + } + + try { + // 京东收银台退款处理 + String str = iRefundService.jdCashRefundHandle(RefundDTO.builder() + .businessType(param.getBusinessType()) + .businessCode(param.getBusinessCode()) + .refundAmount(tOnlinePayment.getPayMoney()) + .userId(SecurityUtils.getUserId()) + .pkCountry(SecurityUtils.getPkCountry()) + .build(), + tOnlinePayment); + // 京东银行卡退款处理 -- 旧 +// String str = iRefundService.jdRefundHandle(RefundDTO.builder() +// .businessType(param.getBusinessType()) +// .businessCode(param.getBusinessCode()) +// .refundAmount(tOnlinePayment.getPayMoney()) +// .userId(SecurityUtils.getUserId()) +// .pkCountry(SecurityUtils.getPkCountry()) +// .build(), +// tOnlinePayment); + if (null == str) { + return AjaxResult.success(); + } + return AjaxResult.error("退款失败:" + str); + } catch (Exception e) { + log.error("调用京东退款处理返回异常!", e); + return AjaxResult.error("退款异常,请刷新后重试"); + } + } + +} 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 522792f2..7d6a3de8 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 @@ -1,10 +1,13 @@ package com.hzs.third.pay.controller.api; -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.hzs.activity.base.IActivityServiceApi; import com.hzs.common.core.annotation.RepeatSubmitSimple; +import com.hzs.common.core.constant.SysConstants; import com.hzs.common.core.domain.R; import com.hzs.common.core.enums.*; import com.hzs.common.core.utils.CommonUtil; +import com.hzs.common.core.utils.StringUtils; import com.hzs.common.core.web.domain.AjaxResult; import com.hzs.common.domain.member.account.CuMemberRecharge; import com.hzs.common.domain.third.pay.TOnlinePayment; @@ -13,9 +16,11 @@ import com.hzs.member.account.IMemberTradeServiceApi; import com.hzs.sale.order.ISaOrderServiceApi; import com.hzs.sale.ticket.ITicketServiceApi; import com.hzs.third.pay.constants.PayConfigConstants; -import com.hzs.third.pay.param.StatusParam; +import com.hzs.third.pay.param.PayParam; import com.hzs.third.pay.param.UnifiedOrderParam; -import com.hzs.third.pay.service.*; +import com.hzs.third.pay.service.IHuiFuPayService; +import com.hzs.third.pay.service.IJdPayService; +import com.hzs.third.pay.service.ITOnlinePaymentService; import com.hzs.third.pay.util.PayUtil; import com.hzs.third.pay.vo.OnlinePayConfigVO; import lombok.extern.slf4j.Slf4j; @@ -24,7 +29,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.web.bind.annotation.*; -import javax.validation.Valid; import java.math.BigDecimal; import java.util.Date; @@ -39,9 +43,9 @@ public class PayController { @Autowired private ITOnlinePaymentService itOnlinePaymentService; @Autowired - private IHuiFuPayService iHuiFuPayService; - @Autowired private IJdPayService iJdPayService; + @Autowired + private IHuiFuPayService iHuiFuPayService; @Autowired private RedisTemplate redisTemplate; @@ -52,18 +56,24 @@ public class PayController { IMemberTradeServiceApi iMemberTradeServiceApi; @DubboReference ITicketServiceApi iTicketServiceApi; + @DubboReference + IActivityServiceApi iActivityServiceApi; /** * 统一下单 * - * @param source 请求来源(EDataSource) + * @param source 请求来源(EDataSource) * @param param * @return */ @RepeatSubmitSimple @PostMapping("/unifiedorder") public AjaxResult unifiedOrder(@RequestHeader("Source") String source, - @Valid @RequestBody UnifiedOrderParam param) { + @RequestBody UnifiedOrderParam param) { + if (null == param.getBusinessType() || StringUtils.isEmpty(param.getBusinessCode()) + || null == param.getPayChannel() || null == param.getPayType()) { + return AjaxResult.error("缺少参数"); + } EPayBusinessType payBusinessType = EPayBusinessType.getEnumByValue(param.getBusinessType()); if (null == payBusinessType) { return AjaxResult.error("业务类型不存在"); @@ -85,6 +95,8 @@ public class PayController { Long userId = SecurityUtils.getUserId(); // 登录用户国家 Integer pkCountry = SecurityUtils.getPkCountry(); + // 微信 openId(用于微信小程序支付) + String openId = null; // 1. 根据业务类型、业务单号进行校验。如果已经支付成功,直接返回;其它情况则继续执行 TOnlinePayment onlinePayment = itOnlinePaymentService.queryByBusiness(payBusinessType.getValue(), param.getBusinessCode(), pkCountry); @@ -116,9 +128,12 @@ public class PayController { onlinePayment.setOriginalOrder(onlinePayment.getBusinessCode()); onlinePayment.setPaySource(dataSource.getValue()); } + onlinePayment.setExtParam(param.getExtParam()); // 业务金额 BigDecimal businessMoney = null; + // 实际支付金额(随机立减之后) + BigDecimal amount = null; // 部分业务金额随机立减 switch (payBusinessType) { @@ -151,17 +166,11 @@ public class PayController { break; default: } - if (null == businessMoney) { - return AjaxResult.error("当前支付金额有误"); - } - if (businessMoney.compareTo(new BigDecimal("10000")) >= 0) { - return AjaxResult.error("当前支付方式单笔金额不能超过10000元"); - } - // 更新业务金额 onlinePayment.setBusinessMoney(businessMoney); - // 暂存于实际支付金额(业务进行随机立减后的) - onlinePayment.setPayMoney(PayUtil.handleAmountRandom(businessMoney)); + amount = PayUtil.handleAmountRandom(businessMoney); + // 暂存于实际支付金额(部分业务进行随机立减) + onlinePayment.setPayMoney(amount); // 结果实体 R payResult = null; @@ -171,53 +180,14 @@ public class PayController { // 京东 switch (payType) { case WECHAT: - // 微信 - break; case ALIPAY: - // 支付宝 + // 微信、支付宝 扫码支付 +// payResult = iJdPayService.scanPay(onlinePayment); + payResult = iJdPayService.cashRegister(onlinePayment, dataSource); break; default: - // 默认京东收银台 - payResult = iJdPayService.cashDeskPay(onlinePayment); } break; - case HUIFU: - // 新汇付 - switch (payType) { - case WECHAT: - // 微信 - switch (dataSource) { - case H5: - // H5浏览器使用微信小程序支付 - if (null != param.getAppletFlag() && EYesNo.YES.getIntValue() == param.getAppletFlag()) { - // 小程序支付 - payResult = iHuiFuPayService.wechatPrePayUrl(onlinePayment, false); - break; - } - default: - // PC以及默认按扫码支付 - // 非小程序支付(微信内置浏览器) - payResult = iHuiFuPayService.wechatPrePayUrl(onlinePayment, true); - break; - } - break; - case ALIPAY: - // 支付宝 - switch (dataSource) { - case H5: - case APP: - // H5浏览器 或者 APP - payResult = iHuiFuPayService.aliPrePayUrl(onlinePayment); - default: - // PC以及默认按扫码支付 - - } - break; - case BANK_CARD: - // 银行卡 - - default: - } default: } @@ -225,8 +195,8 @@ public class PayController { if (null != payResult) { if (payResult.isSuccess()) { // 实际支付金额在支付之前不需要入库,实际以取支付回调为准 - onlinePayment.setPayMoney(null); - itOnlinePaymentService.saveOrUpdate(onlinePayment); +// onlinePayment.setPayMoney(null); +// itOnlinePaymentService.saveOrUpdate(onlinePayment); return AjaxResult.success(payResult.getData()); } @@ -242,16 +212,23 @@ public class PayController { * @return */ @GetMapping("/status") - public AjaxResult status(@Valid StatusParam param) { - // 业务类型(校验业务类型) - EPayBusinessType ePayBusinessType = EPayBusinessType.getEnumByValue(param.getBusinessType()); - if (null == ePayBusinessType) { - return AjaxResult.success("支付业务类型错误", 0); + public AjaxResult status(PayParam param) { +// if (null == param.getBusinessType() || StringUtils.isEmpty(param.getBusinessCode())) { +// return AjaxResult.success("缺少参数"); +// } + if (StringUtils.isEmpty(param.getBusinessCode())) { + return AjaxResult.success("缺少参数"); } - LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); - queryWrapper.eq(TOnlinePayment::getBusinessType, param.getBusinessType()); - queryWrapper.eq(TOnlinePayment::getBusinessCode, param.getBusinessCode()); + // 业务类型(校验业务类型) +// EPayBusinessType ePayBusinessType = EPayBusinessType.getEnumByValue(param.getBusinessType()); +// if (null == ePayBusinessType) { +// return AjaxResult.success("支付业务类型错误", 0); +// } + + QueryWrapper queryWrapper = new QueryWrapper<>(); +// queryWrapper.eq("BUSINESS_TYPE", param.getBusinessType()); + queryWrapper.eq("BUSINESS_CODE", param.getBusinessCode()); TOnlinePayment onlinePayment = itOnlinePaymentService.getOne(queryWrapper); if (null == onlinePayment) { return AjaxResult.success("支付信息不存在", 0); @@ -277,6 +254,28 @@ public class PayController { // 各支付方式: true=显示,false=隐藏 + // 京东收银台(H5) + String pay5 = String.format(PayConfigConstants.PAY_CONFIG_5, pkCountry); + if (redisTemplate.hasKey(pay5)) { + payConfigVO.setPay5((Boolean) redisTemplate.opsForValue().get(pay5)); + } + // 京东收银台(PC) + String pay6 = String.format(PayConfigConstants.PAY_CONFIG_6, pkCountry); + if (redisTemplate.hasKey(pay6)) { + payConfigVO.setPay6((Boolean) redisTemplate.opsForValue().get(pay6)); + } + + // 通联微信 + String pay32 = String.format(PayConfigConstants.PAY_CONFIG_32, pkCountry); + if (redisTemplate.hasKey(pay32)) { + payConfigVO.setPay32((Boolean) redisTemplate.opsForValue().get(pay32)); + } + // 通联银行卡 + String pay33 = String.format(PayConfigConstants.PAY_CONFIG_33, pkCountry); + if (redisTemplate.hasKey(pay33)) { + payConfigVO.setPay33((Boolean) redisTemplate.opsForValue().get(pay33)); + } + // 新汇付PC微信扫码支付 String pay81 = String.format(PayConfigConstants.PAY_CONFIG_81, pkCountry); if (redisTemplate.hasKey(pay81)) { @@ -287,16 +286,12 @@ public class PayController { if (redisTemplate.hasKey(pay82)) { payConfigVO.setPay82((Boolean) redisTemplate.opsForValue().get(pay82)); } - // 新汇付H5微信支付 - String pay83 = String.format(PayConfigConstants.PAY_CONFIG_83, pkCountry); - if (redisTemplate.hasKey(pay83)) { - payConfigVO.setPay83((Boolean) redisTemplate.opsForValue().get(pay83)); - } - // 新汇付H5支付宝支付 - String pay84 = String.format(PayConfigConstants.PAY_CONFIG_84, pkCountry); - if (redisTemplate.hasKey(pay84)) { - payConfigVO.setPay84((Boolean) redisTemplate.opsForValue().get(pay84)); + + if (SysConstants.SPECIAL_CODE.equals(SecurityUtils.getMemberCode())) { + // 在线支付测试账号,强制显示支付方式 + payConfigVO.setPay5(true); } + return AjaxResult.success(payConfigVO); } diff --git a/bd-third/src/main/java/com/hzs/third/pay/controller/base/JdBaseController.java b/bd-third/src/main/java/com/hzs/third/pay/controller/base/JdBaseController.java new file mode 100644 index 00000000..a339dbe4 --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/controller/base/JdBaseController.java @@ -0,0 +1,64 @@ +package com.hzs.third.pay.controller.base; + +import cn.hutool.crypto.SecureUtil; + +import javax.servlet.http.HttpServletRequest; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +/** + * @Description: 京东基础控制器 + * @Author: jiang chao + * @Time: 2022/12/27 17:47 + * @Classname: JdBaseController + * @PackageName: com.hzs.third.pay.controller.base + */ +public class JdBaseController { + + /** + * 获取请求体 + * + * @param request + * @return + * @throws Exception + */ + protected String getRequestBody(HttpServletRequest request) throws IOException { + BufferedReader br = null; + try { + br = new BufferedReader(new InputStreamReader(request.getInputStream())); + String line; + StringBuilder sb = new StringBuilder(); + while ((line = br.readLine()) != null) { + sb.append(line); + } + return sb.toString(); + } finally { + if (null != br) { + try { + br.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + } + + /** + * 校验token + * + * @param request + * @param reqBody + * @param secretKey + * @return + */ + protected boolean checkToken(HttpServletRequest request, String reqBody, String secretKey) { + // SHA1 加密串 + String shaStr = "secretKey=" + secretKey + + "×tamp=" + request.getHeader("timestamp") + + "&body=" + reqBody; + String tokenCheck = SecureUtil.sha1(shaStr).toUpperCase(); + return tokenCheck.equals(request.getHeader("token")); + } + +} diff --git a/bd-third/src/main/java/com/hzs/third/pay/controller/manage/OnlinePayConfigController.java b/bd-third/src/main/java/com/hzs/third/pay/controller/manage/OnlinePayConfigController.java index 554efca0..3c8b17d1 100644 --- a/bd-third/src/main/java/com/hzs/third/pay/controller/manage/OnlinePayConfigController.java +++ b/bd-third/src/main/java/com/hzs/third/pay/controller/manage/OnlinePayConfigController.java @@ -52,15 +52,15 @@ public class OnlinePayConfigController { payConfigVO.setPay82((Boolean) redisTemplate.opsForValue().get(pay82)); } // 新汇付H5微信支付 - String pay83 = String.format(PayConfigConstants.PAY_CONFIG_83, pkCountry); - if (redisTemplate.hasKey(pay83)) { - payConfigVO.setPay83((Boolean) redisTemplate.opsForValue().get(pay83)); - } - // 新汇付H5支付宝支付 - String pay84 = String.format(PayConfigConstants.PAY_CONFIG_84, pkCountry); - if (redisTemplate.hasKey(pay84)) { - payConfigVO.setPay84((Boolean) redisTemplate.opsForValue().get(pay84)); - } +// String pay83 = String.format(PayConfigConstants.PAY_CONFIG_83, pkCountry); +// if (redisTemplate.hasKey(pay83)) { +// payConfigVO.setPay83((Boolean) redisTemplate.opsForValue().get(pay83)); +// } +// // 新汇付H5支付宝支付 +// String pay84 = String.format(PayConfigConstants.PAY_CONFIG_84, pkCountry); +// if (redisTemplate.hasKey(pay84)) { +// payConfigVO.setPay84((Boolean) redisTemplate.opsForValue().get(pay84)); +// } return AjaxResult.success(payConfigVO); } @@ -76,10 +76,16 @@ public class OnlinePayConfigController { public AjaxResult saveConfig(@RequestBody OnlinePayConfigParam param) { Integer pkCountry = SecurityUtils.getPkCountry(); + redisTemplate.opsForValue().set(String.format(PayConfigConstants.PAY_CONFIG_5, pkCountry), param.getPay5()); + redisTemplate.opsForValue().set(String.format(PayConfigConstants.PAY_CONFIG_6, pkCountry), param.getPay6()); + + redisTemplate.opsForValue().set(String.format(PayConfigConstants.PAY_CONFIG_32, pkCountry), param.getPay32()); + redisTemplate.opsForValue().set(String.format(PayConfigConstants.PAY_CONFIG_33, pkCountry), param.getPay33()); + redisTemplate.opsForValue().set(String.format(PayConfigConstants.PAY_CONFIG_81, pkCountry), param.getPay81()); redisTemplate.opsForValue().set(String.format(PayConfigConstants.PAY_CONFIG_82, pkCountry), param.getPay82()); - redisTemplate.opsForValue().set(String.format(PayConfigConstants.PAY_CONFIG_83, pkCountry), param.getPay83()); - redisTemplate.opsForValue().set(String.format(PayConfigConstants.PAY_CONFIG_84, pkCountry), param.getPay84()); +// redisTemplate.opsForValue().set(String.format(PayConfigConstants.PAY_CONFIG_83, pkCountry), param.getPay83()); +// redisTemplate.opsForValue().set(String.format(PayConfigConstants.PAY_CONFIG_84, pkCountry), param.getPay84()); return AjaxResult.success(); } diff --git a/bd-third/src/main/java/com/hzs/third/pay/controller/manage/OnlineRefundController.java b/bd-third/src/main/java/com/hzs/third/pay/controller/manage/OnlineRefundController.java index cf9ab3a3..32e16b43 100644 --- a/bd-third/src/main/java/com/hzs/third/pay/controller/manage/OnlineRefundController.java +++ b/bd-third/src/main/java/com/hzs/third/pay/controller/manage/OnlineRefundController.java @@ -102,27 +102,27 @@ public class OnlineRefundController extends BaseController { * @param pkId 退款ID * @return */ - @GetMapping("/query-status") - public AjaxResult queryStatus(@RequestParam Long pkId) { - TOnlineRefund tOnlineRefund = itOnlineRefundService.getById(pkId); - if (ERefundStatus.REFUNDED.getValue() == tOnlineRefund.getRefundStatus()) { - // 已经退款成功 - return AjaxResult.error("当前数据已退款"); - } - - String str; - - if (EPayChannel.HUIFU.getValue() == tOnlineRefund.getRefundChannel()) { - // 新汇付退款 - str = iRefundService.queryHuifuRefundHandle(tOnlineRefund); - } else { - return AjaxResult.error("暂不支持该数据查询"); - } - - if (null == str) { - return AjaxResult.success(); - } - return AjaxResult.error(str); - } +// @GetMapping("/query-status") +// public AjaxResult queryStatus(@RequestParam Long pkId) { +// TOnlineRefund tOnlineRefund = itOnlineRefundService.getById(pkId); +// if (ERefundStatus.REFUNDED.getValue() == tOnlineRefund.getRefundStatus()) { +// // 已经退款成功 +// return AjaxResult.error("当前数据已退款"); +// } +// +// String str; +// +// if (EPayChannel.HUIFU.getValue() == tOnlineRefund.getRefundChannel()) { +// // 新汇付退款 +// str = iRefundService.queryHuifuRefundHandle(tOnlineRefund); +// } else { +// return AjaxResult.error("暂不支持该数据查询"); +// } +// +// if (null == str) { +// return AjaxResult.success(); +// } +// return AjaxResult.error(str); +// } } diff --git a/bd-third/src/main/java/com/hzs/third/pay/controller/notify/JdPayNotifyController.java b/bd-third/src/main/java/com/hzs/third/pay/controller/notify/JdPayNotifyController.java new file mode 100644 index 00000000..400cbbed --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/controller/notify/JdPayNotifyController.java @@ -0,0 +1,242 @@ +package com.hzs.third.pay.controller.notify; + +import cn.hutool.json.JSONUtil; +import com.hzs.common.core.enums.EPayChannel; +import com.hzs.common.core.enums.EPayType; +import com.hzs.common.core.utils.DateUtils; +import com.hzs.third.pay.config.JdPayConfig; +import com.hzs.third.pay.constants.JdPayConstants; +import com.hzs.third.pay.controller.base.JdBaseController; +import com.hzs.third.pay.dto.jd.JdPayNotifyBody; +import com.hzs.third.pay.jdpay.dto.JdPayTradeSuccessNotify; +import com.hzs.third.pay.jdpay.sdk.JdPay; +import com.hzs.third.pay.jdpay.util.GsonUtil; +import com.hzs.third.pay.service.IPayService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import java.math.BigDecimal; +import java.util.Date; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; + +/** + * 京东支付回调控制器 + */ +@Slf4j +@RestController +@RequestMapping("/jd") +public class JdPayNotifyController extends JdBaseController { + + @Autowired + private JdPayConfig jdPayConfig; + @Autowired + private IPayService iPayService; + + /** + * 京东支付回调 + * + * @param request + */ + @PostMapping("/notify") + public String notify(HttpServletRequest request) { + try { + String reqBody = this.getRequestBody(request); + log.info("京东支付回调! body: {}", reqBody); + + if (this.checkToken(request, reqBody, jdPayConfig.getSecretKey())) { + // 签名校验通过 + JdPayNotifyBody notifyBody = JSONUtil.toBean(reqBody, JdPayNotifyBody.class); + + // 支付流水号 + String payNumber = notifyBody.getOrderNum(); + // 支付时间 + Date payTime = DateUtils.parseDate(notifyBody.getCompleteTime(), DateUtils.YYYY_MM_DD_HH_MM_SS); + // 支付金额 + BigDecimal payMoney = new BigDecimal(notifyBody.getOrderAmount()); + + // 支付扩展类型 + String type = notifyBody.getExtraInfo(); + + // 回调订单编号 + String thirdOrderCode = notifyBody.getRequestNum(); + // 支付信息编号 + String orderCode = thirdOrderCode; + // 处理订单号以及扩展类型 + if (orderCode.indexOf("-") > 0) { + // 带有分隔,需要处理 + orderCode = orderCode.split("-")[0]; + } + + // 支付后续业务处理 + if (iPayService.notifyHandle(type, orderCode, thirdOrderCode, payNumber, payTime, payMoney, EPayChannel.JD, "")) { + return JdPayConstants.RETURN_SUCCESS; + } + } else { + log.error("京东支付回调签名校验失败!"); + } + } catch (Exception e) { + log.error("京东支付回调处理异常", e); + } + return JdPayConstants.RETURN_FAIL; + } + + + @Resource(name = "jdPayBank") + private JdPay jdPay; + + /** + * 京东收银台处理成功返回 + */ + private static final String SUCCESS = "SUCCESS"; + /** + * 京东收银台处理失败返回 + */ + private static final String ERROR = "ERROR"; + + /** + * 京东收银台异步支付回调 + * + * @param reqText + * @return 成功返回"SUCCESS", 失败返回"ERROR",京东支付会再次发起通知,通知频次见接口文档。 + */ + @PostMapping("/trade-notify") + public String tradeNotify(@RequestBody String reqText) { + log.info("京东收银台支付异步回调! reqText: {}", reqText); + try { + // 验证签名与解密 + String interData = jdPay.verifyResponse(reqText); + JdPayTradeSuccessNotify successNotify = GsonUtil.fromJson(interData, JdPayTradeSuccessNotify.class); + if (null != successNotify) { + if ("FINI".equals(successNotify.getTradeStatus())) { + // 支付成功处理 + + // 商户订单号 + String payNumber = successNotify.getTradeNo(); + // 渠道流水号 + String channelNumber = ""; + // 支付完成时间 + Date payTime = DateUtils.parseDateOne(successNotify.getFinishDate(), DateUtils.YAMMERERS); + // 支付扩展类型 + String type = successNotify.getReturnParams(); + + // 回调订单编号 + String thirdOrderCode = successNotify.getOutTradeNo(); + // 订单编号 + String orderCode = thirdOrderCode; + + // 订单金额 + int tradeAmount = Integer.parseInt(successNotify.getTradeAmount()); + BigDecimal payMoney = new BigDecimal(tradeAmount).divide(new BigDecimal("100"), 2, BigDecimal.ROUND_HALF_UP); + Integer payType = convertPayType(successNotify.getPayTool()); + // 支付后续业务处理 + if (iPayService.notifyHandle(type, orderCode, thirdOrderCode, payNumber, payTime, payMoney, EPayChannel.JD, channelNumber, payType)) { + return SUCCESS; + } + } else { + log.error("京东收银台支付异步回调失败,resultDesc: {}", successNotify.getResultDesc()); + } + } else { + log.error("京东收银台支付异步回调解密为空"); + } + } catch (Exception e) { + log.error("京东收银台支付异步回调异常", e); + } + return ERROR; + } + private Integer convertPayType(String payName){ + Integer result = EPayType.WECHAT.getValue(); + switch (payName){ + case "XJK": + result = EPayType.MINI_TREASURY.getValue(); + break; + case "JIOU": + result = EPayType.CREDIT_LINE.getValue(); + break; + case "SJIOU": + result = EPayType.SUPER_CREDIT_LINE.getValue(); + break; + case "ACCT": + result = EPayType.WALLET_BALANCE.getValue(); + break; + case "EXPR": + result = EPayType.BANK_CARD.getValue(); + break; + case "WX": + result = EPayType.WECHAT.getValue(); + break; + case "ALIPAY": + result = EPayType.ALIPAY.getValue(); + break; + case "YSF": + result = EPayType.CLOUD_PAY.getValue(); + break; + + } + return result; + } + + + /** + * 京东收银台页面回调 + * + * @param request + * @return + */ + @PostMapping("/sync-notify") + public String syncNotify(HttpServletRequest request) { + // 签名验证逻辑,需要支持添加通知字段不影响结果 + Map respMap = getAllRequestParam(request); + log.info("京东收银台支付页面回调! request: {}", respMap); + + try { + if (jdPay.verifyPageCallBack(respMap)) { + if ("FINI".equals(respMap.get("status"))) { + // todo 支付成功 + + return SUCCESS; + } else if ("WPAR".equals(respMap.get("status"))) { + // todo 支付处理中 + log.error("京东支付页面回调,支付处理中"); + } else { + // todo 支付失败 + log.error("京东支付页面回调,支付失败"); + } + } else { + // 签名异常,报错 + log.error("京东收银台支付页面回调!签名异常!"); + } + } catch (Exception e) { + log.error("京东支付页面回调参数", e); + } + return ERROR; + } + + /** + * 获取客户端请求参数中所有的信息 + */ + private Map getAllRequestParam(final HttpServletRequest request) { + Map respMap = new HashMap<>(); + Enumeration temp = request.getParameterNames(); + if (null != temp) { + while (temp.hasMoreElements()) { + String en = (String) temp.nextElement(); + String value = request.getParameter(en); + respMap.put(en, value); + //如果字段的值为空,判断若值为空,则删除这个字段> + if (null == respMap.get(en) || "".equals(respMap.get(en))) { + respMap.remove(en); + } + } + } + return respMap; + } + +} diff --git a/bd-third/src/main/java/com/hzs/third/pay/controller/notify/JdRefundNotifyController.java b/bd-third/src/main/java/com/hzs/third/pay/controller/notify/JdRefundNotifyController.java new file mode 100644 index 00000000..63ce6814 --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/controller/notify/JdRefundNotifyController.java @@ -0,0 +1,148 @@ +package com.hzs.third.pay.controller.notify; + +import cn.hutool.json.JSONUtil; +import com.hzs.common.core.enums.EPayChannel; +import com.hzs.common.core.utils.DateUtils; +import com.hzs.third.pay.config.JdPayConfig; +import com.hzs.third.pay.constants.JdPayConstants; +import com.hzs.third.pay.controller.base.JdBaseController; +import com.hzs.third.pay.jdpay.dto.JdPayRefundSuccessNotify; +import com.hzs.third.pay.jdpay.sdk.JdPay; +import com.hzs.third.pay.jdpay.util.GsonUtil; +import com.hzs.third.pay.service.IRefundService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import java.math.BigDecimal; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +/** + * @Description: 京东退填回调控制器 + * @Author: jiang chao + * @Time: 2023/8/17 15:50 + * @Classname: JdRefundNotifyController + * @PackageName: com.hzs.third.pay.controller.notify + */ +@Slf4j +@RestController +@RequestMapping("/jd-refund") +public class JdRefundNotifyController extends JdBaseController { + + @Autowired + private JdPayConfig jdPayConfig; + @Autowired + private IRefundService iRefundService; + + /** + * 京东退款回调 + * + * @param request + * @return + * @throws Exception + */ + @PostMapping("/notify") + public String refundNotify(HttpServletRequest request) { + try { + String reqBody = this.getRequestBody(request); + log.info("京东退款回调! body: {}", reqBody); + + if (this.checkToken(request, reqBody, jdPayConfig.getSecretKey())) { + // 签名校验通过 + Map dataMap = JSONUtil.toBean(reqBody, HashMap.class); + + // 退款编号 + String refundCode = dataMap.get("refundRequestNum"); + // 京东交易流水号 + String refundNumber = dataMap.get("orderNum"); + // 退款完成时间 + Date finishTime = DateUtils.parseDate(dataMap.get("refundSuccessTime"), DateUtils.YYYY_MM_DD_HH_MM_SS); + // 退款金额 + BigDecimal finishMoney = new BigDecimal(dataMap.get("refundAmount")); + + if (JdPayConstants.ORDER_SUCCESS.equals(dataMap.get("refundStatus"))) { + // 退款成功 + // 退款后续业务处理 + if (iRefundService.notifyHandle(EPayChannel.JD, refundCode, refundNumber, finishTime, finishMoney)) { + return JdPayConstants.RETURN_SUCCESS; + } + } else { + // 退款失败 + log.error("京东退款失败: {}", dataMap.get("failReason")); + + if (iRefundService.notifyErrorHandle(EPayChannel.JD, refundCode, refundNumber, dataMap.get("failReason"))) { + return JdPayConstants.RETURN_SUCCESS; + } + } + } else { + log.error("京东支付回调签名校验失败!"); + } + } catch (Exception e) { + log.error("京东支付回调处理异常", e); + } + return JdPayConstants.RETURN_FAIL; + } + + + @Resource(name = "jdPayBank") + private JdPay jdPay; + + /** + * 京东收银台处理成功返回 + */ + private static final String SUCCESS = "SUCCESS"; + /** + * 京东收银台处理失败返回 + */ + private static final String ERROR = "ERROR"; + + /** + * 京东收银台退款异步支付回调 + * + * @param reqText + * @return 成功返回"SUCCESS", 失败返回"ERROR",退款通知会再次发起通知,通知频次见接口文档。 + */ + @PostMapping("/trade-notify") + public String tradeNotify(@RequestBody String reqText) { + log.info("京东收银台退款异步回调! reqText: {}", reqText); + try { + // 验证签名与解密 + String interData = jdPay.verifyResponse(reqText); + JdPayRefundSuccessNotify successNotify = GsonUtil.fromJson(interData, JdPayRefundSuccessNotify.class); + if (null != successNotify) { + if ("FINI".equals(successNotify.getTradeStatus())) { + + // 退款编号 + String refundCode = successNotify.getOutTradeNo(); + // 京东交易流水号 + String refundNumber = successNotify.getTradeNo(); + // 退款完成时间 + Date finishTime = DateUtils.parseDate(successNotify.getFinishDate(), DateUtils.YAMMERERS); + // 退款金额 + int tradeAmount = Integer.parseInt(successNotify.getTradeAmount()); + BigDecimal finishMoney = new BigDecimal(tradeAmount).divide(new BigDecimal("100"), 2, BigDecimal.ROUND_HALF_UP); + + // 退款后续业务处理 + if (iRefundService.notifyHandle(EPayChannel.JD, refundCode, refundNumber, finishTime, finishMoney)) { + return SUCCESS; + } + } else { + log.error("京东收银台退款异步回调失败,resultDesc: {}", successNotify.getResultDesc()); + } + } else { + log.error("京东收银台退款异步回调解密为空"); + } + } catch (Exception e) { + log.error("京东收银台退款异步回调异常", e); + } + return ERROR; + } + +} 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/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/EJdFastBankCode.java b/bd-third/src/main/java/com/hzs/third/pay/enums/EJdFastBankCode.java new file mode 100644 index 00000000..881e2831 --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/enums/EJdFastBankCode.java @@ -0,0 +1,52 @@ +package com.hzs.third.pay.enums; + +import lombok.Getter; + +/** + * 京东快捷支付银行 + */ +@Getter +public enum EJdFastBankCode { + + ICBC("ICBC", "工商银行"), + BOC("BOC", "中国银行"), + CCB("CCB", "建设银行"), + CMBCHINA("CMBCHINA", "招商银行"), + POST("POST", "邮政储蓄银行"), + ECITIC("ECITIC", "中信银行"), + CEB("CEB", "光大银行"), + BOCO("BOCO", "交通银行"), + CIB("CIB", "兴业银行"), + CMBC("CMBC", "民生银行"), + SZCB("SZCB", "平安银行"), + CGB("CGB", "广发银行"), + BCCB("BCCB", "北京银行"), + HXB("HXB", "华夏银行"), + SPDB("SPDB", "浦发银行"), + SHB("SHB", "上海银行"), + ; + + private final String value; + private final String label; + + EJdFastBankCode(String value, String label) { + this.value = value; + this.label = label; + } + + /** + * 根据value获取label + * + * @param value + * @return + */ + public static EJdFastBankCode getEnumByValue(String value) { + for (EJdFastBankCode eJdFastBankCode : EJdFastBankCode.values()) { + if (eJdFastBankCode.getValue().equals(value)) { + return eJdFastBankCode; + } + } + return null; + } + +} 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..ac1f60d4 --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/jdpay/config/JdPayAutoConfiguration.java @@ -0,0 +1,58 @@ +package com.hzs.third.pay.jdpay.config; + +import com.hzs.third.pay.config.JdPayBankProperties; +import com.hzs.third.pay.config.JdPayWechatAlipayProperties; +import com.hzs.third.pay.jdpay.sdk.JdPay; +import com.hzs.third.pay.jdpay.sdk.JdPayDefaultNewConfig; +import com.hzs.third.pay.jdpay.sdk.JdPayNewConfig; +import com.hzs.third.pay.jdpay.util.FileUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/************************************************* + * + * 京东支付sdk初始化 + * + *************************************************/ +@Configuration +public class JdPayAutoConfiguration { + @Autowired + private JdPayBankProperties jdPayBankProperties; + @Autowired + private JdPayWechatAlipayProperties jdPayWechatAlipayProperties; + + + + @Bean(name = "jdPayBank") + public JdPay initJdBankPay() { + // 加载商户私钥证书 +// byte[] privateCert = FileUtil.readFile(priCert); + byte[] privateCert = FileUtil.readFile(jdPayBankProperties.getPriCert()); + // 加载商户公钥证书 +// byte[] publicCert = FileUtil.readFile(pubCert); + byte[] publicCert = FileUtil.readFile(jdPayBankProperties.getPubCert()); + // 检查商户证书 + checkCert(privateCert, publicCert); + // 初始化京东支付配置对象 +// JdPayNewConfig myConfig = new JdPayDefaultNewConfig(merchantNo, signKey, privateCert, priCertPwd, publicCert, apiDomain); + JdPayNewConfig myConfig = new JdPayDefaultNewConfig(jdPayBankProperties.getMerchantNo() + , jdPayBankProperties.getSignKey() + , privateCert + , jdPayBankProperties.getPriCertPwd() + , publicCert + , jdPayBankProperties.getApiDomain()); + 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..3ff50551 --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/jdpay/dto/JdPayAggregateCreateOrderRequest.java @@ -0,0 +1,133 @@ +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 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; + + /** + * 点击完成按钮后跳转页面 + */ + private String callbackUrl; + /** + * 风控信息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/JdPayDivisionAccount.java b/bd-third/src/main/java/com/hzs/third/pay/jdpay/dto/JdPayDivisionAccount.java new file mode 100644 index 00000000..d7f51f69 --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/jdpay/dto/JdPayDivisionAccount.java @@ -0,0 +1,45 @@ +package com.hzs.third.pay.jdpay.dto; + + +import java.io.Serializable; +import java.util.List; + +/** + * 分账业务字段 + */ +public class JdPayDivisionAccount implements Serializable { + + /** + * 版本号 + */ + private String version = "v2"; +// private String version; + /** + * 分账交易信息 + */ + private List divisionAccountTradeInfoList; + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public List getDivisionAccountTradeInfoList() { + return divisionAccountTradeInfoList; + } + + public void setDivisionAccountTradeInfoList(List divisionAccountTradeInfoList) { + this.divisionAccountTradeInfoList = divisionAccountTradeInfoList; + } + + @Override + public String toString() { + return "{\"JdPayDivisionAccount\":{" + + "\"version\":\"" + version + "\"" + + ", \"divisionAccountTradeInfoList\":" + divisionAccountTradeInfoList + + "}}"; + } +} diff --git a/bd-third/src/main/java/com/hzs/third/pay/jdpay/dto/JdPayDivisionAccountRefund.java b/bd-third/src/main/java/com/hzs/third/pay/jdpay/dto/JdPayDivisionAccountRefund.java new file mode 100644 index 00000000..9c9767c9 --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/jdpay/dto/JdPayDivisionAccountRefund.java @@ -0,0 +1,46 @@ +package com.hzs.third.pay.jdpay.dto; + + +import java.io.Serializable; +import java.util.List; + +/** + * 分账业务字段 + * @author liulian115 + */ +public class JdPayDivisionAccountRefund implements Serializable { + + /** + * 版本号 + */ + private String version="V2"; + /** + * 分账交易信息 + * @see JdPayDivisionAccountRefundInfo + */ + private List divisionAccountRefundInfoList; + + @Override + public String toString() { + return "{\"JdPayDivisionAccountRefund\":{" + + "\"version\":\"" + version + "\"" + + ", \"divisionAccountRefundInfoList\":" + divisionAccountRefundInfoList + + "}}"; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public List getDivisionAccountRefundInfoList() { + return divisionAccountRefundInfoList; + } + + public void setDivisionAccountRefundInfoList(List divisionAccountRefundInfoList) { + this.divisionAccountRefundInfoList = divisionAccountRefundInfoList; + } +} diff --git a/bd-third/src/main/java/com/hzs/third/pay/jdpay/dto/JdPayDivisionAccountRefundInfo.java b/bd-third/src/main/java/com/hzs/third/pay/jdpay/dto/JdPayDivisionAccountRefundInfo.java new file mode 100644 index 00000000..b38de727 --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/jdpay/dto/JdPayDivisionAccountRefundInfo.java @@ -0,0 +1,80 @@ +package com.hzs.third.pay.jdpay.dto; + +import java.io.Serializable; + +/** + * 分账交易信息 + */ +public class JdPayDivisionAccountRefundInfo implements Serializable { + /** + * 分账子商户号 + */ + private String merchantNo; + /** + * 分账子商户订单号 + */ + private String outTradeNo; + /** + * 分账子商户业务单号 + */ + private String bizTradeNo; + /** + * 分账子单金额 + */ + private String tradeAmount; + /** + * 原商户订单号 + */ + private String originalOutTradeNo; + + public String getOriginalOutTradeNo() { + return originalOutTradeNo; + } + + public void setOriginalOutTradeNo(String originalOutTradeNo) { + this.originalOutTradeNo = originalOutTradeNo; + } + + public String getMerchantNo() { + return merchantNo; + } + + public void setMerchantNo(String merchantNo) { + this.merchantNo = merchantNo; + } + + public String getOutTradeNo() { + return outTradeNo; + } + + public void setOutTradeNo(String outTradeNo) { + this.outTradeNo = outTradeNo; + } + + public String getBizTradeNo() { + return bizTradeNo; + } + + public void setBizTradeNo(String bizTradeNo) { + this.bizTradeNo = bizTradeNo; + } + + public String getTradeAmount() { + return tradeAmount; + } + + public void setTradeAmount(String tradeAmount) { + this.tradeAmount = tradeAmount; + } + + @Override + public String toString() { + return "{\"JdPayDivisionAccountRefundInfo\":{" + + "\"merchantNo\":\"" + merchantNo + "\"" + + ", \"outTradeNo\":\"" + outTradeNo + "\"" + + ", \"bizTradeNo\":\"" + bizTradeNo + "\"" + + ", \"tradeAmount\":\"" + tradeAmount + "\"" + + ", \"originalOutTradeNo\":\"" + originalOutTradeNo + "\"" + + "}}"; + } +} diff --git a/bd-third/src/main/java/com/hzs/third/pay/jdpay/dto/JdPayDivisionAccountTradeInfo.java b/bd-third/src/main/java/com/hzs/third/pay/jdpay/dto/JdPayDivisionAccountTradeInfo.java new file mode 100644 index 00000000..d56c960f --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/jdpay/dto/JdPayDivisionAccountTradeInfo.java @@ -0,0 +1,67 @@ +package com.hzs.third.pay.jdpay.dto; + +import java.io.Serializable; + +/** + * 分账交易信息 + */ +public class JdPayDivisionAccountTradeInfo implements Serializable { + /** + * 分账子商户号 + */ + private String merchantNo; + /** + * 分账子商户订单号 + */ + private String outTradeNo; + /** + * 分账子商户业务单号 + */ + private String bizTradeNo; + /** + * 分账子单金额 + */ + private String tradeAmount; + + public String getMerchantNo() { + return merchantNo; + } + + public void setMerchantNo(String merchantNo) { + this.merchantNo = merchantNo; + } + + public String getOutTradeNo() { + return outTradeNo; + } + + public void setOutTradeNo(String outTradeNo) { + this.outTradeNo = outTradeNo; + } + + public String getBizTradeNo() { + return bizTradeNo; + } + + public void setBizTradeNo(String bizTradeNo) { + this.bizTradeNo = bizTradeNo; + } + + public String getTradeAmount() { + return tradeAmount; + } + + public void setTradeAmount(String tradeAmount) { + this.tradeAmount = tradeAmount; + } + + @Override + public String toString() { + return "{\"JdPayDivisionAccountTradeInfo\":{" + + "\"merchantNo\":\"" + merchantNo + "\"" + + ", \"outTradeNo\":\"" + outTradeNo + "\"" + + ", \"bizTradeNo\":\"" + bizTradeNo + "\"" + + ", \"tradeAmount\":\"" + tradeAmount + "\"" + + "}}"; + } +} diff --git a/bd-third/src/main/java/com/hzs/third/pay/jdpay/dto/JdPayQueryEnterpriseWalletBalanceRequest.java b/bd-third/src/main/java/com/hzs/third/pay/jdpay/dto/JdPayQueryEnterpriseWalletBalanceRequest.java new file mode 100644 index 00000000..dc6b5f38 --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/jdpay/dto/JdPayQueryEnterpriseWalletBalanceRequest.java @@ -0,0 +1,16 @@ +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 JdPayQueryEnterpriseWalletBalanceRequest implements Serializable { + private String accountNo; +} diff --git a/bd-third/src/main/java/com/hzs/third/pay/jdpay/dto/JdPayQueryEnterpriseWalletBalanceResponse.java b/bd-third/src/main/java/com/hzs/third/pay/jdpay/dto/JdPayQueryEnterpriseWalletBalanceResponse.java new file mode 100644 index 00000000..7d60d58b --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/jdpay/dto/JdPayQueryEnterpriseWalletBalanceResponse.java @@ -0,0 +1,25 @@ +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 JdPayQueryEnterpriseWalletBalanceResponse implements Serializable { + + /** + * 业务结果 + */ + private String resultCode; + /** + * 响应描述 + */ + private String resultDesc; + +} 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/dto/JdPayRefundSuccessNotify.java b/bd-third/src/main/java/com/hzs/third/pay/jdpay/dto/JdPayRefundSuccessNotify.java new file mode 100644 index 00000000..e3b25444 --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/jdpay/dto/JdPayRefundSuccessNotify.java @@ -0,0 +1,66 @@ +package com.hzs.third.pay.jdpay.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * @Description: 退款成功通知 + * @Author: jiang chao + * @Time: 2025/3/21 10:24 + * @Classname: JdPayRefundSuccessNotify + * @PackageName: com.hzs.third.pay.jdpay.dto + */ +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Data +public class JdPayRefundSuccessNotify implements Serializable { + + /** + * 业务结果 + */ + private String resultCode; + /** + * 响应描述 + */ + private String resultDesc; + + /** + * 京东退款订单号 + */ + private String tradeNo; + + /** + * 商户退款订单号 + */ + private String outTradeNo; + /** + * 商户原正单订单号 + */ + private String originalOutTradeNo; + /** + * 订单总金额 + */ + private String tradeAmount; + /** + * 退款完成时间 + */ + private String finishDate; + /** + * 交易状态 + */ + private String tradeStatus; + /** + * 回传字段 + */ + private String returnParams; + /** + * 币种 + */ + private String currency; + +} diff --git a/bd-third/src/main/java/com/hzs/third/pay/jdpay/dto/JdPayTradeSuccessNotify.java b/bd-third/src/main/java/com/hzs/third/pay/jdpay/dto/JdPayTradeSuccessNotify.java new file mode 100644 index 00000000..a42ba4fb --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/jdpay/dto/JdPayTradeSuccessNotify.java @@ -0,0 +1,98 @@ +package com.hzs.third.pay.jdpay.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * @Description: 交易成功通知 + * @Author: jiang chao + * @Time: 2025/3/21 10:24 + * @Classname: JdPayRefundSuccessNotify + * @PackageName: com.hzs.third.pay.jdpay.dto + */ +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Data +public class JdPayTradeSuccessNotify implements Serializable { + + /** + * 业务结果 + */ + private String resultCode; + /** + * 响应描述 + */ + private String resultDesc; + + /** + * 京东交易订单号 + */ + private String tradeNo; + + /** + * 商户订单号 + */ + private String outTradeNo; + /** + * 订单总金额 + */ + private String tradeAmount; + /** + * 币种 + */ + private String currency; + /** + * 支付完成时间 + */ + private String finishDate; + /** + * 交易类型 + */ + private String tradeType; + /** + * 交易状态 + */ + private String tradeStatus; + /** + * 回传字段 + */ + private String returnParams; + /** + * 请求的端 + */ + private String clientType; + /** + * 商户用户标识 + */ + private String userId; + /** + * 优惠金额 + */ + private String discountAmount; + /** + * 支付工具 + */ + private String payTool; + /** + * 掩码卡号 + */ + private String maskCardNo; + /** + * 卡类型 + */ + private String cardType; + /** + * 银行编码 + */ + private String bankCode; + /** + * 白条分期数 + */ + private String installmentNum; + +} 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..f5898780 --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/jdpay/sdk/JdPay.java @@ -0,0 +1,177 @@ +package com.hzs.third.pay.jdpay.sdk; + +import com.hzs.third.pay.jdpay.dto.*; +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); +// } + + + public JdPayQueryEnterpriseWalletBalanceResponse queryEnterpriseWalletBalance(JdPayQueryEnterpriseWalletBalanceRequest request) throws Exception{ + return this.baseExecute(JdPayConstant.QUERY_ENTERPRISE_WALLET_BALANCE_URL, request, JdPayQueryEnterpriseWalletBalanceResponse.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..420673c7 --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/jdpay/sdk/JdPayConstant.java @@ -0,0 +1,98 @@ +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"; + public static final String AGGREGATE_CREATE_ORDER_URL = "/api/createIndustryOrder"; + /** + * 交易查询 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 QUERY_ENTERPRISE_WALLET_BALANCE_URL = "/api/queryEnterpriseWalletBalance"; + + /** + * 随机字符常量 + */ + 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..a07020fc --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/jdpay/util/FileUtil.java @@ -0,0 +1,45 @@ +package com.hzs.third.pay.jdpay.util; + +import org.springframework.core.io.ClassPathResource; + +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +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..05487f57 --- /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.JdPayConstant; +import com.hzs.third.pay.jdpay.sdk.JdPayNewConfig; +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/mapper/TOnlinePaymentSepAccDMapper.java b/bd-third/src/main/java/com/hzs/third/pay/mapper/TOnlinePaymentSepAccDMapper.java new file mode 100644 index 00000000..2d355e24 --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/mapper/TOnlinePaymentSepAccDMapper.java @@ -0,0 +1,16 @@ +package com.hzs.third.pay.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.hzs.common.domain.third.pay.TOnlinePaymentSepAccD; + +/** + *

+ * 支付请求子表(分账) Mapper 接口 + *

+ * + * @author bd + * @since 2025-07-01 + */ +public interface TOnlinePaymentSepAccDMapper extends BaseMapper { + +} diff --git a/bd-third/src/main/java/com/hzs/third/pay/mapper/TOnlinePaymentSepAccMapper.java b/bd-third/src/main/java/com/hzs/third/pay/mapper/TOnlinePaymentSepAccMapper.java new file mode 100644 index 00000000..3a4f9d63 --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/mapper/TOnlinePaymentSepAccMapper.java @@ -0,0 +1,17 @@ +package com.hzs.third.pay.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.hzs.common.domain.third.pay.TOnlinePaymentSepAcc; +import org.apache.ibatis.annotations.Param; + +/** + *

+ * 支付请求主表 Mapper 接口 + *

+ * + * @author bd + * @since 2025-07-01 + */ +public interface TOnlinePaymentSepAccMapper extends BaseMapper { + TOnlinePaymentSepAcc getSepAccByOrderCode(@Param("orderCode") String orderCode); +} diff --git a/bd-third/src/main/java/com/hzs/third/pay/param/OnlinePayConfigParam.java b/bd-third/src/main/java/com/hzs/third/pay/param/OnlinePayConfigParam.java index 04a8a728..e253b9a7 100644 --- a/bd-third/src/main/java/com/hzs/third/pay/param/OnlinePayConfigParam.java +++ b/bd-third/src/main/java/com/hzs/third/pay/param/OnlinePayConfigParam.java @@ -12,23 +12,34 @@ import lombok.Data; @Data public class OnlinePayConfigParam { - /** - * 宝付微信 - */ - private Boolean pay11 = Boolean.FALSE; - /** - * 宝付微信扫码 - */ - private Boolean pay12 = Boolean.FALSE; +// /** +// * 京东银行卡 +// */ +// private Boolean pay4 = Boolean.FALSE; /** - * 汇付微信 + * 京东收银台(H5) */ - private Boolean pay13 = Boolean.FALSE; + private Boolean pay5 = Boolean.FALSE; + /** - * 汇付银行卡 + * 京东收银台(PC) */ - private Boolean pay15 = Boolean.FALSE; + private Boolean pay6 = Boolean.FALSE; + +// /** +// * 微信APP +// */ +// private Boolean pay20 = Boolean.FALSE; + + /** + * 通联微信 + */ + private Boolean pay32 = Boolean.FALSE; + /** + * 通联银行卡 + */ + private Boolean pay33 = Boolean.FALSE; /** * 新汇付PC微信扫码支付 @@ -38,13 +49,5 @@ public class OnlinePayConfigParam { * 新汇付PC支付宝扫码支付 */ private Boolean pay82 = Boolean.FALSE; - /** - * 新汇付H5微信支付 - */ - private Boolean pay83 = Boolean.FALSE; - /** - * 新汇付H5支付宝支付 - */ - private Boolean pay84 = Boolean.FALSE; } diff --git a/bd-third/src/main/java/com/hzs/third/pay/param/PayParam.java b/bd-third/src/main/java/com/hzs/third/pay/param/PayParam.java new file mode 100644 index 00000000..547156cb --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/param/PayParam.java @@ -0,0 +1,43 @@ +package com.hzs.third.pay.param; + +import lombok.Data; + +/** + * 支付入参 + */ +@Data +public class PayParam { + + /** + * 业务类型(EPayBusinessType) + */ + private Integer businessType; + /** + * 业务单号 + */ + private String businessCode; + + /** + * 支付渠道(EPayChannel) + */ + private Integer payChannel; + /** + * 支付类型(EPayType) + */ + private Integer payType; + + /** + * 绑卡编号(银行卡支付使用) + */ + private String bindCode; + + /** + * 小程序支付标记(为0则小程序支付) + */ + private Integer appletFlag; + + /** + * 支付-前端回调页面的扩展参数 + */ + private String extParam; +} diff --git a/bd-third/src/main/java/com/hzs/third/pay/param/UnifiedOrderParam.java b/bd-third/src/main/java/com/hzs/third/pay/param/UnifiedOrderParam.java index 72aaef01..4c9bc23f 100644 --- a/bd-third/src/main/java/com/hzs/third/pay/param/UnifiedOrderParam.java +++ b/bd-third/src/main/java/com/hzs/third/pay/param/UnifiedOrderParam.java @@ -47,4 +47,9 @@ public class UnifiedOrderParam implements Serializable { */ private Integer appletFlag; + /** + * 支付-前端回调页面的扩展参数 + */ + private String extParam; + } diff --git a/bd-third/src/main/java/com/hzs/third/pay/provider/ITOnlinePaymentServiceProvider.java b/bd-third/src/main/java/com/hzs/third/pay/provider/ITOnlinePaymentServiceProvider.java new file mode 100644 index 00000000..d1b2744c --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/provider/ITOnlinePaymentServiceProvider.java @@ -0,0 +1,41 @@ +package com.hzs.third.pay.provider; + +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.hzs.common.core.domain.R; +import com.hzs.common.core.enums.EPayChannel; +import com.hzs.common.domain.third.pay.TOnlinePayment; +import com.hzs.third.pay.ITOnlinePaymentServiceApi; +import com.hzs.third.pay.service.IRefundService; +import com.hzs.third.pay.service.ITOnlinePaymentService; +import org.apache.dubbo.config.annotation.DubboService; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.Objects; + +@DubboService +public class ITOnlinePaymentServiceProvider implements ITOnlinePaymentServiceApi { + @Autowired + private ITOnlinePaymentService itOnlinePaymentService; + @Autowired + private IRefundService iRefundService; + @Override + public R getOnlinePayment(String orderNo) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(TOnlinePayment::getBusinessCode, orderNo); + return R.ok(itOnlinePaymentService.getOne(queryWrapper, false)); + } + + @Override + public R refund(String orderNo, Long userId) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(TOnlinePayment::getBusinessCode, orderNo); + TOnlinePayment tOnlinePayment = itOnlinePaymentService.getOne(queryWrapper, false); + if(ObjectUtil.isNotEmpty(tOnlinePayment)){ + if (Objects.requireNonNull(EPayChannel.getEnumByValue(tOnlinePayment.getPayChannel())) == EPayChannel.JD) { + return R.ok(iRefundService.jdRefundDivision(orderNo, userId)); + } + } + return R.fail(); + } +} 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 index 41d1445f..ccd9a9d1 100644 --- 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 @@ -1,23 +1,21 @@ 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; /** - * @Description: 京东支付服务 - * @Author: jiang chao - * @Time: 2025/2/6 9:57 - * @Classname: IJdPayService - * @PackageName: com.hzs.third.pay.service + * 京东支付服务 */ public interface IJdPayService { /** - * 京东收银台支付 + * 收银台支付 * - * @param tOnlinePayment 在线支付信息 + * @param onlinePayment + * @param dataSource * @return */ - R cashDeskPay(TOnlinePayment tOnlinePayment); + R cashRegister(TOnlinePayment onlinePayment, EDataSource dataSource); } diff --git a/bd-third/src/main/java/com/hzs/third/pay/service/IPayService.java b/bd-third/src/main/java/com/hzs/third/pay/service/IPayService.java index 7bf20566..7ee5101a 100644 --- a/bd-third/src/main/java/com/hzs/third/pay/service/IPayService.java +++ b/bd-third/src/main/java/com/hzs/third/pay/service/IPayService.java @@ -27,6 +27,10 @@ public interface IPayService { boolean notifyHandle(String businessType, String businessCode, String originalOrder, String payNumber, Date payTime, BigDecimal payMoney, EPayChannel ePayChannel, String channelNumber); + boolean notifyHandle(String businessType, String businessCode, String originalOrder, + String payNumber, Date payTime, BigDecimal payMoney, + EPayChannel ePayChannel, String channelNumber, Integer payType); + /** * 业务处理重试 diff --git a/bd-third/src/main/java/com/hzs/third/pay/service/IRefundService.java b/bd-third/src/main/java/com/hzs/third/pay/service/IRefundService.java index e2dc4dc2..54ab541a 100644 --- a/bd-third/src/main/java/com/hzs/third/pay/service/IRefundService.java +++ b/bd-third/src/main/java/com/hzs/third/pay/service/IRefundService.java @@ -2,36 +2,24 @@ package com.hzs.third.pay.service; import com.hzs.common.core.enums.EPayChannel; import com.hzs.common.domain.third.pay.TOnlinePayment; -import com.hzs.common.domain.third.pay.TOnlineRefund; +import com.hzs.third.pay.dto.RefundDTO; import java.math.BigDecimal; import java.util.Date; /** - * @Description: 基础退款服务 - * @Author: jiang chao - * @Time: 2023/8/16 17:00 - * @Classname: IRefundService - * @PackageName: com.hzs.third.pay.service + * 基础退款服务 */ public interface IRefundService { /** - * 新汇付退款处理 + * 通联退款处理 * - * @param tOnlinePayment 在线支付信息 + * @param refundDTO 业务类型 + * @param tOnlinePayment 退款对应支付信息 * @return */ - String huifuRefundHandle(TOnlinePayment tOnlinePayment); - - /** - * 新汇付退款查询 - * - * @param tOnlineRefund 退款信息 - * @return - */ - String queryHuifuRefundHandle(TOnlineRefund tOnlineRefund); - + String allInRefundHandle(RefundDTO refundDTO, TOnlinePayment tOnlinePayment); /** * 退款回调成功处理 @@ -56,4 +44,25 @@ public interface IRefundService { */ boolean notifyErrorHandle(EPayChannel ePayChannel, String refundCode, String refundNumber, String errorMsg); + + /** + * 京东退款处理 + * + * @param refundDTO 业务类型 + * @param tOnlinePayment 退款对应支付信息 + * @return + */ + String jdRefundHandle(RefundDTO refundDTO, TOnlinePayment tOnlinePayment); + + /** + * 京东收银台退款处理 + * + * @param refundDTO 业务类型 + * @param tOnlinePayment 退款对应支付信息 + * @return + */ + String jdCashRefundHandle(RefundDTO refundDTO, TOnlinePayment tOnlinePayment); + + String jdRefundDivision(String orderCode, Long userId); + String jdQueryEnterpriseWalletBalance(); } diff --git a/bd-third/src/main/java/com/hzs/third/pay/service/ITOnlinePaymentSepAccDService.java b/bd-third/src/main/java/com/hzs/third/pay/service/ITOnlinePaymentSepAccDService.java new file mode 100644 index 00000000..4cf631ee --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/service/ITOnlinePaymentSepAccDService.java @@ -0,0 +1,16 @@ +package com.hzs.third.pay.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.hzs.common.domain.third.pay.TOnlinePaymentSepAccD; + +/** + *

+ * 支付请求子表(分账) 服务类 + *

+ * + * @author bd + * @since 2025-07-01 + */ +public interface ITOnlinePaymentSepAccDService extends IService { + +} diff --git a/bd-third/src/main/java/com/hzs/third/pay/service/ITOnlinePaymentSepAccService.java b/bd-third/src/main/java/com/hzs/third/pay/service/ITOnlinePaymentSepAccService.java new file mode 100644 index 00000000..8f48f38a --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/service/ITOnlinePaymentSepAccService.java @@ -0,0 +1,18 @@ +package com.hzs.third.pay.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.hzs.common.domain.third.pay.TOnlinePaymentSepAcc; + +/** + *

+ * 支付请求主表 服务类 + *

+ * + * @author bd + * @since 2025-07-01 + */ +public interface ITOnlinePaymentSepAccService extends IService { + void saveOrUpdateReq(TOnlinePaymentSepAcc tOnlinePaymentSepAcc); + + TOnlinePaymentSepAcc getSepAccByOrderCode(String code); +} diff --git a/bd-third/src/main/java/com/hzs/third/pay/service/ITOnlinePaymentService.java b/bd-third/src/main/java/com/hzs/third/pay/service/ITOnlinePaymentService.java index 5507fdbc..8bc0bb88 100644 --- a/bd-third/src/main/java/com/hzs/third/pay/service/ITOnlinePaymentService.java +++ b/bd-third/src/main/java/com/hzs/third/pay/service/ITOnlinePaymentService.java @@ -9,9 +9,6 @@ import java.util.List; /** * 在线支付信息 服务类 - * - * @author hzs - * @since 2022-07-28 */ public interface ITOnlinePaymentService extends IService { diff --git a/bd-third/src/main/java/com/hzs/third/pay/service/ITOnlineRefundService.java b/bd-third/src/main/java/com/hzs/third/pay/service/ITOnlineRefundService.java index f246c48c..adf43df1 100644 --- a/bd-third/src/main/java/com/hzs/third/pay/service/ITOnlineRefundService.java +++ b/bd-third/src/main/java/com/hzs/third/pay/service/ITOnlineRefundService.java @@ -9,9 +9,6 @@ import java.util.List; /** * 在线退款信息 服务类 - * - * @author hzs - * @since 2023-08-16 */ public interface ITOnlineRefundService extends IService { 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 index 3d4329c8..fdbc8407 100644 --- 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 @@ -1,23 +1,195 @@ package com.hzs.third.pay.service.impl; +import cn.hutool.core.util.ObjectUtil; +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.EDataSource; +import com.hzs.common.core.enums.EEnv; +import com.hzs.common.core.utils.DateUtils; import com.hzs.common.domain.third.pay.TOnlinePayment; +import com.hzs.common.domain.third.pay.TOnlinePaymentSepAcc; +import com.hzs.common.domain.third.pay.TOnlinePaymentSepAccD; +import com.hzs.common.security.utils.SecurityUtils; +import com.hzs.third.pay.config.JdPayBankProperties; +import com.hzs.third.pay.config.JdPayConfig; +import com.hzs.third.pay.config.JdPaySeparateAccountConfig; +import com.hzs.third.pay.config.JdPayWechatAlipayProperties; +import com.hzs.third.pay.jdpay.dto.JdPayAggregateCreateOrderRequest; +import com.hzs.third.pay.jdpay.dto.JdPayAggregateCreateOrderResponse; +import com.hzs.third.pay.jdpay.dto.JdPayDivisionAccount; +import com.hzs.third.pay.jdpay.dto.JdPayDivisionAccountTradeInfo; +import com.hzs.third.pay.jdpay.sdk.JdPay; +import com.hzs.third.pay.jdpay.util.GsonUtil; import com.hzs.third.pay.service.IJdPayService; +import com.hzs.third.pay.service.ITOnlineCardService; +import com.hzs.third.pay.service.ITOnlinePaymentSepAccService; +import com.hzs.third.pay.service.ITOnlinePaymentService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +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.ArrayList; +import java.util.Comparator; +import java.util.List; + /** - * @Description: 京东支付服务 - * @Author: jiang chao - * @Time: 2025/2/6 9:57 - * @Classname: JdPayServiceImpl - * @PackageName: com.hzs.third.pay.service.impl + * 京东支付服务 */ +@Slf4j @Service public class JdPayServiceImpl implements IJdPayService { + @Autowired + private JdPayConfig jdPayConfig; + @Autowired + private ITOnlineCardService itOnlineCardService; + @Resource(name = "jdPayBank") + private JdPay jdPay; + @Autowired + private JdPayBankProperties jdPayBankProperties; + @Autowired + private RedisTemplate redisTemplate; + @Autowired + private ITOnlinePaymentSepAccService itOnlinePaymentSepAccService; + + @Autowired + private ITOnlinePaymentService itOnlinePaymentService; + + @Resource + private JdPayWechatAlipayProperties jdPayWechatAlipayProperties; + @Override - public R cashDeskPay(TOnlinePayment tOnlinePayment) { - return null; + 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(); + String userId = SecurityUtils.getMemberCode(); + 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(jdPayBankProperties.getPageBackUrl() + "?extParam=" + onlinePayment.getExtParam()) + .callbackUrl(jdPayBankProperties.getPageBackUrl() + "?extParam=" + onlinePayment.getExtParam()) + // 支付回调地址 + .notifyUrl(jdPayBankProperties.getNotifyUrl()) + // 交易类型 + .tradeType(tradeType) + // 交易场景(ONLINE_APP:线上移动端 ONLINE_PC:线上PC) + .sceneType(sceneType) + .accessType("MINIAPP") + .build(); + + + JdPayDivisionAccount divisionAccount = new JdPayDivisionAccount(); + List divisionAccountTradeInfoList = new ArrayList(); + List sepAccDList = new ArrayList<>(); + if(ObjectUtil.isNotEmpty(jdPayBankProperties)){ + List separateAccounts = jdPayBankProperties.getSeparateAccounts(); + separateAccounts.sort(Comparator.comparing(JdPaySeparateAccountConfig::getProportion).reversed()); + if(ObjectUtil.isNotEmpty(separateAccounts)){ + BigDecimal tradeAmount = onlinePayment.getPayMoney(); + BigDecimal remaining = onlinePayment.getPayMoney(); + for (int i = 0; i < separateAccounts.size(); i++) { + JdPaySeparateAccountConfig separateAccount = separateAccounts.get(i); + BigDecimal subTradeAmount; + if(i == separateAccounts.size() - 1){ + subTradeAmount = remaining; + }else{ + subTradeAmount = tradeAmount.subtract(tradeAmount.multiply(separateAccount.getProportion().setScale(2, BigDecimal.ROUND_DOWN)).setScale(2, BigDecimal.ROUND_DOWN)); + remaining = remaining.subtract(subTradeAmount).setScale(2, BigDecimal.ROUND_DOWN); + } + JdPayDivisionAccountTradeInfo divisionAccountTradeInfo = new JdPayDivisionAccountTradeInfo(); + divisionAccountTradeInfo.setMerchantNo(separateAccount.getAccount()); + divisionAccountTradeInfo.setOutTradeNo(onlinePayment.getBusinessCode() + "_" + (i + 1)); + divisionAccountTradeInfo.setTradeAmount(subTradeAmount.multiply(new BigDecimal(100)).intValue() + ""); + divisionAccountTradeInfoList.add(divisionAccountTradeInfo); + sepAccDList.add( + TOnlinePaymentSepAccD.builder() + .account(separateAccount.getAccount()) + .proportion(separateAccount.getProportion()) + .tradeAmount(subTradeAmount) + .outTradeNo(divisionAccountTradeInfo.getOutTradeNo()) + .build() + ); + } + } + } + + divisionAccount.setVersion( "V2" ); + divisionAccount.setDivisionAccountTradeInfoList(divisionAccountTradeInfoList); + request.setDivisionAccount(GsonUtil.toJson(divisionAccount)); + + // 请求京东支付接口 + JdPayAggregateCreateOrderResponse response = jdPay.aggregateCreateOrder(request); + TOnlinePaymentSepAcc sepAcc = TOnlinePaymentSepAcc.builder() + .sepAccDList(sepAccDList) + .reqContent(request.getDivisionAccount()) + .reqContentSepAcc(JSONUtil.toJsonStr(divisionAccountTradeInfoList)) + .version(divisionAccount.getVersion()) + .respContent(JSONUtil.toJsonStr(response)) + .tradeAmount(onlinePayment.getPayMoney()) + .outTradeNo(request.getOutTradeNo()) + .build(); + if ("00000".equals(response.getResultCode())) { + // 请求响应成功 + // 京东唯一订单号 + onlinePayment.setPayNumber(response.getTradeNo()); + // 将controller内入库逻辑移至此为止 + onlinePayment.setPayMoney(null); + itOnlinePaymentService.saveOrUpdate(onlinePayment); + // 2025年7月1日 保存分账主表&子表 + sepAcc.setPkPayment(onlinePayment.getPkId()); + itOnlinePaymentSepAccService.saveOrUpdateReq(sepAcc); + 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/service/impl/PayServiceImpl.java b/bd-third/src/main/java/com/hzs/third/pay/service/impl/PayServiceImpl.java index 9e5268a2..13456a99 100644 --- a/bd-third/src/main/java/com/hzs/third/pay/service/impl/PayServiceImpl.java +++ b/bd-third/src/main/java/com/hzs/third/pay/service/impl/PayServiceImpl.java @@ -1,5 +1,6 @@ package com.hzs.third.pay.service.impl; +import cn.hutool.core.util.ObjectUtil; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.hzs.common.core.constant.MagicNumberConstants; import com.hzs.common.core.constant.RabbitMqConstants; @@ -58,7 +59,9 @@ public class PayServiceImpl implements IPayService { String payNumber, Date payTime, BigDecimal payMoney, EPayChannel ePayChannel, String channelNumber) { // 校验支付信息状态、业务与支付金额是否一致等 - TOnlinePayment onlinePayment = itOnlinePaymentService.queryByBusiness(Integer.valueOf(businessType), businessCode, null); + TOnlinePayment onlinePayment = itOnlinePaymentService.queryByBusiness( + ObjectUtil.isNotEmpty(businessType) ? Integer.valueOf(businessType) : null + , businessCode, null); if (null == onlinePayment || EPayStatus.PAID.getValue() == onlinePayment.getPayStatus()) { log.error("支付信息不存在或已支付"); return false; @@ -103,6 +106,57 @@ public class PayServiceImpl implements IPayService { return false; } + @Override + public boolean notifyHandle(String businessType, String businessCode, String originalOrder, String payNumber, Date payTime, BigDecimal payMoney, EPayChannel ePayChannel, String channelNumber, Integer payType) { + // 校验支付信息状态、业务与支付金额是否一致等 + TOnlinePayment onlinePayment = itOnlinePaymentService.queryByBusiness( + ObjectUtil.isNotEmpty(businessType) ? Integer.valueOf(businessType) : null + , businessCode, null); + if (null == onlinePayment || EPayStatus.PAID.getValue() == onlinePayment.getPayStatus()) { + log.error("支付信息不存在或已支付"); + return false; + } + + try { + if (!PayUtil.checkAmount(onlinePayment.getBusinessMoney(), payMoney)) { + // 业务金额 大于 实际支付金额 + log.error("业务金额大于实际支付金额,支付编号:{}, 支付金额:{}", onlinePayment.getPaymentCode(), payMoney); + } + + onlinePayment.setPayStatus(EPayStatus.PAID.getValue()); + onlinePayment.setPayNumber(payNumber); + onlinePayment.setPayTime(payTime); + onlinePayment.setPayMoney(payMoney); + onlinePayment.setOriginalOrder(originalOrder); + onlinePayment.setChannelNumber(channelNumber); + onlinePayment.setPkModified(MagicNumberConstants.PK_ADMIN); + onlinePayment.setModifiedTime(new Date()); + onlinePayment.setPayType(payType); + if (null != ePayChannel) { + onlinePayment.setPayChannel(ePayChannel.getValue()); + } + // 更新支付相关信息 + if (itOnlinePaymentService.updateById(onlinePayment)) { + // 进行支付业务处理 + this.handleBusiness(onlinePayment, true); + // 更新业务处理信息 + itOnlinePaymentService.updateById(onlinePayment); + + return true; + } + } catch (Exception e) { + log.error("在线支付信息回调处理异常", e); + + // 更新业务处理信息 + itOnlinePaymentService.update(Wrappers.lambdaUpdate() + .eq(TOnlinePayment::getPkId, onlinePayment.getPkId()) + .set(TOnlinePayment::getCallbackStatus, ECallbackStatus.FAIL.getValue()) + .set(TOnlinePayment::getCallbackInfo, e.getMessage()) + ); + } + return false; + } + @Override public boolean retryHandle(TOnlinePayment tOnlinePayment, boolean mqPush) { // 支付业务处理 diff --git a/bd-third/src/main/java/com/hzs/third/pay/service/impl/RefundServiceImpl.java b/bd-third/src/main/java/com/hzs/third/pay/service/impl/RefundServiceImpl.java index e9f0a57d..a99bda5d 100644 --- a/bd-third/src/main/java/com/hzs/third/pay/service/impl/RefundServiceImpl.java +++ b/bd-third/src/main/java/com/hzs/third/pay/service/impl/RefundServiceImpl.java @@ -1,165 +1,67 @@ package com.hzs.third.pay.service.impl; import cn.hutool.json.JSONUtil; -import com.huifu.bspay.sdk.opps.client.BasePayClient; -import com.huifu.bspay.sdk.opps.core.request.V2TradePaymentScanpayRefundRequest; -import com.huifu.bspay.sdk.opps.core.request.V2TradePaymentScanpayRefundqueryRequest; -import com.huifu.bspay.sdk.opps.core.utils.DateTools; -import com.hzs.common.core.constant.MagicNumberConstants; -import com.hzs.common.core.domain.R; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.hzs.common.core.enums.*; -import com.hzs.common.core.utils.*; +import com.hzs.common.core.utils.CommonUtil; +import com.hzs.common.core.utils.DateUtils; +import com.hzs.common.core.utils.StringUtils; import com.hzs.common.domain.third.pay.TOnlinePayment; +import com.hzs.common.domain.third.pay.TOnlinePaymentSepAcc; +import com.hzs.common.domain.third.pay.TOnlinePaymentSepAccD; import com.hzs.common.domain.third.pay.TOnlineRefund; import com.hzs.common.domain.third.pay.ext.TOnlineRefundExt; import com.hzs.sale.refund.ISaRefundServiceApi; -import com.hzs.third.pay.config.HuiFuConfig; -import com.hzs.third.pay.constants.HuiFuPayConstants; -import com.hzs.third.pay.service.IRefundService; -import com.hzs.third.pay.service.ITOnlineRefundService; +import com.hzs.third.pay.config.JdPayBankProperties; +import com.hzs.third.pay.config.JdPayConfig; +import com.hzs.third.pay.config.JdPayWechatAlipayProperties; +import com.hzs.third.pay.dto.RefundDTO; +import com.hzs.third.pay.jdpay.dto.*; +import com.hzs.third.pay.jdpay.sdk.JdPay; +import com.hzs.third.pay.jdpay.util.GsonUtil; +import com.hzs.third.pay.service.*; import lombok.extern.slf4j.Slf4j; import org.apache.dubbo.config.annotation.DubboReference; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import javax.annotation.Resource; import java.math.BigDecimal; -import java.text.DecimalFormat; -import java.util.*; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; /** - * @Description: 基础退款服务 - * @Author: jiang chao - * @Time: 2023/8/16 17:00 - * @Classname: RefundServiceImpl - * @PackageName: com.hzs.third.pay.service.impl + * 基础退款服务 */ @Slf4j @Service public class RefundServiceImpl implements IRefundService { - - @Autowired - private HuiFuConfig huiFuConfig; @Autowired private ITOnlineRefundService itOnlineRefundService; + @Autowired + private JdPayConfig jdPayConfig; @DubboReference ISaRefundServiceApi iSaRefundServiceApi; - @Override - public String huifuRefundHandle(TOnlinePayment tOnlinePayment) { - // 返回信息 - String returnStr = null; - try { - // 退款单号 - String refundCode = CommonUtil.createSerialNumber(EOrderPrefix.REFUND_CODE); - // 保存退款信息 - TOnlineRefund tOnlineRefund = TOnlineRefund.builder() - .refundCode(refundCode) - .businessCode(tOnlinePayment.getBusinessCode()) - .refundStatus(ERefundStatus.REFUND_REQUEST.getValue()) - .refundMoney(tOnlinePayment.getPayMoney()) - .refundChannel(tOnlinePayment.getPayChannel()) - .pkOnlinePayment(tOnlinePayment.getPkId()) - .build(); - tOnlineRefund.setPkCreator(MagicNumberConstants.PK_ADMIN); - tOnlineRefund.setPkCountry(tOnlinePayment.getPkCountry()); - itOnlineRefundService.save(tOnlineRefund); + @Resource(name = "jdPayBank") + private JdPay jdPay; + @Resource + private JdPayBankProperties jdPayBankProperties; - // 组装交易参数 - V2TradePaymentScanpayRefundRequest request = new V2TradePaymentScanpayRefundRequest(); - // 请求日期 - request.setReqDate(DateTools.getCurrentDateYYYYMMDD()); - // 请求流水号(保证唯一) - request.setReqSeqId(refundCode); - // 商户号 - request.setHuifuId(huiFuConfig.getSysId()); - // 申请退款金额 - request.setOrdAmt(new DecimalFormat("#0.00").format(tOnlinePayment.getPayMoney())); - // 原交易请求日期 - request.setOrgReqDate(DateTools.dateToDateString(tOnlinePayment.getCreationTime(), DateTools.DATE_YYYYMMDD)); - // 设置非必填字段 - Map extendInfoMap = new HashMap<>(); - // 原交易流水号 - extendInfoMap.put("org_hf_seq_id", tOnlinePayment.getPayNumber()); - // 追加参数 - request.setExtendInfo(extendInfoMap); + @Autowired + private ITOnlinePaymentService itOnlinePaymentService; + @Autowired + private ITOnlinePaymentSepAccService sepAccService; + @Autowired + private ITOnlinePaymentSepAccDService sepAccDService; - log.info("新汇付退款调用参数:{}", JSONUtil.toJsonStr(request)); - - Map response = BasePayClient.request(request); - - log.info("新汇付退款调用返回:{}", JSONUtil.toJsonStr(response)); - - tOnlineRefund.setRefundNumber(response.get("hf_seq_id").toString()); - if (HuiFuPayConstants.TRANS_SUCCESS.equals(response.get("trans_stat").toString())) { - // 处理成功 - tOnlineRefund.setRefundStatus(ERefundStatus.REFUNDED.getValue()); - tOnlineRefund.setFinishTime(DateUtils.parseDateOne(response.get("trans_finish_time"), DateUtils.YAMMERERS)); - if (null != response.get("actual_ref_amt")) { - tOnlineRefund.setFinishMoney(new BigDecimal(response.get("actual_ref_amt").toString())); - } - } else if (HuiFuPayConstants.TRANS_PROCESSING.equals(response.get("trans_stat").toString())) { - // 处理中 - tOnlineRefund.setRefundStatus(ERefundStatus.REFUNDING.getValue()); - } else { - // 退款失败 - tOnlineRefund.setRefundStatus(ERefundStatus.REFUND_FAILED.getValue()); - tOnlineRefund.setErrorMsg(response.get("resp_desc").toString()); - - log.error("新汇付退款交易失败! resp_desc: {}, bank_message: {}, hf_seq_id: {}", response.get("resp_desc"), response.get("bank_message"), response.get("hf_seq_id")); - returnStr = "新汇付退款交易失败"; - } - - // 更新接口返回状态 - itOnlineRefundService.updateById(tOnlineRefund); - } catch (Exception e) { - log.error("新汇付退款处理异常", e); - returnStr = "新汇付退款处理异常:" + e.getMessage(); - } - return returnStr; - } @Override - public String queryHuifuRefundHandle(TOnlineRefund tOnlineRefund) { - try { - V2TradePaymentScanpayRefundqueryRequest request = new V2TradePaymentScanpayRefundqueryRequest(); - - // 商户号 - request.setHuifuId(huiFuConfig.getSysId()); - // 退款请求日期 - request.setOrgReqDate(DateTools.dateToDateString(tOnlineRefund.getCreationTime(), DateTools.DATE_YYYYMMDD)); - // 退款全局流水号退款请求流水号,退款全局流水号,终端订单号三选一不能都为空 - request.setOrgHfSeqId(tOnlineRefund.getRefundNumber()); - - log.info("新汇付退款查询调用参数:{}", JSONUtil.toJsonStr(request)); - - Map response = BasePayClient.request(request); - - log.info("新汇付退款查询调用返回:{}", JSONUtil.toJsonStr(response)); - - if (HuiFuPayConstants.TRANS_SUCCESS.equals(response.get("trans_stat").toString())) { - // 处理成功 - tOnlineRefund.setRefundStatus(ERefundStatus.REFUNDED.getValue()); - tOnlineRefund.setFinishTime(DateUtils.parseDateOne(response.get("trans_finish_time"), DateUtils.YAMMERERS)); - if (null != response.get("actual_ref_amt")) { - tOnlineRefund.setFinishMoney(new BigDecimal(response.get("actual_ref_amt").toString())); - } - } else if (HuiFuPayConstants.TRANS_PROCESSING.equals(response.get("trans_stat").toString())) { - // 处理中 - tOnlineRefund.setRefundStatus(ERefundStatus.REFUNDING.getValue()); - } else if (HuiFuPayConstants.TRANS_FAIL.equals(response.get("trans_stat").toString())) { - // 退款失败 - tOnlineRefund.setRefundStatus(ERefundStatus.REFUND_FAILED.getValue()); - tOnlineRefund.setErrorMsg(response.get("resp_desc").toString()); - } else { - return "新汇付退款查询状态:初始化,需要联系汇付技术人员处理"; - } - - return null; - } catch (Exception e) { - log.error("新汇付退款查询处理异常", e); - return "新汇付退款查询处理异常:" + e.getMessage(); - } + public String allInRefundHandle(RefundDTO refundDTO, TOnlinePayment tOnlinePayment) { + return null; } @Override @@ -189,7 +91,6 @@ public class RefundServiceImpl implements IRefundService { // 退款业务处理 if (StringUtils.isNotEmpty(onlineRefund.getPayBusinessCode())) { // 如果退款业务单号存在则进行业务回调处理,如果不存在则表示直接根据支付信息进行退款,不需要进行业务回调 - if (this.handleBusiness(onlineRefund)) { // 更新业务处理信息 if (itOnlineRefundService.updateById(onlineRefund)) { @@ -270,19 +171,19 @@ public class RefundServiceImpl implements IRefundService { // 服务调用返回信息 String resultMsg = ""; // 根据业务类型调用具体业务处理 dubbo 服务 - switch (businessType) { - case MEMBER_ORDER: - // 会员订单 - R saRefundR = iSaRefundServiceApi.onlinePayRefundCallback(onlineRefund.getPayBusinessCode(), onlineRefund.getBusinessCode(), onlineRefund.getRefundStatus(), onlineRefund.getErrorMsg()); - if (saRefundR.isSuccess()) { - resultBool = true; - } else { - resultBool = false; - resultMsg = saRefundR.getMsg(); - } - break; - default: - } +// switch (businessType) { +// case DIRECT_SELLING: +// // 直销订单 +// R saRefundR = iSaRefundServiceApi.onlinePayRefundCallback(onlineRefund.getPayBusinessCode(), onlineRefund.getRefundCode(), onlineRefund.getRefundStatus(), onlineRefund.getErrorMsg()); +// if (saRefundR.isSuccess()) { +// resultBool = true; +// } else { +// resultBool = false; +// resultMsg = saRefundR.getMsg(); +// } +// break; +// default: +// } if (null != resultBool) { if (resultBool) { @@ -304,4 +205,152 @@ public class RefundServiceImpl implements IRefundService { return false; } + + @Override + public String jdRefundHandle(RefundDTO refundDTO, TOnlinePayment tOnlinePayment) { + return null; + } + + @Override + public String jdCashRefundHandle(RefundDTO refundDTO, TOnlinePayment tOnlinePayment) { + try { + // 退款金额(默认按全款退) + BigDecimal amount = tOnlinePayment.getPayMoney(); + if (null != refundDTO.getRefundAmount()) { + // 如果传入实际退款金额,则按实际退款金额处理 + amount = refundDTO.getRefundAmount(); + } + + // 退款编号 + String refundCode = CommonUtil.createSerialNumber(EOrderPrefix.REFUND_CODE); + // 当前时间 + Date nowDate = new Date(); + + JdPayRefundRequest request = JdPayRefundRequest.builder() + // 退款订单号 + .outTradeNo(refundCode) + // 原订单号(需要退款的订单号) + .originalOutTradeNo(tOnlinePayment.getBusinessCode()) + // 退款时间 + .tradeDate(DateUtils.parseDateToFormat(DateUtils.YAMMERERS, nowDate)) + // 退款金额 + .tradeAmount(amount.multiply(new BigDecimal("100")).intValue() + "") + // 货币种类 + .currency("CNY") + // 退款回调地址 + .notifyUrl(jdPayBankProperties.getRefundNotifyUrl()) + // 回传信息 +// .returnParams("") + .build(); + + // 请求京东退款接口 + JdPayRefundResponse response = jdPay.refund(request); + + if ("0000".equals(response.getResultCode())) { + // 请求响应成功 + // 申请退款成功,保存退款 + TOnlineRefund tOnlineRefund = new TOnlineRefund(); + tOnlineRefund.setRefundCode(refundCode); + tOnlineRefund.setPkOnlinePayment(tOnlinePayment.getPkId()); + tOnlineRefund.setBusinessCode(refundDTO.getRefundBusinessCode()); + tOnlineRefund.setRefundMoney(amount); + tOnlineRefund.setRefundChannel(tOnlinePayment.getPayChannel()); + tOnlineRefund.setRefundNumber(response.getTradeNo()); + tOnlineRefund.setPkCountry(refundDTO.getPkCountry()); + tOnlineRefund.setPkCreator(refundDTO.getUserId()); + if (itOnlineRefundService.save(tOnlineRefund)) { + return null; + } else { + log.error("调用京东收银台退款入库处理失败,desc: {}", response.getResultDesc()); + return "退款失败,请联系系统管理员处理"; + } + } + log.error("调用京东收银台退款处理失败,desc: {}", response.getResultDesc()); + return "退款失败,请重试"; + } catch (Exception e) { + log.error("调用京东收银台退款处理返回异常!", e); + return "退款异常,请重试"; + } + } + + @Override + public String jdRefundDivision(String orderCode, Long userId) { + TOnlinePaymentSepAcc sepAcc = sepAccService.getSepAccByOrderCode(orderCode); + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(TOnlinePaymentSepAccD::getPkSepAcc, sepAcc.getPkId()); + queryWrapper.eq(TOnlinePaymentSepAccD::getDelFlag, EDelFlag.UN_DELETE.getValue()); + queryWrapper.orderByAsc(TOnlinePaymentSepAccD::getPkId); + LambdaQueryWrapper paymentQueryWrapper = new LambdaQueryWrapper<>(); + paymentQueryWrapper.eq(TOnlinePayment::getBusinessCode, orderCode); + TOnlinePayment tOnlinePayment = itOnlinePaymentService.getOne(paymentQueryWrapper, false); + List sepAccDList = sepAccDService.list(queryWrapper); + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss"); + String now = sdf.format(new Date()); + String refundCode = "R_" + orderCode; + JdPayRefundRequest request = JdPayRefundRequest.builder() + .outTradeNo("R_" + orderCode) + .originalOutTradeNo(orderCode) + .tradeAmount(sepAcc.getTradeAmount().multiply(new BigDecimal("100")).intValue() + "") + .currency("CNY") + .tradeDate(now) + .returnParams("") + .notifyUrl(jdPayBankProperties.getRefundNotifyUrl()) + .build(); + + JdPayDivisionAccountRefund divisionAccountRefund = new JdPayDivisionAccountRefund(); + List divisionAccountTradeInfoList = new ArrayList<>(); + for (int i = 0; i < sepAccDList.size(); i++) { + TOnlinePaymentSepAccD sepAccD = sepAccDList.get(i); + JdPayDivisionAccountRefundInfo divisionAccountTradeInfo = new JdPayDivisionAccountRefundInfo(); + divisionAccountTradeInfo.setMerchantNo(sepAccD.getAccount()); + divisionAccountTradeInfo.setOutTradeNo("R_" + orderCode + "_" + ( i + 1 )); + divisionAccountTradeInfo.setOriginalOutTradeNo(sepAccD.getOutTradeNo()); + divisionAccountTradeInfo.setTradeAmount(sepAccD.getTradeAmount().multiply(new BigDecimal("100")).intValue() + ""); + divisionAccountTradeInfoList.add(divisionAccountTradeInfo); + } + divisionAccountRefund.setDivisionAccountRefundInfoList(divisionAccountTradeInfoList); + request.setDivisionAccountRefund(GsonUtil.toJson(divisionAccountRefund)); + try { + JdPayRefundResponse response = jdPay.refund(request); + log.info("JdPayRefundResponse -> {}", JSONUtil.toJsonStr(response)); + if ("0000".equals(response.getResultCode())) { + log.info("提交退款成功"); + TOnlineRefund tOnlineRefund = new TOnlineRefund(); + tOnlineRefund.setRefundCode(refundCode); + tOnlineRefund.setPkOnlinePayment(tOnlinePayment.getPkId()); + tOnlineRefund.setBusinessCode(tOnlinePayment.getBusinessCode()); + tOnlineRefund.setRefundMoney(tOnlinePayment.getPayMoney()); + tOnlineRefund.setRefundChannel(tOnlinePayment.getPayChannel()); + tOnlineRefund.setRefundNumber(response.getTradeNo()); + tOnlineRefund.setPkCountry(1); + tOnlineRefund.setPkCreator(userId); + itOnlineRefundService.save(tOnlineRefund); + return "提交退款成功"; + } else { + log.info("提交退款失败"); + return "提交退款失败!["+response.getResultDesc()+"]"; + } + } catch (Exception e) { + return "提交退款异常! ["+e.getMessage()+"]"; + } + } + + @Override + public String jdQueryEnterpriseWalletBalance() { + JdPayQueryEnterpriseWalletBalanceRequest request1 = JdPayQueryEnterpriseWalletBalanceRequest.builder() + .accountNo("153428607005") + .build(); + JdPayQueryEnterpriseWalletBalanceRequest request2 = JdPayQueryEnterpriseWalletBalanceRequest.builder() + .accountNo("153428607007") + .build(); + try { + JdPayQueryEnterpriseWalletBalanceResponse response1 = jdPay.queryEnterpriseWalletBalance(request1); + log.info("response1 -> {}", JSONUtil.toJsonStr(response1)); + JdPayQueryEnterpriseWalletBalanceResponse response2 = jdPay.queryEnterpriseWalletBalance(request2); + log.info("response2 -> {}", JSONUtil.toJsonStr(response2)); + } catch (Exception e) { + throw new RuntimeException(e); + } + return ""; + } } diff --git a/bd-third/src/main/java/com/hzs/third/pay/service/impl/TOnlinePaymentSepAccDServiceImpl.java b/bd-third/src/main/java/com/hzs/third/pay/service/impl/TOnlinePaymentSepAccDServiceImpl.java new file mode 100644 index 00000000..58929229 --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/service/impl/TOnlinePaymentSepAccDServiceImpl.java @@ -0,0 +1,20 @@ +package com.hzs.third.pay.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.hzs.common.domain.third.pay.TOnlinePaymentSepAccD; +import com.hzs.third.pay.mapper.TOnlinePaymentSepAccDMapper; +import com.hzs.third.pay.service.ITOnlinePaymentSepAccDService; +import org.springframework.stereotype.Service; + +/** + *

+ * 支付请求子表(分账) 服务实现类 + *

+ * + * @author bd + * @since 2025-07-01 + */ +@Service +public class TOnlinePaymentSepAccDServiceImpl extends ServiceImpl implements ITOnlinePaymentSepAccDService { + +} diff --git a/bd-third/src/main/java/com/hzs/third/pay/service/impl/TOnlinePaymentSepAccServiceImpl.java b/bd-third/src/main/java/com/hzs/third/pay/service/impl/TOnlinePaymentSepAccServiceImpl.java new file mode 100644 index 00000000..dc014c05 --- /dev/null +++ b/bd-third/src/main/java/com/hzs/third/pay/service/impl/TOnlinePaymentSepAccServiceImpl.java @@ -0,0 +1,67 @@ +package com.hzs.third.pay.service.impl; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.hzs.common.core.enums.EDelFlag; +import com.hzs.common.core.utils.DateUtils; +import com.hzs.common.domain.third.pay.TOnlinePaymentSepAcc; +import com.hzs.common.domain.third.pay.TOnlinePaymentSepAccD; +import com.hzs.common.security.utils.SecurityUtils; +import com.hzs.third.pay.mapper.TOnlinePaymentSepAccMapper; +import com.hzs.third.pay.service.ITOnlinePaymentSepAccDService; +import com.hzs.third.pay.service.ITOnlinePaymentSepAccService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + *

+ * 支付请求主表 服务实现类 + *

+ * + * @author bd + * @since 2025-07-01 + */ +@Service +public class TOnlinePaymentSepAccServiceImpl extends ServiceImpl implements ITOnlinePaymentSepAccService { + + @Autowired + private ITOnlinePaymentSepAccDService itOnlinePaymentSepAccDService; + @Override + public void saveOrUpdateReq(TOnlinePaymentSepAcc tOnlinePaymentSepAcc) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(TOnlinePaymentSepAcc::getPkPayment, tOnlinePaymentSepAcc.getPkPayment()); + TOnlinePaymentSepAcc source = this.getOne(wrapper, false); + if(ObjectUtil.isNotEmpty(source)){ + tOnlinePaymentSepAcc.setPkId(source.getPkId()); + }else{ + tOnlinePaymentSepAcc.setCreationTime(DateUtils.currentDateTime()); + tOnlinePaymentSepAcc.setPkCreator(SecurityUtils.getUserId()); + } + tOnlinePaymentSepAcc.setDelFlag(EDelFlag.UN_DELETE.getValue()); + tOnlinePaymentSepAcc.setModifiedTime(DateUtils.currentDateTime()); + tOnlinePaymentSepAcc.setPkModified(SecurityUtils.getUserId()); + this.saveOrUpdate(tOnlinePaymentSepAcc); + LambdaQueryWrapper wrapperD = new LambdaQueryWrapper<>(); + wrapperD.eq(TOnlinePaymentSepAccD::getPkSepAcc, tOnlinePaymentSepAcc.getPkId()); + itOnlinePaymentSepAccDService.remove(wrapperD); + if(CollUtil.isNotEmpty(tOnlinePaymentSepAcc.getSepAccDList())){ + for (TOnlinePaymentSepAccD tOnlinePaymentSepAccD : tOnlinePaymentSepAcc.getSepAccDList()) { + tOnlinePaymentSepAccD.setPkSepAcc(tOnlinePaymentSepAcc.getPkId()); + tOnlinePaymentSepAccD.setCreationTime(DateUtils.currentDateTime()); + tOnlinePaymentSepAccD.setPkCreator(SecurityUtils.getUserId()); + tOnlinePaymentSepAccD.setModifiedTime(DateUtils.currentDateTime()); + tOnlinePaymentSepAccD.setPkModified(SecurityUtils.getUserId()); + tOnlinePaymentSepAccD.setDelFlag(EDelFlag.UN_DELETE.getValue()); + } + itOnlinePaymentSepAccDService.saveBatch(tOnlinePaymentSepAcc.getSepAccDList()); + } + + } + + @Override + public TOnlinePaymentSepAcc getSepAccByOrderCode(String code) { + return baseMapper.getSepAccByOrderCode(code); + } +} diff --git a/bd-third/src/main/java/com/hzs/third/pay/service/impl/TOnlinePaymentServiceImpl.java b/bd-third/src/main/java/com/hzs/third/pay/service/impl/TOnlinePaymentServiceImpl.java index 49649f82..0b4f9898 100644 --- a/bd-third/src/main/java/com/hzs/third/pay/service/impl/TOnlinePaymentServiceImpl.java +++ b/bd-third/src/main/java/com/hzs/third/pay/service/impl/TOnlinePaymentServiceImpl.java @@ -1,5 +1,6 @@ package com.hzs.third.pay.service.impl; +import cn.hutool.core.util.ObjectUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.hzs.common.domain.third.pay.TOnlinePayment; @@ -36,7 +37,9 @@ public class TOnlinePaymentServiceImpl extends ServiceImpl + select * from T_ONLINE_PAYMENT_SEP_ACC where pk_payment in (select pk_id from T_ONLINE_PAYMENT where BUSINESS_CODE = #{orderCode}) + + +