feat 3.0.0版本全面升级, 增加服务商+多商户功能

This commit is contained in:
bootx
2025-08-25 23:41:01 +08:00
parent 6df7782ed2
commit 2b785420cc
744 changed files with 14948 additions and 12140 deletions

View File

@@ -5,22 +5,23 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.core.env.Environment;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* 运营
* 融合
* @author xxm
* @since 2024/4/20
*/
@Slf4j
@SpringBootApplication
public class DaxpayStart {
@SpringBootApplication()
public class DaxpayUnionStart {
public static void main(String[] args) throws UnknownHostException {
ConfigurableApplicationContext application = SpringApplication.run(DaxpayStart.class, args);
ConfigurableApplicationContext application = SpringApplication.run(DaxpayUnionStart.class, args);
Environment env = application.getEnvironment();
// 环境变量
String appName = env.getProperty("spring.application.name");

View File

@@ -0,0 +1,81 @@
package org.dromara.daxpay.server.aop;
import cn.bootx.platform.core.code.CommonCode;
import cn.bootx.platform.core.exception.BizException;
import cn.bootx.platform.core.exception.ValidationFailedException;
import cn.bootx.platform.core.util.ValidationUtil;
import org.dromara.daxpay.core.param.PaymentCommonParam;
import org.dromara.daxpay.core.result.DaxResult;
import org.dromara.daxpay.service.pay.common.anno.PaymentVerify;
import org.dromara.daxpay.service.pay.service.assist.PaymentAssistService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.MDC;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
/**
* 支付签名切面, 用于对支付参数进行校验和签名
* 执行顺序: 过滤器 -> 拦截器 -> 切面 -> 方法
* @author xxm
* @since 2023/12/24
*/
@Aspect
@Slf4j
@Component
@Order
@RequiredArgsConstructor
public class PaymentVerifyAspect {
private final PaymentAssistService paymentAssistService;
/**
* 切面处理
*/
@Around("@annotation(paymentVerify)||@within(paymentVerify)")
public Object methodPoint(ProceedingJoinPoint pjp, PaymentVerify paymentVerify) throws Throwable {
Object[] args = pjp.getArgs();
if (args.length == 0){
throw new ValidationFailedException("支付方法至少有一个参数,并且需要签名的支付参数放在第一位");
}
Object param = args[0];
if (param instanceof PaymentCommonParam paymentParam){
// 参数校验
ValidationUtil.validateParam(paymentParam);
// 商户和应用信息初始化
paymentAssistService.initMchAndApp(paymentParam.getMchNo(), paymentParam.getAppId());
// 状态判断
paymentAssistService.checkStatus();
// 终端信息初始化
paymentAssistService.initClient(paymentParam);
// 参数签名校验
paymentAssistService.signVerify(paymentParam);
// 参数请求时间校验
paymentAssistService.reqTimeoutVerify(paymentParam);
} else {
throw new ValidationFailedException("参数需要继承PayCommonParam");
}
Object proceed;
try {
proceed = pjp.proceed();
} catch (BizException ex) {
DaxResult<Void> result = new DaxResult<>(ex.getCode(), ex.getMessage());
paymentAssistService.sign(result);
return result;
}
// 对返回值添加追踪ID/响应时间并进行签名
if (proceed instanceof DaxResult<?> result){
result.setTraceId(MDC.get(CommonCode.TRACE_ID));
result.setResTime(LocalDateTime.now());
paymentAssistService.sign(result);
} else {
throw new ValidationFailedException("支付方法返回类型需要为 DaxResult 类型的对象");
}
return proceed;
}
}

View File

@@ -0,0 +1,138 @@
package org.dromara.daxpay.server.controller.admin.device.qrcode;
import cn.bootx.platform.core.annotation.ClientCode;
import cn.bootx.platform.core.annotation.RequestGroup;
import cn.bootx.platform.core.annotation.RequestPath;
import cn.bootx.platform.core.rest.Res;
import cn.bootx.platform.core.rest.dto.LabelValue;
import cn.bootx.platform.core.rest.param.PageParam;
import cn.bootx.platform.core.rest.result.PageResult;
import cn.bootx.platform.core.rest.result.Result;
import org.dromara.daxpay.service.common.code.DaxPayCode;
import org.dromara.daxpay.service.device.param.qrcode.config.CashierCodeConfigParam;
import org.dromara.daxpay.service.device.param.qrcode.config.CashierCodeConfigQuery;
import org.dromara.daxpay.service.device.param.qrcode.config.CashierCodeSceneConfigParam;
import org.dromara.daxpay.service.device.result.qrcode.config.CashierCodeConfigResult;
import org.dromara.daxpay.service.device.result.qrcode.config.CashierCodeSceneConfigResult;
import org.dromara.daxpay.service.device.service.qrcode.CashierCodeConfigService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 收银码牌配置
* @author xxm
* @since 2024/11/20
*/
@Validated
@ClientCode(DaxPayCode.Client.ADMIN)
@Tag(name = "收银码牌配置")
@RestController
@RequestMapping("/admin/cashier/code/config")
@RequestGroup(groupCode = "CashierCodeConfig", groupName = "收银码牌配置", moduleCode = "device")
@RequiredArgsConstructor
public class CashierCodeConfigController {
private final CashierCodeConfigService codeConfigService;
@RequestPath("分页查询")
@GetMapping("/page")
public Result<PageResult<CashierCodeConfigResult>> page(PageParam pageParam, CashierCodeConfigQuery query) {
return Res.ok(codeConfigService.page(pageParam, query));
}
@RequestPath("根据id查询码牌配置")
@Operation(summary = "根据id查询码牌配置")
@GetMapping("/findById")
public Result<CashierCodeConfigResult> findById(Long id){
return Res.ok(codeConfigService.findById(id));
}
@RequestPath("码牌配置保存")
@Operation(summary = "码牌配置保存")
@PostMapping("/save")
public Result<Void> save(@RequestBody CashierCodeConfigParam param) {
codeConfigService.save(param);
return Res.ok();
}
@RequestPath("码牌配置更新")
@Operation(summary = "码牌配置更新")
@PostMapping("/update")
public Result<Void> update(@RequestBody CashierCodeConfigParam param) {
codeConfigService.update(param);
return Res.ok();
}
@RequestPath("码牌配置删除")
@Operation(summary = "码牌配置删除")
@PostMapping("/delete")
public Result<Void> delete(Long id){
codeConfigService.delete(id);
return Res.ok();
}
@RequestPath("获取码牌支付场景配置列表")
@Operation(summary = "获取码牌支付场景配置列表")
@GetMapping("/scene/findAll")
public Result<List<CashierCodeSceneConfigResult>> findSceneByConfigId(Long configId){
return Res.ok(codeConfigService.findSceneByConfigId(configId));
}
@RequestPath("获取码牌支付场景配置详情")
@Operation(summary = "获取码牌各支付场景配置详情")
@GetMapping("/scene/findById")
public Result<CashierCodeSceneConfigResult> findSceneById(Long id){
return Res.ok(codeConfigService.findItemById(id));
}
@RequestPath("码牌支付场景配置保存")
@Operation(summary = "码牌支付场景配置保存")
@PostMapping("/scene/save")
public Result<Void> saveScene(@RequestBody CashierCodeSceneConfigParam param){
codeConfigService.saveScene(param);
return Res.ok();
}
@RequestPath("码牌支付场景配置更新")
@Operation(summary = "码牌支付场景配置更新")
@PostMapping("/scene/update")
public Result<Void> updateScene(@RequestBody CashierCodeSceneConfigParam param){
codeConfigService.updateScene(param);
return Res.ok();
}
@RequestPath("码牌支付场景配置删除")
@Operation(summary = "码牌支付场景配置删除")
@PostMapping("/scene/delete")
public Result<Void> deleteScene(Long id){
codeConfigService.deleteScene(id);
return Res.ok();
}
@RequestPath("码牌支付场景配置是否存在")
@Operation(summary = "码牌支付场景配置是否存在")
@GetMapping("/scene/exists")
public Result<Boolean> existsByScene(String scene, Long configId){
return Res.ok(codeConfigService.existsByScene(scene, configId));
}
@RequestPath("码牌支付场景配置是否存在(不包括自身)")
@Operation(summary = "码牌支付场景配置是否存在(不包括自身)")
@GetMapping("/scene/existsNotId")
public Result<Boolean> existsBySceneNotId(String scene, Long configId, Long id){
return Res.ok(codeConfigService.existsByScene(scene, configId, id));
}
@ClientCode({DaxPayCode.Client.ADMIN, DaxPayCode.Client.MERCHANT})
@RequestPath("码牌配置下拉")
@Operation(summary = "码牌配置下拉")
@GetMapping("/dropdown")
public Result<List<LabelValue>> dropdown(){
return Res.ok(codeConfigService.dropdown());
}
}

View File

@@ -0,0 +1,72 @@
package org.dromara.daxpay.server.controller.admin.device.qrcode;
import cn.bootx.platform.core.annotation.ClientCode;
import cn.bootx.platform.core.annotation.RequestGroup;
import cn.bootx.platform.core.annotation.RequestPath;
import cn.bootx.platform.core.rest.Res;
import cn.bootx.platform.core.rest.param.PageParam;
import cn.bootx.platform.core.rest.result.PageResult;
import cn.bootx.platform.core.rest.result.Result;
import org.dromara.daxpay.service.common.code.DaxPayCode;
import org.dromara.daxpay.service.device.param.qrcode.template.CashierCodeTemplateParam;
import org.dromara.daxpay.service.device.param.qrcode.template.CashierCodeTemplateQuery;
import org.dromara.daxpay.service.device.result.qrcode.template.CashierCodeTemplateResult;
import org.dromara.daxpay.service.device.service.qrcode.CashierCodeTemplateService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Validated
@ClientCode(DaxPayCode.Client.ADMIN)
@Tag(name = "收银码牌模板")
@RestController
@RequestMapping("/admin/cashier/code/template")
@RequestGroup(groupCode = "CashierCodeTemplate", groupName = "收银码牌模板", moduleCode = "device")
@RequiredArgsConstructor
public class CashierCodeTemplateController {
private final CashierCodeTemplateService cashierCodeTemplateService;
@RequestPath("分页查询")
@Operation(summary = "分页查询")
@GetMapping("/page")
public Result<PageResult<CashierCodeTemplateResult>> page(PageParam pageParam, CashierCodeTemplateQuery query){
return Res.ok(cashierCodeTemplateService.page(pageParam, query));
}
@RequestPath("查询详情")
@Operation(summary = "查询详情")
@GetMapping("/findById")
public Result<CashierCodeTemplateResult> findById(@NotNull(message = "id不能为空") Long id){
return Res.ok(cashierCodeTemplateService.findById(id));
}
@RequestPath("生成预览图片")
@Operation(summary = "生成预览图片")
@PostMapping("/generatePreviewImg")
public Result<String> generatePreviewImg(@Validated CashierCodeTemplateParam param){
return Res.ok(cashierCodeTemplateService.generatePreviewImg(param));
}
@RequestPath("创建")
@Operation(summary = "创建")
@PostMapping("/create")
public Result<Void> create(@Validated CashierCodeTemplateParam param){
cashierCodeTemplateService.create(param);
return Res.ok();
}
@RequestPath("更新")
@Operation(summary = "更新")
@PostMapping("/update")
public Result<Void> update(@Validated CashierCodeTemplateParam param){
cashierCodeTemplateService.update(param);
return Res.ok();
}
}

View File

@@ -0,0 +1,44 @@
package org.dromara.daxpay.server.controller.admin.isv.config;
import cn.bootx.platform.core.annotation.ClientCode;
import cn.bootx.platform.core.annotation.RequestGroup;
import cn.bootx.platform.core.annotation.RequestPath;
import cn.bootx.platform.core.rest.Res;
import cn.bootx.platform.core.rest.result.Result;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.dromara.daxpay.service.common.code.DaxPayCode;
import org.dromara.daxpay.service.isv.result.config.IsvChannelConfigResult;
import org.dromara.daxpay.service.isv.service.config.IsvChannelConfigService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* 服务商通道配置
* @author xxm
* @since 2024/10/29
*/
@Validated
@ClientCode({DaxPayCode.Client.ADMIN})
@Tag(name = "服务商通道配置")
@RestController
@RequestGroup(groupCode = "IsvChannelConfig", groupName = "通道配置", moduleCode = "isv")
@RequestMapping("/isv/channel/config")
@RequiredArgsConstructor
public class IsvChannelConfigController {
private final IsvChannelConfigService service;
@RequestPath("查询服务商配置列表")
@Operation(summary = "查询服务商配置列表")
@GetMapping("/findAll")
public Result<List<IsvChannelConfigResult>> findAll(){
return Res.ok(service.findAll());
}
}

View File

@@ -0,0 +1,141 @@
package org.dromara.daxpay.server.controller.admin.isv.gateway;
import cn.bootx.platform.core.annotation.ClientCode;
import cn.bootx.platform.core.annotation.RequestGroup;
import cn.bootx.platform.core.annotation.RequestPath;
import cn.bootx.platform.core.rest.Res;
import cn.bootx.platform.core.rest.result.Result;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.constraints.NotBlank;
import lombok.RequiredArgsConstructor;
import org.dromara.daxpay.service.common.code.DaxPayCode;
import org.dromara.daxpay.service.isv.param.gateway.IsvAggregateBarPayConfigParam;
import org.dromara.daxpay.service.isv.param.gateway.IsvAggregatePayConfigParam;
import org.dromara.daxpay.service.isv.result.gateway.IsvAggregateBarPayConfigResult;
import org.dromara.daxpay.service.isv.result.gateway.IsvAggregatePayConfigResult;
import org.dromara.daxpay.service.isv.service.gateway.IsvAggregateConfigService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 聚合支付配置
* @author xxm
* @since 2025/3/24
*/
@Validated
@ClientCode({DaxPayCode.Client.ADMIN})
@Tag(name = "聚合支付配置(服务商)")
@RestController
@RequestMapping("/isv/aggregate/config")
@RequestGroup(groupCode = "AggregateConfig", groupName = "聚合支付配置(服务商)", moduleCode = "GatewayPay")
@RequiredArgsConstructor
public class IsvAggregateConfigController {
private final IsvAggregateConfigService aggregateConfigService;
@RequestPath("支付配置列表")
@Operation(summary = "支付配置列表")
@GetMapping("/listByPay")
public Result<List<IsvAggregatePayConfigResult>> listByPay(){
return Res.ok(aggregateConfigService.listByPay());
}
@RequestPath("付款码支付配置列表")
@Operation(summary = "付款码支付配置列表")
@GetMapping("/listByBar")
public Result<List<IsvAggregateBarPayConfigResult>> listByBar(){
return Res.ok(aggregateConfigService.listByBar());
}
@RequestPath("支付配置详情")
@Operation(summary = "支付配置详情")
@GetMapping("/findPayById")
public Result<IsvAggregatePayConfigResult> findPayById(Long id){
return Res.ok(aggregateConfigService.findPayById(id));
}
@RequestPath("付款码支付配置详情")
@Operation(summary = "付款码支付配置详情")
@GetMapping("/findBarById")
public Result<IsvAggregateBarPayConfigResult> findBarById(Long id){
return Res.ok(aggregateConfigService.findBarById(id));
}
@RequestPath("保存支付配置")
@Operation(summary = "保存支付配置")
@PostMapping("/savePay")
public Result<Void> savePay(@Validated @RequestBody IsvAggregatePayConfigParam param){
aggregateConfigService.savePay(param);
return Res.ok();
}
@RequestPath("保存付款码支付配置")
@Operation(summary = "保存付款码支付配置")
@PostMapping("/saveBar")
public Result<Void> saveBar(@Validated @RequestBody IsvAggregateBarPayConfigParam param){
aggregateConfigService.saveBar(param);
return Res.ok();
}
@RequestPath("更新支付配置")
@Operation(summary = "更新支付配置")
@PostMapping("/updatePay")
public Result<Void> updatePay(@Validated @RequestBody IsvAggregatePayConfigParam param){
aggregateConfigService.updatePay(param);
return Res.ok();
}
@RequestPath("更新付款码支付配置")
@Operation(summary = "更新付款码支付配置")
@PostMapping("/updateBar")
public Result<Void> updateBar(@Validated @RequestBody IsvAggregateBarPayConfigParam param){
aggregateConfigService.updateBar(param);
return Res.ok();
}
@RequestPath("删除支付配置")
@Operation(summary = "删除支付配置")
@PostMapping("/deletePay")
public Result<Void> deletePay(Long id){
aggregateConfigService.deletePay(id);
return Res.ok();
}
@RequestPath("删除付款码支付配置")
@Operation(summary = "删除付款码支付配置")
@PostMapping("/deleteBar")
public Result<Void> deleteBar(Long id){
aggregateConfigService.deleteBar(id);
return Res.ok();
}
@RequestPath("支付配置是否存在")
@Operation(summary = "支付配置是否存在")
@GetMapping("/existsPay")
public Result<Boolean> existsPay(String type){
return Res.ok(aggregateConfigService.existsPay(type));
}
@RequestPath("支付配置是否存在(不包含自身)")
@Operation(summary = "支付配置是否存在(不包含自身)")
@GetMapping("/existsPayNotId")
public Result<Boolean> existsPayNotId(String type, Long id){
return Res.ok(aggregateConfigService.existsPay(type, id));
}
@RequestPath("付款码支付配置是否存在")
@Operation(summary = "付款码支付配置是否存在")
@GetMapping("/existsBar")
public Result<Boolean> existsBar(String type){
return Res.ok(aggregateConfigService.existsBar(type));
}
@RequestPath("付款码支付配置是否存在(不包含自身)")
@Operation(summary = "付款码支付配置是否存在(不包含自身)")
@GetMapping("/existsBarNotId")
public Result<Boolean> existsBarNotId(String type, Long id){
return Res.ok(aggregateConfigService.existsBar(type, id));
}
}

View File

@@ -0,0 +1,124 @@
package org.dromara.daxpay.server.controller.admin.isv.gateway;
import cn.bootx.platform.core.annotation.ClientCode;
import cn.bootx.platform.core.annotation.RequestGroup;
import cn.bootx.platform.core.annotation.RequestPath;
import cn.bootx.platform.core.rest.Res;
import cn.bootx.platform.core.rest.result.Result;
import cn.bootx.platform.core.validation.ValidationGroup;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.dromara.daxpay.service.common.code.DaxPayCode;
import org.dromara.daxpay.service.isv.param.gateway.IsvCashierGroupConfigParam;
import org.dromara.daxpay.service.isv.param.gateway.IsvCashierItemConfigParam;
import org.dromara.daxpay.service.isv.result.gateway.IsvCashierGroupConfigResult;
import org.dromara.daxpay.service.isv.result.gateway.IsvCashierItemConfigResult;
import org.dromara.daxpay.service.isv.service.gateway.IsvCashierConfigService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 收银台支付配置
* @author xxm
* @since 2025/3/24
*/
@Validated
@ClientCode({DaxPayCode.Client.ADMIN})
@Tag(name = "收银台支付配置(服务商)")
@RestController
@RequestMapping("/isv/cashier/config")
@RequestGroup(groupCode = "CashierConfig", groupName = "收银台支付配置(服务商)", moduleCode = "GatewayPay")
@RequiredArgsConstructor
public class IsvCashierConfigController {
private final IsvCashierConfigService cashierConfigService;
@RequestPath("获取指定类型收银台分组列表")
@Operation(summary = "获取指定类型收银台分组列表")
@GetMapping("/listByType")
public Result<List<IsvCashierGroupConfigResult>> listByType(@NotBlank(message = "收银台类型不可为空") String cashierType) {
return Res.ok(cashierConfigService.listByGroup(cashierType));
}
@RequestPath("获取收银台分组配置")
@Operation(summary = "获取收银台分组配置")
@GetMapping("/findGroupById")
public Result<IsvCashierGroupConfigResult> findGroupById(@NotNull(message = "分组ID不可为空") Long groupId) {
return Res.ok(cashierConfigService.findGroupById(groupId));
}
@RequestPath("获取收银台条目配置列表")
@Operation(summary = "获取收银台条目配置列表")
@GetMapping("/listByItem")
public Result<List<IsvCashierItemConfigResult>> listByItem(Long groupId) {
return Res.ok(cashierConfigService.listByItem(groupId));
}
@RequestPath("获取收银台条目配置")
@Operation(summary = "获取收银台条目配置")
@GetMapping("/findItemById")
public Result<IsvCashierItemConfigResult> findItemById(Long groupId) {
return Res.ok(cashierConfigService.findItemById(groupId));
}
@RequestPath("保存收银台分组")
@Operation(summary = "保存收银台分组")
@PostMapping("/saveGroup")
public Result<Void> saveGroup(@RequestBody @Validated(ValidationGroup.add.class) IsvCashierGroupConfigParam param) {
cashierConfigService.saveGroup(param);
return Res.ok();
}
@RequestPath("保存默认收银台分组(H5)")
@Operation(summary = "保存默认收银台分组(H5)")
@PostMapping("/saveDefaultGroup")
public Result<Void> saveDefaultGroup() {
cashierConfigService.saveDefaultGroup();
return Res.ok();
}
@RequestPath("修改收银台分组")
@Operation(summary = "修改收银台分组")
@PostMapping("/updateGroup")
public Result<Void> updateGroup(@RequestBody @Validated(ValidationGroup.edit.class) IsvCashierGroupConfigParam param) {
cashierConfigService.updateGroup(param);
return Res.ok();
}
@RequestPath("删除收银台分组")
@Operation(summary = "删除收银台分组")
@PostMapping("/deleteGroup")
public Result<Void> deleteGroup(Long id) {
cashierConfigService.deleteGroup(id);
return Res.ok();
}
@RequestPath("保存收银台条目配置")
@Operation(summary = "保存收银台条目配置")
@PostMapping("/saveItem")
public Result<Void> saveItem(@RequestBody @Validated(ValidationGroup.add.class) IsvCashierItemConfigParam param) {
cashierConfigService.saveItem(param);
return Res.ok();
}
@RequestPath("修改收银台条目配置")
@Operation(summary = "修改收银台条目配置")
@PostMapping("/updateItem")
public Result<Void> updateItem(@RequestBody @Validated(ValidationGroup.edit.class) IsvCashierItemConfigParam param) {
cashierConfigService.updateItem(param);
return Res.ok();
}
@RequestPath("删除收银台条目配置")
@Operation(summary = "删除收银台条目配置")
@PostMapping("/deleteItem")
public Result<Void> deleteItem(Long id) {
cashierConfigService.deleteItem(id);
return Res.ok();
}
}

View File

@@ -0,0 +1,49 @@
package org.dromara.daxpay.server.controller.admin.isv.gateway;
import cn.bootx.platform.core.annotation.ClientCode;
import cn.bootx.platform.core.annotation.RequestGroup;
import cn.bootx.platform.core.annotation.RequestPath;
import cn.bootx.platform.core.rest.Res;
import cn.bootx.platform.core.rest.result.Result;
import cn.bootx.platform.core.validation.ValidationGroup;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.constraints.NotBlank;
import lombok.RequiredArgsConstructor;
import org.dromara.daxpay.service.common.code.DaxPayCode;
import org.dromara.daxpay.service.isv.param.gateway.IsvGatewayPayConfigParam;
import org.dromara.daxpay.service.isv.result.gateway.IsvGatewayPayConfigResult;
import org.dromara.daxpay.service.isv.service.gateway.IsvGatewayPayConfigService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
/**
* 网关支付配置
* @author xxm
* @since 2025/3/24
*/
@Validated
@ClientCode({DaxPayCode.Client.ADMIN})
@Tag(name = "网关支付配置(服务商)")
@RestController
@RequestMapping("/isv/gateway/config")
@RequestGroup(groupCode = "IsvGatewayPayConfig", groupName = "网关支付配置(服务商)", moduleCode = "GatewayPay")
@RequiredArgsConstructor
public class IsvGatewayPayConfigController {
private final IsvGatewayPayConfigService gatewayPayConfigService;
@RequestPath("获取网关支付配置")
@Operation(summary = "获取网关支付配置")
@GetMapping("/getConfig")
public Result<IsvGatewayPayConfigResult> getConfig() {
return Res.ok(gatewayPayConfigService.getConfig());
}
@RequestPath("更新网关支付配置")
@Operation(summary = "更新网关支付配置")
@PostMapping("/update")
public Result<Void> update(@Validated(ValidationGroup.edit.class) @RequestBody IsvGatewayPayConfigParam param) {
gatewayPayConfigService.update(param);
return Res.ok();
}
}

View File

@@ -0,0 +1,85 @@
package org.dromara.daxpay.server.controller.admin.merchant;
import cn.bootx.platform.core.annotation.ClientCode;
import cn.bootx.platform.core.annotation.OperateLog;
import cn.bootx.platform.core.annotation.RequestGroup;
import cn.bootx.platform.core.annotation.RequestPath;
import cn.bootx.platform.core.rest.Res;
import cn.bootx.platform.core.rest.param.PageParam;
import cn.bootx.platform.core.rest.result.PageResult;
import cn.bootx.platform.core.rest.result.Result;
import cn.bootx.platform.core.util.ValidationUtil;
import cn.bootx.platform.core.validation.ValidationGroup;
import org.dromara.daxpay.service.common.code.DaxPayCode;
import org.dromara.daxpay.service.merchant.param.app.MchAppParam;
import org.dromara.daxpay.service.merchant.param.app.MchAppQuery;
import org.dromara.daxpay.service.merchant.result.app.MchAppResult;
import org.dromara.daxpay.service.merchant.service.app.MchAppService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.dromara.core.trans.anno.TransMethodResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
/**
* 商户应用配置(管理)
* @author xxm
* @since 2024/6/25
*/
@Validated
@ClientCode({DaxPayCode.Client.ADMIN})
@Tag(name = "商户应用配置(管理)")
@RestController
@RequestMapping("/admin/mch/app")
@RequestGroup(groupCode = "mchAppAdmin", groupName = "商户应用配置(管理)", moduleCode = "merchant")
@RequiredArgsConstructor
public class MchAppAdminController {
private final MchAppService mchAppService;
@RequestPath("新增商户应用")
@Operation(summary = "新增商户应用")
@PostMapping("/add")
@OperateLog(title = "新增商户应用 ", businessType = OperateLog.BusinessType.ADD, saveParam = true)
public Result<Void> add(@RequestBody @Validated(ValidationGroup.add.class) MchAppParam param){
ValidationUtil.validateParam(param, ValidationGroup.add.class);
mchAppService.add(param);
return Res.ok();
}
@RequestPath("修改商户应用")
@Operation(summary = "修改商户应用")
@PostMapping("/update")
@OperateLog(title = "修改商户应用 ", businessType = OperateLog.BusinessType.UPDATE, saveParam = true)
public Result<Void> update(@RequestBody @Validated(ValidationGroup.edit.class) MchAppParam param){
ValidationUtil.validateParam(param, ValidationGroup.edit.class);
mchAppService.update(param);
return Res.ok();
}
@TransMethodResult
@RequestPath("商户应用分页")
@Operation(summary = "商户应用分页")
@GetMapping("/page")
public Result<PageResult<MchAppResult>> page(PageParam pageParam, MchAppQuery query){
return Res.ok(mchAppService.page(pageParam, query));
}
@TransMethodResult
@RequestPath("根据id查询商户应用")
@Operation(summary = "根据id查询商户应用")
@GetMapping("/findById")
public Result<MchAppResult> findById(@NotNull(message = "id不可为空")Long id){
return Res.ok(mchAppService.findById(id));
}
@RequestPath("删除商户应用")
@Operation(summary = "删除商户应用")
@PostMapping("/delete")
@OperateLog(title = "删除商户应用 ", businessType = OperateLog.BusinessType.DELETE, saveParam = true)
public Result<Void> delete(@NotNull(message = "id不可为空") Long id){
mchAppService.delete(id);
return Res.ok();
}
}

View File

@@ -0,0 +1,84 @@
package org.dromara.daxpay.server.controller.admin.merchant;
import cn.bootx.platform.core.annotation.ClientCode;
import cn.bootx.platform.core.annotation.OperateLog;
import cn.bootx.platform.core.annotation.RequestGroup;
import cn.bootx.platform.core.annotation.RequestPath;
import cn.bootx.platform.core.rest.Res;
import cn.bootx.platform.core.rest.param.PageParam;
import cn.bootx.platform.core.rest.result.PageResult;
import cn.bootx.platform.core.rest.result.Result;
import cn.bootx.platform.core.validation.ValidationGroup;
import org.dromara.daxpay.server.service.admin.MerchantAdminService;
import org.dromara.daxpay.service.common.code.DaxPayCode;
import org.dromara.daxpay.service.merchant.param.info.MerchantCreateParam;
import org.dromara.daxpay.service.merchant.param.info.MerchantParam;
import org.dromara.daxpay.service.merchant.param.info.MerchantQuery;
import org.dromara.daxpay.service.merchant.result.info.MerchantResult;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.dromara.core.trans.anno.TransMethodResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
/**
* 商户配置(管理)
* @author xxm
* @since 2024/6/25
*/
@Validated
@ClientCode({DaxPayCode.Client.ADMIN})
@Tag(name = "商户配置(管理)")
@RestController
@RequestMapping("/admin/merchant")
@RequestGroup(groupCode = "merchantAdmin", groupName = "商户配置(管理)", moduleCode = "merchant", moduleName = "(DaxPay)商户管理")
@RequiredArgsConstructor
public class MerchantAdminController {
private final MerchantAdminService merchantService;
@RequestPath("新增商户")
@Operation(summary = "新增商户")
@PostMapping("/add")
@OperateLog(title = "新增商户 ", businessType = OperateLog.BusinessType.ADD, saveParam = true)
public Result<Void> add(@RequestBody @Validated(ValidationGroup.add.class) MerchantCreateParam param){
merchantService.add(param);
return Res.ok();
}
@RequestPath("修改商户")
@Operation(summary = "修改商户")
@PostMapping("/update")
@OperateLog(title = "修改商户 ", businessType = OperateLog.BusinessType.UPDATE, saveParam = true)
public Result<Void> update(@RequestBody @Validated(ValidationGroup.edit.class) MerchantParam param){
merchantService.update(param);
return Res.ok();
}
@TransMethodResult
@RequestPath("商户分页")
@Operation(summary = "商户分页")
@GetMapping("/page")
public Result<PageResult<MerchantResult>> page(PageParam pageParam, MerchantQuery param){
return Res.ok(merchantService.page(pageParam, param));
}
@TransMethodResult
@RequestPath("根据id查询商户")
@Operation(summary = "根据id查询商户")
@GetMapping("/findById")
public Result<MerchantResult> findById(@NotNull(message = "id不可为空")Long id){
return Res.ok(merchantService.findById(id));
}
@RequestPath("删除商户")
@Operation(summary = "删除商户")
@PostMapping("/delete")
@OperateLog(title = "删除商户 ", businessType = OperateLog.BusinessType.DELETE, saveParam = true)
public Result<Void> delete(@NotNull(message = "id不可为空") Long id){
merchantService.delete(id);
return Res.ok();
}
}

View File

@@ -0,0 +1,88 @@
package org.dromara.daxpay.server.controller.admin.report;
import cn.bootx.platform.core.annotation.ClientCode;
import cn.bootx.platform.core.annotation.RequestGroup;
import cn.bootx.platform.core.annotation.RequestPath;
import cn.bootx.platform.core.rest.Res;
import cn.bootx.platform.core.rest.result.Result;
import org.dromara.daxpay.service.common.code.DaxPayCode;
import org.dromara.daxpay.service.pay.param.report.TradeReportQuery;
import org.dromara.daxpay.service.pay.result.report.MerchantReportResult;
import org.dromara.daxpay.service.pay.result.report.TradeReportResult;
import org.dromara.daxpay.service.pay.result.report.TradeStatisticsReport;
import org.dromara.daxpay.service.pay.service.report.IndexTradeReportService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* 首页交易报表
* @author xxm
* @since 2024/11/17
*/
@Validated
@Tag(name = "运营平台首页交易报表")
@RequestGroup(groupCode = "IndexTradeReport", groupName = "运营平台首页交易报表", moduleCode = "report", moduleName = "(DaxPay)统计报表")
@ClientCode(DaxPayCode.Client.ADMIN)
@RestController
@RequestMapping("/admin/report/index")
@RequiredArgsConstructor
public class IndexTradeReportAdminController {
private final IndexTradeReportService tradeReportService;
@RequestPath("支付交易信息统计")
@Operation(summary = "支付交易信息统计")
@GetMapping("/pay")
public Result<TradeReportResult> pryTradeReport(TradeReportQuery query){
return Res.ok(tradeReportService.pryTradeReport(query));
}
@RequestPath("退款交易信息统计")
@Operation(summary = "退款交易信息统计")
@GetMapping("/refund")
public Result<TradeReportResult> refundTradeReport(TradeReportQuery query){
return Res.ok(tradeReportService.refundTradeReport(query));
}
@RequestPath("支付交易通道统计")
@Operation(summary = "支付交易通道统计")
@GetMapping("/payChannel")
public Result<List<TradeReportResult>> payChannelReport(TradeReportQuery query){
return Res.ok(tradeReportService.payChannelReport(query));
}
@RequestPath("退款交易通道统计")
@Operation(summary = "退款交易通道统计")
@GetMapping("/refundChannel")
public Result<List<TradeReportResult>> refundChannelReport(TradeReportQuery query){
return Res.ok(tradeReportService.refundChannelReport(query));
}
@RequestPath("支付交易方式统计")
@Operation(summary = "支付交易方式统计")
@GetMapping("/payMethod")
public Result<List<TradeReportResult>> payMethodReport(TradeReportQuery query){
return Res.ok(tradeReportService.payMethodReport(query));
}
@RequestPath("商户和应用数量统计")
@Operation(summary = "商户和应用数量统计")
@GetMapping("/merchantCount")
public Result<MerchantReportResult> merchantCount(){
return Res.ok(tradeReportService.merchantCount(new TradeReportQuery()));
}
@RequestPath("交易统计报表")
@Operation(summary = "交易统计报表")
@GetMapping("/tradeStatisticsReport")
public Result<List<TradeStatisticsReport>> tradeStatisticsReport(TradeReportQuery query){
return Res.ok(tradeReportService.tradeStatistics(query));
}
}

View File

@@ -0,0 +1,63 @@
package org.dromara.daxpay.server.controller.gateway;
import cn.bootx.platform.core.annotation.IgnoreAuth;
import cn.bootx.platform.core.rest.Res;
import cn.bootx.platform.core.rest.result.Result;
import cn.bootx.platform.core.util.ValidationUtil;
import org.dromara.daxpay.core.param.assist.AuthCodeParam;
import org.dromara.daxpay.core.param.assist.GenerateAuthUrlParam;
import org.dromara.daxpay.core.result.DaxResult;
import org.dromara.daxpay.core.result.assist.AuthResult;
import org.dromara.daxpay.core.result.assist.AuthUrlResult;
import org.dromara.daxpay.core.util.DaxRes;
import org.dromara.daxpay.service.pay.common.anno.PaymentVerify;
import org.dromara.daxpay.service.pay.service.assist.ChannelAuthService;
import org.dromara.daxpay.service.pay.service.assist.PaymentAssistService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
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.RestController;
/**
* 通道认证服务
* @author xxm
* @since 2024/9/24
*/
@IgnoreAuth
@Tag(name = "通道认证服务")
@RestController
@RequestMapping("/unipay/assist/channel/auth")
@RequiredArgsConstructor
public class ChannelAuthController {
private final ChannelAuthService channelAuthService;
private final PaymentAssistService paymentAssistService;
@PaymentVerify
@Operation(summary = "获取授权链接")
@PostMapping("/generateAuthUrl")
public Result<AuthUrlResult> generateAuthUrl(@RequestBody GenerateAuthUrlParam param){
ValidationUtil.validateParam(param);
paymentAssistService.initMchAndApp(param.getAppId());
return Res.ok(channelAuthService.generateAuthUrl(param));
}
@Operation(summary = "通过AuthCode获取认证结果")
@PostMapping("/auth")
public DaxResult<AuthResult> auth(@RequestBody AuthCodeParam param){
return DaxRes.ok(channelAuthService.auth(param));
}
@Operation(summary = "通过AuthCode获取并设置认证结果")
@PostMapping("/authAndSet")
public Result<Void> authAndSet(@RequestBody AuthCodeParam param){
paymentAssistService.initMchAndApp(param.getAppId());
channelAuthService.auth(param);
return Res.ok();
}
}

View File

@@ -0,0 +1,77 @@
package org.dromara.daxpay.server.controller.gateway;
import cn.bootx.platform.core.annotation.IgnoreAuth;
import cn.bootx.platform.core.rest.Res;
import cn.bootx.platform.core.rest.result.Result;
import cn.bootx.platform.core.util.ValidationUtil;
import org.dromara.daxpay.core.param.gateway.*;
import org.dromara.daxpay.core.result.assist.AuthResult;
import org.dromara.daxpay.core.result.trade.pay.PayResult;
import org.dromara.daxpay.service.pay.result.gateway.GatewayCashierCodeConfigResult;
import org.dromara.daxpay.service.pay.service.gateway.CashierCodePayService;
import org.dromara.daxpay.service.pay.service.gateway.CashierPayService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.constraints.NotBlank;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
/**
* 网关收银支付服务(收银台/码牌)
* @author xxm
* @since 2025/4/12
*/
@Validated
@IgnoreAuth
@Tag(name = "网关收银支付服务")
@RestController
@RequestMapping("/unipay/gateway/cashier")
@RequiredArgsConstructor
public class GatewayCashierController {
private final CashierPayService cashierPayService;
private final CashierCodePayService cashierCodePayService;
@Operation(summary = "获取收银码牌配置信息")
@GetMapping("/getCodeConfig")
public Result<GatewayCashierCodeConfigResult> getCashierCodeConfig(
@NotBlank(message = "码牌编码不能为空") @Parameter(description = "码牌编码") String cashierCode,
@NotBlank(message = "支付场景不能为空") @Parameter(description = "支付场景")String cashierScene){
return Res.ok(cashierCodePayService.getCashierCodeConfig(cashierCode, cashierScene));
}
@Operation(summary = "获取收银码牌支付的授权链接, 用于获取OpenId一类的信息")
@PostMapping("/code/generateAuthUrl")
public Result<String> getCashierCodeConfig(@RequestBody @Validated GatewayCashierCodeAuthUrlParam param){
return Res.ok(cashierCodePayService.genAuthUrl(param));
}
@Operation(summary = "发起支付(码牌)")
@PostMapping("/code/pay")
public Result<PayResult> aggregateBarPay(@RequestBody @Validated GatewayCashierCodePayParam param){
return Res.ok(cashierCodePayService.cashierCodePay(param));
}
@Operation(summary = "获取网关支付授权结果(码牌)")
@PostMapping("/code/auth")
public Result<AuthResult> auth(@RequestBody @Validated GatewayCashierCodeAuthParam param){
ValidationUtil.validateParam(param);
return Res.ok(cashierCodePayService.auth(param));
}
@Operation(summary = "发起支付(收银台)")
@PostMapping("/pay")
public Result<PayResult> pay(@RequestBody @Validated GatewayCashierPayParam param){
ValidationUtil.validateParam(param);
return Res.ok(cashierPayService.cashierPay(param));
}
@Operation(summary = "发起支付(收银台付款码)")
@PostMapping("/barPay")
public Result<PayResult> cashierBarPay(@RequestBody GatewayCashierBarPayParam param){
ValidationUtil.validateParam(param);
return Res.ok(cashierPayService.cashierBarPay(param));
}
}

View File

@@ -0,0 +1,107 @@
package org.dromara.daxpay.server.controller.gateway;
import cn.bootx.platform.core.annotation.IgnoreAuth;
import cn.bootx.platform.core.rest.Res;
import cn.bootx.platform.core.rest.result.Result;
import cn.bootx.platform.core.util.ValidationUtil;
import org.dromara.daxpay.core.param.aggregate.AggregateBarPayParam;
import org.dromara.daxpay.core.param.aggregate.AggregatePayParam;
import org.dromara.daxpay.core.param.gateway.GatewayAuthCodeParam;
import org.dromara.daxpay.core.param.gateway.GatewayAuthUrlParam;
import org.dromara.daxpay.core.param.gateway.GatewayOrderAndConfigParam;
import org.dromara.daxpay.core.param.gateway.GatewayPayParam;
import org.dromara.daxpay.core.result.DaxResult;
import org.dromara.daxpay.core.result.assist.AuthResult;
import org.dromara.daxpay.core.result.trade.pay.PayResult;
import org.dromara.daxpay.core.util.DaxRes;
import org.dromara.daxpay.service.pay.common.anno.PaymentVerify;
import org.dromara.daxpay.service.pay.result.gateway.AggregateOrderAndConfigResult;
import org.dromara.daxpay.service.pay.result.gateway.GatewayOrderAndConfigResult;
import org.dromara.daxpay.service.pay.result.gateway.GatewayOrderResult;
import org.dromara.daxpay.service.pay.result.gateway.GatewayPayUrlResult;
import org.dromara.daxpay.service.pay.service.gateway.GatewayPayQueryService;
import org.dromara.daxpay.service.pay.service.gateway.GatewayPayService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.constraints.NotBlank;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
/**
* 网关支付服务
* @author xxm
* @since 2024/11/26
*/
@Validated
@IgnoreAuth
@Tag(name = "网关支付服务")
@RestController
@RequestMapping("/unipay/gateway")
@RequiredArgsConstructor
public class GatewayPayController {
private final GatewayPayService gatewayPayService;
private final GatewayPayQueryService checkoutQueryService;
@PaymentVerify
@Operation(summary = "创建一个网关支付链接")
@PostMapping("/prePay")
public DaxResult<GatewayPayUrlResult> prePay(@RequestBody GatewayPayParam checkoutParam){
return DaxRes.ok(gatewayPayService.prePay(checkoutParam));
}
@PaymentVerify
@Operation(summary = "发起支付(聚合付款码)")
@PostMapping("/aggregateBarPay")
public DaxResult<PayResult> aggregateBarPay(@RequestBody AggregateBarPayParam param){
return DaxRes.ok(gatewayPayService.aggregateBarPay(param));
}
@Operation(summary = "发起支付(聚合扫码/浏览器访问)")
@PostMapping("/aggregatePay")
public Result<PayResult> aggregatePay(@RequestBody AggregatePayParam param){
ValidationUtil.validateParam(param);
return Res.ok(gatewayPayService.aggregatePay(param));
}
@Operation(summary = "获取收银台配置和支付订单信息")
@GetMapping("/getOrderAndConfig")
public Result<GatewayOrderAndConfigResult> getOrderAndConfig(@Validated GatewayOrderAndConfigParam param){
return Res.ok(checkoutQueryService.getOrderAndConfig(param));
}
@Operation(summary = "获取聚合支付配置支付订单信息")
@GetMapping("/getAggregateConfig")
public Result<AggregateOrderAndConfigResult> getAggregateConfig(
@NotBlank(message = "订单号不能为空") @Parameter(description = "订单号") String orderNo,
@NotBlank(message = "聚合支付类型不能为空") @Parameter(description = "聚合支付类型")String aggregateType){
return Res.ok(checkoutQueryService.getAggregateConfig(orderNo, aggregateType));
}
@Operation(summary = "获取网关支付所需授权链接, 用于获取OpenId一类的信息")
@PostMapping("/generateAuthUrl")
public Result<String> generateAuthUrl(@RequestBody @Validated GatewayAuthUrlParam param){
ValidationUtil.validateParam(param);
return Res.ok(gatewayPayService.genAuthUrl(param));
}
@Operation(summary = "获取网关支付授权结果")
@PostMapping("/auth")
public Result<AuthResult> auth(@RequestBody GatewayAuthCodeParam param){
ValidationUtil.validateParam(param);
return Res.ok(gatewayPayService.auth(param));
}
@Operation(summary = "查询订单状态")
@GetMapping("/findStatusByOrderNo")
public Result<Boolean> findStatusByOrderNo(@NotBlank(message = "订单号不能为空") String orderNo){
return Res.ok(checkoutQueryService.findStatusByOrderNo(orderNo));
}
@Operation(summary = "查询订单信息")
@GetMapping("/findOrderByOrderNo")
public Result<GatewayOrderResult> findOrderByOrderNo(@NotBlank(message = "订单号不能为空") String orderNo){
return Res.ok(checkoutQueryService.findOrderByOrderNo(orderNo));
}
}

View File

@@ -0,0 +1,50 @@
package org.dromara.daxpay.server.controller.gateway;
import cn.bootx.platform.core.annotation.IgnoreAuth;
import cn.bootx.platform.core.util.JsonUtil;
import org.dromara.daxpay.core.result.DaxNoticeResult;
import org.dromara.daxpay.core.result.trade.pay.PayOrderResult;
import org.dromara.daxpay.core.util.PaySignUtil;
import cn.hutool.core.lang.TypeReference;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.RandomUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
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.RestController;
import java.util.Map;
/**
* 测试回调接受控制器
* @author xxm
* @since 2024/8/30
*/
@Slf4j
@Tag(name = "测试商户回调接收控制器")
@RestController
@RequestMapping("/test/callback")
@RequiredArgsConstructor
@IgnoreAuth
public class TestCallbackController {
@Operation(summary = "notify")
@PostMapping("/notify")
public String notify(@RequestBody String data){
log.info("notify:{}",data);
// 转换成实体类, 使用sdk中内置的json工具类转换
DaxNoticeResult<PayOrderResult> x = JsonUtil.toBean(data, new TypeReference<>() {});
// 替换成自己的密钥
PaySignUtil.verifyHmacSha256Sign(x,"bc5b5d592cc34434a27fb57fe923dacc5374da52a4174ff5874768a8215e5fd3",x.getSign());
// 使用map方式验签
DaxNoticeResult<Map<String,Object>> bean = JsonUtil.toBean(data, new TypeReference<>() {});
// 替换成自己的密钥
PaySignUtil.verifyHmacSha256Sign(bean,"bc5b5d592cc34434a27fb57fe923dacc5374da52a4174ff5874768a8215e5fd3",bean.getSign());
ThreadUtil.sleep(RandomUtil.randomInt(2, 7) *1000L);
return "success";
}
}

View File

@@ -0,0 +1,59 @@
package org.dromara.daxpay.server.controller.gateway;
import cn.bootx.platform.core.annotation.IgnoreAuth;
import org.dromara.daxpay.core.param.trade.pay.QueryPayParam;
import org.dromara.daxpay.core.param.trade.refund.QueryRefundParam;
import org.dromara.daxpay.core.param.trade.transfer.QueryTransferParam;
import org.dromara.daxpay.core.result.DaxResult;
import org.dromara.daxpay.core.result.trade.pay.PayOrderResult;
import org.dromara.daxpay.core.result.trade.refund.RefundOrderResult;
import org.dromara.daxpay.core.result.trade.transfer.TransferOrderResult;
import org.dromara.daxpay.core.util.DaxRes;
import org.dromara.daxpay.service.pay.common.anno.PaymentVerify;
import org.dromara.daxpay.service.pay.service.order.pay.PayOrderQueryService;
import org.dromara.daxpay.service.pay.service.order.refund.RefundOrderQueryService;
import org.dromara.daxpay.service.pay.service.order.transfer.TransferOrderQueryService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
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.RestController;
/**
* 统一查询接口
* @author xxm
* @since 2024/6/4
*/
@PaymentVerify
@IgnoreAuth
@Tag(name = "统一查询接口")
@RestController
@RequestMapping("/unipay/query")
@RequiredArgsConstructor
public class UniQueryController {
private final PayOrderQueryService payOrderQueryService;
private final RefundOrderQueryService refundOrderQueryService;
private final TransferOrderQueryService transferOrderQueryService;
@Operation(summary = "支付订单查询接口")
@PostMapping("/payOrder")
public DaxResult<PayOrderResult> queryPayOrder(@RequestBody QueryPayParam param){
return DaxRes.ok(payOrderQueryService.queryPayOrder(param));
}
@Operation(summary = "退款订单查询接口")
@PostMapping("/refundOrder")
public DaxResult<RefundOrderResult> queryRefundOrder(@RequestBody QueryRefundParam param){
return DaxRes.ok(refundOrderQueryService.queryRefundOrder(param));
}
@Operation(summary = "转账订单查询接口")
@PostMapping("/transferOrder")
public DaxResult<TransferOrderResult> transferOrder(@RequestBody QueryTransferParam param){
return DaxRes.ok(transferOrderQueryService.queryTransferOrder(param));
}
}

View File

@@ -0,0 +1,61 @@
package org.dromara.daxpay.server.controller.gateway;
import cn.bootx.platform.core.annotation.IgnoreAuth;
import org.dromara.daxpay.core.param.trade.pay.PaySyncParam;
import org.dromara.daxpay.core.param.trade.refund.RefundSyncParam;
import org.dromara.daxpay.core.param.trade.transfer.TransferSyncParam;
import org.dromara.daxpay.core.result.DaxResult;
import org.dromara.daxpay.core.result.trade.pay.PaySyncResult;
import org.dromara.daxpay.core.result.trade.refund.RefundSyncResult;
import org.dromara.daxpay.core.result.trade.transfer.TransferSyncResult;
import org.dromara.daxpay.core.util.DaxRes;
import org.dromara.daxpay.service.pay.common.anno.PaymentVerify;
import org.dromara.daxpay.service.pay.service.trade.pay.PaySyncService;
import org.dromara.daxpay.service.pay.service.trade.refund.RefundSyncService;
import org.dromara.daxpay.service.pay.service.trade.transfer.TransferSyncService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
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.RestController;
/**
* 统一同步接口
* @author xxm
* @since 2024/6/4
*/
@PaymentVerify
@IgnoreAuth
@Tag(name = "统一同步接口")
@RestController
@RequestMapping("/unipay/sync/order")
@RequiredArgsConstructor
public class UniSyncController {
private final PaySyncService paySyncService;
private final RefundSyncService refundSyncService;
private final TransferSyncService transferSyncService;
@Operation(summary = "支付订单同步接口")
@PostMapping("/pay")
public DaxResult<PaySyncResult> pay(@RequestBody PaySyncParam param){
return DaxRes.ok(paySyncService.sync(param));
}
@Operation(summary = "退款订单同步接口")
@PostMapping("/refund")
public DaxResult<RefundSyncResult> refund(@RequestBody RefundSyncParam param){
return DaxRes.ok(refundSyncService.sync(param));
}
@Operation(summary = "分账订单同步接口")
@PostMapping("/allocation")
public DaxResult<TransferSyncResult> allocation(@RequestBody TransferSyncParam param){
return DaxRes.ok(transferSyncService.sync(param));
}
}

View File

@@ -0,0 +1,67 @@
package org.dromara.daxpay.server.controller.gateway;
import cn.bootx.platform.core.annotation.IgnoreAuth;
import org.dromara.daxpay.core.param.trade.pay.PayCloseParam;
import org.dromara.daxpay.core.param.trade.pay.PayParam;
import org.dromara.daxpay.core.param.trade.refund.RefundParam;
import org.dromara.daxpay.core.param.trade.transfer.TransferParam;
import org.dromara.daxpay.core.result.DaxResult;
import org.dromara.daxpay.core.result.trade.pay.PayResult;
import org.dromara.daxpay.core.result.trade.refund.RefundResult;
import org.dromara.daxpay.core.result.trade.transfer.TransferResult;
import org.dromara.daxpay.core.util.DaxRes;
import org.dromara.daxpay.service.pay.common.anno.PaymentVerify;
import org.dromara.daxpay.service.pay.service.trade.pay.PayCloseService;
import org.dromara.daxpay.service.pay.service.trade.pay.PayService;
import org.dromara.daxpay.service.pay.service.trade.refund.RefundService;
import org.dromara.daxpay.service.pay.service.trade.transfer.TransferService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
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.RestController;
/**
* 统一支付接口
* @author xxm
* @since 2024/6/4
*/
@PaymentVerify
@IgnoreAuth
@Tag(name = "统一交易接口")
@RestController
@RequestMapping("/unipay")
@RequiredArgsConstructor
public class UniTradeController {
private final PayService payService;
private final RefundService refundService;
private final PayCloseService payCloseService;
private final TransferService transferService;
@Operation(summary = "支付接口")
@PostMapping("/pay")
public DaxResult<PayResult> pay(@RequestBody PayParam payParam){
return DaxRes.ok(payService.pay(payParam));
}
@Operation(summary = "退款接口")
@PostMapping("/refund")
public DaxResult<RefundResult> refund(@RequestBody RefundParam payParam){
return DaxRes.ok(refundService.refund(payParam));
}
@Operation(summary = "关闭和撤销接口")
@PostMapping("/close")
public DaxResult<Void> close(@RequestBody PayCloseParam param){
payCloseService.close(param);
return DaxRes.ok();
}
@Operation(summary = "转账接口")
@PostMapping("/transfer")
public DaxResult<TransferResult> transfer(@RequestBody TransferParam transferParam){
return DaxRes.ok(transferService.transfer(transferParam));
}
}

View File

@@ -0,0 +1,89 @@
package org.dromara.daxpay.server.controller.merchant.info;
import cn.bootx.platform.core.annotation.ClientCode;
import cn.bootx.platform.core.annotation.OperateLog;
import cn.bootx.platform.core.annotation.RequestGroup;
import cn.bootx.platform.core.annotation.RequestPath;
import cn.bootx.platform.core.rest.Res;
import cn.bootx.platform.core.rest.param.PageParam;
import cn.bootx.platform.core.rest.result.PageResult;
import cn.bootx.platform.core.rest.result.Result;
import cn.bootx.platform.core.validation.ValidationGroup;
import org.dromara.daxpay.service.common.code.DaxPayCode;
import org.dromara.daxpay.service.merchant.param.app.MchAppParam;
import org.dromara.daxpay.service.merchant.param.app.MchAppQuery;
import org.dromara.daxpay.service.merchant.result.app.MchAppResult;
import org.dromara.daxpay.service.merchant.service.app.MchAppService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 商户应用配置
* @author xxm
* @since 2024/6/25
*/
@Validated
@ClientCode(DaxPayCode.Client.MERCHANT)
@Tag(name = "商户应用配置")
@RestController
@RequestMapping("/mch/app")
@RequestGroup(groupCode = "mchApp", groupName = "商户应用配置", moduleCode = "merchant")
@RequiredArgsConstructor
public class MchAppController {
private final MchAppService mchAppService;
@RequestPath("新增商户应用")
@Operation(summary = "新增商户应用")
@OperateLog(title = "新增商户应用信息 ", businessType = OperateLog.BusinessType.ADD, saveParam = true)
@PostMapping("/add")
public Result<Void> add(@RequestBody @Validated(ValidationGroup.add.class) MchAppParam param){
mchAppService.add(param);
return Res.ok();
}
@RequestPath("修改商户应用")
@Operation(summary = "修改商户应用")
@OperateLog(title = "修改商户应用信息 ", businessType = OperateLog.BusinessType.UPDATE, saveParam = true)
@PostMapping("/update")
public Result<Void> update(@RequestBody @Validated(ValidationGroup.edit.class) MchAppParam param){
mchAppService.update(param);
return Res.ok();
}
@RequestPath("商户应用分页")
@Operation(summary = "商户应用分页")
@GetMapping("/page")
public Result<PageResult<MchAppResult>> page(PageParam pageParam, MchAppQuery query){
return Res.ok(mchAppService.page(pageParam, query));
}
@RequestPath("商户应用列表")
@Operation(summary = "商户应用列表")
@GetMapping("/list")
public Result<List<MchAppResult>> list(){
return Res.ok(mchAppService.list());
}
@RequestPath("根据id查询商户应用")
@Operation(summary = "根据id查询商户应用")
@GetMapping("/findById")
public Result<MchAppResult> findById(@NotNull(message = "id不可为空")Long id){
return Res.ok(mchAppService.findById(id));
}
@RequestPath("删除商户应用")
@OperateLog(title = "删除商户应用", businessType = OperateLog.BusinessType.DELETE, saveParam = true)
@Operation(summary = "删除商户应用")
@PostMapping("/delete")
public Result<Void> delete(@NotNull(message = "id不可为空") Long id){
mchAppService.delete(id);
return Res.ok();
}
}

View File

@@ -0,0 +1,51 @@
package org.dromara.daxpay.server.controller.merchant.info;
import cn.bootx.platform.core.annotation.ClientCode;
import cn.bootx.platform.core.annotation.OperateLog;
import cn.bootx.platform.core.annotation.RequestGroup;
import cn.bootx.platform.core.annotation.RequestPath;
import cn.bootx.platform.core.rest.Res;
import cn.bootx.platform.core.rest.result.Result;
import cn.bootx.platform.core.validation.ValidationGroup;
import org.dromara.daxpay.service.merchant.service.info.MerchantInfoService;
import org.dromara.daxpay.service.common.code.DaxPayCode;
import org.dromara.daxpay.service.merchant.param.info.MerchantParam;
import org.dromara.daxpay.service.merchant.result.info.MerchantResult;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
/**
* 商户配置
* @author xxm
* @since 2024/6/25
*/
@Validated
@Tag(name = "商户信息")
@ClientCode(DaxPayCode.Client.MERCHANT)
@RestController
@RequestMapping("/merchant")
@RequestGroup(groupCode = "MerchantInfo", groupName = "商户信息", moduleCode = "merchant", moduleName = "(DaxPay)商户管理")
@RequiredArgsConstructor
public class MerchantController {
private final MerchantInfoService merchantInfoService;
@RequestPath("获取商户信息")
@Operation(summary = "获取商户信息")
@GetMapping("/get")
public Result<MerchantResult> getMerchant(){
return Res.ok(merchantInfoService.getMerchant());
}
@RequestPath("修改商户信息")
@Operation(summary = "修改商户信息")
@OperateLog(title = "修改商户信息", businessType = OperateLog.BusinessType.UPDATE, saveParam = true)
@PostMapping("/update")
public Result<Void> update(@RequestBody @Validated(ValidationGroup.edit.class) MerchantParam param){
merchantInfoService.update(param);
return Res.ok();
}
}

View File

@@ -0,0 +1,125 @@
package org.dromara.daxpay.server.controller.merchant.info;
import cn.bootx.platform.core.annotation.ClientCode;
import cn.bootx.platform.core.annotation.OperateLog;
import cn.bootx.platform.core.annotation.RequestGroup;
import cn.bootx.platform.core.annotation.RequestPath;
import cn.bootx.platform.core.rest.Res;
import cn.bootx.platform.core.rest.param.PageParam;
import cn.bootx.platform.core.rest.result.PageResult;
import cn.bootx.platform.core.rest.result.Result;
import cn.bootx.platform.core.validation.ValidationGroup;
import cn.bootx.platform.iam.param.user.RestartPwdBatchParam;
import cn.bootx.platform.iam.param.user.RestartPwdParam;
import cn.bootx.platform.iam.param.user.UserInfoParam;
import cn.bootx.platform.iam.result.user.UserInfoResult;
import org.dromara.daxpay.service.merchant.param.info.MerchantUserQuery;
import org.dromara.daxpay.service.merchant.result.info.MerchantUserResult;
import org.dromara.daxpay.service.merchant.service.info.MerchantUserService;
import org.dromara.daxpay.service.common.code.DaxPayCode;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.dromara.core.trans.anno.TransMethodResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 商户用户管理
* @author xxm
* @since 2024/8/23
*/
@ClientCode({DaxPayCode.Client.ADMIN})
@Tag(name = "商户用户管理")
@RestController
@RequestGroup(groupCode = "MerchantUser", groupName = "商户用户管理", moduleCode = "merchant")
@RequestMapping("/merchant/user")
@RequiredArgsConstructor
public class MerchantUserController {
private final MerchantUserService merchantUserService;
@TransMethodResult
@RequestPath("分页")
@Operation(summary = "分页")
@GetMapping("/page")
public Result<PageResult<MerchantUserResult>> page(PageParam pageParam, MerchantUserQuery query) {
return Res.ok(merchantUserService.page(pageParam, query));
}
@RequestPath("根据用户id查询用户 ")
@Operation(summary = "根据用户id查询用户")
@GetMapping("/findById")
public Result<UserInfoResult> findById(@NotNull(message = "主键不可为空") Long id) {
return Res.ok(merchantUserService.findById(id));
}
@RequestPath("修改用户")
@Operation(summary = "修改用户")
@PostMapping("/update")
@OperateLog(title = "修改用户", businessType = OperateLog.BusinessType.UPDATE, saveParam = true)
public Result<Void> update(@RequestBody @Validated(ValidationGroup.edit.class) UserInfoParam userInfoParam) {
merchantUserService.update(userInfoParam);
return Res.ok();
}
@RequestPath("重置密码")
@Operation(summary = "重置密码")
@PostMapping("/restartPassword")
@OperateLog(title = "重置密码", businessType = OperateLog.BusinessType.UPDATE)
public Result<Void> restartPassword(@RequestBody @Validated RestartPwdParam param) {
merchantUserService.restartPassword(param.getUserId(), param.getNewPassword());
return Res.ok();
}
@RequestPath("批量重置密码")
@Operation(summary = "批量重置密码")
@PostMapping("/restartPasswordBatch")
@OperateLog(title = "批量重置密码", businessType = OperateLog.BusinessType.UPDATE)
public Result<Void> restartPasswordBatch(@RequestBody @Validated RestartPwdBatchParam param) {
merchantUserService.restartPasswordBatch(param.getUserIds(), param.getNewPassword());
return Res.ok();
}
@RequestPath("封禁用户")
@Operation(summary = "封禁用户")
@PostMapping("/ban")
@OperateLog(title = "封禁用户", businessType = OperateLog.BusinessType.GRANT, saveParam = true)
public Result<Void> ban(@NotNull(message = "用户不可为空") Long userId) {
merchantUserService.ban(userId);
return Res.ok();
}
@RequestPath("批量封禁用户")
@Operation(summary = "批量封禁用户")
@PostMapping("/banBatch")
@OperateLog(title = "批量封禁用户", businessType = OperateLog.BusinessType.GRANT, saveParam = true)
public Result<Void> banBatch(@RequestBody @NotEmpty(message = "用户集合不可为空") List<Long> userIds) {
merchantUserService.banBatch(userIds);
return Res.ok();
}
@RequestPath("解锁用户")
@Operation(summary = "解锁用户")
@PostMapping("/unlock")
@OperateLog(title = "解锁用户", businessType = OperateLog.BusinessType.GRANT, saveParam = true)
public Result<Void> unlock(@NotNull(message = "用户不可为空") Long userId) {
merchantUserService.unlock(userId);
return Res.ok();
}
@RequestPath("批量解锁用户")
@Operation(summary = "批量解锁用户")
@PostMapping("/unlockBatch")
@OperateLog(title = "批量解锁用户", businessType = OperateLog.BusinessType.GRANT, saveParam = true)
public Result<Void> unlockBatch(@RequestBody @NotEmpty(message = "用户集合不可为空") List<Long> userIds) {
merchantUserService.unlockBatch(userIds);
return Res.ok();
}
}

View File

@@ -0,0 +1,79 @@
package org.dromara.daxpay.server.controller.merchant.report;
import cn.bootx.platform.core.annotation.ClientCode;
import cn.bootx.platform.core.annotation.RequestGroup;
import cn.bootx.platform.core.annotation.RequestPath;
import cn.bootx.platform.core.rest.Res;
import cn.bootx.platform.core.rest.result.Result;
import org.dromara.daxpay.service.common.code.DaxPayCode;
import org.dromara.daxpay.service.pay.param.report.TradeReportQuery;
import org.dromara.daxpay.service.pay.result.report.TradeReportResult;
import org.dromara.daxpay.service.pay.result.report.TradeStatisticsReport;
import org.dromara.daxpay.service.pay.service.report.IndexTradeReportService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* 首页交易报表
* @author xxm
* @since 2024/11/17
*/
@Validated
@Tag(name = "商户平台交易报表")
@RequestGroup(groupCode = "IndexTradeReport", groupName = "商户平台交易报表", moduleCode = "report",moduleName = "(DaxPay)统计报表")
@ClientCode(DaxPayCode.Client.MERCHANT)
@RestController
@RequestMapping("/merchant/report/index")
@RequiredArgsConstructor
public class IndexTradeReportMerchantController {
private final IndexTradeReportService tradeReportService;
@RequestPath("支付交易信息统计")
@Operation(summary = "支付交易信息统计")
@GetMapping("/pay")
public Result<TradeReportResult> pryTradeReport(TradeReportQuery query){
return Res.ok(tradeReportService.pryTradeReport(query));
}
@RequestPath("退款交易信息统计")
@Operation(summary = "退款交易信息统计")
@GetMapping("/refund")
public Result<TradeReportResult> refundTradeReport(TradeReportQuery query){
return Res.ok(tradeReportService.refundTradeReport(query));
}
@RequestPath("支付交易通道统计")
@Operation(summary = "支付交易通道统计")
@GetMapping("/payChannel")
public Result<List<TradeReportResult>> payChannelReport(TradeReportQuery query){
return Res.ok(tradeReportService.payChannelReport(query));
}
@RequestPath("退款交易通道统计")
@Operation(summary = "退款交易通道统计")
@GetMapping("/refundChannel")
public Result<List<TradeReportResult>> refundChannelReport(TradeReportQuery query){
return Res.ok(tradeReportService.refundChannelReport(query));
}
@RequestPath("支付交易方式统计")
@Operation(summary = "支付交易方式统计")
@GetMapping("/payMethod")
public Result<List<TradeReportResult>> payMethodReport(TradeReportQuery query){
return Res.ok(tradeReportService.payMethodReport(query));
}
@RequestPath("交易统计报表")
@Operation(summary = "交易统计报表")
@GetMapping("/tradeStatisticsReport")
public Result<List<TradeStatisticsReport>> tradeStatisticsReport(TradeReportQuery query){
return Res.ok(tradeReportService.tradeStatistics(query));
}
}

View File

@@ -0,0 +1,19 @@
package org.dromara.daxpay.server.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 商户角色枚举
* @author xxm
* @since 2025/1/4
*/
@Getter
@AllArgsConstructor
public enum UserRoleEnum {
MERCHANT_ROLE("merchant_admin","商户管理员"),
;
private final String code;
private final String name;
}

View File

@@ -0,0 +1,81 @@
package org.dromara.daxpay.server.event;
import cn.bootx.platform.starter.redis.delay.annotation.DelayEventListener;
import cn.bootx.platform.starter.redis.delay.annotation.DelayJobEvent;
import org.dromara.daxpay.core.context.PaymentReqInfoLocal;
import org.dromara.daxpay.core.enums.MerchantNotifyTypeEnum;
import org.dromara.daxpay.service.common.code.DaxPayCode;
import org.dromara.daxpay.service.pay.common.local.PaymentContextLocal;
import org.dromara.daxpay.service.pay.dao.notice.callback.MerchantCallbackTaskManager;
import org.dromara.daxpay.service.pay.dao.notice.notify.MerchantNotifyTaskManager;
import org.dromara.daxpay.service.pay.service.assist.PaymentAssistService;
import org.dromara.daxpay.service.merchant.service.config.MerchantNotifyConfigService;
import org.dromara.daxpay.service.pay.service.notice.callback.MerchantCallbackSendService;
import org.dromara.daxpay.service.pay.service.notice.notify.MerchantNotifySendService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.Objects;
/**
* 商户通知事件服务类
* @author xxm
* @since 2024/8/18
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class MerchantNoticeEventService {
private final MerchantNotifySendService merchantNotifySendService;
private final MerchantCallbackSendService merchantCallbackSendService;
private final MerchantNotifyTaskManager merchantNotifyTaskManager;
private final MerchantCallbackTaskManager merchantCallbackTaskManager;
private final PaymentAssistService paymentAssistService;
private final MerchantNotifyConfigService merchantNotifyConfigService;
/**
* 接受商户通知发送任务的延时消息
*/
@DelayEventListener(DaxPayCode.Event.MERCHANT_NOTIFY_SENDER)
public void NotifyTaskReceiveJob(DelayJobEvent<Long> event){
// 获取任务
Long taskId = event.getMessage();
var taskOpt = merchantNotifyTaskManager.findByIdNotTenant(taskId);
if (taskOpt.isPresent()){
var task = taskOpt.get();
paymentAssistService.initMchAndApp(task.getMchNo(), task.getAppId());
PaymentReqInfoLocal reqInfo = PaymentContextLocal.get().getReqInfo();
// 判断通知方式是否为http并且订阅了该类型的通知
boolean subscribe = merchantNotifyConfigService.getSubscribeByAppIdAndType(reqInfo.getAppId(), task.getNotifyType());
if (Objects.equals(reqInfo.getNotifyType(), MerchantNotifyTypeEnum.HTTP.getCode()) && subscribe){
merchantNotifySendService.sendData(task, reqInfo.getNotifyUrl(), LocalDateTime.now(), true);
} else {
log.info("商户消息通知未开启任务ID{}",taskId);
}
} else {
log.error("商户消息通知发送任务不存在任务ID{}",taskId);
}
}
/**
* 接受商户回调消息发送任务的延时消息
*/
@DelayEventListener(DaxPayCode.Event.MERCHANT_CALLBACK_SENDER)
public void callbackReceiveJob(DelayJobEvent<Long> event){
// 获取任务
Long taskId = event.getMessage();
var taskOpt = merchantCallbackTaskManager.findByIdNotTenant(taskId);
if (taskOpt.isPresent()){
var task = taskOpt.get();
paymentAssistService.initMchAndApp(task.getMchNo(), task.getAppId());
merchantCallbackSendService.sendData(task,true);
} else {
log.error("商户回调发送任务不存在任务ID{}",taskId);
}
}
}

View File

@@ -0,0 +1,101 @@
package org.dromara.daxpay.server.event;
import cn.bootx.platform.starter.redis.delay.annotation.DelayEventListener;
import cn.bootx.platform.starter.redis.delay.annotation.DelayJobEvent;
import org.dromara.daxpay.core.enums.PayStatusEnum;
import org.dromara.daxpay.core.enums.RefundStatusEnum;
import org.dromara.daxpay.core.enums.TransferStatusEnum;
import org.dromara.daxpay.service.common.code.DaxPayCode;
import org.dromara.daxpay.service.pay.dao.order.pay.PayOrderManager;
import org.dromara.daxpay.service.pay.dao.order.refund.RefundOrderManager;
import org.dromara.daxpay.service.pay.dao.order.transfer.TransferOrderManager;
import org.dromara.daxpay.service.pay.entity.order.pay.PayOrder;
import org.dromara.daxpay.service.pay.service.assist.PaymentAssistService;
import org.dromara.daxpay.service.pay.service.trade.pay.PayCloseService;
import org.dromara.daxpay.service.pay.service.trade.pay.PaySyncService;
import org.dromara.daxpay.service.pay.service.trade.refund.RefundSyncService;
import org.dromara.daxpay.service.pay.service.trade.transfer.TransferSyncService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.Optional;
/**
* 订单交易相关的延时事件
* @author xxm
* @since 2024/8/16
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class TradeOrderEventService {
private final PaymentAssistService paymentAssistService;
private final PayOrderManager payOrderManager;
private final PaySyncService paySyncService;
private final RefundSyncService refundSyncService;
private final RefundOrderManager refundOrderManager;
private final TransferOrderManager transferOrderManager;
private final TransferSyncService transferSyncService;
private final PayCloseService payCloseService;
/**
* 接收订单超时事件, 发起同步
*/
@DelayEventListener(DaxPayCode.Event.ORDER_PAY_TIMEOUT)
public void payExpired(DelayJobEvent<Long> event) {
Optional<PayOrder> orderOpt = payOrderManager.findByIdNotTenant(event.getMessage());
if (orderOpt.isPresent()) {
PayOrder payOrder = orderOpt.get();
// 不是支付中不需要进行同步
if (payOrder.getStatus().equals(PayStatusEnum.PROGRESS.getCode())) {
paymentAssistService.initMchAndApp(payOrder.getMchNo(),payOrder.getAppId());
paySyncService.syncPayOrder(payOrder);
}
// 待支付走超时关闭
if (payOrder.getStatus().equals(PayStatusEnum.WAIT.getCode()) ) {
paymentAssistService.initMchAndApp(payOrder.getMchNo(),payOrder.getAppId());
payCloseService.closeOrder(payOrder,false);
}
}
}
/**
* 接收退款订单同步事件
*/
@DelayEventListener(DaxPayCode.Event.ORDER_REFUND_SYNC)
public void refundDelaySync(DelayJobEvent<Long> event) {
var orderOpt = refundOrderManager.findByIdNotTenant(event.getMessage());
if (orderOpt.isPresent()) {
var order = orderOpt.get();
// 不是退款中不需要进行同步
if (order.getStatus().equals(RefundStatusEnum.PROGRESS.getCode())) {
paymentAssistService.initMchAndApp(order.getMchNo(), order.getAppId());
refundSyncService.syncRefundOrder(order);
}
}
}
/**
* 接收转账订单超时事件
*/
@DelayEventListener(DaxPayCode.Event.ORDER_TRANSFER_SYNC)
public void TransferDelaySync(DelayJobEvent<Long> event) {
var orderOpt = transferOrderManager.findByIdNotTenant(event.getMessage());
if (orderOpt.isPresent()) {
var order = orderOpt.get();
// 不是退款中不需要进行同步
if (order.getStatus().equals(TransferStatusEnum.PROGRESS.getCode())) {
paymentAssistService.initMchAndApp(order.getMchNo(), order.getAppId());
transferSyncService.syncTransferOrder(order);
}
}
}
}

View File

@@ -0,0 +1,64 @@
package org.dromara.daxpay.server.filter;
import cn.bootx.platform.core.entity.UserDetail;
import cn.bootx.platform.iam.service.client.ClientCodeService;
import cn.bootx.platform.starter.auth.util.SecurityUtil;
import org.dromara.daxpay.service.merchant.service.info.MerchantUserService;
import org.dromara.daxpay.service.common.code.DaxPayCode;
import org.dromara.daxpay.service.merchant.local.MchContextLocal;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.web.servlet.filter.OrderedFilter;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.util.Optional;
/**
* 商户信息过滤器
* @author xxm
* @since 2024/7/17
*/
@Component
@RequiredArgsConstructor
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
public class MchContextLocalFilter extends OncePerRequestFilter implements OrderedFilter {
private final MerchantUserService merchantUserService;
private final ClientCodeService clientCodeService;
/**
* 需要晚于 {@link org.springframework.web.filter.RequestContextFilter} 执行, 否则获取不到登录用户
* RequestContextFilter 默认加载优先级 为 - 150
*/
@Override
public int getOrder() {
return 0;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
// 只处理商户端
String clientCode = clientCodeService.getClientCode();
if (!DaxPayCode.Client.MERCHANT.equals(clientCode)) {
return;
}
// 是否登录
Optional<UserDetail> currentUser = SecurityUtil.getCurrentUser();
currentUser.ifPresent(userDetail -> {
// 登录后获取关联商户号
String mchNo = merchantUserService.findByUserId(userDetail.getId());
MchContextLocal.setMchNo(mchNo);
});
} finally {
filterChain.doFilter(request,response);
MchContextLocal.clearMchNo();
}
}
}

