604 lines
16 KiB
Markdown
604 lines
16 KiB
Markdown
# 支付功能使用指南
|
||
|
||
## 📋 概述
|
||
|
||
本项目已完整接入**微信支付**和**支付宝支付**,支持内容购买、账户充值等支付场景。
|
||
|
||
---
|
||
|
||
## 一、配置信息
|
||
|
||
### 1.1 微信支付配置
|
||
|
||
位置:`src/main/resources/application-dev.yml` 和 `src/main/resources/application-prod.yml`
|
||
|
||
```yaml
|
||
payment:
|
||
wechat:
|
||
appId: wx7d13d99de5be3bfa # 微信应用 ID
|
||
mchId: 1673321732 # 商户号
|
||
mchKey: UDuZXDcmy5Eb6o0nTNZhu6ek4DDh4K8B # 商户密钥
|
||
mchSerialNo: 5EFC47D3AA59BFD1AAE548F96B5E19E1C60F067D # 商户证书序列号
|
||
privateKeyPath: apiclient_key.pem # 商户私钥文件路径
|
||
domain: https://api.mch.weixin.qq.com # 微信服务器地址
|
||
notifyUrl: http://127.0.0.1:19001/api/pay/wx/notify # 支付回调地址
|
||
returnUrl: http://127.0.0.1:19001/api/pay/success # 支付成功跳转地址
|
||
```
|
||
|
||
### 1.2 支付宝支付配置
|
||
|
||
```yaml
|
||
payment:
|
||
alipay:
|
||
appId: 2021004138642603 # 支付宝应用 ID
|
||
publicKey: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnakP04nUmsoFoveIvOhbLqkA1xQuYtvkrqq2AVvTsbtqpsEOTm9G095e2rBYLp89oDcf6L6BhtJPwdrhnA+qifUyVmACI9sprrsGeRYQgndK7y4c6spQcSnsnakSxlIp22j7pvBXNAZuqud2hQV+TOLKEUh1W3izTgMj/Ejoh3ZsCjgDRtTVgaytzSdHYrhNku+pIrl15/xVGJED99RYXkR8GHawxuK+vWVmxU0tiTCwTsqLz43v6TtCZ+/UfLL/luwp9B4ZvB+0qon82LILYr6oxs10kE2IAvryuDToAc1s/v/36jgt+7DXwqzfUDksHhVLHdJHChyc4ax5HmMsBwIDAQAB" # 支付宝公钥
|
||
privateKey: "" # 商户私钥(需配置)
|
||
notifyUrl: http://127.0.0.1:19001/api/pay/alipay/trade/notify # 支付回调地址
|
||
returnUrl: https://shuziren.xueai.art/alipay-success # 支付成功跳转地址
|
||
signType: RSA2 # 签名类型
|
||
charset: UTF-8 # 字符编码
|
||
gatewayUrl: https://openapi.alipay.com/gateway.do # 支付宝网关
|
||
```
|
||
|
||
---
|
||
|
||
## 二、API 接口说明
|
||
|
||
### 2.1 创建微信支付订单
|
||
|
||
**接口地址:** `POST /api/pay/wx/create`
|
||
|
||
**请求参数:**
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
|------|------|------|------|
|
||
| orderNo | String | 否 | 订单号(不传则自动生成) |
|
||
| userId | Long | 是 | 用户 ID |
|
||
| userName | String | 是 | 用户名 |
|
||
| amount | BigDecimal | 是 | 支付金额(单位:元) |
|
||
| productName | String | 是 | 商品名称 |
|
||
| productDesc | String | 否 | 商品描述 |
|
||
| businessId | Long | 是 | 关联业务 ID |
|
||
| businessType | String | 是 | 业务类型:recharge(充值)/purchase_content(购买内容) |
|
||
|
||
**请求示例:**
|
||
|
||
```bash
|
||
curl -X POST http://localhost:19001/api/pay/wx/create \
|
||
-H "Content-Type: application/json" \
|
||
-d '{
|
||
"userId": 123,
|
||
"userName": "张三",
|
||
"amount": 0.01,
|
||
"productName": "测试商品",
|
||
"productDesc": "商品描述",
|
||
"businessId": 456,
|
||
"businessType": "purchase_content"
|
||
}'
|
||
```
|
||
|
||
**响应示例:**
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "success",
|
||
"data": {
|
||
"code_url": "weixin://wxpay/bizpayurl?pr=xxx",
|
||
"order_no": "ORDER_20260331_001"
|
||
}
|
||
}
|
||
```
|
||
|
||
**字段说明:**
|
||
- `code_url`: 微信支付二维码链接,前端可使用此链接生成二维码
|
||
- `order_no`: 支付订单号,用于后续查询订单状态
|
||
|
||
---
|
||
|
||
### 2.2 创建支付宝支付订单
|
||
|
||
**接口地址:** `POST /api/pay/alipay/create`
|
||
|
||
**请求参数:** 与微信支付相同
|
||
|
||
**请求示例:**
|
||
|
||
```bash
|
||
curl -X POST http://localhost:19001/api/pay/alipay/create \
|
||
-H "Content-Type: application/json" \
|
||
-d '{
|
||
"userId": 123,
|
||
"userName": "张三",
|
||
"amount": 0.01,
|
||
"productName": "测试商品",
|
||
"businessId": 456,
|
||
"businessType": "purchase_content"
|
||
}'
|
||
```
|
||
|
||
**响应示例:**
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "success",
|
||
"data": "<form name=\"alipay\" method=\"post\" action=\"https://openapi.alipay.com/gateway.do\">...</form>"
|
||
}
|
||
```
|
||
|
||
**使用说明:**
|
||
- 返回的是 HTML 表单字符串
|
||
- 前端将此外壳写入页面后会自动提交跳转到支付宝支付页面
|
||
|
||
---
|
||
|
||
### 2.3 支付回调接口(系统自动处理)
|
||
|
||
#### 微信支付回调
|
||
|
||
**接口地址:** `POST /api/pay/wx/notify`
|
||
|
||
**说明:**
|
||
- 由微信支付服务器自动调用
|
||
- 处理支付结果并更新订单状态
|
||
- 返回 XML 格式响应给微信服务器
|
||
|
||
#### 支付宝支付回调
|
||
|
||
**异步回调:** `POST /api/pay/alipay/trade/notify`
|
||
|
||
**同步回调:** `GET /api/pay/alipay/trade/return`
|
||
|
||
**说明:**
|
||
- 异步回调由支付宝服务器自动调用
|
||
- 同步回调用于用户支付完成后跳转回指定页面
|
||
|
||
---
|
||
|
||
## 三、使用场景示例
|
||
|
||
### 3.1 场景 1:购买付费内容
|
||
|
||
**业务流程:**
|
||
|
||
1. 用户点击购买按钮
|
||
2. 创建购买记录(待支付状态)
|
||
3. 创建支付订单
|
||
4. 用户扫码或跳转支付
|
||
5. 支付成功后更新订单和购买记录状态
|
||
|
||
**前端调用示例:**
|
||
|
||
```javascript
|
||
// 步骤 1:创建购买记录
|
||
const purchaseResponse = await fetch('/api/content/purchase', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
userId: 123,
|
||
contentId: 456,
|
||
payType: 3 // 3=微信支付,4=支付宝支付
|
||
})
|
||
});
|
||
|
||
const purchaseResult = await purchaseResponse.json();
|
||
|
||
// 步骤 2:创建支付订单
|
||
const payResponse = await fetch('/api/pay/wx/create', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
userId: 123,
|
||
amount: 9.99,
|
||
productName: 'AI 技能教程',
|
||
productDesc: '高级 AI 技能培训内容',
|
||
businessId: 456,
|
||
businessType: 'purchase_content'
|
||
})
|
||
});
|
||
|
||
const payResult = await payResponse.json();
|
||
|
||
if (payResult.code === 200) {
|
||
// 步骤 3:显示支付二维码
|
||
const codeUrl = payResult.data.code_url;
|
||
// 使用 QRCode 库生成二维码
|
||
new QRCode(document.getElementById('qrcode'), {
|
||
text: codeUrl,
|
||
width: 200,
|
||
height: 200
|
||
});
|
||
|
||
// 步骤 4:轮询查询订单状态
|
||
const checkOrderStatus = setInterval(async () => {
|
||
const statusResponse = await fetch(`/api/pay/order/query?orderNo=${payResult.data.order_no}`);
|
||
const statusResult = await statusResponse.json();
|
||
|
||
if (statusResult.data.status === 2) { // 2=已支付
|
||
clearInterval(checkOrderStatus);
|
||
alert('支付成功!');
|
||
window.location.reload();
|
||
}
|
||
}, 3000); // 每 3 秒查询一次
|
||
|
||
// 设置超时(15 分钟后停止查询)
|
||
setTimeout(() => {
|
||
clearInterval(checkOrderStatus);
|
||
alert('支付超时,请重新下单');
|
||
}, 900000);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 3.2 场景 2:账户充值
|
||
|
||
**业务流程:**
|
||
|
||
```javascript
|
||
// 创建充值订单
|
||
const rechargeResponse = await fetch('/api/pay/wx/create', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
userId: 123,
|
||
amount: 100.00, // 充值 100 元
|
||
productName: '账户充值',
|
||
businessId: 123,
|
||
businessType: 'recharge' // 充值业务类型
|
||
})
|
||
});
|
||
|
||
const result = await rechargeResponse.json();
|
||
if (result.code === 200) {
|
||
// 显示支付二维码
|
||
showQRCode(result.data.code_url);
|
||
}
|
||
```
|
||
|
||
**说明:**
|
||
- 支付成功后,系统会自动通过回调将充值金额添加到用户账户余额
|
||
|
||
---
|
||
|
||
### 3.3 场景 3:支付宝网页支付
|
||
|
||
```javascript
|
||
// 创建支付宝订单
|
||
const aliPayResponse = await fetch('/api/pay/alipay/create', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
userId: 123,
|
||
amount: 0.01,
|
||
productName: '测试商品',
|
||
businessId: 456,
|
||
businessType: 'purchase_content'
|
||
})
|
||
});
|
||
|
||
const result = await aliPayResponse.json();
|
||
if (result.code === 200) {
|
||
// 将返回的 HTML 表单写入页面
|
||
document.body.innerHTML = result.data;
|
||
// 表单会自动提交,跳转到支付宝支付页面
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 四、前端集成完整示例
|
||
|
||
### 4.1 微信支付页面
|
||
|
||
```html
|
||
<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<title>微信支付</title>
|
||
<script src="https://cdn.jsdelivr.net/npm/qrcode@1.5.0/build/qrcode.min.js"></script>
|
||
<style>
|
||
.payment-container {
|
||
max-width: 600px;
|
||
margin: 50px auto;
|
||
padding: 20px;
|
||
text-align: center;
|
||
}
|
||
.qrcode-box {
|
||
border: 1px solid #ddd;
|
||
padding: 20px;
|
||
display: inline-block;
|
||
margin: 20px 0;
|
||
}
|
||
.status-tip {
|
||
color: #666;
|
||
font-size: 14px;
|
||
margin-top: 10px;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="payment-container">
|
||
<h2>微信支付</h2>
|
||
<div class="qrcode-box">
|
||
<div id="qrcode"></div>
|
||
</div>
|
||
<p class="status-tip">请使用微信扫码支付</p>
|
||
<p class="status-tip" id="statusTip">等待支付...</p>
|
||
</div>
|
||
|
||
<script>
|
||
// 从 URL 参数获取商品信息
|
||
const urlParams = new URLSearchParams(window.location.search);
|
||
const productId = urlParams.get('productId');
|
||
const amount = urlParams.get('amount');
|
||
|
||
// 创建支付订单
|
||
async function createPayment() {
|
||
try {
|
||
const response = await fetch('/api/pay/wx/create', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
userId: 123, // 实际应从登录信息中获取
|
||
amount: parseFloat(amount),
|
||
productName: '商品-' + productId,
|
||
businessId: productId,
|
||
businessType: 'purchase_content'
|
||
})
|
||
});
|
||
|
||
const result = await response.json();
|
||
if (result.code === 200) {
|
||
// 生成二维码
|
||
QRCode.toCanvas(document.getElementById('qrcode'), result.data.code_url, {
|
||
width: 200,
|
||
height: 200
|
||
});
|
||
|
||
// 开始轮询订单状态
|
||
checkOrderStatus(result.data.order_no);
|
||
} else {
|
||
alert('创建订单失败:' + result.message);
|
||
}
|
||
} catch (error) {
|
||
console.error('创建订单失败:', error);
|
||
alert('网络错误,请稍后重试');
|
||
}
|
||
}
|
||
|
||
// 轮询订单状态
|
||
async function checkOrderStatus(orderNo) {
|
||
const maxAttempts = 300; // 最多查询 300 次(15 分钟)
|
||
let attempts = 0;
|
||
|
||
const timer = setInterval(async () => {
|
||
try {
|
||
const response = await fetch(`/api/pay/order/query?orderNo=${orderNo}`);
|
||
const result = await response.json();
|
||
|
||
if (result.code === 200 && result.data) {
|
||
const status = result.data.status;
|
||
const statusText = document.getElementById('statusTip');
|
||
|
||
if (status === 2) { // 已支付
|
||
clearInterval(timer);
|
||
statusText.textContent = '✅ 支付成功!';
|
||
statusText.style.color = 'green';
|
||
|
||
setTimeout(() => {
|
||
window.location.href = '/pay-success.html?orderNo=' + orderNo;
|
||
}, 1000);
|
||
return;
|
||
} else if (status === 3 || status === 4) { // 支付失败或已取消
|
||
clearInterval(timer);
|
||
statusText.textContent = '❌ 支付失败';
|
||
statusText.style.color = 'red';
|
||
return;
|
||
}
|
||
|
||
attempts++;
|
||
if (attempts >= maxAttempts) {
|
||
clearInterval(timer);
|
||
statusText.textContent = '⏰ 支付超时,请重新下单';
|
||
statusText.style.color = 'orange';
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('查询订单状态失败:', error);
|
||
}
|
||
}, 3000); // 每 3 秒查询一次
|
||
}
|
||
|
||
// 页面加载时创建支付订单
|
||
createPayment();
|
||
</script>
|
||
</body>
|
||
</html>
|
||
```
|
||
|
||
### 4.2 支付宝支付页面
|
||
|
||
```html
|
||
<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<title>支付宝支付</title>
|
||
</head>
|
||
<body>
|
||
<div style="text-align: center; padding: 50px;">
|
||
<h2>正在跳转到支付宝支付页面...</h2>
|
||
<p>请稍候</p>
|
||
</div>
|
||
|
||
<script>
|
||
// 从 URL 获取商品信息
|
||
const urlParams = new URLSearchParams(window.location.search);
|
||
const productId = urlParams.get('productId');
|
||
const amount = urlParams.get('amount');
|
||
|
||
// 创建支付订单
|
||
async function createAlipay() {
|
||
try {
|
||
const response = await fetch('/api/pay/alipay/create', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
userId: 123,
|
||
amount: parseFloat(amount),
|
||
productName: '商品-' + productId,
|
||
businessId: productId,
|
||
businessType: 'purchase_content'
|
||
})
|
||
});
|
||
|
||
const result = await response.json();
|
||
if (result.code === 200) {
|
||
// 将返回的 HTML 表单写入页面并提交
|
||
document.body.innerHTML = result.data;
|
||
document.forms[0].submit();
|
||
} else {
|
||
alert('创建订单失败:' + result.message);
|
||
setTimeout(() => {
|
||
window.location.href = '/index.html';
|
||
}, 2000);
|
||
}
|
||
} catch (error) {
|
||
console.error('创建订单失败:', error);
|
||
alert('网络错误,请稍后重试');
|
||
}
|
||
}
|
||
|
||
createAlipay();
|
||
</script>
|
||
</body>
|
||
</html>
|
||
```
|
||
|
||
---
|
||
|
||
## 五、核心代码文件
|
||
|
||
| 文件路径 | 说明 |
|
||
|---------|------|
|
||
| `src/main/java/com/kexue/skills/controller/PayController.java` | 支付接口控制器 |
|
||
| `src/main/java/com/kexue/skills/service/PayService.java` | 支付服务接口 |
|
||
| `src/main/java/com/kexue/skills/service/impl/PayServiceImpl.java` | 支付服务实现类 |
|
||
| `src/main/java/com/kexue/skills/config/PaymentConfig.java` | 支付配置类 |
|
||
| `src/main/java/com/kexue/skills/entity/PaymentOrder.java` | 支付订单实体类 |
|
||
| `src/main/java/com/kexue/skills/service/PaymentOrderService.java` | 支付订单服务接口 |
|
||
| `src/main/java/com/kexue/skills/service/impl/PaymentOrderServiceImpl.java` | 支付订单服务实现类 |
|
||
|
||
---
|
||
|
||
## 六、注意事项
|
||
|
||
### 6.1 开发环境测试
|
||
|
||
⚠️ **重要提示:** 本地开发环境下,微信和支付宝无法直接访问到你的 localhost 回调地址。
|
||
|
||
**解决方案:**
|
||
|
||
1. **使用内网穿透工具**(推荐)
|
||
```bash
|
||
# 使用 ngrok
|
||
ngrok http 19001
|
||
|
||
# 将生成的域名配置到回调地址
|
||
# 例如:https://abc123.ngrok.io/api/pay/wx/notify
|
||
```
|
||
|
||
2. **部署到测试服务器**
|
||
- 直接部署到具有公网 IP 的服务器进行测试
|
||
|
||
3. **修改配置文件**
|
||
```yaml
|
||
payment:
|
||
wechat:
|
||
notifyUrl: https://your-domain.ngrok.io/api/pay/wx/notify
|
||
alipay:
|
||
notifyUrl: https://your-domain.ngrok.io/api/pay/alipay/trade/notify
|
||
```
|
||
|
||
### 6.2 金额单位
|
||
|
||
- 前端传入金额单位:**元**
|
||
- 微信支付内部转换:**分**(代码自动处理)
|
||
- 建议最小金额:0.01 元
|
||
|
||
### 6.3 业务类型说明
|
||
|
||
| 业务类型 | 说明 | 支付成功后操作 |
|
||
|---------|------|--------------|
|
||
| `recharge` | 账户充值 | 增加用户账户余额 |
|
||
| `purchase_content` | 购买内容 | 更新内容购买记录状态 |
|
||
|
||
### 6.4 支付状态码
|
||
|
||
| 状态码 | 说明 |
|
||
|-------|------|
|
||
| 1 | 待支付 |
|
||
| 2 | 已支付 |
|
||
| 3 | 支付失败 |
|
||
| 4 | 已取消 |
|
||
| 5 | 已退款 |
|
||
|
||
### 6.5 安全建议
|
||
|
||
1. **权限验证**:所有支付接口都应添加权限验证(已部分实现)
|
||
2. **签名验证**:确保支付回调的签名验证正确
|
||
3. **幂等性处理**:支付回调应支持重复通知(已实现)
|
||
4. **日志记录**:完整的支付日志便于问题排查
|
||
|
||
---
|
||
|
||
## 七、常见问题
|
||
|
||
### Q1: 如何查询订单状态?
|
||
|
||
```java
|
||
// 通过订单号查询
|
||
PaymentOrder order = paymentOrderService.queryByOrderNo(orderNo);
|
||
|
||
// 或通过主键查询
|
||
PaymentOrder order = paymentOrderService.queryById(orderId);
|
||
```
|
||
|
||
### Q2: 如何处理支付回调失败?
|
||
|
||
系统已实现幂等性处理,同一订单多次回调不会重复处理。如果回调失败,微信/支付宝会按一定频率重试。
|
||
|
||
### Q3: 如何申请退款?
|
||
|
||
当前版本暂未实现退款功能,如需退款,需要:
|
||
1. 调用微信/支付宝退款 API
|
||
2. 更新支付订单状态为已退款(5)
|
||
3. 恢复用户余额或购买权限
|
||
|
||
### Q4: 测试时使用真实资金吗?
|
||
|
||
是的,对接的是正式环境。如需测试环境,需要:
|
||
- 微信:申请沙箱环境
|
||
- 支付宝:使用测试账号
|
||
|
||
---
|
||
|
||
## 八、技术支持
|
||
|
||
如遇问题,请检查以下内容:
|
||
|
||
1. ✅ 配置文件中的商户号、密钥是否正确
|
||
2. ✅ 回调地址是否可被外网访问
|
||
3. ✅ 商户私钥文件是否存在且路径正确
|
||
4. ✅ 查看日志文件中的详细错误信息
|
||
5. ✅ 确认微信/支付宝商户号状态正常
|
||
|
||
---
|
||
|
||
**文档版本:** v1.0
|
||
**更新时间:** 2026-03-31
|
||
**维护人员:** 系统开发团队
|