Commit 20628987c97fc7253105860f6913e18f16e7740b
1 parent
1b0aaec9
增加支付回调地址的配置
Showing
9 changed files
with
93 additions
and
6 deletions
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<WXPayClientConfig> { | @@ -94,7 +95,7 @@ public class WXPubPayClient extends AbstractPayClient<WXPayClientConfig> { | ||
| 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<WXPayClientConfig> { | @@ -109,11 +110,20 @@ public class WXPubPayClient extends AbstractPayClient<WXPayClientConfig> { | ||
| 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) { |