View File

@@ -0,0 +1,57 @@
package org.dromara.daxpay.server.handler;
import cn.bootx.platform.core.annotation.ClientCode;
import cn.bootx.platform.iam.service.client.ClientCodeService;
import cn.bootx.platform.starter.auth.exception.RouterCheckException;
import cn.bootx.platform.starter.auth.service.RouterCheck;
import cn.hutool.core.collection.CollUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import java.util.Objects;
/**
* 终端路径过滤
* @author xxm
* @since 2025/1/31
*/
@Component
@RequiredArgsConstructor
public class ClientRouterCheck implements RouterCheck {
private final ClientCodeService clientCodeService;
@Override
public int sortNo() {
return Integer.MIN_VALUE;
}
@Override
public boolean check(Object handler) {
// 获取当前终端编码
String clientCode = clientCodeService.getClientCode();
// 判断当前编码是否包含在白名单中
if (handler instanceof HandlerMethod handlerMethod) {
// controller上是否加了跳过鉴权注解
var clientCodeEnum = handlerMethod.getBeanType().getAnnotation(ClientCode.class);
if (Objects.isNull(clientCodeEnum)) {
// 方法上上是否加了跳过鉴权注解
clientCodeEnum = handlerMethod.getMethodAnnotation(ClientCode.class);
}
else {
// controller和方法上都加了跳过鉴权注解,以方法上为准
var annotation = handlerMethod.getMethodAnnotation(ClientCode.class);
if (Objects.nonNull(annotation)) {
clientCodeEnum = annotation;
}
}
// 判断是否包含了当前终端编码
if (Objects.nonNull(clientCodeEnum)){
if (!CollUtil.toList(clientCodeEnum.value()).contains(clientCode)){
throw new RouterCheckException("该终端没有权限访问该路径");
}
}
}
return false;
}
}

