Merge branch 'master-jdk17' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into feature/iot
# Conflicts: # yudao-dependencies/pom.xml # yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotProductStatusEnum.java # yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/thingmodel/IotThingModelTypeEnum.java
This commit is contained in:
@@ -13,6 +13,7 @@ import jakarta.validation.constraints.NotNull;
|
||||
@Data
|
||||
public class PayTransferNotifyReqDTO {
|
||||
|
||||
// TODO 芋艿:要不要改成 orderId 待定;
|
||||
/**
|
||||
* 商户转账单号
|
||||
*/
|
||||
@@ -24,4 +25,5 @@ public class PayTransferNotifyReqDTO {
|
||||
*/
|
||||
@NotNull(message = "转账订单编号不能为空")
|
||||
private Long payTransferId;
|
||||
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.pay.api.order.dto;
|
||||
import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 支付单信息 Response DTO
|
||||
*
|
||||
@@ -41,6 +43,11 @@ public class PayOrderRespDTO {
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 订单支付成功时间
|
||||
*/
|
||||
private LocalDateTime successTime;
|
||||
|
||||
// ========== 渠道相关字段 ==========
|
||||
|
||||
}
|
||||
|
||||
@@ -18,6 +18,13 @@ public class PayRefundRespDTO {
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 渠道编码
|
||||
*
|
||||
* 枚举 PayChannelEnum
|
||||
*/
|
||||
private String channelCode;
|
||||
|
||||
// ========== 退款相关字段 ==========
|
||||
/**
|
||||
* 退款状态
|
||||
|
||||
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.pay.api.transfer;
|
||||
|
||||
import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateReqDTO;
|
||||
|
||||
import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferRespDTO;
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
/**
|
||||
@@ -19,4 +20,12 @@ public interface PayTransferApi {
|
||||
*/
|
||||
Long createTransfer(@Valid PayTransferCreateReqDTO reqDTO);
|
||||
|
||||
/**
|
||||
* 获得转账单
|
||||
*
|
||||
* @param id 转账单编号
|
||||
* @return 转账单
|
||||
*/
|
||||
PayTransferRespDTO getTransfer(Long id);
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package cn.iocoder.yudao.module.pay.api.transfer.dto;
|
||||
|
||||
import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferStatusEnum;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class PayTransferRespDTO {
|
||||
|
||||
/**
|
||||
* 编号
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 转账单号
|
||||
*/
|
||||
private String no;
|
||||
|
||||
/**
|
||||
* 转账金额,单位:分
|
||||
*/
|
||||
private Integer price;
|
||||
|
||||
/**
|
||||
* 转账状态
|
||||
*
|
||||
* 枚举 {@link PayTransferStatusEnum}
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package cn.iocoder.yudao.module.pay.api.wallet;
|
||||
|
||||
import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletAddBalanceReqDTO;
|
||||
|
||||
/**
|
||||
* 钱包 API 接口
|
||||
*
|
||||
* @author liurulin
|
||||
*/
|
||||
public interface PayWalletApi {
|
||||
|
||||
/**
|
||||
* 添加钱包余额
|
||||
*
|
||||
* @param reqDTO 增加余额请求
|
||||
*/
|
||||
void addWalletBalance(PayWalletAddBalanceReqDTO reqDTO);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package cn.iocoder.yudao.module.pay.api.wallet.dto;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 钱包余额增加 Request DTO
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
public class PayWalletAddBalanceReqDTO {
|
||||
|
||||
/**
|
||||
* 用户编号
|
||||
*
|
||||
* 关联 MemberUserDO 的 id 属性,或者 AdminUserDO 的 id 属性
|
||||
*/
|
||||
@NotNull(message = "用户编号不能为空")
|
||||
private Long userId;
|
||||
/**
|
||||
* 用户类型
|
||||
*
|
||||
* 关联 {@link UserTypeEnum}
|
||||
*/
|
||||
@NotNull(message = "用户类型不能为空")
|
||||
private Integer userType;
|
||||
|
||||
/**
|
||||
* 关联业务分类
|
||||
*/
|
||||
@NotNull(message = "关联业务分类不能为空")
|
||||
private Integer bizType;
|
||||
/**
|
||||
* 关联业务编号
|
||||
*/
|
||||
@NotNull(message = "关联业务编号不能为空")
|
||||
private String bizId;
|
||||
|
||||
/**
|
||||
* 交易金额,单位分
|
||||
*
|
||||
* 正值表示余额增加,负值表示余额减少
|
||||
*/
|
||||
@NotNull(message = "交易金额不能为空")
|
||||
private Integer price;
|
||||
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package cn.iocoder.yudao.module.pay.enums.order;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
|
||||
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
|
||||
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
@@ -14,7 +14,7 @@ import java.util.Objects;
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum PayOrderStatusEnum implements IntArrayValuable {
|
||||
public enum PayOrderStatusEnum implements ArrayValuable<Integer> {
|
||||
|
||||
WAITING(0, "未支付"),
|
||||
SUCCESS(10, "支付成功"),
|
||||
@@ -26,8 +26,18 @@ public enum PayOrderStatusEnum implements IntArrayValuable {
|
||||
private final String name;
|
||||
|
||||
@Override
|
||||
public int[] array() {
|
||||
return new int[0];
|
||||
public Integer[] array() {
|
||||
return new Integer[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否等待支付
|
||||
*
|
||||
* @param status 状态
|
||||
* @return 是否等待支付
|
||||
*/
|
||||
public static boolean isWaiting(Integer status) {
|
||||
return Objects.equals(status, WAITING.getStatus());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package cn.iocoder.yudao.module.pay.enums.transfer;
|
||||
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
|
||||
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@@ -14,7 +14,7 @@ import java.util.Arrays;
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum PayTransferTypeEnum implements IntArrayValuable {
|
||||
public enum PayTransferTypeEnum implements ArrayValuable<Integer> {
|
||||
|
||||
ALIPAY_BALANCE(1, "支付宝余额"),
|
||||
WX_BALANCE(2, "微信余额"),
|
||||
@@ -30,10 +30,10 @@ public enum PayTransferTypeEnum implements IntArrayValuable {
|
||||
private final Integer type;
|
||||
private final String name;
|
||||
|
||||
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(PayTransferTypeEnum::getType).toArray();
|
||||
public static final Integer[] ARRAYS = Arrays.stream(values()).map(PayTransferTypeEnum::getType).toArray(Integer[]::new);
|
||||
|
||||
@Override
|
||||
public int[] array() {
|
||||
public Integer[] array() {
|
||||
return ARRAYS;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package cn.iocoder.yudao.module.pay.enums.wallet;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
|
||||
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@@ -13,13 +13,14 @@ import java.util.Arrays;
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum PayWalletBizTypeEnum implements IntArrayValuable {
|
||||
public enum PayWalletBizTypeEnum implements ArrayValuable<Integer> {
|
||||
|
||||
RECHARGE(1, "充值"),
|
||||
RECHARGE_REFUND(2, "充值退款"),
|
||||
PAYMENT(3, "支付"),
|
||||
PAYMENT_REFUND(4, "支付退款"),
|
||||
UPDATE_BALANCE(5, "更新余额");
|
||||
UPDATE_BALANCE(5, "更新余额"),
|
||||
BROKERAGE_WITHDRAW(6, "分佣提现");
|
||||
|
||||
/**
|
||||
* 业务分类
|
||||
@@ -30,10 +31,15 @@ public enum PayWalletBizTypeEnum implements IntArrayValuable {
|
||||
*/
|
||||
private final String description;
|
||||
|
||||
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(PayWalletBizTypeEnum::getType).toArray();
|
||||
public static final Integer[] ARRAYS = Arrays.stream(values()).map(PayWalletBizTypeEnum::getType).toArray(Integer[]::new);
|
||||
|
||||
@Override
|
||||
public int[] array() {
|
||||
public Integer[] array() {
|
||||
return ARRAYS;
|
||||
}
|
||||
|
||||
public static PayWalletBizTypeEnum valueOf(Integer type) {
|
||||
return Arrays.stream(values()).filter(item -> item.getType().equals(type)).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
package cn.iocoder.yudao.module.pay.api.transfer;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferRespDTO;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO;
|
||||
import cn.iocoder.yudao.module.pay.service.transfer.PayTransferService;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 转账单 API 实现类
|
||||
*
|
||||
@@ -24,4 +26,10 @@ public class PayTransferApiImpl implements PayTransferApi {
|
||||
return payTransferService.createTransfer(reqDTO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayTransferRespDTO getTransfer(Long id) {
|
||||
PayTransferDO transfer = payTransferService.getTransfer(id);
|
||||
return BeanUtils.toBean(transfer, PayTransferRespDTO.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package cn.iocoder.yudao.module.pay.api.wallet;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletAddBalanceReqDTO;
|
||||
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;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 钱包 API 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Service
|
||||
public class PayWalletApiImpl implements PayWalletApi {
|
||||
|
||||
@Resource
|
||||
private PayWalletService payWalletService;
|
||||
|
||||
@Override
|
||||
public void addWalletBalance(PayWalletAddBalanceReqDTO reqDTO) {
|
||||
// 创建或获取钱包
|
||||
PayWalletDO wallet = payWalletService.getOrCreateWallet(reqDTO.getUserId(), reqDTO.getUserType());
|
||||
Assert.notNull(wallet, "钱包({}/{})不存在", reqDTO.getUserId(), reqDTO.getUserType());
|
||||
|
||||
// 增加余额
|
||||
PayWalletBizTypeEnum bizType = PayWalletBizTypeEnum.valueOf(reqDTO.getBizType());
|
||||
payWalletService.addWalletBalance(wallet.getId(), reqDTO.getBizId(), bizType, reqDTO.getPrice());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -40,4 +40,8 @@ public class PayAppBaseVO {
|
||||
@URL(message = "退款结果的回调地址必须为 URL 格式")
|
||||
private String refundNotifyUrl;
|
||||
|
||||
@Schema(description = "转账结果的回调地址", example = "http://127.0.0.1:48080/transfer-callback")
|
||||
@URL(message = "转账结果的回调地址必须为 URL 格式")
|
||||
private String transferNotifyUrl;
|
||||
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
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;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO;
|
||||
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;
|
||||
@@ -18,6 +19,7 @@ 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 cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
|
||||
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;
|
||||
@@ -49,6 +51,8 @@ public class PayNotifyController {
|
||||
@Resource
|
||||
private PayRefundService refundService;
|
||||
@Resource
|
||||
private PayTransferService payTransferService;
|
||||
@Resource
|
||||
private PayNotifyService notifyService;
|
||||
@Resource
|
||||
private PayAppService appService;
|
||||
@@ -65,7 +69,7 @@ public class PayNotifyController {
|
||||
// 1. 校验支付渠道是否存在
|
||||
PayClient payClient = channelService.getPayClient(channelId);
|
||||
if (payClient == null) {
|
||||
log.error("[notifyCallback][渠道编号({}) 找不到对应的支付客户端]", channelId);
|
||||
log.error("[notifyOrder][渠道编号({}) 找不到对应的支付客户端]", channelId);
|
||||
throw exception(CHANNEL_NOT_FOUND);
|
||||
}
|
||||
|
||||
@@ -79,13 +83,13 @@ public class PayNotifyController {
|
||||
@Operation(summary = "支付渠道的统一【退款】回调")
|
||||
@PermitAll
|
||||
public String notifyRefund(@PathVariable("channelId") Long channelId,
|
||||
@RequestParam(required = false) Map<String, String> params,
|
||||
@RequestBody(required = false) String body) {
|
||||
@RequestParam(required = false) Map<String, String> params,
|
||||
@RequestBody(required = false) String body) {
|
||||
log.info("[notifyRefund][channelId({}) 回调数据({}/{})]", channelId, params, body);
|
||||
// 1. 校验支付渠道是否存在
|
||||
PayClient payClient = channelService.getPayClient(channelId);
|
||||
if (payClient == null) {
|
||||
log.error("[notifyCallback][渠道编号({}) 找不到对应的支付客户端]", channelId);
|
||||
log.error("[notifyRefund][渠道编号({}) 找不到对应的支付客户端]", channelId);
|
||||
throw exception(CHANNEL_NOT_FOUND);
|
||||
}
|
||||
|
||||
@@ -95,6 +99,26 @@ public class PayNotifyController {
|
||||
return "success";
|
||||
}
|
||||
|
||||
@PostMapping(value = "/transfer/{channelId}")
|
||||
@Operation(summary = "支付渠道的统一【转账】回调")
|
||||
@PermitAll
|
||||
public String notifyTransfer(@PathVariable("channelId") Long channelId,
|
||||
@RequestParam(required = false) Map<String, String> params,
|
||||
@RequestBody(required = false) String body) {
|
||||
log.info("[notifyTransfer][channelId({}) 回调数据({}/{})]", channelId, params, body);
|
||||
// 1. 校验支付渠道是否存在
|
||||
PayClient payClient = channelService.getPayClient(channelId);
|
||||
if (payClient == null) {
|
||||
log.error("[notifyTransfer][渠道编号({}) 找不到对应的支付客户端]", channelId);
|
||||
throw exception(CHANNEL_NOT_FOUND);
|
||||
}
|
||||
|
||||
// 2. 解析通知数据
|
||||
PayTransferRespDTO notify = payClient.parseTransferNotify(params, body);
|
||||
payTransferService.notifyTransfer(channelId, notify);
|
||||
return "success";
|
||||
}
|
||||
|
||||
@GetMapping("/get-detail")
|
||||
@Operation(summary = "获得回调通知的明细")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
|
||||
@@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollectionUtil;
|
||||
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.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.*;
|
||||
@@ -11,12 +12,14 @@ 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.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 com.google.common.collect.Maps;
|
||||
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.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
@@ -51,10 +54,21 @@ public class PayOrderController {
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得支付订单")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
@Parameters({
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024"),
|
||||
@Parameter(name = "sync", description = "是否同步", example = "true")
|
||||
})
|
||||
@PreAuthorize("@ss.hasPermission('pay:order:query')")
|
||||
public CommonResult<PayOrderRespVO> getOrder(@RequestParam("id") Long id) {
|
||||
return success(PayOrderConvert.INSTANCE.convert(orderService.getOrder(id)));
|
||||
public CommonResult<PayOrderRespVO> getOrder(@RequestParam("id") Long id,
|
||||
@RequestParam(value = "sync", required = false) Boolean sync) {
|
||||
PayOrderDO order = orderService.getOrder(id);
|
||||
// sync 仅在等待支付
|
||||
if (Boolean.TRUE.equals(sync) && PayOrderStatusEnum.isWaiting(order.getStatus())) {
|
||||
orderService.syncOrderQuietly(order.getId());
|
||||
// 重新查询,因为同步后,可能会有变化
|
||||
order = orderService.getOrder(id);
|
||||
}
|
||||
return success(BeanUtils.toBean(order, PayOrderRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/get-detail")
|
||||
|
||||
@@ -6,13 +6,13 @@ import cn.iocoder.yudao.module.pay.service.channel.PayChannelService;
|
||||
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 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 jakarta.annotation.Resource;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
@@ -1,24 +1,27 @@
|
||||
package cn.iocoder.yudao.module.pay.controller.app.order;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderRespVO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitRespVO;
|
||||
import cn.iocoder.yudao.module.pay.controller.app.order.vo.AppPayOrderSubmitReqVO;
|
||||
import cn.iocoder.yudao.module.pay.controller.app.order.vo.AppPayOrderSubmitRespVO;
|
||||
import cn.iocoder.yudao.module.pay.convert.order.PayOrderConvert;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
|
||||
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 com.google.common.collect.Maps;
|
||||
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.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
@@ -37,12 +40,22 @@ public class AppPayOrderController {
|
||||
@Resource
|
||||
private PayOrderService payOrderService;
|
||||
|
||||
// TODO 芋艿:临时 demo,技术打样。
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得支付订单")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
public CommonResult<PayOrderRespVO> getOrder(@RequestParam("id") Long id) {
|
||||
return success(PayOrderConvert.INSTANCE.convert(payOrderService.getOrder(id)));
|
||||
@Parameters({
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024"),
|
||||
@Parameter(name = "sync", description = "是否同步", example = "true")
|
||||
})
|
||||
public CommonResult<PayOrderRespVO> getOrder(@RequestParam("id") Long id,
|
||||
@RequestParam(value = "sync", required = false) Boolean sync) {
|
||||
PayOrderDO order = payOrderService.getOrder(id);
|
||||
// sync 仅在等待支付
|
||||
if (Boolean.TRUE.equals(sync) && PayOrderStatusEnum.isWaiting(order.getStatus())) {
|
||||
payOrderService.syncOrderQuietly(order.getId());
|
||||
// 重新查询,因为同步后,可能会有变化
|
||||
order = payOrderService.getOrder(id);
|
||||
}
|
||||
return success(BeanUtils.toBean(order, PayOrderRespVO.class));
|
||||
}
|
||||
|
||||
@PostMapping("/submit")
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
/**
|
||||
* TODO 芋艿:占个位置,没啥用
|
||||
*/
|
||||
package cn.iocoder.yudao.module.pay.controller.app.refund;
|
||||
@@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.pay.controller.app.wallet;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
|
||||
import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.wallet.AppPayWalletRespVO;
|
||||
import cn.iocoder.yudao.module.pay.convert.wallet.PayWalletConvert;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO;
|
||||
@@ -35,7 +34,6 @@ public class AppPayWalletController {
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获取钱包")
|
||||
@PreAuthenticated
|
||||
public CommonResult<AppPayWalletRespVO> getPayWallet() {
|
||||
PayWalletDO wallet = payWalletService.getOrCreateWallet(getLoginUserId(), UserTypeEnum.MEMBER.getValue());
|
||||
return success(PayWalletConvert.INSTANCE.convert(wallet));
|
||||
|
||||
@@ -24,7 +24,8 @@ public interface PayTransferConvert {
|
||||
|
||||
PayTransferCreateReqDTO convert(PayDemoTransferCreateReqVO vo);
|
||||
|
||||
PayTransferRespVO convert(PayTransferDO bean);
|
||||
PayTransferRespVO convert(PayTransferDO bean);
|
||||
|
||||
PageResult<PayTransferPageItemRespVO> convertPage(PageResult<PayTransferDO> pageResult);
|
||||
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ public class PayRefundDO extends BaseDO {
|
||||
*/
|
||||
private Long channelId;
|
||||
/**
|
||||
* 商户编码
|
||||
* 渠道编码
|
||||
*
|
||||
* 枚举 {@link PayChannelEnum}
|
||||
*/
|
||||
|
||||
@@ -24,6 +24,11 @@ public interface PayOrderExtensionMapper extends BaseMapperX<PayOrderExtensionDO
|
||||
return selectList(PayOrderExtensionDO::getOrderId, orderId);
|
||||
}
|
||||
|
||||
default List<PayOrderExtensionDO> selectListByOrderIdAndStatus(Long orderId, Integer status) {
|
||||
return selectList(PayOrderExtensionDO::getOrderId, orderId,
|
||||
PayOrderExtensionDO::getStatus, status);
|
||||
}
|
||||
|
||||
default List<PayOrderExtensionDO> selectListByStatusAndCreateTimeGe(Integer status, LocalDateTime minCreateTime) {
|
||||
return selectList(new LambdaQueryWrapper<PayOrderExtensionDO>()
|
||||
.eq(PayOrderExtensionDO::getStatus, status)
|
||||
|
||||
@@ -23,10 +23,6 @@ public interface PayTransferMapper extends BaseMapperX<PayTransferDO> {
|
||||
PayTransferDO::getMerchantTransferId, merchantTransferId);
|
||||
}
|
||||
|
||||
default PayTransferDO selectByNo(String no){
|
||||
return selectOne(PayTransferDO::getNo, no);
|
||||
}
|
||||
|
||||
default PageResult<PayTransferDO> selectPage(PayTransferPageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<PayTransferDO>()
|
||||
.eqIfPresent(PayTransferDO::getNo, reqVO.getNo())
|
||||
@@ -41,9 +37,15 @@ public interface PayTransferMapper extends BaseMapperX<PayTransferDO> {
|
||||
.orderByDesc(PayTransferDO::getId));
|
||||
}
|
||||
|
||||
default List<PayTransferDO> selectListByStatus(Integer status){
|
||||
default List<PayTransferDO> selectListByStatus(Integer status) {
|
||||
return selectList(PayTransferDO::getStatus, status);
|
||||
}
|
||||
|
||||
default PayTransferDO selectByAppIdAndNo(Long appId, String no) {
|
||||
return selectOne(PayTransferDO::getAppId, appId,
|
||||
PayTransferDO::getNo, no);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -68,6 +68,19 @@ public interface PayWalletMapper extends BaseMapperX<PayWalletDO> {
|
||||
return update(null, lambdaUpdateWrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加余额的时候,更新钱包
|
||||
*
|
||||
* @param id 钱包 id
|
||||
* @param price 钱包金额
|
||||
*/
|
||||
default void updateWhenAdd(Long id, Integer price) {
|
||||
LambdaUpdateWrapper<PayWalletDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<PayWalletDO>()
|
||||
.setSql(" balance = balance + " + price)
|
||||
.eq(PayWalletDO::getId, id);
|
||||
update(null, lambdaUpdateWrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 冻结钱包部分余额
|
||||
*
|
||||
@@ -114,7 +127,6 @@ public interface PayWalletMapper extends BaseMapperX<PayWalletDO> {
|
||||
return update(null, lambdaUpdateWrapper);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -16,6 +16,15 @@ public interface RedisKeyConstants {
|
||||
*/
|
||||
String PAY_NOTIFY_LOCK = "pay_notify:lock:%d";
|
||||
|
||||
/**
|
||||
* 支付钱包的分布式锁
|
||||
*
|
||||
* KEY 格式:pay_wallet:lock:%d
|
||||
* VALUE 数据格式:HASH // RLock.class:Redisson 的 Lock 锁,使用 Hash 数据结构
|
||||
* 过期时间:不固定
|
||||
*/
|
||||
String PAY_WALLET_LOCK = "pay_wallet:lock:%d";
|
||||
|
||||
/**
|
||||
* 支付序号的缓存
|
||||
*
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package cn.iocoder.yudao.module.pay.dal.redis.wallet;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import org.redisson.api.RLock;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static cn.iocoder.yudao.module.pay.dal.redis.RedisKeyConstants.PAY_WALLET_LOCK;
|
||||
|
||||
/**
|
||||
* 支付钱包的锁 Redis DAO
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Repository
|
||||
public class PayWalletLockRedisDAO {
|
||||
|
||||
@Resource
|
||||
private RedissonClient redissonClient;
|
||||
|
||||
public <V> V lock(Long id, Long timeoutMillis, Callable<V> callable) throws Exception {
|
||||
String lockKey = formatKey(id);
|
||||
RLock lock = redissonClient.getLock(lockKey);
|
||||
try {
|
||||
lock.lock(timeoutMillis, TimeUnit.MILLISECONDS);
|
||||
// 执行逻辑
|
||||
return callable.call();
|
||||
} catch (Exception e) {
|
||||
throw e;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private static String formatKey(Long id) {
|
||||
return String.format(PAY_WALLET_LOCK, id);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -39,6 +39,15 @@ public class PayProperties {
|
||||
@URL(message = "支付回调地址的格式必须是 URL")
|
||||
private String refundNotifyUrl;
|
||||
|
||||
/**
|
||||
* 转账回调地址
|
||||
*
|
||||
* 实际上,对应的 PayNotifyController 的 notifyTransfer 方法的 URL
|
||||
*
|
||||
* 回调顺序:支付渠道(支付宝支付、微信支付) => yudao-module-pay 的 transferNotifyUrl 地址 => 业务的 PayAppDO.transferNotifyUrl 地址
|
||||
*/
|
||||
private String transferNotifyUrl;
|
||||
|
||||
/**
|
||||
* 支付订单 no 的前缀
|
||||
*/
|
||||
|
||||
@@ -177,6 +177,11 @@ public class WalletPayClient extends AbstractPayClient<NonePayClientConfig> {
|
||||
throw new IllegalStateException(String.format("支付退款单[%s] 状态不正确", outRefundNo));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PayTransferRespDTO doParseTransferNotify(Map<String, String> params, String body) throws Throwable {
|
||||
throw new UnsupportedOperationException("未实现");
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) {
|
||||
throw new UnsupportedOperationException("待实现");
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
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.api.order.PayOrderApi;
|
||||
@@ -14,11 +15,11 @@ import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoOrderDO;
|
||||
import cn.iocoder.yudao.module.pay.dal.mysql.demo.PayDemoOrderMapper;
|
||||
import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
|
||||
import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
@@ -111,10 +112,29 @@ public class PayDemoOrderServiceImpl implements PayDemoOrderService {
|
||||
|
||||
@Override
|
||||
public void updateDemoOrderPaid(Long id, Long payOrderId) {
|
||||
// 校验并获得支付订单(可支付)
|
||||
PayOrderRespDTO payOrder = validateDemoOrderCanPaid(id, payOrderId);
|
||||
// 1.1 校验订单是否存在
|
||||
PayDemoOrderDO order = payDemoOrderMapper.selectById(id);
|
||||
if (order == null) {
|
||||
log.error("[updateDemoOrderPaid][order({}) payOrder({}) 不存在订单,请进行处理!]", id, payOrderId);
|
||||
throw exception(DEMO_ORDER_NOT_FOUND);
|
||||
}
|
||||
// 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));
|
||||
throw exception(DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR);
|
||||
}
|
||||
|
||||
// 更新 PayDemoOrderDO 状态为已支付
|
||||
// 2. 校验支付订单的合法性
|
||||
PayOrderRespDTO payOrder = validatePayOrderPaid(order, payOrderId);
|
||||
|
||||
// 3. 更新 PayDemoOrderDO 状态为已支付
|
||||
int updateCount = payDemoOrderMapper.updateByIdAndPayed(id, false,
|
||||
new PayDemoOrderDO().setPayStatus(true).setPayTime(LocalDateTime.now())
|
||||
.setPayChannelCode(payOrder.getChannelCode()));
|
||||
@@ -124,56 +144,35 @@ public class PayDemoOrderServiceImpl implements PayDemoOrderService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验交易订单满足被支付的条件
|
||||
* 校验支付订单的合法性
|
||||
*
|
||||
* 1. 交易订单未支付
|
||||
* 2. 支付单已支付
|
||||
*
|
||||
* @param id 交易订单编号
|
||||
* @param order 交易订单
|
||||
* @param payOrderId 支付订单编号
|
||||
* @return 交易订单
|
||||
* @return 支付订单
|
||||
*/
|
||||
private PayOrderRespDTO validateDemoOrderCanPaid(Long id, Long payOrderId) {
|
||||
// 1.1 校验订单是否存在
|
||||
PayDemoOrderDO order = payDemoOrderMapper.selectById(id);
|
||||
if (order == null) {
|
||||
throw exception(DEMO_ORDER_NOT_FOUND);
|
||||
}
|
||||
// 1.2 校验订单未支付
|
||||
if (order.getPayStatus()) {
|
||||
log.error("[validateDemoOrderCanPaid][order({}) 不处于待支付状态,请进行处理!order 数据是:{}]",
|
||||
id, toJsonString(order));
|
||||
throw exception(DEMO_ORDER_UPDATE_PAID_STATUS_NOT_UNPAID);
|
||||
}
|
||||
// 1.3 校验支付订单匹配
|
||||
if (notEqual(order.getPayOrderId(), payOrderId)) { // 支付单号
|
||||
log.error("[validateDemoOrderCanPaid][order({}) 支付单不匹配({}),请进行处理!order 数据是:{}]",
|
||||
id, payOrderId, toJsonString(order));
|
||||
throw exception(DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR);
|
||||
}
|
||||
|
||||
// 2.1 校验支付单是否存在
|
||||
private PayOrderRespDTO validatePayOrderPaid(PayDemoOrderDO order, Long payOrderId) {
|
||||
// 1. 校验支付单是否存在
|
||||
PayOrderRespDTO payOrder = payOrderApi.getOrder(payOrderId);
|
||||
if (payOrder == null) {
|
||||
log.error("[validateDemoOrderCanPaid][order({}) payOrder({}) 不存在,请进行处理!]", id, payOrderId);
|
||||
log.error("[validatePayOrderPaid][order({}) payOrder({}) 不存在,请进行处理!]", order.getId(), payOrderId);
|
||||
throw exception(PAY_ORDER_NOT_FOUND);
|
||||
}
|
||||
// 2.2 校验支付单已支付
|
||||
// 2.1 校验支付单已支付
|
||||
if (!PayOrderStatusEnum.isSuccess(payOrder.getStatus())) {
|
||||
log.error("[validateDemoOrderCanPaid][order({}) payOrder({}) 未支付,请进行处理!payOrder 数据是:{}]",
|
||||
id, payOrderId, toJsonString(payOrder));
|
||||
log.error("[validatePayOrderPaid][order({}) payOrder({}) 未支付,请进行处理!payOrder 数据是:{}]",
|
||||
order.getId(), payOrderId, toJsonString(payOrder));
|
||||
throw exception(DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_STATUS_NOT_SUCCESS);
|
||||
}
|
||||
// 2.3 校验支付金额一致
|
||||
// 2.1 校验支付金额一致
|
||||
if (notEqual(payOrder.getPrice(), order.getPrice())) {
|
||||
log.error("[validateDemoOrderCanPaid][order({}) payOrder({}) 支付金额不匹配,请进行处理!order 数据是:{},payOrder 数据是:{}]",
|
||||
id, payOrderId, toJsonString(order), toJsonString(payOrder));
|
||||
log.error("[validatePayOrderPaid][order({}) payOrder({}) 支付金额不匹配,请进行处理!order 数据是:{},payOrder 数据是:{}]",
|
||||
order.getId(), payOrderId, toJsonString(order), toJsonString(payOrder));
|
||||
throw exception(DEMO_ORDER_UPDATE_PAID_FAIL_PAY_PRICE_NOT_MATCH);
|
||||
}
|
||||
// 2.4 校验支付订单匹配(二次)
|
||||
if (notEqual(payOrder.getMerchantOrderId(), id.toString())) {
|
||||
log.error("[validateDemoOrderCanPaid][order({}) 支付单不匹配({}),请进行处理!payOrder 数据是:{}]",
|
||||
id, payOrderId, toJsonString(payOrder));
|
||||
// 2.2 校验支付订单匹配(二次)
|
||||
if (notEqual(payOrder.getMerchantOrderId(), order.getId().toString())) {
|
||||
log.error("[validatePayOrderPaid][order({}) 支付单不匹配({}),请进行处理!payOrder 数据是:{}]",
|
||||
order.getId(), payOrderId, toJsonString(payOrder));
|
||||
throw exception(DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR);
|
||||
}
|
||||
return payOrder;
|
||||
|
||||
@@ -139,6 +139,16 @@ public interface PayOrderService {
|
||||
*/
|
||||
int syncOrder(LocalDateTime minCreateTime);
|
||||
|
||||
/**
|
||||
* 同步订单的支付状态
|
||||
*
|
||||
* 1. Quietly 表示,即使同步失败,也不会抛出异常
|
||||
* 2. 什么时候回出现异常?因为是主动同步,可能和支付渠道的异步回调存在并发冲突,导致抛出异常
|
||||
*
|
||||
* @param id 订单编号
|
||||
*/
|
||||
void syncOrderQuietly(Long id);
|
||||
|
||||
/**
|
||||
* 将已过期的订单,状态修改为已关闭
|
||||
*
|
||||
|
||||
@@ -163,7 +163,14 @@ public class PayOrderServiceImpl implements PayOrderService {
|
||||
|
||||
// 4. 如果调用直接支付成功,则直接更新支付单状态为成功。例如说:付款码支付,免密支付时,就直接验证支付成功
|
||||
if (unifiedOrderResp != null) {
|
||||
getSelf().notifyOrder(channel, unifiedOrderResp);
|
||||
try {
|
||||
getSelf().notifyOrder(channel, unifiedOrderResp);
|
||||
} catch (Exception e) {
|
||||
// 兼容 https://gitee.com/zhijiantianya/yudao-cloud/issues/I8SM9H 场景
|
||||
// 支付宝或微信扫码之后时,由于 PayClient 是直接返回支付成功,而支付也会有回调,导致存在并发更新问题,此时一般是可以 try catch 直接忽略
|
||||
log.warn("[submitOrder][order({}) channel({}) 支付结果({}) 通知时发生异常,可能是并发问题]",
|
||||
order, channel, unifiedOrderResp, e);
|
||||
}
|
||||
// 如有渠道错误码,则抛出业务异常,提示用户
|
||||
if (StrUtil.isNotEmpty(unifiedOrderResp.getChannelErrorCode())) {
|
||||
throw exception(PAY_ORDER_SUBMIT_CHANNEL_ERROR, unifiedOrderResp.getChannelErrorCode(),
|
||||
@@ -431,9 +438,7 @@ public class PayOrderServiceImpl implements PayOrderService {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO 芋艿:应该 new 出来更新
|
||||
order.setPrice(payPrice);
|
||||
orderMapper.updateById(order);
|
||||
orderMapper.updateById(new PayOrderDO().setId(order.getId()).setPrice(payPrice));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -462,6 +467,18 @@ public class PayOrderServiceImpl implements PayOrderService {
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void syncOrderQuietly(Long id) {
|
||||
// 1. 查询待支付订单
|
||||
List<PayOrderExtensionDO> orderExtensions = orderExtensionMapper.selectListByOrderIdAndStatus(id,
|
||||
PayOrderStatusEnum.WAITING.getStatus());
|
||||
|
||||
// 2. 遍历执行
|
||||
for (PayOrderExtensionDO orderExtension : orderExtensions) {
|
||||
syncOrder(orderExtension);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步单个支付拓展单
|
||||
*
|
||||
|
||||
@@ -1,6 +1,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.controller.admin.transfer.vo.PayTransferPageReqVO;
|
||||
@@ -54,4 +55,13 @@ public interface PayTransferService {
|
||||
* @return 同步到状态的转账数量,包括转账成功、转账失败、转账中的
|
||||
*/
|
||||
int syncTransfer();
|
||||
|
||||
/**
|
||||
* 渠道的转账通知
|
||||
*
|
||||
* @param channelId 渠道编号
|
||||
* @param notify 通知
|
||||
*/
|
||||
void notifyTransfer(Long channelId, PayTransferRespDTO notify);
|
||||
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import cn.iocoder.yudao.module.pay.dal.mysql.transfer.PayTransferMapper;
|
||||
import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO;
|
||||
import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum;
|
||||
import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferStatusEnum;
|
||||
import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties;
|
||||
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;
|
||||
@@ -50,6 +51,9 @@ public class PayTransferServiceImpl implements PayTransferService {
|
||||
|
||||
private static final String TRANSFER_NO_PREFIX = "T";
|
||||
|
||||
@Resource
|
||||
private PayProperties payProperties;
|
||||
|
||||
@Resource
|
||||
private PayTransferMapper transferMapper;
|
||||
@Resource
|
||||
@@ -96,13 +100,15 @@ public class PayTransferServiceImpl implements PayTransferService {
|
||||
transfer = INSTANCE.convert(reqDTO)
|
||||
.setChannelId(channel.getId())
|
||||
.setNo(no).setStatus(WAITING.getStatus())
|
||||
.setNotifyUrl(payApp.getTransferNotifyUrl());
|
||||
.setNotifyUrl(payApp.getTransferNotifyUrl())
|
||||
.setAppId(channel.getAppId());
|
||||
transferMapper.insert(transfer);
|
||||
}
|
||||
try {
|
||||
// 3. 调用三方渠道发起转账
|
||||
PayTransferUnifiedReqDTO transferUnifiedReq = INSTANCE.convert2(transfer)
|
||||
.setOutTransferNo(transfer.getNo());
|
||||
transferUnifiedReq.setNotifyUrl(genChannelTransferNotifyUrl(channel));
|
||||
PayTransferRespDTO unifiedTransferResp = client.unifiedTransfer(transferUnifiedReq);
|
||||
// 4. 通知转账结果
|
||||
getSelf().notifyTransfer(channel, unifiedTransferResp);
|
||||
@@ -116,6 +122,16 @@ public class PayTransferServiceImpl implements PayTransferService {
|
||||
return transfer.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据支付渠道的编码,生成支付渠道的回调地址
|
||||
*
|
||||
* @param channel 支付渠道
|
||||
* @return 支付渠道的回调地址 配置地址 + "/" + channel id
|
||||
*/
|
||||
private String genChannelTransferNotifyUrl(PayChannelDO channel) {
|
||||
return payProperties.getTransferNotifyUrl() + "/" + channel.getId();
|
||||
}
|
||||
|
||||
private PayTransferDO validateTransferCanCreate(PayTransferCreateReqDTO dto, Long appId) {
|
||||
PayTransferDO transfer = transferMapper.selectByAppIdAndMerchantTransferId(appId, dto.getMerchantTransferId());
|
||||
if (transfer != null) {
|
||||
@@ -154,7 +170,7 @@ public class PayTransferServiceImpl implements PayTransferService {
|
||||
|
||||
private void notifyTransferInProgress(PayChannelDO channel, PayTransferRespDTO notify) {
|
||||
// 1.校验
|
||||
PayTransferDO transfer = transferMapper.selectByNo(notify.getOutTransferNo());
|
||||
PayTransferDO transfer = transferMapper.selectByAppIdAndNo(channel.getAppId(), notify.getOutTransferNo());
|
||||
if (transfer == null) {
|
||||
throw exception(PAY_TRANSFER_NOT_FOUND);
|
||||
}
|
||||
@@ -172,16 +188,12 @@ public class PayTransferServiceImpl implements PayTransferService {
|
||||
throw exception(PAY_TRANSFER_STATUS_IS_NOT_WAITING);
|
||||
}
|
||||
log.info("[notifyTransferInProgress][transfer({}) 更新为转账进行中状态]", transfer.getId());
|
||||
|
||||
// 3. 插入转账通知记录
|
||||
notifyService.createPayNotifyTask(PayNotifyTypeEnum.TRANSFER.getType(),
|
||||
transfer.getId());
|
||||
}
|
||||
|
||||
|
||||
private void notifyTransferSuccess(PayChannelDO channel, PayTransferRespDTO notify) {
|
||||
// 1.校验
|
||||
PayTransferDO transfer = transferMapper.selectByNo(notify.getOutTransferNo());
|
||||
PayTransferDO transfer = transferMapper.selectByAppIdAndNo(channel.getAppId(), notify.getOutTransferNo());
|
||||
if (transfer == null) {
|
||||
throw exception(PAY_TRANSFER_NOT_FOUND);
|
||||
}
|
||||
@@ -210,7 +222,7 @@ public class PayTransferServiceImpl implements PayTransferService {
|
||||
|
||||
private void notifyTransferClosed(PayChannelDO channel, PayTransferRespDTO notify) {
|
||||
// 1.校验
|
||||
PayTransferDO transfer = transferMapper.selectByNo(notify.getOutTransferNo());
|
||||
PayTransferDO transfer = transferMapper.selectByAppIdAndNo(channel.getAppId(), notify.getOutTransferNo());
|
||||
if (transfer == null) {
|
||||
throw exception(PAY_TRANSFER_NOT_FOUND);
|
||||
}
|
||||
@@ -283,7 +295,7 @@ public class PayTransferServiceImpl implements PayTransferService {
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyTransfer(Long channelId, PayTransferRespDTO notify) {
|
||||
public void notifyTransfer(Long channelId, PayTransferRespDTO notify) {
|
||||
// 校验渠道是否有效
|
||||
PayChannelDO channel = channelService.validPayChannel(channelId);
|
||||
// 通知转账结果给对应的业务
|
||||
|
||||
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.pay.service.wallet;
|
||||
|
||||
import cn.hutool.core.date.LocalDateTimeUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
@@ -113,16 +114,28 @@ public class PayWalletRechargeServiceImpl implements PayWalletRechargeService {
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void updateWalletRechargerPaid(Long id, Long payOrderId) {
|
||||
// 1.1 获取钱包充值记录
|
||||
PayWalletRechargeDO walletRecharge = walletRechargeMapper.selectById(id);
|
||||
if (walletRecharge == null) {
|
||||
log.error("[updateWalletRechargerPaid][钱包充值记录不存在,钱包充值记录 id({})]", id);
|
||||
// 1.1 校验钱包充值是否存在
|
||||
PayWalletRechargeDO recharge = walletRechargeMapper.selectById(id);
|
||||
if (recharge == null) {
|
||||
log.error("[updateWalletRechargerPaid][recharge({}) payOrder({}) 不存在充值订单,请进行处理!]", id, payOrderId);
|
||||
throw exception(WALLET_RECHARGE_NOT_FOUND);
|
||||
}
|
||||
// 1.2 校验钱包充值是否可以支付
|
||||
PayOrderDO payOrderDO = validateWalletRechargerCanPaid(walletRecharge, payOrderId);
|
||||
if (recharge.getPayStatus()) {
|
||||
// 特殊:如果订单已支付,且支付单号相同,直接返回,说明重复回调
|
||||
if (ObjectUtil.equals(recharge.getPayOrderId(), payOrderId)) {
|
||||
log.warn("[updateWalletRechargerPaid][recharge({}) 已支付,且支付单号相同({}),直接返回]", recharge, payOrderId);
|
||||
return;
|
||||
}
|
||||
// 异常:支付单号不同,说明支付单号错误
|
||||
log.error("[updateWalletRechargerPaid][recharge({}) 已支付,但是支付单号不同({}),请进行处理!]", recharge, payOrderId);
|
||||
throw exception(WALLET_RECHARGE_UPDATE_PAID_PAY_ORDER_ID_ERROR);
|
||||
}
|
||||
|
||||
// 2. 更新钱包充值的支付状态
|
||||
// 2. 校验支付订单的合法性
|
||||
PayOrderDO payOrderDO = validatePayOrderPaid(recharge, payOrderId);
|
||||
|
||||
// 3. 更新钱包充值的支付状态
|
||||
int updateCount = walletRechargeMapper.updateByIdAndPaid(id, false,
|
||||
new PayWalletRechargeDO().setId(id).setPayStatus(true).setPayTime(LocalDateTime.now())
|
||||
.setPayChannelCode(payOrderDO.getChannelCode()));
|
||||
@@ -130,14 +143,14 @@ public class PayWalletRechargeServiceImpl implements PayWalletRechargeService {
|
||||
throw exception(WALLET_RECHARGE_UPDATE_PAID_STATUS_NOT_UNPAID);
|
||||
}
|
||||
|
||||
// 3. 更新钱包余额
|
||||
// 4. 更新钱包余额
|
||||
// TODO @jason:这样的话,未来提现会不会把充值的,也提现走哈。类似先充 100,送 110;然后提现 110;
|
||||
// TODO 需要钱包中加个可提现余额
|
||||
payWalletService.addWalletBalance(walletRecharge.getWalletId(), String.valueOf(id),
|
||||
PayWalletBizTypeEnum.RECHARGE, walletRecharge.getTotalPrice());
|
||||
payWalletService.addWalletBalance(recharge.getWalletId(), String.valueOf(id),
|
||||
PayWalletBizTypeEnum.RECHARGE, recharge.getTotalPrice());
|
||||
|
||||
// 4. 发送订阅消息
|
||||
getSelf().sendWalletRechargerPaidMessage(payOrderId, walletRecharge);
|
||||
// 5. 发送订阅消息
|
||||
getSelf().sendWalletRechargerPaidMessage(payOrderId, recharge);
|
||||
}
|
||||
|
||||
@Async
|
||||
@@ -180,7 +193,6 @@ public class PayWalletRechargeServiceImpl implements PayWalletRechargeService {
|
||||
.setReason("想退钱").setPrice(walletRecharge.getPayPrice()));
|
||||
|
||||
// 4. 更新充值记录退款单号
|
||||
// TODO @jaosn:一般新建这种 update 对象,建议是,第一个 set id 属性,容易知道以它为更新
|
||||
walletRechargeMapper.updateById(new PayWalletRechargeDO().setPayRefundId(payRefundId)
|
||||
.setRefundStatus(WAITING.getStatus()).setId(walletRecharge.getId()));
|
||||
}
|
||||
@@ -267,43 +279,38 @@ public class PayWalletRechargeServiceImpl implements PayWalletRechargeService {
|
||||
return wallet;
|
||||
}
|
||||
|
||||
private PayOrderDO validateWalletRechargerCanPaid(PayWalletRechargeDO walletRecharge, Long payOrderId) {
|
||||
// 1.1 校验充值记录的支付状态
|
||||
if (walletRecharge.getPayStatus()) {
|
||||
log.error("[validateWalletRechargerCanPaid][钱包({}) 不处于未支付状态! 钱包数据是:{}]",
|
||||
walletRecharge.getId(), toJsonString(walletRecharge));
|
||||
throw exception(WALLET_RECHARGE_UPDATE_PAID_STATUS_NOT_UNPAID);
|
||||
}
|
||||
// 1.2 校验支付订单匹配
|
||||
if (notEqual(walletRecharge.getPayOrderId(), payOrderId)) { // 支付单号
|
||||
log.error("[validateWalletRechargerCanPaid][钱包({}) 支付单不匹配({}),请进行处理! 钱包数据是:{}]",
|
||||
walletRecharge.getId(), payOrderId, toJsonString(walletRecharge));
|
||||
throw exception(WALLET_RECHARGE_UPDATE_PAID_PAY_ORDER_ID_ERROR);
|
||||
}
|
||||
|
||||
// 2.1 校验支付单是否存在
|
||||
/**
|
||||
* 校验支付订单的合法性
|
||||
*
|
||||
* @param recharge 充值订单
|
||||
* @param payOrderId 支付订单编号
|
||||
* @return 支付订单
|
||||
*/
|
||||
private PayOrderDO validatePayOrderPaid(PayWalletRechargeDO recharge, Long payOrderId) {
|
||||
// 1. 校验支付单是否存在
|
||||
PayOrderDO payOrder = payOrderService.getOrder(payOrderId);
|
||||
if (payOrder == null) {
|
||||
log.error("[validateWalletRechargerCanPaid][钱包({}) payOrder({}) 不存在,请进行处理!]",
|
||||
walletRecharge.getId(), payOrderId);
|
||||
log.error("[validatePayOrderPaid][充值订单({}) payOrder({}) 不存在,请进行处理!]",
|
||||
recharge.getId(), payOrderId);
|
||||
throw exception(PAY_ORDER_NOT_FOUND);
|
||||
}
|
||||
// 2.2 校验支付单已支付
|
||||
|
||||
// 2.1 校验支付单已支付
|
||||
if (!PayOrderStatusEnum.isSuccess(payOrder.getStatus())) {
|
||||
log.error("[validateWalletRechargerCanPaid][钱包({}) payOrder({}) 未支付,请进行处理!payOrder 数据是:{}]",
|
||||
walletRecharge.getId(), payOrderId, toJsonString(payOrder));
|
||||
log.error("[validatePayOrderPaid][充值订单({}) payOrder({}) 未支付,请进行处理!payOrder 数据是:{}]",
|
||||
recharge.getId(), payOrderId, toJsonString(payOrder));
|
||||
throw exception(WALLET_RECHARGE_UPDATE_PAID_PAY_ORDER_STATUS_NOT_SUCCESS);
|
||||
}
|
||||
// 2.3 校验支付金额一致
|
||||
if (notEqual(payOrder.getPrice(), walletRecharge.getPayPrice())) {
|
||||
log.error("[validateDemoOrderCanPaid][钱包({}) payOrder({}) 支付金额不匹配,请进行处理!钱包 数据是:{},payOrder 数据是:{}]",
|
||||
walletRecharge.getId(), payOrderId, toJsonString(walletRecharge), toJsonString(payOrder));
|
||||
// 2.2 校验支付金额一致
|
||||
if (notEqual(payOrder.getPrice(), recharge.getPayPrice())) {
|
||||
log.error("[validatePayOrderPaid][充值订单({}) payOrder({}) 支付金额不匹配,请进行处理!钱包 数据是:{},payOrder 数据是:{}]",
|
||||
recharge.getId(), payOrderId, toJsonString(recharge), toJsonString(payOrder));
|
||||
throw exception(WALLET_RECHARGE_UPDATE_PAID_PAY_PRICE_NOT_MATCH);
|
||||
}
|
||||
// 2.4 校验支付订单的商户订单匹配
|
||||
if (notEqual(payOrder.getMerchantOrderId(), walletRecharge.getId().toString())) {
|
||||
log.error("[validateDemoOrderCanPaid][钱包({}) 支付单不匹配({}),请进行处理!payOrder 数据是:{}]",
|
||||
walletRecharge.getId(), payOrderId, toJsonString(payOrder));
|
||||
// 2.3 校验支付订单的商户订单匹配
|
||||
if (notEqual(payOrder.getMerchantOrderId(), recharge.getId().toString())) {
|
||||
log.error("[validatePayOrderPaid][充值订单({}) 支付单不匹配({}),请进行处理!payOrder 数据是:{}]",
|
||||
recharge.getId(), payOrderId, toJsonString(payOrder));
|
||||
throw exception(WALLET_RECHARGE_UPDATE_PAID_PAY_ORDER_ID_ERROR);
|
||||
}
|
||||
return payOrder;
|
||||
|
||||
@@ -2,17 +2,20 @@ package cn.iocoder.yudao.module.pay.service.wallet;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet.PayWalletPageReqVO;
|
||||
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.wallet.PayWalletDO;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO;
|
||||
import cn.iocoder.yudao.module.pay.dal.mysql.wallet.PayWalletMapper;
|
||||
import cn.iocoder.yudao.module.pay.dal.redis.wallet.PayWalletLockRedisDAO;
|
||||
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.wallet.bo.WalletTransactionCreateReqBO;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -32,10 +35,17 @@ import static cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum.PAYM
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class PayWalletServiceImpl implements PayWalletService {
|
||||
public class PayWalletServiceImpl implements PayWalletService {
|
||||
|
||||
/**
|
||||
* 通知超时时间,单位:毫秒
|
||||
*/
|
||||
public static final long UPDATE_TIMEOUT_MILLIS = 120 * DateUtils.SECOND_MILLIS;
|
||||
|
||||
@Resource
|
||||
private PayWalletMapper walletMapper;
|
||||
@Resource
|
||||
private PayWalletLockRedisDAO lockRedisDAO;
|
||||
|
||||
@Resource
|
||||
@Lazy // 延迟加载,避免循环依赖
|
||||
@@ -121,75 +131,87 @@ public class PayWalletServiceImpl implements PayWalletService {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@SneakyThrows
|
||||
public PayWalletTransactionDO reduceWalletBalance(Long walletId, Long bizId,
|
||||
PayWalletBizTypeEnum bizType, Integer price) {
|
||||
// 1. 获取钱包
|
||||
PayWalletDO payWallet = getWallet(walletId);
|
||||
if (payWallet == null) {
|
||||
log.error("[reduceWalletBalance],用户钱包({})不存在.", walletId);
|
||||
log.error("[reduceWalletBalance][用户钱包({})不存在]", walletId);
|
||||
throw exception(WALLET_NOT_FOUND);
|
||||
}
|
||||
|
||||
// 2.1 扣除余额
|
||||
int updateCounts;
|
||||
switch (bizType) {
|
||||
case PAYMENT: {
|
||||
updateCounts = walletMapper.updateWhenConsumption(payWallet.getId(), price);
|
||||
break;
|
||||
// 2. 加锁,更新钱包余额(目的:避免钱包流水的并发更新时,余额变化不连贯)
|
||||
return lockRedisDAO.lock(walletId, UPDATE_TIMEOUT_MILLIS, () -> {
|
||||
// 2. 扣除余额
|
||||
int updateCounts;
|
||||
switch (bizType) {
|
||||
case PAYMENT: {
|
||||
updateCounts = walletMapper.updateWhenConsumption(payWallet.getId(), price);
|
||||
break;
|
||||
}
|
||||
case RECHARGE_REFUND: {
|
||||
updateCounts = walletMapper.updateWhenRechargeRefund(payWallet.getId(), price);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// TODO 其它类型待实现
|
||||
throw new UnsupportedOperationException("待实现");
|
||||
}
|
||||
}
|
||||
case RECHARGE_REFUND: {
|
||||
updateCounts = walletMapper.updateWhenRechargeRefund(payWallet.getId(), price);
|
||||
break;
|
||||
if (updateCounts == 0) {
|
||||
throw exception(WALLET_BALANCE_NOT_ENOUGH);
|
||||
}
|
||||
default: {
|
||||
// TODO 其它类型待实现
|
||||
throw new UnsupportedOperationException("待实现");
|
||||
}
|
||||
}
|
||||
if (updateCounts == 0) {
|
||||
throw exception(WALLET_BALANCE_NOT_ENOUGH);
|
||||
}
|
||||
// 2.2 生成钱包流水
|
||||
Integer afterBalance = payWallet.getBalance() - price;
|
||||
WalletTransactionCreateReqBO bo = new WalletTransactionCreateReqBO().setWalletId(payWallet.getId())
|
||||
.setPrice(-price).setBalance(afterBalance).setBizId(String.valueOf(bizId))
|
||||
.setBizType(bizType.getType()).setTitle(bizType.getDescription());
|
||||
return walletTransactionService.createWalletTransaction(bo);
|
||||
|
||||
// 3. 生成钱包流水
|
||||
Integer afterBalance = payWallet.getBalance() - price;
|
||||
WalletTransactionCreateReqBO bo = new WalletTransactionCreateReqBO().setWalletId(payWallet.getId())
|
||||
.setPrice(-price).setBalance(afterBalance).setBizId(String.valueOf(bizId))
|
||||
.setBizType(bizType.getType()).setTitle(bizType.getDescription());
|
||||
return walletTransactionService.createWalletTransaction(bo);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@SneakyThrows
|
||||
public PayWalletTransactionDO addWalletBalance(Long walletId, String bizId,
|
||||
PayWalletBizTypeEnum bizType, Integer price) {
|
||||
// 1.1 获取钱包
|
||||
// 1. 获取钱包
|
||||
PayWalletDO payWallet = getWallet(walletId);
|
||||
if (payWallet == null) {
|
||||
log.error("[addWalletBalance],用户钱包({})不存在.", walletId);
|
||||
log.error("[addWalletBalance][用户钱包({})不存在]", walletId);
|
||||
throw exception(WALLET_NOT_FOUND);
|
||||
}
|
||||
// 1.2 更新钱包金额
|
||||
switch (bizType) {
|
||||
case PAYMENT_REFUND: { // 退款更新
|
||||
walletMapper.updateWhenConsumptionRefund(payWallet.getId(), price);
|
||||
break;
|
||||
}
|
||||
case RECHARGE: { // 充值更新
|
||||
walletMapper.updateWhenRecharge(payWallet.getId(), price);
|
||||
break;
|
||||
}
|
||||
case UPDATE_BALANCE: // 更新余额
|
||||
walletMapper.updateWhenRecharge(payWallet.getId(), price);
|
||||
break;
|
||||
default: {
|
||||
// TODO 其它类型待实现
|
||||
throw new UnsupportedOperationException("待实现");
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 生成钱包流水
|
||||
WalletTransactionCreateReqBO transactionCreateReqBO = new WalletTransactionCreateReqBO()
|
||||
.setWalletId(payWallet.getId()).setPrice(price).setBalance(payWallet.getBalance() + price)
|
||||
.setBizId(bizId).setBizType(bizType.getType()).setTitle(bizType.getDescription());
|
||||
return walletTransactionService.createWalletTransaction(transactionCreateReqBO);
|
||||
// 2. 加锁,更新钱包余额(目的:避免钱包流水的并发更新时,余额变化不连贯)
|
||||
return lockRedisDAO.lock(walletId, UPDATE_TIMEOUT_MILLIS, () -> {
|
||||
// 3. 更新钱包金额
|
||||
switch (bizType) {
|
||||
case PAYMENT_REFUND: { // 退款更新
|
||||
walletMapper.updateWhenConsumptionRefund(payWallet.getId(), price);
|
||||
break;
|
||||
}
|
||||
case RECHARGE: { // 充值更新
|
||||
walletMapper.updateWhenRecharge(payWallet.getId(), price);
|
||||
break;
|
||||
}
|
||||
case UPDATE_BALANCE: // 更新余额
|
||||
case BROKERAGE_WITHDRAW: // 分佣提现
|
||||
walletMapper.updateWhenAdd(payWallet.getId(), price);
|
||||
break;
|
||||
default: {
|
||||
throw new UnsupportedOperationException("待实现:" + bizType);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 生成钱包流水
|
||||
WalletTransactionCreateReqBO transactionCreateReqBO = new WalletTransactionCreateReqBO()
|
||||
.setWalletId(payWallet.getId()).setPrice(price).setBalance(payWallet.getBalance() + price)
|
||||
.setBizId(bizId).setBizType(bizType.getType()).setTitle(bizType.getDescription());
|
||||
return walletTransactionService.createWalletTransaction(transactionCreateReqBO);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -87,8 +87,6 @@ public class PayWalletTransactionServiceImpl implements PayWalletTransactionServ
|
||||
@Override
|
||||
public AppPayWalletTransactionSummaryRespVO getWalletTransactionSummary(Long userId, Integer userType, LocalDateTime[] createTime) {
|
||||
PayWalletDO wallet = payWalletService.getOrCreateWallet(userId, userType);
|
||||
AppPayWalletTransactionSummaryRespVO summary = new AppPayWalletTransactionSummaryRespVO()
|
||||
.setTotalExpense(1).setTotalIncome(100);
|
||||
return new AppPayWalletTransactionSummaryRespVO()
|
||||
.setTotalExpense(payWalletTransactionMapper.selectPriceSum(wallet.getId(), TYPE_EXPENSE, createTime))
|
||||
.setTotalIncome(payWalletTransactionMapper.selectPriceSum(wallet.getId(), TYPE_INCOME, createTime));
|
||||
|
||||
@@ -71,6 +71,7 @@
|
||||
<artifactId>yudao-spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -79,6 +79,8 @@ public interface PayClient {
|
||||
*/
|
||||
PayRefundRespDTO getRefund(String outTradeNo, String outRefundNo);
|
||||
|
||||
// ============ 转账相关 ==========
|
||||
|
||||
/**
|
||||
* 调用渠道,进行转账
|
||||
*
|
||||
@@ -95,4 +97,14 @@ public interface PayClient {
|
||||
* @return 转账信息
|
||||
*/
|
||||
PayTransferRespDTO getTransfer(String outTradeNo, PayTransferTypeEnum type);
|
||||
|
||||
/**
|
||||
* 解析 transfer 回调数据
|
||||
*
|
||||
* @param params HTTP 回调接口 content type 为 application/x-www-form-urlencoded 的所有参数
|
||||
* @param body HTTP 回调接口的 request body
|
||||
* @return 转账信息
|
||||
*/
|
||||
PayTransferRespDTO parseTransferNotify(Map<String, String> params, String body);
|
||||
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import org.hibernate.validator.constraints.URL;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum.*;
|
||||
@@ -75,4 +77,12 @@ public class PayTransferUnifiedReqDTO {
|
||||
* 支付渠道的额外参数
|
||||
*/
|
||||
private Map<String, String> channelExtras;
|
||||
|
||||
/**
|
||||
* 转账结果的 notify 回调地址
|
||||
*/
|
||||
@NotEmpty(message = "转账结果的回调地址不能为空")
|
||||
@URL(message = "转账结果的 notify 回调地址必须是 URL 格式")
|
||||
private String notifyUrl;
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
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;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -219,6 +219,22 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final PayTransferRespDTO parseTransferNotify(Map<String, String> params, String body) {
|
||||
try {
|
||||
return doParseTransferNotify(params, body);
|
||||
} catch (ServiceException ex) { // 业务异常,都是实现类已经翻译,所以直接抛出即可
|
||||
throw ex;
|
||||
} catch (Throwable ex) {
|
||||
log.error("[doParseTransferNotify][客户端({}) params({}) body({}) 解析失败]",
|
||||
getId(), params, body, ex);
|
||||
throw buildPayException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract PayTransferRespDTO doParseTransferNotify(Map<String, String> params, String body)
|
||||
throws Throwable;
|
||||
|
||||
@Override
|
||||
public final PayTransferRespDTO getTransfer(String outTradeNo, PayTransferTypeEnum type) {
|
||||
try {
|
||||
|
||||
@@ -325,6 +325,12 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa
|
||||
}
|
||||
}
|
||||
|
||||
// TODO @chihuo:这里是不是也要实现,支付宝的。
|
||||
@Override
|
||||
protected PayTransferRespDTO doParseTransferNotify(Map<String, String> params, String body) throws Throwable {
|
||||
throw new UnsupportedOperationException("未实现");
|
||||
}
|
||||
|
||||
// ========== 各种工具方法 ==========
|
||||
|
||||
protected String formatAmount(Integer amount) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
|
||||
|
||||
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.client.dto.transfer.PayTransferRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum;
|
||||
import com.alipay.api.AlipayApiException;
|
||||
@@ -56,4 +57,5 @@ public class AlipayAppPayClient extends AbstractAlipayPayClient {
|
||||
return PayOrderRespDTO.waitingOf(displayMode, response.getBody(),
|
||||
reqDTO.getOutTradeNo(), response);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ 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.client.dto.transfer.PayTransferRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum;
|
||||
import com.alipay.api.AlipayApiException;
|
||||
@@ -82,4 +83,5 @@ public class AlipayBarPayClient extends AbstractAlipayPayClient {
|
||||
return PayOrderRespDTO.waitingOf(displayMode, "",
|
||||
reqDTO.getOutTradeNo(), response);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.http.Method;
|
||||
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.client.dto.transfer.PayTransferRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum;
|
||||
import com.alipay.api.AlipayApiException;
|
||||
@@ -66,4 +67,5 @@ public class AlipayPcPayClient extends AbstractAlipayPayClient {
|
||||
return PayOrderRespDTO.waitingOf(displayMode, response.getBody(),
|
||||
reqDTO.getOutTradeNo(), response);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -57,6 +57,11 @@ public class MockPayClient extends AbstractPayClient<NonePayClientConfig> {
|
||||
outRefundNo, MOCK_RESP_SUCCESS_DATA);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PayTransferRespDTO doParseTransferNotify(Map<String, String> params, String body) throws Throwable {
|
||||
throw new UnsupportedOperationException("未实现");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PayRefundRespDTO doParseRefundNotify(Map<String, String> params, String body) {
|
||||
throw new UnsupportedOperationException("模拟支付无退款回调");
|
||||
|
||||
@@ -14,6 +14,7 @@ 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;
|
||||
@@ -23,6 +24,10 @@ import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyResult;
|
||||
import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyV3Result;
|
||||
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.config.WxPayConfig;
|
||||
import com.github.binarywang.wxpay.exception.WxPayException;
|
||||
import com.github.binarywang.wxpay.service.WxPayService;
|
||||
@@ -31,6 +36,8 @@ 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;
|
||||
|
||||
@@ -41,7 +48,7 @@ import static cn.iocoder.yudao.framework.pay.core.client.impl.weixin.WxPayClient
|
||||
/**
|
||||
* 微信支付抽象类,实现微信统一的接口、以及部分实现(退款)
|
||||
*
|
||||
* @author 遇到源码
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Slf4j
|
||||
public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientConfig> {
|
||||
@@ -88,6 +95,7 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
|
||||
throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
|
||||
}
|
||||
} catch (WxPayException e) {
|
||||
log.error("[doUnifiedOrder][退款({}) 发起微信支付异常", reqDTO, e);
|
||||
String errorCode = getErrorCode(e);
|
||||
String errorMessage = getErrorMessage(e);
|
||||
return PayOrderRespDTO.closedOf(errorCode, errorMessage,
|
||||
@@ -266,7 +274,7 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
|
||||
String errorCode = getErrorCode(e);
|
||||
String errorMessage = getErrorMessage(e);
|
||||
return PayRefundRespDTO.failureOf(errorCode, errorMessage,
|
||||
reqDTO.getOutTradeNo(), e.getXmlString());
|
||||
reqDTO.getOutRefundNo(), e.getXmlString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,6 +356,33 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
|
||||
return PayRefundRespDTO.failureOf(result.getOutRefundNo(), response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayTransferRespDTO doParseTransferNotify(Map<String, String> params, String body) throws WxPayException {
|
||||
switch (config.getApiVersion()) {
|
||||
case API_VERSION_V3:
|
||||
return parseTransferNotifyV3(body);
|
||||
case API_VERSION_V2:
|
||||
throw new UnsupportedOperationException("V2 版本暂不支持,建议使用 V3 版本");
|
||||
default:
|
||||
throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
|
||||
}
|
||||
}
|
||||
|
||||
private PayTransferRespDTO parseTransferNotifyV3(String body) throws WxPayException {
|
||||
// 1. 解析回调
|
||||
// TODO @luchi:这个可以复用 wxjava 里的类么?
|
||||
WxPayTransferPartnerNotifyV3Result response = client.baseParseOrderNotifyV3Result(body, null, 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 {
|
||||
@@ -426,13 +461,54 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) {
|
||||
throw new UnsupportedOperationException("待实现");
|
||||
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()
|
||||
.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());
|
||||
// 2.1 执行请求
|
||||
TransferBatchesResult transferBatchesResult = client.getTransferService().transferBatches(transferBatches);
|
||||
// 2.2 创建返回结果
|
||||
return PayTransferRespDTO.dealingOf(transferBatchesResult.getBatchId(), reqDTO.getOutTransferNo(), transferBatchesResult);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PayTransferRespDTO doGetTransfer(String outTradeNo, PayTransferTypeEnum type) {
|
||||
throw new UnsupportedOperationException("待实现");
|
||||
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);
|
||||
}
|
||||
}
|
||||
if (Objects.equals("CLOSED", transferBatch.getBatchStatus())) {
|
||||
return PayTransferRespDTO.closedOf(transferBatch.getBatchStatus(), transferBatch.getCloseReason(),
|
||||
transferBatch.getOutBatchNo(), response);
|
||||
}
|
||||
return PayTransferRespDTO.dealingOf(transferBatch.getBatchId(), transferBatch.getOutBatchNo(), response);
|
||||
}
|
||||
|
||||
// ========== 各种工具方法 ==========
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package cn.iocoder.yudao.framework.pay.core.enums.transfer;
|
||||
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
|
||||
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@@ -14,7 +14,7 @@ import java.util.Arrays;
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum PayTransferTypeEnum implements IntArrayValuable {
|
||||
public enum PayTransferTypeEnum implements ArrayValuable<Integer> {
|
||||
|
||||
ALIPAY_BALANCE(1, "支付宝余额"),
|
||||
WX_BALANCE(2, "微信余额"),
|
||||
@@ -30,10 +30,10 @@ public enum PayTransferTypeEnum implements IntArrayValuable {
|
||||
private final Integer type;
|
||||
private final String name;
|
||||
|
||||
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(PayTransferTypeEnum::getType).toArray();
|
||||
public static final Integer[] ARRAYS = Arrays.stream(values()).map(PayTransferTypeEnum::getType).toArray(Integer[]::new);
|
||||
|
||||
@Override
|
||||
public int[] array() {
|
||||
public Integer[] array() {
|
||||
return ARRAYS;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user