删除gym-payment模块,与dev分支保持一致
This commit is contained in:
@@ -1,62 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>cn.novalon.gym.manage</groupId>
|
||||
<artifactId>gym-manage-api</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
|
||||
<groupId>cn.novalon.gym.manage</groupId>
|
||||
<artifactId>gym-payment</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>Gym Payment</name>
|
||||
<description>支付模块 - 支付宝App支付</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- Spring Boot WebFlux -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 通用模块 (含Redis工具类) -->
|
||||
<dependency>
|
||||
<groupId>cn.novalon.gym.manage</groupId>
|
||||
<artifactId>manage-common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 支付宝SDK -->
|
||||
<dependency>
|
||||
<groupId>com.alipay.sdk</groupId>
|
||||
<artifactId>alipay-sdk-java</artifactId>
|
||||
<version>4.35.120.ALL</version>
|
||||
</dependency>
|
||||
|
||||
<!-- JSON处理 -->
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
<version>2.10.1</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Lombok -->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Swagger -->
|
||||
<dependency>
|
||||
<groupId>io.swagger.core.v3</groupId>
|
||||
<artifactId>swagger-annotations</artifactId>
|
||||
<version>2.2.19</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
package cn.novalon.gym.manage.payment.config;
|
||||
|
||||
import com.alipay.api.AlipayClient;
|
||||
import com.alipay.api.DefaultAlipayClient;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* 支付宝客户端配置
|
||||
*/
|
||||
@Configuration
|
||||
public class AlipayClientConfig {
|
||||
|
||||
private final AlipayProperties alipayProperties;
|
||||
|
||||
public AlipayClientConfig(AlipayProperties alipayProperties) {
|
||||
this.alipayProperties = alipayProperties;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AlipayClient alipayClient() {
|
||||
return new DefaultAlipayClient(
|
||||
alipayProperties.getGateway(),
|
||||
alipayProperties.getAppId(),
|
||||
alipayProperties.getAppPrivateKey(),
|
||||
"json",
|
||||
alipayProperties.getCharset(),
|
||||
alipayProperties.getAlipayPublicKey(),
|
||||
alipayProperties.getSignType()
|
||||
);
|
||||
}
|
||||
}
|
||||
-57
@@ -1,57 +0,0 @@
|
||||
package cn.novalon.gym.manage.payment.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 支付宝配置属性
|
||||
* 沙箱环境配置
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "alipay")
|
||||
public class AlipayProperties {
|
||||
|
||||
/**
|
||||
* 应用ID
|
||||
*/
|
||||
private String appId;
|
||||
|
||||
/**
|
||||
* 应用私钥 (PKCS#8格式)
|
||||
*/
|
||||
private String appPrivateKey;
|
||||
|
||||
/**
|
||||
* 支付宝公钥
|
||||
*/
|
||||
private String alipayPublicKey;
|
||||
|
||||
/**
|
||||
* 签名方式
|
||||
*/
|
||||
private String signType = "RSA2";
|
||||
|
||||
/**
|
||||
* 编码格式
|
||||
*/
|
||||
private String charset = "UTF-8";
|
||||
|
||||
/**
|
||||
* 网关地址
|
||||
* 沙箱环境: https://openapi-sandbox.dl.alipaydev.com/gateway.do
|
||||
* 正式环境: https://openapi.alipay.com/gateway.do
|
||||
*/
|
||||
private String gateway;
|
||||
|
||||
/**
|
||||
* 异步通知地址
|
||||
*/
|
||||
private String notifyUrl;
|
||||
|
||||
/**
|
||||
* 是否沙箱环境
|
||||
*/
|
||||
private boolean sandbox = true;
|
||||
}
|
||||
-52
@@ -1,52 +0,0 @@
|
||||
package cn.novalon.gym.manage.payment.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 统一API响应
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class ApiResponse<T> {
|
||||
|
||||
private int code;
|
||||
private String message;
|
||||
private T data;
|
||||
|
||||
public static <T> ApiResponse<T> success(T data) {
|
||||
return ApiResponse.<T>builder()
|
||||
.code(200)
|
||||
.message("success")
|
||||
.data(data)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static <T> ApiResponse<T> success(String message, T data) {
|
||||
return ApiResponse.<T>builder()
|
||||
.code(200)
|
||||
.message(message)
|
||||
.data(data)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static <T> ApiResponse<T> error(int code, String message) {
|
||||
return ApiResponse.<T>builder()
|
||||
.code(code)
|
||||
.message(message)
|
||||
.data(null)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static <T> ApiResponse<T> error(String message) {
|
||||
return ApiResponse.<T>builder()
|
||||
.code(500)
|
||||
.message(message)
|
||||
.data(null)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
-54
@@ -1,54 +0,0 @@
|
||||
package cn.novalon.gym.manage.payment.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 创建支付请求DTO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "创建支付请求")
|
||||
public class CreatePaymentRequest {
|
||||
|
||||
@Schema(description = "会员ID")
|
||||
private Long memberId;
|
||||
|
||||
@Schema(description = "订单类型: MEMBER_CARD-会员卡, GROUP_COURSE-团课, GOODS-商品")
|
||||
private String orderType;
|
||||
|
||||
@Schema(description = "商品描述")
|
||||
private String goodsDesc;
|
||||
|
||||
@Schema(description = "交易金额(单位:分)")
|
||||
private String transAmt;
|
||||
|
||||
@Schema(description = "交易类型: ALIPAY-支付宝, WECHAT-微信")
|
||||
private String tradeType;
|
||||
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
|
||||
@Schema(description = "账户号")
|
||||
private String acctId;
|
||||
|
||||
@Schema(description = "过期时间(yyyyMMddHHmmss)")
|
||||
private String timeExpire;
|
||||
|
||||
@Schema(description = "延迟入账标识")
|
||||
private String delayAcctFlag;
|
||||
|
||||
@Schema(description = "手续费标识")
|
||||
private Integer feeFlag;
|
||||
|
||||
@Schema(description = "禁用支付方式")
|
||||
private String limitPayType;
|
||||
|
||||
@Schema(description = "渠道号")
|
||||
private String channelNo;
|
||||
|
||||
@Schema(description = "支付场景")
|
||||
private String payScene;
|
||||
|
||||
@Schema(description = "异步通知URL")
|
||||
private String notifyUrl;
|
||||
}
|
||||
-54
@@ -1,54 +0,0 @@
|
||||
package cn.novalon.gym.manage.payment.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 支付响应DTO
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "支付响应")
|
||||
public class PaymentResponse {
|
||||
|
||||
@Schema(description = "订单ID")
|
||||
private String orderId;
|
||||
|
||||
@Schema(description = "支付状态: PENDING-待支付, SUCCESS-成功, FAIL-失败, CLOSED-关闭")
|
||||
private String status;
|
||||
|
||||
@Schema(description = "支付链接(App支付返回 scheme)")
|
||||
private String payUrl;
|
||||
|
||||
@Schema(description = "二维码链接 (扫码支付)")
|
||||
private String qrCode;
|
||||
|
||||
@Schema(description = "完整支付信息")
|
||||
private String payInfo;
|
||||
|
||||
@Schema(description = "H5支付链接")
|
||||
private String h5PayUrl;
|
||||
|
||||
@Schema(description = "错误码")
|
||||
private String errorCode;
|
||||
|
||||
@Schema(description = "错误消息")
|
||||
private String errorMsg;
|
||||
|
||||
@Schema(description = "交易金额(分)")
|
||||
private String transAmt;
|
||||
|
||||
@Schema(description = "商品描述")
|
||||
private String goodsDesc;
|
||||
|
||||
@Schema(description = "交易类型")
|
||||
private String tradeType;
|
||||
|
||||
@Schema(description = "支付成功时间")
|
||||
private String payTime;
|
||||
}
|
||||
-140
@@ -1,140 +0,0 @@
|
||||
package cn.novalon.gym.manage.payment.handler;
|
||||
|
||||
import cn.novalon.gym.manage.payment.dto.ApiResponse;
|
||||
import cn.novalon.gym.manage.payment.dto.CreatePaymentRequest;
|
||||
import cn.novalon.gym.manage.payment.dto.PaymentResponse;
|
||||
import cn.novalon.gym.manage.payment.service.PaymentService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.publisher.MonoSink;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 支付处理器
|
||||
* 提供支付宝App支付相关接口
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@Tag(name = "支付管理", description = "支付宝App支付接口")
|
||||
public class PaymentHandler {
|
||||
|
||||
private final PaymentService paymentService;
|
||||
|
||||
public PaymentHandler(PaymentService paymentService) {
|
||||
this.paymentService = paymentService;
|
||||
}
|
||||
|
||||
@Operation(summary = "创建支付订单", description = "创建支付宝App支付订单")
|
||||
public Mono<ServerResponse> createPayment(ServerRequest request) {
|
||||
return request.bodyToMono(CreatePaymentRequest.class)
|
||||
.flatMap(createReq -> {
|
||||
log.info("[Payment] 创建支付订单: memberId={}, orderType={}, goodsDesc={}, transAmt={}",
|
||||
createReq.getMemberId(), createReq.getOrderType(), createReq.getGoodsDesc(), createReq.getTransAmt());
|
||||
|
||||
PaymentResponse result = paymentService.alipayAppPay(createReq);
|
||||
|
||||
if ("FAIL".equals(result.getStatus())) {
|
||||
return ServerResponse.ok()
|
||||
.bodyValue(ApiResponse.error(result.getErrorMsg()));
|
||||
}
|
||||
|
||||
return ServerResponse.ok()
|
||||
.bodyValue(ApiResponse.success(result));
|
||||
})
|
||||
.onErrorResume(e -> {
|
||||
log.error("[Payment] 创建支付订单异常", e);
|
||||
return ServerResponse.ok()
|
||||
.bodyValue(ApiResponse.error("创建支付订单失败: " + e.getMessage()));
|
||||
});
|
||||
}
|
||||
|
||||
@Operation(summary = "创建扫码支付订单", description = "创建支付宝扫码支付订单,返回二维码链接")
|
||||
public Mono<ServerResponse> createQrCodePayment(ServerRequest request) {
|
||||
return request.bodyToMono(CreatePaymentRequest.class)
|
||||
.flatMap(createReq -> {
|
||||
log.info("[Payment] 创建扫码支付订单: memberId={}, orderType={}, goodsDesc={}, transAmt={}",
|
||||
createReq.getMemberId(), createReq.getOrderType(), createReq.getGoodsDesc(), createReq.getTransAmt());
|
||||
|
||||
PaymentResponse result = paymentService.alipayQrCodePay(createReq);
|
||||
|
||||
if ("FAIL".equals(result.getStatus())) {
|
||||
return ServerResponse.ok()
|
||||
.bodyValue(ApiResponse.error(result.getErrorMsg()));
|
||||
}
|
||||
|
||||
return ServerResponse.ok()
|
||||
.bodyValue(ApiResponse.success(result));
|
||||
})
|
||||
.onErrorResume(e -> {
|
||||
log.error("[Payment] 创建扫码支付订单异常", e);
|
||||
return ServerResponse.ok()
|
||||
.bodyValue(ApiResponse.error("创建扫码支付订单失败: " + e.getMessage()));
|
||||
});
|
||||
}
|
||||
|
||||
@Operation(summary = "查询支付状态", description = "查询支付订单状态")
|
||||
public Mono<ServerResponse> getPaymentStatus(ServerRequest request) {
|
||||
String orderId = request.pathVariable("orderId");
|
||||
log.info("[Payment] 查询支付状态: orderId={}", orderId);
|
||||
|
||||
PaymentResponse result = paymentService.getPaymentStatus(orderId);
|
||||
return ServerResponse.ok()
|
||||
.bodyValue(ApiResponse.success(result));
|
||||
}
|
||||
|
||||
@Operation(summary = "支付宝异步通知", description = "接收支付宝异步回调通知")
|
||||
public Mono<ServerResponse> alipayNotify(ServerRequest request) {
|
||||
return request.bodyToMono(Map.class)
|
||||
.flatMap(params -> {
|
||||
log.info("[Payment] 收到支付宝异步通知: params={}", params);
|
||||
|
||||
// 转换Map类型
|
||||
Map<String, String> notifyParams = new HashMap<>();
|
||||
if (params instanceof Map) {
|
||||
((Map<?, ?>) params).forEach((key, value) ->
|
||||
notifyParams.put(String.valueOf(key), String.valueOf(value)));
|
||||
}
|
||||
|
||||
String result = paymentService.handleAlipayNotify(notifyParams);
|
||||
return ServerResponse.ok()
|
||||
.bodyValue(result);
|
||||
})
|
||||
.onErrorResume(e -> {
|
||||
log.error("[Payment] 异步通知处理异常", e);
|
||||
return ServerResponse.ok()
|
||||
.bodyValue("fail");
|
||||
});
|
||||
}
|
||||
|
||||
@Operation(summary = "申请退款", description = "申请支付退款")
|
||||
public Mono<ServerResponse> refundPayment(ServerRequest request) {
|
||||
String orderId = request.pathVariable("orderId");
|
||||
|
||||
return request.bodyToMono(Map.class)
|
||||
.flatMap(body -> {
|
||||
String refundAmt = String.valueOf(body.get("refundAmt"));
|
||||
log.info("[Payment] 申请退款: orderId={}, refundAmt={}", orderId, refundAmt);
|
||||
|
||||
boolean success = paymentService.refund(orderId, refundAmt);
|
||||
if (success) {
|
||||
return ServerResponse.ok()
|
||||
.bodyValue(ApiResponse.success("退款申请成功"));
|
||||
} else {
|
||||
return ServerResponse.ok()
|
||||
.bodyValue(ApiResponse.error("退款申请失败"));
|
||||
}
|
||||
})
|
||||
.onErrorResume(e -> {
|
||||
log.error("[Payment] 退款异常: orderId={}", orderId, e);
|
||||
return ServerResponse.ok()
|
||||
.bodyValue(ApiResponse.error("退款失败: " + e.getMessage()));
|
||||
});
|
||||
}
|
||||
}
|
||||
-59
@@ -1,59 +0,0 @@
|
||||
package cn.novalon.gym.manage.payment.service;
|
||||
|
||||
import cn.novalon.gym.manage.payment.dto.CreatePaymentRequest;
|
||||
import cn.novalon.gym.manage.payment.dto.PaymentResponse;
|
||||
|
||||
/**
|
||||
* 支付服务接口
|
||||
*/
|
||||
public interface PaymentService {
|
||||
|
||||
/**
|
||||
* 创建支付订单
|
||||
*
|
||||
* @param request 创建支付请求
|
||||
* @return 支付响应
|
||||
*/
|
||||
PaymentResponse createPayment(CreatePaymentRequest request);
|
||||
|
||||
/**
|
||||
* 查询支付状态
|
||||
*
|
||||
* @param orderId 订单ID
|
||||
* @return 支付响应
|
||||
*/
|
||||
PaymentResponse getPaymentStatus(String orderId);
|
||||
|
||||
/**
|
||||
* 支付宝App支付
|
||||
*
|
||||
* @param request 创建支付请求
|
||||
* @return 支付响应
|
||||
*/
|
||||
PaymentResponse alipayAppPay(CreatePaymentRequest request);
|
||||
|
||||
/**
|
||||
* 支付宝扫码支付(二维码支付)
|
||||
*
|
||||
* @param request 创建支付请求
|
||||
* @return 支付响应(包含二维码链接)
|
||||
*/
|
||||
PaymentResponse alipayQrCodePay(CreatePaymentRequest request);
|
||||
|
||||
/**
|
||||
* 处理支付宝异步通知
|
||||
*
|
||||
* @param params 通知参数
|
||||
* @return 处理结果
|
||||
*/
|
||||
String handleAlipayNotify(java.util.Map<String, String> params);
|
||||
|
||||
/**
|
||||
* 申请退款
|
||||
*
|
||||
* @param orderId 订单ID
|
||||
* @param refundAmt 退款金额(分)
|
||||
* @return 退款结果
|
||||
*/
|
||||
boolean refund(String orderId, String refundAmt);
|
||||
}
|
||||
-445
@@ -1,445 +0,0 @@
|
||||
package cn.novalon.gym.manage.payment.service.impl;
|
||||
|
||||
import cn.novalon.gym.manage.common.util.RedisUtil;
|
||||
import cn.novalon.gym.manage.payment.config.AlipayProperties;
|
||||
import cn.novalon.gym.manage.payment.dto.CreatePaymentRequest;
|
||||
import cn.novalon.gym.manage.payment.dto.PaymentResponse;
|
||||
import cn.novalon.gym.manage.payment.service.PaymentService;
|
||||
import com.alipay.api.AlipayApiException;
|
||||
import com.alipay.api.AlipayClient;
|
||||
import com.alipay.api.internal.util.AlipaySignature;
|
||||
import com.alipay.api.request.AlipayTradeAppPayRequest;
|
||||
import com.alipay.api.request.AlipayTradePrecreateRequest;
|
||||
import com.alipay.api.request.AlipayTradeQueryRequest;
|
||||
import com.alipay.api.request.AlipayTradeRefundRequest;
|
||||
import com.alipay.api.response.AlipayTradeAppPayResponse;
|
||||
import com.alipay.api.response.AlipayTradePrecreateResponse;
|
||||
import com.alipay.api.response.AlipayTradeQueryResponse;
|
||||
import com.alipay.api.response.AlipayTradeRefundResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 支付服务实现
|
||||
* App支付 (alipay.trade.app.pay)
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class PaymentServiceImpl implements PaymentService {
|
||||
|
||||
private static final String PROCESSED_TRADE_PREFIX = "alipay:processed:trade:";
|
||||
private static final String REFUND_ORDER_PREFIX = "alipay:refund:order:";
|
||||
private static final String ORDER_CACHE_PREFIX = "alipay:order:";
|
||||
private static final long IDEMPOTENT_EXPIRE_SECONDS = 86400; // 24小时过期
|
||||
|
||||
private final AlipayClient alipayClient;
|
||||
private final AlipayProperties alipayProperties;
|
||||
private final RedisUtil redisUtil;
|
||||
|
||||
/**
|
||||
* 订单本地缓存 (作为Redis的二级缓存)
|
||||
*/
|
||||
private final Map<String, PaymentResponse> localOrderCache = new ConcurrentHashMap<>();
|
||||
|
||||
public PaymentServiceImpl(AlipayClient alipayClient, AlipayProperties alipayProperties, RedisUtil redisUtil) {
|
||||
this.alipayClient = alipayClient;
|
||||
this.alipayProperties = alipayProperties;
|
||||
this.redisUtil = redisUtil;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentResponse createPayment(CreatePaymentRequest request) {
|
||||
// App支付使用 alipay.trade.app.pay
|
||||
return alipayAppPay(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentResponse getPaymentStatus(String orderId) {
|
||||
// 先查本地缓存
|
||||
PaymentResponse cached = localOrderCache.get(orderId);
|
||||
if (cached != null && "SUCCESS".equals(cached.getStatus())) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
// 查Redis缓存
|
||||
PaymentResponse redisCached = getOrderFromRedis(orderId);
|
||||
if (redisCached != null && "SUCCESS".equals(redisCached.getStatus())) {
|
||||
localOrderCache.put(orderId, redisCached);
|
||||
return redisCached;
|
||||
}
|
||||
|
||||
try {
|
||||
AlipayTradeQueryRequest queryRequest = new AlipayTradeQueryRequest();
|
||||
queryRequest.setBizContent("{\"out_trade_no\":\"" + orderId + "\"}");
|
||||
|
||||
AlipayTradeQueryResponse response = alipayClient.execute(queryRequest);
|
||||
if (response.isSuccess()) {
|
||||
Date sendPayDate = response.getSendPayDate();
|
||||
String payTimeStr = sendPayDate != null ? new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(sendPayDate) : null;
|
||||
|
||||
PaymentResponse result = PaymentResponse.builder()
|
||||
.orderId(orderId)
|
||||
.status(response.getTradeStatus())
|
||||
.payTime(payTimeStr)
|
||||
.transAmt(response.getTotalAmount())
|
||||
.build();
|
||||
|
||||
if ("TRADE_SUCCESS".equals(response.getTradeStatus())) {
|
||||
result.setStatus("SUCCESS");
|
||||
// 更新缓存
|
||||
saveOrderToRedis(orderId, result);
|
||||
localOrderCache.put(orderId, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
} catch (AlipayApiException e) {
|
||||
log.error("查询支付状态失败: orderId={}", orderId, e);
|
||||
}
|
||||
|
||||
return PaymentResponse.builder()
|
||||
.orderId(orderId)
|
||||
.status(cached != null ? cached.getStatus() : (redisCached != null ? redisCached.getStatus() : "PENDING"))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentResponse alipayAppPay(CreatePaymentRequest request) {
|
||||
// 生成订单号
|
||||
String orderId = generateOrderId();
|
||||
|
||||
try {
|
||||
AlipayTradeAppPayRequest payRequest = new AlipayTradeAppPayRequest();
|
||||
payRequest.setNotifyUrl(alipayProperties.getNotifyUrl());
|
||||
|
||||
// 构建业务参数 (测试金额:固定1分钱)
|
||||
String bizContent = "{\"out_trade_no\":\"" + orderId + "\","
|
||||
+ "\"total_amount\":\"0.01\","
|
||||
+ "\"subject\":\"" + request.getGoodsDesc() + "\","
|
||||
+ "\"product_code\":\"QUICK_MSECURITY_PAY\","
|
||||
+ "\"timeout_express\":\"30m\"}";
|
||||
|
||||
log.info("[Alipay] App支付请求: orderId={}, bizContent={}", orderId, bizContent);
|
||||
|
||||
payRequest.setBizContent(bizContent);
|
||||
|
||||
AlipayTradeAppPayResponse response = alipayClient.sdkExecute(payRequest);
|
||||
|
||||
if (response.isSuccess()) {
|
||||
String payUrl = response.getBody();
|
||||
|
||||
// App唤起支付宝的scheme格式: alipay://...
|
||||
// 实际使用时,前端使用 plus.runtime.openURL(payUrl) 唤起支付宝
|
||||
log.info("[Alipay] App支付创建成功: orderId={}, payUrl={}", orderId, payUrl);
|
||||
|
||||
PaymentResponse result = PaymentResponse.builder()
|
||||
.orderId(orderId)
|
||||
.status("PENDING")
|
||||
.payUrl(payUrl)
|
||||
.payInfo(response.getBody())
|
||||
.goodsDesc(request.getGoodsDesc())
|
||||
.transAmt(request.getTransAmt())
|
||||
.tradeType(request.getTradeType())
|
||||
.build();
|
||||
|
||||
// 缓存订单到Redis和本地
|
||||
saveOrderToRedis(orderId, result);
|
||||
localOrderCache.put(orderId, result);
|
||||
|
||||
return result;
|
||||
} else {
|
||||
log.error("[Alipay] App支付创建失败: orderId={}, error={}", orderId, response.getMsg());
|
||||
return PaymentResponse.builder()
|
||||
.orderId(orderId)
|
||||
.status("FAIL")
|
||||
.errorCode(response.getCode())
|
||||
.errorMsg(response.getMsg())
|
||||
.build();
|
||||
}
|
||||
} catch (AlipayApiException e) {
|
||||
log.error("[Alipay] App支付异常: orderId={}", orderId, e);
|
||||
String errCode = e.getErrCode() != null ? e.getErrCode() : "NETWORK_ERROR";
|
||||
String errMsg = e.getErrMsg() != null ? e.getErrMsg() : extractErrorMessage(e);
|
||||
return PaymentResponse.builder()
|
||||
.orderId(orderId)
|
||||
.status("FAIL")
|
||||
.errorCode(errCode)
|
||||
.errorMsg(errMsg)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 支付宝扫码支付 (alipay.trade.precreate)
|
||||
* 生成付款二维码,用户使用支付宝扫码完成支付
|
||||
*/
|
||||
@Override
|
||||
public PaymentResponse alipayQrCodePay(CreatePaymentRequest request) {
|
||||
String orderId = generateOrderId();
|
||||
|
||||
try {
|
||||
AlipayTradePrecreateRequest precreateRequest = new AlipayTradePrecreateRequest();
|
||||
precreateRequest.setNotifyUrl(alipayProperties.getNotifyUrl());
|
||||
|
||||
// 构建业务参数
|
||||
String bizContent = "{\"out_trade_no\":\"" + orderId + "\","
|
||||
+ "\"total_amount\":\"0.01\","
|
||||
+ "\"subject\":\"" + request.getGoodsDesc() + "\","
|
||||
+ "\"timeout_express\":\"30m\"}";
|
||||
|
||||
log.info("[Alipay] 扫码支付请求: orderId={}, bizContent={}", orderId, bizContent);
|
||||
|
||||
precreateRequest.setBizContent(bizContent);
|
||||
AlipayTradePrecreateResponse response = alipayClient.execute(precreateRequest);
|
||||
|
||||
if (response.isSuccess()) {
|
||||
String qrCode = response.getQrCode(); // 二维码链接
|
||||
|
||||
log.info("[Alipay] 扫码支付创建成功: orderId={}, qrCode={}", orderId, qrCode);
|
||||
|
||||
PaymentResponse result = PaymentResponse.builder()
|
||||
.orderId(orderId)
|
||||
.status("PENDING")
|
||||
.qrCode(qrCode)
|
||||
.payInfo(response.getBody())
|
||||
.goodsDesc(request.getGoodsDesc())
|
||||
.transAmt(request.getTransAmt())
|
||||
.tradeType("QRCODE")
|
||||
.build();
|
||||
|
||||
// 缓存订单到Redis和本地
|
||||
saveOrderToRedis(orderId, result);
|
||||
localOrderCache.put(orderId, result);
|
||||
|
||||
return result;
|
||||
} else {
|
||||
log.error("[Alipay] 扫码支付创建失败: orderId={}, error={}", orderId, response.getMsg());
|
||||
return PaymentResponse.builder()
|
||||
.orderId(orderId)
|
||||
.status("FAIL")
|
||||
.errorCode(response.getCode())
|
||||
.errorMsg(response.getMsg())
|
||||
.build();
|
||||
}
|
||||
} catch (AlipayApiException e) {
|
||||
log.error("[Alipay] 扫码支付异常: orderId={}", orderId, e);
|
||||
String errCode = e.getErrCode() != null ? e.getErrCode() : "NETWORK_ERROR";
|
||||
String errMsg = e.getErrMsg() != null ? e.getErrMsg() : extractErrorMessage(e);
|
||||
return PaymentResponse.builder()
|
||||
.orderId(orderId)
|
||||
.status("FAIL")
|
||||
.errorCode(errCode)
|
||||
.errorMsg(errMsg)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String handleAlipayNotify(Map<String, String> params) {
|
||||
String outTradeNo = params.get("out_trade_no");
|
||||
|
||||
try {
|
||||
// 1. 幂等处理:检查是否已处理过该通知(使用Redis)
|
||||
if (isTradeProcessed(outTradeNo)) {
|
||||
log.info("[Alipay] 幂等处理:通知已处理,跳过, outTradeNo={}", outTradeNo);
|
||||
return "success";
|
||||
}
|
||||
|
||||
// 2. 验签
|
||||
boolean signVerified = AlipaySignature.rsaCheckV1(
|
||||
params,
|
||||
alipayProperties.getAlipayPublicKey(),
|
||||
alipayProperties.getCharset(),
|
||||
alipayProperties.getSignType()
|
||||
);
|
||||
|
||||
if (!signVerified) {
|
||||
log.warn("[Alipay] 异步通知验签失败: outTradeNo={}", outTradeNo);
|
||||
return "fail";
|
||||
}
|
||||
|
||||
// 3. 关键信息校验
|
||||
// 校验 app_id 是否匹配
|
||||
String notifyAppId = params.get("app_id");
|
||||
if (!alipayProperties.getAppId().equals(notifyAppId)) {
|
||||
log.warn("[Alipay] app_id 不匹配: expected={}, actual={}", alipayProperties.getAppId(), notifyAppId);
|
||||
return "fail";
|
||||
}
|
||||
|
||||
// 校验卖家ID (seller_id)
|
||||
String sellerId = params.get("seller_id");
|
||||
if (sellerId != null && !sellerId.isEmpty()) {
|
||||
// 商家ID校验逻辑根据实际情况添加
|
||||
log.debug("[Alipay] 卖家ID校验: seller_id={}", sellerId);
|
||||
}
|
||||
|
||||
// 校验交易金额
|
||||
String notifyTotalAmount = params.get("total_amount");
|
||||
PaymentResponse cachedOrder = getOrderFromRedis(outTradeNo);
|
||||
if (cachedOrder != null && notifyTotalAmount != null) {
|
||||
// 金额校验:确保通知金额与订单金额一致(防止金额篡改)
|
||||
log.debug("[Alipay] 交易金额校验: orderId={}, amount={}", outTradeNo, notifyTotalAmount);
|
||||
}
|
||||
|
||||
String tradeStatus = params.get("trade_status");
|
||||
log.info("[Alipay] 异步通知验签成功: outTradeNo={}, tradeStatus={}", outTradeNo, tradeStatus);
|
||||
|
||||
// 4. 判断交易状态
|
||||
if ("TRADE_SUCCESS".equals(tradeStatus) || "TRADE_FINISHED".equals(tradeStatus)) {
|
||||
// 更新订单状态
|
||||
if (cachedOrder != null) {
|
||||
cachedOrder.setStatus("SUCCESS");
|
||||
cachedOrder.setPayTime(params.get("gmt_payment"));
|
||||
saveOrderToRedis(outTradeNo, cachedOrder);
|
||||
localOrderCache.put(outTradeNo, cachedOrder);
|
||||
}
|
||||
|
||||
// 5. 标记为已处理(幂等,使用Redis)
|
||||
markTradeProcessed(outTradeNo);
|
||||
|
||||
return "success";
|
||||
}
|
||||
|
||||
return "fail";
|
||||
} catch (AlipayApiException e) {
|
||||
log.error("[Alipay] 异步通知处理异常: outTradeNo={}", outTradeNo, e);
|
||||
return "fail";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean refund(String orderId, String refundAmt) {
|
||||
// 1. 退款幂等检查:防止重复退款(使用Redis)
|
||||
if (isOrderRefunded(orderId)) {
|
||||
log.info("[Alipay] 退款幂等:订单已退款过,跳过, orderId={}", orderId);
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
AlipayTradeRefundRequest refundRequest = new AlipayTradeRefundRequest();
|
||||
refundRequest.setBizContent("{\"out_trade_no\":\"" + orderId + "\",\"refund_amount\":\"" + refundAmt + "\"}");
|
||||
|
||||
AlipayTradeRefundResponse response = alipayClient.execute(refundRequest);
|
||||
if (response.isSuccess()) {
|
||||
// 2. 校验退款是否真正成功:必须判断 fund_change=Y
|
||||
String fundChange = response.getFundChange();
|
||||
if ("Y".equals(fundChange)) {
|
||||
log.info("[Alipay] 退款成功: orderId={}, refundAmt={}, fundChange={}", orderId, refundAmt, fundChange);
|
||||
// 标记为已退款(幂等,使用Redis)
|
||||
markOrderRefunded(orderId);
|
||||
return true;
|
||||
} else {
|
||||
log.warn("[Alipay] 退款返回Y但fund_change!=Y: orderId={}, fundChange={}", orderId, fundChange);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
log.error("[Alipay] 退款失败: orderId={}, error={}", orderId, response.getMsg());
|
||||
}
|
||||
} catch (AlipayApiException e) {
|
||||
log.error("[Alipay] 退款异常: orderId={}", orderId, e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ==================== Redis操作方法 ====================
|
||||
|
||||
/**
|
||||
* 检查交易是否已处理(幂等)
|
||||
*/
|
||||
private boolean isTradeProcessed(String outTradeNo) {
|
||||
try {
|
||||
return Boolean.TRUE.equals(redisUtil.hasKey(PROCESSED_TRADE_PREFIX + outTradeNo).block());
|
||||
} catch (Exception e) {
|
||||
log.error("[Alipay] 检查交易处理状态异常: outTradeNo={}", outTradeNo, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记交易已处理(幂等)
|
||||
*/
|
||||
private void markTradeProcessed(String outTradeNo) {
|
||||
try {
|
||||
redisUtil.setWithExpire(PROCESSED_TRADE_PREFIX + outTradeNo, "1", IDEMPOTENT_EXPIRE_SECONDS).block();
|
||||
} catch (Exception e) {
|
||||
log.error("[Alipay] 标记交易处理状态异常: outTradeNo={}", outTradeNo, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查订单是否已退款(幂等)
|
||||
*/
|
||||
private boolean isOrderRefunded(String orderId) {
|
||||
try {
|
||||
return Boolean.TRUE.equals(redisUtil.hasKey(REFUND_ORDER_PREFIX + orderId).block());
|
||||
} catch (Exception e) {
|
||||
log.error("[Alipay] 检查订单退款状态异常: orderId={}", orderId, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记订单已退款(幂等)
|
||||
*/
|
||||
private void markOrderRefunded(String orderId) {
|
||||
try {
|
||||
redisUtil.setWithExpire(REFUND_ORDER_PREFIX + orderId, "1", IDEMPOTENT_EXPIRE_SECONDS).block();
|
||||
} catch (Exception e) {
|
||||
log.error("[Alipay] 标记订单退款状态异常: orderId={}", orderId, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从Redis获取订单
|
||||
*/
|
||||
private PaymentResponse getOrderFromRedis(String orderId) {
|
||||
try {
|
||||
return redisUtil.get(ORDER_CACHE_PREFIX + orderId, PaymentResponse.class).block();
|
||||
} catch (Exception e) {
|
||||
log.error("[Alipay] 获取Redis订单异常: orderId={}", orderId, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存订单到Redis
|
||||
*/
|
||||
private void saveOrderToRedis(String orderId, PaymentResponse response) {
|
||||
try {
|
||||
redisUtil.setWithExpire(ORDER_CACHE_PREFIX + orderId, response, IDEMPOTENT_EXPIRE_SECONDS).block();
|
||||
} catch (Exception e) {
|
||||
log.error("[Alipay] 保存订单到Redis异常: orderId={}", orderId, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成订单号
|
||||
*/
|
||||
private String generateOrderId() {
|
||||
String timestamp = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
|
||||
String uuid = UUID.randomUUID().toString().replace("-", "").substring(0, 8);
|
||||
return timestamp + uuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取异常中的错误信息(兜底)
|
||||
*/
|
||||
private String extractErrorMessage(AlipayApiException e) {
|
||||
Throwable cause = e.getCause();
|
||||
if (cause != null && cause.getMessage() != null && cause.getMessage().contains("504")) {
|
||||
return "支付宝沙箱网关连接超时(504),请检查服务器网络能否访问 openapi-sandbox.dl.alipaydev.com";
|
||||
}
|
||||
if (cause != null) {
|
||||
return "网络异常: " + cause.getClass().getSimpleName() + " - " + (cause.getMessage() != null ? cause.getMessage().substring(0, Math.min(cause.getMessage().length(), 200)) : "");
|
||||
}
|
||||
if (e.getMessage() != null) {
|
||||
return "支付宝接口异常: " + (e.getMessage().length() > 200 ? e.getMessage().substring(0, 200) : e.getMessage());
|
||||
}
|
||||
return "支付宝接口调用失败,请稍后重试";
|
||||
}
|
||||
}
|
||||
@@ -48,11 +48,6 @@
|
||||
<artifactId>gym-checkIn</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.novalon.gym.manage</groupId>
|
||||
<artifactId>gym-payment</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.novalon.gym.manage</groupId>
|
||||
<artifactId>gym-dataCount</artifactId>
|
||||
|
||||
@@ -47,7 +47,6 @@
|
||||
<module>gym-checkIn</module>
|
||||
<module>gym-dataCount</module>
|
||||
<module>gym-auth</module>
|
||||
<module>gym-payment</module>
|
||||
</modules>
|
||||
|
||||
<dependencyManagement>
|
||||
|
||||
Reference in New Issue
Block a user