Commit 626bd0bd55eb5bd774ad54cabac0fae141c2bf72

Authored by jason
1 parent 86598dd1

转账 - code review 修改

Showing 24 changed files with 323 additions and 204 deletions
sql/mysql/pay_wallet.sql
... ... @@ -9,17 +9,18 @@ CREATE TABLE `pay_transfer`
9 9 `app_id` bigint NOT NULL COMMENT '应用编号',
10 10 `channel_id` bigint NOT NULL COMMENT '转账渠道编号',
11 11 `channel_code` varchar(32) NOT NULL COMMENT '转账渠道编码',
12   - `merchant_order_id` varchar(64) NOT NULL COMMENT '商户订单编号',
  12 + `merchant_transfer_id` varchar(64) NOT NULL COMMENT '商户转账单编号',
13 13 `type` int NOT NULL COMMENT '类型',
14 14 `status` tinyint NOT NULL COMMENT '转账状态',
15 15 `success_time` datetime NULL COMMENT '转账成功时间',
16 16 `price` int NOT NULL COMMENT '转账金额,单位:分',
17 17 `subject` varchar(512) NOT NULL COMMENT '转账标题',
18 18 `alipay_logon_id` varchar(64) NULL COMMENT '支付宝登录号',
19   - `alipay_account_name` varchar(64) NULL COMMENT '支付宝账号名称',
20   - `openid` varchar(64) NULL COMMENT '微信 openId',
21   - `wx_account_name` varchar(64) NULL COMMENT '微信账号名称',
22   - `notify_url` varchar(64) NULL COMMENT '异步通知商户地址',
  19 + `alipay_account_name` varchar(64) NULL COMMENT '支付宝账号名称',
  20 + `openid` varchar(64) NULL COMMENT '微信 openId',
  21 + `wx_account_name` varchar(64) NULL COMMENT '微信账号名称',
  22 + `notify_url` varchar(1024) NOT NULL COMMENT '异步通知商户地址',
  23 + `user_ip` varchar(50) NOT NULL COMMENT '用户 IP',
23 24 `channel_extras` varchar(512) NULL DEFAULT NULL COMMENT '渠道的额外参数',
24 25 `channel_transfer_no` varchar(64) NULL DEFAULT NULL COMMENT '渠道转账单号',
25 26 `channel_error_code` varchar(128) NULL DEFAULT NULL COMMENT '调用渠道的错误码',
... ... @@ -64,6 +65,7 @@ DROP TABLE IF EXISTS `pay_demo_transfer`;
64 65 CREATE TABLE `pay_demo_transfer` (
65 66 `id` bigint NOT NULL AUTO_INCREMENT COMMENT '订单编号',
66 67 `user_id` bigint UNSIGNED NOT NULL COMMENT '用户编号',
  68 + `app_id` bigint NOT NULL COMMENT '应用编号',
67 69 `price` int NOT NULL COMMENT '转账金额,单位:分',
68 70 `type` int NOT NULL COMMENT '转账类型',
69 71 `alipay_logon_id` varchar(64) NULL COMMENT '支付宝登录号',
... ... @@ -81,7 +83,7 @@ CREATE TABLE `pay_demo_transfer` (
81 83 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
82 84 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
83 85 PRIMARY KEY (`id`) USING BTREE
84   -) ENGINE = InnoDB COMMENT = '示例业务转账订单\n';
  86 +) ENGINE = InnoDB COMMENT = '示例业务转账订单';
85 87  
86 88  
87 89 ALTER TABLE `pay_channel`
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/transfer/PayTransferRespDTO.java
... ... @@ -29,7 +29,7 @@ public class PayTransferRespDTO {
29 29 /**
30 30 * 支付渠道编号
31 31 */
32   - private String channelOrderNo;
  32 + private String channelTransferNo;
33 33  
34 34 /**
35 35 * 支付成功时间
... ... @@ -57,7 +57,7 @@ public class PayTransferRespDTO {
57 57 String outTransferNo, Object rawData) {
58 58 PayTransferRespDTO respDTO = new PayTransferRespDTO();
59 59 respDTO.status = PayTransferStatusRespEnum.WAITING.getStatus();
60   - respDTO.channelOrderNo = channelOrderNo;
  60 + respDTO.channelTransferNo = channelOrderNo;
61 61 respDTO.outTransferNo = outTransferNo;
62 62 respDTO.rawData = rawData;
63 63 return respDTO;
... ... @@ -85,7 +85,7 @@ public class PayTransferRespDTO {
85 85 String outTransferNo, Object rawData) {
86 86 PayTransferRespDTO respDTO = new PayTransferRespDTO();
87 87 respDTO.status = PayTransferStatusRespEnum.SUCCESS.getStatus();
88   - respDTO.channelOrderNo = channelTransferNo;
  88 + respDTO.channelTransferNo = channelTransferNo;
89 89 respDTO.successTime = successTime;
90 90 // 相对通用的字段
91 91 respDTO.outTransferNo = outTransferNo;
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/transfer/PayTransferUnifiedReqDTO.java
... ... @@ -6,6 +6,7 @@ import lombok.Data;
6 6 import org.hibernate.validator.constraints.Length;
7 7  
8 8 import javax.validation.constraints.Min;
  9 +import javax.validation.constraints.NotBlank;
9 10 import javax.validation.constraints.NotEmpty;
10 11 import javax.validation.constraints.NotNull;
11 12 import java.util.Map;
... ... @@ -50,27 +51,23 @@ public class PayTransferUnifiedReqDTO {
50 51 @Length(max = 128, message = "转账标题不能超过 128")
51 52 private String title;
52 53  
53   - /**
54   - * 收款方信息。
55   - *
56   - * 转账类型 {@link #type} 不同,收款方信息不同
57   - */
58   - @NotEmpty(message = "收款方信息 不能为空")
59   - private Map<String, String> payeeInfo;
60 54  
61 55 /**
62 56 * 支付宝登录号
63 57 */
  58 + @NotBlank(message = "支付宝登录号不能为空", groups = {PayTransferTypeEnum.Alipay.class})
64 59 private String alipayLogonId;
65 60  
66 61 /**
67 62 * 支付宝账号名称
68 63 */
  64 + @NotBlank(message = "支付宝账号名称不能为空", groups = {PayTransferTypeEnum.Alipay.class})
69 65 private String alipayAccountName;
70 66  
71 67 /**
72 68 * 微信 openId
73 69 */
  70 + @NotBlank(message = "微信 openId 不能为空", groups = {PayTransferTypeEnum.WxPay.class})
74 71 private String openid;
75 72  
76 73 /**
... ... @@ -82,5 +79,4 @@ public class PayTransferUnifiedReqDTO {
82 79 * 支付渠道的额外参数
83 80 */
84 81 private Map<String, String> channelExtras;
85   -
86 82 }
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java
... ... @@ -11,6 +11,7 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReq
11 11 import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO;
12 12 import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO;
13 13 import cn.iocoder.yudao.framework.pay.core.client.exception.PayException;
  14 +import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum;
