完成支付的下单和提交订单的逻辑

This commit is contained in:
YunaiV 2021-10-23 20:14:13 +08:00
parent 6dc65234ef
commit ab19228ca6
24 changed files with 251 additions and 94 deletions

View File

@ -1,28 +0,0 @@
package cn.iocoder.yudao.adminserver.modules.pay.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 支付渠道的编码的枚举
*
* @author 芋道源码
*/
@Getter
@AllArgsConstructor
public enum PayChannelCodeEnum {
wx_pub("wx_pub", "微信 JSAPI 支付");
/**
* 编码
*
* 参考 https://www.pingxx.com/api/支付渠道属性值.html
*/
private String code;
/**
* 名字
*/
private String name;
}

View File

@ -3,16 +3,15 @@ package cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order;
import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayAppDO; import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayAppDO;
import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayChannelDO; import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayChannelDO;
import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayMerchantDO; import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayMerchantDO;
import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderNotifyStatusEnum;
import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderRefundStatusEnum;
import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderStatusEnum; import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderStatusEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum; import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import lombok.*; import lombok.*;
import java.util.Date; import java.util.Date;
import java.util.Map;
/** /**
* 支付订单 DO * 支付订单 DO
@ -61,7 +60,7 @@ public class PayOrderDO extends BaseDO {
/** /**
* 商户订单编号 * 商户订单编号
* 例如说内部系统 A 的订单号需要保证每个 PayMerchantDO 唯一 TODO 芋艿需要在测试下 * 例如说内部系统 A 的订单号需要保证每个 PayMerchantDO 唯一
*/ */
private String merchantOrderId; private String merchantOrderId;
/** /**
@ -72,6 +71,16 @@ public class PayOrderDO extends BaseDO {
* 商品描述信息 * 商品描述信息
*/ */
private String body; private String body;
/**
* 异步通知地址
*/
private String notifyUrl;
/**
* 通知商户支付结果的回调状态
*
* 枚举 {@link PayOrderNotifyStatusEnum}
*/
private Integer notifyStatus;
// /** // /**
// * 商户拓展参数 // * 商户拓展参数
// */ // */
@ -99,11 +108,6 @@ public class PayOrderDO extends BaseDO {
* 枚举 {@link PayOrderStatusEnum} * 枚举 {@link PayOrderStatusEnum}
*/ */
private Integer status; private Integer status;
/**
* 通知商户支付结果的回调状态
* TODO 芋艿0 未发送 1 已发送
*/
private Integer notifyStatus;
/** /**
* 用户 IP * 用户 IP
*/ */
@ -122,23 +126,12 @@ public class PayOrderDO extends BaseDO {
* 关联 {@link PayOrderDO#getId()} * 关联 {@link PayOrderDO#getId()}
*/ */
private Long successExtensionId; private Long successExtensionId;
/**
* 支付渠道的额外参数
*
* 参见 https://www.pingxx.com/api/支付渠道%20extra%20参数说明.html
*/
@TableField(typeHandler = JacksonTypeHandler.class)
private Map<String, String> channelExtras;
/**
* 异步通知地址
*/
private String notifyUrl;
// ========== 退款相关字段 ========== // ========== 退款相关字段 ==========
/** /**
* 退款状态 * 退款状态
* *
* TODO 芋艿0 - 未退款1 - 部分退款 2 - 全额退款 * 枚举 {@link PayOrderRefundStatusEnum}
*/ */
private Integer refundStatus; private Integer refundStatus;
/** /**

View File

@ -3,9 +3,13 @@ package cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order;
import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayChannelDO; import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayChannelDO;
import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderStatusEnum; import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderStatusEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import lombok.*; import lombok.*;
import java.util.Map;
/** /**
* 支付订单拓展 DO * 支付订单拓展 DO
* *
@ -49,7 +53,7 @@ public class PayOrderExtensionDO extends BaseDO {
/** /**
* 渠道编码 * 渠道编码
*/ */
private Integer channelCode; private String channelCode;
/** /**
* 用户 IP * 用户 IP
*/ */
@ -61,6 +65,13 @@ public class PayOrderExtensionDO extends BaseDO {
* 注意只包含上述枚举的 WAITING SUCCESS * 注意只包含上述枚举的 WAITING SUCCESS
*/ */
private Integer status; private Integer status;
/**
* 支付渠道的额外参数
*
* 参见 https://www.pingxx.com/api/支付渠道%20extra%20参数说明.html
*/
@TableField(typeHandler = JacksonTypeHandler.class)
private Map<String, String> channelExtras;
/** /**
* 支付渠道异步通知的内容 * 支付渠道异步通知的内容
* *

View File

@ -71,6 +71,15 @@ public class PayRefundDO extends BaseDO {
// * 商户拓展参数 // * 商户拓展参数
// */ // */
// private String merchantExtra; // private String merchantExtra;
/**
* 异步通知地址
*/
private String notifyUrl;
/**
* 通知商户退款结果的回调状态
* TODO 芋艿0 未发送 1 已发送
*/
private Integer notifyStatus;
// ========== 退款相关字段 ========== // ========== 退款相关字段 ==========
/** /**
@ -79,11 +88,6 @@ public class PayRefundDO extends BaseDO {
* TODO 芋艿状态枚举 * TODO 芋艿状态枚举
*/ */
private Integer status; private Integer status;
/**
* 通知商户退款结果的回调状态
* TODO 芋艿0 未发送 1 已发送
*/
private Integer notifyStatus;
/** /**
* 用户 IP * 用户 IP
*/ */
@ -110,10 +114,6 @@ public class PayRefundDO extends BaseDO {
* 参见 https://www.pingxx.com/api/Refunds%20退款概述.html * 参见 https://www.pingxx.com/api/Refunds%20退款概述.html
*/ */
private String channelExtra; private String channelExtra;
/**
* 异步通知地址
*/
private String notifyUrl;
// ========== 渠道相关字段 ========== // ========== 渠道相关字段 ==========
/** /**

View File

@ -0,0 +1,29 @@
package cn.iocoder.yudao.coreservice.modules.pay.enums.order;
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 支付订单的通知状态枚举
*
* @author 芋道源码
*/
@Getter
@AllArgsConstructor
public enum PayOrderNotifyStatusEnum implements IntArrayValuable {
NO(0, "未通知"),
SUCCESS(10, "通知成功"),
FAILURE(20, "通知失败")
;
private final Integer status;
private final String name;
@Override
public int[] array() {
return new int[0];
}
}

View File

@ -0,0 +1,29 @@
package cn.iocoder.yudao.coreservice.modules.pay.enums.order;
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 支付订单的退款状态枚举
*
* @author 芋道源码
*/
@Getter
@AllArgsConstructor
public enum PayOrderRefundStatusEnum implements IntArrayValuable {
NO(0, "未退款"),
SOME(10, "部分退款"),
ALL(20, "全部退款")
;
private final Integer status;
private final String name;
@Override
public int[] array() {
return new int[0];
}
}

View File

@ -12,6 +12,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.Resource; import javax.annotation.Resource;
import javax.validation.Valid; import javax.validation.Valid;
@ -50,6 +51,7 @@ public class PayChannelCoreServiceImpl implements PayChannelCoreService {
private PayClientFactory payClientFactory; private PayClientFactory payClientFactory;
@Override @Override
@PostConstruct
public void initPayClients() { public void initPayClients() {
// 获取支付渠道如果有更新 // 获取支付渠道如果有更新
List<PayChannelDO> payChannels = this.loadPayChannelIfUpdate(maxUpdateTime); List<PayChannelDO> payChannels = this.loadPayChannelIfUpdate(maxUpdateTime);

View File

@ -28,6 +28,6 @@ public interface PayOrderCoreService {
* @param reqDTO 提交请求 * @param reqDTO 提交请求
* @return 提交结果 * @return 提交结果
*/ */
PayOrderSubmitRespDTO submitPayOrder(PayOrderSubmitReqDTO reqDTO); PayOrderSubmitRespDTO submitPayOrder(@Valid PayOrderSubmitReqDTO reqDTO);
} }

View File

@ -1,11 +1,14 @@
package cn.iocoder.yudao.coreservice.modules.pay.service.order.dto; package cn.iocoder.yudao.coreservice.modules.pay.service.order.dto;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import lombok.Data; import lombok.Data;
import lombok.experimental.Accessors; import lombok.experimental.Accessors;
import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import java.io.Serializable; import java.io.Serializable;
import java.util.Map;
/** /**
* 支付单提交 Request DTO * 支付单提交 Request DTO
@ -38,4 +41,10 @@ public class PayOrderSubmitReqDTO implements Serializable {
@NotEmpty(message = "用户 IP 不能为空") @NotEmpty(message = "用户 IP 不能为空")
private String userIp; private String userIp;
/**
* 支付渠道的额外参数
*/
@TableField(typeHandler = JacksonTypeHandler.class)
private Map<String, String> channelExtras;
} }

View File

@ -9,6 +9,7 @@ import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO;
import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderExtensionDO; import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderExtensionDO;
import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayOrderCoreMapper; import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayOrderCoreMapper;
import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayOrderExtensionCoreMapper; import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayOrderExtensionCoreMapper;
import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderNotifyStatusEnum;
import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderStatusEnum; import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderStatusEnum;
import cn.iocoder.yudao.coreservice.modules.pay.service.merchant.PayAppCoreService; import cn.iocoder.yudao.coreservice.modules.pay.service.merchant.PayAppCoreService;
import cn.iocoder.yudao.coreservice.modules.pay.service.merchant.PayChannelCoreService; import cn.iocoder.yudao.coreservice.modules.pay.service.merchant.PayChannelCoreService;
@ -20,12 +21,14 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.pay.core.client.PayClient; import cn.iocoder.yudao.framework.pay.core.client.PayClient;
import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory; import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource; import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.Date; import java.util.Date;
import java.util.Objects;
import static cn.iocoder.yudao.coreservice.modules.pay.enums.PayErrorCodeCoreConstants.*; import static cn.iocoder.yudao.coreservice.modules.pay.enums.PayErrorCodeCoreConstants.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@ -36,7 +39,7 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU
* @author 芋道源码 * @author 芋道源码
*/ */
@Service @Service
@Valid @Validated
@Slf4j @Slf4j
public class PayOrderCoreServiceImpl implements PayOrderCoreService { public class PayOrderCoreServiceImpl implements PayOrderCoreService {
@ -68,10 +71,16 @@ public class PayOrderCoreServiceImpl implements PayOrderCoreService {
} }
// 创建支付交易单 // 创建支付交易单
// TODO 芋艿需要看看还有啥要补全的字段
order = PayOrderCoreConvert.INSTANCE.convert(reqDTO) order = PayOrderCoreConvert.INSTANCE.convert(reqDTO)
.setStatus(PayOrderStatusEnum.WAITING.getStatus()) .setMerchantId(app.getMerchantId()).setAppId(app.getId());
.setNotifyUrl(app.getPayNotifyUrl()); // 商户相关字段
order.setNotifyUrl(app.getPayNotifyUrl())
.setNotifyStatus(PayOrderNotifyStatusEnum.NO.getStatus());
// 订单相关字段
order.setStatus(PayOrderStatusEnum.WAITING.getStatus());
// 退款相关字段
order.setRefundStatus(PayOrderNotifyStatusEnum.NO.getStatus())
.setRefundTimes(0).setRefundAmount(0L);
payOrderCoreMapper.insert(order); payOrderCoreMapper.insert(order);
// 最终返回 // 最终返回
return order.getId(); return order.getId();
@ -80,9 +89,9 @@ public class PayOrderCoreServiceImpl implements PayOrderCoreService {
@Override @Override
public PayOrderSubmitRespDTO submitPayOrder(PayOrderSubmitReqDTO reqDTO) { public PayOrderSubmitRespDTO submitPayOrder(PayOrderSubmitReqDTO reqDTO) {
// 校验 App // 校验 App
PayAppDO app = payAppCoreService.validPayApp(reqDTO.getId()); PayAppDO app = payAppCoreService.validPayApp(reqDTO.getAppId());
// 校验支付渠道是否有效 // 校验支付渠道是否有效
PayChannelDO channel = payChannelCoreService.validPayChannel(reqDTO.getId(), reqDTO.getChannelCode()); PayChannelDO channel = payChannelCoreService.validPayChannel(reqDTO.getAppId(), reqDTO.getChannelCode());
// 校验支付客户端是否正确初始化 // 校验支付客户端是否正确初始化
PayClient client = payClientFactory.getPayClient(channel.getId()); PayClient client = payClientFactory.getPayClient(channel.getId());
if (client == null) { if (client == null) {
@ -92,7 +101,7 @@ public class PayOrderCoreServiceImpl implements PayOrderCoreService {
// 获得 PayOrderDO 并校验其是否存在 // 获得 PayOrderDO 并校验其是否存在
PayOrderDO order = payOrderCoreMapper.selectById(reqDTO.getId()); PayOrderDO order = payOrderCoreMapper.selectById(reqDTO.getId());
if (order == null || order.getAppId().equals(reqDTO.getAppId())) { // 是否存在 if (order == null || !Objects.equals(order.getAppId(), reqDTO.getAppId())) { // 是否存在
throw exception(PAY_ORDER_NOT_FOUND); throw exception(PAY_ORDER_NOT_FOUND);
} }
if (!PayOrderStatusEnum.WAITING.getStatus().equals(order.getStatus())) { // 校验状态必须是待支付 if (!PayOrderStatusEnum.WAITING.getStatus().equals(order.getStatus())) { // 校验状态必须是待支付
@ -102,18 +111,25 @@ public class PayOrderCoreServiceImpl implements PayOrderCoreService {
// 插入 PayOrderExtensionDO // 插入 PayOrderExtensionDO
PayOrderExtensionDO orderExtension = PayOrderCoreConvert.INSTANCE.convert(reqDTO) PayOrderExtensionDO orderExtension = PayOrderCoreConvert.INSTANCE.convert(reqDTO)
.setOrderId(order.getId()).setNo(generateOrderExtensionNo()) .setOrderId(order.getId()).setNo(generateOrderExtensionNo())
.setChannelId(channel.getId()).setChannelCode(channel.getCode())
.setStatus(PayOrderStatusEnum.WAITING.getStatus()); .setStatus(PayOrderStatusEnum.WAITING.getStatus());
payOrderExtensionCoreMapper.insert(orderExtension); payOrderExtensionCoreMapper.insert(orderExtension);
// 调用三方接口 // 调用三方接口
// TODO 暂时传入 extra = null PayOrderUnifiedReqDTO unifiedOrderReqDTO = PayOrderCoreConvert.INSTANCE.convert2(reqDTO);
CommonResult<?> invokeResult = client.unifiedOrder(PayOrderCoreConvert.INSTANCE.convert2(reqDTO)); // 商户相关字段
invokeResult.checkError(); unifiedOrderReqDTO.setMerchantOrderId(order.getMerchantOrderId())
.setSubject(order.getSubject()).setBody(order.getBody())
.setNotifyUrl(app.getPayNotifyUrl());
// 订单相关字段
unifiedOrderReqDTO.setAmount(order.getAmount()).setExpireTime(order.getExpireTime());
CommonResult<?> unifiedOrderResult = client.unifiedOrder(unifiedOrderReqDTO);
unifiedOrderResult.checkError();
// TODO 轮询三方接口是否已经支付的任务 // TODO 轮询三方接口是否已经支付的任务
// 返回成功 // 返回成功
return new PayOrderSubmitRespDTO().setExtensionId(orderExtension.getId()) return new PayOrderSubmitRespDTO().setExtensionId(orderExtension.getId())
.setInvokeResponse(JsonUtils.toJsonString(invokeResult)); .setInvokeResponse(JsonUtils.toJsonString(unifiedOrderResult));
} }
private String generateOrderExtensionNo() { private String generateOrderExtensionNo() {

View File

@ -0,0 +1,52 @@
package cn.iocoder.yudao.coreservice.modules.pay.service.order;
import cn.iocoder.yudao.coreservice.BaseDbIntegrationTest;
import cn.iocoder.yudao.coreservice.modules.pay.service.merchant.impl.PayAppCoreServiceImpl;
import cn.iocoder.yudao.coreservice.modules.pay.service.merchant.impl.PayChannelCoreServiceImpl;
import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayOrderCreateReqDTO;
import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayOrderSubmitReqDTO;
import cn.iocoder.yudao.coreservice.modules.pay.service.order.impl.PayOrderCoreServiceImpl;
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.framework.pay.config.YudaoPayAutoConfiguration;
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.Import;
import javax.annotation.Resource;
import java.time.Duration;
@Import({PayOrderCoreServiceImpl.class, PayAppCoreServiceImpl.class,
PayChannelCoreServiceImpl.class, YudaoPayAutoConfiguration.class})
public class PayOrderCoreServiceTest extends BaseDbIntegrationTest {
@Resource
private PayOrderCoreService payOrderCoreService;
@Test
public void testCreatePayOrder() {
// 构造请求
PayOrderCreateReqDTO reqDTO = new PayOrderCreateReqDTO();
reqDTO.setAppId(6L);
reqDTO.setUserIp("127.0.0.1");
reqDTO.setMerchantOrderId(String.valueOf(System.currentTimeMillis()));
reqDTO.setSubject("标题");
reqDTO.setBody("内容");
reqDTO.setAmount(100);
reqDTO.setExpireTime(DateUtils.addTime(Duration.ofDays(1)));
// 发起请求
payOrderCoreService.createPayOrder(reqDTO);
}
@Test
public void testSubmitPayOrder() {
// 构造请求
PayOrderSubmitReqDTO reqDTO = new PayOrderSubmitReqDTO();
reqDTO.setId(10L);
reqDTO.setAppId(6L);
reqDTO.setChannelCode(PayChannelEnum.WX_PUB.getCode());
reqDTO.setUserIp("127.0.0.1");
// 发起请求
payOrderCoreService.submitPayOrder(reqDTO);
}
}

View File

@ -0,0 +1 @@
package cn.iocoder.yudao.coreservice.modules.pay.service;

View File

@ -60,7 +60,7 @@ mybatis-plus:
logic-delete-value: 1 # 逻辑已删除值(默认为 1) logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0) logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
mapper-locations: classpath*:mapper/*.xml mapper-locations: classpath*:mapper/*.xml
type-aliases-package: ${yudao.info.base-package}.modules.*.dal.dataobject, ${yudao.core-service.base-package}.modules.*.dal.dataobject type-aliases-package: ${yudao.core-service.base-package}.modules.*.dal.dataobject
--- #################### 定时任务相关配置 #################### --- #################### 定时任务相关配置 ####################

View File

@ -29,4 +29,13 @@ public class ObjectUtils {
return obj1.compareTo(obj2) > 0 ? obj1 : obj2; return obj1.compareTo(obj2) > 0 ? obj1 : obj2;
} }
public static <T> T defaultIfNull(T... array) {
for (T item : array) {
if (item != null) {
return item;
}
}
return null;
}
} }

View File

@ -1,6 +1,5 @@
package cn.iocoder.yudao.framework.pay.core.client; package cn.iocoder.yudao.framework.pay.core.client;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
/** /**
@ -17,7 +16,12 @@ public interface PayClient {
*/ */
Long getId(); Long getId();
// TODO 缺少注释 /**
CommonResult<?> unifiedOrder(PayOrderUnifiedReqDTO reqDTO); * 调用支付渠道统一下单
*
* @param reqDTO 下单信息
* @return 各支付渠道的返回结果
*/
PayCommonResult<?> unifiedOrder(PayOrderUnifiedReqDTO reqDTO);
} }

View File

@ -54,7 +54,7 @@ public class PayOrderUnifiedReqDTO {
*/ */
@NotNull(message = "支付金额不能为空") @NotNull(message = "支付金额不能为空")
@DecimalMin(value = "0", inclusive = false, message = "支付金额必须大于零") @DecimalMin(value = "0", inclusive = false, message = "支付金额必须大于零")
private Integer amount; private Long amount;
/** /**
* 支付过期时间 * 支付过期时间

View File

@ -1,10 +1,15 @@
package cn.iocoder.yudao.framework.pay.core.client.impl; package cn.iocoder.yudao.framework.pay.core.client.impl;
import cn.hutool.extra.validation.ValidationUtil;
import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping;
import cn.iocoder.yudao.framework.pay.core.client.PayClient; import cn.iocoder.yudao.framework.pay.core.client.PayClient;
import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig; import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping; import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
/** /**
* 支付客户端的抽象类提供模板方法减少子类的冗余代码 * 支付客户端的抽象类提供模板方法减少子类的冗余代码
* *
@ -30,7 +35,7 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
*/ */
protected Config config; protected Config config;
protected Double calculateAmount(Integer amount) { protected Double calculateAmount(Long amount) {
return amount / 100.0; return amount / 100.0;
} }
@ -70,4 +75,23 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
return channelId; return channelId;
} }
@Override
public final PayCommonResult<?> unifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
ValidationUtil.validate(reqDTO);
// 执行短信发送
PayCommonResult<?> result;
try {
result = doUnifiedOrder(reqDTO);
} catch (Throwable ex) {
// 打印异常日志
log.error("[unifiedOrder][request({}) 发起支付失败]", toJsonString(reqDTO), ex);
// 封装返回
return PayCommonResult.error(ex);
}
return result;
}
protected abstract PayCommonResult<?> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO)
throws Throwable;
} }

View File

@ -1,7 +1,6 @@
package cn.iocoder.yudao.framework.pay.core.client.impl.alipay; package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.bean.BeanUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult; import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient; import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
@ -42,7 +41,7 @@ public class AlipayQrPayClient extends AbstractPayClient<AlipayPayClientConfig>
} }
@Override @Override
public CommonResult<AlipayTradePrecreateResponse> unifiedOrder(PayOrderUnifiedReqDTO reqDTO) { public PayCommonResult<AlipayTradePrecreateResponse> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
// 构建 AlipayTradePrecreateModel 请求 // 构建 AlipayTradePrecreateModel 请求
AlipayTradePrecreateModel model = new AlipayTradePrecreateModel(); AlipayTradePrecreateModel model = new AlipayTradePrecreateModel();
model.setOutTradeNo(reqDTO.getMerchantOrderId()); model.setOutTradeNo(reqDTO.getMerchantOrderId());

View File

@ -1,7 +1,6 @@
package cn.iocoder.yudao.framework.pay.core.client.impl.alipay; package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.bean.BeanUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult; import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient; import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
@ -37,7 +36,7 @@ public class AlipayWapPayClient extends AbstractPayClient<AlipayPayClientConfig>
} }
@Override @Override
public CommonResult<AlipayTradeWapPayResponse> unifiedOrder(PayOrderUnifiedReqDTO reqDTO) { public PayCommonResult<AlipayTradeWapPayResponse> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
// 构建 AlipayTradeWapPayModel 请求 // 构建 AlipayTradeWapPayModel 请求
AlipayTradeWapPayModel model = new AlipayTradeWapPayModel(); AlipayTradeWapPayModel model = new AlipayTradeWapPayModel();
model.setOutTradeNo(reqDTO.getMerchantOrderId()); model.setOutTradeNo(reqDTO.getMerchantOrderId());

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.framework.pay.core.client.impl.wx; package cn.iocoder.yudao.framework.pay.core.client.impl.wx;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.exception.ErrorCode; import cn.iocoder.yudao.framework.common.exception.ErrorCode;
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants; import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping; import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping;
@ -44,6 +45,11 @@ public class WXCodeMapping extends AbstractPayCodeMapping {
return PAY_OPENID_ERROR; return PAY_OPENID_ERROR;
} }
} }
if (Objects.equals(apiCode, "CustomErrorCode")) {
if (StrUtil.contains(apiMsg, "必填字段")) {
return PAY_PARAM_MISSING;
}
}
return null; return null;
} }

