16 KiB
16 KiB
支付功能使用指南
📋 概述
本项目已完整接入微信支付和支付宝支付,支持内容购买、账户充值等支付场景。
一、配置信息
1.1 微信支付配置
位置:src/main/resources/application-dev.yml 和 src/main/resources/application-prod.yml
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 支付宝支付配置
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(购买内容) |
请求示例:
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"
}'
响应示例:
{
"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
请求参数: 与微信支付相同
请求示例:
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"
}'
响应示例:
{
"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:创建购买记录
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:账户充值
业务流程:
// 创建充值订单
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:支付宝网页支付
// 创建支付宝订单
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 微信支付页面
<!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 支付宝支付页面
<!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 回调地址。
解决方案:
-
使用内网穿透工具(推荐)
# 使用 ngrok ngrok http 19001 # 将生成的域名配置到回调地址 # 例如:https://abc123.ngrok.io/api/pay/wx/notify -
部署到测试服务器
- 直接部署到具有公网 IP 的服务器进行测试
-
修改配置文件
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 安全建议
- 权限验证:所有支付接口都应添加权限验证(已部分实现)
- 签名验证:确保支付回调的签名验证正确
- 幂等性处理:支付回调应支持重复通知(已实现)
- 日志记录:完整的支付日志便于问题排查
七、常见问题
Q1: 如何查询订单状态?
// 通过订单号查询
PaymentOrder order = paymentOrderService.queryByOrderNo(orderNo);
// 或通过主键查询
PaymentOrder order = paymentOrderService.queryById(orderId);
Q2: 如何处理支付回调失败?
系统已实现幂等性处理,同一订单多次回调不会重复处理。如果回调失败,微信/支付宝会按一定频率重试。
Q3: 如何申请退款?
当前版本暂未实现退款功能,如需退款,需要:
- 调用微信/支付宝退款 API
- 更新支付订单状态为已退款(5)
- 恢复用户余额或购买权限
Q4: 测试时使用真实资金吗?
是的,对接的是正式环境。如需测试环境,需要:
- 微信:申请沙箱环境
- 支付宝:使用测试账号
八、技术支持
如遇问题,请检查以下内容:
- ✅ 配置文件中的商户号、密钥是否正确
- ✅ 回调地址是否可被外网访问
- ✅ 商户私钥文件是否存在且路径正确
- ✅ 查看日志文件中的详细错误信息
- ✅ 确认微信/支付宝商户号状态正常
文档版本: v1.0
更新时间: 2026-03-31
维护人员: 系统开发团队