14 15 import lombok.extern.slf4j.Slf4j;
15 16  
16 17 import java.util.Map;
... ... @@ -185,7 +186,7 @@ public abstract class AbstractPayClient&lt;Config extends PayClientConfig&gt; implemen
185 186  
186 187 @Override
187 188 public final PayTransferRespDTO unifiedTransfer(PayTransferUnifiedReqDTO reqDTO) {
188   - ValidationUtils.validate(reqDTO);
  189 + validatePayTransferReqDTO(reqDTO);
189 190 PayTransferRespDTO resp;
190 191 try{
191 192 resp = doUnifiedTransfer(reqDTO);
... ... @@ -199,6 +200,22 @@ public abstract class AbstractPayClient&lt;Config extends PayClientConfig&gt; implemen
199 200 }
200 201 return resp;
201 202 }
  203 + private void validatePayTransferReqDTO(PayTransferUnifiedReqDTO reqDTO) {
  204 + PayTransferTypeEnum transferType = PayTransferTypeEnum.typeOf(reqDTO.getType());
  205 + switch (transferType) {
  206 + case ALIPAY_BALANCE: {
  207 + ValidationUtils.validate(reqDTO, PayTransferTypeEnum.Alipay.class);
  208 + break;
  209 + }
  210 + case WX_BALANCE: {
  211 + ValidationUtils.validate(reqDTO, PayTransferTypeEnum.WxPay.class);
  212 + break;
  213 + }
  214 + default: {
  215 + throw new UnsupportedOperationException("待实现");
  216 + }
  217 + }
  218 + }
202 219  
203 220 protected abstract PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO)
204 221 throws Throwable;
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java
... ... @@ -44,6 +44,8 @@ import java.util.function.Supplier;
44 44  
45 45 import static cn.hutool.core.date.DatePattern.NORM_DATETIME_FORMATTER;
46 46 import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;
  47 +import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.NOT_IMPLEMENTED;
  48 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
47 49 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception0;
48 50 import static cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig.MODE_CERTIFICATE;
49 51  
... ... @@ -227,9 +229,8 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient&lt;AlipayPa
227 229 protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) throws AlipayApiException {
228 230 // 1.1 校验公钥类型 必须使用公钥证书模式
229 231 if (!Objects.equals(config.getMode(), MODE_CERTIFICATE)) {
230   - throw new IllegalStateException("支付宝单笔转账必须使用公钥证书模式");
  232 + throw exception0(BAD_REQUEST.getCode(),"支付宝单笔转账必须使用公钥证书模式");
231 233 }
232   -
233 234 // 1.2 构建 AlipayFundTransUniTransferModel
234 235 AlipayFundTransUniTransferModel model = new AlipayFundTransUniTransferModel();
235 236 // ① 通用的参数
... ... @@ -247,16 +248,8 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient&lt;AlipayPa
247 248 // ② 个性化的参数
248 249 Participant payeeInfo = new Participant();
249 250 payeeInfo.setIdentityType("ALIPAY_LOGON_ID");
250   - String logonId = MapUtil.getStr(reqDTO.getPayeeInfo(), "ALIPAY_LOGON_ID");
251   - if (StrUtil.isEmpty(logonId)) {
252   - throw exception0(BAD_REQUEST.getCode(), "支付包登录 ID 不能为空");
253   - }
254   - String accountName = MapUtil.getStr(reqDTO.getPayeeInfo(), "ALIPAY_ACCOUNT_NAME");
255   - if (StrUtil.isEmpty(accountName)) {
256   - throw exception0(BAD_REQUEST.getCode(), "支付包账户名称不能为空");
257   - }
258   - payeeInfo.setIdentity(logonId); // 支付宝登录号
259   - payeeInfo.setName(accountName); // 支付宝账号姓名
  251 + payeeInfo.setIdentity(reqDTO.getAlipayLogonId()); // 支付宝登录号
  252 + payeeInfo.setName(reqDTO.getAlipayAccountName()); // 支付宝账号姓名
260 253 model.setPayeeInfo(payeeInfo);
261 254 // 1.3 构建 AlipayFundTransUniTransferRequest
262 255 AlipayFundTransUniTransferRequest request = new AlipayFundTransUniTransferRequest();
... ... @@ -279,10 +272,10 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient&lt;AlipayPa
279 272 Participant payeeInfo = new Participant();
280 273 payeeInfo.setIdentityType("BANKCARD_ACCOUNT");
281 274 // TODO 待实现
282   - throw new UnsupportedOperationException("待实现");
  275 + throw exception(NOT_IMPLEMENTED);
283 276 }
284 277 default: {
285   - throw new IllegalStateException("不正确的转账类型: " + transferType);
  278 + throw exception0(BAD_REQUEST.getCode(),"不正确的转账类型: {}",transferType);
286 279 }
287 280 }
288 281 }
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/channel/PayChannelEnum.java
... ... @@ -62,4 +62,7 @@ public enum PayChannelEnum {
62 62 return ArrayUtil.firstMatch(o -> o.getCode().equals(code), values());
63 63 }
64 64  
  65 + public static boolean isAlipay(String channelCode) {
  66 + return channelCode != null && channelCode.startsWith("alipay");
  67 + }
