Initial commit with remote deployment configuration
Some checks failed
aagro-ui-admin CI / build (14.x) (push) Has been cancelled
aagro-ui-admin CI / build (16.x) (push) Has been cancelled
Java CI with Maven / build (11) (push) Has been cancelled
Java CI with Maven / build (17) (push) Has been cancelled
Java CI with Maven / build (8) (push) Has been cancelled

This commit is contained in:
2025-10-04 18:08:05 +08:00
commit 80bc86d703
4365 changed files with 364253 additions and 0 deletions

89
aagro-module-pay/pom.xml Normal file
View File

@@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>cn.aagro.gg</groupId>
<artifactId>aagro</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>aagro-module-pay</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>
pay 模块,我们放支付业务,提供业务的支付能力。
例如说:商户、应用、支付、退款等等
</description>
<dependencies>
<dependency>
<groupId>cn.aagro.gg</groupId>
<artifactId>aagro-module-system</artifactId>
<version>${revision}</version>
</dependency>
<!-- 业务组件 -->
<dependency>
<groupId>cn.aagro.gg</groupId>
<artifactId>aagro-spring-boot-starter-biz-tenant</artifactId>
</dependency>
<!-- Web 相关 -->
<dependency>
<groupId>cn.aagro.gg</groupId>
<artifactId>aagro-spring-boot-starter-security</artifactId>
</dependency>
<!-- DB 相关 -->
<dependency>
<groupId>cn.aagro.gg</groupId>
<artifactId>aagro-spring-boot-starter-mybatis</artifactId>
</dependency>
<dependency>
<groupId>cn.aagro.gg</groupId>
<artifactId>aagro-spring-boot-starter-redis</artifactId>
</dependency>
<!-- Job 定时任务相关 -->
<dependency>
<groupId>cn.aagro.gg</groupId>
<artifactId>aagro-spring-boot-starter-job</artifactId>
</dependency>
<!-- Test 测试相关 -->
<dependency>
<groupId>cn.aagro.gg</groupId>
<artifactId>aagro-spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 工具类相关 -->
<dependency>
<groupId>cn.aagro.gg</groupId>
<artifactId>aagro-spring-boot-starter-excel</artifactId>
</dependency>
<!-- 三方云服务相关 -->
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.35.79.ALL</version>
<exclusions>
<exclusion>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-pay</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,34 @@
package cn.aagro.pp.module.pay.api.notify.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
/**
* 支付单的通知 Request DTO
*
* @author 芋道源码
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PayOrderNotifyReqDTO {
/**
* 商户订单编号
*/
@NotEmpty(message = "商户订单号不能为空")
private String merchantOrderId;
/**
* 支付订单编号
*/
@NotNull(message = "支付订单编号不能为空")
private Long payOrderId;
}

View File

@@ -0,0 +1,40 @@
package cn.aagro.pp.module.pay.api.notify.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
/**
* 退款单的通知 Request DTO
*
* @author 芋道源码
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PayRefundNotifyReqDTO {
/**
* 商户退款单编号
*/
@NotEmpty(message = "商户退款单编号不能为空")
private String merchantOrderId;
/**
* 商户退款编号
*/
@NotEmpty(message = "商户退款编号不能为空")
private String merchantRefundId;
/**
* 支付退款编号
*/
@NotNull(message = "支付退款编号不能为空")
private Long payRefundId;
}

View File

@@ -0,0 +1,34 @@
package cn.aagro.pp.module.pay.api.notify.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
/**
* 转账单的通知 Request DTO
*
* @author jason
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PayTransferNotifyReqDTO {
/**
* 商户转账单号
*/
@NotEmpty(message = "商户转账单号不能为空")
private String merchantTransferId;
/**
* 转账订单编号
*/
@NotNull(message = "转账订单编号不能为空")
private Long payTransferId;
}

View File

@@ -0,0 +1,4 @@
/**
* 占位符,无特殊作用
*/
package cn.aagro.pp.module.pay.api.notify;

View File

@@ -0,0 +1,40 @@
package cn.aagro.pp.module.pay.api.order;
import cn.aagro.pp.module.pay.api.order.dto.PayOrderCreateReqDTO;
import cn.aagro.pp.module.pay.api.order.dto.PayOrderRespDTO;
import javax.validation.Valid;
/**
* 支付单 API 接口
*
* @author LeeYan9
* @since 2022-08-26
*/
public interface PayOrderApi {
/**
* 创建支付单
*
* @param reqDTO 创建请求
* @return 支付单编号
*/
Long createOrder(@Valid PayOrderCreateReqDTO reqDTO);
/**
* 获得支付单
*
* @param id 支付单编号
* @return 支付单
*/
PayOrderRespDTO getOrder(Long id);
/**
* 更新支付订单价格
*
* @param id 支付单编号
* @param payPrice 支付单价格
*/
void updatePayOrderPrice(Long id, Integer payPrice);
}

View File

@@ -0,0 +1,39 @@
package cn.aagro.pp.module.pay.api.order;
import cn.aagro.pp.module.pay.api.order.dto.PayOrderCreateReqDTO;
import cn.aagro.pp.module.pay.api.order.dto.PayOrderRespDTO;
import cn.aagro.pp.module.pay.convert.order.PayOrderConvert;
import cn.aagro.pp.module.pay.dal.dataobject.order.PayOrderDO;
import cn.aagro.pp.module.pay.service.order.PayOrderService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* 支付单 API 实现类
*
* @author 芋道源码
*/
@Service
public class PayOrderApiImpl implements PayOrderApi {
@Resource
private PayOrderService payOrderService;
@Override
public Long createOrder(PayOrderCreateReqDTO reqDTO) {
return payOrderService.createOrder(reqDTO);
}
@Override
public PayOrderRespDTO getOrder(Long id) {
PayOrderDO order = payOrderService.getOrder(id);
return PayOrderConvert.INSTANCE.convert2(order);
}
@Override
public void updatePayOrderPrice(Long id, Integer payPrice) {
payOrderService.updatePayOrderPrice(id, payPrice);
}
}

View File

@@ -0,0 +1,78 @@
package cn.aagro.pp.module.pay.api.order.dto;
import cn.aagro.pp.framework.common.enums.UserTypeEnum;
import cn.aagro.pp.framework.common.validation.InEnum;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 支付单创建 Request DTO
*/
@Data
public class PayOrderCreateReqDTO implements Serializable {
public static final int SUBJECT_MAX_LENGTH = 32;
/**
* 应用标识
*/
@NotNull(message = "应用标识不能为空")
private String appKey;
/**
* 用户 IP
*/
@NotEmpty(message = "用户 IP 不能为空")
private String userIp;
/**
* 用户编号
*/
private Long userId;
/**
* 用户类型
*/
@InEnum(UserTypeEnum.class)
private Integer userType;
// ========== 商户相关字段 ==========
/**
* 商户订单编号
*/
@NotEmpty(message = "商户订单编号不能为空")
private String merchantOrderId;
/**
* 商品标题
*/
@NotEmpty(message = "商品标题不能为空")
@Length(max = SUBJECT_MAX_LENGTH, message = "商品标题不能超过 32")
private String subject;
/**
* 商品描述
*/
@Length(max = 128, message = "商品描述信息长度不能超过128")
private String body;
// ========== 订单相关字段 ==========
/**
* 支付金额,单位:分
*/
@NotNull(message = "支付金额不能为空")
@DecimalMin(value = "0", inclusive = false, message = "支付金额必须大于零")
private Integer price;
/**
* 支付过期时间
*/
@NotNull(message = "支付过期时间不能为空")
private LocalDateTime expireTime;
}

View File

@@ -0,0 +1,68 @@
package cn.aagro.pp.module.pay.api.order.dto;
import cn.aagro.pp.module.pay.enums.order.PayOrderStatusEnum;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 支付单信息 Response DTO
*
* @author 芋道源码
*/
@Data
public class PayOrderRespDTO {
/**
* 订单编号,数据库自增
*/
private Long id;
/**
* 渠道编码
*
* 枚举 PayChannelEnum
*/
private String channelCode;
// ========== 商户相关字段 ==========
/**
* 商户订单编号
* 例如说,内部系统 A 的订单号。需要保证每个 PayMerchantDO 唯一
*/
private String merchantOrderId;
// ========== 订单相关字段 ==========
/**
* 商品标题
*/
private String subject;
/**
* 支付金额,单位:分
*/
private Integer price;
/**
* 支付状态
*
* 枚举 {@link PayOrderStatusEnum}
*/
private Integer status;
/**
* 订单支付成功时间
*/
private LocalDateTime successTime;
// ========== 渠道相关字段 ==========
/**
* 渠道用户编号
*
* 例如说,微信 openid、支付宝账号
*/
private String channelUserId;
/**
* 渠道订单号
*/
private String channelOrderNo;
}

View File

@@ -0,0 +1,4 @@
/**
* pay API 包,定义并实现提供给其它模块的 API
*/
package cn.aagro.pp.module.pay.api;

View File

@@ -0,0 +1,31 @@
package cn.aagro.pp.module.pay.api.refund;
import cn.aagro.pp.module.pay.api.refund.dto.PayRefundCreateReqDTO;
import cn.aagro.pp.module.pay.api.refund.dto.PayRefundRespDTO;
import javax.validation.Valid;
/**
* 退款单 API 接口
*
* @author 芋道源码
*/
public interface PayRefundApi {
/**
* 创建退款单
*
* @param reqDTO 创建请求
* @return 退款单编号
*/
Long createRefund(@Valid PayRefundCreateReqDTO reqDTO);
/**
* 获得退款单
*
* @param id 退款单编号
* @return 退款单
*/
PayRefundRespDTO getRefund(Long id);
}

View File

@@ -0,0 +1,36 @@
package cn.aagro.pp.module.pay.api.refund;
import cn.aagro.pp.framework.common.util.object.BeanUtils;
import cn.aagro.pp.module.pay.api.refund.dto.PayRefundCreateReqDTO;
import cn.aagro.pp.module.pay.api.refund.dto.PayRefundRespDTO;
import cn.aagro.pp.module.pay.dal.dataobject.refund.PayRefundDO;
import cn.aagro.pp.module.pay.service.refund.PayRefundService;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
/**
* 退款单 API 实现类
*
* @author 芋道源码
*/
@Service
@Validated
public class PayRefundApiImpl implements PayRefundApi {
@Resource
private PayRefundService payRefundService;
@Override
public Long createRefund(PayRefundCreateReqDTO reqDTO) {
return payRefundService.createRefund(reqDTO);
}
@Override
public PayRefundRespDTO getRefund(Long id) {
PayRefundDO refund = payRefundService.getRefund(id);
return BeanUtils.toBean(refund, PayRefundRespDTO.class);
}
}

View File

@@ -0,0 +1,71 @@
package cn.aagro.pp.module.pay.api.refund.dto;
import cn.aagro.pp.framework.common.enums.UserTypeEnum;
import cn.aagro.pp.framework.common.validation.InEnum;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
/**
* 退款单创建 Request DTO
*
* @author 芋道源码
*/
@Data
public class PayRefundCreateReqDTO {
/**
* 应用标识
*/
@NotNull(message = "应用标识不能为空")
private String appKey;
/**
* 用户 IP
*/
@NotEmpty(message = "用户 IP 不能为空")
private String userIp;
/**
* 用户编号
*/
private Long userId;
/**
* 用户类型
*/
@InEnum(UserTypeEnum.class)
private Integer userType;
// ========== 商户相关字段 ==========
/**
* 商户订单编号
*/
@NotEmpty(message = "商户订单编号不能为空")
private String merchantOrderId;
/**
* 商户退款编号
*/
@NotEmpty(message = "商户退款编号不能为空")
private String merchantRefundId;
/**
* 退款描述
*/
@NotEmpty(message = "退款描述不能为空")
@Length(max = 128, message = "退款描述长度不能超过 128")
private String reason;
// ========== 订单相关字段 ==========
/**
* 退款金额,单位:分
*/
@NotNull(message = "退款金额不能为空")
@Min(value = 1, message = "退款金额必须大于零")
private Integer price;
}

View File

@@ -0,0 +1,65 @@
package cn.aagro.pp.module.pay.api.refund.dto;
import cn.aagro.pp.module.pay.enums.refund.PayRefundStatusEnum;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 退款单信息 Response DTO
*
* @author 芋道源码
*/
@Data
public class PayRefundRespDTO {
/**
* 退款单编号
*/
private Long id;
/**
* 渠道编码
*
* 枚举 PayChannelEnum
*/
private String channelCode;
// ========== 退款相关字段 ==========
/**
* 退款状态
*
* 枚举 {@link PayRefundStatusEnum}
*/
private Integer status;
/**
* 退款金额,单位:分
*/
private Integer refundPrice;
// ========== 商户相关字段 ==========
/**
* 商户订单编号
*/
private String merchantOrderId;
/**
* 商户退款编号
*/
private String merchantRefundId;
/**
* 退款成功时间
*/
private LocalDateTime successTime;
// ========== 渠道相关字段 ==========
/**
* 调用渠道的错误码
*/
private String channelErrorCode;
/**
* 调用渠道的错误提示
*/
private String channelErrorMsg;
}

View File

@@ -0,0 +1,31 @@
package cn.aagro.pp.module.pay.api.transfer;
import cn.aagro.pp.module.pay.api.transfer.dto.PayTransferCreateReqDTO;
import cn.aagro.pp.module.pay.api.transfer.dto.PayTransferCreateRespDTO;
import cn.aagro.pp.module.pay.api.transfer.dto.PayTransferRespDTO;
import javax.validation.Valid;
/**
* 转账单 API 接口
*
* @author jason
*/
public interface PayTransferApi {
/**
* 创建转账单
*
* @param reqDTO 创建请求
* @return 创建结果
*/
PayTransferCreateRespDTO createTransfer(@Valid PayTransferCreateReqDTO reqDTO);
/**
* 获得转账单
*
* @param id 转账单编号
* @return 转账单
*/
PayTransferRespDTO getTransfer(Long id);
}

View File

@@ -0,0 +1,50 @@
package cn.aagro.pp.module.pay.api.transfer;
import cn.aagro.pp.framework.common.util.object.BeanUtils;
import cn.aagro.pp.module.pay.framework.pay.core.client.impl.weixin.WxPayClientConfig;
import cn.aagro.pp.module.pay.api.transfer.dto.PayTransferCreateReqDTO;
import cn.aagro.pp.module.pay.api.transfer.dto.PayTransferCreateRespDTO;
import cn.aagro.pp.module.pay.api.transfer.dto.PayTransferRespDTO;
import cn.aagro.pp.module.pay.dal.dataobject.channel.PayChannelDO;
import cn.aagro.pp.module.pay.dal.dataobject.transfer.PayTransferDO;
import cn.aagro.pp.module.pay.service.channel.PayChannelService;
import cn.aagro.pp.module.pay.service.transfer.PayTransferService;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
/**
* 转账单 API 实现类
*
* @author jason
*/
@Service
@Validated
public class PayTransferApiImpl implements PayTransferApi {
@Resource
private PayTransferService payTransferService;
@Resource
private PayChannelService payChannelService;
@Override
public PayTransferCreateRespDTO createTransfer(PayTransferCreateReqDTO reqDTO) {
return payTransferService.createTransfer(reqDTO);
}
@Override
public PayTransferRespDTO getTransfer(Long id) {
PayTransferDO transfer = payTransferService.getTransfer(id);
if (transfer == null) {
return null;
}
PayChannelDO channel = payChannelService.getChannel(transfer.getChannelId());
String mchId = null;
if (channel != null && channel.getConfig() instanceof WxPayClientConfig) {
mchId = ((WxPayClientConfig) channel.getConfig()).getMchId();
}
return BeanUtils.toBean(transfer, PayTransferRespDTO.class).setChannelMchId(mchId);
}
}

