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 | 2 | |
| 3 | 3 | import cn.hutool.core.date.DateUtil; |
| 4 | 4 | import cn.hutool.core.util.RandomUtil; |
| 5 | +import cn.hutool.core.util.StrUtil; | |
| 5 | 6 | import cn.iocoder.yudao.coreservice.modules.pay.convert.order.PayOrderCoreConvert; |
| 6 | 7 | import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayAppDO; |
| 7 | 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 | 20 | import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayOrderSubmitRespDTO; |
| 20 | 21 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 21 | 22 | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; |
| 23 | +import cn.iocoder.yudao.framework.pay.config.PayProperties; | |
| 22 | 24 | import cn.iocoder.yudao.framework.pay.core.client.PayClient; |
| 23 | 25 | import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory; |
| 24 | 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 | 46 | public class PayOrderCoreServiceImpl implements PayOrderCoreService { |
| 45 | 47 | |
| 46 | 48 | @Resource |
| 49 | + private PayProperties payProperties; | |
| 50 | + | |
| 51 | + @Resource | |
| 47 | 52 | private PayAppCoreService payAppCoreService; |
| 48 | 53 | @Resource |
| 49 | 54 | private PayChannelCoreService payChannelCoreService; |
| ... | ... | @@ -125,7 +130,7 @@ public class PayOrderCoreServiceImpl implements PayOrderCoreService { |
| 125 | 130 | // 商户相关字段 |
| 126 | 131 | unifiedOrderReqDTO.setMerchantOrderId(order.getMerchantOrderId()) |
| 127 | 132 | .setSubject(order.getSubject()).setBody(order.getBody()) |
| 128 | - .setNotifyUrl(app.getPayNotifyUrl()); | |
| 133 | + .setNotifyUrl(genChannelPayNotifyUrl(reqDTO.getChannelCode())); | |
| 129 | 134 | // 订单相关字段 |
| 130 | 135 | unifiedOrderReqDTO.setAmount(order.getAmount()).setExpireTime(order.getExpireTime()); |
| 131 | 136 | CommonResult<?> unifiedOrderResult = client.unifiedOrder(unifiedOrderReqDTO); |
| ... | ... | @@ -137,6 +142,17 @@ public class PayOrderCoreServiceImpl implements PayOrderCoreService { |
| 137 | 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 | 156 | private String generateOrderExtensionNo() { |
| 141 | 157 | // wx |
| 142 | 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 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory; |
| 4 | 4 | import cn.iocoder.yudao.framework.pay.core.client.impl.PayClientFactoryImpl; |
| 5 | +import org.springframework.boot.context.properties.EnableConfigurationProperties; | |
| 5 | 6 | import org.springframework.context.annotation.Bean; |
| 6 | 7 | import org.springframework.context.annotation.Configuration; |
| 7 | 8 | |
| ... | ... | @@ -10,7 +11,7 @@ import org.springframework.context.annotation.Configuration; |
| 10 | 11 | * |
| 11 | 12 | * @author 芋道源码 |
| 12 | 13 | */ |
| 13 | -@Configuration | |
| 14 | +@EnableConfigurationProperties(PayProperties.class) | |
| 14 | 15 | public class YudaoPayAutoConfiguration { |
| 15 | 16 | |
| 16 | 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 | 8 | import javax.validation.constraints.NotEmpty; |
| 9 | 9 | import javax.validation.constraints.NotNull; |
| 10 | 10 | import java.util.Date; |
| 11 | +import java.util.Map; | |
| 11 | 12 | |
| 12 | 13 | /** |
| 13 | 14 | * 统一下单 Request DTO |
| ... | ... | @@ -63,6 +64,11 @@ public class PayOrderUnifiedReqDTO { |
| 63 | 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 | 2 | |
| 3 | 3 | import cn.hutool.core.bean.BeanUtil; |
| 4 | 4 | import cn.hutool.core.date.DateUtil; |
| 5 | +import cn.hutool.core.map.MapUtil; | |
| 5 | 6 | import cn.hutool.core.util.StrUtil; |
| 6 | 7 | import cn.iocoder.yudao.framework.common.util.io.FileUtils; |
| 7 | 8 | import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; |
| ... | ... | @@ -94,7 +95,7 @@ public class WXPubPayClient extends AbstractPayClient<WXPayClientConfig> { |
| 94 | 95 | .totalFee(reqDTO.getAmount().intValue()) // 单位分 |
| 95 | 96 | .timeExpire(DateUtil.format(reqDTO.getExpireTime(), "yyyyMMddHHmmss")) |
| 96 | 97 | .spbillCreateIp(reqDTO.getUserIp()) |
| 97 | - .openid("ockUAwIZ-0OeMZl9ogcZ4ILrGba0") // TODO 芋艿:先随便写死 | |
| 98 | + .openid(getOpenid(reqDTO)) | |
| 98 | 99 | .notifyUrl(reqDTO.getNotifyUrl()) |
| 99 | 100 | .build(); |
| 100 | 101 | // 执行请求 |
| ... | ... | @@ -109,11 +110,20 @@ public class WXPubPayClient extends AbstractPayClient<WXPayClientConfig> { |
| 109 | 110 | request.setDescription(reqDTO.getBody()); |
| 110 | 111 | request.setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(reqDTO.getAmount().intValue())); // 单位分 |
| 111 | 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 | 114 | request.setSceneInfo(new WxPayUnifiedOrderV3Request.SceneInfo().setPayerClientIp(reqDTO.getUserIp())); |
| 114 | 115 | request.setNotifyUrl(reqDTO.getNotifyUrl()); |
| 115 | 116 | // 执行请求 |
| 116 | 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 | 32 | @PostMapping("/submit") |
| 33 | 33 | @ApiOperation("提交支付订单") |
| 34 | 34 | // @PreAuthenticated // TODO 暂时不加登陆验证,前端暂时没做好 |
| 35 | - public CommonResult<PayOrderSubmitRespVO> submit(@RequestBody PayOrderSubmitReqVO reqVO) { | |
| 35 | + public CommonResult<PayOrderSubmitRespVO> submitPayOrder(@RequestBody PayOrderSubmitReqVO reqVO) { | |
| 36 | 36 | // 获得订单 |
| 37 | 37 | PayOrderDO payOrder = payOrderCoreService.getPayOrder(reqVO.getId()); |
| 38 | 38 | |
| ... | ... | @@ -47,4 +47,13 @@ public class PayOrderController { |
| 47 | 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 | 138 | - ${spring.boot.admin.context-path}/** # 不处理 Spring Boot Admin 的请求 |
| 139 | 139 | - ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求 |
| 140 | 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 | 152 | - ${spring.boot.admin.context-path}/** # 不处理 Spring Boot Admin 的请求 |
| 153 | 153 | - ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求 |
| 154 | 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 | 18 | let payOrderId = undefined; |
| 19 | 19 | // let server = 'http://127.0.0.1:28080'; |
| 20 | 20 | let server = 'http://niubi.natapp1.cc'; |
| 21 | + // TODO openid | |
| 22 | + let openid = "ockUAwIZ-0OeMZl9ogcZ4ILrGba0"; | |
| 21 | 23 | $(function() { |
| 22 | 24 | // 获得 JsapiTicket |
| 23 | 25 | // 参考 https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html 文档 |
| ... | ... | @@ -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 | 80 | $.ajax({ |
| 77 | 81 | url: server + "/api/pay/order/submit", |
| 78 | 82 | method: 'POST', |
| ... | ... | @@ -81,6 +85,9 @@ |
| 81 | 85 | data: JSON.stringify({ |
| 82 | 86 | "id": payOrderId, |
| 83 | 87 | "channelCode": 'wx_pub', |
| 88 | + "channelExtras": { | |
| 89 | + "openid": openid | |
| 90 | + } | |
| 84 | 91 | }), |
| 85 | 92 | success: function( result ) { |
| 86 | 93 | if (result.code !== 0) { | ... | ... |