65 68 }
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/transfer/PayTransferTypeEnum.java
... ... @@ -21,8 +21,11 @@ public enum PayTransferTypeEnum implements IntArrayValuable {
21 21 BANK_CARD(3, "银行卡"),
22 22 WALLET_BALANCE(4, "钱包余额");
23 23  
24   - public static final String ALIPAY_LOGON_ID = "ALIPAY_LOGON_ID";
25   - public static final String ALIPAY_ACCOUNT_NAME = "ALIPAY_ACCOUNT_NAME";
  24 + public interface WxPay {
  25 + }
  26 +
  27 + public interface Alipay {
  28 + }
26 29  
27 30 private final Integer type;
28 31 private final String name;
... ...
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferCreateReqDTO.java
... ... @@ -5,6 +5,7 @@ import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferTypeEnum;
5 5 import lombok.Data;
6 6  
7 7 import javax.validation.constraints.Min;
  8 +import javax.validation.constraints.NotBlank;
8 9 import javax.validation.constraints.NotEmpty;
9 10 import javax.validation.constraints.NotNull;
10 11 import java.util.Map;
... ... @@ -21,6 +22,17 @@ public class PayTransferCreateReqDTO {
21 22 @NotNull(message = "应用编号不能为空")
22 23 private Long appId;
23 24  
  25 + @NotEmpty(message = "转账渠道不能为空")
  26 + private String channelCode;
  27 +
  28 + /**
  29 + * 转账渠道的额外参数
  30 + */
  31 + private Map<String, String> channelExtras;
  32 +
  33 + @NotEmpty(message = "用户 IP 不能为空")
  34 + private String userIp;
  35 +
24 36 /**
25 37 * 类型
26 38 */
... ... @@ -28,11 +40,12 @@ public class PayTransferCreateReqDTO {
28 40 @InEnum(PayTransferTypeEnum.class)
29 41 private Integer type;
30 42  
  43 +
31 44 /**
32   - * 商户订单编号
  45 + * 商户转账单编号
33 46 */
34   - @NotEmpty(message = "商户订单编号不能为空")
35   - private String merchantOrderId;
  47 + @NotEmpty(message = "商户转账单编号能为空")
  48 + private String merchantTransferId;
36 49  
37 50 /**
38 51 * 转账金额,单位:分
... ... @@ -45,9 +58,21 @@ public class PayTransferCreateReqDTO {
45 58 * 转账标题
46 59 */
47 60 @NotEmpty(message = "转账标题不能为空")
48   - private String title;
  61 + private String subject;
49 62  
50 63 @NotEmpty(message = "收款方信息不能为空")
51 64 private Map<String, String> payeeInfo;
52 65  
  66 + @NotBlank(message = "支付宝登录号不能为空", groups = {PayTransferTypeEnum.Alipay.class})
  67 + private String alipayLogonId;
  68 +
  69 + @NotBlank(message = "支付宝登录号不能为空", groups = {PayTransferTypeEnum.Alipay.class})
  70 + private String alipayAccountName;
  71 +
  72 + // ========== 微信转账相关字段 ==========
  73 + @NotBlank(message = "微信 openId 不能为空", groups = {PayTransferTypeEnum.WxPay.class})
  74 + private String openid;
  75 +
  76 + private String wxAccountName;
  77 +
53 78 }
... ...
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/ErrorCodeConstants.java
... ... @@ -67,11 +67,11 @@ public interface ErrorCodeConstants {
67 67 ErrorCode PAY_TRANSFER_SUBMIT_CHANNEL_ERROR = new ErrorCode(1_007_009_000, "发起转账报错,错误码:{},错误提示:{}");
68 68 ErrorCode PAY_TRANSFER_NOT_FOUND = new ErrorCode(1_007_009_001, "转账交易单不存在");
69 69 ErrorCode PAY_TRANSFER_STATUS_IS_SUCCESS = new ErrorCode(1_007_009_002, "转账单已成功转账");
70   - ErrorCode PAY_TRANSFER_STATUS_IS_NOT_WAITING = new ErrorCode(1_007_009_003, "转账单不处于待转账");
71   - ErrorCode PAY_TRANSFER_STATUS_IS_NOT_PENDING = new ErrorCode(1_007_009_004, "转账单不处于待转账或转账中");
72   - ErrorCode PAY_TRANSFER_EXTENSION_NOT_FOUND = new ErrorCode(1_007_009_005, "转账交易拓展单不存在");
73   - ErrorCode PAY_TRANSFER_TYPE_AND_CHANNEL_NOT_MATCH = new ErrorCode(1_007_009_006, "转账类型和转账渠道不匹配");
74   - ErrorCode PAY_TRANSFER_EXTENSION_STATUS_IS_NOT_PENDING = new ErrorCode(1_007_009_007, "转账拓展单不处于待转账或转账中");
  70 + ErrorCode PAY_TRANSFER_EXISTS = new ErrorCode(1_007_009_003, "已经存在转账单");
  71 + ErrorCode PAY_MERCHANT_TRANSFER_EXISTS = new ErrorCode(1_007_009_004, "该笔业务的转账已经存在,请查询相关状态");
  72 + ErrorCode PAY_TRANSFER_STATUS_IS_NOT_WAITING = new ErrorCode(1_007_009_005, "转账单不处于待转账");
  73 + ErrorCode PAY_TRANSFER_STATUS_IS_NOT_PENDING = new ErrorCode(1_007_009_006, "转账单不处于待转账或转账中");
  74 + ErrorCode PAY_TRANSFER_TYPE_AND_CHANNEL_NOT_MATCH = new ErrorCode(1_007_009_008, "转账类型和转账渠道不匹配");
75 75  
76 76 // ========== 示例订单 1-007-900-000 ==========
77 77 ErrorCode DEMO_ORDER_NOT_FOUND = new ErrorCode(1_007_900_000, "示例订单不存在");
... ...
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/transfer/PayTransferStatusEnum.java
... ... @@ -49,7 +49,7 @@ public enum PayTransferStatusEnum {
49 49 * @param status 状态
50 50 */
51 51 public static boolean isPendingStatus(Integer status) {
52   - return Objects.equals(status, WAITING.getStatus()) || Objects.equals(status, IN_PROGRESS.status);
  52 + return Objects.equals(status, WAITING.getStatus()) || Objects.equals(status, IN_PROGRESS.getStatus());
53 53 }
54 54  
55 55 }
... ...
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/transfer/PayTransferTypeEnum.java
... ... @@ -21,8 +21,11 @@ public enum PayTransferTypeEnum implements IntArrayValuable {
21 21 BANK_CARD(3, "银行卡"),
22 22 WALLET_BALANCE(4, "钱包余额");
23 23  
24   - public static final String ALIPAY_LOGON_ID = "ALIPAY_LOGON_ID";
25   - public static final String ALIPAY_ACCOUNT_NAME = "ALIPAY_ACCOUNT_NAME";
  24 + public interface WxPay {
  25 + }
  26 +
  27 + public interface Alipay {
  28 + }
26 29  
27 30 private final Integer type;
28 31 private final String name;
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/transfer/PayDemoTransferCreateReqVO.java
... ... @@ -28,13 +28,13 @@ public class PayDemoTransferCreateReqVO {
28 28 @Min(value = 1, message = "转账金额必须大于零")
29 29 private Integer price;
30 30  
31   - // ========== 支付宝,微信转账相关字段 ==========
  31 + // ========== 支付宝转账相关字段 ==========
32 32 @Schema(description = "支付宝登录号,支持邮箱和手机号格式", example = "test1@@sandbox.com")
33 33 @NotBlank(message = "支付宝登录号不能为空", groups = {Alipay.class})
34 34 private String alipayLogonId;
35 35  
36 36 @Schema(description = "支付宝账号名称", example = "test1")
37   - @NotBlank(message = "支付宝登录号不能为空", groups = {Alipay.class})
  37 + @NotBlank(message = "支付宝账号名称不能为空", groups = {Alipay.class})
38 38 private String alipayAccountName;
39 39  
40 40 // ========== 微信转账相关字段 ==========
... ... @@ -42,7 +42,7 @@ public class PayDemoTransferCreateReqVO {
42 42 @NotBlank(message = "微信 openId 不能为空", groups = {WxPay.class})
43 43 private String openid;
44 44  
45   - @Schema(description = "微信账号名称", example = "oLefc4g5Gjxxxxxx")
  45 + @Schema(description = "微信账号名称", example = "test2")
46 46 private String wxAccountName;
47 47  
48 48 // ========== 转账到银行卡和钱包相关字段 待补充 ==========
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/PayTransferController.java
1 1 package cn.iocoder.yudao.module.pay.controller.admin.transfer;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4   -import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferSubmitReqVO;
5   -import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferSubmitRespVO;
  4 +import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferCreateReqVO;
  5 +import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferCreateRespVO;
  6 +import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO;
6 7 import cn.iocoder.yudao.module.pay.service.transfer.PayTransferService;
7 8 import io.swagger.v3.oas.annotations.Operation;
8 9 import io.swagger.v3.oas.annotations.tags.Tag;
... ... @@ -27,12 +28,12 @@ public class PayTransferController {
27 28 @Resource
28 29 private PayTransferService payTransferService;
29 30  
30   - @PostMapping("/submit")
31   - @Operation(summary = "提交转账订单")
  31 + @PostMapping("/initiate")
  32 + @Operation(summary = "发起转账")
32 33 // TODO @jason:权限的设置, 管理后台页面加的时候加一下
33   - public CommonResult<PayTransferSubmitRespVO> submitPayTransfer(@Valid @RequestBody PayTransferSubmitReqVO reqVO) {
34   - PayTransferSubmitRespVO respVO = payTransferService.submitTransfer(reqVO, getClientIP());
35   - return success(respVO);
  34 + public CommonResult<PayTransferCreateRespVO> initiatePayTransfer(@Valid @RequestBody PayTransferCreateReqVO reqVO) {
  35 + PayTransferDO payTransfer = payTransferService.initiateTransfer(reqVO, getClientIP());
  36 + return success(new PayTransferCreateRespVO().setId(payTransfer.getId()).setStatus(payTransfer.getStatus()));
36 37 }
37 38  
38 39 }
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferCreateReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.pay.controller.admin.transfer.vo;
  2 +
  3 +import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
  4 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  5 +import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.transfer.PayDemoTransferCreateReqVO;
  6 +import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferTypeEnum;
  7 +import io.swagger.v3.oas.annotations.media.Schema;
  8 +import lombok.Data;
  9 +
  10 +import javax.validation.Validator;
  11 +import javax.validation.constraints.Min;
  12 +import javax.validation.constraints.NotBlank;
  13 +import javax.validation.constraints.NotEmpty;
  14 +import javax.validation.constraints.NotNull;
  15 +import java.util.Map;
  16 +
  17 +@Schema(description = "管理后台 - 发起转账 Request VO")
  18 +@Data
  19 +public class PayTransferCreateReqVO {
  20 +
  21 + @Schema(description = "应用编号不能为空", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  22 + @NotNull(message = "应用编号不能为空")
  23 + private Long appId;
  24 +
  25 + @Schema(description = "商户转账单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  26 + @NotNull(message = "商户转账单编号不能为空")
  27 + private String merchantTransferId;
  28 +
  29 + @Schema(description = "转账类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  30 + @NotNull(message = "转账类型不能为空")
  31 + @InEnum(PayTransferTypeEnum.class)
  32 + private Integer type;
  33 +
  34 + @Schema(description = "转账渠道", requiredMode = Schema.RequiredMode.REQUIRED, example = "alipay_pc")
  35 + @NotEmpty(message = "转账渠道不能为空")
  36 + private String channelCode;
  37 +
  38 + @Min(value = 1, message = "转账金额必须大于零")
  39 + @NotNull(message = "转账金额不能为空")
  40 + private Integer price;
  41 +
  42 + @Schema(description = "转账标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "示例转账")
  43 + @NotEmpty(message = "转账标题不能为空")
  44 + private String subject;
  45 +
  46 + @Schema(description = "支付宝登录号", example = "test1@sandbox.com")
  47 + @NotBlank(message = "支付宝登录号不能为空", groups = {PayTransferTypeEnum.Alipay.class})
  48 + private String alipayLogonId;
  49 +
  50 + @Schema(description = "支付宝账号名称", example = "test1")
  51 + @NotBlank(message = "支付宝账号名称不能为空", groups = {PayTransferTypeEnum.Alipay.class})
  52 + private String alipayAccountName;
  53 +
  54 + @Schema(description = "微信 openId", example = "oLefc4g5Gxx")
  55 + @NotBlank(message = "微信 openId 不能为空", groups = {PayTransferTypeEnum.WxPay.class})
  56 + private String openid;
  57 +
  58 + @Schema(description = "微信账号名称", example = "test2")
  59 + private String wxAccountName;
  60 +
  61 + @Schema(description = "转账渠道的额外参数")
  62 + private Map<String, String> channelExtras;
  63 +
  64 + public void validate(Validator validator) {
  65 + PayTransferTypeEnum transferType = PayTransferTypeEnum.typeOf(type);
  66 + switch (transferType) {
  67 + case ALIPAY_BALANCE: {
  68 + ValidationUtils.validate(validator, this, PayDemoTransferCreateReqVO.Alipay.class);
  69 + break;
  70 + }
  71 + case WX_BALANCE: {
  72 + ValidationUtils.validate(validator, this, PayDemoTransferCreateReqVO.WxPay.class);
  73 + break;
  74 + }
  75 + default: {
  76 + throw new UnsupportedOperationException("待实现");
  77 + }
  78 + }
  79 + }
  80 +
  81 +}
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferSubmitRespVO.java renamed to yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferCreateRespVO.java
... ... @@ -3,9 +3,12 @@ package cn.iocoder.yudao.module.pay.controller.admin.transfer.vo;
3 3 import io.swagger.v3.oas.annotations.media.Schema;
4 4 import lombok.Data;
5 5  
6   -@Schema(description = "管理后台 - 转账单提交 Response VO")
  6 +@Schema(description = "管理后台 - 发起转账 Response VO")
7 7 @Data
8   -public class PayTransferSubmitRespVO {
  8 +public class PayTransferCreateRespVO {
  9 +
  10 + @Schema(description = "转账单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  11 + private Long id;
9 12  
10 13 @Schema(description = "转账状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") // 参见 PayTransferStatusEnum 枚举
11 14 private Integer status;
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferSubmitReqVO.java deleted
1   -package cn.iocoder.yudao.module.pay.controller.admin.transfer.vo;
2   -
3   -import io.swagger.v3.oas.annotations.media.Schema;
4   -import lombok.Data;
5   -
6   -import javax.validation.constraints.NotEmpty;
7   -import javax.validation.constraints.NotNull;
8   -import java.util.Map;
9   -
10   -@Schema(description = "管理后台 - 转账单提交 Request VO")
11   -@Data
12   -public class PayTransferSubmitReqVO {
13   -
14   - @Schema(description = "转账单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
15   - @NotNull(message = "转账单编号不能为空")
16   - private Long id;
17   -
18   - @Schema(description = "转账渠道", requiredMode = Schema.RequiredMode.REQUIRED, example = "alipay_transfer")
19   - @NotEmpty(message = "转账渠道不能为空")
20   - private String channelCode;
21   -
22   - @Schema(description = "转账渠道的额外参数")
23   - private Map<String, String> channelExtras;
24   -
25   -}
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/transfer/PayTransferConvert.java
1 1 package cn.iocoder.yudao.module.pay.convert.transfer;
2 2  
  3 +import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO;
3 4 import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateReqDTO;
4 5 import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.transfer.PayDemoTransferCreateReqVO;
  6 +import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferCreateReqVO;
5 7 import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO;
6 8 import org.mapstruct.Mapper;
7 9 import org.mapstruct.Mapping;
... ... @@ -12,9 +14,13 @@ public interface PayTransferConvert {
12 14  
13 15 PayTransferConvert INSTANCE = Mappers.getMapper(PayTransferConvert.class);
14 16  
15   - @Mapping(source = "title", target = "subject") // TODO @jason:是不是都改成 subject 完事呀?
16 17 PayTransferDO convert(PayTransferCreateReqDTO dto);
17 18  
  19 + @Mapping(source = "subject", target = "title")
  20 + PayTransferUnifiedReqDTO convert2(PayTransferCreateReqDTO dto);
  21 +
  22 + PayTransferCreateReqDTO convert(PayTransferCreateReqVO vo);
  23 +
18 24 PayTransferCreateReqDTO convert(PayDemoTransferCreateReqVO vo);
19 25  
20 26 }
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletConvert.java
... ... @@ -22,14 +22,10 @@ public interface PayWalletConvert {
22 22  
23 23 PageResult<PayWalletRespVO> convertPage(PageResult<PayWalletDO> page);
24 24  
25   - default PageResult<PayWalletRespVO> convertPage(PageResult<PayWalletDO> page, Map<Long, MemberUserRespDTO> userMap){
  25 + default PageResult<PayWalletRespVO> convertPage(PageResult<PayWalletDO> page, Map<Long, MemberUserRespDTO> userMap) {
26 26 PageResult<PayWalletRespVO> pageResult = convertPage(page);
27   - pageResult.getList().forEach( wallet -> MapUtils.findAndThen(userMap, wallet.getUserId(),
28   - user -> {
29   - // TODO @jason:可以链式调用哈;
30   - wallet.setNickname(user.getNickname());
31   - wallet.setAvatar(user.getAvatar());
32   - }));
  27 + pageResult.getList().forEach(wallet -> MapUtils.findAndThen(userMap, wallet.getUserId(),
  28 + user -> wallet.setNickname(user.getNickname()).setAvatar(user.getAvatar())));
33 29 return pageResult;
34 30 }
35 31  
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/demo/PayDemoTransferDO.java
... ... @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.pay.dal.dataobject.demo;
2 2  
3 3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
4 4 import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum;
  5 +import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
5 6 import com.baomidou.mybatisplus.annotation.KeySequence;
6 7 import com.baomidou.mybatisplus.annotation.TableId;
7 8 import com.baomidou.mybatisplus.annotation.TableName;
... ... @@ -31,6 +32,13 @@ public class PayDemoTransferDO extends BaseDO {
31 32 private Long userId;
32 33  
33 34 /**
  35 + * 应用编号
  36 + *
  37 + * 关联 {@link PayAppDO#getId()}
  38 + */
  39 + private Long appId;
  40 +
  41 + /**
34 42 * 转账金额,单位:分
35 43 */
36 44 private Integer price;
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/transfer/PayTransferDO.java
... ... @@ -61,11 +61,11 @@ public class PayTransferDO extends BaseDO {
61 61  
62 62 // ========== 商户相关字段 ==========
63 63 /**
64   - * 商户订单编号
  64 + * 商户转账单编号
65 65 *
66 66 * 例如说,内部系统 A 的订单号,需要保证每个 PayAppDO 唯一
67 67 */
68   - private String merchantOrderId;
  68 + private String merchantTransferId;
69 69  
70 70 // ========== 转账相关字段 ==========
71 71  
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/transfer/PayTransferMapper.java
... ... @@ -15,6 +15,14 @@ public interface PayTransferMapper extends BaseMapperX&lt;PayTransferDO&gt; {
15 15 .eq(PayTransferDO::getId, id).in(PayTransferDO::getStatus, status));
16 16 }
17 17  
  18 + default PayTransferDO selectByAppIdAndMerchantTransferId(Long appId, String merchantTransferId){
  19 + return selectOne(PayTransferDO::getAppId, appId,
  20 + PayTransferDO::getMerchantTransferId, merchantTransferId);
  21 + }
  22 +
  23 + default PayTransferDO selectByNo(String no){
  24 + return selectOne(PayTransferDO::getNo, no);
  25 + }
18 26 }
19 27  
20 28  
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoTransferServiceImpl.java
... ... @@ -4,7 +4,6 @@ import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.transfer.PayDemoTran
4 4 import cn.iocoder.yudao.module.pay.convert.demo.PayDemoTransferConvert;
5 5 import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoTransferDO;
6 6 import cn.iocoder.yudao.module.pay.dal.mysql.demo.PayDemoTransferMapper;
7   -import cn.iocoder.yudao.module.pay.service.transfer.PayTransferService;
8 7 import org.springframework.stereotype.Service;
9 8 import org.springframework.transaction.annotation.Transactional;
10 9 import org.springframework.validation.annotation.Validated;
... ... @@ -33,8 +32,6 @@ public class PayDemoTransferServiceImpl implements PayDemoTransferService {
33 32 @Resource
34 33 private PayDemoTransferMapper demoTransferMapper;
35 34 @Resource
36   - private PayTransferService transferService;
37   - @Resource
38 35 private Validator validator;
39 36  
40 37 @Override
... ... @@ -44,7 +41,7 @@ public class PayDemoTransferServiceImpl implements PayDemoTransferService {
44 41 vo.validate(validator);
45 42 // 2 保存示例转账业务表
46 43 PayDemoTransferDO demoTransfer = PayDemoTransferConvert.INSTANCE.convert(vo)
47   - .setUserId(userId).setTransferStatus(WAITING.getStatus());
  44 + .setUserId(userId).setAppId(TRANSFER_APP_ID).setTransferStatus(WAITING.getStatus());
48 45 demoTransferMapper.insert(demoTransfer);
49 46 return demoTransfer.getId();
50 47 }
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferService.java
1 1 package cn.iocoder.yudao.module.pay.service.transfer;
2 2  
3 3 import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateReqDTO;
4   -import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferSubmitReqVO;
5   -import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferSubmitRespVO;
  4 +import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferCreateReqVO;
  5 +import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO;
6 6  
7 7 import javax.validation.Valid;
8 8  
... ... @@ -14,7 +14,7 @@ import javax.validation.Valid;
14 14 public interface PayTransferService {
15 15  
16 16 /**
17   - * 提交转账单
  17 + * 发起转账
18 18 *
19 19 * 此时,会发起支付渠道的调用
20 20 *
... ... @@ -22,7 +22,7 @@ public interface PayTransferService {
22 22 * @param userIp 用户 ip
23 23 * @return 渠道的返回结果
24 24 */
25   - PayTransferSubmitRespVO submitTransfer(@Valid PayTransferSubmitReqVO reqVO, String userIp);
  25 + PayTransferDO initiateTransfer(@Valid PayTransferCreateReqVO reqVO, String userIp);
26 26  
27 27 /**
28 28 * 创建转账单
... ... @@ -32,4 +32,10 @@ public interface PayTransferService {
32 32 */
33 33 Long createTransfer(@Valid PayTransferCreateReqDTO reqDTO);
34 34  
  35 + /**
  36 + * 获取转账单
  37 + * @param id 转账单编号
  38 + */
  39 + PayTransferDO getTransfer(Long id);
  40 +
35 41 }
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceImpl.java
1 1 package cn.iocoder.yudao.module.pay.service.transfer;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4   -import cn.hutool.core.util.StrUtil;
5 4 import cn.hutool.extra.spring.SpringUtil;
6 5 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
7 6 import cn.iocoder.yudao.framework.pay.core.client.PayClient;
8 7 import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO;
9 8 import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO;
  9 +import cn.iocoder.yudao.framework.pay.core.client.exception.PayException;
10 10 import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
11 11 import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferStatusRespEnum;
12 12 import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum;
13 13 import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateReqDTO;
14   -import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferSubmitReqVO;
15   -import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferSubmitRespVO;
16   -import cn.iocoder.yudao.module.pay.convert.transfer.PayTransferConvert;
  14 +import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferCreateReqVO;
  15 +import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
17 16 import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO;
18 17 import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO;
19   -import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferExtensionDO;
20   -import cn.iocoder.yudao.module.pay.dal.mysql.transfer.PayTransferExtensionMapper;
21 18 import cn.iocoder.yudao.module.pay.dal.mysql.transfer.PayTransferMapper;
22 19 import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO;
23 20 import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferStatusEnum;
... ... @@ -28,8 +25,10 @@ import org.springframework.stereotype.Service;
28 25 import org.springframework.transaction.annotation.Transactional;
29 26  
30 27 import javax.annotation.Resource;
  28 +import javax.validation.Validator;
31 29  
32 30 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  31 +import static cn.iocoder.yudao.module.pay.convert.transfer.PayTransferConvert.INSTANCE;
33 32 import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*;
34 33 import static cn.iocoder.yudao.module.pay.enums.transfer.PayTransferStatusEnum.*;
35 34  
... ... @@ -48,60 +47,69 @@ public class PayTransferServiceImpl implements PayTransferService {
48 47 @Resource
49 48 private PayTransferMapper transferMapper;
50 49 @Resource
51   - private PayTransferExtensionMapper transferExtensionMapper;
52   -
53   - @Resource
54 50 private PayAppService appService;
55 51 @Resource
56 52 private PayChannelService channelService;
57   -
58 53 @Resource
59 54 private PayNoRedisDAO noRedisDAO;
  55 + @Resource
  56 + private Validator validator;
60 57  
61 58 @Override
62   - public PayTransferSubmitRespVO submitTransfer(PayTransferSubmitReqVO reqVO, String userIp) {
  59 + @Transactional(rollbackFor = Exception.class)
  60 + public PayTransferDO initiateTransfer(PayTransferCreateReqVO reqVO, String userIp) {
  61 + // 1.1 校验参数
  62 + reqVO.validate(validator);
  63 + // todo 使用 AssertTrue
  64 + validateChannelCodeAndTypeMatch(reqVO.getChannelCode(), reqVO.getType());
  65 + // 1.2 校验转账单是否可以创建
  66 + validateTransferCanCreate(reqVO.getAppId(), reqVO.getMerchantTransferId());
  67 + // 2. 创建转账单,发起转账
  68 + PayTransferCreateReqDTO req = INSTANCE.convert(reqVO).setUserIp(userIp);
  69 + Long transferId = createTransfer(req);
  70 + // 3. 返回转账单
  71 + return getTransfer(transferId);
  72 + }
  73 +
  74 + @Override
  75 + @Transactional(rollbackFor = Exception.class)
  76 + public Long createTransfer(PayTransferCreateReqDTO reqDTO) {
63 77 // 1.1 校验转账单是否可以提交
64   - PayTransferDO transfer = validateTransferCanSubmit(reqVO.getId());
65   - // 1.2 校验转账类型和渠道是否匹配
66   - validateChannelCodeAndTypeMatch(reqVO.getChannelCode(), transfer.getType());
  78 + validateTransferExist(reqDTO.getAppId(), reqDTO.getMerchantTransferId());
  79 + // 1.2 校验 App
  80 + PayAppDO appDO = appService.validPayApp(reqDTO.getAppId());
67 81 // 1.3 校验支付渠道是否有效
68   - PayChannelDO channel = validateChannelCanSubmit(transfer.getAppId(), reqVO.getChannelCode());
  82 + PayChannelDO channel = channelService.validPayChannel(reqDTO.getAppId(), reqDTO.getChannelCode());
69 83 PayClient client = channelService.getPayClient(channel.getId());
70   -
71   - // 2. 新增转账拓展单
  84 + if (client == null) {
  85 + log.error("[createTransfer][渠道编号({}) 找不到对应的支付客户端]", channel.getId());
  86 + throw exception(CHANNEL_NOT_FOUND);
  87 + }
  88 + // 2.创建转账单
72 89 String no = noRedisDAO.generate(TRANSFER_NO_PREFIX);
73   - PayTransferExtensionDO transferExtension = new PayTransferExtensionDO().setNo(no)
74   - .setTransferId(transfer.getId()).setChannelId(channel.getId())
75   - .setChannelCode(channel.getCode()).setStatus(WAITING.getStatus());
76   - transferExtensionMapper.insert(transferExtension);
77   -
78   - // 3. 调用三方渠道发起转账
79   - PayTransferUnifiedReqDTO transferUnifiedReq = new PayTransferUnifiedReqDTO()
80   - .setOutTransferNo(transferExtension.getNo()).setPrice(transfer.getPrice())
81   - .setType(transfer.getType()).setTitle(transfer.getSubject())
82   - .setUserIp(userIp)
83   - .setChannelExtras(reqVO.getChannelExtras());
84   - PayTransferRespDTO unifiedTransferResp = client.unifiedTransfer(transferUnifiedReq);
85   -
86   - // 4. 通知转账结果
87   - getSelf().notifyTransfer(channel, unifiedTransferResp);
88   - // 如有渠道错误码,则抛出业务异常,提示用户
89   - if (StrUtil.isNotEmpty(unifiedTransferResp.getChannelErrorCode())) {
90   - throw exception(PAY_TRANSFER_SUBMIT_CHANNEL_ERROR, unifiedTransferResp.getChannelErrorCode(),
91   - unifiedTransferResp.getChannelErrorMsg());
  90 + PayTransferDO transfer = INSTANCE.convert(reqDTO)
  91 + .setChannelId(channel.getId())
  92 + .setNo(no).setStatus(WAITING.getStatus())
  93 + .setNotifyUrl(appDO.getRefundNotifyUrl()); // TODO 需要加个transfer Notify url
  94 + transferMapper.insert(transfer);
  95 + try {
  96 + // 3. 调用三方渠道发起转账
  97 + PayTransferUnifiedReqDTO transferUnifiedReq = INSTANCE.convert2(reqDTO)
  98 + .setOutTransferNo(no);
  99 + PayTransferRespDTO unifiedTransferResp = client.unifiedTransfer(transferUnifiedReq);
  100 + // 4. 通知转账结果
  101 + getSelf().notifyTransfer(channel, unifiedTransferResp);
  102 + } catch (PayException e) {
  103 + // 这里仅打印异常,不进行抛出。
  104 + // TODO 说明
  105 + log.error("[createTransfer][转账 id({}) requestDTO({}) 发生异常]", transfer.getId(), reqDTO, e);
92 106 }
93   - return new PayTransferSubmitRespVO().setStatus(unifiedTransferResp.getStatus());
  107 + return transfer.getId();
94 108 }
95 109  
96 110 @Override
97   - public Long createTransfer(PayTransferCreateReqDTO reqDTO) {
98   - // 校验 App
99   - appService.validPayApp(reqDTO.getAppId());
100   - // 创建转账单
101   - PayTransferDO transfer = PayTransferConvert.INSTANCE.convert(reqDTO)
102   - .setStatus(WAITING.getStatus());
103   - transferMapper.insert(transfer);
104   - return transfer.getId();
  111 + public PayTransferDO getTransfer(Long id) {
  112 + return transferMapper.selectById(id);
105 113 }
106 114  
107 115 @Transactional(rollbackFor = Exception.class)
... ... @@ -119,26 +127,38 @@ public class PayTransferServiceImpl implements PayTransferService {
119 127 // TODO IN_PROGRESS 待处理
120 128 }
121 129  
122   - private void notifyTransferSuccess(PayChannelDO channel, PayTransferRespDTO notify) {
123   - // 1. 更新 PayTransferExtensionDO 转账成功
124   - PayTransferExtensionDO transferExtension = updateTransferExtensionSuccess(notify);
  130 + private void validateTransferExist(Long appId, String merchantTransferId ) {
  131 + PayTransferDO transfer = transferMapper.selectByAppIdAndMerchantTransferId(appId, merchantTransferId);
  132 + if (transfer != null) { // 是否存在
  133 + throw exception(PAY_TRANSFER_EXISTS);
  134 + }
  135 + }
125 136  
126   - // 2. 更新 PayTransferDO 转账成功
127   - Boolean transferred = updateTransferSuccess(channel,transferExtension, notify);
  137 + private void validateTransferCanCreate(Long appId, String merchantTransferId ) {
  138 + PayTransferDO transfer = transferMapper.selectByAppIdAndMerchantTransferId(appId, merchantTransferId);
  139 + if (transfer != null) { // 是否存在
  140 + throw exception(PAY_MERCHANT_TRANSFER_EXISTS);
  141 + }
  142 + }
  143 +
  144 + private void notifyTransferSuccess(PayChannelDO channel, PayTransferRespDTO notify) {
  145 + // 1. 更新 PayTransferDO 转账成功
  146 + Boolean transferred = updateTransferSuccess(channel, notify);
128 147 if (transferred) {
129 148 return;
130 149 }
131   - // 3. TODO 插入转账通知记录
  150 + // 2. TODO 插入转账通知记录
132 151 }
133 152  
134   - private Boolean updateTransferSuccess(PayChannelDO channel, PayTransferExtensionDO transferExtension,
135   - PayTransferRespDTO notify) {
  153 + private Boolean updateTransferSuccess(PayChannelDO channel, PayTransferRespDTO notify) {
136 154 // 1.校验
137   - PayTransferDO transfer = transferMapper.selectById(transferExtension.getTransferId());
  155 + PayTransferDO transfer = transferMapper.selectByNo(notify.getOutTransferNo());
138 156 if (transfer == null) {
139 157 throw exception(PAY_TRANSFER_NOT_FOUND);
140 158 }
141   -
  159 + if (isSuccess(transfer.getStatus())) { // 如果已成功,直接返回,不用重复更新
  160 + return Boolean.TRUE;
  161 + }
142 162 if (!isPendingStatus(transfer.getStatus())) {
143 163 throw exception(PAY_TRANSFER_STATUS_IS_NOT_PENDING);
144 164 }
... ... @@ -146,76 +166,52 @@ public class PayTransferServiceImpl implements PayTransferService {
146 166 int updateCounts = transferMapper.updateByIdAndStatus(transfer.getId(),
147 167 CollUtil.newArrayList(WAITING.getStatus(), IN_PROGRESS.getStatus()),
148 168 new PayTransferDO().setStatus(SUCCESS.getStatus()).setSuccessTime(notify.getSuccessTime())
  169 + .setChannelTransferNo(notify.getChannelTransferNo())
149 170 .setChannelId(channel.getId()).setChannelCode(channel.getCode())
150   - .setNo(transferExtension.getNo()));
  171 + .setChannelNotifyData(JsonUtils.toJsonString(notify)));
151 172 if (updateCounts == 0) {
152 173 throw exception(PAY_TRANSFER_STATUS_IS_NOT_PENDING);
153 174 }
154 175 log.info("[updateTransferSuccess][transfer({}) 更新为已转账]", transfer.getId());
155   - return false;
  176 + return Boolean.FALSE;
156 177 }
157 178  
158   - private PayTransferExtensionDO updateTransferExtensionSuccess(PayTransferRespDTO notify) {
159   - // 1 校验
160   - PayTransferExtensionDO transferExtension = transferExtensionMapper.selectByNo(notify.getOutTransferNo());
161   - if (transferExtension == null) {
162   - throw exception(PAY_TRANSFER_EXTENSION_NOT_FOUND);
  179 + private void updateTransferClosed(PayChannelDO channel, PayTransferRespDTO notify) {
  180 + // 1.校验
  181 + PayTransferDO transfer = transferMapper.selectByNo(notify.getOutTransferNo());
  182 + if (transfer == null) {
  183 + throw exception(PAY_TRANSFER_NOT_FOUND);
163 184 }
164   - if (isSuccess(transferExtension.getStatus())) { // 如果已成功,直接返回,不用重复更新
165   - log.info("[updateTransferExtensionSuccess][transferExtension({}) 已经是成功状态,无需更新]", transferExtension.getId());
166   - return transferExtension;
  185 + if (isClosed(transfer.getStatus())) { // 如果已是关闭状态,直接返回,不用重复更新
  186 + log.info("[updateTransferClosed][transfer({}) 已经是关闭状态,无需更新]", transfer.getId());
  187 + return;
167 188 }
168   - if (!isPendingStatus(transferExtension.getStatus())) {
169   - throw exception(PAY_TRANSFER_EXTENSION_STATUS_IS_NOT_PENDING);
  189 + if (!isPendingStatus(transfer.getStatus())) {
  190 + throw exception(PAY_TRANSFER_STATUS_IS_NOT_PENDING);
170 191 }
171   - // 2. 更新 PayTransferExtensionDO
172   - int updateCount = transferExtensionMapper.updateByIdAndStatus(transferExtension.getId(),
  192 + // 2.更新
  193 + int updateCount = transferMapper.updateByIdAndStatus(transfer.getId(),
173 194 CollUtil.newArrayList(WAITING.getStatus(), IN_PROGRESS.getStatus()),
174   - new PayTransferExtensionDO().setStatus(SUCCESS.getStatus())
  195 + new PayTransferDO().setStatus(CLOSED.getStatus()) .setChannelId(channel.getId())
  196 + .setChannelCode(channel.getCode()).setChannelTransferNo(notify.getChannelTransferNo())
  197 + .setChannelErrorCode(notify.getChannelErrorCode()).setChannelErrorMsg(notify.getChannelErrorMsg())
175 198 .setChannelNotifyData(JsonUtils.toJsonString(notify)));
176 199 if (updateCount == 0) {
177   - throw exception(PAY_TRANSFER_EXTENSION_STATUS_IS_NOT_PENDING);
  200 + throw exception(PAY_TRANSFER_STATUS_IS_NOT_PENDING);
178 201 }
179   - log.info("[updateTransferExtensionSuccess][transferExtension({}) 更新为已转账]", transferExtension.getId());
180   - return transferExtension;
  202 + log.info("[updateTransferClosed][transfer({}) 更新为关闭状态]", transfer.getId());
181 203 }
182 204  
183 205 private void notifyTransferClosed(PayChannelDO channel, PayTransferRespDTO notify) {
184   - // 更新 PayTransferExtensionDO 转账关闭
185   - updateTransferExtensionClosed(notify);
186   - }
187   -
188   - private void updateTransferExtensionClosed(PayTransferRespDTO notify) {
189   - // 1 校验
190   - PayTransferExtensionDO transferExtension = transferExtensionMapper.selectByNo(notify.getOutTransferNo());
191   - if (transferExtension == null) {
192   - throw exception(PAY_TRANSFER_EXTENSION_NOT_FOUND);
193   - }
194   - if (isClosed(transferExtension.getStatus())) { // 如果已是关闭状态,直接返回,不用重复更新
195   - log.info("[updateTransferExtensionSuccess][transferExtension({}) 已经是关闭状态,无需更新]", transferExtension.getId());
196   - return;
197   - }
198   - if (!isPendingStatus(transferExtension.getStatus())) {
199   - throw exception(PAY_TRANSFER_EXTENSION_STATUS_IS_NOT_PENDING);
200   - }
201   - // 2. 更新 PayTransferExtensionDO
202   - int updateCount = transferExtensionMapper.updateByIdAndStatus(transferExtension.getId(),
203   - CollUtil.newArrayList(WAITING.getStatus(), IN_PROGRESS.getStatus()),
204   - new PayTransferExtensionDO().setStatus(CLOSED.getStatus())
205   - .setChannelNotifyData(JsonUtils.toJsonString(notify)));
206   - if (updateCount == 0) {
207   - throw exception(PAY_TRANSFER_EXTENSION_STATUS_IS_NOT_PENDING);
208   - }
209   - log.info("[updateTransferExtensionSuccess][transferExtension({}) 更新为关闭状态]", transferExtension.getId());
  206 + // 更新 PayTransferDO 转账关闭
  207 + updateTransferClosed(channel,notify);
210 208 }
211 209  
212 210 private void validateChannelCodeAndTypeMatch(String channelCode, Integer type) {
213 211 PayTransferTypeEnum transferType = PayTransferTypeEnum.typeOf(type);
214   - PayChannelEnum payChannel = PayChannelEnum.getByCode(channelCode);
215 212 switch (transferType) {
216 213 case ALIPAY_BALANCE: {
217   - // TODO @jason:可以抽到 PayChannelEnum 里,isAlipay? 类似这种哈
218   - if (!payChannel.getCode().startsWith("alipay")) {
  214 + if (!PayChannelEnum.isAlipay(channelCode)) {
219 215 throw exception(PAY_TRANSFER_TYPE_AND_CHANNEL_NOT_MATCH);
220 216 }
221 217 break;
... ... @@ -241,7 +237,7 @@ public class PayTransferServiceImpl implements PayTransferService {
241 237 return channel;
242 238 }
243 239  
244   - private PayTransferDO validateTransferCanSubmit(Long id) {
  240 + private PayTransferDO validateTransferExist(Long id) {
245 241 PayTransferDO transfer = transferMapper.selectById(id);
246 242 if (transfer == null) { // 是否存在
247 243 throw exception(PAY_TRANSFER_NOT_FOUND);
... ...