View File

@@ -0,0 +1,95 @@
package org.dromara.daxpay.server.handler;
import cn.bootx.platform.common.mybatisplus.util.MpUtil;
import cn.bootx.platform.iam.service.client.ClientCodeService;
import org.dromara.daxpay.service.common.code.DaxPayCode;
import org.dromara.daxpay.service.merchant.local.MchContextLocal;
import org.dromara.daxpay.service.pay.common.entity.MchAppBaseEntity;
import org.dromara.daxpay.service.pay.common.entity.MchAppEditEntity;
import org.dromara.daxpay.service.pay.common.entity.MchAppRecordEntity;
import cn.hutool.core.util.ClassUtil;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import lombok.RequiredArgsConstructor;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.StringValue;
import net.sf.jsqlparser.schema.Column;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;
/**
* 租户拦截处理器, 处理代理商和商户数据隔离
* @author xxm
* @since 2024/6/25
*/
@Component
@RequiredArgsConstructor
public class CustomTenantLineHandler implements TenantLineHandler {
private final ClientCodeService clientCodeService;
/**
* 获取租户ID
* 商户返回商户号
*/
@Override
public Expression getTenantId() {
// 获取当前用户的商户
String mchNo = Optional.ofNullable(MchContextLocal.getMchNo()).orElse("");
// 获取商户
return new StringValue(mchNo);
}
/**
* 租户字段
* 商户返回商户号
* 代理商返回代理商号
*/
@Override
public String getTenantIdColumn() {
// 商户端和支付网关以及其他都使用商户号
return "mch_no";
}
/**
* 是否忽略租户拦截, 运营端不需要进行数据隔离数据隔离, 其他端各自进行处理
*/
@Override
public boolean ignoreTable(String tableName) {
// 运营端不需要进行数据隔离数据隔离
String clientCode = clientCodeService.getClientCode();
if (Objects.equals(clientCode, DaxPayCode.Client.ADMIN)){
return true;
}
// 商户端和网关端以及其他端都是用商户隔离机制
return ignoreTableByMch(tableName);
}
/**
* 忽略插入租户字段逻辑
*/
@Override
public boolean ignoreInsert(List<Column> columns, String tenantIdColumn) {
// 如果租户字段已经存在不进行处理
return TenantLineHandler.super.ignoreInsert(columns, tenantIdColumn);
}
/**
* 商户权限控制
*/
public boolean ignoreTableByMch(String tableName){
// 读取注解, 判断是否启用商户数据隔离
TableInfo tableInfo = MpUtil.getTableInfo(tableName);
if (tableInfo == null){
return true;
}
// 如果为 MchAppBaseEntity、MchAppEditEntity、MchAppRecordEntity 子类, 不可以忽略过滤
boolean anyMatch = Stream.of(MchAppBaseEntity.class, MchAppEditEntity.class, MchAppRecordEntity.class)
.anyMatch(entityClass -> ClassUtil.isAssignable(entityClass, tableInfo.getEntityType()));
return !anyMatch;
}
}

