转账 - 增加转账通知

This commit is contained in:
jason 2023-11-06 11:33:08 +08:00
parent cf114409b0
commit 09d45e6393
17 changed files with 160 additions and 31 deletions

View File

@ -245,4 +245,12 @@ INSERT INTO system_menu(
VALUES (
'转账订单', '', 2, 3, 1117,
'transfer', 'ep:credit-card', 'pay/transfer/index', 0, 'PayTransfer'
);
);
-- 转账通知脚本
ALTER TABLE `pay_app`
ADD COLUMN `transfer_notify_url` varchar(1024) NOT NULL COMMENT '转账结果的回调地址' AFTER `refund_notify_url`;
ALTER TABLE `pay_notify_task`
MODIFY COLUMN `merchant_order_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '商户订单编号' AFTER `status`,
ADD COLUMN `merchant_transfer_id` varchar(64) COMMENT '商户转账单编号' AFTER `merchant_order_id`;

View File

@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.pay.api.notify.dto;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
/**
* 转账单的通知 Request DTO
*
* @author jason
*/
@Data
public class PayTransferNotifyReqDTO {
/**
* 商户转账单号
*/
@NotEmpty(message = "商户转账单号不能为空")
private String merchantTransferId;
/**
* 转账订单编号
*/
@NotNull(message = "转账订单编号不能为空")
private Long payTransferId;
}

View File

@ -65,12 +65,13 @@ public interface ErrorCodeConstants {
// ========== 转账模块 1-007-009-000 ==========
ErrorCode PAY_TRANSFER_SUBMIT_CHANNEL_ERROR = new ErrorCode(1_007_009_000, "发起转账报错,错误码:{},错误提示:{}");
ErrorCode PAY_TRANSFER_NOT_FOUND = new ErrorCode(1_007_009_001, "转账交易单不存在");
ErrorCode PAY_TRANSFER_NOT_FOUND = new ErrorCode(1_007_009_001, "转账单不存在");
ErrorCode PAY_TRANSFER_STATUS_IS_SUCCESS = new ErrorCode(1_007_009_002, "转账单已成功转账");
ErrorCode PAY_TRANSFER_EXISTS = new ErrorCode(1_007_009_003, "已经存在转账单");
ErrorCode PAY_MERCHANT_TRANSFER_EXISTS = new ErrorCode(1_007_009_004, "该笔业务的转账已经存在,请查询转账订单相关状态");
ErrorCode PAY_TRANSFER_STATUS_IS_NOT_WAITING = new ErrorCode(1_007_009_005, "转账单不处于待转账");
ErrorCode PAY_TRANSFER_STATUS_IS_NOT_PENDING = new ErrorCode(1_007_009_006, "转账单不处于待转账或转账中");
// ========== 示例订单 1-007-900-000 ==========
ErrorCode DEMO_ORDER_NOT_FOUND = new ErrorCode(1_007_900_000, "示例订单不存在");
ErrorCode DEMO_ORDER_UPDATE_PAID_STATUS_NOT_UNPAID = new ErrorCode(1_007_900_001, "示例订单更新支付状态失败,订单不是【未支付】状态");
@ -84,4 +85,8 @@ public interface ErrorCodeConstants {
ErrorCode DEMO_ORDER_REFUND_FAIL_REFUND_ORDER_ID_ERROR = new ErrorCode(1_007_900_009, "发起退款失败,退款单编号不匹配");
ErrorCode DEMO_ORDER_REFUND_FAIL_REFUND_PRICE_NOT_MATCH = new ErrorCode(1_007_900_010, "发起退款失败,退款单金额不匹配");
// ========== 示例转账订单 1-007-901-001 ==========
ErrorCode DEMO_TRANSFER_NOT_FOUND = new ErrorCode(1_007_901_001, "示例转账单不存在");
ErrorCode DEMO_TRANSFER_FAIL_TRANSFER_ID_ERROR = new ErrorCode(1_007_901_002, "转账失败,转账单编号不匹配");
ErrorCode DEMO_TRANSFER_FAIL_PRICE_NOT_MATCH = new ErrorCode(1_007_901_003, "转账失败,转账单金额不匹配");
}

View File

@ -14,6 +14,7 @@ public enum PayNotifyTypeEnum {
ORDER(1, "支付单"),
REFUND(2, "退款单"),
TRANSFER(3, "转账单")
;
/**

View File

@ -6,8 +6,8 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
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.controller.admin.demo.vo.PayDemoOrderCreateReqVO;
import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.PayDemoOrderRespVO;
import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.order.PayDemoOrderCreateReqVO;
import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.order.PayDemoOrderRespVO;
import cn.iocoder.yudao.module.pay.convert.demo.PayDemoOrderConvert;
import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoOrderDO;
import cn.iocoder.yudao.module.pay.service.demo.PayDemoOrderService;

View File

@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.pay.controller.admin.demo;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import cn.iocoder.yudao.module.pay.api.notify.dto.PayTransferNotifyReqDTO;
import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.transfer.PayDemoTransferCreateReqVO;
import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.transfer.PayDemoTransferRespVO;
import cn.iocoder.yudao.module.pay.convert.demo.PayDemoTransferConvert;
@ -14,6 +16,7 @@ import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.annotation.security.PermitAll;
import javax.validation.Valid;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@ -33,9 +36,19 @@ public class PayDemoTransferController {
}
@GetMapping("/page")
@Operation(summary = "获得示例订单分页")
@Operation(summary = "获得示例转账订单分页")
public CommonResult<PageResult<PayDemoTransferRespVO>> getDemoTransferPage(@Valid PageParam pageVO) {
PageResult<PayDemoTransferDO> pageResult = demoTransferService.getDemoTransferPage(pageVO);
return success(PayDemoTransferConvert.INSTANCE.convertPage(pageResult));
}
@PostMapping("/update-status")
@Operation(summary = "更新示例转账订单的转账状态") // pay-module 转账服务进行回调
@PermitAll // 无需登录安全由 PayDemoTransferService 内部校验实现
@OperateLog(enable = false) // 禁用操作日志因为没有操作人
public CommonResult<Boolean> updateDemoTransferStatus(@RequestBody PayTransferNotifyReqDTO notifyReqDTO) {
demoTransferService.updateDemoTransferStatus(Long.valueOf(notifyReqDTO.getMerchantTransferId()),
notifyReqDTO.getPayTransferId());
return success(true);
}
}

View File

@ -1,9 +1,8 @@
package cn.iocoder.yudao.module.pay.controller.admin.demo.vo;
package cn.iocoder.yudao.module.pay.controller.admin.demo.vo.order;
import lombok.*;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
@Schema(description = "管理后台 - 示例订单创建 Request VO")

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.pay.controller.admin.demo.vo;
package cn.iocoder.yudao.module.pay.controller.admin.demo.vo.order;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;

View File

@ -1,8 +1,8 @@
package cn.iocoder.yudao.module.pay.convert.demo;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.PayDemoOrderCreateReqVO;
import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.PayDemoOrderRespVO;
import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.order.PayDemoOrderCreateReqVO;
import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.order.PayDemoOrderRespVO;
import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoOrderDO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;

View File

@ -54,4 +54,9 @@ public class PayAppDO extends BaseDO {
*/
private String refundNotifyUrl;
/**
* 转账结果的回调地址
*/
private String transferNotifyUrl;
}

View File

@ -66,6 +66,10 @@ public class PayNotifyTaskDO extends TenantBaseDO {
* 商户订单编号
*/
private String merchantOrderId;
/**
* 商户转账单编号
*/
private String merchantTransferId;
/**
* 通知状态
*

View File

@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.pay.service.demo;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.PayDemoOrderCreateReqVO;
import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.order.PayDemoOrderCreateReqVO;
import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoOrderDO;
import javax.validation.Valid;

View File

@ -9,7 +9,7 @@ import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO;
import cn.iocoder.yudao.module.pay.api.refund.PayRefundApi;
import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO;
import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundRespDTO;
import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.PayDemoOrderCreateReqVO;
import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.order.PayDemoOrderCreateReqVO;
import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoOrderDO;
import cn.iocoder.yudao.module.pay.dal.mysql.demo.PayDemoOrderMapper;
import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;

View File

@ -28,4 +28,12 @@ public interface PayDemoTransferService {
* @param pageVO 分页查询参数
*/
PageResult<PayDemoTransferDO> getDemoTransferPage(PageParam pageVO);
/**
* 更新转账业务示例订单的转账状态
*
* @param id 编号
* @param payTransferId 转账单编号
*/
void updateDemoTransferStatus(Long id, Long payTransferId);
}

View File

@ -1,18 +1,25 @@
package cn.iocoder.yudao.module.pay.service.demo;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.transfer.PayDemoTransferCreateReqVO;
import cn.iocoder.yudao.module.pay.convert.demo.PayDemoTransferConvert;
import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoTransferDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO;
import cn.iocoder.yudao.module.pay.dal.mysql.demo.PayDemoTransferMapper;
import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferStatusEnum;
import cn.iocoder.yudao.module.pay.service.transfer.PayTransferService;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import javax.validation.Valid;
import javax.validation.Validator;
import java.util.Objects;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.pay.enums.transfer.PayTransferStatusEnum.WAITING;
/**
@ -33,6 +40,8 @@ public class PayDemoTransferServiceImpl implements PayDemoTransferService {
@Resource
private PayDemoTransferMapper demoTransferMapper;
@Resource
private PayTransferService payTransferService;
@Resource
private Validator validator;
@Override
@ -50,4 +59,41 @@ public class PayDemoTransferServiceImpl implements PayDemoTransferService {
public PageResult<PayDemoTransferDO> getDemoTransferPage(PageParam pageVO) {
return demoTransferMapper.selectPage(pageVO);
}
@Override
public void updateDemoTransferStatus(Long id, Long payTransferId) {
PayTransferDO payTransfer = validateDemoTransferStatusCanUpdate(id, payTransferId);
// 更新示例订单状态
if (payTransfer != null) {
demoTransferMapper.updateById(new PayDemoTransferDO().setId(id)
.setPayTransferId(payTransferId)
.setPayChannelCode(payTransfer.getChannelCode())
.setTransferStatus(payTransfer.getStatus())
.setTransferTime(payTransfer.getSuccessTime()));
}
}
private PayTransferDO validateDemoTransferStatusCanUpdate(Long id, Long payTransferId) {
PayDemoTransferDO demoTransfer = demoTransferMapper.selectById(id);
if (demoTransfer == null) {
throw exception(DEMO_TRANSFER_NOT_FOUND);
}
if (PayTransferStatusEnum.isSuccess(demoTransfer.getTransferStatus())
|| PayTransferStatusEnum.isClosed(demoTransfer.getTransferStatus())) {
// 无需更新返回 null
return null;
}
PayTransferDO transfer = payTransferService.getTransfer(payTransferId);
if (transfer == null) {
throw exception(PAY_TRANSFER_NOT_FOUND);
}
if (!Objects.equals(demoTransfer.getPrice(), transfer.getPrice())) {
throw exception(DEMO_TRANSFER_FAIL_PRICE_NOT_MATCH);
}
if (ObjectUtil.notEqual(transfer.getMerchantTransferId(), id.toString())) {
throw exception(DEMO_TRANSFER_FAIL_TRANSFER_ID_ERROR);
}
// TODO 校验账号
return transfer;
}
}

View File

@ -13,11 +13,13 @@ import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
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.api.notify.dto.PayTransferNotifyReqDTO;
import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskPageReqVO;
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;
import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO;
import cn.iocoder.yudao.module.pay.dal.mysql.notify.PayNotifyLogMapper;
import cn.iocoder.yudao.module.pay.dal.mysql.notify.PayNotifyTaskMapper;
import cn.iocoder.yudao.module.pay.dal.redis.notify.PayNotifyLockRedisDAO;
@ -25,6 +27,7 @@ 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.order.PayOrderService;
import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
import cn.iocoder.yudao.module.pay.service.transfer.PayTransferService;
import com.google.common.annotations.VisibleForTesting;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
@ -73,6 +76,9 @@ public class PayNotifyServiceImpl implements PayNotifyService {
@Resource
@Lazy // 循环依赖避免报错
private PayRefundService refundService;
@Resource
@Lazy // 循环依赖避免报错
private PayTransferService transferService;
@Resource
private PayNotifyTaskMapper notifyTaskMapper;
@ -100,6 +106,10 @@ public class PayNotifyServiceImpl implements PayNotifyService {
PayRefundDO refundDO = refundService.getRefund(task.getDataId());
task.setAppId(refundDO.getAppId())
.setMerchantOrderId(refundDO.getMerchantOrderId()).setNotifyUrl(refundDO.getNotifyUrl());
} else if (Objects.equals(task.getType(), PayNotifyTypeEnum.TRANSFER.getType())) {
PayTransferDO transfer = transferService.getTransfer(task.getDataId());
task.setAppId(transfer.getAppId()).setMerchantTransferId(transfer.getMerchantTransferId())
.setNotifyUrl(transfer.getNotifyUrl());
}
// 执行插入
@ -214,6 +224,9 @@ public class PayNotifyServiceImpl implements PayNotifyService {
} else if (Objects.equals(task.getType(), PayNotifyTypeEnum.REFUND.getType())) {
request = PayRefundNotifyReqDTO.builder().merchantOrderId(task.getMerchantOrderId())
.payRefundId(task.getDataId()).build();
} else if (Objects.equals(task.getType(), PayNotifyTypeEnum.TRANSFER.getType())) {
request = new PayTransferNotifyReqDTO().setMerchantTransferId(task.getMerchantTransferId())
.setPayTransferId(task.getDataId());
} else {
throw new RuntimeException("未知的通知任务类型:" + JsonUtils.toJsonString(task));
}

View File

@ -12,12 +12,15 @@ import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferStatusRespE
import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateReqDTO;
import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferCreateReqVO;
import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferPageReqVO;
import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO;
import cn.iocoder.yudao.module.pay.dal.mysql.transfer.PayTransferMapper;
import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO;
import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum;
import cn.iocoder.yudao.module.pay.service.app.PayAppService;
import cn.iocoder.yudao.module.pay.service.channel.PayChannelService;
import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -51,6 +54,8 @@ public class PayTransferServiceImpl implements PayTransferService {
@Resource
private PayChannelService channelService;
@Resource
private PayNotifyService notifyService;
@Resource
private PayNoRedisDAO noRedisDAO;
@Resource
private Validator validator;
@ -73,7 +78,7 @@ public class PayTransferServiceImpl implements PayTransferService {
// 1.1 校验转账单是否可以提交
validateTransferCanCreate(reqDTO.getAppId(), reqDTO.getMerchantTransferId());
// 1.2 校验 App
appService.validPayApp(reqDTO.getAppId());
PayAppDO payApp = appService.validPayApp(reqDTO.getAppId());
// 1.3 校验支付渠道是否有效
PayChannelDO channel = channelService.validPayChannel(reqDTO.getAppId(), reqDTO.getChannelCode());
PayClient client = channelService.getPayClient(channel.getId());
@ -86,7 +91,7 @@ public class PayTransferServiceImpl implements PayTransferService {
PayTransferDO transfer = INSTANCE.convert(reqDTO)
.setChannelId(channel.getId())
.setNo(no).setStatus(WAITING.getStatus())
.setNotifyUrl("http://127.0.0.1:48080/admin-api/pay/todo"); // TODO 需要加个transfer Notify url
.setNotifyUrl(payApp.getTransferNotifyUrl());
transferMapper.insert(transfer);
PayTransferRespDTO unifiedTransferResp = null;
try {
@ -145,22 +150,13 @@ public class PayTransferServiceImpl implements PayTransferService {
}
private void notifyTransferSuccess(PayChannelDO channel, PayTransferRespDTO notify) {
// 1. 更新 PayTransferDO 转账成功
Boolean transferred = updateTransferSuccess(channel, notify);
if (transferred) {
return;
}
// 2. TODO 插入转账通知记录
}
private Boolean updateTransferSuccess(PayChannelDO channel, PayTransferRespDTO notify) {
// 1.校验
PayTransferDO transfer = transferMapper.selectByNo(notify.getOutTransferNo());
if (transfer == null) {
throw exception(PAY_TRANSFER_NOT_FOUND);
}
if (isSuccess(transfer.getStatus())) { // 如果已成功直接返回不用重复更新
return Boolean.TRUE;
return;
}
if (!isPendingStatus(transfer.getStatus())) {
throw exception(PAY_TRANSFER_STATUS_IS_NOT_PENDING);
@ -176,10 +172,13 @@ public class PayTransferServiceImpl implements PayTransferService {
throw exception(PAY_TRANSFER_STATUS_IS_NOT_PENDING);
}
log.info("[updateTransferSuccess][transfer({}) 更新为已转账]", transfer.getId());
return Boolean.FALSE;
// 3. 插入转账通知记录
notifyService.createPayNotifyTask(PayNotifyTypeEnum.TRANSFER.getType(),
transfer.getId());
}
private void updateTransferClosed(PayChannelDO channel, PayTransferRespDTO notify) {
private void notifyTransferClosed(PayChannelDO channel, PayTransferRespDTO notify) {
// 1.校验
PayTransferDO transfer = transferMapper.selectByNo(notify.getOutTransferNo());
if (transfer == null) {
@ -192,6 +191,7 @@ public class PayTransferServiceImpl implements PayTransferService {
if (!isPendingStatus(transfer.getStatus())) {
throw exception(PAY_TRANSFER_STATUS_IS_NOT_PENDING);
}
// 2.更新
int updateCount = transferMapper.updateByIdAndStatus(transfer.getId(),
CollUtil.newArrayList(WAITING.getStatus(), IN_PROGRESS.getStatus()),
@ -203,11 +203,11 @@ public class PayTransferServiceImpl implements PayTransferService {
throw exception(PAY_TRANSFER_STATUS_IS_NOT_PENDING);
}
log.info("[updateTransferClosed][transfer({}) 更新为关闭状态]", transfer.getId());
}
private void notifyTransferClosed(PayChannelDO channel, PayTransferRespDTO notify) {
// 更新 PayTransferDO 转账关闭
updateTransferClosed(channel, notify);
// 3. 插入转账通知记录
notifyService.createPayNotifyTask(PayNotifyTypeEnum.TRANSFER.getType(),
transfer.getId());
}
/**