mirror of
https://github.com/codewendao/PayPro.git
synced 2026-09-03 06:13:46 +08:00
全局异常处理
订单开放接口
This commit is contained in:
204
OPEN_API.md
Normal file
204
OPEN_API.md
Normal file
@@ -0,0 +1,204 @@
|
||||
# 外部订单接口文档
|
||||
|
||||
## 接口概述
|
||||
|
||||
该接口用于外部系统向支付系统添加订单,支持MD5签名验证、时间戳校验、事务处理等功能。
|
||||
|
||||
## 接口信息
|
||||
|
||||
- **接口路径**: `POST /api/openapi/orders`
|
||||
- **请求方法**: POST
|
||||
- **Content-Type**: application/json
|
||||
|
||||
## 请求参数
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------------------------------------------|
|
||||
| orderNo | String | 是 | 订单号,唯一标识 |
|
||||
| amount | BigDecimal | 是 | 订单金额,必须大于0且小于等于100000 |
|
||||
| payType | String | 是 | 支付方式(alipay/wechat/alipay_dmf/wechat_zs) |
|
||||
| nickName | String | 否 | 用户昵称 |
|
||||
| description | String | 否 | 订单描述 |
|
||||
| email | String | 否 | 用户邮箱 |
|
||||
| notifyUrl | String | 否 | 异步通知地址 |
|
||||
| userId | String | 否 | 用户ID |
|
||||
| productId | Long | 否 | 产品ID |
|
||||
| timestamp | Long | 是 | 请求时间戳(毫秒),有效期5分钟 |
|
||||
| sign | String | 是 | MD5签名 |
|
||||
|
||||
## 签名算法
|
||||
|
||||
1. 将所有参数(除sign外)按字母顺序排序
|
||||
2. 拼接成 `key1=value1&key2=value2&...&key=secretKey` 格式
|
||||
3. 对拼接后的字符串进行MD5加密,并转为大写
|
||||
|
||||
### 签名示例
|
||||
|
||||
假设配置的密钥为:`your_openapi_secret_key_here`
|
||||
|
||||
参数:
|
||||
```json
|
||||
{
|
||||
"orderNo": "EXT20250303001",
|
||||
"amount": 10.00,
|
||||
"payType": "alipay",
|
||||
"timestamp": 1733232000000
|
||||
}
|
||||
```
|
||||
|
||||
排序后的参数:
|
||||
```
|
||||
amount=10.00&orderNo=EXT20250303001&payType=alipay×tamp=1733232000000&key=your_openapi_secret_key_here
|
||||
```
|
||||
|
||||
MD5签名:`XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX`
|
||||
|
||||
## 响应格式
|
||||
|
||||
### 成功响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"orderId": "uuid-generated-id",
|
||||
"orderNo": "EXT20250303001",
|
||||
"amount": 10.00,
|
||||
"payType": "alipay",
|
||||
"payNum": "随机支付标识",
|
||||
"state": 0,
|
||||
"message": "订单创建成功",
|
||||
"timestamp": 1733232000000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 失败响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 400,
|
||||
"msg": "错误信息描述",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
## 错误码说明
|
||||
|
||||
| 错误码 | 说明 |
|
||||
|--------|------|
|
||||
| 200 | 成功 |
|
||||
| 400 | 参数错误 |
|
||||
| 401 | 签名验证失败 |
|
||||
| 402 | 时间戳无效或已过期 |
|
||||
| 403 | 订单号已存在 |
|
||||
| 404 | 金额错误 |
|
||||
| 500 | 系统内部异常 |
|
||||
|
||||
## 请求示例
|
||||
|
||||
### cURL 示例
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8889/api/openapi/orders \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"orderNo": "EXT20250303001",
|
||||
"amount": 10.00,
|
||||
"payType": "alipay",
|
||||
"nickName": "测试用户",
|
||||
"description": "测试订单",
|
||||
"email": "test@example.com",
|
||||
"userId": "USER001",
|
||||
"productId": 1,
|
||||
"notifyUrl": "http://example.com/notify",
|
||||
"timestamp": 1733232000000,
|
||||
"sign": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
|
||||
}'
|
||||
```
|
||||
|
||||
### Java 示例
|
||||
|
||||
```java
|
||||
import cn.hutool.http.HttpRequest;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.crypto.SecureUtil;
|
||||
|
||||
public class ExternalOrderClient {
|
||||
|
||||
private static final String API_URL = "http://localhost:8889/api/openapi/orders";
|
||||
private static final String SECRET_KEY = "your_openapi_secret_key_here";
|
||||
|
||||
public static void main(String[] args) {
|
||||
JSONObject params = new JSONObject();
|
||||
params.put("orderNo", "EXT20250303001");
|
||||
params.put("amount", 10.00);
|
||||
params.put("payType", "alipay");
|
||||
params.put("nickName", "测试用户");
|
||||
params.put("description", "测试订单");
|
||||
params.put("email", "test@example.com");
|
||||
params.put("userId", "USER001");
|
||||
params.put("productId", 1);
|
||||
params.put("notifyUrl", "http://example.com/notify");
|
||||
params.put("timestamp", System.currentTimeMillis());
|
||||
|
||||
String sign = generateSign(params);
|
||||
params.put("sign", sign);
|
||||
|
||||
String response = HttpRequest.post(API_URL)
|
||||
.header("Content-Type", "application/json")
|
||||
.body(params.toString())
|
||||
.execute()
|
||||
.body();
|
||||
|
||||
System.out.println(response);
|
||||
}
|
||||
|
||||
private static String generateSign(JSONObject params) {
|
||||
List<String> keys = new ArrayList<>(params.keySet());
|
||||
Collections.sort(keys);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String key : keys) {
|
||||
Object value = params.get(key);
|
||||
if (value != null && !"".equals(value) && !"sign".equals(key)) {
|
||||
sb.append(key).append("=").append(value).append("&");
|
||||
}
|
||||
}
|
||||
sb.append("key=").append(SECRET_KEY);
|
||||
|
||||
return SecureUtil.md5(sb.toString()).toUpperCase();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 配置说明
|
||||
|
||||
在 `application.yml` 中配置外部接口密钥:
|
||||
|
||||
```yaml
|
||||
paypro:
|
||||
openapi:
|
||||
secret: your_openapi_secret_key_here
|
||||
```
|
||||
|
||||
## 安全特性
|
||||
|
||||
1. **MD5签名验证**:确保请求参数未被篡改
|
||||
2. **时间戳校验**:防止重放攻击,时间戳有效期5分钟
|
||||
3. **订单号唯一性**:防止重复订单
|
||||
4. **金额限制**:单笔订单金额不超过100000元
|
||||
5. **事务处理**:确保订单创建的原子性
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 请妥善保管密钥,不要泄露给第三方
|
||||
2. 时间戳必须使用毫秒级时间戳
|
||||
3. 订单号必须唯一,重复的订单号会返回错误
|
||||
4. 建议在生产环境中使用HTTPS协议
|
||||
5. 建议实现IP白名单等额外的安全措施
|
||||
|
||||
## 测试页面
|
||||
|
||||
访问 `http://localhost:8889/open-api-test.html` 可以使用测试页面进行接口测试。
|
||||
@@ -3,12 +3,15 @@ package com.wendao.controller;
|
||||
import cn.hutool.log.StaticLog;
|
||||
import com.wendao.config.PayProConfig;
|
||||
import com.wendao.entity.Order;
|
||||
import com.wendao.exception.ApiException;
|
||||
import com.wendao.model.ResponseVO;
|
||||
import com.wendao.common.utils.*;
|
||||
import com.wendao.enums.OrderStatesEnum;
|
||||
import com.wendao.model.req.GetOrderListReq;
|
||||
import com.wendao.model.req.OpenApiOrderReq;
|
||||
import com.wendao.model.req.OrderReq;
|
||||
import com.wendao.model.resp.AddOrderResp;
|
||||
import com.wendao.model.resp.OpenApiOrderResp;
|
||||
import com.wendao.service.OrderService;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import io.swagger.annotations.Api;
|
||||
@@ -235,4 +238,26 @@ public class OrderController {
|
||||
public String getOrderId(String id){
|
||||
return id;
|
||||
}
|
||||
|
||||
@PostMapping("/api/openapi/orders")
|
||||
@ApiOperation(value = "创建OpenApi订单", notes = "OpenApi通过此接口创建支付订单")
|
||||
@ResponseBody
|
||||
public ResponseVO<OpenApiOrderResp> createOpenApiOrder(@RequestBody @Validated OpenApiOrderReq req) {
|
||||
try {
|
||||
log.info("收到OpenApi订单创建请求 - 订单号: {}, 金额: {}, 支付方式: {}",
|
||||
req.getOrderNo(), req.getAmount(), req.getPayType());
|
||||
|
||||
OpenApiOrderResp resp = orderService.createOpenApiOrder(req);
|
||||
|
||||
log.info("OpenApi订单创建成功 - 订单ID: {}, 订单号: {}", resp.getOrderId(), resp.getOrderNo());
|
||||
|
||||
return new ResponseVO<>(ApiException.ErrorCode.SUCCESS, "success", resp);
|
||||
} catch (ApiException e) {
|
||||
log.warn("OpenApi订单创建失败 - 错误码: {}, 错误信息: {}", e.getCode(), e.getMessage());
|
||||
return new ResponseVO<>(e.getCode(), e.getMessage());
|
||||
} catch (Exception e) {
|
||||
log.error("OpenApi订单创建系统异常", e);
|
||||
return new ResponseVO<>(ApiException.ErrorCode.SYSTEM_ERROR, "系统内部异常");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
30
src/main/java/com/wendao/exception/ApiException.java
Normal file
30
src/main/java/com/wendao/exception/ApiException.java
Normal file
@@ -0,0 +1,30 @@
|
||||
package com.wendao.exception;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public class ApiException extends RuntimeException {
|
||||
|
||||
private final int code;
|
||||
private final String message;
|
||||
|
||||
public ApiException(int code, String message) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public ApiException(String message) {
|
||||
this(500, message);
|
||||
}
|
||||
|
||||
public static class ErrorCode {
|
||||
public static final int SUCCESS = 200;
|
||||
public static final int INVALID_PARAM = 400;
|
||||
public static final int SIGN_ERROR = 401;
|
||||
public static final int TIMESTAMP_ERROR = 402;
|
||||
public static final int DUPLICATE_ORDER = 403;
|
||||
public static final int AMOUNT_ERROR = 404;
|
||||
public static final int SYSTEM_ERROR = 500;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.wendao.exception;
|
||||
|
||||
import com.wendao.model.ResponseVO;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
|
||||
|
||||
@ExceptionHandler(ApiException.class)
|
||||
public ResponseVO<?> handleApiException(ApiException e) {
|
||||
log.warn("API异常 - 错误码: {}, 错误信息: {}", e.getCode(), e.getMessage());
|
||||
return ResponseVO.builder()
|
||||
.code(e.getCode())
|
||||
.msg(e.getMessage())
|
||||
.build();
|
||||
}
|
||||
|
||||
@ExceptionHandler(IllegalArgumentException.class)
|
||||
public ResponseVO<?> handleIllegalArgumentException(IllegalArgumentException e) {
|
||||
log.warn("参数异常: {}", e.getMessage());
|
||||
return ResponseVO.builder()
|
||||
.code(ApiException.ErrorCode.INVALID_PARAM)
|
||||
.msg(e.getMessage())
|
||||
.build();
|
||||
}
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
public ResponseVO<?> handleException(Exception e) {
|
||||
log.error("系统异常", e);
|
||||
return ResponseVO.builder()
|
||||
.code(ApiException.ErrorCode.SYSTEM_ERROR)
|
||||
.msg("系统内部异常")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
38
src/main/java/com/wendao/model/req/OpenApiOrderReq.java
Normal file
38
src/main/java/com/wendao/model/req/OpenApiOrderReq.java
Normal file
@@ -0,0 +1,38 @@
|
||||
package com.wendao.model.req;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Data
|
||||
public class OpenApiOrderReq implements Serializable {
|
||||
|
||||
@NotBlank(message = "订单号不能为空")
|
||||
private String orderNo;
|
||||
|
||||
@NotNull(message = "金额不能为空")
|
||||
private BigDecimal amount;
|
||||
|
||||
@NotBlank(message = "支付方式不能为空")
|
||||
private String payType;
|
||||
|
||||
private String nickName;
|
||||
|
||||
private String description;
|
||||
|
||||
private String email;
|
||||
|
||||
private String notifyUrl;
|
||||
|
||||
private String userId;
|
||||
|
||||
private Long productId;
|
||||
|
||||
@NotBlank(message = "签名不能为空")
|
||||
private String sign;
|
||||
|
||||
private Long timestamp;
|
||||
}
|
||||
32
src/main/java/com/wendao/model/resp/OpenApiOrderResp.java
Normal file
32
src/main/java/com/wendao/model/resp/OpenApiOrderResp.java
Normal file
@@ -0,0 +1,32 @@
|
||||
package com.wendao.model.resp;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class OpenApiOrderResp implements Serializable {
|
||||
|
||||
private String orderId;
|
||||
|
||||
private String orderNo;
|
||||
|
||||
private BigDecimal amount;
|
||||
|
||||
private String payType;
|
||||
|
||||
private String payNum;
|
||||
|
||||
private Integer state;
|
||||
|
||||
private String message;
|
||||
|
||||
private Long timestamp;
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.wendao.service;
|
||||
|
||||
import com.wendao.entity.Order;
|
||||
import com.wendao.entity.PayChatMessage;
|
||||
import com.wendao.model.req.OpenApiOrderReq;
|
||||
import com.wendao.model.resp.CountResp;
|
||||
import com.wendao.model.ResponseVO;
|
||||
import com.wendao.dto.WeChatMsgDTO;
|
||||
@@ -9,6 +10,8 @@ import com.wendao.model.req.GetOrderListReq;
|
||||
import com.wendao.model.req.OrderReq;
|
||||
import com.wendao.model.resp.AddOrderResp;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.wendao.model.resp.OpenApiOrderResp;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
@@ -71,7 +74,6 @@ public interface OrderService {
|
||||
|
||||
Order getByPayNum(String desc, Date time);
|
||||
|
||||
void autoPass(WeChatMsgDTO dto);
|
||||
OpenApiOrderResp createOpenApiOrder(OpenApiOrderReq req);
|
||||
|
||||
void autoPass(PayChatMessage dto);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.wendao.config.PayProConfig;
|
||||
import com.wendao.entity.AutoPassPay;
|
||||
import com.wendao.entity.Order;
|
||||
import com.wendao.entity.PayChatMessage;
|
||||
import com.wendao.exception.ApiException;
|
||||
import com.wendao.mapper.AutoPassPayMapper;
|
||||
import com.wendao.mapper.OrderMapper;
|
||||
import com.wendao.mapper.PayChatMessageMapper;
|
||||
@@ -12,8 +13,10 @@ import com.wendao.dto.MsgContentsDTO;
|
||||
import com.wendao.dto.WeChatMsgDTO;
|
||||
import com.wendao.enums.OrderStatesEnum;
|
||||
import com.wendao.model.req.GetOrderListReq;
|
||||
import com.wendao.model.req.OpenApiOrderReq;
|
||||
import com.wendao.model.resp.AddOrderResp;
|
||||
import com.wendao.model.resp.CountResp;
|
||||
import com.wendao.model.resp.OpenApiOrderResp;
|
||||
import com.wendao.service.OrderService;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.lang.Snowflake;
|
||||
@@ -27,6 +30,7 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.wendao.utils.OpenApiSignUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
@@ -36,6 +40,7 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@@ -74,6 +79,9 @@ public class OrderServiceImpl implements OrderService {
|
||||
@Autowired
|
||||
PayChatMessageMapper payChatMessageMapper;
|
||||
|
||||
@Autowired
|
||||
OpenApiSignUtil openApiSignUtil;
|
||||
|
||||
@Override
|
||||
public Order getOrderById(String id) {
|
||||
Order byId = orderMapper.selectById(id);
|
||||
@@ -291,64 +299,67 @@ public class OrderServiceImpl implements OrderService {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void autoPass(WeChatMsgDTO dto) {
|
||||
|
||||
QueryWrapper<AutoPassPay> autoPassPayQueryWrapper = new QueryWrapper<>();
|
||||
autoPassPayQueryWrapper.lambda().eq(AutoPassPay::getMessageId, dto.getId());
|
||||
Long l = autoPassPayMapper.selectCount(autoPassPayQueryWrapper);
|
||||
if (l > 0) {
|
||||
return;
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public OpenApiOrderResp createOpenApiOrder(OpenApiOrderReq req) {
|
||||
if (!openApiSignUtil.verifyTimestamp(req.getTimestamp())) {
|
||||
throw new ApiException(ApiException.ErrorCode.TIMESTAMP_ERROR, "请求时间戳无效或已过期");
|
||||
}
|
||||
|
||||
String desc = dto.getContents().extractRemark();
|
||||
Order byPayNum = null;
|
||||
if (StringUtils.isNotBlank(desc)) {
|
||||
/** 找到匹配的订单*/
|
||||
byPayNum = thisService.getByPayNum(desc,dto.getTime());
|
||||
thisService.pass(byPayNum.getId());
|
||||
if (!openApiSignUtil.verifySign(req)) {
|
||||
throw new ApiException(ApiException.ErrorCode.SIGN_ERROR, "签名验证失败");
|
||||
}
|
||||
|
||||
AutoPassPay autoPassPay = new AutoPassPay();
|
||||
autoPassPay.setId(snowflake.nextId());
|
||||
if (byPayNum != null) {
|
||||
autoPassPay.setOrderId(byPayNum.getId());
|
||||
if (req.getAmount() == null || req.getAmount().compareTo(new BigDecimal("0")) <= 0) {
|
||||
throw new ApiException(ApiException.ErrorCode.AMOUNT_ERROR, "金额必须大于0");
|
||||
}
|
||||
autoPassPay.setMessageCreateTime(dto.getTime());
|
||||
autoPassPay.setMessageId(dto.getId());
|
||||
autoPassPay.setMessageDesc(desc);
|
||||
autoPassPayMapper.insert(autoPassPay);
|
||||
|
||||
if (req.getAmount().compareTo(new BigDecimal("100000")) > 0) {
|
||||
throw new ApiException(ApiException.ErrorCode.AMOUNT_ERROR, "金额超出限制,单笔订单不能超过100000元");
|
||||
}
|
||||
|
||||
Order existingOrder = orderMapper.selectById(req.getOrderNo());
|
||||
if (existingOrder != null) {
|
||||
throw new ApiException(ApiException.ErrorCode.DUPLICATE_ORDER, "订单号已存在");
|
||||
}
|
||||
|
||||
Order order = new Order();
|
||||
order.setId(req.getOrderNo());
|
||||
order.setMoney(req.getAmount());
|
||||
order.setPayType(req.getPayType());
|
||||
order.setNickName(req.getNickName());
|
||||
order.setEmail(req.getEmail());
|
||||
order.setNotifyUrl(req.getNotifyUrl());
|
||||
order.setUserId(req.getUserId());
|
||||
order.setProductId(req.getProductId());
|
||||
order.setOrderSource("OPENAPI");
|
||||
order.setState(OrderStatesEnum.WAIT_PAY.getState());
|
||||
order.setCreateTime(new Date());
|
||||
order.setPayNum(StringUtils.getRandomNum());
|
||||
|
||||
if (req.getProductId() != null) {
|
||||
int i = new Random().nextInt(payProConfig.getQrCodeNum()) + 1;
|
||||
order.setPayQrNum(i);
|
||||
}
|
||||
|
||||
try {
|
||||
orderMapper.insert(order);
|
||||
} catch (Exception e) {
|
||||
log.error("创建OpenApi订单失败: {}", e.getMessage(), e);
|
||||
throw new ApiException(ApiException.ErrorCode.SYSTEM_ERROR, "创建订单失败");
|
||||
}
|
||||
|
||||
return OpenApiOrderResp.builder()
|
||||
.orderId(order.getId())
|
||||
.orderNo(req.getOrderNo())
|
||||
.amount(req.getAmount())
|
||||
.payType(req.getPayType())
|
||||
.payNum(order.getPayNum())
|
||||
.state(order.getState())
|
||||
.message("订单创建成功")
|
||||
.timestamp(System.currentTimeMillis())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void autoPass(PayChatMessage dto) {
|
||||
|
||||
String desc = null;
|
||||
if (dto.getPlatformType().equals("weixin")) {
|
||||
MsgContentsDTO bean = JSONUtil.toBean(dto.getContents(), MsgContentsDTO.class);
|
||||
desc = bean.extractRemark();
|
||||
if (StringUtils.isBlank(desc)) {
|
||||
dto.setProcessStatus(2);
|
||||
payChatMessageMapper.updateById(dto);
|
||||
log.info("处理:{}.没有提取到备注" + JSONUtil.toJsonStr(dto));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Order byPayNum = null;
|
||||
if (StringUtils.isNotBlank(desc)) {
|
||||
/** 找到匹配的订单*/
|
||||
byPayNum = thisService.getByPayNum(desc,dto.getTime());
|
||||
if (byPayNum != null){
|
||||
thisService.pass(byPayNum.getId());
|
||||
dto.setOrderId(byPayNum.getId());
|
||||
dto.setProcessStatus(1);
|
||||
payChatMessageMapper.updateById(dto);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 拼接管理员链接
|
||||
|
||||
112
src/main/java/com/wendao/utils/OpenApiSignUtil.java
Normal file
112
src/main/java/com/wendao/utils/OpenApiSignUtil.java
Normal file
@@ -0,0 +1,112 @@
|
||||
package com.wendao.utils;
|
||||
|
||||
import cn.hutool.crypto.SecureUtil;
|
||||
import cn.hutool.crypto.digest.DigestUtil;
|
||||
import com.wendao.model.req.OpenApiOrderReq;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
|
||||
@Component
|
||||
public class OpenApiSignUtil {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(OpenApiSignUtil.class);
|
||||
|
||||
@Value("${paypro.openapi.secret:default_secret_key}")
|
||||
private String secretKey;
|
||||
|
||||
public String generateSign(Map<String, Object> params) {
|
||||
List<String> keys = new ArrayList<>(params.keySet());
|
||||
Collections.sort(keys);
|
||||
|
||||
log.info("开始生成签名,参数数量: {}", keys.size());
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String key : keys) {
|
||||
Object value = params.get(key);
|
||||
if (value != null && !"".equals(value) && !"sign".equals(key)) {
|
||||
// 对BigDecimal进行特殊处理,保持与前端一致的格式
|
||||
String valueStr;
|
||||
if (value instanceof BigDecimal) {
|
||||
valueStr = ((BigDecimal) value).setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString();
|
||||
} else {
|
||||
valueStr = value.toString();
|
||||
}
|
||||
// 检查 valueStr 是否包含空格
|
||||
log.info("处理参数: key={}, value类型={}, value值=[{}], 包含空格={}",
|
||||
key, value.getClass().getSimpleName(), valueStr, valueStr.contains(" "));
|
||||
String paramPart = key + "=" + valueStr + "&";
|
||||
log.info("添加参数: key={}, value={}, paramPart={}", key, value, paramPart);
|
||||
sb.append(paramPart);
|
||||
}
|
||||
}
|
||||
sb.append("key=").append(secretKey);
|
||||
|
||||
String signStr = sb.toString();
|
||||
log.info("服务端签名字符串: {}", signStr);
|
||||
log.info("服务端使用的密钥: {}", secretKey);
|
||||
|
||||
return SecureUtil.md5(signStr).toUpperCase();
|
||||
}
|
||||
|
||||
public boolean verifySign(OpenApiOrderReq req) {
|
||||
Map<String, Object> params = new TreeMap<>();
|
||||
params.put("orderNo", req.getOrderNo());
|
||||
params.put("amount", req.getAmount());
|
||||
params.put("payType", req.getPayType());
|
||||
params.put("nickName", req.getNickName());
|
||||
params.put("description", req.getDescription());
|
||||
params.put("email", req.getEmail());
|
||||
params.put("notifyUrl", req.getNotifyUrl());
|
||||
params.put("userId", req.getUserId());
|
||||
params.put("productId", req.getProductId());
|
||||
params.put("timestamp", req.getTimestamp());
|
||||
|
||||
log.info("服务端接收到的参数: orderNo={}, amount={}, payType={}, nickName={}, description={}, email={}, notifyUrl={}, userId={}, productId={}, timestamp={}",
|
||||
req.getOrderNo(), req.getAmount(), req.getPayType(), req.getNickName(),
|
||||
req.getDescription(), req.getEmail(), req.getNotifyUrl(), req.getUserId(),
|
||||
req.getProductId(), req.getTimestamp());
|
||||
log.info("服务端接收到的签名: {}", req.getSign());
|
||||
|
||||
// 检查 notifyUrl 是否包含空格
|
||||
if (req.getNotifyUrl() != null) {
|
||||
String notifyUrl = req.getNotifyUrl();
|
||||
log.info("notifyUrl 原始值: [{}]", notifyUrl);
|
||||
log.info("notifyUrl 长度: {}, 包含空格: {}", notifyUrl.length(), notifyUrl.contains(" "));
|
||||
log.info("notifyUrl 字节数组: {}", notifyUrl.getBytes());
|
||||
}
|
||||
|
||||
String calculatedSign = generateSign(params);
|
||||
boolean isValid = calculatedSign.equals(req.getSign());
|
||||
|
||||
if (!isValid) {
|
||||
log.warn("签名验证失败 - 期望: {}, 实际: {}", calculatedSign, req.getSign());
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
public boolean verifyTimestamp(Long timestamp) {
|
||||
if (timestamp == null) {
|
||||
return false;
|
||||
}
|
||||
long currentTime = System.currentTimeMillis();
|
||||
long diff = Math.abs(currentTime - timestamp);
|
||||
long maxDiff = 5 * 60 * 1000;
|
||||
|
||||
if (diff > maxDiff) {
|
||||
log.warn("时间戳验证失败 - 当前: {}, 请求: {}, 差值: {}ms", currentTime, timestamp, diff);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
System.out.println(DigestUtil.md5Hex("amount=10.00&description=1&email=test@example.com&nickName=1"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user