View File

@@ -0,0 +1,29 @@
package org.dromara.daxpay.server.handler;
import cn.bootx.platform.common.mybatisplus.interceptor.MpInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 商户数据隔离插件
* @author xxm
* @since 2024/6/25
*/
@Configuration
@RequiredArgsConstructor
public class MchTenantInterceptorConfiguration {
private final CustomTenantLineHandler customTenantHandler;
/**
* 租户拦截器
*/
@Bean
public MpInterceptor MchTenantInterceptor() {
TenantLineInnerInterceptor tenantInterceptor = new TenantLineInnerInterceptor();
tenantInterceptor.setTenantLineHandler(customTenantHandler);
return new MpInterceptor(tenantInterceptor, -999);
}
}

View File

@@ -0,0 +1,23 @@
package org.dromara.daxpay.server.interceptor;
import cn.bootx.platform.common.mybatisplus.interceptor.MpInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.DataPermissionInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
*
* @author xxm
* @since 2025/2/2
*/
@Configuration
public class DataScopeInterceptorConfiguration {
/**
* 数据范围权限插件
*/
@Bean
public MpInterceptor dataPermInterceptorMp(MchDataScopeHandler dataScopeInterceptor) {
return new MpInterceptor(new DataPermissionInterceptor(dataScopeInterceptor),-99);
}
}

