mirror of
https://gitee.com/hhyykk/ipms-sjy.git
synced 2025-08-18 12:11:54 +08:00
Merge remote-tracking branch 'origin/master' into feature/springdoc
# Conflicts: # README.md # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/ProductPropertyController.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/ProductPropertyValueController.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/ProductPropertyViewRespVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyAndValueRespVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyBaseVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyCreateReqVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyListReqVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyPageReqVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyRespVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyUpdateReqVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueBaseVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueCreateReqVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValuePageReqVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueRespVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueUpdateReqVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/ProductSkuBaseVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/ProductSkuCreateOrUpdateReqVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/ProductSpuController.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuBaseVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuDetailRespVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuPageReqVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/AppProductSpuController.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppSpuPageReqVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppSpuPageRespVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppSpuRespVO.java # yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/base/property/AppProductPropertyValueDetailRespVO.java # yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/AppTradeOrderController.java # yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderPageReqVO.java # yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/TradeOrderItemRespVO.java # yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/TradeOrderRespVO.java # yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/AppPayOrderController.java # yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/notify/vo/PayNotifyOrderReqVO.java # yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/notify/vo/PayRefundOrderReqVO.java # yudao-server/pom.xml # yudao-server/src/main/java/cn/iocoder/yudao/module/shop/controller/app/AppShopOrderController.java
This commit is contained in:
@@ -1,18 +0,0 @@
|
||||
package cn.iocoder.yudao.module.pay.api;
|
||||
|
||||
import cn.iocoder.yudao.module.pay.api.order.PayOrderApi;
|
||||
import cn.iocoder.yudao.module.pay.api.order.PayOrderInfoCreateReqDTO;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* TODO 注释
|
||||
*/
|
||||
@Service
|
||||
public class PayOrderApiImpl implements PayOrderApi {
|
||||
|
||||
@Override
|
||||
public Long createPayOrder(PayOrderInfoCreateReqDTO reqDTO) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
@@ -1,19 +1,34 @@
|
||||
//package cn.iocoder.yudao.module.pay.api.order;
|
||||
//
|
||||
//import org.springframework.stereotype.Service;
|
||||
//import org.springframework.transaction.annotation.Transactional;
|
||||
//
|
||||
///**
|
||||
// * @author LeeYan9
|
||||
// * @since 2022-09-06
|
||||
// */
|
||||
//@Service
|
||||
//public class PayOrderApiImpl implements PayOrderApi {
|
||||
//
|
||||
// @Override
|
||||
// @Transactional(rollbackFor = Exception.class)
|
||||
// public Long createPayOrder(PayOrderInfoCreateReqDTO reqDTO) {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
//}
|
||||
package cn.iocoder.yudao.module.pay.api.order;
|
||||
|
||||
import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO;
|
||||
import cn.iocoder.yudao.module.pay.convert.order.PayOrderConvert;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
|
||||
import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 支付单 API 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Service
|
||||
public class PayOrderApiImpl implements PayOrderApi {
|
||||
|
||||
@Resource
|
||||
private PayOrderService payOrderService;
|
||||
|
||||
@Override
|
||||
public Long createOrder(PayOrderCreateReqDTO reqDTO) {
|
||||
return payOrderService.createPayOrder(reqDTO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayOrderRespDTO getOrder(Long id) {
|
||||
PayOrderDO order = payOrderService.getOrder(id);
|
||||
return PayOrderConvert.INSTANCE.convert2(order);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,29 @@
|
||||
package cn.iocoder.yudao.module.pay.api.refund;
|
||||
|
||||
import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundRespDTO;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
/**
|
||||
* 退款单 API 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
public class PayRefundApiImpl implements PayRefundApi {
|
||||
|
||||
@Override
|
||||
public Long createPayRefund(PayRefundCreateReqDTO reqDTO) {
|
||||
// TODO 芋艿:暂未实现
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayRefundRespDTO getPayRefund(Long id) {
|
||||
// TODO 芋艿:暂未实现
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,91 @@
|
||||
package cn.iocoder.yudao.module.pay.controller.admin.notify;
|
||||
|
||||
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
|
||||
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.PayNotifyDataDTO;
|
||||
import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
|
||||
import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.annotation.security.PermitAll;
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.PAY_CHANNEL_CLIENT_NOT_FOUND;
|
||||
|
||||
@Api(tags = "管理后台 - 支付通知")
|
||||
@RestController
|
||||
@RequestMapping("/pay/notify")
|
||||
@Validated
|
||||
@Slf4j
|
||||
public class PayNotifyController {
|
||||
|
||||
@Resource
|
||||
private PayOrderService orderService;
|
||||
@Resource
|
||||
private PayRefundService refundService;
|
||||
|
||||
@Resource
|
||||
private PayClientFactory payClientFactory;
|
||||
|
||||
/**
|
||||
* 统一的跳转页面,支付宝跳转参数说明
|
||||
*
|
||||
* <a href="https://opendocs.alipay.com/open/203/105285#前台回跳参数说明">支付宝 - 前台回跳参数说明</a>
|
||||
*
|
||||
* @param channelId 渠道编号
|
||||
* @return 返回跳转页面
|
||||
*/
|
||||
@GetMapping(value = "/return/{channelId}")
|
||||
@ApiOperation("渠道统一的支付成功返回地址")
|
||||
@Deprecated // TODO yunai:如果是 way 的情况,应该是跳转回前端地址
|
||||
public String returnCallback(@PathVariable("channelId") Long channelId,
|
||||
@RequestParam Map<String, String> params) {
|
||||
log.info("[returnCallback][app_id({}) 跳转]", params.get("app_id"));
|
||||
return String.format("渠道[%s]支付成功", channelId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一的渠道支付回调,支付宝的退款回调
|
||||
*
|
||||
* @param channelId 渠道编号
|
||||
* @param params form 参数
|
||||
* @param body request body
|
||||
* @return 成功返回 "success"
|
||||
*/
|
||||
@PostMapping(value = "/callback/{channelId}")
|
||||
@ApiOperation(value = "支付渠道的统一回调接口", notes = "包括支付回调,退款回调")
|
||||
@PermitAll
|
||||
@OperateLog(enable = false) // 回调地址,无需记录操作日志
|
||||
public String notifyCallback(@PathVariable("channelId") Long channelId,
|
||||
@RequestParam Map<String, String> params,
|
||||
@RequestBody String body) throws Exception {
|
||||
// 校验支付渠道是否存在
|
||||
PayClient payClient = payClientFactory.getPayClient(channelId);
|
||||
if (payClient == null) {
|
||||
log.error("[notifyCallback][渠道编号({}) 找不到对应的支付客户端]", channelId);
|
||||
throw exception(PAY_CHANNEL_CLIENT_NOT_FOUND);
|
||||
}
|
||||
// 校验通知数据是否合法
|
||||
PayNotifyDataDTO notifyData = PayNotifyDataDTO.builder().params(params).body(body).build();
|
||||
payClient.verifyNotifyData(notifyData);
|
||||
|
||||
// 情况一:如果是退款,则发起退款通知
|
||||
if (payClient.isRefundNotify(notifyData)) {
|
||||
refundService.notifyPayRefund(channelId, PayNotifyDataDTO.builder().params(params).body(body).build());
|
||||
return "success";
|
||||
}
|
||||
|
||||
// 情况二:如果非退款,则发起支付通知
|
||||
orderService.notifyPayOrder(channelId, PayNotifyDataDTO.builder().params(params).body(body).build());
|
||||
return "success";
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,10 @@
|
||||
### /pay/create 提交支付订单
|
||||
POST {{appApi}}/pay/order/submit
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer {{appToken}}
|
||||
tenant-id: {{appTenentId}}
|
||||
|
||||
{
|
||||
"id": 125,
|
||||
"channelCode": "alipay_qr"
|
||||
}
|
@@ -2,29 +2,25 @@ package cn.iocoder.yudao.module.pay.controller.app.order;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
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.PayNotifyDataDTO;
|
||||
import cn.iocoder.yudao.module.pay.controller.app.order.vo.AppPayOrderSubmitReqVO;
|
||||
import cn.iocoder.yudao.module.pay.controller.app.order.vo.AppPayOrderSubmitRespVO;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
|
||||
import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
|
||||
import cn.iocoder.yudao.module.pay.service.order.dto.PayOrderSubmitReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.service.order.dto.PayOrderSubmitRespDTO;
|
||||
import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
|
||||
import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*;
|
||||
|
||||
@Tag(name = "用户 APP - 支付订单")
|
||||
@RestController
|
||||
@@ -35,11 +31,6 @@ public class AppPayOrderController {
|
||||
|
||||
@Resource
|
||||
private PayOrderService orderService;
|
||||
@Resource
|
||||
private PayRefundService refundService;
|
||||
|
||||
@Resource
|
||||
private PayClientFactory payClientFactory;
|
||||
|
||||
@PostMapping("/submit")
|
||||
@Operation(summary = "提交支付订单")
|
||||
@@ -59,64 +50,4 @@ public class AppPayOrderController {
|
||||
return success(AppPayOrderSubmitRespVO.builder().invokeResponse(respDTO.getInvokeResponse()).build());
|
||||
}
|
||||
|
||||
// ========== 支付渠道的回调 ==========
|
||||
// TODO @芋艿:是不是放到 notify 模块更合适
|
||||
//TODO 芋道源码 换成了统一的地址了 /notify/{channelId},测试通过可以删除
|
||||
@PostMapping("/notify/wx-pub/{channelId}")
|
||||
@Operation(summary = "通知微信公众号支付的结果")
|
||||
public String notifyWxPayOrder(@PathVariable("channelId") Long channelId,
|
||||
@RequestBody String xmlData) throws Exception {
|
||||
orderService.notifyPayOrder(channelId, PayNotifyDataDTO.builder().body(xmlData).build());
|
||||
return "success";
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一的跳转页面, 支付宝跳转参数说明
|
||||
* https://opendocs.alipay.com/open/203/105285#%E5%89%8D%E5%8F%B0%E5%9B%9E%E8%B7%B3%E5%8F%82%E6%95%B0%E8%AF%B4%E6%98%8E
|
||||
* @param channelId 渠道id
|
||||
* @return 返回跳转页面
|
||||
*/
|
||||
@GetMapping(value = "/return/{channelId}")
|
||||
@Operation(summary = "渠道统一的支付成功返回地址")
|
||||
public String returnAliPayOrder(@PathVariable("channelId") Long channelId, @RequestParam Map<String, String> params){
|
||||
//TODO 可以根据渠道和 app_id 返回不同的页面
|
||||
log.info("app_id is {}", params.get("app_id"));
|
||||
return String.format("渠道[%s]支付成功", channelId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一的渠道支付回调,支付宝的退款回调
|
||||
*
|
||||
* @param channelId 渠道编号
|
||||
* @param params form 参数
|
||||
* @param originData http request body
|
||||
* @return 成功返回 "success"
|
||||
*/
|
||||
@PostMapping(value = "/notify/{channelId}")
|
||||
@Operation(summary = "渠道统一的支付成功,或退款成功 通知url")
|
||||
public String notifyChannelPay(@PathVariable("channelId") Long channelId,
|
||||
@RequestParam Map<String, String> params,
|
||||
@RequestBody String originData) throws Exception {
|
||||
// 校验支付渠道是否存在
|
||||
PayClient payClient = payClientFactory.getPayClient(channelId);
|
||||
if (payClient == null) {
|
||||
log.error("[notifyPayOrder][渠道编号({}) 找不到对应的支付客户端]", channelId);
|
||||
throw exception(PAY_CHANNEL_CLIENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
// 校验通知数据是否合法
|
||||
PayNotifyDataDTO notifyData = PayNotifyDataDTO.builder().params(params).body(originData).build();
|
||||
payClient.verifyNotifyData(notifyData);
|
||||
|
||||
// 如果是退款,则发起退款通知
|
||||
if (payClient.isRefundNotify(notifyData)) {
|
||||
refundService.notifyPayRefund(channelId, PayNotifyDataDTO.builder().params(params).body(originData).build());
|
||||
return "success";
|
||||
}
|
||||
|
||||
// 如果非退款,则发起支付通知
|
||||
orderService.notifyPayOrder(channelId, PayNotifyDataDTO.builder().params(params).body(originData).build());
|
||||
return "success";
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,16 +1,18 @@
|
||||
package cn.iocoder.yudao.module.pay.convert.order;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderDetailsRespVO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderExcelVO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderPageItemRespVO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderRespVO;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO;
|
||||
import cn.iocoder.yudao.module.pay.service.order.dto.PayOrderCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.service.order.dto.PayOrderSubmitReqDTO;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
@@ -40,13 +42,14 @@ public interface PayOrderConvert {
|
||||
List<PayOrderExcelVO> convertList02(List<PayOrderDO> list);
|
||||
|
||||
/**
|
||||
* 订单DO转自定义分页对象
|
||||
* 订单 DO 转自定义分页对象
|
||||
*
|
||||
* @param bean 订单DO
|
||||
* @return 分页对象
|
||||
*/
|
||||
PayOrderPageItemRespVO pageConvertItemPage(PayOrderDO bean);
|
||||
|
||||
// TODO 芋艿:优化下 convert 逻辑
|
||||
default PayOrderExcelVO excelConvert(PayOrderDO bean) {
|
||||
if (bean == null) {
|
||||
return null;
|
||||
@@ -88,8 +91,11 @@ public interface PayOrderConvert {
|
||||
|
||||
PayOrderDO convert(PayOrderCreateReqDTO bean);
|
||||
|
||||
@Mapping(target = "id", ignore = true)
|
||||
PayOrderExtensionDO convert(PayOrderSubmitReqDTO bean);
|
||||
|
||||
PayOrderUnifiedReqDTO convert2(PayOrderSubmitReqDTO bean);
|
||||
|
||||
PayOrderRespDTO convert2(PayOrderDO bean);
|
||||
|
||||
}
|
||||
|
@@ -1,9 +1,9 @@
|
||||
package cn.iocoder.yudao.module.pay.dal.dataobject.merchant;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
|
||||
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
@@ -26,7 +26,7 @@ import lombok.*;
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class PayChannelDO extends BaseDO {
|
||||
public class PayChannelDO extends TenantBaseDO {
|
||||
|
||||
/**
|
||||
* 渠道编号,数据库自增
|
||||
|
@@ -93,7 +93,7 @@ public class PayOrderDO extends BaseDO {
|
||||
/**
|
||||
* 支付金额,单位:分
|
||||
*/
|
||||
private Long amount;
|
||||
private Integer amount;
|
||||
/**
|
||||
* 渠道手续费,单位:百分比
|
||||
*
|
||||
|
@@ -132,11 +132,11 @@ public class PayRefundDO extends BaseDO {
|
||||
/**
|
||||
* 支付金额,单位:分
|
||||
*/
|
||||
private Long payAmount;
|
||||
private Integer payAmount;
|
||||
/**
|
||||
* 退款金额,单位:分
|
||||
*/
|
||||
private Long refundAmount;
|
||||
private Integer refundAmount;
|
||||
|
||||
/**
|
||||
* 退款原因
|
||||
@@ -194,6 +194,4 @@ public class PayRefundDO extends BaseDO {
|
||||
private LocalDateTime notifyTime;
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@@ -1,29 +0,0 @@
|
||||
package cn.iocoder.yudao.module.pay.enums.order;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 支付订单的状态枚举
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum PayOrderStatusEnum implements IntArrayValuable {
|
||||
|
||||
WAITING(0, "未支付"),
|
||||
SUCCESS(10, "支付成功"),
|
||||
CLOSED(20, "支付关闭"), // 未付款交易超时关闭,或支付完成后全额退款 TODO 芋艿:需要优化下
|
||||
;
|
||||
|
||||
private final Integer status;
|
||||
private final String name;
|
||||
|
||||
@Override
|
||||
public int[] array() {
|
||||
return new int[0];
|
||||
}
|
||||
|
||||
}
|
@@ -1,17 +0,0 @@
|
||||
package cn.iocoder.yudao.module.pay.enums.refund;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum PayRefundStatusEnum {
|
||||
|
||||
CREATE(0, "退款订单生成"),
|
||||
SUCCESS(1, "退款成功"),
|
||||
FAILURE(2, "退款失败"),
|
||||
CLOSE(99, "退款关闭");
|
||||
|
||||
private final Integer status;
|
||||
private final String name;
|
||||
}
|
@@ -15,8 +15,8 @@ import javax.annotation.Resource;
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Component
|
||||
@TenantJob // 多租户
|
||||
@Slf4j
|
||||
@TenantJob
|
||||
public class PayNotifyJob implements JobHandler {
|
||||
|
||||
@Resource
|
||||
|
@@ -2,8 +2,8 @@
|
||||
* pay 模块,我们放支付业务,提供业务的支付能力。
|
||||
* 例如说:商户、应用、支付、退款等等
|
||||
*
|
||||
* 1. Controller URL:以 /member/ 开头,避免和其它 Module 冲突
|
||||
* 2. DataObject 表名:以 member_ 开头,方便在数据库中区分
|
||||
* 1. Controller URL:以 /pay/ 开头,避免和其它 Module 冲突
|
||||
* 2. DataObject 表名:以 pay_ 开头,方便在数据库中区分
|
||||
*
|
||||
* 注意,由于 Pay 模块和 Trade 模块,容易重名,所以类名都加载 Pay 的前缀~
|
||||
*/
|
||||
|
@@ -22,7 +22,7 @@ public interface PayChannelService {
|
||||
/**
|
||||
* 初始化支付客户端
|
||||
*/
|
||||
void initPayClients();
|
||||
void initLocalCache();
|
||||
|
||||
/**
|
||||
* 创建支付渠道
|
||||
|
@@ -1,6 +1,5 @@
|
||||
package cn.iocoder.yudao.module.pay.service.merchant;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
@@ -10,7 +9,7 @@ import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
|
||||
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
|
||||
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.merchant.vo.channel.PayChannelCreateReqVO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.merchant.vo.channel.PayChannelExportReqVO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.merchant.vo.channel.PayChannelPageReqVO;
|
||||
@@ -20,7 +19,6 @@ import cn.iocoder.yudao.module.pay.dal.dataobject.merchant.PayChannelDO;
|
||||
import cn.iocoder.yudao.module.pay.dal.mysql.merchant.PayChannelMapper;
|
||||
import cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
@@ -66,53 +64,47 @@ public class PayChannelServiceImpl implements PayChannelService {
|
||||
@Resource
|
||||
private Validator validator;
|
||||
|
||||
@Resource
|
||||
@Lazy // 注入自己,所以延迟加载
|
||||
private PayChannelService self;
|
||||
|
||||
/**
|
||||
* 初始化 {@link #payClientFactory} 缓存
|
||||
*/
|
||||
@Override
|
||||
@PostConstruct
|
||||
@TenantIgnore // 忽略自动化租户,全局初始化本地缓存
|
||||
public void initPayClients() {
|
||||
// 获取支付渠道,如果有更新
|
||||
List<PayChannelDO> payChannels = loadPayChannelIfUpdate(maxUpdateTime);
|
||||
if (CollUtil.isEmpty(payChannels)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建或更新支付 Client
|
||||
payChannels.forEach(payChannel -> payClientFactory.createOrUpdatePayClient(payChannel.getId(),
|
||||
payChannel.getCode(), payChannel.getConfig()));
|
||||
|
||||
// 写入缓存
|
||||
maxUpdateTime = CollectionUtils.getMaxValue(payChannels, PayChannelDO::getUpdateTime);
|
||||
log.info("[initPayClients][初始化 PayChannel 数量为 {}]", payChannels.size());
|
||||
public void initLocalCache() {
|
||||
initLocalCacheIfUpdate(null);
|
||||
}
|
||||
|
||||
@Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
|
||||
public void schedulePeriodicRefresh() {
|
||||
self.initPayClients();
|
||||
initLocalCacheIfUpdate(this.maxUpdateTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果支付渠道发生变化,从数据库中获取最新的全量支付渠道。
|
||||
* 如果未发生变化,则返回空
|
||||
* 刷新本地缓存
|
||||
*
|
||||
* @param maxUpdateTime 当前支付渠道的最大更新时间
|
||||
* @return 支付渠道列表
|
||||
* @param maxUpdateTime 最大更新时间
|
||||
* 1. 如果 maxUpdateTime 为 null,则“强制”刷新缓存
|
||||
* 2. 如果 maxUpdateTime 不为 null,判断自 maxUpdateTime 是否有数据发生变化,有的情况下才刷新缓存
|
||||
*/
|
||||
private List<PayChannelDO> loadPayChannelIfUpdate(LocalDateTime maxUpdateTime) {
|
||||
// 第一步,判断是否要更新。
|
||||
if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
|
||||
log.info("[loadPayChannelIfUpdate][首次加载全量支付渠道]");
|
||||
} else { // 判断数据库中是否有更新的支付渠道
|
||||
if (channelMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
|
||||
return null;
|
||||
private void initLocalCacheIfUpdate(LocalDateTime maxUpdateTime) {
|
||||
// 注意:忽略自动多租户,因为要全局初始化缓存
|
||||
TenantUtils.executeIgnore(() -> {
|
||||
// 第一步:基于 maxUpdateTime 判断缓存是否刷新。
|
||||
// 如果没有增量的数据变化,则不进行本地缓存的刷新
|
||||
if (maxUpdateTime != null
|
||||
&& channelMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
|
||||
log.info("[initLocalCacheIfUpdate][数据未发生变化({}),本地缓存不刷新]", maxUpdateTime);
|
||||
return;
|
||||
}
|
||||
log.info("[loadPayChannelIfUpdate][增量加载全量支付渠道]");
|
||||
}
|
||||
// 第二步,如果有更新,则从数据库加载所有支付渠道
|
||||
return channelMapper.selectList();
|
||||
List<PayChannelDO> channels = channelMapper.selectList();
|
||||
log.info("[initLocalCacheIfUpdate][缓存支付渠道,数量为:{}]", channels.size());
|
||||
|
||||
// 第二步:构建缓存。创建或更新支付 Client
|
||||
channels.forEach(payChannel -> payClientFactory.createOrUpdatePayClient(payChannel.getId(),
|
||||
payChannel.getCode(), payChannel.getConfig()));
|
||||
|
||||
// 第三步:设置最新的 maxUpdateTime,用于下次的增量判断。
|
||||
this.maxUpdateTime = CollectionUtils.getMaxValue(channels, PayChannelDO::getUpdateTime);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -2,8 +2,13 @@ package cn.iocoder.yudao.module.pay.service.notify;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.exceptions.ExceptionUtil;
|
||||
import cn.hutool.http.HttpResponse;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyLogDO;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyTaskDO;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
|
||||
@@ -14,11 +19,8 @@ import cn.iocoder.yudao.module.pay.dal.redis.notify.PayNotifyLockRedisDAO;
|
||||
import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyStatusEnum;
|
||||
import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum;
|
||||
import cn.iocoder.yudao.module.pay.service.notify.dto.PayNotifyTaskCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.service.notify.vo.PayNotifyOrderReqVO;
|
||||
import cn.iocoder.yudao.module.pay.service.notify.vo.PayRefundOrderReqVO;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.module.pay.api.notify.dto.PayOrderNotifyReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.api.notify.dto.PayRefundNotifyReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
|
||||
import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -31,7 +33,9 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -166,7 +170,8 @@ public class PayNotifyServiceImpl implements PayNotifyService {
|
||||
// 虽然已经通过分布式加锁,但是可能同时满足通知的条件,然后都去获得锁。此时,第一个执行完后,第二个还是能拿到锁,然后会再执行一次。
|
||||
PayNotifyTaskDO dbTask = payNotifyTaskCoreMapper.selectById(task.getId());
|
||||
if (LocalDateTimeUtils.afterNow(dbTask.getNextNotifyTime())) {
|
||||
log.info("[executeNotify][dbTask({}) 任务被忽略,原因是未到达下次通知时间,可能是因为并发执行了]", JsonUtils.toJsonString(dbTask));
|
||||
log.info("[executeNotifySync][dbTask({}) 任务被忽略,原因是未到达下次通知时间,可能是因为并发执行了]",
|
||||
JsonUtils.toJsonString(dbTask));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -186,11 +191,12 @@ public class PayNotifyServiceImpl implements PayNotifyService {
|
||||
invokeException = e;
|
||||
}
|
||||
|
||||
// 处理
|
||||
Integer newStatus = this.processNotifyResult(task, invokeResult, invokeException);
|
||||
// 处理结果
|
||||
Integer newStatus = processNotifyResult(task, invokeResult, invokeException);
|
||||
|
||||
// 记录 PayNotifyLog 日志
|
||||
String response = invokeException != null ? ExceptionUtil.getRootCauseMessage(invokeException) : JsonUtils.toJsonString(invokeResult);
|
||||
String response = invokeException != null ? ExceptionUtil.getRootCauseMessage(invokeException) :
|
||||
JsonUtils.toJsonString(invokeResult);
|
||||
payNotifyLogCoreMapper.insert(PayNotifyLogDO.builder().taskId(task.getId())
|
||||
.notifyTimes(task.getNotifyTimes() + 1).status(newStatus).response(response).build());
|
||||
}
|
||||
@@ -202,22 +208,28 @@ public class PayNotifyServiceImpl implements PayNotifyService {
|
||||
* @return HTTP 响应
|
||||
*/
|
||||
private CommonResult<?> executeNotifyInvoke(PayNotifyTaskDO task) {
|
||||
// 拼接参数
|
||||
// 拼接 body 参数
|
||||
Object request;
|
||||
if (Objects.equals(task.getType(), PayNotifyTypeEnum.ORDER.getType())) {
|
||||
request = PayNotifyOrderReqVO.builder().merchantOrderId(task.getMerchantOrderId())
|
||||
.payOrderId(task.getDataId()).build();
|
||||
request = PayOrderNotifyReqDTO.builder().merchantOrderId(task.getMerchantOrderId())
|
||||
.payOrderId(task.getDataId()).build();
|
||||
} else if (Objects.equals(task.getType(), PayNotifyTypeEnum.REFUND.getType())) {
|
||||
request = PayRefundOrderReqVO.builder().merchantOrderId(task.getMerchantOrderId())
|
||||
request = PayRefundNotifyReqDTO.builder().merchantOrderId(task.getMerchantOrderId())
|
||||
.payRefundId(task.getDataId()).build();
|
||||
} else {
|
||||
throw new RuntimeException("未知的通知任务类型:" + JsonUtils.toJsonString(task));
|
||||
}
|
||||
// 请求地址
|
||||
String response = HttpUtil.post(task.getNotifyUrl(), JsonUtils.toJsonString(request),
|
||||
(int) NOTIFY_TIMEOUT_MILLIS);
|
||||
// 解析结果
|
||||
return JsonUtils.parseObject(response, CommonResult.class);
|
||||
// 拼接 header 参数
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
TenantUtils.addTenantHeader(headers);
|
||||
|
||||
// 发起请求
|
||||
try (HttpResponse response = HttpUtil.createPost(task.getNotifyUrl())
|
||||
.body(JsonUtils.toJsonString(request)).addHeaders(headers)
|
||||
.timeout((int) NOTIFY_TIMEOUT_MILLIS).execute()) {
|
||||
// 解析结果
|
||||
return JsonUtils.parseObject(response.body(), CommonResult.class);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,27 +0,0 @@
|
||||
package cn.iocoder.yudao.module.pay.service.notify.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@Schema(description = "支付单的通知 Request VO,业务方接入支付回调时,使用该 VO 对象")
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class PayNotifyOrderReqVO {
|
||||
|
||||
@Schema(description = "商户订单编号", required = true, example = "10")
|
||||
@NotEmpty(message = "商户订单号不能为空")
|
||||
private String merchantOrderId;
|
||||
|
||||
@Schema(description = "支付订单编号", required = true, example = "20")
|
||||
@NotNull(message = "支付订单编号不能为空")
|
||||
private Long payOrderId;
|
||||
|
||||
}
|
||||
|
@@ -1,30 +0,0 @@
|
||||
package cn.iocoder.yudao.module.pay.service.notify.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@Schema(description = "退款单的通知 Request VO,业务方接入退款回调时,使用该 VO 对象")
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class PayRefundOrderReqVO {
|
||||
|
||||
@Schema(description = "商户退款单编号", required = true, example = "10")
|
||||
@NotEmpty(message = "商户退款单编号不能为空")
|
||||
private String merchantOrderId;
|
||||
|
||||
@Schema(description = "支付退款编号", required = true, example = "20")
|
||||
@NotNull(message = "支付退款编号不能为空")
|
||||
private Long payRefundId;
|
||||
|
||||
@Schema(description = "退款状态(成功,失败)", required = true, example = "10")
|
||||
private Integer status;
|
||||
|
||||
}
|
||||
|
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* 这里的 VO 包有点特殊,是提供给接入支付模块的业务,提供回调接口时,可以直接使用 VO
|
||||
*
|
||||
* 例如说,支付单的回调,使用 TODO 芋艿:想下怎么优化下
|
||||
*/
|
||||
package cn.iocoder.yudao.module.pay.service.notify.vo;
|
@@ -6,7 +6,7 @@ import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderPageReqVO;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
|
||||
import cn.iocoder.yudao.module.pay.service.order.dto.PayOrderCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.service.order.dto.PayOrderSubmitReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.service.order.dto.PayOrderSubmitRespDTO;
|
||||
|
||||
|
@@ -1,9 +1,10 @@
|
||||
package cn.iocoder.yudao.module.pay.service.order;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
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;
|
||||
@@ -11,6 +12,8 @@ import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayNotifyDataDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderNotifyRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
|
||||
import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderExportReqVO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderPageReqVO;
|
||||
import cn.iocoder.yudao.module.pay.convert.order.PayOrderConvert;
|
||||
@@ -28,8 +31,6 @@ import cn.iocoder.yudao.module.pay.service.merchant.PayAppService;
|
||||
import cn.iocoder.yudao.module.pay.service.merchant.PayChannelService;
|
||||
import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService;
|
||||
import cn.iocoder.yudao.module.pay.service.notify.dto.PayNotifyTaskCreateReqDTO;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.pay.service.order.dto.PayOrderCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.service.order.dto.PayOrderSubmitReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.service.order.dto.PayOrderSubmitRespDTO;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -43,6 +44,8 @@ import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
|
||||
/**
|
||||
* 支付订单 Service 实现类
|
||||
*
|
||||
@@ -133,16 +136,16 @@ public class PayOrderServiceImpl implements PayOrderService {
|
||||
PayClient client = payClientFactory.getPayClient(channel.getId());
|
||||
if (client == null) {
|
||||
log.error("[submitPayOrder][渠道编号({}) 找不到对应的支付客户端]", channel.getId());
|
||||
throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_CHANNEL_CLIENT_NOT_FOUND);
|
||||
throw exception(ErrorCodeConstants.PAY_CHANNEL_CLIENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
// 获得 PayOrderDO ,并校验其是否存在
|
||||
PayOrderDO order = orderMapper.selectById(reqDTO.getId());
|
||||
if (order == null || !Objects.equals(order.getAppId(), reqDTO.getAppId())) { // 是否存在
|
||||
throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_ORDER_NOT_FOUND);
|
||||
throw exception(ErrorCodeConstants.PAY_ORDER_NOT_FOUND);
|
||||
}
|
||||
if (!PayOrderStatusEnum.WAITING.getStatus().equals(order.getStatus())) { // 校验状态,必须是待支付
|
||||
throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_ORDER_STATUS_IS_NOT_WAITING);
|
||||
throw exception(ErrorCodeConstants.PAY_ORDER_STATUS_IS_NOT_WAITING);
|
||||
}
|
||||
|
||||
// 插入 PayOrderExtensionDO
|
||||
@@ -177,7 +180,7 @@ public class PayOrderServiceImpl implements PayOrderService {
|
||||
* @return 支付成功返回的地址。 配置地址 + "/" + channel id
|
||||
*/
|
||||
private String genChannelReturnUrl(PayChannelDO channel) {
|
||||
return payProperties.getPayReturnUrl() + "/" + channel.getId();
|
||||
return payProperties.getReturnUrl() + "/" + channel.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -187,8 +190,7 @@ public class PayOrderServiceImpl implements PayOrderService {
|
||||
* @return 支付渠道的回调地址 配置地址 + "/" + channel id
|
||||
*/
|
||||
private String genChannelPayNotifyUrl(PayChannelDO channel) {
|
||||
//去掉channel code, 似乎没啥用, 用统一的回调地址
|
||||
return payProperties.getPayNotifyUrl() + "/" + channel.getId();
|
||||
return payProperties.getCallbackUrl() + "/" + channel.getId();
|
||||
}
|
||||
|
||||
private String generateOrderExtensionNo() {
|
||||
@@ -210,64 +212,99 @@ public class PayOrderServiceImpl implements PayOrderService {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void notifyPayOrder(Long channelId, PayNotifyDataDTO notifyData) throws Exception {
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void notifyPayOrder(Long channelId, PayNotifyDataDTO notifyData) {
|
||||
// TODO 芋艿,记录回调日志
|
||||
log.info("[notifyPayOrder][channelId({}) 回调数据({})]", channelId, notifyData.getBody());
|
||||
|
||||
// 校验支付渠道是否有效
|
||||
PayChannelDO channel = channelService.validPayChannel(channelId);
|
||||
TenantUtils.execute(channel.getTenantId(), () -> {
|
||||
try {
|
||||
notifyPayOrder(channel, notifyData);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void notifyPayOrder(PayChannelDO channel, PayNotifyDataDTO notifyData) throws Exception {
|
||||
// 校验支付客户端是否正确初始化
|
||||
PayClient client = payClientFactory.getPayClient(channel.getId());
|
||||
if (client == null) {
|
||||
log.error("[notifyPayOrder][渠道编号({}) 找不到对应的支付客户端]", channel.getId());
|
||||
throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_CHANNEL_CLIENT_NOT_FOUND);
|
||||
throw exception(ErrorCodeConstants.PAY_CHANNEL_CLIENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
// 解析支付结果
|
||||
// 0. 解析支付结果
|
||||
PayOrderNotifyRespDTO notifyRespDTO = client.parseOrderNotify(notifyData);
|
||||
|
||||
// TODO 芋艿,先最严格的校验。即使调用方重复调用,实际哪个订单已经被重复回调的支付,也返回 false 。也没问题,因为实际已经回调成功了。
|
||||
// 1.1 查询 PayOrderExtensionDO
|
||||
PayOrderExtensionDO orderExtension = orderExtensionMapper.selectByNo(notifyRespDTO.getOrderExtensionNo());
|
||||
if (orderExtension == null) {
|
||||
throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_ORDER_EXTENSION_NOT_FOUND);
|
||||
}
|
||||
if (!PayOrderStatusEnum.WAITING.getStatus().equals(orderExtension.getStatus())) { // 校验状态,必须是待支付
|
||||
throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING);
|
||||
}
|
||||
// 1.2 更新 PayOrderExtensionDO
|
||||
//TODO 支付宝交易超时 TRADE_FINISHED 需要更新交易关闭
|
||||
int updateCounts = orderExtensionMapper.updateByIdAndStatus(orderExtension.getId(),
|
||||
PayOrderStatusEnum.WAITING.getStatus(), PayOrderExtensionDO.builder().id(orderExtension.getId())
|
||||
.status(PayOrderStatusEnum.SUCCESS.getStatus()).channelNotifyData(notifyData.getBody()).build());
|
||||
if (updateCounts == 0) { // 校验状态,必须是待支付
|
||||
throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING);
|
||||
}
|
||||
log.info("[notifyPayOrder][支付拓展单({}) 更新为已支付]", orderExtension.getId());
|
||||
|
||||
// 2.1 判断 PayOrderDO 是否处于待支付
|
||||
PayOrderDO order = orderMapper.selectById(orderExtension.getOrderId());
|
||||
if (order == null) {
|
||||
throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_ORDER_NOT_FOUND);
|
||||
}
|
||||
if (!PayOrderStatusEnum.WAITING.getStatus().equals(order.getStatus())) { // 校验状态,必须是待支付
|
||||
throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_ORDER_STATUS_IS_NOT_WAITING);
|
||||
}
|
||||
// 2.2 更新 PayOrderDO
|
||||
updateCounts = orderMapper.updateByIdAndStatus(order.getId(), PayOrderStatusEnum.WAITING.getStatus(),
|
||||
PayOrderDO.builder().status(PayOrderStatusEnum.SUCCESS.getStatus()).channelId(channelId).channelCode(channel.getCode())
|
||||
.successTime(notifyRespDTO.getSuccessTime()).successExtensionId(orderExtension.getId())
|
||||
.channelOrderNo(notifyRespDTO.getChannelOrderNo()).channelUserId(notifyRespDTO.getChannelUserId())
|
||||
.notifyTime(LocalDateTime.now()).build());
|
||||
if (updateCounts == 0) { // 校验状态,必须是待支付
|
||||
throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_ORDER_STATUS_IS_NOT_WAITING);
|
||||
}
|
||||
log.info("[notifyPayOrder][支付订单({}) 更新为已支付]", order.getId());
|
||||
// 1. 更新 PayOrderExtensionDO 支付成功
|
||||
PayOrderExtensionDO orderExtension = updatePayOrderExtensionSuccess(notifyRespDTO.getOrderExtensionNo(), notifyData.getBody());
|
||||
// 2. 更新 PayOrderDO 支付成功
|
||||
PayOrderDO order = updatePayOrderSuccess(channel, orderExtension, notifyRespDTO);
|
||||
|
||||
// 3. 插入支付通知记录
|
||||
notifyService.createPayNotifyTask(PayNotifyTaskCreateReqDTO.builder()
|
||||
.type(PayNotifyTypeEnum.ORDER.getType()).dataId(order.getId()).build());
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新 PayOrderExtensionDO 支付成功
|
||||
*
|
||||
* @param no 支付订单号(支付模块)
|
||||
* @param body 回调内容
|
||||
* @return PayOrderExtensionDO 对象
|
||||
*/
|
||||
private PayOrderExtensionDO updatePayOrderExtensionSuccess(String no, String body) {
|
||||
// 1.1 查询 PayOrderExtensionDO
|
||||
PayOrderExtensionDO orderExtension = orderExtensionMapper.selectByNo(no);
|
||||
if (orderExtension == null) {
|
||||
throw exception(ErrorCodeConstants.PAY_ORDER_EXTENSION_NOT_FOUND);
|
||||
}
|
||||
if (ObjectUtil.notEqual(orderExtension.getStatus(), PayOrderStatusEnum.WAITING.getStatus())) { // 校验状态,必须是待支付
|
||||
throw exception(ErrorCodeConstants.PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING);
|
||||
}
|
||||
// 1.2 更新 PayOrderExtensionDO
|
||||
int updateCounts = orderExtensionMapper.updateByIdAndStatus(orderExtension.getId(),
|
||||
PayOrderStatusEnum.WAITING.getStatus(), PayOrderExtensionDO.builder().id(orderExtension.getId())
|
||||
.status(PayOrderStatusEnum.SUCCESS.getStatus()).channelNotifyData(body).build());
|
||||
if (updateCounts == 0) { // 校验状态,必须是待支付
|
||||
throw exception(ErrorCodeConstants.PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING);
|
||||
}
|
||||
log.info("[updatePayOrderSuccess][支付拓展单({}) 更新为已支付]", orderExtension.getId());
|
||||
return orderExtension;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新 PayOrderDO 支付成功
|
||||
*
|
||||
* @param channel 支付渠道
|
||||
* @param orderExtension 支付拓展单
|
||||
* @param notifyRespDTO 通知回调
|
||||
* @return PayOrderDO 对象
|
||||
*/
|
||||
private PayOrderDO updatePayOrderSuccess(PayChannelDO channel, PayOrderExtensionDO orderExtension,
|
||||
PayOrderNotifyRespDTO notifyRespDTO) {
|
||||
// 2.1 判断 PayOrderDO 是否处于待支付
|
||||
PayOrderDO order = orderMapper.selectById(orderExtension.getOrderId());
|
||||
if (order == null) {
|
||||
throw exception(ErrorCodeConstants.PAY_ORDER_NOT_FOUND);
|
||||
}
|
||||
if (!PayOrderStatusEnum.WAITING.getStatus().equals(order.getStatus())) { // 校验状态,必须是待支付
|
||||
throw exception(ErrorCodeConstants.PAY_ORDER_STATUS_IS_NOT_WAITING);
|
||||
}
|
||||
// 2.2 更新 PayOrderDO
|
||||
int updateCounts = orderMapper.updateByIdAndStatus(order.getId(), PayOrderStatusEnum.WAITING.getStatus(),
|
||||
PayOrderDO.builder().status(PayOrderStatusEnum.SUCCESS.getStatus())
|
||||
.channelId(channel.getId()).channelCode(channel.getCode())
|
||||
.successTime(notifyRespDTO.getSuccessTime()).successExtensionId(orderExtension.getId())
|
||||
.channelOrderNo(notifyRespDTO.getChannelOrderNo()).channelUserId(notifyRespDTO.getChannelUserId())
|
||||
.notifyTime(LocalDateTime.now()).build());
|
||||
if (updateCounts == 0) { // 校验状态,必须是待支付
|
||||
throw exception(ErrorCodeConstants.PAY_ORDER_STATUS_IS_NOT_WAITING);
|
||||
}
|
||||
log.info("[updatePayOrderSuccess][支付订单({}) 更新为已支付]", order.getId());
|
||||
return order;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,64 +0,0 @@
|
||||
package cn.iocoder.yudao.module.pay.service.order.dto;
|
||||
|
||||
import lombok.Data;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
|
||||
import javax.validation.constraints.DecimalMin;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 支付单创建 Request DTO
|
||||
*/
|
||||
@Data
|
||||
public class PayOrderCreateReqDTO implements Serializable {
|
||||
|
||||
/**
|
||||
* 应用编号
|
||||
*/
|
||||
@NotNull(message = "应用编号不能为空")
|
||||
private Long appId;
|
||||
/**
|
||||
* 用户 IP
|
||||
*/
|
||||
@NotEmpty(message = "用户 IP 不能为空")
|
||||
private String userIp;
|
||||
|
||||
// ========== 商户相关字段 ==========
|
||||
|
||||
/**
|
||||
* 商户订单编号
|
||||
*/
|
||||
@NotEmpty(message = "商户订单编号不能为空")
|
||||
private String merchantOrderId;
|
||||
/**
|
||||
* 商品标题
|
||||
*/
|
||||
@NotEmpty(message = "商品标题不能为空")
|
||||
@Length(max = 32, message = "商品标题不能超过 32")
|
||||
private String subject;
|
||||
/**
|
||||
* 商品描述
|
||||
*/
|
||||
@NotEmpty(message = "商品描述信息不能为空")
|
||||
@Length(max = 128, message = "商品描述信息长度不能超过128")
|
||||
private String body;
|
||||
|
||||
// ========== 订单相关字段 ==========
|
||||
|
||||
/**
|
||||
* 支付金额,单位:分
|
||||
*/
|
||||
@NotNull(message = "支付金额不能为空")
|
||||
@DecimalMin(value = "0", inclusive = false, message = "支付金额必须大于零")
|
||||
private Integer amount;
|
||||
|
||||
/**
|
||||
* 支付过期时间
|
||||
*/
|
||||
@NotNull(message = "支付过期时间不能为空")
|
||||
private LocalDateTime expireTime;
|
||||
|
||||
}
|
@@ -31,7 +31,7 @@ public class PayRefundReqDTO {
|
||||
*/
|
||||
@NotNull(message = "退款金额不能为空")
|
||||
@DecimalMin(value = "0", inclusive = false, message = "退款金额必须大于零")
|
||||
private Long amount;
|
||||
private Integer amount;
|
||||
|
||||
/**
|
||||
* 退款原因
|
||||
|
@@ -1 +0,0 @@
|
||||
package cn.iocoder.yudao.module.pay.service;
|
@@ -49,7 +49,6 @@ public class PayChannelServiceTest extends BaseDbUnitTest {
|
||||
@Test
|
||||
public void testCreateWechatVersion2Channel_success() {
|
||||
// 准备参数
|
||||
|
||||
WXPayClientConfig v2Config = getV2Config();
|
||||
PayChannelCreateReqVO reqVO = randomPojo(PayChannelCreateReqVO.class, o -> {
|
||||
o.setCode(PayChannelEnum.WX_PUB.getCode());
|
||||
|
@@ -76,7 +76,7 @@ public class PayOrderServiceTest extends BaseDbUnitTest {
|
||||
o.setBody("斌斌子送给灿灿子的炸弹猫");
|
||||
o.setNotifyUrl("https://hc.com/lbh");
|
||||
o.setNotifyStatus(PayOrderNotifyStatusEnum.SUCCESS.getStatus());
|
||||
o.setAmount(10000L);
|
||||
o.setAmount(10000);
|
||||
o.setChannelFeeRate(0.01);
|
||||
o.setChannelFeeAmount(1L);
|
||||
o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus());
|
||||
@@ -148,7 +148,7 @@ public class PayOrderServiceTest extends BaseDbUnitTest {
|
||||
o.setBody("斌斌子送给灿灿子的炸弹猫");
|
||||
o.setNotifyUrl("https://hc.com/lbh");
|
||||
o.setNotifyStatus(PayOrderNotifyStatusEnum.SUCCESS.getStatus());
|
||||
o.setAmount(10000L);
|
||||
o.setAmount(10000);
|
||||
o.setChannelFeeRate(0.01);
|
||||
o.setChannelFeeAmount(1L);
|
||||
o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus());
|
||||
|
@@ -67,8 +67,8 @@ public class PayRefundServiceTest extends BaseDbUnitTest {
|
||||
o.setNotifyStatus(PayOrderNotifyStatusEnum.SUCCESS.getStatus());
|
||||
o.setStatus(PayRefundStatusEnum.SUCCESS.getStatus());
|
||||
o.setType(PayRefundTypeEnum.SOME.getStatus());
|
||||
o.setPayAmount(100L);
|
||||
o.setRefundAmount(500L);
|
||||
o.setPayAmount(100);
|
||||
o.setRefundAmount(500);
|
||||
o.setReason("就是想退款了,你有意见吗");
|
||||
o.setUserIp("127.0.0.1");
|
||||
o.setChannelOrderNo("CH0000001");
|
||||
@@ -136,8 +136,8 @@ public class PayRefundServiceTest extends BaseDbUnitTest {
|
||||
o.setNotifyStatus(PayOrderNotifyStatusEnum.SUCCESS.getStatus());
|
||||
o.setStatus(PayRefundStatusEnum.SUCCESS.getStatus());
|
||||
o.setType(PayRefundTypeEnum.SOME.getStatus());
|
||||
o.setPayAmount(100L);
|
||||
o.setRefundAmount(500L);
|
||||
o.setPayAmount(100);
|
||||
o.setRefundAmount(500);
|
||||
o.setReason("就是想退款了,你有意见吗");
|
||||
o.setUserIp("127.0.0.1");
|
||||
o.setChannelOrderNo("CH0000001");
|
||||
|
@@ -9,7 +9,7 @@ spring:
|
||||
# 数据源配置项
|
||||
datasource:
|
||||
name: ruoyi-vue-pro
|
||||
url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false; # MODE 使用 MySQL 模式;DATABASE_TO_UPPER 配置表和字段使用小写
|
||||
url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false;NON_KEYWORDS=value; # MODE 使用 MySQL 模式;DATABASE_TO_UPPER 配置表和字段使用小写
|
||||
driver-class-name: org.h2.Driver
|
||||
username: sa
|
||||
password:
|
||||
|
@@ -43,6 +43,7 @@ CREATE TABLE IF NOT EXISTS "pay_channel" (
|
||||
"updater" varchar(64) NULL DEFAULT '',
|
||||
"update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
"deleted" bit(1) NOT NULL DEFAULT FALSE,
|
||||
"tenant_id" bigint not null default '0',
|
||||
PRIMARY KEY ("id")
|
||||
) COMMENT = '支付渠道';
|
||||
|
||||
|
Reference in New Issue
Block a user