From 6381c3be2f51276cc3ec508671911c706551f8e7 Mon Sep 17 00:00:00 2001 From: dzc <2388224167@qq.com> Date: Fri, 6 Jun 2025 14:42:08 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E4=BD=8D=E7=BD=AE=E4=BB=98=E6=AC=BE?= =?UTF-8?q?=E7=A0=81=E6=94=AF=E4=BB=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/constant/order/HardPayTypeEnum.java | 11 +- .../api/dto/wechat/WechatMicropayDto.java | 28 ++ .../hard/api/dto/wechat/WechatQueryDto.java | 21 ++ .../hard/api/dto/wechat/WechatRefundDto.java | 21 ++ .../api/dto/wechat/WechatRefundQueryDto.java | 17 + .../wechat/WechantRefunCallbackSoureVo.java | 30 ++ .../api/vo/wechat/WechantRefunCallbackVo.java | 77 ++++ .../hard/api/vo/wechat/WechatMicropayVo.java | 43 +++ .../hard/api/vo/wechat/WechatQueryVo.java | 125 +++++++ .../api/vo/wechat/WechatRefundQueryVo.java | 102 ++++++ .../hard/api/vo/wechat/WechatRefundVo.java | 84 +++++ .../bsd/pjcloud/hard/config/WechatPay.java | 43 +++ .../wechant/WechantPayController.java | 40 +++ .../hard/service/HardOrderService.java | 7 +- .../hard/service/WechatPayService.java | 46 +++ .../impl/order/HardOrderServiceImpl.java | 62 +++- .../impl/wechat/WechatPayServiceImpl.java | 332 ++++++++++++++++++ .../utils/constant/WechantPayConstant.java | 63 ++++ .../src/main/resources/application.yml | 8 + .../pjcloud-thirdparty-biz/pom.xml | 6 + .../wechant/WechantPayController.java | 40 +++ .../com/bsd/pjcloud/wallet/WechatPayTest.java | 68 ++++ 22 files changed, 1257 insertions(+), 17 deletions(-) create mode 100644 pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/wechat/WechatMicropayDto.java create mode 100644 pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/wechat/WechatQueryDto.java create mode 100644 pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/wechat/WechatRefundDto.java create mode 100644 pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/wechat/WechatRefundQueryDto.java create mode 100644 pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/wechat/WechantRefunCallbackSoureVo.java create mode 100644 pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/wechat/WechantRefunCallbackVo.java create mode 100644 pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/wechat/WechatMicropayVo.java create mode 100644 pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/wechat/WechatQueryVo.java create mode 100644 pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/wechat/WechatRefundQueryVo.java create mode 100644 pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/wechat/WechatRefundVo.java create mode 100644 pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/config/WechatPay.java create mode 100644 pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/wechant/WechantPayController.java create mode 100644 pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/WechatPayService.java create mode 100644 pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/wechat/WechatPayServiceImpl.java create mode 100644 pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/utils/constant/WechantPayConstant.java create mode 100644 pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/controller/wechant/WechantPayController.java create mode 100644 pjcloud-wallet/pjcloud-wallet-biz/src/test/java/com/bsd/pjcloud/wallet/WechatPayTest.java diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/constant/order/HardPayTypeEnum.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/constant/order/HardPayTypeEnum.java index 1d054be0e..63bbb45b3 100644 --- a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/constant/order/HardPayTypeEnum.java +++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/constant/order/HardPayTypeEnum.java @@ -24,6 +24,8 @@ public enum HardPayTypeEnum { RESERVE_QR_CODE("7", "线上二维码支付"), CASH("8", "现金支付"), HARD_DOOR_FREE_PAY("9", "智能柜免密支付"), + WECHANT_SELF_QR_CODE_PAY("10", "微信付款码支付"), + ALI_SELF_QR_CODE_PAY("11", "支付宝付款码支付"), ; private final String type; private final String description; @@ -36,7 +38,14 @@ public enum HardPayTypeEnum { CASH ); - public static final List isNotPay = List.of( + public static final List isNotWalletPay = List.of( + STRIDE_DEPT_QR_CODE, + FOREIGN_TICKET_QR_CODE, + WECHANT_SELF_QR_CODE_PAY, + ALI_SELF_QR_CODE_PAY + ); + + public static final List isNeedCancel = List.of( STRIDE_DEPT_QR_CODE, FOREIGN_TICKET_QR_CODE ); diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/wechat/WechatMicropayDto.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/wechat/WechatMicropayDto.java new file mode 100644 index 000000000..79b1b40d4 --- /dev/null +++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/wechat/WechatMicropayDto.java @@ -0,0 +1,28 @@ +package com.bsd.pjcloud.hard.api.dto.wechat; + +import lombok.Data; + +@Data +public class WechatMicropayDto { + + /** + * 商户id + */ + private Long merId; + + /** + * 订单id + */ + private Long orderId; + + /** + * 支付金额(分) + */ + private Long price; + + /** + * 微信付款码code + */ + private String wechatQrCode; + +} diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/wechat/WechatQueryDto.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/wechat/WechatQueryDto.java new file mode 100644 index 000000000..c3678e794 --- /dev/null +++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/wechat/WechatQueryDto.java @@ -0,0 +1,21 @@ +package com.bsd.pjcloud.hard.api.dto.wechat; + +import lombok.Data; + +/** + * 查询订单入参 + */ +@Data +public class WechatQueryDto { + + /** + * 微信订单号 + */ + private String wechantOrderId; + + /** + * 商户订单号 + */ + private String orderId; + +} diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/wechat/WechatRefundDto.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/wechat/WechatRefundDto.java new file mode 100644 index 000000000..b3e67c85c --- /dev/null +++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/wechat/WechatRefundDto.java @@ -0,0 +1,21 @@ +package com.bsd.pjcloud.hard.api.dto.wechat; + +import lombok.Data; + +/** + * 退款入参 + */ +@Data +public class WechatRefundDto { + + private String orderId; + + private String wechantOrderId; + + private String refundId; + + private Long orderPrice; + + private Long refundPrice; + +} diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/wechat/WechatRefundQueryDto.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/wechat/WechatRefundQueryDto.java new file mode 100644 index 000000000..b01d99737 --- /dev/null +++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/wechat/WechatRefundQueryDto.java @@ -0,0 +1,17 @@ +package com.bsd.pjcloud.hard.api.dto.wechat; + +import lombok.Data; + +/** + * 微信退款查询入参 + */ +@Data +public class WechatRefundQueryDto { + + /** + * 退款id + */ + private String refundId; + + +} diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/wechat/WechantRefunCallbackSoureVo.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/wechat/WechantRefunCallbackSoureVo.java new file mode 100644 index 000000000..8f0905354 --- /dev/null +++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/wechat/WechantRefunCallbackSoureVo.java @@ -0,0 +1,30 @@ +package com.bsd.pjcloud.hard.api.vo.wechat; + +import lombok.Data; + +/** + * 微信退款回调加密的返回参数 + */ +@Data +public class WechantRefunCallbackSoureVo { + + /** + * 公众账号ID + */ + private String appid; + + /** + * 退款的商户号 + */ + private String mch_id; + + /** + * 随机字符串 + */ + private String nonce_str; + + /** + * 加密信息 + */ + private String req_info; +} diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/wechat/WechantRefunCallbackVo.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/wechat/WechantRefunCallbackVo.java new file mode 100644 index 000000000..bd00563b5 --- /dev/null +++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/wechat/WechantRefunCallbackVo.java @@ -0,0 +1,77 @@ +package com.bsd.pjcloud.hard.api.vo.wechat; + +import cn.hutool.core.util.ObjectUtil; +import lombok.Data; + +/** + * 微信退款回调返回参数 + */ +@Data +public class WechantRefunCallbackVo { + + public static final String SUCCESS_CODE = "SUCCESS"; + + /** + * 微信订单号 + */ + private String transaction_id; + + /** + * 商户订单号 + */ + private String out_trade_no; + + /** + * 微信退款单号 + */ + private String refund_id; + + /** + * 商户退款单号 + */ + private String out_refund_no; + + /** + * 订单金额 + */ + private String total_fee; + + /** + * 应结订单金额 + */ + private String settlement_total_fee; + + /** + * 申请退款金额 + */ + private String refund_fee; + + /** + * 退款金额 + */ + private String settlement_refund_fee; + + /** + * 退款状态 + */ + private String refund_status; + + public boolean isAllSuccess() { + return ObjectUtil.equals(refund_status, SUCCESS_CODE); + } + + public static String wechantError() { + return "" + + "" + + "" + + ""; + } + + public static String wechantSuccess() { + return "" + + "" + + "" + + ""; + } + +} diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/wechat/WechatMicropayVo.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/wechat/WechatMicropayVo.java new file mode 100644 index 000000000..0c3878e6a --- /dev/null +++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/wechat/WechatMicropayVo.java @@ -0,0 +1,43 @@ +package com.bsd.pjcloud.hard.api.vo.wechat; + +import cn.hutool.core.util.ObjectUtil; +import lombok.Data; + +@Data +public class WechatMicropayVo { + + public static final String SUCCESS = "SUCCESS"; + + private String return_code; + + private String return_msg; + + private String appid; + + private String mch_id; + + private String device_info; + + private String nonce_str; + + private String sign; + + private String result_code; + + private String err_code; + + private String err_code_des; + + public boolean isHttpSucess() { + return ObjectUtil.equals(WechatMicropayVo.SUCCESS, return_code); + } + + public boolean isPaySucess() { + return ObjectUtil.equals(WechatMicropayVo.SUCCESS, result_code); + } + + public boolean isAllSuccess(){ + return isHttpSucess() && isPaySucess(); + } + +} diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/wechat/WechatQueryVo.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/wechat/WechatQueryVo.java new file mode 100644 index 000000000..c4cd034b6 --- /dev/null +++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/wechat/WechatQueryVo.java @@ -0,0 +1,125 @@ +package com.bsd.pjcloud.hard.api.vo.wechat; + +import cn.hutool.core.util.ObjectUtil; +import lombok.Data; + +/** + * 查询订单返回 + */ +@Data +public class WechatQueryVo { + public static final String SUCCESS = "SUCCESS"; + + private String return_code; + + private String return_msg; + + private String appid; + + private String mch_id; + + private String nonce_str; + + private String sign; + + private String result_code; + + private String err_code; + + private String err_code_des; + + private String device_info; + + /** + * 用户标识 + */ + private String openid; + + /** + * 是否关注公众账号 + */ + private String is_subscribe; + + /** + * 交易类型 + */ + private String trade_type; + + /** + * 交易状态 + */ + private String trade_state; + + /** + * 付款银行 + */ + private String bank_type; + + /** + * 标价金额 + */ + private String total_fee; + + /** + * 应结订单金额 + */ + private String settlement_total_fee; + + /** + * 标价币种 + */ + private String fee_type; + + /** + * 现金支付金额 + */ + private String cash_fee; + + /** + * 现金支付币种 + */ + private String cash_fee_type; + + /** + * 代金券金额 + */ + private String coupon_fee; + + /** + * 代金券使用数量 + */ + private String coupon_count; + + /** + * 微信支付订单号 + */ + private String transaction_id; + + /** + * 商户订单号 + */ + private String out_trade_no; + + /** + * 附加数据 + */ + private String attach; + + /** + * 支付完成时间 + */ + private String time_end; + + /** + * 交易状态描述 + */ + private String trade_state_desc; + + public boolean isHttpSucess() { + return ObjectUtil.equals(WechatMicropayVo.SUCCESS, return_code); + } + + public boolean isPaySucess() { + return ObjectUtil.equals(WechatMicropayVo.SUCCESS, result_code); + } +} diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/wechat/WechatRefundQueryVo.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/wechat/WechatRefundQueryVo.java new file mode 100644 index 000000000..3479d177e --- /dev/null +++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/wechat/WechatRefundQueryVo.java @@ -0,0 +1,102 @@ +package com.bsd.pjcloud.hard.api.vo.wechat; + +import cn.hutool.core.util.ObjectUtil; +import lombok.Data; + +/** + * 退款返回 + */ +@Data +public class WechatRefundQueryVo { + + public static final String SUCCESS = "SUCCESS"; + + /** + * 业务结果 + */ + private String result_code; + + /** + * 错误码 + */ + private String err_code; + + /** + * 错误描述 + */ + private String err_code_des; + + /** + * 公众账号ID + */ + private String appid; + + /** + * 商户号 + */ + private String mch_id; + + /** + * 随机字符串 + */ + private String nonce_str; + + /** + * 签名 + */ + private String sign; + + /** + * 订单总退款次数 + */ + private Long total_refund_count; + + /** + * 微信订单号 + */ + private String transaction_id; + + /** + * 商户订单号 + */ + private String out_trade_no; + + /** + * 订单金额 + */ + private Long total_fee; + + /** + * 应结订单金额 + */ + private Long settlement_total_fee; + + /** + * 货币种类 + */ + private String fee_type; + + /** + * 现金支付金额 + */ + private Long cash_fee; + + /** + * 退款笔数 + */ + private Long refund_count; + + /** + * 退款总金额 + */ + private Long refund_fee; + + public boolean isHttpSucess() { + return ObjectUtil.equals(WechatMicropayVo.SUCCESS, err_code); + } + + public boolean isPaySucess() { + return ObjectUtil.equals(WechatMicropayVo.SUCCESS, result_code); + } + +} diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/wechat/WechatRefundVo.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/wechat/WechatRefundVo.java new file mode 100644 index 000000000..4355502e7 --- /dev/null +++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/wechat/WechatRefundVo.java @@ -0,0 +1,84 @@ +package com.bsd.pjcloud.hard.api.vo.wechat; + +import cn.hutool.core.util.ObjectUtil; +import lombok.Data; + +/** + * 退款返回 + */ +@Data +public class WechatRefundVo { + + public static final String SUCCESS = "SUCCESS"; + + private String return_code; + + private String return_msg; + + private String result_code; + + private String err_code; + + private String err_code_des; + + private String appid; + + private String mch_id; + + private String nonce_str; + + + private String sign; + + /** + * 微信支付订单号 + */ + private String transaction_id; + + /** + * 商户订单号 + */ + private String out_trade_no; + + /** + * 商户退款单号 + */ + private String out_refund_no; + + /** + * 微信退款单号 + */ + private String refund_id; + + /** + * 退款金额 + */ + private String refund_fee; + + /** + * 应结退款金额 + */ + private String settlement_refund_fee; + + /** + * 标价金额 + */ + private String total_fee; + + /** + * 应结订单金额 + */ + private String settlement_total_fee; + + public boolean isHttpSucess() { + return ObjectUtil.equals(WechatMicropayVo.SUCCESS, return_code); + } + + public boolean isPaySucess() { + return ObjectUtil.equals(WechatMicropayVo.SUCCESS, result_code); + } + + public boolean isAllSuccess() { + return isHttpSucess() && isPaySucess(); + } +} diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/config/WechatPay.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/config/WechatPay.java new file mode 100644 index 000000000..b201ed6bd --- /dev/null +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/config/WechatPay.java @@ -0,0 +1,43 @@ +package com.bsd.pjcloud.hard.config; + + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Component +@ConfigurationProperties(prefix = "wechat-pay") +@Data +public class WechatPay { + + /** + * 基础路径 + */ + private String baseUrl; + + /** + * 微信公共平台 appId + */ + private String appId; + + /** + * 商户号 + */ + private String merchantId; + + /** + * 商户证书序列path + */ + private String merchantSerialPath; + + /** + * 商户APIV2密钥 + */ + private String apiV2Key; + + /** + * 退款回调地址 + */ + private String refundCallbackUrl; + +} diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/wechant/WechantPayController.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/wechant/WechantPayController.java new file mode 100644 index 000000000..c96891b49 --- /dev/null +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/wechant/WechantPayController.java @@ -0,0 +1,40 @@ +package com.bsd.pjcloud.hard.controller.wechant; + +import com.bsd.pjcloud.common.security.annotation.Inner; +import com.bsd.pjcloud.hard.service.WechatPayService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/hard-wechant-pay") +@Tag(description = "hard-wechant", name = "微信相关") +@SecurityRequirement(name = HttpHeaders.AUTHORIZATION) +@Validated +public class WechantPayController { + + private final WechatPayService wechatPayService; + + /* ************************************* start callback start ************************************* */ + + @Inner(value = false) + @PostMapping("/callback/refund") + @Operation(summary = "微信退款回调") + public String refundCallback(HttpEntity httpEntity, @RequestHeader Map headers) { + return wechatPayService.refundCallback(httpEntity, headers); + } + + /* ************************************* end callback end ************************************* */ + +} diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/HardOrderService.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/HardOrderService.java index ec193b7b2..0cc4b93f6 100644 --- a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/HardOrderService.java +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/HardOrderService.java @@ -63,10 +63,15 @@ public interface HardOrderService extends IService { Long cancelHardDoorOrder(Long orderId); /** - * 快速扣款,离线订单使用 + * 快速扣款 */ void fastReduceBalance(HardOrderEntity order); + /** + * 订单成功 + */ + void orderSuccess(HardOrderEntity order, Boolean isPay); + /** * 计算额外金额 */ diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/WechatPayService.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/WechatPayService.java new file mode 100644 index 000000000..e939494e4 --- /dev/null +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/WechatPayService.java @@ -0,0 +1,46 @@ +package com.bsd.pjcloud.hard.service; + + +import com.bsd.pjcloud.hard.api.dto.wechat.WechatMicropayDto; +import com.bsd.pjcloud.hard.api.dto.wechat.WechatQueryDto; +import com.bsd.pjcloud.hard.api.dto.wechat.WechatRefundDto; +import com.bsd.pjcloud.hard.api.dto.wechat.WechatRefundQueryDto; +import com.bsd.pjcloud.hard.api.vo.wechat.WechatMicropayVo; +import com.bsd.pjcloud.hard.api.vo.wechat.WechatQueryVo; +import com.bsd.pjcloud.hard.api.vo.wechat.WechatRefundQueryVo; +import com.bsd.pjcloud.hard.api.vo.wechat.WechatRefundVo; +import org.springframework.http.HttpEntity; +import org.springframework.web.bind.annotation.RequestHeader; + +import java.util.Map; + +/** + * 微信支付 + */ +public interface WechatPayService { + + /** + * 付款码支付 + */ + WechatMicropayVo micropay(WechatMicropayDto dto); + + /** + * 查询订单 + */ + WechatQueryVo query(WechatQueryDto dto); + + /** + * 申请退款 + */ + WechatRefundVo refund(WechatRefundDto dto); + + /** + * 查询退款 + */ + WechatRefundQueryVo refundQuery(WechatRefundQueryDto dto); + + /** + * 退款回调 + */ + String refundCallback(HttpEntity httpEntity, @RequestHeader Map headers); +} diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/order/HardOrderServiceImpl.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/order/HardOrderServiceImpl.java index 7b9becf98..5afcb52fa 100644 --- a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/order/HardOrderServiceImpl.java +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/order/HardOrderServiceImpl.java @@ -196,14 +196,18 @@ public class HardOrderServiceImpl extends ServiceImpl walletResult = remoteWalletService.getWallet(dto.getUserId(), WalletTypeEnum.EAT); @@ -237,7 +241,7 @@ public class HardOrderServiceImpl extends ServiceImpl params) { + String apiKey = wechatPay.getApiV2Key(); + Map filtered = new TreeMap<>(); + for (Map.Entry entry : params.entrySet()) { + if (ObjectUtil.isEmpty(entry.getValue()) || ObjectUtil.equals("sign", entry.getKey())) { + continue; + } + filtered.put(entry.getKey(), String.valueOf(entry.getValue())); + } + StringBuilder sb = new StringBuilder(); + for (Map.Entry entry : filtered.entrySet()) { + sb.append(entry.getKey()).append("=").append(entry.getValue()).append("&"); + } + sb.append("key=").append(apiKey); + try { + String sign = DigestUtils.md5Hex(String.valueOf(sb)); + return sign.toUpperCase(); + } catch (Exception e) { + log.error("微信支付签名生成失败", e); + } + return ""; + } + + private WechantPayConstant.Api initApi(WechantPayConstant.Api api) { + api.setBaseUrl(wechatPay.getBaseUrl()); + return api; + } + + private String getNonceStr() { + int length = 32; + return RandomUtil.randomString(length); + } + + private SSLSocketFactory createSSLSocketFactory() throws Exception { + KeyStore keyStore = KeyStore.getInstance("PKCS12"); + FileInputStream certStream = new FileInputStream(wechatPay.getMerchantSerialPath()); + keyStore.load(certStream, wechatPay.getMerchantId().toCharArray()); + certStream.close(); + // 创建KeyManagerFactory + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + keyManagerFactory.init(keyStore, wechatPay.getMerchantId().toCharArray()); + // 创建TrustManagerFactory(信任所有证书) + TrustManager[] trustManagers = new TrustManager[]{ + new X509TrustManager() { + public java.security.cert.X509Certificate[] getAcceptedIssuers() { + return null; + } + + public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) { + } + + public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) { + } + } + }; + // 创建SSLContext + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(keyManagerFactory.getKeyManagers(), trustManagers, new SecureRandom()); + return sslContext.getSocketFactory(); + } + + /** + * 解密回调信息解密 + */ + private WechantRefunCallbackVo decrypt(String xml) { + try { + WechantRefunCallbackSoureVo vo = JSONUtil.toBean(JSONUtil.toJsonStr(XmlUtil.xmlToMap(xml)), WechantRefunCallbackSoureVo.class); + String reqInfo = vo.getReq_info(); + // 1 对加密串A做base64解码,得到加密串B + byte[] encryptedBytes = Base64.decode(reqInfo); + // 2 对商户key做md5,得到32位小写key + String md5Key = DigestUtils.md5Hex(wechatPay.getApiV2Key()).toLowerCase(); + // 3. AES-256-ECB 解密 + SecretKeySpec secretKey = new SecretKeySpec(md5Key.getBytes(StandardCharsets.UTF_8), "AES"); + Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); + cipher.init(Cipher.DECRYPT_MODE, secretKey); + byte[] decryptedBytes = cipher.doFinal(encryptedBytes); + String json = new String(decryptedBytes, StandardCharsets.UTF_8); + WechantRefunCallbackVo entity = JSONUtil.toBean(JSONUtil.toJsonStr(XmlUtil.xmlToMap(json)), WechantRefunCallbackVo.class); + log.debug("解密回调信息,json|{}", JSONUtil.toJsonStr(entity)); + return entity; + } catch (Exception e) { + e.printStackTrace(); + log.error("解密回调信息解密异常", e); + return null; + } + } + + /** + * 付款码支付 + */ + @Override + public WechatMicropayVo micropay(WechatMicropayDto dto) { + try { + Map params = new HashMap<>(); + // 公众账号ID + params.put("appid", wechatPay.getAppId()); + // 商户号 + params.put("mch_id", wechatPay.getMerchantId()); + // 设备号-终端设备号(商户自定义,如门店编号) + params.put("device_info", dto.getMerId()); + // 随机字符串 + params.put("nonce_str", getNonceStr()); + // 签名 + params.put("sign", ""); + // 签名类型 + params.put("sign_type", "MD5"); + // 商品描述 + params.put("body", "小铺故事超市-超市"); + // 商户单号 + params.put("out_trade_no", dto.getOrderId()); + // 订单金额(分) + params.put("total_fee", dto.getPrice()); + // 终端IP + params.put("spbill_create_ip", "220.191.229.131"); + // 付款码 + params.put("auth_code", dto.getWechatQrCode()); + // 生成签名 + params.put("sign", generateSign(params)); + WechantPayConstant.Api api = initApi(WechantPayConstant.APY); + String res = api.getHttpRequest().body(XmlUtil.mapToXmlStr(params)).execute().body(); + log.debug("付款码支付,json,xml|{}", res); + if (ObjectUtil.isEmpty(res)) { + log.error("付款码支付,res为空"); + return null; + } + WechatMicropayVo vo = JSONUtil.toBean(JSONUtil.toJsonStr(XmlUtil.xmlToMap(res)), WechatMicropayVo.class); + log.debug("付款码支付,json,res|{}", JSONUtil.toJsonStr(vo)); + if (!vo.isHttpSucess()) { + return null; + } + return vo; + } catch (Exception e) { + log.error("付款码支付,异常", e); + return null; + } + } + + /** + * 查询订单 + */ + @Override + public WechatQueryVo query(WechatQueryDto dto) { + try { + Map params = new HashMap<>(); + // 公众账号ID + params.put("appid", wechatPay.getAppId()); + // 商户号 + params.put("mch_id", wechatPay.getMerchantId()); + // 微信订单号 + params.put("transaction_id", dto.getWechantOrderId()); + // 商户订单号 + params.put("out_trade_no", dto.getOrderId()); + // 随机字符串 + params.put("nonce_str", getNonceStr()); + // 签名 + params.put("sign", ""); + // 签名类型 + params.put("sign_type", "MD5"); + // 生成签名 + params.put("sign", generateSign(params)); + WechantPayConstant.Api api = initApi(WechantPayConstant.QUERY); + String res = api.getHttpRequest().body(XmlUtil.mapToXmlStr(params)).execute().body(); + log.debug("查询订单,json,xml|{}", res); + if (ObjectUtil.isEmpty(res)) { + log.error("查询订单,res为空"); + return null; + } + WechatQueryVo vo = JSONUtil.toBean(JSONUtil.toJsonStr(XmlUtil.xmlToMap(res)), WechatQueryVo.class); + log.debug("查询订单,json,res|{}", JSONUtil.toJsonStr(vo)); + if (!vo.isHttpSucess()) { + return null; + } + return vo; + } catch (Exception e) { + log.error("查询订单,异常", e); + return null; + } + } + + /** + * 申请退款 + */ + @Override + public WechatRefundVo refund(WechatRefundDto dto) { + try { + Map params = new HashMap<>(); + // 公众账号ID + params.put("appid", wechatPay.getAppId()); + // 商户号 + params.put("mch_id", wechatPay.getMerchantId()); + // 随机字符串 + params.put("nonce_str", getNonceStr()); + // 签名 + params.put("sign", ""); + // 签名类型 + params.put("sign_type", "MD5"); + // 微信支付订单号 + params.put("transaction_id", dto.getWechantOrderId()); + // 商户订单号 + params.put("out_trade_no", dto.getOrderId()); + // 商户退款单号 + params.put("out_refund_no", dto.getRefundId()); + // 订单金额 + params.put("total_fee", dto.getOrderPrice()); + // 退款金额 + params.put("refund_fee", dto.getRefundPrice()); + // 退款结果通知url + params.put("notify_url", wechatPay.getRefundCallbackUrl()); + // 生成签名 + params.put("sign", generateSign(params)); + WechantPayConstant.Api api = initApi(WechantPayConstant.REFUND); + api.setSslSocketFactory(createSSLSocketFactory()); + String res = api.getHttpRequest().body(XmlUtil.mapToXmlStr(params)).execute().body(); + log.debug("申请退款,json,xml|{}", res); + if (ObjectUtil.isEmpty(res)) { + log.error("申请退款,res为空"); + return null; + } + WechatRefundVo vo = JSONUtil.toBean(JSONUtil.toJsonStr(XmlUtil.xmlToMap(res)), WechatRefundVo.class); + log.debug("申请退款,json,res|{}", JSONUtil.toJsonStr(vo)); + if (!vo.isHttpSucess()) { + return null; + } + return vo; + } catch (Exception e) { + log.error("申请退款,异常", e); + return null; + } + } + + /** + * 查询退款 + */ + @Override + public WechatRefundQueryVo refundQuery(WechatRefundQueryDto dto) { + try { + Map params = new HashMap<>(); + // 公众账号ID + params.put("appid", wechatPay.getAppId()); + // 商户号 + params.put("mch_id", wechatPay.getMerchantId()); + // 随机字符串 + params.put("nonce_str", getNonceStr()); + // 签名 + params.put("sign", ""); + // 签名类型 + params.put("sign_type", "MD5"); + // 签名类型 + params.put("out_refund_no", dto.getRefundId()); + // 生成签名 + params.put("sign", generateSign(params)); + WechantPayConstant.Api api = initApi(WechantPayConstant.REFUND_QUERY); + String res = api.getHttpRequest().body(XmlUtil.mapToXmlStr(params)).execute().body(); + log.debug("查询退款,json,xml|{}", res); + if (ObjectUtil.isEmpty(res)) { + log.error("查询退款,res为空"); + return null; + } + WechatRefundQueryVo vo = JSONUtil.toBean(JSONUtil.toJsonStr(XmlUtil.xmlToMap(res)), WechatRefundQueryVo.class); + log.debug("查询退款,json,res|{}", JSONUtil.toJsonStr(vo)); + if (!vo.isHttpSucess()) { + return null; + } + return vo; + } catch (Exception e) { + log.error("查询退款,异常", e); + return null; + } + } + + /** + * 退款回调 + */ + @Override + public String refundCallback(HttpEntity httpEntity, Map headers) { + String body = httpEntity.getBody(); + WechantRefunCallbackVo decrypt = decrypt(body); + if (ObjectUtil.isEmpty(decrypt)) { + // 告诉微信错误 + return WechantRefunCallbackVo.wechantError(); + } + + return WechantRefunCallbackVo.wechantError(); + } +} diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/utils/constant/WechantPayConstant.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/utils/constant/WechantPayConstant.java new file mode 100644 index 000000000..7b44a4638 --- /dev/null +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/utils/constant/WechantPayConstant.java @@ -0,0 +1,63 @@ +package com.bsd.pjcloud.hard.utils.constant; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.http.HttpRequest; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.http.HttpMethod; + +import javax.net.ssl.SSLSocketFactory; + +/** + * 微信支付api + */ +public class WechantPayConstant { + + public static final Api APY = Api.builder().summary("付款码支付").url("/pay/micropay").method(HttpMethod.POST).isNeedCertificate(false).build(); + + public static final Api QUERY = Api.builder().summary("查询订单").url("/pay/orderquery").method(HttpMethod.POST).isNeedCertificate(false).build(); + + public static final Api REVERSE = Api.builder().summary("撤销订单").url("/pay/reverse").method(HttpMethod.POST).isNeedCertificate(false).build(); + + public static final Api REFUND = Api.builder().summary("申请退款").url("/secapi/pay/refund").method(HttpMethod.POST).isNeedCertificate(true).build(); + + public static final Api REFUND_QUERY = Api.builder().summary("查询退款").url("/pay/refundquery").method(HttpMethod.POST).isNeedCertificate(false).build(); + + @Data + @Builder + @AllArgsConstructor + @NoArgsConstructor + static public class Api { + + private String summary; + + private String baseUrl; + + private String url; + + private String fullUrl; + + private HttpMethod method; + + private boolean isNeedCertificate = false; + + private SSLSocketFactory sslSocketFactory; + + public HttpRequest getHttpRequest() { + String fullUrl = baseUrl + url; + HttpRequest req = switch (method.name()) { + case "GET" -> HttpRequest.get(fullUrl); + case "POST" -> HttpRequest.post(fullUrl); + default -> null; + }; + if (ObjectUtil.isNotEmpty(req) && ObjectUtil.isNotEmpty(sslSocketFactory) && isNeedCertificate) { + req.setSSLSocketFactory(sslSocketFactory); + } + return req; + } + + } + +} diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/resources/application.yml b/pjcloud-hardware/pjcloud-hardware-biz/src/main/resources/application.yml index b0027050e..58e2aa349 100644 --- a/pjcloud-hardware/pjcloud-hardware-biz/src/main/resources/application.yml +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/resources/application.yml @@ -24,3 +24,11 @@ spring: # 第三方回调配置 # pay-callback: # lb-mch-id: 1919673632357584898 + +wechat-pay: + merchant-id: 1718803142 + api-v2-key: EDn3OfIZZFwyS9Obx73XgQ8NHt01piUa + app-id: wx4ec0210f4bf42f9f + base-url: https://api.mch.weixin.qq.com + refund-callback-url: https://rj4jv5rwqs.by.takin.cc/hard/hard-wechant-pay/callback/refund + merchant-serial-path: /Users/dengzhangchen/Desktop/backups/pay/cert/apiclient_cert.p12 diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/pom.xml b/pjcloud-thirdparty/pjcloud-thirdparty-biz/pom.xml index 4355abcd2..ebc2867aa 100644 --- a/pjcloud-thirdparty/pjcloud-thirdparty-biz/pom.xml +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/pom.xml @@ -131,6 +131,12 @@ com.bsd pjcloud-common-idempotent + + + com.github.wechatpay-apiv3 + wechatpay-java + 0.2.17 + com.bsd pjcloud-hardware-api diff --git a/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/controller/wechant/WechantPayController.java b/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/controller/wechant/WechantPayController.java new file mode 100644 index 000000000..120b9125f --- /dev/null +++ b/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/controller/wechant/WechantPayController.java @@ -0,0 +1,40 @@ +package com.bsd.pjcloud.wallet.controller.wechant; + +import com.bsd.pjcloud.common.core.util.R; +import com.bsd.pjcloud.common.security.annotation.Inner; +import com.bsd.pjcloud.wallet.api.dto.WalletActiveDto; +import com.bsd.pjcloud.wallet.service.WechatPayService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springdoc.core.annotations.ParameterObject; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/wallet-wechant-pay") +@Tag(description = "wallet-wechant", name = "微信相关") +@SecurityRequirement(name = HttpHeaders.AUTHORIZATION) +@Validated +public class WechantPayController { + + private final WechatPayService wechatPayService; + + /* ************************************* start callback start ************************************* */ + + @Inner(value = false) + @PostMapping("/callback/refund") + @Operation(summary = "微信退款回调") + public String refundCallback(HttpEntity httpEntity, @RequestHeader Map headers) { + return wechatPayService.refundCallback(httpEntity, headers); + } + + /* ************************************* end callback end ************************************* */ + +} diff --git a/pjcloud-wallet/pjcloud-wallet-biz/src/test/java/com/bsd/pjcloud/wallet/WechatPayTest.java b/pjcloud-wallet/pjcloud-wallet-biz/src/test/java/com/bsd/pjcloud/wallet/WechatPayTest.java new file mode 100644 index 000000000..c655e14d9 --- /dev/null +++ b/pjcloud-wallet/pjcloud-wallet-biz/src/test/java/com/bsd/pjcloud/wallet/WechatPayTest.java @@ -0,0 +1,68 @@ +package com.bsd.pjcloud.wallet; + +import cn.hutool.json.JSONUtil; +import com.bsd.pjcloud.thirdparty.dto.lb.LbH5LoginDto; +import com.bsd.pjcloud.thirdparty.vo.lb.LbH5LoginVo; +import com.bsd.pjcloud.wallet.api.dto.wechat.WechatMicropayDto; +import com.bsd.pjcloud.wallet.api.dto.wechat.WechatQueryDto; +import com.bsd.pjcloud.wallet.api.dto.wechat.WechatRefundDto; +import com.bsd.pjcloud.wallet.api.vo.wechat.WechatMicropayVo; +import com.bsd.pjcloud.wallet.api.vo.wechat.WechatQueryVo; +import com.bsd.pjcloud.wallet.api.vo.wechat.WechatRefundVo; +import com.bsd.pjcloud.wallet.service.WechatPayService; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +/** + * 微信支付配置 + */ +@Slf4j +@SpringBootTest(classes = WalletApp.class) +public class WechatPayTest { + + @Resource + private WechatPayService wechatPayService; + + @Test + public void micropay() throws Exception { + long currentTimeMillis = System.currentTimeMillis(); + + WechatMicropayDto dto = new WechatMicropayDto(); + dto.setMerId(currentTimeMillis); + dto.setOrderId(currentTimeMillis); + dto.setPrice(1L); + dto.setWechatQrCode("132796932599913148"); + + WechatMicropayVo micropay = wechatPayService.micropay(dto); + if (micropay.isAllSuccess()) { + log.info("支付成功json:{}", JSONUtil.toJsonStr(micropay)); + } + } + + @Test + public void query() throws Exception { + WechatQueryDto dto = new WechatQueryDto(); + dto.setWechantOrderId("4200002719202506059568204561"); + + WechatQueryVo micropay = wechatPayService.query(dto); + } + + @Test + public void rerfund() throws Exception { + String currentOrderId = "4200002648202506062041165177"; + + WechatRefundDto dto = new WechatRefundDto(); + dto.setWechantOrderId(currentOrderId); + dto.setRefundId("refund_" + currentOrderId); + dto.setOrderPrice(1L); + dto.setRefundPrice(1L); + + WechatRefundVo micropay = wechatPayService.refund(dto); + if (micropay.isAllSuccess()) { + log.info("退款成功json:{}", JSONUtil.toJsonStr(micropay)); + } + } + +}