微信支付
parent
8c34ebb528
commit
99c86f5d2e
|
|
@ -0,0 +1,295 @@
|
|||
# PaymentController 接口文档
|
||||
|
||||
## 概述
|
||||
PaymentController 是支付相关的控制器,提供订单支付、微信支付、退款等功能。
|
||||
|
||||
## 基础信息
|
||||
- **基础路径**: `/payment`
|
||||
- **控制器**: `PaymentController`
|
||||
- **包路径**: `com.njzscloud.supervisory.order.controller`
|
||||
|
||||
---
|
||||
|
||||
## 接口列表
|
||||
|
||||
### 1. 发起支付
|
||||
**接口路径**: `POST /payment/pay`
|
||||
|
||||
**功能描述**: 发起订单支付,支持公司支付和微信支付两种方式
|
||||
|
||||
**请求参数**:
|
||||
```json
|
||||
{
|
||||
"orderId": 123456,
|
||||
"paymentCategory": "company|wx",
|
||||
"settleTotalMoney": 100.00,
|
||||
"items": [
|
||||
{
|
||||
"id": 1,
|
||||
"expenseItemName": "运费",
|
||||
"settleMoney": 50.00
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**参数说明**:
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| orderId | Long | 是 | 订单ID |
|
||||
| paymentCategory | String | 是 | 支付方式:company(公司支付)、wx(微信支付) |
|
||||
| settleTotalMoney | BigDecimal | 是 | 结算总金额 |
|
||||
| items | Array | 是 | 支付清单项 |
|
||||
| items[].id | Long | 是 | 费用项ID |
|
||||
| items[].expenseItemName | String | 是 | 费用项名称 |
|
||||
| items[].settleMoney | BigDecimal | 是 | 结算金额 |
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"success": true,
|
||||
"msg": "成功",
|
||||
"data": {
|
||||
"prepayId": "wx123456789",
|
||||
"timeStamp": "1234567890",
|
||||
"nonceStr": "abc123",
|
||||
"package": "prepay_id=wx123456789",
|
||||
"signType": "RSA",
|
||||
"paySign": "signature"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**错误码**:
|
||||
- 参数不完整或支付总金额小于0
|
||||
- 支付清单项不合法
|
||||
- 支付方式不合法
|
||||
- 订单不存在
|
||||
- 公司账户余额不足
|
||||
|
||||
---
|
||||
|
||||
### 2. 微信支付回调
|
||||
**接口路径**: `POST /payment/wechat/callback`
|
||||
|
||||
**功能描述**: 微信支付结果回调接口
|
||||
|
||||
**请求参数**:
|
||||
```json
|
||||
{
|
||||
"id": "event_id",
|
||||
"createTime": "2023-01-01T00:00:00+08:00",
|
||||
"eventType": "TRANSACTION.SUCCESS",
|
||||
"resource": {
|
||||
"originalType": "transaction",
|
||||
"ciphertext": "encrypted_data"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**响应示例**:
|
||||
```
|
||||
SUCCESS
|
||||
```
|
||||
|
||||
**说明**: 此接口由微信支付平台调用,用于通知支付结果
|
||||
|
||||
---
|
||||
|
||||
### 3. 查询微信支付订单状态
|
||||
**接口路径**: `GET /payment/wechat/query/{outTradeNo}`
|
||||
|
||||
**功能描述**: 查询微信支付订单的支付状态
|
||||
|
||||
**路径参数**:
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| outTradeNo | String | 是 | 商户订单号 |
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"success": true,
|
||||
"msg": "成功",
|
||||
"data": {
|
||||
"tradeState": "SUCCESS",
|
||||
"tradeStateDesc": "支付成功",
|
||||
"transactionId": "wx123456789",
|
||||
"outTradeNo": "ORDER_123_1234567890"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. 申请微信支付退款
|
||||
**接口路径**: `POST /payment/wechat/refund`
|
||||
|
||||
**功能描述**: 申请微信支付退款
|
||||
|
||||
**请求参数**:
|
||||
```json
|
||||
{
|
||||
"outTradeNo": "ORDER_123_1234567890",
|
||||
"outRefundNo": "REFUND_123_1234567890",
|
||||
"total": 10000,
|
||||
"refund": 5000,
|
||||
"reason": "用户申请退款"
|
||||
}
|
||||
```
|
||||
|
||||
**参数说明**:
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| outTradeNo | String | 是 | 原商户订单号 |
|
||||
| outRefundNo | String | 是 | 商户退款单号 |
|
||||
| total | Integer | 是 | 原订单金额(分) |
|
||||
| refund | Integer | 是 | 退款金额(分) |
|
||||
| reason | String | 否 | 退款原因 |
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"success": true,
|
||||
"msg": "成功",
|
||||
"data": {
|
||||
"refundId": "wx_refund_123456789",
|
||||
"outRefundNo": "REFUND_123_1234567890",
|
||||
"status": "SUCCESS"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5. 绑定微信账号信息
|
||||
**接口路径**: `POST /payment/wechat/bind`
|
||||
|
||||
**功能描述**: 绑定用户微信账号信息,获取openId和unionId
|
||||
|
||||
**请求参数**:
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| userId | Long | 是 | 用户ID |
|
||||
| wxCode | String | 是 | 微信授权码 |
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"success": true,
|
||||
"msg": "微信账号绑定成功",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
**错误处理**:
|
||||
- 用户不存在
|
||||
- 微信登录失败
|
||||
|
||||
---
|
||||
|
||||
## 数据模型
|
||||
|
||||
### PaymentParam (支付参数)
|
||||
```java
|
||||
public class PaymentParam {
|
||||
private Long orderId; // 订单ID
|
||||
private String paymentCategory; // 支付方式
|
||||
private BigDecimal settleTotalMoney; // 结算总金额
|
||||
private List<PaymentItemParam> items; // 支付清单
|
||||
}
|
||||
```
|
||||
|
||||
### PaymentItemParam (支付清单项)
|
||||
```java
|
||||
public class PaymentItemParam {
|
||||
private Long id; // 费用项ID
|
||||
private String expenseItemName; // 费用项名称
|
||||
private BigDecimal settleMoney; // 结算金额
|
||||
}
|
||||
```
|
||||
|
||||
### WechatPayCallbackDto (微信支付回调)
|
||||
```java
|
||||
public class WechatPayCallbackDto {
|
||||
private String id; // 事件ID
|
||||
private String createTime; // 创建时间
|
||||
private String eventType; // 事件类型
|
||||
private Resource resource; // 资源信息
|
||||
}
|
||||
```
|
||||
|
||||
### WechatPayRefundDto (微信退款参数)
|
||||
```java
|
||||
public class WechatPayRefundDto {
|
||||
private String outTradeNo; // 商户订单号
|
||||
private String outRefundNo; // 商户退款单号
|
||||
private Integer total; // 原订单金额(分)
|
||||
private Integer refund; // 退款金额(分)
|
||||
private String reason; // 退款原因
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 业务规则
|
||||
|
||||
### 支付流程
|
||||
1. **参数校验**: 验证支付参数完整性
|
||||
2. **金额校验**: 验证支付金额与数据库一致
|
||||
3. **支付处理**:
|
||||
- 公司支付:直接扣减公司账户余额
|
||||
- 微信支付:创建微信支付订单
|
||||
4. **状态更新**: 更新订单支付状态
|
||||
|
||||
### 公司支付规则
|
||||
- 需要验证公司账户余额是否充足
|
||||
- 直接扣减公司账户余额
|
||||
- 记录资金变动明细
|
||||
- 支付成功后订单状态变更为已完成
|
||||
|
||||
### 微信支付规则
|
||||
- 创建微信支付订单
|
||||
- 订单状态变更为待支付
|
||||
- 等待微信支付回调确认支付结果
|
||||
- 支付成功后订单状态变更为已完成
|
||||
|
||||
### 退款规则
|
||||
- 只能对已支付的订单申请退款
|
||||
- 退款成功后订单状态变更为已退款
|
||||
- 支持部分退款
|
||||
|
||||
---
|
||||
|
||||
## 错误码说明
|
||||
|
||||
| 错误码 | 说明 |
|
||||
|--------|------|
|
||||
| 0 | 成功 |
|
||||
| 11111 | 系统异常 |
|
||||
| 40001 | 参数错误 |
|
||||
| 40002 | 业务异常 |
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **事务处理**: 支付接口使用`@Transactional`注解确保数据一致性
|
||||
2. **金额精度**: 所有金额计算使用`BigDecimal`类型
|
||||
3. **微信支付**: 金额需要转换为分(乘以100)
|
||||
4. **回调验证**: 微信支付回调需要验证签名
|
||||
5. **幂等性**: 支付接口需要保证幂等性,避免重复支付
|
||||
6. **日志记录**: 关键操作都有详细的日志记录
|
||||
|
||||
---
|
||||
|
||||
## 更新日志
|
||||
|
||||
| 版本 | 日期 | 更新内容 |
|
||||
|------|------|----------|
|
||||
| 1.0.0 | 2024-01-01 | 初始版本,包含基础支付功能 |
|
||||
| 1.1.0 | 2024-01-15 | 新增微信账号绑定功能 |
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package com.njzscloud.supervisory.order.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
/**
|
||||
* RestTemplate配置类
|
||||
*/
|
||||
@Configuration
|
||||
public class RestTemplateConfig {
|
||||
|
||||
@Bean
|
||||
public RestTemplate restTemplate() {
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
restTemplate.setRequestFactory(clientHttpRequestFactory());
|
||||
return restTemplate;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ClientHttpRequestFactory clientHttpRequestFactory() {
|
||||
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
|
||||
factory.setConnectTimeout(10000); // 连接超时10秒
|
||||
factory.setReadTimeout(30000); // 读取超时30秒
|
||||
return factory;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
package com.njzscloud.supervisory.order.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 微信支付配置
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "wechat.pay")
|
||||
public class WechatPayConfig {
|
||||
|
||||
/**
|
||||
* 子商户APPID
|
||||
*/
|
||||
private String subAppId;
|
||||
|
||||
/**
|
||||
* 子商户号
|
||||
*/
|
||||
private String subMchId;
|
||||
|
||||
/**
|
||||
* API密钥
|
||||
*/
|
||||
private String apiKey;
|
||||
|
||||
/**
|
||||
* 证书序列号
|
||||
*/
|
||||
private String certSerialNo;
|
||||
|
||||
/**
|
||||
* 私钥文件路径
|
||||
*/
|
||||
private String privateKeyPath;
|
||||
|
||||
/**
|
||||
* 支付回调地址
|
||||
*/
|
||||
private String notifyUrl;
|
||||
|
||||
/**
|
||||
* 退款回调地址
|
||||
*/
|
||||
private String refundNotifyUrl;
|
||||
}
|
||||
|
|
@ -1,28 +1,39 @@
|
|||
package com.njzscloud.supervisory.order.controller;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.njzscloud.common.core.ex.ExceptionMsg;
|
||||
import com.njzscloud.common.core.ex.Exceptions;
|
||||
import com.njzscloud.common.core.utils.R;
|
||||
import com.njzscloud.common.security.ex.UserLoginException;
|
||||
import com.njzscloud.common.wechat.WechatUtil;
|
||||
import com.njzscloud.common.wechat.param.Code2SessionParam;
|
||||
import com.njzscloud.common.wechat.result.Code2SessionResult;
|
||||
import com.njzscloud.supervisory.money.contant.MoneyChangeCategory;
|
||||
import com.njzscloud.supervisory.money.pojo.entity.MoneyAccountEntity;
|
||||
import com.njzscloud.supervisory.money.pojo.entity.MoneyChangeDetailEntity;
|
||||
import com.njzscloud.supervisory.money.service.MoneyAccountService;
|
||||
import com.njzscloud.supervisory.money.service.MoneyChangeDetailService;
|
||||
import com.njzscloud.supervisory.order.contant.OrderStatus;
|
||||
import com.njzscloud.supervisory.order.contant.PaymentStatus;
|
||||
import com.njzscloud.supervisory.order.contant.PaymentWay;
|
||||
import com.njzscloud.supervisory.order.pojo.dto.*;
|
||||
import com.njzscloud.supervisory.order.pojo.entity.OrderExpenseItemsEntity;
|
||||
import com.njzscloud.supervisory.order.pojo.entity.OrderInfoEntity;
|
||||
import com.njzscloud.supervisory.order.pojo.param.PaymentItemParam;
|
||||
import com.njzscloud.supervisory.order.pojo.param.PaymentParam;
|
||||
import com.njzscloud.supervisory.order.pojo.result.PaymentContextResult;
|
||||
import com.njzscloud.supervisory.order.service.OrderExpenseItemsService;
|
||||
import com.njzscloud.supervisory.order.service.OrderInfoService;
|
||||
import com.njzscloud.supervisory.order.service.WechatPayService;
|
||||
import com.njzscloud.supervisory.sys.user.pojo.entity.UserAccountEntity;
|
||||
import com.njzscloud.supervisory.sys.user.service.UserAccountService;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
|
|
@ -39,6 +50,8 @@ public class PaymentController {
|
|||
private final OrderInfoService orderInfoService;
|
||||
private final MoneyAccountService moneyAccountService;
|
||||
private final MoneyChangeDetailService moneyChangeDetailService;
|
||||
private final WechatPayService wechatPayService;
|
||||
private final UserAccountService userAccountService;
|
||||
|
||||
|
||||
/**
|
||||
|
|
@ -123,20 +136,43 @@ public class PaymentController {
|
|||
// 直接扣减公司账户余额
|
||||
deductCompanyBalance(ctx, dbTotalAmount, paymentParam.getOrderId());
|
||||
} else if (PaymentWay.WX.getVal().equals(paymentParam.getPaymentCategory())) {
|
||||
// 微信支付:当前不接入,模拟失败以触发回滚
|
||||
throw Exceptions.clierr("微信支付失败(占位):已回滚之前扣款");
|
||||
// 微信支付:创建微信支付订单
|
||||
WechatPayJsapiOrderDto orderDto = new WechatPayJsapiOrderDto();
|
||||
orderDto.setOutTradeNo("ORDER_" + paymentParam.getOrderId() + "_" + System.currentTimeMillis());
|
||||
orderDto.setDescription("订单支付-" + paymentParam.getOrderId());
|
||||
orderDto.setTotal(dbTotalAmount.multiply(new BigDecimal("100")).longValue()); // 转换为分
|
||||
// TODO: 需要从上下文中获取用户openid,暂时使用固定值
|
||||
orderDto.setOpenid("test_openid");
|
||||
|
||||
WechatPayJsapiOrderResponseDto response = wechatPayService.createMiniProgramOrder(orderDto);
|
||||
|
||||
// 更新订单状态为待支付
|
||||
orderInfoService.lambdaUpdate()
|
||||
.eq(OrderInfoEntity::getId, paymentParam.getOrderId())
|
||||
.set(OrderInfoEntity::getPaymentStatus, PaymentStatus.WeiZhiFu)
|
||||
.set(OrderInfoEntity::getPaymentCategory, paymentParam.getPaymentCategory())
|
||||
.set(OrderInfoEntity::getSn, orderDto.getOutTradeNo())
|
||||
.update();
|
||||
|
||||
log.info("微信支付订单创建成功,订单ID:{},微信订单号:{}", paymentParam.getOrderId(), orderDto.getOutTradeNo());
|
||||
|
||||
return R.success(response);
|
||||
} else {
|
||||
throw Exceptions.clierr("不支持的支付方式");
|
||||
}
|
||||
|
||||
// 3. 更新订单支付状态为已支付
|
||||
orderInfoService.lambdaUpdate()
|
||||
.eq(com.njzscloud.supervisory.order.pojo.entity.OrderInfoEntity::getId, paymentParam.getOrderId())
|
||||
.set(com.njzscloud.supervisory.order.pojo.entity.OrderInfoEntity::getPaymentStatus, PaymentStatus.YiZhiFu)
|
||||
.update();
|
||||
// 3. 更新订单支付状态为已支付(仅公司支付需要)
|
||||
if (PaymentWay.COMPANY.getVal().equals(paymentParam.getPaymentCategory())) {
|
||||
orderInfoService.lambdaUpdate()
|
||||
.eq(OrderInfoEntity::getId, paymentParam.getOrderId())
|
||||
.set(OrderInfoEntity::getPaymentStatus, PaymentStatus.YiZhiFu)
|
||||
.set(OrderInfoEntity::getOrderStatus, OrderStatus.YiWanCheng.getVal())
|
||||
.set(OrderInfoEntity::getPaymentCategory, paymentParam.getPaymentCategory())
|
||||
.update();
|
||||
|
||||
log.info("订单支付成功,订单ID:{},支付方式:{},支付金额:{}",
|
||||
paymentParam.getOrderId(), paymentParam.getPaymentCategory(), dbTotalAmount);
|
||||
log.info("订单支付成功,订单ID:{},支付方式:{},支付金额:{}",
|
||||
paymentParam.getOrderId(), paymentParam.getPaymentCategory(), dbTotalAmount);
|
||||
}
|
||||
|
||||
return R.success();
|
||||
}
|
||||
|
|
@ -176,6 +212,139 @@ public class PaymentController {
|
|||
ctx.getCompanyUserId(), amount, oldBalance, newBalance);
|
||||
}
|
||||
|
||||
/**
|
||||
* 微信支付回调接口
|
||||
*/
|
||||
@PostMapping("/wechat/callback")
|
||||
public ResponseEntity<String> wechatPayCallback(@RequestBody WechatPayCallbackDto callbackDto) {
|
||||
try {
|
||||
// 验证签名
|
||||
boolean isValid = wechatPayService.verifyCallback(
|
||||
callbackDto.getId(),
|
||||
callbackDto.getCreateTime(),
|
||||
callbackDto.getEventType(),
|
||||
callbackDto.getResource().getCiphertext()
|
||||
);
|
||||
if (!isValid) {
|
||||
log.error("微信支付回调签名验证失败");
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("FAIL");
|
||||
}
|
||||
|
||||
// 处理支付结果
|
||||
if ("SUCCESS".equals(callbackDto.getResource().getOriginalType())) {
|
||||
// 支付成功,更新订单状态
|
||||
String outTradeNo = callbackDto.getResource().getCiphertext(); // 需要解密获取
|
||||
Long orderId = extractOrderIdFromOutTradeNo(outTradeNo);
|
||||
|
||||
if (orderId != null) {
|
||||
orderInfoService.lambdaUpdate()
|
||||
.eq(OrderInfoEntity::getId, orderId)
|
||||
.set(OrderInfoEntity::getPaymentStatus, PaymentStatus.YiZhiFu)
|
||||
.set(OrderInfoEntity::getOrderStatus, OrderStatus.YiWanCheng.getVal())
|
||||
.set(OrderInfoEntity::getSn, callbackDto.getResource().getCiphertext()) // 需要解密获取
|
||||
.update();
|
||||
|
||||
log.info("微信支付成功,订单ID:{},微信交易号:{}", orderId, callbackDto.getResource().getCiphertext());
|
||||
}
|
||||
}
|
||||
|
||||
return ResponseEntity.ok("SUCCESS");
|
||||
} catch (Exception e) {
|
||||
log.error("处理微信支付回调异常", e);
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("FAIL");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询微信支付订单状态
|
||||
*/
|
||||
@GetMapping("/wechat/query/{outTradeNo}")
|
||||
public R<?> queryWechatPayOrder(@PathVariable String outTradeNo) {
|
||||
try {
|
||||
WechatPayOrderQueryResponseDto result = wechatPayService.queryOrder(outTradeNo);
|
||||
return R.success(result);
|
||||
} catch (Exception e) {
|
||||
log.error("查询微信支付订单异常,订单号:{}", outTradeNo, e);
|
||||
return R.failed();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 申请微信支付退款
|
||||
*/
|
||||
@PostMapping("/wechat/refund")
|
||||
public R<?> wechatPayRefund(@RequestBody WechatPayRefundDto refundDto) {
|
||||
try {
|
||||
WechatPayRefundResponseDto result = wechatPayService.refund(refundDto);
|
||||
|
||||
// 更新订单退款状态
|
||||
if ("SUCCESS".equals(result.getStatus())) {
|
||||
orderInfoService.lambdaUpdate()
|
||||
.eq(OrderInfoEntity::getSn, refundDto.getOutTradeNo())
|
||||
.set(OrderInfoEntity::getPaymentStatus, PaymentStatus.YiTuiKuan)
|
||||
.update();
|
||||
|
||||
log.info("微信支付退款成功,订单号:{},退款单号:{}", refundDto.getOutTradeNo(), result.getRefundId());
|
||||
}
|
||||
|
||||
return R.success(result);
|
||||
} catch (Exception e) {
|
||||
log.error("微信支付退款异常,订单号:{}", refundDto.getOutTradeNo(), e);
|
||||
return R.failed();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从商户订单号中提取订单ID
|
||||
*/
|
||||
private Long extractOrderIdFromOutTradeNo(String outTradeNo) {
|
||||
try {
|
||||
if (outTradeNo != null && outTradeNo.startsWith("ORDER_")) {
|
||||
String[] parts = outTradeNo.split("_");
|
||||
if (parts.length >= 2) {
|
||||
return Long.parseLong(parts[1]);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("解析订单号失败:{}", outTradeNo, e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定微信账号信息
|
||||
*/
|
||||
@PostMapping("/wechat/bind")
|
||||
public R<?> bindWechatAccount(@RequestParam Long userId, @RequestParam String wxCode) {
|
||||
// 通过userId查询用户账号信息
|
||||
UserAccountEntity userAccount = userAccountService.getOne(
|
||||
Wrappers.<UserAccountEntity>lambdaQuery()
|
||||
.eq(UserAccountEntity::getUserId, userId)
|
||||
);
|
||||
|
||||
if (userAccount == null) {
|
||||
throw new UserLoginException(ExceptionMsg.CLI_ERR_MSG, "用户不存在");
|
||||
}
|
||||
|
||||
// 调用微信API获取openId和unionId
|
||||
Code2SessionResult code2SessionResult = WechatUtil.code2Session(new Code2SessionParam().setJs_code(wxCode));
|
||||
Integer errcode = code2SessionResult.getErrcode();
|
||||
if (errcode != null && errcode != 0) {
|
||||
log.error("微信登录失败, errcode: {}, errmsg: {}", errcode, code2SessionResult.getErrmsg());
|
||||
throw new UserLoginException(ExceptionMsg.CLI_ERR_MSG, "微信登录失败");
|
||||
}
|
||||
|
||||
String openid = code2SessionResult.getOpenid();
|
||||
String unionid = code2SessionResult.getUnionid();
|
||||
|
||||
// 更新用户账号的微信信息
|
||||
userAccount.setWechatOpenid(openid);
|
||||
userAccount.setWechatUnionid(unionid);
|
||||
userAccountService.updateById(userAccount);
|
||||
|
||||
return R.success("微信账号绑定成功");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,239 @@
|
|||
package com.njzscloud.supervisory.order.pojo.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 微信支付回调通知DTO
|
||||
*/
|
||||
@Data
|
||||
public class WechatPayCallbackDto {
|
||||
|
||||
/**
|
||||
* 通知ID
|
||||
*/
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 通知创建时间
|
||||
*/
|
||||
private String createTime;
|
||||
|
||||
/**
|
||||
* 通知类型
|
||||
*/
|
||||
private String eventType;
|
||||
|
||||
/**
|
||||
* 通知数据类型
|
||||
*/
|
||||
private String resourceType;
|
||||
|
||||
/**
|
||||
* 通知数据
|
||||
*/
|
||||
private ResourceData resource;
|
||||
|
||||
/**
|
||||
* 通知数据
|
||||
*/
|
||||
@Data
|
||||
public static class ResourceData {
|
||||
/**
|
||||
* 加密算法类型
|
||||
*/
|
||||
private String algorithm;
|
||||
|
||||
/**
|
||||
* 数据密文
|
||||
*/
|
||||
private String ciphertext;
|
||||
|
||||
/**
|
||||
* 附加数据
|
||||
*/
|
||||
private String associatedData;
|
||||
|
||||
/**
|
||||
* 原始回调类型
|
||||
*/
|
||||
private String originalType;
|
||||
|
||||
/**
|
||||
* 随机字符串
|
||||
*/
|
||||
private String nonce;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密后的支付信息
|
||||
*/
|
||||
@Data
|
||||
public static class PaymentInfo {
|
||||
/**
|
||||
* 服务商APPID
|
||||
*/
|
||||
private String spAppid;
|
||||
|
||||
/**
|
||||
* 服务商商户号
|
||||
*/
|
||||
private String spMchid;
|
||||
|
||||
/**
|
||||
* 子商户APPID
|
||||
*/
|
||||
private String subAppid;
|
||||
|
||||
/**
|
||||
* 子商户号
|
||||
*/
|
||||
private String subMchid;
|
||||
|
||||
/**
|
||||
* 商户订单号
|
||||
*/
|
||||
private String outTradeNo;
|
||||
|
||||
/**
|
||||
* 微信支付订单号
|
||||
*/
|
||||
private String transactionId;
|
||||
|
||||
/**
|
||||
* 交易类型
|
||||
*/
|
||||
private String tradeType;
|
||||
|
||||
/**
|
||||
* 交易状态
|
||||
*/
|
||||
private String tradeState;
|
||||
|
||||
/**
|
||||
* 交易状态描述
|
||||
*/
|
||||
private String tradeStateDesc;
|
||||
|
||||
/**
|
||||
* 付款银行
|
||||
*/
|
||||
private String bankType;
|
||||
|
||||
/**
|
||||
* 支付完成时间
|
||||
*/
|
||||
private String successTime;
|
||||
|
||||
/**
|
||||
* 支付者信息
|
||||
*/
|
||||
private PayerInfo payer;
|
||||
|
||||
/**
|
||||
* 订单金额信息
|
||||
*/
|
||||
private AmountInfo amount;
|
||||
|
||||
/**
|
||||
* 场景信息
|
||||
*/
|
||||
private SceneInfo sceneInfo;
|
||||
|
||||
/**
|
||||
* 优惠功能
|
||||
*/
|
||||
private PromotionDetail promotionDetail;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class PayerInfo {
|
||||
/**
|
||||
* 用户openid
|
||||
*/
|
||||
private String openid;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class AmountInfo {
|
||||
/**
|
||||
* 订单总金额(分)
|
||||
*/
|
||||
private Long total;
|
||||
|
||||
/**
|
||||
* 用户支付金额(分)
|
||||
*/
|
||||
private Long payerTotal;
|
||||
|
||||
/**
|
||||
* 货币类型
|
||||
*/
|
||||
private String currency;
|
||||
|
||||
/**
|
||||
* 用户支付币种
|
||||
*/
|
||||
private String payerCurrency;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class SceneInfo {
|
||||
/**
|
||||
* 商户端设备号
|
||||
*/
|
||||
private String deviceId;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class PromotionDetail {
|
||||
/**
|
||||
* 券ID
|
||||
*/
|
||||
private String couponId;
|
||||
|
||||
/**
|
||||
* 优惠名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 优惠范围
|
||||
*/
|
||||
private String scope;
|
||||
|
||||
/**
|
||||
* 优惠类型
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 优惠券面额(分)
|
||||
*/
|
||||
private Long amount;
|
||||
|
||||
/**
|
||||
* 活动ID
|
||||
*/
|
||||
private String stockId;
|
||||
|
||||
/**
|
||||
* 微信出资(分)
|
||||
*/
|
||||
private Long wechatpayContribute;
|
||||
|
||||
/**
|
||||
* 商户出资(分)
|
||||
*/
|
||||
private Long merchantContribute;
|
||||
|
||||
/**
|
||||
* 其他出资(分)
|
||||
*/
|
||||
private Long otherContribute;
|
||||
|
||||
/**
|
||||
* 优惠币种
|
||||
*/
|
||||
private String currency;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
package com.njzscloud.supervisory.order.pojo.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Size;
|
||||
|
||||
/**
|
||||
* 微信支付JSAPI/小程序下单请求DTO
|
||||
*/
|
||||
@Data
|
||||
public class WechatPayJsapiOrderDto {
|
||||
|
||||
/**
|
||||
* 商品描述
|
||||
*/
|
||||
@NotBlank(message = "商品描述不能为空")
|
||||
@Size(max = 127, message = "商品描述不能超过127个字符")
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 商户订单号
|
||||
*/
|
||||
@NotBlank(message = "商户订单号不能为空")
|
||||
@Size(min = 6, max = 32, message = "商户订单号长度必须在6-32个字符之间")
|
||||
private String outTradeNo;
|
||||
|
||||
/**
|
||||
* 用户openid
|
||||
*/
|
||||
@NotBlank(message = "用户openid不能为空")
|
||||
private String openid;
|
||||
|
||||
/**
|
||||
* 订单金额(分)
|
||||
*/
|
||||
@NotNull(message = "订单金额不能为空")
|
||||
private Long total;
|
||||
|
||||
/**
|
||||
* 货币类型
|
||||
*/
|
||||
private String currency = "CNY";
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package com.njzscloud.supervisory.order.pojo.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 微信支付JSAPI/小程序下单响应DTO
|
||||
*/
|
||||
@Data
|
||||
public class WechatPayJsapiOrderResponseDto {
|
||||
|
||||
/**
|
||||
* 预支付交易会话标识
|
||||
*/
|
||||
private String prepayId;
|
||||
|
||||
/**
|
||||
* 小程序调起支付参数
|
||||
*/
|
||||
private String timeStamp;
|
||||
|
||||
/**
|
||||
* 小程序调起支付参数
|
||||
*/
|
||||
private String nonceStr;
|
||||
|
||||
/**
|
||||
* 小程序调起支付参数
|
||||
*/
|
||||
private String packageValue;
|
||||
|
||||
/**
|
||||
* 小程序调起支付参数
|
||||
*/
|
||||
private String signType;
|
||||
|
||||
/**
|
||||
* 小程序调起支付参数
|
||||
*/
|
||||
private String paySign;
|
||||
}
|
||||
|
|
@ -0,0 +1,177 @@
|
|||
package com.njzscloud.supervisory.order.pojo.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 微信支付订单查询响应DTO
|
||||
*/
|
||||
@Data
|
||||
public class WechatPayOrderQueryResponseDto {
|
||||
|
||||
/**
|
||||
* 服务商APPID
|
||||
*/
|
||||
private String spAppid;
|
||||
|
||||
/**
|
||||
* 服务商商户号
|
||||
*/
|
||||
private String spMchid;
|
||||
|
||||
/**
|
||||
* 子商户APPID
|
||||
*/
|
||||
private String subAppid;
|
||||
|
||||
/**
|
||||
* 子商户号
|
||||
*/
|
||||
private String subMchid;
|
||||
|
||||
/**
|
||||
* 商户订单号
|
||||
*/
|
||||
private String outTradeNo;
|
||||
|
||||
/**
|
||||
* 微信支付订单号
|
||||
*/
|
||||
private String transactionId;
|
||||
|
||||
/**
|
||||
* 交易类型
|
||||
*/
|
||||
private String tradeType;
|
||||
|
||||
/**
|
||||
* 交易状态
|
||||
*/
|
||||
private String tradeState;
|
||||
|
||||
/**
|
||||
* 交易状态描述
|
||||
*/
|
||||
private String tradeStateDesc;
|
||||
|
||||
/**
|
||||
* 付款银行
|
||||
*/
|
||||
private String bankType;
|
||||
|
||||
/**
|
||||
* 支付完成时间
|
||||
*/
|
||||
private String successTime;
|
||||
|
||||
/**
|
||||
* 支付者信息
|
||||
*/
|
||||
private PayerInfo payer;
|
||||
|
||||
/**
|
||||
* 订单金额信息
|
||||
*/
|
||||
private AmountInfo amount;
|
||||
|
||||
/**
|
||||
* 场景信息
|
||||
*/
|
||||
private SceneInfo sceneInfo;
|
||||
|
||||
/**
|
||||
* 优惠功能
|
||||
*/
|
||||
private PromotionDetail promotionDetail;
|
||||
|
||||
@Data
|
||||
public static class PayerInfo {
|
||||
/**
|
||||
* 用户openid
|
||||
*/
|
||||
private String openid;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class AmountInfo {
|
||||
/**
|
||||
* 订单总金额(分)
|
||||
*/
|
||||
private Long total;
|
||||
|
||||
/**
|
||||
* 用户支付金额(分)
|
||||
*/
|
||||
private Long payerTotal;
|
||||
|
||||
/**
|
||||
* 货币类型
|
||||
*/
|
||||
private String currency;
|
||||
|
||||
/**
|
||||
* 用户支付币种
|
||||
*/
|
||||
private String payerCurrency;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class SceneInfo {
|
||||
/**
|
||||
* 商户端设备号
|
||||
*/
|
||||
private String deviceId;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class PromotionDetail {
|
||||
/**
|
||||
* 券ID
|
||||
*/
|
||||
private String couponId;
|
||||
|
||||
/**
|
||||
* 优惠名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 优惠范围
|
||||
*/
|
||||
private String scope;
|
||||
|
||||
/**
|
||||
* 优惠类型
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 优惠券面额(分)
|
||||
*/
|
||||
private Long amount;
|
||||
|
||||
/**
|
||||
* 活动ID
|
||||
*/
|
||||
private String stockId;
|
||||
|
||||
/**
|
||||
* 微信出资(分)
|
||||
*/
|
||||
private Long wechatpayContribute;
|
||||
|
||||
/**
|
||||
* 商户出资(分)
|
||||
*/
|
||||
private Long merchantContribute;
|
||||
|
||||
/**
|
||||
* 其他出资(分)
|
||||
*/
|
||||
private Long otherContribute;
|
||||
|
||||
/**
|
||||
* 优惠币种
|
||||
*/
|
||||
private String currency;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,213 @@
|
|||
package com.njzscloud.supervisory.order.pojo.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 微信支付退款回调通知DTO
|
||||
*/
|
||||
@Data
|
||||
public class WechatPayRefundCallbackDto {
|
||||
|
||||
/**
|
||||
* 通知ID
|
||||
*/
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 通知创建时间
|
||||
*/
|
||||
private String createTime;
|
||||
|
||||
/**
|
||||
* 通知类型
|
||||
*/
|
||||
private String eventType;
|
||||
|
||||
/**
|
||||
* 通知数据类型
|
||||
*/
|
||||
private String resourceType;
|
||||
|
||||
/**
|
||||
* 通知数据
|
||||
*/
|
||||
private ResourceData resource;
|
||||
|
||||
/**
|
||||
* 通知数据
|
||||
*/
|
||||
@Data
|
||||
public static class ResourceData {
|
||||
/**
|
||||
* 加密算法类型
|
||||
*/
|
||||
private String algorithm;
|
||||
|
||||
/**
|
||||
* 数据密文
|
||||
*/
|
||||
private String ciphertext;
|
||||
|
||||
/**
|
||||
* 附加数据
|
||||
*/
|
||||
private String associatedData;
|
||||
|
||||
/**
|
||||
* 原始回调类型
|
||||
*/
|
||||
private String originalType;
|
||||
|
||||
/**
|
||||
* 随机字符串
|
||||
*/
|
||||
private String nonce;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密后的退款信息
|
||||
*/
|
||||
@Data
|
||||
public static class RefundInfo {
|
||||
/**
|
||||
* 服务商APPID
|
||||
*/
|
||||
private String spAppid;
|
||||
|
||||
/**
|
||||
* 服务商商户号
|
||||
*/
|
||||
private String spMchid;
|
||||
|
||||
/**
|
||||
* 子商户APPID
|
||||
*/
|
||||
private String subAppid;
|
||||
|
||||
/**
|
||||
* 子商户号
|
||||
*/
|
||||
private String subMchid;
|
||||
|
||||
/**
|
||||
* 商户订单号
|
||||
*/
|
||||
private String outTradeNo;
|
||||
|
||||
/**
|
||||
* 微信支付订单号
|
||||
*/
|
||||
private String transactionId;
|
||||
|
||||
/**
|
||||
* 商户退款单号
|
||||
*/
|
||||
private String outRefundNo;
|
||||
|
||||
/**
|
||||
* 微信支付退款单号
|
||||
*/
|
||||
private String refundId;
|
||||
|
||||
/**
|
||||
* 退款状态
|
||||
*/
|
||||
private String refundStatus;
|
||||
|
||||
/**
|
||||
* 退款成功时间
|
||||
*/
|
||||
private String successTime;
|
||||
|
||||
/**
|
||||
* 退款入账账户
|
||||
*/
|
||||
private String userReceivedAccount;
|
||||
|
||||
/**
|
||||
* 退款金额信息
|
||||
*/
|
||||
private RefundAmount amount;
|
||||
|
||||
/**
|
||||
* 优惠退款信息
|
||||
*/
|
||||
private PromotionDetail promotionDetail;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class RefundAmount {
|
||||
/**
|
||||
* 订单金额(分)
|
||||
*/
|
||||
private Long total;
|
||||
|
||||
/**
|
||||
* 退款金额(分)
|
||||
*/
|
||||
private Long refund;
|
||||
|
||||
/**
|
||||
* 用户支付金额(分)
|
||||
*/
|
||||
private Long payerTotal;
|
||||
|
||||
/**
|
||||
* 用户退款金额(分)
|
||||
*/
|
||||
private Long payerRefund;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class PromotionDetail {
|
||||
/**
|
||||
* 券ID
|
||||
*/
|
||||
private String couponId;
|
||||
|
||||
/**
|
||||
* 优惠名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 优惠范围
|
||||
*/
|
||||
private String scope;
|
||||
|
||||
/**
|
||||
* 优惠类型
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 优惠券面额(分)
|
||||
*/
|
||||
private Long amount;
|
||||
|
||||
/**
|
||||
* 活动ID
|
||||
*/
|
||||
private String stockId;
|
||||
|
||||
/**
|
||||
* 微信出资(分)
|
||||
*/
|
||||
private Long wechatpayContribute;
|
||||
|
||||
/**
|
||||
* 商户出资(分)
|
||||
*/
|
||||
private Long merchantContribute;
|
||||
|
||||
/**
|
||||
* 其他出资(分)
|
||||
*/
|
||||
private Long otherContribute;
|
||||
|
||||
/**
|
||||
* 优惠币种
|
||||
*/
|
||||
private String currency;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
package com.njzscloud.supervisory.order.pojo.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Size;
|
||||
|
||||
/**
|
||||
* 微信支付退款请求DTO
|
||||
*/
|
||||
@Data
|
||||
public class WechatPayRefundDto {
|
||||
|
||||
/**
|
||||
* 商户订单号
|
||||
*/
|
||||
@NotBlank(message = "商户订单号不能为空")
|
||||
@Size(max = 32, message = "商户订单号不能超过32个字符")
|
||||
private String outTradeNo;
|
||||
|
||||
/**
|
||||
* 微信支付订单号
|
||||
*/
|
||||
@Size(max = 32, message = "微信支付订单号不能超过32个字符")
|
||||
private String transactionId;
|
||||
|
||||
/**
|
||||
* 商户退款单号
|
||||
*/
|
||||
@NotBlank(message = "商户退款单号不能为空")
|
||||
@Size(max = 64, message = "商户退款单号不能超过64个字符")
|
||||
private String outRefundNo;
|
||||
|
||||
/**
|
||||
* 退款原因
|
||||
*/
|
||||
@Size(max = 80, message = "退款原因不能超过80个字符")
|
||||
private String reason;
|
||||
|
||||
/**
|
||||
* 退款金额(分)
|
||||
*/
|
||||
@NotNull(message = "退款金额不能为空")
|
||||
private Long refund;
|
||||
|
||||
/**
|
||||
* 原订单金额(分)
|
||||
*/
|
||||
@NotNull(message = "原订单金额不能为空")
|
||||
private Long total;
|
||||
|
||||
/**
|
||||
* 退款资金来源
|
||||
*/
|
||||
private String fundsAccount;
|
||||
|
||||
/**
|
||||
* 退款结果回调地址
|
||||
*/
|
||||
private String notifyUrl;
|
||||
}
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
package com.njzscloud.supervisory.order.pojo.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 微信支付退款响应DTO
|
||||
*/
|
||||
@Data
|
||||
public class WechatPayRefundResponseDto {
|
||||
|
||||
/**
|
||||
* 微信支付退款单号
|
||||
*/
|
||||
private String refundId;
|
||||
|
||||
/**
|
||||
* 商户退款单号
|
||||
*/
|
||||
private String outRefundNo;
|
||||
|
||||
/**
|
||||
* 退款状态
|
||||
*/
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 退款入账账户
|
||||
*/
|
||||
private String userReceivedAccount;
|
||||
|
||||
/**
|
||||
* 退款成功时间
|
||||
*/
|
||||
private String successTime;
|
||||
|
||||
/**
|
||||
* 退款创建时间
|
||||
*/
|
||||
private String createTime;
|
||||
|
||||
/**
|
||||
* 退款金额信息
|
||||
*/
|
||||
private RefundAmount amount;
|
||||
|
||||
/**
|
||||
* 优惠退款信息
|
||||
*/
|
||||
private PromotionDetail promotionDetail;
|
||||
|
||||
@Data
|
||||
public static class RefundAmount {
|
||||
/**
|
||||
* 订单金额(分)
|
||||
*/
|
||||
private Long total;
|
||||
|
||||
/**
|
||||
* 退款金额(分)
|
||||
*/
|
||||
private Long refund;
|
||||
|
||||
/**
|
||||
* 用户支付金额(分)
|
||||
*/
|
||||
private Long payerTotal;
|
||||
|
||||
/**
|
||||
* 用户退款金额(分)
|
||||
*/
|
||||
private Long payerRefund;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class PromotionDetail {
|
||||
/**
|
||||
* 券ID
|
||||
*/
|
||||
private String couponId;
|
||||
|
||||
/**
|
||||
* 优惠名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 优惠范围
|
||||
*/
|
||||
private String scope;
|
||||
|
||||
/**
|
||||
* 优惠类型
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 优惠券面额(分)
|
||||
*/
|
||||
private Long amount;
|
||||
|
||||
/**
|
||||
* 活动ID
|
||||
*/
|
||||
private String stockId;
|
||||
|
||||
/**
|
||||
* 微信出资(分)
|
||||
*/
|
||||
private Long wechatpayContribute;
|
||||
|
||||
/**
|
||||
* 商户出资(分)
|
||||
*/
|
||||
private Long merchantContribute;
|
||||
|
||||
/**
|
||||
* 其他出资(分)
|
||||
*/
|
||||
private Long otherContribute;
|
||||
|
||||
/**
|
||||
* 优惠币种
|
||||
*/
|
||||
private String currency;
|
||||
}
|
||||
}
|
||||
|
|
@ -202,6 +202,11 @@ public class OrderInfoEntity {
|
|||
*/
|
||||
private String checkerMemo;
|
||||
|
||||
/**
|
||||
* 支付方式
|
||||
*/
|
||||
private String paymentCategory;
|
||||
|
||||
/**
|
||||
* 装车照片
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,75 @@
|
|||
package com.njzscloud.supervisory.order.service;
|
||||
|
||||
import com.njzscloud.supervisory.order.pojo.dto.*;
|
||||
|
||||
/**
|
||||
* 微信支付服务接口
|
||||
*/
|
||||
public interface WechatPayService {
|
||||
|
||||
/**
|
||||
* 小程序支付下单
|
||||
* @param orderDto 下单请求参数
|
||||
* @return 支付参数
|
||||
*/
|
||||
WechatPayJsapiOrderResponseDto createMiniProgramOrder(WechatPayJsapiOrderDto orderDto);
|
||||
|
||||
/**
|
||||
* JSAPI支付下单
|
||||
* @param orderDto 下单请求参数
|
||||
* @return 支付参数
|
||||
*/
|
||||
WechatPayJsapiOrderResponseDto createJsapiOrder(WechatPayJsapiOrderDto orderDto);
|
||||
|
||||
/**
|
||||
* 查询订单状态
|
||||
* @param outTradeNo 商户订单号
|
||||
* @return 订单信息
|
||||
*/
|
||||
WechatPayOrderQueryResponseDto queryOrder(String outTradeNo);
|
||||
|
||||
/**
|
||||
* 关闭订单
|
||||
* @param outTradeNo 商户订单号
|
||||
* @return 是否成功
|
||||
*/
|
||||
boolean closeOrder(String outTradeNo);
|
||||
|
||||
/**
|
||||
* 申请退款
|
||||
* @param refundDto 退款请求参数
|
||||
* @return 退款结果
|
||||
*/
|
||||
WechatPayRefundResponseDto refund(WechatPayRefundDto refundDto);
|
||||
|
||||
/**
|
||||
* 查询退款状态
|
||||
* @param outRefundNo 商户退款单号
|
||||
* @return 退款信息
|
||||
*/
|
||||
WechatPayRefundResponseDto queryRefund(String outRefundNo);
|
||||
|
||||
/**
|
||||
* 处理支付回调通知
|
||||
* @param callbackDto 回调数据
|
||||
* @return 处理结果
|
||||
*/
|
||||
String handlePaymentCallback(WechatPayCallbackDto callbackDto);
|
||||
|
||||
/**
|
||||
* 处理退款回调通知
|
||||
* @param callbackDto 回调数据
|
||||
* @return 处理结果
|
||||
*/
|
||||
String handleRefundCallback(WechatPayRefundCallbackDto callbackDto);
|
||||
|
||||
/**
|
||||
* 验证回调签名
|
||||
* @param timestamp 时间戳
|
||||
* @param nonce 随机字符串
|
||||
* @param body 请求体
|
||||
* @param signature 签名
|
||||
* @return 是否验证通过
|
||||
*/
|
||||
boolean verifyCallback(String timestamp, String nonce, String body, String signature);
|
||||
}
|
||||
|
|
@ -0,0 +1,350 @@
|
|||
package com.njzscloud.supervisory.order.service.impl;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.njzscloud.supervisory.order.config.WechatPayConfig;
|
||||
import com.njzscloud.supervisory.order.pojo.dto.*;
|
||||
import com.njzscloud.supervisory.order.service.WechatPayService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.*;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 微信支付服务实现类
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class WechatPayServiceImpl implements WechatPayService {
|
||||
|
||||
private final WechatPayConfig wechatPayConfig;
|
||||
private final RestTemplate restTemplate;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
private static final String WECHAT_PAY_API_BASE = "https://api.mch.weixin.qq.com";
|
||||
private static final String JSAPI_ORDER_URL = WECHAT_PAY_API_BASE + "/v3/pay/transactions/jsapi";
|
||||
private static final String ORDER_QUERY_URL = WECHAT_PAY_API_BASE + "/v3/pay/transactions/out-trade-no/";
|
||||
private static final String ORDER_CLOSE_URL = WECHAT_PAY_API_BASE + "/v3/pay/transactions/out-trade-no/";
|
||||
private static final String REFUND_URL = WECHAT_PAY_API_BASE + "/v3/refund/domestic/refunds";
|
||||
private static final String REFUND_QUERY_URL = WECHAT_PAY_API_BASE + "/v3/refund/domestic/refunds/";
|
||||
|
||||
@Override
|
||||
public WechatPayJsapiOrderResponseDto createMiniProgramOrder(WechatPayJsapiOrderDto orderDto) {
|
||||
return createJsapiOrder(orderDto);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WechatPayJsapiOrderResponseDto createJsapiOrder(WechatPayJsapiOrderDto orderDto) {
|
||||
try {
|
||||
// 构建请求体
|
||||
Map<String, Object> requestBody = new HashMap<>();
|
||||
requestBody.put("appid", wechatPayConfig.getSubAppId());
|
||||
requestBody.put("mchid", wechatPayConfig.getSubMchId());
|
||||
requestBody.put("description", orderDto.getDescription());
|
||||
requestBody.put("out_trade_no", orderDto.getOutTradeNo());
|
||||
requestBody.put("notify_url", wechatPayConfig.getNotifyUrl());
|
||||
|
||||
// 金额信息
|
||||
Map<String, Object> amount = new HashMap<>();
|
||||
amount.put("total", orderDto.getTotal());
|
||||
amount.put("currency", orderDto.getCurrency());
|
||||
requestBody.put("amount", amount);
|
||||
|
||||
// 支付者信息
|
||||
Map<String, Object> payer = new HashMap<>();
|
||||
payer.put("openid", orderDto.getOpenid());
|
||||
requestBody.put("payer", payer);
|
||||
|
||||
// 发送请求
|
||||
HttpHeaders headers = createHeaders();
|
||||
HttpEntity<String> entity = new HttpEntity<>(objectMapper.writeValueAsString(requestBody), headers);
|
||||
|
||||
ResponseEntity<WechatPayJsapiOrderResponseDto> response = restTemplate.exchange(
|
||||
JSAPI_ORDER_URL,
|
||||
HttpMethod.POST,
|
||||
entity,
|
||||
WechatPayJsapiOrderResponseDto.class
|
||||
);
|
||||
|
||||
if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) {
|
||||
WechatPayJsapiOrderResponseDto responseDto = response.getBody();
|
||||
// 生成小程序支付参数
|
||||
responseDto.setPaySign(generateMiniProgramPaySign(responseDto));
|
||||
return responseDto;
|
||||
}
|
||||
|
||||
throw new RuntimeException("创建订单失败");
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("创建JSAPI订单失败", e);
|
||||
throw new RuntimeException("创建订单失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public WechatPayOrderQueryResponseDto queryOrder(String outTradeNo) {
|
||||
try {
|
||||
HttpHeaders headers = createHeaders();
|
||||
HttpEntity<String> entity = new HttpEntity<>(headers);
|
||||
|
||||
ResponseEntity<WechatPayOrderQueryResponseDto> response = restTemplate.exchange(
|
||||
ORDER_QUERY_URL + outTradeNo + "?mchid=" + wechatPayConfig.getSubMchId(),
|
||||
HttpMethod.GET,
|
||||
entity,
|
||||
WechatPayOrderQueryResponseDto.class
|
||||
);
|
||||
|
||||
return response.getBody();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("查询订单失败", e);
|
||||
throw new RuntimeException("查询订单失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean closeOrder(String outTradeNo) {
|
||||
try {
|
||||
Map<String, Object> requestBody = new HashMap<>();
|
||||
requestBody.put("mchid", wechatPayConfig.getSubMchId());
|
||||
|
||||
HttpHeaders headers = createHeaders();
|
||||
HttpEntity<String> entity = new HttpEntity<>(objectMapper.writeValueAsString(requestBody), headers);
|
||||
|
||||
ResponseEntity<Void> response = restTemplate.exchange(
|
||||
ORDER_CLOSE_URL + outTradeNo + "/close",
|
||||
HttpMethod.POST,
|
||||
entity,
|
||||
Void.class
|
||||
);
|
||||
|
||||
return response.getStatusCode() == HttpStatus.NO_CONTENT;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("关闭订单失败", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public WechatPayRefundResponseDto refund(WechatPayRefundDto refundDto) {
|
||||
try {
|
||||
Map<String, Object> requestBody = new HashMap<>();
|
||||
requestBody.put("out_trade_no", refundDto.getOutTradeNo());
|
||||
requestBody.put("out_refund_no", refundDto.getOutRefundNo());
|
||||
requestBody.put("reason", refundDto.getReason());
|
||||
requestBody.put("notify_url", wechatPayConfig.getRefundNotifyUrl());
|
||||
|
||||
// 金额信息
|
||||
Map<String, Object> amount = new HashMap<>();
|
||||
amount.put("refund", refundDto.getRefund());
|
||||
amount.put("total", refundDto.getTotal());
|
||||
amount.put("currency", "CNY");
|
||||
requestBody.put("amount", amount);
|
||||
|
||||
HttpHeaders headers = createHeaders();
|
||||
HttpEntity<String> entity = new HttpEntity<>(objectMapper.writeValueAsString(requestBody), headers);
|
||||
|
||||
ResponseEntity<WechatPayRefundResponseDto> response = restTemplate.exchange(
|
||||
REFUND_URL,
|
||||
HttpMethod.POST,
|
||||
entity,
|
||||
WechatPayRefundResponseDto.class
|
||||
);
|
||||
|
||||
return response.getBody();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("申请退款失败", e);
|
||||
throw new RuntimeException("申请退款失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public WechatPayRefundResponseDto queryRefund(String outRefundNo) {
|
||||
try {
|
||||
HttpHeaders headers = createHeaders();
|
||||
HttpEntity<String> entity = new HttpEntity<>(headers);
|
||||
|
||||
ResponseEntity<WechatPayRefundResponseDto> response = restTemplate.exchange(
|
||||
REFUND_QUERY_URL + outRefundNo,
|
||||
HttpMethod.GET,
|
||||
entity,
|
||||
WechatPayRefundResponseDto.class
|
||||
);
|
||||
|
||||
return response.getBody();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("查询退款失败", e);
|
||||
throw new RuntimeException("查询退款失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String handlePaymentCallback(WechatPayCallbackDto callbackDto) {
|
||||
try {
|
||||
// 解密回调数据
|
||||
WechatPayCallbackDto.PaymentInfo paymentInfo = decryptCallbackData(
|
||||
callbackDto.getResource().getCiphertext(),
|
||||
callbackDto.getResource().getAssociatedData(),
|
||||
callbackDto.getResource().getNonce()
|
||||
);
|
||||
|
||||
// 处理支付结果
|
||||
if ("SUCCESS".equals(paymentInfo.getTradeState())) {
|
||||
// 支付成功,更新订单状态
|
||||
log.info("支付成功,订单号: {}, 微信支付单号: {}",
|
||||
paymentInfo.getOutTradeNo(), paymentInfo.getTransactionId());
|
||||
// TODO: 更新订单状态到数据库
|
||||
}
|
||||
|
||||
return "SUCCESS";
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("处理支付回调失败", e);
|
||||
return "FAIL";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String handleRefundCallback(WechatPayRefundCallbackDto callbackDto) {
|
||||
try {
|
||||
// 解密回调数据
|
||||
WechatPayRefundCallbackDto.RefundInfo refundInfo = decryptRefundCallbackData(
|
||||
callbackDto.getResource().getCiphertext(),
|
||||
callbackDto.getResource().getAssociatedData(),
|
||||
callbackDto.getResource().getNonce()
|
||||
);
|
||||
|
||||
// 处理退款结果
|
||||
if ("SUCCESS".equals(refundInfo.getRefundStatus())) {
|
||||
// 退款成功,更新订单状态
|
||||
log.info("退款成功,退款单号: {}, 微信退款单号: {}",
|
||||
refundInfo.getOutRefundNo(), refundInfo.getRefundId());
|
||||
// TODO: 更新订单状态到数据库
|
||||
}
|
||||
|
||||
return "SUCCESS";
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("处理退款回调失败", e);
|
||||
return "FAIL";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verifyCallback(String timestamp, String nonce, String body, String signature) {
|
||||
try {
|
||||
String message = timestamp + "\n" + nonce + "\n" + body + "\n";
|
||||
String expectedSignature = generateSignature(message);
|
||||
return expectedSignature.equals(signature);
|
||||
} catch (Exception e) {
|
||||
log.error("验证回调签名失败", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建请求头
|
||||
*/
|
||||
private HttpHeaders createHeaders() {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
headers.set("Authorization", "WECHATPAY2-SHA256-RSA2048 " + generateAuthorization());
|
||||
headers.set("Accept", "application/json");
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成授权信息
|
||||
*/
|
||||
private String generateAuthorization() {
|
||||
// TODO: 实现微信支付签名生成逻辑
|
||||
return "mock_authorization";
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成小程序支付签名
|
||||
*/
|
||||
private String generateMiniProgramPaySign(WechatPayJsapiOrderResponseDto responseDto) {
|
||||
try {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("appId", wechatPayConfig.getSubAppId());
|
||||
params.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000));
|
||||
params.put("nonceStr", generateNonceStr());
|
||||
params.put("package", "prepay_id=" + responseDto.getPrepayId());
|
||||
params.put("signType", "RSA");
|
||||
|
||||
// 生成签名
|
||||
String sign = generateSignature(buildSignString(params));
|
||||
params.put("paySign", sign);
|
||||
|
||||
return objectMapper.writeValueAsString(params);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("生成小程序支付签名失败", e);
|
||||
throw new RuntimeException("生成支付签名失败");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成签名
|
||||
*/
|
||||
private String generateSignature(String message) {
|
||||
try {
|
||||
// TODO: 使用商户私钥生成RSA签名
|
||||
return "mock_signature";
|
||||
} catch (Exception e) {
|
||||
log.error("生成签名失败", e);
|
||||
throw new RuntimeException("生成签名失败");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建签名字符串
|
||||
*/
|
||||
private String buildSignString(Map<String, String> params) {
|
||||
return params.entrySet().stream()
|
||||
.sorted(Map.Entry.comparingByKey())
|
||||
.map(entry -> entry.getKey() + "=" + entry.getValue())
|
||||
.reduce((a, b) -> a + "&" + b)
|
||||
.orElse("");
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成随机字符串
|
||||
*/
|
||||
private String generateNonceStr() {
|
||||
return UUID.randomUUID().toString().replace("-", "");
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密回调数据
|
||||
*/
|
||||
private WechatPayCallbackDto.PaymentInfo decryptCallbackData(String ciphertext, String associatedData, String nonce) {
|
||||
try {
|
||||
// TODO: 实现AES-256-GCM解密
|
||||
return new WechatPayCallbackDto.PaymentInfo();
|
||||
} catch (Exception e) {
|
||||
log.error("解密回调数据失败", e);
|
||||
throw new RuntimeException("解密回调数据失败");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密退款回调数据
|
||||
*/
|
||||
private WechatPayRefundCallbackDto.RefundInfo decryptRefundCallbackData(String ciphertext, String associatedData, String nonce) {
|
||||
try {
|
||||
// TODO: 实现AES-256-GCM解密
|
||||
return new WechatPayRefundCallbackDto.RefundInfo();
|
||||
} catch (Exception e) {
|
||||
log.error("解密退款回调数据失败", e);
|
||||
throw new RuntimeException("解密退款回调数据失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -22,4 +22,6 @@ public interface AuthMapper {
|
|||
MyResult selectUser(@Param("ew") QueryWrapper<Object> ew);
|
||||
|
||||
SearchCompanyResult selectCompanyInfo(@Param("userId") Long userId);
|
||||
|
||||
String selectWechatOpenidByUserId(@Param("userId") Long userId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,4 +38,10 @@ public class MyResult extends UserDetail {
|
|||
* 手机号
|
||||
*/
|
||||
private String phone;
|
||||
|
||||
/**
|
||||
* 微信openId
|
||||
*/
|
||||
private String wechatOpenid;
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,11 +83,13 @@ public class AuthService implements IUserService, IRoleService {
|
|||
List<EndpointResource> endpointResources = authMapper.selectUserEndpoint(userId);
|
||||
SearchCompanyResult company = authMapper.selectCompanyInfo(userId);
|
||||
Set<String> strings = authMapper.selectRoleByUserId(userId);
|
||||
String wechatOpenid = authMapper.selectWechatOpenidByUserId(userId);
|
||||
return BeanUtil.copyProperties(userEntity, MyResult.class)
|
||||
.setRoles(strings)
|
||||
.setMenus(menuResources)
|
||||
.setEndpoints(endpointResources)
|
||||
.setCompany(company);
|
||||
.setCompany(company)
|
||||
.setWechatOpenid(wechatOpenid != null ? wechatOpenid : "");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,3 +57,15 @@ wechat:
|
|||
app-id: wx3c06d9dd4e56c58d
|
||||
app-secret: ff280a71a4c06fc2956178f8c472ef96
|
||||
base-url: https://api.weixin.qq.com
|
||||
pay:
|
||||
# 子商户配置
|
||||
sub-app-id: wx3c06d9dd4e56c58d
|
||||
sub-mch-id: 1900000100
|
||||
# API密钥(32位字符串)
|
||||
api-key: your-32-character-api-key-here
|
||||
# 证书序列号
|
||||
cert-serial-no: your-cert-serial-number
|
||||
# 私钥文件路径
|
||||
private-key-path: classpath:cert/apiclient_key.pem
|
||||
# 支付回调地址
|
||||
notify-url: https://your-domain.com/api/payment/wechat/callback
|
||||
|
|
|
|||
|
|
@ -106,4 +106,10 @@
|
|||
FROM biz_company
|
||||
WHERE user_id = #{userId}
|
||||
</select>
|
||||
|
||||
<select id="selectWechatOpenidByUserId" resultType="java.lang.String">
|
||||
SELECT wechat_openid
|
||||
FROM sys_user_account
|
||||
WHERE user_id = #{userId} AND deleted = 0
|
||||
</select>
|
||||
</mapper>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,116 @@
|
|||
# 微信支付配置说明
|
||||
|
||||
## 配置项说明
|
||||
|
||||
根据[微信支付官方文档](https://pay.weixin.qq.com/doc/v3/merchant/4012791897),以下是简化的微信支付配置说明(仅包含必填参数):
|
||||
|
||||
### 1. 基础配置
|
||||
|
||||
```yaml
|
||||
wechat:
|
||||
pay:
|
||||
# 子商户配置
|
||||
sub-app-id: wx3c06d9dd4e56c58d # 子商户APPID
|
||||
sub-mch-id: 1900000100 # 子商户号
|
||||
|
||||
# API密钥(32位字符串)
|
||||
api-key: your-32-character-api-key-here
|
||||
|
||||
# 证书序列号
|
||||
cert-serial-no: your-cert-serial-number
|
||||
|
||||
# 私钥文件路径
|
||||
private-key-path: classpath:cert/apiclient_key.pem
|
||||
|
||||
# 支付回调地址
|
||||
notify-url: https://your-domain.com/api/payment/wechat/callback
|
||||
```
|
||||
|
||||
### 2. 参数说明
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| sub-app-id | String | 是 | 子商户APPID |
|
||||
| sub-mch-id | String | 是 | 子商户号 |
|
||||
| api-key | String | 是 | API密钥,32位字符串 |
|
||||
| cert-serial-no | String | 是 | 证书序列号 |
|
||||
| private-key-path | String | 是 | 私钥文件路径 |
|
||||
| notify-url | String | 是 | 支付成功回调地址 |
|
||||
|
||||
### 3. 证书配置
|
||||
|
||||
1. 将微信支付商户私钥文件 `apiclient_key.pem` 放置在 `src/main/resources/cert/` 目录下
|
||||
2. 确保证书序列号与私钥文件匹配
|
||||
3. 私钥文件格式应为 PEM 格式
|
||||
|
||||
### 4. 回调地址配置
|
||||
|
||||
- 支付回调地址:用于接收支付成功通知
|
||||
- 退款回调地址:用于接收退款成功通知
|
||||
- 回调地址必须是 HTTPS 协议
|
||||
- 回调地址需要能够接收 POST 请求
|
||||
|
||||
### 5. 安全注意事项
|
||||
|
||||
1. **API密钥安全**:
|
||||
- API密钥是32位字符串,请妥善保管
|
||||
- 不要将API密钥提交到代码仓库
|
||||
- 建议使用环境变量或配置中心管理
|
||||
|
||||
2. **证书安全**:
|
||||
- 私钥文件不要提交到代码仓库
|
||||
- 确保证书文件权限设置正确
|
||||
- 定期更新证书
|
||||
|
||||
3. **回调地址安全**:
|
||||
- 使用HTTPS协议
|
||||
- 验证回调签名
|
||||
- 处理重复通知
|
||||
|
||||
### 6. 环境配置
|
||||
|
||||
#### 开发环境 (application-dev.yml)
|
||||
```yaml
|
||||
wechat:
|
||||
pay:
|
||||
# 使用测试商户号和测试APPID
|
||||
sub-app-id: wx3c06d9dd4e56c58d
|
||||
sub-mch-id: 1900000100
|
||||
api-key: your-test-api-key
|
||||
cert-serial-no: your-test-cert-serial
|
||||
private-key-path: classpath:cert/test_apiclient_key.pem
|
||||
notify-url: https://test-domain.com/api/payment/wechat/callback
|
||||
```
|
||||
|
||||
#### 生产环境 (application-prod.yml)
|
||||
```yaml
|
||||
wechat:
|
||||
pay:
|
||||
# 使用正式商户号和正式APPID
|
||||
sub-app-id: wx3c06d9dd4e56c58d
|
||||
sub-mch-id: 1900000100
|
||||
api-key: ${WECHAT_PAY_API_KEY}
|
||||
cert-serial-no: ${WECHAT_PAY_CERT_SERIAL}
|
||||
private-key-path: classpath:cert/prod_apiclient_key.pem
|
||||
notify-url: https://your-domain.com/api/payment/wechat/callback
|
||||
```
|
||||
|
||||
### 7. 常见问题
|
||||
|
||||
1. **证书序列号获取**:
|
||||
- 登录微信支付商户平台
|
||||
- 在"账户中心" -> "API安全" -> "API证书"中查看
|
||||
|
||||
2. **API密钥设置**:
|
||||
- 在微信支付商户平台设置32位API密钥
|
||||
- 确保与代码中配置的密钥一致
|
||||
|
||||
3. **回调地址配置**:
|
||||
- 确保回调地址可以正常访问
|
||||
- 使用HTTPS协议
|
||||
- 处理跨域问题
|
||||
|
||||
4. **测试环境**:
|
||||
- 使用微信支付沙箱环境进行测试
|
||||
- 测试商户号和正式商户号不同
|
||||
- 测试环境需要特殊配置
|
||||
Loading…
Reference in New Issue