Commit 20628987c97fc7253105860f6913e18f16e7740b

Authored by YunaiV
1 parent 1b0aaec9

增加支付回调地址的配置

yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayOrderCoreServiceImpl.java
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.coreservice.modules.pay.service.order.impl; @@ -2,6 +2,7 @@ package cn.iocoder.yudao.coreservice.modules.pay.service.order.impl;
2 2
3 import cn.hutool.core.date.DateUtil; 3 import cn.hutool.core.date.DateUtil;
4 import cn.hutool.core.util.RandomUtil; 4 import cn.hutool.core.util.RandomUtil;
  5 +import cn.hutool.core.util.StrUtil;
5 import cn.iocoder.yudao.coreservice.modules.pay.convert.order.PayOrderCoreConvert; 6 import cn.iocoder.yudao.coreservice.modules.pay.convert.order.PayOrderCoreConvert;
6 import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayAppDO; 7 import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayAppDO;
7 import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayChannelDO; 8 import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayChannelDO;
@@ -19,6 +20,7 @@ import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayOrderSubmit @@ -19,6 +20,7 @@ import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayOrderSubmit
19 import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayOrderSubmitRespDTO; 20 import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayOrderSubmitRespDTO;
20 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 21 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
21 import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 22 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
  23 +import cn.iocoder.yudao.framework.pay.config.PayProperties;
