feat(payment): 初始化支付模块核心代码
- 添加支付常量类PaymentConstants,定义支付状态、微信、支付宝、银联等相关常量 - 创建微信支付类型常量类WechatPayType,支持JSAPI、NATIVE、H5、APP支付方式 - 新增支付控制器PaymentController,提供创建支付、查询状态、退款等统一接口 - 实现支付回调控制器PaymentNotifyController,处理微信、支付宝、银联异步通知 - 添加支付请求数据传输对象PaymentRequest,支持多种支付方式参数校验 - 定义支付响应、状态更新请求等相关DTO类- 集成Swagger注解,完善接口文档说明- 添加参数校验和异常处理机制,确保支付流程安全可靠
This commit is contained in:
244
java/payment/constants/PaymentConstants.java
Normal file
244
java/payment/constants/PaymentConstants.java
Normal file
@@ -0,0 +1,244 @@
|
||||
package com.gxwebsoft.payment.constants;
|
||||
|
||||
/**
|
||||
* 支付模块常量类
|
||||
* 统一管理支付相关的常量配置
|
||||
*
|
||||
* @author 科技小王子
|
||||
* @since 2025-01-26
|
||||
*/
|
||||
public class PaymentConstants {
|
||||
|
||||
/**
|
||||
* 支付状态常量
|
||||
*/
|
||||
public static class Status {
|
||||
/** 待支付 */
|
||||
public static final String PENDING = "PENDING";
|
||||
/** 支付成功 */
|
||||
public static final String SUCCESS = "SUCCESS";
|
||||
/** 支付失败 */
|
||||
public static final String FAILED = "FAILED";
|
||||
/** 支付取消 */
|
||||
public static final String CANCELLED = "CANCELLED";
|
||||
/** 支付超时 */
|
||||
public static final String TIMEOUT = "TIMEOUT";
|
||||
/** 退款成功 */
|
||||
public static final String REFUNDED = "REFUNDED";
|
||||
}
|
||||
|
||||
/**
|
||||
* 微信支付相关常量
|
||||
*/
|
||||
public static class Wechat {
|
||||
/** 货币类型 */
|
||||
public static final String CURRENCY = "CNY";
|
||||
/** 金额转换倍数(元转分) */
|
||||
public static final int AMOUNT_MULTIPLIER = 100;
|
||||
|
||||
/** 支付状态 */
|
||||
public static final String PAY_SUCCESS = "SUCCESS";
|
||||
public static final String PAY_REFUND = "REFUND";
|
||||
public static final String PAY_NOTPAY = "NOTPAY";
|
||||
public static final String PAY_CLOSED = "CLOSED";
|
||||
public static final String PAY_REVOKED = "REVOKED";
|
||||
public static final String PAY_USERPAYING = "USERPAYING";
|
||||
public static final String PAY_PAYERROR = "PAYERROR";
|
||||
|
||||
/** 回调响应 */
|
||||
public static final String NOTIFY_SUCCESS = "SUCCESS";
|
||||
public static final String NOTIFY_FAIL = "FAIL";
|
||||
|
||||
/** 通知类型 */
|
||||
public static final String EVENT_PAYMENT = "TRANSACTION.SUCCESS";
|
||||
public static final String EVENT_REFUND = "REFUND.SUCCESS";
|
||||
|
||||
/** HTTP头部 */
|
||||
public static final String HEADER_SIGNATURE = "Wechatpay-Signature";
|
||||
public static final String HEADER_TIMESTAMP = "Wechatpay-Timestamp";
|
||||
public static final String HEADER_NONCE = "Wechatpay-Nonce";
|
||||
public static final String HEADER_SERIAL = "Wechatpay-Serial";
|
||||
public static final String HEADER_REQUEST_ID = "Request-ID";
|
||||
}
|
||||
|
||||
/**
|
||||
* 支付宝相关常量
|
||||
*/
|
||||
public static class Alipay {
|
||||
/** 货币类型 */
|
||||
public static final String CURRENCY = "CNY";
|
||||
|
||||
/** 支付状态 */
|
||||
public static final String PAY_SUCCESS = "TRADE_SUCCESS";
|
||||
public static final String PAY_FINISHED = "TRADE_FINISHED";
|
||||
public static final String PAY_CLOSED = "TRADE_CLOSED";
|
||||
|
||||
/** 回调响应 */
|
||||
public static final String NOTIFY_SUCCESS = "success";
|
||||
public static final String NOTIFY_FAIL = "failure";
|
||||
|
||||
/** 产品码 */
|
||||
public static final String PRODUCT_CODE_WEB = "FAST_INSTANT_TRADE_PAY";
|
||||
public static final String PRODUCT_CODE_WAP = "QUICK_WAP_WAY";
|
||||
public static final String PRODUCT_CODE_APP = "QUICK_MSECURITY_PAY";
|
||||
}
|
||||
|
||||
/**
|
||||
* 银联支付相关常量
|
||||
*/
|
||||
public static class UnionPay {
|
||||
/** 货币类型 */
|
||||
public static final String CURRENCY = "156"; // 人民币代码
|
||||
|
||||
/** 支付状态 */
|
||||
public static final String PAY_SUCCESS = "00";
|
||||
public static final String PAY_FAILED = "01";
|
||||
|
||||
/** 交易类型 */
|
||||
public static final String TXN_TYPE_CONSUME = "01"; // 消费
|
||||
public static final String TXN_TYPE_REFUND = "04"; // 退货
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存键常量
|
||||
*/
|
||||
public static class CacheKey {
|
||||
/** 支付配置缓存前缀 */
|
||||
public static final String PAYMENT_CONFIG = "payment:config:";
|
||||
/** 支付订单缓存前缀 */
|
||||
public static final String PAYMENT_ORDER = "payment:order:";
|
||||
/** 支付锁前缀 */
|
||||
public static final String PAYMENT_LOCK = "payment:lock:";
|
||||
/** 回调处理锁前缀 */
|
||||
public static final String NOTIFY_LOCK = "payment:notify:lock:";
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置相关常量
|
||||
*/
|
||||
public static class Config {
|
||||
/** 订单超时时间(分钟) */
|
||||
public static final int ORDER_TIMEOUT_MINUTES = 30;
|
||||
/** 订单描述最大长度 */
|
||||
public static final int DESCRIPTION_MAX_LENGTH = 127;
|
||||
/** 最大重试次数 */
|
||||
public static final int MAX_RETRY_COUNT = 3;
|
||||
/** 重试间隔(毫秒) */
|
||||
public static final long RETRY_INTERVAL_MS = 1000;
|
||||
/** 签名有效期(秒) */
|
||||
public static final long SIGNATURE_VALID_SECONDS = 300;
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误信息常量
|
||||
*/
|
||||
public static class ErrorMessage {
|
||||
/** 参数错误 */
|
||||
public static final String PARAM_ERROR = "参数错误";
|
||||
/** 配置未找到 */
|
||||
public static final String CONFIG_NOT_FOUND = "支付配置未找到";
|
||||
/** 支付方式不支持 */
|
||||
public static final String PAYMENT_TYPE_NOT_SUPPORTED = "支付方式不支持";
|
||||
/** 金额错误 */
|
||||
public static final String AMOUNT_ERROR = "金额错误";
|
||||
/** 订单不存在 */
|
||||
public static final String ORDER_NOT_FOUND = "订单不存在";
|
||||
/** 订单状态错误 */
|
||||
public static final String ORDER_STATUS_ERROR = "订单状态错误";
|
||||
/** 签名验证失败 */
|
||||
public static final String SIGNATURE_ERROR = "签名验证失败";
|
||||
/** 网络请求失败 */
|
||||
public static final String NETWORK_ERROR = "网络请求失败";
|
||||
/** 系统内部错误 */
|
||||
public static final String SYSTEM_ERROR = "系统内部错误";
|
||||
/** 余额不足 */
|
||||
public static final String INSUFFICIENT_BALANCE = "余额不足";
|
||||
/** 支付超时 */
|
||||
public static final String PAYMENT_TIMEOUT = "支付超时";
|
||||
/** 重复支付 */
|
||||
public static final String DUPLICATE_PAYMENT = "重复支付";
|
||||
}
|
||||
|
||||
/**
|
||||
* 日志消息常量
|
||||
*/
|
||||
public static class LogMessage {
|
||||
/** 支付请求开始 */
|
||||
public static final String PAYMENT_START = "开始处理支付请求";
|
||||
/** 支付请求成功 */
|
||||
public static final String PAYMENT_SUCCESS = "支付请求处理成功";
|
||||
/** 支付请求失败 */
|
||||
public static final String PAYMENT_FAILED = "支付请求处理失败";
|
||||
|
||||
/** 回调处理开始 */
|
||||
public static final String NOTIFY_START = "开始处理支付回调";
|
||||
/** 回调处理成功 */
|
||||
public static final String NOTIFY_SUCCESS = "支付回调处理成功";
|
||||
/** 回调处理失败 */
|
||||
public static final String NOTIFY_FAILED = "支付回调处理失败";
|
||||
|
||||
/** 退款请求开始 */
|
||||
public static final String REFUND_START = "开始处理退款请求";
|
||||
/** 退款请求成功 */
|
||||
public static final String REFUND_SUCCESS = "退款请求处理成功";
|
||||
/** 退款请求失败 */
|
||||
public static final String REFUND_FAILED = "退款请求处理失败";
|
||||
}
|
||||
|
||||
/**
|
||||
* 正则表达式常量
|
||||
*/
|
||||
public static class Regex {
|
||||
/** 订单号格式 */
|
||||
public static final String ORDER_NO = "^[a-zA-Z0-9_-]{1,32}$";
|
||||
/** 金额格式(分) */
|
||||
public static final String AMOUNT = "^[1-9]\\d*$";
|
||||
/** 手机号格式 */
|
||||
public static final String MOBILE = "^1[3-9]\\d{9}$";
|
||||
/** 邮箱格式 */
|
||||
public static final String EMAIL = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$";
|
||||
}
|
||||
|
||||
/**
|
||||
* 时间相关常量
|
||||
*/
|
||||
public static class Time {
|
||||
/** 配置缓存有效期(秒) */
|
||||
public static final long CONFIG_CACHE_SECONDS = 3600;
|
||||
/** 订单缓存有效期(秒) */
|
||||
public static final long ORDER_CACHE_SECONDS = 1800;
|
||||
/** 支付锁有效期(秒) */
|
||||
public static final long PAYMENT_LOCK_SECONDS = 60;
|
||||
/** 回调锁有效期(秒) */
|
||||
public static final long NOTIFY_LOCK_SECONDS = 30;
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件相关常量
|
||||
*/
|
||||
public static class File {
|
||||
/** 证书文件扩展名 */
|
||||
public static final String CERT_EXTENSION = ".pem";
|
||||
/** 私钥文件后缀 */
|
||||
public static final String PRIVATE_KEY_SUFFIX = "_key.pem";
|
||||
/** 公钥文件后缀 */
|
||||
public static final String PUBLIC_KEY_SUFFIX = "_cert.pem";
|
||||
}
|
||||
|
||||
/**
|
||||
* 环境相关常量
|
||||
*/
|
||||
public static class Environment {
|
||||
/** 开发环境 */
|
||||
public static final String DEV = "dev";
|
||||
/** 测试环境 */
|
||||
public static final String TEST = "test";
|
||||
/** 生产环境 */
|
||||
public static final String PROD = "prod";
|
||||
}
|
||||
|
||||
// 私有构造函数,防止实例化
|
||||
private PaymentConstants() {
|
||||
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
|
||||
}
|
||||
}
|
||||
81
java/payment/constants/WechatPayType.java
Normal file
81
java/payment/constants/WechatPayType.java
Normal file
@@ -0,0 +1,81 @@
|
||||
package com.gxwebsoft.payment.constants;
|
||||
|
||||
/**
|
||||
* 微信支付类型常量
|
||||
* 定义微信支付的具体实现方式
|
||||
*
|
||||
* @author 科技小王子
|
||||
* @since 2025-08-30
|
||||
*/
|
||||
public class WechatPayType {
|
||||
|
||||
/**
|
||||
* JSAPI支付 - 小程序/公众号内支付
|
||||
* 需要用户的openid
|
||||
*/
|
||||
public static final String JSAPI = "JSAPI";
|
||||
|
||||
/**
|
||||
* Native支付 - 扫码支付
|
||||
* 生成二维码供用户扫描支付
|
||||
*/
|
||||
public static final String NATIVE = "NATIVE";
|
||||
|
||||
/**
|
||||
* H5支付 - 手机网页支付
|
||||
* 在手机浏览器中调起微信支付
|
||||
*/
|
||||
public static final String H5 = "H5";
|
||||
|
||||
/**
|
||||
* APP支付 - 移动应用支付
|
||||
* 在APP中调起微信支付
|
||||
*/
|
||||
public static final String APP = "APP";
|
||||
|
||||
/**
|
||||
* 根据openid自动选择微信支付类型
|
||||
*
|
||||
* @param openid 用户openid
|
||||
* @return JSAPI 或 NATIVE
|
||||
*/
|
||||
public static String getAutoType(String openid) {
|
||||
return (openid != null && !openid.trim().isEmpty()) ? JSAPI : NATIVE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为有效的微信支付类型
|
||||
*
|
||||
* @param payType 支付类型
|
||||
* @return true表示有效
|
||||
*/
|
||||
public static boolean isValidType(String payType) {
|
||||
return JSAPI.equals(payType) || NATIVE.equals(payType) ||
|
||||
H5.equals(payType) || APP.equals(payType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取支付类型描述
|
||||
*
|
||||
* @param payType 支付类型
|
||||
* @return 描述文本
|
||||
*/
|
||||
public static String getDescription(String payType) {
|
||||
if (payType == null) {
|
||||
return "未知支付类型";
|
||||
}
|
||||
|
||||
switch (payType) {
|
||||
case JSAPI:
|
||||
return "小程序/公众号支付";
|
||||
case NATIVE:
|
||||
return "扫码支付";
|
||||
case H5:
|
||||
return "手机网页支付";
|
||||
case APP:
|
||||
return "移动应用支付";
|
||||
default:
|
||||
return "未知支付类型: " + payType;
|
||||
}
|
||||
}
|
||||
}
|
||||
360
java/payment/controller/PaymentController.java
Normal file
360
java/payment/controller/PaymentController.java
Normal file
@@ -0,0 +1,360 @@
|
||||
package com.gxwebsoft.payment.controller;
|
||||
|
||||
import com.gxwebsoft.common.core.web.ApiResult;
|
||||
import com.gxwebsoft.common.core.web.BaseController;
|
||||
import com.gxwebsoft.common.system.entity.User;
|
||||
import com.gxwebsoft.payment.constants.PaymentConstants;
|
||||
import com.gxwebsoft.payment.dto.PaymentRequest;
|
||||
import com.gxwebsoft.payment.dto.PaymentResponse;
|
||||
import com.gxwebsoft.payment.dto.PaymentStatusUpdateRequest;
|
||||
import com.gxwebsoft.payment.dto.PaymentWithOrderRequest;
|
||||
import com.gxwebsoft.payment.enums.PaymentType;
|
||||
import com.gxwebsoft.payment.exception.PaymentException;
|
||||
import com.gxwebsoft.payment.service.PaymentService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Positive;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 统一支付控制器
|
||||
* 提供所有支付方式的统一入口
|
||||
*
|
||||
* @author 科技小王子
|
||||
* @since 2025-01-26
|
||||
*/
|
||||
@Slf4j
|
||||
@Validated
|
||||
@Tag(name = "统一支付接口", description = "支持所有支付方式的统一支付接口")
|
||||
@RestController("unifiedPaymentController")
|
||||
@RequestMapping("/api/payment")
|
||||
public class PaymentController extends BaseController {
|
||||
|
||||
@Resource(name = "unifiedPaymentServiceImpl")
|
||||
private PaymentService paymentService;
|
||||
|
||||
@Operation(summary = "创建支付订单", description = "支持微信、支付宝、银联等多种支付方式")
|
||||
@PostMapping("/create")
|
||||
public ApiResult<?> createPayment(@Valid @RequestBody PaymentRequest request) {
|
||||
log.info("收到支付请求: {}", request);
|
||||
final User loginUser = getLoginUser();
|
||||
|
||||
if(loginUser == null){
|
||||
return fail("请先登录");
|
||||
}
|
||||
|
||||
request.setUserId(loginUser.getUserId());
|
||||
if(request.getTenantId() == null){
|
||||
request.setTenantId(loginUser.getTenantId());
|
||||
}
|
||||
try {
|
||||
PaymentResponse response = paymentService.createPayment(request);
|
||||
return this.<PaymentResponse>success("支付订单创建成功", response);
|
||||
|
||||
} catch (PaymentException e) {
|
||||
log.error("支付订单创建失败: {}", e.getMessage());
|
||||
return fail(e.getMessage());
|
||||
} catch (Exception e) {
|
||||
log.error("支付订单创建系统错误: {}", e.getMessage(), e);
|
||||
return fail(PaymentConstants.ErrorMessage.SYSTEM_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "创建支付订单(包含订单信息)", description = "统一支付模块:创建订单并发起支付")
|
||||
@PostMapping("/create-with-order")
|
||||
public ApiResult<?> createPaymentWithOrder(@Valid @RequestBody PaymentWithOrderRequest request) {
|
||||
log.info("收到支付与订单创建请求: {}", request);
|
||||
final User loginUser = getLoginUser();
|
||||
|
||||
if(loginUser == null){
|
||||
return fail("请先登录");
|
||||
}
|
||||
|
||||
// 设置用户信息
|
||||
if(request.getTenantId() == null){
|
||||
request.setTenantId(loginUser.getTenantId());
|
||||
}
|
||||
|
||||
try {
|
||||
PaymentResponse response = paymentService.createPaymentWithOrder(request, loginUser);
|
||||
return this.<PaymentResponse>success("订单创建并发起支付成功", response);
|
||||
} catch (PaymentException e) {
|
||||
log.error("创建支付订单失败: {}", e.getMessage());
|
||||
return fail(e.getMessage());
|
||||
} catch (Exception e) {
|
||||
log.error("创建支付订单系统错误: {}", e.getMessage(), e);
|
||||
return fail(PaymentConstants.ErrorMessage.SYSTEM_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "查询支付状态", description = "查询指定订单的支付状态")
|
||||
@GetMapping("/query")
|
||||
public ApiResult<?> queryPayment(
|
||||
@Parameter(description = "订单号", required = true)
|
||||
@RequestParam @NotBlank(message = "订单号不能为空") String orderNo,
|
||||
|
||||
@Parameter(description = "支付类型", required = true)
|
||||
@RequestParam @NotNull(message = "支付类型不能为空") PaymentType paymentType,
|
||||
|
||||
@Parameter(description = "租户ID", required = true)
|
||||
@RequestParam @NotNull(message = "租户ID不能为空") @Positive(message = "租户ID必须为正数") Integer tenantId) {
|
||||
|
||||
log.info("查询支付状态: orderNo={}, paymentType={}, tenantId={}", orderNo, paymentType, tenantId);
|
||||
|
||||
// 参数验证
|
||||
if (orderNo == null || orderNo.trim().isEmpty()) {
|
||||
return fail("订单号不能为空");
|
||||
}
|
||||
if (paymentType == null) {
|
||||
return fail("支付类型不能为空");
|
||||
}
|
||||
if (tenantId == null || tenantId <= 0) {
|
||||
return fail("租户ID不能为空且必须为正数");
|
||||
}
|
||||
|
||||
try {
|
||||
PaymentResponse response = paymentService.queryPayment(orderNo, paymentType, tenantId);
|
||||
return this.<PaymentResponse>success("支付状态查询成功", response);
|
||||
|
||||
} catch (PaymentException e) {
|
||||
log.error("支付状态查询失败: {}", e.getMessage());
|
||||
return fail(e.getMessage());
|
||||
} catch (Exception e) {
|
||||
log.error("支付状态查询系统错误: {}", e.getMessage(), e);
|
||||
return fail(PaymentConstants.ErrorMessage.SYSTEM_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "申请退款", description = "申请订单退款")
|
||||
@PostMapping("/refund")
|
||||
public ApiResult<?> refund(
|
||||
@Parameter(description = "订单号", required = true)
|
||||
@RequestParam @NotBlank(message = "订单号不能为空") String orderNo,
|
||||
|
||||
@Parameter(description = "退款单号", required = true)
|
||||
@RequestParam @NotBlank(message = "退款单号不能为空") String refundNo,
|
||||
|
||||
@Parameter(description = "支付类型", required = true)
|
||||
@RequestParam @NotNull(message = "支付类型不能为空") PaymentType paymentType,
|
||||
|
||||
@Parameter(description = "订单总金额", required = true)
|
||||
@RequestParam @NotNull(message = "订单总金额不能为空") @Positive(message = "订单总金额必须大于0") BigDecimal totalAmount,
|
||||
|
||||
@Parameter(description = "退款金额", required = true)
|
||||
@RequestParam @NotNull(message = "退款金额不能为空") @Positive(message = "退款金额必须大于0") BigDecimal refundAmount,
|
||||
|
||||
@Parameter(description = "退款原因")
|
||||
@RequestParam(required = false) String reason,
|
||||
|
||||
@Parameter(description = "租户ID", required = true)
|
||||
@RequestParam @NotNull(message = "租户ID不能为空") @Positive(message = "租户ID必须为正数") Integer tenantId) {
|
||||
|
||||
log.info("申请退款: orderNo={}, refundNo={}, paymentType={}, totalAmount={}, refundAmount={}, tenantId={}",
|
||||
orderNo, refundNo, paymentType, totalAmount, refundAmount, tenantId);
|
||||
|
||||
try {
|
||||
PaymentResponse response = paymentService.refund(orderNo, refundNo, paymentType,
|
||||
totalAmount, refundAmount, reason, tenantId);
|
||||
return this.<PaymentResponse>success("退款申请成功", response);
|
||||
|
||||
} catch (PaymentException e) {
|
||||
log.error("退款申请失败: {}", e.getMessage());
|
||||
return fail(e.getMessage());
|
||||
} catch (Exception e) {
|
||||
log.error("退款申请系统错误: {}", e.getMessage(), e);
|
||||
return fail(PaymentConstants.ErrorMessage.SYSTEM_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "查询退款状态", description = "查询指定退款单的状态")
|
||||
@GetMapping("/refund/query")
|
||||
public ApiResult<?> queryRefund(
|
||||
@Parameter(description = "退款单号", required = true)
|
||||
@RequestParam @NotBlank(message = "退款单号不能为空") String refundNo,
|
||||
|
||||
@Parameter(description = "支付类型", required = true)
|
||||
@RequestParam @NotNull(message = "支付类型不能为空") PaymentType paymentType,
|
||||
|
||||
@Parameter(description = "租户ID", required = true)
|
||||
@RequestParam @NotNull(message = "租户ID不能为空") @Positive(message = "租户ID必须为正数") Integer tenantId) {
|
||||
|
||||
log.info("查询退款状态: refundNo={}, paymentType={}, tenantId={}", refundNo, paymentType, tenantId);
|
||||
|
||||
try {
|
||||
PaymentResponse response = paymentService.queryRefund(refundNo, paymentType, tenantId);
|
||||
return this.<PaymentResponse>success("退款状态查询成功", response);
|
||||
|
||||
} catch (PaymentException e) {
|
||||
log.error("退款状态查询失败: {}", e.getMessage());
|
||||
return fail(e.getMessage());
|
||||
} catch (Exception e) {
|
||||
log.error("退款状态查询系统错误: {}", e.getMessage(), e);
|
||||
return fail(PaymentConstants.ErrorMessage.SYSTEM_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "关闭订单", description = "关闭未支付的订单")
|
||||
@PostMapping("/close")
|
||||
public ApiResult<?> closeOrder(
|
||||
@Parameter(description = "订单号", required = true)
|
||||
@RequestParam @NotBlank(message = "订单号不能为空") String orderNo,
|
||||
|
||||
@Parameter(description = "支付类型", required = true)
|
||||
@RequestParam @NotNull(message = "支付类型不能为空") PaymentType paymentType,
|
||||
|
||||
@Parameter(description = "租户ID", required = true)
|
||||
@RequestParam @NotNull(message = "租户ID不能为空") @Positive(message = "租户ID必须为正数") Integer tenantId) {
|
||||
|
||||
log.info("关闭订单: orderNo={}, paymentType={}, tenantId={}", orderNo, paymentType, tenantId);
|
||||
|
||||
try {
|
||||
boolean result = paymentService.closeOrder(orderNo, paymentType, tenantId);
|
||||
return success(result ? "订单关闭成功" : "订单关闭失败", result);
|
||||
|
||||
} catch (PaymentException e) {
|
||||
log.error("订单关闭失败: {}", e.getMessage());
|
||||
return fail(e.getMessage());
|
||||
} catch (Exception e) {
|
||||
log.error("订单关闭系统错误: {}", e.getMessage(), e);
|
||||
return fail(PaymentConstants.ErrorMessage.SYSTEM_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "获取支持的支付类型", description = "获取系统支持的所有支付类型列表")
|
||||
@GetMapping("/types")
|
||||
public ApiResult<?> getSupportedPaymentTypes() {
|
||||
try {
|
||||
List<PaymentType> paymentTypes = paymentService.getSupportedPaymentTypes();
|
||||
return this.<List<PaymentType>>success("获取支付类型成功", paymentTypes);
|
||||
} catch (Exception e) {
|
||||
log.error("获取支付类型失败: {}", e.getMessage(), e);
|
||||
return fail(PaymentConstants.ErrorMessage.SYSTEM_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "获取支付策略信息", description = "获取指定支付类型的策略信息")
|
||||
@GetMapping("/strategy/{paymentType}")
|
||||
public ApiResult<?> getPaymentStrategyInfo(
|
||||
@Parameter(description = "支付类型", required = true)
|
||||
@PathVariable @NotNull(message = "支付类型不能为空") PaymentType paymentType) {
|
||||
|
||||
try {
|
||||
Map<String, Object> strategyInfo = paymentService.getPaymentStrategyInfo(paymentType);
|
||||
if (strategyInfo == null) {
|
||||
return fail("不支持的支付类型: " + paymentType);
|
||||
}
|
||||
return success("获取策略信息成功", strategyInfo);
|
||||
} catch (Exception e) {
|
||||
log.error("获取策略信息失败: {}", e.getMessage(), e);
|
||||
return fail(PaymentConstants.ErrorMessage.SYSTEM_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "获取所有支付策略信息", description = "获取系统所有支付策略的详细信息")
|
||||
@GetMapping("/strategies")
|
||||
public ApiResult<?> getAllPaymentStrategyInfo() {
|
||||
try {
|
||||
List<Map<String, Object>> strategiesInfo = paymentService.getAllPaymentStrategyInfo();
|
||||
return this.<List<Map<String, Object>>>success("获取所有策略信息成功", strategiesInfo);
|
||||
} catch (Exception e) {
|
||||
log.error("获取所有策略信息失败: {}", e.getMessage(), e);
|
||||
return fail(PaymentConstants.ErrorMessage.SYSTEM_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "检查支付类型支持情况", description = "检查指定支付类型的功能支持情况")
|
||||
@GetMapping("/support/{paymentType}")
|
||||
public ApiResult<?> checkPaymentTypeSupport(
|
||||
@Parameter(description = "支付类型", required = true)
|
||||
@PathVariable @NotNull(message = "支付类型不能为空") PaymentType paymentType) {
|
||||
|
||||
try {
|
||||
Map<String, Boolean> support = Map.of(
|
||||
"supported", paymentService.isPaymentTypeSupported(paymentType),
|
||||
"refundSupported", paymentService.isRefundSupported(paymentType),
|
||||
"querySupported", paymentService.isQuerySupported(paymentType),
|
||||
"closeSupported", paymentService.isCloseSupported(paymentType),
|
||||
"notifyNeeded", paymentService.isNotifyNeeded(paymentType)
|
||||
);
|
||||
return this.<Map<String, Boolean>>success("检查支持情况成功", support);
|
||||
} catch (Exception e) {
|
||||
log.error("检查支持情况失败: {}", e.getMessage(), e);
|
||||
return fail(PaymentConstants.ErrorMessage.SYSTEM_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "手动更新支付状态", description = "用于手动同步支付状态,通常用于异常情况处理")
|
||||
@PutMapping("/update-status")
|
||||
public ApiResult<?> updatePaymentStatus(@Valid @RequestBody PaymentStatusUpdateRequest request) {
|
||||
log.info("收到支付状态更新请求: {}", request);
|
||||
|
||||
try {
|
||||
// 查询并更新支付状态
|
||||
PaymentResponse response = paymentService.queryPayment(
|
||||
request.getOrderNo(),
|
||||
PaymentType.WECHAT_NATIVE,
|
||||
request.getTenantId()
|
||||
);
|
||||
|
||||
return this.<PaymentResponse>success("支付状态更新成功", response);
|
||||
} catch (Exception e) {
|
||||
log.error("更新支付状态失败: {}", e.getMessage(), e);
|
||||
return fail("更新支付状态失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "检查支付配置", description = "检查指定租户的支付配置是否完整")
|
||||
@GetMapping("/config/check")
|
||||
public ApiResult<?> checkPaymentConfig(
|
||||
@Parameter(description = "租户ID", required = true)
|
||||
@RequestParam @NotNull(message = "租户ID不能为空") @Positive(message = "租户ID必须为正数") Integer tenantId) {
|
||||
|
||||
log.info("检查支付配置,租户ID: {}", tenantId);
|
||||
|
||||
try {
|
||||
Map<String, Object> configStatus = paymentService.checkPaymentConfig(tenantId);
|
||||
return this.<Map<String, Object>>success("配置检查完成", configStatus);
|
||||
} catch (Exception e) {
|
||||
log.error("检查支付配置失败: {}", e.getMessage(), e);
|
||||
return fail("检查支付配置失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "查询用户最近的支付订单", description = "当orderNo缺失时,查询用户最近创建的支付订单")
|
||||
@GetMapping("/query-recent")
|
||||
public ApiResult<?> queryRecentPayment(
|
||||
@Parameter(description = "支付类型", required = true)
|
||||
@RequestParam @NotNull(message = "支付类型不能为空") PaymentType paymentType,
|
||||
|
||||
@Parameter(description = "租户ID", required = true)
|
||||
@RequestParam @NotNull(message = "租户ID不能为空") @Positive(message = "租户ID必须为正数") Integer tenantId) {
|
||||
|
||||
log.info("查询用户最近支付订单: paymentType={}, tenantId={}", paymentType, tenantId);
|
||||
|
||||
final User loginUser = getLoginUser();
|
||||
if(loginUser == null){
|
||||
return fail("请先登录");
|
||||
}
|
||||
|
||||
try {
|
||||
// 这里需要实现查询用户最近订单的逻辑
|
||||
// 可以通过用户ID和租户ID查询最近创建的订单
|
||||
return fail("此功能需要实现查询用户最近订单的业务逻辑");
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("查询用户最近支付订单失败: {}", e.getMessage(), e);
|
||||
return fail("查询失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
188
java/payment/controller/PaymentNotifyController.java
Normal file
188
java/payment/controller/PaymentNotifyController.java
Normal file
@@ -0,0 +1,188 @@
|
||||
package com.gxwebsoft.payment.controller;
|
||||
|
||||
import com.gxwebsoft.payment.constants.PaymentConstants;
|
||||
import com.gxwebsoft.payment.enums.PaymentType;
|
||||
import com.gxwebsoft.payment.exception.PaymentException;
|
||||
import com.gxwebsoft.payment.service.PaymentService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 统一支付回调控制器
|
||||
* 处理所有支付方式的异步通知回调
|
||||
*
|
||||
* @author 科技小王子
|
||||
* @since 2025-01-26
|
||||
*/
|
||||
@Slf4j
|
||||
@Tag(name = "统一支付回调接口", description = "处理所有支付方式的异步通知回调")
|
||||
@RestController
|
||||
@RequestMapping("/api/payment/notify")
|
||||
public class PaymentNotifyController {
|
||||
|
||||
@Resource
|
||||
private PaymentService paymentService;
|
||||
|
||||
@Operation(summary = "微信支付回调通知", description = "处理微信支付的异步通知")
|
||||
@PostMapping("/wechat/{tenantId}")
|
||||
public String wechatNotify(
|
||||
@Parameter(description = "租户ID", required = true)
|
||||
@PathVariable("tenantId") Integer tenantId,
|
||||
@RequestBody String body,
|
||||
HttpServletRequest request) {
|
||||
|
||||
log.info("收到微信支付回调通知, 租户ID: {}", tenantId);
|
||||
|
||||
try {
|
||||
// 提取请求头
|
||||
Map<String, String> headers = extractHeaders(request);
|
||||
|
||||
// 处理回调
|
||||
String result = paymentService.handlePaymentNotify(PaymentType.WECHAT_NATIVE, headers, body, tenantId);
|
||||
|
||||
log.info("微信支付回调处理完成, 租户ID: {}, 结果: {}", tenantId, result);
|
||||
return result;
|
||||
|
||||
} catch (PaymentException e) {
|
||||
log.error("微信支付回调处理失败, 租户ID: {}, 错误: {}", tenantId, e.getMessage());
|
||||
return PaymentConstants.Wechat.NOTIFY_FAIL;
|
||||
} catch (Exception e) {
|
||||
log.error("微信支付回调系统错误, 租户ID: {}, 错误: {}", tenantId, e.getMessage(), e);
|
||||
return PaymentConstants.Wechat.NOTIFY_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "支付宝支付回调通知", description = "处理支付宝支付的异步通知")
|
||||
@PostMapping("/alipay/{tenantId}")
|
||||
public String alipayNotify(
|
||||
@Parameter(description = "租户ID", required = true)
|
||||
@PathVariable("tenantId") Integer tenantId,
|
||||
@RequestBody String body,
|
||||
HttpServletRequest request) {
|
||||
|
||||
log.info("收到支付宝支付回调通知, 租户ID: {}", tenantId);
|
||||
|
||||
try {
|
||||
// 提取请求头
|
||||
Map<String, String> headers = extractHeaders(request);
|
||||
|
||||
// 处理回调
|
||||
String result = paymentService.handlePaymentNotify(PaymentType.ALIPAY, headers, body, tenantId);
|
||||
|
||||
log.info("支付宝支付回调处理完成, 租户ID: {}, 结果: {}", tenantId, result);
|
||||
return result;
|
||||
|
||||
} catch (PaymentException e) {
|
||||
log.error("支付宝支付回调处理失败, 租户ID: {}, 错误: {}", tenantId, e.getMessage());
|
||||
return PaymentConstants.Alipay.NOTIFY_FAIL;
|
||||
} catch (Exception e) {
|
||||
log.error("支付宝支付回调系统错误, 租户ID: {}, 错误: {}", tenantId, e.getMessage(), e);
|
||||
return PaymentConstants.Alipay.NOTIFY_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "银联支付回调通知", description = "处理银联支付的异步通知")
|
||||
@PostMapping("/unionpay/{tenantId}")
|
||||
public String unionPayNotify(
|
||||
@Parameter(description = "租户ID", required = true)
|
||||
@PathVariable("tenantId") Integer tenantId,
|
||||
@RequestBody String body,
|
||||
HttpServletRequest request) {
|
||||
|
||||
log.info("收到银联支付回调通知, 租户ID: {}", tenantId);
|
||||
|
||||
try {
|
||||
// 提取请求头
|
||||
Map<String, String> headers = extractHeaders(request);
|
||||
|
||||
// 处理回调
|
||||
String result = paymentService.handlePaymentNotify(PaymentType.UNION_PAY, headers, body, tenantId);
|
||||
|
||||
log.info("银联支付回调处理完成, 租户ID: {}, 结果: {}", tenantId, result);
|
||||
return result;
|
||||
|
||||
} catch (PaymentException e) {
|
||||
log.error("银联支付回调处理失败, 租户ID: {}, 错误: {}", tenantId, e.getMessage());
|
||||
return "failure";
|
||||
} catch (Exception e) {
|
||||
log.error("银联支付回调系统错误, 租户ID: {}, 错误: {}", tenantId, e.getMessage(), e);
|
||||
return "failure";
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "通用支付回调通知", description = "处理指定支付类型的异步通知")
|
||||
@PostMapping("/{paymentType}/{tenantId}")
|
||||
public String genericNotify(
|
||||
@Parameter(description = "支付类型", required = true)
|
||||
@PathVariable("paymentType") PaymentType paymentType,
|
||||
@Parameter(description = "租户ID", required = true)
|
||||
@PathVariable("tenantId") Integer tenantId,
|
||||
@RequestBody String body,
|
||||
HttpServletRequest request) {
|
||||
|
||||
log.info("收到{}支付回调通知, 租户ID: {}", paymentType.getName(), tenantId);
|
||||
|
||||
try {
|
||||
// 提取请求头
|
||||
Map<String, String> headers = extractHeaders(request);
|
||||
|
||||
// 处理回调
|
||||
String result = paymentService.handlePaymentNotify(paymentType, headers, body, tenantId);
|
||||
|
||||
log.info("{}支付回调处理完成, 租户ID: {}, 结果: {}", paymentType.getName(), tenantId, result);
|
||||
return result;
|
||||
|
||||
} catch (PaymentException e) {
|
||||
log.error("{}支付回调处理失败, 租户ID: {}, 错误: {}", paymentType.getName(), tenantId, e.getMessage());
|
||||
return getFailureResponse(paymentType);
|
||||
} catch (Exception e) {
|
||||
log.error("{}支付回调系统错误, 租户ID: {}, 错误: {}", paymentType.getName(), tenantId, e.getMessage(), e);
|
||||
return getFailureResponse(paymentType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取HTTP请求头
|
||||
*/
|
||||
private Map<String, String> extractHeaders(HttpServletRequest request) {
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
|
||||
Enumeration<String> headerNames = request.getHeaderNames();
|
||||
while (headerNames.hasMoreElements()) {
|
||||
String headerName = headerNames.nextElement();
|
||||
String headerValue = request.getHeader(headerName);
|
||||
headers.put(headerName, headerValue);
|
||||
}
|
||||
|
||||
// 记录关键头部信息(不记录敏感信息)
|
||||
log.debug("提取请求头完成, 头部数量: {}", headers.size());
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据支付类型获取失败响应
|
||||
*/
|
||||
private String getFailureResponse(PaymentType paymentType) {
|
||||
switch (paymentType) {
|
||||
case WECHAT:
|
||||
case WECHAT_NATIVE:
|
||||
return PaymentConstants.Wechat.NOTIFY_FAIL;
|
||||
case ALIPAY:
|
||||
return PaymentConstants.Alipay.NOTIFY_FAIL;
|
||||
case UNION_PAY:
|
||||
return "failure";
|
||||
default:
|
||||
return "fail";
|
||||
}
|
||||
}
|
||||
}
|
||||
207
java/payment/dto/PaymentRequest.java
Normal file
207
java/payment/dto/PaymentRequest.java
Normal file
@@ -0,0 +1,207 @@
|
||||
package com.gxwebsoft.payment.dto;
|
||||
|
||||
import com.gxwebsoft.payment.enums.PaymentChannel;
|
||||
import com.gxwebsoft.payment.enums.PaymentType;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 统一支付请求DTO
|
||||
* 支持所有支付方式的统一请求格式
|
||||
*
|
||||
* @author 科技小王子
|
||||
* @since 2025-01-26
|
||||
*/
|
||||
@Data
|
||||
@Schema(name = "统一支付请求", description = "支持所有支付方式的统一支付请求参数")
|
||||
public class PaymentRequest {
|
||||
|
||||
@Schema(description = "租户ID", required = true)
|
||||
@NotNull(message = "租户ID不能为空")
|
||||
@Positive(message = "租户ID必须为正数")
|
||||
private Integer tenantId;
|
||||
|
||||
@Schema(description = "用户ID", required = true)
|
||||
@NotNull(message = "用户ID不能为空")
|
||||
@Positive(message = "用户ID必须为正数")
|
||||
private Integer userId;
|
||||
|
||||
@Schema(description = "支付类型", required = true, example = "WECHAT")
|
||||
@NotNull(message = "支付类型不能为空")
|
||||
private PaymentType paymentType;
|
||||
|
||||
@Schema(description = "支付渠道", example = "wechat_native")
|
||||
private PaymentChannel paymentChannel;
|
||||
|
||||
@Schema(description = "支付金额", required = true, example = "0.01")
|
||||
@NotNull(message = "支付金额不能为空")
|
||||
@DecimalMin(value = "0.01", message = "支付金额必须大于0.01元")
|
||||
@DecimalMax(value = "999999.99", message = "支付金额不能超过999999.99元")
|
||||
@Digits(integer = 6, fraction = 2, message = "支付金额格式不正确,最多6位整数2位小数")
|
||||
private BigDecimal amount;
|
||||
|
||||
@Schema(description = "订单号(可选,不提供则自动生成)")
|
||||
@Size(max = 32, message = "订单号不能超过32个字符")
|
||||
@Pattern(regexp = "^[a-zA-Z0-9_-]*$", message = "订单号只能包含字母、数字、下划线和横线")
|
||||
private String orderNo;
|
||||
|
||||
@Schema(description = "订单标题", required = true)
|
||||
@NotBlank(message = "订单标题不能为空")
|
||||
@Size(max = 127, message = "订单标题不能超过127个字符")
|
||||
private String subject;
|
||||
|
||||
@Schema(description = "订单描述")
|
||||
@Size(max = 500, message = "订单描述不能超过500个字符")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "商品ID")
|
||||
@Positive(message = "商品ID必须为正数")
|
||||
private Integer goodsId;
|
||||
|
||||
@Schema(description = "购买数量", example = "1")
|
||||
@Min(value = 1, message = "购买数量必须大于0")
|
||||
@Max(value = 9999, message = "购买数量不能超过9999")
|
||||
private Integer quantity = 1;
|
||||
|
||||
@Schema(description = "订单类型", example = "0")
|
||||
@Min(value = 0, message = "订单类型不能为负数")
|
||||
private Integer orderType = 0;
|
||||
|
||||
@Schema(description = "客户端IP地址")
|
||||
private String clientIp;
|
||||
|
||||
@Schema(description = "用户代理")
|
||||
private String userAgent;
|
||||
|
||||
@Schema(description = "回调通知URL")
|
||||
private String notifyUrl;
|
||||
|
||||
@Schema(description = "支付成功跳转URL")
|
||||
private String returnUrl;
|
||||
|
||||
@Schema(description = "支付取消跳转URL")
|
||||
private String cancelUrl;
|
||||
|
||||
@Schema(description = "订单超时时间(分钟)", example = "30")
|
||||
@Min(value = 1, message = "订单超时时间必须大于0分钟")
|
||||
@Max(value = 1440, message = "订单超时时间不能超过1440分钟(24小时)")
|
||||
private Integer timeoutMinutes = 30;
|
||||
|
||||
@Schema(description = "买家备注")
|
||||
@Size(max = 500, message = "买家备注不能超过500个字符")
|
||||
private String buyerRemarks;
|
||||
|
||||
@Schema(description = "商户备注")
|
||||
@Size(max = 500, message = "商户备注不能超过500个字符")
|
||||
private String merchantRemarks;
|
||||
|
||||
@Schema(description = "收货地址ID")
|
||||
@Positive(message = "收货地址ID必须为正数")
|
||||
private Integer addressId;
|
||||
|
||||
@Schema(description = "扩展参数")
|
||||
private Map<String, Object> extraParams;
|
||||
|
||||
// 微信支付特有参数
|
||||
@Schema(description = "微信OpenID(JSAPI支付必填)")
|
||||
private String openId;
|
||||
|
||||
@Schema(description = "微信UnionID")
|
||||
private String unionId;
|
||||
|
||||
// 支付宝特有参数
|
||||
@Schema(description = "支付宝用户ID")
|
||||
private String alipayUserId;
|
||||
|
||||
@Schema(description = "花呗分期数")
|
||||
private Integer hbFqNum;
|
||||
|
||||
// 银联支付特有参数
|
||||
@Schema(description = "银行卡号")
|
||||
private String cardNo;
|
||||
|
||||
@Schema(description = "银行代码")
|
||||
private String bankCode;
|
||||
|
||||
/**
|
||||
* 获取有效的支付渠道
|
||||
*/
|
||||
public PaymentChannel getEffectivePaymentChannel() {
|
||||
if (paymentChannel != null) {
|
||||
return paymentChannel;
|
||||
}
|
||||
return PaymentChannel.getDefaultByPaymentType(paymentType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取有效的订单描述
|
||||
*/
|
||||
public String getEffectiveDescription() {
|
||||
if (description != null && !description.trim().isEmpty()) {
|
||||
return description.trim();
|
||||
}
|
||||
return subject;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取格式化的金额字符串
|
||||
*/
|
||||
public String getFormattedAmount() {
|
||||
if (amount == null) {
|
||||
return "0.00";
|
||||
}
|
||||
return String.format("%.2f", amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为分(微信支付API需要)
|
||||
*/
|
||||
public Integer getAmountInCents() {
|
||||
if (amount == null) {
|
||||
return 0;
|
||||
}
|
||||
return amount.multiply(new BigDecimal(100)).intValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证必要参数是否完整
|
||||
*/
|
||||
public boolean isValid() {
|
||||
return tenantId != null && tenantId > 0
|
||||
&& userId != null && userId > 0
|
||||
&& paymentType != null
|
||||
&& amount != null && amount.compareTo(BigDecimal.ZERO) > 0
|
||||
&& subject != null && !subject.trim().isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证微信JSAPI支付参数
|
||||
*/
|
||||
public boolean isValidForWechatJsapi() {
|
||||
return isValid() && paymentType.isWechatPay() && openId != null && !openId.trim().isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证支付宝支付参数
|
||||
*/
|
||||
public boolean isValidForAlipay() {
|
||||
return isValid() && paymentType == PaymentType.ALIPAY;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取订单超时时间(秒)
|
||||
*/
|
||||
public long getTimeoutSeconds() {
|
||||
return timeoutMinutes * 60L;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("PaymentRequest{tenantId=%d, userId=%d, paymentType=%s, amount=%s, orderNo='%s', subject='%s'}",
|
||||
tenantId, userId, paymentType, getFormattedAmount(), orderNo, subject);
|
||||
}
|
||||
}
|
||||
294
java/payment/dto/PaymentResponse.java
Normal file
294
java/payment/dto/PaymentResponse.java
Normal file
@@ -0,0 +1,294 @@
|
||||
package com.gxwebsoft.payment.dto;
|
||||
|
||||
import com.gxwebsoft.payment.enums.PaymentChannel;
|
||||
import com.gxwebsoft.payment.enums.PaymentStatus;
|
||||
import com.gxwebsoft.payment.enums.PaymentType;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 统一支付响应DTO
|
||||
* 支持所有支付方式的统一响应格式
|
||||
*
|
||||
* @author 科技小王子
|
||||
* @since 2025-01-26
|
||||
*/
|
||||
@Data
|
||||
@Schema(name = "统一支付响应", description = "支持所有支付方式的统一支付响应")
|
||||
public class PaymentResponse {
|
||||
|
||||
@Schema(description = "是否成功")
|
||||
private Boolean success;
|
||||
|
||||
@Schema(description = "错误代码")
|
||||
private String errorCode;
|
||||
|
||||
@Schema(description = "错误信息")
|
||||
private String errorMessage;
|
||||
|
||||
@Schema(description = "订单号")
|
||||
private String orderNo;
|
||||
|
||||
@Schema(description = "第三方交易号")
|
||||
private String transactionId;
|
||||
|
||||
@Schema(description = "支付类型")
|
||||
private PaymentType paymentType;
|
||||
|
||||
@Schema(description = "支付渠道")
|
||||
private PaymentChannel paymentChannel;
|
||||
|
||||
@Schema(description = "支付状态")
|
||||
private PaymentStatus paymentStatus;
|
||||
|
||||
@Schema(description = "支付金额")
|
||||
private BigDecimal amount;
|
||||
|
||||
@Schema(description = "实际支付金额")
|
||||
private BigDecimal paidAmount;
|
||||
|
||||
@Schema(description = "货币类型")
|
||||
private String currency;
|
||||
|
||||
@Schema(description = "租户ID")
|
||||
private Integer tenantId;
|
||||
|
||||
@Schema(description = "用户ID")
|
||||
private Integer userId;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(description = "支付时间")
|
||||
private LocalDateTime payTime;
|
||||
|
||||
@Schema(description = "过期时间")
|
||||
private LocalDateTime expireTime;
|
||||
|
||||
// 微信支付特有字段
|
||||
@Schema(description = "微信支付二维码URL(Native支付)")
|
||||
private String codeUrl;
|
||||
|
||||
@Schema(description = "微信支付参数(JSAPI支付)")
|
||||
private WechatPayParams wechatPayParams;
|
||||
|
||||
@Schema(description = "微信H5支付URL")
|
||||
private String h5Url;
|
||||
|
||||
// 支付宝特有字段
|
||||
@Schema(description = "支付宝支付表单(网页支付)")
|
||||
private String alipayForm;
|
||||
|
||||
@Schema(description = "支付宝支付URL(手机网站支付)")
|
||||
private String alipayUrl;
|
||||
|
||||
@Schema(description = "支付宝支付参数(APP支付)")
|
||||
private String alipayParams;
|
||||
|
||||
// 银联支付特有字段
|
||||
@Schema(description = "银联支付表单")
|
||||
private String unionPayForm;
|
||||
|
||||
@Schema(description = "银联支付URL")
|
||||
private String unionPayUrl;
|
||||
|
||||
@Schema(description = "扩展参数")
|
||||
private Map<String, Object> extraParams;
|
||||
|
||||
/**
|
||||
* 微信支付参数
|
||||
*/
|
||||
@Data
|
||||
@Schema(name = "微信支付参数", description = "微信JSAPI支付所需参数")
|
||||
public static class WechatPayParams {
|
||||
@Schema(description = "应用ID")
|
||||
private String appId;
|
||||
|
||||
@Schema(description = "时间戳")
|
||||
private String timeStamp;
|
||||
|
||||
@Schema(description = "随机字符串")
|
||||
private String nonceStr;
|
||||
|
||||
@Schema(description = "订单详情扩展字符串")
|
||||
private String packageValue;
|
||||
|
||||
@Schema(description = "签名方式")
|
||||
private String signType;
|
||||
|
||||
@Schema(description = "签名")
|
||||
private String paySign;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建成功响应
|
||||
*/
|
||||
public static PaymentResponse success(String orderNo, PaymentType paymentType) {
|
||||
PaymentResponse response = new PaymentResponse();
|
||||
response.setSuccess(true);
|
||||
response.setOrderNo(orderNo);
|
||||
response.setPaymentType(paymentType);
|
||||
response.setPaymentStatus(PaymentStatus.PENDING);
|
||||
response.setCreateTime(LocalDateTime.now());
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建失败响应
|
||||
*/
|
||||
public static PaymentResponse failure(String errorCode, String errorMessage) {
|
||||
PaymentResponse response = new PaymentResponse();
|
||||
response.setSuccess(false);
|
||||
response.setErrorCode(errorCode);
|
||||
response.setErrorMessage(errorMessage);
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建微信Native支付响应
|
||||
*/
|
||||
public static PaymentResponse wechatNative(String orderNo, String codeUrl, BigDecimal amount, Integer tenantId) {
|
||||
PaymentResponse response = success(orderNo, PaymentType.WECHAT_NATIVE);
|
||||
response.setCodeUrl(codeUrl);
|
||||
response.setPaymentChannel(PaymentChannel.WECHAT_NATIVE);
|
||||
response.setAmount(amount);
|
||||
response.setTenantId(tenantId);
|
||||
response.setCurrency("CNY");
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建微信JSAPI支付响应
|
||||
*/
|
||||
public static PaymentResponse wechatJsapi(String orderNo, WechatPayParams payParams, BigDecimal amount, Integer tenantId) {
|
||||
PaymentResponse response = success(orderNo, PaymentType.WECHAT);
|
||||
response.setWechatPayParams(payParams);
|
||||
response.setPaymentChannel(PaymentChannel.WECHAT_JSAPI);
|
||||
response.setAmount(amount);
|
||||
response.setTenantId(tenantId);
|
||||
response.setCurrency("CNY");
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建微信H5支付响应
|
||||
*/
|
||||
public static PaymentResponse wechatH5(String orderNo, String h5Url, BigDecimal amount, Integer tenantId) {
|
||||
PaymentResponse response = success(orderNo, PaymentType.WECHAT);
|
||||
response.setH5Url(h5Url);
|
||||
response.setPaymentChannel(PaymentChannel.WECHAT_H5);
|
||||
response.setAmount(amount);
|
||||
response.setTenantId(tenantId);
|
||||
response.setCurrency("CNY");
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建支付宝网页支付响应
|
||||
*/
|
||||
public static PaymentResponse alipayWeb(String orderNo, String alipayForm, BigDecimal amount, Integer tenantId) {
|
||||
PaymentResponse response = success(orderNo, PaymentType.ALIPAY);
|
||||
response.setAlipayForm(alipayForm);
|
||||
response.setPaymentChannel(PaymentChannel.ALIPAY_WEB);
|
||||
response.setAmount(amount);
|
||||
response.setTenantId(tenantId);
|
||||
response.setCurrency("CNY");
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建支付宝手机网站支付响应
|
||||
*/
|
||||
public static PaymentResponse alipayWap(String orderNo, String alipayUrl, BigDecimal amount, Integer tenantId) {
|
||||
PaymentResponse response = success(orderNo, PaymentType.ALIPAY);
|
||||
response.setAlipayUrl(alipayUrl);
|
||||
response.setPaymentChannel(PaymentChannel.ALIPAY_WAP);
|
||||
response.setAmount(amount);
|
||||
response.setTenantId(tenantId);
|
||||
response.setCurrency("CNY");
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建支付宝APP支付响应
|
||||
*/
|
||||
public static PaymentResponse alipayApp(String orderNo, String alipayParams, BigDecimal amount, Integer tenantId) {
|
||||
PaymentResponse response = success(orderNo, PaymentType.ALIPAY);
|
||||
response.setAlipayParams(alipayParams);
|
||||
response.setPaymentChannel(PaymentChannel.ALIPAY_APP);
|
||||
response.setAmount(amount);
|
||||
response.setTenantId(tenantId);
|
||||
response.setCurrency("CNY");
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建余额支付响应
|
||||
*/
|
||||
public static PaymentResponse balance(String orderNo, BigDecimal amount, Integer tenantId, Integer userId) {
|
||||
PaymentResponse response = success(orderNo, PaymentType.BALANCE);
|
||||
response.setPaymentChannel(PaymentChannel.BALANCE);
|
||||
response.setPaymentStatus(PaymentStatus.SUCCESS);
|
||||
response.setAmount(amount);
|
||||
response.setPaidAmount(amount);
|
||||
response.setTenantId(tenantId);
|
||||
response.setUserId(userId);
|
||||
response.setCurrency("CNY");
|
||||
response.setPayTime(LocalDateTime.now());
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为成功响应
|
||||
*/
|
||||
public boolean isSuccess() {
|
||||
return Boolean.TRUE.equals(success);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否需要用户进一步操作
|
||||
*/
|
||||
public boolean needUserAction() {
|
||||
return isSuccess() && paymentStatus == PaymentStatus.PENDING;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取支付结果描述
|
||||
*/
|
||||
public String getResultDescription() {
|
||||
if (!isSuccess()) {
|
||||
return errorMessage != null ? errorMessage : "支付失败";
|
||||
}
|
||||
|
||||
if (paymentStatus == null) {
|
||||
return "支付状态未知";
|
||||
}
|
||||
|
||||
switch (paymentStatus) {
|
||||
case SUCCESS:
|
||||
return "支付成功";
|
||||
case PENDING:
|
||||
return "等待支付";
|
||||
case PROCESSING:
|
||||
return "支付处理中";
|
||||
case FAILED:
|
||||
return "支付失败";
|
||||
case CANCELLED:
|
||||
return "支付已取消";
|
||||
case TIMEOUT:
|
||||
return "支付超时";
|
||||
default:
|
||||
return paymentStatus.getName();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("PaymentResponse{success=%s, orderNo='%s', paymentType=%s, paymentStatus=%s, amount=%s}",
|
||||
success, orderNo, paymentType, paymentStatus, amount);
|
||||
}
|
||||
}
|
||||
41
java/payment/dto/PaymentStatusUpdateRequest.java
Normal file
41
java/payment/dto/PaymentStatusUpdateRequest.java
Normal file
@@ -0,0 +1,41 @@
|
||||
package com.gxwebsoft.payment.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Positive;
|
||||
|
||||
/**
|
||||
* 支付状态更新请求DTO
|
||||
* 用于手动更新支付状态的请求参数
|
||||
*
|
||||
* @author 科技小王子
|
||||
* @since 2025-01-26
|
||||
*/
|
||||
@Data
|
||||
@Schema(name = "支付状态更新请求", description = "用于手动更新支付状态")
|
||||
public class PaymentStatusUpdateRequest {
|
||||
|
||||
@Schema(description = "订单号", required = true, example = "ORDER_1756544921075")
|
||||
@NotBlank(message = "订单号不能为空")
|
||||
private String orderNo;
|
||||
|
||||
@Schema(description = "租户ID", required = true, example = "10398")
|
||||
@NotNull(message = "租户ID不能为空")
|
||||
@Positive(message = "租户ID必须为正数")
|
||||
private Integer tenantId;
|
||||
|
||||
@Schema(description = "第三方交易号", example = "4200001234567890123")
|
||||
private String transactionId;
|
||||
|
||||
@Schema(description = "支付时间", example = "2025-01-26T10:30:00")
|
||||
private String payTime;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("PaymentStatusUpdateRequest{orderNo='%s', tenantId=%d, transactionId='%s', payTime='%s'}",
|
||||
orderNo, tenantId, transactionId, payTime);
|
||||
}
|
||||
}
|
||||
158
java/payment/dto/PaymentWithOrderRequest.java
Normal file
158
java/payment/dto/PaymentWithOrderRequest.java
Normal file
@@ -0,0 +1,158 @@
|
||||
package com.gxwebsoft.payment.dto;
|
||||
|
||||
import com.gxwebsoft.payment.enums.PaymentType;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 支付与订单创建请求DTO
|
||||
* 用于统一支付模块中的订单创建和支付
|
||||
*
|
||||
* @author 科技小王子
|
||||
* @since 2025-01-26
|
||||
*/
|
||||
@Data
|
||||
@Schema(name = "PaymentWithOrderRequest", description = "支付与订单创建请求")
|
||||
public class PaymentWithOrderRequest {
|
||||
|
||||
// ========== 支付相关字段 ==========
|
||||
|
||||
@Schema(description = "支付类型", required = true)
|
||||
@NotNull(message = "支付类型不能为空")
|
||||
private PaymentType paymentType;
|
||||
|
||||
@Schema(description = "支付金额", required = true)
|
||||
@NotNull(message = "支付金额不能为空")
|
||||
@DecimalMin(value = "0.01", message = "支付金额必须大于0")
|
||||
@Digits(integer = 10, fraction = 2, message = "支付金额格式不正确")
|
||||
private BigDecimal amount;
|
||||
|
||||
@Schema(description = "订单标题", required = true)
|
||||
@NotBlank(message = "订单标题不能为空")
|
||||
@Size(max = 60, message = "订单标题长度不能超过60个字符")
|
||||
private String subject;
|
||||
|
||||
@Schema(description = "订单描述")
|
||||
@Size(max = 500, message = "订单描述长度不能超过500个字符")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "租户ID", required = true)
|
||||
@NotNull(message = "租户ID不能为空")
|
||||
@Positive(message = "租户ID必须为正数")
|
||||
private Integer tenantId;
|
||||
|
||||
// ========== 订单相关字段 ==========
|
||||
|
||||
@Schema(description = "订单信息", required = true)
|
||||
@Valid
|
||||
@NotNull(message = "订单信息不能为空")
|
||||
private OrderInfo orderInfo;
|
||||
|
||||
/**
|
||||
* 订单信息
|
||||
*/
|
||||
@Data
|
||||
@Schema(name = "OrderInfo", description = "订单信息")
|
||||
public static class OrderInfo {
|
||||
|
||||
@Schema(description = "订单类型,0商城订单 1预定订单/外卖 2会员卡")
|
||||
@NotNull(message = "订单类型不能为空")
|
||||
@Min(value = 0, message = "订单类型值无效")
|
||||
@Max(value = 2, message = "订单类型值无效")
|
||||
private Integer type;
|
||||
|
||||
@Schema(description = "收货人姓名")
|
||||
@Size(max = 50, message = "收货人姓名长度不能超过50个字符")
|
||||
private String realName;
|
||||
|
||||
@Schema(description = "收货地址")
|
||||
@Size(max = 200, message = "收货地址长度不能超过200个字符")
|
||||
private String address;
|
||||
|
||||
@Schema(description = "关联收货地址ID")
|
||||
private Integer addressId;
|
||||
|
||||
@Schema(description = "快递/自提,0快递 1自提")
|
||||
private Integer deliveryType;
|
||||
|
||||
@Schema(description = "下单渠道,0小程序预定 1俱乐部训练场 3活动订场")
|
||||
private Integer channel;
|
||||
|
||||
@Schema(description = "商户ID")
|
||||
private Long merchantId;
|
||||
|
||||
@Schema(description = "商户名称")
|
||||
private String merchantName;
|
||||
|
||||
@Schema(description = "使用的优惠券ID")
|
||||
private Integer couponId;
|
||||
|
||||
@Schema(description = "备注")
|
||||
@Size(max = 500, message = "备注长度不能超过500字符")
|
||||
private String comments;
|
||||
|
||||
@Schema(description = "订单商品列表", required = true)
|
||||
@Valid
|
||||
@NotEmpty(message = "订单商品列表不能为空")
|
||||
private List<OrderGoodsItem> goodsItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* 订单商品项
|
||||
*/
|
||||
@Data
|
||||
@Schema(name = "OrderGoodsItem", description = "订单商品项")
|
||||
public static class OrderGoodsItem {
|
||||
|
||||
@Schema(description = "商品ID", required = true)
|
||||
@NotNull(message = "商品ID不能为空")
|
||||
@Positive(message = "商品ID必须为正数")
|
||||
private Integer goodsId;
|
||||
|
||||
@Schema(description = "商品SKU ID")
|
||||
private Integer skuId;
|
||||
|
||||
@Schema(description = "商品数量", required = true)
|
||||
@NotNull(message = "商品数量不能为空")
|
||||
@Min(value = 1, message = "商品数量必须大于0")
|
||||
private Integer quantity;
|
||||
|
||||
@Schema(description = "规格信息,如:颜色:红色|尺寸:L")
|
||||
private String specInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取格式化的金额字符串
|
||||
*/
|
||||
public String getFormattedAmount() {
|
||||
if (amount == null) {
|
||||
return "0.00";
|
||||
}
|
||||
return String.format("%.2f", amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证订单商品总金额是否与支付金额一致
|
||||
*/
|
||||
public boolean isAmountConsistent() {
|
||||
if (amount == null || orderInfo == null || orderInfo.getGoodsItems() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 这里可以添加商品金额计算逻辑
|
||||
// 实际实现时需要查询数据库获取商品价格
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("PaymentWithOrderRequest{paymentType=%s, amount=%s, subject='%s', tenantId=%d, goodsCount=%d}",
|
||||
paymentType, getFormattedAmount(), subject, tenantId,
|
||||
orderInfo != null && orderInfo.getGoodsItems() != null ? orderInfo.getGoodsItems().size() : 0);
|
||||
}
|
||||
}
|
||||
159
java/payment/enums/PaymentChannel.java
Normal file
159
java/payment/enums/PaymentChannel.java
Normal file
@@ -0,0 +1,159 @@
|
||||
package com.gxwebsoft.payment.enums;
|
||||
|
||||
/**
|
||||
* 支付渠道枚举
|
||||
* 定义具体的支付渠道类型
|
||||
*
|
||||
* @author 科技小王子
|
||||
* @since 2025-01-26
|
||||
*/
|
||||
public enum PaymentChannel {
|
||||
|
||||
/** 微信JSAPI支付 */
|
||||
WECHAT_JSAPI("wechat_jsapi", "微信JSAPI支付", PaymentType.WECHAT),
|
||||
|
||||
/** 微信Native支付 */
|
||||
WECHAT_NATIVE("wechat_native", "微信Native支付", PaymentType.WECHAT_NATIVE),
|
||||
|
||||
/** 微信H5支付 */
|
||||
WECHAT_H5("wechat_h5", "微信H5支付", PaymentType.WECHAT),
|
||||
|
||||
/** 微信APP支付 */
|
||||
WECHAT_APP("wechat_app", "微信APP支付", PaymentType.WECHAT),
|
||||
|
||||
/** 微信小程序支付 */
|
||||
WECHAT_MINI("wechat_mini", "微信小程序支付", PaymentType.WECHAT),
|
||||
|
||||
/** 支付宝网页支付 */
|
||||
ALIPAY_WEB("alipay_web", "支付宝网页支付", PaymentType.ALIPAY),
|
||||
|
||||
/** 支付宝手机网站支付 */
|
||||
ALIPAY_WAP("alipay_wap", "支付宝手机网站支付", PaymentType.ALIPAY),
|
||||
|
||||
/** 支付宝APP支付 */
|
||||
ALIPAY_APP("alipay_app", "支付宝APP支付", PaymentType.ALIPAY),
|
||||
|
||||
/** 支付宝小程序支付 */
|
||||
ALIPAY_MINI("alipay_mini", "支付宝小程序支付", PaymentType.ALIPAY),
|
||||
|
||||
/** 银联网关支付 */
|
||||
UNION_WEB("union_web", "银联网关支付", PaymentType.UNION_PAY),
|
||||
|
||||
/** 银联手机支付 */
|
||||
UNION_WAP("union_wap", "银联手机支付", PaymentType.UNION_PAY),
|
||||
|
||||
/** 余额支付 */
|
||||
BALANCE("balance", "余额支付", PaymentType.BALANCE),
|
||||
|
||||
/** 现金支付 */
|
||||
CASH("cash", "现金支付", PaymentType.CASH),
|
||||
|
||||
/** POS机支付 */
|
||||
POS("pos", "POS机支付", PaymentType.POS);
|
||||
|
||||
private final String code;
|
||||
private final String name;
|
||||
private final PaymentType paymentType;
|
||||
|
||||
PaymentChannel(String code, String name, PaymentType paymentType) {
|
||||
this.code = code;
|
||||
this.name = name;
|
||||
this.paymentType = paymentType;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public PaymentType getPaymentType() {
|
||||
return paymentType;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据代码获取支付渠道
|
||||
*/
|
||||
public static PaymentChannel getByCode(String code) {
|
||||
if (code == null) {
|
||||
return null;
|
||||
}
|
||||
for (PaymentChannel channel : values()) {
|
||||
if (channel.code.equals(code)) {
|
||||
return channel;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据支付类型获取默认渠道
|
||||
*/
|
||||
public static PaymentChannel getDefaultByPaymentType(PaymentType paymentType) {
|
||||
if (paymentType == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (paymentType) {
|
||||
case WECHAT:
|
||||
return WECHAT_JSAPI;
|
||||
case WECHAT_NATIVE:
|
||||
return WECHAT_NATIVE;
|
||||
case ALIPAY:
|
||||
return ALIPAY_WEB;
|
||||
case UNION_PAY:
|
||||
return UNION_WEB;
|
||||
case BALANCE:
|
||||
return BALANCE;
|
||||
case CASH:
|
||||
return CASH;
|
||||
case POS:
|
||||
return POS;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为微信支付渠道
|
||||
*/
|
||||
public boolean isWechatChannel() {
|
||||
return paymentType.isWechatPay();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为支付宝支付渠道
|
||||
*/
|
||||
public boolean isAlipayChannel() {
|
||||
return paymentType == PaymentType.ALIPAY;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为银联支付渠道
|
||||
*/
|
||||
public boolean isUnionPayChannel() {
|
||||
return paymentType == PaymentType.UNION_PAY;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为第三方支付渠道
|
||||
*/
|
||||
public boolean isThirdPartyChannel() {
|
||||
return paymentType.isThirdPartyPay();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否支持退款
|
||||
*/
|
||||
public boolean supportRefund() {
|
||||
return isThirdPartyChannel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("PaymentChannel{code='%s', name='%s', paymentType=%s}",
|
||||
code, name, paymentType);
|
||||
}
|
||||
}
|
||||
141
java/payment/enums/PaymentStatus.java
Normal file
141
java/payment/enums/PaymentStatus.java
Normal file
@@ -0,0 +1,141 @@
|
||||
package com.gxwebsoft.payment.enums;
|
||||
|
||||
/**
|
||||
* 支付状态枚举
|
||||
* 定义支付过程中的各种状态
|
||||
*
|
||||
* @author 科技小王子
|
||||
* @since 2025-01-26
|
||||
*/
|
||||
public enum PaymentStatus {
|
||||
|
||||
/** 待支付 */
|
||||
PENDING(0, "待支付", "PENDING"),
|
||||
|
||||
/** 支付中 */
|
||||
PROCESSING(1, "支付中", "PROCESSING"),
|
||||
|
||||
/** 支付成功 */
|
||||
SUCCESS(2, "支付成功", "SUCCESS"),
|
||||
|
||||
/** 支付失败 */
|
||||
FAILED(3, "支付失败", "FAILED"),
|
||||
|
||||
/** 支付取消 */
|
||||
CANCELLED(4, "支付取消", "CANCELLED"),
|
||||
|
||||
/** 支付超时 */
|
||||
TIMEOUT(5, "支付超时", "TIMEOUT"),
|
||||
|
||||
/** 退款中 */
|
||||
REFUNDING(6, "退款中", "REFUNDING"),
|
||||
|
||||
/** 退款成功 */
|
||||
REFUNDED(7, "退款成功", "REFUNDED"),
|
||||
|
||||
/** 退款失败 */
|
||||
REFUND_FAILED(8, "退款失败", "REFUND_FAILED"),
|
||||
|
||||
/** 部分退款 */
|
||||
PARTIAL_REFUNDED(9, "部分退款", "PARTIAL_REFUNDED");
|
||||
|
||||
private final Integer code;
|
||||
private final String name;
|
||||
private final String status;
|
||||
|
||||
PaymentStatus(Integer code, String name, String status) {
|
||||
this.code = code;
|
||||
this.name = name;
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public Integer getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据代码获取支付状态
|
||||
*/
|
||||
public static PaymentStatus getByCode(Integer code) {
|
||||
if (code == null) {
|
||||
return null;
|
||||
}
|
||||
for (PaymentStatus status : values()) {
|
||||
if (status.code.equals(code)) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据状态字符串获取支付状态
|
||||
*/
|
||||
public static PaymentStatus getByStatus(String status) {
|
||||
if (status == null) {
|
||||
return null;
|
||||
}
|
||||
for (PaymentStatus paymentStatus : values()) {
|
||||
if (paymentStatus.status.equals(status)) {
|
||||
return paymentStatus;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为最终状态(不会再变化)
|
||||
*/
|
||||
public boolean isFinalStatus() {
|
||||
return this == SUCCESS || this == FAILED || this == CANCELLED ||
|
||||
this == TIMEOUT || this == REFUNDED || this == REFUND_FAILED;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为成功状态
|
||||
*/
|
||||
public boolean isSuccessStatus() {
|
||||
return this == SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为失败状态
|
||||
*/
|
||||
public boolean isFailedStatus() {
|
||||
return this == FAILED || this == CANCELLED || this == TIMEOUT || this == REFUND_FAILED;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为退款相关状态
|
||||
*/
|
||||
public boolean isRefundStatus() {
|
||||
return this == REFUNDING || this == REFUNDED || this == REFUND_FAILED || this == PARTIAL_REFUNDED;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否可以退款
|
||||
*/
|
||||
public boolean canRefund() {
|
||||
return this == SUCCESS || this == PARTIAL_REFUNDED;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否可以取消
|
||||
*/
|
||||
public boolean canCancel() {
|
||||
return this == PENDING || this == PROCESSING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("PaymentStatus{code=%d, name='%s', status='%s'}", code, name, status);
|
||||
}
|
||||
}
|
||||
224
java/payment/enums/PaymentType.java
Normal file
224
java/payment/enums/PaymentType.java
Normal file
@@ -0,0 +1,224 @@
|
||||
package com.gxwebsoft.payment.enums;
|
||||
|
||||
/**
|
||||
* 支付类型枚举
|
||||
* 定义系统支持的所有支付方式
|
||||
*
|
||||
* @author 科技小王子
|
||||
* @since 2025-01-26
|
||||
*/
|
||||
public enum PaymentType {
|
||||
|
||||
/** 余额支付 */
|
||||
BALANCE(0, "余额支付", "balance"),
|
||||
|
||||
/** 微信支付(包含JSAPI和Native) */
|
||||
WECHAT(1, "微信支付", "wechat"),
|
||||
|
||||
/** 支付宝支付 */
|
||||
ALIPAY(2, "支付宝支付", "alipay"),
|
||||
|
||||
/** 银联支付 */
|
||||
UNION_PAY(3, "银联支付", "union_pay"),
|
||||
|
||||
/** 现金支付 */
|
||||
CASH(4, "现金支付", "cash"),
|
||||
|
||||
/** POS机支付 */
|
||||
POS(5, "POS机支付", "pos"),
|
||||
|
||||
/** 免费 */
|
||||
FREE(6, "免费", "free"),
|
||||
|
||||
/** 积分支付 */
|
||||
POINTS(7, "积分支付", "points"),
|
||||
|
||||
// ========== 已废弃的支付方式(保留用于数据兼容) ==========
|
||||
|
||||
/** @deprecated 微信Native支付 - 已合并到WECHAT */
|
||||
@Deprecated
|
||||
WECHAT_NATIVE(102, "微信Native支付", "wechat_native"),
|
||||
|
||||
/** @deprecated 会员卡支付 - 建议使用余额支付 */
|
||||
@Deprecated
|
||||
MEMBER_CARD_OLD(8, "会员卡支付", "member_card"),
|
||||
|
||||
/** @deprecated VIP月卡 - 建议使用余额支付 */
|
||||
@Deprecated
|
||||
VIP_MONTHLY(9, "VIP月卡", "vip_monthly"),
|
||||
|
||||
/** @deprecated VIP年卡 - 建议使用余额支付 */
|
||||
@Deprecated
|
||||
VIP_YEARLY(10, "VIP年卡", "vip_yearly"),
|
||||
|
||||
/** @deprecated VIP次卡 - 建议使用余额支付 */
|
||||
@Deprecated
|
||||
VIP_COUNT(11, "VIP次卡", "vip_count"),
|
||||
|
||||
/** @deprecated 免费(旧编号) - 已迁移到新编号6 */
|
||||
@Deprecated
|
||||
FREE_OLD(12, "免费", "free"),
|
||||
|
||||
/** @deprecated VIP充值卡 - 建议使用余额支付 */
|
||||
@Deprecated
|
||||
VIP_RECHARGE(13, "VIP充值卡", "vip_recharge"),
|
||||
|
||||
/** @deprecated IC充值卡 - 建议使用余额支付 */
|
||||
@Deprecated
|
||||
IC_RECHARGE(14, "IC充值卡", "ic_recharge"),
|
||||
|
||||
/** @deprecated 积分支付(旧编号) - 已迁移到新编号7 */
|
||||
@Deprecated
|
||||
POINTS_OLD(15, "积分支付", "points"),
|
||||
|
||||
/** @deprecated VIP季卡 - 建议使用余额支付 */
|
||||
@Deprecated
|
||||
VIP_QUARTERLY(16, "VIP季卡", "vip_quarterly"),
|
||||
|
||||
/** @deprecated IC月卡 - 建议使用余额支付 */
|
||||
@Deprecated
|
||||
IC_MONTHLY(17, "IC月卡", "ic_monthly"),
|
||||
|
||||
/** @deprecated IC年卡 - 建议使用余额支付 */
|
||||
@Deprecated
|
||||
IC_YEARLY(18, "IC年卡", "ic_yearly"),
|
||||
|
||||
/** @deprecated IC次卡 - 建议使用余额支付 */
|
||||
@Deprecated
|
||||
IC_COUNT(19, "IC次卡", "ic_count"),
|
||||
|
||||
/** @deprecated IC季卡 - 建议使用余额支付 */
|
||||
@Deprecated
|
||||
IC_QUARTERLY(20, "IC季卡", "ic_quarterly"),
|
||||
|
||||
/** @deprecated 代付 - 建议通过业务逻辑实现 */
|
||||
@Deprecated
|
||||
PROXY_PAY(21, "代付", "proxy_pay"),
|
||||
|
||||
/** @deprecated 支付宝(旧编号) - 已迁移到新编号2 */
|
||||
@Deprecated
|
||||
ALIPAY_OLD(22, "支付宝支付", "alipay"),
|
||||
|
||||
/** @deprecated 银联支付(旧编号) - 已迁移到新编号3 */
|
||||
@Deprecated
|
||||
UNION_PAY_OLD(23, "银联支付", "union_pay");
|
||||
|
||||
private final Integer code;
|
||||
private final String name;
|
||||
private final String channel;
|
||||
|
||||
PaymentType(Integer code, String name, String channel) {
|
||||
this.code = code;
|
||||
this.name = name;
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
public Integer getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getChannel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据代码获取支付类型
|
||||
*/
|
||||
public static PaymentType getByCode(Integer code) {
|
||||
if (code == null) {
|
||||
return null;
|
||||
}
|
||||
for (PaymentType type : values()) {
|
||||
if (type.code.equals(code)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据渠道获取支付类型
|
||||
*/
|
||||
public static PaymentType getByChannel(String channel) {
|
||||
if (channel == null) {
|
||||
return null;
|
||||
}
|
||||
for (PaymentType type : values()) {
|
||||
if (type.channel.equals(channel)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为微信支付类型
|
||||
*/
|
||||
public boolean isWechatPay() {
|
||||
return this == WECHAT || this == WECHAT_NATIVE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取微信支付的具体类型
|
||||
* @param openid 用户openid
|
||||
* @return JSAPI 或 NATIVE
|
||||
*/
|
||||
public String getWechatPayType(String openid) {
|
||||
if (!isWechatPay()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 有openid使用JSAPI,无openid使用Native
|
||||
return (openid != null && !openid.trim().isEmpty()) ? "JSAPI" : "NATIVE";
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为第三方支付
|
||||
*/
|
||||
public boolean isThirdPartyPay() {
|
||||
return isWechatPay() || this == ALIPAY || this == UNION_PAY;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否需要在线支付
|
||||
*/
|
||||
public boolean isOnlinePay() {
|
||||
return isThirdPartyPay();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为卡类支付(已废弃的支付方式)
|
||||
* @deprecated 卡类支付已废弃,建议使用余额支付
|
||||
*/
|
||||
@Deprecated
|
||||
public boolean isCardPay() {
|
||||
return this == MEMBER_CARD_OLD ||
|
||||
this == VIP_MONTHLY || this == VIP_YEARLY || this == VIP_COUNT || this == VIP_QUARTERLY ||
|
||||
this == IC_MONTHLY || this == IC_YEARLY || this == IC_COUNT || this == IC_QUARTERLY ||
|
||||
this == VIP_RECHARGE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为推荐使用的核心支付方式
|
||||
*/
|
||||
public boolean isCorePaymentType() {
|
||||
return this == BALANCE || this == WECHAT || this == ALIPAY || this == UNION_PAY ||
|
||||
this == CASH || this == POS || this == FREE || this == POINTS;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为已废弃的支付方式
|
||||
*/
|
||||
public boolean isDeprecated() {
|
||||
return !isCorePaymentType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("PaymentType{code=%d, name='%s', channel='%s'}", code, name, channel);
|
||||
}
|
||||
}
|
||||
221
java/payment/exception/PaymentException.java
Normal file
221
java/payment/exception/PaymentException.java
Normal file
@@ -0,0 +1,221 @@
|
||||
package com.gxwebsoft.payment.exception;
|
||||
|
||||
import com.gxwebsoft.payment.enums.PaymentType;
|
||||
|
||||
/**
|
||||
* 支付异常基类
|
||||
* 统一处理支付相关的业务异常
|
||||
*
|
||||
* @author 科技小王子
|
||||
* @since 2025-01-26
|
||||
*/
|
||||
public class PaymentException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 错误代码
|
||||
*/
|
||||
private String errorCode;
|
||||
|
||||
/**
|
||||
* 支付类型
|
||||
*/
|
||||
private PaymentType paymentType;
|
||||
|
||||
/**
|
||||
* 租户ID
|
||||
*/
|
||||
private Integer tenantId;
|
||||
|
||||
/**
|
||||
* 订单号
|
||||
*/
|
||||
private String orderNo;
|
||||
|
||||
public PaymentException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public PaymentException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public PaymentException(String errorCode, String message) {
|
||||
super(message);
|
||||
this.errorCode = errorCode;
|
||||
}
|
||||
|
||||
public PaymentException(String errorCode, String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.errorCode = errorCode;
|
||||
}
|
||||
|
||||
public PaymentException(String errorCode, String message, PaymentType paymentType) {
|
||||
super(message);
|
||||
this.errorCode = errorCode;
|
||||
this.paymentType = paymentType;
|
||||
}
|
||||
|
||||
public PaymentException(String errorCode, String message, PaymentType paymentType, Integer tenantId) {
|
||||
super(message);
|
||||
this.errorCode = errorCode;
|
||||
this.paymentType = paymentType;
|
||||
this.tenantId = tenantId;
|
||||
}
|
||||
|
||||
public PaymentException(String errorCode, String message, PaymentType paymentType, Integer tenantId, String orderNo) {
|
||||
super(message);
|
||||
this.errorCode = errorCode;
|
||||
this.paymentType = paymentType;
|
||||
this.tenantId = tenantId;
|
||||
this.orderNo = orderNo;
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
public String getErrorCode() {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
public void setErrorCode(String errorCode) {
|
||||
this.errorCode = errorCode;
|
||||
}
|
||||
|
||||
public PaymentType getPaymentType() {
|
||||
return paymentType;
|
||||
}
|
||||
|
||||
public void setPaymentType(PaymentType paymentType) {
|
||||
this.paymentType = paymentType;
|
||||
}
|
||||
|
||||
public Integer getTenantId() {
|
||||
return tenantId;
|
||||
}
|
||||
|
||||
public void setTenantId(Integer tenantId) {
|
||||
this.tenantId = tenantId;
|
||||
}
|
||||
|
||||
public String getOrderNo() {
|
||||
return orderNo;
|
||||
}
|
||||
|
||||
public void setOrderNo(String orderNo) {
|
||||
this.orderNo = orderNo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("PaymentException{");
|
||||
if (errorCode != null) {
|
||||
sb.append("errorCode='").append(errorCode).append("', ");
|
||||
}
|
||||
if (paymentType != null) {
|
||||
sb.append("paymentType=").append(paymentType).append(", ");
|
||||
}
|
||||
if (tenantId != null) {
|
||||
sb.append("tenantId=").append(tenantId).append(", ");
|
||||
}
|
||||
if (orderNo != null) {
|
||||
sb.append("orderNo='").append(orderNo).append("', ");
|
||||
}
|
||||
sb.append("message='").append(getMessage()).append("'");
|
||||
sb.append("}");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 支付错误代码常量
|
||||
*/
|
||||
public static class ErrorCode {
|
||||
/** 参数错误 */
|
||||
public static final String PARAM_ERROR = "PARAM_ERROR";
|
||||
/** 配置错误 */
|
||||
public static final String CONFIG_ERROR = "CONFIG_ERROR";
|
||||
/** 证书错误 */
|
||||
public static final String CERTIFICATE_ERROR = "CERTIFICATE_ERROR";
|
||||
/** 网络错误 */
|
||||
public static final String NETWORK_ERROR = "NETWORK_ERROR";
|
||||
/** 签名错误 */
|
||||
public static final String SIGNATURE_ERROR = "SIGNATURE_ERROR";
|
||||
/** 金额错误 */
|
||||
public static final String AMOUNT_ERROR = "AMOUNT_ERROR";
|
||||
/** 订单错误 */
|
||||
public static final String ORDER_ERROR = "ORDER_ERROR";
|
||||
/** 状态错误 */
|
||||
public static final String STATUS_ERROR = "STATUS_ERROR";
|
||||
/** 余额不足 */
|
||||
public static final String INSUFFICIENT_BALANCE = "INSUFFICIENT_BALANCE";
|
||||
/** 支付超时 */
|
||||
public static final String TIMEOUT_ERROR = "TIMEOUT_ERROR";
|
||||
/** 重复支付 */
|
||||
public static final String DUPLICATE_PAYMENT = "DUPLICATE_PAYMENT";
|
||||
/** 不支持的支付方式 */
|
||||
public static final String UNSUPPORTED_PAYMENT = "UNSUPPORTED_PAYMENT";
|
||||
/** 系统错误 */
|
||||
public static final String SYSTEM_ERROR = "SYSTEM_ERROR";
|
||||
}
|
||||
|
||||
// 静态工厂方法
|
||||
public static PaymentException paramError(String message) {
|
||||
return new PaymentException(ErrorCode.PARAM_ERROR, message);
|
||||
}
|
||||
|
||||
public static PaymentException configError(String message, PaymentType paymentType, Integer tenantId) {
|
||||
return new PaymentException(ErrorCode.CONFIG_ERROR, message, paymentType, tenantId);
|
||||
}
|
||||
|
||||
public static PaymentException certificateError(String message, PaymentType paymentType) {
|
||||
return new PaymentException(ErrorCode.CERTIFICATE_ERROR, message, paymentType);
|
||||
}
|
||||
|
||||
public static PaymentException networkError(String message, PaymentType paymentType, Throwable cause) {
|
||||
return new PaymentException(ErrorCode.NETWORK_ERROR, message, cause);
|
||||
}
|
||||
|
||||
public static PaymentException signatureError(String message, PaymentType paymentType) {
|
||||
return new PaymentException(ErrorCode.SIGNATURE_ERROR, message, paymentType);
|
||||
}
|
||||
|
||||
public static PaymentException amountError(String message) {
|
||||
return new PaymentException(ErrorCode.AMOUNT_ERROR, message);
|
||||
}
|
||||
|
||||
public static PaymentException orderError(String message, String orderNo) {
|
||||
PaymentException exception = new PaymentException(ErrorCode.ORDER_ERROR, message);
|
||||
exception.setOrderNo(orderNo);
|
||||
return exception;
|
||||
}
|
||||
|
||||
public static PaymentException statusError(String message, String orderNo) {
|
||||
PaymentException exception = new PaymentException(ErrorCode.STATUS_ERROR, message);
|
||||
exception.setOrderNo(orderNo);
|
||||
return exception;
|
||||
}
|
||||
|
||||
public static PaymentException insufficientBalance(String message, Integer tenantId) {
|
||||
return new PaymentException(ErrorCode.INSUFFICIENT_BALANCE, message, PaymentType.BALANCE, tenantId);
|
||||
}
|
||||
|
||||
public static PaymentException timeoutError(String message, PaymentType paymentType, String orderNo) {
|
||||
PaymentException exception = new PaymentException(ErrorCode.TIMEOUT_ERROR, message, paymentType);
|
||||
exception.setOrderNo(orderNo);
|
||||
return exception;
|
||||
}
|
||||
|
||||
public static PaymentException duplicatePayment(String message, String orderNo) {
|
||||
PaymentException exception = new PaymentException(ErrorCode.DUPLICATE_PAYMENT, message);
|
||||
exception.setOrderNo(orderNo);
|
||||
return exception;
|
||||
}
|
||||
|
||||
public static PaymentException unsupportedPayment(String message, PaymentType paymentType) {
|
||||
return new PaymentException(ErrorCode.UNSUPPORTED_PAYMENT, message, paymentType);
|
||||
}
|
||||
|
||||
public static PaymentException systemError(String message, Throwable cause) {
|
||||
return new PaymentException(ErrorCode.SYSTEM_ERROR, message, cause);
|
||||
}
|
||||
}
|
||||
153
java/payment/exception/PaymentExceptionHandler.java
Normal file
153
java/payment/exception/PaymentExceptionHandler.java
Normal file
@@ -0,0 +1,153 @@
|
||||
package com.gxwebsoft.payment.exception;
|
||||
|
||||
import com.gxwebsoft.common.core.web.ApiResult;
|
||||
import com.gxwebsoft.common.core.web.BaseController;
|
||||
import com.gxwebsoft.payment.constants.PaymentConstants;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.ConstraintViolationException;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 统一支付异常处理器
|
||||
* 处理所有支付相关的异常和参数验证异常
|
||||
*
|
||||
* @author 科技小王子
|
||||
* @since 2025-01-26
|
||||
*/
|
||||
@Slf4j
|
||||
@RestControllerAdvice(basePackages = {"com.gxwebsoft.payment.controller", "com.gxwebsoft.shop.controller"})
|
||||
public class PaymentExceptionHandler extends BaseController {
|
||||
|
||||
/**
|
||||
* 处理支付业务异常
|
||||
*/
|
||||
@ExceptionHandler(PaymentException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public ApiResult<?> handlePaymentException(PaymentException e) {
|
||||
log.warn("支付业务异常: {}", e.getMessage());
|
||||
|
||||
// 记录详细的异常信息
|
||||
if (e.getTenantId() != null) {
|
||||
log.warn("异常租户ID: {}", e.getTenantId());
|
||||
}
|
||||
|
||||
if (e.getPaymentType() != null) {
|
||||
log.warn("异常支付类型: {}", e.getPaymentType());
|
||||
}
|
||||
|
||||
if (e.getOrderNo() != null) {
|
||||
log.warn("异常订单号: {}", e.getOrderNo());
|
||||
}
|
||||
|
||||
if (e.getErrorCode() != null) {
|
||||
log.warn("错误代码: {}", e.getErrorCode());
|
||||
}
|
||||
|
||||
return fail(e.getMessage());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 处理参数验证异常(@Valid注解)
|
||||
*/
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public ApiResult<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
|
||||
List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
|
||||
|
||||
String errorMessage = fieldErrors.stream()
|
||||
.map(error -> error.getField() + ": " + error.getDefaultMessage())
|
||||
.collect(Collectors.joining("; "));
|
||||
|
||||
log.warn("参数验证失败: {}", errorMessage);
|
||||
|
||||
return fail(PaymentConstants.ErrorMessage.PARAM_ERROR + ": " + errorMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理绑定异常
|
||||
*/
|
||||
@ExceptionHandler(BindException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public ApiResult<?> handleBindException(BindException e) {
|
||||
List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
|
||||
|
||||
String errorMessage = fieldErrors.stream()
|
||||
.map(error -> error.getField() + ": " + error.getDefaultMessage())
|
||||
.collect(Collectors.joining("; "));
|
||||
|
||||
log.warn("数据绑定失败: {}", errorMessage);
|
||||
|
||||
return fail(PaymentConstants.ErrorMessage.PARAM_ERROR + ": " + errorMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理约束违反异常(@Validated注解)
|
||||
*/
|
||||
@ExceptionHandler(ConstraintViolationException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public ApiResult<?> handleConstraintViolationException(ConstraintViolationException e) {
|
||||
Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
|
||||
|
||||
String errorMessage = violations.stream()
|
||||
.map(violation -> violation.getPropertyPath() + ": " + violation.getMessage())
|
||||
.collect(Collectors.joining("; "));
|
||||
|
||||
log.warn("约束验证失败: {}", errorMessage);
|
||||
|
||||
return fail(PaymentConstants.ErrorMessage.PARAM_ERROR + ": " + errorMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理非法参数异常
|
||||
*/
|
||||
@ExceptionHandler(IllegalArgumentException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public ApiResult<?> handleIllegalArgumentException(IllegalArgumentException e) {
|
||||
log.warn("非法参数异常: {}", e.getMessage());
|
||||
return fail(PaymentConstants.ErrorMessage.PARAM_ERROR + ": " + e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理空指针异常
|
||||
*/
|
||||
@ExceptionHandler(NullPointerException.class)
|
||||
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
public ApiResult<?> handleNullPointerException(NullPointerException e) {
|
||||
log.error("空指针异常", e);
|
||||
return fail(PaymentConstants.ErrorMessage.SYSTEM_ERROR);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理其他运行时异常
|
||||
*/
|
||||
@ExceptionHandler(RuntimeException.class)
|
||||
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
public ApiResult<?> handleRuntimeException(RuntimeException e) {
|
||||
log.error("运行时异常: {}", e.getMessage(), e);
|
||||
return fail(PaymentConstants.ErrorMessage.SYSTEM_ERROR);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理其他异常
|
||||
*/
|
||||
@ExceptionHandler(Exception.class)
|
||||
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
public ApiResult<?> handleException(Exception e) {
|
||||
log.error("未知异常: {}", e.getMessage(), e);
|
||||
return fail(PaymentConstants.ErrorMessage.SYSTEM_ERROR);
|
||||
}
|
||||
}
|
||||
182
java/payment/service/PaymentService.java
Normal file
182
java/payment/service/PaymentService.java
Normal file
@@ -0,0 +1,182 @@
|
||||
package com.gxwebsoft.payment.service;
|
||||
|
||||
import com.gxwebsoft.common.system.entity.User;
|
||||
import com.gxwebsoft.payment.dto.PaymentRequest;
|
||||
import com.gxwebsoft.payment.dto.PaymentResponse;
|
||||
import com.gxwebsoft.payment.dto.PaymentWithOrderRequest;
|
||||
import com.gxwebsoft.payment.enums.PaymentType;
|
||||
import com.gxwebsoft.payment.exception.PaymentException;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 统一支付服务接口
|
||||
* 提供所有支付方式的统一入口
|
||||
*
|
||||
* @author 科技小王子
|
||||
* @since 2025-01-26
|
||||
*/
|
||||
public interface PaymentService {
|
||||
|
||||
/**
|
||||
* 创建支付订单
|
||||
*
|
||||
* @param request 支付请求
|
||||
* @return 支付响应
|
||||
* @throws PaymentException 支付创建失败时抛出
|
||||
*/
|
||||
PaymentResponse createPayment(PaymentRequest request) throws PaymentException;
|
||||
|
||||
/**
|
||||
* 创建支付订单(包含订单信息)
|
||||
* 统一支付模块:先创建订单,再发起支付
|
||||
*
|
||||
* @param request 支付与订单创建请求
|
||||
* @param loginUser 当前登录用户
|
||||
* @return 支付响应
|
||||
* @throws PaymentException 创建失败时抛出
|
||||
*/
|
||||
PaymentResponse createPaymentWithOrder(PaymentWithOrderRequest request, User loginUser) throws PaymentException;
|
||||
|
||||
/**
|
||||
* 查询支付状态
|
||||
*
|
||||
* @param orderNo 订单号
|
||||
* @param paymentType 支付类型
|
||||
* @param tenantId 租户ID
|
||||
* @return 支付响应
|
||||
* @throws PaymentException 查询失败时抛出
|
||||
*/
|
||||
PaymentResponse queryPayment(String orderNo, PaymentType paymentType, Integer tenantId) throws PaymentException;
|
||||
|
||||
/**
|
||||
* 处理支付回调通知
|
||||
*
|
||||
* @param paymentType 支付类型
|
||||
* @param headers 请求头
|
||||
* @param body 请求体
|
||||
* @param tenantId 租户ID
|
||||
* @return 处理结果,返回给第三方的响应内容
|
||||
* @throws PaymentException 处理失败时抛出
|
||||
*/
|
||||
String handlePaymentNotify(PaymentType paymentType, Map<String, String> headers, String body, Integer tenantId) throws PaymentException;
|
||||
|
||||
/**
|
||||
* 申请退款
|
||||
*
|
||||
* @param orderNo 订单号
|
||||
* @param refundNo 退款单号
|
||||
* @param paymentType 支付类型
|
||||
* @param totalAmount 订单总金额
|
||||
* @param refundAmount 退款金额
|
||||
* @param reason 退款原因
|
||||
* @param tenantId 租户ID
|
||||
* @return 退款响应
|
||||
* @throws PaymentException 退款申请失败时抛出
|
||||
*/
|
||||
PaymentResponse refund(String orderNo, String refundNo, PaymentType paymentType,
|
||||
BigDecimal totalAmount, BigDecimal refundAmount,
|
||||
String reason, Integer tenantId) throws PaymentException;
|
||||
|
||||
/**
|
||||
* 查询退款状态
|
||||
*
|
||||
* @param refundNo 退款单号
|
||||
* @param paymentType 支付类型
|
||||
* @param tenantId 租户ID
|
||||
* @return 退款查询响应
|
||||
* @throws PaymentException 查询失败时抛出
|
||||
*/
|
||||
PaymentResponse queryRefund(String refundNo, PaymentType paymentType, Integer tenantId) throws PaymentException;
|
||||
|
||||
/**
|
||||
* 关闭订单
|
||||
*
|
||||
* @param orderNo 订单号
|
||||
* @param paymentType 支付类型
|
||||
* @param tenantId 租户ID
|
||||
* @return 关闭结果
|
||||
* @throws PaymentException 关闭失败时抛出
|
||||
*/
|
||||
boolean closeOrder(String orderNo, PaymentType paymentType, Integer tenantId) throws PaymentException;
|
||||
|
||||
/**
|
||||
* 获取支持的支付类型列表
|
||||
*
|
||||
* @return 支付类型列表
|
||||
*/
|
||||
List<PaymentType> getSupportedPaymentTypes();
|
||||
|
||||
/**
|
||||
* 检查支付类型是否支持
|
||||
*
|
||||
* @param paymentType 支付类型
|
||||
* @return true表示支持
|
||||
*/
|
||||
boolean isPaymentTypeSupported(PaymentType paymentType);
|
||||
|
||||
/**
|
||||
* 检查支付类型是否支持退款
|
||||
*
|
||||
* @param paymentType 支付类型
|
||||
* @return true表示支持退款
|
||||
*/
|
||||
boolean isRefundSupported(PaymentType paymentType);
|
||||
|
||||
/**
|
||||
* 检查支付类型是否支持查询
|
||||
*
|
||||
* @param paymentType 支付类型
|
||||
* @return true表示支持查询
|
||||
*/
|
||||
boolean isQuerySupported(PaymentType paymentType);
|
||||
|
||||
/**
|
||||
* 检查支付类型是否支持关闭订单
|
||||
*
|
||||
* @param paymentType 支付类型
|
||||
* @return true表示支持关闭订单
|
||||
*/
|
||||
boolean isCloseSupported(PaymentType paymentType);
|
||||
|
||||
/**
|
||||
* 检查支付类型是否需要异步通知
|
||||
*
|
||||
* @param paymentType 支付类型
|
||||
* @return true表示需要异步通知
|
||||
*/
|
||||
boolean isNotifyNeeded(PaymentType paymentType);
|
||||
|
||||
/**
|
||||
* 验证支付请求参数
|
||||
*
|
||||
* @param request 支付请求
|
||||
* @throws PaymentException 参数验证失败时抛出
|
||||
*/
|
||||
void validatePaymentRequest(PaymentRequest request) throws PaymentException;
|
||||
|
||||
/**
|
||||
* 获取支付策略信息
|
||||
*
|
||||
* @param paymentType 支付类型
|
||||
* @return 策略信息Map,包含策略名称、描述等
|
||||
*/
|
||||
Map<String, Object> getPaymentStrategyInfo(PaymentType paymentType);
|
||||
|
||||
/**
|
||||
* 获取所有支付策略信息
|
||||
*
|
||||
* @return 所有策略信息列表
|
||||
*/
|
||||
List<Map<String, Object>> getAllPaymentStrategyInfo();
|
||||
|
||||
/**
|
||||
* 检查支付配置
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
* @return 配置检查结果
|
||||
*/
|
||||
Map<String, Object> checkPaymentConfig(Integer tenantId);
|
||||
}
|
||||
338
java/payment/service/WxPayConfigService.java
Normal file
338
java/payment/service/WxPayConfigService.java
Normal file
@@ -0,0 +1,338 @@
|
||||
package com.gxwebsoft.payment.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.gxwebsoft.common.core.config.CertificateProperties;
|
||||
import com.gxwebsoft.common.core.service.CertificateService;
|
||||
import com.gxwebsoft.common.core.utils.RedisUtil;
|
||||
import com.gxwebsoft.common.core.utils.WxNativeUtil;
|
||||
import com.gxwebsoft.common.system.entity.Payment;
|
||||
import com.gxwebsoft.common.system.param.PaymentParam;
|
||||
import com.gxwebsoft.common.system.service.PaymentService;
|
||||
import com.gxwebsoft.payment.exception.PaymentException;
|
||||
import com.wechat.pay.java.core.Config;
|
||||
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 微信支付配置服务
|
||||
* 负责管理微信支付的配置信息和证书
|
||||
*
|
||||
* @author 科技小王子
|
||||
* @since 2025-01-26
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class WxPayConfigService {
|
||||
|
||||
@Resource
|
||||
private RedisUtil redisUtil;
|
||||
|
||||
@Resource
|
||||
private CertificateService certificateService;
|
||||
|
||||
@Resource
|
||||
private CertificateProperties certificateProperties;
|
||||
|
||||
@Resource
|
||||
private PaymentService paymentService;
|
||||
|
||||
@Value("${spring.profiles.active:dev}")
|
||||
private String activeProfile;
|
||||
|
||||
/**
|
||||
* 获取支付配置信息(Payment对象)- 公开方法
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
* @return 支付配置信息
|
||||
* @throws PaymentException 配置获取失败时抛出
|
||||
*/
|
||||
public Payment getPaymentConfigForStrategy(Integer tenantId) throws PaymentException {
|
||||
if (tenantId == null) {
|
||||
throw PaymentException.paramError("租户ID不能为空");
|
||||
}
|
||||
return getPaymentConfig(tenantId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取微信支付配置
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
* @return 微信支付配置
|
||||
* @throws PaymentException 配置获取失败时抛出
|
||||
*/
|
||||
public Config getWxPayConfig(Integer tenantId) throws PaymentException {
|
||||
if (tenantId == null) {
|
||||
throw PaymentException.paramError("租户ID不能为空");
|
||||
}
|
||||
|
||||
// 先从缓存获取已构建的配置
|
||||
Config cachedConfig = WxNativeUtil.getConfig(tenantId);
|
||||
if (cachedConfig != null) {
|
||||
log.debug("从缓存获取微信支付配置成功,租户ID: {}", tenantId);
|
||||
return cachedConfig;
|
||||
}
|
||||
|
||||
// 构建新的配置
|
||||
Config newConfig = buildWxPayConfig(tenantId);
|
||||
|
||||
// 缓存配置
|
||||
WxNativeUtil.addConfig(tenantId, newConfig);
|
||||
log.info("微信支付配置创建并缓存成功,租户ID: {}", tenantId);
|
||||
|
||||
return newConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建微信支付配置
|
||||
*/
|
||||
private Config buildWxPayConfig(Integer tenantId) throws PaymentException {
|
||||
try {
|
||||
// 获取支付配置信息
|
||||
Payment payment = getPaymentConfig(tenantId);
|
||||
|
||||
// 获取证书文件路径
|
||||
String certificatePath = getCertificatePath(tenantId, payment);
|
||||
|
||||
// 创建微信支付配置对象
|
||||
return createWxPayConfig(payment, certificatePath);
|
||||
|
||||
} catch (Exception e) {
|
||||
if (e instanceof PaymentException) {
|
||||
throw e;
|
||||
}
|
||||
throw PaymentException.systemError("构建微信支付配置失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取支付配置信息
|
||||
* 优先从缓存获取,缓存没有则查询数据库,最后兜底到开发环境测试配置
|
||||
*/
|
||||
private Payment getPaymentConfig(Integer tenantId) throws PaymentException {
|
||||
String cacheKey = "Payment:wxPay:" + tenantId;
|
||||
Payment payment = redisUtil.get(cacheKey, Payment.class);
|
||||
System.out.println("payment = " + payment);
|
||||
if (payment != null) {
|
||||
log.debug("从缓存获取支付配置成功,租户ID: {}", tenantId);
|
||||
// return payment;
|
||||
}
|
||||
|
||||
// 缓存中没有,尝试从数据库查询
|
||||
try {
|
||||
final PaymentParam paymentParam = new PaymentParam();
|
||||
paymentParam.setType(102);
|
||||
paymentParam.setTenantId(tenantId);
|
||||
|
||||
log.debug("查询数据库支付配置,参数: type=102, tenantId={}", tenantId);
|
||||
payment = paymentService.getByType(paymentParam);
|
||||
log.debug("数据库查询结果: {}", payment != null ? "找到配置" : "未找到配置");
|
||||
|
||||
if (payment != null) {
|
||||
log.info("从数据库获取支付配置成功,租户ID: {},将缓存配置", tenantId);
|
||||
// 将查询到的配置缓存起来,缓存1天
|
||||
redisUtil.set(cacheKey, payment, 1L, TimeUnit.DAYS);
|
||||
return payment;
|
||||
} else {
|
||||
log.warn("数据库中未找到支付配置,租户ID: {}, type: 102", tenantId);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("从数据库查询支付配置失败,租户ID: {},错误: {}", tenantId, e.getMessage(), e);
|
||||
// 抛出更详细的异常信息
|
||||
throw PaymentException.systemError("查询支付配置失败,租户ID: " + tenantId + ",错误: " + e.getMessage(), e);
|
||||
}
|
||||
|
||||
// 数据库也没有配置
|
||||
if (!"dev".equals(activeProfile)) {
|
||||
throw PaymentException.systemError("微信支付配置未找到,租户ID: " + tenantId + ",请检查数据库配置", null);
|
||||
}
|
||||
|
||||
log.debug("开发环境模式,将使用测试配置,租户ID: {}", tenantId);
|
||||
// 开发环境返回测试Payment配置
|
||||
return createDevTestPayment(tenantId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取证书文件路径
|
||||
*/
|
||||
private String getCertificatePath(Integer tenantId, Payment payment) throws PaymentException {
|
||||
if ("dev".equals(activeProfile)) {
|
||||
return getDevCertificatePath(tenantId);
|
||||
} else {
|
||||
return getProdCertificatePath(payment);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取开发环境证书路径
|
||||
*/
|
||||
private String getDevCertificatePath(Integer tenantId) throws PaymentException {
|
||||
try {
|
||||
// 根据租户ID构建证书路径
|
||||
String certPath = "dev/wechat/" + tenantId + "/apiclient_key.pem";
|
||||
ClassPathResource resource = new ClassPathResource(certPath);
|
||||
|
||||
if (!resource.exists()) {
|
||||
throw PaymentException.systemError("开发环境微信支付证书文件不存在: " + certPath, null);
|
||||
}
|
||||
|
||||
String absolutePath = resource.getFile().getAbsolutePath();
|
||||
log.debug("开发环境证书路径: {}", absolutePath);
|
||||
return absolutePath;
|
||||
|
||||
} catch (IOException e) {
|
||||
throw PaymentException.systemError("获取开发环境证书路径失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取生产环境证书路径
|
||||
*/
|
||||
private String getProdCertificatePath(Payment payment) throws PaymentException {
|
||||
if (payment == null || payment.getApiclientKey() == null || payment.getApiclientKey().trim().isEmpty()) {
|
||||
throw PaymentException.systemError("生产环境支付配置或证书密钥文件为空", null);
|
||||
}
|
||||
|
||||
try {
|
||||
// 使用微信支付证书路径
|
||||
String certificatePath = certificateService.getWechatPayCertPath(payment.getApiclientKey());
|
||||
if (certificatePath == null) {
|
||||
throw PaymentException.systemError("证书文件路径获取失败,证书文件: " + payment.getApiclientKey(), null);
|
||||
}
|
||||
|
||||
log.debug("生产环境证书路径: {}", certificatePath);
|
||||
return certificatePath;
|
||||
|
||||
} catch (Exception e) {
|
||||
throw PaymentException.systemError("获取生产环境证书路径失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建微信支付配置对象
|
||||
*/
|
||||
private Config createWxPayConfig(Payment payment, String certificatePath) throws PaymentException {
|
||||
try {
|
||||
if ("dev".equals(activeProfile) && payment == null) {
|
||||
// 开发环境测试配置
|
||||
return createDevTestConfig(certificatePath);
|
||||
} else if (payment != null) {
|
||||
// 正常配置
|
||||
return createNormalConfig(payment, certificatePath);
|
||||
} else {
|
||||
throw PaymentException.systemError("无法创建微信支付配置:配置信息不完整", null);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (e instanceof PaymentException) {
|
||||
throw e;
|
||||
}
|
||||
throw PaymentException.systemError("创建微信支付配置对象失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建开发环境测试Payment配置
|
||||
*/
|
||||
private Payment createDevTestPayment(Integer tenantId) {
|
||||
Payment testPayment = new Payment();
|
||||
testPayment.setTenantId(tenantId);
|
||||
testPayment.setType(102); // Native支付
|
||||
testPayment.setAppId("wxa67c676fc445590e"); // 开发环境测试AppID
|
||||
testPayment.setMchId("1246610101"); // 开发环境测试商户号
|
||||
testPayment.setMerchantSerialNumber("48749613B40AA8F1D768583FC352358E13EB5AF0");
|
||||
testPayment.setApiKey(certificateProperties.getWechatPay().getDev().getApiV3Key());
|
||||
testPayment.setNotifyUrl("http://frps-10550.s209.websoft.top/api/payment/notify");
|
||||
testPayment.setName("微信Native支付-开发环境");
|
||||
testPayment.setStatus(true); // 启用
|
||||
|
||||
log.info("创建开发环境测试Payment配置,租户ID: {}, AppID: {}, 商户号: {}",
|
||||
tenantId, testPayment.getAppId(), testPayment.getMchId());
|
||||
|
||||
return testPayment;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建开发环境测试配置
|
||||
*/
|
||||
private Config createDevTestConfig(String certificatePath) throws PaymentException {
|
||||
String testMerchantId = "1246610101";
|
||||
String testMerchantSerialNumber = "48749613B40AA8F1D768583FC352358E13EB5AF0";
|
||||
String testApiV3Key = certificateProperties.getWechatPay().getDev().getApiV3Key();
|
||||
|
||||
if (testApiV3Key == null || testApiV3Key.trim().isEmpty()) {
|
||||
throw PaymentException.systemError("开发环境APIv3密钥未配置", null);
|
||||
}
|
||||
|
||||
log.info("使用开发环境测试配置");
|
||||
log.debug("测试商户号: {}", testMerchantId);
|
||||
log.debug("测试序列号: {}", testMerchantSerialNumber);
|
||||
|
||||
return new RSAAutoCertificateConfig.Builder()
|
||||
.merchantId(testMerchantId)
|
||||
.privateKeyFromPath(certificatePath)
|
||||
.merchantSerialNumber(testMerchantSerialNumber)
|
||||
.apiV3Key(testApiV3Key)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建正常配置
|
||||
*/
|
||||
private Config createNormalConfig(Payment payment, String certificatePath) throws PaymentException {
|
||||
// 验证配置完整性
|
||||
validatePaymentConfig(payment);
|
||||
|
||||
log.info("使用数据库支付配置");
|
||||
log.debug("商户号: {}", payment.getMchId());
|
||||
|
||||
return new RSAAutoCertificateConfig.Builder()
|
||||
.merchantId(payment.getMchId())
|
||||
.privateKeyFromPath(certificatePath)
|
||||
.merchantSerialNumber(payment.getMerchantSerialNumber())
|
||||
.apiV3Key(payment.getApiKey())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证支付配置完整性
|
||||
*/
|
||||
private void validatePaymentConfig(Payment payment) throws PaymentException {
|
||||
if (payment == null) {
|
||||
throw PaymentException.systemError("支付配置为空", null);
|
||||
}
|
||||
|
||||
if (payment.getMchId() == null || payment.getMchId().trim().isEmpty()) {
|
||||
throw PaymentException.systemError("商户号(mchId)未配置", null);
|
||||
}
|
||||
|
||||
if (payment.getMerchantSerialNumber() == null || payment.getMerchantSerialNumber().trim().isEmpty()) {
|
||||
throw PaymentException.systemError("商户证书序列号(merchantSerialNumber)未配置", null);
|
||||
}
|
||||
|
||||
if (payment.getApiKey() == null || payment.getApiKey().trim().isEmpty()) {
|
||||
throw PaymentException.systemError("APIv3密钥(apiKey)未配置", null);
|
||||
}
|
||||
|
||||
if (payment.getApiclientKey() == null || payment.getApiclientKey().trim().isEmpty()) {
|
||||
throw PaymentException.systemError("证书文件名(apiclientKey)未配置", null);
|
||||
}
|
||||
|
||||
log.debug("支付配置验证通过,租户ID: {}, 商户号: {}", payment.getTenantId(), payment.getMchId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除指定租户的配置缓存
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
*/
|
||||
public void clearConfigCache(Integer tenantId) {
|
||||
WxNativeUtil.addConfig(tenantId, null);
|
||||
log.info("清除微信支付配置缓存,租户ID: {}", tenantId);
|
||||
}
|
||||
}
|
||||
366
java/payment/service/WxPayNotifyService.java
Normal file
366
java/payment/service/WxPayNotifyService.java
Normal file
@@ -0,0 +1,366 @@
|
||||
package com.gxwebsoft.payment.service;
|
||||
|
||||
|
||||
import com.gxwebsoft.payment.constants.PaymentConstants;
|
||||
import com.gxwebsoft.payment.exception.PaymentException;
|
||||
import com.gxwebsoft.shop.entity.ShopOrder;
|
||||
import com.gxwebsoft.shop.service.ShopOrderService;
|
||||
import com.wechat.pay.java.core.Config;
|
||||
import com.wechat.pay.java.core.notification.NotificationConfig;
|
||||
import com.wechat.pay.java.core.notification.NotificationParser;
|
||||
import com.wechat.pay.java.core.notification.RequestParam;
|
||||
import com.wechat.pay.java.service.payments.model.Transaction;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 微信支付回调通知处理服务
|
||||
* 负责处理微信支付的异步通知回调
|
||||
*
|
||||
* @author 科技小王子
|
||||
* @since 2025-01-26
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class WxPayNotifyService {
|
||||
|
||||
@Resource
|
||||
private WxPayConfigService wxPayConfigService;
|
||||
|
||||
@Resource
|
||||
private ShopOrderService shopOrderService;
|
||||
|
||||
/**
|
||||
* 处理微信支付回调通知
|
||||
*
|
||||
* @param headers 请求头
|
||||
* @param body 请求体
|
||||
* @param tenantId 租户ID
|
||||
* @return 处理结果响应
|
||||
*/
|
||||
public String handlePaymentNotify(Map<String, String> headers, String body, Integer tenantId) {
|
||||
log.info("{}, 租户ID: {}", PaymentConstants.LogMessage.NOTIFY_START, tenantId);
|
||||
|
||||
try {
|
||||
// 参数验证
|
||||
validateNotifyParams(headers, body, tenantId);
|
||||
|
||||
// 获取微信支付配置
|
||||
Config wxPayConfig = wxPayConfigService.getWxPayConfig(tenantId);
|
||||
|
||||
// 解析并验证回调数据
|
||||
Transaction transaction = parseAndVerifyNotification(headers, body, wxPayConfig);
|
||||
|
||||
// 处理支付结果
|
||||
processPaymentResult(transaction, tenantId);
|
||||
|
||||
log.info("{}, 租户ID: {}, 订单号: {}",
|
||||
PaymentConstants.LogMessage.NOTIFY_SUCCESS, tenantId, transaction.getOutTradeNo());
|
||||
|
||||
return "SUCCESS";
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("{}, 租户ID: {}, 错误: {}",
|
||||
PaymentConstants.LogMessage.NOTIFY_FAILED, tenantId, e.getMessage(), e);
|
||||
return "FAIL";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证回调通知参数
|
||||
*/
|
||||
private void validateNotifyParams(Map<String, String> headers, String body, Integer tenantId) throws PaymentException {
|
||||
if (tenantId == null) {
|
||||
throw PaymentException.paramError("租户ID不能为空");
|
||||
}
|
||||
|
||||
if (headers == null || headers.isEmpty()) {
|
||||
throw PaymentException.paramError("请求头不能为空");
|
||||
}
|
||||
|
||||
if (!StringUtils.hasText(body)) {
|
||||
throw PaymentException.paramError("请求体不能为空");
|
||||
}
|
||||
|
||||
// 验证必要的微信支付头部信息
|
||||
String signature = headers.get("Wechatpay-Signature");
|
||||
String timestamp = headers.get("Wechatpay-Timestamp");
|
||||
String nonce = headers.get("Wechatpay-Nonce");
|
||||
String serial = headers.get("Wechatpay-Serial");
|
||||
|
||||
if (!StringUtils.hasText(signature)) {
|
||||
throw PaymentException.paramError("微信支付签名不能为空");
|
||||
}
|
||||
|
||||
if (!StringUtils.hasText(timestamp)) {
|
||||
throw PaymentException.paramError("微信支付时间戳不能为空");
|
||||
}
|
||||
|
||||
if (!StringUtils.hasText(nonce)) {
|
||||
throw PaymentException.paramError("微信支付随机数不能为空");
|
||||
}
|
||||
|
||||
if (!StringUtils.hasText(serial)) {
|
||||
throw PaymentException.paramError("微信支付序列号不能为空");
|
||||
}
|
||||
|
||||
log.debug("回调通知参数验证通过, 租户ID: {}", tenantId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析并验证回调通知
|
||||
*/
|
||||
private Transaction parseAndVerifyNotification(Map<String, String> headers, String body, Config wxPayConfig) throws PaymentException {
|
||||
if (wxPayConfig == null) {
|
||||
throw PaymentException.systemError("微信支付配置为空", null);
|
||||
}
|
||||
|
||||
try {
|
||||
// 构建请求参数
|
||||
RequestParam requestParam = new RequestParam.Builder()
|
||||
.serialNumber(headers.get("Wechatpay-Serial"))
|
||||
.nonce(headers.get("Wechatpay-Nonce"))
|
||||
.signature(headers.get("Wechatpay-Signature"))
|
||||
.timestamp(headers.get("Wechatpay-Timestamp"))
|
||||
.body(body)
|
||||
.build();
|
||||
|
||||
// 创建通知解析器
|
||||
NotificationParser parser = new NotificationParser((NotificationConfig) wxPayConfig);
|
||||
|
||||
// 解析并验证通知
|
||||
Transaction transaction = parser.parse(requestParam, Transaction.class);
|
||||
|
||||
if (transaction == null) {
|
||||
throw PaymentException.systemError("解析回调通知失败:transaction为空", null);
|
||||
}
|
||||
|
||||
log.debug("回调通知解析成功, 订单号: {}, 交易状态: {}",
|
||||
transaction.getOutTradeNo(), transaction.getTradeState());
|
||||
|
||||
return transaction;
|
||||
|
||||
} catch (Exception e) {
|
||||
if (e instanceof PaymentException) {
|
||||
throw e;
|
||||
}
|
||||
throw PaymentException.systemError("解析回调通知失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理支付结果
|
||||
*/
|
||||
private void processPaymentResult(Transaction transaction, Integer tenantId) throws PaymentException {
|
||||
String outTradeNo = transaction.getOutTradeNo();
|
||||
String tradeState = String.valueOf(transaction.getTradeState());
|
||||
|
||||
if (!StringUtils.hasText(outTradeNo)) {
|
||||
throw PaymentException.paramError("商户订单号不能为空");
|
||||
}
|
||||
|
||||
// 查询订单
|
||||
ShopOrder order = shopOrderService.getByOutTradeNo(outTradeNo);
|
||||
if (order == null) {
|
||||
throw PaymentException.systemError("订单不存在: " + outTradeNo, null);
|
||||
}
|
||||
|
||||
// 验证租户ID
|
||||
if (!tenantId.equals(order.getTenantId())) {
|
||||
throw PaymentException.paramError("订单租户ID不匹配");
|
||||
}
|
||||
|
||||
// 验证订单状态 - 使用Boolean类型的payStatus字段
|
||||
if (Boolean.TRUE.equals(order.getPayStatus())) {
|
||||
log.info("订单已支付,跳过处理, 订单号: {}", outTradeNo);
|
||||
return;
|
||||
}
|
||||
|
||||
// 根据交易状态处理
|
||||
switch (tradeState) {
|
||||
case "SUCCESS":
|
||||
handlePaymentSuccess(order, transaction);
|
||||
break;
|
||||
case "REFUND":
|
||||
handlePaymentRefund(order, transaction);
|
||||
break;
|
||||
case "CLOSED":
|
||||
case "REVOKED":
|
||||
case "PAYERROR":
|
||||
handlePaymentFailed(order, transaction);
|
||||
break;
|
||||
default:
|
||||
log.warn("未处理的交易状态: {}, 订单号: {}", tradeState, outTradeNo);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理支付成功
|
||||
*/
|
||||
private void handlePaymentSuccess(ShopOrder order, Transaction transaction) throws PaymentException {
|
||||
try {
|
||||
// 验证金额
|
||||
validateAmount(order, transaction);
|
||||
|
||||
// 更新订单状态
|
||||
order.setPayStatus(true); // 使用Boolean类型
|
||||
order.setTransactionId(transaction.getTransactionId());
|
||||
order.setPayTime(LocalDateTime.parse(transaction.getSuccessTime()));
|
||||
|
||||
// 使用专门的更新方法,会触发支付成功后的业务逻辑
|
||||
shopOrderService.updateByOutTradeNo(order);
|
||||
|
||||
// 推送支付结果通知
|
||||
pushPaymentNotification(order, transaction);
|
||||
|
||||
log.info("支付成功处理完成, 订单号: {}, 微信交易号: {}",
|
||||
order.getOrderNo(), transaction.getTransactionId());
|
||||
|
||||
} catch (Exception e) {
|
||||
throw PaymentException.systemError("处理支付成功回调失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理支付退款
|
||||
*/
|
||||
private void handlePaymentRefund(ShopOrder order, Transaction transaction) throws PaymentException {
|
||||
try {
|
||||
log.info("处理支付退款, 订单号: {}, 微信交易号: {}",
|
||||
order.getOrderNo(), transaction.getTransactionId());
|
||||
|
||||
// 这里可以添加退款相关的业务逻辑
|
||||
// 例如:更新订单状态、处理库存、发送通知等
|
||||
|
||||
} catch (Exception e) {
|
||||
throw PaymentException.systemError("处理支付退款回调失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理支付失败
|
||||
*/
|
||||
private void handlePaymentFailed(ShopOrder order, Transaction transaction) throws PaymentException {
|
||||
try {
|
||||
log.info("处理支付失败, 订单号: {}, 交易状态: {}",
|
||||
order.getOrderNo(), transaction.getTradeState());
|
||||
|
||||
// 这里可以添加支付失败相关的业务逻辑
|
||||
// 例如:释放库存、发送通知等
|
||||
|
||||
} catch (Exception e) {
|
||||
throw PaymentException.systemError("处理支付失败回调失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证支付金额
|
||||
*/
|
||||
private void validateAmount(ShopOrder order, Transaction transaction) throws PaymentException {
|
||||
if (transaction.getAmount() == null || transaction.getAmount().getTotal() == null) {
|
||||
throw PaymentException.amountError("回调通知中金额信息为空");
|
||||
}
|
||||
|
||||
// 将订单金额转换为分
|
||||
BigDecimal orderAmount = order.getMoney();
|
||||
if (orderAmount == null) {
|
||||
throw PaymentException.amountError("订单金额为空");
|
||||
}
|
||||
|
||||
int orderAmountFen = orderAmount.multiply(new BigDecimal(100)).intValue();
|
||||
int callbackAmountFen = transaction.getAmount().getTotal();
|
||||
|
||||
if (orderAmountFen != callbackAmountFen) {
|
||||
throw PaymentException.amountError(
|
||||
String.format("订单金额不匹配,订单金额: %d分, 回调金额: %d分",
|
||||
orderAmountFen, callbackAmountFen));
|
||||
}
|
||||
|
||||
log.debug("金额验证通过, 订单号: {}, 金额: {}分", order.getOrderNo(), orderAmountFen);
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送支付结果通知
|
||||
*/
|
||||
private void pushPaymentNotification(ShopOrder order, Transaction transaction) {
|
||||
try {
|
||||
log.info("开始推送支付成功通知, 订单号: {}, 交易号: {}, 用户ID: {}",
|
||||
order.getOrderNo(), transaction.getTransactionId(), order.getUserId());
|
||||
|
||||
// 1. 记录支付成功日志
|
||||
logPaymentSuccess(order, transaction);
|
||||
|
||||
// 2. 发送支付成功通知(可扩展)
|
||||
sendPaymentSuccessNotification(order, transaction);
|
||||
|
||||
// 3. 触发其他业务逻辑(可扩展)
|
||||
triggerPostPaymentActions(order, transaction);
|
||||
|
||||
log.info("支付结果通知推送完成, 订单号: {}, 交易号: {}",
|
||||
order.getOrderNo(), transaction.getTransactionId());
|
||||
} catch (Exception e) {
|
||||
log.warn("支付结果通知推送失败, 订单号: {}, 错误: {}", order.getOrderNo(), e.getMessage());
|
||||
// 推送失败不影响主流程,只记录日志
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录支付成功日志
|
||||
*/
|
||||
private void logPaymentSuccess(ShopOrder order, Transaction transaction) {
|
||||
try {
|
||||
log.info("=== 支付成功详细信息 ===");
|
||||
log.info("订单号: {}", order.getOrderNo());
|
||||
log.info("微信交易号: {}", transaction.getTransactionId());
|
||||
log.info("支付金额: {}元", order.getPayPrice());
|
||||
log.info("支付时间: {}", transaction.getSuccessTime());
|
||||
log.info("用户ID: {}", order.getUserId());
|
||||
log.info("租户ID: {}", order.getTenantId());
|
||||
log.info("订单标题: {}", order.getTitle());
|
||||
log.info("========================");
|
||||
} catch (Exception e) {
|
||||
log.warn("记录支付成功日志失败: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送支付成功通知
|
||||
*/
|
||||
private void sendPaymentSuccessNotification(ShopOrder order, Transaction transaction) {
|
||||
try {
|
||||
// TODO: 实现具体的通知逻辑
|
||||
// 1. 发送邮件通知
|
||||
// 2. 发送短信通知
|
||||
// 3. 站内消息通知
|
||||
// 4. 微信模板消息通知
|
||||
|
||||
log.debug("支付成功通知发送完成, 订单号: {}", order.getOrderNo());
|
||||
} catch (Exception e) {
|
||||
log.warn("发送支付成功通知失败, 订单号: {}, 错误: {}", order.getOrderNo(), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发支付成功后的其他业务逻辑
|
||||
*/
|
||||
private void triggerPostPaymentActions(ShopOrder order, Transaction transaction) {
|
||||
try {
|
||||
// TODO: 根据业务需求实现
|
||||
// 1. 开通网站服务
|
||||
// 2. 激活会员权益
|
||||
// 3. 发放积分奖励
|
||||
// 4. 触发营销活动
|
||||
|
||||
log.debug("支付后业务逻辑触发完成, 订单号: {}", order.getOrderNo());
|
||||
} catch (Exception e) {
|
||||
log.warn("触发支付后业务逻辑失败, 订单号: {}, 错误: {}", order.getOrderNo(), e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
668
java/payment/service/impl/PaymentServiceImpl.java
Normal file
668
java/payment/service/impl/PaymentServiceImpl.java
Normal file
@@ -0,0 +1,668 @@
|
||||
package com.gxwebsoft.payment.service.impl;
|
||||
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import com.gxwebsoft.common.core.utils.CommonUtil;
|
||||
import com.gxwebsoft.common.system.entity.User;
|
||||
import com.gxwebsoft.payment.constants.PaymentConstants;
|
||||
import com.gxwebsoft.payment.dto.PaymentRequest;
|
||||
import com.gxwebsoft.payment.dto.PaymentResponse;
|
||||
import com.gxwebsoft.payment.dto.PaymentWithOrderRequest;
|
||||
import com.gxwebsoft.payment.enums.PaymentType;
|
||||
import com.gxwebsoft.payment.exception.PaymentException;
|
||||
import com.gxwebsoft.payment.service.PaymentService;
|
||||
import com.gxwebsoft.payment.service.WxPayConfigService;
|
||||
import com.gxwebsoft.payment.strategy.PaymentStrategy;
|
||||
import com.gxwebsoft.shop.dto.OrderCreateRequest;
|
||||
import com.gxwebsoft.shop.service.OrderBusinessService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 统一支付服务实现
|
||||
* 基于策略模式实现多种支付方式的统一管理
|
||||
*
|
||||
* @author 科技小王子
|
||||
* @since 2025-01-26
|
||||
*/
|
||||
@Slf4j
|
||||
@Service("unifiedPaymentServiceImpl")
|
||||
public class PaymentServiceImpl implements PaymentService {
|
||||
|
||||
/**
|
||||
* 支付策略映射表
|
||||
*/
|
||||
private final Map<PaymentType, PaymentStrategy> strategyMap = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 注入所有支付策略实现
|
||||
*/
|
||||
@Resource
|
||||
private List<PaymentStrategy> paymentStrategies;
|
||||
|
||||
/**
|
||||
* 订单业务服务
|
||||
*/
|
||||
@Resource
|
||||
private OrderBusinessService orderBusinessService;
|
||||
|
||||
/**
|
||||
* 微信支付配置服务
|
||||
*/
|
||||
@Resource
|
||||
private WxPayConfigService wxPayConfigService;
|
||||
|
||||
/**
|
||||
* 初始化策略映射
|
||||
*/
|
||||
@PostConstruct
|
||||
public void initStrategies() {
|
||||
if (paymentStrategies != null && !paymentStrategies.isEmpty()) {
|
||||
for (PaymentStrategy strategy : paymentStrategies) {
|
||||
try {
|
||||
PaymentType paymentType = strategy.getSupportedPaymentType();
|
||||
strategyMap.put(paymentType, strategy);
|
||||
log.info("注册支付策略: {} -> {}", paymentType.getName(), strategy.getClass().getSimpleName());
|
||||
} catch (Exception e) {
|
||||
log.warn("注册支付策略失败: {}, 错误: {}", strategy.getClass().getSimpleName(), e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
log.info("支付策略初始化完成,共注册 {} 种支付方式", strategyMap.size());
|
||||
|
||||
if (strategyMap.isEmpty()) {
|
||||
log.warn("⚠️ 没有可用的支付策略,支付功能将不可用");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentResponse createPayment(PaymentRequest request) throws PaymentException {
|
||||
log.info("{}, 支付类型: {}, 租户ID: {}, 用户ID: {}, 金额: {}",
|
||||
PaymentConstants.LogMessage.PAYMENT_START, request.getPaymentType(),
|
||||
request.getTenantId(), request.getUserId(), request.getFormattedAmount());
|
||||
|
||||
try {
|
||||
// 基础参数验证
|
||||
validatePaymentRequest(request);
|
||||
|
||||
// 获取支付策略
|
||||
PaymentStrategy strategy = getPaymentStrategy(request.getPaymentType());
|
||||
|
||||
// 执行支付
|
||||
PaymentResponse response = strategy.createPayment(request);
|
||||
|
||||
log.info("{}, 支付类型: {}, 租户ID: {}, 订单号: {}, 金额: {}",
|
||||
PaymentConstants.LogMessage.PAYMENT_SUCCESS, request.getPaymentType(),
|
||||
request.getTenantId(), response.getOrderNo(), request.getFormattedAmount());
|
||||
|
||||
return response;
|
||||
|
||||
} catch (PaymentException e) {
|
||||
log.error("{}, 支付类型: {}, 租户ID: {}, 错误: {}",
|
||||
PaymentConstants.LogMessage.PAYMENT_FAILED, request.getPaymentType(),
|
||||
request.getTenantId(), e.getMessage());
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.error("{}, 支付类型: {}, 租户ID: {}, 系统错误: {}",
|
||||
PaymentConstants.LogMessage.PAYMENT_FAILED, request.getPaymentType(),
|
||||
request.getTenantId(), e.getMessage(), e);
|
||||
throw PaymentException.systemError("支付创建失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentResponse createPaymentWithOrder(PaymentWithOrderRequest request, User loginUser) throws PaymentException {
|
||||
log.info("开始创建支付订单(包含订单信息), 支付类型: {}, 租户ID: {}, 用户ID: {}, 金额: {}",
|
||||
request.getPaymentType(), request.getTenantId(), loginUser.getUserId(), request.getFormattedAmount());
|
||||
|
||||
try {
|
||||
// 1. 参数验证
|
||||
validatePaymentWithOrderRequest(request, loginUser);
|
||||
|
||||
// 2. 转换为订单创建请求
|
||||
OrderCreateRequest orderRequest = convertToOrderCreateRequest(request, loginUser);
|
||||
|
||||
// 3. 创建订单(包含商品验证、库存扣减等完整业务逻辑)
|
||||
Map<String, String> wxOrderInfo = orderBusinessService.createOrder(orderRequest, loginUser);
|
||||
|
||||
// 4. 构建支付响应(复用现有的微信支付返回格式)
|
||||
PaymentResponse response = buildPaymentResponseFromWxOrder(wxOrderInfo, request, orderRequest.getOrderNo());
|
||||
|
||||
log.info("支付订单创建成功(包含订单信息), 支付类型: {}, 租户ID: {}, 订单号: {}, 金额: {}",
|
||||
request.getPaymentType(), request.getTenantId(), response.getOrderNo(), request.getFormattedAmount());
|
||||
|
||||
return response;
|
||||
|
||||
} catch (PaymentException e) {
|
||||
log.error("创建支付订单失败(包含订单信息), 支付类型: {}, 租户ID: {}, 错误: {}",
|
||||
request.getPaymentType(), request.getTenantId(), e.getMessage());
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.error("创建支付订单系统错误(包含订单信息), 支付类型: {}, 租户ID: {}, 系统错误: {}",
|
||||
request.getPaymentType(), request.getTenantId(), e.getMessage(), e);
|
||||
throw PaymentException.systemError("支付订单创建失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentResponse queryPayment(String orderNo, PaymentType paymentType, Integer tenantId) throws PaymentException {
|
||||
log.info("开始查询支付状态, 支付类型: {}, 租户ID: {}, 订单号: {}",
|
||||
paymentType, tenantId, orderNo);
|
||||
|
||||
try {
|
||||
// 参数验证
|
||||
validateQueryParams(orderNo, paymentType, tenantId);
|
||||
|
||||
// 获取支付策略
|
||||
PaymentStrategy strategy = getPaymentStrategy(paymentType);
|
||||
|
||||
// 检查是否支持查询
|
||||
if (!strategy.supportQuery()) {
|
||||
throw PaymentException.unsupportedPayment("该支付方式不支持查询", paymentType);
|
||||
}
|
||||
|
||||
// 执行查询
|
||||
PaymentResponse response = strategy.queryPayment(orderNo, tenantId);
|
||||
|
||||
log.info("支付状态查询成功, 支付类型: {}, 租户ID: {}, 订单号: {}, 状态: {}",
|
||||
paymentType, tenantId, orderNo, response.getPaymentStatus());
|
||||
|
||||
return response;
|
||||
|
||||
} catch (PaymentException e) {
|
||||
log.error("支付状态查询失败, 支付类型: {}, 租户ID: {}, 订单号: {}, 错误: {}",
|
||||
paymentType, tenantId, orderNo, e.getMessage());
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.error("支付状态查询系统错误, 支付类型: {}, 租户ID: {}, 订单号: {}, 错误: {}",
|
||||
paymentType, tenantId, orderNo, e.getMessage(), e);
|
||||
throw PaymentException.systemError("支付查询失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String handlePaymentNotify(PaymentType paymentType, Map<String, String> headers, String body, Integer tenantId) throws PaymentException {
|
||||
log.info("{}, 支付类型: {}, 租户ID: {}",
|
||||
PaymentConstants.LogMessage.NOTIFY_START, paymentType, tenantId);
|
||||
|
||||
try {
|
||||
// 参数验证
|
||||
validateNotifyParams(paymentType, headers, body, tenantId);
|
||||
|
||||
// 获取支付策略
|
||||
PaymentStrategy strategy = getPaymentStrategy(paymentType);
|
||||
|
||||
// 检查是否需要异步通知
|
||||
if (!strategy.needNotify()) {
|
||||
log.warn("该支付方式不需要异步通知, 支付类型: {}", paymentType);
|
||||
return PaymentConstants.Wechat.NOTIFY_SUCCESS;
|
||||
}
|
||||
|
||||
// 处理回调
|
||||
String result = strategy.handleNotify(headers, body, tenantId);
|
||||
|
||||
log.info("{}, 支付类型: {}, 租户ID: {}",
|
||||
PaymentConstants.LogMessage.NOTIFY_SUCCESS, paymentType, tenantId);
|
||||
|
||||
return result;
|
||||
|
||||
} catch (PaymentException e) {
|
||||
log.error("{}, 支付类型: {}, 租户ID: {}, 错误: {}",
|
||||
PaymentConstants.LogMessage.NOTIFY_FAILED, paymentType, tenantId, e.getMessage());
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.error("{}, 支付类型: {}, 租户ID: {}, 系统错误: {}",
|
||||
PaymentConstants.LogMessage.NOTIFY_FAILED, paymentType, tenantId, e.getMessage(), e);
|
||||
throw PaymentException.systemError("支付回调处理失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentResponse refund(String orderNo, String refundNo, PaymentType paymentType,
|
||||
BigDecimal totalAmount, BigDecimal refundAmount,
|
||||
String reason, Integer tenantId) throws PaymentException {
|
||||
log.info("{}, 支付类型: {}, 租户ID: {}, 订单号: {}, 退款单号: {}, 退款金额: {}",
|
||||
PaymentConstants.LogMessage.REFUND_START, paymentType, tenantId, orderNo, refundNo, refundAmount);
|
||||
|
||||
try {
|
||||
// 参数验证
|
||||
validateRefundParams(orderNo, refundNo, paymentType, totalAmount, refundAmount, tenantId);
|
||||
|
||||
// 获取支付策略
|
||||
PaymentStrategy strategy = getPaymentStrategy(paymentType);
|
||||
|
||||
// 检查是否支持退款
|
||||
if (!strategy.supportRefund()) {
|
||||
throw PaymentException.unsupportedPayment("该支付方式不支持退款", paymentType);
|
||||
}
|
||||
|
||||
// 执行退款
|
||||
PaymentResponse response = strategy.refund(orderNo, refundNo, totalAmount, refundAmount, reason, tenantId);
|
||||
|
||||
log.info("{}, 支付类型: {}, 租户ID: {}, 订单号: {}, 退款单号: {}, 退款金额: {}",
|
||||
PaymentConstants.LogMessage.REFUND_SUCCESS, paymentType, tenantId, orderNo, refundNo, refundAmount);
|
||||
|
||||
return response;
|
||||
|
||||
} catch (PaymentException e) {
|
||||
log.error("{}, 支付类型: {}, 租户ID: {}, 订单号: {}, 退款单号: {}, 错误: {}",
|
||||
PaymentConstants.LogMessage.REFUND_FAILED, paymentType, tenantId, orderNo, refundNo, e.getMessage());
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.error("{}, 支付类型: {}, 租户ID: {}, 订单号: {}, 退款单号: {}, 系统错误: {}",
|
||||
PaymentConstants.LogMessage.REFUND_FAILED, paymentType, tenantId, orderNo, refundNo, e.getMessage(), e);
|
||||
throw PaymentException.systemError("退款申请失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentResponse queryRefund(String refundNo, PaymentType paymentType, Integer tenantId) throws PaymentException {
|
||||
log.info("开始查询退款状态, 支付类型: {}, 租户ID: {}, 退款单号: {}",
|
||||
paymentType, tenantId, refundNo);
|
||||
|
||||
try {
|
||||
// 参数验证
|
||||
validateRefundQueryParams(refundNo, paymentType, tenantId);
|
||||
|
||||
// 获取支付策略
|
||||
PaymentStrategy strategy = getPaymentStrategy(paymentType);
|
||||
|
||||
// 检查是否支持退款查询
|
||||
if (!strategy.supportRefund()) {
|
||||
throw PaymentException.unsupportedPayment("该支付方式不支持退款查询", paymentType);
|
||||
}
|
||||
|
||||
// 执行查询
|
||||
PaymentResponse response = strategy.queryRefund(refundNo, tenantId);
|
||||
|
||||
log.info("退款状态查询成功, 支付类型: {}, 租户ID: {}, 退款单号: {}, 状态: {}",
|
||||
paymentType, tenantId, refundNo, response.getPaymentStatus());
|
||||
|
||||
return response;
|
||||
|
||||
} catch (PaymentException e) {
|
||||
log.error("退款状态查询失败, 支付类型: {}, 租户ID: {}, 退款单号: {}, 错误: {}",
|
||||
paymentType, tenantId, refundNo, e.getMessage());
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.error("退款状态查询系统错误, 支付类型: {}, 租户ID: {}, 退款单号: {}, 错误: {}",
|
||||
paymentType, tenantId, refundNo, e.getMessage(), e);
|
||||
throw PaymentException.systemError("退款查询失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean closeOrder(String orderNo, PaymentType paymentType, Integer tenantId) throws PaymentException {
|
||||
log.info("开始关闭订单, 支付类型: {}, 租户ID: {}, 订单号: {}",
|
||||
paymentType, tenantId, orderNo);
|
||||
|
||||
try {
|
||||
// 参数验证
|
||||
validateCloseParams(orderNo, paymentType, tenantId);
|
||||
|
||||
// 获取支付策略
|
||||
PaymentStrategy strategy = getPaymentStrategy(paymentType);
|
||||
|
||||
// 检查是否支持关闭订单
|
||||
if (!strategy.supportClose()) {
|
||||
throw PaymentException.unsupportedPayment("该支付方式不支持关闭订单", paymentType);
|
||||
}
|
||||
|
||||
// 执行关闭
|
||||
boolean result = strategy.closeOrder(orderNo, tenantId);
|
||||
|
||||
log.info("订单关闭{}, 支付类型: {}, 租户ID: {}, 订单号: {}",
|
||||
result ? "成功" : "失败", paymentType, tenantId, orderNo);
|
||||
|
||||
return result;
|
||||
|
||||
} catch (PaymentException e) {
|
||||
log.error("订单关闭失败, 支付类型: {}, 租户ID: {}, 订单号: {}, 错误: {}",
|
||||
paymentType, tenantId, orderNo, e.getMessage());
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.error("订单关闭系统错误, 支付类型: {}, 租户ID: {}, 订单号: {}, 错误: {}",
|
||||
paymentType, tenantId, orderNo, e.getMessage(), e);
|
||||
throw PaymentException.systemError("订单关闭失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PaymentType> getSupportedPaymentTypes() {
|
||||
return new ArrayList<>(strategyMap.keySet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPaymentTypeSupported(PaymentType paymentType) {
|
||||
return strategyMap.containsKey(paymentType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRefundSupported(PaymentType paymentType) {
|
||||
PaymentStrategy strategy = strategyMap.get(paymentType);
|
||||
return strategy != null && strategy.supportRefund();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isQuerySupported(PaymentType paymentType) {
|
||||
PaymentStrategy strategy = strategyMap.get(paymentType);
|
||||
return strategy != null && strategy.supportQuery();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCloseSupported(PaymentType paymentType) {
|
||||
PaymentStrategy strategy = strategyMap.get(paymentType);
|
||||
return strategy != null && strategy.supportClose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNotifyNeeded(PaymentType paymentType) {
|
||||
PaymentStrategy strategy = strategyMap.get(paymentType);
|
||||
return strategy != null && strategy.needNotify();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validatePaymentRequest(PaymentRequest request) throws PaymentException {
|
||||
if (request == null) {
|
||||
throw PaymentException.paramError("支付请求不能为空");
|
||||
}
|
||||
|
||||
if (request.getPaymentType() == null) {
|
||||
throw PaymentException.paramError("支付类型不能为空");
|
||||
}
|
||||
|
||||
if (request.getTenantId() == null || request.getTenantId() <= 0) {
|
||||
throw PaymentException.paramError("租户ID不能为空且必须大于0");
|
||||
}
|
||||
|
||||
if (request.getUserId() == null || request.getUserId() <= 0) {
|
||||
throw PaymentException.paramError("用户ID不能为空且必须大于0");
|
||||
}
|
||||
|
||||
if (request.getAmount() == null || request.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
|
||||
throw PaymentException.amountError("支付金额必须大于0");
|
||||
}
|
||||
|
||||
if (!StringUtils.hasText(request.getSubject())) {
|
||||
throw PaymentException.paramError("订单标题不能为空");
|
||||
}
|
||||
|
||||
// 检查支付类型是否支持
|
||||
if (!isPaymentTypeSupported(request.getPaymentType())) {
|
||||
throw PaymentException.unsupportedPayment("不支持的支付类型: " + request.getPaymentType(), request.getPaymentType());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getPaymentStrategyInfo(PaymentType paymentType) {
|
||||
PaymentStrategy strategy = strategyMap.get(paymentType);
|
||||
if (strategy == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Map<String, Object> info = new HashMap<>();
|
||||
info.put("paymentType", paymentType);
|
||||
info.put("strategyName", strategy.getStrategyName());
|
||||
info.put("strategyDescription", strategy.getStrategyDescription());
|
||||
info.put("supportRefund", strategy.supportRefund());
|
||||
info.put("supportQuery", strategy.supportQuery());
|
||||
info.put("supportClose", strategy.supportClose());
|
||||
info.put("needNotify", strategy.needNotify());
|
||||
return info;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Map<String, Object>> getAllPaymentStrategyInfo() {
|
||||
return strategyMap.keySet().stream()
|
||||
.map(this::getPaymentStrategyInfo)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> checkPaymentConfig(Integer tenantId) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("tenantId", tenantId);
|
||||
|
||||
try {
|
||||
// 检查微信支付配置
|
||||
wxPayConfigService.getPaymentConfigForStrategy(tenantId);
|
||||
result.put("wechatConfigExists", true);
|
||||
result.put("wechatConfigError", null);
|
||||
} catch (Exception e) {
|
||||
result.put("wechatConfigExists", false);
|
||||
result.put("wechatConfigError", e.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
// 检查微信支付Config构建
|
||||
wxPayConfigService.getWxPayConfig(tenantId);
|
||||
result.put("wechatConfigValid", true);
|
||||
result.put("wechatConfigValidError", null);
|
||||
} catch (Exception e) {
|
||||
result.put("wechatConfigValid", false);
|
||||
result.put("wechatConfigValidError", e.getMessage());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取支付策略
|
||||
*/
|
||||
private PaymentStrategy getPaymentStrategy(PaymentType paymentType) throws PaymentException {
|
||||
PaymentStrategy strategy = strategyMap.get(paymentType);
|
||||
if (strategy == null) {
|
||||
throw PaymentException.unsupportedPayment("不支持的支付类型: " + paymentType, paymentType);
|
||||
}
|
||||
return strategy;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证查询参数
|
||||
*/
|
||||
private void validateQueryParams(String orderNo, PaymentType paymentType, Integer tenantId) throws PaymentException {
|
||||
if (!StringUtils.hasText(orderNo)) {
|
||||
throw PaymentException.paramError("订单号不能为空");
|
||||
}
|
||||
if (paymentType == null) {
|
||||
throw PaymentException.paramError("支付类型不能为空");
|
||||
}
|
||||
if (tenantId == null || tenantId <= 0) {
|
||||
throw PaymentException.paramError("租户ID不能为空且必须大于0");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证回调参数
|
||||
*/
|
||||
private void validateNotifyParams(PaymentType paymentType, Map<String, String> headers, String body, Integer tenantId) throws PaymentException {
|
||||
if (paymentType == null) {
|
||||
throw PaymentException.paramError("支付类型不能为空");
|
||||
}
|
||||
if (headers == null || headers.isEmpty()) {
|
||||
throw PaymentException.paramError("请求头不能为空");
|
||||
}
|
||||
if (!StringUtils.hasText(body)) {
|
||||
throw PaymentException.paramError("请求体不能为空");
|
||||
}
|
||||
if (tenantId == null || tenantId <= 0) {
|
||||
throw PaymentException.paramError("租户ID不能为空且必须大于0");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证退款参数
|
||||
*/
|
||||
private void validateRefundParams(String orderNo, String refundNo, PaymentType paymentType,
|
||||
BigDecimal totalAmount, BigDecimal refundAmount, Integer tenantId) throws PaymentException {
|
||||
if (!StringUtils.hasText(orderNo)) {
|
||||
throw PaymentException.paramError("订单号不能为空");
|
||||
}
|
||||
if (!StringUtils.hasText(refundNo)) {
|
||||
throw PaymentException.paramError("退款单号不能为空");
|
||||
}
|
||||
if (paymentType == null) {
|
||||
throw PaymentException.paramError("支付类型不能为空");
|
||||
}
|
||||
if (totalAmount == null || totalAmount.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
throw PaymentException.amountError("订单总金额必须大于0");
|
||||
}
|
||||
if (refundAmount == null || refundAmount.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
throw PaymentException.amountError("退款金额必须大于0");
|
||||
}
|
||||
if (refundAmount.compareTo(totalAmount) > 0) {
|
||||
throw PaymentException.amountError("退款金额不能大于订单总金额");
|
||||
}
|
||||
if (tenantId == null || tenantId <= 0) {
|
||||
throw PaymentException.paramError("租户ID不能为空且必须大于0");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证退款查询参数
|
||||
*/
|
||||
private void validateRefundQueryParams(String refundNo, PaymentType paymentType, Integer tenantId) throws PaymentException {
|
||||
if (!StringUtils.hasText(refundNo)) {
|
||||
throw PaymentException.paramError("退款单号不能为空");
|
||||
}
|
||||
if (paymentType == null) {
|
||||
throw PaymentException.paramError("支付类型不能为空");
|
||||
}
|
||||
if (tenantId == null || tenantId <= 0) {
|
||||
throw PaymentException.paramError("租户ID不能为空且必须大于0");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证关闭订单参数
|
||||
*/
|
||||
private void validateCloseParams(String orderNo, PaymentType paymentType, Integer tenantId) throws PaymentException {
|
||||
if (!StringUtils.hasText(orderNo)) {
|
||||
throw PaymentException.paramError("订单号不能为空");
|
||||
}
|
||||
if (paymentType == null) {
|
||||
throw PaymentException.paramError("支付类型不能为空");
|
||||
}
|
||||
if (tenantId == null || tenantId <= 0) {
|
||||
throw PaymentException.paramError("租户ID不能为空且必须大于0");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证支付与订单创建请求参数
|
||||
*/
|
||||
private void validatePaymentWithOrderRequest(PaymentWithOrderRequest request, User loginUser) throws PaymentException {
|
||||
if (request == null) {
|
||||
throw PaymentException.paramError("请求参数不能为空");
|
||||
}
|
||||
if (loginUser == null) {
|
||||
throw PaymentException.paramError("用户未登录");
|
||||
}
|
||||
if (request.getPaymentType() == null) {
|
||||
throw PaymentException.paramError("支付类型不能为空");
|
||||
}
|
||||
if (request.getAmount() == null || request.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
|
||||
throw PaymentException.amountError("支付金额必须大于0");
|
||||
}
|
||||
if (!StringUtils.hasText(request.getSubject())) {
|
||||
throw PaymentException.paramError("订单标题不能为空");
|
||||
}
|
||||
if (request.getTenantId() == null || request.getTenantId() <= 0) {
|
||||
throw PaymentException.paramError("租户ID不能为空且必须大于0");
|
||||
}
|
||||
if (request.getOrderInfo() == null) {
|
||||
throw PaymentException.paramError("订单信息不能为空");
|
||||
}
|
||||
if (request.getOrderInfo().getGoodsItems() == null || request.getOrderInfo().getGoodsItems().isEmpty()) {
|
||||
throw PaymentException.paramError("订单商品列表不能为空");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为订单创建请求
|
||||
*/
|
||||
private OrderCreateRequest convertToOrderCreateRequest(PaymentWithOrderRequest request, User loginUser) {
|
||||
OrderCreateRequest orderRequest = new OrderCreateRequest();
|
||||
|
||||
// 生成订单号(使用雪花算法保证全局唯一)
|
||||
String orderNo = Long.toString(IdUtil.getSnowflakeNextId());
|
||||
orderRequest.setOrderNo(orderNo);
|
||||
log.info("为订单创建请求生成订单号(雪花算法): {}", orderNo);
|
||||
|
||||
// 设置基本信息
|
||||
orderRequest.setType(request.getOrderInfo().getType());
|
||||
orderRequest.setTitle(request.getSubject());
|
||||
orderRequest.setComments(request.getOrderInfo().getComments());
|
||||
orderRequest.setTenantId(request.getTenantId());
|
||||
|
||||
// 设置收货信息
|
||||
orderRequest.setRealName(request.getOrderInfo().getRealName());
|
||||
orderRequest.setAddress(request.getOrderInfo().getAddress());
|
||||
orderRequest.setAddressId(request.getOrderInfo().getAddressId());
|
||||
orderRequest.setDeliveryType(request.getOrderInfo().getDeliveryType());
|
||||
|
||||
// 设置商户信息
|
||||
orderRequest.setMerchantId(request.getOrderInfo().getMerchantId());
|
||||
orderRequest.setMerchantName(request.getOrderInfo().getMerchantName());
|
||||
|
||||
// 设置支付信息
|
||||
orderRequest.setPayType(request.getPaymentType().getCode());
|
||||
orderRequest.setTotalPrice(request.getAmount());
|
||||
orderRequest.setPayPrice(request.getAmount());
|
||||
|
||||
// 设置优惠券
|
||||
orderRequest.setCouponId(request.getOrderInfo().getCouponId());
|
||||
|
||||
// 转换商品列表
|
||||
List<OrderCreateRequest.OrderGoodsItem> goodsItems = request.getOrderInfo().getGoodsItems().stream()
|
||||
.map(this::convertToOrderGoodsItem)
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
orderRequest.setGoodsItems(goodsItems);
|
||||
|
||||
return orderRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换商品项
|
||||
*/
|
||||
private OrderCreateRequest.OrderGoodsItem convertToOrderGoodsItem(PaymentWithOrderRequest.OrderGoodsItem item) {
|
||||
OrderCreateRequest.OrderGoodsItem orderItem = new OrderCreateRequest.OrderGoodsItem();
|
||||
orderItem.setGoodsId(item.getGoodsId());
|
||||
orderItem.setSkuId(item.getSkuId());
|
||||
orderItem.setQuantity(item.getQuantity());
|
||||
orderItem.setSpecInfo(item.getSpecInfo());
|
||||
return orderItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从微信订单信息构建支付响应
|
||||
*/
|
||||
private PaymentResponse buildPaymentResponseFromWxOrder(Map<String, String> wxOrderInfo,
|
||||
PaymentWithOrderRequest request,
|
||||
String orderNo) {
|
||||
PaymentResponse response = PaymentResponse.wechatNative(
|
||||
orderNo,
|
||||
wxOrderInfo.get("codeUrl"),
|
||||
request.getAmount(),
|
||||
request.getTenantId()
|
||||
);
|
||||
|
||||
// 设置额外信息
|
||||
response.setSuccess(true);
|
||||
// 确保orderNo被正确设置
|
||||
response.setOrderNo(orderNo);
|
||||
|
||||
// 调试日志
|
||||
log.info("构建支付响应成功, 订单号: {}, 二维码URL: {}, 响应中的orderNo: {}",
|
||||
orderNo, wxOrderInfo.get("codeUrl"), response.getOrderNo());
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
153
java/payment/strategy/PaymentStrategy.java
Normal file
153
java/payment/strategy/PaymentStrategy.java
Normal file
@@ -0,0 +1,153 @@
|
||||
package com.gxwebsoft.payment.strategy;
|
||||
|
||||
import com.gxwebsoft.payment.dto.PaymentRequest;
|
||||
import com.gxwebsoft.payment.dto.PaymentResponse;
|
||||
import com.gxwebsoft.payment.enums.PaymentType;
|
||||
import com.gxwebsoft.payment.exception.PaymentException;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 支付策略接口
|
||||
* 定义所有支付方式的统一接口
|
||||
*
|
||||
* @author 科技小王子
|
||||
* @since 2025-01-26
|
||||
*/
|
||||
public interface PaymentStrategy {
|
||||
|
||||
/**
|
||||
* 获取支持的支付类型
|
||||
*
|
||||
* @return 支付类型
|
||||
*/
|
||||
PaymentType getSupportedPaymentType();
|
||||
|
||||
/**
|
||||
* 验证支付请求参数
|
||||
*
|
||||
* @param request 支付请求
|
||||
* @throws PaymentException 参数验证失败时抛出
|
||||
*/
|
||||
void validateRequest(PaymentRequest request) throws PaymentException;
|
||||
|
||||
/**
|
||||
* 创建支付订单
|
||||
*
|
||||
* @param request 支付请求
|
||||
* @return 支付响应
|
||||
* @throws PaymentException 支付创建失败时抛出
|
||||
*/
|
||||
PaymentResponse createPayment(PaymentRequest request) throws PaymentException;
|
||||
|
||||
/**
|
||||
* 查询支付状态
|
||||
*
|
||||
* @param orderNo 订单号
|
||||
* @param tenantId 租户ID
|
||||
* @return 支付响应
|
||||
* @throws PaymentException 查询失败时抛出
|
||||
*/
|
||||
PaymentResponse queryPayment(String orderNo, Integer tenantId) throws PaymentException;
|
||||
|
||||
/**
|
||||
* 处理支付回调通知
|
||||
*
|
||||
* @param headers 请求头
|
||||
* @param body 请求体
|
||||
* @param tenantId 租户ID
|
||||
* @return 处理结果,返回给第三方的响应内容
|
||||
* @throws PaymentException 处理失败时抛出
|
||||
*/
|
||||
String handleNotify(Map<String, String> headers, String body, Integer tenantId) throws PaymentException;
|
||||
|
||||
/**
|
||||
* 申请退款
|
||||
*
|
||||
* @param orderNo 订单号
|
||||
* @param refundNo 退款单号
|
||||
* @param totalAmount 订单总金额
|
||||
* @param refundAmount 退款金额
|
||||
* @param reason 退款原因
|
||||
* @param tenantId 租户ID
|
||||
* @return 退款响应
|
||||
* @throws PaymentException 退款申请失败时抛出
|
||||
*/
|
||||
PaymentResponse refund(String orderNo, String refundNo,
|
||||
java.math.BigDecimal totalAmount, java.math.BigDecimal refundAmount,
|
||||
String reason, Integer tenantId) throws PaymentException;
|
||||
|
||||
/**
|
||||
* 查询退款状态
|
||||
*
|
||||
* @param refundNo 退款单号
|
||||
* @param tenantId 租户ID
|
||||
* @return 退款查询响应
|
||||
* @throws PaymentException 查询失败时抛出
|
||||
*/
|
||||
PaymentResponse queryRefund(String refundNo, Integer tenantId) throws PaymentException;
|
||||
|
||||
/**
|
||||
* 关闭订单
|
||||
*
|
||||
* @param orderNo 订单号
|
||||
* @param tenantId 租户ID
|
||||
* @return 关闭结果
|
||||
* @throws PaymentException 关闭失败时抛出
|
||||
*/
|
||||
boolean closeOrder(String orderNo, Integer tenantId) throws PaymentException;
|
||||
|
||||
/**
|
||||
* 是否支持退款
|
||||
*
|
||||
* @return true表示支持退款
|
||||
*/
|
||||
default boolean supportRefund() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否支持查询
|
||||
*
|
||||
* @return true表示支持查询
|
||||
*/
|
||||
default boolean supportQuery() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否支持关闭订单
|
||||
*
|
||||
* @return true表示支持关闭订单
|
||||
*/
|
||||
default boolean supportClose() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否需要异步通知
|
||||
*
|
||||
* @return true表示需要异步通知
|
||||
*/
|
||||
default boolean needNotify() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取策略名称
|
||||
*
|
||||
* @return 策略名称
|
||||
*/
|
||||
default String getStrategyName() {
|
||||
return getSupportedPaymentType().getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取策略描述
|
||||
*
|
||||
* @return 策略描述
|
||||
*/
|
||||
default String getStrategyDescription() {
|
||||
return getSupportedPaymentType().getName() + "支付策略";
|
||||
}
|
||||
}
|
||||
401
java/payment/strategy/WechatNativeStrategy.java
Normal file
401
java/payment/strategy/WechatNativeStrategy.java
Normal file
@@ -0,0 +1,401 @@
|
||||
package com.gxwebsoft.payment.strategy;
|
||||
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import com.gxwebsoft.common.core.utils.CommonUtil;
|
||||
import com.gxwebsoft.common.system.entity.Payment;
|
||||
import com.gxwebsoft.payment.constants.PaymentConstants;
|
||||
import com.gxwebsoft.payment.dto.PaymentRequest;
|
||||
import com.gxwebsoft.payment.dto.PaymentResponse;
|
||||
import com.gxwebsoft.payment.enums.PaymentStatus;
|
||||
import com.gxwebsoft.payment.enums.PaymentType;
|
||||
import com.gxwebsoft.payment.exception.PaymentException;
|
||||
import com.gxwebsoft.payment.service.WxPayConfigService;
|
||||
import com.gxwebsoft.payment.service.WxPayNotifyService;
|
||||
import com.wechat.pay.java.core.Config;
|
||||
import com.wechat.pay.java.service.payments.nativepay.NativePayService;
|
||||
import com.wechat.pay.java.service.payments.nativepay.model.Amount;
|
||||
import com.wechat.pay.java.service.payments.nativepay.model.PrepayRequest;
|
||||
import com.wechat.pay.java.service.payments.nativepay.model.PrepayResponse;
|
||||
import com.wechat.pay.java.service.payments.nativepay.model.QueryOrderByOutTradeNoRequest;
|
||||
import com.wechat.pay.java.service.payments.model.Transaction;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 微信Native支付策略实现
|
||||
* 处理微信Native扫码支付
|
||||
*
|
||||
* @author 科技小王子
|
||||
* @since 2025-01-26
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class WechatNativeStrategy implements PaymentStrategy {
|
||||
|
||||
@Resource
|
||||
private WxPayConfigService wxPayConfigService;
|
||||
|
||||
@Resource
|
||||
private WxPayNotifyService wxPayNotifyService;
|
||||
|
||||
@Override
|
||||
public PaymentType getSupportedPaymentType() {
|
||||
return PaymentType.WECHAT_NATIVE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateRequest(PaymentRequest request) throws PaymentException {
|
||||
if (request == null) {
|
||||
throw PaymentException.paramError("支付请求不能为空");
|
||||
}
|
||||
|
||||
if (request.getTenantId() == null) {
|
||||
throw PaymentException.paramError("租户ID不能为空");
|
||||
}
|
||||
|
||||
if (request.getUserId() == null) {
|
||||
throw PaymentException.paramError("用户ID不能为空");
|
||||
}
|
||||
|
||||
if (request.getAmount() == null || request.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
|
||||
throw PaymentException.amountError("支付金额必须大于0");
|
||||
}
|
||||
|
||||
if (!StringUtils.hasText(request.getSubject())) {
|
||||
throw PaymentException.paramError("订单标题不能为空");
|
||||
}
|
||||
|
||||
// 验证金额范围
|
||||
if (request.getAmount().compareTo(new BigDecimal("0.01")) < 0) {
|
||||
throw PaymentException.amountError("支付金额不能小于0.01元");
|
||||
}
|
||||
|
||||
if (request.getAmount().compareTo(new BigDecimal("999999.99")) > 0) {
|
||||
throw PaymentException.amountError("支付金额不能超过999999.99元");
|
||||
}
|
||||
|
||||
// 验证订单标题长度
|
||||
if (request.getSubject().length() > PaymentConstants.Config.DESCRIPTION_MAX_LENGTH) {
|
||||
throw PaymentException.paramError("订单标题长度不能超过" + PaymentConstants.Config.DESCRIPTION_MAX_LENGTH + "个字符");
|
||||
}
|
||||
|
||||
log.debug("微信Native支付请求参数验证通过, 租户ID: {}, 金额: {}",
|
||||
request.getTenantId(), request.getFormattedAmount());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentResponse createPayment(PaymentRequest request) throws PaymentException {
|
||||
log.info("{}, 支付类型: {}, 租户ID: {}, 金额: {}",
|
||||
PaymentConstants.LogMessage.PAYMENT_START, getSupportedPaymentType(),
|
||||
request.getTenantId(), request.getFormattedAmount());
|
||||
|
||||
try {
|
||||
// 验证请求参数
|
||||
validateRequest(request);
|
||||
|
||||
// 生成订单号
|
||||
String orderNo = generateOrderNo(request);
|
||||
log.info("生成的订单号: {}", orderNo);
|
||||
|
||||
// 获取Native支付的Payment配置(包含appId等信息)
|
||||
Payment paymentConfig = wxPayConfigService.getPaymentConfigForStrategy(request.getTenantId());
|
||||
|
||||
// 获取微信支付配置
|
||||
Config wxPayConfig = wxPayConfigService.getWxPayConfig(request.getTenantId());
|
||||
|
||||
// 构建预支付请求
|
||||
PrepayRequest prepayRequest = buildPrepayRequest(request, orderNo, paymentConfig);
|
||||
|
||||
// 调用微信支付API
|
||||
PrepayResponse prepayResponse = callWechatPayApi(prepayRequest, wxPayConfig);
|
||||
|
||||
// 构建响应
|
||||
PaymentResponse response = PaymentResponse.wechatNative(
|
||||
orderNo, prepayResponse.getCodeUrl(), request.getAmount(), request.getTenantId());
|
||||
response.setUserId(request.getUserId());
|
||||
|
||||
// 确保orderNo被正确设置
|
||||
response.setOrderNo(orderNo);
|
||||
|
||||
// 调试日志:检查响应对象的orderNo
|
||||
log.info("构建的响应对象 - orderNo: {}, codeUrl: {}, success: {}",
|
||||
response.getOrderNo(), response.getCodeUrl(), response.getSuccess());
|
||||
|
||||
log.info("{}, 支付类型: {}, 租户ID: {}, 订单号: {}, 金额: {}",
|
||||
PaymentConstants.LogMessage.PAYMENT_SUCCESS, getSupportedPaymentType(),
|
||||
request.getTenantId(), orderNo, request.getFormattedAmount());
|
||||
|
||||
return response;
|
||||
|
||||
} catch (PaymentException e) {
|
||||
log.error("{}, 支付类型: {}, 租户ID: {}, 错误: {}",
|
||||
PaymentConstants.LogMessage.PAYMENT_FAILED, getSupportedPaymentType(),
|
||||
request.getTenantId(), e.getMessage());
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.error("{}, 支付类型: {}, 租户ID: {}, 系统错误: {}",
|
||||
PaymentConstants.LogMessage.PAYMENT_FAILED, getSupportedPaymentType(),
|
||||
request.getTenantId(), e.getMessage(), e);
|
||||
throw PaymentException.systemError("微信Native支付创建失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentResponse queryPayment(String orderNo, Integer tenantId) throws PaymentException {
|
||||
log.info("开始查询微信Native支付状态, 订单号: {}, 租户ID: {}", orderNo, tenantId);
|
||||
|
||||
try {
|
||||
// 参数验证
|
||||
if (!StringUtils.hasText(orderNo)) {
|
||||
throw PaymentException.paramError("订单号不能为空");
|
||||
}
|
||||
if (tenantId == null) {
|
||||
throw PaymentException.paramError("租户ID不能为空");
|
||||
}
|
||||
|
||||
// 获取支付配置(包含商户号等信息)
|
||||
Payment paymentConfig = wxPayConfigService.getPaymentConfigForStrategy(tenantId);
|
||||
|
||||
// 获取微信支付配置
|
||||
Config wxPayConfig = wxPayConfigService.getWxPayConfig(tenantId);
|
||||
|
||||
// 调用微信支付查询API
|
||||
return queryWechatPaymentStatus(orderNo, tenantId, paymentConfig, wxPayConfig);
|
||||
|
||||
} catch (Exception e) {
|
||||
if (e instanceof PaymentException) {
|
||||
throw e;
|
||||
}
|
||||
log.error("查询微信Native支付状态失败, 订单号: {}, 租户ID: {}, 错误: {}",
|
||||
orderNo, tenantId, e.getMessage(), e);
|
||||
throw PaymentException.systemError("查询微信支付状态失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String handleNotify(Map<String, String> headers, String body, Integer tenantId) throws PaymentException {
|
||||
log.info("{}, 支付类型: {}, 租户ID: {}",
|
||||
PaymentConstants.LogMessage.NOTIFY_START, getSupportedPaymentType(), tenantId);
|
||||
|
||||
try {
|
||||
// 委托给专门的回调处理服务
|
||||
return wxPayNotifyService.handlePaymentNotify(headers, body, tenantId);
|
||||
} catch (Exception e) {
|
||||
log.error("{}, 支付类型: {}, 租户ID: {}, 错误: {}",
|
||||
PaymentConstants.LogMessage.NOTIFY_FAILED, getSupportedPaymentType(),
|
||||
tenantId, e.getMessage(), e);
|
||||
throw PaymentException.systemError("微信支付回调处理失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentResponse refund(String orderNo, String refundNo, BigDecimal totalAmount,
|
||||
BigDecimal refundAmount, String reason, Integer tenantId) throws PaymentException {
|
||||
// TODO: 实现微信支付退款逻辑
|
||||
throw PaymentException.unsupportedPayment("暂不支持微信支付退款", PaymentType.WECHAT_NATIVE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentResponse queryRefund(String refundNo, Integer tenantId) throws PaymentException {
|
||||
// TODO: 实现微信退款查询逻辑
|
||||
throw PaymentException.unsupportedPayment("暂不支持微信退款查询", PaymentType.WECHAT_NATIVE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean closeOrder(String orderNo, Integer tenantId) throws PaymentException {
|
||||
// TODO: 实现微信订单关闭逻辑
|
||||
throw PaymentException.unsupportedPayment("暂不支持微信订单关闭", PaymentType.WECHAT_NATIVE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportRefund() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportQuery() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportClose() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needNotify() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成订单号(使用雪花算法保证全局唯一)
|
||||
*/
|
||||
private String generateOrderNo(PaymentRequest request) {
|
||||
if (StringUtils.hasText(request.getOrderNo())) {
|
||||
return request.getOrderNo();
|
||||
}
|
||||
return Long.toString(IdUtil.getSnowflakeNextId());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 构建微信预支付请求
|
||||
*/
|
||||
private PrepayRequest buildPrepayRequest(PaymentRequest request, String orderNo, Payment paymentConfig) {
|
||||
PrepayRequest prepayRequest = new PrepayRequest();
|
||||
|
||||
// 设置应用ID和商户号(关键修复)
|
||||
prepayRequest.setAppid(paymentConfig.getAppId());
|
||||
prepayRequest.setMchid(paymentConfig.getMchId());
|
||||
|
||||
// 设置金额
|
||||
Amount amount = new Amount();
|
||||
amount.setTotal(request.getAmountInCents());
|
||||
amount.setCurrency(PaymentConstants.Wechat.CURRENCY);
|
||||
prepayRequest.setAmount(amount);
|
||||
|
||||
// 设置基本信息
|
||||
prepayRequest.setOutTradeNo(orderNo);
|
||||
prepayRequest.setDescription(request.getEffectiveDescription());
|
||||
|
||||
log.info("创建微信支付订单 - 订单号: {}, 商户号: {}, 金额: {}分",
|
||||
orderNo, paymentConfig.getMchId(), request.getAmountInCents());
|
||||
|
||||
// 设置回调URL(必填字段)
|
||||
String notifyUrl = null;
|
||||
if (StringUtils.hasText(request.getNotifyUrl())) {
|
||||
// 优先使用请求中的回调URL
|
||||
notifyUrl = request.getNotifyUrl();
|
||||
} else if (StringUtils.hasText(paymentConfig.getNotifyUrl())) {
|
||||
// 使用配置中的回调URL
|
||||
notifyUrl = paymentConfig.getNotifyUrl();
|
||||
} else {
|
||||
// 如果都没有,抛出异常
|
||||
throw new RuntimeException("回调通知地址不能为空,请在支付请求中设置notifyUrl或在支付配置中设置notifyUrl");
|
||||
}
|
||||
prepayRequest.setNotifyUrl(notifyUrl);
|
||||
|
||||
log.debug("构建微信预支付请求完成, 订单号: {}, 金额: {}分, AppID: {}, 商户号: {}, 回调URL: {}",
|
||||
orderNo, request.getAmountInCents(), paymentConfig.getAppId(), paymentConfig.getMchId(), notifyUrl);
|
||||
|
||||
return prepayRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询微信支付状态
|
||||
*/
|
||||
private PaymentResponse queryWechatPaymentStatus(String orderNo, Integer tenantId, Payment paymentConfig, Config wxPayConfig) throws PaymentException {
|
||||
try {
|
||||
log.info("开始查询微信支付状态 - 订单号: {}, 商户号: {}, 租户ID: {}",
|
||||
orderNo, paymentConfig.getMchId(), tenantId);
|
||||
|
||||
// 构建查询请求
|
||||
QueryOrderByOutTradeNoRequest queryRequest = new QueryOrderByOutTradeNoRequest();
|
||||
queryRequest.setOutTradeNo(orderNo);
|
||||
queryRequest.setMchid(paymentConfig.getMchId());
|
||||
|
||||
// 构建服务
|
||||
NativePayService service = new NativePayService.Builder().config(wxPayConfig).build();
|
||||
|
||||
// 调用查询接口
|
||||
Transaction transaction = service.queryOrderByOutTradeNo(queryRequest);
|
||||
|
||||
if (transaction == null) {
|
||||
throw PaymentException.systemError("微信支付查询返回空结果", null);
|
||||
}
|
||||
|
||||
// 转换支付状态
|
||||
PaymentStatus paymentStatus = convertWechatPaymentStatus(transaction.getTradeState());
|
||||
|
||||
// 构建响应
|
||||
PaymentResponse response = new PaymentResponse();
|
||||
response.setSuccess(true);
|
||||
response.setOrderNo(orderNo);
|
||||
response.setPaymentStatus(paymentStatus);
|
||||
response.setTenantId(tenantId);
|
||||
response.setPaymentType(PaymentType.WECHAT_NATIVE);
|
||||
|
||||
if (transaction.getAmount() != null) {
|
||||
// 微信返回的金额是分,需要转换为元
|
||||
BigDecimal amount = new BigDecimal(transaction.getAmount().getTotal()).divide(new BigDecimal("100"));
|
||||
response.setAmount(amount);
|
||||
}
|
||||
|
||||
if (transaction.getTransactionId() != null) {
|
||||
response.setTransactionId(transaction.getTransactionId());
|
||||
}
|
||||
|
||||
log.info("微信Native支付状态查询成功, 订单号: {}, 状态: {}, 微信交易号: {}",
|
||||
orderNo, paymentStatus, transaction.getTransactionId());
|
||||
|
||||
return response;
|
||||
|
||||
} catch (Exception e) {
|
||||
if (e instanceof PaymentException) {
|
||||
throw e;
|
||||
}
|
||||
log.error("查询微信支付状态失败, 订单号: {}, 错误: {}", orderNo, e.getMessage(), e);
|
||||
throw PaymentException.networkError("查询微信支付状态失败: " + e.getMessage(), PaymentType.WECHAT_NATIVE, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换微信支付状态
|
||||
*/
|
||||
private PaymentStatus convertWechatPaymentStatus(Transaction.TradeStateEnum tradeState) {
|
||||
if (tradeState == null) {
|
||||
return PaymentStatus.PENDING;
|
||||
}
|
||||
|
||||
switch (tradeState) {
|
||||
case SUCCESS:
|
||||
return PaymentStatus.SUCCESS;
|
||||
case REFUND:
|
||||
return PaymentStatus.REFUNDED;
|
||||
case NOTPAY:
|
||||
return PaymentStatus.PENDING;
|
||||
case CLOSED:
|
||||
return PaymentStatus.CANCELLED;
|
||||
case REVOKED:
|
||||
return PaymentStatus.CANCELLED;
|
||||
case USERPAYING:
|
||||
return PaymentStatus.PROCESSING;
|
||||
case PAYERROR:
|
||||
return PaymentStatus.FAILED;
|
||||
default:
|
||||
return PaymentStatus.PENDING;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用微信支付API
|
||||
*/
|
||||
private PrepayResponse callWechatPayApi(PrepayRequest request, Config wxPayConfig) throws PaymentException {
|
||||
try {
|
||||
// 构建服务
|
||||
NativePayService service = new NativePayService.Builder().config(wxPayConfig).build();
|
||||
|
||||
// 调用预支付接口
|
||||
PrepayResponse response = service.prepay(request);
|
||||
|
||||
if (response == null || !StringUtils.hasText(response.getCodeUrl())) {
|
||||
throw PaymentException.networkError("微信支付API返回数据异常", PaymentType.WECHAT_NATIVE, null);
|
||||
}
|
||||
|
||||
log.debug("微信支付API调用成功, 订单号: {}", request.getOutTradeNo());
|
||||
return response;
|
||||
|
||||
} catch (Exception e) {
|
||||
if (e instanceof PaymentException) {
|
||||
throw e;
|
||||
}
|
||||
throw PaymentException.networkError("调用微信支付API失败: " + e.getMessage(), PaymentType.WECHAT_NATIVE, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
165
java/payment/utils/PaymentTypeCompatibilityUtil.java
Normal file
165
java/payment/utils/PaymentTypeCompatibilityUtil.java
Normal file
@@ -0,0 +1,165 @@
|
||||
package com.gxwebsoft.payment.utils;
|
||||
|
||||
import com.gxwebsoft.payment.enums.PaymentType;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 支付方式兼容性处理工具类
|
||||
* 处理废弃支付方式到核心支付方式的映射转换
|
||||
*
|
||||
* @author 科技小王子
|
||||
* @since 2025-08-30
|
||||
*/
|
||||
@Slf4j
|
||||
public class PaymentTypeCompatibilityUtil {
|
||||
|
||||
/**
|
||||
* 废弃支付方式到核心支付方式的映射表
|
||||
*/
|
||||
private static final Map<Integer, Integer> DEPRECATED_TO_CORE_MAPPING = new HashMap<>();
|
||||
|
||||
static {
|
||||
// 旧编号到新编号的映射
|
||||
DEPRECATED_TO_CORE_MAPPING.put(3, 2); // 支付宝(旧3) -> 支付宝(新2)
|
||||
DEPRECATED_TO_CORE_MAPPING.put(12, 6); // 免费(旧12) -> 免费(新6)
|
||||
DEPRECATED_TO_CORE_MAPPING.put(15, 7); // 积分支付(旧15) -> 积分支付(新7)
|
||||
DEPRECATED_TO_CORE_MAPPING.put(19, 3); // 银联支付(旧19) -> 银联支付(新3)
|
||||
|
||||
// 会员卡类支付 -> 余额支付
|
||||
DEPRECATED_TO_CORE_MAPPING.put(2, 0); // 会员卡支付 -> 余额支付
|
||||
DEPRECATED_TO_CORE_MAPPING.put(6, 0); // VIP月卡 -> 余额支付
|
||||
DEPRECATED_TO_CORE_MAPPING.put(7, 0); // VIP年卡 -> 余额支付
|
||||
DEPRECATED_TO_CORE_MAPPING.put(8, 0); // VIP次卡 -> 余额支付
|
||||
DEPRECATED_TO_CORE_MAPPING.put(9, 0); // IC月卡 -> 余额支付
|
||||
DEPRECATED_TO_CORE_MAPPING.put(10, 0); // IC年卡 -> 余额支付
|
||||
DEPRECATED_TO_CORE_MAPPING.put(11, 0); // IC次卡 -> 余额支付
|
||||
DEPRECATED_TO_CORE_MAPPING.put(13, 0); // VIP充值卡 -> 余额支付
|
||||
DEPRECATED_TO_CORE_MAPPING.put(14, 0); // IC充值卡 -> 余额支付
|
||||
DEPRECATED_TO_CORE_MAPPING.put(16, 0); // VIP季卡 -> 余额支付
|
||||
DEPRECATED_TO_CORE_MAPPING.put(17, 0); // IC季卡 -> 余额支付
|
||||
|
||||
// 微信Native -> 微信支付
|
||||
DEPRECATED_TO_CORE_MAPPING.put(102, 1); // 微信Native -> 微信支付
|
||||
|
||||
// 代付 -> 微信支付(默认)
|
||||
DEPRECATED_TO_CORE_MAPPING.put(18, 1); // 代付 -> 微信支付
|
||||
}
|
||||
|
||||
/**
|
||||
* 将废弃的支付方式转换为核心支付方式
|
||||
*
|
||||
* @param originalPayType 原始支付方式代码
|
||||
* @return 转换后的核心支付方式代码
|
||||
*/
|
||||
public static Integer convertToCore(Integer originalPayType) {
|
||||
if (originalPayType == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 检查是否为废弃的支付方式
|
||||
if (DEPRECATED_TO_CORE_MAPPING.containsKey(originalPayType)) {
|
||||
Integer corePayType = DEPRECATED_TO_CORE_MAPPING.get(originalPayType);
|
||||
log.warn("检测到废弃的支付方式: {} -> {},建议升级到核心支付方式",
|
||||
originalPayType, corePayType);
|
||||
return corePayType;
|
||||
}
|
||||
|
||||
// 如果是核心支付方式,直接返回
|
||||
return originalPayType;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查支付方式是否已废弃
|
||||
*
|
||||
* @param payType 支付方式代码
|
||||
* @return true表示已废弃
|
||||
*/
|
||||
public static boolean isDeprecated(Integer payType) {
|
||||
return payType != null && DEPRECATED_TO_CORE_MAPPING.containsKey(payType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取支付方式的迁移说明
|
||||
*
|
||||
* @param payType 支付方式代码
|
||||
* @return 迁移说明文本
|
||||
*/
|
||||
public static String getMigrationMessage(Integer payType) {
|
||||
if (payType == null || !isDeprecated(payType)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
PaymentType originalType = PaymentType.getByCode(payType);
|
||||
PaymentType coreType = PaymentType.getByCode(convertToCore(payType));
|
||||
|
||||
if (originalType != null && coreType != null) {
|
||||
return String.format("支付方式 %s(%d) 已废弃,建议使用 %s(%d)",
|
||||
originalType.getName(), payType,
|
||||
coreType.getName(), coreType.getCode());
|
||||
}
|
||||
|
||||
return "该支付方式已废弃,请使用核心支付方式";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有核心支付方式代码
|
||||
*
|
||||
* @return 核心支付方式代码数组
|
||||
*/
|
||||
public static Integer[] getCorePaymentTypeCodes() {
|
||||
return new Integer[]{0, 1, 2, 3, 4, 5, 6, 7};
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为核心支付方式
|
||||
*
|
||||
* @param payType 支付方式代码
|
||||
* @return true表示是核心支付方式
|
||||
*/
|
||||
public static boolean isCorePaymentType(Integer payType) {
|
||||
if (payType == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (Integer coreType : getCorePaymentTypeCodes()) {
|
||||
if (coreType.equals(payType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成支付方式迁移报告
|
||||
*
|
||||
* @return 迁移报告文本
|
||||
*/
|
||||
public static String generateMigrationReport() {
|
||||
StringBuilder report = new StringBuilder();
|
||||
report.append("=== 支付方式迁移报告 ===\n");
|
||||
report.append("核心支付方式(8种):\n");
|
||||
|
||||
for (Integer coreType : getCorePaymentTypeCodes()) {
|
||||
PaymentType type = PaymentType.getByCode(coreType);
|
||||
if (type != null) {
|
||||
report.append(String.format(" %d - %s\n", coreType, type.getName()));
|
||||
}
|
||||
}
|
||||
|
||||
report.append("\n废弃支付方式映射:\n");
|
||||
for (Map.Entry<Integer, Integer> entry : DEPRECATED_TO_CORE_MAPPING.entrySet()) {
|
||||
PaymentType originalType = PaymentType.getByCode(entry.getKey());
|
||||
PaymentType coreType = PaymentType.getByCode(entry.getValue());
|
||||
if (originalType != null && coreType != null) {
|
||||
report.append(String.format(" %d(%s) -> %d(%s)\n",
|
||||
entry.getKey(), originalType.getName(),
|
||||
entry.getValue(), coreType.getName()));
|
||||
}
|
||||
}
|
||||
|
||||
return report.toString();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user