【同步】BOOT 和 CLOUD 的功能(PAY 相关功能)
This commit is contained in:
@@ -24,6 +24,12 @@ public class PayRefundNotifyReqDTO {
|
||||
@NotEmpty(message = "商户退款单编号不能为空")
|
||||
private String merchantOrderId;
|
||||
|
||||
/**
|
||||
* 商户退款编号
|
||||
*/
|
||||
@NotEmpty(message = "商户退款编号不能为空")
|
||||
private String merchantRefundId;
|
||||
|
||||
/**
|
||||
* 支付退款编号
|
||||
*/
|
||||
|
||||
@@ -2,7 +2,10 @@ package cn.iocoder.yudao.module.pay.api.notify.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 转账单的通知 Request DTO
|
||||
@@ -10,6 +13,9 @@ import lombok.Data;
|
||||
* @author jason
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class PayTransferNotifyReqDTO {
|
||||
|
||||
/**
|
||||
@@ -23,4 +29,5 @@ public class PayTransferNotifyReqDTO {
|
||||
*/
|
||||
@NotNull(message = "转账订单编号不能为空")
|
||||
private Long payTransferId;
|
||||
|
||||
}
|
||||
|
||||
@@ -18,6 +18,13 @@ public class PayRefundRespDTO {
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 渠道编码
|
||||
*
|
||||
* 枚举 PayChannelEnum
|
||||
*/
|
||||
private String channelCode;
|
||||
|
||||
// ========== 退款相关字段 ==========
|
||||
/**
|
||||
* 退款状态
|
||||
@@ -35,9 +42,24 @@ public class PayRefundRespDTO {
|
||||
* 商户订单编号
|
||||
*/
|
||||
private String merchantOrderId;
|
||||
/**
|
||||
* 商户退款编号
|
||||
*/
|
||||
private String merchantRefundId;
|
||||
/**
|
||||
* 退款成功时间
|
||||
*/
|
||||
private LocalDateTime successTime;
|
||||
|
||||
// ========== 渠道相关字段 ==========
|
||||
|
||||
/**
|
||||
* 调用渠道的错误码
|
||||
*/
|
||||
private String channelErrorCode;
|
||||
/**
|
||||
* 调用渠道的错误提示
|
||||
*/
|
||||
private String channelErrorMsg;
|
||||
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.pay.api.transfer;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateRespDTO;
|
||||
import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferRespDTO;
|
||||
import cn.iocoder.yudao.module.pay.enums.ApiConstants;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
@@ -23,7 +24,7 @@ public interface PayTransferApi {
|
||||
|
||||
@PostMapping(PREFIX + "/create")
|
||||
@Operation(summary = "创建转账单")
|
||||
CommonResult<Long> createTransfer(@Valid @RequestBody PayTransferCreateReqDTO reqDTO);
|
||||
CommonResult<PayTransferCreateRespDTO> createTransfer(@Valid @RequestBody PayTransferCreateReqDTO reqDTO);
|
||||
|
||||
@GetMapping(PREFIX + "/get")
|
||||
@Operation(summary = "获得转账单")
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
package cn.iocoder.yudao.module.pay.api.transfer.dto;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.validation.InEnum;
|
||||
import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferTypeEnum;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@@ -24,6 +26,9 @@ public class PayTransferCreateReqDTO {
|
||||
@NotNull(message = "应用标识不能为空")
|
||||
private String appKey;
|
||||
|
||||
/**
|
||||
* 转账渠道
|
||||
*/
|
||||
@NotEmpty(message = "转账渠道不能为空")
|
||||
private String channelCode;
|
||||
|
||||
@@ -32,17 +37,12 @@ public class PayTransferCreateReqDTO {
|
||||
*/
|
||||
private Map<String, String> channelExtras;
|
||||
|
||||
/**
|
||||
* 用户 IP
|
||||
*/
|
||||
@NotEmpty(message = "用户 IP 不能为空")
|
||||
private String userIp;
|
||||
|
||||
/**
|
||||
* 类型
|
||||
*/
|
||||
@NotNull(message = "转账类型不能为空")
|
||||
@InEnum(PayTransferTypeEnum.class)
|
||||
private Integer type;
|
||||
|
||||
|
||||
/**
|
||||
* 商户转账单编号
|
||||
*/
|
||||
@@ -62,16 +62,59 @@ public class PayTransferCreateReqDTO {
|
||||
@NotEmpty(message = "转账标题不能为空")
|
||||
private String subject;
|
||||
|
||||
/**
|
||||
* 收款人账号
|
||||
*
|
||||
* 微信场景下:openid
|
||||
* 支付宝场景下:支付宝账号
|
||||
*/
|
||||
@NotEmpty(message = "收款人账号不能为空")
|
||||
private String userAccount;
|
||||
/**
|
||||
* 收款人姓名
|
||||
*/
|
||||
@NotBlank(message = "收款人姓名不能为空", groups = {PayTransferTypeEnum.Alipay.class})
|
||||
private String userName;
|
||||
|
||||
@NotBlank(message = "支付宝登录号不能为空", groups = {PayTransferTypeEnum.Alipay.class})
|
||||
private String alipayLogonId;
|
||||
/**
|
||||
* 【微信】现金营销场景
|
||||
*
|
||||
* @param activityName 活动名称
|
||||
* @param rewardDescription 奖励说明
|
||||
* @return channelExtras
|
||||
*/
|
||||
public static Map<String, String> buildWeiXinChannelExtra1000(String activityName, String rewardDescription) {
|
||||
return buildWeiXinChannelExtra(1000,
|
||||
"活动名称", activityName,
|
||||
"奖励说明", rewardDescription);
|
||||
}
|
||||
|
||||
/**
|
||||
* 【微信】企业报销场景
|
||||
*
|
||||
* @param expenseType 报销类型
|
||||
* @param expenseDescription 报销说明
|
||||
* @return channelExtras
|
||||
*/
|
||||
public static Map<String, String> buildWeiXinChannelExtra1006(String expenseType, String expenseDescription) {
|
||||
return buildWeiXinChannelExtra(1006,
|
||||
"报销类型", expenseType,
|
||||
"报销说明", expenseDescription);
|
||||
}
|
||||
|
||||
private static Map<String, String> buildWeiXinChannelExtra(Integer sceneId, String... values) {
|
||||
Map<String, String> channelExtras = new HashMap<>();
|
||||
// 构建场景报备信息列表
|
||||
List<Map<String, String>> sceneReportInfos = new ArrayList<>();
|
||||
for (int i = 0; i < values.length; i += 2) {
|
||||
Map<String, String> info = new HashMap<>();
|
||||
info.put("infoType", values[i]);
|
||||
info.put("infoContent", values[i + 1]);
|
||||
sceneReportInfos.add(info);
|
||||
}
|
||||
// 设置场景ID和场景报备信息
|
||||
channelExtras.put("sceneId", StrUtil.toString(sceneId));
|
||||
channelExtras.put("sceneReportInfos", JsonUtils.toJsonString(sceneReportInfos));
|
||||
return channelExtras;
|
||||
}
|
||||
|
||||
// ========== 微信转账相关字段 ==========
|
||||
@NotBlank(message = "微信 openId 不能为空", groups = {PayTransferTypeEnum.WxPay.class})
|
||||
private String openid;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package cn.iocoder.yudao.module.pay.api.transfer.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 转账单创建 Response DTO
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
public class PayTransferCreateRespDTO {
|
||||
|
||||
/**
|
||||
* 编号
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
// ========== 其它字段 ==========
|
||||
|
||||
/**
|
||||
* 渠道 package 信息
|
||||
*
|
||||
* 特殊:目前只有微信转账有这个东西!!!
|
||||
* @see <a href="https://pay.weixin.qq.com/doc/v3/merchant/4012716430">JSAPI 调起用户确认收款</a>
|
||||
*/
|
||||
private String channelPackageInfo;
|
||||
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
package cn.iocoder.yudao.module.pay.api.transfer.dto;
|
||||
|
||||
import cn.iocoder.yudao.module.pay.enums.PayChannelEnum;
|
||||
import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferStatusEnum;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class PayTransferRespDTO {
|
||||
|
||||
@@ -16,6 +19,22 @@ public class PayTransferRespDTO {
|
||||
*/
|
||||
private String no;
|
||||
|
||||
/**
|
||||
* 转账渠道编码
|
||||
*
|
||||
* 枚举 {@link PayChannelEnum}
|
||||
*/
|
||||
private String channelCode;
|
||||
|
||||
// ========== 商户相关字段 ==========
|
||||
|
||||
/**
|
||||
* 商户转账单编号
|
||||
*/
|
||||
private String merchantTransferId;
|
||||
|
||||
// ========== 转账相关字段 ==========
|
||||
|
||||
/**
|
||||
* 转账金额,单位:分
|
||||
*/
|
||||
@@ -28,4 +47,35 @@ public class PayTransferRespDTO {
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 订单转账成功时间
|
||||
*/
|
||||
private LocalDateTime successTime;
|
||||
|
||||
// ========== 其它字段 ==========
|
||||
|
||||
/**
|
||||
* 调用渠道的错误码
|
||||
*/
|
||||
private String channelErrorCode;
|
||||
/**
|
||||
* 调用渠道的错误提示
|
||||
*/
|
||||
private String channelErrorMsg;
|
||||
|
||||
/**
|
||||
* 渠道 package 信息
|
||||
*
|
||||
* 特殊:目前只有微信转账有这个东西!!!
|
||||
* @see <a href="https://pay.weixin.qq.com/doc/v3/merchant/4012716430">JSAPI 调起用户确认收款</a>
|
||||
*/
|
||||
private String channelPackageInfo;
|
||||
/**
|
||||
* 渠道商户号
|
||||
*
|
||||
* 特殊:目前只有微信转账有这个东西!!!
|
||||
* @see <a href="https://pay.weixin.qq.com/doc/v3/merchant/4012716430">JSAPI 调起用户确认收款</a>
|
||||
*/
|
||||
private String channelMchId;
|
||||
|
||||
}
|
||||
|
||||
@@ -2,13 +2,18 @@ package cn.iocoder.yudao.module.pay.api.wallet;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletAddBalanceReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletRespDTO;
|
||||
import cn.iocoder.yudao.module.pay.enums.ApiConstants;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.Parameters;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
@FeignClient(name = ApiConstants.NAME) // TODO 芋艿:fallbackFactory =
|
||||
@Tag(name = "RPC 服务 - 钱包")
|
||||
@@ -20,4 +25,14 @@ public interface PayWalletApi {
|
||||
@Operation(summary = "添加钱包余额")
|
||||
CommonResult<Boolean> addWalletBalance(@Valid @RequestBody PayWalletAddBalanceReqDTO reqDTO);
|
||||
|
||||
@GetMapping(PREFIX + "/get-or-create")
|
||||
@Operation(summary = "获取钱包信息")
|
||||
@Parameters({
|
||||
@Parameter(name = "userId", description = "用户编号", required = true, example = "1024"),
|
||||
@Parameter(name = "userType", description = "用户类型", required = true, example = "1")
|
||||
})
|
||||
CommonResult<PayWalletRespDTO> getOrCreateWallet(@RequestParam("userId") Long userId,
|
||||
@RequestParam("userType") Integer userType);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package cn.iocoder.yudao.module.pay.api.wallet.dto;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 钱包 Response DTO
|
||||
*
|
||||
* @author jason
|
||||
*/
|
||||
@Data
|
||||
public class PayWalletRespDTO {
|
||||
|
||||
/**
|
||||
* 编号
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 用户 id
|
||||
*
|
||||
* 关联 MemberUserDO 的 id 编号
|
||||
* 关联 AdminUserDO 的 id 编号
|
||||
*/
|
||||
private Long userId;
|
||||
/**
|
||||
* 用户类型, 预留 多商户转帐可能需要用到
|
||||
*
|
||||
* 关联 {@link UserTypeEnum}
|
||||
*/
|
||||
private Integer userType;
|
||||
|
||||
/**
|
||||
* 余额,单位分
|
||||
*/
|
||||
private Integer balance;
|
||||
|
||||
/**
|
||||
* 冻结金额,单位分
|
||||
*/
|
||||
private Integer freezePrice;
|
||||
|
||||
/**
|
||||
* 累计支出,单位分
|
||||
*/
|
||||
private Integer totalExpense;
|
||||
/**
|
||||
* 累计充值,单位分
|
||||
*/
|
||||
private Integer totalRecharge;
|
||||
|
||||
}
|
||||
@@ -15,4 +15,6 @@ public interface DictTypeConstants {
|
||||
|
||||
String NOTIFY_STATUS = "pay_notify_status"; // 回调状态
|
||||
|
||||
String TRANSFER_STATUS = "pay_transfer_status"; // 转账状态
|
||||
|
||||
}
|
||||
|
||||
@@ -65,13 +65,12 @@ public interface ErrorCodeConstants {
|
||||
ErrorCode WALLET_RECHARGE_PACKAGE_NAME_EXISTS = new ErrorCode(1_007_008_013, "钱包充值套餐名称已存在");
|
||||
|
||||
// ========== 转账模块 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_SAME_MERCHANT_TRANSFER_TYPE_NOT_MATCH = new ErrorCode(1_007_009_002, "两次相同转账请求的类型不匹配");
|
||||
ErrorCode PAY_SAME_MERCHANT_TRANSFER_PRICE_NOT_MATCH = 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, "转账单不处于待转账或转账中");
|
||||
ErrorCode PAY_TRANSFER_CREATE_CHANNEL_NOT_MATCH = new ErrorCode(1_007_009_002, "转账发起失败,原因:两次相同转账请求的类型不匹配");
|
||||
ErrorCode PAY_TRANSFER_CREATE_PRICE_NOT_MATCH = new ErrorCode(1_007_009_003, "转账发起失败,原因:两次相同转账请求的金额不匹配");
|
||||
ErrorCode PAY_TRANSFER_CREATE_FAIL_STATUS_NOT_CLOSED = new ErrorCode(1_007_009_004, "转账发起失败,原因:已经存在相同的转账单,且状态不是已关闭");
|
||||
ErrorCode PAY_TRANSFER_NOTIFY_FAIL_STATUS_IS_NOT_WAITING = new ErrorCode(1_007_009_006, "通知转账结果失败,原因:转账单不处于待转账");
|
||||
ErrorCode PAY_TRANSFER_NOTIFY_FAIL_STATUS_NOT_WAITING_OR_PROCESSING = new ErrorCode(1_007_009_007, "通知转账结果失败,原因:转账单不处于待转账或转账中");
|
||||
|
||||
// ========== 示例订单 1-007-900-000 ==========
|
||||
ErrorCode DEMO_ORDER_NOT_FOUND = new ErrorCode(1_007_900_000, "示例订单不存在");
|
||||
@@ -86,8 +85,13 @@ 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, "转账失败,转账单金额不匹配");
|
||||
// ========== 示例提现单 1-007-901-000 ==========
|
||||
ErrorCode DEMO_WITHDRAW_NOT_FOUND = new ErrorCode(1_007_901_000, "示例提现单不存在");
|
||||
ErrorCode DEMO_WITHDRAW_UPDATE_STATUS_FAIL_PAY_TRANSFER_ID_ERROR = new ErrorCode(1_007_901_001, "更新示例提现单状态失败,支付转账单编号不匹配");
|
||||
ErrorCode DEMO_WITHDRAW_UPDATE_STATUS_FAIL_PAY_TRANSFER_STATUS_NOT_SUCCESS_OR_CLOSED = new ErrorCode(1_007_901_002, "更新示例提现单状态失败,支付转账单状态不是【转账成功】或【转账失败】状态");
|
||||
ErrorCode DEMO_WITHDRAW_UPDATE_STATUS_FAIL_PAY_PRICE_NOT_MATCH = new ErrorCode(1_007_901_003, "更新示例提现单状态失败,支付转账单金额不匹配");
|
||||
ErrorCode DEMO_WITHDRAW_UPDATE_STATUS_FAIL_PAY_MERCHANT_EXISTS = new ErrorCode(1_007_901_004, "更新示例提现单状态失败,支付转账单商户订单号不匹配");
|
||||
ErrorCode DEMO_WITHDRAW_UPDATE_STATUS_FAIL_PAY_CHANNEL_NOT_MATCH = new ErrorCode(1_007_901_005, "更新示例提现单状态失败,支付转账单渠道不匹配");
|
||||
ErrorCode DEMO_WITHDRAW_TRANSFER_FAIL_STATUS_NOT_WAITING_OR_CLOSED = new ErrorCode(1_007_901_008, "发起转账失败,原因:示例提现单状态不是【等待提现】或【提现关闭】");
|
||||
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package cn.iocoder.yudao.module.pay.enums;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* 支付渠道的编码的枚举
|
||||
*
|
||||
@@ -10,7 +13,7 @@ import lombok.Getter;
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum PayChannelEnum {
|
||||
public enum PayChannelEnum implements ArrayValuable<String> {
|
||||
|
||||
WX_PUB("wx_pub", "微信 JSAPI 支付"), // 公众号网页
|
||||
WX_LITE("wx_lite", "微信小程序支付"),
|
||||
@@ -28,6 +31,8 @@ public enum PayChannelEnum {
|
||||
|
||||
WALLET("wallet", "钱包支付");
|
||||
|
||||
public static final String[] ARRAYS = Arrays.stream(values()).map(PayChannelEnum::getCode).toArray(String[]::new);
|
||||
|
||||
/**
|
||||
* 编码
|
||||
*
|
||||
@@ -39,4 +44,9 @@ public enum PayChannelEnum {
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
@Override
|
||||
public String[] array() {
|
||||
return ARRAYS;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package cn.iocoder.yudao.module.pay.enums.demo;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 示例提现单的状态枚举
|
||||
*
|
||||
* @author jason
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum PayDemoWithdrawStatusEnum {
|
||||
|
||||
WAITING(0, "等待提现"),
|
||||
SUCCESS(10, "提现成功"),
|
||||
CLOSED(20, "提现关闭");
|
||||
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
private final Integer status;
|
||||
/**
|
||||
* 状态名
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
public static boolean isSuccess(Integer status) {
|
||||
return Objects.equals(status, SUCCESS.getStatus());
|
||||
}
|
||||
|
||||
public static boolean isClosed(Integer status) {
|
||||
return Objects.equals(status, CLOSED.getStatus());
|
||||
}
|
||||
|
||||
public static boolean isWaiting(Integer status) {
|
||||
return Objects.equals(status, WAITING.getStatus());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package cn.iocoder.yudao.module.pay.enums.demo;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* 示例提现单的类型枚举
|
||||
*
|
||||
* @author owen
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum PayDemoWithdrawTypeEnum implements ArrayValuable<Integer> {
|
||||
|
||||
WECHAT(2, "微信"),
|
||||
ALIPAY(1, "支付宝"),
|
||||
WALLET(3, "钱包"),
|
||||
;
|
||||
|
||||
public static final Integer[] ARRAYS = Arrays.stream(values()).map(PayDemoWithdrawTypeEnum::getType).toArray(Integer[]::new);
|
||||
|
||||
/**
|
||||
* 类型
|
||||
*/
|
||||
private final Integer type;
|
||||
/**
|
||||
* 名字
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
@Override
|
||||
public Integer[] array() {
|
||||
return ARRAYS;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,6 +6,8 @@ import lombok.Getter;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 渠道的转账状态枚举
|
||||
*
|
||||
* @author jason
|
||||
*/
|
||||
@Getter
|
||||
@@ -13,16 +15,9 @@ import java.util.Objects;
|
||||
public enum PayTransferStatusEnum {
|
||||
|
||||
WAITING(0, "等待转账"),
|
||||
/**
|
||||
* TODO 转账到银行卡. 会有T+0 T+1 到账的请情况。 还未实现
|
||||
*/
|
||||
IN_PROGRESS(10, "转账进行中"),
|
||||
|
||||
SUCCESS(20, "转账成功"),
|
||||
/**
|
||||
* 转账关闭 (失败,或者其它情况) // TODO 改成 转账失败状态
|
||||
*/
|
||||
CLOSED(30, "转账关闭");
|
||||
PROCESSING(5, "转账进行中"),
|
||||
SUCCESS(10, "转账成功"),
|
||||
CLOSED(20, "转账关闭");
|
||||
|
||||
/**
|
||||
* 状态
|
||||
@@ -44,16 +39,29 @@ public enum PayTransferStatusEnum {
|
||||
public static boolean isWaiting(Integer status) {
|
||||
return Objects.equals(status, WAITING.getStatus());
|
||||
}
|
||||
public static boolean isInProgress(Integer status) {
|
||||
return Objects.equals(status, IN_PROGRESS.getStatus());
|
||||
|
||||
public static boolean isProgressing(Integer status) {
|
||||
return Objects.equals(status, PROCESSING.getStatus());
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否处于待转账或者转账中的状态
|
||||
*
|
||||
* @param status 状态
|
||||
* @return 是否
|
||||
*/
|
||||
public static boolean isPendingStatus(Integer status) {
|
||||
return Objects.equals(status, WAITING.getStatus()) || Objects.equals(status, IN_PROGRESS.getStatus());
|
||||
public static boolean isWaitingOrProcessing(Integer status) {
|
||||
return isWaiting(status) || isProgressing(status);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否处于成功或者关闭中的状态
|
||||
*
|
||||
* @param status 状态
|
||||
* @return 是否
|
||||
*/
|
||||
public static boolean isSuccessOrClosed(Integer status) {
|
||||
return isSuccess(status) || isClosed(status);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
package cn.iocoder.yudao.module.pay.enums.transfer;
|
||||
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* 转账类型枚举
|
||||
*
|
||||
* @author jason
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum PayTransferTypeEnum implements ArrayValuable<Integer> {
|
||||
|
||||
ALIPAY_BALANCE(1, "支付宝余额"),
|
||||
WX_BALANCE(2, "微信余额"),
|
||||
BANK_CARD(3, "银行卡"),
|
||||
WALLET_BALANCE(4, "钱包余额");
|
||||
|
||||
public interface WxPay {
|
||||
}
|
||||
|
||||
public interface Alipay {
|
||||
}
|
||||
|
||||
private final Integer type;
|
||||
private final String name;
|
||||
|
||||
public static final Integer[] ARRAYS = Arrays.stream(values()).map(PayTransferTypeEnum::getType).toArray(Integer[]::new);
|
||||
|
||||
@Override
|
||||
public Integer[] array() {
|
||||
return ARRAYS;
|
||||
}
|
||||
|
||||
public static PayTransferTypeEnum typeOf(Integer type) {
|
||||
return ArrayUtil.firstMatch(item -> item.getType().equals(type), values());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,7 +20,7 @@ public enum PayWalletBizTypeEnum implements ArrayValuable<Integer> {
|
||||
PAYMENT(3, "支付"),
|
||||
PAYMENT_REFUND(4, "支付退款"),
|
||||
UPDATE_BALANCE(5, "更新余额"),
|
||||
BROKERAGE_WITHDRAW(6, "分佣提现");
|
||||
TRANSFER(6, "转账");
|
||||
|
||||
/**
|
||||
* 业务分类
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package cn.iocoder.yudao.module.pay.api.refund;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
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.convert.refund.PayRefundConvert;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
|
||||
import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
@@ -21,12 +23,13 @@ public class PayRefundApiImpl implements PayRefundApi {
|
||||
|
||||
@Override
|
||||
public CommonResult<Long> createRefund(PayRefundCreateReqDTO reqDTO) {
|
||||
return success(payRefundService.createPayRefund(reqDTO));
|
||||
return success(payRefundService.createRefund(reqDTO));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommonResult<PayRefundRespDTO> getRefund(Long id) {
|
||||
return success(PayRefundConvert.INSTANCE.convert02(payRefundService.getRefund(id)));
|
||||
PayRefundDO refund = payRefundService.getRefund(id);
|
||||
return success(BeanUtils.toBean(refund, PayRefundRespDTO.class));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,9 +2,13 @@ package cn.iocoder.yudao.module.pay.api.transfer;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.weixin.WxPayClientConfig;
|
||||
import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateRespDTO;
|
||||
import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferRespDTO;
|
||||
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.service.channel.PayChannelService;
|
||||
import cn.iocoder.yudao.module.pay.service.transfer.PayTransferService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
@@ -24,16 +28,26 @@ public class PayTransferApiImpl implements PayTransferApi {
|
||||
|
||||
@Resource
|
||||
private PayTransferService payTransferService;
|
||||
@Resource
|
||||
private PayChannelService payChannelService;
|
||||
|
||||
@Override
|
||||
public CommonResult<Long> createTransfer(PayTransferCreateReqDTO reqDTO) {
|
||||
public CommonResult<PayTransferCreateRespDTO> createTransfer(PayTransferCreateReqDTO reqDTO) {
|
||||
return success(payTransferService.createTransfer(reqDTO));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommonResult<PayTransferRespDTO> getTransfer(Long id) {
|
||||
PayTransferDO transfer = payTransferService.getTransfer(id);
|
||||
return success(BeanUtils.toBean(transfer, PayTransferRespDTO.class));
|
||||
if (transfer == null) {
|
||||
return null;
|
||||
}
|
||||
PayChannelDO channel = payChannelService.getChannel(transfer.getChannelId());
|
||||
String mchId = null;
|
||||
if (channel != null && channel.getConfig() instanceof WxPayClientConfig) {
|
||||
mchId = ((WxPayClientConfig) channel.getConfig()).getMchId();
|
||||
}
|
||||
return success(BeanUtils.toBean(transfer, PayTransferRespDTO.class).setChannelMchId(mchId));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ package cn.iocoder.yudao.module.pay.api.wallet;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletAddBalanceReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletRespDTO;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO;
|
||||
import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum;
|
||||
import cn.iocoder.yudao.module.pay.service.wallet.PayWalletService;
|
||||
@@ -36,4 +38,10 @@ public class PayWalletApiImpl implements PayWalletApi {
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommonResult<PayWalletRespDTO> getOrCreateWallet(Long userId, Integer userType) {
|
||||
PayWalletDO wallet = payWalletService.getOrCreateWallet(userId, userType);
|
||||
return success(BeanUtils.toBean(wallet, PayWalletRespDTO.class));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,11 +3,11 @@ 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.common.util.object.BeanUtils;
|
||||
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.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;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
@@ -23,7 +23,7 @@ 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.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
||||
|
||||
@Tag(name = "管理后台 - 示例订单")
|
||||
@Tag(name = "管理后台 - 示例订单") // 目的:演示支付、退款功能
|
||||
@RestController
|
||||
@RequestMapping("/pay/demo-order")
|
||||
@Validated
|
||||
@@ -42,7 +42,7 @@ public class PayDemoOrderController {
|
||||
@Operation(summary = "获得示例订单分页")
|
||||
public CommonResult<PageResult<PayDemoOrderRespVO>> getDemoOrderPage(@Valid PageParam pageVO) {
|
||||
PageResult<PayDemoOrderDO> pageResult = payDemoOrderService.getDemoOrderPage(pageVO);
|
||||
return success(PayDemoOrderConvert.INSTANCE.convertPage(pageResult));
|
||||
return success(BeanUtils.toBean(pageResult, PayDemoOrderRespVO.class));
|
||||
}
|
||||
|
||||
@PostMapping("/update-paid")
|
||||
@@ -66,7 +66,9 @@ public class PayDemoOrderController {
|
||||
@Operation(summary = "更新示例订单为已退款") // 由 pay-module 支付服务,进行回调,可见 PayNotifyJob
|
||||
@PermitAll // 无需登录,安全由 PayDemoOrderService 内部校验实现
|
||||
public CommonResult<Boolean> updateDemoOrderRefunded(@RequestBody PayRefundNotifyReqDTO notifyReqDTO) {
|
||||
payDemoOrderService.updateDemoOrderRefunded(Long.valueOf(notifyReqDTO.getMerchantOrderId()),
|
||||
payDemoOrderService.updateDemoOrderRefunded(
|
||||
Long.valueOf(notifyReqDTO.getMerchantOrderId()),
|
||||
notifyReqDTO.getMerchantRefundId(),
|
||||
notifyReqDTO.getPayRefundId());
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
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.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;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoTransferDO;
|
||||
import cn.iocoder.yudao.module.pay.service.demo.PayDemoTransferService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.annotation.security.PermitAll;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@Tag(name = "管理后台 - 示例转账单")
|
||||
@RestController
|
||||
@RequestMapping("/pay/demo-transfer")
|
||||
@Validated
|
||||
public class PayDemoTransferController {
|
||||
@Resource
|
||||
private PayDemoTransferService demoTransferService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建示例转账订单")
|
||||
public CommonResult<Long> createDemoTransfer(@Valid @RequestBody PayDemoTransferCreateReqVO createReqVO) {
|
||||
return success(demoTransferService.createDemoTransfer(createReqVO));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@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 内部校验实现
|
||||
public CommonResult<Boolean> updateDemoTransferStatus(@RequestBody PayTransferNotifyReqDTO notifyReqDTO) {
|
||||
demoTransferService.updateDemoTransferStatus(Long.valueOf(notifyReqDTO.getMerchantTransferId()),
|
||||
notifyReqDTO.getPayTransferId());
|
||||
return success(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
### 请求 /pay/demo-withdraw/create 接口(支付宝)
|
||||
POST {{baseUrl}}/pay/demo-withdraw/create
|
||||
Authorization: Bearer {{token}}
|
||||
Content-Type: application/json
|
||||
tenant-id: {{adminTenantId}}
|
||||
|
||||
{
|
||||
"type": 1,
|
||||
"subject": "测试转账",
|
||||
"price": 10,
|
||||
"userAccount": "oespxk7368@sandbox.com",
|
||||
"userName": "oespxk7368"
|
||||
}
|
||||
|
||||
### 请求 /pay/demo-withdraw/create 接口(微信余额)
|
||||
POST {{baseUrl}}/pay/demo-withdraw/create
|
||||
Authorization: Bearer {{token}}
|
||||
Content-Type: application/json
|
||||
tenant-id: {{adminTenantId}}
|
||||
|
||||
{
|
||||
"type": 2,
|
||||
"subject": "测试转账",
|
||||
"price": 1,
|
||||
"userAccount": "oiSC85elO_OZogXODC5RoGyXamK4",
|
||||
"userName": "芋艿"
|
||||
}
|
||||
|
||||
### 请求 /pay/demo-withdraw/create 接口(钱包余额)
|
||||
POST {{baseUrl}}/pay/demo-withdraw/create
|
||||
Authorization: Bearer {{token}}
|
||||
Content-Type: application/json
|
||||
tenant-id: {{adminTenantId}}
|
||||
|
||||
{
|
||||
"type": 3,
|
||||
"subject": "测试转账",
|
||||
"price": 1,
|
||||
"userAccount": "1"
|
||||
}
|
||||
|
||||
### 请求 /pay/demo-withdraw/transfer 接口(发起转账)
|
||||
POST {{baseUrl}}/pay/demo-withdraw/transfer?id=1
|
||||
Authorization: Bearer {{token}}
|
||||
tenant-id: {{adminTenantId}}
|
||||
|
||||
### 请求 /pay/demo-withdraw/page 接口(查询分页)
|
||||
GET {{baseUrl}}/pay/demo-withdraw/page?pageNo=1&pageSize=10
|
||||
Authorization: Bearer {{token}}
|
||||
tenant-id: {{adminTenantId}}
|
||||
@@ -0,0 +1,63 @@
|
||||
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.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.pay.api.notify.dto.PayTransferNotifyReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.withdraw.PayDemoWithdrawCreateReqVO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.withdraw.PayDemoWithdrawRespVO;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoWithdrawDO;
|
||||
import cn.iocoder.yudao.module.pay.service.demo.PayDemoWithdrawService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.annotation.security.PermitAll;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@Tag(name = "管理后台 - 示例提现订单") // 目的:演示转账功能
|
||||
@RestController
|
||||
@RequestMapping("/pay/demo-withdraw")
|
||||
@Validated
|
||||
public class PayDemoWithdrawController {
|
||||
|
||||
@Resource
|
||||
private PayDemoWithdrawService demoWithdrawService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建示例提现单")
|
||||
public CommonResult<Long> createDemoWithdraw(@Valid @RequestBody PayDemoWithdrawCreateReqVO createReqVO) {
|
||||
Long id = demoWithdrawService.createDemoWithdraw(createReqVO);
|
||||
return success(id);
|
||||
}
|
||||
|
||||
@PostMapping("/transfer")
|
||||
@Operation(summary = "提现单转账")
|
||||
@Parameter(name = "id", required = true, description = "提现单编号", example = "1024")
|
||||
public CommonResult<Long> transferDemoWithdraw(@RequestParam("id") Long id) {
|
||||
Long payTransferId = demoWithdrawService.transferDemoWithdraw(id);
|
||||
return success(payTransferId);
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得示例提现单分页")
|
||||
public CommonResult<PageResult<PayDemoWithdrawRespVO>> getDemoWithdrawPage(@Valid PageParam pageVO) {
|
||||
PageResult<PayDemoWithdrawDO> pageResult = demoWithdrawService.getDemoWithdrawPage(pageVO);
|
||||
return success(BeanUtils.toBean(pageResult, PayDemoWithdrawRespVO.class));
|
||||
}
|
||||
|
||||
@PostMapping("/update-transferred")
|
||||
@Operation(summary = "更新示例提现单的转账状态") // 由 pay-module 转账服务,进行回调
|
||||
@PermitAll // 无需登录,安全由 PayDemoTransferService 内部校验实现
|
||||
public CommonResult<Boolean> updateDemoWithdrawTransferred(@RequestBody PayTransferNotifyReqDTO notifyReqDTO) {
|
||||
demoWithdrawService.updateDemoWithdrawTransferred(Long.valueOf(notifyReqDTO.getMerchantTransferId()),
|
||||
notifyReqDTO.getPayTransferId());
|
||||
return success(true);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
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 jakarta.validation.Validator;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import static cn.iocoder.yudao.module.pay.enums.transfer.PayTransferTypeEnum.Alipay;
|
||||
import static cn.iocoder.yudao.module.pay.enums.transfer.PayTransferTypeEnum.WxPay;
|
||||
|
||||
/**
|
||||
* @author jason
|
||||
*/
|
||||
@Schema(description = "管理后台 - 示例转账单创建 Request VO")
|
||||
@Data
|
||||
public class PayDemoTransferCreateReqVO {
|
||||
|
||||
@Schema(description = "转账类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "转账类型不能为空")
|
||||
@InEnum(PayTransferTypeEnum.class)
|
||||
private Integer type;
|
||||
|
||||
@Schema(description = "转账金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
|
||||
@NotNull(message = "转账金额不能为空")
|
||||
@Min(value = 1, message = "转账金额必须大于零")
|
||||
private Integer price;
|
||||
|
||||
@Schema(description = "收款人姓名", example = "test1")
|
||||
@NotBlank(message = "收款人姓名不能为空", groups = {Alipay.class})
|
||||
private String userName;
|
||||
|
||||
// ========== 支付宝转账相关字段 ==========
|
||||
@Schema(description = "支付宝登录号,支持邮箱和手机号格式", example = "test1@@sandbox.com")
|
||||
@NotBlank(message = "支付宝登录号不能为空", groups = {Alipay.class})
|
||||
private String alipayLogonId;
|
||||
|
||||
// ========== 微信转账相关字段 ==========
|
||||
@Schema(description = "微信 openId", example = "oLefc4g5Gxx")
|
||||
@NotBlank(message = "微信 openId 不能为空", groups = {WxPay.class})
|
||||
private String openid;
|
||||
|
||||
|
||||
// ========== 转账到银行卡和钱包相关字段 待补充 ==========
|
||||
|
||||
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("待实现");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package cn.iocoder.yudao.module.pay.controller.admin.demo.vo.transfer;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 示例业务转账订单 Base VO,提供给添加、修改、详细的子 VO 使用
|
||||
* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
|
||||
*/
|
||||
@Data
|
||||
public class PayDemoTransferRespVO {
|
||||
|
||||
@Schema(description = "订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Long appId;
|
||||
|
||||
@Schema(description = "转账金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "22338")
|
||||
private Integer price;
|
||||
|
||||
@Schema(description = "转账类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Integer type;
|
||||
|
||||
@Schema(description = "收款人姓名", example = "test")
|
||||
private String userName;
|
||||
|
||||
@Schema(description = "支付宝登录号", example = "32167")
|
||||
private String alipayLogonId;
|
||||
|
||||
@Schema(description = "微信 openId", example = "31139")
|
||||
private String openid;
|
||||
|
||||
@Schema(description = "转账状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
|
||||
private Integer transferStatus;
|
||||
|
||||
@Schema(description = "转账订单编号", example = "23695")
|
||||
private Long payTransferId;
|
||||
|
||||
@Schema(description = "转账支付成功渠道")
|
||||
private String payChannelCode;
|
||||
|
||||
@Schema(description = "转账支付时间")
|
||||
private LocalDateTime transferTime;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package cn.iocoder.yudao.module.pay.controller.admin.demo.vo.withdraw;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.iocoder.yudao.framework.common.validation.InEnum;
|
||||
import cn.iocoder.yudao.module.pay.enums.demo.PayDemoWithdrawTypeEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.*;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - 示例提现单创建 Request VO")
|
||||
@Data
|
||||
public class PayDemoWithdrawCreateReqVO {
|
||||
|
||||
@Schema(description = "提现标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿是一种菜")
|
||||
@NotEmpty(message = "提现标题不能为空")
|
||||
private String subject;
|
||||
|
||||
@Schema(description = "提现金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
|
||||
@NotNull(message = "提现金额不能为空")
|
||||
@Min(value = 1, message = "提现金额必须大于零")
|
||||
private Integer price;
|
||||
|
||||
@Schema(description = "收款人账号", requiredMode= Schema.RequiredMode.REQUIRED, example = "test1")
|
||||
@NotBlank(message = "收款人账号不能为空")
|
||||
private String userAccount;
|
||||
|
||||
@Schema(description = "收款人姓名", example = "test1")
|
||||
private String userName;
|
||||
|
||||
@Schema(description = "提现方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "提现方式不能为空")
|
||||
@InEnum(PayDemoWithdrawTypeEnum.class)
|
||||
private Integer type;
|
||||
|
||||
@AssertTrue(message = "收款人姓名")
|
||||
public boolean isUserNameValid() {
|
||||
// 特殊:支付宝必须填写用户名!!!
|
||||
return ObjectUtil.notEqual(type, PayDemoWithdrawTypeEnum.ALIPAY.getType())
|
||||
|| ObjectUtil.isNotEmpty(userName);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package cn.iocoder.yudao.module.pay.controller.admin.demo.vo.withdraw;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - 示例转账单创建 Request VO")
|
||||
@Data
|
||||
public class PayDemoWithdrawRespVO {
|
||||
|
||||
@Schema(description = "转账单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "提现标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "吃饭报销")
|
||||
private String subject;
|
||||
|
||||
@Schema(description = "提现金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "22338")
|
||||
private Integer price;
|
||||
|
||||
@Schema(description = "收款人姓名", example = "test")
|
||||
private String userName;
|
||||
|
||||
@Schema(description = "收款人账号", example = "32167")
|
||||
private String userAccount;
|
||||
|
||||
@Schema(description = "提现类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Integer type;
|
||||
|
||||
@Schema(description = "提现状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
|
||||
private Integer status;
|
||||
|
||||
// ========== 转账相关字段 ==========
|
||||
|
||||
@Schema(description = "转账单编号", example = "23695")
|
||||
private Long payTransferId;
|
||||
|
||||
@Schema(description = "转账渠道", example = "wx_lite")
|
||||
private String transferChannelCode;
|
||||
|
||||
@Schema(description = "转账成功时间")
|
||||
private LocalDateTime transferTime;
|
||||
|
||||
@Schema(description = "转账失败原因", example = "IP 不正确")
|
||||
private String transferErrorMsg;
|
||||
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.pay.controller.admin.notify;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
|
||||
@@ -11,7 +12,6 @@ import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskDetailRespVO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskPageReqVO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskRespVO;
|
||||
import cn.iocoder.yudao.module.pay.convert.notify.PayNotifyTaskConvert;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyLogDO;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyTaskDO;
|
||||
@@ -138,7 +138,12 @@ public class PayNotifyController {
|
||||
// 拼接返回
|
||||
PayAppDO app = appService.getApp(task.getAppId());
|
||||
List<PayNotifyLogDO> logs = notifyService.getNotifyLogList(id);
|
||||
return success(PayNotifyTaskConvert.INSTANCE.convert(task, app, logs));
|
||||
return success(BeanUtils.toBean(task, PayNotifyTaskDetailRespVO.class, respVO -> {
|
||||
if (app != null) {
|
||||
respVO.setAppName(app.getName());
|
||||
}
|
||||
respVO.setLogs(BeanUtils.toBean(logs, PayNotifyTaskDetailRespVO.Log.class));
|
||||
}));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@@ -150,8 +155,15 @@ public class PayNotifyController {
|
||||
return success(PageResult.empty());
|
||||
}
|
||||
// 拼接返回
|
||||
Map<Long, PayAppDO> appMap = appService.getAppMap(convertList(pageResult.getList(), PayNotifyTaskDO::getAppId));
|
||||
return success(PayNotifyTaskConvert.INSTANCE.convertPage(pageResult, appMap));
|
||||
Map<Long, PayAppDO> apps = appService.getAppMap(convertList(pageResult.getList(), PayNotifyTaskDO::getAppId));
|
||||
|
||||
// 转换对象
|
||||
return success(BeanUtils.toBean(pageResult, PayNotifyTaskRespVO.class, order -> {
|
||||
PayAppDO app = apps.get(order.getAppId());
|
||||
if (app != null) {
|
||||
order.setAppName(app.getName());
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
package cn.iocoder.yudao.module.pay.controller.admin.notify.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 回调通知 Base VO,提供给添加、修改、详细的子 VO 使用
|
||||
* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
|
||||
*/
|
||||
@Data
|
||||
public class PayNotifyTaskBaseVO {
|
||||
|
||||
@Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10636")
|
||||
private Long appId;
|
||||
|
||||
@Schema(description = "通知类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
|
||||
private Byte type;
|
||||
|
||||
@Schema(description = "数据编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "6722")
|
||||
private Long dataId;
|
||||
|
||||
@Schema(description = "通知状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Byte status;
|
||||
|
||||
@Schema(description = "商户订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "26697")
|
||||
private String merchantOrderId;
|
||||
|
||||
@Schema(description = "下一次通知时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDateTime nextNotifyTime;
|
||||
|
||||
@Schema(description = "最后一次执行时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDateTime lastExecuteTime;
|
||||
|
||||
@Schema(description = "当前通知次数", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private Byte notifyTimes;
|
||||
|
||||
@Schema(description = "最大可通知次数", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private Byte maxNotifyTimes;
|
||||
|
||||
@Schema(description = "异步通知地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn")
|
||||
private String notifyUrl;
|
||||
|
||||
}
|
||||
@@ -13,19 +13,7 @@ import java.util.List;
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class PayNotifyTaskDetailRespVO extends PayNotifyTaskBaseVO {
|
||||
|
||||
@Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3380")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
@Schema(description = "应用名称", example = "wx_pay")
|
||||
private String appName;
|
||||
public class PayNotifyTaskDetailRespVO extends PayNotifyTaskRespVO {
|
||||
|
||||
@Schema(description = "回调日志列表")
|
||||
private List<Log> logs;
|
||||
|
||||
@@ -32,6 +32,12 @@ public class PayNotifyTaskPageReqVO extends PageParam {
|
||||
@Schema(description = "商户订单编号", example = "26697")
|
||||
private String merchantOrderId;
|
||||
|
||||
@Schema(description = "商户退款编号", example = "26697")
|
||||
private String merchantRefundId;
|
||||
|
||||
@Schema(description = "商户转账编号", example = "26697")
|
||||
private String merchantTransferId;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private LocalDateTime[] createTime;
|
||||
|
||||
@@ -1,25 +1,56 @@
|
||||
package cn.iocoder.yudao.module.pay.controller.admin.notify.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
|
||||
import lombok.*;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - 回调通知 Response VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class PayNotifyTaskRespVO extends PayNotifyTaskBaseVO {
|
||||
public class PayNotifyTaskRespVO {
|
||||
|
||||
@Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3380")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDateTime createTime;
|
||||
@Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10636")
|
||||
private Long appId;
|
||||
|
||||
@Schema(description = "应用名称", example = "wx_pay")
|
||||
private String appName;
|
||||
|
||||
@Schema(description = "通知类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
|
||||
private Byte type;
|
||||
|
||||
@Schema(description = "数据编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "6722")
|
||||
private Long dataId;
|
||||
|
||||
@Schema(description = "商户订单编号", example = "26697")
|
||||
private String merchantOrderId;
|
||||
|
||||
@Schema(description = "商户退款编号", example = "26697")
|
||||
private String merchantRefundId;
|
||||
|
||||
@Schema(description = "商户转账编号", example = "26697")
|
||||
private String merchantTransferId;
|
||||
|
||||
@Schema(description = "通知状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Byte status;
|
||||
|
||||
@Schema(description = "下一次通知时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDateTime nextNotifyTime;
|
||||
|
||||
@Schema(description = "最后一次执行时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDateTime lastExecuteTime;
|
||||
|
||||
@Schema(description = "当前通知次数", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private Byte notifyTimes;
|
||||
|
||||
@Schema(description = "最大可通知次数", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private Byte maxNotifyTimes;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
}
|
||||
|
||||
@@ -12,10 +12,12 @@ import cn.iocoder.yudao.module.pay.convert.order.PayOrderConvert;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
|
||||
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.dal.dataobject.wallet.PayWalletDO;
|
||||
import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
|
||||
import cn.iocoder.yudao.module.pay.framework.pay.core.WalletPayClient;
|
||||
import cn.iocoder.yudao.module.pay.service.app.PayAppService;
|
||||
import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
|
||||
import cn.iocoder.yudao.module.pay.service.wallet.PayWalletService;
|
||||
import com.google.common.collect.Maps;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
@@ -51,6 +53,8 @@ public class PayOrderController {
|
||||
private PayOrderService orderService;
|
||||
@Resource
|
||||
private PayAppService appService;
|
||||
@Resource
|
||||
private PayWalletService payWalletService;
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得支付订单")
|
||||
@@ -92,11 +96,11 @@ public class PayOrderController {
|
||||
public CommonResult<PayOrderSubmitRespVO> submitPayOrder(@RequestBody PayOrderSubmitReqVO reqVO) {
|
||||
// 1. 钱包支付事,需要额外传 user_id 和 user_type
|
||||
if (Objects.equals(reqVO.getChannelCode(), PayChannelEnum.WALLET.getCode())) {
|
||||
Map<String, String> channelExtras = reqVO.getChannelExtras() == null ?
|
||||
Maps.newHashMapWithExpectedSize(2) : reqVO.getChannelExtras();
|
||||
channelExtras.put(WalletPayClient.USER_ID_KEY, String.valueOf(getLoginUserId()));
|
||||
channelExtras.put(WalletPayClient.USER_TYPE_KEY, String.valueOf(getLoginUserType()));
|
||||
reqVO.setChannelExtras(channelExtras);
|
||||
if (reqVO.getChannelExtras() == null) {
|
||||
reqVO.setChannelExtras(Maps.newHashMapWithExpectedSize(1));
|
||||
}
|
||||
PayWalletDO wallet = payWalletService.getOrCreateWallet(getLoginUserId(), getLoginUserType());
|
||||
reqVO.getChannelExtras().put(WalletPayClient.WALLET_ID_KEY, String.valueOf(wallet.getId()));
|
||||
}
|
||||
|
||||
// 2. 提交支付
|
||||
@@ -123,7 +127,7 @@ public class PayOrderController {
|
||||
@PreAuthorize("@ss.hasPermission('pay:order:export')")
|
||||
@ApiAccessLog(operateType = EXPORT)
|
||||
public void exportOrderExcel(@Valid PayOrderExportReqVO exportReqVO,
|
||||
HttpServletResponse response) throws IOException {
|
||||
HttpServletResponse response) throws IOException {
|
||||
List<PayOrderDO> list = orderService.getOrderList(exportReqVO);
|
||||
if (CollectionUtil.isEmpty(list)) {
|
||||
ExcelUtils.write(response, "支付订单.xls", "数据",
|
||||
|
||||
@@ -25,7 +25,6 @@ import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -78,13 +77,8 @@ public class PayRefundController {
|
||||
@PreAuthorize("@ss.hasPermission('pay:refund:export')")
|
||||
@ApiAccessLog(operateType = EXPORT)
|
||||
public void exportRefundExcel(@Valid PayRefundExportReqVO exportReqVO,
|
||||
HttpServletResponse response) throws IOException {
|
||||
HttpServletResponse response) throws IOException {
|
||||
List<PayRefundDO> list = refundService.getRefundList(exportReqVO);
|
||||
if (CollectionUtil.isEmpty(list)) {
|
||||
ExcelUtils.write(response, "退款订单.xls", "数据",
|
||||
PayRefundExcelVO.class, new ArrayList<>());
|
||||
return;
|
||||
}
|
||||
|
||||
// 拼接返回
|
||||
Map<Long, PayAppDO> appMap = appService.getAppMap(convertList(list, PayRefundDO::getAppId));
|
||||
|
||||
@@ -1,21 +1,35 @@
|
||||
package cn.iocoder.yudao.module.pay.controller.admin.transfer;
|
||||
|
||||
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.*;
|
||||
import cn.iocoder.yudao.module.pay.convert.transfer.PayTransferConvert;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferPageReqVO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferRespVO;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO;
|
||||
import cn.iocoder.yudao.module.pay.service.app.PayAppService;
|
||||
import cn.iocoder.yudao.module.pay.service.transfer.PayTransferService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
|
||||
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.framework.common.pojo.PageParam.PAGE_SIZE_NONE;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||
|
||||
@Tag(name = "管理后台 - 转账单")
|
||||
@RestController
|
||||
@@ -25,27 +39,53 @@ public class PayTransferController {
|
||||
|
||||
@Resource
|
||||
private PayTransferService payTransferService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建转账单,发起转账")
|
||||
@PreAuthorize("@ss.hasPermission('pay:transfer:create')")
|
||||
public CommonResult<PayTransferCreateRespVO> createPayTransfer(@Valid @RequestBody PayTransferCreateReqVO reqVO) {
|
||||
PayTransferDO payTransfer = payTransferService.createTransfer(reqVO, getClientIP());
|
||||
return success(new PayTransferCreateRespVO().setId(payTransfer.getId()).setStatus(payTransfer.getStatus()));
|
||||
}
|
||||
@Resource
|
||||
private PayAppService payAppService;
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得转账订单")
|
||||
@PreAuthorize("@ss.hasPermission('pay:transfer:query')")
|
||||
public CommonResult<PayTransferRespVO> getTransfer(@RequestParam("id") Long id) {
|
||||
return success(PayTransferConvert.INSTANCE.convert(payTransferService.getTransfer(id)));
|
||||
PayTransferDO transfer = payTransferService.getTransfer(id);
|
||||
if (transfer == null) {
|
||||
return success(new PayTransferRespVO());
|
||||
}
|
||||
|
||||
// 拼接数据
|
||||
PayAppDO app = payAppService.getApp(transfer.getAppId());
|
||||
return success(BeanUtils.toBean(transfer, PayTransferRespVO.class, transferVO -> {
|
||||
if (app != null) {
|
||||
transferVO.setAppName(app.getName());
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得转账订单分页")
|
||||
@PreAuthorize("@ss.hasPermission('pay:transfer:query')")
|
||||
public CommonResult<PageResult<PayTransferPageItemRespVO>> getTransferPage(@Valid PayTransferPageReqVO pageVO) {
|
||||
public CommonResult<PageResult<PayTransferRespVO>> getTransferPage(@Valid PayTransferPageReqVO pageVO) {
|
||||
PageResult<PayTransferDO> pageResult = payTransferService.getTransferPage(pageVO);
|
||||
return success(PayTransferConvert.INSTANCE.convertPage(pageResult));
|
||||
|
||||
// 拼接数据
|
||||
Map<Long, PayAppDO> apps = payAppService.getAppMap(convertList(pageResult.getList(), PayTransferDO::getAppId));
|
||||
return success(BeanUtils.toBean(pageResult, PayTransferRespVO.class, transferVO -> {
|
||||
if (apps.containsKey(transferVO.getAppId())) {
|
||||
transferVO.setAppName(apps.get(transferVO.getAppId()).getName());
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
@GetMapping("/export-excel")
|
||||
@Operation(summary = "导出转账订单 Excel")
|
||||
@PreAuthorize("@ss.hasPermission('pay:transfer:export')")
|
||||
@ApiAccessLog(operateType = EXPORT)
|
||||
public void exportTransfer(PayTransferPageReqVO pageReqVO,
|
||||
HttpServletResponse response) throws IOException {
|
||||
pageReqVO.setPageSize(PAGE_SIZE_NONE);
|
||||
PageResult<PayTransferRespVO> pageResult = getTransferPage(pageReqVO).getData();
|
||||
|
||||
// 导出 Excel
|
||||
ExcelUtils.write(response, "转账订单.xls", "数据", PayTransferRespVO.class, pageResult.getList());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
package cn.iocoder.yudao.module.pay.controller.admin.transfer.vo;
|
||||
|
||||
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.channel.PayChannelEnum;
|
||||
import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferTypeEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.Validator;
|
||||
import jakarta.validation.constraints.*;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.NOT_IMPLEMENTED;
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.module.pay.enums.transfer.PayTransferTypeEnum.*;
|
||||
|
||||
@Schema(description = "管理后台 - 发起转账 Request VO")
|
||||
@Data
|
||||
public class PayTransferCreateReqVO {
|
||||
|
||||
@Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "应用编号不能为空")
|
||||
private Long appId;
|
||||
|
||||
@Schema(description = "商户转账单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "商户转账单编号不能为空")
|
||||
private String merchantTransferId;
|
||||
|
||||
@Schema(description = "转账类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "转账类型不能为空")
|
||||
@InEnum(PayTransferTypeEnum.class)
|
||||
private Integer type;
|
||||
|
||||
@Schema(description = "转账渠道", requiredMode = Schema.RequiredMode.REQUIRED, example = "alipay_pc")
|
||||
@NotEmpty(message = "转账渠道不能为空")
|
||||
private String channelCode;
|
||||
|
||||
@Min(value = 1, message = "转账金额必须大于零")
|
||||
@NotNull(message = "转账金额不能为空")
|
||||
private Integer price;
|
||||
|
||||
@Schema(description = "转账标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "示例转账")
|
||||
@NotEmpty(message = "转账标题不能为空")
|
||||
private String subject;
|
||||
|
||||
@Schema(description = "收款人姓名", example = "test1")
|
||||
@NotBlank(message = "收款人姓名不能为空", groups = {Alipay.class})
|
||||
private String userName;
|
||||
|
||||
@Schema(description = "支付宝登录号", example = "test1@sandbox.com")
|
||||
@NotBlank(message = "支付宝登录号不能为空", groups = {Alipay.class})
|
||||
private String alipayLogonId;
|
||||
|
||||
@Schema(description = "微信 openId", example = "oLefc4g5Gxx")
|
||||
@NotBlank(message = "微信 openId 不能为空", groups = {WxPay.class})
|
||||
private String openid;
|
||||
|
||||
@Schema(description = "转账渠道的额外参数")
|
||||
private Map<String, String> channelExtras;
|
||||
|
||||
public void validate(Validator validator) {
|
||||
PayTransferTypeEnum transferType = 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("待实现");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AssertTrue(message = "转账类型和转账渠道不匹配")
|
||||
public boolean isValidChannelCode() {
|
||||
PayTransferTypeEnum transferType = typeOf(type);
|
||||
switch (transferType) {
|
||||
case ALIPAY_BALANCE: {
|
||||
return PayChannelEnum.isAlipay(channelCode);
|
||||
}
|
||||
case WX_BALANCE:
|
||||
case BANK_CARD:
|
||||
case WALLET_BALANCE: {
|
||||
throw exception(NOT_IMPLEMENTED);
|
||||
}
|
||||
}
|
||||
return Boolean.FALSE;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
package cn.iocoder.yudao.module.pay.controller.admin.transfer.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* @author jason
|
||||
*/
|
||||
@Schema(description = "管理后台 - 转账单分页项 Response VO")
|
||||
@Data
|
||||
public class PayTransferPageItemRespVO {
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2931")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "转账单号", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String no;
|
||||
|
||||
@Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "12831")
|
||||
private Long appId;
|
||||
|
||||
@Schema(description = "转账渠道编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24833")
|
||||
private Long channelId;
|
||||
|
||||
@Schema(description = "转账渠道编码", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String channelCode;
|
||||
|
||||
@Schema(description = "商户转账单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17481")
|
||||
private String merchantTransferId;
|
||||
|
||||
@Schema(description = "类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
|
||||
private Integer type;
|
||||
|
||||
@Schema(description = "转账状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "转账成功时间")
|
||||
private LocalDateTime successTime;
|
||||
|
||||
@Schema(description = "转账金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "964")
|
||||
private Integer price;
|
||||
|
||||
@Schema(description = "转账标题", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String subject;
|
||||
|
||||
@Schema(description = "收款人姓名", example = "王五")
|
||||
private String userName;
|
||||
|
||||
@Schema(description = "支付宝登录号", example = "29245")
|
||||
private String alipayLogonId;
|
||||
|
||||
@Schema(description = "微信 openId", example = "26589")
|
||||
private String openid;
|
||||
|
||||
@Schema(description = "渠道转账单号")
|
||||
private String channelTransferNo;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
@@ -1,12 +1,9 @@
|
||||
package cn.iocoder.yudao.module.pay.controller.admin.transfer.vo;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import lombok.*;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
@@ -27,10 +24,7 @@ public class PayTransferPageReqVO extends PageParam {
|
||||
private String channelCode;
|
||||
|
||||
@Schema(description = "商户转账单编号", example = "17481")
|
||||
private String merchantTransferId;
|
||||
|
||||
@Schema(description = "类型", example = "2")
|
||||
private Integer type;
|
||||
private String merchantOrderId;
|
||||
|
||||
@Schema(description = "转账状态", example = "2")
|
||||
private Integer status;
|
||||
@@ -38,6 +32,9 @@ public class PayTransferPageReqVO extends PageParam {
|
||||
@Schema(description = "收款人姓名", example = "王五")
|
||||
private String userName;
|
||||
|
||||
@Schema(description = "收款人账号", example = "26589")
|
||||
private String userAccount;
|
||||
|
||||
@Schema(description = "渠道转账单号")
|
||||
private String channelTransferNo;
|
||||
|
||||
|
||||
@@ -1,60 +1,78 @@
|
||||
package cn.iocoder.yudao.module.pay.controller.admin.transfer.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
|
||||
import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
|
||||
import cn.iocoder.yudao.framework.excel.core.convert.MoneyConvert;
|
||||
import cn.iocoder.yudao.module.pay.enums.DictTypeConstants;
|
||||
|
||||
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
|
||||
import com.alibaba.excel.annotation.ExcelProperty;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.*;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
|
||||
@Schema(description = "管理后台 - 转账单 Response VO")
|
||||
@Data
|
||||
@ExcelIgnoreUnannotated
|
||||
public class PayTransferRespVO {
|
||||
|
||||
@ExcelProperty("转账单编号")
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2931")
|
||||
private Long id;
|
||||
|
||||
@ExcelProperty("转账单号")
|
||||
@Schema(description = "转账单号", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String no;
|
||||
|
||||
@Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "12831")
|
||||
private Long appId;
|
||||
|
||||
@ExcelProperty("应用名称")
|
||||
@Schema(description = "应用名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
|
||||
private String appName;
|
||||
|
||||
@Schema(description = "转账渠道编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24833")
|
||||
private Long channelId;
|
||||
|
||||
@ExcelProperty(value = "转账渠道", converter = DictConvert.class)
|
||||
@DictFormat(DictTypeConstants.CHANNEL_CODE)
|
||||
@Schema(description = "转账渠道编码", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String channelCode;
|
||||
|
||||
@ExcelProperty("商户转账单编号")
|
||||
@Schema(description = "商户转账单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17481")
|
||||
private String merchantTransferId;
|
||||
|
||||
@Schema(description = "类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
|
||||
private Integer type;
|
||||
|
||||
@ExcelProperty(value = "转账状态", converter = DictConvert.class)
|
||||
@DictFormat(DictTypeConstants.TRANSFER_STATUS)
|
||||
@Schema(description = "转账状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
|
||||
private Integer status;
|
||||
|
||||
@ExcelProperty("转账成功时间")
|
||||
@Schema(description = "转账成功时间")
|
||||
private LocalDateTime successTime;
|
||||
|
||||
@Schema(description = "转账金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "964")
|
||||
@ExcelProperty(value = "转账金额", converter = MoneyConvert.class)
|
||||
private Integer price;
|
||||
|
||||
@Schema(description = "转账标题", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@ExcelProperty("转账标题")
|
||||
@Schema(description = "转账标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "冲冲冲!")
|
||||
private String subject;
|
||||
|
||||
@Schema(description = "收款人姓名", example = "王五")
|
||||
@ExcelProperty("收款人姓名")
|
||||
private String userName;
|
||||
|
||||
@Schema(description = "支付宝登录号", example = "29245")
|
||||
private String alipayLogonId;
|
||||
|
||||
@Schema(description = "微信 openId", example = "26589")
|
||||
private String openid;
|
||||
@Schema(description = "收款人账号", requiredMode = Schema.RequiredMode.REQUIRED, example = "26589")
|
||||
@ExcelProperty("收款人账号")
|
||||
private String userAccount;
|
||||
|
||||
@Schema(description = "异步通知商户地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn")
|
||||
private String notifyUrl;
|
||||
|
||||
@ExcelProperty("用户 IP")
|
||||
@Schema(description = "用户 IP", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String userIp;
|
||||
|
||||
@@ -62,11 +80,13 @@ public class PayTransferRespVO {
|
||||
private Map<String, String> channelExtras;
|
||||
|
||||
@Schema(description = "渠道转账单号")
|
||||
@ExcelProperty("渠道转账单号")
|
||||
private String channelTransferNo;
|
||||
|
||||
@Schema(description = "调用渠道的错误码")
|
||||
private String channelErrorCode;
|
||||
|
||||
@ExcelProperty("渠道错误提示")
|
||||
@Schema(description = "调用渠道的错误提示")
|
||||
private String channelErrorMsg;
|
||||
|
||||
@@ -74,6 +94,7 @@ public class PayTransferRespVO {
|
||||
private String channelNotifyData;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@ExcelProperty("创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
|
||||
@@ -7,13 +7,14 @@ import cn.iocoder.yudao.module.pay.service.wallet.PayWalletRechargeService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.annotation.security.PermitAll;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.annotation.security.PermitAll;
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
|
||||
|
||||
@@ -36,8 +37,7 @@ public class PayWalletRechargeController {
|
||||
return success(true);
|
||||
}
|
||||
|
||||
// TODO @jason:发起退款,要 post 操作哈;
|
||||
@GetMapping("/refund")
|
||||
@PostMapping("/refund")
|
||||
@Operation(summary = "发起钱包充值退款")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
public CommonResult<Boolean> refundWalletRecharge(@RequestParam("id") Long id) {
|
||||
@@ -50,7 +50,9 @@ public class PayWalletRechargeController {
|
||||
@PermitAll // 无需登录, 内部校验实现
|
||||
public CommonResult<Boolean> updateWalletRechargeRefunded(@RequestBody PayRefundNotifyReqDTO notifyReqDTO) {
|
||||
walletRechargeService.updateWalletRechargeRefunded(
|
||||
Long.valueOf(notifyReqDTO.getMerchantOrderId()), notifyReqDTO.getPayRefundId());
|
||||
Long.valueOf(notifyReqDTO.getMerchantOrderId()),
|
||||
Long.valueOf(notifyReqDTO.getMerchantRefundId()),
|
||||
notifyReqDTO.getPayRefundId());
|
||||
return success(true);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,9 +9,11 @@ import cn.iocoder.yudao.module.pay.controller.app.order.vo.AppPayOrderSubmitReqV
|
||||
import cn.iocoder.yudao.module.pay.controller.app.order.vo.AppPayOrderSubmitRespVO;
|
||||
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.dal.dataobject.wallet.PayWalletDO;
|
||||
import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
|
||||
import cn.iocoder.yudao.module.pay.framework.pay.core.WalletPayClient;
|
||||
import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
|
||||
import cn.iocoder.yudao.module.pay.service.wallet.PayWalletService;
|
||||
import com.google.common.collect.Maps;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
@@ -22,7 +24,6 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
@@ -39,6 +40,8 @@ public class AppPayOrderController {
|
||||
|
||||
@Resource
|
||||
private PayOrderService payOrderService;
|
||||
@Resource
|
||||
private PayWalletService payWalletService;
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得支付订单")
|
||||
@@ -63,11 +66,11 @@ public class AppPayOrderController {
|
||||
public CommonResult<AppPayOrderSubmitRespVO> submitPayOrder(@RequestBody AppPayOrderSubmitReqVO reqVO) {
|
||||
// 1. 钱包支付事,需要额外传 user_id 和 user_type
|
||||
if (Objects.equals(reqVO.getChannelCode(), PayChannelEnum.WALLET.getCode())) {
|
||||
Map<String, String> channelExtras = reqVO.getChannelExtras() == null ?
|
||||
Maps.newHashMapWithExpectedSize(2) : reqVO.getChannelExtras();
|
||||
channelExtras.put(WalletPayClient.USER_ID_KEY, String.valueOf(getLoginUserId()));
|
||||
channelExtras.put(WalletPayClient.USER_TYPE_KEY, String.valueOf(getLoginUserType()));
|
||||
reqVO.setChannelExtras(channelExtras);
|
||||
if (reqVO.getChannelExtras() == null) {
|
||||
reqVO.setChannelExtras(Maps.newHashMapWithExpectedSize(1));
|
||||
}
|
||||
PayWalletDO wallet = payWalletService.getOrCreateWallet(getLoginUserId(), getLoginUserType());
|
||||
reqVO.getChannelExtras().put(WalletPayClient.WALLET_ID_KEY, String.valueOf(wallet.getId()));
|
||||
}
|
||||
|
||||
// 2. 提交支付
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package cn.iocoder.yudao.module.pay.controller.app.transfer;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.pay.service.transfer.PayTransferService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@Tag(name = "用户 APP - 转账单")
|
||||
@RestController
|
||||
@RequestMapping("/pay/transfer")
|
||||
@Validated
|
||||
@Slf4j
|
||||
public class AppPayTransferController {
|
||||
|
||||
@Resource
|
||||
private PayTransferService transferService;
|
||||
|
||||
@GetMapping("/sync")
|
||||
@Operation(summary = "同步转账单") // 目的:解决微信转账的异步回调可能有延迟的问题
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
public CommonResult<Boolean> syncTransfer(@RequestParam("id") Long id) {
|
||||
transferService.syncTransfer(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
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.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;
|
||||
|
||||
/**
|
||||
* 示例订单 Convert
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Mapper
|
||||
public interface PayDemoOrderConvert {
|
||||
|
||||
PayDemoOrderConvert INSTANCE = Mappers.getMapper(PayDemoOrderConvert.class);
|
||||
|
||||
PayDemoOrderDO convert(PayDemoOrderCreateReqVO bean);
|
||||
|
||||
PayDemoOrderRespVO convert(PayDemoOrderDO bean);
|
||||
|
||||
PageResult<PayDemoOrderRespVO> convertPage(PageResult<PayDemoOrderDO> page);
|
||||
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
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.transfer.PayDemoTransferCreateReqVO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.transfer.PayDemoTransferRespVO;
|
||||
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);
|
||||
|
||||
PageResult<PayDemoTransferRespVO> convertPage(PageResult<PayDemoTransferDO> pageResult);
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package cn.iocoder.yudao.module.pay.convert.notify;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskDetailRespVO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskRespVO;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyLogDO;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyTaskDO;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 支付通知 Convert
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Mapper
|
||||
public interface PayNotifyTaskConvert {
|
||||
|
||||
PayNotifyTaskConvert INSTANCE = Mappers.getMapper(PayNotifyTaskConvert.class);
|
||||
|
||||
PayNotifyTaskRespVO convert(PayNotifyTaskDO bean);
|
||||
|
||||
default PageResult<PayNotifyTaskRespVO> convertPage(PageResult<PayNotifyTaskDO> page, Map<Long, PayAppDO> appMap){
|
||||
PageResult<PayNotifyTaskRespVO> result = convertPage(page);
|
||||
result.getList().forEach(order -> MapUtils.findAndThen(appMap, order.getAppId(), app -> order.setAppName(app.getName())));
|
||||
return result;
|
||||
}
|
||||
PageResult<PayNotifyTaskRespVO> convertPage(PageResult<PayNotifyTaskDO> page);
|
||||
|
||||
default PayNotifyTaskDetailRespVO convert(PayNotifyTaskDO task, PayAppDO app, List<PayNotifyLogDO> logs) {
|
||||
PayNotifyTaskDetailRespVO respVO = convert(task, logs);
|
||||
if (app != null) {
|
||||
respVO.setAppName(app.getName());
|
||||
}
|
||||
return respVO;
|
||||
}
|
||||
PayNotifyTaskDetailRespVO convert(PayNotifyTaskDO task, List<PayNotifyLogDO> logs);
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
|
||||
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.refund.vo.PayRefundDetailsRespVO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundExcelVO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundPageItemRespVO;
|
||||
@@ -42,8 +41,6 @@ public interface PayRefundConvert {
|
||||
|
||||
PayRefundDO convert(PayRefundCreateReqDTO bean);
|
||||
|
||||
PayRefundRespDTO convert02(PayRefundDO bean);
|
||||
|
||||
default List<PayRefundExcelVO> convertList(List<PayRefundDO> list, Map<Long, PayAppDO> appMap) {
|
||||
return CollectionUtils.convertList(list, order -> {
|
||||
PayRefundExcelVO excelVO = convertExcel(order);
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
package cn.iocoder.yudao.module.pay.convert.transfer;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.transfer.PayDemoTransferCreateReqVO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferCreateReqVO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferPageItemRespVO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferRespVO;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
@Mapper
|
||||
public interface PayTransferConvert {
|
||||
|
||||
PayTransferConvert INSTANCE = Mappers.getMapper(PayTransferConvert.class);
|
||||
|
||||
PayTransferDO convert(PayTransferCreateReqDTO dto);
|
||||
|
||||
PayTransferUnifiedReqDTO convert2(PayTransferDO dto);
|
||||
|
||||
PayTransferCreateReqDTO convert(PayTransferCreateReqVO vo);
|
||||
|
||||
PayTransferCreateReqDTO convert(PayDemoTransferCreateReqVO vo);
|
||||
|
||||
PayTransferRespVO convert(PayTransferDO bean);
|
||||
|
||||
PageResult<PayTransferPageItemRespVO> convertPage(PageResult<PayTransferDO> pageResult);
|
||||
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
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 cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
// TODO 芋艿:需要详细 review
|
||||
/**
|
||||
* 示例转账订单
|
||||
*
|
||||
* 演示业务系统的转账业务
|
||||
*/
|
||||
@TableName(value ="pay_demo_transfer", autoResultMap = true)
|
||||
@KeySequence("pay_demo_transfer_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||
@Data
|
||||
public class PayDemoTransferDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 订单编号
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 应用编号
|
||||
*
|
||||
* 关联 {@link PayAppDO#getId()}
|
||||
*/
|
||||
private Long appId;
|
||||
|
||||
/**
|
||||
* 转账类型
|
||||
* <p>
|
||||
* 枚举 {@link PayTransferTypeEnum}
|
||||
*/
|
||||
private Integer type;
|
||||
|
||||
/**
|
||||
* 转账金额,单位:分
|
||||
*/
|
||||
private Integer price;
|
||||
|
||||
/**
|
||||
* 收款人姓名
|
||||
*/
|
||||
private String userName;
|
||||
|
||||
/**
|
||||
* 支付宝登录号
|
||||
*/
|
||||
private String alipayLogonId;
|
||||
|
||||
/**
|
||||
* 微信 openId
|
||||
*/
|
||||
private String openid;
|
||||
|
||||
/**
|
||||
* 转账状态
|
||||
*/
|
||||
private Integer transferStatus;
|
||||
|
||||
/**
|
||||
* 转账单编号
|
||||
*/
|
||||
private Long payTransferId;
|
||||
|
||||
/**
|
||||
* 转账支付成功渠道
|
||||
*/
|
||||
private String payChannelCode;
|
||||
|
||||
/**
|
||||
* 转账支付时间
|
||||
*/
|
||||
private LocalDateTime transferTime;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package cn.iocoder.yudao.module.pay.dal.dataobject.demo;
|
||||
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO;
|
||||
import cn.iocoder.yudao.module.pay.enums.demo.PayDemoWithdrawStatusEnum;
|
||||
import cn.iocoder.yudao.module.pay.enums.demo.PayDemoWithdrawTypeEnum;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 示例提现订单
|
||||
*
|
||||
* 演示业务系统的转账业务
|
||||
*/
|
||||
@TableName(value ="pay_demo_withdraw", autoResultMap = true)
|
||||
@KeySequence("pay_demo_withdraw_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||
@Data
|
||||
public class PayDemoWithdrawDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 提现单编号,自增
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 提现标题
|
||||
*/
|
||||
private String subject;
|
||||
/**
|
||||
* 提现金额,单位:分
|
||||
*/
|
||||
private Integer price;
|
||||
|
||||
/**
|
||||
* 收款人账号
|
||||
*/
|
||||
private String userAccount;
|
||||
/**
|
||||
* 收款人姓名
|
||||
*/
|
||||
private String userName;
|
||||
|
||||
/**
|
||||
* 提现方式
|
||||
*
|
||||
* 枚举 {@link PayDemoWithdrawTypeEnum}
|
||||
*/
|
||||
private Integer type;
|
||||
/**
|
||||
* 提现状态
|
||||
*
|
||||
* 枚举 {@link PayDemoWithdrawStatusEnum}
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
// ========== 转账相关字段 ==========
|
||||
|
||||
/**
|
||||
* 转账单编号
|
||||
*
|
||||
* 关联 {@link PayTransferDO#getId()}
|
||||
*/
|
||||
private Long payTransferId;
|
||||
/**
|
||||
* 转账渠道
|
||||
*
|
||||
* 枚举 {@link cn.iocoder.yudao.module.pay.enums.PayChannelEnum}
|
||||
*/
|
||||
private String transferChannelCode;
|
||||
/**
|
||||
* 转账成功时间
|
||||
*/
|
||||
private LocalDateTime transferTime;
|
||||
/**
|
||||
* 转账错误提示
|
||||
*/
|
||||
private String transferErrorMsg;
|
||||
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
|
||||
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.enums.notify.PayNotifyStatusEnum;
|
||||
import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
@@ -60,6 +61,7 @@ public class PayNotifyTaskDO extends TenantBaseDO {
|
||||
*
|
||||
* 1. {@link PayNotifyTypeEnum#ORDER} 时,关联 {@link PayOrderDO#getId()}
|
||||
* 2. {@link PayNotifyTypeEnum#REFUND} 时,关联 {@link PayRefundDO#getId()}
|
||||
* 3. {@link PayNotifyTypeEnum#TRANSFER} 时,关联 {@link PayTransferDO#getId()}
|
||||
*/
|
||||
private Long dataId;
|
||||
/**
|
||||
@@ -67,7 +69,11 @@ public class PayNotifyTaskDO extends TenantBaseDO {
|
||||
*/
|
||||
private String merchantOrderId;
|
||||
/**
|
||||
* 商户转账单编号
|
||||
* 商户退款编号
|
||||
*/
|
||||
private String merchantRefundId;
|
||||
/**
|
||||
* 商户转账编号
|
||||
*/
|
||||
private String merchantTransferId;
|
||||
/**
|
||||
|
||||
@@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.pay.dal.dataobject.transfer;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferStatusRespEnum;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
@@ -16,7 +15,6 @@ import lombok.Data;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
|
||||
// TODO 芋艿:需要详细 review
|
||||
/**
|
||||
* 转账单 DO
|
||||
*
|
||||
@@ -35,7 +33,6 @@ public class PayTransferDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 转账单号
|
||||
*
|
||||
*/
|
||||
private String no;
|
||||
|
||||
@@ -70,13 +67,6 @@ public class PayTransferDO extends BaseDO {
|
||||
|
||||
// ========== 转账相关字段 ==========
|
||||
|
||||
/**
|
||||
* 类型
|
||||
*
|
||||
* 枚举 {@link PayTransferTypeEnum}
|
||||
*/
|
||||
private Integer type;
|
||||
|
||||
/**
|
||||
* 转账标题
|
||||
*/
|
||||
@@ -87,6 +77,10 @@ public class PayTransferDO extends BaseDO {
|
||||
*/
|
||||
private Integer price;
|
||||
|
||||
/**
|
||||
* 收款人账号
|
||||
*/
|
||||
private String userAccount;
|
||||
/**
|
||||
* 收款人姓名
|
||||
*/
|
||||
@@ -104,19 +98,6 @@ public class PayTransferDO extends BaseDO {
|
||||
*/
|
||||
private LocalDateTime successTime;
|
||||
|
||||
// ========== 支付宝转账相关字段 ==========
|
||||
/**
|
||||
* 支付宝登录号
|
||||
*/
|
||||
private String alipayLogonId;
|
||||
|
||||
|
||||
// ========== 微信转账相关字段 ==========
|
||||
/**
|
||||
* 微信 openId
|
||||
*/
|
||||
private String openid;
|
||||
|
||||
// ========== 其它字段 ==========
|
||||
|
||||
/**
|
||||
@@ -151,8 +132,15 @@ public class PayTransferDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 渠道的同步/异步通知的内容
|
||||
*
|
||||
*/
|
||||
private String channelNotifyData;
|
||||
|
||||
/**
|
||||
* 渠道 package 信息
|
||||
*
|
||||
* 特殊:目前只有微信转账有这个东西!!!
|
||||
* @see <a href="https://pay.weixin.qq.com/doc/v3/merchant/4012716430">JSAPI 调起用户确认收款</a>
|
||||
*/
|
||||
private String channelPackageInfo;
|
||||
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package cn.iocoder.yudao.module.pay.dal.mysql.demo;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoTransferDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface PayDemoTransferMapper extends BaseMapperX<PayDemoTransferDO> {
|
||||
|
||||
default PageResult<PayDemoTransferDO> selectPage(PageParam pageParam){
|
||||
return selectPage(pageParam, new LambdaQueryWrapperX<PayDemoTransferDO>()
|
||||
.orderByDesc(PayDemoTransferDO::getId));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package cn.iocoder.yudao.module.pay.dal.mysql.demo;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoWithdrawDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface PayDemoWithdrawMapper extends BaseMapperX<PayDemoWithdrawDO> {
|
||||
|
||||
default PageResult<PayDemoWithdrawDO> selectPage(PageParam pageParam){
|
||||
return selectPage(pageParam, new LambdaQueryWrapperX<PayDemoWithdrawDO>()
|
||||
.orderByDesc(PayDemoWithdrawDO::getId));
|
||||
}
|
||||
|
||||
default int updateByIdAndStatus(Long id, Integer whereStatus, PayDemoWithdrawDO updateObj) {
|
||||
return update(updateObj, new LambdaQueryWrapperX<PayDemoWithdrawDO>()
|
||||
.eq(PayDemoWithdrawDO::getId, id)
|
||||
.eq(PayDemoWithdrawDO::getStatus, whereStatus));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -37,6 +37,8 @@ public interface PayNotifyTaskMapper extends BaseMapperX<PayNotifyTaskDO> {
|
||||
.eqIfPresent(PayNotifyTaskDO::getDataId, reqVO.getDataId())
|
||||
.eqIfPresent(PayNotifyTaskDO::getStatus, reqVO.getStatus())
|
||||
.eqIfPresent(PayNotifyTaskDO::getMerchantOrderId, reqVO.getMerchantOrderId())
|
||||
.eqIfPresent(PayNotifyTaskDO::getMerchantRefundId, reqVO.getMerchantRefundId())
|
||||
.eqIfPresent(PayNotifyTaskDO::getMerchantTransferId, reqVO.getMerchantTransferId())
|
||||
.betweenIfPresent(PayNotifyTaskDO::getCreateTime, reqVO.getCreateTime())
|
||||
.orderByDesc(PayNotifyTaskDO::getId));
|
||||
}
|
||||
|
||||
@@ -8,19 +8,27 @@ import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface PayTransferMapper extends BaseMapperX<PayTransferDO> {
|
||||
|
||||
default int updateByIdAndStatus(Long id, List<Integer> status, PayTransferDO updateObj) {
|
||||
default int updateByIdAndStatus(Long id, List<Integer> whereStatuses, PayTransferDO updateObj) {
|
||||
return update(updateObj, new LambdaQueryWrapper<PayTransferDO>()
|
||||
.eq(PayTransferDO::getId, id).in(PayTransferDO::getStatus, status));
|
||||
.eq(PayTransferDO::getId, id)
|
||||
.in(PayTransferDO::getStatus, whereStatuses));
|
||||
}
|
||||
|
||||
default PayTransferDO selectByAppIdAndMerchantTransferId(Long appId, String merchantTransferId){
|
||||
default int updateByIdAndStatus(Long id, Integer whereStatus, PayTransferDO updateObj) {
|
||||
return update(updateObj, new LambdaQueryWrapper<PayTransferDO>()
|
||||
.eq(PayTransferDO::getId, id)
|
||||
.eq(PayTransferDO::getStatus, whereStatus));
|
||||
}
|
||||
|
||||
default PayTransferDO selectByAppIdAndMerchantOrderId(Long appId, String merchantOrderId) {
|
||||
return selectOne(PayTransferDO::getAppId, appId,
|
||||
PayTransferDO::getMerchantTransferId, merchantTransferId);
|
||||
PayTransferDO::getMerchantTransferId, merchantOrderId);
|
||||
}
|
||||
|
||||
default PageResult<PayTransferDO> selectPage(PayTransferPageReqVO reqVO) {
|
||||
@@ -28,17 +36,17 @@ public interface PayTransferMapper extends BaseMapperX<PayTransferDO> {
|
||||
.eqIfPresent(PayTransferDO::getNo, reqVO.getNo())
|
||||
.eqIfPresent(PayTransferDO::getAppId, reqVO.getAppId())
|
||||
.eqIfPresent(PayTransferDO::getChannelCode, reqVO.getChannelCode())
|
||||
.eqIfPresent(PayTransferDO::getMerchantTransferId, reqVO.getMerchantTransferId())
|
||||
.eqIfPresent(PayTransferDO::getType, reqVO.getType())
|
||||
.eqIfPresent(PayTransferDO::getMerchantTransferId, reqVO.getMerchantOrderId())
|
||||
.eqIfPresent(PayTransferDO::getStatus, reqVO.getStatus())
|
||||
.likeIfPresent(PayTransferDO::getUserName, reqVO.getUserName())
|
||||
.likeIfPresent(PayTransferDO::getUserAccount, reqVO.getUserAccount())
|
||||
.eqIfPresent(PayTransferDO::getChannelTransferNo, reqVO.getChannelTransferNo())
|
||||
.betweenIfPresent(PayTransferDO::getCreateTime, reqVO.getCreateTime())
|
||||
.orderByDesc(PayTransferDO::getId));
|
||||
}
|
||||
|
||||
default List<PayTransferDO> selectListByStatus(Integer status) {
|
||||
return selectList(PayTransferDO::getStatus, status);
|
||||
default List<PayTransferDO> selectListByStatus(Collection<Integer> statuses) {
|
||||
return selectList(PayTransferDO::getStatus, statuses);
|
||||
}
|
||||
|
||||
default PayTransferDO selectByAppIdAndNo(Long appId, String no) {
|
||||
@@ -46,6 +54,10 @@ public interface PayTransferMapper extends BaseMapperX<PayTransferDO> {
|
||||
PayTransferDO::getNo, no);
|
||||
}
|
||||
|
||||
default PayTransferDO selectByNo(String no) {
|
||||
return selectOne(PayTransferDO::getNo, no);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -14,14 +14,16 @@ import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.NonePayClientConfig;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.refund.PayRefundStatusRespEnum;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferStatusRespEnum;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO;
|
||||
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.dataobject.wallet.PayWalletTransactionDO;
|
||||
import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum;
|
||||
import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
|
||||
import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum;
|
||||
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 cn.iocoder.yudao.module.pay.service.wallet.PayWalletService;
|
||||
import cn.iocoder.yudao.module.pay.service.wallet.PayWalletTransactionService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -40,13 +42,14 @@ import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.REFUND_NOT_FO
|
||||
@Slf4j
|
||||
public class WalletPayClient extends AbstractPayClient<NonePayClientConfig> {
|
||||
|
||||
public static final String USER_ID_KEY = "user_id";
|
||||
public static final String USER_TYPE_KEY = "user_type";
|
||||
public static final String WALLET_ID_KEY = "walletId";
|
||||
|
||||
private PayWalletService wallService;
|
||||
private PayWalletTransactionService walletTransactionService;
|
||||
|
||||
private PayOrderService orderService;
|
||||
private PayRefundService refundService;
|
||||
private PayTransferService transferService;
|
||||
|
||||
public WalletPayClient(Long channelId, NonePayClientConfig config) {
|
||||
super(channelId, PayChannelEnum.WALLET.getCode(), config);
|
||||
@@ -63,19 +66,18 @@ public class WalletPayClient extends AbstractPayClient<NonePayClientConfig> {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("PatternVariableCanBeUsed")
|
||||
protected PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
|
||||
try {
|
||||
Long userId = MapUtil.getLong(reqDTO.getChannelExtras(), USER_ID_KEY);
|
||||
Integer userType = MapUtil.getInt(reqDTO.getChannelExtras(), USER_TYPE_KEY);
|
||||
Assert.notNull(userId, "用户 id 不能为空");
|
||||
Assert.notNull(userType, "用户类型不能为空");
|
||||
PayWalletTransactionDO transaction = wallService.orderPay(userId, userType, reqDTO.getOutTradeNo(),
|
||||
reqDTO.getPrice());
|
||||
Long walletId = MapUtil.getLong(reqDTO.getChannelExtras(), WALLET_ID_KEY);
|
||||
Assert.notNull(walletId, "钱包编号");
|
||||
PayWalletTransactionDO transaction = wallService.orderPay(walletId,
|
||||
reqDTO.getOutTradeNo(), reqDTO.getPrice());
|
||||
return PayOrderRespDTO.successOf(transaction.getNo(), transaction.getCreator(),
|
||||
transaction.getCreateTime(),
|
||||
reqDTO.getOutTradeNo(), transaction);
|
||||
} catch (Throwable ex) {
|
||||
log.error("[doUnifiedOrder] 失败", ex);
|
||||
log.error("[doUnifiedOrder][reqDTO({}) 异常]", reqDTO, ex);
|
||||
Integer errorCode = INTERNAL_SERVER_ERROR.getCode();
|
||||
String errorMsg = INTERNAL_SERVER_ERROR.getMsg();
|
||||
if (ex instanceof ServiceException) {
|
||||
@@ -123,6 +125,7 @@ public class WalletPayClient extends AbstractPayClient<NonePayClientConfig> {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("PatternVariableCanBeUsed")
|
||||
protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
|
||||
try {
|
||||
PayWalletTransactionDO payWalletTransaction = wallService.orderRefund(reqDTO.getOutRefundNo(),
|
||||
@@ -130,7 +133,7 @@ public class WalletPayClient extends AbstractPayClient<NonePayClientConfig> {
|
||||
return PayRefundRespDTO.successOf(payWalletTransaction.getNo(), payWalletTransaction.getCreateTime(),
|
||||
reqDTO.getOutRefundNo(), payWalletTransaction);
|
||||
} catch (Throwable ex) {
|
||||
log.error("[doUnifiedRefund] 失败", ex);
|
||||
log.error("[doUnifiedRefund][reqDOT({}) 异常]", reqDTO, ex);
|
||||
Integer errorCode = INTERNAL_SERVER_ERROR.getCode();
|
||||
String errorMsg = INTERNAL_SERVER_ERROR.getMsg();
|
||||
if (ex instanceof ServiceException) {
|
||||
@@ -178,18 +181,71 @@ public class WalletPayClient extends AbstractPayClient<NonePayClientConfig> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PayTransferRespDTO doParseTransferNotify(Map<String, String> params, String body, Map<String, String> headers) {
|
||||
throw new UnsupportedOperationException("未实现");
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("PatternVariableCanBeUsed")
|
||||
public PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) {
|
||||
throw new UnsupportedOperationException("待实现");
|
||||
try {
|
||||
Long walletId = Long.parseLong(reqDTO.getUserAccount());
|
||||
PayWalletTransactionDO transaction = wallService.addWalletBalance(walletId, String.valueOf(reqDTO.getOutTransferNo()),
|
||||
PayWalletBizTypeEnum.TRANSFER, reqDTO.getPrice());
|
||||
return PayTransferRespDTO.successOf(transaction.getNo(), transaction.getCreateTime(),
|
||||
reqDTO.getOutTransferNo(), transaction);
|
||||
} catch (Throwable ex) {
|
||||
log.error("[doUnifiedTransfer][reqDTO({}) 异常]", reqDTO, ex);
|
||||
Integer errorCode = INTERNAL_SERVER_ERROR.getCode();
|
||||
String errorMsg = INTERNAL_SERVER_ERROR.getMsg();
|
||||
if (ex instanceof ServiceException) {
|
||||
ServiceException serviceException = (ServiceException) ex;
|
||||
errorCode = serviceException.getCode();
|
||||
errorMsg = serviceException.getMessage();
|
||||
}
|
||||
return PayTransferRespDTO.closedOf(String.valueOf(errorCode), errorMsg,
|
||||
reqDTO.getOutTransferNo(), "");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PayTransferRespDTO doGetTransfer(String outTradeNo, PayTransferTypeEnum type) {
|
||||
throw new UnsupportedOperationException("待实现");
|
||||
protected PayTransferRespDTO doParseTransferNotify(Map<String, String> params, String body, Map<String, String> headers) {
|
||||
throw new UnsupportedOperationException("钱包支付无转账回调");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PayTransferRespDTO doGetTransfer(String outTradeNo) {
|
||||
if (transferService == null) {
|
||||
transferService = SpringUtil.getBean(PayTransferService.class);
|
||||
}
|
||||
// 获取转账单
|
||||
PayTransferDO transfer = transferService.getTransferByNo(outTradeNo);
|
||||
// 转账单不存在,返回关闭状态
|
||||
if (transfer == null) {
|
||||
return PayTransferRespDTO.closedOf(String.valueOf(PAY_ORDER_EXTENSION_NOT_FOUND.getCode()),
|
||||
PAY_ORDER_EXTENSION_NOT_FOUND.getMsg(), outTradeNo, "");
|
||||
}
|
||||
// 关闭状态
|
||||
if (PayTransferStatusRespEnum.isClosed(transfer.getStatus())) {
|
||||
return PayTransferRespDTO.closedOf(transfer.getChannelErrorCode(),
|
||||
transfer.getChannelErrorMsg(), outTradeNo, "");
|
||||
}
|
||||
// 成功状态
|
||||
if (PayTransferStatusRespEnum.isSuccess(transfer.getStatus())) {
|
||||
PayWalletTransactionDO walletTransaction = walletTransactionService.getWalletTransaction(
|
||||
String.valueOf(transfer.getId()), PayWalletBizTypeEnum.TRANSFER);
|
||||
Assert.notNull(walletTransaction, "转账单 {} 钱包流水不能为空", outTradeNo);
|
||||
return PayTransferRespDTO.successOf(walletTransaction.getNo(), walletTransaction.getCreateTime(),
|
||||
outTradeNo, walletTransaction);
|
||||
}
|
||||
// 处理中状态
|
||||
if (PayTransferStatusRespEnum.isProcessing(transfer.getStatus())) {
|
||||
return PayTransferRespDTO.processingOf(transfer.getChannelTransferNo(),
|
||||
outTradeNo, transfer);
|
||||
}
|
||||
// 等待状态
|
||||
if (transfer.getStatus().equals(PayTransferStatusRespEnum.WAITING.getStatus())) {
|
||||
return PayTransferRespDTO.waitingOf(transfer.getChannelTransferNo(),
|
||||
outTradeNo, transfer);
|
||||
}
|
||||
// 其它状态为无效状态
|
||||
log.error("[doGetTransfer] 转账单 {} 的状态不正确", outTradeNo);
|
||||
throw new IllegalStateException(String.format("转账单[%s] 状态不正确", outTradeNo));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package cn.iocoder.yudao.module.pay.service.app;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.app.vo.PayAppCreateReqVO;
|
||||
@@ -17,6 +18,7 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
@@ -116,6 +118,9 @@ public class PayAppServiceImpl implements PayAppService {
|
||||
|
||||
@Override
|
||||
public List<PayAppDO> getAppList(Collection<Long> ids) {
|
||||
if (CollUtil.isEmpty(ids)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return appMapper.selectBatchIds(ids);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ 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.order.PayDemoOrderCreateReqVO;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoOrderDO;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
/**
|
||||
@@ -58,8 +59,9 @@ public interface PayDemoOrderService {
|
||||
* 更新示例订单为已退款
|
||||
*
|
||||
* @param id 编号
|
||||
* @param refundId 退款编号
|
||||
* @param payRefundId 退款订单号
|
||||
*/
|
||||
void updateDemoOrderRefunded(Long id, Long payRefundId);
|
||||
void updateDemoOrderRefunded(Long id, String refundId, Long payRefundId);
|
||||
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@ import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.hutool.core.util.ObjectUtil.notEqual;
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
@@ -97,7 +96,6 @@ public class PayDemoOrderServiceImpl implements PayDemoOrderService {
|
||||
// 2.2 更新支付单到 demo 订单
|
||||
payDemoOrderMapper.updateById(new PayDemoOrderDO().setId(demoOrder.getId())
|
||||
.setPayOrderId(payOrderId));
|
||||
// 返回
|
||||
return demoOrder.getId();
|
||||
}
|
||||
|
||||
@@ -121,14 +119,14 @@ public class PayDemoOrderServiceImpl implements PayDemoOrderService {
|
||||
}
|
||||
// 1.2 校验订单已支付
|
||||
if (order.getPayStatus()) {
|
||||
// 特殊:如果订单已支付,且支付单号相同,直接返回,说明重复回调
|
||||
// 特殊:支付单号相同,直接返回,说明重复回调
|
||||
if (ObjectUtil.equals(order.getPayOrderId(), payOrderId)) {
|
||||
log.warn("[updateDemoOrderPaid][order({}) 已支付,且支付单号相同({}),直接返回]", order, payOrderId);
|
||||
return;
|
||||
}
|
||||
// 异常:支付单号不同,说明支付单号错误
|
||||
log.error("[updateDemoOrderPaid][order({}) 支付单不匹配({}),请进行处理!order 数据是:{}]",
|
||||
order, payOrderId, toJsonString(order));
|
||||
log.error("[updateDemoOrderPaid][order({}) 支付单不匹配({}),请进行处理!]",
|
||||
order, payOrderId);
|
||||
throw exception(DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR);
|
||||
}
|
||||
|
||||
@@ -186,7 +184,7 @@ public class PayDemoOrderServiceImpl implements PayDemoOrderService {
|
||||
|
||||
// 2.1 生成退款单号
|
||||
// 一般来说,用户发起退款的时候,都会单独插入一个售后维权表,然后使用该表的 id 作为 refundId
|
||||
// 这里我们是个简单的 demo,所以没有售后维权表,直接使用订单 id + "-refund" 来演示
|
||||
// 这里我们是个简单的 demo,所以没有售后维权表,直接使用订单 id + "-refund" 来演示
|
||||
String refundId = order.getId() + "-refund";
|
||||
// 2.2 创建退款单
|
||||
Long payRefundId = payRefundApi.createRefund(new PayRefundCreateReqDTO()
|
||||
@@ -217,16 +215,18 @@ public class PayDemoOrderServiceImpl implements PayDemoOrderService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDemoOrderRefunded(Long id, Long payRefundId) {
|
||||
public void updateDemoOrderRefunded(Long id, String refundId, Long payRefundId) {
|
||||
// 1. 校验并获得退款订单(可退款)
|
||||
PayRefundRespDTO payRefund = validateDemoOrderCanRefunded(id, payRefundId);
|
||||
PayRefundRespDTO payRefund = validateDemoOrderCanRefunded(id, refundId, payRefundId);
|
||||
// 2.2 更新退款单到 demo 订单
|
||||
payDemoOrderMapper.updateById(new PayDemoOrderDO().setId(id)
|
||||
.setRefundTime(payRefund.getSuccessTime()));
|
||||
}
|
||||
|
||||
private PayRefundRespDTO validateDemoOrderCanRefunded(Long id, Long payRefundId) {
|
||||
private PayRefundRespDTO validateDemoOrderCanRefunded(Long id, String refundId, Long payRefundId) {
|
||||
// 1.1 校验示例订单
|
||||
// 一般来说,这里应该用 refundId 来查询退款单,然后再校验订单是否匹配
|
||||
// 这里我们是个简单的 demo,所以没有售后维权表,直接使用订单 id 来查询订单
|
||||
PayDemoOrderDO order = payDemoOrderMapper.selectById(id);
|
||||
if (order == null) {
|
||||
throw exception(DEMO_ORDER_NOT_FOUND);
|
||||
@@ -243,7 +243,7 @@ public class PayDemoOrderServiceImpl implements PayDemoOrderService {
|
||||
if (payRefund == null) {
|
||||
throw exception(DEMO_ORDER_REFUND_FAIL_REFUND_NOT_FOUND);
|
||||
}
|
||||
// 2.2
|
||||
// 2.2 必须是退款成功状态
|
||||
if (!PayRefundStatusEnum.isSuccess(payRefund.getStatus())) {
|
||||
throw exception(DEMO_ORDER_REFUND_FAIL_REFUND_NOT_SUCCESS);
|
||||
}
|
||||
@@ -254,7 +254,7 @@ public class PayDemoOrderServiceImpl implements PayDemoOrderService {
|
||||
throw exception(DEMO_ORDER_REFUND_FAIL_REFUND_PRICE_NOT_MATCH);
|
||||
}
|
||||
// 2.4 校验退款订单匹配(二次)
|
||||
if (notEqual(payRefund.getMerchantOrderId(), id.toString())) {
|
||||
if (notEqual(payRefund.getMerchantRefundId(), id.toString() + "-refund")) {
|
||||
log.error("[validateDemoOrderCanRefunded][order({}) 退款单不匹配({}),请进行处理!payRefund 数据是:{}]",
|
||||
id, payRefundId, toJsonString(payRefund));
|
||||
throw exception(DEMO_ORDER_REFUND_FAIL_REFUND_ORDER_ID_ERROR);
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
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.transfer.PayDemoTransferCreateReqVO;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoTransferDO;
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
/**
|
||||
* 示例转账业务 Service 接口
|
||||
*
|
||||
* @author jason
|
||||
*/
|
||||
public interface PayDemoTransferService {
|
||||
|
||||
/**
|
||||
* 创建转账业务示例订单
|
||||
*
|
||||
* @param createReqVO 创建信息
|
||||
* @return 编号
|
||||
*/
|
||||
Long createDemoTransfer(@Valid PayDemoTransferCreateReqVO createReqVO);
|
||||
|
||||
/**
|
||||
* 获得转账业务示例订单分页
|
||||
*
|
||||
* @param pageVO 分页查询参数
|
||||
*/
|
||||
PageResult<PayDemoTransferDO> getDemoTransferPage(PageParam pageVO);
|
||||
|
||||
/**
|
||||
* 更新转账业务示例订单的转账状态
|
||||
*
|
||||
* @param id 编号
|
||||
* @param payTransferId 转账单编号
|
||||
*/
|
||||
void updateDemoTransferStatus(Long id, Long payTransferId);
|
||||
}
|
||||
@@ -1,26 +1,31 @@
|
||||
package cn.iocoder.yudao.module.pay.service.demo;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
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.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.pay.api.transfer.PayTransferApi;
|
||||
import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateRespDTO;
|
||||
import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferRespDTO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.withdraw.PayDemoWithdrawCreateReqVO;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoWithdrawDO;
|
||||
import cn.iocoder.yudao.module.pay.dal.mysql.demo.PayDemoWithdrawMapper;
|
||||
import cn.iocoder.yudao.module.pay.enums.PayChannelEnum;
|
||||
import cn.iocoder.yudao.module.pay.enums.demo.PayDemoWithdrawStatusEnum;
|
||||
import cn.iocoder.yudao.module.pay.enums.demo.PayDemoWithdrawTypeEnum;
|
||||
import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferStatusEnum;
|
||||
import cn.iocoder.yudao.module.pay.service.transfer.PayTransferService;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.Validator;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
|
||||
import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*;
|
||||
import static cn.iocoder.yudao.module.pay.enums.transfer.PayTransferStatusEnum.WAITING;
|
||||
|
||||
/**
|
||||
* 示例转账业务 Service 实现类
|
||||
@@ -29,71 +34,162 @@ import static cn.iocoder.yudao.module.pay.enums.transfer.PayTransferStatusEnum.W
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
public class PayDemoTransferServiceImpl implements PayDemoTransferService {
|
||||
@Slf4j
|
||||
public class PayDemoTransferServiceImpl implements PayDemoWithdrawService {
|
||||
|
||||
/**
|
||||
* 接入的实力应用编号
|
||||
|
||||
* 接入的支付应用标识
|
||||
*
|
||||
* 从 [支付管理 -> 应用信息] 里添加
|
||||
*/
|
||||
private static final Long TRANSFER_APP_ID = 8L;
|
||||
private static final String PAY_APP_KEY = "demo";
|
||||
|
||||
@Resource
|
||||
private PayDemoTransferMapper demoTransferMapper;
|
||||
private PayDemoWithdrawMapper demoTransferMapper;
|
||||
|
||||
@Resource
|
||||
private PayTransferService payTransferService;
|
||||
@Resource
|
||||
private Validator validator;
|
||||
private PayTransferApi payTransferApi;
|
||||
|
||||
@Override
|
||||
public Long createDemoTransfer(@Valid PayDemoTransferCreateReqVO vo) {
|
||||
// 1 校验参数
|
||||
vo.validate(validator);
|
||||
// 2 保存示例转账业务表
|
||||
PayDemoTransferDO demoTransfer = PayDemoTransferConvert.INSTANCE.convert(vo)
|
||||
.setAppId(TRANSFER_APP_ID).setTransferStatus(WAITING.getStatus());
|
||||
demoTransferMapper.insert(demoTransfer);
|
||||
return demoTransfer.getId();
|
||||
public Long createDemoWithdraw(@Valid PayDemoWithdrawCreateReqVO reqVO) {
|
||||
PayDemoWithdrawDO withdraw = BeanUtils.toBean(reqVO, PayDemoWithdrawDO.class)
|
||||
.setTransferChannelCode(getTransferChannelCode(reqVO.getType()))
|
||||
.setStatus(PayDemoWithdrawStatusEnum.WAITING.getStatus());
|
||||
demoTransferMapper.insert(withdraw);
|
||||
return withdraw.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<PayDemoTransferDO> getDemoTransferPage(PageParam pageVO) {
|
||||
public Long transferDemoWithdraw(Long id) {
|
||||
// 1.1 校验提现单
|
||||
PayDemoWithdrawDO withdraw = validateDemoWithdrawCanTransfer(id);
|
||||
// 1.2 特殊:如果是转账失败的情况,需要充值下
|
||||
if (PayDemoWithdrawStatusEnum.isClosed(withdraw.getStatus())) {
|
||||
int updateCount = demoTransferMapper.updateByIdAndStatus(withdraw.getId(), withdraw.getStatus(),
|
||||
new PayDemoWithdrawDO().setStatus(PayDemoWithdrawStatusEnum.WAITING.getStatus()).setTransferErrorMsg(""));
|
||||
if (updateCount == 0) {
|
||||
throw exception(DEMO_WITHDRAW_TRANSFER_FAIL_STATUS_NOT_WAITING_OR_CLOSED);
|
||||
}
|
||||
withdraw.setStatus(PayDemoWithdrawStatusEnum.WAITING.getStatus());
|
||||
}
|
||||
|
||||
// 2.1 创建支付单
|
||||
PayTransferCreateReqDTO transferReqDTO = new PayTransferCreateReqDTO()
|
||||
.setAppKey(PAY_APP_KEY).setChannelCode(withdraw.getTransferChannelCode()).setUserIp(getClientIP()) // 支付应用
|
||||
.setMerchantTransferId(String.valueOf(withdraw.getId())) // 业务的订单编号
|
||||
.setSubject(withdraw.getSubject()).setPrice(withdraw.getPrice()) // 价格信息
|
||||
.setUserAccount(withdraw.getUserAccount()).setUserName(withdraw.getUserName()); // 收款信息
|
||||
if (ObjectUtil.equal(withdraw.getType(), PayDemoWithdrawTypeEnum.WECHAT.getType())) {
|
||||
// 注意:微信很特殊!提现需要写明用途!!!
|
||||
transferReqDTO.setChannelExtras(PayTransferCreateReqDTO.buildWeiXinChannelExtra1000(
|
||||
"测试活动", "测试奖励"));
|
||||
}
|
||||
PayTransferCreateRespDTO transferRespDTO = payTransferApi.createTransfer(transferReqDTO).getCheckedData();
|
||||
|
||||
// 2.2 更新转账单到 demo 示例提现单,并将状态更新为转账中
|
||||
demoTransferMapper.updateByIdAndStatus(withdraw.getId(), withdraw.getStatus(),
|
||||
new PayDemoWithdrawDO().setPayTransferId(transferRespDTO.getId()));
|
||||
return transferRespDTO.getId();
|
||||
}
|
||||
|
||||
private PayDemoWithdrawDO validateDemoWithdrawCanTransfer(Long id) {
|
||||
// 校验存在
|
||||
PayDemoWithdrawDO withdraw = demoTransferMapper.selectById(id);
|
||||
if (withdraw == null) {
|
||||
throw exception(DEMO_WITHDRAW_NOT_FOUND);
|
||||
}
|
||||
// 校验状态,只有等待中或转账失败的订单,才能发起转账
|
||||
if (!PayDemoWithdrawStatusEnum.isWaiting(withdraw.getStatus())
|
||||
&& !PayDemoWithdrawStatusEnum.isClosed(withdraw.getStatus())) {
|
||||
throw exception(DEMO_WITHDRAW_TRANSFER_FAIL_STATUS_NOT_WAITING_OR_CLOSED);
|
||||
}
|
||||
return withdraw;
|
||||
}
|
||||
|
||||
private String getTransferChannelCode(Integer type) {
|
||||
if (ObjectUtil.equal(type, PayDemoWithdrawTypeEnum.ALIPAY.getType())) {
|
||||
return PayChannelEnum.ALIPAY_PC.getCode();
|
||||
}
|
||||
if (ObjectUtil.equal(type, PayDemoWithdrawTypeEnum.WECHAT.getType())) {
|
||||
return PayChannelEnum.WX_LITE.getCode();
|
||||
}
|
||||
if (ObjectUtil.equal(type, PayDemoWithdrawTypeEnum.WALLET.getType())) {
|
||||
return PayChannelEnum.WALLET.getCode();
|
||||
}
|
||||
throw new IllegalArgumentException("未知提现方式:" + type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<PayDemoWithdrawDO> getDemoWithdrawPage(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()));
|
||||
public void updateDemoWithdrawTransferred(Long id, Long payTransferId) {
|
||||
// 1.1 校验转账单是否存在
|
||||
PayDemoWithdrawDO withdraw = demoTransferMapper.selectById(id);
|
||||
if (withdraw == null) {
|
||||
log.error("[updateDemoWithdrawStatus][withdraw({}) payOrder({}) 不存在提现单,请进行处理!]", id, payTransferId);
|
||||
throw exception(DEMO_WITHDRAW_NOT_FOUND);
|
||||
}
|
||||
// 1.2 校验转账单已成结束(成功或失败)
|
||||
if (PayDemoWithdrawStatusEnum.isSuccess(withdraw.getStatus())
|
||||
|| PayDemoWithdrawStatusEnum.isClosed(withdraw.getStatus())) {
|
||||
// 特殊:转账单编号相同,直接返回,说明重复回调
|
||||
if (ObjectUtil.equal(withdraw.getPayTransferId(), payTransferId)) {
|
||||
log.warn("[updateDemoWithdrawStatus][withdraw({}) 已结束,且转账单编号相同({}),直接返回]", withdraw, payTransferId);
|
||||
return;
|
||||
}
|
||||
// 异常:转账单编号不同,说明转账单编号错误
|
||||
log.error("[updateDemoWithdrawStatus][withdraw({}) 转账单不匹配({}),请进行处理!]", withdraw, payTransferId);
|
||||
throw exception(DEMO_WITHDRAW_UPDATE_STATUS_FAIL_PAY_TRANSFER_ID_ERROR);
|
||||
}
|
||||
|
||||
// 2. 校验提现单的合法性
|
||||
PayTransferRespDTO payTransfer = validateDemoTransferStatusCanUpdate(withdraw, payTransferId);
|
||||
|
||||
// 3. 更新示例订单状态
|
||||
Integer newStatus = PayTransferStatusEnum.isSuccess(payTransfer.getStatus()) ? PayDemoWithdrawStatusEnum.SUCCESS.getStatus() :
|
||||
PayTransferStatusEnum.isClosed(payTransfer.getStatus()) ? PayDemoWithdrawStatusEnum.CLOSED.getStatus() : null;
|
||||
Assert.notNull(newStatus, "转账单状态({}) 不合法", payTransfer.getStatus());
|
||||
demoTransferMapper.updateByIdAndStatus(withdraw.getId(), withdraw.getStatus(),
|
||||
new PayDemoWithdrawDO().setStatus(newStatus).setTransferTime(payTransfer.getSuccessTime())
|
||||
.setTransferErrorMsg(payTransfer.getChannelErrorMsg()));
|
||||
}
|
||||
|
||||
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) {
|
||||
private PayTransferRespDTO validateDemoTransferStatusCanUpdate(PayDemoWithdrawDO withdraw, Long payTransferId) {
|
||||
// 1. 校验转账单是否存在
|
||||
PayTransferRespDTO payTransfer = payTransferApi.getTransfer(payTransferId).getCheckedData();
|
||||
if (payTransfer == null) {
|
||||
log.error("[validateDemoTransferStatusCanUpdate][withdraw({}) payTransfer({}) 不存在,请进行处理!]", withdraw.getId(), payTransferId);
|
||||
throw exception(PAY_TRANSFER_NOT_FOUND);
|
||||
}
|
||||
if (!Objects.equals(demoTransfer.getPrice(), transfer.getPrice())) {
|
||||
throw exception(DEMO_TRANSFER_FAIL_PRICE_NOT_MATCH);
|
||||
|
||||
// 2.1 校验转账单已成功
|
||||
if (!PayTransferStatusEnum.isSuccessOrClosed(payTransfer.getStatus())) {
|
||||
log.error("[validateDemoTransferStatusCanUpdate][withdraw({}) payTransfer({}) 未结束,请进行处理!payTransfer 数据是:{}]",
|
||||
withdraw.getId(), payTransferId, JsonUtils.toJsonString(payTransfer));
|
||||
throw exception(DEMO_WITHDRAW_UPDATE_STATUS_FAIL_PAY_TRANSFER_STATUS_NOT_SUCCESS_OR_CLOSED);
|
||||
}
|
||||
if (ObjectUtil.notEqual(transfer.getMerchantTransferId(), id.toString())) {
|
||||
throw exception(DEMO_TRANSFER_FAIL_TRANSFER_ID_ERROR);
|
||||
// 2.2 校验转账金额一致
|
||||
if (ObjectUtil.notEqual(payTransfer.getPrice(), withdraw.getPrice())) {
|
||||
log.error("[validateDemoTransferStatusCanUpdate][withdraw({}) payTransfer({}) 转账金额不匹配,请进行处理!withdraw 数据是:{},payTransfer 数据是:{}]",
|
||||
withdraw.getId(), payTransferId, JsonUtils.toJsonString(withdraw), JsonUtils.toJsonString(payTransfer));
|
||||
throw exception(DEMO_WITHDRAW_UPDATE_STATUS_FAIL_PAY_PRICE_NOT_MATCH);
|
||||
}
|
||||
// TODO 校验账号
|
||||
return transfer;
|
||||
// 2.3 校验转账订单匹配(二次)
|
||||
if (ObjectUtil.notEqual(payTransfer.getMerchantTransferId(), withdraw.getId().toString())) {
|
||||
log.error("[validateDemoTransferStatusCanUpdate][withdraw({}) 转账单不匹配({}),请进行处理!payTransfer 数据是:{}]",
|
||||
withdraw.getId(), payTransferId, JsonUtils.toJsonString(payTransfer));
|
||||
throw exception(DEMO_WITHDRAW_UPDATE_STATUS_FAIL_PAY_MERCHANT_EXISTS);
|
||||
}
|
||||
// 2.4 校验转账渠道一致
|
||||
if (ObjectUtil.notEqual(payTransfer.getChannelCode(), withdraw.getTransferChannelCode())) {
|
||||
log.error("[validateDemoTransferStatusCanUpdate][withdraw({}) payTransfer({}) 转账渠道不匹配,请进行处理!withdraw 数据是:{},payTransfer 数据是:{}]",
|
||||
withdraw.getId(), payTransferId, JsonUtils.toJsonString(withdraw), JsonUtils.toJsonString(payTransfer));
|
||||
throw exception(DEMO_WITHDRAW_UPDATE_STATUS_FAIL_PAY_CHANNEL_NOT_MATCH);
|
||||
}
|
||||
return payTransfer;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
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.withdraw.PayDemoWithdrawCreateReqVO;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoWithdrawDO;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
/**
|
||||
* 示例提现单 Service 接口
|
||||
*
|
||||
* @author jason
|
||||
*/
|
||||
public interface PayDemoWithdrawService {
|
||||
|
||||
/**
|
||||
* 创建示例提现单
|
||||
*
|
||||
* @param createReqVO 创建信息
|
||||
* @return 编号
|
||||
*/
|
||||
Long createDemoWithdraw(@Valid PayDemoWithdrawCreateReqVO createReqVO);
|
||||
|
||||
/**
|
||||
* 提现单转账
|
||||
*
|
||||
* @param id 提现单编号
|
||||
* @return 转账编号
|
||||
*/
|
||||
Long transferDemoWithdraw(Long id);
|
||||
|
||||
/**
|
||||
* 获得示例提现单分页
|
||||
*
|
||||
* @param pageVO 分页查询参数
|
||||
*/
|
||||
PageResult<PayDemoWithdrawDO> getDemoWithdrawPage(PageParam pageVO);
|
||||
|
||||
/**
|
||||
* 更新示例提现单的状态
|
||||
*
|
||||
* @param id 编号
|
||||
* @param payTransferId 转账单编号
|
||||
*/
|
||||
void updateDemoWithdrawTransferred(Long id, Long payTransferId);
|
||||
|
||||
}
|
||||
@@ -29,16 +29,17 @@ 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 jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.support.TransactionSynchronization;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
@@ -97,19 +98,19 @@ public class PayNotifyServiceImpl implements PayNotifyService {
|
||||
PayNotifyTaskDO task = new PayNotifyTaskDO().setType(type).setDataId(dataId);
|
||||
task.setStatus(PayNotifyStatusEnum.WAITING.getStatus()).setNextNotifyTime(LocalDateTime.now())
|
||||
.setNotifyTimes(0).setMaxNotifyTimes(PayNotifyTaskDO.NOTIFY_FREQUENCY.length + 1);
|
||||
// 补充 appId + notifyUrl 字段
|
||||
// 补充 appId + notifyUrl + merchant* 字段
|
||||
if (Objects.equals(task.getType(), PayNotifyTypeEnum.ORDER.getType())) {
|
||||
PayOrderDO order = orderService.getOrder(task.getDataId()); // 不进行非空判断,有问题直接异常
|
||||
task.setAppId(order.getAppId()).
|
||||
setMerchantOrderId(order.getMerchantOrderId()).setNotifyUrl(order.getNotifyUrl());
|
||||
task.setAppId(order.getAppId()).setNotifyUrl(order.getNotifyUrl())
|
||||
.setMerchantOrderId(order.getMerchantOrderId());
|
||||
} else if (Objects.equals(task.getType(), PayNotifyTypeEnum.REFUND.getType())) {
|
||||
PayRefundDO refundDO = refundService.getRefund(task.getDataId());
|
||||
task.setAppId(refundDO.getAppId())
|
||||
.setMerchantOrderId(refundDO.getMerchantOrderId()).setNotifyUrl(refundDO.getNotifyUrl());
|
||||
PayRefundDO refund = refundService.getRefund(task.getDataId());
|
||||
task.setAppId(refund.getAppId()).setNotifyUrl(refund.getNotifyUrl())
|
||||
.setMerchantOrderId(refund.getMerchantOrderId()).setMerchantRefundId(refund.getMerchantRefundId());
|
||||
} 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());
|
||||
task.setAppId(transfer.getAppId()).setNotifyUrl(transfer.getNotifyUrl())
|
||||
.setMerchantTransferId(transfer.getMerchantTransferId());
|
||||
}
|
||||
|
||||
// 执行插入
|
||||
@@ -117,10 +118,13 @@ public class PayNotifyServiceImpl implements PayNotifyService {
|
||||
|
||||
// 必须在事务提交后,在发起任务,否则 PayNotifyTaskDO 还没入库,就提前回调接入的业务
|
||||
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
|
||||
|
||||
@Override
|
||||
public void afterCommit() {
|
||||
executeNotify(task);
|
||||
// 异步的原因:避免阻塞当前事务,无需等待结果
|
||||
getSelf().executeNotifyAsync(task);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@@ -166,7 +170,17 @@ public class PayNotifyServiceImpl implements PayNotifyService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步执行单个支付通知
|
||||
* 异步执行单个支付通知
|
||||
*
|
||||
* @param task 通知任务
|
||||
*/
|
||||
@Async
|
||||
public void executeNotifyAsync(PayNotifyTaskDO task) {
|
||||
executeNotify(task);
|
||||
}
|
||||
|
||||
/**
|
||||
* 【加锁】执行单个支付通知
|
||||
*
|
||||
* @param task 通知任务
|
||||
*/
|
||||
@@ -223,10 +237,11 @@ public class PayNotifyServiceImpl implements PayNotifyService {
|
||||
.payOrderId(task.getDataId()).build();
|
||||
} else if (Objects.equals(task.getType(), PayNotifyTypeEnum.REFUND.getType())) {
|
||||
request = PayRefundNotifyReqDTO.builder().merchantOrderId(task.getMerchantOrderId())
|
||||
.merchantRefundId(task.getMerchantRefundId())
|
||||
.payRefundId(task.getDataId()).build();
|
||||
} else if (Objects.equals(task.getType(), PayNotifyTypeEnum.TRANSFER.getType())) {
|
||||
request = new PayTransferNotifyReqDTO().setMerchantTransferId(task.getMerchantTransferId())
|
||||
.setPayTransferId(task.getDataId());
|
||||
request = PayTransferNotifyReqDTO.builder().merchantTransferId(task.getMerchantTransferId())
|
||||
.payTransferId(task.getDataId()).build();
|
||||
} else {
|
||||
throw new RuntimeException("未知的通知任务类型:" + JsonUtils.toJsonString(task));
|
||||
}
|
||||
|
||||
@@ -32,12 +32,12 @@ 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 com.google.common.annotations.VisibleForTesting;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
@@ -140,7 +140,7 @@ public class PayOrderServiceImpl implements PayOrderService {
|
||||
PayOrderDO order = validateOrderCanSubmit(reqVO.getId());
|
||||
// 1.32 校验支付渠道是否有效
|
||||
PayChannelDO channel = validateChannelCanSubmit(order.getAppId(), reqVO.getChannelCode());
|
||||
PayClient client = channelService.getPayClient(channel.getId());
|
||||
PayClient<?> client = channelService.getPayClient(channel.getId());
|
||||
|
||||
// 2. 插入 PayOrderExtensionDO
|
||||
String no = noRedisDAO.generate(payProperties.getOrderNoPrefix());
|
||||
@@ -237,7 +237,7 @@ public class PayOrderServiceImpl implements PayOrderService {
|
||||
appService.validPayApp(appId);
|
||||
// 校验支付渠道是否有效
|
||||
PayChannelDO channel = channelService.validPayChannel(appId, channelCode);
|
||||
PayClient client = channelService.getPayClient(channel.getId());
|
||||
PayClient<?> client = channelService.getPayClient(channel.getId());
|
||||
if (client == null) {
|
||||
log.error("[validatePayChannelCanSubmit][渠道编号({}) 找不到对应的支付客户端]", channel.getId());
|
||||
throw exception(CHANNEL_NOT_FOUND);
|
||||
|
||||
@@ -62,7 +62,7 @@ public interface PayRefundService {
|
||||
* @param reqDTO 退款申请信息
|
||||
* @return 退款单号
|
||||
*/
|
||||
Long createPayRefund(PayRefundCreateReqDTO reqDTO);
|
||||
Long createRefund(PayRefundCreateReqDTO reqDTO);
|
||||
|
||||
/**
|
||||
* 渠道的退款通知
|
||||
|
||||
@@ -91,14 +91,14 @@ public class PayRefundServiceImpl implements PayRefundService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long createPayRefund(PayRefundCreateReqDTO reqDTO) {
|
||||
public Long createRefund(PayRefundCreateReqDTO reqDTO) {
|
||||
// 1.1 校验 App
|
||||
PayAppDO app = appService.validPayApp(reqDTO.getAppKey());
|
||||
// 1.2 校验支付订单
|
||||
PayOrderDO order = validatePayOrderCanRefund(reqDTO, app.getId());
|
||||
// 1.3 校验支付渠道是否有效
|
||||
PayChannelDO channel = channelService.validPayChannel(order.getChannelId());
|
||||
PayClient client = channelService.getPayClient(channel.getId());
|
||||
PayClient<?> client = channelService.getPayClient(channel.getId());
|
||||
if (client == null) {
|
||||
log.error("[refund][渠道编号({}) 找不到对应的支付客户端]", channel.getId());
|
||||
throw exception(CHANNEL_NOT_FOUND);
|
||||
|
||||
@@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.pay.service.transfer;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO;
|
||||
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.api.transfer.dto.PayTransferCreateRespDTO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferPageReqVO;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO;
|
||||
import jakarta.validation.Valid;
|
||||
@@ -15,24 +15,13 @@ import jakarta.validation.Valid;
|
||||
*/
|
||||
public interface PayTransferService {
|
||||
|
||||
/**
|
||||
* 创建转账单,并发起转账
|
||||
*
|
||||
* 此时,会发起转账渠道的调用
|
||||
*
|
||||
* @param reqVO 请求
|
||||
* @param userIp 用户 ip
|
||||
* @return 渠道的返回结果
|
||||
*/
|
||||
PayTransferDO createTransfer(@Valid PayTransferCreateReqVO reqVO, String userIp);
|
||||
|
||||
/**
|
||||
* 创建转账单,并发起转账
|
||||
*
|
||||
* @param reqDTO 创建请求
|
||||
* @return 转账单编号
|
||||
*/
|
||||
Long createTransfer(@Valid PayTransferCreateReqDTO reqDTO);
|
||||
PayTransferCreateRespDTO createTransfer(@Valid PayTransferCreateReqDTO reqDTO);
|
||||
|
||||
/**
|
||||
* 获取转账单
|
||||
@@ -40,6 +29,14 @@ public interface PayTransferService {
|
||||
*/
|
||||
PayTransferDO getTransfer(Long id);
|
||||
|
||||
/**
|
||||
* 根据转账单号获取转账单
|
||||
*
|
||||
* @param no 转账单号
|
||||
* @return 转账单
|
||||
*/
|
||||
PayTransferDO getTransferByNo(String no);
|
||||
|
||||
/**
|
||||
* 获得转账单分页
|
||||
*
|
||||
@@ -55,6 +52,13 @@ public interface PayTransferService {
|
||||
*/
|
||||
int syncTransfer();
|
||||
|
||||
/**
|
||||
* 【单个】同步渠道转账单状态
|
||||
*
|
||||
* @param id 转账单编号
|
||||
*/
|
||||
void syncTransfer(Long id);
|
||||
|
||||
/**
|
||||
* 渠道的转账通知
|
||||
*
|
||||
|
||||
@@ -5,14 +5,14 @@ import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferStatusRespEnum;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum;
|
||||
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
|
||||
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.api.transfer.dto.PayTransferCreateRespDTO;
|
||||
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;
|
||||
@@ -26,7 +26,6 @@ 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 jakarta.annotation.Resource;
|
||||
import jakarta.validation.Validator;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@@ -34,9 +33,7 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.module.pay.convert.transfer.PayTransferConvert.INSTANCE;
|
||||
import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*;
|
||||
import static cn.iocoder.yudao.module.pay.enums.transfer.PayTransferStatusEnum.*;
|
||||
|
||||
// TODO @jason:等彻底实现完,单测写写;
|
||||
|
||||
@@ -64,62 +61,51 @@ public class PayTransferServiceImpl implements PayTransferService {
|
||||
private PayNotifyService notifyService;
|
||||
@Resource
|
||||
private PayNoRedisDAO noRedisDAO;
|
||||
@Resource
|
||||
private Validator validator;
|
||||
|
||||
@Override
|
||||
public PayTransferDO createTransfer(PayTransferCreateReqVO reqVO, String userIp) {
|
||||
// 1. 校验参数
|
||||
reqVO.validate(validator);
|
||||
|
||||
// 2. 创建转账单,发起转账
|
||||
PayTransferCreateReqDTO req = INSTANCE.convert(reqVO).setUserIp(userIp);
|
||||
Long transferId = createTransfer(req);
|
||||
|
||||
// 3. 返回转账单
|
||||
return getTransfer(transferId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long createTransfer(PayTransferCreateReqDTO reqDTO) {
|
||||
public PayTransferCreateRespDTO createTransfer(PayTransferCreateReqDTO reqDTO) {
|
||||
// 1.1 校验 App
|
||||
PayAppDO payApp = appService.validPayApp(reqDTO.getAppKey());
|
||||
// 1.2 校验支付渠道是否有效
|
||||
PayChannelDO channel = channelService.validPayChannel(payApp.getId(), reqDTO.getChannelCode());
|
||||
PayClient client = channelService.getPayClient(channel.getId());
|
||||
PayClient<?> client = channelService.getPayClient(channel.getId());
|
||||
if (client == null) {
|
||||
log.error("[createTransfer][渠道编号({}) 找不到对应的支付客户端]", channel.getId());
|
||||
throw exception(CHANNEL_NOT_FOUND);
|
||||
}
|
||||
// 1.3 校验转账单已经发起过转账。
|
||||
// 1.3 校验转账单已经发起过转账
|
||||
PayTransferDO transfer = validateTransferCanCreate(reqDTO, payApp.getId());
|
||||
|
||||
// 2.1 情况一:不存在创建转账单,则进行创建
|
||||
if (transfer == null) {
|
||||
// 2.不存在创建转账单. 否则允许使用相同的 no 再次发起转账
|
||||
String no = noRedisDAO.generate(TRANSFER_NO_PREFIX);
|
||||
transfer = INSTANCE.convert(reqDTO)
|
||||
.setChannelId(channel.getId())
|
||||
.setNo(no).setStatus(WAITING.getStatus())
|
||||
.setNotifyUrl(payApp.getTransferNotifyUrl())
|
||||
.setAppId(channel.getAppId());
|
||||
transfer = BeanUtils.toBean(reqDTO, PayTransferDO.class)
|
||||
.setAppId(channel.getAppId()).setChannelId(channel.getId())
|
||||
.setNo(no).setStatus(PayTransferStatusEnum.WAITING.getStatus())
|
||||
.setNotifyUrl(payApp.getTransferNotifyUrl());
|
||||
transferMapper.insert(transfer);
|
||||
} else {
|
||||
// 2.2 情况二:存在创建转账单,但是状态为关闭,则更新为等待中
|
||||
transferMapper.updateByIdAndStatus(transfer.getId(), transfer.getStatus(),
|
||||
new PayTransferDO().setStatus(PayTransferStatusEnum.WAITING.getStatus()));
|
||||
}
|
||||
PayTransferRespDTO unifiedTransferResp = null;
|
||||
try {
|
||||
// 3. 调用三方渠道发起转账
|
||||
PayTransferUnifiedReqDTO transferUnifiedReq = INSTANCE.convert2(transfer)
|
||||
.setOutTransferNo(transfer.getNo());
|
||||
transferUnifiedReq.setNotifyUrl(genChannelTransferNotifyUrl(channel));
|
||||
PayTransferRespDTO unifiedTransferResp = client.unifiedTransfer(transferUnifiedReq);
|
||||
PayTransferUnifiedReqDTO transferUnifiedReq = BeanUtils.toBean(reqDTO, PayTransferUnifiedReqDTO.class)
|
||||
.setOutTransferNo(transfer.getNo())
|
||||
.setNotifyUrl(genChannelTransferNotifyUrl(channel));
|
||||
unifiedTransferResp = client.unifiedTransfer(transferUnifiedReq);
|
||||
// 4. 通知转账结果
|
||||
getSelf().notifyTransfer(channel, unifiedTransferResp);
|
||||
} catch (Throwable e) {
|
||||
// 注意这里仅打印异常,不进行抛出。
|
||||
// 原因是:虽然调用支付渠道进行转账发生异常(网络请求超时),实际转账成功。这个结果,后续转账轮询可以拿到。
|
||||
// 或者使用相同 no 再次发起转账请求
|
||||
log.error("[createTransfer][转账 id({}) requestDTO({}) 发生异常]", transfer.getId(), reqDTO, e);
|
||||
// 或者,使用相同 no 再次发起转账请求
|
||||
log.error("[createTransfer][转账编号({}) requestDTO({}) 发生异常]", transfer.getId(), reqDTO, e);
|
||||
}
|
||||
|
||||
return transfer.getId();
|
||||
return new PayTransferCreateRespDTO().setId(transfer.getId())
|
||||
.setChannelPackageInfo(unifiedTransferResp != null ? unifiedTransferResp.getChannelPackageInfo() : null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -132,21 +118,23 @@ public class PayTransferServiceImpl implements PayTransferService {
|
||||
return payProperties.getTransferNotifyUrl() + "/" + channel.getId();
|
||||
}
|
||||
|
||||
private PayTransferDO validateTransferCanCreate(PayTransferCreateReqDTO dto, Long appId) {
|
||||
PayTransferDO transfer = transferMapper.selectByAppIdAndMerchantTransferId(appId, dto.getMerchantTransferId());
|
||||
private PayTransferDO validateTransferCanCreate(PayTransferCreateReqDTO reqDTO, Long appId) {
|
||||
PayTransferDO transfer = transferMapper.selectByAppIdAndMerchantOrderId(appId, reqDTO.getMerchantTransferId());
|
||||
if (transfer != null) {
|
||||
// 已经存在,并且状态不为等待状态。说明已经调用渠道转账并返回结果.
|
||||
if (!PayTransferStatusEnum.isWaiting(transfer.getStatus())) {
|
||||
throw exception(PAY_MERCHANT_TRANSFER_EXISTS);
|
||||
// 只有转账单状态为关闭,才能再次发起转账
|
||||
if (!PayTransferStatusEnum.isClosed(transfer.getStatus())) {
|
||||
throw exception(PAY_TRANSFER_CREATE_FAIL_STATUS_NOT_CLOSED);
|
||||
}
|
||||
if (ObjectUtil.notEqual(dto.getPrice(), transfer.getPrice())) {
|
||||
throw exception(PAY_SAME_MERCHANT_TRANSFER_PRICE_NOT_MATCH);
|
||||
// 校验参数是否一致
|
||||
if (ObjectUtil.notEqual(reqDTO.getPrice(), transfer.getPrice())) {
|
||||
throw exception(PAY_TRANSFER_CREATE_PRICE_NOT_MATCH);
|
||||
}
|
||||
if (ObjectUtil.notEqual(dto.getType(), transfer.getType())) {
|
||||
throw exception(PAY_SAME_MERCHANT_TRANSFER_TYPE_NOT_MATCH);
|
||||
if (ObjectUtil.notEqual(reqDTO.getChannelCode(), transfer.getChannelCode())) {
|
||||
throw exception(PAY_TRANSFER_CREATE_CHANNEL_NOT_MATCH);
|
||||
}
|
||||
}
|
||||
// 如果状态为等待状态。不知道渠道转账是否发起成功。 允许使用相同的 no 再次发起转账,渠道会保证幂等
|
||||
// 如果状态为等待状态:不知道渠道转账是否发起成功
|
||||
// 特殊:允许使用相同的 no 再次发起转账,渠道会保证幂等
|
||||
return transfer;
|
||||
}
|
||||
|
||||
@@ -162,94 +150,95 @@ public class PayTransferServiceImpl implements PayTransferService {
|
||||
notifyTransferClosed(channel, notify);
|
||||
}
|
||||
// 转账处理中的回调
|
||||
if (PayTransferStatusRespEnum.isInProgress(notify.getStatus())) {
|
||||
notifyTransferInProgress(channel, notify);
|
||||
if (PayTransferStatusRespEnum.isProcessing(notify.getStatus())) {
|
||||
notifyTransferProgressing(channel, notify);
|
||||
}
|
||||
// WAITING 状态无需处理
|
||||
}
|
||||
|
||||
private void notifyTransferInProgress(PayChannelDO channel, PayTransferRespDTO notify) {
|
||||
// 1.校验
|
||||
private void notifyTransferProgressing(PayChannelDO channel, PayTransferRespDTO notify) {
|
||||
// 1. 校验
|
||||
PayTransferDO transfer = transferMapper.selectByAppIdAndNo(channel.getAppId(), notify.getOutTransferNo());
|
||||
if (transfer == null) {
|
||||
throw exception(PAY_TRANSFER_NOT_FOUND);
|
||||
}
|
||||
if (isInProgress(transfer.getStatus())) { // 如果已经是转账中,直接返回,不用重复更新
|
||||
if (PayTransferStatusEnum.isProgressing(transfer.getStatus())) { // 如果已经是转账中,直接返回,不用重复更新
|
||||
log.info("[notifyTransferProgressing][transfer({}) 已经是转账中状态,无需更新]", transfer.getId());
|
||||
return;
|
||||
}
|
||||
if (!isWaiting(transfer.getStatus())) {
|
||||
throw exception(PAY_TRANSFER_STATUS_IS_NOT_WAITING);
|
||||
if (!PayTransferStatusEnum.isWaiting(transfer.getStatus())) {
|
||||
throw exception(PAY_TRANSFER_NOTIFY_FAIL_STATUS_IS_NOT_WAITING);
|
||||
}
|
||||
// 2.更新
|
||||
|
||||
// 2. 更新状态
|
||||
int updateCounts = transferMapper.updateByIdAndStatus(transfer.getId(),
|
||||
CollUtil.newArrayList(WAITING.getStatus()),
|
||||
new PayTransferDO().setStatus(IN_PROGRESS.getStatus()));
|
||||
PayTransferStatusEnum.WAITING.getStatus(),
|
||||
new PayTransferDO().setStatus(PayTransferStatusEnum.PROCESSING.getStatus())
|
||||
.setChannelPackageInfo(notify.getChannelPackageInfo()));
|
||||
if (updateCounts == 0) {
|
||||
throw exception(PAY_TRANSFER_STATUS_IS_NOT_WAITING);
|
||||
throw exception(PAY_TRANSFER_NOTIFY_FAIL_STATUS_IS_NOT_WAITING);
|
||||
}
|
||||
log.info("[notifyTransferInProgress][transfer({}) 更新为转账进行中状态]", transfer.getId());
|
||||
log.info("[notifyTransferProgressing][transfer({}) 更新为转账进行中状态]", transfer.getId());
|
||||
}
|
||||
|
||||
|
||||
private void notifyTransferSuccess(PayChannelDO channel, PayTransferRespDTO notify) {
|
||||
// 1.校验
|
||||
// 1. 校验状态
|
||||
PayTransferDO transfer = transferMapper.selectByAppIdAndNo(channel.getAppId(), notify.getOutTransferNo());
|
||||
if (transfer == null) {
|
||||
throw exception(PAY_TRANSFER_NOT_FOUND);
|
||||
}
|
||||
if (isSuccess(transfer.getStatus())) { // 如果已成功,直接返回,不用重复更新
|
||||
if (PayTransferStatusEnum.isSuccess(transfer.getStatus())) { // 如果已成功,直接返回,不用重复更新
|
||||
log.info("[notifyTransferSuccess][transfer({}) 已经是成功状态,无需更新]", transfer.getId());
|
||||
return;
|
||||
}
|
||||
if (!isPendingStatus(transfer.getStatus())) {
|
||||
throw exception(PAY_TRANSFER_STATUS_IS_NOT_PENDING);
|
||||
if (!PayTransferStatusEnum.isWaitingOrProcessing(transfer.getStatus())) {
|
||||
throw exception(PAY_TRANSFER_NOTIFY_FAIL_STATUS_NOT_WAITING_OR_PROCESSING);
|
||||
}
|
||||
// 2.更新
|
||||
|
||||
// 2. 更新状态
|
||||
int updateCounts = transferMapper.updateByIdAndStatus(transfer.getId(),
|
||||
CollUtil.newArrayList(WAITING.getStatus(), IN_PROGRESS.getStatus()),
|
||||
new PayTransferDO().setStatus(SUCCESS.getStatus()).setSuccessTime(notify.getSuccessTime())
|
||||
CollUtil.newArrayList(PayTransferStatusEnum.WAITING.getStatus(), PayTransferStatusEnum.PROCESSING.getStatus()),
|
||||
new PayTransferDO().setStatus(PayTransferStatusEnum.SUCCESS.getStatus())
|
||||
.setSuccessTime(notify.getSuccessTime())
|
||||
.setChannelTransferNo(notify.getChannelTransferNo())
|
||||
.setChannelId(channel.getId()).setChannelCode(channel.getCode())
|
||||
.setChannelNotifyData(JsonUtils.toJsonString(notify)));
|
||||
if (updateCounts == 0) {
|
||||
throw exception(PAY_TRANSFER_STATUS_IS_NOT_PENDING);
|
||||
throw exception(PAY_TRANSFER_NOTIFY_FAIL_STATUS_NOT_WAITING_OR_PROCESSING);
|
||||
}
|
||||
log.info("[updateTransferSuccess][transfer({}) 更新为已转账]", transfer.getId());
|
||||
log.info("[notifyTransferSuccess][transfer({}) 更新为已转账]", transfer.getId());
|
||||
|
||||
// 3. 插入转账通知记录
|
||||
notifyService.createPayNotifyTask(PayNotifyTypeEnum.TRANSFER.getType(),
|
||||
transfer.getId());
|
||||
notifyService.createPayNotifyTask(PayNotifyTypeEnum.TRANSFER.getType(), transfer.getId());
|
||||
}
|
||||
|
||||
private void notifyTransferClosed(PayChannelDO channel, PayTransferRespDTO notify) {
|
||||
// 1.校验
|
||||
// 1. 校验状态
|
||||
PayTransferDO transfer = transferMapper.selectByAppIdAndNo(channel.getAppId(), notify.getOutTransferNo());
|
||||
if (transfer == null) {
|
||||
throw exception(PAY_TRANSFER_NOT_FOUND);
|
||||
}
|
||||
if (isClosed(transfer.getStatus())) { // 如果已是关闭状态,直接返回,不用重复更新
|
||||
log.info("[updateTransferClosed][transfer({}) 已经是关闭状态,无需更新]", transfer.getId());
|
||||
if (PayTransferStatusEnum.isClosed(transfer.getStatus())) { // 如果已是关闭状态,直接返回,不用重复更新
|
||||
log.info("[notifyTransferClosed][transfer({}) 已经是关闭状态,无需更新]", transfer.getId());
|
||||
return;
|
||||
}
|
||||
if (!isPendingStatus(transfer.getStatus())) {
|
||||
throw exception(PAY_TRANSFER_STATUS_IS_NOT_PENDING);
|
||||
if (!PayTransferStatusEnum.isWaitingOrProcessing(transfer.getStatus())) {
|
||||
throw exception(PAY_TRANSFER_NOTIFY_FAIL_STATUS_NOT_WAITING_OR_PROCESSING);
|
||||
}
|
||||
|
||||
// 2.更新
|
||||
// 2. 更新状态
|
||||
int updateCount = transferMapper.updateByIdAndStatus(transfer.getId(),
|
||||
CollUtil.newArrayList(WAITING.getStatus(), IN_PROGRESS.getStatus()),
|
||||
new PayTransferDO().setStatus(CLOSED.getStatus()).setChannelId(channel.getId())
|
||||
.setChannelCode(channel.getCode()).setChannelTransferNo(notify.getChannelTransferNo())
|
||||
.setChannelErrorCode(notify.getChannelErrorCode()).setChannelErrorMsg(notify.getChannelErrorMsg())
|
||||
.setChannelNotifyData(JsonUtils.toJsonString(notify)));
|
||||
CollUtil.newArrayList(PayTransferStatusEnum.WAITING.getStatus(), PayTransferStatusEnum.PROCESSING.getStatus()),
|
||||
new PayTransferDO().setStatus(PayTransferStatusEnum.CLOSED.getStatus())
|
||||
.setChannelTransferNo(notify.getChannelTransferNo())
|
||||
.setChannelNotifyData(JsonUtils.toJsonString(notify))
|
||||
.setChannelErrorCode(notify.getChannelErrorCode()).setChannelErrorMsg(notify.getChannelErrorMsg()));
|
||||
if (updateCount == 0) {
|
||||
throw exception(PAY_TRANSFER_STATUS_IS_NOT_PENDING);
|
||||
throw exception(PAY_TRANSFER_NOTIFY_FAIL_STATUS_NOT_WAITING_OR_PROCESSING);
|
||||
}
|
||||
log.info("[updateTransferClosed][transfer({}) 更新为关闭状态]", transfer.getId());
|
||||
log.info("[notifyTransferClosed][transfer({}) 更新为关闭状态]", transfer.getId());
|
||||
|
||||
// 3. 插入转账通知记录
|
||||
notifyService.createPayNotifyTask(PayNotifyTypeEnum.TRANSFER.getType(),
|
||||
transfer.getId());
|
||||
|
||||
notifyService.createPayNotifyTask(PayNotifyTypeEnum.TRANSFER.getType(), transfer.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -257,6 +246,11 @@ public class PayTransferServiceImpl implements PayTransferService {
|
||||
return transferMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayTransferDO getTransferByNo(String no) {
|
||||
return transferMapper.selectByNo(no);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<PayTransferDO> getTransferPage(PayTransferPageReqVO pageReqVO) {
|
||||
return transferMapper.selectPage(pageReqVO);
|
||||
@@ -264,27 +258,39 @@ public class PayTransferServiceImpl implements PayTransferService {
|
||||
|
||||
@Override
|
||||
public int syncTransfer() {
|
||||
List<PayTransferDO> list = transferMapper.selectListByStatus(WAITING.getStatus());
|
||||
List<PayTransferDO> list = transferMapper.selectListByStatus(CollUtil.newArrayList(
|
||||
PayTransferStatusEnum.WAITING.getStatus(), PayTransferStatusEnum.PROCESSING.getStatus()));
|
||||
if (CollUtil.isEmpty(list)) {
|
||||
return 0;
|
||||
}
|
||||
int count = 0;
|
||||
for (PayTransferDO transfer : list) {
|
||||
if (!transfer.getId().equals(54L)) {
|
||||
continue;
|
||||
}
|
||||
count += syncTransfer(transfer) ? 1 : 0;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void syncTransfer(Long id) {
|
||||
PayTransferDO transfer = transferMapper.selectById(id);
|
||||
if (transfer == null) {
|
||||
throw exception(PAY_TRANSFER_NOT_FOUND);
|
||||
}
|
||||
syncTransfer(transfer);
|
||||
}
|
||||
|
||||
private boolean syncTransfer(PayTransferDO transfer) {
|
||||
try {
|
||||
// 1. 查询转账订单信息
|
||||
PayClient payClient = channelService.getPayClient(transfer.getChannelId());
|
||||
PayClient<?> payClient = channelService.getPayClient(transfer.getChannelId());
|
||||
if (payClient == null) {
|
||||
log.error("[syncTransfer][渠道编号({}) 找不到对应的支付客户端]", transfer.getChannelId());
|
||||
return false;
|
||||
}
|
||||
PayTransferRespDTO resp = payClient.getTransfer(transfer.getNo(),
|
||||
PayTransferTypeEnum.typeOf(transfer.getType()));
|
||||
PayTransferRespDTO resp = payClient.getTransfer(transfer.getNo());
|
||||
|
||||
// 2. 回调转账结果
|
||||
notifyTransfer(transfer.getChannelId(), resp);
|
||||
@@ -310,4 +316,5 @@ public class PayTransferServiceImpl implements PayTransferService {
|
||||
private PayTransferServiceImpl getSelf() {
|
||||
return SpringUtil.getBean(getClass());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ public interface PayWalletRechargeService {
|
||||
/**
|
||||
* 创建钱包充值记录(发起充值)
|
||||
*
|
||||
* @param userId 用户 id
|
||||
* @param userId 用户编号
|
||||
* @param userType 用户类型
|
||||
* @param createReqVO 钱包充值请求 VO
|
||||
* @param userIp 用户Ip
|
||||
@@ -39,8 +39,8 @@ public interface PayWalletRechargeService {
|
||||
/**
|
||||
* 更新钱包充值成功
|
||||
*
|
||||
* @param id 钱包充值记录 id
|
||||
* @param payOrderId 支付订单 id
|
||||
* @param id 钱包充值记录编号
|
||||
* @param payOrderId 支付订单编号
|
||||
*/
|
||||
void updateWalletRechargerPaid(Long id, Long payOrderId);
|
||||
|
||||
@@ -55,9 +55,10 @@ public interface PayWalletRechargeService {
|
||||
/**
|
||||
* 更新钱包充值记录为已退款
|
||||
*
|
||||
* @param id 钱包充值 id
|
||||
* @param id 钱包充值记录编号
|
||||
* @param refundId 钱包充值退款编号(实际和 id 相同)
|
||||
* @param payRefundId 退款单id
|
||||
*/
|
||||
void updateWalletRechargeRefunded(Long id, Long payRefundId);
|
||||
void updateWalletRechargeRefunded(Long id, Long refundId, Long payRefundId);
|
||||
|
||||
}
|
||||
|
||||
@@ -8,10 +8,11 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.refund.PayRefundStatusRespEnum;
|
||||
import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
|
||||
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.app.wallet.vo.recharge.AppPayWalletRechargeCreateReqVO;
|
||||
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.wallet.PayWalletDO;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargeDO;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargePackageDO;
|
||||
@@ -21,7 +22,6 @@ import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;
|
||||
import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum;
|
||||
import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties;
|
||||
import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
|
||||
import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
|
||||
import cn.iocoder.yudao.module.system.api.social.SocialClientApi;
|
||||
import cn.iocoder.yudao.module.system.api.social.dto.SocialWxaSubscribeMessageSendReqDTO;
|
||||
import jakarta.annotation.Resource;
|
||||
@@ -61,13 +61,15 @@ public class PayWalletRechargeServiceImpl implements PayWalletRechargeService {
|
||||
private PayWalletService payWalletService;
|
||||
@Resource
|
||||
private PayOrderService payOrderService;
|
||||
@Resource
|
||||
private PayRefundService payRefundService;
|
||||
// @Resource
|
||||
// private PayRefundService payRefundService;
|
||||
@Resource
|
||||
private PayWalletRechargePackageService payWalletRechargePackageService;
|
||||
|
||||
@Resource
|
||||
public SocialClientApi socialClientApi;
|
||||
@Resource
|
||||
private PayRefundApi payRefundApi;
|
||||
|
||||
@Resource
|
||||
private PayProperties payProperties;
|
||||
@@ -122,7 +124,7 @@ public class PayWalletRechargeServiceImpl implements PayWalletRechargeService {
|
||||
}
|
||||
// 1.2 校验钱包充值是否可以支付
|
||||
if (recharge.getPayStatus()) {
|
||||
// 特殊:如果订单已支付,且支付单号相同,直接返回,说明重复回调
|
||||
// 特殊:支付单号相同,直接返回,说明重复回调
|
||||
if (ObjectUtil.equals(recharge.getPayOrderId(), payOrderId)) {
|
||||
log.warn("[updateWalletRechargerPaid][recharge({}) 已支付,且支付单号相同({}),直接返回]", recharge, payOrderId);
|
||||
return;
|
||||
@@ -186,11 +188,11 @@ public class PayWalletRechargeServiceImpl implements PayWalletRechargeService {
|
||||
// 3. 创建退款单
|
||||
String walletRechargeId = String.valueOf(id);
|
||||
String refundId = walletRechargeId + "-refund";
|
||||
Long payRefundId = payRefundService.createPayRefund(new PayRefundCreateReqDTO()
|
||||
Long payRefundId = payRefundApi.createRefund(new PayRefundCreateReqDTO()
|
||||
.setAppKey(payProperties.getWalletPayAppKey()).setUserIp(userIp)
|
||||
.setMerchantOrderId(walletRechargeId)
|
||||
.setMerchantRefundId(refundId)
|
||||
.setReason("想退钱").setPrice(walletRecharge.getPayPrice()));
|
||||
.setReason("想退钱").setPrice(walletRecharge.getPayPrice())).getCheckedData();
|
||||
|
||||
// 4. 更新充值记录退款单号
|
||||
walletRechargeMapper.updateById(new PayWalletRechargeDO().setPayRefundId(payRefundId)
|
||||
@@ -199,18 +201,20 @@ public class PayWalletRechargeServiceImpl implements PayWalletRechargeService {
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void updateWalletRechargeRefunded(Long id, Long payRefundId) {
|
||||
public void updateWalletRechargeRefunded(Long id, Long refundId, Long payRefundId) {
|
||||
// 1.1 获取钱包充值记录
|
||||
// 说明:因为 id 和 refundId 是相同的,所以直接使用 id 查询即可!
|
||||
PayWalletRechargeDO walletRecharge = walletRechargeMapper.selectById(id);
|
||||
if (walletRecharge == null) {
|
||||
log.error("[updateWalletRechargerPaid][钱包充值记录不存在,钱包充值记录 id({})]", id);
|
||||
throw exception(WALLET_RECHARGE_NOT_FOUND);
|
||||
}
|
||||
// 1.2 校验钱包充值是否可以更新已退款
|
||||
PayRefundDO payRefund = validateWalletRechargeCanRefunded(walletRecharge, payRefundId);
|
||||
PayRefundRespDTO payRefund = validateWalletRechargeCanRefunded(walletRecharge, payRefundId);
|
||||
|
||||
// 2. 处理退款结果
|
||||
PayWalletRechargeDO updateObj = new PayWalletRechargeDO().setId(id);
|
||||
// 退款成功
|
||||
// 情况一:退款成功
|
||||
if (PayRefundStatusEnum.isSuccess(payRefund.getStatus())) {
|
||||
// 2.1 更新钱包余额
|
||||
payWalletService.reduceWalletBalance(walletRecharge.getWalletId(), id,
|
||||
@@ -219,19 +223,19 @@ public class PayWalletRechargeServiceImpl implements PayWalletRechargeService {
|
||||
updateObj.setRefundStatus(SUCCESS.getStatus()).setRefundTime(payRefund.getSuccessTime())
|
||||
.setRefundTotalPrice(walletRecharge.getTotalPrice()).setRefundPayPrice(walletRecharge.getPayPrice())
|
||||
.setRefundBonusPrice(walletRecharge.getBonusPrice());
|
||||
}
|
||||
// 退款失败
|
||||
if (PayRefundStatusRespEnum.isFailure(payRefund.getStatus())) {
|
||||
// 情况二:退款失败
|
||||
} else if (PayRefundStatusRespEnum.isFailure(payRefund.getStatus())) {
|
||||
// 2.2 解冻余额
|
||||
payWalletService.unfreezePrice(walletRecharge.getWalletId(), walletRecharge.getTotalPrice());
|
||||
|
||||
updateObj.setRefundStatus(FAILURE.getStatus());
|
||||
}
|
||||
|
||||
// 3. 更新钱包充值的退款字段
|
||||
walletRechargeMapper.updateByIdAndRefunded(id, WAITING.getStatus(), updateObj);
|
||||
}
|
||||
|
||||
private PayRefundDO validateWalletRechargeCanRefunded(PayWalletRechargeDO walletRecharge, Long payRefundId) {
|
||||
private PayRefundRespDTO validateWalletRechargeCanRefunded(PayWalletRechargeDO walletRecharge, Long payRefundId) {
|
||||
// 1. 校验退款订单匹配
|
||||
if (notEqual(walletRecharge.getPayRefundId(), payRefundId)) {
|
||||
log.error("[validateWalletRechargeCanRefunded][钱包充值({}) 退款单不匹配({}),请进行处理!钱包充值的数据是:{}]",
|
||||
@@ -240,7 +244,7 @@ public class PayWalletRechargeServiceImpl implements PayWalletRechargeService {
|
||||
}
|
||||
|
||||
// 2.1 校验退款订单
|
||||
PayRefundDO payRefund = payRefundService.getRefund(payRefundId);
|
||||
PayRefundRespDTO payRefund = payRefundApi.getRefund(payRefundId).getCheckedData();
|
||||
if (payRefund == null) {
|
||||
log.error("[validateWalletRechargeCanRefunded][payRefund({})不存在]", payRefundId);
|
||||
throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUND_NOT_FOUND);
|
||||
@@ -252,7 +256,7 @@ public class PayWalletRechargeServiceImpl implements PayWalletRechargeService {
|
||||
throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUND_PRICE_NOT_MATCH);
|
||||
}
|
||||
// 2.3 校验退款订单商户订单是否匹配
|
||||
if (notEqual(payRefund.getMerchantOrderId(), walletRecharge.getId().toString())) {
|
||||
if (notEqual(payRefund.getMerchantRefundId(), walletRecharge.getId().toString())) {
|
||||
log.error("[validateWalletRechargeCanRefunded][钱包({}) 退款单不匹配({}),请进行处理!payRefund 数据是:{}]",
|
||||
walletRecharge.getId(), payRefundId, toJsonString(payRefund));
|
||||
throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUND_ORDER_ID_ERROR);
|
||||
|
||||
@@ -41,12 +41,11 @@ public interface PayWalletService {
|
||||
/**
|
||||
* 钱包订单支付
|
||||
*
|
||||
* @param userId 用户 id
|
||||
* @param userType 用户类型
|
||||
* @param walletId 钱包编号
|
||||
* @param outTradeNo 外部订单号
|
||||
* @param price 金额
|
||||
*/
|
||||
PayWalletTransactionDO orderPay(Long userId, Integer userType, String outTradeNo, Integer price);
|
||||
PayWalletTransactionDO orderPay(Long walletId, String outTradeNo, Integer price);
|
||||
|
||||
/**
|
||||
* 钱包订单支付退款
|
||||
@@ -60,8 +59,8 @@ public interface PayWalletService {
|
||||
/**
|
||||
* 扣减钱包余额
|
||||
*
|
||||
* @param walletId 钱包 id
|
||||
* @param bizId 业务关联 id
|
||||
* @param walletId 钱包编号
|
||||
* @param bizId 业务关联编号
|
||||
* @param bizType 业务关联分类
|
||||
* @param price 扣减金额
|
||||
* @return 钱包流水
|
||||
@@ -72,8 +71,8 @@ public interface PayWalletService {
|
||||
/**
|
||||
* 增加钱包余额
|
||||
*
|
||||
* @param walletId 钱包 id
|
||||
* @param bizId 业务关联 id
|
||||
* @param walletId 钱包编号
|
||||
* @param bizId 业务关联编号
|
||||
* @param bizType 业务关联分类
|
||||
* @param price 增加金额
|
||||
* @return 钱包流水
|
||||
|
||||
@@ -81,13 +81,13 @@ public class PayWalletServiceImpl implements PayWalletService {
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public PayWalletTransactionDO orderPay(Long userId, Integer userType, String outTradeNo, Integer price) {
|
||||
public PayWalletTransactionDO orderPay(Long walletId, String outTradeNo, Integer price) {
|
||||
// 1. 判断支付交易拓展单是否存
|
||||
PayOrderExtensionDO orderExtension = orderService.getOrderExtensionByNo(outTradeNo);
|
||||
if (orderExtension == null) {
|
||||
throw exception(PAY_ORDER_EXTENSION_NOT_FOUND);
|
||||
}
|
||||
PayWalletDO wallet = getOrCreateWallet(userId, userType);
|
||||
PayWalletDO wallet = walletMapper.selectById(walletId);
|
||||
// 2. 扣减余额
|
||||
return reduceWalletBalance(wallet.getId(), orderExtension.getOrderId(), PAYMENT, price);
|
||||
}
|
||||
@@ -198,7 +198,7 @@ public class PayWalletServiceImpl implements PayWalletService {
|
||||
break;
|
||||
}
|
||||
case UPDATE_BALANCE: // 更新余额
|
||||
case BROKERAGE_WITHDRAW: // 分佣提现
|
||||
case TRANSFER: // 分佣提现
|
||||
walletMapper.updateWhenAdd(payWallet.getId(), price);
|
||||
break;
|
||||
default: {
|
||||
|
||||
@@ -136,6 +136,6 @@ yudao:
|
||||
access-log: # 访问日志的配置项
|
||||
enable: false
|
||||
pay:
|
||||
order-notify-url: http://yunai.natapp1.cc/admin-api/pay/notify/order # 支付渠道的【支付】回调地址
|
||||
refund-notify-url: http://yunai.natapp1.cc/admin-api/pay/notify/refund # 支付渠道的【退款】回调地址
|
||||
transfer-notify-url: http://yunai.natapp1.cc/admin-api/pay/notify/transfer # 支付渠道的【转账】回调地址
|
||||
order-notify-url: https://yutou.mynatapp.cc/admin-api/pay/notify/order # 支付渠道的【支付】回调地址
|
||||
refund-notify-url: https://yutou.mynatapp.cc/admin-api/pay/notify/refund # 支付渠道的【退款】回调地址
|
||||
transfer-notify-url: https://yutou.mynatapp.cc/admin-api/pay/notify/transfer # 支付渠道的【转账】回调地址
|
||||
@@ -311,7 +311,7 @@ public class PayChannelServiceTest extends BaseDbUnitTest {
|
||||
.thenReturn(mockClient);
|
||||
|
||||
// 调用
|
||||
PayClient client = channelService.getPayClient(id);
|
||||
PayClient<?> client = channelService.getPayClient(id);
|
||||
// 断言
|
||||
assertSame(client, mockClient);
|
||||
}
|
||||
|
||||
@@ -350,7 +350,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest {
|
||||
when(channelService.validPayChannel(eq(1L), eq(PayChannelEnum.ALIPAY_APP.getCode())))
|
||||
.thenReturn(channel);
|
||||
// mock 方法(client)
|
||||
PayClient client = mock(PayClient.class);
|
||||
PayClient<?> client = mock(PayClient.class);
|
||||
when(channelService.getPayClient(eq(10L))).thenReturn(client);
|
||||
// mock 方法()
|
||||
PayOrderRespDTO unifiedOrderResp = randomPojo(PayOrderRespDTO.class, o ->
|
||||
@@ -404,7 +404,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest {
|
||||
when(channelService.validPayChannel(eq(1L), eq(PayChannelEnum.ALIPAY_APP.getCode())))
|
||||
.thenReturn(channel);
|
||||
// mock 方法(client)
|
||||
PayClient client = mock(PayClient.class);
|
||||
PayClient<?> client = mock(PayClient.class);
|
||||
when(channelService.getPayClient(eq(10L))).thenReturn(client);
|
||||
// mock 方法(支付渠道的调用)
|
||||
PayOrderRespDTO unifiedOrderResp = randomPojo(PayOrderRespDTO.class, o -> o.setChannelErrorCode(null).setChannelErrorMsg(null)
|
||||
@@ -462,7 +462,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest {
|
||||
o -> o.setOrderId(id).setStatus(PayOrderStatusEnum.WAITING.getStatus()));
|
||||
orderExtensionMapper.insert(orderExtension);
|
||||
// mock 方法(PayClient 已支付)
|
||||
PayClient client = mock(PayClient.class);
|
||||
PayClient<?> client = mock(PayClient.class);
|
||||
when(channelService.getPayClient(eq(orderExtension.getChannelId()))).thenReturn(client);
|
||||
when(client.getOrder(eq(orderExtension.getNo()))).thenReturn(randomPojo(PayOrderRespDTO.class,
|
||||
o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus())));
|
||||
@@ -481,7 +481,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest {
|
||||
o -> o.setOrderId(id).setStatus(PayOrderStatusEnum.WAITING.getStatus()));
|
||||
orderExtensionMapper.insert(orderExtension);
|
||||
// mock 方法(PayClient 已支付)
|
||||
PayClient client = mock(PayClient.class);
|
||||
PayClient<?> client = mock(PayClient.class);
|
||||
when(channelService.getPayClient(eq(orderExtension.getChannelId()))).thenReturn(client);
|
||||
when(client.getOrder(eq(orderExtension.getNo()))).thenReturn(randomPojo(PayOrderRespDTO.class,
|
||||
o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus())));
|
||||
@@ -873,7 +873,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest {
|
||||
.setCreateTime(LocalDateTime.now()));
|
||||
orderExtensionMapper.insert(orderExtension);
|
||||
// mock 方法(PayClient)
|
||||
PayClient client = mock(PayClient.class);
|
||||
PayClient<?> client = mock(PayClient.class);
|
||||
when(channelService.getPayClient(eq(10L))).thenReturn(client);
|
||||
// mock 方法(PayClient 异常)
|
||||
when(client.getOrder(any())).thenThrow(new RuntimeException());
|
||||
@@ -900,7 +900,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest {
|
||||
.setCreateTime(LocalDateTime.now()));
|
||||
orderExtensionMapper.insert(orderExtension);
|
||||
// mock 方法(PayClient)
|
||||
PayClient client = mock(PayClient.class);
|
||||
PayClient<?> client = mock(PayClient.class);
|
||||
when(channelService.getPayClient(eq(10L))).thenReturn(client);
|
||||
// mock 方法(PayClient 成功返回)
|
||||
PayOrderRespDTO respDTO = randomPojo(PayOrderRespDTO.class,
|
||||
@@ -934,7 +934,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest {
|
||||
.setCreateTime(LocalDateTime.now()));
|
||||
orderExtensionMapper.insert(orderExtension);
|
||||
// mock 方法(PayClient)
|
||||
PayClient client = mock(PayClient.class);
|
||||
PayClient<?> client = mock(PayClient.class);
|
||||
when(channelService.getPayClient(eq(10L))).thenReturn(client);
|
||||
// mock 方法(PayClient 成功返回)
|
||||
PayOrderRespDTO respDTO = randomPojo(PayOrderRespDTO.class,
|
||||
@@ -965,7 +965,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest {
|
||||
.setOrderId(order.getId()));
|
||||
orderExtensionMapper.insert(orderExtension);
|
||||
// mock 方法(PayClient)
|
||||
PayClient client = mock(PayClient.class);
|
||||
PayClient<?> client = mock(PayClient.class);
|
||||
when(channelService.getPayClient(eq(10L))).thenReturn(client);
|
||||
|
||||
// 调用
|
||||
@@ -1012,7 +1012,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest {
|
||||
.setChannelId(10L));
|
||||
orderExtensionMapper.insert(orderExtension);
|
||||
// mock 方法(PayClient)
|
||||
PayClient client = mock(PayClient.class);
|
||||
PayClient<?> client = mock(PayClient.class);
|
||||
when(channelService.getPayClient(eq(10L))).thenReturn(client);
|
||||
// mock 方法(PayClient 退款返回)
|
||||
PayOrderRespDTO respDTO = randomPojo(PayOrderRespDTO.class,
|
||||
@@ -1046,7 +1046,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest {
|
||||
.setChannelId(10L));
|
||||
orderExtensionMapper.insert(orderExtension);
|
||||
// mock 方法(PayClient)
|
||||
PayClient client = mock(PayClient.class);
|
||||
PayClient<?> client = mock(PayClient.class);
|
||||
when(channelService.getPayClient(eq(10L))).thenReturn(client);
|
||||
// mock 方法(PayClient 成功返回)
|
||||
PayOrderRespDTO respDTO = randomPojo(PayOrderRespDTO.class,
|
||||
@@ -1080,7 +1080,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest {
|
||||
.setChannelId(10L));
|
||||
orderExtensionMapper.insert(orderExtension);
|
||||
// mock 方法(PayClient)
|
||||
PayClient client = mock(PayClient.class);
|
||||
PayClient<?> client = mock(PayClient.class);
|
||||
when(channelService.getPayClient(eq(10L))).thenReturn(client);
|
||||
// mock 方法(PayClient 关闭返回)
|
||||
PayOrderRespDTO respDTO = randomPojo(PayOrderRespDTO.class,
|
||||
|
||||
@@ -25,13 +25,13 @@ 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 cn.iocoder.yudao.module.pay.service.order.PayOrderService;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
|
||||
@@ -215,7 +215,7 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
|
||||
when(appService.validPayApp(eq("demo"))).thenReturn(app);
|
||||
|
||||
// 调用,并断言异常
|
||||
assertServiceException(() -> refundService.createPayRefund(reqDTO),
|
||||
assertServiceException(() -> refundService.createRefund(reqDTO),
|
||||
PAY_ORDER_NOT_FOUND);
|
||||
}
|
||||
|
||||
@@ -241,7 +241,7 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
|
||||
when(orderService.getOrder(eq(1L), eq("100"))).thenReturn(order);
|
||||
|
||||
// 调用,并断言异常
|
||||
assertServiceException(() -> refundService.createPayRefund(reqDTO),
|
||||
assertServiceException(() -> refundService.createRefund(reqDTO),
|
||||
PAY_ORDER_REFUND_FAIL_STATUS_ERROR);
|
||||
}
|
||||
|
||||
@@ -260,7 +260,7 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
|
||||
when(orderService.getOrder(eq(1L), eq("100"))).thenReturn(order);
|
||||
|
||||
// 调用,并断言异常
|
||||
assertServiceException(() -> refundService.createPayRefund(reqDTO),
|
||||
assertServiceException(() -> refundService.createRefund(reqDTO),
|
||||
REFUND_PRICE_EXCEED);
|
||||
}
|
||||
|
||||
@@ -283,7 +283,7 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
|
||||
refundMapper.insert(refund);
|
||||
|
||||
// 调用,并断言异常
|
||||
assertServiceException(() -> refundService.createPayRefund(reqDTO),
|
||||
assertServiceException(() -> refundService.createRefund(reqDTO),
|
||||
REFUND_PRICE_EXCEED);
|
||||
}
|
||||
|
||||
@@ -307,7 +307,7 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
|
||||
when(channelService.validPayChannel(eq(1L))).thenReturn(channel);
|
||||
|
||||
// 调用,并断言异常
|
||||
assertServiceException(() -> refundService.createPayRefund(reqDTO),
|
||||
assertServiceException(() -> refundService.createRefund(reqDTO),
|
||||
CHANNEL_NOT_FOUND);
|
||||
}
|
||||
|
||||
@@ -331,7 +331,7 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
|
||||
.setCode(PayChannelEnum.ALIPAY_APP.getCode()));
|
||||
when(channelService.validPayChannel(eq(1L))).thenReturn(channel);
|
||||
// mock 方法(client)
|
||||
PayClient client = mock(PayClient.class);
|
||||
PayClient<?> client = mock(PayClient.class);
|
||||
when(channelService.getPayClient(eq(10L))).thenReturn(client);
|
||||
// mock 数据(refund 已存在)
|
||||
PayRefundDO refund = randomPojo(PayRefundDO.class, o ->
|
||||
@@ -339,7 +339,7 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
|
||||
refundMapper.insert(refund);
|
||||
|
||||
// 调用,并断言异常
|
||||
assertServiceException(() -> refundService.createPayRefund(reqDTO),
|
||||
assertServiceException(() -> refundService.createRefund(reqDTO),
|
||||
REFUND_EXISTS);
|
||||
}
|
||||
|
||||
@@ -363,13 +363,13 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
|
||||
.setCode(PayChannelEnum.ALIPAY_APP.getCode()));
|
||||
when(channelService.validPayChannel(eq(10L))).thenReturn(channel);
|
||||
// mock 方法(client)
|
||||
PayClient client = mock(PayClient.class);
|
||||
PayClient<?> client = mock(PayClient.class);
|
||||
when(channelService.getPayClient(eq(10L))).thenReturn(client);
|
||||
// mock 方法(client 调用发生异常)
|
||||
when(client.unifiedRefund(any(PayRefundUnifiedReqDTO.class))).thenThrow(new RuntimeException());
|
||||
|
||||
// 调用
|
||||
Long refundId = refundService.createPayRefund(reqDTO);
|
||||
Long refundId = refundService.createRefund(reqDTO);
|
||||
// 断言
|
||||
PayRefundDO refundDO = refundMapper.selectById(refundId);
|
||||
assertPojoEquals(reqDTO, refundDO);
|
||||
@@ -407,7 +407,7 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
|
||||
.setCode(PayChannelEnum.ALIPAY_APP.getCode()));
|
||||
when(channelService.validPayChannel(eq(10L))).thenReturn(channel);
|
||||
// mock 方法(client)
|
||||
PayClient client = mock(PayClient.class);
|
||||
PayClient<?> client = mock(PayClient.class);
|
||||
when(channelService.getPayClient(eq(10L))).thenReturn(client);
|
||||
// mock 方法(client 成功)
|
||||
PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class);
|
||||
@@ -422,7 +422,7 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
|
||||
}))).thenReturn(refundRespDTO);
|
||||
|
||||
// 调用
|
||||
Long refundId = refundService.createPayRefund(reqDTO);
|
||||
Long refundId = refundService.createRefund(reqDTO);
|
||||
// 断言
|
||||
PayRefundDO refundDO = refundMapper.selectById(refundId);
|
||||
assertPojoEquals(reqDTO, refundDO);
|
||||
@@ -664,7 +664,7 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
|
||||
.setOrderNo("P110").setNo("R220"));
|
||||
refundMapper.insert(refund);
|
||||
// mock 方法(client)
|
||||
PayClient client = mock(PayClient.class);
|
||||
PayClient<?> client = mock(PayClient.class);
|
||||
when(channelService.getPayClient(eq(10L))).thenReturn(client);
|
||||
// mock 方法(client 返回指定状态)
|
||||
PayRefundRespDTO respDTO = randomPojo(PayRefundRespDTO.class, o -> o.setStatus(status));
|
||||
@@ -686,7 +686,7 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
|
||||
.setOrderNo("P110").setNo("R220"));
|
||||
refundMapper.insert(refund);
|
||||
// mock 方法(client)
|
||||
PayClient client = mock(PayClient.class);
|
||||
PayClient<?> client = mock(PayClient.class);
|
||||
when(channelService.getPayClient(eq(10L))).thenReturn(client);
|
||||
// mock 方法(client 抛出异常)
|
||||
when(client.getRefund(eq("P110"), eq("R220"))).thenThrow(new RuntimeException());
|
||||
|
||||
@@ -3,7 +3,6 @@ package cn.iocoder.yudao.framework.pay.config;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.PayClientFactoryImpl;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,7 +6,6 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@@ -15,7 +14,7 @@ import java.util.Map;
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface PayClient {
|
||||
public interface PayClient<Config> {
|
||||
|
||||
/**
|
||||
* 获得渠道编号
|
||||
@@ -24,6 +23,13 @@ public interface PayClient {
|
||||
*/
|
||||
Long getId();
|
||||
|
||||
/**
|
||||
* 获得渠道配置
|
||||
*
|
||||
* @return 渠道配置
|
||||
*/
|
||||
Config getConfig();
|
||||
|
||||
// ============ 支付相关 ==========
|
||||
|
||||
/**
|
||||
@@ -95,10 +101,9 @@ public interface PayClient {
|
||||
* 获得转账订单信息
|
||||
*
|
||||
* @param outTradeNo 外部订单号
|
||||
* @param type 转账类型
|
||||
* @return 转账信息
|
||||
*/
|
||||
PayTransferRespDTO getTransfer(String outTradeNo, PayTransferTypeEnum type);
|
||||
PayTransferRespDTO getTransfer(String outTradeNo);
|
||||
|
||||
/**
|
||||
* 解析 transfer 回调数据
|
||||
|
||||
@@ -22,7 +22,6 @@ public class PayTransferRespDTO {
|
||||
|
||||
/**
|
||||
* 外部转账单号
|
||||
*
|
||||
*/
|
||||
private String outTransferNo;
|
||||
|
||||
@@ -50,11 +49,19 @@ public class PayTransferRespDTO {
|
||||
*/
|
||||
private String channelErrorMsg;
|
||||
|
||||
/**
|
||||
* 渠道 package 信息
|
||||
*
|
||||
* 特殊:目前只有微信转账有这个东西!!!
|
||||
* @see <a href="https://pay.weixin.qq.com/doc/v3/merchant/4012716430">JSAPI 调起用户确认收款</a>
|
||||
*/
|
||||
private String channelPackageInfo;
|
||||
|
||||
/**
|
||||
* 创建【WAITING】状态的转账返回
|
||||
*/
|
||||
public static PayTransferRespDTO waitingOf(String channelTransferNo,
|
||||
String outTransferNo, Object rawData) {
|
||||
String outTransferNo, Object rawData) {
|
||||
PayTransferRespDTO respDTO = new PayTransferRespDTO();
|
||||
respDTO.status = PayTransferStatusRespEnum.WAITING.getStatus();
|
||||
respDTO.channelTransferNo = channelTransferNo;
|
||||
@@ -66,10 +73,10 @@ public class PayTransferRespDTO {
|
||||
/**
|
||||
* 创建【IN_PROGRESS】状态的转账返回
|
||||
*/
|
||||
public static PayTransferRespDTO dealingOf(String channelTransferNo,
|
||||
String outTransferNo, Object rawData) {
|
||||
public static PayTransferRespDTO processingOf(String channelTransferNo,
|
||||
String outTransferNo, Object rawData) {
|
||||
PayTransferRespDTO respDTO = new PayTransferRespDTO();
|
||||
respDTO.status = PayTransferStatusRespEnum.IN_PROGRESS.getStatus();
|
||||
respDTO.status = PayTransferStatusRespEnum.PROCESSING.getStatus();
|
||||
respDTO.channelTransferNo = channelTransferNo;
|
||||
respDTO.outTransferNo = outTransferNo;
|
||||
respDTO.rawData = rawData;
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package cn.iocoder.yudao.framework.pay.core.client.dto.transfer;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.validation.InEnum;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
@@ -12,9 +9,6 @@ import org.hibernate.validator.constraints.URL;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum.Alipay;
|
||||
import static cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum.WxPay;
|
||||
|
||||
/**
|
||||
* 统一转账 Request DTO
|
||||
*
|
||||
@@ -23,21 +17,15 @@ import static cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferType
|
||||
@Data
|
||||
public class PayTransferUnifiedReqDTO {
|
||||
|
||||
/**
|
||||
* 转账类型
|
||||
*
|
||||
* 关联 {@link PayTransferTypeEnum#getType()}
|
||||
*/
|
||||
@NotNull(message = "转账类型不能为空")
|
||||
@InEnum(PayTransferTypeEnum.class)
|
||||
private Integer type;
|
||||
|
||||
/**
|
||||
* 用户 IP
|
||||
*/
|
||||
@NotEmpty(message = "用户 IP 不能为空")
|
||||
private String userIp;
|
||||
|
||||
/**
|
||||
* 外部转账单编号
|
||||
*/
|
||||
@NotEmpty(message = "外部转账单编号不能为空")
|
||||
private String outTransferNo;
|
||||
|
||||
@@ -55,26 +43,23 @@ public class PayTransferUnifiedReqDTO {
|
||||
@Length(max = 128, message = "转账标题不能超过 128")
|
||||
private String subject;
|
||||
|
||||
/**
|
||||
* 收款人账号
|
||||
*
|
||||
* 微信场景下:openid
|
||||
* 支付宝场景下:支付宝账号
|
||||
*/
|
||||
@NotEmpty(message = "收款人账号不能为空")
|
||||
private String userAccount;
|
||||
/**
|
||||
* 收款人姓名
|
||||
*/
|
||||
@NotBlank(message = "收款人姓名不能为空", groups = {Alipay.class})
|
||||
private String userName;
|
||||
|
||||
/**
|
||||
* 支付宝登录号
|
||||
*/
|
||||
@NotBlank(message = "支付宝登录号不能为空", groups = {Alipay.class})
|
||||
private String alipayLogonId;
|
||||
|
||||
/**
|
||||
* 微信 openId
|
||||
*/
|
||||
@NotBlank(message = "微信 openId 不能为空", groups = {WxPay.class})
|
||||
private String openid;
|
||||
|
||||
/**
|
||||
* 支付渠道的额外参数
|
||||
*
|
||||
* 微信支付:sceneId 和 scene_report_infos 字段,必须传递;参考 <a href="https://pay.weixin.qq.com/doc/v3/merchant/4012711988#%EF%BC%883%EF%BC%89%E6%8C%89%E8%BD%AC%E8%B4%A6%E5%9C%BA%E6%99%AF%E6%8A%A5%E5%A4%87%E8%83%8C%E6%99%AF%E4%BF%A1%E6%81%AF">按转账场景报备背景信息</>
|
||||
*/
|
||||
private Map<String, String> channelExtras;
|
||||
|
||||
|
||||
@@ -1,129 +0,0 @@
|
||||
package cn.iocoder.yudao.framework.pay.core.client.dto.transfer;
|
||||
|
||||
import com.github.binarywang.wxpay.bean.notify.OriginNotifyResponse;
|
||||
import com.github.binarywang.wxpay.bean.notify.WxPayBaseNotifyV3Result;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
// TODO @luchi:这个可以复用 wxjava 里的类么?
|
||||
@NoArgsConstructor
|
||||
public class WxPayTransferPartnerNotifyV3Result implements Serializable, WxPayBaseNotifyV3Result<WxPayTransferPartnerNotifyV3Result.TransferNotifyResult> {
|
||||
|
||||
private static final long serialVersionUID = -1L;
|
||||
|
||||
/**
|
||||
* 源数据
|
||||
*/
|
||||
private OriginNotifyResponse rawData;
|
||||
|
||||
/**
|
||||
* 解密后的数据
|
||||
*/
|
||||
private TransferNotifyResult result;
|
||||
|
||||
@Override
|
||||
public void setRawData(OriginNotifyResponse rawData) {
|
||||
this.rawData = rawData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setResult(TransferNotifyResult data) {
|
||||
this.result = data;
|
||||
}
|
||||
|
||||
public TransferNotifyResult getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
public OriginNotifyResponse getRawData() {
|
||||
return rawData;
|
||||
}
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public static class TransferNotifyResult implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/*********************** 公共字段 ********************
|
||||
|
||||
/**
|
||||
* 商家批次单号
|
||||
*/
|
||||
@SerializedName(value = "out_batch_no")
|
||||
protected String outBatchNo;
|
||||
|
||||
/**
|
||||
* 微信批次单号
|
||||
*/
|
||||
@SerializedName(value = "batch_id")
|
||||
protected String batchId;
|
||||
|
||||
/**
|
||||
* 批次状态
|
||||
*/
|
||||
@SerializedName(value = "batch_status")
|
||||
protected String batchStatus;
|
||||
|
||||
/**
|
||||
* 批次总笔数
|
||||
*/
|
||||
@SerializedName(value = "total_num")
|
||||
protected Integer totalNum;
|
||||
|
||||
/**
|
||||
* 批次总金额
|
||||
*/
|
||||
@SerializedName(value = "total_amount")
|
||||
protected Integer totalAmount;
|
||||
|
||||
/**
|
||||
* 批次更新时间
|
||||
*/
|
||||
@SerializedName(value = "update_time")
|
||||
private String updateTime;
|
||||
|
||||
/*********************** FINISHED ********************
|
||||
|
||||
/**
|
||||
* 转账成功金额
|
||||
*/
|
||||
@SerializedName(value = "success_amount")
|
||||
protected Integer successAmount;
|
||||
|
||||
/**
|
||||
* 转账成功笔数
|
||||
*/
|
||||
@SerializedName(value = "success_num")
|
||||
protected Integer successNum;
|
||||
|
||||
/**
|
||||
* 转账失败金额
|
||||
*/
|
||||
@SerializedName(value = "fail_amount")
|
||||
protected Integer failAmount;
|
||||
|
||||
/**
|
||||
* 转账失败笔数
|
||||
*/
|
||||
@SerializedName(value = "fail_num")
|
||||
protected Integer failNum;
|
||||
|
||||
/*********************** CLOSED ********************
|
||||
|
||||
/**
|
||||
* 商户号
|
||||
*/
|
||||
@SerializedName(value = "mchid")
|
||||
protected String mchId;
|
||||
|
||||
/**
|
||||
* 批次关闭原因
|
||||
*/
|
||||
@SerializedName(value = "close_reason")
|
||||
protected String closeReason;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -11,13 +11,10 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReq
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.exception.PayException;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.NOT_IMPLEMENTED;
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
|
||||
|
||||
/**
|
||||
@@ -26,7 +23,7 @@ import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Slf4j
|
||||
public abstract class AbstractPayClient<Config extends PayClientConfig> implements PayClient {
|
||||
public abstract class AbstractPayClient<Config extends PayClientConfig> implements PayClient<Config> {
|
||||
|
||||
/**
|
||||
* 渠道编号
|
||||
@@ -77,6 +74,11 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
|
||||
return channelId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Config getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
// ============ 支付相关 ==========
|
||||
|
||||
@Override
|
||||
@@ -188,7 +190,6 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
|
||||
|
||||
@Override
|
||||
public final PayTransferRespDTO unifiedTransfer(PayTransferUnifiedReqDTO reqDTO) {
|
||||
validatePayTransferReqDTO(reqDTO);
|
||||
PayTransferRespDTO resp;
|
||||
try {
|
||||
resp = doUnifiedTransfer(reqDTO);
|
||||
@@ -202,22 +203,6 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
|
||||
}
|
||||
return resp;
|
||||
}
|
||||
private void validatePayTransferReqDTO(PayTransferUnifiedReqDTO reqDTO) {
|
||||
PayTransferTypeEnum transferType = PayTransferTypeEnum.typeOf(reqDTO.getType());
|
||||
switch (transferType) {
|
||||
case ALIPAY_BALANCE: {
|
||||
ValidationUtils.validate(reqDTO, PayTransferTypeEnum.Alipay.class);
|
||||
break;
|
||||
}
|
||||
case WX_BALANCE: {
|
||||
ValidationUtils.validate(reqDTO, PayTransferTypeEnum.WxPay.class);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw exception(NOT_IMPLEMENTED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final PayTransferRespDTO parseTransferNotify(Map<String, String> params, String body, Map<String, String> headers) {
|
||||
@@ -236,14 +221,14 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
|
||||
throws Throwable;
|
||||
|
||||
@Override
|
||||
public final PayTransferRespDTO getTransfer(String outTradeNo, PayTransferTypeEnum type) {
|
||||
public final PayTransferRespDTO getTransfer(String outTradeNo) {
|
||||
try {
|
||||
return doGetTransfer(outTradeNo, type);
|
||||
return doGetTransfer(outTradeNo);
|
||||
} catch (ServiceException ex) { // 业务异常,都是实现类已经翻译,所以直接抛出即可
|
||||
throw ex;
|
||||
} catch (Throwable ex) {
|
||||
log.error("[getTransfer][客户端({}) outTradeNo({}) type({}) 查询转账单异常]",
|
||||
getId(), outTradeNo, type, ex);
|
||||
log.error("[getTransfer][客户端({}) outTradeNo({}) 查询转账单异常]",
|
||||
getId(), outTradeNo, ex);
|
||||
throw buildPayException(ex);
|
||||
}
|
||||
}
|
||||
@@ -251,7 +236,7 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
|
||||
protected abstract PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO)
|
||||
throws Throwable;
|
||||
|
||||
protected abstract PayTransferRespDTO doGetTransfer(String outTradeNo, PayTransferTypeEnum type)
|
||||
protected abstract PayTransferRespDTO doGetTransfer(String outTradeNo)
|
||||
throws Throwable;
|
||||
|
||||
// ========== 各种工具方法 ==========
|
||||
|
||||
@@ -16,13 +16,14 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDT
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum;
|
||||
import com.alipay.api.AlipayApiException;
|
||||
import com.alipay.api.AlipayConfig;
|
||||
import com.alipay.api.AlipayResponse;
|
||||
import com.alipay.api.DefaultAlipayClient;
|
||||
import com.alipay.api.domain.*;
|
||||
import com.alipay.api.internal.util.AlipaySignature;
|
||||
import com.alipay.api.internal.util.AntCertificationUtil;
|
||||
import com.alipay.api.internal.util.codec.Base64;
|
||||
import com.alipay.api.request.*;
|
||||
import com.alipay.api.response.*;
|
||||
import lombok.Getter;
|
||||
@@ -30,6 +31,7 @@ import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
@@ -37,10 +39,8 @@ import java.util.Objects;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static cn.hutool.core.date.DatePattern.NORM_DATETIME_FORMATTER;
|
||||
import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.*;
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception0;
|
||||
import static cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig.MODE_CERTIFICATE;
|
||||
import static cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig.MODE_PUBLIC_KEY;
|
||||
|
||||
/**
|
||||
* 支付宝抽象类,实现支付宝统一的接口、以及部分实现(退款)
|
||||
@@ -81,12 +81,11 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa
|
||||
@Override
|
||||
public PayOrderRespDTO doParseOrderNotify(Map<String, String> params, String body, Map<String, String> headers) throws Throwable {
|
||||
// 1. 校验回调数据
|
||||
Map<String, String> bodyObj = HttpUtil.decodeParamMap(body, StandardCharsets.UTF_8);
|
||||
AlipaySignature.rsaCheckV1(bodyObj, config.getAlipayPublicKey(),
|
||||
StandardCharsets.UTF_8.name(), config.getSignType());
|
||||
verifyNotifyData(params);
|
||||
|
||||
// 2. 解析订单的状态
|
||||
// 额外说明:支付宝不仅仅支付成功会回调,再各种触发支付单数据变化时,都会进行回调,所以这里 status 的解析会写的比较复杂
|
||||
Map<String, String> bodyObj = HttpUtil.decodeParamMap(body, StandardCharsets.UTF_8);
|
||||
Integer status = parseStatus(bodyObj.get("trade_status"));
|
||||
// 特殊逻辑: 支付宝没有退款成功的状态,所以,如果有退款金额,我们认为是退款成功
|
||||
if (MapUtil.getDouble(bodyObj, "refund_fee", 0D) > 0) {
|
||||
@@ -220,11 +219,11 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa
|
||||
|
||||
@Override
|
||||
protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) throws AlipayApiException {
|
||||
// 1.1 校验公钥类型 必须使用公钥证书模式
|
||||
if (!Objects.equals(config.getMode(), MODE_CERTIFICATE)) {
|
||||
throw exception0(ERROR_CONFIGURATION.getCode(), "支付宝单笔转账必须使用公钥证书模式");
|
||||
}
|
||||
// 1.2 构建 AlipayFundTransUniTransferModel
|
||||
// 补充说明:https://opendocs.alipay.com/open/03dcrm?pathHash=4ba3b20b
|
||||
// 沙箱环境:可通过 公钥模式 或 公钥证书模式 加签进行调试
|
||||
// 生产环境:必须使用 公钥证书模式 加签请求强校验请求
|
||||
|
||||
// 1.1 构建 AlipayFundTransUniTransferModel
|
||||
AlipayFundTransUniTransferModel model = new AlipayFundTransUniTransferModel();
|
||||
// ① 通用的参数
|
||||
model.setTransAmount(formatAmount(reqDTO.getPrice())); // 转账金额
|
||||
@@ -237,32 +236,21 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa
|
||||
}
|
||||
// ② 个性化的参数
|
||||
Participant payeeInfo = new Participant();
|
||||
PayTransferTypeEnum transferType = PayTransferTypeEnum.typeOf(reqDTO.getType());
|
||||
switch (transferType) {
|
||||
// TODO @jason:是不是不用传递 transferType 参数哈?因为应该已经明确是支付宝啦?
|
||||
// @芋艿。 是不是还要考虑转账到银行卡。所以传 transferType 但是转账到银行卡不知道要如何测试??
|
||||
case ALIPAY_BALANCE: {
|
||||
payeeInfo.setIdentityType("ALIPAY_LOGON_ID");
|
||||
payeeInfo.setIdentity(reqDTO.getAlipayLogonId()); // 支付宝登录号
|
||||
payeeInfo.setName(reqDTO.getUserName()); // 支付宝账号姓名
|
||||
model.setPayeeInfo(payeeInfo);
|
||||
break;
|
||||
}
|
||||
case BANK_CARD: {
|
||||
payeeInfo.setIdentityType("BANKCARD_ACCOUNT");
|
||||
// TODO 待实现
|
||||
throw exception(NOT_IMPLEMENTED);
|
||||
}
|
||||
default: {
|
||||
throw exception0(BAD_REQUEST.getCode(), "不正确的转账类型: {}", transferType);
|
||||
}
|
||||
}
|
||||
// 1.3 构建 AlipayFundTransUniTransferRequest
|
||||
payeeInfo.setIdentityType("ALIPAY_LOGON_ID"); // 暂时只考虑转账到支付宝,银行没有权限 https://opendocs.alipay.com/open/02byvc?scene=66dd06f5a923403393b85de68d3c0055
|
||||
payeeInfo.setIdentity(reqDTO.getUserAccount()); // 支付宝登录号
|
||||
payeeInfo.setName(reqDTO.getUserName()); // 支付宝账号姓名
|
||||
model.setPayeeInfo(payeeInfo);
|
||||
// 1.2 构建 AlipayFundTransUniTransferRequest
|
||||
AlipayFundTransUniTransferRequest request = new AlipayFundTransUniTransferRequest();
|
||||
request.setBizModel(model);
|
||||
// 执行请求
|
||||
AlipayFundTransUniTransferResponse response = client.certificateExecute(request);
|
||||
// 处理结果
|
||||
|
||||
// 2.1 执行请求
|
||||
AlipayFundTransUniTransferResponse response;
|
||||
if (Objects.equals(config.getMode(), MODE_CERTIFICATE)) { // 证书模式
|
||||
response = client.certificateExecute(request);
|
||||
} else {
|
||||
response = client.execute(request);
|
||||
}
|
||||
if (!response.isSuccess()) {
|
||||
// 当出现 SYSTEM_ERROR, 转账可能成功也可能失败。 返回 WAIT 状态. 后续 job 会轮询,或相同 outBizNo 重新发起转账
|
||||
// 发现 outBizNo 相同 两次请求参数相同. 会返回 "PAYMENT_INFO_INCONSISTENCY", 不知道哪里的问题. 暂时返回 WAIT. 后续job 会轮询
|
||||
@@ -271,25 +259,24 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa
|
||||
}
|
||||
return PayTransferRespDTO.closedOf(response.getSubCode(), response.getSubMsg(),
|
||||
reqDTO.getOutTransferNo(), response);
|
||||
} else {
|
||||
if (ObjectUtils.equalsAny(response.getStatus(), "REFUND", "FAIL")) { // 转账到银行卡会出现 "REFUND" "FAIL"
|
||||
return PayTransferRespDTO.closedOf(response.getSubCode(), response.getSubMsg(),
|
||||
reqDTO.getOutTransferNo(), response);
|
||||
}
|
||||
if (Objects.equals(response.getStatus(), "DEALING")) { // 转账到银行卡会出现 "DEALING" 处理中
|
||||
return PayTransferRespDTO.dealingOf(response.getOrderId(), reqDTO.getOutTransferNo(), response);
|
||||
}
|
||||
return PayTransferRespDTO.successOf(response.getOrderId(), parseTime(response.getTransDate()),
|
||||
response.getOutBizNo(), response);
|
||||
}
|
||||
|
||||
// 2.2 处理结果
|
||||
if (ObjectUtils.equalsAny(response.getStatus(), "REFUND", "FAIL")) { // 转账到银行卡会出现 "REFUND" "FAIL"
|
||||
return PayTransferRespDTO.closedOf(response.getSubCode(), response.getSubMsg(),
|
||||
reqDTO.getOutTransferNo(), response);
|
||||
}
|
||||
if (Objects.equals(response.getStatus(), "DEALING")) { // 转账到银行卡会出现 "DEALING" 处理中
|
||||
return PayTransferRespDTO.processingOf(response.getOrderId(), reqDTO.getOutTransferNo(), response);
|
||||
}
|
||||
return PayTransferRespDTO.successOf(response.getOrderId(), parseTime(response.getTransDate()),
|
||||
response.getOutBizNo(), response);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PayTransferRespDTO doGetTransfer(String outTradeNo, PayTransferTypeEnum type) throws Throwable {
|
||||
protected PayTransferRespDTO doGetTransfer(String outTradeNo) throws Throwable {
|
||||
// 1.1 构建 AlipayFundTransCommonQueryModel
|
||||
AlipayFundTransCommonQueryModel model = new AlipayFundTransCommonQueryModel();
|
||||
model.setProductCode(type == PayTransferTypeEnum.BANK_CARD ? "TRANS_BANKCARD_NO_PWD" : "TRANS_ACCOUNT_NO_PWD");
|
||||
model.setProductCode("TRANS_ACCOUNT_NO_PWD");
|
||||
model.setBizScene("DIRECT_TRANSFER"); //业务场景
|
||||
model.setOutBizNo(outTradeNo);
|
||||
// 1.2 构建 AlipayFundTransCommonQueryRequest
|
||||
@@ -303,18 +290,7 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa
|
||||
} else {
|
||||
response = client.execute(request);
|
||||
}
|
||||
// 2.2 处理返回结果
|
||||
if (response.isSuccess()) {
|
||||
if (ObjectUtils.equalsAny(response.getStatus(), "REFUND", "FAIL")) { // 转账到银行卡会出现 "REFUND" "FAIL"
|
||||
return PayTransferRespDTO.closedOf(response.getSubCode(), response.getSubMsg(),
|
||||
outTradeNo, response);
|
||||
}
|
||||
if (Objects.equals(response.getStatus(), "DEALING")) { // 转账到银行卡会出现 "DEALING" 处理中
|
||||
return PayTransferRespDTO.dealingOf(response.getOrderId(), outTradeNo, response);
|
||||
}
|
||||
return PayTransferRespDTO.successOf(response.getOrderId(), parseTime(response.getPayDate()),
|
||||
response.getOutBizNo(), response);
|
||||
} else {
|
||||
if (!response.isSuccess()) {
|
||||
// 当出现 SYSTEM_ERROR, 转账可能成功也可能失败。 返回 WAIT 状态. 后续 job 会轮询, 或相同 outBizNo 重新发起转账
|
||||
// 当出现 ORDER_NOT_EXIST 可能是转账还在处理中,也可能是转账处理失败. 返回 WAIT 状态. 后续 job 会轮询, 或相同 outBizNo 重新发起转账
|
||||
if (ObjectUtils.equalsAny(response.getSubCode(), "ORDER_NOT_EXIST", "SYSTEM_ERROR", "ACQ.SYSTEM_ERROR")) {
|
||||
@@ -323,12 +299,67 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa
|
||||
return PayTransferRespDTO.closedOf(response.getSubCode(), response.getSubMsg(),
|
||||
outTradeNo, response);
|
||||
}
|
||||
// 2.2 处理返回结果
|
||||
if (ObjectUtils.equalsAny(response.getStatus(), "REFUND", "FAIL")) { // 转账到银行卡会出现 "REFUND" "FAIL"
|
||||
return PayTransferRespDTO.closedOf(response.getSubCode(), response.getSubMsg(),
|
||||
outTradeNo, response);
|
||||
}
|
||||
if (Objects.equals(response.getStatus(), "DEALING")) { // 转账到银行卡会出现 "DEALING" 处理中
|
||||
return PayTransferRespDTO.processingOf(response.getOrderId(), outTradeNo, response);
|
||||
}
|
||||
return PayTransferRespDTO.successOf(response.getOrderId(), parseTime(response.getPayDate()),
|
||||
response.getOutBizNo(), response);
|
||||
}
|
||||
|
||||
// TODO @chihuo:这里是不是也要实现,支付宝的。
|
||||
// TODO @芋艿:由于支付宝一直没触发回调,这个方法暂时没办法测试
|
||||
@Override
|
||||
protected PayTransferRespDTO doParseTransferNotify(Map<String, String> params, String body, Map<String, String> headers) {
|
||||
throw new UnsupportedOperationException("未实现");
|
||||
protected PayTransferRespDTO doParseTransferNotify(Map<String, String> params, String body, Map<String, String> headers)
|
||||
throws Throwable {
|
||||
// 1. 校验回调数据
|
||||
verifyNotifyData(params);
|
||||
|
||||
// 2. 解析转账状态
|
||||
Map<String, String> bodyObj = HttpUtil.decodeParamMap(body, StandardCharsets.UTF_8);
|
||||
String status = bodyObj.get("status");
|
||||
String outBizNo = bodyObj.get("out_biz_no");
|
||||
String orderId = bodyObj.get("order_id");
|
||||
String payDate = bodyObj.get("pay_date");
|
||||
|
||||
// 3. 根据状态返回对应的结果
|
||||
if (Objects.equals(status, "SUCCESS")) {
|
||||
return PayTransferRespDTO.successOf(orderId, parseTime(payDate), outBizNo, bodyObj);
|
||||
}
|
||||
if (Objects.equals(status, "DEALING")) {
|
||||
return PayTransferRespDTO.processingOf(orderId, outBizNo, bodyObj);
|
||||
}
|
||||
if (ObjectUtils.equalsAny(status, "REFUND", "FAIL")) {
|
||||
return PayTransferRespDTO.closedOf(bodyObj.get("sub_code"), bodyObj.get("sub_msg"),
|
||||
outBizNo, bodyObj);
|
||||
}
|
||||
return PayTransferRespDTO.waitingOf(orderId, outBizNo, bodyObj);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验回调数据
|
||||
*
|
||||
* @param params 回调参数
|
||||
* @throws Throwable 验签失败时抛出异常
|
||||
*/
|
||||
protected void verifyNotifyData(Map<String, String> params) throws Throwable {
|
||||
boolean verify;
|
||||
if (Objects.equals(config.getMode(), MODE_PUBLIC_KEY)) {
|
||||
verify = AlipaySignature.rsaCheckV1(params, config.getAlipayPublicKey(),
|
||||
StandardCharsets.UTF_8.name(), config.getSignType());
|
||||
} else if (Objects.equals(config.getMode(), MODE_CERTIFICATE)) {
|
||||
// 由于 rsaCertCheckV1 的第二个参数是 path,所以不能这么调用!!!通过阅读源码,发现可以采用如下方式!
|
||||
X509Certificate cert = AntCertificationUtil.getCertFromContent(config.getAlipayPublicCertContent());
|
||||
String publicKey = Base64.encodeBase64String(cert.getEncoded());
|
||||
verify = AlipaySignature.rsaCheckV1(params, publicKey,
|
||||
StandardCharsets.UTF_8.name(), config.getSignType());
|
||||
} else {
|
||||
throw new IllegalArgumentException("未知的公钥类型:" + config.getMode());
|
||||
}
|
||||
Assert.isTrue(verify, "验签结果不通过");
|
||||
}
|
||||
|
||||
// ========== 各种工具方法 ==========
|
||||
|
||||
@@ -9,7 +9,6 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifie
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.NonePayClientConfig;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
@@ -78,7 +77,7 @@ public class MockPayClient extends AbstractPayClient<NonePayClientConfig> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PayTransferRespDTO doGetTransfer(String outTradeNo, PayTransferTypeEnum type) {
|
||||
protected PayTransferRespDTO doGetTransfer(String outTradeNo) {
|
||||
throw new UnsupportedOperationException("待实现");
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import cn.hutool.core.date.LocalDateTimeUtil;
|
||||
import cn.hutool.core.date.TemporalAccessorUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.io.FileUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
||||
@@ -14,17 +15,15 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.WxPayTransferPartnerNotifyV3Result;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum;
|
||||
import com.github.binarywang.wxpay.bean.notify.*;
|
||||
import com.github.binarywang.wxpay.bean.request.*;
|
||||
import com.github.binarywang.wxpay.bean.result.*;
|
||||
import com.github.binarywang.wxpay.bean.transfer.QueryTransferBatchesRequest;
|
||||
import com.github.binarywang.wxpay.bean.transfer.QueryTransferBatchesResult;
|
||||
import com.github.binarywang.wxpay.bean.transfer.TransferBatchesRequest;
|
||||
import com.github.binarywang.wxpay.bean.transfer.TransferBatchesResult;
|
||||
import com.github.binarywang.wxpay.bean.transfer.TransferBillsGetResult;
|
||||
import com.github.binarywang.wxpay.bean.transfer.TransferBillsNotifyResult;
|
||||
import com.github.binarywang.wxpay.bean.transfer.TransferBillsRequest;
|
||||
import com.github.binarywang.wxpay.bean.transfer.TransferBillsResult;
|
||||
import com.github.binarywang.wxpay.config.WxPayConfig;
|
||||
import com.github.binarywang.wxpay.exception.WxPayException;
|
||||
import com.github.binarywang.wxpay.service.WxPayService;
|
||||
@@ -33,8 +32,6 @@ import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
@@ -72,6 +69,8 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
|
||||
} else if (Objects.equals(config.getApiVersion(), API_VERSION_V3)) {
|
||||
payConfig.setPrivateKeyPath(FileUtils.createTempFile(config.getPrivateKeyContent()).getPath());
|
||||
payConfig.setPublicKeyPath(FileUtils.createTempFile(config.getPublicKeyContent()).getPath());
|
||||
// 特殊:强制使用微信公用模式,避免灰度期间的问题!!!
|
||||
payConfig.setStrictlyNeedWechatPaySerial(true);
|
||||
}
|
||||
|
||||
// 创建 client 客户端
|
||||
@@ -88,12 +87,14 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
|
||||
case API_VERSION_V2:
|
||||
return doUnifiedOrderV2(reqDTO);
|
||||
case API_VERSION_V3:
|
||||
// TODO @芋艿:【可能是 wxjava 的 bug】参考 https://github.com/binarywang/WxJava/issues/1557
|
||||
client.getConfig().setApiV3HttpClient(null);
|
||||
return doUnifiedOrderV3(reqDTO);
|
||||
default:
|
||||
throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
|
||||
}
|
||||
} catch (WxPayException e) {
|
||||
log.error("[doUnifiedOrder][退款({}) 发起微信支付异常", reqDTO, e);
|
||||
log.error("[doUnifiedOrder][支付({}) 发起微信支付异常", reqDTO, e);
|
||||
String errorCode = getErrorCode(e);
|
||||
String errorMessage = getErrorMessage(e);
|
||||
return PayOrderRespDTO.closedOf(errorCode, errorMessage,
|
||||
@@ -225,6 +226,7 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
|
||||
}
|
||||
|
||||
private PayOrderRespDTO doGetOrderV3(String outTradeNo) throws WxPayException {
|
||||
fixV3HttpClientConnectionPoolShutDown();
|
||||
// 构建 WxPayUnifiedOrderRequest 对象
|
||||
WxPayOrderQueryV3Request request = new WxPayOrderQueryV3Request()
|
||||
.setOutTradeNo(outTradeNo);
|
||||
@@ -297,6 +299,7 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
|
||||
}
|
||||
|
||||
private PayRefundRespDTO doUnifiedRefundV3(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
|
||||
fixV3HttpClientConnectionPoolShutDown();
|
||||
// 1. 构建 WxPayRefundRequest 请求
|
||||
WxPayRefundV3Request request = new WxPayRefundV3Request()
|
||||
.setOutTradeNo(reqDTO.getOutTradeNo())
|
||||
@@ -356,34 +359,6 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
|
||||
return PayRefundRespDTO.failureOf(result.getOutRefundNo(), response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayTransferRespDTO doParseTransferNotify(Map<String, String> params, String body, Map<String, String> headers) throws WxPayException {
|
||||
switch (config.getApiVersion()) {
|
||||
case API_VERSION_V3:
|
||||
return parseTransferNotifyV3(body, headers);
|
||||
case API_VERSION_V2:
|
||||
throw new UnsupportedOperationException("V2 版本暂不支持,建议使用 V3 版本");
|
||||
default:
|
||||
throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
|
||||
}
|
||||
}
|
||||
|
||||
private PayTransferRespDTO parseTransferNotifyV3(String body, Map<String, String> headers) throws WxPayException {
|
||||
// 1. 解析回调
|
||||
SignatureHeader signatureHeader = getRequestHeader(headers);
|
||||
// TODO @luchi:这个可以复用 wxjava 里的类么?
|
||||
WxPayTransferPartnerNotifyV3Result response = client.baseParseOrderNotifyV3Result(body, signatureHeader, WxPayTransferPartnerNotifyV3Result.class, WxPayTransferPartnerNotifyV3Result.TransferNotifyResult.class);
|
||||
WxPayTransferPartnerNotifyV3Result.TransferNotifyResult result = response.getResult();
|
||||
// 2. 构建结果
|
||||
if (Objects.equals("FINISHED", result.getBatchStatus())) {
|
||||
if (result.getFailNum() <= 0) {
|
||||
return PayTransferRespDTO.successOf(result.getBatchId(), parseDateV3(result.getUpdateTime()),
|
||||
result.getOutBatchNo(), response);
|
||||
}
|
||||
}
|
||||
return PayTransferRespDTO.closedOf(result.getBatchStatus(), result.getCloseReason(), result.getOutBatchNo(), response);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PayRefundRespDTO doGetRefund(String outTradeNo, String outRefundNo) throws WxPayException {
|
||||
try {
|
||||
@@ -440,6 +415,7 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
|
||||
}
|
||||
|
||||
private PayRefundRespDTO doGetRefundV3(String outTradeNo, String outRefundNo) throws WxPayException {
|
||||
fixV3HttpClientConnectionPoolShutDown();
|
||||
// 1. 构建 WxPayRefundRequest 请求
|
||||
WxPayRefundQueryV3Request request = new WxPayRefundQueryV3Request();
|
||||
request.setOutRefundNo(outRefundNo);
|
||||
@@ -463,53 +439,98 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
|
||||
|
||||
@Override
|
||||
protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) throws WxPayException {
|
||||
// 1. 构建 TransferBatchesRequest 请求
|
||||
List<TransferBatchesRequest.TransferDetail> transferDetailList = Collections.singletonList(
|
||||
TransferBatchesRequest.TransferDetail.newBuilder()
|
||||
.outDetailNo(reqDTO.getOutTransferNo())
|
||||
.transferAmount(reqDTO.getPrice())
|
||||
.transferRemark(reqDTO.getSubject())
|
||||
.openid(reqDTO.getOpenid())
|
||||
.build());
|
||||
// TODO @luchi:能不能我们搞个 TransferBatchesRequestX extends TransferBatchesRequest,这样更简洁一点。
|
||||
TransferBatchesRequest transferBatches = TransferBatchesRequest.newBuilder()
|
||||
fixV3HttpClientConnectionPoolShutDown();
|
||||
// 1. 构建 TransferBillsRequest 请求
|
||||
TransferBillsRequest request = TransferBillsRequest.newBuilder()
|
||||
.appid(this.config.getAppId())
|
||||
.outBatchNo(reqDTO.getOutTransferNo())
|
||||
.batchName(reqDTO.getSubject())
|
||||
.batchRemark(reqDTO.getSubject())
|
||||
.totalAmount(reqDTO.getPrice())
|
||||
.totalNum(transferDetailList.size())
|
||||
.transferDetailList(transferDetailList).build()
|
||||
.setNotifyUrl(reqDTO.getNotifyUrl());
|
||||
.outBillNo(reqDTO.getOutTransferNo())
|
||||
.transferAmount(reqDTO.getPrice())
|
||||
.transferRemark(reqDTO.getSubject())
|
||||
.transferSceneId(reqDTO.getChannelExtras().get("sceneId"))
|
||||
.openid(reqDTO.getUserAccount())
|
||||
.userName(reqDTO.getUserName())
|
||||
.transferSceneReportInfos(JsonUtils.parseArray(reqDTO.getChannelExtras().get("sceneReportInfos"),
|
||||
TransferBillsRequest.TransferSceneReportInfo.class))
|
||||
.notifyUrl(reqDTO.getNotifyUrl())
|
||||
.build();
|
||||
// 特殊:微信转账,必须 0.3 元起,才允许传入姓名
|
||||
if (reqDTO.getPrice() < 30) {
|
||||
request.setUserName(null);
|
||||
}
|
||||
|
||||
// 2.1 执行请求
|
||||
TransferBatchesResult transferBatchesResult = client.getTransferService().transferBatches(transferBatches);
|
||||
// 2.2 创建返回结果
|
||||
return PayTransferRespDTO.dealingOf(transferBatchesResult.getBatchId(), reqDTO.getOutTransferNo(), transferBatchesResult);
|
||||
try {
|
||||
TransferBillsResult response = client.getTransferService().transferBills(request);
|
||||
|
||||
// 2.2 创建返回结果
|
||||
String state = response.getState();
|
||||
if (ObjectUtils.equalsAny(state, "ACCEPTED", "PROCESSING", "WAIT_USER_CONFIRM", "TRANSFERING")) {
|
||||
return PayTransferRespDTO.processingOf(response.getTransferBillNo(), response.getOutBillNo(), response)
|
||||
.setChannelPackageInfo(response.getPackageInfo()); // 一般情况下,只有 WAIT_USER_CONFIRM 会有!
|
||||
}
|
||||
if (Objects.equals("SUCCESS", state)) {
|
||||
return PayTransferRespDTO.successOf(response.getTransferBillNo(), parseDateV3(response.getCreateTime()),
|
||||
response.getOutBillNo(), response);
|
||||
}
|
||||
return PayTransferRespDTO.closedOf(state, response.getFailReason(),
|
||||
response.getOutBillNo(), response);
|
||||
} catch (WxPayException e) {
|
||||
log.error("[doUnifiedTransfer][转账({}) 发起微信支付异常", reqDTO, e);
|
||||
String errorCode = getErrorCode(e);
|
||||
String errorMessage = getErrorMessage(e);
|
||||
return PayTransferRespDTO.closedOf(errorCode, errorMessage,
|
||||
reqDTO.getOutTransferNo(), e.getXmlString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PayTransferRespDTO doGetTransfer(String outTradeNo, PayTransferTypeEnum type) throws WxPayException {
|
||||
QueryTransferBatchesRequest request = QueryTransferBatchesRequest.newBuilder()
|
||||
.outBatchNo(outTradeNo).needQueryDetail(true).offset(0).limit(20).detailStatus("ALL")
|
||||
.build();
|
||||
QueryTransferBatchesResult response = client.getTransferService().transferBatchesOutBatchNo(request);
|
||||
QueryTransferBatchesResult.TransferBatch transferBatch = response.getTransferBatch();
|
||||
if (Objects.equals("FINISHED", transferBatch.getBatchStatus())) {
|
||||
// 明细中全部成功则成功,任一失败则失败
|
||||
if (response.getTransferDetailList().stream().allMatch(detail -> Objects.equals("SUCCESS", detail.getDetailStatus()))) {
|
||||
return PayTransferRespDTO.successOf(transferBatch.getBatchId(), parseDateV3(transferBatch.getUpdateTime()),
|
||||
transferBatch.getOutBatchNo(), response);
|
||||
}
|
||||
if (response.getTransferDetailList().stream().anyMatch(detail -> Objects.equals("FAIL", detail.getDetailStatus()))) {
|
||||
return PayTransferRespDTO.closedOf(transferBatch.getBatchStatus(), transferBatch.getCloseReason(),
|
||||
transferBatch.getOutBatchNo(), response);
|
||||
}
|
||||
protected PayTransferRespDTO doGetTransfer(String outTradeNo) throws WxPayException {
|
||||
fixV3HttpClientConnectionPoolShutDown();
|
||||
// 1. 执行请求
|
||||
TransferBillsGetResult response = client.getTransferService().getBillsByOutBillNo(outTradeNo);
|
||||
|
||||
// 2. 创建返回结果
|
||||
String state = response.getState();
|
||||
if (ObjectUtils.equalsAny(state, "ACCEPTED", "PROCESSING", "WAIT_USER_CONFIRM", "TRANSFERING")) {
|
||||
return PayTransferRespDTO.processingOf(response.getTransferBillNo(), response.getOutBillNo(), response);
|
||||
}
|
||||
if (Objects.equals("CLOSED", transferBatch.getBatchStatus())) {
|
||||
return PayTransferRespDTO.closedOf(transferBatch.getBatchStatus(), transferBatch.getCloseReason(),
|
||||
transferBatch.getOutBatchNo(), response);
|
||||
if (Objects.equals("SUCCESS", state)) {
|
||||
return PayTransferRespDTO.successOf(response.getTransferBillNo(), parseDateV3(response.getUpdateTime()),
|
||||
response.getOutBillNo(), response);
|
||||
}
|
||||
return PayTransferRespDTO.dealingOf(transferBatch.getBatchId(), transferBatch.getOutBatchNo(), response);
|
||||
return PayTransferRespDTO.closedOf(state, response.getFailReason(),
|
||||
response.getOutBillNo(), response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayTransferRespDTO doParseTransferNotify(Map<String, String> params, String body, Map<String, String> headers) throws WxPayException {
|
||||
switch (config.getApiVersion()) {
|
||||
case API_VERSION_V3:
|
||||
return parseTransferNotifyV3(body, headers);
|
||||
case API_VERSION_V2:
|
||||
throw new UnsupportedOperationException("V2 版本暂不支持,建议使用 V3 版本");
|
||||
default:
|
||||
throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
|
||||
}
|
||||
}
|
||||
|
||||
private PayTransferRespDTO parseTransferNotifyV3(String body, Map<String, String> headers) throws WxPayException {
|
||||
// 1. 解析回调
|
||||
SignatureHeader signatureHeader = getRequestHeader(headers);
|
||||
TransferBillsNotifyResult response = client.getTransferService().parseTransferBillsNotifyResult(body, signatureHeader);
|
||||
TransferBillsNotifyResult.DecryptNotifyResult result = response.getResult();
|
||||
|
||||
// 2. 创建返回结果
|
||||
String state = result.getState();
|
||||
if (ObjectUtils.equalsAny(state, "ACCEPTED", "PROCESSING", "WAIT_USER_CONFIRM", "TRANSFERING")) {
|
||||
return PayTransferRespDTO.processingOf(result.getTransferBillNo(), result.getOutBillNo(), response);
|
||||
}
|
||||
if (Objects.equals("SUCCESS", state)) {
|
||||
return PayTransferRespDTO.successOf(result.getTransferBillNo(), parseDateV3(result.getUpdateTime()),
|
||||
result.getOutBillNo(), response);
|
||||
}
|
||||
return PayTransferRespDTO.closedOf(state, result.getFailReason(),
|
||||
result.getOutBillNo(), response);
|
||||
}
|
||||
|
||||
// ========== 各种工具方法 ==========
|
||||
@@ -528,6 +549,11 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
|
||||
.build();
|
||||
}
|
||||
|
||||
// TODO @芋艿:可能是 wxjava 的 bug:https://github.com/binarywang/WxJava/issues/1557
|
||||
private void fixV3HttpClientConnectionPoolShutDown() {
|
||||
client.getConfig().setApiV3HttpClient(null);
|
||||
}
|
||||
|
||||
static String formatDateV2(LocalDateTime time) {
|
||||
return TemporalAccessorUtil.format(time.atZone(ZoneId.systemDefault()), PURE_DATETIME_PATTERN);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum;
|
||||
import com.github.binarywang.wxpay.bean.order.WxPayMpOrderResult;
|
||||
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
|
||||
@@ -28,10 +27,6 @@ import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString
|
||||
@Slf4j
|
||||
public class WxPubPayClient extends AbstractWxPayClient {
|
||||
|
||||
public WxPubPayClient(Long channelId, WxPayClientConfig config) {
|
||||
super(channelId, PayChannelEnum.WX_PUB.getCode(), config);
|
||||
}
|
||||
|
||||
protected WxPubPayClient(Long channelId, String channelCode, WxPayClientConfig config) {
|
||||
super(channelId, channelCode, config);
|
||||
}
|
||||
|
||||
@@ -15,18 +15,9 @@ import java.util.Objects;
|
||||
public enum PayTransferStatusRespEnum {
|
||||
|
||||
WAITING(0, "等待转账"),
|
||||
|
||||
/**
|
||||
* TODO 转账到银行卡. 会有T+0 T+1 到账的请情况。 还未实现
|
||||
* TODO @jason:可以看看其它开源项目,针对这个场景,处理策略是怎么样的?例如说,每天主动轮询?这个状态的单子?
|
||||
*/
|
||||
IN_PROGRESS(10, "转账进行中"),
|
||||
|
||||
SUCCESS(20, "转账成功"),
|
||||
/**
|
||||
* 转账关闭 (失败,或者其它情况)
|
||||
*/
|
||||
CLOSED(30, "转账关闭");
|
||||
PROCESSING(5, "转账进行中"),
|
||||
SUCCESS(10, "转账成功"),
|
||||
CLOSED(20, "转账关闭");
|
||||
|
||||
private final Integer status;
|
||||
private final String name;
|
||||
@@ -39,7 +30,8 @@ public enum PayTransferStatusRespEnum {
|
||||
return Objects.equals(status, CLOSED.getStatus());
|
||||
}
|
||||
|
||||
public static boolean isInProgress(Integer status) {
|
||||
return Objects.equals(status, IN_PROGRESS.getStatus());
|
||||
public static boolean isProcessing(Integer status) {
|
||||
return Objects.equals(status, PROCESSING.getStatus());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
package cn.iocoder.yudao.framework.pay.core.enums.transfer;
|
||||
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* 转账类型枚举
|
||||
*
|
||||
* @author jason
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum PayTransferTypeEnum implements ArrayValuable<Integer> {
|
||||
|
||||
ALIPAY_BALANCE(1, "支付宝余额"),
|
||||
WX_BALANCE(2, "微信余额"),
|
||||
BANK_CARD(3, "银行卡"),
|
||||
WALLET_BALANCE(4, "钱包余额");
|
||||
|
||||
public interface WxPay {
|
||||
}
|
||||
|
||||
public interface Alipay {
|
||||
}
|
||||
|
||||
private final Integer type;
|
||||
private final String name;
|
||||
|
||||
public static final Integer[] ARRAYS = Arrays.stream(values()).map(PayTransferTypeEnum::getType).toArray(Integer[]::new);
|
||||
|
||||
@Override
|
||||
public Integer[] array() {
|
||||
return ARRAYS;
|
||||
}
|
||||
|
||||
public static PayTransferTypeEnum typeOf(Integer type) {
|
||||
return ArrayUtil.firstMatch(item -> item.getType().equals(type), values());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
package com.github.binarywang.wxpay.bean.transfer;
|
||||
|
||||
import com.github.binarywang.wxpay.v3.SpecEncrypt;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 发起商家转账API参数
|
||||
*
|
||||
* @author zhongjun
|
||||
* created on 2022/6/17
|
||||
**/
|
||||
@Data
|
||||
@Builder(builderMethodName = "newBuilder")
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class TransferBatchesRequest implements Serializable {
|
||||
private static final long serialVersionUID = -2175582517588397426L;
|
||||
|
||||
/**
|
||||
* 直连商户的appid
|
||||
*/
|
||||
@SerializedName("appid")
|
||||
private String appid;
|
||||
|
||||
/**
|
||||
* 商家批次单号
|
||||
*/
|
||||
@SerializedName("out_batch_no")
|
||||
private String outBatchNo;
|
||||
|
||||
/**
|
||||
* 批次名称
|
||||
*/
|
||||
@SerializedName("batch_name")
|
||||
private String batchName;
|
||||
|
||||
/**
|
||||
* 批次备注
|
||||
*/
|
||||
@SerializedName("batch_remark")
|
||||
private String batchRemark;
|
||||
|
||||
/**
|
||||
* 转账总金额
|
||||
*/
|
||||
@SerializedName("total_amount")
|
||||
private Integer totalAmount;
|
||||
|
||||
/**
|
||||
* 转账总笔数
|
||||
*/
|
||||
@SerializedName("total_num")
|
||||
private Integer totalNum;
|
||||
|
||||
/**
|
||||
* 转账明细列表
|
||||
*/
|
||||
@SpecEncrypt
|
||||
@SerializedName("transfer_detail_list")
|
||||
private List<TransferDetail> transferDetailList;
|
||||
|
||||
/**
|
||||
* 转账场景ID
|
||||
*/
|
||||
@SerializedName("transfer_scene_id")
|
||||
private String transferSceneId;
|
||||
|
||||
/**
|
||||
* 通知地址 说明:异步接收微信支付结果通知的回调地址,通知url必须为公网可访问的url,必须为https,不能携带参数。
|
||||
*/
|
||||
@SerializedName("notify_url")
|
||||
private String notifyUrl;
|
||||
|
||||
@Data
|
||||
@Builder(builderMethodName = "newBuilder")
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public static class TransferDetail {
|
||||
|
||||
/**
|
||||
* 商家明细单号
|
||||
*/
|
||||
@SerializedName("out_detail_no")
|
||||
private String outDetailNo;
|
||||
|
||||
/**
|
||||
* 转账金额
|
||||
*/
|
||||
@SerializedName("transfer_amount")
|
||||
private Integer transferAmount;
|
||||
|
||||
/**
|
||||
* 转账备注
|
||||
*/
|
||||
@SerializedName("transfer_remark")
|
||||
private String transferRemark;
|
||||
|
||||
/**
|
||||
* 用户在直连商户应用下的用户标示
|
||||
*/
|
||||
@SerializedName("openid")
|
||||
private String openid;
|
||||
|
||||
/**
|
||||
* 收款用户姓名
|
||||
*/
|
||||
@SpecEncrypt
|
||||
@SerializedName("user_name")
|
||||
private String userName;
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,7 @@ public class PayClientFactoryImplIntegrationTest {
|
||||
// 创建客户端
|
||||
Long channelId = RandomUtil.randomLong();
|
||||
payClientFactory.createOrUpdatePayClient(channelId, PayChannelEnum.WX_PUB.getCode(), config);
|
||||
PayClient client = payClientFactory.getPayClient(channelId);
|
||||
PayClient<?> client = payClientFactory.getPayClient(channelId);
|
||||
// 发起支付
|
||||
PayOrderUnifiedReqDTO reqDTO = buildPayOrderUnifiedReqDTO();
|
||||
// CommonResult<?> result = client.unifiedOrder(reqDTO);
|
||||
@@ -60,12 +60,12 @@ public class PayClientFactoryImplIntegrationTest {
|
||||
config.setMchId("1545083881");
|
||||
config.setApiVersion(WxPayClientConfig.API_VERSION_V3);
|
||||
config.setPrivateKeyContent(IoUtil.readUtf8(new FileInputStream("/Users/yunai/Downloads/wx_pay/apiclient_key.pem")));
|
||||
config.setPrivateCertContent(IoUtil.readUtf8(new FileInputStream("/Users/yunai/Downloads/wx_pay/apiclient_cert.pem")));
|
||||
// config.setPrivateCertContent(IoUtil.readUtf8(new FileInputStream("/Users/yunai/Downloads/wx_pay/apiclient_cert.pem")));
|
||||
config.setApiV3Key("joerVi8y5DJ3o4ttA0o1uH47Xz1u2Ase");
|
||||
// 创建客户端
|
||||
Long channelId = RandomUtil.randomLong();
|
||||
payClientFactory.createOrUpdatePayClient(channelId, PayChannelEnum.WX_PUB.getCode(), config);
|
||||
PayClient client = payClientFactory.getPayClient(channelId);
|
||||
PayClient<?> client = payClientFactory.getPayClient(channelId);
|
||||
// 发起支付
|
||||
PayOrderUnifiedReqDTO reqDTO = buildPayOrderUnifiedReqDTO();
|
||||
// CommonResult<?> result = client.unifiedOrder(reqDTO);
|
||||
@@ -88,7 +88,7 @@ public class PayClientFactoryImplIntegrationTest {
|
||||
// 创建客户端
|
||||
Long channelId = RandomUtil.randomLong();
|
||||
payClientFactory.createOrUpdatePayClient(channelId, PayChannelEnum.ALIPAY_QR.getCode(), config);
|
||||
PayClient client = payClientFactory.getPayClient(channelId);
|
||||
PayClient<?> client = payClientFactory.getPayClient(channelId);
|
||||
// 发起支付
|
||||
PayOrderUnifiedReqDTO reqDTO = buildPayOrderUnifiedReqDTO();
|
||||
reqDTO.setNotifyUrl("http://yunai.natapp1.cc/admin-api/pay/notify/callback/18"); // TODO @tina: 这里改成你的 natapp 回调地址
|
||||
@@ -112,7 +112,7 @@ public class PayClientFactoryImplIntegrationTest {
|
||||
// 创建客户端
|
||||
Long channelId = RandomUtil.randomLong();
|
||||
payClientFactory.createOrUpdatePayClient(channelId, PayChannelEnum.ALIPAY_WAP.getCode(), config);
|
||||
PayClient client = payClientFactory.getPayClient(channelId);
|
||||
PayClient<?> client = payClientFactory.getPayClient(channelId);
|
||||
// 发起支付
|
||||
PayOrderUnifiedReqDTO reqDTO = buildPayOrderUnifiedReqDTO();
|
||||
// CommonResult<?> result = client.unifiedOrder(reqDTO);
|
||||
|
||||
Reference in New Issue
Block a user