View File

@@ -0,0 +1,133 @@
package cn.aagro.pp.module.pay.api.transfer.dto;
import cn.hutool.core.util.StrUtil;
import cn.aagro.pp.framework.common.enums.UserTypeEnum;
import cn.aagro.pp.framework.common.util.json.JsonUtils;
import cn.aagro.pp.framework.common.validation.InEnum;
import lombok.Data;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 转账单创建 Request DTO
*
* @author jason
*/
@Data
public class PayTransferCreateReqDTO {
/**
* 应用标识
*/
@NotNull(message = "应用标识不能为空")
private String appKey;
/**
* 用户 IP
*/
@NotEmpty(message = "用户 IP 不能为空")
private String userIp;
/**
* 用户编号
*/
private Long userId;
/**
* 用户类型
*/
@InEnum(UserTypeEnum.class)
private Integer userType;
// ========== 商户相关字段 ==========
/**
* 商户转账单编号
*/
@NotEmpty(message = "商户转账单编号能为空")
private String merchantTransferId;
/**
* 转账金额,单位:分
*/
@Min(value = 1, message = "转账金额必须大于零")
@NotNull(message = "转账金额不能为空")
private Integer price;
/**
* 转账标题
*/
@NotEmpty(message = "转账标题不能为空")
private String subject;
/**
* 收款人账号
*
* 微信场景下openid
* 支付宝场景下:支付宝账号
*/
@NotEmpty(message = "收款人账号不能为空")
private String userAccount;
/**
* 收款人姓名
*/
private String userName;
/**
* 转账渠道
*/
@NotEmpty(message = "转账渠道不能为空")
private String channelCode;
/**
* 转账渠道的额外参数
*/
private Map<String, String> channelExtras;
/**
* 【微信】现金营销场景
*
* @param activityName 活动名称
* @param rewardDescription 奖励说明
* @return channelExtras
*/
public static Map<String, String> buildWeiXinChannelExtra1000(String activityName, String rewardDescription) {
return buildWeiXinChannelExtra(1000,
"活动名称", activityName,
"奖励说明", rewardDescription);
}
/**
* 【微信】企业报销场景
*
* @param expenseType 报销类型
* @param expenseDescription 报销说明
* @return channelExtras
*/
public static Map<String, String> buildWeiXinChannelExtra1006(String expenseType, String expenseDescription) {
return buildWeiXinChannelExtra(1006,
"报销类型", expenseType,
"报销说明", expenseDescription);
}
private static Map<String, String> buildWeiXinChannelExtra(Integer sceneId, String... values) {
Map<String, String> channelExtras = new HashMap<>();
// 构建场景报备信息列表
List<Map<String, String>> sceneReportInfos = new ArrayList<>();
for (int i = 0; i < values.length; i += 2) {
Map<String, String> info = new HashMap<>();
info.put("infoType", values[i]);
info.put("infoContent", values[i + 1]);
sceneReportInfos.add(info);
}
// 设置场景ID和场景报备信息
channelExtras.put("sceneId", StrUtil.toString(sceneId));
channelExtras.put("sceneReportInfos", JsonUtils.toJsonString(sceneReportInfos));
return channelExtras;
}
}

View File

@@ -0,0 +1,28 @@
package cn.aagro.pp.module.pay.api.transfer.dto;
import lombok.Data;
/**
* 转账单创建 Response DTO
*
* @author 芋道源码
*/
@Data
public class PayTransferCreateRespDTO {
/**
* 编号
*/
private Long id;
// ========== 其它字段 ==========
/**
* 渠道 package 信息
*
* 特殊:目前只有微信转账有这个东西!!!
* @see <a href="https://pay.weixin.qq.com/doc/v3/merchant/4012716430">JSAPI 调起用户确认收款</a>
*/
private String channelPackageInfo;
}

View File

@@ -0,0 +1,81 @@
package cn.aagro.pp.module.pay.api.transfer.dto;
import cn.aagro.pp.module.pay.enums.PayChannelEnum;
import cn.aagro.pp.module.pay.enums.transfer.PayTransferStatusEnum;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class PayTransferRespDTO {
/**
* 编号
*/
private Long id;
/**
* 转账单号
*/
private String no;
/**
* 转账渠道编码
*
* 枚举 {@link PayChannelEnum}
*/
private String channelCode;
// ========== 商户相关字段 ==========
/**
* 商户转账单编号
*/
private String merchantTransferId;
// ========== 转账相关字段 ==========
/**
* 转账金额,单位:分
*/
private Integer price;
/**
* 转账状态
*
* 枚举 {@link PayTransferStatusEnum}
*/
private Integer status;
/**
* 订单转账成功时间
*/
private LocalDateTime successTime;
// ========== 其它字段 ==========
/**
* 调用渠道的错误码
*/
private String channelErrorCode;
/**
* 调用渠道的错误提示
*/
private String channelErrorMsg;
/**
* 渠道 package 信息
*
* 特殊:目前只有微信转账有这个东西!!!
* @see <a href="https://pay.weixin.qq.com/doc/v3/merchant/4012716430">JSAPI 调起用户确认收款</a>
*/
private String channelPackageInfo;
/**
* 渠道商户号
*
* 特殊:目前只有微信转账有这个东西!!!
* @see <a href="https://pay.weixin.qq.com/doc/v3/merchant/4012716430">JSAPI 调起用户确认收款</a>
*/
private String channelMchId;
}

View File

@@ -0,0 +1,29 @@
package cn.aagro.pp.module.pay.api.wallet;
import cn.aagro.pp.module.pay.api.wallet.dto.PayWalletAddBalanceReqDTO;
import cn.aagro.pp.module.pay.api.wallet.dto.PayWalletRespDTO;
/**
* 钱包 API 接口
*
* @author liurulin
*/
public interface PayWalletApi {
/**
* 添加钱包余额
*
* @param reqDTO 增加余额请求
*/
void addWalletBalance(PayWalletAddBalanceReqDTO reqDTO);
/**
* 获取钱包信息
*
* @param userId 用户编号
* @param userType 用户类型
* @return 钱包信息
*/
PayWalletRespDTO getOrCreateWallet(Long userId, Integer userType);
}

View File

@@ -0,0 +1,42 @@
package cn.aagro.pp.module.pay.api.wallet;
import cn.hutool.core.lang.Assert;
import cn.aagro.pp.framework.common.util.object.BeanUtils;
import cn.aagro.pp.module.pay.api.wallet.dto.PayWalletAddBalanceReqDTO;
import cn.aagro.pp.module.pay.api.wallet.dto.PayWalletRespDTO;
import cn.aagro.pp.module.pay.dal.dataobject.wallet.PayWalletDO;
import cn.aagro.pp.module.pay.enums.wallet.PayWalletBizTypeEnum;
import cn.aagro.pp.module.pay.service.wallet.PayWalletService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* 钱包 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());
}
@Override
public PayWalletRespDTO getOrCreateWallet(Long userId, Integer userType) {
PayWalletDO wallet = payWalletService.getOrCreateWallet(userId, userType);
return BeanUtils.toBean(wallet, PayWalletRespDTO.class);
}
}

View File

@@ -0,0 +1,50 @@
package cn.aagro.pp.module.pay.api.wallet.dto;
import cn.aagro.pp.framework.common.enums.UserTypeEnum;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* 钱包余额增加 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;
}

View File

@@ -0,0 +1,52 @@
package cn.aagro.pp.module.pay.api.wallet.dto;
import cn.aagro.pp.framework.common.enums.UserTypeEnum;
import lombok.Data;
/**
* 钱包 Response DTO
*
* @author jason
*/
@Data
public class PayWalletRespDTO {
/**
* 编号
*/
private Long id;
/**
* 用户 id
*
* 关联 MemberUserDO 的 id 编号
* 关联 AdminUserDO 的 id 编号
*/
private Long userId;
/**
* 用户类型, 预留 多商户转帐可能需要用到
*
* 关联 {@link UserTypeEnum}
*/
private Integer userType;
/**
* 余额,单位分
*/
private Integer balance;
/**
* 冻结金额,单位分
*/
private Integer freezePrice;
/**
* 累计支出,单位分
*/
private Integer totalExpense;
/**
* 累计充值,单位分
*/
private Integer totalRecharge;
}

View File

