第一次提交
This commit is contained in:
68
fastbee-pay/fastbee-pay-core/pom.xml
Normal file
68
fastbee-pay/fastbee-pay-core/pom.xml
Normal 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>
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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 {
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
@ -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"
|
||||
}
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
@ -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 {
|
||||
}
|
@ -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 {
|
||||
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
/**
|
||||
* TODO fastbee:占个位置,没啥用
|
||||
*/
|
||||
package com.fastbee.pay.core.controller.app.refund;
|
@ -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;
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
/**
|
||||
* 提供 POJO 类的实体转换
|
||||
* 目前使用 MapStruct 框架
|
||||
*/
|
||||
package com.fastbee.pay.core.convert;
|
@ -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);
|
||||
|
||||
}
|
@ -0,0 +1 @@
|
||||
<http://www.iocoder.cn/Spring-Boot/MapStruct/>
|
@ -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;
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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> {
|
||||
|
||||
|
||||
}
|
@ -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> {
|
||||
|
||||
}
|
@ -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> {
|
||||
|
||||
}
|
@ -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> {
|
||||
|
||||
}
|
@ -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> {
|
||||
|
||||
}
|
@ -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> {
|
||||
|
||||
}
|
@ -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> {
|
||||
|
||||
}
|
@ -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> {
|
||||
|
||||
|
||||
}
|
@ -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.class:Redisson 的 Lock 锁,使用 Hash 数据结构
|
||||
* 过期时间:不固定
|
||||
*/
|
||||
String PAY_NOTIFY_LOCK = "pay_notify:lock:%d";
|
||||
|
||||
/**
|
||||
* 支付序号的缓存
|
||||
*
|
||||
* KEY 格式:pay_no:{prefix}
|
||||
* VALUE 数据格式:编号自增
|
||||
*/
|
||||
String PAY_NO = "pay_no";
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
// }
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
/**
|
||||
* 占位
|
||||
*/
|
||||
package com.fastbee.pay.core.framework.job.core;
|
@ -0,0 +1,6 @@
|
||||
/**
|
||||
* 属于 pay 模块的 framework 封装
|
||||
*
|
||||
* @author fastbee
|
||||
*/
|
||||
package com.fastbee.pay.core.framework;
|
@ -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 {
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
/**
|
||||
* 占位,无实际作用
|
||||
*/
|
||||
package com.fastbee.pay.core.framework.pay.core;
|
@ -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");
|
||||
// }
|
||||
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
/**
|
||||
* pay 模块的 web 配置
|
||||
*/
|
||||
package com.fastbee.pay.core.framework.web;
|
@ -0,0 +1,19 @@
|
||||
package com.fastbee.pay.core.job.notify;
|
||||
|
||||
/**
|
||||
* 任务处理器
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface JobHandler {
|
||||
|
||||
/**
|
||||
* 执行任务
|
||||
*
|
||||
* @param param 参数
|
||||
* @return 结果
|
||||
* @throws Exception 异常
|
||||
*/
|
||||
String execute(String param) throws Exception;
|
||||
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package com.fastbee.pay.core.job.notify;
|
||||
|
||||
import com.fastbee.pay.core.service.notify.PayNotifyService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 支付通知 Job
|
||||
* 通过不断扫描待通知的 PayNotifyTask 记录,回调业务线的回调接口
|
||||
*
|
||||
* @author fastbee
|
||||
*/
|
||||
@Component
|
||||
//@TenantJob // 多租户
|
||||
@Slf4j
|
||||
public class PayNotifyJob implements JobHandler {
|
||||
|
||||
@Resource
|
||||
private PayNotifyService payNotifyService;
|
||||
|
||||
@Override
|
||||
public String execute(String param) throws Exception {
|
||||
int notifyCount = payNotifyService.executeNotify();
|
||||
return String.format("执行支付通知 %s 个", notifyCount);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package com.fastbee.pay.core.job.order;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.fastbee.pay.core.job.notify.JobHandler;
|
||||
import com.fastbee.pay.core.service.order.PayOrderService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 支付订单的过期 Job
|
||||
* 支付超过过期时间时,支付渠道是不会通知进行过期,所以需要定时进行过期关闭。
|
||||
*
|
||||
* @author fastbee
|
||||
*/
|
||||
@Component
|
||||
//@TenantJob
|
||||
public class PayOrderExpireJob implements JobHandler {
|
||||
|
||||
@Resource
|
||||
private PayOrderService orderService;
|
||||
|
||||
@Override
|
||||
public String execute(String param) {
|
||||
int count = orderService.expireOrder();
|
||||
return StrUtil.format("支付过期 {} 个", count);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package com.fastbee.pay.core.job.order;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.fastbee.pay.core.job.notify.JobHandler;
|
||||
import com.fastbee.pay.core.service.order.PayOrderService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 支付订单的同步 Job
|
||||
* 由于支付订单的状态,是由支付渠道异步通知进行同步,考虑到异步通知可能会失败(小概率),所以需要定时进行同步。
|
||||
*
|
||||
* @author fastbee
|
||||
*/
|
||||
@Component
|
||||
//@TenantJob
|
||||
public class PayOrderSyncJob implements JobHandler {
|
||||
|
||||
/**
|
||||
* 同步创建时间在 N 分钟之前的订单
|
||||
*
|
||||
* 为什么同步 10 分钟之前的订单?
|
||||
* 因为一个订单发起支付,到支付成功,大多数在 10 分钟内,需要保证轮询到。
|
||||
* 如果设置为 30、60 或者更大时间范围,会导致轮询的订单太多,影响性能。当然,你也可以根据自己的业务情况来处理。
|
||||
*/
|
||||
private static final Duration CREATE_TIME_DURATION_BEFORE = Duration.ofMinutes(10);
|
||||
|
||||
@Resource
|
||||
private PayOrderService orderService;
|
||||
|
||||
@Override
|
||||
public String execute(String param) {
|
||||
LocalDateTime minCreateTime = LocalDateTime.now().minus(CREATE_TIME_DURATION_BEFORE);
|
||||
int count = orderService.syncOrder(minCreateTime);
|
||||
return StrUtil.format("同步支付订单 {} 个", count);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package com.fastbee.pay.core.job.refund;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.fastbee.pay.core.job.notify.JobHandler;
|
||||
import com.fastbee.pay.core.service.refund.PayRefundService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 退款订单的同步 Job
|
||||
* 由于退款订单的状态,是由支付渠道异步通知进行同步,考虑到异步通知可能会失败(小概率),所以需要定时进行同步。
|
||||
*
|
||||
* @author fastbee
|
||||
*/
|
||||
@Component
|
||||
//@TenantJob
|
||||
public class PayRefundSyncJob implements JobHandler {
|
||||
|
||||
@Resource
|
||||
private PayRefundService refundService;
|
||||
|
||||
@Override
|
||||
public String execute(String param) {
|
||||
int count = refundService.syncRefund();
|
||||
return StrUtil.format("同步退款订单 {} 个", count);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
package com.fastbee.pay.core.service.app;
|
||||
|
||||
import com.fastbee.common.core.domain.PageResult;
|
||||
import com.fastbee.common.exception.ServiceException;
|
||||
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.PayAppPageReqVO;
|
||||
import com.fastbee.pay.core.controller.admin.app.vo.PayAppUpdateReqVO;
|
||||
import com.fastbee.pay.core.domain.dataobject.app.PayApp;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 支付应用 Service 接口
|
||||
*
|
||||
* @author fastbee
|
||||
*/
|
||||
public interface PayAppService {
|
||||
|
||||
/**
|
||||
* 创建支付应用
|
||||
*
|
||||
* @param createReqVO 创建
|
||||
* @return 编号
|
||||
*/
|
||||
Long createApp(@Valid PayAppCreateReqVO createReqVO);
|
||||
|
||||
/**
|
||||
* 更新支付应用
|
||||
*
|
||||
* @param updateReqVO 更新
|
||||
*/
|
||||
void updateApp(@Valid PayAppUpdateReqVO updateReqVO);
|
||||
|
||||
/**
|
||||
* 修改应用状态
|
||||
*
|
||||
* @param id 应用编号
|
||||
* @param status 状态
|
||||
*/
|
||||
void updateAppStatus(Long id, Integer status);
|
||||
|
||||
/**
|
||||
* 删除支付应用
|
||||
*
|
||||
* @param id 编号
|
||||
*/
|
||||
void deleteApp(Long id);
|
||||
|
||||
/**
|
||||
* 获得支付应用
|
||||
*
|
||||
* @param id 编号
|
||||
* @return 支付应用
|
||||
*/
|
||||
PayApp getApp(Long id);
|
||||
|
||||
/**
|
||||
* 获得支付应用列表
|
||||
*
|
||||
* @param ids 编号
|
||||
* @return 支付应用列表
|
||||
*/
|
||||
List<PayApp> getAppList(Collection<Long> ids);
|
||||
|
||||
/**
|
||||
* 获得支付应用列表
|
||||
*
|
||||
* @return 支付应用列表
|
||||
*/
|
||||
List<PayApp> getAppList();
|
||||
|
||||
/**
|
||||
* 获得支付应用分页
|
||||
*
|
||||
* @param pageReqVO 分页查询
|
||||
* @return 支付应用分页
|
||||
*/
|
||||
PageResult<PayApp> getAppPage(PayAppPageReqVO pageReqVO);
|
||||
|
||||
/**
|
||||
* 获得指定编号的商户 Map
|
||||
*
|
||||
* @param ids 应用编号集合
|
||||
* @return 商户 Map
|
||||
*/
|
||||
default Map<Long, PayApp> getAppMap(Collection<Long> ids) {
|
||||
List<PayApp> list = getAppList(ids);
|
||||
return CollectionUtils.convertMap(list, PayApp::getId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 支付应用的合法性
|
||||
*
|
||||
* 如果不合法,抛出 {@link ServiceException} 业务异常
|
||||
*
|
||||
* @param id 应用编号
|
||||
* @return 应用
|
||||
*/
|
||||
PayApp validPayApp(Long id);
|
||||
|
||||
}
|
@ -0,0 +1,131 @@
|
||||
package com.fastbee.pay.core.service.app;
|
||||
|
||||
import com.fastbee.common.core.domain.PageResult;
|
||||
import com.fastbee.common.enums.CommonStatusEnum;
|
||||
import com.fastbee.framework.mybatis.LambdaQueryWrapperX;
|
||||
import com.fastbee.pay.api.enums.ErrorCodeConstants;
|
||||
import com.fastbee.pay.core.controller.admin.app.vo.PayAppCreateReqVO;
|
||||
import com.fastbee.pay.core.controller.admin.app.vo.PayAppPageReqVO;
|
||||
import com.fastbee.pay.core.controller.admin.app.vo.PayAppUpdateReqVO;
|
||||
import com.fastbee.pay.core.convert.app.PayAppConvert;
|
||||
import com.fastbee.pay.core.domain.dataobject.app.PayApp;
|
||||
import com.fastbee.pay.core.domain.mapper.app.PayAppMapper;
|
||||
import com.fastbee.pay.core.service.order.PayOrderService;
|
||||
import com.fastbee.pay.core.service.refund.PayRefundService;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import static com.fastbee.common.exception.ServiceExceptionUtil.exception;
|
||||
import static com.fastbee.pay.api.enums.ErrorCodeConstants.*;
|
||||
|
||||
/**
|
||||
* 支付应用 Service 实现类
|
||||
*
|
||||
* @author aquan
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
public class PayAppServiceImpl implements PayAppService {
|
||||
|
||||
@Resource
|
||||
private PayAppMapper payAppMapper;
|
||||
|
||||
@Resource
|
||||
@Lazy // 延迟加载,避免循环依赖报错
|
||||
private PayOrderService payOrderService;
|
||||
@Resource
|
||||
@Lazy // 延迟加载,避免循环依赖报错
|
||||
private PayRefundService payRefundService;
|
||||
|
||||
@Override
|
||||
public Long createApp(PayAppCreateReqVO createReqVO) {
|
||||
// 插入
|
||||
PayApp app = PayAppConvert.INSTANCE.convert(createReqVO);
|
||||
payAppMapper.insert(app);
|
||||
// 返回
|
||||
return app.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateApp(PayAppUpdateReqVO updateReqVO) {
|
||||
// 校验存在
|
||||
validateAppExists(updateReqVO.getId());
|
||||
// 更新
|
||||
PayApp updateObj = PayAppConvert.INSTANCE.convert(updateReqVO);
|
||||
payAppMapper.updateById(updateObj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateAppStatus(Long id, Integer status) {
|
||||
// 校验商户存在
|
||||
validateAppExists(id);
|
||||
// 更新状态
|
||||
payAppMapper.updateById(new PayApp().setId(id).setStatus(status));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteApp(Long id) {
|
||||
// 校验存在
|
||||
validateAppExists(id);
|
||||
// 校验关联数据是否存在
|
||||
if (payOrderService.getOrderCountByAppId(id) > 0) {
|
||||
throw exception(APP_EXIST_ORDER_CANT_DELETE);
|
||||
}
|
||||
if (payRefundService.getRefundCountByAppId(id) > 0) {
|
||||
throw exception(APP_EXIST_REFUND_CANT_DELETE);
|
||||
}
|
||||
|
||||
// 删除
|
||||
payAppMapper.deleteById(id);
|
||||
}
|
||||
|
||||
private void validateAppExists(Long id) {
|
||||
if (payAppMapper.selectById(id) == null) {
|
||||
throw exception(APP_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayApp getApp(Long id) {
|
||||
return payAppMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PayApp> getAppList(Collection<Long> ids) {
|
||||
return payAppMapper.selectBatchIds(ids);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PayApp> getAppList() {
|
||||
return payAppMapper.selectList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<PayApp> getAppPage(PayAppPageReqVO pageReqVO) {
|
||||
return payAppMapper.selectPage(pageReqVO, new LambdaQueryWrapperX<PayApp>()
|
||||
.likeIfPresent(PayApp::getName, pageReqVO.getName())
|
||||
.eqIfPresent(PayApp::getStatus, pageReqVO.getStatus())
|
||||
.betweenIfPresent(PayApp::getCreateTime, pageReqVO.getCreateTime())
|
||||
.orderByDesc(PayApp::getId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayApp validPayApp(Long id) {
|
||||
PayApp app = payAppMapper.selectById(id);
|
||||
// 校验是否存在
|
||||
if (app == null) {
|
||||
throw exception(ErrorCodeConstants.APP_NOT_FOUND);
|
||||
}
|
||||
// 校验是否禁用
|
||||
if (CommonStatusEnum.DISABLE.getStatus().equals(app.getStatus())) {
|
||||
throw exception(ErrorCodeConstants.APP_IS_DISABLE);
|
||||
}
|
||||
return app;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
package com.fastbee.pay.core.service.channel;
|
||||
|
||||
import com.fastbee.common.exception.ServiceException;
|
||||
import com.fastbee.pay.core.controller.admin.channel.vo.PayChannelCreateReqVO;
|
||||
import com.fastbee.pay.core.controller.admin.channel.vo.PayChannelUpdateReqVO;
|
||||
import com.fastbee.pay.core.domain.dataobject.channel.PayChannel;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 支付渠道 Service 接口
|
||||
*
|
||||
* @author aquan
|
||||
*/
|
||||
public interface PayChannelService {
|
||||
|
||||
/**
|
||||
* 创建支付渠道
|
||||
*
|
||||
* @param createReqVO 创建信息
|
||||
* @return 编号
|
||||
*/
|
||||
Long createChannel(@Valid PayChannelCreateReqVO createReqVO);
|
||||
|
||||
/**
|
||||
* 更新支付渠道
|
||||
*
|
||||
* @param updateReqVO 更新信息
|
||||
*/
|
||||
void updateChannel(@Valid PayChannelUpdateReqVO updateReqVO);
|
||||
|
||||
/**
|
||||
* 删除支付渠道
|
||||
*
|
||||
* @param id 编号
|
||||
*/
|
||||
void deleteChannel(Long id);
|
||||
|
||||
/**
|
||||
* 获得支付渠道
|
||||
*
|
||||
* @param id 编号
|
||||
* @return 支付渠道
|
||||
*/
|
||||
PayChannel getChannel(Long id);
|
||||
|
||||
/**
|
||||
* 根据支付应用 ID 集合,获得支付渠道列表
|
||||
*
|
||||
* @param appIds 应用编号集合
|
||||
* @return 支付渠道列表
|
||||
*/
|
||||
List<PayChannel> getChannelListByAppIds(Collection<Long> appIds);
|
||||
|
||||
/**
|
||||
* 根据条件获取渠道
|
||||
*
|
||||
* @param appId 应用编号
|
||||
* @param code 渠道编码
|
||||
* @return 数量
|
||||
*/
|
||||
PayChannel getChannelByAppIdAndCode(Long appId, String code);
|
||||
|
||||
/**
|
||||
* 支付渠道的合法性
|
||||
*
|
||||
* 如果不合法,抛出 {@link ServiceException} 业务异常
|
||||
*
|
||||
* @param id 渠道编号
|
||||
* @return 渠道信息
|
||||
*/
|
||||
PayChannel validPayChannel(Long id);
|
||||
|
||||
/**
|
||||
* 支付渠道的合法性
|
||||
*
|
||||
* 如果不合法,抛出 {@link ServiceException} 业务异常
|
||||
*
|
||||
* @param appId 应用编号
|
||||
* @param code 支付渠道
|
||||
* @return 渠道信息
|
||||
*/
|
||||
PayChannel validPayChannel(Long appId, String code);
|
||||
|
||||
/**
|
||||
* 获得指定应用的开启的渠道列表
|
||||
*
|
||||
* @param appId 应用编号
|
||||
* @return 渠道列表
|
||||
*/
|
||||
List<PayChannel> getEnableChannelList(Long appId);
|
||||
|
||||
PayChannel selectByAppIdAndCode(Long appId, String code);
|
||||
|
||||
}
|
@ -0,0 +1,231 @@
|
||||
package com.fastbee.pay.core.service.channel;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.fastbee.common.enums.CommonStatusEnum;
|
||||
import com.fastbee.common.utils.json.JsonUtils;
|
||||
import com.fastbee.framework.mybatis.LambdaQueryWrapperX;
|
||||
import com.fastbee.pay.core.controller.admin.channel.vo.PayChannelCreateReqVO;
|
||||
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.domain.mapper.channel.PayChannelMapper;
|
||||
import com.fastbee.pay.framework.client.PayClientConfig;
|
||||
import com.fastbee.pay.framework.client.PayClientFactory;
|
||||
import com.fastbee.pay.framework.enums.channel.PayChannelEnum;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Validator;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static com.fastbee.common.exception.ServiceExceptionUtil.exception;
|
||||
import static com.fastbee.pay.api.enums.ErrorCodeConstants.CHANNEL_EXIST_SAME_CHANNEL_ERROR;
|
||||
import static com.fastbee.pay.api.enums.ErrorCodeConstants.CHANNEL_IS_DISABLE;
|
||||
import static com.fastbee.pay.api.enums.ErrorCodeConstants.CHANNEL_NOT_FOUND;
|
||||
|
||||
|
||||
/**
|
||||
* 支付渠道 Service 实现类
|
||||
*
|
||||
* @author aquan
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
@Validated
|
||||
public class PayChannelServiceImpl implements PayChannelService {
|
||||
|
||||
@Getter // 为了方便测试,这里提供 getter 方法
|
||||
@Setter
|
||||
private volatile List<PayChannel> channelCache;
|
||||
|
||||
@Resource
|
||||
private PayClientFactory payClientFactory;
|
||||
|
||||
@Resource
|
||||
private PayChannelMapper payChannelMapper;
|
||||
|
||||
@Resource
|
||||
private Validator validator;
|
||||
|
||||
/**
|
||||
* 初始化 {@link #payClientFactory} 缓存
|
||||
*/
|
||||
@PostConstruct
|
||||
public void initLocalCache() {
|
||||
// 注意:忽略自动多租户,因为要全局初始化缓存
|
||||
// TenantUtils.executeIgnore(() -> {
|
||||
// // 第一步:查询数据
|
||||
// List<PayChannel> channels = Collections.emptyList();
|
||||
// try {
|
||||
// channels = channelMapper.selectList();
|
||||
// } catch (Throwable ex) {
|
||||
// if (!ex.getMessage().contains("doesn't exist")) {
|
||||
// throw ex;
|
||||
// }
|
||||
// log.error("[支付模块 yudao-module-pay - 表结构未导入][参考 https://doc.iocoder.cn/pay/build/ 开启]");
|
||||
// }
|
||||
// log.info("[initLocalCache][缓存支付渠道,数量为:{}]", channels.size());
|
||||
//
|
||||
// // 第二步:构建缓存:创建或更新支付 Client
|
||||
// channels.forEach(payChannel -> payClientFactory.createOrUpdatePayClient(payChannel.getId(),
|
||||
// payChannel.getCode(), payChannel.getConfig()));
|
||||
// this.channelCache = channels;
|
||||
// });
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过定时任务轮询,刷新缓存
|
||||
*
|
||||
* 目的:多节点部署时,通过轮询”通知“所有节点,进行刷新
|
||||
*/
|
||||
@Scheduled(initialDelay = 60, fixedRate = 60, timeUnit = TimeUnit.SECONDS)
|
||||
public void refreshLocalCache() {
|
||||
// 注意:忽略自动多租户,因为要全局初始化缓存
|
||||
// TenantUtils.executeIgnore(() -> {
|
||||
// // 情况一:如果缓存里没有数据,则直接刷新缓存
|
||||
// if (CollUtil.isEmpty(channelCache)) {
|
||||
// initLocalCache();
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // 情况二,如果缓存里数据,则通过 updateTime 判断是否有数据变更,有变更则刷新缓存
|
||||
// LocalDateTime maxTime = CollectionUtils.getMaxValue(channelCache, PayChannel::getUpdateTime);
|
||||
// if (channelMapper.selectCountByUpdateTimeGt(maxTime) > 0) {
|
||||
// initLocalCache();
|
||||
// }
|
||||
// });
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long createChannel(PayChannelCreateReqVO reqVO) {
|
||||
// 断言是否有重复的
|
||||
PayChannel dbChannel = getChannelByAppIdAndCode(reqVO.getAppId(), reqVO.getCode());
|
||||
if (dbChannel != null) {
|
||||
throw exception(CHANNEL_EXIST_SAME_CHANNEL_ERROR);
|
||||
}
|
||||
|
||||
// 新增渠道
|
||||
PayChannel channel = PayChannelConvert.INSTANCE.convert(reqVO)
|
||||
.setConfig(parseConfig(reqVO.getCode(), reqVO.getConfig()));
|
||||
payChannelMapper.insert(channel);
|
||||
|
||||
// 刷新缓存
|
||||
initLocalCache();
|
||||
return channel.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateChannel(PayChannelUpdateReqVO updateReqVO) {
|
||||
// 校验存在
|
||||
PayChannel dbChannel = validateChannelExists(updateReqVO.getId());
|
||||
|
||||
// 更新
|
||||
PayChannel channel = PayChannelConvert.INSTANCE.convert(updateReqVO)
|
||||
.setConfig(parseConfig(dbChannel.getCode(), updateReqVO.getConfig()));
|
||||
payChannelMapper.updateById(channel);
|
||||
|
||||
// 刷新缓存
|
||||
initLocalCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析并校验配置
|
||||
*
|
||||
* @param code 渠道编码
|
||||
* @param configStr 配置
|
||||
* @return 支付配置
|
||||
*/
|
||||
private PayClientConfig parseConfig(String code, String configStr) {
|
||||
// 解析配置
|
||||
Class<? extends PayClientConfig> payClass = PayChannelEnum.getByCode(code).getConfigClass();
|
||||
if (ObjectUtil.isNull(payClass)) {
|
||||
throw exception(CHANNEL_NOT_FOUND);
|
||||
}
|
||||
PayClientConfig config = JsonUtils.parseObject2(configStr, payClass);
|
||||
Assert.notNull(config);
|
||||
|
||||
// 验证参数
|
||||
config.validate(validator);
|
||||
return config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteChannel(Long id) {
|
||||
// 校验存在
|
||||
validateChannelExists(id);
|
||||
|
||||
// 删除
|
||||
payChannelMapper.deleteById(id);
|
||||
|
||||
// 刷新缓存
|
||||
initLocalCache();
|
||||
}
|
||||
|
||||
private PayChannel validateChannelExists(Long id) {
|
||||
PayChannel channel = payChannelMapper.selectById(id);
|
||||
if (channel == null) {
|
||||
throw exception(CHANNEL_NOT_FOUND);
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayChannel getChannel(Long id) {
|
||||
return payChannelMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PayChannel> getChannelListByAppIds(Collection<Long> appIds) {
|
||||
return payChannelMapper.selectList(PayChannel::getAppId, appIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayChannel getChannelByAppIdAndCode(Long appId, String code) {
|
||||
return this.selectByAppIdAndCode(appId, code);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayChannel validPayChannel(Long id) {
|
||||
PayChannel channel = payChannelMapper.selectById(id);
|
||||
validPayChannel(channel);
|
||||
return channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayChannel validPayChannel(Long appId, String code) {
|
||||
PayChannel channel = this.selectByAppIdAndCode(appId, code);
|
||||
validPayChannel(channel);
|
||||
return channel;
|
||||
}
|
||||
|
||||
private void validPayChannel(PayChannel channel) {
|
||||
if (channel == null) {
|
||||
throw exception(CHANNEL_NOT_FOUND);
|
||||
}
|
||||
if (CommonStatusEnum.DISABLE.getStatus().equals(channel.getStatus())) {
|
||||
throw exception(CHANNEL_IS_DISABLE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PayChannel> getEnableChannelList(Long appId) {
|
||||
return payChannelMapper.selectList(new LambdaQueryWrapperX<PayChannel>()
|
||||
.eq(PayChannel::getAppId, appId)
|
||||
.eq(PayChannel::getStatus, CommonStatusEnum.ENABLE.getStatus()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayChannel selectByAppIdAndCode(Long appId, String code) {
|
||||
return payChannelMapper.selectOne(PayChannel::getAppId, appId, PayChannel::getCode, code);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
package com.fastbee.pay.core.service.demo;
|
||||
|
||||
import com.fastbee.common.core.domain.PageParam;
|
||||
import com.fastbee.common.core.domain.PageResult;
|
||||
import com.fastbee.pay.core.controller.admin.demo.vo.OrderInfoCreateReqVO;
|
||||
import com.fastbee.pay.core.domain.dataobject.demo.OrderInfo;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
||||
/**
|
||||
* 示例订单 Service 接口
|
||||
*
|
||||
* @author fastbee
|
||||
*/
|
||||
public interface OrderInfoService {
|
||||
|
||||
/**
|
||||
* 创建示例订单
|
||||
*
|
||||
* @param userId 用户编号
|
||||
* @param createReqVO 创建信息
|
||||
* @return 编号
|
||||
*/
|
||||
Long createOrder(Long userId, @Valid OrderInfoCreateReqVO createReqVO);
|
||||
|
||||
/**
|
||||
* 获得示例订单
|
||||
*
|
||||
* @param id 编号
|
||||
* @return 示例订单
|
||||
*/
|
||||
OrderInfo getOrder(Long id);
|
||||
|
||||
/**
|
||||
* 获得示例订单分页
|
||||
*
|
||||
* @param pageReqVO 分页查询
|
||||
* @return 示例订单分页
|
||||
*/
|
||||
PageResult<OrderInfo> getOrderPage(PageParam pageReqVO);
|
||||
|
||||
/**
|
||||
* 更新示例订单为已支付
|
||||
*
|
||||
* @param id 编号
|
||||
* @param payOrderId 支付订单号
|
||||
*/
|
||||
void updateOrderPaid(Long id, Long payOrderId);
|
||||
|
||||
/**
|
||||
* 发起示例订单的退款
|
||||
*
|
||||
* @param id 编号
|
||||
* @param userIp 用户编号
|
||||
*/
|
||||
void refundOrder(Long id, String userIp);
|
||||
|
||||
/**
|
||||
* 更新示例订单为已退款
|
||||
*
|
||||
* @param id 编号
|
||||
* @param payRefundId 退款订单号
|
||||
*/
|
||||
void updateOrderRefunded(Long id, Long payRefundId);
|
||||
|
||||
}
|
@ -0,0 +1,264 @@
|
||||
package com.fastbee.pay.core.service.demo;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import com.fastbee.common.core.domain.PageParam;
|
||||
import com.fastbee.common.core.domain.PageResult;
|
||||
import com.fastbee.framework.mybatis.LambdaQueryWrapperX;
|
||||
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.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.api.enums.order.PayOrderStatusEnum;
|
||||
import com.fastbee.pay.api.enums.refund.PayRefundStatusEnum;
|
||||
import com.fastbee.pay.core.controller.admin.demo.vo.OrderInfoCreateReqVO;
|
||||
import com.fastbee.pay.core.domain.dataobject.demo.OrderInfo;
|
||||
import com.fastbee.pay.core.domain.mapper.demo.OrderInfoMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.hutool.core.util.ObjectUtil.notEqual;
|
||||
import static com.fastbee.common.exception.ServiceExceptionUtil.exception;
|
||||
import static com.fastbee.common.utils.ServletUtils.getClientIP;
|
||||
import static com.fastbee.common.utils.date.LocalDateTimeUtils.addTime;
|
||||
import static com.fastbee.common.utils.json.JsonUtils.toJsonString;
|
||||
import static com.fastbee.pay.api.enums.ErrorCodeConstants.*;
|
||||
|
||||
/**
|
||||
* 示例订单 Service 实现类
|
||||
*
|
||||
* @author fastbee
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
@Slf4j
|
||||
public class OrderInfoServiceImpl implements OrderInfoService {
|
||||
|
||||
/**
|
||||
* 接入的实力应用编号
|
||||
* 从 [支付管理 -> 应用信息] 里添加
|
||||
*/
|
||||
private static final Long PAY_APP_ID = 7L;
|
||||
|
||||
/**
|
||||
* 商品信息 Map
|
||||
* key:商品编号
|
||||
* value:[商品名、商品价格]
|
||||
*/
|
||||
private final Map<Long, Object[]> spuNames = new HashMap<>();
|
||||
|
||||
@Resource
|
||||
private PayOrderApi payOrderApi;
|
||||
@Resource
|
||||
private PayRefundApi payRefundApi;
|
||||
|
||||
@Resource
|
||||
private OrderInfoMapper OrderInfoMapper;
|
||||
|
||||
public OrderInfoServiceImpl() {
|
||||
spuNames.put(1L, new Object[]{"华为手机", 1});
|
||||
spuNames.put(2L, new Object[]{"小米电视", 10});
|
||||
spuNames.put(3L, new Object[]{"苹果手表", 100});
|
||||
spuNames.put(4L, new Object[]{"华硕笔记本", 1000});
|
||||
spuNames.put(5L, new Object[]{"蔚来汽车", 200000});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long createOrder(Long userId, OrderInfoCreateReqVO createReqVO) {
|
||||
// 1.1 获得商品
|
||||
Object[] spu = spuNames.get(createReqVO.getSpuId());
|
||||
Assert.notNull(spu, "商品({}) 不存在", createReqVO.getSpuId());
|
||||
String spuName = (String) spu[0];
|
||||
Integer price = (Integer) spu[1];
|
||||
// 1.2 插入 demo 订单
|
||||
OrderInfo demoOrder = new OrderInfo().setUserId(userId)
|
||||
.setSpuId(createReqVO.getSpuId()).setSpuName(spuName)
|
||||
.setPrice(price).setPayStatus(false).setRefundPrice(0);
|
||||
OrderInfoMapper.insert(demoOrder);
|
||||
|
||||
// 2.1 创建支付单
|
||||
Long payOrderId = payOrderApi.createOrder(new PayOrderCreateReqDTO()
|
||||
.setAppId(PAY_APP_ID).setUserIp(getClientIP()) // 支付应用
|
||||
.setMerchantOrderId(demoOrder.getId().toString()) // 业务的订单编号
|
||||
.setSubject(spuName).setBody("").setPrice(price) // 价格信息
|
||||
.setExpireTime(addTime(Duration.ofHours(2L)))); // 支付的过期时间
|
||||
// 2.2 更新支付单到 demo 订单
|
||||
OrderInfoMapper.updateById(new OrderInfo().setId(demoOrder.getId())
|
||||
.setPayOrderId(payOrderId));
|
||||
// 返回
|
||||
return demoOrder.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public OrderInfo getOrder(Long id) {
|
||||
return OrderInfoMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<OrderInfo> getOrderPage(PageParam pageReqVO) {
|
||||
return OrderInfoMapper.selectPage(pageReqVO, new LambdaQueryWrapperX<OrderInfo>()
|
||||
.orderByDesc(OrderInfo::getId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateOrderPaid(Long id, Long payOrderId) {
|
||||
// 校验并获得支付订单(可支付)
|
||||
PayOrderRespDTO payOrder = validateDemoOrderCanPaid(id, payOrderId);
|
||||
|
||||
// 更新 OrderInfo 状态为已支付
|
||||
int updateCount = OrderInfoMapper.update(new OrderInfo().setPayStatus(true).setPayTime(LocalDateTime.now())
|
||||
.setPayChannelCode(payOrder.getChannelCode()), new LambdaQueryWrapperX<OrderInfo>()
|
||||
.eq(OrderInfo::getId, id).eq(OrderInfo::getPayStatus, false));
|
||||
if (updateCount == 0) {
|
||||
throw exception(DEMO_ORDER_UPDATE_PAID_STATUS_NOT_UNPAID);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验交易订单满足被支付的条件
|
||||
* 1. 交易订单未支付
|
||||
* 2. 支付单已支付
|
||||
*
|
||||
* @param id 交易订单编号
|
||||
* @param payOrderId 支付订单编号
|
||||
* @return 交易订单
|
||||
*/
|
||||
private PayOrderRespDTO validateDemoOrderCanPaid(Long id, Long payOrderId) {
|
||||
// 1.1 校验订单是否存在
|
||||
OrderInfo order = OrderInfoMapper.selectById(id);
|
||||
if (order == null) {
|
||||
throw exception(DEMO_ORDER_NOT_FOUND);
|
||||
}
|
||||
// 1.2 校验订单未支付
|
||||
if (order.getPayStatus()) {
|
||||
log.error("[validateDemoOrderCanPaid][order({}) 不处于待支付状态,请进行处理!order 数据是:{}]",
|
||||
id, toJsonString(order));
|
||||
throw exception(DEMO_ORDER_UPDATE_PAID_STATUS_NOT_UNPAID);
|
||||
}
|
||||
// 1.3 校验支付订单匹配
|
||||
if (notEqual(order.getPayOrderId(), payOrderId)) { // 支付单号
|
||||
log.error("[validateDemoOrderCanPaid][order({}) 支付单不匹配({}),请进行处理!order 数据是:{}]",
|
||||
id, payOrderId, toJsonString(order));
|
||||
throw exception(DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR);
|
||||
}
|
||||
|
||||
// 2.1 校验支付单是否存在
|
||||
PayOrderRespDTO payOrder = payOrderApi.getOrder(payOrderId);
|
||||
if (payOrder == null) {
|
||||
log.error("[validateDemoOrderCanPaid][order({}) payOrder({}) 不存在,请进行处理!]", id, payOrderId);
|
||||
throw exception(ORDER_NOT_FOUND);
|
||||
}
|
||||
// 2.2 校验支付单已支付
|
||||
if (!PayOrderStatusEnum.isSuccess(payOrder.getStatus())) {
|
||||
log.error("[validateDemoOrderCanPaid][order({}) payOrder({}) 未支付,请进行处理!payOrder 数据是:{}]",
|
||||
id, payOrderId, toJsonString(payOrder));
|
||||
throw exception(DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_STATUS_NOT_SUCCESS);
|
||||
}
|
||||
// 2.3 校验支付金额一致
|
||||
if (notEqual(payOrder.getPrice(), order.getPrice())) {
|
||||
log.error("[validateDemoOrderCanPaid][order({}) payOrder({}) 支付金额不匹配,请进行处理!order 数据是:{},payOrder 数据是:{}]",
|
||||
id, payOrderId, toJsonString(order), toJsonString(payOrder));
|
||||
throw exception(DEMO_ORDER_UPDATE_PAID_FAIL_PAY_PRICE_NOT_MATCH);
|
||||
}
|
||||
// 2.4 校验支付订单匹配(二次)
|
||||
if (notEqual(payOrder.getMerchantOrderId(), id.toString())) {
|
||||
log.error("[validateDemoOrderCanPaid][order({}) 支付单不匹配({}),请进行处理!payOrder 数据是:{}]",
|
||||
id, payOrderId, toJsonString(payOrder));
|
||||
throw exception(DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR);
|
||||
}
|
||||
return payOrder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refundOrder(Long id, String userIp) {
|
||||
// 1. 校验订单是否可以退款
|
||||
OrderInfo order = validateDemoOrderCanRefund(id);
|
||||
|
||||
// 2.1 生成退款单号
|
||||
// 一般来说,用户发起退款的时候,都会单独插入一个售后维权表,然后使用该表的 id 作为 refundId
|
||||
// 这里我们是个简单的 demo,所以没有售后维权表,直接使用订单 id + "-refund" 来演示
|
||||
String refundId = order.getId() + "-refund";
|
||||
// 2.2 创建退款单
|
||||
Long payRefundId = payRefundApi.createRefund(new PayRefundCreateReqDTO()
|
||||
.setAppId(PAY_APP_ID).setUserIp(getClientIP()) // 支付应用
|
||||
.setMerchantOrderId(String.valueOf(order.getId())) // 支付单号
|
||||
.setMerchantRefundId(refundId)
|
||||
.setReason("想退钱").setPrice(order.getPrice()));// 价格信息
|
||||
// 2.3 更新退款单到 demo 订单
|
||||
OrderInfoMapper.updateById(new OrderInfo().setId(id)
|
||||
.setPayRefundId(payRefundId).setRefundPrice(order.getPrice()));
|
||||
}
|
||||
|
||||
private OrderInfo validateDemoOrderCanRefund(Long id) {
|
||||
// 校验订单是否存在
|
||||
OrderInfo order = OrderInfoMapper.selectById(id);
|
||||
if (order == null) {
|
||||
throw exception(DEMO_ORDER_NOT_FOUND);
|
||||
}
|
||||
// 校验订单是否支付
|
||||
if (!order.getPayStatus()) {
|
||||
throw exception(DEMO_ORDER_REFUND_FAIL_NOT_PAID);
|
||||
}
|
||||
// 校验订单是否已退款
|
||||
if (order.getPayRefundId() != null) {
|
||||
throw exception(DEMO_ORDER_REFUND_FAIL_REFUNDED);
|
||||
}
|
||||
return order;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateOrderRefunded(Long id, Long payRefundId) {
|
||||
// 1. 校验并获得退款订单(可退款)
|
||||
PayRefundRespDTO payRefund = validateDemoOrderCanRefunded(id, payRefundId);
|
||||
// 2.2 更新退款单到 demo 订单
|
||||
OrderInfoMapper.updateById(new OrderInfo().setId(id)
|
||||
.setRefundTime(payRefund.getSuccessTime()));
|
||||
}
|
||||
|
||||
private PayRefundRespDTO validateDemoOrderCanRefunded(Long id, Long payRefundId) {
|
||||
// 1.1 校验示例订单
|
||||
OrderInfo order = OrderInfoMapper.selectById(id);
|
||||
if (order == null) {
|
||||
throw exception(DEMO_ORDER_NOT_FOUND);
|
||||
}
|
||||
// 1.2 校验退款订单匹配
|
||||
if (Objects.equals(order.getPayOrderId(), payRefundId)) {
|
||||
log.error("[validateDemoOrderCanRefunded][order({}) 退款单不匹配({}),请进行处理!order 数据是:{}]",
|
||||
id, payRefundId, toJsonString(order));
|
||||
throw exception(DEMO_ORDER_REFUND_FAIL_REFUND_ORDER_ID_ERROR);
|
||||
}
|
||||
|
||||
// 2.1 校验退款订单
|
||||
PayRefundRespDTO payRefund = payRefundApi.getRefund(payRefundId);
|
||||
if (payRefund == null) {
|
||||
throw exception(DEMO_ORDER_REFUND_FAIL_REFUND_NOT_FOUND);
|
||||
}
|
||||
// 2.2
|
||||
if (!PayRefundStatusEnum.isSuccess(payRefund.getStatus())) {
|
||||
throw exception(DEMO_ORDER_REFUND_FAIL_REFUND_NOT_SUCCESS);
|
||||
}
|
||||
// 2.3 校验退款金额一致
|
||||
if (notEqual(payRefund.getRefundPrice(), order.getPrice())) {
|
||||
log.error("[validateDemoOrderCanRefunded][order({}) payRefund({}) 退款金额不匹配,请进行处理!order 数据是:{},payRefund 数据是:{}]",
|
||||
id, payRefundId, toJsonString(order), toJsonString(payRefund));
|
||||
throw exception(DEMO_ORDER_REFUND_FAIL_REFUND_PRICE_NOT_MATCH);
|
||||
}
|
||||
// 2.4 校验退款订单匹配(二次)
|
||||
if (notEqual(payRefund.getMerchantOrderId(), id.toString())) {
|
||||
log.error("[validateDemoOrderCanRefunded][order({}) 退款单不匹配({}),请进行处理!payRefund 数据是:{}]",
|
||||
id, payRefundId, toJsonString(payRefund));
|
||||
throw exception(DEMO_ORDER_REFUND_FAIL_REFUND_ORDER_ID_ERROR);
|
||||
}
|
||||
return payRefund;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
package com.fastbee.pay.core.service.notify;
|
||||
|
||||
import com.fastbee.common.core.domain.PageResult;
|
||||
import com.fastbee.pay.core.controller.admin.notify.vo.PayNotifyTaskPageReqVO;
|
||||
import com.fastbee.pay.core.domain.dataobject.notify.PayNotifyLog;
|
||||
import com.fastbee.pay.core.domain.dataobject.notify.PayNotifyTask;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 回调通知 Service 接口
|
||||
*
|
||||
* @author fastbee
|
||||
*/
|
||||
public interface PayNotifyService {
|
||||
|
||||
/**
|
||||
* 创建回调通知任务
|
||||
*
|
||||
* @param type 类型
|
||||
* @param dataId 数据编号
|
||||
*/
|
||||
void createPayNotifyTask(Integer type, Long dataId);
|
||||
|
||||
/**
|
||||
* 执行回调通知
|
||||
* 注意,该方法提供给定时任务调用。目前是 yudao-server 进行调用
|
||||
* @return 通知数量
|
||||
*/
|
||||
int executeNotify() throws InterruptedException;
|
||||
|
||||
/**
|
||||
* 获得回调通知
|
||||
*
|
||||
* @param id 编号
|
||||
* @return 回调通知
|
||||
*/
|
||||
PayNotifyTask getNotifyTask(Long id);
|
||||
|
||||
/**
|
||||
* 获得回调通知分页
|
||||
*
|
||||
* @param pageReqVO 分页查询
|
||||
* @return 回调通知分页
|
||||
*/
|
||||
PageResult<PayNotifyTask> getNotifyTaskPage(PayNotifyTaskPageReqVO pageReqVO);
|
||||
|
||||
/**
|
||||
* 获得回调日志列表
|
||||
*
|
||||
* @param taskId 任务编号
|
||||
* @return 日志列表
|
||||
*/
|
||||
List<PayNotifyLog> getNotifyLogList(Long taskId);
|
||||
|
||||
}
|
@ -0,0 +1,305 @@
|
||||
package com.fastbee.pay.core.service.notify;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.exceptions.ExceptionUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import cn.hutool.http.HttpResponse;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.fastbee.common.core.domain.CommonResult;
|
||||
import com.fastbee.common.core.domain.PageResult;
|
||||
import com.fastbee.common.utils.date.DateUtils;
|
||||
import com.fastbee.common.utils.json.JsonUtils;
|
||||
import com.fastbee.framework.mybatis.LambdaQueryWrapperX;
|
||||
import com.fastbee.pay.api.api.notify.dto.PayOrderNotifyReqDTO;
|
||||
import com.fastbee.pay.api.api.notify.dto.PayRefundNotifyReqDTO;
|
||||
import com.fastbee.pay.api.enums.notify.PayNotifyStatusEnum;
|
||||
import com.fastbee.pay.api.enums.notify.PayNotifyTypeEnum;
|
||||
import com.fastbee.pay.core.controller.admin.notify.vo.PayNotifyTaskPageReqVO;
|
||||
import com.fastbee.pay.core.domain.dataobject.notify.PayNotifyLog;
|
||||
import com.fastbee.pay.core.domain.dataobject.notify.PayNotifyTask;
|
||||
import com.fastbee.pay.core.domain.dataobject.order.PayOrder;
|
||||
import com.fastbee.pay.core.domain.dataobject.refund.PayRefund;
|
||||
import com.fastbee.pay.core.domain.mapper.notify.PayNotifyLogMapper;
|
||||
import com.fastbee.pay.core.domain.mapper.notify.PayNotifyTaskMapper;
|
||||
import com.fastbee.pay.core.service.order.PayOrderService;
|
||||
import com.fastbee.pay.core.service.refund.PayRefundService;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.support.TransactionSynchronization;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static com.fastbee.common.utils.date.LocalDateTimeUtils.addTime;
|
||||
import static com.fastbee.pay.core.framework.job.config.PayJobConfiguration.NOTIFY_THREAD_POOL_TASK_EXECUTOR;
|
||||
|
||||
|
||||
/**
|
||||
* 支付通知 Core Service 实现类
|
||||
*
|
||||
* @author fastbee
|
||||
*/
|
||||
@Service
|
||||
@Valid
|
||||
@Slf4j
|
||||
public class PayNotifyServiceImpl implements PayNotifyService {
|
||||
|
||||
/**
|
||||
* 通知超时时间,单位:秒
|
||||
*/
|
||||
public static final int NOTIFY_TIMEOUT = 120;
|
||||
/**
|
||||
* {@link #NOTIFY_TIMEOUT} 的毫秒
|
||||
*/
|
||||
public static final long NOTIFY_TIMEOUT_MILLIS = 120 * DateUtils.SECOND_MILLIS;
|
||||
|
||||
@Resource
|
||||
@Lazy // 循环依赖,避免报错
|
||||
private PayOrderService payOrderService;
|
||||
@Resource
|
||||
@Lazy // 循环依赖,避免报错
|
||||
private PayRefundService payRefundService;
|
||||
|
||||
@Resource
|
||||
private PayNotifyTaskMapper payNotifyTaskMapper;
|
||||
@Resource
|
||||
private PayNotifyLogMapper payNotifyLogMapper;
|
||||
|
||||
@Resource(name = NOTIFY_THREAD_POOL_TASK_EXECUTOR)
|
||||
private ThreadPoolTaskExecutor threadPoolTaskExecutor;
|
||||
|
||||
// @Resource
|
||||
// private PayNotifyLockRedisDAO notifyLockCoreRedisDAO;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void createPayNotifyTask(Integer type, Long dataId) {
|
||||
PayNotifyTask task = new PayNotifyTask().setType(type).setDataId(dataId);
|
||||
task.setStatus(PayNotifyStatusEnum.WAITING.getStatus()).setNextNotifyTime(LocalDateTime.now())
|
||||
.setNotifyTimes(0).setMaxNotifyTimes(PayNotifyTask.NOTIFY_FREQUENCY.length + 1);
|
||||
// 补充 appId + notifyUrl 字段
|
||||
if (Objects.equals(task.getType(), PayNotifyTypeEnum.ORDER.getType())) {
|
||||
PayOrder order = payOrderService.getOrder(task.getDataId()); // 不进行非空判断,有问题直接异常
|
||||
task.setAppId(order.getAppId()).
|
||||
setMerchantOrderId(order.getMerchantOrderId()).setNotifyUrl(order.getNotifyUrl());
|
||||
} else if (Objects.equals(task.getType(), PayNotifyTypeEnum.REFUND.getType())) {
|
||||
PayRefund refundDO = payRefundService.getRefund(task.getDataId());
|
||||
task.setAppId(refundDO.getAppId())
|
||||
.setMerchantOrderId(refundDO.getMerchantOrderId()).setNotifyUrl(refundDO.getNotifyUrl());
|
||||
}
|
||||
|
||||
// 执行插入
|
||||
payNotifyTaskMapper.insert(task);
|
||||
|
||||
// 必须在事务提交后,在发起任务,否则 PayNotifyTask 还没入库,就提前回调接入的业务
|
||||
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
|
||||
@Override
|
||||
public void afterCommit() {
|
||||
// executeNotify(task);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int executeNotify() throws InterruptedException {
|
||||
// 获得需要通知的任务
|
||||
List<PayNotifyTask> tasks = payNotifyTaskMapper.selectList(new LambdaQueryWrapper<PayNotifyTask>()
|
||||
.in(PayNotifyTask::getStatus, PayNotifyStatusEnum.WAITING.getStatus(),
|
||||
PayNotifyStatusEnum.REQUEST_SUCCESS.getStatus(), PayNotifyStatusEnum.REQUEST_FAILURE.getStatus())
|
||||
.le(PayNotifyTask::getNextNotifyTime, LocalDateTime.now()));
|
||||
if (CollUtil.isEmpty(tasks)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 遍历,逐个通知
|
||||
CountDownLatch latch = new CountDownLatch(tasks.size());
|
||||
tasks.forEach(task -> threadPoolTaskExecutor.execute(() -> {
|
||||
try {
|
||||
// executeNotify(task);
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
}));
|
||||
// 等待完成
|
||||
awaitExecuteNotify(latch);
|
||||
// 返回执行完成的任务数(成功 + 失败)
|
||||
return tasks.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* 等待全部支付通知的完成
|
||||
* 每 1 秒会打印一次剩余任务数量
|
||||
*
|
||||
* @param latch Latch
|
||||
* @throws InterruptedException 如果被打断
|
||||
*/
|
||||
private void awaitExecuteNotify(CountDownLatch latch) throws InterruptedException {
|
||||
long size = latch.getCount();
|
||||
for (int i = 0; i < NOTIFY_TIMEOUT; i++) {
|
||||
if (latch.await(1L, TimeUnit.SECONDS)) {
|
||||
return;
|
||||
}
|
||||
log.info("[awaitExecuteNotify][任务处理中, 总任务数({}) 剩余任务数({})]", size, latch.getCount());
|
||||
}
|
||||
log.error("[awaitExecuteNotify][任务未处理完,总任务数({}) 剩余任务数({})]", size, latch.getCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步执行单个支付通知
|
||||
*
|
||||
* @param task 通知任务
|
||||
*/
|
||||
// public void executeNotify(PayNotifyTask task) {
|
||||
// // 分布式锁,避免并发问题
|
||||
// notifyLockCoreRedisDAO.lock(task.getId(), NOTIFY_TIMEOUT_MILLIS, () -> {
|
||||
// // 校验,当前任务是否已经被通知过
|
||||
// // 虽然已经通过分布式加锁,但是可能同时满足通知的条件,然后都去获得锁。此时,第一个执行完后,第二个还是能拿到锁,然后会再执行一次。
|
||||
// // 因此,此处我们通过第 notifyTimes 通知次数是否匹配来判断
|
||||
// PayNotifyTask dbTask = payNotifyTaskMapper.selectById(task.getId());
|
||||
// if (ObjectUtil.notEqual(task.getNotifyTimes(), dbTask.getNotifyTimes())) {
|
||||
// log.warn("[executeNotifySync][task({}) 任务被忽略,原因是它的通知不是第 ({}) 次,可能是因为并发执行了]",
|
||||
// JsonUtils.toJsonString(task), dbTask.getNotifyTimes());
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // 执行通知
|
||||
// getSelf().executeNotify0(dbTask);
|
||||
// });
|
||||
// }
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void executeNotify0(PayNotifyTask task) {
|
||||
// 发起回调
|
||||
CommonResult<?> invokeResult = null;
|
||||
Throwable invokeException = null;
|
||||
try {
|
||||
invokeResult = executeNotifyInvoke(task);
|
||||
} catch (Throwable e) {
|
||||
invokeException = e;
|
||||
}
|
||||
|
||||
// 处理结果
|
||||
Integer newStatus = processNotifyResult(task, invokeResult, invokeException);
|
||||
|
||||
// 记录 PayNotifyLog 日志
|
||||
String response = invokeException != null ? ExceptionUtil.getRootCauseMessage(invokeException) :
|
||||
JsonUtils.toJsonString(invokeResult);
|
||||
payNotifyLogMapper.insert(PayNotifyLog.builder().taskId(task.getId())
|
||||
.notifyTimes(task.getNotifyTimes() + 1).status(newStatus).response(response).build());
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行单个支付任务的 HTTP 调用
|
||||
*
|
||||
* @param task 通知任务
|
||||
* @return HTTP 响应
|
||||
*/
|
||||
private CommonResult<?> executeNotifyInvoke(PayNotifyTask task) {
|
||||
// 拼接 body 参数
|
||||
Object request;
|
||||
if (Objects.equals(task.getType(), PayNotifyTypeEnum.ORDER.getType())) {
|
||||
request = PayOrderNotifyReqDTO.builder().merchantOrderId(task.getMerchantOrderId())
|
||||
.payOrderId(task.getDataId()).build();
|
||||
} else if (Objects.equals(task.getType(), PayNotifyTypeEnum.REFUND.getType())) {
|
||||
request = PayRefundNotifyReqDTO.builder().merchantOrderId(task.getMerchantOrderId())
|
||||
.payRefundId(task.getDataId()).build();
|
||||
} else {
|
||||
throw new RuntimeException("未知的通知任务类型:" + JsonUtils.toJsonString(task));
|
||||
}
|
||||
// 拼接 header 参数
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
// TenantUtils.addTenantHeader(headers, task.getTenantId());
|
||||
|
||||
// 发起请求
|
||||
try (HttpResponse response = HttpUtil.createPost(task.getNotifyUrl())
|
||||
.body(JsonUtils.toJsonString(request)).addHeaders(headers)
|
||||
.timeout((int) NOTIFY_TIMEOUT_MILLIS).execute()) {
|
||||
// 解析结果
|
||||
return JsonUtils.parseObject(response.body(), CommonResult.class);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理并更新通知结果
|
||||
*
|
||||
* @param task 通知任务
|
||||
* @param invokeResult 通知结果
|
||||
* @param invokeException 通知异常
|
||||
* @return 最终任务的状态
|
||||
*/
|
||||
@VisibleForTesting
|
||||
Integer processNotifyResult(PayNotifyTask task, CommonResult<?> invokeResult, Throwable invokeException) {
|
||||
// 设置通用的更新 PayNotifyTask 的字段
|
||||
PayNotifyTask updateTask = new PayNotifyTask()
|
||||
.setId(task.getId())
|
||||
.setLastExecuteTime(LocalDateTime.now())
|
||||
.setNotifyTimes(task.getNotifyTimes() + 1);
|
||||
|
||||
// 情况一:调用成功
|
||||
if (invokeResult != null && invokeResult.isSuccess()) {
|
||||
updateTask.setStatus(PayNotifyStatusEnum.SUCCESS.getStatus());
|
||||
payNotifyTaskMapper.updateById(updateTask);
|
||||
return updateTask.getStatus();
|
||||
}
|
||||
|
||||
// 情况二:调用失败、调用异常
|
||||
// 2.1 超过最大回调次数
|
||||
if (updateTask.getNotifyTimes() >= PayNotifyTask.NOTIFY_FREQUENCY.length) {
|
||||
updateTask.setStatus(PayNotifyStatusEnum.FAILURE.getStatus());
|
||||
payNotifyTaskMapper.updateById(updateTask);
|
||||
return updateTask.getStatus();
|
||||
}
|
||||
// 2.2 未超过最大回调次数
|
||||
updateTask.setNextNotifyTime(addTime(Duration.ofSeconds(PayNotifyTask.NOTIFY_FREQUENCY[updateTask.getNotifyTimes()])));
|
||||
updateTask.setStatus(invokeException != null ? PayNotifyStatusEnum.REQUEST_FAILURE.getStatus()
|
||||
: PayNotifyStatusEnum.REQUEST_SUCCESS.getStatus());
|
||||
payNotifyTaskMapper.updateById(updateTask);
|
||||
return updateTask.getStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayNotifyTask getNotifyTask(Long id) {
|
||||
return payNotifyTaskMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<PayNotifyTask> getNotifyTaskPage(PayNotifyTaskPageReqVO pageReqVO) {
|
||||
return payNotifyTaskMapper.selectPage(pageReqVO, new LambdaQueryWrapperX<PayNotifyTask>()
|
||||
.eqIfPresent(PayNotifyTask::getAppId, pageReqVO.getAppId())
|
||||
.eqIfPresent(PayNotifyTask::getType, pageReqVO.getType())
|
||||
.eqIfPresent(PayNotifyTask::getDataId, pageReqVO.getDataId())
|
||||
.eqIfPresent(PayNotifyTask::getStatus, pageReqVO.getStatus())
|
||||
.eqIfPresent(PayNotifyTask::getMerchantOrderId, pageReqVO.getMerchantOrderId())
|
||||
.betweenIfPresent(PayNotifyTask::getCreateTime, pageReqVO.getCreateTime())
|
||||
.orderByDesc(PayNotifyTask::getId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PayNotifyLog> getNotifyLogList(Long taskId) {
|
||||
return payNotifyLogMapper.selectList(PayNotifyLog::getTaskId, taskId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得自身的代理对象,解决 AOP 生效问题
|
||||
*
|
||||
* @return 自己
|
||||
*/
|
||||
private PayNotifyServiceImpl getSelf() {
|
||||
return SpringUtil.getBean(getClass());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package com.fastbee.pay.core.service.order;
|
||||
|
||||
|
||||
import com.fastbee.pay.core.domain.dataobject.order.PayOrderExtension;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 支付订单扩展 Service 接口
|
||||
*
|
||||
* @author aquan
|
||||
*/
|
||||
public interface PayOrderExtensionService {
|
||||
|
||||
PayOrderExtension selectByNo(String no);
|
||||
|
||||
int updateByIdAndStatus(Long id, Integer status, PayOrderExtension update);
|
||||
|
||||
List<PayOrderExtension> selectListByOrderId(Long orderId);
|
||||
|
||||
List<PayOrderExtension> selectListByStatusAndCreateTimeGe(Integer status, LocalDateTime minCreateTime);
|
||||
|
||||
PayOrderExtension selectById(Long id);
|
||||
|
||||
void insert(PayOrderExtension payOrderExtension);
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
package com.fastbee.pay.core.service.order;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.fastbee.pay.core.domain.dataobject.order.PayOrderExtension;
|
||||
import com.fastbee.pay.core.domain.mapper.order.PayOrderExtensionMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* 支付订单 Service 实现类
|
||||
*
|
||||
* @author aquan
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
@Slf4j
|
||||
public class PayOrderExtensionServiceImpl implements PayOrderExtensionService {
|
||||
|
||||
@Resource
|
||||
private PayOrderExtensionMapper payOrderExtensionMapper;
|
||||
|
||||
@Override
|
||||
public PayOrderExtension selectByNo(String no) {
|
||||
return payOrderExtensionMapper.selectOne(PayOrderExtension::getNo, no);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int updateByIdAndStatus(Long id, Integer status, PayOrderExtension update) {
|
||||
return payOrderExtensionMapper.update(update, new LambdaQueryWrapper<PayOrderExtension>()
|
||||
.eq(PayOrderExtension::getId, id).eq(PayOrderExtension::getStatus, status));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PayOrderExtension> selectListByOrderId(Long orderId) {
|
||||
return payOrderExtensionMapper.selectList(PayOrderExtension::getOrderId, orderId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PayOrderExtension> selectListByStatusAndCreateTimeGe(Integer status, LocalDateTime minCreateTime) {
|
||||
return payOrderExtensionMapper.selectList(new LambdaQueryWrapper<PayOrderExtension>()
|
||||
.eq(PayOrderExtension::getStatus, status)
|
||||
.ge(PayOrderExtension::getCreateTime, minCreateTime));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayOrderExtension selectById(Long id) {
|
||||
return payOrderExtensionMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insert(PayOrderExtension payOrderExtension) {
|
||||
payOrderExtensionMapper.insert(payOrderExtension);
|
||||
}
|
||||
}
|
@ -0,0 +1,129 @@
|
||||
package com.fastbee.pay.core.service.order;
|
||||
|
||||
|
||||
import com.fastbee.common.core.domain.PageResult;
|
||||
import com.fastbee.pay.api.api.order.dto.PayOrderCreateReqDTO;
|
||||
import com.fastbee.pay.core.controller.admin.order.vo.PayOrderExportReqVO;
|
||||
import com.fastbee.pay.core.controller.admin.order.vo.PayOrderPageReqVO;
|
||||
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.domain.dataobject.order.PayOrder;
|
||||
import com.fastbee.pay.core.domain.dataobject.order.PayOrderExtension;
|
||||
import com.fastbee.pay.framework.client.dto.order.PayOrderRespDTO;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 支付订单 Service 接口
|
||||
*
|
||||
* @author aquan
|
||||
*/
|
||||
public interface PayOrderService {
|
||||
|
||||
/**
|
||||
* 获得支付订单
|
||||
*
|
||||
* @param id 编号
|
||||
* @return 支付订单
|
||||
*/
|
||||
PayOrder getOrder(Long id);
|
||||
|
||||
/**
|
||||
* 获得支付订单
|
||||
*
|
||||
* @param appId 应用编号
|
||||
* @param merchantOrderId 商户订单编号
|
||||
* @return 支付订单
|
||||
*/
|
||||
PayOrder getOrder(Long appId, String merchantOrderId);
|
||||
|
||||
/**
|
||||
* 获得指定应用的订单数量
|
||||
*
|
||||
* @param appId 应用编号
|
||||
* @return 订单数量
|
||||
*/
|
||||
Long getOrderCountByAppId(Long appId);
|
||||
|
||||
/**
|
||||
* 获得支付订单分页
|
||||
*
|
||||
* @param pageReqVO 分页查询
|
||||
* @return 支付订单分页
|
||||
*/
|
||||
PageResult<PayOrder> getOrderPage(PayOrderPageReqVO pageReqVO);
|
||||
|
||||
/**
|
||||
* 获得支付订单列表, 用于 Excel 导出
|
||||
*
|
||||
* @param exportReqVO 查询条件
|
||||
* @return 支付订单列表
|
||||
*/
|
||||
List<PayOrder> getOrderList(PayOrderExportReqVO exportReqVO);
|
||||
|
||||
/**
|
||||
* 创建支付单
|
||||
*
|
||||
* @param reqDTO 创建请求
|
||||
* @return 支付单编号
|
||||
*/
|
||||
Long createOrder(@Valid PayOrderCreateReqDTO reqDTO);
|
||||
|
||||
/**
|
||||
* 提交支付
|
||||
* 此时,会发起支付渠道的调用
|
||||
*
|
||||
* @param reqVO 提交请求
|
||||
* @param userIp 提交 IP
|
||||
* @return 提交结果
|
||||
*/
|
||||
PayOrderSubmitRespVO submitOrder(@Valid PayOrderSubmitReqVO reqVO,
|
||||
@NotEmpty(message = "提交 IP 不能为空") String userIp);
|
||||
|
||||
/**
|
||||
* 通知支付单成功
|
||||
*
|
||||
* @param channelId 渠道编号
|
||||
* @param notify 通知
|
||||
*/
|
||||
void notifyOrder(Long channelId, PayOrderRespDTO notify);
|
||||
|
||||
/**
|
||||
* 更新支付订单的退款金额
|
||||
*
|
||||
* @param id 编号
|
||||
* @param incrRefundPrice 增加的退款金额
|
||||
*/
|
||||
void updateOrderRefundPrice(Long id, Integer incrRefundPrice);
|
||||
|
||||
/**
|
||||
* 获得支付订单
|
||||
*
|
||||
* @param id 编号
|
||||
* @return 支付订单
|
||||
*/
|
||||
PayOrderExtension getOrderExtension(Long id);
|
||||
|
||||
/**
|
||||
* 同步订单的支付状态
|
||||
*
|
||||
* @param minCreateTime 最小创建时间
|
||||
* @return 同步到已支付的订单数量
|
||||
*/
|
||||
int syncOrder(LocalDateTime minCreateTime);
|
||||
|
||||
/**
|
||||
* 将已过期的订单,状态修改为已关闭
|
||||
*
|
||||
* @return 过期的订单数量
|
||||
*/
|
||||
int expireOrder();
|
||||
|
||||
PayOrder selectByAppIdAndMerchantOrderId(Long appId, String merchantOrderId);
|
||||
|
||||
int updateByIdAndStatus(Long id, Integer status, PayOrder update);
|
||||
|
||||
}
|
@ -0,0 +1,589 @@
|
||||
package com.fastbee.pay.core.service.order;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.fastbee.common.core.domain.PageResult;
|
||||
import com.fastbee.common.utils.date.LocalDateTimeUtils;
|
||||
import com.fastbee.framework.mybatis.LambdaQueryWrapperX;
|
||||
import com.fastbee.pay.api.api.order.dto.PayOrderCreateReqDTO;
|
||||
import com.fastbee.pay.api.enums.notify.PayNotifyTypeEnum;
|
||||
import com.fastbee.pay.api.enums.order.PayOrderStatusEnum;
|
||||
import com.fastbee.pay.core.controller.admin.order.vo.PayOrderExportReqVO;
|
||||
import com.fastbee.pay.core.controller.admin.order.vo.PayOrderPageReqVO;
|
||||
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.channel.PayChannel;
|
||||
import com.fastbee.pay.core.domain.dataobject.order.PayOrder;
|
||||
import com.fastbee.pay.core.domain.dataobject.order.PayOrderExtension;
|
||||
import com.fastbee.pay.core.domain.mapper.order.PayOrderMapper;
|
||||
import com.fastbee.pay.core.domain.redis.no.PayNoRedisDAO;
|
||||
import com.fastbee.pay.core.framework.pay.config.PayProperties;
|
||||
import com.fastbee.pay.core.service.app.PayAppService;
|
||||
import com.fastbee.pay.core.service.channel.PayChannelService;
|
||||
import com.fastbee.pay.core.service.notify.PayNotifyService;
|
||||
import com.fastbee.pay.core.util.MoneyUtils;
|
||||
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.order.PayOrderUnifiedReqDTO;
|
||||
import com.fastbee.pay.framework.enums.order.PayOrderStatusRespEnum;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static com.fastbee.common.exception.ServiceExceptionUtil.exception;
|
||||
import static com.fastbee.common.utils.json.JsonUtils.toJsonString;
|
||||
import static com.fastbee.pay.api.enums.ErrorCodeConstants.CHANNEL_NOT_FOUND;
|
||||
import static com.fastbee.pay.api.enums.ErrorCodeConstants.ORDER_EXTENSION_IS_PAID;
|
||||
import static com.fastbee.pay.api.enums.ErrorCodeConstants.ORDER_EXTENSION_NOT_FOUND;
|
||||
import static com.fastbee.pay.api.enums.ErrorCodeConstants.ORDER_EXTENSION_STATUS_IS_NOT_WAITING;
|
||||
import static com.fastbee.pay.api.enums.ErrorCodeConstants.ORDER_IS_EXPIRED;
|
||||
import static com.fastbee.pay.api.enums.ErrorCodeConstants.ORDER_NOT_FOUND;
|
||||
import static com.fastbee.pay.api.enums.ErrorCodeConstants.ORDER_REFUND_FAIL_STATUS_ERROR;
|
||||
import static com.fastbee.pay.api.enums.ErrorCodeConstants.ORDER_STATUS_IS_NOT_WAITING;
|
||||
import static com.fastbee.pay.api.enums.ErrorCodeConstants.ORDER_STATUS_IS_SUCCESS;
|
||||
import static com.fastbee.pay.api.enums.ErrorCodeConstants.ORDER_SUBMIT_CHANNEL_ERROR;
|
||||
import static com.fastbee.pay.api.enums.ErrorCodeConstants.REFUND_PRICE_EXCEED;
|
||||
|
||||
|
||||
/**
|
||||
* 支付订单 Service 实现类
|
||||
*
|
||||
* @author aquan
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
@Slf4j
|
||||
public class PayOrderServiceImpl implements PayOrderService {
|
||||
|
||||
@Resource
|
||||
private PayProperties payProperties;
|
||||
|
||||
@Resource
|
||||
private PayClientFactory payClientFactory;
|
||||
|
||||
@Resource
|
||||
private PayOrderMapper payOrderMapper;
|
||||
@Resource
|
||||
private PayOrderExtensionService payOrderExtensionService;
|
||||
@Resource
|
||||
private PayNoRedisDAO noRedisDAO;
|
||||
|
||||
@Resource
|
||||
private PayAppService appService;
|
||||
@Resource
|
||||
private PayChannelService channelService;
|
||||
@Resource
|
||||
private PayNotifyService notifyService;
|
||||
|
||||
@Override
|
||||
public PayOrder getOrder(Long id) {
|
||||
return payOrderMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayOrder getOrder(Long appId, String merchantOrderId) {
|
||||
return this.selectByAppIdAndMerchantOrderId(appId, merchantOrderId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getOrderCountByAppId(Long appId) {
|
||||
return payOrderMapper.selectCount(PayOrder::getAppId, appId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<PayOrder> getOrderPage(PayOrderPageReqVO pageReqVO) {
|
||||
return payOrderMapper.selectPage(pageReqVO, new LambdaQueryWrapperX<PayOrder>()
|
||||
.eqIfPresent(PayOrder::getAppId, pageReqVO.getAppId())
|
||||
.eqIfPresent(PayOrder::getChannelCode, pageReqVO.getChannelCode())
|
||||
.likeIfPresent(PayOrder::getMerchantOrderId, pageReqVO.getMerchantOrderId())
|
||||
.likeIfPresent(PayOrder::getChannelOrderNo, pageReqVO.getChannelOrderNo())
|
||||
.likeIfPresent(PayOrder::getNo, pageReqVO.getNo())
|
||||
.eqIfPresent(PayOrder::getStatus, pageReqVO.getStatus())
|
||||
.betweenIfPresent(PayOrder::getCreateTime, pageReqVO.getCreateTime())
|
||||
.orderByDesc(PayOrder::getId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PayOrder> getOrderList(PayOrderExportReqVO exportReqVO) {
|
||||
return payOrderMapper.selectList(new LambdaQueryWrapperX<PayOrder>()
|
||||
.eqIfPresent(PayOrder::getAppId, exportReqVO.getAppId())
|
||||
.eqIfPresent(PayOrder::getChannelCode, exportReqVO.getChannelCode())
|
||||
.likeIfPresent(PayOrder::getMerchantOrderId, exportReqVO.getMerchantOrderId())
|
||||
.likeIfPresent(PayOrder::getChannelOrderNo, exportReqVO.getChannelOrderNo())
|
||||
.likeIfPresent(PayOrder::getNo, exportReqVO.getNo())
|
||||
.eqIfPresent(PayOrder::getStatus, exportReqVO.getStatus())
|
||||
.betweenIfPresent(PayOrder::getCreateTime, exportReqVO.getCreateTime())
|
||||
.orderByDesc(PayOrder::getId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long createOrder(PayOrderCreateReqDTO reqDTO) {
|
||||
// 校验 App
|
||||
PayApp app = appService.validPayApp(reqDTO.getAppId());
|
||||
|
||||
// 查询对应的支付交易单是否已经存在。如果是,则直接返回
|
||||
PayOrder order = this.selectByAppIdAndMerchantOrderId(
|
||||
reqDTO.getAppId(), reqDTO.getMerchantOrderId());
|
||||
if (order != null) {
|
||||
log.warn("[createOrder][appId({}) merchantOrderId({}) 已经存在对应的支付单({})]", order.getAppId(),
|
||||
order.getMerchantOrderId(), toJsonString(order)); // 理论来说,不会出现这个情况
|
||||
return order.getId();
|
||||
}
|
||||
|
||||
// 创建支付交易单
|
||||
order = PayOrderConvert.INSTANCE.convert(reqDTO).setAppId(app.getId())
|
||||
// 商户相关字段
|
||||
.setNotifyUrl(app.getOrderNotifyUrl())
|
||||
// 订单相关字段
|
||||
.setStatus(PayOrderStatusEnum.WAITING.getStatus())
|
||||
// 退款相关字段
|
||||
.setRefundPrice(0);
|
||||
payOrderMapper.insert(order);
|
||||
return order.getId();
|
||||
}
|
||||
|
||||
@Override // 注意,这里不能添加事务注解,避免调用支付渠道失败时,将 PayOrderExtension 回滚了
|
||||
public PayOrderSubmitRespVO submitOrder(PayOrderSubmitReqVO reqVO, String userIp) {
|
||||
// 1.1 获得 PayOrder ,并校验其是否存在
|
||||
PayOrder order = validateOrderCanSubmit(reqVO.getId());
|
||||
// 1.32 校验支付渠道是否有效
|
||||
PayChannel channel = validateChannelCanSubmit(order.getAppId(), reqVO.getChannelCode());
|
||||
PayClient client = payClientFactory.getPayClient(channel.getId());
|
||||
|
||||
// 2. 插入 PayOrderExtension
|
||||
String no = noRedisDAO.generate(payProperties.getOrderNoPrefix());
|
||||
PayOrderExtension orderExtension = PayOrderConvert.INSTANCE.convert(reqVO, userIp)
|
||||
.setOrderId(order.getId()).setNo(no)
|
||||
.setChannelId(channel.getId()).setChannelCode(channel.getCode())
|
||||
.setStatus(PayOrderStatusEnum.WAITING.getStatus());
|
||||
payOrderExtensionService.insert(orderExtension);
|
||||
|
||||
// 3. 调用三方接口
|
||||
PayOrderUnifiedReqDTO unifiedOrderReqDTO = PayOrderConvert.INSTANCE.convert2(reqVO, userIp)
|
||||
// 商户相关的字段
|
||||
.setOutTradeNo(orderExtension.getNo()) // 注意,此处使用的是 PayOrderExtension.no 属性!
|
||||
.setSubject(order.getSubject()).setBody(order.getBody())
|
||||
.setNotifyUrl(genChannelOrderNotifyUrl(channel))
|
||||
.setReturnUrl(reqVO.getReturnUrl())
|
||||
// 订单相关字段
|
||||
.setPrice(order.getPrice()).setExpireTime(order.getExpireTime());
|
||||
PayOrderRespDTO unifiedOrderResp = client.unifiedOrder(unifiedOrderReqDTO);
|
||||
|
||||
// 4. 如果调用直接支付成功,则直接更新支付单状态为成功。例如说:付款码支付,免密支付时,就直接验证支付成功
|
||||
if (unifiedOrderResp != null) {
|
||||
getSelf().notifyOrder(channel, unifiedOrderResp);
|
||||
// 如有渠道错误码,则抛出业务异常,提示用户
|
||||
if (StrUtil.isNotEmpty(unifiedOrderResp.getChannelErrorCode())) {
|
||||
throw exception(ORDER_SUBMIT_CHANNEL_ERROR, unifiedOrderResp.getChannelErrorCode(),
|
||||
unifiedOrderResp.getChannelErrorMsg());
|
||||
}
|
||||
// 此处需要读取最新的状态
|
||||
order = payOrderMapper.selectById(order.getId());
|
||||
}
|
||||
return PayOrderConvert.INSTANCE.convert(order, unifiedOrderResp);
|
||||
}
|
||||
|
||||
private PayOrder validateOrderCanSubmit(Long id) {
|
||||
PayOrder order = payOrderMapper.selectById(id);
|
||||
if (order == null) { // 是否存在
|
||||
throw exception(ORDER_NOT_FOUND);
|
||||
}
|
||||
if (PayOrderStatusEnum.isSuccess(order.getStatus())) { // 校验状态,发现已支付
|
||||
throw exception(ORDER_STATUS_IS_SUCCESS);
|
||||
}
|
||||
if (!PayOrderStatusEnum.WAITING.getStatus().equals(order.getStatus())) { // 校验状态,必须是待支付
|
||||
throw exception(ORDER_STATUS_IS_NOT_WAITING);
|
||||
}
|
||||
if (LocalDateTimeUtils.beforeNow(order.getExpireTime())) { // 校验是否过期
|
||||
throw exception(ORDER_IS_EXPIRED);
|
||||
}
|
||||
|
||||
// 【重要】校验是否支付拓展单已支付,只是没有回调、或者数据不正常
|
||||
validateOrderActuallyPaid(id);
|
||||
return order;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验支付订单实际已支付
|
||||
*
|
||||
* @param id 支付编号
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void validateOrderActuallyPaid(Long id) {
|
||||
List<PayOrderExtension> orderExtensions = payOrderExtensionService.selectListByOrderId(id);
|
||||
orderExtensions.forEach(orderExtension -> {
|
||||
// 情况一:校验数据库中的 orderExtension 是不是已支付
|
||||
if (PayOrderStatusEnum.isSuccess(orderExtension.getStatus())) {
|
||||
log.warn("[validateOrderCanSubmit][order({}) 的 extension({}) 已支付,可能是数据不一致]",
|
||||
id, orderExtension.getId());
|
||||
throw exception(ORDER_EXTENSION_IS_PAID);
|
||||
}
|
||||
// 情况二:调用三方接口,查询支付单状态,是不是已支付
|
||||
PayClient payClient = payClientFactory.getPayClient(orderExtension.getChannelId());
|
||||
if (payClient == null) {
|
||||
log.error("[validateOrderCanSubmit][渠道编号({}) 找不到对应的支付客户端]", orderExtension.getChannelId());
|
||||
return;
|
||||
}
|
||||
PayOrderRespDTO respDTO = payClient.getOrder(orderExtension.getNo());
|
||||
if (respDTO != null && PayOrderStatusRespEnum.isSuccess(respDTO.getStatus())) {
|
||||
log.warn("[validateOrderCanSubmit][order({}) 的 PayOrderRespDTO({}) 已支付,可能是回调延迟]",
|
||||
id, toJsonString(respDTO));
|
||||
throw exception(ORDER_EXTENSION_IS_PAID);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private PayChannel validateChannelCanSubmit(Long appId, String channelCode) {
|
||||
// 校验 App
|
||||
appService.validPayApp(appId);
|
||||
// 校验支付渠道是否有效
|
||||
PayChannel channel = channelService.validPayChannel(appId, channelCode);
|
||||
PayClient client = payClientFactory.getPayClient(channel.getId());
|
||||
if (client == null) {
|
||||
log.error("[validatePayChannelCanSubmit][渠道编号({}) 找不到对应的支付客户端]", channel.getId());
|
||||
throw exception(CHANNEL_NOT_FOUND);
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据支付渠道的编码,生成支付渠道的回调地址
|
||||
*
|
||||
* @param channel 支付渠道
|
||||
* @return 支付渠道的回调地址 配置地址 + "/" + channel id
|
||||
*/
|
||||
private String genChannelOrderNotifyUrl(PayChannel channel) {
|
||||
return payProperties.getOrderNotifyUrl() + "/" + channel.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyOrder(Long channelId, PayOrderRespDTO notify) {
|
||||
// 校验支付渠道是否有效
|
||||
PayChannel channel = channelService.validPayChannel(channelId);
|
||||
// 更新支付订单为已支付
|
||||
// TenantUtils.execute(channel.getTenantId(), () -> getSelf().notifyOrder(channel, notify));
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知并更新订单的支付结果
|
||||
*
|
||||
* @param channel 支付渠道
|
||||
* @param notify 通知
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class) // 注意,如果是方法内调用该方法,需要通过 getSelf().notifyPayOrder(channel, notify) 调用,否则事务不生效
|
||||
public void notifyOrder(PayChannel channel, PayOrderRespDTO notify) {
|
||||
// 情况一:支付成功的回调
|
||||
if (PayOrderStatusRespEnum.isSuccess(notify.getStatus())) {
|
||||
notifyOrderSuccess(channel, notify);
|
||||
return;
|
||||
}
|
||||
// 情况二:支付失败的回调
|
||||
if (PayOrderStatusRespEnum.isClosed(notify.getStatus())) {
|
||||
notifyOrderClosed(channel, notify);
|
||||
}
|
||||
// 情况三:WAITING:无需处理
|
||||
// 情况四:REFUND:通过退款回调处理
|
||||
}
|
||||
|
||||
private void notifyOrderSuccess(PayChannel channel, PayOrderRespDTO notify) {
|
||||
// 1. 更新 PayOrderExtension 支付成功
|
||||
PayOrderExtension orderExtension = updateOrderSuccess(notify);
|
||||
// 2. 更新 PayOrder 支付成功
|
||||
Boolean paid = updateOrderSuccess(channel, orderExtension, notify);
|
||||
if (paid) { // 如果之前已经成功回调,则直接返回,不用重复记录支付通知记录;例如说:支付平台重复回调
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 插入支付通知记录
|
||||
notifyService.createPayNotifyTask(PayNotifyTypeEnum.ORDER.getType(),
|
||||
orderExtension.getOrderId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新 PayOrderExtension 支付成功
|
||||
*
|
||||
* @param notify 通知
|
||||
* @return PayOrderExtension 对象
|
||||
*/
|
||||
private PayOrderExtension updateOrderSuccess(PayOrderRespDTO notify) {
|
||||
// 1. 查询 PayOrderExtension
|
||||
PayOrderExtension orderExtension = payOrderExtensionService.selectByNo(notify.getOutTradeNo());
|
||||
if (orderExtension == null) {
|
||||
throw exception(ORDER_EXTENSION_NOT_FOUND);
|
||||
}
|
||||
if (PayOrderStatusEnum.isSuccess(orderExtension.getStatus())) { // 如果已经是成功,直接返回,不用重复更新
|
||||
log.info("[updateOrderExtensionSuccess][orderExtension({}) 已经是已支付,无需更新]", orderExtension.getId());
|
||||
return orderExtension;
|
||||
}
|
||||
if (ObjectUtil.notEqual(orderExtension.getStatus(), PayOrderStatusEnum.WAITING.getStatus())) { // 校验状态,必须是待支付
|
||||
throw exception(ORDER_EXTENSION_STATUS_IS_NOT_WAITING);
|
||||
}
|
||||
|
||||
// 2. 更新 PayOrderExtension
|
||||
int updateCounts = payOrderExtensionService.updateByIdAndStatus(orderExtension.getId(), orderExtension.getStatus(),
|
||||
PayOrderExtension.builder().status(PayOrderStatusEnum.SUCCESS.getStatus()).channelNotifyData(toJsonString(notify)).build());
|
||||
if (updateCounts == 0) { // 校验状态,必须是待支付
|
||||
throw exception(ORDER_EXTENSION_STATUS_IS_NOT_WAITING);
|
||||
}
|
||||
log.info("[updateOrderExtensionSuccess][orderExtension({}) 更新为已支付]", orderExtension.getId());
|
||||
return orderExtension;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新 PayOrder 支付成功
|
||||
*
|
||||
* @param channel 支付渠道
|
||||
* @param orderExtension 支付拓展单
|
||||
* @param notify 通知回调
|
||||
* @return 是否之前已经成功回调
|
||||
*/
|
||||
private Boolean updateOrderSuccess(PayChannel channel, PayOrderExtension orderExtension,
|
||||
PayOrderRespDTO notify) {
|
||||
// 1. 判断 PayOrder 是否处于待支付
|
||||
PayOrder order = payOrderMapper.selectById(orderExtension.getOrderId());
|
||||
if (order == null) {
|
||||
throw exception(ORDER_NOT_FOUND);
|
||||
}
|
||||
if (PayOrderStatusEnum.isSuccess(order.getStatus()) // 如果已经是成功,直接返回,不用重复更新
|
||||
&& Objects.equals(order.getExtensionId(), orderExtension.getId())) {
|
||||
log.info("[updateOrderExtensionSuccess][order({}) 已经是已支付,无需更新]", order.getId());
|
||||
return true;
|
||||
}
|
||||
if (!PayOrderStatusEnum.WAITING.getStatus().equals(order.getStatus())) { // 校验状态,必须是待支付
|
||||
throw exception(ORDER_STATUS_IS_NOT_WAITING);
|
||||
}
|
||||
|
||||
// 2. 更新 PayOrder
|
||||
int updateCounts = this.updateByIdAndStatus(order.getId(), PayOrderStatusEnum.WAITING.getStatus(),
|
||||
PayOrder.builder().status(PayOrderStatusEnum.SUCCESS.getStatus())
|
||||
.channelId(channel.getId()).channelCode(channel.getCode())
|
||||
.successTime(notify.getSuccessTime()).extensionId(orderExtension.getId()).no(orderExtension.getNo())
|
||||
.channelOrderNo(notify.getChannelOrderNo()).channelUserId(notify.getChannelUserId())
|
||||
.channelFeeRate(channel.getFeeRate()).channelFeePrice(MoneyUtils.calculateRatePrice(order.getPrice(), channel.getFeeRate()))
|
||||
.build());
|
||||
if (updateCounts == 0) { // 校验状态,必须是待支付
|
||||
throw exception(ORDER_STATUS_IS_NOT_WAITING);
|
||||
}
|
||||
log.info("[updateOrderExtensionSuccess][order({}) 更新为已支付]", order.getId());
|
||||
return false;
|
||||
}
|
||||
|
||||
private void notifyOrderClosed(PayChannel channel, PayOrderRespDTO notify) {
|
||||
updateOrderExtensionClosed(channel, notify);
|
||||
}
|
||||
|
||||
private void updateOrderExtensionClosed(PayChannel channel, PayOrderRespDTO notify) {
|
||||
// 1. 查询 PayOrderExtension
|
||||
PayOrderExtension orderExtension = payOrderExtensionService.selectByNo(notify.getOutTradeNo());
|
||||
if (orderExtension == null) {
|
||||
throw exception(ORDER_EXTENSION_NOT_FOUND);
|
||||
}
|
||||
if (PayOrderStatusEnum.isClosed(orderExtension.getStatus())) { // 如果已经是关闭,直接返回,不用重复更新
|
||||
log.info("[updateOrderExtensionClosed][orderExtension({}) 已经是支付关闭,无需更新]", orderExtension.getId());
|
||||
return;
|
||||
}
|
||||
// 一般出现先是支付成功,然后支付关闭,都是全部退款导致关闭的场景。这个情况,我们不更新支付拓展单,只通过退款流程,更新支付单
|
||||
if (PayOrderStatusEnum.isSuccess(orderExtension.getStatus())) {
|
||||
log.info("[updateOrderExtensionClosed][orderExtension({}) 是已支付,无需更新为支付关闭]", orderExtension.getId());
|
||||
return;
|
||||
}
|
||||
if (ObjectUtil.notEqual(orderExtension.getStatus(), PayOrderStatusEnum.WAITING.getStatus())) { // 校验状态,必须是待支付
|
||||
throw exception(ORDER_EXTENSION_STATUS_IS_NOT_WAITING);
|
||||
}
|
||||
|
||||
// 2. 更新 PayOrderExtension
|
||||
int updateCounts = payOrderExtensionService.updateByIdAndStatus(orderExtension.getId(), orderExtension.getStatus(),
|
||||
PayOrderExtension.builder().status(PayOrderStatusEnum.CLOSED.getStatus()).channelNotifyData(toJsonString(notify))
|
||||
.channelErrorCode(notify.getChannelErrorCode()).channelErrorMsg(notify.getChannelErrorMsg()).build());
|
||||
if (updateCounts == 0) { // 校验状态,必须是待支付
|
||||
throw exception(ORDER_EXTENSION_STATUS_IS_NOT_WAITING);
|
||||
}
|
||||
log.info("[updateOrderExtensionClosed][orderExtension({}) 更新为支付关闭]", orderExtension.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateOrderRefundPrice(Long id, Integer incrRefundPrice) {
|
||||
PayOrder order = payOrderMapper.selectById(id);
|
||||
if (order == null) {
|
||||
throw exception(ORDER_NOT_FOUND);
|
||||
}
|
||||
if (!PayOrderStatusEnum.isSuccessOrRefund(order.getStatus())) {
|
||||
throw exception(ORDER_REFUND_FAIL_STATUS_ERROR);
|
||||
}
|
||||
if (order.getRefundPrice() + incrRefundPrice > order.getPrice()) {
|
||||
throw exception(REFUND_PRICE_EXCEED);
|
||||
}
|
||||
|
||||
// 更新订单
|
||||
PayOrder updateObj = new PayOrder()
|
||||
.setRefundPrice(order.getRefundPrice() + incrRefundPrice)
|
||||
.setStatus(PayOrderStatusEnum.REFUND.getStatus());
|
||||
int updateCount = this.updateByIdAndStatus(id, order.getStatus(), updateObj);
|
||||
if (updateCount == 0) {
|
||||
throw exception(ORDER_REFUND_FAIL_STATUS_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayOrderExtension getOrderExtension(Long id) {
|
||||
return payOrderExtensionService.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int syncOrder(LocalDateTime minCreateTime) {
|
||||
// 1. 查询指定创建时间内的待支付订单
|
||||
List<PayOrderExtension> orderExtensions = payOrderExtensionService.selectListByStatusAndCreateTimeGe(
|
||||
PayOrderStatusEnum.WAITING.getStatus(), minCreateTime);
|
||||
if (CollUtil.isEmpty(orderExtensions)) {
|
||||
return 0;
|
||||
}
|
||||
// 2. 遍历执行
|
||||
int count = 0;
|
||||
for (PayOrderExtension orderExtension : orderExtensions) {
|
||||
count += syncOrder(orderExtension) ? 1 : 0;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步单个支付拓展单
|
||||
*
|
||||
* @param orderExtension 支付拓展单
|
||||
* @return 是否已支付
|
||||
*/
|
||||
private boolean syncOrder(PayOrderExtension orderExtension) {
|
||||
try {
|
||||
// 1.1 查询支付订单信息
|
||||
PayClient payClient = payClientFactory.getPayClient(orderExtension.getChannelId());
|
||||
if (payClient == null) {
|
||||
log.error("[syncOrder][渠道编号({}) 找不到对应的支付客户端]", orderExtension.getChannelId());
|
||||
return false;
|
||||
}
|
||||
PayOrderRespDTO respDTO = payClient.getOrder(orderExtension.getNo());
|
||||
// 1.2 回调支付结果
|
||||
notifyOrder(orderExtension.getChannelId(), respDTO);
|
||||
|
||||
// 2. 如果是已支付,则返回 true
|
||||
return PayOrderStatusRespEnum.isSuccess(respDTO.getStatus());
|
||||
} catch (Throwable e) {
|
||||
log.error("[syncOrder][orderExtension({}) 同步支付状态异常]", orderExtension.getId(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int expireOrder() {
|
||||
// 1. 查询过期的待支付订单
|
||||
List<PayOrder> orders = payOrderMapper.selectList(new LambdaQueryWrapper<PayOrder>()
|
||||
.eq(PayOrder::getStatus, PayOrderStatusEnum.WAITING.getStatus())
|
||||
.lt(PayOrder::getExpireTime, LocalDateTime.now()));
|
||||
if (CollUtil.isEmpty(orders)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 2. 遍历执行
|
||||
int count = 0;
|
||||
for (PayOrder order : orders) {
|
||||
count += expireOrder(order) ? 1 : 0;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayOrder selectByAppIdAndMerchantOrderId(Long appId, String merchantOrderId) {
|
||||
return payOrderMapper.selectOne(PayOrder::getAppId, appId,
|
||||
PayOrder::getMerchantOrderId, merchantOrderId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int updateByIdAndStatus(Long id, Integer status, PayOrder update) {
|
||||
return payOrderMapper.update(update, new LambdaQueryWrapper<PayOrder>()
|
||||
.eq(PayOrder::getId, id).eq(PayOrder::getStatus, status));
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步单个支付单
|
||||
*
|
||||
* @param order 支付单
|
||||
* @return 是否已过期
|
||||
*/
|
||||
private boolean expireOrder(PayOrder order) {
|
||||
try {
|
||||
// 1. 需要先处理关联的支付拓展单,避免错误的过期已支付 or 已退款的订单
|
||||
List<PayOrderExtension> orderExtensions = payOrderExtensionService.selectListByOrderId(order.getId());
|
||||
for (PayOrderExtension orderExtension : orderExtensions) {
|
||||
if (PayOrderStatusEnum.isClosed(orderExtension.getStatus())) {
|
||||
continue;
|
||||
}
|
||||
// 情况一:校验数据库中的 orderExtension 是不是已支付
|
||||
if (PayOrderStatusEnum.isSuccess(orderExtension.getStatus())) {
|
||||
log.error("[expireOrder][order({}) 的 extension({}) 已支付,可能是数据不一致]",
|
||||
order.getId(), orderExtension.getId());
|
||||
return false;
|
||||
}
|
||||
// 情况二:调用三方接口,查询支付单状态,是不是已支付/已退款
|
||||
PayClient payClient = payClientFactory.getPayClient(orderExtension.getChannelId());
|
||||
if (payClient == null) {
|
||||
log.error("[expireOrder][渠道编号({}) 找不到对应的支付客户端]", orderExtension.getChannelId());
|
||||
return false;
|
||||
}
|
||||
PayOrderRespDTO respDTO = payClient.getOrder(orderExtension.getNo());
|
||||
if (PayOrderStatusRespEnum.isRefund(respDTO.getStatus())) {
|
||||
// 补充说明:按道理,应该是 WAITING => SUCCESS => REFUND 状态,如果直接 WAITING => REFUND 状态,说明中间丢了过程
|
||||
// 此时,需要人工介入,手工补齐数据,保持 WAITING => SUCCESS => REFUND 的过程
|
||||
log.error("[expireOrder][extension({}) 的 PayOrderRespDTO({}) 已退款,可能是回调延迟]",
|
||||
orderExtension.getId(), toJsonString(respDTO));
|
||||
return false;
|
||||
}
|
||||
if (PayOrderStatusRespEnum.isSuccess(respDTO.getStatus())) {
|
||||
notifyOrder(orderExtension.getChannelId(), respDTO);
|
||||
return false;
|
||||
}
|
||||
// 兜底逻辑:将支付拓展单更新为已关闭
|
||||
PayOrderExtension updateObj = new PayOrderExtension().setStatus(PayOrderStatusEnum.CLOSED.getStatus())
|
||||
.setChannelNotifyData(toJsonString(respDTO));
|
||||
if (payOrderExtensionService.updateByIdAndStatus(orderExtension.getId(), PayOrderStatusEnum.WAITING.getStatus(),
|
||||
updateObj) == 0) {
|
||||
log.error("[expireOrder][extension({}) 更新为支付关闭失败]", orderExtension.getId());
|
||||
return false;
|
||||
}
|
||||
log.info("[expireOrder][extension({}) 更新为支付关闭成功]", orderExtension.getId());
|
||||
}
|
||||
|
||||
// 2. 都没有上述情况,可以安心更新为已关闭
|
||||
PayOrder updateObj = new PayOrder().setStatus(PayOrderStatusEnum.CLOSED.getStatus());
|
||||
if (this.updateByIdAndStatus(order.getId(), order.getStatus(), updateObj) == 0) {
|
||||
log.error("[expireOrder][order({}) 更新为支付关闭失败]", order.getId());
|
||||
return false;
|
||||
}
|
||||
log.info("[expireOrder][order({}) 更新为支付关闭失败]", order.getId());
|
||||
return true;
|
||||
} catch (Throwable e) {
|
||||
log.error("[expireOrder][order({}) 过期订单异常]", order.getId(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得自身的代理对象,解决 AOP 生效问题
|
||||
*
|
||||
* @return 自己
|
||||
*/
|
||||
private PayOrderServiceImpl getSelf() {
|
||||
return SpringUtil.getBean(getClass());
|
||||
}
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user