View File

@@ -0,0 +1,33 @@
package org.dromara.daxpay.server.interceptor;
import com.baomidou.mybatisplus.extension.plugins.handler.MultiDataPermissionHandler;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.schema.Table;
import org.springframework.stereotype.Service;
/**
* 支持多表的数据权限处理器, 每个表都会追加条件
* @author xxm
* @since 2025/2/2
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class MchDataScopeHandler implements MultiDataPermissionHandler {
/**
* 语句拼装
* @param table 所执行的数据库表信息,可以通过此参数获取表名和表别名
* @param where 原有的 where 条件信息
* @param mappedStatementId Mybatis MappedStatement Id 根据该参数可以判断具体执行方法
* @return 追加的 where 条件信息
*/
@Override
public Expression getSqlSegment(Table table, Expression where, String mappedStatementId) {
return null;
// AND (hello = 1) 格式的SQL语句
// return new ParenthesedExpressionList<>(new EqualsTo(new Column("1"), new LongValue(1L)));
}
}

View File

@@ -0,0 +1,182 @@
package org.dromara.daxpay.server.service.admin;
import cn.bootx.platform.common.mybatisplus.util.MpUtil;
import cn.bootx.platform.core.exception.BizException;
import cn.bootx.platform.core.exception.DataNotExistException;
import cn.bootx.platform.core.exception.ValidationFailedException;
import cn.bootx.platform.core.rest.param.PageParam;
import cn.bootx.platform.core.rest.result.PageResult;
import cn.bootx.platform.iam.dao.role.RoleManager;
import cn.bootx.platform.iam.entity.role.Role;
import cn.bootx.platform.iam.entity.user.UserInfo;
import cn.bootx.platform.iam.param.user.UserInfoParam;
import cn.bootx.platform.iam.service.client.ClientCodeService;
import cn.bootx.platform.iam.service.upms.UserRoleService;
import cn.bootx.platform.iam.service.user.UserAdminService;
import org.dromara.daxpay.core.enums.MerchantNotifyTypeEnum;
import org.dromara.daxpay.core.enums.MerchantStatusEnum;
import org.dromara.daxpay.core.enums.SignTypeEnum;
import org.dromara.daxpay.core.exception.ConfigNotExistException;
import org.dromara.daxpay.core.exception.OperationFailException;
import org.dromara.daxpay.server.enums.UserRoleEnum;
import org.dromara.daxpay.service.merchant.convert.info.MerchantConvert;
import org.dromara.daxpay.service.merchant.dao.app.MchAppManager;
import org.dromara.daxpay.service.merchant.dao.info.MerchantManager;
import org.dromara.daxpay.service.merchant.dao.info.MerchantUserManager;
import org.dromara.daxpay.service.merchant.entity.app.MchApp;
import org.dromara.daxpay.service.merchant.entity.info.Merchant;
import org.dromara.daxpay.service.merchant.entity.info.MerchantUser;
import org.dromara.daxpay.service.merchant.param.info.MerchantCreateParam;
import org.dromara.daxpay.service.merchant.param.info.MerchantParam;
import org.dromara.daxpay.service.merchant.param.info.MerchantQuery;
import org.dromara.daxpay.service.merchant.result.info.MerchantResult;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.util.RandomUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.util.Collections;
/**
* 商户服务类
* @author xxm
* @since 2024/5/27
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class MerchantAdminService {
private final MerchantManager merchantManager;
private final MchAppManager mchAppManager;
private final UserAdminService userAdminService;
private final RoleManager roleManager;
private final UserRoleService userRoleService;
private final MerchantUserManager merchantUserManager;
private final ClientCodeService clientCodeService;
/**
* 添加商户
*/
@Transactional(rollbackFor = Exception.class)
public void add(MerchantCreateParam param) {
var merchant = MerchantConvert.CONVERT.toEntity(param);
merchant.setMchNo(this.getMchNo());
merchant.setStatus(MerchantStatusEnum.ENABLE.getCode());
// 创建商户管理员
this.createMerchantAdmin(param, merchant);
merchantManager.save(merchant);
// 是否创建创建默认应用
if (param.getCreateDefaultApp()){
MchApp mchApp = new MchApp()
.setAppName("默认应用")
.setLimitAmount(BigDecimal.valueOf(2000))
.setReqTimeout(false)
.setReqTimeoutSecond(30)
.setOrderTimeout(30)
.setSignType(SignTypeEnum.HMAC_SHA256.getCode())
.setSignSecret(RandomUtil.randomString(32))
.setReqSign(true)
.setNotifyType(MerchantNotifyTypeEnum.NONE.getCode())
.setStatus(MerchantStatusEnum.ENABLE.getCode());
mchApp.setAppId(this.generateAppId())
.setMchNo(merchant.getMchNo());
mchAppManager.save(mchApp);
}
}
/**
* 修改
*/
public void update(MerchantParam param) {
Merchant merchant = merchantManager.findById(param.getId()).orElseThrow(DataNotExistException::new);
BeanUtil.copyProperties(param, merchant, CopyOptions.create().ignoreNullValue());
merchantManager.updateById(merchant);
}
/**
* 分页
*/
public PageResult<MerchantResult> page(PageParam pageParam, MerchantQuery query) {
return MpUtil.toPageResult(merchantManager.page(pageParam,query));
}
/**
* 获取单条
*/
public MerchantResult findById(Long id) {
return merchantManager.findById(id).map(Merchant::toResult).orElseThrow(DataNotExistException::new);
}
/**
* 删除
*/
public void delete(Long id) {
if (true){
throw new OperationFailException("商户不允许删除");
}
Merchant merchant = merchantManager.findById(id).orElseThrow(DataNotExistException::new);
// 创建管理员后不可以被删除
if (merchant.isAdministrator()){
throw new ValidationFailedException("已存在关联商户管理员用户, 不允许删除");
}
// 判断是否存在应用APP, 存在不允许删除
if (mchAppManager.existByMchNo(merchant.getMchNo())){
throw new ValidationFailedException("商户下存在应用, 不允许删除");
}
merchantManager.deleteById(id);
}
/**
* 创建商户管理员
*/
private void createMerchantAdmin(MerchantCreateParam param, Merchant merchant){
// 创建用户
UserInfoParam userInfoParam = new UserInfoParam();
BeanUtil.copyProperties(param, userInfoParam);
UserInfo userInfo = userAdminService.add(userInfoParam);
// 查询普通商户管理员角色
Role role = roleManager.findByCode(UserRoleEnum.MERCHANT_ROLE.getCode())
.orElseThrow(() -> new ConfigNotExistException("商户管理员角色不存在, 请检查"));
// 分配角色
userRoleService.saveAssign(userInfo.getId(), Collections.singletonList(role.getId()),true);
// 创建商户绑定关系
merchantUserManager.save(new MerchantUser(userInfo.getId(), merchant.getMchNo(),true));
// 商户信息更新
merchant.setAdministrator(true)
.setAdminUserId(userInfo.getId());
merchantManager.updateById(merchant);
}
/**
* 生成商户号
*/
private String getMchNo(){
String mchNo = "M" + System.currentTimeMillis();
for (int i = 0; i < 10; i++){
if (!merchantManager.existedByField(Merchant::getMchNo, mchNo)){
return mchNo;
}
mchNo = "M" + System.currentTimeMillis();
}
throw new BizException("商户号生成失败");
}
/**
* 生成应用号
*/
private String generateAppId() {
String appId = "A"+RandomUtil.randomNumbers(16);
for (int i = 0; i < 10; i++){
if (!mchAppManager.existsByAppId(appId)){
return appId;
}
appId = "A"+ RandomUtil.randomNumbers(16);
}
throw new BizException("应用号生成失败");
}
}