22 import cn.iocoder.yudao.framework.pay.core.client.PayClient; 24 import cn.iocoder.yudao.framework.pay.core.client.PayClient;
23 import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory; 25 import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
24 import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO; 26 import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
@@ -44,6 +46,9 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU @@ -44,6 +46,9 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU
44 public class PayOrderCoreServiceImpl implements PayOrderCoreService { 46 public class PayOrderCoreServiceImpl implements PayOrderCoreService {
45 47
46 @Resource 48 @Resource
  49 + private PayProperties payProperties;
  50 +
  51 + @Resource
47 private PayAppCoreService payAppCoreService; 52 private PayAppCoreService payAppCoreService;
48 @Resource 53 @Resource
49 private PayChannelCoreService payChannelCoreService; 54 private PayChannelCoreService payChannelCoreService;
@@ -125,7 +130,7 @@ public class PayOrderCoreServiceImpl implements PayOrderCoreService { @@ -125,7 +130,7 @@ public class PayOrderCoreServiceImpl implements PayOrderCoreService {
125 // 商户相关字段 130 // 商户相关字段
126 unifiedOrderReqDTO.setMerchantOrderId(order.getMerchantOrderId()) 131 unifiedOrderReqDTO.setMerchantOrderId(order.getMerchantOrderId())
127 .setSubject(order.getSubject()).setBody(order.getBody()) 132 .setSubject(order.getSubject()).setBody(order.getBody())
128 - .setNotifyUrl(app.getPayNotifyUrl()); 133 + .setNotifyUrl(genChannelPayNotifyUrl(reqDTO.getChannelCode()));
129 // 订单相关字段 134 // 订单相关字段
130 unifiedOrderReqDTO.setAmount(order.getAmount()).setExpireTime(order.getExpireTime()); 135 unifiedOrderReqDTO.setAmount(order.getAmount()).setExpireTime(order.getExpireTime());
131 CommonResult<?> unifiedOrderResult = client.unifiedOrder(unifiedOrderReqDTO); 136 CommonResult<?> unifiedOrderResult = client.unifiedOrder(unifiedOrderReqDTO);
@@ -137,6 +142,17 @@ public class PayOrderCoreServiceImpl implements PayOrderCoreService { @@ -137,6 +142,17 @@ public class PayOrderCoreServiceImpl implements PayOrderCoreService {
137 .setInvokeResponse(unifiedOrderResult.getData()); 142 .setInvokeResponse(unifiedOrderResult.getData());
138 } 143 }
139 144
  145 + /**
  146 + * 根据支付渠道的编码,生成支付渠道的回调地址
  147 + *
  148 + * @param channelCode 支付渠道的编码
  149 + * @return 支付渠道的回调地址
  150 + */
  151 + private String genChannelPayNotifyUrl(String channelCode) {
  152 + // _ 转化为 - 的原因,是因为 URL 我们统一采用中划线的原则
  153 + return payProperties.getPayNotifyUrl() + "/" + StrUtil.replace(channelCode, "_", "-");
  154 + }
  155 +
140 private String generateOrderExtensionNo() { 156 private String generateOrderExtensionNo() {
141 // wx 157 // wx
142 // 2014 158 // 2014
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/config/PayProperties.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.pay.config;
  2 +
  3 +import lombok.Data;
  4 +import org.hibernate.validator.constraints.URL;
  5 +import org.springframework.boot.context.properties.ConfigurationProperties;
  6 +import org.springframework.validation.annotation.Validated;
  7 +
  8 +import javax.validation.constraints.NotEmpty;
  9 +import javax.validation.constraints.NotNull;
  10 +import java.time.Duration;
  11 +
  12 +@ConfigurationProperties(prefix = "yudao.pay")
  13 +@Validated
  14 +@Data
  15 +public class PayProperties {
  16 +
  17 + /**
  18 + * 支付回调地址
  19 + * 注意,支付渠道统一回调到 payNotifyUrl 地址,由支付模块统一处理;然后,自己的支付模块,在回调 PayAppDO.payNotifyUrl 地址
  20 + */
  21 + @NotEmpty(message = "支付回调地址不能为空")
  22 + @URL(message = "支付回调地址的格式必须是 URL")
  23 + private String payNotifyUrl;
  24 + /**
  25 + * 退款回调地址
  26 + * 注意点,同 {@link #payNotifyUrl} 属性
  27 + */
  28 + @NotNull(message = "短信发送频率不能为空")
  29 + @URL(message = "退款回调地址的格式必须是 URL")
  30 + private String refundNotifyUrl;
  31 +
  32 +}
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/config/YudaoPayAutoConfiguration.java
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.framework.pay.config; @@ -2,6 +2,7 @@ package cn.iocoder.yudao.framework.pay.config;
2 2
3 import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory; 3 import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
4 import cn.iocoder.yudao.framework.pay.core.client.impl.PayClientFactoryImpl; 4 import cn.iocoder.yudao.framework.pay.core.client.impl.PayClientFactoryImpl;
  5 +import org.springframework.boot.context.properties.EnableConfigurationProperties;
5 import org.springframework.context.annotation.Bean; 6 import org.springframework.context.annotation.Bean;
6 import org.springframework.context.annotation.Configuration; 7 import org.springframework.context.annotation.Configuration;
7 8
@@ -10,7 +11,7 @@ import org.springframework.context.annotation.Configuration; @@ -10,7 +11,7 @@ import org.springframework.context.annotation.Configuration;
10 * 11 *
11 * @author 芋道源码 12 * @author 芋道源码
12 */ 13 */
13 -@Configuration 14 +@EnableConfigurationProperties(PayProperties.class)
14 public class YudaoPayAutoConfiguration { 15 public class YudaoPayAutoConfiguration {
15 16
16 @Bean 17 @Bean
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayOrderUnifiedReqDTO.java
@@ -8,6 +8,7 @@ import javax.validation.constraints.DecimalMin; @@ -8,6 +8,7 @@ import javax.validation.constraints.DecimalMin;
8 import javax.validation.constraints.NotEmpty; 8 import javax.validation.constraints.NotEmpty;
9 import javax.validation.constraints.NotNull; 9 import javax.validation.constraints.NotNull;
10 import java.util.Date; 10 import java.util.Date;
  11 +import java.util.Map;
11 12
12 /** 13 /**
13 * 统一下单 Request DTO 14 * 统一下单 Request DTO
@@ -63,6 +64,11 @@ public class PayOrderUnifiedReqDTO { @@ -63,6 +64,11 @@ public class PayOrderUnifiedReqDTO {
63 private Date expireTime; 64 private Date expireTime;
64 65
65 // ========== 拓展参数 ========== 66 // ========== 拓展参数 ==========
66 - // TODO 芋艿:待完善 67 + /**
  68 + * 支付渠道的额外参数
  69 + *
  70 + * 例如说,微信公众号需要传递 openid 参数
  71 + */
  72 + private Map<String, String> channelExtras;
67 73
68 } 74 }
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXPubPayClient.java
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.framework.pay.core.client.impl.wx; @@ -2,6 +2,7 @@ package cn.iocoder.yudao.framework.pay.core.client.impl.wx;
2 2
3 import cn.hutool.core.bean.BeanUtil; 3 import cn.hutool.core.bean.BeanUtil;
4 import cn.hutool.core.date.DateUtil; 4 import cn.hutool.core.date.DateUtil;
  5 +import cn.hutool.core.map.MapUtil;
5 import cn.hutool.core.util.StrUtil; 6 import cn.hutool.core.util.StrUtil;
6 import cn.iocoder.yudao.framework.common.util.io.FileUtils; 7 import cn.iocoder.yudao.framework.common.util.io.FileUtils;
7 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; 8 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
@@ -94,7 +95,7 @@ public class WXPubPayClient extends AbstractPayClient&lt;WXPayClientConfig&gt; { @@ -94,7 +95,7 @@ public class WXPubPayClient extends AbstractPayClient&lt;WXPayClientConfig&gt; {
94 .totalFee(reqDTO.getAmount().intValue()) // 单位分 95 .totalFee(reqDTO.getAmount().intValue()) // 单位分
95 .timeExpire(DateUtil.format(reqDTO.getExpireTime(), "yyyyMMddHHmmss")) 96 .timeExpire(DateUtil.format(reqDTO.getExpireTime(), "yyyyMMddHHmmss"))
96 .spbillCreateIp(reqDTO.getUserIp()) 97 .spbillCreateIp(reqDTO.getUserIp())
97 - .openid("ockUAwIZ-0OeMZl9ogcZ4ILrGba0") // TODO 芋艿:先随便写死 98 + .openid(getOpenid(reqDTO))
98 .notifyUrl(reqDTO.getNotifyUrl()) 99 .notifyUrl(reqDTO.getNotifyUrl())
99 .build(); 100 .build();
100 // 执行请求 101 // 执行请求
@@ -109,11 +110,20 @@ public class WXPubPayClient extends AbstractPayClient&lt;WXPayClientConfig&gt; { @@ -109,11 +110,20 @@ public class WXPubPayClient extends AbstractPayClient&lt;WXPayClientConfig&gt; {
109 request.setDescription(reqDTO.getBody()); 110 request.setDescription(reqDTO.getBody());
110 request.setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(reqDTO.getAmount().intValue())); // 单位分 111 request.setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(reqDTO.getAmount().intValue())); // 单位分
111 request.setTimeExpire(DateUtil.format(reqDTO.getExpireTime(), "yyyyMMddHHmmss")); 112 request.setTimeExpire(DateUtil.format(reqDTO.getExpireTime(), "yyyyMMddHHmmss"));
112 - request.setPayer(new WxPayUnifiedOrderV3Request.Payer().setOpenid("ockUAwIZ-0OeMZl9ogcZ4ILrGba0")); // TODO 芋艿:先随便写死 113 + request.setPayer(new WxPayUnifiedOrderV3Request.Payer().setOpenid(getOpenid(reqDTO)));
113 request.setSceneInfo(new WxPayUnifiedOrderV3Request.SceneInfo().setPayerClientIp(reqDTO.getUserIp())); 114 request.setSceneInfo(new WxPayUnifiedOrderV3Request.SceneInfo().setPayerClientIp(reqDTO.getUserIp()));
114 request.setNotifyUrl(reqDTO.getNotifyUrl()); 115 request.setNotifyUrl(reqDTO.getNotifyUrl());
115 // 执行请求 116 // 执行请求
116 return client.createOrderV3(TradeTypeEnum.JSAPI, request); 117 return client.createOrderV3(TradeTypeEnum.JSAPI, request);
117 } 118 }
118 119
  120 +
  121 + private static String getOpenid(PayOrderUnifiedReqDTO reqDTO) {
  122 + String openid = MapUtil.getStr(reqDTO.getChannelExtras(), "openid");
  123 + if (StrUtil.isEmpty(openid)) {
  124 + throw new IllegalArgumentException("支付请求的 openid 不能为空!");
  125 + }
  126 + return openid;
  127 + }
  128 +
119 } 129 }
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/controller/order/PayOrderController.java
@@ -32,7 +32,7 @@ public class PayOrderController { @@ -32,7 +32,7 @@ public class PayOrderController {
32 @PostMapping("/submit") 32 @PostMapping("/submit")
33 @ApiOperation("提交支付订单") 33 @ApiOperation("提交支付订单")
34 // @PreAuthenticated // TODO 暂时不加登陆验证,前端暂时没做好 34 // @PreAuthenticated // TODO 暂时不加登陆验证,前端暂时没做好
35 - public CommonResult<PayOrderSubmitRespVO> submit(@RequestBody PayOrderSubmitReqVO reqVO) { 35 + public CommonResult<PayOrderSubmitRespVO> submitPayOrder(@RequestBody PayOrderSubmitReqVO reqVO) {
36 // 获得订单 36 // 获得订单
37 PayOrderDO payOrder = payOrderCoreService.getPayOrder(reqVO.getId()); 37 PayOrderDO payOrder = payOrderCoreService.getPayOrder(reqVO.getId());
38 38
@@ -47,4 +47,13 @@ public class PayOrderController { @@ -47,4 +47,13 @@ public class PayOrderController {
47 return success(PayOrderSubmitRespVO.builder().invokeResponse(respDTO.getInvokeResponse()).build()); 47 return success(PayOrderSubmitRespVO.builder().invokeResponse(respDTO.getInvokeResponse()).build());
48 } 48 }
49 49
  50 + // ========== 支付渠道的回调 ==========
  51 +
  52 + @PostMapping("/notify/wx-pub")
  53 + @ApiOperation("通知微信公众号的结果")
  54 + public String notifyWxPayOrder(@RequestBody String xmlData) {
  55 + System.out.println(xmlData);
  56 + return "success";
  57 + }
  58 +
50 } 59 }
yudao-user-server/src/main/resources/application-dev.yaml
@@ -138,3 +138,6 @@ yudao: @@ -138,3 +138,6 @@ yudao:
138 - ${spring.boot.admin.context-path}/** # 不处理 Spring Boot Admin 的请求 138 - ${spring.boot.admin.context-path}/** # 不处理 Spring Boot Admin 的请求
139 - ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求 139 - ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求
140 demo: true # 开启演示模式 140 demo: true # 开启演示模式
  141 + pay:
  142 + pay-notify-url: http://niubi.natapp1.cc/api/pay/order/notify
  143 + refund-notify-url: http://niubi.natapp1.cc/api/pay/refund/notify
yudao-user-server/src/main/resources/application-local.yaml
@@ -152,3 +152,6 @@ yudao: @@ -152,3 +152,6 @@ yudao:
152 - ${spring.boot.admin.context-path}/** # 不处理 Spring Boot Admin 的请求 152 - ${spring.boot.admin.context-path}/** # 不处理 Spring Boot Admin 的请求
153 - ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求 153 - ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求
154 demo: false # 关闭演示模式 154 demo: false # 关闭演示模式
  155 + pay:
  156 + pay-notify-url: http://niubi.natapp1.cc/api/pay/order/notify
  157 + refund-notify-url: http://niubi.natapp1.cc/api/pay/refund/notify
yudao-user-server/src/main/resources/static/pay.html
@@ -18,6 +18,8 @@ @@ -18,6 +18,8 @@
18 let payOrderId = undefined; 18 let payOrderId = undefined;
19 // let server = 'http://127.0.0.1:28080'; 19 // let server = 'http://127.0.0.1:28080';
20 let server = 'http://niubi.natapp1.cc'; 20 let server = 'http://niubi.natapp1.cc';
  21 + // TODO openid
  22 + let openid = "ockUAwIZ-0OeMZl9ogcZ4ILrGba0";
21 $(function() { 23 $(function() {
22 // 获得 JsapiTicket 24 // 获得 JsapiTicket
23 // 参考 https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html 文档 25 // 参考 https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html 文档
@@ -73,6 +75,8 @@ @@ -73,6 +75,8 @@
73 } 75 }
74 76
75 // 提交支付 77 // 提交支付
  78 + // 参考 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=6 文档
  79 + // 参考 https://segmentfault.com/a/1190000020704650 文档
76 $.ajax({ 80 $.ajax({
77 url: server + "/api/pay/order/submit", 81 url: server + "/api/pay/order/submit",
78 method: 'POST', 82 method: 'POST',
@@ -81,6 +85,9 @@ @@ -81,6 +85,9 @@
81 data: JSON.stringify({ 85 data: JSON.stringify({
82 "id": payOrderId, 86 "id": payOrderId,
83 "channelCode": 'wx_pub', 87 "channelCode": 'wx_pub',
  88 + "channelExtras": {
  89 + "openid": openid
  90 + }
84 }), 91 }),
85 success: function( result ) { 92 success: function( result ) {
86 if (result.code !== 0) { 93 if (result.code !== 0) {