View File

@ -3,8 +3,8 @@ package cn.iocoder.yudao.framework.pay.core.client.impl.wx;
import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil; import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.io.FileUtils; import cn.iocoder.yudao.framework.common.util.io.FileUtils;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult; import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient; import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
@ -21,7 +21,6 @@ import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl; import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import static cn.hutool.core.util.ObjectUtil.defaultIfNull;
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
import static cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXCodeMapping.CODE_SUCCESS; import static cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXCodeMapping.CODE_SUCCESS;
import static cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXCodeMapping.MESSAGE_SUCCESS; import static cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXCodeMapping.MESSAGE_SUCCESS;
@ -62,7 +61,7 @@ public class WXPubPayClient extends AbstractPayClient<WXPayClientConfig> {
} }
@Override @Override
public CommonResult<WxPayMpOrderResult> unifiedOrder(PayOrderUnifiedReqDTO reqDTO) { public PayCommonResult<WxPayMpOrderResult> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
WxPayMpOrderResult response; WxPayMpOrderResult response;
try { try {
switch (config.getApiVersion()) { switch (config.getApiVersion()) {
@ -80,8 +79,8 @@ public class WXPubPayClient extends AbstractPayClient<WXPayClientConfig> {
} }
} catch (WxPayException e) { } catch (WxPayException e) {
log.error("[unifiedOrder][request({}) 发起支付失败,原因({})]", toJsonString(reqDTO), e); log.error("[unifiedOrder][request({}) 发起支付失败,原因({})]", toJsonString(reqDTO), e);
return PayCommonResult.build(defaultIfNull(e.getErrCode(), e.getReturnCode()), return PayCommonResult.build(ObjectUtils.defaultIfNull(e.getErrCode(), e.getReturnCode(), "CustomErrorCode"),
defaultIfNull(e.getErrCodeDes(), e.getReturnMsg()),null, codeMapping); ObjectUtils.defaultIfNull(e.getErrCodeDes(), e.getCustomErrorMsg()),null, codeMapping);
} }
return PayCommonResult.build(CODE_SUCCESS, MESSAGE_SUCCESS, response, codeMapping); return PayCommonResult.build(CODE_SUCCESS, MESSAGE_SUCCESS, response, codeMapping);
} }
@ -92,9 +91,9 @@ public class WXPubPayClient extends AbstractPayClient<WXPayClientConfig> {
.outTradeNo(reqDTO.getMerchantOrderId()) .outTradeNo(reqDTO.getMerchantOrderId())
// TODO 芋艿貌似没 title // TODO 芋艿貌似没 title
.body(reqDTO.getBody()) .body(reqDTO.getBody())
.totalFee(reqDTO.getAmount()) // 单位分 .totalFee(reqDTO.getAmount().intValue()) // 单位分
.timeExpire(DateUtil.format(reqDTO.getExpireTime(), "yyyyMMddHHmmss")) .timeExpire(DateUtil.format(reqDTO.getExpireTime(), "yyyyMMddHHmmss"))
.spbillCreateIp(reqDTO.getClientIp()) .spbillCreateIp(reqDTO.getUserIp())
.openid("ockUAwIZ-0OeMZl9ogcZ4ILrGba0") // TODO 芋艿先随便写死 .openid("ockUAwIZ-0OeMZl9ogcZ4ILrGba0") // TODO 芋艿先随便写死
.notifyUrl(reqDTO.getNotifyUrl()) .notifyUrl(reqDTO.getNotifyUrl())
.build(); .build();
@ -108,10 +107,10 @@ public class WXPubPayClient extends AbstractPayClient<WXPayClientConfig> {
request.setOutTradeNo(reqDTO.getMerchantOrderId()); request.setOutTradeNo(reqDTO.getMerchantOrderId());
// TODO 芋艿貌似没 title // TODO 芋艿貌似没 title
request.setDescription(reqDTO.getBody()); request.setDescription(reqDTO.getBody());
request.setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(reqDTO.getAmount())); // 单位分 request.setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(reqDTO.getAmount().intValue())); // 单位分
request.setTimeExpire(DateUtil.format(reqDTO.getExpireTime(), "yyyyMMddHHmmss")); request.setTimeExpire(DateUtil.format(reqDTO.getExpireTime(), "yyyyMMddHHmmss"));
request.setPayer(new WxPayUnifiedOrderV3Request.Payer().setOpenid("ockUAwIZ-0OeMZl9ogcZ4ILrGba0")); // TODO 芋艿先随便写死 request.setPayer(new WxPayUnifiedOrderV3Request.Payer().setOpenid("ockUAwIZ-0OeMZl9ogcZ4ILrGba0")); // TODO 芋艿先随便写死
request.setSceneInfo(new WxPayUnifiedOrderV3Request.SceneInfo().setPayerClientIp(reqDTO.getClientIp())); request.setSceneInfo(new WxPayUnifiedOrderV3Request.SceneInfo().setPayerClientIp(reqDTO.getUserIp()));
request.setNotifyUrl(reqDTO.getNotifyUrl()); request.setNotifyUrl(reqDTO.getNotifyUrl());
// 执行请求 // 执行请求
return client.createOrderV3(TradeTypeEnum.JSAPI, request); return client.createOrderV3(TradeTypeEnum.JSAPI, request);

View File

@ -20,6 +20,7 @@ public interface PayFrameworkErrorCodeConstants {
// ========== 其它相关 2002000900 开头 ========== // ========== 其它相关 2002000900 开头 ==========
ErrorCode PAY_OPENID_ERROR = new ErrorCode(2002000900, "无效的 openid"); // 例如说微信 openid 未授权过 ErrorCode PAY_OPENID_ERROR = new ErrorCode(2002000900, "无效的 openid"); // 例如说微信 openid 未授权过
ErrorCode PAY_PARAM_MISSING = new ErrorCode(2002000901, "请求参数缺失"); // 例如说支付少传了金额
ErrorCode EXCEPTION = new ErrorCode(2002000999, "调用异常"); ErrorCode EXCEPTION = new ErrorCode(2002000999, "调用异常");

View File

@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.iocoder.yudao.framework.pay.config.YudaoPayAutoConfiguration

View File

@ -117,11 +117,11 @@ public class PayClientFactoryImplTest {
private static PayOrderUnifiedReqDTO buildPayOrderUnifiedReqDTO() { private static PayOrderUnifiedReqDTO buildPayOrderUnifiedReqDTO() {
PayOrderUnifiedReqDTO reqDTO = new PayOrderUnifiedReqDTO(); PayOrderUnifiedReqDTO reqDTO = new PayOrderUnifiedReqDTO();
reqDTO.setAmount(123); reqDTO.setAmount(123L);
reqDTO.setSubject("IPhone 13"); reqDTO.setSubject("IPhone 13");
reqDTO.setBody("biubiubiu"); reqDTO.setBody("biubiubiu");
reqDTO.setMerchantOrderId(String.valueOf(System.currentTimeMillis())); reqDTO.setMerchantOrderId(String.valueOf(System.currentTimeMillis()));
reqDTO.setClientIp("127.0.0.1"); reqDTO.setUserIp("127.0.0.1");
reqDTO.setNotifyUrl("http://127.0.0.1:8080"); reqDTO.setNotifyUrl("http://127.0.0.1:8080");
return reqDTO; return reqDTO;
} }