ea8ffb6c74
- 修复订阅模式登录时错误检查余额的问题 - 区分无限余额和永久订阅两种永久会员类型 - 修复动态代码HTTP请求返回值在JS中无法正确访问的问题 - 添加侧边栏滚动位置保持功能 - 移除developer角色相关代码,统一使用admin - 添加缺失的i18n翻译key
350 lines
7.9 KiB
Go
350 lines
7.9 KiB
Go
package main
|
||
|
||
import (
|
||
"log"
|
||
"verification-platform-backend/internal/database"
|
||
"verification-platform-backend/internal/model"
|
||
)
|
||
|
||
func main() {
|
||
database.Init()
|
||
|
||
docContent := `# 手动扣费 API
|
||
|
||
## 概述
|
||
|
||
手动扣费功能允许开发者在余额模式下,通过 API 自行控制扣费时机和金额。这为开发者提供了最大的灵活性,可以根据业务需求实现自定义的计费逻辑。
|
||
|
||
## 前提条件
|
||
|
||
1. 应用的运营模式必须设置为**余额模式**
|
||
2. 扣费方式必须设置为**手动扣费**
|
||
|
||
## 扣费方式说明
|
||
|
||
扣费方式分为两级选择:
|
||
|
||
### 第一级:扣费方式
|
||
|
||
| 方式 | 说明 |
|
||
|------|------|
|
||
| 自动扣费 | 系统自动触发扣费 |
|
||
| 手动扣费 | 通过 API 自行控制扣费 |
|
||
|
||
### 第二级:自动扣费类型(仅自动扣费时显示)
|
||
|
||
| 类型 | 说明 |
|
||
|------|------|
|
||
| 登录扣费 | 每次登录验证时扣费 |
|
||
| 计时扣费 | 按设定的时间间隔自动扣费 |
|
||
|
||
### 计时扣费配置
|
||
|
||
选择计时扣费后,可自定义扣费间隔:
|
||
|
||
- **间隔值**:1-9999 之间的整数
|
||
- **时间单位**:分钟、小时、天
|
||
|
||
例如:设置为 "每 30 分钟扣费一次" 或 "每 2 小时扣费一次"
|
||
|
||
## 接口详情
|
||
|
||
### 请求地址
|
||
|
||
POST /api/v1/dev/applications/:id/deduct
|
||
|
||
### 请求头
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
|------|------|------|------|
|
||
| Authorization | string | 是 | Bearer {token},开发者登录后获取的令牌 |
|
||
| Content-Type | string | 是 | application/json |
|
||
|
||
### 路径参数
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
|------|------|------|------|
|
||
| id | integer | 是 | 应用ID |
|
||
|
||
### 请求参数
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
|------|------|------|------|
|
||
| user_id | integer | 是 | 要扣费的用户ID |
|
||
| amount | number | 是 | 扣费金额,必须大于0 |
|
||
| description | string | 否 | 扣费描述/原因 |
|
||
|
||
### 请求示例
|
||
|
||
` + "```json" + `
|
||
{
|
||
"user_id": 123,
|
||
"amount": 10.5,
|
||
"description": "使用高级功能扣费"
|
||
}
|
||
` + "```" + `
|
||
|
||
### 响应示例
|
||
|
||
#### 成功响应
|
||
|
||
` + "```json" + `
|
||
{
|
||
"code": 200,
|
||
"message": "success",
|
||
"data": {
|
||
"user_id": 123,
|
||
"username": "testuser",
|
||
"amount": 10.5,
|
||
"balance_before": 100.5,
|
||
"balance_after": 90.0,
|
||
"description": "使用高级功能扣费",
|
||
"message": "扣费成功"
|
||
}
|
||
}
|
||
` + "```" + `
|
||
|
||
#### 错误响应
|
||
|
||
**应用不存在**
|
||
|
||
` + "```json" + `
|
||
{
|
||
"code": 404,
|
||
"message": "应用不存在"
|
||
}
|
||
` + "```" + `
|
||
|
||
**非余额模式**
|
||
|
||
` + "```json" + `
|
||
{
|
||
"code": 400,
|
||
"message": "只有余额模式的应用才支持手动扣费"
|
||
}
|
||
` + "```" + `
|
||
|
||
**未开启手动扣费**
|
||
|
||
` + "```json" + `
|
||
{
|
||
"code": 400,
|
||
"message": "该应用未开启手动扣费模式,请先在设置中修改扣费频率为手动扣费"
|
||
}
|
||
` + "```" + `
|
||
|
||
**用户不存在**
|
||
|
||
` + "```json" + `
|
||
{
|
||
"code": 404,
|
||
"message": "用户不存在"
|
||
}
|
||
` + "```" + `
|
||
|
||
**余额不足**
|
||
|
||
` + "```json" + `
|
||
{
|
||
"code": 400,
|
||
"message": "用户余额不足,当前余额: 5.00,需扣除: 10.50"
|
||
}
|
||
` + "```" + `
|
||
|
||
**永久会员**
|
||
|
||
` + "```json" + `
|
||
{
|
||
"code": 400,
|
||
"message": "该用户为永久会员,无法扣费"
|
||
}
|
||
` + "```" + `
|
||
|
||
## 使用场景
|
||
|
||
### 1. 按次计费
|
||
|
||
用户每次使用特定功能时扣费:
|
||
|
||
` + "```javascript" + `
|
||
// 用户使用高级分析功能
|
||
async function useAdvancedAnalysis(userId) {
|
||
const response = await fetch('/api/v1/dev/applications/1/deduct', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Authorization': 'Bearer ' + token,
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify({
|
||
user_id: userId,
|
||
amount: 5.0,
|
||
description: '使用高级分析功能'
|
||
})
|
||
});
|
||
|
||
const result = await response.json();
|
||
if (result.code === 200) {
|
||
// 扣费成功,执行功能
|
||
return true;
|
||
} else {
|
||
// 扣费失败,提示用户
|
||
alert(result.message);
|
||
return false;
|
||
}
|
||
}
|
||
` + "```" + `
|
||
|
||
### 2. 阶梯计费
|
||
|
||
根据使用量阶梯定价:
|
||
|
||
` + "```javascript" + `
|
||
function calculatePrice(usage) {
|
||
if (usage <= 100) {
|
||
return 1.0; // 前100次,每次1元
|
||
} else if (usage <= 500) {
|
||
return 0.8; // 101-500次,每次0.8元
|
||
} else {
|
||
return 0.5; // 500次以上,每次0.5元
|
||
}
|
||
}
|
||
|
||
async function chargeUser(userId, currentUsage) {
|
||
const price = calculatePrice(currentUsage);
|
||
// 调用扣费API...
|
||
}
|
||
` + "```" + `
|
||
|
||
### 3. 动态定价
|
||
|
||
根据时间段或活动动态调整价格:
|
||
|
||
` + "```javascript" + `
|
||
function getDynamicPrice() {
|
||
const hour = new Date().getHours();
|
||
const dayOfWeek = new Date().getDay();
|
||
|
||
// 周末打折
|
||
if (dayOfWeek === 0 || dayOfWeek === 6) {
|
||
return 5.0 * 0.8;
|
||
}
|
||
|
||
// 深夜时段打折
|
||
if (hour >= 22 || hour < 6) {
|
||
return 5.0 * 0.5;
|
||
}
|
||
|
||
return 5.0;
|
||
}
|
||
` + "```" + `
|
||
|
||
### 4. 批量扣费
|
||
|
||
对多个用户批量扣费:
|
||
|
||
` + "```javascript" + `
|
||
async function batchDeduct(users) {
|
||
const results = [];
|
||
|
||
for (const user of users) {
|
||
try {
|
||
const response = await fetch('/api/v1/dev/applications/1/deduct', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Authorization': 'Bearer ' + token,
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify({
|
||
user_id: user.id,
|
||
amount: user.amount,
|
||
description: user.description
|
||
})
|
||
});
|
||
|
||
const result = await response.json();
|
||
results.push({
|
||
user_id: user.id,
|
||
success: result.code === 200,
|
||
message: result.message
|
||
});
|
||
} catch (error) {
|
||
results.push({
|
||
user_id: user.id,
|
||
success: false,
|
||
message: error.message
|
||
});
|
||
}
|
||
}
|
||
|
||
return results;
|
||
}
|
||
` + "```" + `
|
||
|
||
## 注意事项
|
||
|
||
1. **余额检查**: 调用扣费接口前,建议先检查用户余额是否充足
|
||
2. **幂等性**: 如果业务需要保证幂等性,请在业务层实现去重逻辑
|
||
3. **并发控制**: 高并发场景下,建议使用锁机制防止余额扣成负数
|
||
4. **记录保存**: 每次扣费都会自动创建消费记录,可在后台查看
|
||
5. **错误处理**: 请妥善处理各种错误情况,给用户友好的提示
|
||
|
||
## 与自动扣费的区别
|
||
|
||
| 特性 | 手动扣费 | 自动扣费 |
|
||
|------|----------|----------|
|
||
| 扣费时机 | 开发者自行控制 | 系统自动执行 |
|
||
| 扣费金额 | 每次可不同 | 固定金额 |
|
||
| 灵活性 | 高 | 低 |
|
||
| 实现复杂度 | 需要开发者实现 | 无需开发 |
|
||
| 适用场景 | 复杂计费逻辑 | 简单按时/按次计费 |
|
||
|
||
## 相关接口
|
||
|
||
- [获取用户信息](/docs/app-api-docs#获取账户信息) - 查询用户当前余额
|
||
- [用户充值](/docs/app-api-docs#卡密充值) - 用户通过卡密充值
|
||
- [消费记录](/docs/finance) - 查看扣费记录`
|
||
|
||
var devCategory model.DocCategory
|
||
if err := database.DB.Where("slug = ?", "dev-docs").First(&devCategory).Error; err != nil {
|
||
devCategory = model.DocCategory{
|
||
Name: "开发者文档",
|
||
Slug: "dev-docs",
|
||
Description: "开发者API接口文档",
|
||
Sort: 90,
|
||
}
|
||
if err := database.DB.Create(&devCategory).Error; err != nil {
|
||
log.Printf("创建开发者文档分类失败: %v", err)
|
||
return
|
||
}
|
||
}
|
||
|
||
var existingDoc model.Doc
|
||
if err := database.DB.Where("slug = ?", "manual-deduct-api").First(&existingDoc).Error; err == nil {
|
||
existingDoc.Content = docContent
|
||
if err := database.DB.Save(&existingDoc).Error; err != nil {
|
||
log.Printf("更新手动扣费文档失败: %v", err)
|
||
} else {
|
||
log.Println("手动扣费文档更新成功")
|
||
}
|
||
return
|
||
}
|
||
|
||
doc := model.Doc{
|
||
Title: "手动扣费 API",
|
||
CategoryID: &devCategory.ID,
|
||
Slug: "manual-deduct-api",
|
||
Content: docContent,
|
||
Summary: "余额模式下通过API自行控制扣费时机和金额,支持按次计费、阶梯计费、动态定价等场景",
|
||
Icon: "💳",
|
||
Sort: 10,
|
||
Status: "published",
|
||
}
|
||
|
||
if err := database.DB.Create(&doc).Error; err != nil {
|
||
log.Printf("创建手动扣费文档失败: %v", err)
|
||
return
|
||
}
|
||
|
||
log.Println("手动扣费文档创建成功")
|
||
}
|