转账 - 动态收款人字段修改

This commit is contained in:
jason 2023-10-24 08:44:30 +08:00
parent 9095394fed
commit 86598dd177
9 changed files with 211 additions and 102 deletions

View File

@ -5,18 +5,26 @@ DROP TABLE IF EXISTS `pay_transfer`;
CREATE TABLE `pay_transfer`
(
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
`type` int NOT NULL COMMENT '类型',
`no` varchar(64) NOT NULL COMMENT '转账单号',
`app_id` bigint NOT NULL COMMENT '应用编号',
`channel_id` bigint NOT NULL COMMENT '转账渠道编号',
`channel_code` varchar(32) NOT NULL COMMENT '转账渠道编码',
`merchant_order_id` varchar(64) NOT NULL COMMENT '商户订单编号',
`price` int NOT NULL COMMENT '转账金额单位',
`subject` varchar(512) NOT NULL COMMENT '转账标题',
`payee_info` varchar(512) NOT NULL COMMENT '收款人信息不同类型和渠道不同',
`type` int NOT NULL COMMENT '类型',
`status` tinyint NOT NULL COMMENT '转账状态',
`success_time` datetime NULL COMMENT '转账成功时间',
`extension_id` bigint NULL COMMENT '转账渠道编号',
`no` varchar(64) NULL COMMENT '转账单号',
`channel_id` bigint NULL COMMENT '转账渠道编号',
`channel_code` varchar(32) NULL COMMENT '转账渠道编码',
`price` int NOT NULL COMMENT '转账金额单位',
`subject` varchar(512) NOT NULL COMMENT '转账标题',
`alipay_logon_id` varchar(64) NULL COMMENT '支付宝登录号',
`alipay_account_name` varchar(64) NULL COMMENT '支付宝账号名称',
`openid` varchar(64) NULL COMMENT '微信 openId',
`wx_account_name` varchar(64) NULL COMMENT '微信账号名称',
`notify_url` varchar(64) NULL COMMENT '异步通知商户地址',
`channel_extras` varchar(512) NULL DEFAULT NULL COMMENT '渠道的额外参数',
`channel_transfer_no` varchar(64) NULL DEFAULT NULL COMMENT '渠道转账单号',
`channel_error_code` varchar(128) NULL DEFAULT NULL COMMENT '调用渠道的错误码',
`channel_error_msg` varchar(256) NULL DEFAULT NULL COMMENT '调用渠道的错误提示',
`channel_notify_data` varchar(4096) NULL DEFAULT NULL COMMENT '渠道的同步/异步通知的内容',
`creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
@ -58,7 +66,10 @@ CREATE TABLE `pay_demo_transfer` (
`user_id` bigint UNSIGNED NOT NULL COMMENT '用户编号',
`price` int NOT NULL COMMENT '转账金额单位',
`type` int NOT NULL COMMENT '转账类型',
`payee_info` varchar(512) NOT NULL COMMENT '收款人信息不同类型和渠道不同',
`alipay_logon_id` varchar(64) NULL COMMENT '支付宝登录号',
`alipay_account_name` varchar(64) NULL COMMENT '支付宝账号名称',
`openid` varchar(64) NULL COMMENT '微信 openId',
`wx_account_name` varchar(64) NULL COMMENT '微信账号名称',
`transfer_status` tinyint NOT NULL DEFAULT 0 COMMENT '转账状态',
`pay_transfer_id` bigint NULL DEFAULT NULL COMMENT '转账订单编号',
`pay_channel_code` varchar(16) NULL DEFAULT NULL COMMENT '转账支付成功渠道',

View File

@ -58,6 +58,26 @@ public class PayTransferUnifiedReqDTO {
@NotEmpty(message = "收款方信息 不能为空")
private Map<String, String> payeeInfo;
/**
* 支付宝登录号
*/
private String alipayLogonId;
/**
* 支付宝账号名称
*/
private String alipayAccountName;
/**
* 微信 openId
*/
private String openid;
/**
* 微信账号名称
*/
private String wxAccountName;
/**
* 支付渠道的额外参数
*/

View File

@ -65,15 +65,13 @@ public interface ErrorCodeConstants {
// ========== 转账模块 1-007-009-000 ==========
ErrorCode PAY_TRANSFER_SUBMIT_CHANNEL_ERROR = new ErrorCode(1_007_009_000, "发起转账报错,错误码:{},错误提示:{}");
ErrorCode PAY_TRANSFER_ALIPAY_LOGIN_ID_IS_EMPTY = new ErrorCode(1_007_009_001, "支付宝登录 ID 不能为空");
ErrorCode PAY_TRANSFER_ALIPAY_ACCOUNT_NAME_IS_EMPTY = new ErrorCode(1_007_009_002, "支付宝账号名称不能为空");
ErrorCode PAY_TRANSFER_NOT_FOUND = new ErrorCode(1_007_009_003, "转账交易单不存在");
ErrorCode PAY_TRANSFER_STATUS_IS_SUCCESS = 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, "转账单不处于待转账或转账中");
ErrorCode PAY_TRANSFER_EXTENSION_NOT_FOUND = new ErrorCode(1_007_009_007, "转账交易拓展单不存在");
ErrorCode PAY_TRANSFER_TYPE_AND_CHANNEL_NOT_MATCH = new ErrorCode(1_007_009_008, "转账类型和转账渠道不匹配");
ErrorCode PAY_TRANSFER_EXTENSION_STATUS_IS_NOT_PENDING = new ErrorCode(1_007_009_009, "转账拓展单不处于待转账或转账中");
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_STATUS_IS_NOT_WAITING = new ErrorCode(1_007_009_003, "转账单不处于待转账");
ErrorCode PAY_TRANSFER_STATUS_IS_NOT_PENDING = new ErrorCode(1_007_009_004, "转账单不处于待转账或转账中");
ErrorCode PAY_TRANSFER_EXTENSION_NOT_FOUND = new ErrorCode(1_007_009_005, "转账交易拓展单不存在");
ErrorCode PAY_TRANSFER_TYPE_AND_CHANNEL_NOT_MATCH = new ErrorCode(1_007_009_006, "转账类型和转账渠道不匹配");
ErrorCode PAY_TRANSFER_EXTENSION_STATUS_IS_NOT_PENDING = new ErrorCode(1_007_009_007, "转账拓展单不处于待转账或转账中");
// ========== 示例订单 1-007-900-000 ==========
ErrorCode DEMO_ORDER_NOT_FOUND = new ErrorCode(1_007_900_000, "示例订单不存在");

View File

@ -1,14 +1,15 @@
package cn.iocoder.yudao.module.pay.controller.admin.demo.vo.transfer;
import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.Validator;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.Map;
/**
* @author jason
@ -22,13 +23,50 @@ public class PayDemoTransferCreateReqVO {
@InEnum(PayTransferTypeEnum.class)
private Integer type;
@Schema(description = "转账金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
@NotNull(message = "转账金额不能为空")
@Min(value = 1, message = "转账金额必须大于零")
private Integer price;
// TODO @jason感觉这个动态字段晚点改可能要讨论下怎么搞好
@Schema(description = "收款方信息", requiredMode = Schema.RequiredMode.REQUIRED, example = "{'ALIPAY_LOGON_ID':'xxxx'}")
@NotEmpty(message = "收款方信息不能为空")
private Map<String, String> payeeInfo;
// ========== 支付宝,微信转账相关字段 ==========
@Schema(description = "支付宝登录号,支持邮箱和手机号格式", example = "test1@@sandbox.com")
@NotBlank(message = "支付宝登录号不能为空", groups = {Alipay.class})
private String alipayLogonId;
@Schema(description = "支付宝账号名称", example = "test1")
@NotBlank(message = "支付宝登录号不能为空", groups = {Alipay.class})
private String alipayAccountName;
// ========== 微信转账相关字段 ==========
@Schema(description = "微信 openId", example = "oLefc4g5Gxx")
@NotBlank(message = "微信 openId 不能为空", groups = {WxPay.class})
private String openid;
@Schema(description = "微信账号名称", example = "oLefc4g5Gjxxxxxx")
private String wxAccountName;
// ========== 转账到银行卡和钱包相关字段 待补充 ==========
public interface WxPay {
}
public interface Alipay {
}
public void validate(Validator validator) {
PayTransferTypeEnum transferType = PayTransferTypeEnum.typeOf(type);
switch (transferType) {
case ALIPAY_BALANCE: {
ValidationUtils.validate(validator, this, Alipay.class);
break;
}
case WX_BALANCE: {
ValidationUtils.validate(validator, this, WxPay.class);
break;
}
default: {
throw new UnsupportedOperationException("待实现");
}
}
}
}

View File

@ -0,0 +1,17 @@
package cn.iocoder.yudao.module.pay.convert.demo;
import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.transfer.PayDemoTransferCreateReqVO;
import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoTransferDO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/**
* @author jason
*/
@Mapper
public interface PayDemoTransferConvert {
PayDemoTransferConvert INSTANCE = Mappers.getMapper(PayDemoTransferConvert.class);
PayDemoTransferDO convert(PayDemoTransferCreateReqVO bean);
}

View File

@ -1,15 +1,13 @@
package cn.iocoder.yudao.module.pay.dal.dataobject.demo;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.Map;
/**
* 示例转账订单
@ -39,15 +37,30 @@ public class PayDemoTransferDO extends BaseDO {
/**
* 转账类型
* <p>
* 枚举 {@link PayTransferTypeEnum}
*/
private Integer type;
// TODO @jason要不字段还是弄成正确的平铺开
/**
* 收款人信息不同类型和渠道不同
* 支付宝登录号
*/
@TableField(typeHandler = JacksonTypeHandler.class)
private Map<String, String> payeeInfo;
private String alipayLogonId;
/**
* 支付宝账号名称
*/
private String alipayAccountName;
/**
* 微信 openId
*/
private String openid;
/**
* 微信账号名称
*/
private String wxAccountName;
/**
* 转账状态

View File

@ -31,33 +31,35 @@ public class PayTransferDO extends BaseDO {
*/
@TableId
private Long id;
/**
* 转账单号
*
*/
private String no;
/**
* 应用编号
*
* 关联 {@link PayAppDO#getId()}
*/
private Long appId;
/**
* 转账渠道编号
*
* 关联 {@link PayChannelDO#getId()}
*/
private Long channelId;
/**
* 转账渠道编码
*
* 枚举 {@link PayChannelEnum}
*/
private String channelCode;
/**
* 类型
*
* 枚举 {@link PayTransferTypeEnum}
*/
private Integer type;
// ========== 商户相关字段 ==========
/**
* 商户订单编号
*
@ -65,12 +67,20 @@ public class PayTransferDO extends BaseDO {
*/
private String merchantOrderId;
// ========== 转账相关字段 ==========
/**
* 类型
*
* 枚举 {@link PayTransferTypeEnum}
*/
private Integer type;
/**
* 转账标题
*/
private String subject;
// ========== 转账相关字段 ==========
/**
* 转账金额单位
*/
@ -81,26 +91,70 @@ public class PayTransferDO extends BaseDO {
* 枚举 {@link PayTransferStatusRespEnum}
*/
private Integer status;
/**
* 订单转账成功时间
*/
private LocalDateTime successTime;
// ========== 支付宝转账相关字段 ==========
/**
* 转账成功的转账拓展单编号
*
* 关联 {@link PayTransferExtensionDO#getId()}
* 支付宝登录号
*/
private Long extensionId;
private String alipayLogonId;
/**
* 转账成功的转账拓展单号
*
* 关联 {@link PayTransferExtensionDO#getNo()}
* 支付宝账号名称
*/
private String no;
private String alipayAccountName;
// ========== 微信转账相关字段 ==========
/**
* 收款人信息不同类型和渠道不同
* 微信 openId
*/
private String openid;
/**
* 微信账号名称
*/
private String wxAccountName;
// ========== 其它字段 ==========
/**
* 异步通知地址
*/
private String notifyUrl;
/**
* 用户 IP
*/
private String userIp;
/**
* 渠道的额外参数
*/
@TableField(typeHandler = JacksonTypeHandler.class)
private Map<String, String> payeeInfo;
private Map<String, String> channelExtras;
/**
* 渠道转账单号
*/
private String channelTransferNo;
/**
* 调用渠道的错误码
*/
private String channelErrorCode;
/**
* 调用渠道的错误提示
*/
private String channelErrorMsg;
/**
* 渠道的同步/异步通知的内容
*
*/
private String channelNotifyData;
}

View File

@ -1,10 +1,7 @@
package cn.iocoder.yudao.module.pay.service.demo;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum;
import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.transfer.PayDemoTransferCreateReqVO;
import cn.iocoder.yudao.module.pay.convert.transfer.PayTransferConvert;
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.mysql.demo.PayDemoTransferMapper;
import cn.iocoder.yudao.module.pay.service.transfer.PayTransferService;
@ -14,12 +11,8 @@ import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.Map;
import javax.validation.Validator;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum.*;
import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.PAY_TRANSFER_ALIPAY_ACCOUNT_NAME_IS_EMPTY;
import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.PAY_TRANSFER_ALIPAY_LOGIN_ID_IS_EMPTY;
import static cn.iocoder.yudao.module.pay.enums.transfer.PayTransferStatusEnum.WAITING;
/**
@ -41,49 +34,18 @@ public class PayDemoTransferServiceImpl implements PayDemoTransferService {
private PayDemoTransferMapper demoTransferMapper;
@Resource
private PayTransferService transferService;
@Resource
private Validator validator;
@Override
@Transactional(rollbackFor = Exception.class)
public Long createDemoTransfer(Long userId, @Valid PayDemoTransferCreateReqVO vo) {
// 1 校验收款账号
validatePayeeInfo(vo.getType(), vo.getPayeeInfo());
// 1 校验参数
vo.validate(validator);
// 2 保存示例转账业务表
PayDemoTransferDO demoTransfer = new PayDemoTransferDO().setUserId(userId).setType(vo.getType())
.setPrice(vo.getPrice()).setPayeeInfo(vo.getPayeeInfo())
.setTransferStatus(WAITING.getStatus());
PayDemoTransferDO demoTransfer = PayDemoTransferConvert.INSTANCE.convert(vo)
.setUserId(userId).setTransferStatus(WAITING.getStatus());
demoTransferMapper.insert(demoTransfer);
// 3.1 创建转账单
Long transferId = transferService.createTransfer(PayTransferConvert.INSTANCE.convert(vo)
.setAppId(TRANSFER_APP_ID).setTitle("示例转账")
.setMerchantOrderId(String.valueOf(demoTransfer.getId())));
// 3.2 更新转账单编号
demoTransferMapper.updateById(new PayDemoTransferDO().setId(demoTransfer.getId())
.setPayTransferId(transferId));
return demoTransfer.getId();
}
// TODO @jason可以参考 AppBrokerageWithdrawCreateReqVO 搞下字段哈进行校验
// @jason payeeinfo 字段确定改一下
private void validatePayeeInfo(Integer transferType, Map<String, String> payeeInfo) {
PayTransferTypeEnum transferTypeEnum = typeOf(transferType);
switch (transferTypeEnum) {
case ALIPAY_BALANCE: {
if (StrUtil.isEmpty(MapUtil.getStr(payeeInfo, ALIPAY_LOGON_ID))) {
throw exception(PAY_TRANSFER_ALIPAY_LOGIN_ID_IS_EMPTY);
}
if (StrUtil.isEmpty(MapUtil.getStr(payeeInfo, ALIPAY_ACCOUNT_NAME))) {
throw exception(PAY_TRANSFER_ALIPAY_ACCOUNT_NAME_IS_EMPTY);
}
break;
}
case WX_BALANCE:
case BANK_CARD:
case WALLET_BALANCE: {
throw new UnsupportedOperationException("待实现");
}
}
}
}

View File

@ -28,7 +28,6 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.Objects;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*;
@ -80,7 +79,7 @@ public class PayTransferServiceImpl implements PayTransferService {
PayTransferUnifiedReqDTO transferUnifiedReq = new PayTransferUnifiedReqDTO()
.setOutTransferNo(transferExtension.getNo()).setPrice(transfer.getPrice())
.setType(transfer.getType()).setTitle(transfer.getSubject())
.setPayeeInfo(transfer.getPayeeInfo()).setUserIp(userIp)
.setUserIp(userIp)
.setChannelExtras(reqVO.getChannelExtras());
PayTransferRespDTO unifiedTransferResp = client.unifiedTransfer(transferUnifiedReq);
@ -139,10 +138,7 @@ public class PayTransferServiceImpl implements PayTransferService {
if (transfer == null) {
throw exception(PAY_TRANSFER_NOT_FOUND);
}
if (isSuccess(transfer.getStatus()) && Objects.equals(transfer.getExtensionId(), transferExtension.getId())) {
log.info("[updateTransferSuccess][transfer({}) 已经是已转账,无需更新]", transfer.getId());
return true;
}
if (!isPendingStatus(transfer.getStatus())) {
throw exception(PAY_TRANSFER_STATUS_IS_NOT_PENDING);
}
@ -151,7 +147,7 @@ public class PayTransferServiceImpl implements PayTransferService {
CollUtil.newArrayList(WAITING.getStatus(), IN_PROGRESS.getStatus()),
new PayTransferDO().setStatus(SUCCESS.getStatus()).setSuccessTime(notify.getSuccessTime())
.setChannelId(channel.getId()).setChannelCode(channel.getCode())
.setExtensionId(transferExtension.getId()).setNo(transferExtension.getNo()));
.setNo(transferExtension.getNo()));
if (updateCounts == 0) {
throw exception(PAY_TRANSFER_STATUS_IS_NOT_PENDING);
}