View File

@@ -1,9 +1,8 @@
package org.dromara.daxpay.server.service;
package org.dromara.daxpay.server.service.common;
import cn.bootx.platform.iam.service.client.ClientCodeService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.daxpay.service.code.DaxPayCode;
import org.springframework.stereotype.Service;
/**
@@ -16,8 +15,4 @@ import org.springframework.stereotype.Service;
@RequiredArgsConstructor
public class ClientCodeServiceImpl implements ClientCodeService {
@Override
public String getClientCode() {
return DaxPayCode.Client.ADMIN;
}
}

View File

@@ -0,0 +1,122 @@
package org.dromara.daxpay.server.service.common;
import cn.bootx.platform.core.exception.ValidationFailedException;
import cn.bootx.platform.iam.service.client.ClientCodeService;
import org.dromara.daxpay.core.context.PaymentReqInfoLocal;
import org.dromara.daxpay.service.common.code.DaxPayCode;
import org.dromara.daxpay.service.common.service.config.PlatformConfigService;
import org.dromara.daxpay.service.merchant.cache.MchAppCacheService;
import org.dromara.daxpay.service.merchant.cache.MerchantCacheService;
import org.dromara.daxpay.service.merchant.entity.app.MchApp;
import org.dromara.daxpay.service.merchant.entity.info.Merchant;
import org.dromara.daxpay.service.merchant.local.MchContextLocal;
import org.dromara.daxpay.service.pay.common.local.PaymentContextLocal;
import org.dromara.daxpay.service.pay.service.assist.PaymentAssistService;
import cn.hutool.core.bean.BeanUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.Objects;
/**
* 支付网关端交易(支付、退款等各类操作)支持服务接口
* @author xxm
* @since 2024/12/21
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class PaymentAssistServiceImpl implements PaymentAssistService {
private final ClientCodeService clientCodeService;
private final PlatformConfigService platformConfigService;
private final MerchantCacheService merchantCacheService;
private final MchAppCacheService mchAppCacheService;
/**
* 初始化商户和应用等相关信息
* 1. 统一支付相关接口调用时,要进行初始化
* 2. 接收到回调时,要进行初始化
* 3. 接收到消息通知时, 要进行初始化
* 4. 手动发起根据订单记录发起一些操作时, 读取信息进行初始化
* 5. 针对核心能力进行包装成功能时(收银台), 手动进行初始化
*/
public void initMchAndApp(String mchNo, String appId) {
// 商户端商户号读取系统, 不允许自行设置
if (Objects.equals(clientCodeService.getClientCode(), DaxPayCode.Client.MERCHANT)){
mchNo = MchContextLocal.getMchNo();
}
// 获取应用信息
Merchant merchant = merchantCacheService.get(mchNo);
MchApp mchApp = mchAppCacheService.get(appId);
this.initData(merchant, mchApp);
}
/**
* 初始化商户和应用信息
* 1. 统一支付相关接口调用时,要进行初始化
* 2. 接收到回调时,要进行初始化
* 3. 接收到消息通知时, 要进行初始化
* 4. 手动发起根据订单记录发起一些操作时, 读取信息进行初始化
* 5. 针对核心能力进行包装成功能时(收银台), 手动进行初始化
*/
public void initMchAndApp(String appId) {
// 获取应用信息
var mchApp = mchAppCacheService.get(appId);
// 商户端商户号读取系统, 不允许自行设置
if (Objects.equals(clientCodeService.getClientCode(), DaxPayCode.Client.MERCHANT)){
if (!Objects.equals(mchApp.getMchNo(), MchContextLocal.getMchNo())){
throw new ValidationFailedException("该商户不拥有该应用");
}
}
Merchant merchant = merchantCacheService.get(mchApp.getMchNo());
this.initData(merchant, mchApp);
}
/**
* 初始化数据,
* 1.商户信息
* 2.应用信息
* 3.服务商信息
* 4.代理商信息
* 5.平台配置信息
*/
private void initData(Merchant merchant, MchApp mchApp){
// 判断是否匹配
if (!Objects.equals(mchApp.getMchNo(), merchant.getMchNo())){
throw new ValidationFailedException("商户号和应用号不匹配");
}
// 初始化支付上下文信息
PaymentReqInfoLocal reqInfo = PaymentContextLocal.get().getReqInfo();
BeanUtil.copyProperties(mchApp, reqInfo);
// 商户信息
reqInfo.setMchName(merchant.getMchName())
.setMchNo(merchant.getMchNo())
.setMchStatus(merchant.getStatus());
// 平台信息
var basicConfig = platformConfigService.getBasicConfig();
var urlConfig = platformConfigService.getUrlConfig();
reqInfo.setGatewayServiceUrl(urlConfig.getGatewayServiceUrl())
.setGatewayH5Url(urlConfig.getGatewayH5Url());
// 支付限额
if (basicConfig.getSingleLimitAmount() != null){
// 如果应用为空读取平台配置
if (reqInfo.getLimitAmount() == null){
reqInfo.setLimitAmount(basicConfig.getSingleLimitAmount());
} else {
// 都不为空读取小的
reqInfo.setLimitAmount(reqInfo.getLimitAmount().min(basicConfig.getSingleLimitAmount()));
}
}
// 初始化商户租户上下文信息
MchContextLocal.setMchNo(merchant.getMchNo());
}
}

