第一次提交

This commit is contained in:
wyw
2024-08-08 00:31:26 +08:00
commit c202e2b63d
1819 changed files with 221890 additions and 0 deletions

View File

@ -0,0 +1,38 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.fastbee.pay</groupId>
<artifactId>fastbee-pay</artifactId>
<version>3.8.5</version>
</parent>
<groupId>com.fastbee.pay.api</groupId>
<artifactId>fastbee-pay-api</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- <dependency>-->
<!-- <groupId>com.fastbee</groupId>-->
<!-- <artifactId>fastbee-common</artifactId>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>org.projectlombok</groupId>-->
<!-- <artifactId>lombok</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>com.fastbee.pay.framework</groupId>
<artifactId>fastbee-pay-framework</artifactId>
<version>3.8.5</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,34 @@
package com.fastbee.pay.api.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 fastbee
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PayOrderNotifyReqDTO {
/**
* 商户订单编号
*/
@NotEmpty(message = "商户订单号不能为空")
private String merchantOrderId;
/**
* 支付订单编号
*/
@NotNull(message = "支付订单编号不能为空")
private Long payOrderId;
}

View File

@ -0,0 +1,34 @@
package com.fastbee.pay.api.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 fastbee
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PayRefundNotifyReqDTO {
/**
* 商户退款单编号
*/
@NotEmpty(message = "商户退款单编号不能为空")
private String merchantOrderId;
/**
* 支付退款编号
*/
@NotNull(message = "支付退款编号不能为空")
private Long payRefundId;
}

View File

@ -0,0 +1,4 @@
/**
* 占位符,无特殊作用
*/
package com.fastbee.pay.api.api.notify;

View File

@ -0,0 +1,32 @@
package com.fastbee.pay.api.api.order;
import com.fastbee.pay.api.api.order.dto.PayOrderCreateReqDTO;
import com.fastbee.pay.api.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);
}

View File

