agent-skill-backend/PAYMENT_GUIDE.md

604 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 支付功能使用指南
## 📋 概述
本项目已完整接入**微信支付**和**支付宝支付**,支持内容购买、账户充值等支付场景。
---
## 一、配置信息
### 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
**维护人员:** 系统开发团队