mirror of
				https://github.com/YunaiV/ruoyi-vue-pro.git
				synced 2025-10-31 18:49:06 +08:00 
			
		
		
		
	mall + pay:
1、优化微信公众号 MP 支付的实现
This commit is contained in:
		| @ -35,14 +35,4 @@ public class PayOrderNotifyRespDTO { | |||||||
|      */ |      */ | ||||||
|     private LocalDateTime successTime; |     private LocalDateTime successTime; | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * TODO @jason 结合其他的渠道定义成枚举, |  | ||||||
|      * |  | ||||||
|      * alipay |  | ||||||
|      * TRADE_CLOSED,未付款交易超时关闭,或支付完成后全额退款。 |  | ||||||
|      * TRADE_SUCCESS, 交易支付成功 |  | ||||||
|      * TRADE_FINISHED 	交易结束,不可退款。 |  | ||||||
|      */ |  | ||||||
|     private String tradeStatus; |  | ||||||
|  |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -0,0 +1,26 @@ | |||||||
|  | package cn.iocoder.yudao.framework.pay.core.client.exception; | ||||||
|  |  | ||||||
|  | import lombok.AllArgsConstructor; | ||||||
|  | import lombok.Data; | ||||||
|  | import lombok.EqualsAndHashCode; | ||||||
|  | import lombok.NoArgsConstructor; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 业务逻辑异常 Exception | ||||||
|  |  */ | ||||||
|  | @Data | ||||||
|  | @EqualsAndHashCode(callSuper = true) | ||||||
|  | @NoArgsConstructor | ||||||
|  | @AllArgsConstructor | ||||||
|  | public class PayException extends RuntimeException { | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 第三方平台的错误码 | ||||||
|  |      */ | ||||||
|  |     private String code; | ||||||
|  |     /** | ||||||
|  |      * 第三方平台的错误提示 | ||||||
|  |      */ | ||||||
|  |     private String message; | ||||||
|  |  | ||||||
|  | } | ||||||
| @ -7,18 +7,18 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDT | |||||||
| import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO; | import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO; | import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedRespDTO; | import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedRespDTO; | ||||||
| import com.alipay.api.AlipayResponse;import lombok.extern.slf4j.Slf4j; | import com.alipay.api.AlipayResponse; | ||||||
|  | import lombok.extern.slf4j.Slf4j; | ||||||
|  |  | ||||||
| import javax.validation.Validation; | import javax.validation.Validation; | ||||||
| import java.time.LocalDateTime; | import java.time.LocalDateTime; | ||||||
|  |  | ||||||
| import static cn.hutool.core.date.DatePattern.NORM_DATETIME_FORMATTER; | import static cn.hutool.core.date.DatePattern.NORM_DATETIME_FORMATTER; | ||||||
| import static cn.hutool.core.date.DatePattern.NORM_DATETIME_MS_FORMATTER; |  | ||||||
| import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |  | ||||||
| import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception0; | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception0; | ||||||
| import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; | import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; | ||||||
| import static cn.iocoder.yudao.framework.pay.core.enums.PayFrameworkErrorCodeConstants.PAY_EXCEPTION; | import static cn.iocoder.yudao.framework.pay.core.enums.PayFrameworkErrorCodeConstants.PAY_EXCEPTION; | ||||||
|  |  | ||||||
|  | // TODO 芋艿:优化下,替换异常; | ||||||
| /** | /** | ||||||
|  * 支付客户端的抽象类,提供模板方法,减少子类的冗余代码 |  * 支付客户端的抽象类,提供模板方法,减少子类的冗余代码 | ||||||
|  * |  * | ||||||
|  | |||||||
| @ -7,8 +7,8 @@ import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory; | |||||||
| import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.*; | import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.*; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.impl.weixin.WXLitePayClient; | import cn.iocoder.yudao.framework.pay.core.client.impl.weixin.WXLitePayClient; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.impl.weixin.WXNativePayClient; | import cn.iocoder.yudao.framework.pay.core.client.impl.weixin.WXNativePayClient; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.impl.weixin.WXPayClientConfig; | import cn.iocoder.yudao.framework.pay.core.client.impl.weixin.WxPayClientConfig; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.impl.weixin.WXPubPayClient; | import cn.iocoder.yudao.framework.pay.core.client.impl.weixin.WxPubPayClient; | ||||||
| import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum; | import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum; | ||||||
| import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||||
|  |  | ||||||
| @ -60,10 +60,10 @@ public class PayClientFactoryImpl implements PayClientFactory { | |||||||
|         // 创建客户端 |         // 创建客户端 | ||||||
|         // TODO @芋艿 WX_LITE WX_APP 如果不添加在 项目启动的时候去初始化会报错无法启动。所以我手动加了两个,具体需要你来配 |         // TODO @芋艿 WX_LITE WX_APP 如果不添加在 项目启动的时候去初始化会报错无法启动。所以我手动加了两个,具体需要你来配 | ||||||
|         switch (channelEnum) { |         switch (channelEnum) { | ||||||
|             case WX_PUB: return (AbstractPayClient<Config>) new WXPubPayClient(channelId, (WXPayClientConfig) config); |             case WX_PUB: return (AbstractPayClient<Config>) new WxPubPayClient(channelId, (WxPayClientConfig) config); | ||||||
|             case WX_LITE: return (AbstractPayClient<Config>) new WXLitePayClient(channelId, (WXPayClientConfig) config); //微信小程序请求支付 |             case WX_LITE: return (AbstractPayClient<Config>) new WXLitePayClient(channelId, (WxPayClientConfig) config); //微信小程序请求支付 | ||||||
|             case WX_APP: return (AbstractPayClient<Config>) new WXPubPayClient(channelId, (WXPayClientConfig) config); |             case WX_APP: return (AbstractPayClient<Config>) new WxPubPayClient(channelId, (WxPayClientConfig) config); | ||||||
|             case WX_NATIVE: return (AbstractPayClient<Config>) new WXNativePayClient(channelId, (WXPayClientConfig) config); |             case WX_NATIVE: return (AbstractPayClient<Config>) new WXNativePayClient(channelId, (WxPayClientConfig) config); | ||||||
|             case ALIPAY_WAP: return (AbstractPayClient<Config>) new AlipayWapPayClient(channelId, (AlipayPayClientConfig) config); |             case ALIPAY_WAP: return (AbstractPayClient<Config>) new AlipayWapPayClient(channelId, (AlipayPayClientConfig) config); | ||||||
|             case ALIPAY_QR: return (AbstractPayClient<Config>) new AlipayQrPayClient(channelId, (AlipayPayClientConfig) config); |             case ALIPAY_QR: return (AbstractPayClient<Config>) new AlipayQrPayClient(channelId, (AlipayPayClientConfig) config); | ||||||
|             case ALIPAY_APP: return (AbstractPayClient<Config>) new AlipayAppPayClient(channelId, (AlipayPayClientConfig) config); |             case ALIPAY_APP: return (AbstractPayClient<Config>) new AlipayAppPayClient(channelId, (AlipayPayClientConfig) config); | ||||||
|  | |||||||
| @ -25,16 +25,16 @@ import java.util.Map; | |||||||
| import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; | import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * 支付宝抽象类, 实现支付宝统一的接口。如退款 |  * 支付宝抽象类,实现支付宝统一的接口、以及部分实现(退款) | ||||||
|  * |  * | ||||||
|  * @author jason |  * @author jason | ||||||
|  */ |  */ | ||||||
| @Slf4j | @Slf4j | ||||||
| public abstract class AbstractAlipayClient extends AbstractPayClient<AlipayPayClientConfig> { | public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPayClientConfig> { | ||||||
| 
 | 
 | ||||||
|     protected DefaultAlipayClient client; |     protected DefaultAlipayClient client; | ||||||
| 
 | 
 | ||||||
|     public AbstractAlipayClient(Long channelId, String channelCode, AlipayPayClientConfig config) { |     public AbstractAlipayPayClient(Long channelId, String channelCode, AlipayPayClientConfig config) { | ||||||
|         super(channelId, channelCode, config); |         super(channelId, channelCode, config); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -106,9 +106,11 @@ public abstract class AbstractAlipayClient extends AbstractPayClient<AlipayPayCl | |||||||
|                     .build(); |                     .build(); | ||||||
|         } |         } | ||||||
|         // 2.2 支付的情况 |         // 2.2 支付的情况 | ||||||
|         return PayOrderNotifyRespDTO.builder().orderExtensionNo(bodyObj.get("out_trade_no")) |         return PayOrderNotifyRespDTO.builder() | ||||||
|                 .channelOrderNo(bodyObj.get("trade_no")).channelUserId(bodyObj.get("seller_id")) |                 .orderExtensionNo(bodyObj.get("out_trade_no")) | ||||||
|                 .tradeStatus(bodyObj.get("trade_status")).successTime(parseTime(params.get("notify_time"))) |                 .channelOrderNo(bodyObj.get("trade_no")) | ||||||
|  |                 .channelUserId(bodyObj.get("seller_id")) | ||||||
|  |                 .successTime(parseTime(params.get("notify_time"))) | ||||||
|                 .build(); |                 .build(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -20,7 +20,7 @@ import lombok.extern.slf4j.Slf4j; | |||||||
|  * @author 芋道源码 |  * @author 芋道源码 | ||||||
|  */ |  */ | ||||||
| @Slf4j | @Slf4j | ||||||
| public class AlipayAppPayClient extends AbstractAlipayClient { | public class AlipayAppPayClient extends AbstractAlipayPayClient { | ||||||
|  |  | ||||||
|     public AlipayAppPayClient(Long channelId, AlipayPayClientConfig config) { |     public AlipayAppPayClient(Long channelId, AlipayPayClientConfig config) { | ||||||
|         super(channelId, PayChannelEnum.ALIPAY_APP.getCode(), config); |         super(channelId, PayChannelEnum.ALIPAY_APP.getCode(), config); | ||||||
|  | |||||||
| @ -24,7 +24,7 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU | |||||||
|  * @author 芋道源码 |  * @author 芋道源码 | ||||||
|  */ |  */ | ||||||
| @Slf4j | @Slf4j | ||||||
| public class AlipayBarPayClient extends AbstractAlipayClient { | public class AlipayBarPayClient extends AbstractAlipayPayClient { | ||||||
|  |  | ||||||
|     public AlipayBarPayClient(Long channelId, AlipayPayClientConfig config) { |     public AlipayBarPayClient(Long channelId, AlipayPayClientConfig config) { | ||||||
|         super(channelId, PayChannelEnum.ALIPAY_BAR.getCode(), config); |         super(channelId, PayChannelEnum.ALIPAY_BAR.getCode(), config); | ||||||
|  | |||||||
| @ -22,7 +22,7 @@ import java.util.Objects; | |||||||
|  * @author XGD |  * @author XGD | ||||||
|  */ |  */ | ||||||
| @Slf4j | @Slf4j | ||||||
| public class AlipayPcPayClient extends AbstractAlipayClient { | public class AlipayPcPayClient extends AbstractAlipayPayClient { | ||||||
|  |  | ||||||
|     public AlipayPcPayClient(Long channelId, AlipayPayClientConfig config) { |     public AlipayPcPayClient(Long channelId, AlipayPayClientConfig config) { | ||||||
|         super(channelId, PayChannelEnum.ALIPAY_PC.getCode(), config); |         super(channelId, PayChannelEnum.ALIPAY_PC.getCode(), config); | ||||||
|  | |||||||
| @ -18,7 +18,7 @@ import lombok.extern.slf4j.Slf4j; | |||||||
|  * @author 芋道源码 |  * @author 芋道源码 | ||||||
|  */ |  */ | ||||||
| @Slf4j | @Slf4j | ||||||
| public class AlipayQrPayClient extends AbstractAlipayClient { | public class AlipayQrPayClient extends AbstractAlipayPayClient { | ||||||
|  |  | ||||||
|     public AlipayQrPayClient(Long channelId, AlipayPayClientConfig config) { |     public AlipayQrPayClient(Long channelId, AlipayPayClientConfig config) { | ||||||
|         super(channelId, PayChannelEnum.ALIPAY_QR.getCode(), config); |         super(channelId, PayChannelEnum.ALIPAY_QR.getCode(), config); | ||||||
|  | |||||||
| @ -19,7 +19,7 @@ import lombok.extern.slf4j.Slf4j; | |||||||
|  * @author 芋道源码 |  * @author 芋道源码 | ||||||
|  */ |  */ | ||||||
| @Slf4j | @Slf4j | ||||||
| public class AlipayWapPayClient extends AbstractAlipayClient { | public class AlipayWapPayClient extends AbstractAlipayPayClient { | ||||||
|  |  | ||||||
|     public AlipayWapPayClient(Long channelId, AlipayPayClientConfig config) { |     public AlipayWapPayClient(Long channelId, AlipayPayClientConfig config) { | ||||||
|         super(channelId, PayChannelEnum.ALIPAY_WAP.getCode(), config); |         super(channelId, PayChannelEnum.ALIPAY_WAP.getCode(), config); | ||||||
|  | |||||||
| @ -0,0 +1,183 @@ | |||||||
|  | package cn.iocoder.yudao.framework.pay.core.client.impl.weixin; | ||||||
|  |  | ||||||
|  | import cn.hutool.core.bean.BeanUtil; | ||||||
|  | import cn.hutool.core.date.LocalDateTimeUtil; | ||||||
|  | import cn.hutool.core.date.TemporalAccessorUtil; | ||||||
|  | import cn.hutool.core.lang.Assert; | ||||||
|  | import cn.hutool.core.map.MapUtil; | ||||||
|  | import cn.hutool.core.util.StrUtil; | ||||||
|  | import cn.iocoder.yudao.framework.common.util.io.FileUtils; | ||||||
|  | import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; | ||||||
|  | import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyReqDTO; | ||||||
|  | import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayOrderNotifyRespDTO; | ||||||
|  | import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; | ||||||
|  | import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO; | ||||||
|  | import cn.iocoder.yudao.framework.pay.core.client.exception.PayException; | ||||||
|  | import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient; | ||||||
|  | import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult; | ||||||
|  | import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyV3Result; | ||||||
|  | import com.github.binarywang.wxpay.config.WxPayConfig; | ||||||
|  | import com.github.binarywang.wxpay.exception.WxPayException; | ||||||
|  | import com.github.binarywang.wxpay.service.WxPayService; | ||||||
|  | import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl; | ||||||
|  | import lombok.extern.slf4j.Slf4j; | ||||||
|  |  | ||||||
|  | import java.time.LocalDateTime; | ||||||
|  | import java.time.ZoneId; | ||||||
|  | import java.util.Objects; | ||||||
|  |  | ||||||
|  | import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 微信支付抽象类,实现微信统一的接口、以及部分实现(退款) | ||||||
|  |  * | ||||||
|  |  * @author 遇到源码 | ||||||
|  |  */ | ||||||
|  | @Slf4j | ||||||
|  | public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientConfig> { | ||||||
|  |  | ||||||
|  |     protected WxPayService client; | ||||||
|  |  | ||||||
|  |     public AbstractWxPayClient(Long channelId, String channelCode, WxPayClientConfig config) { | ||||||
|  |         super(channelId, channelCode, config); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 初始化 client 客户端 | ||||||
|  |      * | ||||||
|  |      * @param tradeType 交易类型 | ||||||
|  |      */ | ||||||
|  |     protected void doInit(String tradeType) { | ||||||
|  |         // 创建 config 配置 | ||||||
|  |         WxPayConfig payConfig = new WxPayConfig(); | ||||||
|  |         BeanUtil.copyProperties(config, payConfig, "keyContent"); | ||||||
|  |         payConfig.setTradeType(tradeType); | ||||||
|  |         if (StrUtil.isNotEmpty(config.getPrivateKeyContent())) { | ||||||
|  |             // weixin-pay-java 存在 BUG,无法直接设置内容,所以创建临时文件来解决 | ||||||
|  |             payConfig.setPrivateKeyPath(FileUtils.createTempFile(config.getPrivateKeyContent()).getPath()); | ||||||
|  |         } | ||||||
|  |         if (StrUtil.isNotEmpty(config.getPrivateCertContent())) { | ||||||
|  |             // weixin-pay-java 存在 BUG,无法直接设置内容,所以创建临时文件来解决 | ||||||
|  |             payConfig.setPrivateCertPath(FileUtils.createTempFile(config.getPrivateCertContent()).getPath()); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // 创建 client 客户端 | ||||||
|  |         client = new WxPayServiceImpl(); | ||||||
|  |         client.setConfig(payConfig); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     protected PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) | ||||||
|  |             throws Throwable { | ||||||
|  |         try { | ||||||
|  |             switch (config.getApiVersion()) { | ||||||
|  |                 case WxPayClientConfig.API_VERSION_V2: | ||||||
|  |                     return doUnifiedOrderV2(reqDTO); | ||||||
|  |                 case WxPayClientConfig.API_VERSION_V3: | ||||||
|  |                     return doUnifiedOrderV3(reqDTO); | ||||||
|  |                 default: | ||||||
|  |                     throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion())); | ||||||
|  |             } | ||||||
|  |         } catch (WxPayException e) { | ||||||
|  |             log.error("[doUnifiedOrder][request({}) 发起支付失败]", toJsonString(reqDTO), e); | ||||||
|  |             throw buildPayException(e); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 【V2】调用支付渠道,统一下单 | ||||||
|  |      * | ||||||
|  |      * @param reqDTO 下单信息 | ||||||
|  |      * @return 各支付渠道的返回结果 | ||||||
|  |      */ | ||||||
|  |     protected abstract PayOrderUnifiedRespDTO doUnifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) | ||||||
|  |             throws WxPayException; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 【V3】调用支付渠道,统一下单 | ||||||
|  |      * | ||||||
|  |      * @param reqDTO 下单信息 | ||||||
|  |      * @return 各支付渠道的返回结果 | ||||||
|  |      */ | ||||||
|  |     protected abstract PayOrderUnifiedRespDTO doUnifiedOrderV3(PayOrderUnifiedReqDTO reqDTO) | ||||||
|  |             throws WxPayException; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public Object parseNotify(PayNotifyReqDTO rawNotify) { | ||||||
|  |         log.info("[parseNotify][微信支付回调 data 数据: {}]", rawNotify.getBody()); | ||||||
|  |         try { | ||||||
|  |             // 微信支付 v2 回调结果处理 | ||||||
|  |             switch (config.getApiVersion()) { | ||||||
|  |                 case WxPayClientConfig.API_VERSION_V2: | ||||||
|  |                     return parseOrderNotifyV2(rawNotify); | ||||||
|  |                 case WxPayClientConfig.API_VERSION_V3: | ||||||
|  |                     return parseOrderNotifyV3(rawNotify); | ||||||
|  |                 default: | ||||||
|  |                     throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion())); | ||||||
|  |             } | ||||||
|  |         } catch (WxPayException e) { | ||||||
|  |             log.error("[parseNotify][rawNotify({}) 解析失败]", toJsonString(rawNotify), e); | ||||||
|  |             throw buildPayException(e); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private PayOrderNotifyRespDTO parseOrderNotifyV2(PayNotifyReqDTO data) throws WxPayException { | ||||||
|  |         WxPayOrderNotifyResult notifyResult = client.parseOrderNotifyResult(data.getBody()); | ||||||
|  |         Assert.isTrue(Objects.equals(notifyResult.getResultCode(), "SUCCESS"), "支付结果非 SUCCESS"); | ||||||
|  |         // 转换结果 | ||||||
|  |         return PayOrderNotifyRespDTO | ||||||
|  |                 .builder() | ||||||
|  |                 .orderExtensionNo(notifyResult.getOutTradeNo()) | ||||||
|  |                 .channelOrderNo(notifyResult.getTransactionId()) | ||||||
|  |                 .channelUserId(notifyResult.getOpenid()) | ||||||
|  |                 .successTime(parseDateV2(notifyResult.getTimeEnd())) | ||||||
|  |                 .build(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private PayOrderNotifyRespDTO parseOrderNotifyV3(PayNotifyReqDTO data) throws WxPayException { | ||||||
|  |         WxPayOrderNotifyV3Result notifyResult = client.parseOrderNotifyV3Result(data.getBody(), null); | ||||||
|  |         WxPayOrderNotifyV3Result.DecryptNotifyResult result = notifyResult.getResult(); | ||||||
|  |         // 转换结果 | ||||||
|  |         Assert.isTrue(Objects.equals(notifyResult.getResult().getTradeState(), "SUCCESS"), | ||||||
|  |                 "支付结果非 SUCCESS"); | ||||||
|  |         return PayOrderNotifyRespDTO.builder() | ||||||
|  |                 .orderExtensionNo(result.getOutTradeNo()) | ||||||
|  |                 .channelOrderNo(result.getTradeState()) | ||||||
|  |                 .channelUserId(result.getPayer() != null ? result.getPayer().getOpenid() : null) | ||||||
|  |                 .successTime(parseDateV3(result.getSuccessTime())) | ||||||
|  |                 .build(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // ========== 各种工具方法 ========== | ||||||
|  |  | ||||||
|  |     static String getOpenid(PayOrderUnifiedReqDTO reqDTO) { | ||||||
|  |         String openid = MapUtil.getStr(reqDTO.getChannelExtras(), "openid"); | ||||||
|  |         if (StrUtil.isEmpty(openid)) { | ||||||
|  |             throw new IllegalArgumentException("支付请求的 openid 不能为空!"); | ||||||
|  |         } | ||||||
|  |         return openid; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     static PayException buildPayException(WxPayException e) { | ||||||
|  |         return new PayException(ObjectUtils.defaultIfNull(e.getErrCode(), e.getReturnCode()), | ||||||
|  |                 ObjectUtils.defaultIfNull(e.getErrCodeDes(), e.getCustomErrorMsg())); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     static String formatDateV2(LocalDateTime time) { | ||||||
|  |         return TemporalAccessorUtil.format(time.atZone(ZoneId.systemDefault()), "yyyyMMddHHmmss"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     static LocalDateTime parseDateV2(String time) { | ||||||
|  |         return LocalDateTimeUtil.parse(time, "yyyyMMddHHmmss"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     static String formatDateV3(LocalDateTime time) { | ||||||
|  |         return TemporalAccessorUtil.format(time.atZone(ZoneId.systemDefault()), "yyyy-MM-dd'T'HH:mm:ssXXX"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     static LocalDateTime parseDateV3(String time) { | ||||||
|  |         return LocalDateTimeUtil.parse(time, "yyyy-MM-dd'T'HH:mm:ssXXX"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @ -39,11 +39,11 @@ import java.util.Objects; | |||||||
|  * @author zwy |  * @author zwy | ||||||
|  */ |  */ | ||||||
| @Slf4j | @Slf4j | ||||||
| public class WXLitePayClient extends AbstractPayClient<WXPayClientConfig> { | public class WXLitePayClient extends AbstractPayClient<WxPayClientConfig> { | ||||||
|  |  | ||||||
|     private WxPayService client; |     private WxPayService client; | ||||||
|  |  | ||||||
|     public WXLitePayClient(Long channelId, WXPayClientConfig config) { |     public WXLitePayClient(Long channelId, WxPayClientConfig config) { | ||||||
|         super(channelId, PayChannelEnum.WX_LITE.getCode(), config); |         super(channelId, PayChannelEnum.WX_LITE.getCode(), config); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @ -99,7 +99,7 @@ public class WXLitePayClient extends AbstractPayClient<WXPayClientConfig> { | |||||||
|         WxPayUnifiedOrderRequest request = WxPayUnifiedOrderRequest.newBuilder() |         WxPayUnifiedOrderRequest request = WxPayUnifiedOrderRequest.newBuilder() | ||||||
|                 .outTradeNo(reqDTO.getMerchantOrderId()) |                 .outTradeNo(reqDTO.getMerchantOrderId()) | ||||||
|                 .body(reqDTO.getBody()) |                 .body(reqDTO.getBody()) | ||||||
|                 .totalFee(reqDTO.getAmount().intValue()) // 单位分 |                 .totalFee(reqDTO.getAmount()) // 单位分 | ||||||
|                 .timeExpire(DateUtil.format(reqDTO.getExpireTime(), "yyyyMMddHHmmss")) // v2的时间格式 |                 .timeExpire(DateUtil.format(reqDTO.getExpireTime(), "yyyyMMddHHmmss")) // v2的时间格式 | ||||||
|                 .spbillCreateIp(reqDTO.getUserIp()) |                 .spbillCreateIp(reqDTO.getUserIp()) | ||||||
|                 .openid(getOpenid(reqDTO)) |                 .openid(getOpenid(reqDTO)) | ||||||
| @ -117,9 +117,7 @@ public class WXLitePayClient extends AbstractPayClient<WXPayClientConfig> { | |||||||
|         request.setDescription(reqDTO.getBody()); |         request.setDescription(reqDTO.getBody()); | ||||||
|         request.setAmount(new WxPayUnifiedOrderV3Request |         request.setAmount(new WxPayUnifiedOrderV3Request | ||||||
|                 .Amount() |                 .Amount() | ||||||
|                 .setTotal(reqDTO |                 .setTotal(reqDTO.getAmount())); // 单位分 | ||||||
|                         .getAmount() |  | ||||||
|                         .intValue())); // 单位分 |  | ||||||
|         request.setTimeExpire(DateUtil.format(Date.from(reqDTO.getExpireTime().atZone(ZoneId.systemDefault()).toInstant()), "yyyy-MM-dd'T'HH:mm:ssXXX")); // v3的时间格式 |         request.setTimeExpire(DateUtil.format(Date.from(reqDTO.getExpireTime().atZone(ZoneId.systemDefault()).toInstant()), "yyyy-MM-dd'T'HH:mm:ssXXX")); // v3的时间格式 | ||||||
|         request.setPayer(new WxPayUnifiedOrderV3Request.Payer().setOpenid(getOpenid(reqDTO))); |         request.setPayer(new WxPayUnifiedOrderV3Request.Payer().setOpenid(getOpenid(reqDTO))); | ||||||
|         request.setSceneInfo(new WxPayUnifiedOrderV3Request.SceneInfo().setPayerClientIp(reqDTO.getUserIp())); |         request.setSceneInfo(new WxPayUnifiedOrderV3Request.SceneInfo().setPayerClientIp(reqDTO.getUserIp())); | ||||||
| @ -149,9 +147,9 @@ public class WXLitePayClient extends AbstractPayClient<WXPayClientConfig> { | |||||||
|         log.info("[parseOrderNotify][微信支付回调data数据:{}]", data.getBody()); |         log.info("[parseOrderNotify][微信支付回调data数据:{}]", data.getBody()); | ||||||
|         // 微信支付 v2 回调结果处理 |         // 微信支付 v2 回调结果处理 | ||||||
|         switch (config.getApiVersion()) { |         switch (config.getApiVersion()) { | ||||||
|             case WXPayClientConfig.API_VERSION_V2: |             case WxPayClientConfig.API_VERSION_V2: | ||||||
|                 return parseOrderNotifyV2(data); |                 return parseOrderNotifyV2(data); | ||||||
|             case WXPayClientConfig.API_VERSION_V3: |             case WxPayClientConfig.API_VERSION_V3: | ||||||
|                 return parseOrderNotifyV3(data); |                 return parseOrderNotifyV3(data); | ||||||
|             default: |             default: | ||||||
|                 throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion())); |                 throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion())); | ||||||
|  | |||||||
| @ -37,11 +37,11 @@ import java.util.Objects; | |||||||
|  * @author zwy |  * @author zwy | ||||||
|  */ |  */ | ||||||
| @Slf4j | @Slf4j | ||||||
| public class WXNativePayClient extends AbstractPayClient<WXPayClientConfig> { | public class WXNativePayClient extends AbstractPayClient<WxPayClientConfig> { | ||||||
|  |  | ||||||
|     private WxPayService client; |     private WxPayService client; | ||||||
|  |  | ||||||
|     public WXNativePayClient(Long channelId, WXPayClientConfig config) { |     public WXNativePayClient(Long channelId, WxPayClientConfig config) { | ||||||
|         super(channelId, PayChannelEnum.WX_NATIVE.getCode(), config); |         super(channelId, PayChannelEnum.WX_NATIVE.getCode(), config); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @ -134,9 +134,9 @@ public class WXNativePayClient extends AbstractPayClient<WXPayClientConfig> { | |||||||
|         log.info("微信支付回调data数据:{}", data.getBody()); |         log.info("微信支付回调data数据:{}", data.getBody()); | ||||||
|         // 微信支付 v2 回调结果处理 |         // 微信支付 v2 回调结果处理 | ||||||
|         switch (config.getApiVersion()) { |         switch (config.getApiVersion()) { | ||||||
|             case WXPayClientConfig.API_VERSION_V2: |             case WxPayClientConfig.API_VERSION_V2: | ||||||
|                 return parseOrderNotifyV2(data); |                 return parseOrderNotifyV2(data); | ||||||
|             case WXPayClientConfig.API_VERSION_V3: |             case WxPayClientConfig.API_VERSION_V3: | ||||||
|                 return parseOrderNotifyV3(data); |                 return parseOrderNotifyV3(data); | ||||||
|             default: |             default: | ||||||
|                 throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion())); |                 throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion())); | ||||||
|  | |||||||
| @ -1,201 +0,0 @@ | |||||||
| package cn.iocoder.yudao.framework.pay.core.client.impl.weixin; |  | ||||||
|  |  | ||||||
| import cn.hutool.core.bean.BeanUtil; |  | ||||||
| import cn.hutool.core.date.LocalDateTimeUtil; |  | ||||||
| import cn.hutool.core.date.TemporalAccessorUtil; |  | ||||||
| import cn.hutool.core.lang.Assert; |  | ||||||
| import cn.hutool.core.map.MapUtil; |  | ||||||
| import cn.hutool.core.util.StrUtil; |  | ||||||
| import cn.iocoder.yudao.framework.common.util.io.FileUtils; |  | ||||||
| import cn.iocoder.yudao.framework.common.util.json.JsonUtils; |  | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyReqDTO; |  | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayOrderNotifyRespDTO; |  | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; |  | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO; |  | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO; |  | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedRespDTO; |  | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient; |  | ||||||
| import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum; |  | ||||||
| import cn.iocoder.yudao.framework.pay.core.enums.PayDisplayModeEnum; |  | ||||||
| import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult; |  | ||||||
| import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyV3Result; |  | ||||||
| import com.github.binarywang.wxpay.bean.order.WxPayMpOrderResult; |  | ||||||
| import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest; |  | ||||||
| import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request; |  | ||||||
| import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result; |  | ||||||
| import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum; |  | ||||||
| import com.github.binarywang.wxpay.config.WxPayConfig; |  | ||||||
| import com.github.binarywang.wxpay.constant.WxPayConstants; |  | ||||||
| import com.github.binarywang.wxpay.exception.WxPayException; |  | ||||||
| import com.github.binarywang.wxpay.service.WxPayService; |  | ||||||
| import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl; |  | ||||||
| import lombok.extern.slf4j.Slf4j; |  | ||||||
|  |  | ||||||
| import java.time.LocalDateTime; |  | ||||||
| import java.time.ZoneId; |  | ||||||
| import java.util.Objects; |  | ||||||
|  |  | ||||||
| import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * 微信支付(公众号)的 PayClient 实现类 |  | ||||||
|  * |  | ||||||
|  * @author 芋道源码 |  | ||||||
|  */ |  | ||||||
| @Slf4j |  | ||||||
| public class WXPubPayClient extends AbstractPayClient<WXPayClientConfig> { |  | ||||||
|  |  | ||||||
|     private WxPayService client; |  | ||||||
|  |  | ||||||
|     public WXPubPayClient(Long channelId, WXPayClientConfig config) { |  | ||||||
|         super(channelId, PayChannelEnum.WX_PUB.getCode(), config); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     protected void doInit() { |  | ||||||
|         WxPayConfig payConfig = new WxPayConfig(); |  | ||||||
|         BeanUtil.copyProperties(config, payConfig, "keyContent"); |  | ||||||
|         payConfig.setTradeType(WxPayConstants.TradeType.JSAPI); // 设置使用 JS API 支付方式 |  | ||||||
| //        if (StrUtil.isNotEmpty(config.getKeyContent())) { |  | ||||||
| //            payConfig.setKeyContent(config.getKeyContent().getBytes(StandardCharsets.UTF_8)); |  | ||||||
| //        } |  | ||||||
|         if (StrUtil.isNotEmpty(config.getPrivateKeyContent())) { |  | ||||||
|             // weixin-pay-java 存在 BUG,无法直接设置内容,所以创建临时文件来解决 |  | ||||||
|             payConfig.setPrivateKeyPath(FileUtils.createTempFile(config.getPrivateKeyContent()).getPath()); |  | ||||||
|         } |  | ||||||
|         if (StrUtil.isNotEmpty(config.getPrivateCertContent())) { |  | ||||||
|             // weixin-pay-java 存在 BUG,无法直接设置内容,所以创建临时文件来解决 |  | ||||||
|             payConfig.setPrivateCertPath(FileUtils.createTempFile(config.getPrivateCertContent()).getPath()); |  | ||||||
|         } |  | ||||||
|         // 真实客户端 |  | ||||||
|         this.client = new WxPayServiceImpl(); |  | ||||||
|         client.setConfig(payConfig); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) { |  | ||||||
|         WxPayMpOrderResult response = null; |  | ||||||
|         try { |  | ||||||
|             switch (config.getApiVersion()) { |  | ||||||
|                 case WXPayClientConfig.API_VERSION_V2: |  | ||||||
|                     response = this.unifiedOrderV2(reqDTO); |  | ||||||
|                     break; |  | ||||||
|                 case WXPayClientConfig.API_VERSION_V3: |  | ||||||
|                     WxPayUnifiedOrderV3Result.JsapiResult responseV3 = this.unifiedOrderV3(reqDTO); |  | ||||||
|                     // 将 V3 的结果,统一转换成 V2。返回的字段是一致的 |  | ||||||
|                     response = new WxPayMpOrderResult(); |  | ||||||
|                     BeanUtil.copyProperties(responseV3, response, true); |  | ||||||
|                     break; |  | ||||||
|                 default: |  | ||||||
|                     throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion())); |  | ||||||
|             } |  | ||||||
|         } catch (WxPayException e) { |  | ||||||
|             log.error("[unifiedOrder][request({}) 发起支付失败,原因({})]", toJsonString(reqDTO), e); |  | ||||||
| //            return PayCommonResult.build(ObjectUtils.defaultIfNull(e.getErrCode(), e.getReturnCode(), "CustomErrorCode"), |  | ||||||
| //                    ObjectUtils.defaultIfNull(e.getErrCodeDes(), e.getCustomErrorMsg()),null, codeMapping); |  | ||||||
|             System.out.println(); |  | ||||||
|         } |  | ||||||
|         return new PayOrderUnifiedRespDTO().setDisplayMode(PayDisplayModeEnum.CUSTOM.getMode()) |  | ||||||
|                 .setDisplayContent(JsonUtils.toJsonString(response)); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     private WxPayMpOrderResult unifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException { |  | ||||||
|         // 构建 WxPayUnifiedOrderRequest 对象 |  | ||||||
|         WxPayUnifiedOrderRequest request = WxPayUnifiedOrderRequest.newBuilder() |  | ||||||
|                 .outTradeNo(reqDTO.getMerchantOrderId()) |  | ||||||
|                 .body(reqDTO.getBody()) |  | ||||||
|                 .totalFee(reqDTO.getAmount()) // 单位分 |  | ||||||
|                 .timeExpire(formatDate(reqDTO.getExpireTime())) |  | ||||||
|                 .spbillCreateIp(reqDTO.getUserIp()) |  | ||||||
|                 .openid(getOpenid(reqDTO)) |  | ||||||
|                 .notifyUrl(reqDTO.getNotifyUrl()) |  | ||||||
|                 .build(); |  | ||||||
|         // 执行请求 |  | ||||||
|         return client.createOrder(request); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private WxPayUnifiedOrderV3Result.JsapiResult unifiedOrderV3(PayOrderUnifiedReqDTO reqDTO) throws WxPayException { |  | ||||||
|         // 构建 WxPayUnifiedOrderRequest 对象 |  | ||||||
|         WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request(); |  | ||||||
|         request.setOutTradeNo(reqDTO.getMerchantOrderId()); |  | ||||||
|         request.setDescription(reqDTO.getBody()); |  | ||||||
|         request.setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(reqDTO.getAmount())); // 单位分 |  | ||||||
|         request.setTimeExpire(formatDate(reqDTO.getExpireTime())); |  | ||||||
|         request.setPayer(new WxPayUnifiedOrderV3Request.Payer().setOpenid(getOpenid(reqDTO))); |  | ||||||
|         request.setSceneInfo(new WxPayUnifiedOrderV3Request.SceneInfo().setPayerClientIp(reqDTO.getUserIp())); |  | ||||||
|         request.setNotifyUrl(reqDTO.getNotifyUrl()); |  | ||||||
|         // 执行请求 |  | ||||||
|         return client.createOrderV3(TradeTypeEnum.JSAPI, request); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private static String getOpenid(PayOrderUnifiedReqDTO reqDTO) { |  | ||||||
|         String openid = MapUtil.getStr(reqDTO.getChannelExtras(), "openid"); |  | ||||||
|         if (StrUtil.isEmpty(openid)) { |  | ||||||
|             throw new IllegalArgumentException("支付请求的 openid 不能为空!"); |  | ||||||
|         } |  | ||||||
|         return openid; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * |  | ||||||
|      * 微信支付回调 分v2 和v3 的处理方式 |  | ||||||
|      * |  | ||||||
|      * @param data 通知结果 |  | ||||||
|      * @return 支付回调对象 |  | ||||||
|      * @throws WxPayException 微信异常类 |  | ||||||
|      */ |  | ||||||
| //    @Override |  | ||||||
|     public PayOrderNotifyRespDTO parseOrderNotify(PayNotifyReqDTO data) throws WxPayException { |  | ||||||
|         log.info("[parseOrderNotify][微信支付回调data数据: {}]", data.getBody()); |  | ||||||
|         // 微信支付 v2 回调结果处理 |  | ||||||
|         switch (config.getApiVersion()) { |  | ||||||
|             case WXPayClientConfig.API_VERSION_V2: |  | ||||||
|                 return parseOrderNotifyV2(data); |  | ||||||
|             case WXPayClientConfig.API_VERSION_V3: |  | ||||||
|                 return parseOrderNotifyV3(data); |  | ||||||
|             default: |  | ||||||
|                 throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion())); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private PayOrderNotifyRespDTO parseOrderNotifyV3(PayNotifyReqDTO data) throws WxPayException { |  | ||||||
|         WxPayOrderNotifyV3Result wxPayOrderNotifyV3Result = client.parseOrderNotifyV3Result(data.getBody(), null); |  | ||||||
|         WxPayOrderNotifyV3Result.DecryptNotifyResult result = wxPayOrderNotifyV3Result.getResult(); |  | ||||||
|         // 转换结果 |  | ||||||
|         Assert.isTrue(Objects.equals(wxPayOrderNotifyV3Result.getResult().getTradeState(), "SUCCESS"), |  | ||||||
|                 "支付结果非 SUCCESS"); |  | ||||||
|         return PayOrderNotifyRespDTO |  | ||||||
|                 .builder() |  | ||||||
|                 .orderExtensionNo(result.getOutTradeNo()) |  | ||||||
|                 .channelOrderNo(result.getTradeState()) |  | ||||||
|                 .successTime(LocalDateTimeUtil.parse(result.getSuccessTime(), "yyyy-MM-dd'T'HH:mm:ssXXX")) |  | ||||||
|                 .build(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private PayOrderNotifyRespDTO parseOrderNotifyV2(PayNotifyReqDTO data) throws WxPayException { |  | ||||||
|         WxPayOrderNotifyResult notifyResult = client.parseOrderNotifyResult(data.getBody()); |  | ||||||
|         Assert.isTrue(Objects.equals(notifyResult.getResultCode(), "SUCCESS"), "支付结果非 SUCCESS"); |  | ||||||
|         // 转换结果 |  | ||||||
|         return PayOrderNotifyRespDTO |  | ||||||
|                 .builder() |  | ||||||
|                 .orderExtensionNo(notifyResult.getOutTradeNo()) |  | ||||||
|                 .channelOrderNo(notifyResult.getTransactionId()) |  | ||||||
|                 .channelUserId(notifyResult.getOpenid()) |  | ||||||
|                 .successTime(LocalDateTimeUtil.parse(notifyResult.getTimeEnd(), "yyyyMMddHHmmss")) |  | ||||||
|                 .build(); |  | ||||||
|  |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     protected PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable { |  | ||||||
|         // TODO 需要实现 |  | ||||||
|         throw new UnsupportedOperationException(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private static String formatDate(LocalDateTime time) { |  | ||||||
| //        return TemporalAccessorUtil.format(time.atZone(ZoneId.systemDefault()), "yyyy-MM-dd'T'HH:mm:ssXXX"); |  | ||||||
|         return TemporalAccessorUtil.format(time.atZone(ZoneId.systemDefault()), "yyyyMMddHHmmss"); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @ -18,15 +18,17 @@ import java.util.Set; | |||||||
|  * @author 芋道源码 |  * @author 芋道源码 | ||||||
|  */ |  */ | ||||||
| @Data | @Data | ||||||
| public class WXPayClientConfig implements PayClientConfig { | public class WxPayClientConfig implements PayClientConfig { | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * API 版本 - V2 |      * API 版本 - V2 | ||||||
|  |      * | ||||||
|      * https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_1 |      * https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_1 | ||||||
|      */ |      */ | ||||||
|     public static final String API_VERSION_V2 = "v2"; |     public static final String API_VERSION_V2 = "v2"; | ||||||
|     /** |     /** | ||||||
|      * API 版本 - V3 |      * API 版本 - V3 | ||||||
|  |      * | ||||||
|      * https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay-1.shtml |      * https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay-1.shtml | ||||||
|      */ |      */ | ||||||
|     public static final String API_VERSION_V3 = "v3"; |     public static final String API_VERSION_V3 = "v3"; | ||||||
| @ -0,0 +1,81 @@ | |||||||
|  | package cn.iocoder.yudao.framework.pay.core.client.impl.weixin; | ||||||
|  |  | ||||||
|  | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; | ||||||
|  | import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; | ||||||
|  | import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO; | ||||||
|  | import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO; | ||||||
|  | import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedRespDTO; | ||||||
|  | import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum; | ||||||
|  | import cn.iocoder.yudao.framework.pay.core.enums.PayDisplayModeEnum; | ||||||
|  | import com.github.binarywang.wxpay.bean.order.WxPayMpOrderResult; | ||||||
|  | import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest; | ||||||
|  | import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request; | ||||||
|  | import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result; | ||||||
|  | import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum; | ||||||
|  | import com.github.binarywang.wxpay.constant.WxPayConstants; | ||||||
|  | import com.github.binarywang.wxpay.exception.WxPayException; | ||||||
|  | import lombok.extern.slf4j.Slf4j; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 微信支付(公众号)的 PayClient 实现类 | ||||||
|  |  * | ||||||
|  |  * @author 芋道源码 | ||||||
|  |  */ | ||||||
|  | @Slf4j | ||||||
|  | public class WxPubPayClient extends AbstractWxPayClient { | ||||||
|  |  | ||||||
|  |     public WxPubPayClient(Long channelId, WxPayClientConfig config) { | ||||||
|  |         super(channelId, PayChannelEnum.WX_PUB.getCode(), config); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     protected void doInit() { | ||||||
|  |         super.doInit(WxPayConstants.TradeType.JSAPI); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     protected PayOrderUnifiedRespDTO doUnifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException { | ||||||
|  |         // 构建 WxPayUnifiedOrderRequest 对象 | ||||||
|  |         WxPayUnifiedOrderRequest request = WxPayUnifiedOrderRequest.newBuilder() | ||||||
|  |                 .outTradeNo(reqDTO.getMerchantOrderId()) | ||||||
|  |                 .body(reqDTO.getBody()) | ||||||
|  |                 .totalFee(reqDTO.getAmount()) // 单位分 | ||||||
|  |                 .timeExpire(formatDateV2(reqDTO.getExpireTime())) | ||||||
|  |                 .spbillCreateIp(reqDTO.getUserIp()) | ||||||
|  |                 .openid(getOpenid(reqDTO)) | ||||||
|  |                 .notifyUrl(reqDTO.getNotifyUrl()) | ||||||
|  |                 .build(); | ||||||
|  |         // 执行请求 | ||||||
|  |         WxPayMpOrderResult response = client.createOrder(request); | ||||||
|  |  | ||||||
|  |         // 转换结果 | ||||||
|  |         return new PayOrderUnifiedRespDTO().setDisplayMode(PayDisplayModeEnum.CUSTOM.getMode()) | ||||||
|  |                 .setDisplayContent(JsonUtils.toJsonString(response)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     protected PayOrderUnifiedRespDTO doUnifiedOrderV3(PayOrderUnifiedReqDTO reqDTO) throws WxPayException { | ||||||
|  |         // 构建 WxPayUnifiedOrderRequest 对象 | ||||||
|  |         WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request(); | ||||||
|  |         request.setOutTradeNo(reqDTO.getMerchantOrderId()); | ||||||
|  |         request.setDescription(reqDTO.getBody()); | ||||||
|  |         request.setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(reqDTO.getAmount())); // 单位分 | ||||||
|  |         request.setTimeExpire(formatDateV3(reqDTO.getExpireTime())); | ||||||
|  |         request.setPayer(new WxPayUnifiedOrderV3Request.Payer().setOpenid(getOpenid(reqDTO))); | ||||||
|  |         request.setSceneInfo(new WxPayUnifiedOrderV3Request.SceneInfo().setPayerClientIp(reqDTO.getUserIp())); | ||||||
|  |         request.setNotifyUrl(reqDTO.getNotifyUrl()); | ||||||
|  |         // 执行请求 | ||||||
|  |         WxPayUnifiedOrderV3Result.JsapiResult response = client.createOrderV3(TradeTypeEnum.JSAPI, request); | ||||||
|  |  | ||||||
|  |         // 转换结果 | ||||||
|  |         return new PayOrderUnifiedRespDTO().setDisplayMode(PayDisplayModeEnum.CUSTOM.getMode()) | ||||||
|  |                 .setDisplayContent(JsonUtils.toJsonString(response)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     protected PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) { | ||||||
|  |         // TODO 需要实现 | ||||||
|  |         throw new UnsupportedOperationException(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @ -3,7 +3,7 @@ package cn.iocoder.yudao.framework.pay.core.enums; | |||||||
| import cn.hutool.core.util.ArrayUtil; | import cn.hutool.core.util.ArrayUtil; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig; | import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig; | import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.impl.weixin.WXPayClientConfig; | import cn.iocoder.yudao.framework.pay.core.client.impl.weixin.WxPayClientConfig; | ||||||
| import lombok.AllArgsConstructor; | import lombok.AllArgsConstructor; | ||||||
| import lombok.Getter; | import lombok.Getter; | ||||||
|  |  | ||||||
| @ -17,10 +17,10 @@ import lombok.Getter; | |||||||
| @AllArgsConstructor | @AllArgsConstructor | ||||||
| public enum PayChannelEnum { | public enum PayChannelEnum { | ||||||
|  |  | ||||||
|     WX_PUB("wx_pub", "微信 JSAPI 支付", WXPayClientConfig.class), // 公众号网页 |     WX_PUB("wx_pub", "微信 JSAPI 支付", WxPayClientConfig.class), // 公众号网页 | ||||||
|     WX_LITE("wx_lite", "微信小程序支付", WXPayClientConfig.class), |     WX_LITE("wx_lite", "微信小程序支付", WxPayClientConfig.class), | ||||||
|     WX_APP("wx_app", "微信 App 支付", WXPayClientConfig.class), |     WX_APP("wx_app", "微信 App 支付", WxPayClientConfig.class), | ||||||
|     WX_NATIVE("wx_native", "微信 native 支付", WXPayClientConfig.class), |     WX_NATIVE("wx_native", "微信 native 支付", WxPayClientConfig.class), | ||||||
|  |  | ||||||
|     ALIPAY_PC("alipay_pc", "支付宝 PC 网站支付", AlipayPayClientConfig.class), |     ALIPAY_PC("alipay_pc", "支付宝 PC 网站支付", AlipayPayClientConfig.class), | ||||||
|     ALIPAY_WAP("alipay_wap", "支付宝 Wap 网站支付", AlipayPayClientConfig.class), |     ALIPAY_WAP("alipay_wap", "支付宝 Wap 网站支付", AlipayPayClientConfig.class), | ||||||
|  | |||||||
| @ -7,8 +7,8 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDT | |||||||
| import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig; | import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayQrPayClient; | import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayQrPayClient; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayWapPayClient; | import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayWapPayClient; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.impl.weixin.WXPayClientConfig; | import cn.iocoder.yudao.framework.pay.core.client.impl.weixin.WxPayClientConfig; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.impl.weixin.WXPubPayClient; | import cn.iocoder.yudao.framework.pay.core.client.impl.weixin.WxPubPayClient; | ||||||
| import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum; | import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum; | ||||||
| import org.junit.jupiter.api.Disabled; | import org.junit.jupiter.api.Disabled; | ||||||
| import org.junit.jupiter.api.Test; | import org.junit.jupiter.api.Test; | ||||||
| @ -27,15 +27,15 @@ public class PayClientFactoryImplIntegrationTest { | |||||||
|     private final PayClientFactoryImpl payClientFactory = new PayClientFactoryImpl(); |     private final PayClientFactoryImpl payClientFactory = new PayClientFactoryImpl(); | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * {@link WXPubPayClient} 的 V2 版本 |      * {@link WxPubPayClient} 的 V2 版本 | ||||||
|      */ |      */ | ||||||
|     @Test |     @Test | ||||||
|     public void testCreatePayClient_WX_PUB_V2() { |     public void testCreatePayClient_WX_PUB_V2() { | ||||||
|         // 创建配置 |         // 创建配置 | ||||||
|         WXPayClientConfig config = new WXPayClientConfig(); |         WxPayClientConfig config = new WxPayClientConfig(); | ||||||
|         config.setAppId("wx041349c6f39b268b"); |         config.setAppId("wx041349c6f39b268b"); | ||||||
|         config.setMchId("1545083881"); |         config.setMchId("1545083881"); | ||||||
|         config.setApiVersion(WXPayClientConfig.API_VERSION_V2); |         config.setApiVersion(WxPayClientConfig.API_VERSION_V2); | ||||||
|         config.setMchKey("0alL64UDQdlCwiKZ73ib7ypaIjMns06p"); |         config.setMchKey("0alL64UDQdlCwiKZ73ib7ypaIjMns06p"); | ||||||
|         // 创建客户端 |         // 创建客户端 | ||||||
|         Long channelId = RandomUtil.randomLong(); |         Long channelId = RandomUtil.randomLong(); | ||||||
| @ -48,15 +48,15 @@ public class PayClientFactoryImplIntegrationTest { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * {@link WXPubPayClient} 的 V3 版本 |      * {@link WxPubPayClient} 的 V3 版本 | ||||||
|      */ |      */ | ||||||
|     @Test |     @Test | ||||||
|     public void testCreatePayClient_WX_PUB_V3() throws FileNotFoundException { |     public void testCreatePayClient_WX_PUB_V3() throws FileNotFoundException { | ||||||
|         // 创建配置 |         // 创建配置 | ||||||
|         WXPayClientConfig config = new WXPayClientConfig(); |         WxPayClientConfig config = new WxPayClientConfig(); | ||||||
|         config.setAppId("wx041349c6f39b268b"); |         config.setAppId("wx041349c6f39b268b"); | ||||||
|         config.setMchId("1545083881"); |         config.setMchId("1545083881"); | ||||||
|         config.setApiVersion(WXPayClientConfig.API_VERSION_V3); |         config.setApiVersion(WxPayClientConfig.API_VERSION_V3); | ||||||
|         config.setPrivateKeyContent(IoUtil.readUtf8(new FileInputStream("/Users/yunai/Downloads/wx_pay/apiclient_key.pem"))); |         config.setPrivateKeyContent(IoUtil.readUtf8(new FileInputStream("/Users/yunai/Downloads/wx_pay/apiclient_key.pem"))); | ||||||
|         config.setPrivateCertContent(IoUtil.readUtf8(new FileInputStream("/Users/yunai/Downloads/wx_pay/apiclient_cert.pem"))); |         config.setPrivateCertContent(IoUtil.readUtf8(new FileInputStream("/Users/yunai/Downloads/wx_pay/apiclient_cert.pem"))); | ||||||
|         config.setApiV3Key("joerVi8y5DJ3o4ttA0o1uH47Xz1u2Ase"); |         config.setApiV3Key("joerVi8y5DJ3o4ttA0o1uH47Xz1u2Ase"); | ||||||
|  | |||||||
| @ -4,7 +4,7 @@ import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | |||||||
| import cn.iocoder.yudao.framework.common.pojo.PageResult; | import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory; | import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig; | import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.impl.weixin.WXPayClientConfig; | import cn.iocoder.yudao.framework.pay.core.client.impl.weixin.WxPayClientConfig; | ||||||
| import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum; | import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum; | ||||||
| import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; | import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; | ||||||
| import cn.iocoder.yudao.module.pay.controller.admin.merchant.vo.channel.PayChannelCreateReqVO; | import cn.iocoder.yudao.module.pay.controller.admin.merchant.vo.channel.PayChannelCreateReqVO; | ||||||
| @ -49,7 +49,7 @@ public class PayChannelServiceTest extends BaseDbUnitTest { | |||||||
|     @Test |     @Test | ||||||
|     public void testCreateWechatVersion2Channel_success() { |     public void testCreateWechatVersion2Channel_success() { | ||||||
|         // 准备参数 |         // 准备参数 | ||||||
|         WXPayClientConfig v2Config = getV2Config(); |         WxPayClientConfig v2Config = getV2Config(); | ||||||
|         PayChannelCreateReqVO reqVO = randomPojo(PayChannelCreateReqVO.class, o -> { |         PayChannelCreateReqVO reqVO = randomPojo(PayChannelCreateReqVO.class, o -> { | ||||||
|             o.setCode(PayChannelEnum.WX_PUB.getCode()); |             o.setCode(PayChannelEnum.WX_PUB.getCode()); | ||||||
|             o.setStatus(CommonStatusEnum.ENABLE.getStatus()); |             o.setStatus(CommonStatusEnum.ENABLE.getStatus()); | ||||||
| @ -70,7 +70,7 @@ public class PayChannelServiceTest extends BaseDbUnitTest { | |||||||
|     @Test |     @Test | ||||||
|     public void testCreateWechatVersion3Channel_success() { |     public void testCreateWechatVersion3Channel_success() { | ||||||
|         // 准备参数 |         // 准备参数 | ||||||
|         WXPayClientConfig v3Config = getV3Config(); |         WxPayClientConfig v3Config = getV3Config(); | ||||||
|         PayChannelCreateReqVO reqVO = randomPojo(PayChannelCreateReqVO.class, o -> { |         PayChannelCreateReqVO reqVO = randomPojo(PayChannelCreateReqVO.class, o -> { | ||||||
|             o.setCode(PayChannelEnum.WX_PUB.getCode()); |             o.setCode(PayChannelEnum.WX_PUB.getCode()); | ||||||
|             o.setStatus(CommonStatusEnum.ENABLE.getStatus()); |             o.setStatus(CommonStatusEnum.ENABLE.getStatus()); | ||||||
| @ -346,22 +346,22 @@ public class PayChannelServiceTest extends BaseDbUnitTest { | |||||||
|         assertPojoEquals(payClientConfig, list.get(0).getConfig()); |         assertPojoEquals(payClientConfig, list.get(0).getConfig()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public WXPayClientConfig getV2Config() { |     public WxPayClientConfig getV2Config() { | ||||||
|         return new WXPayClientConfig() |         return new WxPayClientConfig() | ||||||
|                 .setAppId("APP00001") |                 .setAppId("APP00001") | ||||||
|                 .setMchId("MCH00001") |                 .setMchId("MCH00001") | ||||||
|                 .setApiVersion(WXPayClientConfig.API_VERSION_V2) |                 .setApiVersion(WxPayClientConfig.API_VERSION_V2) | ||||||
|                 .setMchKey("dsa1d5s6a1d6sa16d1sa56d15a61das6") |                 .setMchKey("dsa1d5s6a1d6sa16d1sa56d15a61das6") | ||||||
|                 .setApiV3Key("") |                 .setApiV3Key("") | ||||||
|                 .setPrivateCertContent("") |                 .setPrivateCertContent("") | ||||||
|                 .setPrivateKeyContent(""); |                 .setPrivateKeyContent(""); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public WXPayClientConfig getV3Config() { |     public WxPayClientConfig getV3Config() { | ||||||
|         return new WXPayClientConfig() |         return new WxPayClientConfig() | ||||||
|                 .setAppId("APP00001") |                 .setAppId("APP00001") | ||||||
|                 .setMchId("MCH00001") |                 .setMchId("MCH00001") | ||||||
|                 .setApiVersion(WXPayClientConfig.API_VERSION_V3) |                 .setApiVersion(WxPayClientConfig.API_VERSION_V3) | ||||||
|                 .setMchKey("") |                 .setMchKey("") | ||||||
|                 .setApiV3Key("sdadasdsadadsa") |                 .setApiV3Key("sdadasdsadadsa") | ||||||
|                 .setPrivateKeyContent("dsa445das415d15asd16ad156as") |                 .setPrivateKeyContent("dsa445das415d15asd16ad156as") | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 YunaiV
					YunaiV