View File

@@ -0,0 +1,308 @@
package org.dromara.daxpay.server.task;
import cn.bootx.platform.core.util.DateTimeUtil;
import org.dromara.daxpay.core.enums.PayStatusEnum;
import org.dromara.daxpay.service.pay.dao.order.pay.PayOrderManager;
import org.dromara.daxpay.service.pay.dao.order.refund.RefundOrderManager;
import org.dromara.daxpay.service.pay.dao.order.transfer.TransferOrderManager;
import org.dromara.daxpay.service.pay.entity.order.pay.PayOrder;
import org.dromara.daxpay.service.pay.entity.order.refund.RefundOrder;
import org.dromara.daxpay.service.pay.entity.order.transfer.TransferOrder;
import org.dromara.daxpay.service.pay.service.assist.PaymentAssistService;
import org.dromara.daxpay.service.pay.service.trade.pay.PayAssistService;
import org.dromara.daxpay.service.pay.service.trade.pay.PayCloseService;
import org.dromara.daxpay.service.pay.service.trade.pay.PaySyncService;
import org.dromara.daxpay.service.pay.service.trade.refund.RefundSyncService;
import org.dromara.daxpay.service.pay.service.trade.transfer.TransferSyncService;
import cn.hutool.core.date.LocalDateTimeUtil;
import com.baomidou.lock.LockInfo;
import com.baomidou.lock.LockTemplate;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Objects;
/**
* 交易订单同步定时任务
* @author xxm
* @since 2024/8/29
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class OrderSyncTask {
private final PayOrderManager payOrderManager;
private final PaySyncService paySyncService;
private final RefundOrderManager refundOrderManager;
private final RefundSyncService refundSyncService;
private final TransferOrderManager transferOrderManager;
private final TransferSyncService transferSyncService;
private final PaymentAssistService paymentAssistService;
private final PayAssistService payAssistService;
private final PayCloseService payCloseService;
private final LockTemplate lockTemplate;
/**
* 支付单超时检测 一分钟一次
*/
@Scheduled(cron = "0 */1 * * * ?")
public void queryExpiredTask(){
// 加锁
LockInfo lock = lockTemplate.lock("task:queryExpiredTask",30000,200);
if (Objects.isNull(lock)){
log.info("检测任务存在锁,其他节点可能在执行中");
return;
}
try {
// 从数据库查询获取超时的任务对象
List<PayOrder> payOrders = payOrderManager.queryExpiredOrderNotTenant();
for (PayOrder order : payOrders) {
try {
paymentAssistService.initMchAndApp(order.getMchNo(),order.getAppId());
if (!List.of(PayStatusEnum.WAIT.getCode(),PayStatusEnum.TIMEOUT.getCode()).contains(order.getStatus())){
paySyncService.syncPayOrder(order);
} else {
// 判断是超时走关闭订单
if (Objects.nonNull(order.getExpiredTime()) && DateTimeUtil.ge(LocalDateTime.now(), order.getExpiredTime())) {
payCloseService.closeOrder(order,false);
}
}
} catch (Exception e) {
log.error("超时取消任务异常, ID: {}, 订单号: {}",order.getId(), order.getOrderNo(), e);
// 设置订单任务为失败
payAssistService.fail(order, e.getMessage());
}
}
} finally {
lockTemplate.releaseLock(lock);
}
}
/**
* 10分钟内一分钟一次
*/
@Scheduled(cron = "0 */1 * * * ?")
public void syncOrderBy10M(){
// 加锁
LockInfo lock = lockTemplate.lock("task:syncOrderBy10M",30000,200);
if (Objects.isNull(lock)){
log.info("检测任务存在锁,其他节点可能在执行中");
return;
}
try {
// 获取1分钟之前到10分钟之内的时间
var now = LocalDateTime.now();
var end = LocalDateTimeUtil.offset(now, -1, ChronoUnit.MINUTES);
var start = LocalDateTimeUtil.offset(now, -10, ChronoUnit.MINUTES);
List<PayOrder> orders = payOrderManager.queryExpiredOrderNotTenant(start, end);
this.syncPayOrder(orders);
} finally {
lockTemplate.releaseLock(lock);
}
}
/**
* 1小时内10分钟一次
*/
@Scheduled(cron = "0 */10 * * * ?")
public void syncPayOrderBy1H(){
// 加锁
LockInfo lock = lockTemplate.lock("task:syncPayOrderBy1H",30000,200);
if (Objects.isNull(lock)){
log.info("检测任务存在锁,其他节点可能在执行中");
return;
}
try {
// 获取 10分钟之前到1小时内的时间
var now = LocalDateTime.now();
var end = LocalDateTimeUtil.offset(now, -10, ChronoUnit.MINUTES);
var start = LocalDateTimeUtil.offset(now, -1, ChronoUnit.HOURS);
List<PayOrder> orders = payOrderManager.queryExpiredOrderNotTenant(start,end);
this.syncPayOrder(orders);
} finally {
lockTemplate.releaseLock(lock);
}
}
/**
* 1天内1小时一次
*/
@Scheduled(cron = "0 0 */1 * * ?")
public void syncPayOrderBy1D(){
// 加锁
LockInfo lock = lockTemplate.lock("task:syncPayOrderBy1D",30000,200);
if (Objects.isNull(lock)){
log.info("检测任务存在锁,其他节点可能在执行中");
return;
}
try {
// 获取 1小时之前到24小时内的时间
var now = LocalDateTime.now();
var end = LocalDateTimeUtil.offset(now, -1, ChronoUnit.HOURS);
var start = LocalDateTimeUtil.offset(now, -1, ChronoUnit.DAYS);
List<PayOrder> orders = payOrderManager.queryExpiredOrderNotTenant(start,end);
this.syncPayOrder(orders);
} finally {
lockTemplate.releaseLock(lock);
}
}
/**
* 支付订单同步检测
* 10分钟内一分钟一次
* 一小时内10分钟一次
* 一天内一小时一次
*/
public void syncPayOrder(List<PayOrder> payOrders){
for (PayOrder order : payOrders) {
try {
paymentAssistService.initMchAndApp(order.getMchNo(),order.getAppId());
if (!List.of(PayStatusEnum.WAIT.getCode(),PayStatusEnum.TIMEOUT.getCode()).contains(order.getStatus())){
paySyncService.syncPayOrder(order);
} else {
// 判断是超时走关闭订单
if (Objects.nonNull(order.getExpiredTime()) && DateTimeUtil.ge(LocalDateTime.now(), order.getExpiredTime())) {
payCloseService.closeOrder(order,false);
}
}
} catch (Exception e) {
log.error("超时取消任务异常, ID: {}, 订单号: {}",order.getId(), order.getOrderNo(), e);
// 设置订单任务为失败
payAssistService.fail(order, e.getMessage());
}
}
}
/**
* 10分钟内一分钟一次
*/
@Scheduled(cron = "0 */1 * * * ?")
public void refundSyncBy10M(){
// 加锁
LockInfo lock = lockTemplate.lock("task:refundSyncBy10M",30000,200);
if (Objects.isNull(lock)){
log.info("检测任务存在锁,其他节点可能在执行中");
return;
}
try {
// 获取1分钟之前到10分钟之内的时间
var now = LocalDateTime.now();
var end = LocalDateTimeUtil.offset(now, -1, ChronoUnit.MINUTES);
var start = LocalDateTimeUtil.offset(now, -10, ChronoUnit.MINUTES);
List<RefundOrder> list = refundOrderManager.findAllByProgress(start, end);
this.refundSync(list);
} finally {
lockTemplate.releaseLock(lock);
}
}
/**
* 一小时内10分钟一次
*/
@Scheduled(cron = "0 */10 * * * ?")
public void refundSyncBy1H(){
// 加锁
LockInfo lock = lockTemplate.lock("task:refundSyncBy1H",30000,200);
if (Objects.isNull(lock)){
log.info("检测任务存在锁,其他节点可能在执行中");
return;
}
try {
// 获取 10分钟之前到1小时内的时间
var now = LocalDateTime.now();
var end = LocalDateTimeUtil.offset(now, -10, ChronoUnit.MINUTES);
var start = LocalDateTimeUtil.offset(now, -1, ChronoUnit.HOURS);
List<RefundOrder> list = refundOrderManager.findAllByProgress(start, end);
this.refundSync(list);
} finally {
lockTemplate.releaseLock(lock);
}
}
/**
* 一天内一小时一次
*/
@Scheduled(cron = "0 5 */1 * * ?")
public void refundSyncBy1D(){
// 加锁
LockInfo lock = lockTemplate.lock("task:refundSyncBy1D",30000,200);
if (Objects.isNull(lock)){
log.info("检测任务存在锁,其他节点可能在执行中");
return;
}
try {
// 获取 1小时之前到24小时内的时间
var now = LocalDateTime.now();
var end = LocalDateTimeUtil.offset(now, -1, ChronoUnit.HOURS);
var start = LocalDateTimeUtil.offset(now, -1, ChronoUnit.DAYS);
List<RefundOrder> list = refundOrderManager.findAllByProgress(start, end);
this.refundSync(list);
} finally {
lockTemplate.releaseLock(lock);
}
}
/**
* 超过一天一天一次
*/
@Scheduled(cron = "0 0 1 * * ?")
public void refundSyncByAll(){
// 加锁
LockInfo lock = lockTemplate.lock("task:refundSyncByAll",30000,200);
if (Objects.isNull(lock)){
log.info("检测任务存在锁,其他节点可能在执行中");
return;
}
try {
// 获取 1天之前到1天内的时间
var now = LocalDateTime.now().plusDays(-1);
List<RefundOrder> list = refundOrderManager.findAllByBeforeProgress(now);
this.refundSync(list);
} finally {
lockTemplate.releaseLock(lock);
}
}
/**
* 退款定时同步任务
* 10分钟内一分钟一次
* 一小时内10分钟一次
* 一天内一小时一次
* 超过一天一天一次
*/
public void refundSync(List<RefundOrder> list){
// 查询退款中的退款订单
for (RefundOrder refundOrder : list) {
try {
// 调用同步方法
paymentAssistService.initMchAndApp(refundOrder.getMchNo(),refundOrder.getAppId());
refundSyncService.syncRefundOrder(refundOrder);
} catch (Exception e) {
log.warn("退款执行同步失败, ID: {}, 退款号: {}",refundOrder.getId(), refundOrder.getRefundNo(), e);
}
}
}
/**
* 转账订单同步, 一分钟一次, 获取一分钟之前转账中的订单
*/
@Scheduled(cron = "0 */1 * * * ?")
public void transferSyncTask(){
List<TransferOrder> list = transferOrderManager.findAllByProgress();
for (var transferOrder : list) {
try {
// 调用同步方法
paymentAssistService.initMchAndApp(transferOrder.getMchNo(),transferOrder.getAppId());
transferSyncService.syncTransferOrder(transferOrder);
} catch (Exception e) {
log.warn("转账执行同步失败, ID: {}, 转账号: {}",transferOrder.getId(),transferOrder.getTransferNo(), e);
}
}
}
}

