diff --git a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayOrderCoreServiceImpl.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayOrderCoreServiceImpl.java index 3c86c5593..17f7885fb 100644 --- a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayOrderCoreServiceImpl.java +++ b/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; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.coreservice.modules.pay.convert.order.PayOrderCoreConvert; import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayAppDO; 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 import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayOrderSubmitRespDTO; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import cn.iocoder.yudao.framework.pay.config.PayProperties; import cn.iocoder.yudao.framework.pay.core.client.PayClient; import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory; import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO; @@ -43,6 +45,9 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU @Slf4j public class PayOrderCoreServiceImpl implements PayOrderCoreService { + @Resource + private PayProperties payProperties; + @Resource private PayAppCoreService payAppCoreService; @Resource @@ -125,7 +130,7 @@ public class PayOrderCoreServiceImpl implements PayOrderCoreService { // 商户相关字段 unifiedOrderReqDTO.setMerchantOrderId(order.getMerchantOrderId()) .setSubject(order.getSubject()).setBody(order.getBody()) - .setNotifyUrl(app.getPayNotifyUrl()); + .setNotifyUrl(genChannelPayNotifyUrl(reqDTO.getChannelCode())); // 订单相关字段 unifiedOrderReqDTO.setAmount(order.getAmount()).setExpireTime(order.getExpireTime()); CommonResult unifiedOrderResult = client.unifiedOrder(unifiedOrderReqDTO); @@ -137,6 +142,17 @@ public class PayOrderCoreServiceImpl implements PayOrderCoreService { .setInvokeResponse(unifiedOrderResult.getData()); } + /** + * 根据支付渠道的编码,生成支付渠道的回调地址 + * + * @param channelCode 支付渠道的编码 + * @return 支付渠道的回调地址 + */ + private String genChannelPayNotifyUrl(String channelCode) { + // _ 转化为 - 的原因,是因为 URL 我们统一采用中划线的原则 + return payProperties.getPayNotifyUrl() + "/" + StrUtil.replace(channelCode, "_", "-"); + } + private String generateOrderExtensionNo() { // wx // 2014 diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/config/PayProperties.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/config/PayProperties.java new file mode 100644 index 000000000..c56868477 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/config/PayProperties.java @@ -0,0 +1,32 @@ +package cn.iocoder.yudao.framework.pay.config; + +import lombok.Data; +import org.hibernate.validator.constraints.URL; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.validation.annotation.Validated; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import java.time.Duration; + +@ConfigurationProperties(prefix = "yudao.pay") +@Validated +@Data +public class PayProperties { + + /** + * 支付回调地址 + * 注意,支付渠道统一回调到 payNotifyUrl 地址,由支付模块统一处理;然后,自己的支付模块,在回调 PayAppDO.payNotifyUrl 地址 + */ + @NotEmpty(message = "支付回调地址不能为空") + @URL(message = "支付回调地址的格式必须是 URL") + private String payNotifyUrl; + /** + * 退款回调地址 + * 注意点,同 {@link #payNotifyUrl} 属性 + */ + @NotNull(message = "短信发送频率不能为空") + @URL(message = "退款回调地址的格式必须是 URL") + private String refundNotifyUrl; + +} diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/config/YudaoPayAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/config/YudaoPayAutoConfiguration.java index fc2b20253..d49c1c2b2 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/config/YudaoPayAutoConfiguration.java +++ b/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; import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory; import cn.iocoder.yudao.framework.pay.core.client.impl.PayClientFactoryImpl; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -10,7 +11,7 @@ import org.springframework.context.annotation.Configuration; * * @author 芋道源码 */ -@Configuration +@EnableConfigurationProperties(PayProperties.class) public class YudaoPayAutoConfiguration { @Bean diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayOrderUnifiedReqDTO.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayOrderUnifiedReqDTO.java index bb8c0d8dc..117add515 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayOrderUnifiedReqDTO.java +++ b/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; import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; import java.util.Date; +import java.util.Map; /** * 统一下单 Request DTO @@ -63,6 +64,11 @@ public class PayOrderUnifiedReqDTO { private Date expireTime; // ========== 拓展参数 ========== - // TODO 芋艿:待完善 + /** + * 支付渠道的额外参数 + * + * 例如说,微信公众号需要传递 openid 参数 + */ + private Map channelExtras; } diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXPubPayClient.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXPubPayClient.java index d2a2de521..156522e39 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXPubPayClient.java +++ b/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; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.date.DateUtil; +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; @@ -94,7 +95,7 @@ public class WXPubPayClient extends AbstractPayClient { .totalFee(reqDTO.getAmount().intValue()) // 单位分 .timeExpire(DateUtil.format(reqDTO.getExpireTime(), "yyyyMMddHHmmss")) .spbillCreateIp(reqDTO.getUserIp()) - .openid("ockUAwIZ-0OeMZl9ogcZ4ILrGba0") // TODO 芋艿:先随便写死 + .openid(getOpenid(reqDTO)) .notifyUrl(reqDTO.getNotifyUrl()) .build(); // 执行请求 @@ -109,11 +110,20 @@ public class WXPubPayClient extends AbstractPayClient { request.setDescription(reqDTO.getBody()); request.setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(reqDTO.getAmount().intValue())); // 单位分 request.setTimeExpire(DateUtil.format(reqDTO.getExpireTime(), "yyyyMMddHHmmss")); - request.setPayer(new WxPayUnifiedOrderV3Request.Payer().setOpenid("ockUAwIZ-0OeMZl9ogcZ4ILrGba0")); // TODO 芋艿:先随便写死 + 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; + } + } diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/controller/order/PayOrderController.java b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/controller/order/PayOrderController.java index a38396e3d..8f8dad0f1 100644 --- a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/controller/order/PayOrderController.java +++ b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/controller/order/PayOrderController.java @@ -32,7 +32,7 @@ public class PayOrderController { @PostMapping("/submit") @ApiOperation("提交支付订单") // @PreAuthenticated // TODO 暂时不加登陆验证,前端暂时没做好 - public CommonResult submit(@RequestBody PayOrderSubmitReqVO reqVO) { + public CommonResult submitPayOrder(@RequestBody PayOrderSubmitReqVO reqVO) { // 获得订单 PayOrderDO payOrder = payOrderCoreService.getPayOrder(reqVO.getId()); @@ -47,4 +47,13 @@ public class PayOrderController { return success(PayOrderSubmitRespVO.builder().invokeResponse(respDTO.getInvokeResponse()).build()); } + // ========== 支付渠道的回调 ========== + + @PostMapping("/notify/wx-pub") + @ApiOperation("通知微信公众号的结果") + public String notifyWxPayOrder(@RequestBody String xmlData) { + System.out.println(xmlData); + return "success"; + } + } diff --git a/yudao-user-server/src/main/resources/application-dev.yaml b/yudao-user-server/src/main/resources/application-dev.yaml index b6bfbd1dd..6cbd627f8 100644 --- a/yudao-user-server/src/main/resources/application-dev.yaml +++ b/yudao-user-server/src/main/resources/application-dev.yaml @@ -138,3 +138,6 @@ yudao: - ${spring.boot.admin.context-path}/** # 不处理 Spring Boot Admin 的请求 - ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求 demo: true # 开启演示模式 + pay: + pay-notify-url: http://niubi.natapp1.cc/api/pay/order/notify + refund-notify-url: http://niubi.natapp1.cc/api/pay/refund/notify diff --git a/yudao-user-server/src/main/resources/application-local.yaml b/yudao-user-server/src/main/resources/application-local.yaml index 3e9fba818..ac20815c4 100644 --- a/yudao-user-server/src/main/resources/application-local.yaml +++ b/yudao-user-server/src/main/resources/application-local.yaml @@ -152,3 +152,6 @@ yudao: - ${spring.boot.admin.context-path}/** # 不处理 Spring Boot Admin 的请求 - ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求 demo: false # 关闭演示模式 + pay: + pay-notify-url: http://niubi.natapp1.cc/api/pay/order/notify + refund-notify-url: http://niubi.natapp1.cc/api/pay/refund/notify diff --git a/yudao-user-server/src/main/resources/static/pay.html b/yudao-user-server/src/main/resources/static/pay.html index 20587749b..2a80e869d 100644 --- a/yudao-user-server/src/main/resources/static/pay.html +++ b/yudao-user-server/src/main/resources/static/pay.html @@ -18,6 +18,8 @@ let payOrderId = undefined; // let server = 'http://127.0.0.1:28080'; let server = 'http://niubi.natapp1.cc'; + // TODO openid + let openid = "ockUAwIZ-0OeMZl9ogcZ4ILrGba0"; $(function() { // 获得 JsapiTicket // 参考 https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html 文档 @@ -73,6 +75,8 @@ } // 提交支付 + // 参考 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=6 文档 + // 参考 https://segmentfault.com/a/1190000020704650 文档 $.ajax({ url: server + "/api/pay/order/submit", method: 'POST', @@ -81,6 +85,9 @@ data: JSON.stringify({ "id": payOrderId, "channelCode": 'wx_pub', + "channelExtras": { + "openid": openid + } }), success: function( result ) { if (result.code !== 0) {