@ -0,0 +1,65 @@
package com.fastbee.pay.api.api.order.dto;
import lombok.Data;
import lombok.experimental.Accessors;
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
@Accessors(chain = true)
public class PayOrderCreateReqDTO implements Serializable {
/**
* 应用编号
*/
@NotNull(message = "应用编号不能为空")
private Long appId;
/**
* 用户 IP
*/
@NotEmpty(message = "用户 IP 不能为空")
private String userIp;
// ========== 商户相关字段 ==========
/**
* 商户订单编号
*/
@NotEmpty(message = "商户订单编号不能为空")
private String merchantOrderId;
/**
* 商品标题
*/
@NotEmpty(message = "商品标题不能为空")
@Length(max = 32, 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,44 @@
package com.fastbee.pay.api.api.order.dto;
import com.fastbee.pay.api.enums.order.PayOrderStatusEnum;
import lombok.Data;
/**
* 支付单信息 Response DTO
*
* @author fastbee
*/
@Data
public class PayOrderRespDTO {
/**
* 订单编号,数据库自增
*/
private Long id;
/**
* 渠道编码
* 枚举 PayChannelEnum
*/
private String channelCode;
// ========== 商户相关字段 ==========
/**
* 商户订单编号
* 例如说,内部系统 A 的订单号。需要保证每个 PayMerchantDO 唯一
*/
private String merchantOrderId;
// ========== 订单相关字段 ==========
/**
* 支付金额,单位:分
*/
private Integer price;
/**
* 支付状态
* 枚举 {@link PayOrderStatusEnum}
*/
private Integer status;
// ========== 渠道相关字段 ==========
}

View File

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

View File

@ -0,0 +1,60 @@
package com.fastbee.pay.api.api.refund.dto;
import lombok.Data;
import lombok.experimental.Accessors;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
/**
* 退款单创建 Request DTO
*
* @author fastbee
*/
@Data
@Accessors(chain = true)
public class PayRefundCreateReqDTO {
/**
* 应用编号
*/
@NotNull(message = "应用编号不能为空")
private Long appId;
/**
* 用户 IP
*/
@NotEmpty(message = "用户 IP 不能为空")
private String userIp;
// ========== 商户相关字段 ==========
/**
* 商户订单编号
*/
@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,42 @@
package com.fastbee.pay.api.api.refund.dto;
import com.fastbee.pay.api.enums.refund.PayRefundStatusEnum;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 退款单信息 Response DTO
*
* @author fastbee
*/
@Data
public class PayRefundRespDTO {
/**
* 退款单编号
*/
private Long id;
// ========== 退款相关字段 ==========
/**
* 退款状态
* 枚举 {@link PayRefundStatusEnum}
*/
private Integer status;
/**
* 退款金额,单位:分
*/
private Integer refundPrice;
// ========== 商户相关字段 ==========
/**
* 商户订单编号
*/
private String merchantOrderId;
/**
* 退款成功时间
*/
private LocalDateTime successTime;
}

View File

@ -0,0 +1,18 @@
package com.fastbee.pay.api.enums;
/**
* Pay 字典类型的枚举类
*
* @author fastbee
*/
public interface DictTypeConstants {
String CHANNEL_CODE = "pay_channel_code"; // 支付渠道编码
String ORDER_STATUS = "pay_order_status"; // 支付渠道
String REFUND_STATUS = "pay_order_status"; // 退款状态
String NOTIFY_STATUS = "pay_notify_status"; // 回调状态
}

View File

@ -0,0 +1,57 @@
package com.fastbee.pay.api.enums;
import com.fastbee.common.exception.ErrorCode;
import com.fastbee.common.utils.MessageUtils;
/**
* Pay 错误码 Core 枚举类
* pay 系统,使用 1-007-000-000 段
*/
public interface ErrorCodeConstants {
// ========== APP 模块 1007000000 ==========
ErrorCode APP_NOT_FOUND = new ErrorCode(1007000000, MessageUtils.message("app.not.found"));
ErrorCode APP_IS_DISABLE = new ErrorCode(1007000002, MessageUtils.message("app.is.disable"));
ErrorCode APP_EXIST_ORDER_CANT_DELETE = new ErrorCode(1007000003, MessageUtils.message("app.exist.order.cant.delete"));
ErrorCode APP_EXIST_REFUND_CANT_DELETE = new ErrorCode(1007000004, MessageUtils.message("app.exist.refund.cant.delete"));
// ========== CHANNEL 模块 1007001000 ==========
ErrorCode CHANNEL_NOT_FOUND = new ErrorCode(1007001000, MessageUtils.message("channel.not.found"));
ErrorCode CHANNEL_IS_DISABLE = new ErrorCode(1007001001, MessageUtils.message("channel.is.disable"));
ErrorCode CHANNEL_EXIST_SAME_CHANNEL_ERROR = new ErrorCode(1007001004, MessageUtils.message("channel.exists.same.channel.error"));
// ========== ORDER 模块 1007002000 ==========
ErrorCode ORDER_NOT_FOUND = new ErrorCode(1007002000, MessageUtils.message("order.not.found"));
ErrorCode ORDER_STATUS_IS_NOT_WAITING = new ErrorCode(1007002001, MessageUtils.message("order.status.is.not.waiting"));
ErrorCode ORDER_STATUS_IS_SUCCESS = new ErrorCode(1007002002, MessageUtils.message("order.status.is.success"));
ErrorCode ORDER_IS_EXPIRED = new ErrorCode(1007002003, MessageUtils.message("order.is.expired"));
ErrorCode ORDER_SUBMIT_CHANNEL_ERROR = new ErrorCode(1007002004, MessageUtils.message("order.submit.channel.error"));
ErrorCode ORDER_REFUND_FAIL_STATUS_ERROR = new ErrorCode(1007002005, MessageUtils.message("order.refund.fail.status.error"));
// ========== ORDER 模块(拓展单) 1007003000 ==========
ErrorCode ORDER_EXTENSION_NOT_FOUND = new ErrorCode(1007003000, MessageUtils.message("order.extension.not.found"));
ErrorCode ORDER_EXTENSION_STATUS_IS_NOT_WAITING = new ErrorCode(1007003001, MessageUtils.message("order.extension.status.is.not.waiting"));
ErrorCode ORDER_EXTENSION_IS_PAID = new ErrorCode(1007003002, MessageUtils.message("order.extension.is.paid"));
// ========== 支付模块(退款) 1007006000 ==========
ErrorCode REFUND_PRICE_EXCEED = new ErrorCode(1007006000, MessageUtils.message("refund.price.exceed"));
ErrorCode REFUND_HAS_REFUNDING = new ErrorCode(1007006002, MessageUtils.message("refund.has.refunding"));
ErrorCode REFUND_EXISTS = new ErrorCode(1007006003, MessageUtils.message("refund.exists"));
ErrorCode REFUND_NOT_FOUND = new ErrorCode(1007006004, MessageUtils.message("refund.not.found"));
ErrorCode REFUND_STATUS_IS_NOT_WAITING = new ErrorCode(1007006005, MessageUtils.message("refund.statue.is.not.waiting"));
// ========== 示例订单 1007900000 ==========
ErrorCode DEMO_ORDER_NOT_FOUND = new ErrorCode(1007900000, MessageUtils.message("demo.order.not.found"));
ErrorCode DEMO_ORDER_UPDATE_PAID_STATUS_NOT_UNPAID = new ErrorCode(1007900001, MessageUtils.message("demo.order.update.paid.status.not.unpaid"));
ErrorCode DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR = new ErrorCode(1007900002, MessageUtils.message("demo.order.update.paid.fail.pay.order.id.error"));
ErrorCode DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_STATUS_NOT_SUCCESS = new ErrorCode(1007900003, MessageUtils.message("demo.order.update.paid.fail.pay.order.status.not.success"));
ErrorCode DEMO_ORDER_UPDATE_PAID_FAIL_PAY_PRICE_NOT_MATCH = new ErrorCode(1007900004, MessageUtils.message("demo.order.update.paid.fail.pay.price.not.match"));
ErrorCode DEMO_ORDER_REFUND_FAIL_NOT_PAID = new ErrorCode(1007900005, MessageUtils.message("demo.order.refund.fail.not.paid"));
ErrorCode DEMO_ORDER_REFUND_FAIL_REFUNDED = new ErrorCode(1007900006, MessageUtils.message("demo.order.refund.fail.refunded"));
ErrorCode DEMO_ORDER_REFUND_FAIL_REFUND_NOT_FOUND = new ErrorCode(1007900007, MessageUtils.message("demo.order.refund.fail.refund.not.found"));
ErrorCode DEMO_ORDER_REFUND_FAIL_REFUND_NOT_SUCCESS = new ErrorCode(1007900008, MessageUtils.message("demo.order.refund.fail.refund.not.success"));
ErrorCode DEMO_ORDER_REFUND_FAIL_REFUND_ORDER_ID_ERROR = new ErrorCode(1007900009, MessageUtils.message("demo.order.refund.fail.refund.order.id.error"));
ErrorCode DEMO_ORDER_REFUND_FAIL_REFUND_PRICE_NOT_MATCH = new ErrorCode(1007900010, MessageUtils.message("demo.order.refund.fail.refund.price.not.match"));
}

View File

@ -0,0 +1,21 @@
package com.fastbee.pay.api.enums.member;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 钱包操作类型枚举
*
* @author jason
*/
@AllArgsConstructor
@Getter
public enum WalletOperateTypeEnum {
TOP_UP_INC(1, "充值增加"),
ORDER_DEC(2, "订单消费扣除");
// TODO 其它类型
private final Integer type;
private final String desc;
}

View File

@ -0,0 +1,26 @@
package com.fastbee.pay.api.enums.member;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 钱包交易大类枚举
*
* @author jason
*/
@AllArgsConstructor
@Getter
public enum WalletTransactionGategoryEnum {
TOP_UP(1, "充值"),
SPENDING(2, "支出");
/**
* 分类
*/
private final Integer category;
/**
* 说明
*/
private final String desc;
}

View File

@ -0,0 +1,32 @@
package com.fastbee.pay.api.enums.notify;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 支付通知状态枚举
*
* @author fastbee
*/
@Getter
@AllArgsConstructor
public enum PayNotifyStatusEnum {
WAITING(0, "等待通知"),
SUCCESS(10, "通知成功"),
FAILURE(20, "通知失败"), // 多次尝试,彻底失败
REQUEST_SUCCESS(21, "请求成功,但是结果失败"),
REQUEST_FAILURE(22, "请求失败"),
;
/**
* 状态
*/
private final Integer status;
/**
* 名字
*/
private final String name;
}

View File

@ -0,0 +1,28 @@
package com.fastbee.pay.api.enums.notify;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 支付通知类型
*
* @author fastbee
*/
@Getter
@AllArgsConstructor
public enum PayNotifyTypeEnum {
ORDER(1, "支付单"),
REFUND(2, "退款单"),
;
/**
* 类型
*/
private final Integer type;
/**
* 名字
*/
private final String name;
}

View File

@ -0,0 +1,64 @@
package com.fastbee.pay.api.enums.order;
import com.fastbee.common.core.text.IntArrayValuable;
import com.fastbee.common.utils.object.ObjectUtils;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Objects;
/**
* 支付订单的状态枚举
*
* @author fastbee
*/
@Getter
@AllArgsConstructor
public enum PayOrderStatusEnum implements IntArrayValuable {
WAITING(0, "未支付"),
SUCCESS(10, "支付成功"),
REFUND(20, "已退款"),
CLOSED(30, "支付关闭"), // 注意:全部退款后,还是 REFUND 状态
;
private final Integer status;
private final String name;
@Override
public int[] array() {
return new int[0];
}
/**
* 判断是否支付成功
*
* @param status 状态
* @return 是否支付成功
*/
public static boolean isSuccess(Integer status) {
return Objects.equals(status, SUCCESS.getStatus());
}
/**
* 判断是否支付成功或者已退款
*
* @param status 状态
* @return 是否支付成功或者已退款
*/
public static boolean isSuccessOrRefund(Integer status) {
return ObjectUtils.equalsAny(status,
SUCCESS.getStatus(), REFUND.getStatus());
}
/**
* 判断是否支付关闭
*
* @param status 状态
* @return 是否支付关闭
*/
public static boolean isClosed(Integer status) {
return Objects.equals(status, CLOSED.getStatus());
}
}

View File

@ -0,0 +1,32 @@
package com.fastbee.pay.api.enums.refund;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Objects;
/**
* 渠道的退款状态枚举
*
* @author fastbee
*/
@Getter
@AllArgsConstructor
public enum PayRefundStatusEnum {
WAITING(0, "未退款"),
SUCCESS(10, "退款成功"),
FAILURE(20, "退款失败");
private final Integer status;
private final String name;
public static boolean isSuccess(Integer status) {
return Objects.equals(status, SUCCESS.getStatus());
}
public static boolean isFailure(Integer status) {
return Objects.equals(status, FAILURE.getStatus());
}
}

View File

@ -0,0 +1,68 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.fastbee.pay</groupId>
<artifactId>fastbee-pay</artifactId>
<version>3.8.5</version>
</parent>
<groupId>com.fastbee.pay.core</groupId>
<artifactId>fastbee-pay-core</artifactId>
<!-- <properties>-->
<!-- <maven.compiler.source>8</maven.compiler.source>-->
<!-- <maven.compiler.target>8</maven.compiler.target>-->
<!-- <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>-->
<!-- </properties>-->
<dependencies>
<!-- <dependency>-->
<!-- <groupId>org.projectlombok</groupId>-->
<!-- <artifactId>lombok</artifactId>-->
<!-- <version>1.18.22</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>com.fastbee</groupId>-->
<!-- <artifactId>fastbee-common</artifactId>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>com.fastbee.pay.framework</groupId>-->
<!-- <artifactId>fastbee-pay-framework</artifactId>-->
<!-- <version>3.8.5</version>-->
<!-- </dependency>-->
<dependency>
<groupId>com.fastbee.pay.api</groupId>
<artifactId>fastbee-pay-api</artifactId>
<version>3.8.5</version>
</dependency>
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>13.0</version>
<scope>compile</scope>
</dependency>
<!-- <dependency>-->
<!-- <groupId>com.alibaba</groupId>-->
<!-- <artifactId>easyexcel-core</artifactId>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>org.mapstruct</groupId>-->
<!-- <artifactId>mapstruct</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>com.google.guava</groupId>-->
<!-- <artifactId>guava</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>com.fastbee</groupId>
<artifactId>fastbee-framework</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,35 @@
package com.fastbee.pay.core.api.order;
import com.fastbee.pay.api.api.order.PayOrderApi;
import com.fastbee.pay.api.api.order.dto.PayOrderCreateReqDTO;
import com.fastbee.pay.api.api.order.dto.PayOrderRespDTO;
import com.fastbee.pay.core.convert.order.PayOrderConvert;
import com.fastbee.pay.core.domain.dataobject.order.PayOrder;
import com.fastbee.pay.core.service.order.PayOrderService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* 支付单 API 实现类
*
* @author fastbee
*/
@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) {
PayOrder order = payOrderService.getOrder(id);
return PayOrderConvert.INSTANCE.convert2(order);
}
}

View File

@ -0,0 +1,35 @@
package com.fastbee.pay.core.api.refund;
import com.fastbee.pay.api.api.refund.PayRefundApi;
import com.fastbee.pay.api.api.refund.dto.PayRefundCreateReqDTO;
import com.fastbee.pay.api.api.refund.dto.PayRefundRespDTO;
import com.fastbee.pay.core.convert.refund.PayRefundConvert;
import com.fastbee.pay.core.service.refund.PayRefundService;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
/**
* 退款单 API 实现类
*
* @author fastbee
*/
@Service
@Validated
public class PayRefundApiImpl implements PayRefundApi {
@Resource
private PayRefundService payRefundService;
@Override
public Long createRefund(PayRefundCreateReqDTO reqDTO) {
return payRefundService.createPayRefund(reqDTO);
}
@Override
public PayRefundRespDTO getRefund(Long id) {
return PayRefundConvert.INSTANCE.convert02(payRefundService.getRefund(id));
}
}

View File

@ -0,0 +1,108 @@
package com.fastbee.pay.core.controller.admin.app;
import cn.hutool.core.collection.CollUtil;
import com.fastbee.common.core.domain.CommonResult;
import com.fastbee.common.core.domain.PageResult;
import com.fastbee.pay.core.controller.admin.app.vo.*;
import com.fastbee.pay.core.convert.app.PayAppConvert;
import com.fastbee.pay.core.domain.dataobject.app.PayApp;
import com.fastbee.pay.core.domain.dataobject.channel.PayChannel;
import com.fastbee.pay.core.service.app.PayAppService;
import com.fastbee.pay.core.service.channel.PayChannelService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
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.Collection;
import java.util.List;
import static com.fastbee.common.core.domain.CommonResult.success;
import static com.fastbee.common.utils.collection.CollectionUtils.convertList;
@Slf4j
@Api(tags = "管理后台 - 支付应用信息")
@RestController
@RequestMapping("/pay/app")
@Validated
public class PayAppController {
@Resource
private PayAppService appService;
@Resource
private PayChannelService channelService;
@PostMapping("/create")
@ApiOperation("创建支付应用信息")
@PreAuthorize("@ss.hasPermission('pay:app:create')")
public CommonResult<Long> createApp(@Valid @RequestBody PayAppCreateReqVO createReqVO) {
return success(appService.createApp(createReqVO));
}
@PutMapping("/update")
@ApiOperation("更新支付应用信息")
@PreAuthorize("@ss.hasPermission('pay:app:update')")
public CommonResult<Boolean> updateApp(@Valid @RequestBody PayAppUpdateReqVO updateReqVO) {
appService.updateApp(updateReqVO);
return success(true);
}
@PutMapping("/update-status")
@ApiOperation("更新支付应用状态")
@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")
@ApiOperation("删除支付应用信息")
@ApiParam(name = "id", value = "编号", required = true)
@PreAuthorize("@ss.hasPermission('pay:app:delete')")
public CommonResult<Boolean> deleteApp(@RequestParam("id") Long id) {
appService.deleteApp(id);
return success(true);
}
@GetMapping("/get")
@ApiOperation("获得支付应用信息")
@ApiParam(name = "id", value = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pay:app:query')")
public CommonResult<PayAppRespVO> getApp(@RequestParam("id") Long id) {
PayApp app = appService.getApp(id);
return success(PayAppConvert.INSTANCE.convert(app));
}
@GetMapping("/page")
@ApiOperation("获得支付应用信息分页")
@PreAuthorize("@ss.hasPermission('pay:app:query')")
public CommonResult<PageResult<PayAppPageItemRespVO>> getAppPage(@Valid PayAppPageReqVO pageVO) {
// 得到应用分页列表
PageResult<PayApp> pageResult = appService.getAppPage(pageVO);
if (CollUtil.isEmpty(pageResult.getList())) {
return success(PageResult.empty());
}
// 得到所有的应用编号,查出所有的渠道
Collection<Long> appIds = convertList(pageResult.getList(), PayApp::getId);
List<PayChannel> channels = channelService.getChannelListByAppIds(appIds);
// 拼接后返回
return success(PayAppConvert.INSTANCE.convertPage(pageResult, channels));
}
@GetMapping("/list")
@ApiOperation("获得应用列表")
@PreAuthorize("@ss.hasPermission('pay:merchant:query')")
public CommonResult<List<PayAppRespVO>> getAppList() {
List<PayApp> appListDO = appService.getAppList();
return success(PayAppConvert.INSTANCE.convertList(appListDO));
}
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,32 @@
package com.fastbee.pay.core.controller.admin.app.vo;
import com.fastbee.common.core.domain.PageParam;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static com.fastbee.common.utils.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@ApiModel(description = "管理后台 - 支付应用信息分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class PayAppPageReqVO extends PageParam {
@ApiModelProperty(value = "应用名", example = "小豆")
private String name;
@ApiModelProperty(value = "开启状态", example = "0")
private Integer status;
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@ApiModelProperty(value = "创建时间")
private LocalDateTime[] createTime;
}

View File

@ -0,0 +1,23 @@
package com.fastbee.pay.core.controller.admin.app.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.time.LocalDateTime;
@ApiModel(description = "管理后台 - 支付应用信息 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class PayAppRespVO extends PayAppBaseVO {
@ApiModelProperty(value = "应用编号", required = true, example = "1024")
private Long id;
@ApiModelProperty(value = "创建时间", required = true)
private LocalDateTime createTime;
}

View File

@ -0,0 +1,21 @@
package com.fastbee.pay.core.controller.admin.app.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.validation.constraints.NotNull;
@ApiModel(description = "管理后台 - 支付应用信息更新 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class PayAppUpdateReqVO extends PayAppBaseVO {
@ApiModelProperty(value = "应用编号", required = true, example = "1024")
@NotNull(message = "应用编号不能为空")
private Long id;
}

View File

@ -0,0 +1,21 @@
package com.fastbee.pay.core.controller.admin.app.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
@ApiModel(description = "管理后台 - 应用更新状态 Request VO")
@Data
public class PayAppUpdateStatusReqVO {
@ApiModelProperty(value = "应用编号", required = true, example = "1024")
@NotNull(message = "应用编号不能为空")
private Long id;
@ApiModelProperty(value = "状态,见 SysCommonStatusEnum 枚举", required = true, example = "1")
@NotNull(message = "状态不能为空")
private Integer status;
}

View File

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

View File

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

View File

@ -0,0 +1,26 @@
package com.fastbee.pay.core.controller.admin.channel.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@ApiModel(description = "管理后台 - 支付渠道 创建 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class PayChannelCreateReqVO extends PayChannelBaseVO {
@ApiModelProperty(value = "渠道编码", required = true, example = "alipay_pc")
@NotNull(message = "渠道编码不能为空")
private String code;
@ApiModelProperty(value = "渠道配置的 json 字符串")
@NotBlank(message = "渠道配置不能为空")
private String config;
}

View File

@ -0,0 +1,29 @@
package com.fastbee.pay.core.controller.admin.channel.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.time.LocalDateTime;
@ApiModel(description = "管理后台 - 支付渠道 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class PayChannelRespVO extends PayChannelBaseVO {
@ApiModelProperty(value = "商户编号", required = true, example = "1024")
private Long id;
@ApiModelProperty(value = "创建时间", required = true, example = "1024")
private LocalDateTime createTime;
@ApiModelProperty(value = "渠道编码", required = true, example = "alipay_pc")
private String code;
@ApiModelProperty(value = "配置", required = true)
private String config;
}

View File

@ -0,0 +1,26 @@
package com.fastbee.pay.core.controller.admin.channel.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@ApiModel(description = "管理后台 - 支付渠道 更新 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class PayChannelUpdateReqVO extends PayChannelBaseVO {
@ApiModelProperty(value = "商户编号", required = true)
@NotNull(message = "商户编号不能为空")
private Long id;
@ApiModelProperty(value = "渠道配置的json字符串")
@NotBlank(message = "渠道配置不能为空")
private String config;
}

View File

@ -0,0 +1,83 @@
package com.fastbee.pay.core.controller.admin.demo;
import com.fastbee.common.core.domain.CommonResult;
import com.fastbee.common.core.domain.PageParam;
import com.fastbee.common.core.domain.PageResult;
import com.fastbee.pay.api.api.notify.dto.PayOrderNotifyReqDTO;
import com.fastbee.pay.api.api.notify.dto.PayRefundNotifyReqDTO;
import com.fastbee.pay.core.controller.admin.demo.vo.OrderInfoCreateReqVO;
import com.fastbee.pay.core.controller.admin.demo.vo.OrderInfoRespVO;
import com.fastbee.pay.core.convert.demo.OrderInfoConvert;
import com.fastbee.pay.core.domain.dataobject.demo.OrderInfo;
import com.fastbee.pay.core.service.demo.OrderInfoService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
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.annotation.security.PermitAll;
import javax.validation.Valid;
import static com.fastbee.common.core.domain.CommonResult.success;
import static com.fastbee.common.utils.SecurityUtils.getLoginUser;
import static com.fastbee.common.utils.ServletUtils.getClientIP;
@Api(tags = "管理后台 - 示例订单")
@RestController
@RequestMapping("/order")
@Validated
public class OrderInfoController {
@Resource
private OrderInfoService OrderInfoService;
@PostMapping("/create")
@ApiOperation("创建示例订单")
public CommonResult<Long> createOrder(@Valid @RequestBody OrderInfoCreateReqVO createReqVO) {
return success(OrderInfoService.createOrder(getLoginUser().getUserId(), createReqVO));
}
@GetMapping("/page")
@ApiOperation("获得示例订单分页")
public CommonResult<PageResult<OrderInfoRespVO>> getOrderPage(@Valid PageParam pageVO) {
PageResult<OrderInfo> pageResult = OrderInfoService.getOrderPage(pageVO);
return success(OrderInfoConvert.INSTANCE.convertPage(pageResult));
}
@PostMapping("/update-paid")
@ApiOperation("更新示例订单为已支付") // 由 pay-module 支付服务,进行回调,可见 PayNotifyJob
@PermitAll // 无需登录,安全由 OrderInfoService 内部校验实现
// @OperateLog(enable = false) // 禁用操作日志,因为没有操作人
public CommonResult<Boolean> updateOrderPaid(@RequestBody PayOrderNotifyReqDTO notifyReqDTO) {
OrderInfoService.updateOrderPaid(Long.valueOf(notifyReqDTO.getMerchantOrderId()),
notifyReqDTO.getPayOrderId());
return success(true);
}
@PutMapping("/refund")
@ApiOperation("发起示例订单的退款")
@ApiParam(name = "id", value = "编号", required = true, example = "1024")
public CommonResult<Boolean> refundOrder(@RequestParam("id") Long id) {
OrderInfoService.refundOrder(id, getClientIP());
return success(true);
}
@PostMapping("/update-refunded")
@ApiOperation("更新示例订单为已退款") // 由 pay-module 支付服务,进行回调,可见 PayNotifyJob
@PermitAll // 无需登录,安全由 OrderInfoService 内部校验实现
// @OperateLog(enable = false) // 禁用操作日志,因为没有操作人
public CommonResult<Boolean> updateOrderRefunded(@RequestBody PayRefundNotifyReqDTO notifyReqDTO) {
OrderInfoService.updateOrderRefunded(Long.valueOf(notifyReqDTO.getMerchantOrderId()),
notifyReqDTO.getPayRefundId());
return success(true);
}
}

View File

@ -0,0 +1,17 @@
package com.fastbee.pay.core.controller.admin.demo.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
@ApiModel(description = "管理后台 - 示例订单创建 Request VO")
@Data
public class OrderInfoCreateReqVO {
@ApiModelProperty(value = "商品编号", required = true, example = "17682")
@NotNull(message = "商品编号不能为空")
private Long spuId;
}

View File

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

View File

@ -0,0 +1,137 @@
package com.fastbee.pay.core.controller.admin.notify;
import cn.hutool.core.collection.CollUtil;
import com.fastbee.common.core.domain.CommonResult;
import com.fastbee.common.core.domain.PageResult;
import com.fastbee.common.utils.MessageUtils;
import com.fastbee.pay.core.controller.admin.notify.vo.PayNotifyTaskDetailRespVO;
import com.fastbee.pay.core.controller.admin.notify.vo.PayNotifyTaskPageReqVO;
import com.fastbee.pay.core.controller.admin.notify.vo.PayNotifyTaskRespVO;
import com.fastbee.pay.core.convert.notify.PayNotifyTaskConvert;
import com.fastbee.pay.core.domain.dataobject.app.PayApp;
import com.fastbee.pay.core.domain.dataobject.notify.PayNotifyLog;
import com.fastbee.pay.core.domain.dataobject.notify.PayNotifyTask;
import com.fastbee.pay.core.service.app.PayAppService;
import com.fastbee.pay.core.service.notify.PayNotifyService;
import com.fastbee.pay.core.service.order.PayOrderService;
import com.fastbee.pay.core.service.refund.PayRefundService;
import com.fastbee.pay.framework.client.PayClient;
import com.fastbee.pay.framework.client.PayClientFactory;
import com.fastbee.pay.framework.client.dto.order.PayOrderRespDTO;
import com.fastbee.pay.framework.client.dto.refund.PayRefundRespDTO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
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.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
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.annotation.security.PermitAll;
import javax.validation.Valid;
import java.util.List;
import java.util.Map;
import static com.fastbee.common.core.domain.CommonResult.success;
import static com.fastbee.common.exception.ServiceExceptionUtil.exception;
import static com.fastbee.common.utils.collection.CollectionUtils.convertList;
import static com.fastbee.pay.api.enums.ErrorCodeConstants.CHANNEL_NOT_FOUND;
@Api(tags = "管理后台 - 回调通知")
@RestController
@RequestMapping("/pay/notify")
@Validated
@Slf4j
public class PayNotifyController {
@Resource
private PayOrderService orderService;
@Resource
private PayRefundService refundService;
@Resource
private PayNotifyService notifyService;
@Resource
private PayAppService appService;
@Resource
private PayClientFactory payClientFactory;
@PostMapping(value = "/order/{channelId}")
@ApiOperation("支付渠道的统一【支付】回调")
@PermitAll
// @OperateLog(enable = false) // 回调地址,无需记录操作日志
public String notifyOrder(@PathVariable("channelId") Long channelId,
@RequestParam(required = false) Map<String, String> params,
@RequestBody(required = false) String body) {
log.info("[notifyOrder][channelId({}) 回调数据({}/{})]", channelId, params, body);
// 1. 校验支付渠道是否存在
PayClient payClient = payClientFactory.getPayClient(channelId);
if (payClient == null) {
log.error("[notifyCallback][渠道编号({}) 找不到对应的支付客户端]", channelId);
throw exception(CHANNEL_NOT_FOUND);
}
// 2. 解析通知数据
PayOrderRespDTO notify = payClient.parseOrderNotify(params, body);
orderService.notifyOrder(channelId, notify);
return MessageUtils.message("success");
}
@PostMapping(value = "/refund/{channelId}")
@ApiOperation("支付渠道的统一【退款】回调")
@PermitAll
// @OperateLog(enable = false) // 回调地址,无需记录操作日志
public String notifyRefund(@PathVariable("channelId") Long channelId,
@RequestParam(required = false) Map<String, String> params,
@RequestBody(required = false) String body) {
log.info("[notifyRefund][channelId({}) 回调数据({}/{})]", channelId, params, body);
// 1. 校验支付渠道是否存在
PayClient payClient = payClientFactory.getPayClient(channelId);
if (payClient == null) {
log.error("[notifyCallback][渠道编号({}) 找不到对应的支付客户端]", channelId);
throw exception(CHANNEL_NOT_FOUND);
}
// 2. 解析通知数据
PayRefundRespDTO notify = payClient.parseRefundNotify(params, body);
refundService.notifyRefund(channelId, notify);
return MessageUtils.message("success");
}
@GetMapping("/get-detail")
@ApiOperation("获得回调通知的明细")
@ApiParam(name = "id", value = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pay:notify:query')")
public CommonResult<PayNotifyTaskDetailRespVO> getNotifyTaskDetail(@RequestParam("id") Long id) {
PayNotifyTask task = notifyService.getNotifyTask(id);
if (task == null) {
return success(null);
}
// 拼接返回
PayApp app = appService.getApp(task.getAppId());
List<PayNotifyLog> logs = notifyService.getNotifyLogList(id);
return success(PayNotifyTaskConvert.INSTANCE.convert(task, app, logs));
}
@GetMapping("/page")
@ApiOperation("获得回调通知分页")
@PreAuthorize("@ss.hasPermission('pay:notify:query')")
public CommonResult<PageResult<PayNotifyTaskRespVO>> getNotifyTaskPage(@Valid PayNotifyTaskPageReqVO pageVO) {
PageResult<PayNotifyTask> pageResult = notifyService.getNotifyTaskPage(pageVO);
if (CollUtil.isEmpty(pageResult.getList())) {
return success(PageResult.empty());
}
// 拼接返回
Map<Long, PayApp> appMap = appService.getAppMap(convertList(pageResult.getList(), PayNotifyTask::getAppId));
return success(PayNotifyTaskConvert.INSTANCE.convertPage(pageResult, appMap));
}
}

View File

@ -0,0 +1,45 @@
package com.fastbee.pay.core.controller.admin.notify.vo;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 回调通知 Base VO提供给添加、修改、详细的子 VO 使用
* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
*/
@Data
public class PayNotifyTaskBaseVO {
@ApiModelProperty(value = "应用编号", required = true, example = "10636")
private Long appId;
@ApiModelProperty(value = "通知类型", required = true, example = "2")
private Byte type;
@ApiModelProperty(value = "数据编号", required = true, example = "6722")
private Long dataId;
@ApiModelProperty(value = "通知状态", required = true, example = "1")
private Byte status;
@ApiModelProperty(value = "商户订单编号", required = true, example = "26697")
private String merchantOrderId;
@ApiModelProperty(value = "下一次通知时间", required = true)
private LocalDateTime nextNotifyTime;
@ApiModelProperty(value = "最后一次执行时间", required = true)
private LocalDateTime lastExecuteTime;
@ApiModelProperty(value = "当前通知次数", required = true)
private Byte notifyTimes;
@ApiModelProperty(value = "最大可通知次数", required = true)
private Byte maxNotifyTimes;
@ApiModelProperty(value = "异步通知地址", required = true, example = "https://www.iocoder.cn")
private String notifyUrl;
}

View File

@ -0,0 +1,55 @@
package com.fastbee.pay.core.controller.admin.notify.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.time.LocalDateTime;
import java.util.List;
@ApiModel(description = "管理后台 - 回调通知的明细 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class PayNotifyTaskDetailRespVO extends PayNotifyTaskBaseVO {
@ApiModelProperty(value = "任务编号", required = true, example = "3380")
private Long id;
@ApiModelProperty(value = "创建时间", required = true)
private LocalDateTime createTime;
@ApiModelProperty(value = "更新时间", required = true)
private LocalDateTime updateTime;
@ApiModelProperty(value = "应用名称", example = "wx_pay")
private String appName;
@ApiModelProperty(value = "回调日志列表")
private List<Log> logs;
@ApiModel(description = "管理后台 - 回调日志")
@Data
public static class Log {
@ApiModelProperty(value = "日志编号", required = true, example = "8848")
private Long id;
@ApiModelProperty(value = "通知状态", required = true, example = "1")
private Byte status;
@ApiModelProperty(value = "当前通知次数", required = true)
private Byte notifyTimes;
@ApiModelProperty(value = "HTTP 响应结果", required = true)
private String response;
@ApiModelProperty(value = "创建时间", required = true)
private LocalDateTime createTime;
}
}

View File

@ -0,0 +1,41 @@
package com.fastbee.pay.core.controller.admin.notify.vo;
import com.fastbee.common.core.domain.PageParam;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static com.fastbee.common.utils.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@ApiModel(description = "管理后台 - 回调通知分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class PayNotifyTaskPageReqVO extends PageParam {
@ApiModelProperty(value = "应用编号", example = "10636")
private Long appId;
@ApiModelProperty(value = "通知类型", example = "2")
private Integer type;
@ApiModelProperty(value = "数据编号", example = "6722")
private Long dataId;
@ApiModelProperty(value = "通知状态", example = "1")
private Integer status;
@ApiModelProperty(value = "商户订单编号", example = "26697")
private String merchantOrderId;
@ApiModelProperty(value = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}

View File

@ -0,0 +1,26 @@
package com.fastbee.pay.core.controller.admin.notify.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.time.LocalDateTime;
@ApiModel(description = "管理后台 - 回调通知 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class PayNotifyTaskRespVO extends PayNotifyTaskBaseVO {
@ApiModelProperty(value = "任务编号", required = true, example = "3380")
private Long id;
@ApiModelProperty(value = "创建时间", required = true)
private LocalDateTime createTime;
@ApiModelProperty(value = "应用名称", example = "wx_pay")
private String appName;
}

View File

@ -0,0 +1,120 @@
package com.fastbee.pay.core.controller.admin.order;
import cn.hutool.core.collection.CollectionUtil;
import com.fastbee.common.core.domain.CommonResult;
import com.fastbee.common.core.domain.PageResult;
import com.fastbee.pay.core.controller.admin.order.vo.PayOrderDetailsRespVO;
import com.fastbee.pay.core.controller.admin.order.vo.PayOrderExcelVO;
import com.fastbee.pay.core.controller.admin.order.vo.PayOrderExportReqVO;
import com.fastbee.pay.core.controller.admin.order.vo.PayOrderPageItemRespVO;
import com.fastbee.pay.core.controller.admin.order.vo.PayOrderPageReqVO;
import com.fastbee.pay.core.controller.admin.order.vo.PayOrderRespVO;
import com.fastbee.pay.core.controller.admin.order.vo.PayOrderSubmitReqVO;
import com.fastbee.pay.core.controller.admin.order.vo.PayOrderSubmitRespVO;
import com.fastbee.pay.core.convert.order.PayOrderConvert;
import com.fastbee.pay.core.domain.dataobject.app.PayApp;
import com.fastbee.pay.core.domain.dataobject.order.PayOrder;
import com.fastbee.pay.core.domain.dataobject.order.PayOrderExtension;
import com.fastbee.pay.core.service.app.PayAppService;
import com.fastbee.pay.core.service.order.PayOrderService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
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.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
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 com.fastbee.common.core.domain.CommonResult.success;
import static com.fastbee.common.utils.ServletUtils.getClientIP;
import static com.fastbee.common.utils.collection.CollectionUtils.convertList;
@Api(tags = "管理后台 - 支付订单")
@RestController
@RequestMapping("/pay/order")
@Validated
public class PayOrderController {
@Resource
private PayOrderService orderService;
@Resource
private PayAppService appService;
@GetMapping("/get")
@ApiOperation("获得支付订单")
@ApiParam(name = "id", value = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pay:order:query')")
public CommonResult<PayOrderRespVO> getOrder(@RequestParam("id") Long id) {
return success(PayOrderConvert.INSTANCE.convert(orderService.getOrder(id)));
}
@GetMapping("/get-detail")
@ApiOperation("获得支付订单详情")
@ApiParam(name = "id", value = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pay:order:query')")
public CommonResult<PayOrderDetailsRespVO> getOrderDetail(@RequestParam("id") Long id) {
PayOrder order = orderService.getOrder(id);
if (order == null) {
return success(null);
}
// 拼接返回
PayApp app = appService.getApp(order.getAppId());
PayOrderExtension orderExtension = orderService.getOrderExtension(order.getExtensionId());
return success(PayOrderConvert.INSTANCE.convert(order, orderExtension, app));
}
@PostMapping("/submit")
@ApiOperation("提交支付订单")
public CommonResult<PayOrderSubmitRespVO> submitPayOrder(@RequestBody PayOrderSubmitReqVO reqVO) {
PayOrderSubmitRespVO respVO = orderService.submitOrder(reqVO, getClientIP());
return success(respVO);
}
@GetMapping("/page")
@ApiOperation("获得支付订单分页")
@PreAuthorize("@ss.hasPermission('pay:order:query')")
public CommonResult<PageResult<PayOrderPageItemRespVO>> getOrderPage(@Valid PayOrderPageReqVO pageVO) {
PageResult<PayOrder> pageResult = orderService.getOrderPage(pageVO);
if (CollectionUtil.isEmpty(pageResult.getList())) {
return success(new PageResult<>(pageResult.getTotal()));
}
// 拼接返回
Map<Long, PayApp> appMap = appService.getAppMap(convertList(pageResult.getList(), PayOrder::getAppId));
return success(PayOrderConvert.INSTANCE.convertPage(pageResult, appMap));
}
@GetMapping("/export-excel")
@ApiOperation("导出支付订单 Excel")
@PreAuthorize("@ss.hasPermission('pay:order:export')")
// @OperateLog(type = EXPORT)
public void exportOrderExcel(@Valid PayOrderExportReqVO exportReqVO,
HttpServletResponse response) throws IOException {
List<PayOrder> list = orderService.getOrderList(exportReqVO);
if (CollectionUtil.isEmpty(list)) {
// ExcelUtils.write(response, "支付订单.xls", "数据",
// PayOrderExcelVO.class, new ArrayList<>());
return;
}
// 拼接返回
Map<Long, PayApp> appMap = appService.getAppMap(convertList(list, PayOrder::getAppId));
List<PayOrderExcelVO> excelList = PayOrderConvert.INSTANCE.convertList(list, appMap);
// 导出 Excel
// ExcelUtils.write(response, "支付订单.xls", "数据", PayOrderExcelVO.class, excelList);
}
}

View File

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

View File

@ -0,0 +1,46 @@
package com.fastbee.pay.core.controller.admin.order.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.time.LocalDateTime;
@ApiModel(description = "管理后台 - 支付订单详细信息 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class PayOrderDetailsRespVO extends PayOrderBaseVO {
@ApiModelProperty(value = "支付订单编号", required = true, example = "1024")
private Long id;
@ApiModelProperty(value = "应用名称", required = true, example = "fastbee")
private String appName;
@ApiModelProperty(value = "创建时间", required = true)
private LocalDateTime createTime;
@ApiModelProperty(value = "更新时间", required = true)
private LocalDateTime updateTime;
/**
* 支付订单扩展
*/
private PayOrderExtension extension;
@Data
@ApiModel(description = "支付订单扩展")
public static class PayOrderExtension {
@ApiModelProperty(value = "支付订单号", required = true, example = "1024")
private String no;
@ApiModelProperty(value = "支付异步通知的内容")
private String channelNotifyData;
}
}

View File

@ -0,0 +1,62 @@
package com.fastbee.pay.core.controller.admin.order.vo;
import com.alibaba.excel.annotation.ExcelProperty;
import com.fastbee.pay.api.enums.DictTypeConstants;
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 = "支付金额")
private Integer price;
@ExcelProperty(value = "退款金额")
private Integer refundPrice;
@ExcelProperty(value = "手续金额")
private Integer channelFeePrice;
@ExcelProperty("商户单号")
private String merchantOrderId;
@ExcelProperty(value = "支付单号")
private String no;
@ExcelProperty("渠道单号")
private String channelOrderNo;
@ExcelProperty(value = "支付状态")
private Integer status;
@ExcelProperty(value = "渠道编号名称")
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,39 @@
package com.fastbee.pay.core.controller.admin.order.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static com.fastbee.common.utils.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@ApiModel(description = "管理后台 - 支付订单 Excel 导出 Request VO参数和 PayOrderPageReqVO 是一致的")
@Data
public class PayOrderExportReqVO {
@ApiModelProperty(value = "应用编号", example = "1024")
private Long appId;
@ApiModelProperty(value = "渠道编码", example = "wx_app")
private String channelCode;
@ApiModelProperty(value = "商户订单编号", example = "4096")
private String merchantOrderId;
@ApiModelProperty(value = "渠道编号", example = "1888")
private String channelOrderNo;
@ApiModelProperty(value = "支付单号", example = "2014888")
private String no;
@ApiModelProperty(value = "支付状态", example = "0")
private Integer status;
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@ApiModelProperty(value = "创建时间")
private LocalDateTime[] createTime;
}

View File

@ -0,0 +1,26 @@
package com.fastbee.pay.core.controller.admin.order.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.time.LocalDateTime;
@ApiModel(description = "管理后台 - 支付订单分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class PayOrderPageItemRespVO extends PayOrderBaseVO {
@ApiModelProperty(value = "支付订单编号", required = true, example = "1024")
private Long id;
@ApiModelProperty(value = "创建时间", required = true)
private LocalDateTime createTime;
@ApiModelProperty(value = "应用名称", example = "wx_pay")
private String appName;
}

View File

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

View File

@ -0,0 +1,23 @@
package com.fastbee.pay.core.controller.admin.order.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.time.LocalDateTime;
@ApiModel(description = "管理后台 - 支付订单 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class PayOrderRespVO extends PayOrderBaseVO {
@ApiModelProperty(value = "支付订单编号", required = true)
private Long id;
@ApiModelProperty(value = "创建时间", required = true)
private LocalDateTime createTime;
}

View File

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

View File

@ -0,0 +1,21 @@
package com.fastbee.pay.core.controller.admin.order.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@ApiModel(description = "管理后台 - 支付订单提交 Response VO")
@Data
public class PayOrderSubmitRespVO {
@ApiModelProperty(value = "支付状态", required = true, example = "10") // 参见 PayOrderStatusEnum 枚举
private Integer status;
@ApiModelProperty(value = "展示模式", required = true, example = "url") // 参见 PayDisplayModeEnum 枚举
private String displayMode;
@ApiModelProperty(value = "展示内容", required = true)
private String displayContent;
}

View File

@ -0,0 +1,94 @@
package com.fastbee.pay.core.controller.admin.refund;
import cn.hutool.core.collection.CollectionUtil;
import com.fastbee.common.core.domain.CommonResult;
import com.fastbee.common.core.domain.PageResult;
import com.fastbee.pay.core.controller.admin.refund.vo.*;
import com.fastbee.pay.core.convert.refund.PayRefundConvert;
import com.fastbee.pay.core.domain.dataobject.app.PayApp;
import com.fastbee.pay.core.domain.dataobject.refund.PayRefund;
import com.fastbee.pay.core.service.app.PayAppService;
import com.fastbee.pay.core.service.refund.PayRefundService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
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 com.fastbee.common.core.domain.CommonResult.success;
import static com.fastbee.common.utils.collection.CollectionUtils.convertList;
@Api(tags = "管理后台 - 退款订单")
@RestController
@RequestMapping("/pay/refund")
@Validated
public class PayRefundController {
@Resource
private PayRefundService refundService;
@Resource
private PayAppService appService;
@GetMapping("/get")
@ApiOperation("获得退款订单")
@ApiParam(name = "id", value = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pay:refund:query')")
public CommonResult<PayRefundDetailsRespVO> getRefund(@RequestParam("id") Long id) {
PayRefund refund = refundService.getRefund(id);
if (refund == null) {
return success(new PayRefundDetailsRespVO());
}
// 拼接数据
PayApp app = appService.getApp(refund.getAppId());
return success(PayRefundConvert.INSTANCE.convert(refund, app));
}
@GetMapping("/page")
@ApiModelProperty("获得退款订单分页")
@PreAuthorize("@ss.hasPermission('pay:refund:query')")
public CommonResult<PageResult<PayRefundPageItemRespVO>> getRefundPage(@Valid PayRefundPageReqVO pageVO) {
PageResult<PayRefund> pageResult = refundService.getRefundPage(pageVO);
if (CollectionUtil.isEmpty(pageResult.getList())) {
return success(new PageResult<>(pageResult.getTotal()));
}
// 处理应用ID数据
Map<Long, PayApp> appMap = appService.getAppMap(convertList(pageResult.getList(), PayRefund::getAppId));
return success(PayRefundConvert.INSTANCE.convertPage(pageResult, appMap));
}
@GetMapping("/export-excel")
@ApiModelProperty("导出退款订单 Excel")
@PreAuthorize("@ss.hasPermission('pay:refund:export')")
// @OperateLog(type = EXPORT)
public void exportRefundExcel(@Valid PayRefundExportReqVO exportReqVO,
HttpServletResponse response) throws IOException {
List<PayRefund> list = refundService.getRefundList(exportReqVO);
if (CollectionUtil.isEmpty(list)) {
// ExcelUtils.write(response, "退款订单.xls", "数据",
// PayRefundExcelVO.class, new ArrayList<>());
return;
}
// 拼接返回
Map<Long, PayApp> appMap = appService.getAppMap(convertList(list, PayRefund::getAppId));
List<PayRefundExcelVO> excelList = PayRefundConvert.INSTANCE.convertList(list, appMap);
// 导出 Excel
// ExcelUtils.write(response, "退款订单.xls", "数据", PayRefundExcelVO.class, excelList);
}
}

View File

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

View File

@ -0,0 +1,41 @@
package com.fastbee.pay.core.controller.admin.refund.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.time.LocalDateTime;
@ApiModel(description = "管理后台 - 退款订单详情 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class PayRefundDetailsRespVO extends PayRefundBaseVO {
@ApiModelProperty(value = "支付退款编号", required = true)
private Long id;
@ApiModelProperty(value = "应用名称", required = true, example = "我是fastbee")
private String appName;
@ApiModelProperty(value = "支付订单", required = true)
private Order order;
@ApiModelProperty(value = "创建时间")
private LocalDateTime createTime;
@ApiModelProperty(value = "更新时间")
private LocalDateTime updateTime;
@ApiModel(value = "管理后台 - 支付订单")
@Data
public static class Order {
@ApiModelProperty(value = "商品标题", required = true, example = "土豆")
private String subject;
}
}

View File

@ -0,0 +1,59 @@
package com.fastbee.pay.core.controller.admin.refund.vo;
import com.alibaba.excel.annotation.ExcelProperty;
import com.fastbee.common.annotation.DictFormat;
import com.fastbee.pay.api.enums.DictTypeConstants;
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 = "支付金额")
private Integer payPrice;
@ExcelProperty(value = "退款金额")
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 = "退款状态")
// @DictFormat(DictTypeConstants.REFUND_STATUS)
private Integer status;
@ExcelProperty(value = "退款渠道")
// @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,42 @@
package com.fastbee.pay.core.controller.admin.refund.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static com.fastbee.common.utils.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@ApiModel(description = "管理后台 - 退款订单 Excel 导出 Request VO参数和 PayRefundPageReqVO 是一致的")
@Data
public class PayRefundExportReqVO {
@ApiModelProperty(value = "应用编号", example = "1024")
private Long appId;
@ApiModelProperty(value = "渠道编码", example = "wx_app")
private String channelCode;
@ApiModelProperty(value = "商户支付单号", example = "10")
private String merchantOrderId;
@ApiModelProperty(value = "商户退款单号", example = "20")
private String merchantRefundId;
@ApiModelProperty(value = "渠道支付单号", example = "30")
private String channelOrderNo;
@ApiModelProperty(value = "渠道退款单号", example = "40")
private String channelRefundNo;
@ApiModelProperty(value = "退款状态", example = "0")
private Integer status;
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@ApiModelProperty(value = "创建时间")
private LocalDateTime[] createTime;
}

View File

@ -0,0 +1,27 @@
package com.fastbee.pay.core.controller.admin.refund.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.time.LocalDateTime;
@ApiModel(description = "管理后台 - 退款订单分页查询 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class PayRefundPageItemRespVO extends PayRefundBaseVO {
@ApiModelProperty(value = "支付订单编号", required = true, example = "1024")
private Long id;
@ApiModelProperty(value = "应用名称", required = true, example = "我是fastbee")
private String appName;
@ApiModelProperty(value = "创建时间", required = true)
private LocalDateTime createTime;
}

View File

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

View File

@ -0,0 +1,40 @@
package com.fastbee.pay.core.controller.app.channel;
import com.fastbee.common.core.domain.CommonResult;
import com.fastbee.pay.core.domain.dataobject.channel.PayChannel;
import com.fastbee.pay.core.service.channel.PayChannelService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
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 com.fastbee.common.core.domain.CommonResult.success;
import static com.fastbee.common.utils.collection.CollectionUtils.convertSet;
@Api(tags = "用户 App - 支付渠道")
@RestController
@RequestMapping("/pay/channel")
@Validated
public class AppPayChannelController {
@Resource
private PayChannelService channelService;
@GetMapping("/get-enable-code-list")
@ApiOperation("获得指定应用的开启的支付渠道编码列表")
@ApiParam(name = "appId", value = "应用编号", required = true, example = "1")
public CommonResult<Set<String>> getEnableChannelCodeList(@RequestParam("appId") Long appId) {
List<PayChannel> channels = channelService.getEnableChannelList(appId);
return success(convertSet(channels, PayChannel::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: {{appTenentId}}
{
"id": 174,
"channelCode": "alipay_pc"
}
### /pay/create 提交支付订单【wx_bar】
POST {{appApi}}/pay/order/submit
Content-Type: application/json
Authorization: Bearer {{appToken}}
tenant-id: {{appTenentId}}
{
"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: {{appTenentId}}
{
"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: {{appTenentId}}
{
"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: {{appTenentId}}
{
"id": 202,
"channelCode": "wx_native"
}

View File

@ -0,0 +1,48 @@
package com.fastbee.pay.core.controller.app.order;
import com.fastbee.common.core.domain.CommonResult;
import com.fastbee.pay.core.controller.admin.order.vo.PayOrderRespVO;
import com.fastbee.pay.core.controller.admin.order.vo.PayOrderSubmitRespVO;
import com.fastbee.pay.core.controller.app.order.vo.AppPayOrderSubmitReqVO;
import com.fastbee.pay.core.controller.app.order.vo.AppPayOrderSubmitRespVO;
import com.fastbee.pay.core.convert.order.PayOrderConvert;
import com.fastbee.pay.core.service.order.PayOrderService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import static com.fastbee.common.core.domain.CommonResult.success;
import static com.fastbee.common.utils.ServletUtils.getClientIP;
@Api(tags = "用户 APP - 支付订单")
@RestController
@RequestMapping("/pay/order")
@Validated
@Slf4j
public class AppPayOrderController {
@Resource
private PayOrderService payOrderService;
// TODO fastbee临时 demo技术打样。
@GetMapping("/get")
@ApiOperation("获得支付订单")
@ApiParam(name = "id", value = "编号", required = true, example = "1024")
public CommonResult<PayOrderRespVO> getOrder(@RequestParam("id") Long id) {
return success(PayOrderConvert.INSTANCE.convert(payOrderService.getOrder(id)));
}
@PostMapping("/submit")
@ApiOperation("提交支付订单")
public CommonResult<AppPayOrderSubmitRespVO> submitPayOrder(@RequestBody AppPayOrderSubmitReqVO reqVO) {
PayOrderSubmitRespVO respVO = payOrderService.submitOrder(reqVO, getClientIP());
return success(PayOrderConvert.INSTANCE.convert3(respVO));
}
}

View File

@ -0,0 +1,10 @@
package com.fastbee.pay.core.controller.app.order.vo;
import com.fastbee.pay.core.controller.admin.order.vo.PayOrderSubmitReqVO;
import io.swagger.annotations.ApiModel;
import lombok.Data;
@ApiModel(description = "用户 APP - 支付订单提交 Request VO")
@Data
public class AppPayOrderSubmitReqVO extends PayOrderSubmitReqVO {
}

View File

@ -0,0 +1,11 @@
package com.fastbee.pay.core.controller.app.order.vo;
import com.fastbee.pay.core.controller.admin.order.vo.PayOrderSubmitRespVO;
import io.swagger.annotations.ApiModel;
import lombok.Data;
@ApiModel(description = "用户 APP - 支付订单提交 Response VO")
@Data
public class AppPayOrderSubmitRespVO extends PayOrderSubmitRespVO {
}

View File

@ -0,0 +1,4 @@
/**
* TODO fastbee占个位置没啥用
*/
package com.fastbee.pay.core.controller.app.refund;

View File

@ -0,0 +1,6 @@
/**
* 提供 RESTful API 给前端:
* 1. admin 包:提供给管理后台 yudao-ui-admin 前端项目
* 2. app 包:提供给用户 APP yudao-ui-app 前端项目,它的 Controller 和 VO 都要添加 App 前缀,用于和管理后台进行区分
*/
package com.fastbee.pay.core.controller;

View File

@ -0,0 +1,48 @@
package com.fastbee.pay.core.convert.app;
import com.fastbee.common.core.domain.PageResult;
import com.fastbee.common.utils.collection.CollectionUtils;
import com.fastbee.pay.core.controller.admin.app.vo.PayAppCreateReqVO;
import com.fastbee.pay.core.controller.admin.app.vo.PayAppPageItemRespVO;
import com.fastbee.pay.core.controller.admin.app.vo.PayAppRespVO;
import com.fastbee.pay.core.controller.admin.app.vo.PayAppUpdateReqVO;
import com.fastbee.pay.core.domain.dataobject.app.PayApp;
import com.fastbee.pay.core.domain.dataobject.channel.PayChannel;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* 支付应用信息 Convert
*
* @author fastbee
*/
@Mapper
public interface PayAppConvert {
PayAppConvert INSTANCE = Mappers.getMapper(PayAppConvert.class);
PayAppPageItemRespVO pageConvert (PayApp bean);
PayApp convert(PayAppCreateReqVO bean);
PayApp convert(PayAppUpdateReqVO bean);
PayAppRespVO convert(PayApp bean);
List<PayAppRespVO> convertList(List<PayApp> list);
PageResult<PayAppPageItemRespVO> convertPage(PageResult<PayApp> page);
default PageResult<PayAppPageItemRespVO> convertPage(PageResult<PayApp> pageResult, List<PayChannel> channels) {
PageResult<PayAppPageItemRespVO> voPageResult = convertPage(pageResult);
// 处理 channel 关系
Map<Long, Set<String>> appIdChannelMap = CollectionUtils.convertMultiMap2(channels, PayChannel::getAppId, PayChannel::getCode);
voPageResult.getList().forEach(app -> app.setChannelCodes(appIdChannelMap.get(app.getId())));
return voPageResult;
}
}

View File

@ -0,0 +1,28 @@
package com.fastbee.pay.core.convert.channel;
import com.fastbee.common.core.domain.PageResult;
import com.fastbee.pay.core.controller.admin.channel.vo.PayChannelCreateReqVO;
import com.fastbee.pay.core.controller.admin.channel.vo.PayChannelRespVO;
import com.fastbee.pay.core.controller.admin.channel.vo.PayChannelUpdateReqVO;
import com.fastbee.pay.core.domain.dataobject.channel.PayChannel;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
@Mapper
public interface PayChannelConvert {
PayChannelConvert INSTANCE = Mappers.getMapper(PayChannelConvert.class);
@Mapping(target = "config",ignore = true)
PayChannel convert(PayChannelCreateReqVO bean);
@Mapping(target = "config",ignore = true)
PayChannel convert(PayChannelUpdateReqVO bean);
// @Mapping(target = "config",expression = "java(cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString(bean.getConfig()))")
// PayChannelRespVO convert(PayChannel bean);
// PageResult<PayChannelRespVO> convertPage(PageResult<PayChannel> page);
}

View File

@ -0,0 +1,26 @@
package com.fastbee.pay.core.convert.demo;
import com.fastbee.common.core.domain.PageResult;
import com.fastbee.pay.core.controller.admin.demo.vo.OrderInfoCreateReqVO;
import com.fastbee.pay.core.controller.admin.demo.vo.OrderInfoRespVO;
import com.fastbee.pay.core.domain.dataobject.demo.OrderInfo;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/**
* 示例订单 Convert
*
* @author fastbee
*/
@Mapper
public interface OrderInfoConvert {
OrderInfoConvert INSTANCE = Mappers.getMapper(OrderInfoConvert.class);
OrderInfo convert(OrderInfoCreateReqVO bean);
OrderInfoRespVO convert(OrderInfo bean);
PageResult<OrderInfoRespVO> convertPage(PageResult<OrderInfo> page);
}

View File

@ -0,0 +1,43 @@
package com.fastbee.pay.core.convert.notify;
import com.fastbee.common.core.domain.PageResult;
import com.fastbee.common.utils.MapUtils;
import com.fastbee.pay.core.controller.admin.notify.vo.PayNotifyTaskDetailRespVO;
import com.fastbee.pay.core.controller.admin.notify.vo.PayNotifyTaskRespVO;
import com.fastbee.pay.core.domain.dataobject.app.PayApp;
import com.fastbee.pay.core.domain.dataobject.notify.PayNotifyLog;
import com.fastbee.pay.core.domain.dataobject.notify.PayNotifyTask;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
import java.util.Map;
/**
* 支付通知 Convert
*
* @author fastbee
*/
@Mapper
public interface PayNotifyTaskConvert {
PayNotifyTaskConvert INSTANCE = Mappers.getMapper(PayNotifyTaskConvert.class);
PayNotifyTaskRespVO convert(PayNotifyTask bean);
default PageResult<PayNotifyTaskRespVO> convertPage(PageResult<PayNotifyTask> page, Map<Long, PayApp> appMap){
PageResult<PayNotifyTaskRespVO> result = convertPage(page);
result.getList().forEach(order -> MapUtils.findAndThen(appMap, order.getAppId(), app -> order.setAppName(app.getName())));
return result;
}
PageResult<PayNotifyTaskRespVO> convertPage(PageResult<PayNotifyTask> page);
default PayNotifyTaskDetailRespVO convert(PayNotifyTask task, PayApp app, List<PayNotifyLog> logs) {
PayNotifyTaskDetailRespVO respVO = convert(task, logs);
if (app != null) {
respVO.setAppName(app.getName());
}
return respVO;
}
PayNotifyTaskDetailRespVO convert(PayNotifyTask task, List<PayNotifyLog> logs);
}

View File

@ -0,0 +1,79 @@
package com.fastbee.pay.core.convert.order;
import com.fastbee.common.core.domain.PageResult;
import com.fastbee.common.utils.MapUtils;
import com.fastbee.common.utils.collection.CollectionUtils;
import com.fastbee.pay.api.api.order.dto.PayOrderCreateReqDTO;
import com.fastbee.pay.api.api.order.dto.PayOrderRespDTO;
import com.fastbee.pay.core.controller.admin.order.vo.PayOrderDetailsRespVO;
import com.fastbee.pay.core.controller.admin.order.vo.PayOrderExcelVO;
import com.fastbee.pay.core.controller.admin.order.vo.PayOrderPageItemRespVO;
import com.fastbee.pay.core.controller.admin.order.vo.PayOrderRespVO;
import com.fastbee.pay.core.controller.admin.order.vo.PayOrderSubmitReqVO;
import com.fastbee.pay.core.controller.admin.order.vo.PayOrderSubmitRespVO;
import com.fastbee.pay.core.controller.app.order.vo.AppPayOrderSubmitRespVO;
import com.fastbee.pay.core.domain.dataobject.app.PayApp;
import com.fastbee.pay.core.domain.dataobject.order.PayOrder;
import com.fastbee.pay.core.domain.dataobject.order.PayOrderExtension;
import com.fastbee.pay.framework.client.dto.order.PayOrderUnifiedReqDTO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
import java.util.List;
import java.util.Map;
/**
* 支付订单 Convert
*
* @author aquan
*/
@Mapper
public interface PayOrderConvert {
PayOrderConvert INSTANCE = Mappers.getMapper(PayOrderConvert.class);
PayOrderRespVO convert(PayOrder bean);
PayOrderRespDTO convert2(PayOrder order);
default PayOrderDetailsRespVO convert(PayOrder order, PayOrderExtension orderExtension, PayApp app) {
PayOrderDetailsRespVO respVO = convertDetail(order);
respVO.setExtension(convert(orderExtension));
if (app != null) {
respVO.setAppName(app.getName());
}
return respVO;
}
PayOrderDetailsRespVO convertDetail(PayOrder bean);
PayOrderDetailsRespVO.PayOrderExtension convert(PayOrderExtension bean);
default PageResult<PayOrderPageItemRespVO> convertPage(PageResult<PayOrder> page, Map<Long, PayApp> appMap) {
PageResult<PayOrderPageItemRespVO> result = convertPage(page);
result.getList().forEach(order -> MapUtils.findAndThen(appMap, order.getAppId(), app -> order.setAppName(app.getName())));
return result;
}
PageResult<PayOrderPageItemRespVO> convertPage(PageResult<PayOrder> page);
default List<PayOrderExcelVO> convertList(List<PayOrder> list, Map<Long, PayApp> appMap) {
return CollectionUtils.convertList(list, order -> {
PayOrderExcelVO excelVO = convertExcel(order);
MapUtils.findAndThen(appMap, order.getAppId(), app -> excelVO.setAppName(app.getName()));
return excelVO;
});
}
PayOrderExcelVO convertExcel(PayOrder bean);
PayOrder convert(PayOrderCreateReqDTO bean);
@Mapping(target = "id", ignore = true)
PayOrderExtension convert(PayOrderSubmitReqVO bean, String userIp);
PayOrderUnifiedReqDTO convert2(PayOrderSubmitReqVO reqVO, String userIp);
@Mapping(source = "order.status", target = "status")
PayOrderSubmitRespVO convert(PayOrder order, com.fastbee.pay.framework.client.dto.order.PayOrderRespDTO respDTO);
AppPayOrderSubmitRespVO convert3(PayOrderSubmitRespVO bean);
}

View File

@ -0,0 +1,5 @@
/**
* 提供 POJO 类的实体转换
* 目前使用 MapStruct 框架
*/
package com.fastbee.pay.core.convert;

View File

@ -0,0 +1,56 @@
package com.fastbee.pay.core.convert.refund;
import com.fastbee.common.core.domain.PageResult;
import com.fastbee.common.utils.MapUtils;
import com.fastbee.common.utils.collection.CollectionUtils;
import com.fastbee.pay.api.api.refund.dto.PayRefundCreateReqDTO;
import com.fastbee.pay.api.api.refund.dto.PayRefundRespDTO;
import com.fastbee.pay.core.controller.admin.refund.vo.PayRefundDetailsRespVO;
import com.fastbee.pay.core.controller.admin.refund.vo.PayRefundExcelVO;
import com.fastbee.pay.core.controller.admin.refund.vo.PayRefundPageItemRespVO;
import com.fastbee.pay.core.domain.dataobject.app.PayApp;
import com.fastbee.pay.core.domain.dataobject.order.PayOrder;
import com.fastbee.pay.core.domain.dataobject.refund.PayRefund;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
import java.util.Map;
@Mapper
public interface PayRefundConvert {
PayRefundConvert INSTANCE = Mappers.getMapper(PayRefundConvert.class);
default PayRefundDetailsRespVO convert(PayRefund refund, PayApp app) {
PayRefundDetailsRespVO respVO = convert(refund);
if (app != null) {
respVO.setAppName(app.getName());
}
return respVO;
}
PayRefundDetailsRespVO convert(PayRefund bean);
PayRefundDetailsRespVO.Order convert(PayOrder bean);
default PageResult<PayRefundPageItemRespVO> convertPage(PageResult<PayRefund> page, Map<Long, PayApp> appMap) {
PageResult<PayRefundPageItemRespVO> result = convertPage(page);
result.getList().forEach(order -> MapUtils.findAndThen(appMap, order.getAppId(), app -> order.setAppName(app.getName())));
return result;
}
PageResult<PayRefundPageItemRespVO> convertPage(PageResult<PayRefund> page);
PayRefund convert(PayRefundCreateReqDTO bean);
PayRefundRespDTO convert02(PayRefund bean);
default List<PayRefundExcelVO> convertList(List<PayRefund> list, Map<Long, PayApp> appMap) {
return CollectionUtils.convertList(list, order -> {
PayRefundExcelVO excelVO = convertExcel(order);
MapUtils.findAndThen(appMap, order.getAppId(), app -> excelVO.setAppName(app.getName()));
return excelVO;
});
}
PayRefundExcelVO convertExcel(PayRefund bean);
}

View File

@ -0,0 +1 @@
<http://www.iocoder.cn/Spring-Boot/MapStruct/>

View File

@ -0,0 +1,56 @@
package com.fastbee.pay.core.domain.dataobject.app;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fastbee.common.core.domain.BaseDO;
import com.fastbee.common.enums.CommonStatusEnum;
import lombok.*;
import lombok.experimental.Accessors;
/**
* 支付应用 DO
* 一个商户下,可能会有多个支付应用。例如说,京东有京东商城、京东到家等等
* 不过一般来说,一个商户,只有一个应用哈~
* 即 PayMerchantDO : PayApp = 1 : n
*
* @author fastbee
*/
@TableName("pay_app")
//@KeySequence("pay_app_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class PayApp extends BaseDO {
/**
* 应用编号,数据库自增
*/
@TableId
private Long id;
/**
* 应用名
*/
private String name;
/**
* 状态
* 枚举 {@link CommonStatusEnum}
*/
private Integer status;
/**
* 备注
*/
private String remark;
/**
* 支付结果的回调地址
*/
private String orderNotifyUrl;
/**
* 退款结果的回调地址
*/
private String refundNotifyUrl;
}

View File

@ -0,0 +1,70 @@
package com.fastbee.pay.core.domain.dataobject.channel;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import com.fastbee.common.core.domain.TenantBaseDO;
import com.fastbee.common.enums.CommonStatusEnum;
import com.fastbee.pay.core.domain.dataobject.app.PayApp;
import com.fastbee.pay.framework.client.PayClientConfig;
import com.fastbee.pay.framework.enums.channel.PayChannelEnum;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.experimental.Accessors;
/**
* 支付渠道 DO
* 一个应用下,会有多种支付渠道,例如说微信支付、支付宝支付等等
* 即 PayApp : PayChannel = 1 : n
*
* @author fastbee
*/
@TableName(value = "pay_channel", autoResultMap = true)
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class PayChannel extends TenantBaseDO {
/**
* 渠道编号,数据库自增
*/
private Long id;
/**
* 渠道编码
* 枚举 {@link PayChannelEnum}
*/
private String code;
/**
* 状态
* 枚举 {@link CommonStatusEnum}
*/
private Integer status;
/**
* 渠道费率,单位:百分比
*/
private Double feeRate;
/**
* 备注
*/
private String remark;
/**
* 应用编号
* 关联 {@link PayApp#getId()}
*/
private Long appId;
/**
* 支付渠道配置
*/
@TableField(typeHandler = JacksonTypeHandler.class)
private PayClientConfig config;
}

View File

@ -0,0 +1,87 @@
package com.fastbee.pay.core.domain.dataobject.demo;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fastbee.common.core.domain.BaseDO;
import lombok.*;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
/**
* 订单信息, 实际根据自己的业务系统设计
* 用id关联 pay 系统的支付与退款
*
* @author fastbee
*/
@TableName("order_info")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class OrderInfo extends BaseDO {
/**
* 订单编号,自增
*/
@TableId
private Long id;
/**
* 用户编号
*/
private Long userId;
/**
* 商户显示订单编号
*/
private String orderNo;
/**
* 商品编号
*/
private Long spuId;
/**
* 商品名称
*/
private String spuName;
/**
* 价格,单位:分
*/
private Integer price;
// ========== 支付相关字段 ==========
/**
* 是否支付
*/
private Boolean payStatus;
/**
* 支付订单编号
* 对接 pay-module-biz 支付服务的支付订单编号,即 PayOrder 的 id 编号
*/
private Long payOrderId;
/**
* 付款时间
*/
private LocalDateTime payTime;
/**
* 支付渠道
* 对应 PayChannelEnum 枚举
*/
private String payChannelCode;
// ========== 退款相关字段 ==========
/**
* 支付退款单号
*/
private Long payRefundId;
/**
* 退款金额,单位:分
*/
private Integer refundPrice;
/**
* 退款完成时间
*/
private LocalDateTime refundTime;
}

View File

@ -0,0 +1,48 @@
package com.fastbee.pay.core.domain.dataobject.notify;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fastbee.common.core.domain.BaseDO;
import com.fastbee.pay.api.enums.notify.PayNotifyStatusEnum;
import lombok.*;
/**
* 商户支付、退款等的通知 Log
* 每次通知时,都会在该表中,记录一次 Log方便排查问题
*
* @author fastbee
*/
@TableName("pay_notify_log")
@KeySequence("pay_notify_log_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PayNotifyLog extends BaseDO {
/**
* 日志编号,自增
*/
private Long id;
/**
* 通知任务编号
* 关联 {@link PayNotifyTask#getId()}
*/
private Long taskId;
/**
* 第几次被通知
* 对应到 {@link PayNotifyTask#getNotifyTimes()}
*/
private Integer notifyTimes;
/**
* HTTP 响应结果
*/
private String response;
/**
* 支付通知状态
* 外键 {@link PayNotifyStatusEnum}
*/
private Integer status;
}

View File

@ -0,0 +1,92 @@
package com.fastbee.pay.core.domain.dataobject.notify;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fastbee.common.core.domain.TenantBaseDO;
import com.fastbee.pay.api.enums.notify.PayNotifyStatusEnum;
import com.fastbee.pay.api.enums.notify.PayNotifyTypeEnum;
import com.fastbee.pay.core.domain.dataobject.app.PayApp;
import com.fastbee.pay.core.domain.dataobject.order.PayOrder;
import com.fastbee.pay.core.domain.dataobject.refund.PayRefund;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
/**
* 支付通知
* 在支付系统收到支付渠道的支付、退款的结果后,需要不断的通知到业务系统,直到成功。
*
* @author fastbee
*/
@TableName("pay_notify_task")
@KeySequence("pay_notify_task_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
public class PayNotifyTask extends TenantBaseDO {
/**
* 通知频率,单位为秒。
*
* 算上首次的通知,实际是一共 1 + 8 = 9 次。
*/
public static final Integer[] NOTIFY_FREQUENCY = new Integer[]{
15, 15, 30, 180,
1800, 1800, 1800, 3600
};
/**
* 编号,自增
*/
@TableId
private Long id;
/**
* 应用编号
* 关联 {@link PayApp#getId()}
*/
private Long appId;
/**
* 通知类型
* 外键 {@link PayNotifyTypeEnum}
*/
private Integer type;
/**
* 数据编号,根据不同 type 进行关联:
* 1. {@link PayNotifyTypeEnum#ORDER} 时,关联 {@link PayOrder#getId()}
* 2. {@link PayNotifyTypeEnum#REFUND} 时,关联 {@link PayRefund#getId()}
*/
private Long dataId;
/**
* 商户订单编号
*/
private String merchantOrderId;
/**
* 通知状态
* 外键 {@link PayNotifyStatusEnum}
*/
private Integer status;
/**
* 下一次通知时间
*/
private LocalDateTime nextNotifyTime;
/**
* 最后一次执行时间
*/
private LocalDateTime lastExecuteTime;
/**
* 当前通知次数
*/
private Integer notifyTimes;
/**
* 最大可通知次数
*/
private Integer maxNotifyTimes;
/**
* 通知地址
*/
private String notifyUrl;
}

View File

@ -0,0 +1,134 @@
package com.fastbee.pay.core.domain.dataobject.order;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fastbee.common.core.domain.BaseDO;
import com.fastbee.pay.api.enums.order.PayOrderStatusEnum;
import com.fastbee.pay.core.domain.dataobject.app.PayApp;
import com.fastbee.pay.core.domain.dataobject.channel.PayChannel;
import com.fastbee.pay.framework.enums.channel.PayChannelEnum;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
/**
* 支付订单 DO
*
* @author fastbee
*/
@TableName("pay_order")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class PayOrder extends BaseDO {
/**
* 订单编号,数据库自增
*/
private Long id;
/**
* 应用编号
* 关联 {@link PayApp#getId()}
*/
private Long appId;
/**
* 渠道编号
* 关联 {@link PayChannel#getId()}
*/
private Long channelId;
/**
* 渠道编码
* 枚举 {@link PayChannelEnum}
*/
private String channelCode;
// ========== 商户相关字段 ==========
/**
* 商户订单编号
* 例如说,内部系统 A 的订单号,需要保证每个 PayApp 唯一
*/
private String merchantOrderId;
/**
* 商品标题
*/
private String subject;
/**
* 商品描述信息
*/
private String body;
/**
* 异步通知地址
*/
private String notifyUrl;
// ========== 订单相关字段 ==========
/**
* 支付金额,单位:分
*/
private Integer price;
/**
* 渠道手续费,单位:百分比
* 冗余 {@link PayChannel#getFeeRate()}
*/
private Double channelFeeRate;
/**
* 渠道手续金额,单位:分
*/
private Integer channelFeePrice;
/**
* 支付状态
* 枚举 {@link PayOrderStatusEnum}
*/
private Integer status;
/**
* 用户 IP
*/
private String userIp;
/**
* 订单失效时间
*/
private LocalDateTime expireTime;
/**
* 订单支付成功时间
*/
private LocalDateTime successTime;
/**
* 支付成功的订单拓展单编号
* 关联 {@link PayOrderExtension#getId()}
*/
private Long extensionId;
/**
* 支付成功的外部订单号
* 关联 {@link PayOrderExtension#getNo()}
*/
private String no;
// ========== 退款相关字段 ==========
/**
* 退款总金额,单位:分
*/
private Integer refundPrice;
// ========== 渠道相关字段 ==========
/**
* 渠道用户编号
* 例如说,微信 openid、支付宝账号
*/
private String channelUserId;
/**
* 渠道订单号
*/
private String channelOrderNo;
}

View File

@ -0,0 +1,93 @@
package com.fastbee.pay.core.domain.dataobject.order;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import com.fastbee.common.core.domain.BaseDO;
import com.fastbee.pay.api.api.order.dto.PayOrderRespDTO;
import com.fastbee.pay.api.enums.order.PayOrderStatusEnum;
import com.fastbee.pay.core.domain.dataobject.channel.PayChannel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.experimental.Accessors;
import java.util.Map;
/**
* 支付订单拓展 DO
* 每次调用支付渠道,都会生成一条对应记录
*
* @author fastbee
*/
@TableName(value = "pay_order_extension",autoResultMap = true)
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class PayOrderExtension extends BaseDO {
/**
* 订单拓展编号,数据库自增
*/
private Long id;
/**
* 外部订单号,根据规则生成
* 调用支付渠道时,使用该字段作为对接的订单号:
* 1. 微信支付:对应 <a href="https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_1.shtml">JSAPI 支付</a> 的 out_trade_no 字段
* 2. 支付宝支付:对应 <a href="https://opendocs.alipay.com/open/270/105898">电脑网站支付</a> 的 out_trade_no 字段
* 例如说P202110132239124200055
*/
private String no;
/**
* 订单号
* 关联 {@link PayOrder#getId()}
*/
private Long orderId;
/**
* 渠道编号
* 关联 {@link PayChannel#getId()}
*/
private Long channelId;
/**
* 渠道编码
*/
private String channelCode;
/**
* 用户 IP
*/
private String userIp;
/**
* 支付状态
* 枚举 {@link PayOrderStatusEnum}
*/
private Integer status;
/**
* 支付渠道的额外参数
* 参见 <a href="https://www.pingxx.com/api/支付渠道%20extra%20参数说明.html">参数说明</>
*/
@TableField(typeHandler = JacksonTypeHandler.class)
private Map<String, String> channelExtras;
/**
* 调用渠道的错误码
*/
private String channelErrorCode;
/**
* 调用渠道报错时,错误信息
*/
private String channelErrorMsg;
/**
* 支付渠道的同步/异步通知的内容
* 对应 {@link PayOrderRespDTO#getRawData()}
*/
private String channelNotifyData;
}

View File

@ -0,0 +1,152 @@
package com.fastbee.pay.core.domain.dataobject.refund;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fastbee.common.core.domain.BaseDO;
import com.fastbee.pay.api.api.refund.dto.PayRefundRespDTO;
import com.fastbee.pay.api.enums.refund.PayRefundStatusEnum;
import com.fastbee.pay.core.domain.dataobject.app.PayApp;
import com.fastbee.pay.core.domain.dataobject.channel.PayChannel;
import com.fastbee.pay.core.domain.dataobject.order.PayOrder;
import com.fastbee.pay.framework.enums.channel.PayChannelEnum;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
/**
* 支付退款单 DO
* 一个支付订单,可以拥有多个支付退款单
* 即 PayOrder : PayRefund = 1 : n
*
* @author fastbee
*/
@TableName("pay_refund")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class PayRefund extends BaseDO {
/**
* 退款单编号,数据库自增
*/
@TableId
private Long id;
/**
* 外部退款号,根据规则生成
* 调用支付渠道时,使用该字段作为对接的退款号:
* 1. 微信退款:对应 <a href="https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=9_4">申请退款</a> 的 out_refund_no 字段
* 2. 支付宝退款:对应 <a href="https://opendocs.alipay.com/open/02e7go"统一收单交易退款接口></a> 的 out_request_no 字段
*/
private String no;
/**
* 应用编号
* 关联 {@link PayApp#getId()}
*/
private Long appId;
/**
* 渠道编号
* 关联 {@link PayChannel#getId()}
*/
private Long channelId;
/**
* 商户编码
* 枚举 {@link PayChannelEnum}
*/
private String channelCode;
/**
* 订单编号
* 关联 {@link PayOrder#getId()}
*/
private Long orderId;
/**
* 支付订单编号
* 冗余 {@link PayOrder#getNo()}
*/
private String orderNo;
// ========== 商户相关字段 ==========
/**
* 商户订单编号
* 例如说,内部系统 A 的订单号,需要保证每个 PayApp 唯一
*/
private String merchantOrderId;
/**
* 商户退款订单号
* 例如说,内部系统 A 的订单号,需要保证每个 PayApp 唯一
*/
private String merchantRefundId;
/**
* 异步通知地址
*/
private String notifyUrl;
// ========== 退款相关字段 ==========
/**
* 退款状态
* 枚举 {@link PayRefundStatusEnum}
*/
private Integer status;
/**
* 支付金额,单位:分
*/
private Integer payPrice;
/**
* 退款金额,单位:分
*/
private Integer refundPrice;
/**
* 退款原因
*/
private String reason;
/**
* 用户 IP
*/
private String userIp;
// ========== 渠道相关字段 ==========
/**
* 渠道订单号
* 冗余 {@link PayOrder#getChannelOrderNo()}
*/
private String channelOrderNo;
/**
* 渠道退款单号
* 1. 微信退款:对应 <a href="https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=9_4">申请退款</a> 的 refund_id 字段
* 2. 支付宝退款:没有字段
*/
private String channelRefundNo;
/**
* 退款成功时间
*/
private LocalDateTime successTime;
/**
* 调用渠道的错误码
*/
private String channelErrorCode;
/**
* 调用渠道的错误提示
*/
private String channelErrorMsg;
/**
* 支付渠道的同步/异步通知的内容
* 对应 {@link PayRefundRespDTO#getRawData()}
*/
private String channelNotifyData;
}

View File

@ -0,0 +1,11 @@
package com.fastbee.pay.core.domain.mapper.app;
import com.fastbee.framework.mybatis.mapper.BaseMapperX;
import com.fastbee.pay.core.domain.dataobject.app.PayApp;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface PayAppMapper extends BaseMapperX<PayApp> {
}

View File

@ -0,0 +1,10 @@
package com.fastbee.pay.core.domain.mapper.channel;
import com.fastbee.framework.mybatis.mapper.BaseMapperX;
import com.fastbee.pay.core.domain.dataobject.channel.PayChannel;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface PayChannelMapper extends BaseMapperX<PayChannel> {
}

View File

@ -0,0 +1,15 @@
package com.fastbee.pay.core.domain.mapper.demo;
import com.fastbee.framework.mybatis.mapper.BaseMapperX;
import com.fastbee.pay.core.domain.dataobject.demo.OrderInfo;
import org.apache.ibatis.annotations.Mapper;
/**
* 示例订单 Mapper
*
* @author fastbee
*/
@Mapper
public interface OrderInfoMapper extends BaseMapperX<OrderInfo> {
}

View File

@ -0,0 +1,10 @@
package com.fastbee.pay.core.domain.mapper.notify;
import com.fastbee.framework.mybatis.mapper.BaseMapperX;
import com.fastbee.pay.core.domain.dataobject.notify.PayNotifyLog;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface PayNotifyLogMapper extends BaseMapperX<PayNotifyLog> {
}

View File

@ -0,0 +1,10 @@
package com.fastbee.pay.core.domain.mapper.notify;
import com.fastbee.framework.mybatis.mapper.BaseMapperX;
import com.fastbee.pay.core.domain.dataobject.notify.PayNotifyTask;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface PayNotifyTaskMapper extends BaseMapperX<PayNotifyTask> {
}

View File

@ -0,0 +1,10 @@
package com.fastbee.pay.core.domain.mapper.order;
import com.fastbee.framework.mybatis.mapper.BaseMapperX;
import com.fastbee.pay.core.domain.dataobject.order.PayOrderExtension;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface PayOrderExtensionMapper extends BaseMapperX<PayOrderExtension> {
}

View File

@ -0,0 +1,10 @@
package com.fastbee.pay.core.domain.mapper.order;
import com.fastbee.framework.mybatis.mapper.BaseMapperX;
import com.fastbee.pay.core.domain.dataobject.order.PayOrder;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface PayOrderMapper extends BaseMapperX<PayOrder> {
}

View File

@ -0,0 +1,11 @@
package com.fastbee.pay.core.domain.mapper.refund;
import com.fastbee.framework.mybatis.mapper.BaseMapperX;
import com.fastbee.pay.core.domain.dataobject.refund.PayRefund;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface PayRefundMapper extends BaseMapperX<PayRefund> {
}

View File

@ -0,0 +1,27 @@
package com.fastbee.pay.core.domain.redis;
/**
* 支付 Redis Key 枚举类
*
* @author fastbee
*/
public interface RedisKeyConstants {
/**
* 通知任务的分布式锁
*
* KEY 格式pay_notify:lock:%d // 参数来自 DefaultLockKeyBuilder 类
* VALUE 数据格式HASH // RLock.classRedisson 的 Lock 锁,使用 Hash 数据结构
* 过期时间:不固定
*/
String PAY_NOTIFY_LOCK = "pay_notify:lock:%d";
/**
* 支付序号的缓存
*
* KEY 格式pay_no:{prefix}
* VALUE 数据格式:编号自增
*/
String PAY_NO = "pay_no";
}

View File

@ -0,0 +1,34 @@
package com.fastbee.pay.core.domain.redis.no;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
import java.time.LocalDateTime;
/**
* 支付序号的 Redis DAO
*
* @author fastbee
*/
@Repository
public class PayNoRedisDAO {
@Resource
private StringRedisTemplate stringRedisTemplate;
/**
* 生成序号
*
* @param prefix 前缀
* @return 序号
*/
public String generate(String prefix) {
String noPrefix = prefix + DateUtil.format(LocalDateTime.now(), DatePattern.PURE_DATETIME_PATTERN);
Long no = stringRedisTemplate.opsForValue().increment(noPrefix);
return noPrefix + no;
}
}

View File

@ -0,0 +1,33 @@
package com.fastbee.pay.core.domain.redis.notify;
import org.springframework.stereotype.Repository;
/**
* 支付通知的锁 Redis DAO
*
* @author fastbee
*/
@Repository
public class PayNotifyLockRedisDAO {
// @Resource
// private RedissonClient redissonClient;
//
// public void lock(Long id, Long timeoutMillis, Runnable runnable) {
// String lockKey = formatKey(id);
// RLock lock = redissonClient.getLock(lockKey);
// try {
// lock.lock(timeoutMillis, TimeUnit.MILLISECONDS);
// // 执行逻辑
// runnable.run();
// } finally {
// lock.unlock();
// }
// }
//
// private static String formatKey(Long id) {
// return String.format(PAY_NOTIFY_LOCK, id);
// }
}

View File

@ -0,0 +1,28 @@
package com.fastbee.pay.core.framework.job.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration(proxyBeanMethods = false)
public class PayJobConfiguration {
public static final String NOTIFY_THREAD_POOL_TASK_EXECUTOR = "NOTIFY_THREAD_POOL_TASK_EXECUTOR";
@Bean(NOTIFY_THREAD_POOL_TASK_EXECUTOR)
public ThreadPoolTaskExecutor notifyThreadPoolTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(8); // 设置核心线程数
executor.setMaxPoolSize(16); // 设置最大线程数
executor.setKeepAliveSeconds(60); // 设置空闲时间
executor.setQueueCapacity(100); // 设置队列大小
executor.setThreadNamePrefix("notify-task-"); // 配置线程池的前缀
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 进行加载
executor.initialize();
return executor;
}
}

View File

@ -0,0 +1,4 @@
/**
* 占位
*/
package com.fastbee.pay.core.framework.job.core;

View File

@ -0,0 +1,6 @@
/**
* 属于 pay 模块的 framework 封装
*
* @author fastbee
*/
package com.fastbee.pay.core.framework;

View File

@ -0,0 +1,9 @@
package com.fastbee.pay.core.framework.pay.config;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(PayProperties.class)
public class PayConfiguration {
}

View File

@ -0,0 +1,48 @@
package com.fastbee.pay.core.framework.pay.config;
import lombok.Data;
import org.hibernate.validator.constraints.URL;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.NotEmpty;
@ConfigurationProperties(prefix = "yudao.pay")
@Validated
@Data
public class PayProperties {
private static final String ORDER_NO_PREFIX = "P";
private static final String REFUND_NO_PREFIX = "R";
/**
* 支付回调地址
* 实际上,对应的 PayNotifyController 的 notifyOrder 方法的 URL
* 回调顺序:支付渠道(支付宝支付、微信支付) => yudao-module-pay 的 orderNotifyUrl 地址 => 业务的 PayApp.orderNotifyUrl 地址
*/
@NotEmpty(message = "支付回调地址不能为空")
@URL(message = "支付回调地址的格式必须是 URL")
private String orderNotifyUrl;
/**
* 退款回调地址
* 实际上,对应的 PayNotifyController 的 notifyRefund 方法的 URL
* 回调顺序:支付渠道(支付宝支付、微信支付) => yudao-module-pay 的 refundNotifyUrl 地址 => 业务的 PayApp.notifyRefundUrl 地址
*/
@NotEmpty(message = "支付回调地址不能为空")
@URL(message = "支付回调地址的格式必须是 URL")
private String refundNotifyUrl;
/**
* 支付订单 no 的前缀
*/
@NotEmpty(message = "支付订单 no 的前缀不能为空")
private String orderNoPrefix = ORDER_NO_PREFIX;
/**
* 退款订单 no 的前缀
*/
@NotEmpty(message = "退款订单 no 的前缀不能为空")
private String refundNoPrefix = REFUND_NO_PREFIX;
}

View File

@ -0,0 +1,4 @@
/**
* 占位,无实际作用
*/
package com.fastbee.pay.core.framework.pay.core;

View File

@ -0,0 +1,22 @@
package com.fastbee.pay.core.framework.web.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* pay 模块的 web 组件的 Configuration
*
* @author fastbee
*/
@Configuration(proxyBeanMethods = false)
public class PayWebConfiguration {
/**
* pay 模块的 API 分组
*/
// @Bean
// public GroupedOpenApi payGroupedOpenApi() {
// return YudaoSwaggerAutoConfiguration.buildGroupedOpenApi("pay");
// }
}

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