View File

@@ -6,11 +6,21 @@ spring:
master:
# Postgresql连接
driver-class-name: org.postgresql.Driver
url: jdbc:postgresql://postgresql:5432/dax-pay-single?serverTimezone=Asia/Shanghai&autoReconnect=true&reWriteBatchedInserts=true
username: bootx
password: bootx123
url: jdbc:postgresql://${DB_HOST:postgresql}:${DB_PORT:5432}/dax-pay-dev?serverTimezone=Asia/Shanghai&autoReconnect=true&reWriteBatchedInserts=true
username: ${DB_USER:bootx}
password: ${DB_PASSWORD:bootx123}
# MySQL连接
# driver-class-name: com.mysql.cj.jdbc.Driver
# url: jdbc:mysql://${DB_HOST:mysql}:${DB_PORT:3306}/dax-pay-dev?serverTimezone=GMT%2B8&characterEncoding=utf8&allowMultiQueries=true&useSSL=false&nullCatalogMeansCurrent=true
# username: ${DB_USER:root}
# password: ${DB_PASSWORD:bootx123}
hikari:
keepalive-time: 300000
minimumIdle: 5 # 最小连接数
maximumPoolSize: 50 # 最大连接数
leak-detection-threshold: 5000 # 检测连接泄漏的时间阈值(毫秒)
connection-timeout: 30000 # 获取连接超时时间(毫秒)
idle-timeout: 600000 # 空闲连接超时时间(毫秒)
max-lifetime: 1800000 # 连接最大存活时间(毫秒)
data:
redis:
host: redis
@@ -68,26 +78,26 @@ bootx-platform:
- '/css/**'
- '/error'
- '/favicon.ico'
# 支付系统配置
dax-pay:
# 环境标识
env: DEV_
# 通常为两位内 机器码, 用于区分不同机器生成的流水号
machine-no: 70
dromara:
# 注意, 不要设置 domain 访问路径, 自行进行拼接访问路径, 来保证可迁移性
x-file-storage:
default-platform: local
# 使用Nginx映射到存储路径, 然后将nginx的地址设置到 bootx-platform.starter.file-upload.file-server-url参数
local-plus:
- platform: local
enable-storage: true
base-path: /file/ # 基础路径
storage-path: D:/data/files # 存储路径
# 将 minio访问地址+桶名称 进行组合, 然后设置到 bootx-platform.starter.file-upload.file-server-url
# 例如 minio地址 http://127.0.0.1:9001 桶名称 daxpay, 拼接后的地址为 http://127.0.0.1:9001/daxpay/
minio:
- platform: minio
enable-storage: true
access-key: yDAArSoyQAligC2IGf7C
secret-key: vDgpt5R4kR2UCapMzx32Rb6qZegok21dRsU6XJ1j
end-point: http://127.0.0.1:9002 # minio访问地址
bucket-name: daxpay # 存储桶名称
base-path: /file/ # 基础存储路径
default-platform: amazon-s3
# 文件存储都是用S3协议方式存储
amazon-s3:
- platform: amazon-s3 # 存储平台标识
enable-storage: true # 启用存储
access-key: ${OSS_SECRET_ID:lwnS2DEQzveX3fIpRJBP}
secret-key: ${OSS_SECRET_KEY:LcBdOJzqD57wSXK9hxGlrnCgmojNpWET4I2yaekf}
end-point: ${OSS_DOMAIN:http://192.168.1.229:9000} #文件存储服务地址, 不要添加/后缀,
bucket-name: ${OSS_BUKET:daxpay} # 桶名称,不要添加/后缀
base-path: file/ # 基础路径, 不要添加/前缀, 但需要添加/后缀
region: ${OSS_REGION:''}
attr:
# 开启路径样式访问, 不配置默认为二级域名方式
forcePathStyle: true

View File

@@ -0,0 +1,75 @@
spring:
datasource:
dynamic:
primary: master
datasource:
master:
# Postgresql连接
driver-class-name: org.postgresql.Driver
url: jdbc:postgresql://127.0.0.1:5432/dax-pay-plus?serverTimezone=Asia/Shanghai&autoReconnect=true&reWriteBatchedInserts=true
username: postgresql
password: postgresql
hikari:
minimumIdle: 5 # 最小连接数
maximumPoolSize: 50 # 最大连接数
leak-detection-threshold: 5000 # 检测连接泄漏的时间阈值(毫秒)
connection-timeout: 30000 # 获取连接超时时间(毫秒)
idle-timeout: 600000 # 空闲连接超时时间(毫秒)
max-lifetime: 1800000 # 连接最大存活时间(毫秒)
data:
redis:
host: 127.0.0.1
port: 6379
database: 13
password: bootx123
lettuce:
pool:
max-wait: 1000ms
# 开发时显示debug日志
logging:
level:
cn.bootx.**: info
cn.daxpay.**: info
# swagger 配置
knife4j:
# 是否开启Knife4j增强模式
enable: true
# 是否生产环境
production: true
# 基础脚手架配置
bootx-platform:
starter:
auth:
# 开启超级管理员
enable-admin: false
# 用户管理列表中是否显示超级管理员用户
admin-in-list: false
ignore-urls:
- '/actuator/**'
- '/token/**'
- '/ws/**'
- '/front/**'
- '/error'
- '/favicon.ico'
# 支付系统配置
dax-pay:
# 环境标识
env: DAX
# 通常为两位内 机器码, 用于区分不同机器生成的流水号
machine-no: 70
dromara:
# 注意, 不要设置 domain 访问路径, 自行进行拼接访问路径, 来保证可迁移性
x-file-storage:
default-platform: amazon-s3
# 文件存储都是用S3协议方式存储
amazon-s3:
- platform: amazon-s3 # 存储平台标识
enable-storage: true # 启用存储
access-key: test
secret-key: test
end-point: https://files.daxpay.com # 文件存储服务地址
bucket-name: daxpay # 桶名称,不要添加/后缀,
base-path: file/ # 基础路径, 不要添加/前缀, 但需要添加/后缀
attr:
# 开启路径样式访问, 不配置默认为二级域名方式
forcePathStyle: true

View File

@@ -2,7 +2,7 @@ server:
port: 9999
spring:
application:
name: dax-pay-admin
name: dax-pay
profiles:
active: dev
task:
@@ -45,3 +45,5 @@ bootx-platform:
deploy-mode: fusion
client-codes:
- dax-pay-admin
- dax-pay-merchant
- dax-pay-gateway