@@ -0,0 +1,108 @@
package cn.aagro.pp.module.pay.controller.admin.app;
import cn.hutool.core.collection.CollUtil;
import cn.aagro.pp.framework.common.enums.CommonStatusEnum;
import cn.aagro.pp.framework.common.pojo.CommonResult;
import cn.aagro.pp.framework.common.pojo.PageResult;
import cn.aagro.pp.module.pay.controller.admin.app.vo.*;
import cn.aagro.pp.module.pay.convert.app.PayAppConvert;
import cn.aagro.pp.module.pay.dal.dataobject.app.PayAppDO;
import cn.aagro.pp.module.pay.dal.dataobject.channel.PayChannelDO;
import cn.aagro.pp.module.pay.service.app.PayAppService;
import cn.aagro.pp.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 lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;
import static cn.aagro.pp.framework.common.pojo.CommonResult.success;
import static cn.aagro.pp.framework.common.util.collection.CollectionUtils.convertList;
@Slf4j
@Tag(name = "管理后台 - 支付应用信息")
@RestController
@RequestMapping("/pay/app")
@Validated
public class PayAppController {
@Resource
private PayAppService appService;
@Resource
private PayChannelService channelService;
@PostMapping("/create")
@Operation(summary = "创建支付应用信息")
@PreAuthorize("@ss.hasPermission('pay:app:create')")
public CommonResult<Long> createApp(@Valid @RequestBody PayAppCreateReqVO createReqVO) {
return success(appService.createApp(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新支付应用信息")
@PreAuthorize("@ss.hasPermission('pay:app:update')")
public CommonResult<Boolean> updateApp(@Valid @RequestBody PayAppUpdateReqVO updateReqVO) {
appService.updateApp(updateReqVO);
return success(true);
}
@PutMapping("/update-status")
@Operation(summary = "更新支付应用状态")
@PreAuthorize("@ss.hasPermission('pay:app:update')")
public CommonResult<Boolean> updateAppStatus(@Valid @RequestBody PayAppUpdateStatusReqVO updateReqVO) {
appService.updateAppStatus(updateReqVO.getId(), updateReqVO.getStatus());
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除支付应用信息")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('pay:app:delete')")
public CommonResult<Boolean> deleteApp(@RequestParam("id") Long id) {
appService.deleteApp(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得支付应用信息")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pay:app:query')")
public CommonResult<PayAppRespVO> getApp(@RequestParam("id") Long id) {
PayAppDO app = appService.getApp(id);
return success(PayAppConvert.INSTANCE.convert(app));
}
@GetMapping("/page")
@Operation(summary = "获得支付应用信息分页")
@PreAuthorize("@ss.hasPermission('pay:app:query')")
public CommonResult<PageResult<PayAppPageItemRespVO>> getAppPage(@Valid PayAppPageReqVO pageVO) {
// 得到应用分页列表
PageResult<PayAppDO> pageResult = appService.getAppPage(pageVO);
if (CollUtil.isEmpty(pageResult.getList())) {
return success(PageResult.empty());
}
// 得到所有的应用编号,查出所有的渠道,并移除未启用的渠道
List<PayChannelDO> channels = channelService.getChannelListByAppIds(
convertList(pageResult.getList(), PayAppDO::getId));
channels.removeIf(channel -> !CommonStatusEnum.ENABLE.getStatus().equals(channel.getStatus()));
// 拼接后返回
return success(PayAppConvert.INSTANCE.convertPage(pageResult, channels));
}
@GetMapping("/list")
@Operation(summary = "获得应用列表")
@PreAuthorize("@ss.hasPermission('pay:merchant:query')")
public CommonResult<List<PayAppRespVO>> getAppList() {
List<PayAppDO> appListDO = appService.getAppList();
return success(PayAppConvert.INSTANCE.convertList(appListDO));
}
}

View File

@@ -0,0 +1,47 @@
package cn.aagro.pp.module.pay.controller.admin.app.vo;
import cn.aagro.pp.framework.common.enums.CommonStatusEnum;
import cn.aagro.pp.framework.common.validation.InEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import org.hibernate.validator.constraints.URL;
import javax.validation.constraints.*;
/**
* 支付应用信息 Base VO提供给添加、修改、详细的子 VO 使用
* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
*/
@Data
public class PayAppBaseVO {
@Schema(description = "应用标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "aagro")
@NotEmpty(message = "应用标识不能为空")
private String appKey;
@Schema(description = "应用名", requiredMode = Schema.RequiredMode.REQUIRED, example = "小豆")
@NotNull(message = "应用名不能为空")
private String name;
@Schema(description = "开启状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
@NotNull(message = "开启状态不能为空")
@InEnum(CommonStatusEnum.class)
private Integer status;
@Schema(description = "备注", example = "我是一个测试应用")
private String remark;
@Schema(description = "支付结果的回调地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "http://127.0.0.1:48080/pay-callback")
@NotNull(message = "支付结果的回调地址不能为空")
@URL(message = "支付结果的回调地址必须为 URL 格式")
private String orderNotifyUrl;
@Schema(description = "退款结果的回调地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "http://127.0.0.1:48080/refund-callback")
@NotNull(message = "退款结果的回调地址不能为空")
@URL(message = "退款结果的回调地址必须为 URL 格式")
private String refundNotifyUrl;
@Schema(description = "转账结果的回调地址", example = "http://127.0.0.1:48080/transfer-callback")
@URL(message = "转账结果的回调地址必须为 URL 格式")
private String transferNotifyUrl;
}

View File

@@ -0,0 +1,14 @@
package cn.aagro.pp.module.pay.controller.admin.app.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Schema(description = "管理后台 - 支付应用信息创建 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class PayAppCreateReqVO extends PayAppBaseVO {
}

View File

@@ -0,0 +1,26 @@
package cn.aagro.pp.module.pay.controller.admin.app.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.time.LocalDateTime;
import java.util.Set;
@Schema(description = "管理后台 - 支付应用信息分页查询 Response VO,相比于支付信息,还会多出应用渠道的开关信息")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class PayAppPageItemRespVO extends PayAppBaseVO {
@Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
@Schema(description = "已配置的支付渠道编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "[alipay_pc, alipay_wap]")
private Set<String> channelCodes;
}

View File

@@ -0,0 +1,33 @@
package cn.aagro.pp.module.pay.controller.admin.app.vo;
import cn.aagro.pp.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.aagro.pp.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 支付应用信息分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class PayAppPageReqVO extends PageParam {
@Schema(description = "应用名", example = "小豆")
private String name;
@Schema(description = "应用标识", example = "aagro")
private String appKey;
@Schema(description = "开启状态", example = "0")
private Integer status;
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@Schema(description = "创建时间")
private LocalDateTime[] createTime;
}

View File

@@ -0,0 +1,25 @@
package cn.aagro.pp.module.pay.controller.admin.app.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 支付应用信息 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class PayAppRespVO extends PayAppBaseVO {
@Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "应用标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "aagro")
private String appKey;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}

View File

@@ -0,0 +1,17 @@
package cn.aagro.pp.module.pay.controller.admin.app.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import javax.validation.constraints.*;
@Schema(description = "管理后台 - 支付应用信息更新 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class PayAppUpdateReqVO extends PayAppBaseVO {
@Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "应用编号不能为空")
private Long id;
}

View File

@@ -0,0 +1,20 @@
package cn.aagro.pp.module.pay.controller.admin.app.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotNull;
@Schema(description = "管理后台 - 应用更新状态 Request VO")
@Data
public class PayAppUpdateStatusReqVO {
@Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "应用编号不能为空")
private Long id;
@Schema(description = "状态,见 SysCommonStatusEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "状态不能为空")
private Integer status;
}

View File

@@ -0,0 +1,82 @@
package cn.aagro.pp.module.pay.controller.admin.channel;
import cn.aagro.pp.framework.common.pojo.CommonResult;
import cn.aagro.pp.module.pay.controller.admin.channel.vo.PayChannelCreateReqVO;
import cn.aagro.pp.module.pay.controller.admin.channel.vo.PayChannelRespVO;
import cn.aagro.pp.module.pay.controller.admin.channel.vo.PayChannelUpdateReqVO;
import cn.aagro.pp.module.pay.convert.channel.PayChannelConvert;
import cn.aagro.pp.module.pay.dal.dataobject.channel.PayChannelDO;
import cn.aagro.pp.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 org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;
import java.util.Set;
import static cn.aagro.pp.framework.common.pojo.CommonResult.success;
import static cn.aagro.pp.framework.common.util.collection.CollectionUtils.convertSet;
@Tag(name = "管理后台 - 支付渠道")
@RestController
@RequestMapping("/pay/channel")
@Validated
public class PayChannelController {
@Resource
private PayChannelService channelService;
@PostMapping("/create")
@Operation(summary = "创建支付渠道 ")
@PreAuthorize("@ss.hasPermission('pay:channel:create')")
public CommonResult<Long> createChannel(@Valid @RequestBody PayChannelCreateReqVO createReqVO) {
return success(channelService.createChannel(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新支付渠道 ")
@PreAuthorize("@ss.hasPermission('pay:channel:update')")
public CommonResult<Boolean> updateChannel(@Valid @RequestBody PayChannelUpdateReqVO updateReqVO) {
channelService.updateChannel(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除支付渠道 ")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('pay:channel:delete')")
public CommonResult<Boolean> deleteChannel(@RequestParam("id") Long id) {
channelService.deleteChannel(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得支付渠道")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pay:channel:query')")
public CommonResult<PayChannelRespVO> getChannel(@RequestParam(value = "id", required = false) Long id,
@RequestParam(value = "appId", required = false) Long appId,
@RequestParam(value = "code", required = false) String code) {
PayChannelDO channel = null;
if (id != null) {
channel = channelService.getChannel(id);
} else if (appId != null && code != null) {
channel = channelService.getChannelByAppIdAndCode(appId, code);
}
return success(PayChannelConvert.INSTANCE.convert(channel));
}
@GetMapping("/get-enable-code-list")
@Operation(summary = "获得指定应用的开启的支付渠道编码列表")
@Parameter(name = "appId", description = "应用编号", required = true, example = "1")
public CommonResult<Set<String>> getEnableChannelCodeList(@RequestParam("appId") Long appId) {
List<PayChannelDO> channels = channelService.getEnableChannelList(appId);
return success(convertSet(channels, PayChannelDO::getCode));
}
}

View File

@@ -0,0 +1,31 @@
package cn.aagro.pp.module.pay.controller.admin.channel.vo;
import cn.aagro.pp.framework.common.enums.CommonStatusEnum;
import cn.aagro.pp.framework.common.validation.InEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import javax.validation.constraints.*;
/**
* 支付渠道 Base VO提供给添加、修改、详细的子 VO 使用
* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
*/
@Data
public class PayChannelBaseVO {
@Schema(description = "开启状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "开启状态不能为空")
@InEnum(CommonStatusEnum.class)
private Integer status;
@Schema(description = "备注", example = "我是小备注")
private String remark;
@Schema(description = "渠道费率,单位:百分比", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
@NotNull(message = "渠道费率,单位:百分比不能为空")
private Double feeRate;
@Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "应用编号不能为空")
private Long appId;
}

View File

@@ -0,0 +1,25 @@
package cn.aagro.pp.module.pay.controller.admin.channel.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Schema(description = "管理后台 - 支付渠道 创建 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class PayChannelCreateReqVO extends PayChannelBaseVO {
@Schema(description = "渠道编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "alipay_pc")
@NotNull(message = "渠道编码不能为空")
private String code;
@Schema(description = "渠道配置的 json 字符串")
@NotBlank(message = "渠道配置不能为空")
private String config;
}

View File

@@ -0,0 +1,25 @@
package cn.aagro.pp.module.pay.controller.admin.channel.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 支付渠道 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class PayChannelRespVO extends PayChannelBaseVO {
@Schema(description = "商户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private LocalDateTime createTime;
@Schema(description = "渠道编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "alipay_pc")
private String code;
@Schema(description = "配置", requiredMode = Schema.RequiredMode.REQUIRED)
private String config;
}

View File

@@ -0,0 +1,20 @@
package cn.aagro.pp.module.pay.controller.admin.channel.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import javax.validation.constraints.*;
@Schema(description = "管理后台 - 支付渠道 更新 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class PayChannelUpdateReqVO extends PayChannelBaseVO {
@Schema(description = "商户编号", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "商户编号不能为空")
private Long id;
@Schema(description = "渠道配置的json字符串")
@NotBlank(message = "渠道配置不能为空")
private String config;
}

View File

@@ -0,0 +1,77 @@
package cn.aagro.pp.module.pay.controller.admin.demo;
import cn.aagro.pp.framework.common.pojo.CommonResult;
import cn.aagro.pp.framework.common.pojo.PageParam;
import cn.aagro.pp.framework.common.pojo.PageResult;
import cn.aagro.pp.framework.common.util.object.BeanUtils;
import cn.aagro.pp.module.pay.api.notify.dto.PayOrderNotifyReqDTO;
import cn.aagro.pp.module.pay.api.notify.dto.PayRefundNotifyReqDTO;
import cn.aagro.pp.module.pay.controller.admin.demo.vo.order.PayDemoOrderCreateReqVO;
import cn.aagro.pp.module.pay.controller.admin.demo.vo.order.PayDemoOrderRespVO;
import cn.aagro.pp.module.pay.dal.dataobject.demo.PayDemoOrderDO;
import cn.aagro.pp.module.pay.service.demo.PayDemoOrderService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.annotation.security.PermitAll;
import javax.validation.Valid;
import static cn.aagro.pp.framework.common.pojo.CommonResult.success;
import static cn.aagro.pp.framework.common.util.servlet.ServletUtils.getClientIP;
import static cn.aagro.pp.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - 示例订单") // 目的:演示支付、退款功能
@RestController
@RequestMapping("/pay/demo-order")
@Validated
public class PayDemoOrderController {
@Resource
private PayDemoOrderService payDemoOrderService;
@PostMapping("/create")
@Operation(summary = "创建示例订单")
public CommonResult<Long> createDemoOrder(@Valid @RequestBody PayDemoOrderCreateReqVO createReqVO) {
return success(payDemoOrderService.createDemoOrder(getLoginUserId(), createReqVO));
}
@GetMapping("/page")
@Operation(summary = "获得示例订单分页")
public CommonResult<PageResult<PayDemoOrderRespVO>> getDemoOrderPage(@Valid PageParam pageVO) {
PageResult<PayDemoOrderDO> pageResult = payDemoOrderService.getDemoOrderPage(pageVO);
return success(BeanUtils.toBean(pageResult, PayDemoOrderRespVO.class));
}
@PostMapping("/update-paid")
@Operation(summary = "更新示例订单为已支付") // 由 pay-module 支付服务,进行回调,可见 PayNotifyJob
@PermitAll // 无需登录,安全由 PayDemoOrderService 内部校验实现
public CommonResult<Boolean> updateDemoOrderPaid(@RequestBody PayOrderNotifyReqDTO notifyReqDTO) {
payDemoOrderService.updateDemoOrderPaid(Long.valueOf(notifyReqDTO.getMerchantOrderId()),
notifyReqDTO.getPayOrderId());
return success(true);
}
@PutMapping("/refund")
@Operation(summary = "发起示例订单的退款")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
public CommonResult<Boolean> refundDemoOrder(@RequestParam("id") Long id) {
payDemoOrderService.refundDemoOrder(id, getClientIP());
return success(true);
}
@PostMapping("/update-refunded")
@Operation(summary = "更新示例订单为已退款") // 由 pay-module 支付服务,进行回调,可见 PayNotifyJob
@PermitAll // 无需登录,安全由 PayDemoOrderService 内部校验实现
public CommonResult<Boolean> updateDemoOrderRefunded(@RequestBody PayRefundNotifyReqDTO notifyReqDTO) {
payDemoOrderService.updateDemoOrderRefunded(
Long.valueOf(notifyReqDTO.getMerchantOrderId()),
notifyReqDTO.getMerchantRefundId(),
notifyReqDTO.getPayRefundId());
return success(true);
}
}

View File

@@ -0,0 +1,50 @@
### 请求 /pay/demo-withdraw/create 接口(支付宝)
POST {{baseUrl}}/pay/demo-withdraw/create
Authorization: Bearer {{token}}
Content-Type: application/json
tenant-id: {{adminTenantId}}
{
"type": 1,
"subject": "测试转账",
"price": 10,
"userAccount": "oespxk7368@sandbox.com",
"userName": "oespxk7368"
}
### 请求 /pay/demo-withdraw/create 接口(微信余额)
POST {{baseUrl}}/pay/demo-withdraw/create
Authorization: Bearer {{token}}
Content-Type: application/json
tenant-id: {{adminTenantId}}
{
"type": 2,
"subject": "测试转账",
"price": 1,
"userAccount": "oiSC85elO_OZogXODC5RoGyXamK4",
"userName": "芋艿"
}
### 请求 /pay/demo-withdraw/create 接口(钱包余额)
POST {{baseUrl}}/pay/demo-withdraw/create
Authorization: Bearer {{token}}
Content-Type: application/json
tenant-id: {{adminTenantId}}
{
"type": 3,
"subject": "测试转账",
"price": 1,
"userAccount": "1"
}
### 请求 /pay/demo-withdraw/transfer 接口(发起转账)
POST {{baseUrl}}/pay/demo-withdraw/transfer?id=1
Authorization: Bearer {{token}}
tenant-id: {{adminTenantId}}
### 请求 /pay/demo-withdraw/page 接口(查询分页)
GET {{baseUrl}}/pay/demo-withdraw/page?pageNo=1&pageSize=10
Authorization: Bearer {{token}}
tenant-id: {{adminTenantId}}

View File

@@ -0,0 +1,65 @@
package cn.aagro.pp.module.pay.controller.admin.demo;
import cn.aagro.pp.framework.common.pojo.CommonResult;
import cn.aagro.pp.framework.common.pojo.PageParam;
import cn.aagro.pp.framework.common.pojo.PageResult;
import cn.aagro.pp.framework.common.util.object.BeanUtils;
import cn.aagro.pp.module.pay.api.notify.dto.PayTransferNotifyReqDTO;
import cn.aagro.pp.module.pay.controller.admin.demo.vo.withdraw.PayDemoWithdrawCreateReqVO;
import cn.aagro.pp.module.pay.controller.admin.demo.vo.withdraw.PayDemoWithdrawRespVO;
import cn.aagro.pp.module.pay.dal.dataobject.demo.PayDemoWithdrawDO;
import cn.aagro.pp.module.pay.service.demo.PayDemoWithdrawService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.annotation.security.PermitAll;
import javax.validation.Valid;
import static cn.aagro.pp.framework.common.pojo.CommonResult.success;
import static cn.aagro.pp.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - 示例提现订单") // 目的:演示转账功能
@RestController
@RequestMapping("/pay/demo-withdraw")
@Validated
public class PayDemoWithdrawController {
@Resource
private PayDemoWithdrawService demoWithdrawService;
@PostMapping("/create")
@Operation(summary = "创建示例提现单")
public CommonResult<Long> createDemoWithdraw(@Valid @RequestBody PayDemoWithdrawCreateReqVO createReqVO) {
Long id = demoWithdrawService.createDemoWithdraw(createReqVO);
return success(id);
}
@PostMapping("/transfer")
@Operation(summary = "提现单转账")
@Parameter(name = "id", required = true, description = "提现单编号", example = "1024")
public CommonResult<Long> transferDemoWithdraw(@RequestParam("id") Long id) {
Long payTransferId = demoWithdrawService.transferDemoWithdraw(id, getLoginUserId());
return success(payTransferId);
}
@GetMapping("/page")
@Operation(summary = "获得示例提现单分页")
public CommonResult<PageResult<PayDemoWithdrawRespVO>> getDemoWithdrawPage(@Valid PageParam pageVO) {
PageResult<PayDemoWithdrawDO> pageResult = demoWithdrawService.getDemoWithdrawPage(pageVO);
return success(BeanUtils.toBean(pageResult, PayDemoWithdrawRespVO.class));
}
@PostMapping("/update-transferred")
@Operation(summary = "更新示例提现单的转账状态") // 由 pay-module 转账服务,进行回调
@PermitAll // 无需登录,安全由 PayDemoTransferService 内部校验实现
public CommonResult<Boolean> updateDemoWithdrawTransferred(@RequestBody PayTransferNotifyReqDTO notifyReqDTO) {
demoWithdrawService.updateDemoWithdrawTransferred(Long.valueOf(notifyReqDTO.getMerchantTransferId()),
notifyReqDTO.getPayTransferId());
return success(true);
}
}

View File

@@ -0,0 +1,16 @@
package cn.aagro.pp.module.pay.controller.admin.demo.vo.order;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotNull;
@Schema(description = "管理后台 - 示例订单创建 Request VO")
@Data
public class PayDemoOrderCreateReqVO {
@Schema(description = "商品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17682")
@NotNull(message = "商品编号不能为空")
private Long spuId;
}

View File

@@ -0,0 +1,54 @@
package cn.aagro.pp.module.pay.controller.admin.demo.vo.order;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.time.LocalDateTime;
/**
* 示例订单 Base VO提供给添加、修改、详细的子 VO 使用
* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
*/
@Data
public class PayDemoOrderRespVO {
@Schema(description = "订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23199")
private Long userId;
@Schema(description = "商品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17682")
private Long spuId;
@Schema(description = "商家备注", example = "李四")
private String spuName;
@Schema(description = "价格,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "30381")
private Integer price;
@Schema(description = "是否已支付", requiredMode = Schema.RequiredMode.REQUIRED)
private Boolean payStatus;
@Schema(description = "支付订单编号", example = "16863")
private Long payOrderId;
@Schema(description = "订单支付时间")
private LocalDateTime payTime;
@Schema(description = "支付渠道", example = "alipay_qr")
private String payChannelCode;
@Schema(description = "支付退款编号", example = "23366")
private Long payRefundId;
@Schema(description = "退款金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "14039")
private Integer refundPrice;
@Schema(description = "退款时间")
private LocalDateTime refundTime;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}

View File

@@ -0,0 +1,43 @@
package cn.aagro.pp.module.pay.controller.admin.demo.vo.withdraw;
import cn.hutool.core.util.ObjectUtil;
import cn.aagro.pp.framework.common.validation.InEnum;
import cn.aagro.pp.module.pay.enums.demo.PayDemoWithdrawTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.*;
@Schema(description = "管理后台 - 示例提现单创建 Request VO")
@Data
public class PayDemoWithdrawCreateReqVO {
@Schema(description = "提现标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿是一种菜")
@NotEmpty(message = "提现标题不能为空")
private String subject;
@Schema(description = "提现金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
@NotNull(message = "提现金额不能为空")
@Min(value = 1, message = "提现金额必须大于零")
private Integer price;
@Schema(description = "收款人账号", requiredMode= Schema.RequiredMode.REQUIRED, example = "test1")
@NotBlank(message = "收款人账号不能为空")
private String userAccount;
@Schema(description = "收款人姓名", example = "test1")
private String userName;
@Schema(description = "提现方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "提现方式不能为空")
@InEnum(PayDemoWithdrawTypeEnum.class)
private Integer type;
@AssertTrue(message = "收款人姓名")
public boolean isUserNameValid() {
// 特殊:支付宝必须填写用户名!!!
return ObjectUtil.notEqual(type, PayDemoWithdrawTypeEnum.ALIPAY.getType())
|| ObjectUtil.isNotEmpty(userName);
}
}

View File

@@ -0,0 +1,47 @@
package cn.aagro.pp.module.pay.controller.admin.demo.vo.withdraw;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 示例转账单创建 Request VO")
@Data
public class PayDemoWithdrawRespVO {
@Schema(description = "转账单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "提现标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "吃饭报销")
private String subject;
@Schema(description = "提现金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "22338")
private Integer price;
@Schema(description = "收款人姓名", example = "test")
private String userName;
@Schema(description = "收款人账号", example = "32167")
private String userAccount;
@Schema(description = "提现类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer type;
@Schema(description = "提现状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
private Integer status;
// ========== 转账相关字段 ==========
@Schema(description = "转账单编号", example = "23695")
private Long payTransferId;
@Schema(description = "转账渠道", example = "wx_lite")
private String transferChannelCode;
@Schema(description = "转账成功时间")
private LocalDateTime transferTime;
@Schema(description = "转账失败原因", example = "IP 不正确")
private String transferErrorMsg;
}

View File

@@ -0,0 +1,169 @@
package cn.aagro.pp.module.pay.controller.admin.notify;
import cn.hutool.core.collection.CollUtil;
import cn.aagro.pp.framework.common.pojo.CommonResult;
import cn.aagro.pp.framework.common.pojo.PageResult;
import cn.aagro.pp.framework.common.util.object.BeanUtils;
import cn.aagro.pp.module.pay.framework.pay.core.client.PayClient;
import cn.aagro.pp.module.pay.framework.pay.core.client.dto.order.PayOrderRespDTO;
import cn.aagro.pp.module.pay.framework.pay.core.client.dto.refund.PayRefundRespDTO;
import cn.aagro.pp.module.pay.framework.pay.core.client.dto.transfer.PayTransferRespDTO;
import cn.aagro.pp.framework.tenant.core.aop.TenantIgnore;
import cn.aagro.pp.module.pay.controller.admin.notify.vo.PayNotifyTaskDetailRespVO;
import cn.aagro.pp.module.pay.controller.admin.notify.vo.PayNotifyTaskPageReqVO;
import cn.aagro.pp.module.pay.controller.admin.notify.vo.PayNotifyTaskRespVO;
import cn.aagro.pp.module.pay.dal.dataobject.app.PayAppDO;
import cn.aagro.pp.module.pay.dal.dataobject.notify.PayNotifyLogDO;
import cn.aagro.pp.module.pay.dal.dataobject.notify.PayNotifyTaskDO;
import cn.aagro.pp.module.pay.service.app.PayAppService;
import cn.aagro.pp.module.pay.service.channel.PayChannelService;
import cn.aagro.pp.module.pay.service.notify.PayNotifyService;
import cn.aagro.pp.module.pay.service.order.PayOrderService;
import cn.aagro.pp.module.pay.service.refund.PayRefundService;
import cn.aagro.pp.module.pay.service.transfer.PayTransferService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.annotation.security.PermitAll;
import javax.validation.Valid;
import java.util.List;
import java.util.Map;
import static cn.aagro.pp.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.aagro.pp.framework.common.pojo.CommonResult.success;
import static cn.aagro.pp.framework.common.util.collection.CollectionUtils.convertList;
import static cn.aagro.pp.module.pay.enums.ErrorCodeConstants.CHANNEL_NOT_FOUND;
@Tag(name = "管理后台 - 回调通知")
@RestController
@RequestMapping("/pay/notify")
@Validated
@Slf4j
public class PayNotifyController {
@Resource
private PayOrderService orderService;
@Resource
private PayRefundService refundService;
@Resource
private PayTransferService payTransferService;
@Resource
private PayNotifyService notifyService;
@Resource
private PayAppService appService;
@Resource
private PayChannelService channelService;
@PostMapping(value = "/order/{channelId}")
@Operation(summary = "支付渠道的统一【支付】回调")
@PermitAll
@TenantIgnore
public String notifyOrder(@PathVariable("channelId") Long channelId,
@RequestParam(required = false) Map<String, String> params,
@RequestBody(required = false) String body,
@RequestHeader Map<String, String> headers) {
log.info("[notifyOrder][channelId({}) 回调数据({}/{})]", channelId, params, body);
// 1. 校验支付渠道是否存在
PayClient payClient = channelService.getPayClient(channelId);
if (payClient == null) {
log.error("[notifyOrder][渠道编号({}) 找不到对应的支付客户端]", channelId);
throw exception(CHANNEL_NOT_FOUND);
}
// 2. 解析通知数据
PayOrderRespDTO notify = payClient.parseOrderNotify(params, body, headers);
orderService.notifyOrder(channelId, notify);
return "success";
}
@PostMapping(value = "/refund/{channelId}")
@Operation(summary = "支付渠道的统一【退款】回调")
@PermitAll
@TenantIgnore
public String notifyRefund(@PathVariable("channelId") Long channelId,
@RequestParam(required = false) Map<String, String> params,
@RequestBody(required = false) String body,
@RequestHeader Map<String, String> headers) {
log.info("[notifyRefund][channelId({}) 回调数据({}/{})]", channelId, params, body);
// 1. 校验支付渠道是否存在
PayClient payClient = channelService.getPayClient(channelId);
if (payClient == null) {
log.error("[notifyRefund][渠道编号({}) 找不到对应的支付客户端]", channelId);
throw exception(CHANNEL_NOT_FOUND);
}
// 2. 解析通知数据
PayRefundRespDTO notify = payClient.parseRefundNotify(params, body, headers);
refundService.notifyRefund(channelId, notify);
return "success";
}
@PostMapping(value = "/transfer/{channelId}")
@Operation(summary = "支付渠道的统一【转账】回调")
@PermitAll
@TenantIgnore
public String notifyTransfer(@PathVariable("channelId") Long channelId,
@RequestParam(required = false) Map<String, String> params,
@RequestBody(required = false) String body,
@RequestHeader Map<String, String> headers) {
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, headers);
payTransferService.notifyTransfer(channelId, notify);
return "success";
}
@GetMapping("/get-detail")
@Operation(summary = "获得回调通知的明细")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pay:notify:query')")
public CommonResult<PayNotifyTaskDetailRespVO> getNotifyTaskDetail(@RequestParam("id") Long id) {
PayNotifyTaskDO task = notifyService.getNotifyTask(id);
if (task == null) {
return success(null);
}
// 拼接返回
PayAppDO app = appService.getApp(task.getAppId());
List<PayNotifyLogDO> logs = notifyService.getNotifyLogList(id);
return success(BeanUtils.toBean(task, PayNotifyTaskDetailRespVO.class, respVO -> {
if (app != null) {
respVO.setAppName(app.getName());
}
respVO.setLogs(BeanUtils.toBean(logs, PayNotifyTaskDetailRespVO.Log.class));
}));
}
@GetMapping("/page")
@Operation(summary = "获得回调通知分页")
@PreAuthorize("@ss.hasPermission('pay:notify:query')")
public CommonResult<PageResult<PayNotifyTaskRespVO>> getNotifyTaskPage(@Valid PayNotifyTaskPageReqVO pageVO) {
PageResult<PayNotifyTaskDO> pageResult = notifyService.getNotifyTaskPage(pageVO);
if (CollUtil.isEmpty(pageResult.getList())) {
return success(PageResult.empty());
}
// 拼接返回
Map<Long, PayAppDO> apps = appService.getAppMap(convertList(pageResult.getList(), PayNotifyTaskDO::getAppId));
// 转换对象
return success(BeanUtils.toBean(pageResult, PayNotifyTaskRespVO.class, order -> {
PayAppDO app = apps.get(order.getAppId());
if (app != null) {
order.setAppName(app.getName());
}
}));
}
}

View File

@@ -0,0 +1,42 @@
package cn.aagro.pp.module.pay.controller.admin.notify.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "管理后台 - 回调通知的明细 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class PayNotifyTaskDetailRespVO extends PayNotifyTaskRespVO {
@Schema(description = "回调日志列表")
private List<Log> logs;
@Schema(description = "管理后台 - 回调日志")
@Data
public static class Log {
@Schema(description = "日志编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "8848")
private Long id;
@Schema(description = "通知状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Byte status;
@Schema(description = "当前通知次数", requiredMode = Schema.RequiredMode.REQUIRED)
private Byte notifyTimes;
@Schema(description = "HTTP 响应结果", requiredMode = Schema.RequiredMode.REQUIRED)
private String response;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}
}

View File

@@ -0,0 +1,45 @@
package cn.aagro.pp.module.pay.controller.admin.notify.vo;
import cn.aagro.pp.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.aagro.pp.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 回调通知分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class PayNotifyTaskPageReqVO extends PageParam {
@Schema(description = "应用编号", example = "10636")
private Long appId;
@Schema(description = "通知类型", example = "2")
private Integer type;
@Schema(description = "数据编号", example = "6722")
private Long dataId;
@Schema(description = "通知状态", example = "1")
private Integer status;
@Schema(description = "商户订单编号", example = "26697")
private String merchantOrderId;
@Schema(description = "商户退款编号", example = "26697")
private String merchantRefundId;
@Schema(description = "商户转账编号", example = "26697")
private String merchantTransferId;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}

View File

@@ -0,0 +1,56 @@
package cn.aagro.pp.module.pay.controller.admin.notify.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 回调通知 Response VO")
@Data
public class PayNotifyTaskRespVO {
@Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3380")
private Long id;
@Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10636")
private Long appId;
@Schema(description = "应用名称", example = "wx_pay")
private String appName;
@Schema(description = "通知类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
private Byte type;
@Schema(description = "数据编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "6722")
private Long dataId;
@Schema(description = "商户订单编号", example = "26697")
private String merchantOrderId;
@Schema(description = "商户退款编号", example = "26697")
private String merchantRefundId;
@Schema(description = "商户转账编号", example = "26697")
private String merchantTransferId;
@Schema(description = "通知状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Byte status;
@Schema(description = "下一次通知时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime nextNotifyTime;
@Schema(description = "最后一次执行时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime lastExecuteTime;
@Schema(description = "当前通知次数", requiredMode = Schema.RequiredMode.REQUIRED)
private Byte notifyTimes;
@Schema(description = "最大可通知次数", requiredMode = Schema.RequiredMode.REQUIRED)
private Byte maxNotifyTimes;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
@Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,145 @@
package cn.aagro.pp.module.pay.controller.admin.order;
import cn.hutool.core.collection.CollectionUtil;
import cn.aagro.pp.framework.apilog.core.annotation.ApiAccessLog;
import cn.aagro.pp.framework.common.pojo.CommonResult;
import cn.aagro.pp.framework.common.pojo.PageResult;
import cn.aagro.pp.framework.common.util.object.BeanUtils;
import cn.aagro.pp.framework.excel.core.util.ExcelUtils;
import cn.aagro.pp.module.pay.controller.admin.order.vo.*;
import cn.aagro.pp.module.pay.convert.order.PayOrderConvert;
import cn.aagro.pp.module.pay.dal.dataobject.app.PayAppDO;
import cn.aagro.pp.module.pay.dal.dataobject.order.PayOrderDO;
import cn.aagro.pp.module.pay.dal.dataobject.order.PayOrderExtensionDO;
import cn.aagro.pp.module.pay.dal.dataobject.wallet.PayWalletDO;
import cn.aagro.pp.module.pay.enums.PayChannelEnum;
import cn.aagro.pp.module.pay.enums.order.PayOrderStatusEnum;
import cn.aagro.pp.module.pay.framework.pay.core.client.impl.wallet.WalletPayClient;
import cn.aagro.pp.module.pay.service.app.PayAppService;
import cn.aagro.pp.module.pay.service.order.PayOrderService;
import cn.aagro.pp.module.pay.service.wallet.PayWalletService;
import com.google.common.collect.Maps;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import static cn.aagro.pp.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static cn.aagro.pp.framework.common.pojo.CommonResult.success;
import static cn.aagro.pp.framework.common.util.collection.CollectionUtils.convertList;
import static cn.aagro.pp.framework.common.util.servlet.ServletUtils.getClientIP;
import static cn.aagro.pp.framework.web.core.util.WebFrameworkUtils.getLoginUserId;
import static cn.aagro.pp.framework.web.core.util.WebFrameworkUtils.getLoginUserType;
@Tag(name = "管理后台 - 支付订单")
@RestController
@RequestMapping("/pay/order")
@Validated
public class PayOrderController {
@Resource
private PayOrderService orderService;
@Resource
private PayAppService appService;
@Resource
private PayWalletService payWalletService;
@GetMapping("/get")
@Operation(summary = "获得支付订单")
@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,
@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")
@Operation(summary = "获得支付订单详情")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pay:order:query')")
public CommonResult<PayOrderDetailsRespVO> getOrderDetail(@RequestParam("id") Long id) {
PayOrderDO order = orderService.getOrder(id);
if (order == null) {
return success(null);
}
// 拼接返回
PayAppDO app = appService.getApp(order.getAppId());
PayOrderExtensionDO orderExtension = orderService.getOrderExtension(order.getExtensionId());
return success(PayOrderConvert.INSTANCE.convert(order, orderExtension, app));
}
@PostMapping("/submit")
@Operation(summary = "提交支付订单")
public CommonResult<PayOrderSubmitRespVO> submitPayOrder(@RequestBody PayOrderSubmitReqVO reqVO) {
// 1. 钱包支付事,需要额外传 user_id 和 user_type
if (Objects.equals(reqVO.getChannelCode(), PayChannelEnum.WALLET.getCode())) {
if (reqVO.getChannelExtras() == null) {
reqVO.setChannelExtras(Maps.newHashMapWithExpectedSize(1));
}
PayWalletDO wallet = payWalletService.getOrCreateWallet(getLoginUserId(), getLoginUserType());
reqVO.getChannelExtras().put(WalletPayClient.WALLET_ID_KEY, String.valueOf(wallet.getId()));
}
// 2. 提交支付
PayOrderSubmitRespVO respVO = orderService.submitOrder(reqVO, getClientIP());
return success(respVO);
}
@GetMapping("/page")
@Operation(summary = "获得支付订单分页")
@PreAuthorize("@ss.hasPermission('pay:order:query')")
public CommonResult<PageResult<PayOrderPageItemRespVO>> getOrderPage(@Valid PayOrderPageReqVO pageVO) {
PageResult<PayOrderDO> pageResult = orderService.getOrderPage(pageVO);
if (CollectionUtil.isEmpty(pageResult.getList())) {
return success(new PageResult<>(pageResult.getTotal()));
}
// 拼接返回
Map<Long, PayAppDO> appMap = appService.getAppMap(convertList(pageResult.getList(), PayOrderDO::getAppId));
return success(PayOrderConvert.INSTANCE.convertPage(pageResult, appMap));
}
@GetMapping("/export-excel")
@Operation(summary = "导出支付订单 Excel")
@PreAuthorize("@ss.hasPermission('pay:order:export')")
@ApiAccessLog(operateType = EXPORT)
public void exportOrderExcel(@Valid PayOrderExportReqVO exportReqVO,
HttpServletResponse response) throws IOException {
List<PayOrderDO> list = orderService.getOrderList(exportReqVO);
if (CollectionUtil.isEmpty(list)) {
ExcelUtils.write(response, "支付订单.xls", "数据",
PayOrderExcelVO.class, new ArrayList<>());
return;
}
// 拼接返回
Map<Long, PayAppDO> appMap = appService.getAppMap(convertList(list, PayOrderDO::getAppId));
List<PayOrderExcelVO> excelList = PayOrderConvert.INSTANCE.convertList(list, appMap);
// 导出 Excel
ExcelUtils.write(response, "支付订单.xls", "数据", PayOrderExcelVO.class, excelList);
}
}

View File

@@ -0,0 +1,89 @@
package cn.aagro.pp.module.pay.controller.admin.order.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
import static cn.aagro.pp.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
/**
* 支付订单 Base VO提供给添加、修改、详细的子 VO 使用
* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
*
* @author aquan
*/
@Data
public class PayOrderBaseVO {
@Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "应用编号不能为空")
private Long appId;
@Schema(description = "渠道编号", example = "2048")
private Long channelId;
@Schema(description = "渠道编码", example = "wx_app")
private String channelCode;
@Schema(description = "商户订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "888")
@NotNull(message = "商户订单编号不能为空")
private String merchantOrderId;
@Schema(description = "商品标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "土豆")
@NotNull(message = "商品标题不能为空")
private String subject;
@Schema(description = "商品描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是土豆")
@NotNull(message = "商品描述不能为空")
private String body;
@Schema(description = "异步通知地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "http://127.0.0.1:48080/pay/notify")
@NotNull(message = "异步通知地址不能为空")
private String notifyUrl;
@Schema(description = "支付金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
@NotNull(message = "支付金额,单位:分不能为空")
private Long price;
@Schema(description = "渠道手续费,单位:百分比", example = "10")
private Double channelFeeRate;
@Schema(description = "渠道手续金额,单位:分", example = "100")
private Integer channelFeePrice;
@Schema(description = "支付状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "支付状态不能为空")
private Integer status;
@Schema(description = "用户 IP", requiredMode = Schema.RequiredMode.REQUIRED, example = "127.0.0.1")
@NotNull(message = "用户 IP不能为空")
private String userIp;
@Schema(description = "订单失效时间", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "订单失效时间不能为空")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime expireTime;
@Schema(description = "订单支付成功时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime successTime;
@Schema(description = "支付成功的订单拓展单编号", example = "50")
private Long extensionId;
@Schema(description = "支付订单号", example = "2048888")
private String no;
@Schema(description = "退款总金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
@NotNull(message = "退款总金额,单位:分不能为空")
private Long refundPrice;
@Schema(description = "渠道用户编号", example = "2048")
private String channelUserId;
@Schema(description = "渠道订单号", example = "4096")
private String channelOrderNo;
}

View File

@@ -0,0 +1,45 @@
package cn.aagro.pp.module.pay.controller.admin.order.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 支付订单详细信息 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class PayOrderDetailsRespVO extends PayOrderBaseVO {
@Schema(description = "支付订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "应用名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道源码")
private String appName;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
@Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime updateTime;
/**
* 支付订单扩展
*/
private PayOrderExtension extension;
@Data
@Schema(description = "支付订单扩展")
public static class PayOrderExtension {
@Schema(description = "支付订单号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private String no;
@Schema(description = "支付异步通知的内容")
private String channelNotifyData;
}
}

View File

@@ -0,0 +1,67 @@
package cn.aagro.pp.module.pay.controller.admin.order.vo;
import cn.aagro.pp.framework.excel.core.annotations.DictFormat;
import cn.aagro.pp.framework.excel.core.convert.DictConvert;
import cn.aagro.pp.framework.excel.core.convert.MoneyConvert;
import cn.aagro.pp.module.pay.enums.DictTypeConstants;
import cn.idev.excel.annotation.ExcelProperty;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 支付订单 Excel VO
*
* @author aquan
*/
@Data
public class PayOrderExcelVO {
@ExcelProperty("编号")
private Long id;
@ExcelProperty("创建时间")
private LocalDateTime createTime;
@ExcelProperty(value = "支付金额", converter = MoneyConvert.class)
private Integer price;
@ExcelProperty(value = "退款金额", converter = MoneyConvert.class)
private Integer refundPrice;
@ExcelProperty(value = "手续金额", converter = MoneyConvert.class)
private Integer channelFeePrice;
@ExcelProperty("商户单号")
private String merchantOrderId;
@ExcelProperty(value = "支付单号")
private String no;
@ExcelProperty("渠道单号")
private String channelOrderNo;
@ExcelProperty(value = "支付状态", converter = DictConvert.class)
@DictFormat(DictTypeConstants.ORDER_STATUS)
private Integer status;
@ExcelProperty(value = "渠道编号名称", converter = DictConvert.class)
@DictFormat(DictTypeConstants.CHANNEL_CODE)
private String channelCode;
@ExcelProperty("订单支付成功时间")
private LocalDateTime successTime;
@ExcelProperty("订单失效时间")
private LocalDateTime expireTime;
@ExcelProperty(value = "应用名称")
private String appName;
@ExcelProperty("商品标题")
private String subject;
@ExcelProperty("商品描述")
private String body;
}

View File

@@ -0,0 +1,37 @@
package cn.aagro.pp.module.pay.controller.admin.order.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.aagro.pp.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 支付订单 Excel 导出 Request VO参数和 PayOrderPageReqVO 是一致的")
@Data
public class PayOrderExportReqVO {
@Schema(description = "应用编号", example = "1024")
private Long appId;
@Schema(description = "渠道编码", example = "wx_app")
private String channelCode;
@Schema(description = "商户订单编号", example = "4096")
private String merchantOrderId;
@Schema(description = "渠道编号", example = "1888")
private String channelOrderNo;
@Schema(description = "支付单号", example = "2014888")
private String no;
@Schema(description = "支付状态", example = "0")
private Integer status;
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@Schema(description = "创建时间")
private LocalDateTime[] createTime;
}

View File

@@ -0,0 +1,25 @@
package cn.aagro.pp.module.pay.controller.admin.order.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 支付订单分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class PayOrderPageItemRespVO extends PayOrderBaseVO {
@Schema(description = "支付订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
@Schema(description = "应用名称", example = "wx_pay")
private String appName;
}

View File

@@ -0,0 +1,42 @@
package cn.aagro.pp.module.pay.controller.admin.order.vo;
import cn.aagro.pp.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.aagro.pp.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 支付订单分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class PayOrderPageReqVO extends PageParam {
@Schema(description = "应用编号", example = "1024")
private Long appId;
@Schema(description = "渠道编码", example = "wx_app")
private String channelCode;
@Schema(description = "商户订单编号", example = "4096")
private String merchantOrderId;
@Schema(description = "渠道编号", example = "1888")
private String channelOrderNo;
@Schema(description = "支付单号", example = "2014888")
private String no;
@Schema(description = "支付状态", example = "0")
private Integer status;
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@Schema(description = "创建时间")
private LocalDateTime[] createTime;
}

View File

@@ -0,0 +1,22 @@
package cn.aagro.pp.module.pay.controller.admin.order.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 支付订单 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class PayOrderRespVO extends PayOrderBaseVO {
@Schema(description = "支付订单编号", requiredMode = Schema.RequiredMode.REQUIRED)
private Long id;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}

View File

@@ -0,0 +1,33 @@
package cn.aagro.pp.module.pay.controller.admin.order.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.hibernate.validator.constraints.URL;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.Map;
@Schema(description = "管理后台 - 支付订单提交 Request VO")
@Data
public class PayOrderSubmitReqVO {
@Schema(description = "支付单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "支付单编号不能为空")
private Long id;
@Schema(description = "支付渠道", requiredMode = Schema.RequiredMode.REQUIRED, example = "wx_pub")
@NotEmpty(message = "支付渠道不能为空")
private String channelCode;
@Schema(description = "支付渠道的额外参数,例如说,微信公众号需要传递 openid 参数")
private Map<String, String> channelExtras;
@Schema(description = "展示模式", example = "url") // 参见 {@link PayDisplayModeEnum} 枚举。如果不传递,则每个支付渠道使用默认的方式
private String displayMode;
@Schema(description = "回跳地址")
@URL(message = "回跳地址的格式必须是 URL")
private String returnUrl;
}

View File

@@ -0,0 +1,18 @@
package cn.aagro.pp.module.pay.controller.admin.order.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - 支付订单提交 Response VO")
@Data
public class PayOrderSubmitRespVO {
@Schema(description = "支付状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") // 参见 PayOrderStatusEnum 枚举
private Integer status;
@Schema(description = "展示模式", requiredMode = Schema.RequiredMode.REQUIRED, example = "url") // 参见 PayDisplayModeEnum 枚举
private String displayMode;
@Schema(description = "展示内容", requiredMode = Schema.RequiredMode.REQUIRED)
private String displayContent;
}

View File

@@ -0,0 +1,90 @@
package cn.aagro.pp.module.pay.controller.admin.refund;
import cn.hutool.core.collection.CollectionUtil;
import cn.aagro.pp.framework.apilog.core.annotation.ApiAccessLog;
import cn.aagro.pp.framework.common.pojo.CommonResult;
import cn.aagro.pp.framework.common.pojo.PageResult;
import cn.aagro.pp.framework.excel.core.util.ExcelUtils;
import cn.aagro.pp.module.pay.controller.admin.refund.vo.*;
import cn.aagro.pp.module.pay.convert.refund.PayRefundConvert;
import cn.aagro.pp.module.pay.dal.dataobject.app.PayAppDO;
import cn.aagro.pp.module.pay.dal.dataobject.refund.PayRefundDO;
import cn.aagro.pp.module.pay.service.app.PayAppService;
import cn.aagro.pp.module.pay.service.refund.PayRefundService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
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 javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import static cn.aagro.pp.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static cn.aagro.pp.framework.common.pojo.CommonResult.success;
import static cn.aagro.pp.framework.common.util.collection.CollectionUtils.convertList;
@Tag(name = "管理后台 - 退款订单")
@RestController
@RequestMapping("/pay/refund")
@Validated
public class PayRefundController {
@Resource
private PayRefundService refundService;
@Resource
private PayAppService appService;
@GetMapping("/get")
@Operation(summary = "获得退款订单")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pay:refund:query')")
public CommonResult<PayRefundDetailsRespVO> getRefund(@RequestParam("id") Long id) {
PayRefundDO refund = refundService.getRefund(id);
if (refund == null) {
return success(new PayRefundDetailsRespVO());
}
// 拼接数据
PayAppDO app = appService.getApp(refund.getAppId());
return success(PayRefundConvert.INSTANCE.convert(refund, app));
}
@GetMapping("/page")
@Operation(summary = "获得退款订单分页")
@PreAuthorize("@ss.hasPermission('pay:refund:query')")
public CommonResult<PageResult<PayRefundPageItemRespVO>> getRefundPage(@Valid PayRefundPageReqVO pageVO) {
PageResult<PayRefundDO> pageResult = refundService.getRefundPage(pageVO);
if (CollectionUtil.isEmpty(pageResult.getList())) {
return success(new PageResult<>(pageResult.getTotal()));
}
// 处理应用ID数据
Map<Long, PayAppDO> appMap = appService.getAppMap(convertList(pageResult.getList(), PayRefundDO::getAppId));
return success(PayRefundConvert.INSTANCE.convertPage(pageResult, appMap));
}
@GetMapping("/export-excel")
@Operation(summary = "导出退款订单 Excel")
@PreAuthorize("@ss.hasPermission('pay:refund:export')")
@ApiAccessLog(operateType = EXPORT)
public void exportRefundExcel(@Valid PayRefundExportReqVO exportReqVO,
HttpServletResponse response) throws IOException {
List<PayRefundDO> list = refundService.getRefundList(exportReqVO);
// 拼接返回
Map<Long, PayAppDO> appMap = appService.getAppMap(convertList(list, PayRefundDO::getAppId));
List<PayRefundExcelVO> excelList = PayRefundConvert.INSTANCE.convertList(list, appMap);
// 导出 Excel
ExcelUtils.write(response, "退款订单.xls", "数据", PayRefundExcelVO.class, excelList);
}
}

View File

@@ -0,0 +1,78 @@
package cn.aagro.pp.module.pay.controller.admin.refund.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 退款订单 Base VO提供给添加、修改、详细的子 VO 使用
* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
*/
@Data
public class PayRefundBaseVO {
@Schema(description = "外部退款号", requiredMode = Schema.RequiredMode.REQUIRED, example = "110")
private String no;
@Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long appId;
@Schema(description = "渠道编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
private Long channelId;
@Schema(description = "渠道编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "wx_app")
private String channelCode;
@Schema(description = "订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long orderId;
// ========== 商户相关字段 ==========
@Schema(description = "商户订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "225")
private String merchantOrderId;
@Schema(description = "商户退款订单号", requiredMode = Schema.RequiredMode.REQUIRED, example = "512")
private String merchantRefundId;
@Schema(description = "异步通知地址", requiredMode = Schema.RequiredMode.REQUIRED)
private String notifyUrl;
// ========== 退款相关字段 ==========
@Schema(description = "退款状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
private Integer status;
@Schema(description = "支付金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
private Long payPrice;
@Schema(description = "退款金额,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "200")
private Long refundPrice;
@Schema(description = "退款原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "我要退了")
private String reason;
@Schema(description = "用户 IP", requiredMode = Schema.RequiredMode.REQUIRED, example = "127.0.0.1")
private String userIp;
// ========== 渠道相关字段 ==========
@Schema(description = "渠道订单号", requiredMode = Schema.RequiredMode.REQUIRED, example = "233")
private String channelOrderNo;
@Schema(description = "渠道退款单号", example = "2022")
private String channelRefundNo;
@Schema(description = "退款成功时间")
private LocalDateTime successTime;
@Schema(description = "调用渠道的错误码")
private String channelErrorCode;
@Schema(description = "调用渠道的错误提示")
private String channelErrorMsg;
@Schema(description = "支付渠道的额外参数")
private String channelNotifyData;
}

View File

@@ -0,0 +1,40 @@
package cn.aagro.pp.module.pay.controller.admin.refund.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 退款订单详情 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class PayRefundDetailsRespVO extends PayRefundBaseVO {
@Schema(description = "支付退款编号", requiredMode = Schema.RequiredMode.REQUIRED)
private Long id;
@Schema(description = "应用名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是芋艿")
private String appName;
@Schema(description = "支付订单", requiredMode = Schema.RequiredMode.REQUIRED)
private Order order;
@Schema(description = "创建时间")
private LocalDateTime createTime;
@Schema(description = "更新时间")
private LocalDateTime updateTime;
@Schema(description = "管理后台 - 支付订单")
@Data
public static class Order {
@Schema(description = "商品标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "土豆")
private String subject;
}
}

View File

@@ -0,0 +1,61 @@
package cn.aagro.pp.module.pay.controller.admin.refund.vo;
import cn.aagro.pp.framework.excel.core.annotations.DictFormat;
import cn.aagro.pp.framework.excel.core.convert.DictConvert;
import cn.aagro.pp.framework.excel.core.convert.MoneyConvert;
import cn.aagro.pp.module.pay.enums.DictTypeConstants;
import cn.idev.excel.annotation.ExcelProperty;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 退款订单 Excel VO
*
* @author aquan
*/
@Data
public class PayRefundExcelVO {
@ExcelProperty("支付退款编号")
private Long id;
@ExcelProperty("创建时间")
private LocalDateTime createTime;
@ExcelProperty(value = "支付金额", converter = MoneyConvert.class)
private Integer payPrice;
@ExcelProperty(value = "退款金额", converter = MoneyConvert.class)
private Integer refundPrice;
@ExcelProperty("商户退款单号")
private String merchantRefundId;
@ExcelProperty("退款单号")
private String no;
@ExcelProperty("渠道退款单号")
private String channelRefundNo;
@ExcelProperty("商户支付单号")
private String merchantOrderId;
@ExcelProperty("渠道支付单号")
private String channelOrderNo;
@ExcelProperty(value = "退款状态", converter = DictConvert.class)
@DictFormat(DictTypeConstants.REFUND_STATUS)
private Integer status;
@ExcelProperty(value = "退款渠道", converter = DictConvert.class)
@DictFormat(DictTypeConstants.CHANNEL_CODE)
private String channelCode;
@ExcelProperty("成功时间")
private LocalDateTime successTime;
@ExcelProperty(value = "支付应用")
private String appName;
@ExcelProperty("退款原因")
private String reason;
}

View File

@@ -0,0 +1,40 @@
package cn.aagro.pp.module.pay.controller.admin.refund.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.aagro.pp.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 退款订单 Excel 导出 Request VO参数和 PayRefundPageReqVO 是一致的")
@Data
public class PayRefundExportReqVO {
@Schema(description = "应用编号", example = "1024")
private Long appId;
@Schema(description = "渠道编码", example = "wx_app")
private String channelCode;
@Schema(description = "商户支付单号", example = "10")
private String merchantOrderId;
@Schema(description = "商户退款单号", example = "20")
private String merchantRefundId;
@Schema(description = "渠道支付单号", example = "30")
private String channelOrderNo;
@Schema(description = "渠道退款单号", example = "40")
private String channelRefundNo;
@Schema(description = "退款状态", example = "0")
private Integer status;
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@Schema(description = "创建时间")
private LocalDateTime[] createTime;
}

View File

@@ -0,0 +1,25 @@
package cn.aagro.pp.module.pay.controller.admin.refund.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 退款订单分页查询 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class PayRefundPageItemRespVO extends PayRefundBaseVO {
@Schema(description = "支付订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "应用名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是芋艿")
private String appName;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}

View File

@@ -0,0 +1,45 @@
package cn.aagro.pp.module.pay.controller.admin.refund.vo;
import cn.aagro.pp.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.aagro.pp.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 退款订单分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class PayRefundPageReqVO extends PageParam {
@Schema(description = "应用编号", example = "1024")
private Long appId;
@Schema(description = "渠道编码", example = "wx_app")
private String channelCode;
@Schema(description = "商户支付单号", example = "10")
private String merchantOrderId;
@Schema(description = "商户退款单号", example = "20")
private String merchantRefundId;
@Schema(description = "渠道支付单号", example = "30")
private String channelOrderNo;
@Schema(description = "渠道退款单号", example = "40")
private String channelRefundNo;
@Schema(description = "退款状态", example = "0")
private Integer status;
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@Schema(description = "创建时间")
private LocalDateTime[] createTime;
}

View File

@@ -0,0 +1,91 @@
package cn.aagro.pp.module.pay.controller.admin.transfer;
import cn.aagro.pp.framework.apilog.core.annotation.ApiAccessLog;
import cn.aagro.pp.framework.common.pojo.CommonResult;
import cn.aagro.pp.framework.common.pojo.PageResult;
import cn.aagro.pp.framework.common.util.object.BeanUtils;
import cn.aagro.pp.framework.excel.core.util.ExcelUtils;
import cn.aagro.pp.module.pay.controller.admin.transfer.vo.PayTransferPageReqVO;
import cn.aagro.pp.module.pay.controller.admin.transfer.vo.PayTransferRespVO;
import cn.aagro.pp.module.pay.dal.dataobject.app.PayAppDO;
import cn.aagro.pp.module.pay.dal.dataobject.transfer.PayTransferDO;
import cn.aagro.pp.module.pay.service.app.PayAppService;
import cn.aagro.pp.module.pay.service.transfer.PayTransferService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
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 javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.IOException;
import java.util.Map;
import static cn.aagro.pp.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static cn.aagro.pp.framework.common.pojo.CommonResult.success;
import static cn.aagro.pp.framework.common.pojo.PageParam.PAGE_SIZE_NONE;
import static cn.aagro.pp.framework.common.util.collection.CollectionUtils.convertList;
@Tag(name = "管理后台 - 转账单")
@RestController
@RequestMapping("/pay/transfer")
@Validated
public class PayTransferController {
@Resource
private PayTransferService payTransferService;
@Resource
private PayAppService payAppService;
@GetMapping("/get")
@Operation(summary = "获得转账订单")
@PreAuthorize("@ss.hasPermission('pay:transfer:query')")
public CommonResult<PayTransferRespVO> getTransfer(@RequestParam("id") Long id) {
PayTransferDO transfer = payTransferService.getTransfer(id);
if (transfer == null) {
return success(new PayTransferRespVO());
}
// 拼接数据
PayAppDO app = payAppService.getApp(transfer.getAppId());
return success(BeanUtils.toBean(transfer, PayTransferRespVO.class, transferVO -> {
if (app != null) {
transferVO.setAppName(app.getName());
}
}));
}
@GetMapping("/page")
@Operation(summary = "获得转账订单分页")
@PreAuthorize("@ss.hasPermission('pay:transfer:query')")
public CommonResult<PageResult<PayTransferRespVO>> getTransferPage(@Valid PayTransferPageReqVO pageVO) {
PageResult<PayTransferDO> pageResult = payTransferService.getTransferPage(pageVO);
// 拼接数据
Map<Long, PayAppDO> apps = payAppService.getAppMap(convertList(pageResult.getList(), PayTransferDO::getAppId));
return success(BeanUtils.toBean(pageResult, PayTransferRespVO.class, transferVO -> {
if (apps.containsKey(transferVO.getAppId())) {
transferVO.setAppName(apps.get(transferVO.getAppId()).getName());
}
}));
}
@GetMapping("/export-excel")
@Operation(summary = "导出转账订单 Excel")
@PreAuthorize("@ss.hasPermission('pay:transfer:export')")
@ApiAccessLog(operateType = EXPORT)
public void exportTransfer(PayTransferPageReqVO pageReqVO,
HttpServletResponse response) throws IOException {
pageReqVO.setPageSize(PAGE_SIZE_NONE);
PageResult<PayTransferRespVO> pageResult = getTransferPage(pageReqVO).getData();
// 导出 Excel
ExcelUtils.write(response, "转账订单.xls", "数据", PayTransferRespVO.class, pageResult.getList());
}
}

View File

@@ -0,0 +1,16 @@
package cn.aagro.pp.module.pay.controller.admin.transfer.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - 发起转账 Response VO")
@Data
public class PayTransferCreateRespVO {
@Schema(description = "转账单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long id;
@Schema(description = "转账状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") // 参见 PayTransferStatusEnum 枚举
private Integer status;
}

View File

@@ -0,0 +1,45 @@
package cn.aagro.pp.module.pay.controller.admin.transfer.vo;
import lombok.*;
import io.swagger.v3.oas.annotations.media.Schema;
import cn.aagro.pp.framework.common.pojo.PageParam;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.aagro.pp.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 转账单分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class PayTransferPageReqVO extends PageParam {
@Schema(description = "转账单号")
private String no;
@Schema(description = "应用编号", example = "12831")
private Long appId;
@Schema(description = "渠道编码", example = "wx_app")
private String channelCode;
@Schema(description = "商户转账单编号", example = "17481")
private String merchantOrderId;
@Schema(description = "转账状态", example = "2")
private Integer status;
@Schema(description = "收款人姓名", example = "王五")
private String userName;
@Schema(description = "收款人账号", example = "26589")
private String userAccount;
@Schema(description = "渠道转账单号")
private String channelTransferNo;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}

View File

@@ -0,0 +1,100 @@
package cn.aagro.pp.module.pay.controller.admin.transfer.vo;
import cn.aagro.pp.framework.excel.core.annotations.DictFormat;
import cn.aagro.pp.framework.excel.core.convert.DictConvert;
import cn.aagro.pp.framework.excel.core.convert.MoneyConvert;
import cn.aagro.pp.module.pay.enums.DictTypeConstants;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.time.LocalDateTime;
import java.util.Map;
@Schema(description = "管理后台 - 转账单 Response VO")
@Data
@ExcelIgnoreUnannotated
public class PayTransferRespVO {
@ExcelProperty("转账单编号")
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2931")
private Long id;
@ExcelProperty("转账单号")
@Schema(description = "转账单号", requiredMode = Schema.RequiredMode.REQUIRED)
private String no;
@Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "12831")
private Long appId;
@ExcelProperty("应用名称")
@Schema(description = "应用名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
private String appName;
@Schema(description = "转账渠道编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24833")
private Long channelId;
@ExcelProperty(value = "转账渠道", converter = DictConvert.class)
@DictFormat(DictTypeConstants.CHANNEL_CODE)
@Schema(description = "转账渠道编码", requiredMode = Schema.RequiredMode.REQUIRED)
private String channelCode;
@ExcelProperty("商户转账单编号")
@Schema(description = "商户转账单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17481")
private String merchantTransferId;
@ExcelProperty(value = "转账状态", converter = DictConvert.class)
@DictFormat(DictTypeConstants.TRANSFER_STATUS)
@Schema(description = "转账状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
private Integer status;
@ExcelProperty("转账成功时间")
@Schema(description = "转账成功时间")
private LocalDateTime successTime;
@Schema(description = "转账金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "964")
@ExcelProperty(value = "转账金额", converter = MoneyConvert.class)
private Integer price;
@ExcelProperty("转账标题")
@Schema(description = "转账标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "冲冲冲!")
private String subject;
@Schema(description = "收款人姓名", example = "王五")
@ExcelProperty("收款人姓名")
private String userName;
@Schema(description = "收款人账号", requiredMode = Schema.RequiredMode.REQUIRED, example = "26589")
@ExcelProperty("收款人账号")
private String userAccount;
@Schema(description = "异步通知商户地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn")
private String notifyUrl;
@ExcelProperty("用户 IP")
@Schema(description = "用户 IP", requiredMode = Schema.RequiredMode.REQUIRED)
private String userIp;
@Schema(description = "渠道的额外参数")
private Map<String, String> channelExtras;
@Schema(description = "渠道转账单号")
@ExcelProperty("渠道转账单号")
private String channelTransferNo;
@Schema(description = "调用渠道的错误码")
private String channelErrorCode;
@ExcelProperty("渠道错误提示")
@Schema(description = "调用渠道的错误提示")
private String channelErrorMsg;
@Schema(description = "渠道的同步/异步通知的内容")
private String channelNotifyData;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("创建时间")
private LocalDateTime createTime;
}

View File

@@ -0,0 +1,71 @@
package cn.aagro.pp.module.pay.controller.admin.wallet;
import cn.aagro.pp.framework.common.pojo.CommonResult;
import cn.aagro.pp.framework.common.pojo.PageResult;
import cn.aagro.pp.module.pay.controller.admin.wallet.vo.wallet.PayWalletPageReqVO;
import cn.aagro.pp.module.pay.controller.admin.wallet.vo.wallet.PayWalletRespVO;
import cn.aagro.pp.module.pay.controller.admin.wallet.vo.wallet.PayWalletUpdateBalanceReqVO;
import cn.aagro.pp.module.pay.controller.admin.wallet.vo.wallet.PayWalletUserReqVO;
import cn.aagro.pp.module.pay.convert.wallet.PayWalletConvert;
import cn.aagro.pp.module.pay.dal.dataobject.wallet.PayWalletDO;
import cn.aagro.pp.module.pay.enums.wallet.PayWalletBizTypeEnum;
import cn.aagro.pp.module.pay.service.wallet.PayWalletService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import static cn.aagro.pp.framework.common.enums.UserTypeEnum.MEMBER;
import static cn.aagro.pp.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.aagro.pp.framework.common.pojo.CommonResult.success;
import static cn.aagro.pp.module.pay.enums.ErrorCodeConstants.WALLET_NOT_FOUND;
@Tag(name = "管理后台 - 用户钱包")
@RestController
@RequestMapping("/pay/wallet")
@Validated
@Slf4j
public class PayWalletController {
@Resource
private PayWalletService payWalletService;
@GetMapping("/get")
@PreAuthorize("@ss.hasPermission('pay:wallet:query')")
@Operation(summary = "获得用户钱包明细")
public CommonResult<PayWalletRespVO> getWallet(PayWalletUserReqVO reqVO) {
PayWalletDO wallet = payWalletService.getOrCreateWallet(reqVO.getUserId(), MEMBER.getValue());
return success(PayWalletConvert.INSTANCE.convert02(wallet));
}
@GetMapping("/page")
@Operation(summary = "获得会员钱包分页")
@PreAuthorize("@ss.hasPermission('pay:wallet:query')")
public CommonResult<PageResult<PayWalletRespVO>> getWalletPage(@Valid PayWalletPageReqVO pageVO) {
PageResult<PayWalletDO> pageResult = payWalletService.getWalletPage(pageVO);
return success(PayWalletConvert.INSTANCE.convertPage(pageResult));
}
@PutMapping("/update-balance")
@Operation(summary = "更新会员用户余额")
@PreAuthorize("@ss.hasPermission('pay:wallet:update-balance')")
public CommonResult<Boolean> updateWalletBalance(@Valid @RequestBody PayWalletUpdateBalanceReqVO updateReqVO) {
// 获得用户钱包
PayWalletDO wallet = payWalletService.getOrCreateWallet(updateReqVO.getUserId(), MEMBER.getValue());
if (wallet == null) {
log.error("[updateWalletBalance]updateReqVO({}) 用户钱包不存在.", updateReqVO);
throw exception(WALLET_NOT_FOUND);
}
// 更新钱包余额
payWalletService.addWalletBalance(wallet.getId(), String.valueOf(updateReqVO.getUserId()),
PayWalletBizTypeEnum.UPDATE_BALANCE, updateReqVO.getBalance());
return success(true);
}
}

View File

@@ -0,0 +1,59 @@
package cn.aagro.pp.module.pay.controller.admin.wallet;
import cn.aagro.pp.framework.common.pojo.CommonResult;
import cn.aagro.pp.module.pay.api.notify.dto.PayOrderNotifyReqDTO;
import cn.aagro.pp.module.pay.api.notify.dto.PayRefundNotifyReqDTO;
import cn.aagro.pp.module.pay.service.wallet.PayWalletRechargeService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.annotation.security.PermitAll;
import javax.validation.Valid;
import static cn.aagro.pp.framework.common.pojo.CommonResult.success;
import static cn.aagro.pp.framework.common.util.servlet.ServletUtils.getClientIP;
@Tag(name = "管理后台 - 钱包充值")
@RestController
@RequestMapping("/pay/wallet-recharge")
@Validated
@Slf4j
public class PayWalletRechargeController {
@Resource
private PayWalletRechargeService walletRechargeService;
@PostMapping("/update-paid")
@Operation(summary = "更新钱包充值为已充值") // 由 pay-module 支付服务,进行回调,可见 PayNotifyJob
@PermitAll // 无需登录, 内部校验实现
public CommonResult<Boolean> updateWalletRechargerPaid(@Valid @RequestBody PayOrderNotifyReqDTO notifyReqDTO) {
walletRechargeService.updateWalletRechargerPaid(Long.valueOf(notifyReqDTO.getMerchantOrderId()),
notifyReqDTO.getPayOrderId());
return success(true);
}
@PostMapping("/refund")
@Operation(summary = "发起钱包充值退款")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
public CommonResult<Boolean> refundWalletRecharge(@RequestParam("id") Long id) {
walletRechargeService.refundWalletRecharge(id, getClientIP());
return success(true);
}
@PostMapping("/update-refunded")
@Operation(summary = "更新钱包充值为已退款") // 由 pay-module 支付服务,进行回调,可见 PayNotifyJob
@PermitAll // 无需登录, 内部校验实现
public CommonResult<Boolean> updateWalletRechargeRefunded(@RequestBody PayRefundNotifyReqDTO notifyReqDTO) {
walletRechargeService.updateWalletRechargeRefunded(
Long.valueOf(notifyReqDTO.getMerchantOrderId()),
Long.valueOf(notifyReqDTO.getMerchantRefundId()),
notifyReqDTO.getPayRefundId());
return success(true);
}
}

View File

@@ -0,0 +1,75 @@
package cn.aagro.pp.module.pay.controller.admin.wallet;
import cn.aagro.pp.framework.common.pojo.CommonResult;
import cn.aagro.pp.framework.common.pojo.PageResult;
import cn.aagro.pp.module.pay.controller.admin.wallet.vo.rechargepackage.WalletRechargePackageCreateReqVO;
import cn.aagro.pp.module.pay.controller.admin.wallet.vo.rechargepackage.WalletRechargePackagePageReqVO;
import cn.aagro.pp.module.pay.controller.admin.wallet.vo.rechargepackage.WalletRechargePackageRespVO;
import cn.aagro.pp.module.pay.controller.admin.wallet.vo.rechargepackage.WalletRechargePackageUpdateReqVO;
import cn.aagro.pp.module.pay.convert.wallet.PayWalletRechargePackageConvert;
import cn.aagro.pp.module.pay.dal.dataobject.wallet.PayWalletRechargePackageDO;
import cn.aagro.pp.module.pay.service.wallet.PayWalletRechargePackageService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import static cn.aagro.pp.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 钱包充值套餐")
@RestController
@RequestMapping("/pay/wallet-recharge-package")
@Validated
public class PayWalletRechargePackageController {
@Resource
private PayWalletRechargePackageService walletRechargePackageService;
@PostMapping("/create")
@Operation(summary = "创建钱包充值套餐")
@PreAuthorize("@ss.hasPermission('pay:wallet-recharge-package:create')")
public CommonResult<Long> createWalletRechargePackage(@Valid @RequestBody WalletRechargePackageCreateReqVO createReqVO) {
return success(walletRechargePackageService.createWalletRechargePackage(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新钱包充值套餐")
@PreAuthorize("@ss.hasPermission('pay:wallet-recharge-package:update')")
public CommonResult<Boolean> updateWalletRechargePackage(@Valid @RequestBody WalletRechargePackageUpdateReqVO updateReqVO) {
walletRechargePackageService.updateWalletRechargePackage(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除钱包充值套餐")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('pay:wallet-recharge-package:delete')")
public CommonResult<Boolean> deleteWalletRechargePackage(@RequestParam("id") Long id) {
walletRechargePackageService.deleteWalletRechargePackage(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得钱包充值套餐")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pay:wallet-recharge-package:query')")
public CommonResult<WalletRechargePackageRespVO> getWalletRechargePackage(@RequestParam("id") Long id) {
PayWalletRechargePackageDO walletRechargePackage = walletRechargePackageService.getWalletRechargePackage(id);
return success(PayWalletRechargePackageConvert.INSTANCE.convert(walletRechargePackage));
}
@GetMapping("/page")
@Operation(summary = "获得钱包充值套餐分页")
@PreAuthorize("@ss.hasPermission('pay:wallet-recharge-package:query')")
public CommonResult<PageResult<WalletRechargePackageRespVO>> getWalletRechargePackagePage(@Valid WalletRechargePackagePageReqVO pageVO) {
PageResult<PayWalletRechargePackageDO> pageResult = walletRechargePackageService.getWalletRechargePackagePage(pageVO);
return success(PayWalletRechargePackageConvert.INSTANCE.convertPage(pageResult));
}
}

View File

@@ -0,0 +1,43 @@
package cn.aagro.pp.module.pay.controller.admin.wallet;
import cn.aagro.pp.framework.common.pojo.CommonResult;
import cn.aagro.pp.framework.common.pojo.PageResult;
import cn.aagro.pp.module.pay.controller.admin.wallet.vo.transaction.PayWalletTransactionPageReqVO;
import cn.aagro.pp.module.pay.controller.admin.wallet.vo.transaction.PayWalletTransactionRespVO;
import cn.aagro.pp.module.pay.convert.wallet.PayWalletTransactionConvert;
import cn.aagro.pp.module.pay.dal.dataobject.wallet.PayWalletTransactionDO;
import cn.aagro.pp.module.pay.service.wallet.PayWalletTransactionService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
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.RestController;
import javax.annotation.Resource;
import javax.validation.Valid;
import static cn.aagro.pp.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 钱包余额明细")
@RestController
@RequestMapping("/pay/wallet-transaction")
@Validated
@Slf4j
public class PayWalletTransactionController {
@Resource
private PayWalletTransactionService payWalletTransactionService;
@GetMapping("/page")
@Operation(summary = "获得钱包流水分页")
@PreAuthorize("@ss.hasPermission('pay:wallet:query')")
public CommonResult<PageResult<PayWalletTransactionRespVO>> getWalletTransactionPage(
@Valid PayWalletTransactionPageReqVO pageReqVO) {
PageResult<PayWalletTransactionDO> result = payWalletTransactionService.getWalletTransactionPage(pageReqVO);
return success(PayWalletTransactionConvert.INSTANCE.convertPage2(result));
}
}

View File

@@ -0,0 +1,31 @@
package cn.aagro.pp.module.pay.controller.admin.wallet.vo.rechargepackage;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* 充值套餐 Base VO提供给添加、修改、详细的子 VO 使用
* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
*/
@Data
public class WalletRechargePackageBaseVO {
@Schema(description = "套餐名", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
@NotNull(message = "套餐名不能为空")
private String name;
@Schema(description = "支付金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "16454")
@NotNull(message = "支付金额不能为空")
private Integer payPrice;
@Schema(description = "赠送金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "20887")
@NotNull(message = "赠送金额不能为空")
private Integer bonusPrice;
@Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
@NotNull(message = "状态不能为空")
private Byte status;
}

View File

@@ -0,0 +1,14 @@
package cn.aagro.pp.module.pay.controller.admin.wallet.vo.rechargepackage;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Schema(description = "管理后台 - 充值套餐创建 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class WalletRechargePackageCreateReqVO extends WalletRechargePackageBaseVO {
}

View File

@@ -0,0 +1,30 @@
package cn.aagro.pp.module.pay.controller.admin.wallet.vo.rechargepackage;
import cn.aagro.pp.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.aagro.pp.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 充值套餐分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class WalletRechargePackagePageReqVO extends PageParam {
@Schema(description = "套餐名", example = "李四")
private String name;
@Schema(description = "状态", example = "2")
private Integer status;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}

View File

@@ -0,0 +1,19 @@
package cn.aagro.pp.module.pay.controller.admin.wallet.vo.rechargepackage;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 充值套餐 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class WalletRechargePackageRespVO extends WalletRechargePackageBaseVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "9032")
private Long id;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}

View File

@@ -0,0 +1,20 @@
package cn.aagro.pp.module.pay.controller.admin.wallet.vo.rechargepackage;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.validation.constraints.NotNull;
@Schema(description = "管理后台 - 充值套餐更新 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class WalletRechargePackageUpdateReqVO extends WalletRechargePackageBaseVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "9032")
@NotNull(message = "编号不能为空")
private Long id;
}

View File

@@ -0,0 +1,23 @@
package cn.aagro.pp.module.pay.controller.admin.wallet.vo.transaction;
import cn.aagro.pp.framework.common.enums.UserTypeEnum;
import cn.aagro.pp.framework.common.pojo.PageParam;
import cn.aagro.pp.framework.common.validation.InEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - 钱包流水分页 Request VO")
@Data
public class PayWalletTransactionPageReqVO extends PageParam {
@Schema(description = "钱包编号", example = "888")
private Long walletId;
@Schema(description = "用户编号", example = "1024")
private Long userId;
@Schema(description = "用户类型", example = "1")
@InEnum(UserTypeEnum.class)
private Integer userType;
}

View File

@@ -0,0 +1,35 @@
package cn.aagro.pp.module.pay.controller.admin.wallet.vo.transaction;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "用户 APP - 钱包流水分页 Response VO")
@Data
public class PayWalletTransactionRespVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long id;
@Schema(description = "钱包编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
private Long walletId;
@Schema(description = "业务分类", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer bizType;
@Schema(description = "交易金额,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
private Long price;
@Schema(description = "流水标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "土豆土豆")
private String title;
@Schema(description = "交易后的余额,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
private Long balance;
@Schema(description = "交易时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
// TODO @jasonmerchantOrderId 字段,需要在 PayWalletTransaction 存储下;然后,前端也返回下这个字段,界面也展示下商户名
}

View File

@@ -0,0 +1,39 @@
package cn.aagro.pp.module.pay.controller.admin.wallet.vo.wallet;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* 用户钱包 Base VO提供给添加、修改、详细的子 VO 使用
* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
*/
@Data
public class PayWalletBaseVO {
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20020")
@NotNull(message = "用户编号不能为空")
private Long userId;
@Schema(description = "用户类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "用户类型不能为空")
private Integer userType;
@Schema(description = "余额,单位分", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "余额,单位分不能为空")
private Integer balance;
@Schema(description = "累计支出,单位分", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "累计支出,单位分不能为空")
private Integer totalExpense;
@Schema(description = "累计充值,单位分", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "累计充值,单位分不能为空")
private Integer totalRecharge;
@Schema(description = "冻结金额,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "20737")
@NotNull(message = "冻结金额,单位分不能为空")
private Integer freezePrice;
}

View File

@@ -0,0 +1,33 @@
package cn.aagro.pp.module.pay.controller.admin.wallet.vo.wallet;
import cn.aagro.pp.framework.common.enums.UserTypeEnum;
import cn.aagro.pp.framework.common.pojo.PageParam;
import cn.aagro.pp.framework.common.validation.InEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.aagro.pp.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 会员钱包分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class PayWalletPageReqVO extends PageParam {
@Schema(description = "用户编号", example = "1024")
private Long userId;
@Schema(description = "用户类型", example = "1")
@InEnum(value = UserTypeEnum.class)
private Integer userType;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}

View File

@@ -0,0 +1,22 @@
package cn.aagro.pp.module.pay.controller.admin.wallet.vo.wallet;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 用户钱包 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class PayWalletRespVO extends PayWalletBaseVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "29528")
private Long id;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}

View File

@@ -0,0 +1,20 @@
package cn.aagro.pp.module.pay.controller.admin.wallet.vo.wallet;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotNull;
@Schema(description = "管理后台 - 修改钱包余额 Request VO")
@Data
public class PayWalletUpdateBalanceReqVO {
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23788")
@NotNull(message = "用户编号不能为空")
private Long userId;
@Schema(description = "变动余额,正数为增加,负数为减少", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
@NotNull(message = "变动余额不能为空")
private Integer balance;
}

View File

@@ -0,0 +1,16 @@
package cn.aagro.pp.module.pay.controller.admin.wallet.vo.wallet;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotNull;
@Schema(description = "管理后台 - 用户钱包明细 Request VO")
@Data
public class PayWalletUserReqVO {
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "用户编号不能为空")
private Long userId;
}

View File

@@ -0,0 +1,39 @@
package cn.aagro.pp.module.pay.controller.app.channel;
import cn.aagro.pp.framework.common.pojo.CommonResult;
import cn.aagro.pp.module.pay.dal.dataobject.channel.PayChannelDO;
import cn.aagro.pp.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 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 javax.annotation.Resource;
import java.util.List;
import java.util.Set;
import static cn.aagro.pp.framework.common.pojo.CommonResult.success;
import static cn.aagro.pp.framework.common.util.collection.CollectionUtils.convertSet;
@Tag(name = "用户 App - 支付渠道")
@RestController
@RequestMapping("/pay/channel")
@Validated
public class AppPayChannelController {
@Resource
private PayChannelService channelService;
@GetMapping("/get-enable-code-list")
@Operation(summary = "获得指定应用的开启的支付渠道编码列表")
@Parameter(name = "appId", description = "应用编号", required = true, example = "1")
public CommonResult<Set<String>> getEnableChannelCodeList(@RequestParam("appId") Long appId) {
List<PayChannelDO> channels = channelService.getEnableChannelList(appId);
return success(convertSet(channels, PayChannelDO::getCode));
}
}

View File

@@ -0,0 +1,63 @@
### /pay/create 提交支付订单【alipay_pc】
POST {{appApi}}/pay/order/submit
Content-Type: application/json
Authorization: Bearer {{appToken}}
tenant-id: {{appTenantId}}
{
"id": 174,
"channelCode": "alipay_pc"
}
### /pay/create 提交支付订单【wx_bar】
POST {{appApi}}/pay/order/submit
Content-Type: application/json
Authorization: Bearer {{appToken}}
tenant-id: {{appTenantId}}
{
"id": 202,
"channelCode": "wx_bar",
"channelExtras": {
"authCode": "134042110834344848"
}
}
### /pay/create 提交支付订单【wx_pub】
POST {{appApi}}/pay/order/submit
Content-Type: application/json
Authorization: Bearer {{appToken}}
tenant-id: {{appTenantId}}
{
"id": 202,
"channelCode": "wx_pub",
"channelExtras": {
"openid": "ockUAwIZ-0OeMZl9ogcZ4ILrGba0"
}
}
### /pay/create 提交支付订单【wx_lite】
POST {{appApi}}/pay/order/submit
Content-Type: application/json
Authorization: Bearer {{appToken}}
tenant-id: {{appTenantId}}
{
"id": 202,
"channelCode": "wx_lite",
"channelExtras": {
"openid": "oLefc4g5GjKWHJjLjMSXB3wX0fD0"
}
}
### /pay/create 提交支付订单【wx_native】
POST {{appApi}}/pay/order/submit
Content-Type: application/json
Authorization: Bearer {{appToken}}
tenant-id: {{appTenantId}}
{
"id": 202,
"channelCode": "wx_native"
}

View File

@@ -0,0 +1,90 @@
package cn.aagro.pp.module.pay.controller.app.order;
import cn.hutool.core.util.ObjUtil;
import cn.aagro.pp.framework.common.pojo.CommonResult;
import cn.aagro.pp.framework.common.util.object.BeanUtils;
import cn.aagro.pp.module.pay.controller.admin.order.vo.PayOrderRespVO;
import cn.aagro.pp.module.pay.controller.admin.order.vo.PayOrderSubmitRespVO;
import cn.aagro.pp.module.pay.controller.app.order.vo.AppPayOrderSubmitReqVO;
import cn.aagro.pp.module.pay.controller.app.order.vo.AppPayOrderSubmitRespVO;
import cn.aagro.pp.module.pay.dal.dataobject.order.PayOrderDO;
import cn.aagro.pp.module.pay.dal.dataobject.wallet.PayWalletDO;
import cn.aagro.pp.module.pay.enums.PayChannelEnum;
import cn.aagro.pp.module.pay.enums.order.PayOrderStatusEnum;
import cn.aagro.pp.module.pay.framework.pay.core.client.impl.wallet.WalletPayClient;
import cn.aagro.pp.module.pay.service.order.PayOrderService;
import cn.aagro.pp.module.pay.service.wallet.PayWalletService;
import com.google.common.collect.Maps;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.Objects;
import static cn.aagro.pp.framework.common.pojo.CommonResult.success;
import static cn.aagro.pp.framework.common.util.servlet.ServletUtils.getClientIP;
import static cn.aagro.pp.framework.web.core.util.WebFrameworkUtils.getLoginUserId;
import static cn.aagro.pp.framework.web.core.util.WebFrameworkUtils.getLoginUserType;
@Tag(name = "用户 APP - 支付订单")
@RestController
@RequestMapping("/pay/order")
@Validated
@Slf4j
public class AppPayOrderController {
@Resource
private PayOrderService payOrderService;
@Resource
private PayWalletService payWalletService;
@GetMapping("/get")
@Operation(summary = "获得支付订单")
@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);
if (order== null) {
return success(null);
}
// 重要:校验订单是否是当前用户,避免越权
if (order.getUserId() != null // 特殊:早期订单未存储 userId所以忽略
&& ObjUtil.notEqual(order.getUserId(), getLoginUserId())) {
return success(null);
}
// 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")
@Operation(summary = "提交支付订单")
public CommonResult<AppPayOrderSubmitRespVO> submitPayOrder(@RequestBody AppPayOrderSubmitReqVO reqVO) {
// 1. 钱包支付事,需要额外传 user_id 和 user_type
if (Objects.equals(reqVO.getChannelCode(), PayChannelEnum.WALLET.getCode())) {
if (reqVO.getChannelExtras() == null) {
reqVO.setChannelExtras(Maps.newHashMapWithExpectedSize(1));
}
PayWalletDO wallet = payWalletService.getOrCreateWallet(getLoginUserId(), getLoginUserType());
reqVO.getChannelExtras().put(WalletPayClient.WALLET_ID_KEY, String.valueOf(wallet.getId()));
}
// 2. 提交支付
PayOrderSubmitRespVO respVO = payOrderService.submitOrder(reqVO, getClientIP());
return success(BeanUtils.toBean(respVO, AppPayOrderSubmitRespVO.class));
}
}

View File

@@ -0,0 +1,14 @@
package cn.aagro.pp.module.pay.controller.app.order.vo;
import cn.aagro.pp.module.pay.controller.admin.order.vo.PayOrderSubmitReqVO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.Map;
@Schema(description = "用户 APP - 支付订单提交 Request VO")
@Data
public class AppPayOrderSubmitReqVO extends PayOrderSubmitReqVO {
}

View File

@@ -0,0 +1,14 @@
package cn.aagro.pp.module.pay.controller.app.order.vo;
import cn.aagro.pp.module.pay.controller.admin.order.vo.PayOrderSubmitRespVO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Schema(description = "用户 APP - 支付订单提交 Response VO")
@Data
public class AppPayOrderSubmitRespVO extends PayOrderSubmitRespVO {
}

View File

@@ -0,0 +1,37 @@
package cn.aagro.pp.module.pay.controller.app.transfer;
import cn.aagro.pp.framework.common.pojo.CommonResult;
import cn.aagro.pp.module.pay.service.transfer.PayTransferService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import static cn.aagro.pp.framework.common.pojo.CommonResult.success;
@Tag(name = "用户 APP - 转账单")
@RestController
@RequestMapping("/pay/transfer")
@Validated
@Slf4j
public class AppPayTransferController {
@Resource
private PayTransferService transferService;
@GetMapping("/sync")
@Operation(summary = "同步转账单") // 目的:解决微信转账的异步回调可能有延迟的问题
@Parameter(name = "id", description = "编号", required = true, example = "1024")
public CommonResult<Boolean> syncTransfer(@RequestParam("id") Long id) {
transferService.syncTransfer(id);
return success(true);
}
}

View File

@@ -0,0 +1,42 @@
package cn.aagro.pp.module.pay.controller.app.wallet;
import cn.aagro.pp.framework.common.enums.UserTypeEnum;
import cn.aagro.pp.framework.common.pojo.CommonResult;
import cn.aagro.pp.module.pay.controller.app.wallet.vo.wallet.AppPayWalletRespVO;
import cn.aagro.pp.module.pay.convert.wallet.PayWalletConvert;
import cn.aagro.pp.module.pay.dal.dataobject.wallet.PayWalletDO;
import cn.aagro.pp.module.pay.service.wallet.PayWalletService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import static cn.aagro.pp.framework.common.pojo.CommonResult.success;
import static cn.aagro.pp.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
/**
* @author jason
*/
@Tag(name = "用户 APP - 钱包")
@RestController
@RequestMapping("/pay/wallet")
@Validated
@Slf4j
public class AppPayWalletController {
@Resource
private PayWalletService payWalletService;
@GetMapping("/get")
@Operation(summary = "获取钱包")
public CommonResult<AppPayWalletRespVO> getPayWallet() {
PayWalletDO wallet = payWalletService.getOrCreateWallet(getLoginUserId(), UserTypeEnum.MEMBER.getValue());
return success(PayWalletConvert.INSTANCE.convert(wallet));
}
}

View File

@@ -0,0 +1,72 @@
package cn.aagro.pp.module.pay.controller.app.wallet;
import cn.hutool.core.collection.CollUtil;
import cn.aagro.pp.framework.common.enums.UserTypeEnum;
import cn.aagro.pp.framework.common.pojo.CommonResult;
import cn.aagro.pp.framework.common.pojo.PageParam;
import cn.aagro.pp.framework.common.pojo.PageResult;
import cn.aagro.pp.module.pay.controller.app.wallet.vo.recharge.AppPayWalletRechargeCreateReqVO;
import cn.aagro.pp.module.pay.controller.app.wallet.vo.recharge.AppPayWalletRechargeCreateRespVO;
import cn.aagro.pp.module.pay.controller.app.wallet.vo.recharge.AppPayWalletRechargeRespVO;
import cn.aagro.pp.module.pay.convert.wallet.PayWalletConvert;
import cn.aagro.pp.module.pay.convert.wallet.PayWalletRechargeConvert;
import cn.aagro.pp.module.pay.dal.dataobject.order.PayOrderDO;
import cn.aagro.pp.module.pay.dal.dataobject.wallet.PayWalletRechargeDO;
import cn.aagro.pp.module.pay.service.order.PayOrderService;
import cn.aagro.pp.module.pay.service.wallet.PayWalletRechargeService;
import com.google.common.collect.Lists;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import static cn.aagro.pp.framework.common.pojo.CommonResult.success;
import static cn.aagro.pp.framework.common.util.collection.CollectionUtils.convertList;
import static cn.aagro.pp.framework.common.util.servlet.ServletUtils.getClientIP;
import static cn.aagro.pp.framework.web.core.util.WebFrameworkUtils.getLoginUserId;
import static cn.aagro.pp.framework.web.core.util.WebFrameworkUtils.getLoginUserType;
@Tag(name = "用户 APP - 钱包充值")
@RestController
@RequestMapping("/pay/wallet-recharge")
@Validated
@Slf4j
public class AppPayWalletRechargeController {
@Resource
private PayWalletRechargeService walletRechargeService;
@Resource
private PayOrderService payOrderService;
@PostMapping("/create")
@Operation(summary = "创建钱包充值记录(发起充值)")
public CommonResult<AppPayWalletRechargeCreateRespVO> createWalletRecharge(
@Valid @RequestBody AppPayWalletRechargeCreateReqVO reqVO) {
PayWalletRechargeDO walletRecharge = walletRechargeService.createWalletRecharge(
getLoginUserId(), getLoginUserType(), getClientIP(), reqVO);
return success(PayWalletRechargeConvert.INSTANCE.convert(walletRecharge));
}
@GetMapping("/page")
@Operation(summary = "获得钱包充值记录分页")
public CommonResult<PageResult<AppPayWalletRechargeRespVO>> getWalletRechargePage(@Valid PageParam pageReqVO) {
PageResult<PayWalletRechargeDO> pageResult = walletRechargeService.getWalletRechargePackagePage(
getLoginUserId(), UserTypeEnum.MEMBER.getValue(), pageReqVO, true);
if (CollUtil.isEmpty(pageResult.getList())) {
return success(PageResult.empty(pageResult.getTotal()));
}
// 拼接数据
List<PayOrderDO> payOrderList = payOrderService.getOrderList(
convertList(pageResult.getList(), PayWalletRechargeDO::getPayOrderId));
return success(PayWalletRechargeConvert.INSTANCE.convertPage(pageResult, payOrderList));
}
}

View File

@@ -0,0 +1,42 @@
package cn.aagro.pp.module.pay.controller.app.wallet;
import cn.aagro.pp.framework.common.enums.CommonStatusEnum;
import cn.aagro.pp.framework.common.pojo.CommonResult;
import cn.aagro.pp.framework.common.util.object.BeanUtils;
import cn.aagro.pp.module.pay.controller.app.wallet.vo.recharge.AppPayWalletPackageRespVO;
import cn.aagro.pp.module.pay.dal.dataobject.wallet.PayWalletRechargePackageDO;
import cn.aagro.pp.module.pay.service.wallet.PayWalletRechargePackageService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.Comparator;
import java.util.List;
import static cn.aagro.pp.framework.common.pojo.CommonResult.success;
@Tag(name = "用户 APP - 钱包充值套餐")
@RestController
@RequestMapping("/pay/wallet-recharge-package")
@Validated
@Slf4j
public class AppPayWalletRechargePackageController {
@Resource
private PayWalletRechargePackageService walletRechargePackageService;
@GetMapping("/list")
@Operation(summary = "获得钱包充值套餐列表")
public CommonResult<List<AppPayWalletPackageRespVO>> getWalletRechargePackageList() {
List<PayWalletRechargePackageDO> list = walletRechargePackageService.getWalletRechargePackageList(
CommonStatusEnum.ENABLE.getStatus());
list.sort(Comparator.comparingInt(PayWalletRechargePackageDO::getPayPrice));
return success(BeanUtils.toBean(list, AppPayWalletPackageRespVO.class));
}
}

View File

@@ -0,0 +1,60 @@
package cn.aagro.pp.module.pay.controller.app.wallet;
import cn.aagro.pp.framework.common.enums.UserTypeEnum;
import cn.aagro.pp.framework.common.pojo.CommonResult;
import cn.aagro.pp.framework.common.pojo.PageResult;
import cn.aagro.pp.framework.common.util.object.BeanUtils;
import cn.aagro.pp.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionPageReqVO;
import cn.aagro.pp.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionRespVO;
import cn.aagro.pp.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionSummaryRespVO;
import cn.aagro.pp.module.pay.dal.dataobject.wallet.PayWalletTransactionDO;
import cn.aagro.pp.module.pay.service.wallet.PayWalletTransactionService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.format.annotation.DateTimeFormat;
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 javax.annotation.Resource;
import javax.validation.Valid;
import java.time.LocalDateTime;
import static cn.aagro.pp.framework.common.pojo.CommonResult.success;
import static cn.aagro.pp.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
import static cn.aagro.pp.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "用户 APP - 钱包余额明细")
@RestController
@RequestMapping("/pay/wallet-transaction")
@Validated
@Slf4j
public class AppPayWalletTransactionController {
@Resource
private PayWalletTransactionService payWalletTransactionService;
@GetMapping("/page")
@Operation(summary = "获得钱包流水分页")
public CommonResult<PageResult<AppPayWalletTransactionRespVO>> getWalletTransactionPage(
@Valid AppPayWalletTransactionPageReqVO pageReqVO) {
PageResult<PayWalletTransactionDO> pageResult = payWalletTransactionService.getWalletTransactionPage(
getLoginUserId(), UserTypeEnum.MEMBER.getValue(), pageReqVO);
return success(BeanUtils.toBean(pageResult, AppPayWalletTransactionRespVO.class));
}
@GetMapping("/get-summary")
@Operation(summary = "获得钱包流水统计")
@Parameter(name = "times", description = "时间段", required = true)
public CommonResult<AppPayWalletTransactionSummaryRespVO> getWalletTransactionSummary(
@RequestParam("createTime") @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) LocalDateTime[] createTime) {
AppPayWalletTransactionSummaryRespVO summary = payWalletTransactionService.getWalletTransactionSummary(
getLoginUserId(), UserTypeEnum.MEMBER.getValue(), createTime);
return success(summary);
}
}

View File

@@ -0,0 +1,20 @@
package cn.aagro.pp.module.pay.controller.app.wallet.vo.recharge;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "用户 APP - 用户充值套餐 Response VO")
@Data
public class AppPayWalletPackageRespVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "套餐名", requiredMode = Schema.RequiredMode.REQUIRED, example = "小套餐")
private String name;
@Schema(description = "支付金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
private Integer payPrice;
@Schema(description = "赠送金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "20")
private Integer bonusPrice;
}

View File

@@ -0,0 +1,26 @@
package cn.aagro.pp.module.pay.controller.app.wallet.vo.recharge;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.Min;
import java.util.Objects;
@Schema(description = "用户 APP - 创建钱包充值 Request VO")
@Data
public class AppPayWalletRechargeCreateReqVO {
@Schema(description = "支付金额", example = "1000")
@Min(value = 1, message = "支付金额必须大于零")
private Integer payPrice;
@Schema(description = "充值套餐编号", example = "1024")
private Long packageId;
@AssertTrue(message = "充值金额和充钱套餐不能同时为空")
public boolean isValidPayPriceAndPackageId() {
return Objects.nonNull(payPrice) || Objects.nonNull(packageId);
}
}

View File

@@ -0,0 +1,16 @@
package cn.aagro.pp.module.pay.controller.app.wallet.vo.recharge;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "用户 APP - 创建钱包充值 Resp VO")
@Data
public class AppPayWalletRechargeCreateRespVO {
@Schema(description = "钱包充值编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long id;
@Schema(description = "支付订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
private Long payOrderId;
}

View File

@@ -0,0 +1,42 @@
package cn.aagro.pp.module.pay.controller.app.wallet.vo.recharge;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "用户 APP - 钱包充值记录 Resp VO")
@Data
public class AppPayWalletRechargeRespVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long id;
@Schema(description = "用户实际到账余额", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
private Integer totalPrice;
@Schema(description = "实际支付金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "20")
private Integer payPrice;
@Schema(description = "钱包赠送金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "80")
private Integer bonusPrice;
@Schema(description = "支付成功的支付渠道", requiredMode = Schema.RequiredMode.REQUIRED)
private String payChannelCode;
@Schema(description = "支付渠道名", example = "微信小程序支付")
private String payChannelName;
@Schema(description = "支付订单编号", requiredMode = Schema.RequiredMode.REQUIRED)
private Long payOrderId;
@Schema(description = "支付成功的外部订单号", requiredMode = Schema.RequiredMode.REQUIRED)
private String payOrderChannelOrderNo; // 从 PayOrderDO 的 channelOrderNo 字段
@Schema(description = "订单支付时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime payTime;
@Schema(description = "退款状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
private Integer refundStatus;
}

View File

@@ -0,0 +1,31 @@
package cn.aagro.pp.module.pay.controller.app.wallet.vo.transaction;
import cn.aagro.pp.framework.common.pojo.PageParam;
import cn.aagro.pp.framework.common.util.date.DateUtils;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
@Schema(description = "用户 APP - 钱包流水分页 Request VO")
@Data
public class AppPayWalletTransactionPageReqVO extends PageParam {
/**
* 类型 - 收入
*/
public static final Integer TYPE_INCOME = 1;
/**
* 类型 - 支出
*/
public static final Integer TYPE_EXPENSE = 2;
@Schema(description = "类型", example = "1")
private Integer type;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}

View File

@@ -0,0 +1,24 @@
package cn.aagro.pp.module.pay.controller.app.wallet.vo.transaction;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "用户 APP - 钱包流水分页 Response VO")
@Data
public class AppPayWalletTransactionRespVO {
@Schema(description = "业务分类", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer bizType;
@Schema(description = "交易金额,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
private Long price;
@Schema(description = "流水标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "土豆土豆")
private String title;
@Schema(description = "交易时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}

Some files were not shown because too many files have changed in this diff Show More