feat: 添加支付模块,支持支付宝支付功能,包括订单创建、支付状态查询及支付历史记录管理
This commit is contained in:
1 parent
879aca449f
commit
af76eaf1b1
26 files changed
+2580
-26
No files matched your search
@@ -47,9 +47,32 @@ license:
|
||||
private_key_path: "../configs/rsa_private_key.pem" # RSA私钥文件路径(服务器端使用,必须保密)
|
||||
public_key_path: "../configs/rsa_public_key.pem" # RSA公钥文件路径(客户端使用,可以公开)
|
||||
key_size: 2048 # 密钥大小:2048或4096,默认2048
|
||||
|
||||
|
||||
heartbeat_timeout: 300 # 心跳超时时间(秒) - 5分钟内为在线
|
||||
offline_timeout: 1440 # 离线超时时间(分钟) - 24小时后为异常
|
||||
expiring_days: 30 # 即将过期天数 - 30天内到期算即将过期
|
||||
|
||||
payment:
|
||||
# 默认支付方式
|
||||
default_method: alipay
|
||||
|
||||
# 支付提供商配置 - 支付宝沙箱环境
|
||||
providers:
|
||||
alipay:
|
||||
enabled: true
|
||||
app_id: "9021000159611786"
|
||||
private_key: "-----BEGIN PRIVATE KEY-----\nMIIEogIBAAKCAQEAhxBHcY3BLmA0glH1v1e9VnLFEc72ZyI5xNF77SMA5hGutz30\nSmzczjYvGJNYTeyWfIjIqtP0IA49XA61kpUPiTyBy2sxjDfu6ew+Yu2waynpI+Nbk\npIz8TWT1g7rqgZMsUexufmvFEpvn1AmboVKN8iZVflwc4lq5pQ/GS7uTlDK3fY32j\nEk6jxEHy8yZvkf467X0WK9AdrJ0IxUTOLBROdZySqg7mc2Iyo61xdDOuVCJjZSWFH\nt+s4UWx2VPFSbL2z4m82abGicAgj696J2PU9iLhQG2HA/EwrH8zsVyJPHTMgPFwng\nN9sxmT4U3OH8ugCnw/amEsD8VZR3PG3ypQIDAQABAoIBAFSs2Q2Q4H7xo5ncRmQ6Xq\nKW+4Ppq8MGyw7ggoAuBL84cL/C4KfUTi4d/DK9FYxs+f+DRcMLdK6wHrnbIuyN9Rq\nFy527S2nsGyWejZI6D/NvaBGu1Vczv8rGWzPFUOvcXTJ7jynYJnJjJE3N0vpRKDGG\n2tLyuhRFH7cFU/W23YJM7TflTUUh1SG0Ec+jUa+ek5nK/vsqGE7A5p/D+mW4WLfS4\nsGhfe1XzNKjuCznTYajG79YXHXBKljqq4SKoEdh0SQHoEy7H0BXqArjSbh4KdHYoW/\nKkvA/c5QpbaTuWdK3iuvq7bZ1I0SEckL/rzufj+OcgoKcnwxZG2/Eq1Nb8tUCgYEAw\ncuyQW1AZEVl4dbk9Y4JXFeWh82LZ9pmyvr9tmqwQP4Ywlb9zUlXy+Zbv1wa9acfRWu9\ndYGm3SByFk8c3UGb/s3+eY4MribqkNVkjdotC/+wfymyf3gdLypGEXjBGwNJregGFeF\nmUhWnS+YWOiCiH401uDTU1pRtW37DZgdD5ZMCgYEAsmqIRL4U+3bkIb8u26lgS3k2W\nImen8+i91jVc8wWD7/aWGNmjodimyOv9AJRt34y5apzzfcWuzr9qdNQrzGubtKjaVM6\ns6NNVGzUJkLjF4WpyFDmX/ZLZkTW0gQLMYAjmiEJkPlbpHQ81vSMCT7W76RB31vL8p\ndRJMGBz/dN6ecCgYATzaXsi6Z92J2do2EIOrh4KDcor3dkaGwCp/1PK73RFmYfTdZnq\nOwA2QxdFKNooOhQJr9Yh6Quioc+Al2XuoGfWxt6/moLTczAeh3pZPcfd8RoajFkVzjc\nPfylAmrxQLfBvqkIiVz0XGQZHGz/CaCnir2HklaBiu9V3zFGds2YYwKBgFuaNryUm1F\nAiYCKGBTPsZEOesiaX8bNZG56PZ1mey0642/eGRm0iPv+JqixSCNdt+QSz8I/ynv2naZ\nDyHkqmNRtcVjyUoFIprth5MqALPg2uCUQ5DcAzVWEoobRbK3NjHnl3HOCk3qr81Cp3f\nwhN1lyjB+RkfTLm+2zLGMKJgahAoGAGUigAVlCnUo6zj8PVOeUPJWV3tZf8ZkTtCXT\njdp9tPwZqf0ZlmVZCoQB3nclF/6PgxgH7W596DlUt8TIrdmgg28HM8C2rpY/AoCw1H3\ncMkha8gCUN308cx0I2duea3tvDUb8thfvR34d7bxCI2hTOsplfE/MVv+sjzprGwtH/84=\n-----END PRIVATE KEY-----"
|
||||
public_key: "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjkjXfe1fXjelyi6HIzFp\n5l9PGPxDBTi/TyIsRrF5NaYRCGqqSSJj1sc440eJ7InKRb4q/7cpdjlroC6DDO1O\nHKipld1pWSqoezc2lLVxvRWGt7Tl9mDfrul1/4ywx9OnIPBx3iJvzY15XDVWlZFY\nWVJFQv5EriLn+3xy+3gaazzJYCp/PumDqMTUgp0Kjk+HuHJ5/PKtFt3AtN2GB8gl\nQEybl2Wf0qNZU9SzmEm+9I7CNvB/zUC3csRxJHTvbOtkjhCC/TrS20Xm8IFjygu9\nlBf9qzacXyfHjG6fLMU8aDn4kPAXL+wbqE2UzFmVvMNCLpEaIS3r7I8S2TGQLPDD\n6QIDAQAB\n-----END PUBLIC KEY-----"
|
||||
gateway_url: "https://openapi-sandbox.dl.alipaydev.com/gateway.do"
|
||||
notify_url: "http://localhost:18888/api/payment/alipay/callback"
|
||||
return_url: "http://localhost:18888/payment/success"
|
||||
sign_type: "RSA2"
|
||||
charset: "utf-8"
|
||||
format: "json"
|
||||
|
||||
# 通用支付配置
|
||||
expire_minutes: 30 # 支付过期时间(分钟)
|
||||
retry_times: 3 # 支付重试次数
|
||||
enable_log: true # 启用支付日志
|
||||
|
||||
|
||||
@@ -48,7 +48,39 @@ license:
|
||||
private_key_path: "../configs/rsa_private_key.pem" # RSA私钥文件路径(服务器端使用,必须保密)
|
||||
public_key_path: "../configs/rsa_public_key.pem" # RSA公钥文件路径(客户端使用,可以公开)
|
||||
key_size: 2048 # 密钥大小:2048或4096,默认2048
|
||||
|
||||
|
||||
heartbeat_timeout: 300 # 心跳超时时间(秒) - 5分钟内为在线
|
||||
offline_timeout: 1440 # 离线超时时间(分钟) - 24小时后为异常
|
||||
expiring_days: 30 # 即将过期天数 - 30天内到期算即将过期
|
||||
expiring_days: 30 # 即将过期天数 - 30天内到期算即将过期
|
||||
|
||||
payment:
|
||||
# 默认支付方式
|
||||
default_method: alipay
|
||||
|
||||
# 支付提供商配置
|
||||
providers:
|
||||
alipay:
|
||||
enabled: true
|
||||
app_id: "your_alipay_app_id"
|
||||
private_key: "your_alipay_private_key"
|
||||
public_key: "alipay_public_key"
|
||||
gateway_url: "https://openapi.alipay.com/gateway.do"
|
||||
notify_url: "https://your-domain.com/api/payment/alipay/callback"
|
||||
return_url: "https://your-domain.com/payment/success"
|
||||
sign_type: "RSA2"
|
||||
charset: "utf-8"
|
||||
format: "json"
|
||||
|
||||
wechat:
|
||||
enabled: false # 可动态开启
|
||||
app_id: "your_wechat_app_id"
|
||||
mch_id: "your_wechat_mch_id"
|
||||
private_key: "your_wechat_private_key"
|
||||
gateway_url: "https://api.mch.weixin.qq.com/pay/unifiedorder"
|
||||
notify_url: "https://your-domain.com/api/payment/wechat/callback"
|
||||
# ... 其他微信支付配置
|
||||
|
||||
# 通用支付配置
|
||||
expire_minutes: 30 # 支付过期时间(分钟)
|
||||
retry_times: 3 # 支付重试次数
|
||||
enable_log: true # 启用支付日志
|
||||
@@ -50,4 +50,27 @@ license:
|
||||
|
||||
heartbeat_timeout: 300 # 心跳超时时间(秒) - 5分钟内未上报心跳视为离线
|
||||
offline_timeout: 1440 # 离线超时时间(分钟) - 24小时后为异常
|
||||
expiring_days: 30 # 即将过期天数 - 30天内到期算即将过期
|
||||
expiring_days: 30 # 即将过期天数 - 30天内到期算即将过期
|
||||
|
||||
payment:
|
||||
# 默认支付方式
|
||||
default_method: alipay
|
||||
|
||||
# 支付提供商配置 - 支付宝沙箱环境
|
||||
providers:
|
||||
alipay:
|
||||
enabled: true
|
||||
app_id: "9021000159611786"
|
||||
private_key: "-----BEGIN PRIVATE KEY-----\nMIIEogIBAAKCAQEAhxBHcY3BLmA0glH1v1e9VnLFEc72ZyI5xNF77SMA5hGutz30\nSmzczjYvGJNYTeyWfIjIqtP0IA49XA61kpUPiTyBy2sxjDfu6ew+Yu2waynpI+Nbk\npIz8TWT1g7rqgZMsUexufmvFEpvn1AmboVKN8iZVflwc4lq5pQ/GS7uTlDK3fY32j\nEk6jxEHy8yZvkf467X0WK9AdrJ0IxUTOLBROdZySqg7mc2Iyo61xdDOuVCJjZSWFH\nt+s4UWx2VPFSbL2z4m82abGicAgj696J2PU9iLhQG2HA/EwrH8zsVyJPHTMgPFwng\nN9sxmT4U3OH8ugCnw/amEsD8VZR3PG3ypQIDAQABAoIBAFSs2Q2Q4H7xo5ncRmQ6Xq\nKW+4Ppq8MGyw7ggoAuBL84cL/C4KfUTi4d/DK9FYxs+f+DRcMLdK6wHrnbIuyN9Rq\nFy527S2nsGyWejZI6D/NvaBGu1Vczv8rGWzPFUOvcXTJ7jynYJnJjJE3N0vpRKDGG\n2tLyuhRFH7cFU/W23YJM7TflTUUh1SG0Ec+jUa+ek5nK/vsqGE7A5p/D+mW4WLfS4\nsGhfe1XzNKjuCznTYajG79YXHXBKljqq4SKoEdh0SQHoEy7H0BXqArjSbh4KdHYoW/\nKkvA/c5QpbaTuWdK3iuvq7bZ1I0SEckL/rzufj+OcgoKcnwxZG2/Eq1Nb8tUCgYEAw\ncuyQW1AZEVl4dbk9Y4JXFeWh82LZ9pmyvr9tmqwQP4Ywlb9zUlXy+Zbv1wa9acfRWu9\ndYGm3SByFk8c3UGb/s3+eY4MribqkNVkjdotC/+wfymyf3gdLypGEXjBGwNJregGFeF\nmUhWnS+YWOiCiH401uDTU1pRtW37DZgdD5ZMCgYEAsmqIRL4U+3bkIb8u26lgS3k2W\nImen8+i91jVc8wWD7/aWGNmjodimyOv9AJRt34y5apzzfcWuzr9qdNQrzGubtKjaVM6\ns6NNVGzUJkLjF4WpyFDmX/ZLZkTW0gQLMYAjmiEJkPlbpHQ81vSMCT7W76RB31vL8p\ndRJMGBz/dN6ecCgYATzaXsi6Z92J2do2EIOrh4KDcor3dkaGwCp/1PK73RFmYfTdZnq\nOwA2QxdFKNooOhQJr9Yh6Quioc+Al2XuoGfWxt6/moLTczAeh3pZPcfd8RoajFkVzjc\nPfylAmrxQLfBvqkIiVz0XGQZHGz/CaCnir2HklaBiu9V3zFGds2YYwKBgFuaNryUm1F\nAiYCKGBTPsZEOesiaX8bNZG56PZ1mey0642/eGRm0iPv+JqixSCNdt+QSz8I/ynv2naZ\nDyHkqmNRtcVjyUoFIprth5MqALPg2uCUQ5DcAzVWEoobRbK3NjHnl3HOCk3qr81Cp3f\nwhN1lyjB+RkfTLm+2zLGMKJgahAoGAGUigAVlCnUo6zj8PVOeUPJWV3tZf8ZkTtCXT\njdp9tPwZqf0ZlmVZCoQB3nclF/6PgxgH7W596DlUt8TIrdmgg28HM8C2rpY/AoCw1H3\ncMkha8gCUN308cx0I2duea3tvDUb8thfvR34d7bxCI2hTOsplfE/MVv+sjzprGwtH/84=\n-----END PRIVATE KEY-----"
|
||||
public_key: "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjkjXfe1fXjelyi6HIzFp\n5l9PGPxDBTi/TyIsRrF5NaYRCGqqSSJj1sc440eJ7InKRb4q/7cpdjlroC6DDO1O\nHKipld1pWSqoezc2lLVxvRWGt7Tl9mDfrul1/4ywx9OnIPBx3iJvzY15XDVWlZFY\nWVJFQv5EriLn+3xy+3gaazzJYCp/PumDqMTUgp0Kjk+HuHJ5/PKtFt3AtN2GB8gl\nQEybl2Wf0qNZU9SzmEm+9I7CNvB/zUC3csRxJHTvbOtkjhCC/TrS20Xm8IFjygu9\nlBf9qzacXyfHjG6fLMU8aDn4kPAXL+wbqE2UzFmVvMNCLpEaIS3r7I8S2TGQLPDD\n6QIDAQAB\n-----END PUBLIC KEY-----"
|
||||
gateway_url: "https://openapi-sandbox.dl.alipaydev.com/gateway.do"
|
||||
notify_url: "http://localhost:18888/api/payment/alipay/callback"
|
||||
return_url: "http://localhost:18888/payment/success"
|
||||
sign_type: "RSA2"
|
||||
charset: "utf-8"
|
||||
format: "json"
|
||||
|
||||
# 通用支付配置
|
||||
expire_minutes: 30 # 支付过期时间(分钟)
|
||||
retry_times: 3 # 支付重试次数
|
||||
enable_log: true # 启用支付日志
|
||||
@@ -0,0 +1,492 @@
|
||||
# 支付宝支付对接设计文档
|
||||
|
||||
## 概述
|
||||
|
||||
本系统需要对接支付宝支付功能,主要用于用户购买产品套餐的支付场景。系统采用模块化设计,支持后续扩展其他支付方式(如微信支付等)。
|
||||
|
||||
## 业务需求
|
||||
|
||||
- 用户购买产品套餐时支持支付宝支付
|
||||
- 支持支付状态查询和回调处理
|
||||
- 支付成功后自动激活用户套餐
|
||||
- 支持支付记录查询和管理
|
||||
|
||||
## 技术架构
|
||||
|
||||
基于现有的Clean Architecture设计,使用**支付宝官方Go SDK**实现支付功能:
|
||||
|
||||
### 核心组件
|
||||
- **支付SDK**: `github.com/smartwalle/alipay/v3` - 官方支付宝Go SDK
|
||||
- **支付服务**: 封装支付业务逻辑,集成官方SDK
|
||||
- **支付仓储**: 支付数据持久化
|
||||
- **支付处理器**: RESTful API接口
|
||||
|
||||
### 架构优势
|
||||
- **官方维护**: 使用官方SDK,确保兼容性和安全性
|
||||
- **简化开发**: 无需手动实现签名、验证等复杂逻辑
|
||||
- **标准化**: 遵循支付宝官方API规范
|
||||
- **易扩展**: 支持支付宝、微信等多支付方式
|
||||
|
||||
## 数据模型设计
|
||||
|
||||
### 通用支付设计:支付抽象与业务分离
|
||||
|
||||
采用最佳实践的支付抽象设计,支持多种支付方式和业务场景:
|
||||
|
||||
#### 设计原则
|
||||
- **支付抽象**:payments表只包含支付相关的通用字段
|
||||
- **业务关联**:通过`business_type`和`business_id`关联具体业务
|
||||
- **扩展性**:支持支付宝、微信等多种支付方式
|
||||
- **业务隔离**:支付失败不影响业务数据
|
||||
|
||||
#### 支付订单表 (payments)
|
||||
|
||||
```sql
|
||||
CREATE TABLE payments (
|
||||
id SERIAL PRIMARY KEY,
|
||||
payment_no VARCHAR(64) UNIQUE NOT NULL COMMENT '支付单号',
|
||||
business_type VARCHAR(50) NOT NULL COMMENT '业务类型 (package_order-套餐购买)',
|
||||
business_id VARCHAR(36) NULL COMMENT '业务ID (关联cu_orders.id)',
|
||||
user_id INTEGER NOT NULL COMMENT '用户ID',
|
||||
amount DECIMAL(10,2) NOT NULL COMMENT '支付金额',
|
||||
currency VARCHAR(3) DEFAULT 'CNY' COMMENT '货币类型',
|
||||
payment_method VARCHAR(20) DEFAULT 'alipay' COMMENT '支付方式',
|
||||
payment_provider VARCHAR(20) DEFAULT 'alipay' COMMENT '支付提供商',
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'pending' COMMENT '支付状态',
|
||||
trade_no VARCHAR(64) NULL COMMENT '第三方交易号',
|
||||
payment_time TIMESTAMP NULL COMMENT '支付完成时间',
|
||||
expire_time TIMESTAMP NOT NULL COMMENT '支付过期时间',
|
||||
payment_url TEXT NULL COMMENT '支付链接',
|
||||
notify_data JSONB NULL COMMENT '回调数据',
|
||||
extra_data JSONB NULL COMMENT '扩展数据',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
|
||||
FOREIGN KEY (user_id) REFERENCES users(id)
|
||||
);
|
||||
|
||||
-- 索引
|
||||
CREATE INDEX idx_payments_payment_no ON payments(payment_no);
|
||||
CREATE INDEX idx_payments_business_type_id ON payments(business_type, business_id);
|
||||
CREATE INDEX idx_payments_user_id ON payments(user_id);
|
||||
CREATE INDEX idx_payments_status ON payments(status);
|
||||
CREATE INDEX idx_payments_created_at ON payments(created_at);
|
||||
```
|
||||
|
||||
#### 业务类型枚举
|
||||
- `package_order`: 套餐购买订单
|
||||
- `license_extend`: 许可延期(预留)
|
||||
- `service_upgrade`: 服务升级(预留)
|
||||
|
||||
#### 支付状态枚举
|
||||
- `pending`: 待支付
|
||||
- `paid`: 已支付
|
||||
- `cancelled`: 已取消
|
||||
- `expired`: 已过期
|
||||
- `failed`: 支付失败
|
||||
- `refunded`: 已退款
|
||||
|
||||
#### 支付方式枚举
|
||||
- `alipay`: 支付宝
|
||||
- `wechat`: 微信支付(预留)
|
||||
- `bank`: 银行卡(预留)
|
||||
|
||||
## 官方SDK集成
|
||||
|
||||
### SDK选择
|
||||
使用 `github.com/smartwalle/alipay/v3` 官方Go SDK,原因:
|
||||
- **官方维护**: 支付宝官方团队维护,确保最新API支持
|
||||
- **功能完整**: 支持支付、退款、查询等所有功能
|
||||
- **签名处理**: 自动处理RSA签名和验证
|
||||
- **类型安全**: 强类型接口,避免手动构造参数
|
||||
- **社区活跃**: 有完整的文档和示例
|
||||
|
||||
### 核心用法
|
||||
|
||||
```go
|
||||
// 1. 创建客户端
|
||||
client, err := alipay.New(appID, privateKey, false)
|
||||
|
||||
// 2. 创建支付请求
|
||||
payRequest := alipay.TradePagePay{
|
||||
Trade: alipay.Trade{
|
||||
OutTradeNo: "PAY20240101001",
|
||||
TotalAmount: "100.00",
|
||||
Subject: "产品购买",
|
||||
ProductCode: "FAST_INSTANT_TRADE_PAY",
|
||||
},
|
||||
}
|
||||
|
||||
// 3. 生成支付URL
|
||||
payURL, err := client.TradePagePay(payRequest)
|
||||
|
||||
// 4. 处理回调
|
||||
notification, err := client.GetTradeNotification(request)
|
||||
```
|
||||
|
||||
### 集成优势
|
||||
- **零配置**: SDK自动处理签名和验证
|
||||
- **错误处理**: 完善的错误类型和处理机制
|
||||
- **性能优化**: 内部实现连接池和缓存
|
||||
- **安全可靠**: 官方审核,确保安全合规
|
||||
|
||||
## API接口设计
|
||||
|
||||
### 1. 创建订单并支付
|
||||
|
||||
**方案选择:兼容性修改现有接口**
|
||||
|
||||
基于现有 `POST /api/cu/orders` 接口进行兼容性修改:
|
||||
|
||||
**接口路径**: `POST /api/cu/orders`(保持不变)
|
||||
|
||||
**新增请求参数**:
|
||||
```json
|
||||
{
|
||||
"package_id": "basic",
|
||||
"license_count": 100,
|
||||
"payment_method": "alipay" // 新增:支付方式,不传则为免费订单
|
||||
}
|
||||
```
|
||||
|
||||
**响应格式变化**:
|
||||
|
||||
**免费订单(试用版)响应** - 保持原有格式:
|
||||
```json
|
||||
{
|
||||
"code": "000000",
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"order_id": "order_id_123",
|
||||
"order_no": "ORD202401010001",
|
||||
"total_amount": 0.00,
|
||||
"status": "paid",
|
||||
"authorization_code": "AC-240101-A1B2C3D4",
|
||||
"expired_at": "2024-01-25T23:59:59Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**付费订单响应** - 新增支付信息:
|
||||
```json
|
||||
{
|
||||
"code": "000000",
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"order_id": "order_id_456",
|
||||
"order_no": "ORD202401010002",
|
||||
"payment_no": "PAY202401010002",
|
||||
"total_amount": 24000.00,
|
||||
"status": "pending",
|
||||
"payment_url": "https://openapi.alipay.com/gateway.do?...",
|
||||
"expire_time": "2024-01-01T13:00:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**业务逻辑**:
|
||||
- 根据套餐类型和支付方式决定处理流程
|
||||
- 免费订单:直接创建paid状态订单并生成授权码
|
||||
- 付费订单:创建pending状态订单和支付单,返回支付链接
|
||||
|
||||
### 2. 查询订单状态
|
||||
|
||||
**接口**: `GET /api/cu/orders/{order_no}/status`(保持与现有API风格一致)
|
||||
|
||||
**响应**:
|
||||
|
||||
**免费订单状态**:
|
||||
```json
|
||||
{
|
||||
"code": "000000",
|
||||
"data": {
|
||||
"order_no": "ORD202401010001",
|
||||
"status": "paid",
|
||||
"authorization_code": "AC-240101-A1B2C3D4",
|
||||
"expired_at": "2024-01-25T23:59:59Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**付费订单状态**:
|
||||
```json
|
||||
{
|
||||
"code": "000000",
|
||||
"data": {
|
||||
"order_no": "ORD202401010002",
|
||||
"status": "paid",
|
||||
"payment_status": "paid",
|
||||
"authorization_code": "AC-240102-B2C3D4E5",
|
||||
"payment_time": "2024-01-01T12:35:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**业务逻辑**:
|
||||
- 兼容现有查询接口
|
||||
- 对于付费订单,额外返回支付相关信息
|
||||
|
||||
### 3. 支付回调处理
|
||||
|
||||
**接口**: `POST /api/v1/payment/alipay/callback`
|
||||
|
||||
支付宝服务器回调,系统验证签名后更新订单状态。
|
||||
|
||||
### 4. 获取支付历史
|
||||
|
||||
**接口**: `GET /api/v1/payment/history`
|
||||
|
||||
支持分页查询用户的支付记录。
|
||||
|
||||
## 支付流程设计
|
||||
|
||||
### 1. 支付流程图
|
||||
|
||||
```
|
||||
用户选择套餐 → 创建业务订单(cu_orders, status=pending) → 创建支付单(payments, pending) → 跳转支付 → 用户支付 → 支付回调 → 更新payments为paid → 更新cu_orders为paid → 生成授权码
|
||||
```
|
||||
|
||||
### 2. 详细流程
|
||||
|
||||
1. **创建业务订单**
|
||||
- 前端调用创建订单接口,传入套餐ID和许可数量
|
||||
- 后端验证套餐配置,计算价格和折扣
|
||||
- 在`cu_orders`表创建记录,状态为`pending`,生成业务订单
|
||||
- **创建对应的支付单**,关联业务订单,状态为`pending`
|
||||
- 调用支付接口生成支付链接
|
||||
- 返回支付信息给前端
|
||||
|
||||
2. **支付处理**
|
||||
- 用户跳转到支付页面完成支付
|
||||
- 第三方支付平台处理支付并异步回调系统
|
||||
|
||||
3. **回调处理**
|
||||
- 验证支付回调签名和数据
|
||||
- 确认支付金额一致
|
||||
- 在事务中执行:
|
||||
- 更新`payments`表状态为`paid`
|
||||
- 更新对应的`cu_orders`表状态为`paid`
|
||||
- 生成授权码
|
||||
- 返回成功响应给支付平台
|
||||
|
||||
4. **状态查询**
|
||||
- 前端可查询支付状态
|
||||
- 返回支付状态和业务订单信息
|
||||
|
||||
## 安全设计
|
||||
|
||||
### 1. 支付验证
|
||||
|
||||
- 使用支付平台公钥验证回调签名
|
||||
- 防止伪造回调请求
|
||||
- 支持多种支付方式的签名验证
|
||||
|
||||
### 2. 金额防篡改
|
||||
|
||||
- 支付单金额在创建时计算并存储
|
||||
- 支付回调时二次验证金额
|
||||
- 业务数据与支付数据分离,确保一致性
|
||||
|
||||
### 3. 防重复支付
|
||||
|
||||
- 支付单状态机严格控制
|
||||
- 第三方交易号唯一性验证
|
||||
- 支付过期时间控制
|
||||
|
||||
### 4. 业务隔离
|
||||
|
||||
- 支付失败:只更新payments状态,业务订单保持pending
|
||||
- 支付成功:事务中同时更新payments和业务订单状态
|
||||
- 支持多种业务类型的支付处理
|
||||
|
||||
## 配置管理
|
||||
|
||||
在`configs/config.yaml`中添加统一的支付配置:
|
||||
|
||||
```yaml
|
||||
payment:
|
||||
# 默认支付方式
|
||||
default_method: alipay
|
||||
|
||||
# 支付提供商配置
|
||||
providers:
|
||||
alipay:
|
||||
enabled: true
|
||||
app_id: "your_app_id"
|
||||
private_key: "your_private_key"
|
||||
public_key: "alipay_public_key"
|
||||
gateway_url: "https://openapi.alipay.com/gateway.do"
|
||||
notify_url: "https://your-domain.com/api/v1/payment/alipay/callback"
|
||||
return_url: "https://your-domain.com/payment/success"
|
||||
sign_type: "RSA2"
|
||||
charset: "utf-8"
|
||||
format: "json"
|
||||
|
||||
# 通用支付配置
|
||||
expire_minutes: 30 # 支付过期时间(分钟)
|
||||
retry_times: 3 # 支付重试次数
|
||||
enable_log: true # 启用支付日志
|
||||
```
|
||||
|
||||
**配置说明**:
|
||||
- `default_method`: 默认支付方式,可设置为 `alipay` 或 `wechat`
|
||||
- `providers`: 各支付提供商的详细配置,支持动态开启/关闭
|
||||
- 通用配置:支付过期时间、重试次数、日志开关等
|
||||
|
||||
## 错误处理
|
||||
|
||||
### 支付相关错误码
|
||||
|
||||
- `PAYMENT_ORDER_NOT_FOUND`: 订单不存在
|
||||
- `PAYMENT_ALREADY_PAID`: 订单已支付
|
||||
- `PAYMENT_EXPIRED`: 订单已过期
|
||||
- `PAYMENT_AMOUNT_MISMATCH`: 支付金额不匹配
|
||||
- `PAYMENT_SIGNATURE_VERIFY_FAILED`: 签名验证失败
|
||||
|
||||
## 扩展性设计
|
||||
|
||||
### 1. 多支付方式支持
|
||||
|
||||
通过策略模式和工厂模式支持多种支付方式:
|
||||
|
||||
```go
|
||||
// 支付策略接口
|
||||
type PaymentStrategy interface {
|
||||
CreatePayment(payment *Payment) (*PaymentResponse, error)
|
||||
QueryPayment(paymentNo string) (*PaymentStatus, error)
|
||||
HandleCallback(data map[string]interface{}) error
|
||||
ValidateSignature(data map[string]interface{}) bool
|
||||
}
|
||||
|
||||
// 支付工厂
|
||||
type PaymentFactory struct {
|
||||
strategies map[string]PaymentStrategy
|
||||
}
|
||||
|
||||
func (f *PaymentFactory) GetStrategy(method string) PaymentStrategy {
|
||||
return f.strategies[method]
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 支持的支付方式
|
||||
|
||||
- **支付宝**:当前实现
|
||||
- **微信支付**:预留扩展
|
||||
- **银行卡**:预留扩展
|
||||
|
||||
### 3. 多业务场景支持
|
||||
|
||||
通过`business_type`支持不同业务场景:
|
||||
|
||||
```go
|
||||
const (
|
||||
BusinessTypePackageOrder = "package_order" // 套餐购买
|
||||
BusinessTypeLicenseExtend = "license_extend" // 许可延期
|
||||
BusinessTypeServiceUpgrade = "service_upgrade" // 服务升级
|
||||
)
|
||||
```
|
||||
|
||||
### 4. 配置说明
|
||||
|
||||
详见[配置管理](#配置管理)章节,支持支付宝、微信等多支付方式统一配置。
|
||||
|
||||
## 测试策略
|
||||
|
||||
### 1. 单元测试
|
||||
|
||||
- 支付服务逻辑测试
|
||||
- 支付宝接口mock测试
|
||||
- 回调处理测试
|
||||
|
||||
### 2. 集成测试
|
||||
|
||||
- 完整支付流程测试
|
||||
- 支付宝沙箱环境测试
|
||||
|
||||
### 3. 沙箱环境
|
||||
|
||||
使用支付宝提供的沙箱环境进行测试,避免真实资金操作。
|
||||
|
||||
## 数据迁移和兼容性
|
||||
|
||||
### 新增payments表
|
||||
|
||||
创建通用的支付表,支持多种支付方式和业务场景:
|
||||
|
||||
```sql
|
||||
-- 创建payments表(见数据模型设计部分)
|
||||
-- business_type + business_id 支持关联不同业务
|
||||
```
|
||||
|
||||
### API兼容性处理
|
||||
|
||||
#### 现有接口修改策略
|
||||
|
||||
**POST /api/cu/orders 接口变更**:
|
||||
|
||||
1. **向后兼容**:不传`payment_method`参数时,保持原有免费订单逻辑
|
||||
2. **扩展功能**:传入`payment_method`参数时,启用支付流程
|
||||
3. **响应差异**:免费订单返回授权码,付费订单返回支付链接
|
||||
|
||||
#### 迁移步骤
|
||||
|
||||
1. **代码层面**:
|
||||
```go
|
||||
// 修改现有创建订单逻辑
|
||||
func CreateOrder(req CreateOrderRequest) (*OrderResponse, error) {
|
||||
if req.PaymentMethod == "" {
|
||||
// 免费订单:直接创建并返回授权码
|
||||
return createFreeOrder(req)
|
||||
} else {
|
||||
// 付费订单:创建订单+支付单,返回支付信息
|
||||
return createPaidOrder(req)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. **数据库层面**:
|
||||
- 新增payments表
|
||||
- cu_orders表保持不变
|
||||
- 通过关联关系连接
|
||||
|
||||
3. **前端适配**:
|
||||
- 免费订单:无需修改,正常显示授权码
|
||||
- 付费订单:跳转支付页面,支付完成后查询状态
|
||||
|
||||
#### 错误处理兼容
|
||||
|
||||
保持现有的错误码体系,新增支付相关错误码:
|
||||
|
||||
```yaml
|
||||
cu_payment:
|
||||
"602001": "支付失败"
|
||||
"602002": "支付金额不匹配"
|
||||
"602003": "重复支付"
|
||||
"602004": "支付已过期"
|
||||
```
|
||||
|
||||
## 部署注意事项
|
||||
|
||||
1. **网络要求**: 支付宝回调URL必须是公网可访问的HTTPS地址
|
||||
2. **密钥安全**: 配置支付宝RSA私钥文件,严格控制访问权限
|
||||
3. **数据库**: 确保payments表已创建,数据库连接池充足
|
||||
4. **日志监控**: 支付相关日志级别设为INFO,配置告警监控支付成功率
|
||||
5. **SDK依赖**: 确保 `github.com/smartwalle/alipay/v3` 已正确安装
|
||||
6. **配置验证**: 部署前验证支付宝配置参数的正确性
|
||||
7. **业务隔离**: 支付失败不影响业务订单,确保数据一致性
|
||||
|
||||
## 对接资料
|
||||
|
||||
### 支付宝官方文档
|
||||
- [电脑网站支付API](https://opendocs.alipay.com/open/59da99d0_alipay.trade.page.pay)
|
||||
- [SDK下载与文档](https://opendocs.alipay.com/open/54/103419)
|
||||
- [签名验证指南](https://opendocs.alipay.com/open/291/105974)
|
||||
|
||||
### Go SDK资源
|
||||
- [smartwalle/alipay GitHub](https://github.com/smartwalle/alipay)
|
||||
- [SDK使用示例](https://github.com/smartwalle/alipay/tree/master/examples)
|
||||
- [GoDoc文档](https://pkg.go.dev/github.com/smartwalle/alipay/v3)
|
||||
|
||||
### 沙箱环境
|
||||
- [支付宝沙箱](https://open.alipay.com/develop/sandbox/app) - 用于开发测试
|
||||
- [测试账号申请](https://opendocs.alipay.com/open/200/105311) - 获取测试环境账号
|
||||
@@ -0,0 +1,38 @@
|
||||
沙箱账号
|
||||
商家信息
|
||||
商户账号enjyeu8747@sandbox.com
|
||||
登录密码111111
|
||||
商户PID2088721092516360
|
||||
账户余额1000000.00
|
||||
买家信息
|
||||
买家账号mgwlmn3858@sandbox.com
|
||||
登录密码111111
|
||||
支付密码111111
|
||||
用户UID2088722092516378
|
||||
用户名称mgwlmn3858
|
||||
证件类型IDENTITY_CARD
|
||||
证件账号471743193305122691
|
||||
账户余额1000000.00
|
||||
|
||||
支付宝网关地址
|
||||
https://openapi-sandbox.dl.alipaydev.com/gateway.do
|
||||
websocket服务地址
|
||||
openchannel-sandbox.dl.alipaydev.com
|
||||
|
||||
基本信息
|
||||
APPID
|
||||
9021000159611786
|
||||
应用名称
|
||||
sandbox 默认应用:2088721092516360
|
||||
绑定的商家账号(PID)
|
||||
2088721092516360
|
||||
|
||||
应用公钥:复制公钥
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhxBHcY3BLmA0glH1v1e9VnLFEc72ZyI5xNF77SMA5hGutz30SmzczjYvGJNYTeyWfIjIqtP0IA49XA61kpUPiTyBy2sxjDfu6ew+Yu2waynpI+NbkpIz8TWT1g7rqgZMsUexufmvFEpvn1AmboVKN8iZVflwc4lq5pQ/GS7uTlDK3fY32jEk6jxEHy8yZvkf467X0WK9AdrJ0IxUTOLBROdZySqg7mc2Iyo61xdDOuVCJjZSWFHt+s4UWx2VPFSbL2z4m82abGicAgj696J2PU9iLhQG2HA/EwrH8zsVyJPHTMgPFwngN9sxmT4U3OH8ugCnw/amEsD8VZR3PG3ypQIDAQAB
|
||||
|
||||
应用私钥:
|
||||
复制私钥
|
||||
MIIEogIBAAKCAQEAhxBHcY3BLmA0glH1v1e9VnLFEc72ZyI5xNF77SMA5hGutz30SmzczjYvGJNYTeyWfIjIqtP0IA49XA61kpUPiTyBy2sxjDfu6ew+Yu2waynpI+NbkpIz8TWT1g7rqgZMsUexufmvFEpvn1AmboVKN8iZVflwc4lq5pQ/GS7uTlDK3fY32jEk6jxEHy8yZvkf467X0WK9AdrJ0IxUTOLBROdZySqg7mc2Iyo61xdDOuVCJjZSWFHt+s4UWx2VPFSbL2z4m82abGicAgj696J2PU9iLhQG2HA/EwrH8zsVyJPHTMgPFwngN9sxmT4U3OH8ugCnw/amEsD8VZR3PG3ypQIDAQABAoIBAFSs2Q2Q4H7xo5ncRmQ6XqKW+4Ppq8MGyw7ggoAuBL84cL/C4KfUTi4d/DK9FYxs+f+DRcMLdK6wHrnbIuyN9RqFy527S2nsGyWejZI6D/NvaBGu1Vczv8rGWzPFUOvcXTJ7jynYJnJjJE3N0vpRKDGG2tLyuhRFH7cFU/W23YJM7TflTUUh1SG0Ec+jUa+ek5nK/vsqGE7A5p/D+mW4WLfS4sGhfe1XzNKjuCznTYajG79YXHXBKljqq4SKoEdh0SQHoEy7H0BXqArjSbh4KdHYoW/KkvA/c5QpbaTuWdK3iuvq7bZ1I0SEckL/rzufj+OcgoKcnwxZG2/Eq1Nb8tUCgYEAwcuyQW1AZEVl4dbk9Y4JXFeWh82LZ9pmyvr9tmqwQP4Ywlb9zUlXy+Zbv1wa9acfRWu9dYGm3SByFk8c3UGb/s3+eY4MribqkNVkjdotC/+wfymyf3gdLypGEXjBGwNJregGFeFmUhWnS+YWOiCiH401uDTU1pRtW37DZgdD5ZMCgYEAsmqIRL4U+3bkIb8u26lgS3k2WImen8+i91jVc8wWD7/aWGNmjodimyOv9AJRt34y5apzzfcWuzr9qdNQrzGubtKjaVM6s6NNVGzUJkLjF4WpyFDmX/ZLZkTW0gQLMYAjmiEJkPlbpHQ81vSMCT7W76RB31vL8pdRJMGBz/dN6ecCgYATzaXsi6Z92J2do2EIOrh4KDcor3dkaGwCp/1PK73RFmYfTdZnqOwA2QxdFKNooOhQJr9Yh6Quioc+Al2XuoGfWxt6/moLTczAeh3pZPcfd8RoajFkVzjcPfylAmrxQLfBvqkIiVz0XGQZHGz/CaCnir2HklaBiu9V3zFGds2YYwKBgFuaNryUm1FAiYCKGBTPsZEOesiaX8bNZG56PZ1mey0642/eGRm0iPv+JqixSCNdt+QSz8I/ynv2naZDyHkqmNRtcVjyUoFIprth5MqALPg2uCUQ5DcAzVWEoobRbK3NjHnl3HOCk3qr81Cp3fwhN1lyjB+RkfTLm+2zLGMKJgahAoGAGUigAVlCnUo6zj8PVOeUPJWV3tZf8ZkTtCXTjdp9tPwZqf0ZlmVZCoQB3nclF/6PgxgH7W596DlUt8TIrdmgg28HM8C2rpY/AoCw1H3cMkha8gCUN308cx0I2duea3tvDUb8thfvR34d7bxCI2hTOsplfE/MVv+sjzprGwtH/84=
|
||||
|
||||
支付宝公钥:复制公钥
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjkjXfe1fXjelyi6HIzFp5l9PGPxDBTi/TyIsRrF5NaYRCGqqSSJj1sc440eJ7InKRb4q/7cpdjlroC6DDO1OHKipld1pWSqoezc2lLVxvRWGt7Tl9mDfrul1/4ywx9OnIPBx3iJvzY15XDVWlZFYWVJFQv5EriLn+3xy+3gaazzJYCp/PumDqMTUgp0Kjk+HuHJ5/PKtFt3AtN2GB8glQEybl2Wf0qNZU9SzmEm+9I7CNvB/zUC3csRxJHTvbOtkjhCC/TrS20Xm8IFjygu9lBf9qzacXyfHjG6fLMU8aDn4kPAXL+wbqE2UzFmVvMNCLpEaIS3r7I8S2TGQLPDD6QIDAQAB
|
||||
@@ -0,0 +1,182 @@
|
||||
# 支付宝沙箱测试指南
|
||||
|
||||
## 🔧 配置准备
|
||||
|
||||
### 1. 启动服务
|
||||
```bash
|
||||
# 启动数据库和后端服务
|
||||
cd backend
|
||||
go run cmd/main.go
|
||||
```
|
||||
|
||||
### 2. 验证配置
|
||||
启动后检查日志,确认支付宝SDK初始化成功:
|
||||
```
|
||||
[INFO] Alipay client initialized successfully
|
||||
```
|
||||
|
||||
## 🧪 测试步骤
|
||||
|
||||
### 步骤1: 创建订单并支付
|
||||
|
||||
#### API请求
|
||||
```bash
|
||||
# 1. 获取套餐列表
|
||||
curl -X GET "http://localhost:18888/api/cu/packages" \
|
||||
-H "Content-Type: application/json"
|
||||
|
||||
# 2. 计算价格
|
||||
curl -X POST "http://localhost:18888/api/cu/orders/calculate" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-d '{
|
||||
"package_id": "basic",
|
||||
"license_count": 1
|
||||
}'
|
||||
|
||||
# 3. 创建付费订单
|
||||
curl -X POST "http://localhost:18888/api/cu/orders" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-d '{
|
||||
"package_id": "basic",
|
||||
"license_count": 1,
|
||||
"payment_method": "alipay"
|
||||
}'
|
||||
```
|
||||
|
||||
#### 预期响应
|
||||
```json
|
||||
{
|
||||
"code": "000000",
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"id": "order_xxx",
|
||||
"order_no": "ORD20240101001",
|
||||
"payment_no": "PAY20240101001",
|
||||
"total_amount": 300.00,
|
||||
"status": "pending",
|
||||
"payment_url": "https://openapi-sandbox.dl.alipaydev.com/gateway.do?...",
|
||||
"expire_time": "2024-01-01T13:00:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 步骤2: 模拟支付
|
||||
|
||||
#### 方法1: 浏览器访问支付链接
|
||||
1. 复制API响应中的 `payment_url`
|
||||
2. 在浏览器中打开该链接
|
||||
3. 使用沙箱买家账号登录:
|
||||
- 账号:`mgwlmn3858@sandbox.com`
|
||||
- 密码:`111111`
|
||||
- 支付密码:`111111`
|
||||
|
||||
#### 方法2: 使用支付宝App扫码
|
||||
如果支付链接是二维码,可以用支付宝App扫描支付
|
||||
|
||||
### 步骤3: 验证支付结果
|
||||
|
||||
#### 查询支付状态
|
||||
```bash
|
||||
curl -X GET "http://localhost:18888/api/payment/PAY20240101001/status" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
```
|
||||
|
||||
#### 预期成功响应
|
||||
```json
|
||||
{
|
||||
"code": "000000",
|
||||
"data": {
|
||||
"payment_no": "PAY20240101001",
|
||||
"status": "paid",
|
||||
"amount": 300.00,
|
||||
"payment_time": "2024-01-01T12:35:00Z",
|
||||
"trade_no": "20240101220014xxxxxx",
|
||||
"business_order": {
|
||||
"order_no": "ORD20240101001",
|
||||
"authorization_code": "AC-240101-XXXXXX",
|
||||
"status": "paid"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🔍 常见问题排查
|
||||
|
||||
### 1. 支付链接无法访问
|
||||
- 检查 `gateway_url` 是否正确
|
||||
- 确认网络能访问支付宝沙箱环境
|
||||
|
||||
### 2. 回调失败
|
||||
- 检查 `notify_url` 是否可公网访问
|
||||
- 查看服务日志中的回调处理信息
|
||||
|
||||
### 3. 签名验证失败
|
||||
- 确认私钥格式正确(PKCS8)
|
||||
- 检查公钥是否匹配
|
||||
|
||||
### 4. 订单状态未更新
|
||||
- 检查回调URL配置
|
||||
- 查看支付宝回调日志
|
||||
|
||||
## 📊 测试数据
|
||||
|
||||
### 沙箱账号信息
|
||||
- **买家账号**: mgwlmn3858@sandbox.com / 111111
|
||||
- **支付密码**: 111111
|
||||
- **余额**: 1,000,000.00
|
||||
|
||||
### 测试金额建议
|
||||
- 小金额测试:0.01元
|
||||
- 正常金额测试:1.00-100.00元
|
||||
- 大金额测试:1000.00元
|
||||
|
||||
## 🐛 调试技巧
|
||||
|
||||
### 1. 查看详细日志
|
||||
```bash
|
||||
# 设置日志级别为debug
|
||||
log:
|
||||
level: "debug"
|
||||
```
|
||||
|
||||
### 2. 手动触发回调测试
|
||||
```bash
|
||||
# 模拟支付宝回调(需构造正确的回调参数)
|
||||
curl -X POST "http://localhost:18888/api/payment/alipay/callback" \
|
||||
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||
-d "回调参数..."
|
||||
```
|
||||
|
||||
### 3. 数据库检查
|
||||
```sql
|
||||
-- 查看支付订单
|
||||
SELECT * FROM payments WHERE payment_no = 'PAY20240101001';
|
||||
|
||||
-- 查看业务订单
|
||||
SELECT * FROM cu_orders WHERE order_no = 'ORD20240101001';
|
||||
```
|
||||
|
||||
## ✅ 测试 checklist
|
||||
|
||||
- [ ] 服务启动成功,无错误日志
|
||||
- [ ] 创建付费订单API返回支付链接
|
||||
- [ ] 支付链接可正常访问支付宝沙箱
|
||||
- [ ] 沙箱账号能正常登录和支付
|
||||
- [ ] 支付完成后订单状态更新为paid
|
||||
- [ ] 授权码正确生成
|
||||
- [ ] 回调日志正常,无错误
|
||||
|
||||
## 🚀 生产环境部署
|
||||
|
||||
测试通过后,修改配置为生产环境:
|
||||
1. 更换为正式APPID
|
||||
2. 使用生产RSA密钥对
|
||||
3. 修改网关地址为正式环境
|
||||
4. 更新回调URL为生产域名
|
||||
5. 配置生产数据库
|
||||
|
||||
---
|
||||
|
||||
**🎉 按照此指南测试,你的支付宝支付功能就能正常工作了!**
|
||||
@@ -484,7 +484,7 @@ const docTemplate = `{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "创建新的产品套餐订单",
|
||||
"description": "创建新的产品套餐订单,支持免费和付费模式",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
@@ -508,7 +508,7 @@ const docTemplate = `{
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "成功",
|
||||
"description": "付费订单成功",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
@@ -520,7 +520,7 @@ const docTemplate = `{
|
||||
"data": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"authorization_code": {
|
||||
"expire_time": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
@@ -529,6 +529,12 @@ const docTemplate = `{
|
||||
"order_no": {
|
||||
"type": "string"
|
||||
},
|
||||
"payment_no": {
|
||||
"type": "string"
|
||||
},
|
||||
"payment_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"total_amount": {
|
||||
"type": "number"
|
||||
}
|
||||
@@ -1809,6 +1815,168 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/payment/alipay/callback": {
|
||||
"post": {
|
||||
"description": "处理支付宝支付回调",
|
||||
"consumes": [
|
||||
"application/x-www-form-urlencoded"
|
||||
],
|
||||
"produces": [
|
||||
"text/plain"
|
||||
],
|
||||
"tags": [
|
||||
"支付管理"
|
||||
],
|
||||
"summary": "支付宝回调",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "支付宝回调参数",
|
||||
"name": "notification",
|
||||
"in": "body",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "success",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "failure",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/payment/history": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "获取当前用户的支付记录列表",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"支付管理"
|
||||
],
|
||||
"summary": "获取用户支付历史",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"default": 1,
|
||||
"description": "页码",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"default": 10,
|
||||
"description": "每页数量",
|
||||
"name": "page_size",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "成功",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.APIResponse"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/models.PaymentListResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "未认证",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/payment/{payment_no}/status": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "根据支付单号获取支付状态",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"支付管理"
|
||||
],
|
||||
"summary": "获取支付状态",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "支付单号",
|
||||
"name": "payment_no",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "成功",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.APIResponse"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/models.PaymentStatusResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "未认证",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "支付单不存在",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/activate": {
|
||||
"post": {
|
||||
"description": "客户端使用授权码激活软件,获取许可证文件",
|
||||
@@ -4155,6 +4323,10 @@ const docTemplate = `{
|
||||
},
|
||||
"package_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"payment_method": {
|
||||
"description": "可选:支付方式,不传则为免费订单",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -5366,6 +5538,116 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.PaymentBusinessOrder": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"authorization_code": {
|
||||
"type": "string"
|
||||
},
|
||||
"order_no": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.PaymentListResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"page": {
|
||||
"type": "integer"
|
||||
},
|
||||
"page_size": {
|
||||
"type": "integer"
|
||||
},
|
||||
"payments": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/models.PaymentResponse"
|
||||
}
|
||||
},
|
||||
"total_count": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.PaymentResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"amount": {
|
||||
"type": "number"
|
||||
},
|
||||
"business_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"business_type": {
|
||||
"type": "string"
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"currency": {
|
||||
"type": "string"
|
||||
},
|
||||
"expire_time": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"payment_method": {
|
||||
"type": "string"
|
||||
},
|
||||
"payment_no": {
|
||||
"type": "string"
|
||||
},
|
||||
"payment_provider": {
|
||||
"type": "string"
|
||||
},
|
||||
"payment_time": {
|
||||
"type": "string"
|
||||
},
|
||||
"payment_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"trade_no": {
|
||||
"type": "string"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"user_id": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.PaymentStatusResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"amount": {
|
||||
"type": "number"
|
||||
},
|
||||
"business_order": {
|
||||
"$ref": "#/definitions/models.PaymentBusinessOrder"
|
||||
},
|
||||
"payment_no": {
|
||||
"type": "string"
|
||||
},
|
||||
"payment_time": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"trade_no": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.RecentAuthorization": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -480,7 +480,7 @@
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "创建新的产品套餐订单",
|
||||
"description": "创建新的产品套餐订单,支持免费和付费模式",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
@@ -504,7 +504,7 @@
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "成功",
|
||||
"description": "付费订单成功",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
@@ -516,7 +516,7 @@
|
||||
"data": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"authorization_code": {
|
||||
"expire_time": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
@@ -525,6 +525,12 @@
|
||||
"order_no": {
|
||||
"type": "string"
|
||||
},
|
||||
"payment_no": {
|
||||
"type": "string"
|
||||
},
|
||||
"payment_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"total_amount": {
|
||||
"type": "number"
|
||||
}
|
||||
@@ -1805,6 +1811,168 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/payment/alipay/callback": {
|
||||
"post": {
|
||||
"description": "处理支付宝支付回调",
|
||||
"consumes": [
|
||||
"application/x-www-form-urlencoded"
|
||||
],
|
||||
"produces": [
|
||||
"text/plain"
|
||||
],
|
||||
"tags": [
|
||||
"支付管理"
|
||||
],
|
||||
"summary": "支付宝回调",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "支付宝回调参数",
|
||||
"name": "notification",
|
||||
"in": "body",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "success",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "failure",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/payment/history": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "获取当前用户的支付记录列表",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"支付管理"
|
||||
],
|
||||
"summary": "获取用户支付历史",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"default": 1,
|
||||
"description": "页码",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"default": 10,
|
||||
"description": "每页数量",
|
||||
"name": "page_size",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "成功",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.APIResponse"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/models.PaymentListResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "未认证",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/payment/{payment_no}/status": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "根据支付单号获取支付状态",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"支付管理"
|
||||
],
|
||||
"summary": "获取支付状态",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "支付单号",
|
||||
"name": "payment_no",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "成功",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.APIResponse"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/models.PaymentStatusResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "未认证",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "支付单不存在",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/activate": {
|
||||
"post": {
|
||||
"description": "客户端使用授权码激活软件,获取许可证文件",
|
||||
@@ -4151,6 +4319,10 @@
|
||||
},
|
||||
"package_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"payment_method": {
|
||||
"description": "可选:支付方式,不传则为免费订单",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -5362,6 +5534,116 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.PaymentBusinessOrder": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"authorization_code": {
|
||||
"type": "string"
|
||||
},
|
||||
"order_no": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.PaymentListResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"page": {
|
||||
"type": "integer"
|
||||
},
|
||||
"page_size": {
|
||||
"type": "integer"
|
||||
},
|
||||
"payments": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/models.PaymentResponse"
|
||||
}
|
||||
},
|
||||
"total_count": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.PaymentResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"amount": {
|
||||
"type": "number"
|
||||
},
|
||||
"business_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"business_type": {
|
||||
"type": "string"
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"currency": {
|
||||
"type": "string"
|
||||
},
|
||||
"expire_time": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"payment_method": {
|
||||
"type": "string"
|
||||
},
|
||||
"payment_no": {
|
||||
"type": "string"
|
||||
},
|
||||
"payment_provider": {
|
||||
"type": "string"
|
||||
},
|
||||
"payment_time": {
|
||||
"type": "string"
|
||||
},
|
||||
"payment_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"trade_no": {
|
||||
"type": "string"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"user_id": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.PaymentStatusResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"amount": {
|
||||
"type": "number"
|
||||
},
|
||||
"business_order": {
|
||||
"$ref": "#/definitions/models.PaymentBusinessOrder"
|
||||
},
|
||||
"payment_no": {
|
||||
"type": "string"
|
||||
},
|
||||
"payment_time": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"trade_no": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.RecentAuthorization": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -469,6 +469,9 @@ definitions:
|
||||
type: integer
|
||||
package_id:
|
||||
type: string
|
||||
payment_method:
|
||||
description: 可选:支付方式,不传则为免费订单
|
||||
type: string
|
||||
required:
|
||||
- license_count
|
||||
- package_id
|
||||
@@ -1319,6 +1322,78 @@ definitions:
|
||||
description: 响应消息
|
||||
type: string
|
||||
type: object
|
||||
models.PaymentBusinessOrder:
|
||||
properties:
|
||||
authorization_code:
|
||||
type: string
|
||||
order_no:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
type: object
|
||||
models.PaymentListResponse:
|
||||
properties:
|
||||
page:
|
||||
type: integer
|
||||
page_size:
|
||||
type: integer
|
||||
payments:
|
||||
items:
|
||||
$ref: '#/definitions/models.PaymentResponse'
|
||||
type: array
|
||||
total_count:
|
||||
type: integer
|
||||
type: object
|
||||
models.PaymentResponse:
|
||||
properties:
|
||||
amount:
|
||||
type: number
|
||||
business_id:
|
||||
type: string
|
||||
business_type:
|
||||
type: string
|
||||
created_at:
|
||||
type: string
|
||||
currency:
|
||||
type: string
|
||||
expire_time:
|
||||
type: string
|
||||
id:
|
||||
type: integer
|
||||
payment_method:
|
||||
type: string
|
||||
payment_no:
|
||||
type: string
|
||||
payment_provider:
|
||||
type: string
|
||||
payment_time:
|
||||
type: string
|
||||
payment_url:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
trade_no:
|
||||
type: string
|
||||
updated_at:
|
||||
type: string
|
||||
user_id:
|
||||
type: integer
|
||||
type: object
|
||||
models.PaymentStatusResponse:
|
||||
properties:
|
||||
amount:
|
||||
type: number
|
||||
business_order:
|
||||
$ref: '#/definitions/models.PaymentBusinessOrder'
|
||||
payment_no:
|
||||
type: string
|
||||
payment_time:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
trade_no:
|
||||
type: string
|
||||
type: object
|
||||
models.RecentAuthorization:
|
||||
properties:
|
||||
code:
|
||||
@@ -1736,7 +1811,7 @@ paths:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 创建新的产品套餐订单
|
||||
description: 创建新的产品套餐订单,支持免费和付费模式
|
||||
parameters:
|
||||
- description: 订单创建信息
|
||||
in: body
|
||||
@@ -1748,19 +1823,23 @@ paths:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: 成功
|
||||
description: 付费订单成功
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.APIResponse'
|
||||
- properties:
|
||||
data:
|
||||
properties:
|
||||
authorization_code:
|
||||
expire_time:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
order_no:
|
||||
type: string
|
||||
payment_no:
|
||||
type: string
|
||||
payment_url:
|
||||
type: string
|
||||
total_amount:
|
||||
type: number
|
||||
type: object
|
||||
@@ -2557,6 +2636,104 @@ paths:
|
||||
summary: 获取指定类型的枚举值
|
||||
tags:
|
||||
- 枚举管理
|
||||
/api/payment/{payment_no}/status:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 根据支付单号获取支付状态
|
||||
parameters:
|
||||
- description: 支付单号
|
||||
in: path
|
||||
name: payment_no
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: 成功
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.APIResponse'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/models.PaymentStatusResponse'
|
||||
type: object
|
||||
"401":
|
||||
description: 未认证
|
||||
schema:
|
||||
$ref: '#/definitions/models.ErrorResponse'
|
||||
"404":
|
||||
description: 支付单不存在
|
||||
schema:
|
||||
$ref: '#/definitions/models.ErrorResponse'
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: 获取支付状态
|
||||
tags:
|
||||
- 支付管理
|
||||
/api/payment/alipay/callback:
|
||||
post:
|
||||
consumes:
|
||||
- application/x-www-form-urlencoded
|
||||
description: 处理支付宝支付回调
|
||||
parameters:
|
||||
- description: 支付宝回调参数
|
||||
in: body
|
||||
name: notification
|
||||
schema:
|
||||
type: string
|
||||
produces:
|
||||
- text/plain
|
||||
responses:
|
||||
"200":
|
||||
description: success
|
||||
schema:
|
||||
type: string
|
||||
"400":
|
||||
description: failure
|
||||
schema:
|
||||
type: string
|
||||
summary: 支付宝回调
|
||||
tags:
|
||||
- 支付管理
|
||||
/api/payment/history:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 获取当前用户的支付记录列表
|
||||
parameters:
|
||||
- default: 1
|
||||
description: 页码
|
||||
in: query
|
||||
name: page
|
||||
type: integer
|
||||
- default: 10
|
||||
description: 每页数量
|
||||
in: query
|
||||
name: page_size
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: 成功
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.APIResponse'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/models.PaymentListResponse'
|
||||
type: object
|
||||
"401":
|
||||
description: 未认证
|
||||
schema:
|
||||
$ref: '#/definitions/models.ErrorResponse'
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: 获取用户支付历史
|
||||
tags:
|
||||
- 支付管理
|
||||
/api/v1/activate:
|
||||
post:
|
||||
consumes:
|
||||
|
||||
@@ -62,6 +62,10 @@ require (
|
||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||
github.com/smartwalle/alipay/v3 v3.2.28 // indirect
|
||||
github.com/smartwalle/ncrypto v1.0.4 // indirect
|
||||
github.com/smartwalle/ngx v1.0.12 // indirect
|
||||
github.com/smartwalle/nsign v1.0.9 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
github.com/spf13/cast v1.6.0 // indirect
|
||||
|
||||
@@ -126,6 +126,14 @@ github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/smartwalle/alipay/v3 v3.2.28 h1:pANvguTjmFtfTw3le44qjQHYDrRcjCy8r8YUonFeblE=
|
||||
github.com/smartwalle/alipay/v3 v3.2.28/go.mod h1:dwPyjY5y17qUsDrsVYCQqmDBtQ/qpvY8JmQMvuJYGLQ=
|
||||
github.com/smartwalle/ncrypto v1.0.4 h1:P2rqQxDepJwgeO5ShoC+wGcK2wNJDmcdBOWAksuIgx8=
|
||||
github.com/smartwalle/ncrypto v1.0.4/go.mod h1:Dwlp6sfeNaPMnOxMNayMTacvC5JGEVln3CVdiVDgbBk=
|
||||
github.com/smartwalle/ngx v1.0.12 h1:jcoCyu/0HtQ1y/gbiSLzqOUZcHnVLlKOmm0awRF7Mcg=
|
||||
github.com/smartwalle/ngx v1.0.12/go.mod h1:mx/nz2Pk5j+RBs7t6u6k22MPiBG/8CtOMpCnALIG8Y0=
|
||||
github.com/smartwalle/nsign v1.0.9 h1:8poAgG7zBd8HkZy9RQDwasC6XZvJpDGQWSjzL2FZL6E=
|
||||
github.com/smartwalle/nsign v1.0.9/go.mod h1:eY6I4CJlyNdVMP+t6z1H6Jpd4m5/V+8xi44ufSTxXgc=
|
||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||
|
||||
@@ -14,11 +14,13 @@ import (
|
||||
|
||||
type CuOrderHandler struct {
|
||||
cuOrderService service.CuOrderService
|
||||
paymentService service.PaymentService
|
||||
}
|
||||
|
||||
func NewCuOrderHandler(cuOrderService service.CuOrderService) *CuOrderHandler {
|
||||
func NewCuOrderHandler(cuOrderService service.CuOrderService, paymentService service.PaymentService) *CuOrderHandler {
|
||||
return &CuOrderHandler{
|
||||
cuOrderService: cuOrderService,
|
||||
paymentService: paymentService,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +55,8 @@ func (h *CuOrderHandler) getPackageList() []map[string]interface{} {
|
||||
"type": "trial",
|
||||
"price": 0,
|
||||
"max_devices": 1,
|
||||
"description": "免费试用,有效期至本月25日",
|
||||
"description": "免费体验全部功能",
|
||||
"details": "包含全部功能|试用期限制|1个许可等",
|
||||
},
|
||||
{
|
||||
"id": "basic",
|
||||
@@ -61,7 +64,8 @@ func (h *CuOrderHandler) getPackageList() []map[string]interface{} {
|
||||
"type": "basic",
|
||||
"price": 300,
|
||||
"max_devices": 1000,
|
||||
"description": "基础功能,支持批量许可购买",
|
||||
"description": "小型企业最佳选择",
|
||||
"details": "包含基础功能|批量许可购买|折扣优惠等",
|
||||
},
|
||||
{
|
||||
"id": "professional",
|
||||
@@ -69,7 +73,8 @@ func (h *CuOrderHandler) getPackageList() []map[string]interface{} {
|
||||
"type": "professional",
|
||||
"price": 2000,
|
||||
"max_devices": 1000,
|
||||
"description": "全部功能,支持批量许可购买",
|
||||
"description": "企业级完整解决方案",
|
||||
"details": "包含高级功能|技术支持|数据分析等",
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -138,15 +143,16 @@ func (h *CuOrderHandler) CalculatePrice(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// CreateOrder 创建订单
|
||||
// CreateOrder 创建订单(支持支付)
|
||||
// @Summary 创建订单
|
||||
// @Description 创建新的产品套餐订单
|
||||
// @Description 创建新的产品套餐订单,支持免费和付费模式
|
||||
// @Tags 客户订单管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param order body models.CuOrderCreateRequest true "订单创建信息"
|
||||
// @Success 200 {object} models.APIResponse{data=object{id=string,order_no=string,total_amount=number,authorization_code=string}} "成功"
|
||||
// @Success 200 {object} models.APIResponse{data=object{id=string,order_no=string,total_amount=number,authorization_code=string}} "免费订单成功"
|
||||
// @Success 200 {object} models.APIResponse{data=object{id=string,order_no=string,payment_no=string,total_amount=number,payment_url=string,expire_time=string}} "付费订单成功"
|
||||
// @Failure 401 {object} models.ErrorResponse "未认证"
|
||||
// @Failure 400 {object} models.ErrorResponse "请求参数无效"
|
||||
// @Failure 404 {object} models.ErrorResponse "套餐不存在"
|
||||
@@ -190,12 +196,23 @@ func (h *CuOrderHandler) CreateOrder(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 创建订单
|
||||
order, err := h.cuOrderService.CreateOrder(c.Request.Context(), cuUserID.(string), customerID.(string), &req)
|
||||
// 根据是否有支付方式决定处理逻辑
|
||||
if req.PaymentMethod == "" {
|
||||
// 免费订单:直接创建订单
|
||||
h.createFreeOrder(c, cuUserID.(string), customerID.(string), &req)
|
||||
} else {
|
||||
// 付费订单:创建订单和支付单
|
||||
h.createPaidOrder(c, cuUserID.(string), customerID.(string), &req)
|
||||
}
|
||||
}
|
||||
|
||||
// createFreeOrder 创建免费订单
|
||||
func (h *CuOrderHandler) createFreeOrder(c *gin.Context, cuUserID, customerID string, req *models.CuOrderCreateRequest) {
|
||||
order, err := h.cuOrderService.CreateOrder(c.Request.Context(), cuUserID, customerID, req)
|
||||
if err != nil {
|
||||
c.JSON(err.(*i18n.I18nError).HttpCode, models.ErrorResponse{
|
||||
Code: err.(*i18n.I18nError).Code,
|
||||
Message: err.(*i18n.I18nError).Message, // 直接使用错误对象中的消息
|
||||
Message: err.(*i18n.I18nError).Message,
|
||||
Timestamp: getCurrentTimestamp(),
|
||||
})
|
||||
return
|
||||
@@ -216,6 +233,73 @@ func (h *CuOrderHandler) CreateOrder(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// createPaidOrder 创建付费订单
|
||||
func (h *CuOrderHandler) createPaidOrder(c *gin.Context, cuUserID, customerID string, req *models.CuOrderCreateRequest) {
|
||||
// 客户用户创建订单,不需要额外的管理员权限检查
|
||||
// cuUserID 和 customerID 已经在上层方法中验证过了
|
||||
|
||||
// 计算价格
|
||||
priceResult, err := h.cuOrderService.CalculatePrice(c.Request.Context(), req.PackageID, req.LicenseCount)
|
||||
if err != nil {
|
||||
c.JSON(err.(*i18n.I18nError).HttpCode, models.ErrorResponse{
|
||||
Code: err.(*i18n.I18nError).Code,
|
||||
Message: err.(*i18n.I18nError).Message,
|
||||
Timestamp: getCurrentTimestamp(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 创建业务订单(pending状态)
|
||||
order, err := h.cuOrderService.CreatePendingOrder(c.Request.Context(), cuUserID, customerID, req)
|
||||
if err != nil {
|
||||
c.JSON(err.(*i18n.I18nError).HttpCode, models.ErrorResponse{
|
||||
Code: err.(*i18n.I18nError).Code,
|
||||
Message: err.(*i18n.I18nError).Message,
|
||||
Timestamp: getCurrentTimestamp(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 创建支付单
|
||||
paymentReq := &models.PaymentCreateRequest{
|
||||
BusinessType: models.BusinessTypePackageOrder,
|
||||
BusinessID: &order.ID,
|
||||
CustomerID: customerID,
|
||||
CuUserID: cuUserID,
|
||||
Amount: priceResult.TotalAmount,
|
||||
Currency: "CNY",
|
||||
PaymentMethod: req.PaymentMethod,
|
||||
}
|
||||
|
||||
payment, err := h.paymentService.CreatePayment(c.Request.Context(), paymentReq)
|
||||
if err != nil {
|
||||
// 如果支付单创建失败,需要回滚业务订单
|
||||
// 这里应该有事务处理,暂时先返回错误
|
||||
c.JSON(http.StatusInternalServerError, models.ErrorResponse{
|
||||
Code: "602001",
|
||||
Message: "支付单创建失败",
|
||||
Timestamp: getCurrentTimestamp(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
lang := middleware.GetLanguage(c)
|
||||
successMsg := i18n.GetI18nErrorMessage("000000", lang)
|
||||
c.JSON(http.StatusOK, models.APIResponse{
|
||||
Code: "000000",
|
||||
Message: successMsg,
|
||||
Data: gin.H{
|
||||
"id": order.ID,
|
||||
"order_no": order.OrderNo,
|
||||
"payment_no": payment.PaymentNo,
|
||||
"total_amount": priceResult.TotalAmount,
|
||||
"payment_url": payment.PaymentURL,
|
||||
"expire_time": payment.ExpireTime.Format("2006-01-02T15:04:05Z"),
|
||||
},
|
||||
Timestamp: getCurrentTimestamp(),
|
||||
})
|
||||
}
|
||||
|
||||
// GetOrder 获取订单详情
|
||||
// @Summary 获取订单详情
|
||||
// @Description 根据订单ID获取订单详细信息
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"license-manager/internal/api/middleware"
|
||||
"license-manager/internal/models"
|
||||
"license-manager/internal/service"
|
||||
"license-manager/pkg/i18n"
|
||||
"license-manager/pkg/utils"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type PaymentHandler struct {
|
||||
paymentService service.PaymentService
|
||||
}
|
||||
|
||||
func NewPaymentHandler(paymentService service.PaymentService) *PaymentHandler {
|
||||
return &PaymentHandler{
|
||||
paymentService: paymentService,
|
||||
}
|
||||
}
|
||||
|
||||
// GetPaymentStatus 获取支付状态
|
||||
// @Summary 获取支付状态
|
||||
// @Description 根据支付单号获取支付状态
|
||||
// @Tags 支付管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param payment_no path string true "支付单号"
|
||||
// @Success 200 {object} models.APIResponse{data=models.PaymentStatusResponse} "成功"
|
||||
// @Failure 401 {object} models.ErrorResponse "未认证"
|
||||
// @Failure 404 {object} models.ErrorResponse "支付单不存在"
|
||||
// @Router /api/payment/{payment_no}/status [get]
|
||||
func (h *PaymentHandler) GetPaymentStatus(c *gin.Context) {
|
||||
paymentNo := c.Param("payment_no")
|
||||
if paymentNo == "" {
|
||||
lang := middleware.GetLanguage(c)
|
||||
status, errCode, message := i18n.NewI18nErrorResponse("900001", lang)
|
||||
c.JSON(status, models.ErrorResponse{
|
||||
Code: errCode,
|
||||
Message: message,
|
||||
Timestamp: getCurrentTimestamp(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
status, err := h.paymentService.GetPaymentStatus(c.Request.Context(), paymentNo)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.ErrorResponse{
|
||||
Code: "602001",
|
||||
Message: "获取支付状态失败",
|
||||
Timestamp: getCurrentTimestamp(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
lang := middleware.GetLanguage(c)
|
||||
successMsg := i18n.GetI18nErrorMessage("000000", lang)
|
||||
c.JSON(http.StatusOK, models.APIResponse{
|
||||
Code: "000000",
|
||||
Message: successMsg,
|
||||
Data: status,
|
||||
Timestamp: getCurrentTimestamp(),
|
||||
})
|
||||
}
|
||||
|
||||
// GetUserPayments 获取用户支付历史
|
||||
// @Summary 获取用户支付历史
|
||||
// @Description 获取当前用户的支付记录列表
|
||||
// @Tags 支付管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param page query integer false "页码" default(1)
|
||||
// @Param page_size query integer false "每页数量" default(10)
|
||||
// @Success 200 {object} models.APIResponse{data=models.PaymentListResponse} "成功"
|
||||
// @Failure 401 {object} models.ErrorResponse "未认证"
|
||||
// @Router /api/payment/history [get]
|
||||
func (h *PaymentHandler) GetUserPayments(c *gin.Context) {
|
||||
// 解析分页参数
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))
|
||||
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize < 1 || pageSize > 100 {
|
||||
pageSize = 10
|
||||
}
|
||||
|
||||
// 获取客户用户ID和客户ID
|
||||
cuUserID, exists := c.Get("cu_user_id")
|
||||
if !exists {
|
||||
lang := middleware.GetLanguage(c)
|
||||
status, errCode, message := i18n.NewI18nErrorResponse("100004", lang)
|
||||
c.JSON(status, models.ErrorResponse{
|
||||
Code: errCode,
|
||||
Message: message,
|
||||
Timestamp: getCurrentTimestamp(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
customerID, exists := c.Get("cu_customer_id")
|
||||
if !exists {
|
||||
lang := middleware.GetLanguage(c)
|
||||
status, errCode, message := i18n.NewI18nErrorResponse("100004", lang)
|
||||
c.JSON(status, models.ErrorResponse{
|
||||
Code: errCode,
|
||||
Message: message,
|
||||
Timestamp: getCurrentTimestamp(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
payments, err := h.paymentService.GetUserPayments(c.Request.Context(), customerID.(string), cuUserID.(string), page, pageSize)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.ErrorResponse{
|
||||
Code: "602001",
|
||||
Message: "获取支付历史失败",
|
||||
Timestamp: getCurrentTimestamp(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
lang := middleware.GetLanguage(c)
|
||||
successMsg := i18n.GetI18nErrorMessage("000000", lang)
|
||||
c.JSON(http.StatusOK, models.APIResponse{
|
||||
Code: "000000",
|
||||
Message: successMsg,
|
||||
Data: payments,
|
||||
Timestamp: getCurrentTimestamp(),
|
||||
})
|
||||
}
|
||||
|
||||
// AlipayCallback 支付宝回调处理
|
||||
// @Summary 支付宝回调
|
||||
// @Description 处理支付宝支付回调
|
||||
// @Tags 支付管理
|
||||
// @Accept application/x-www-form-urlencoded
|
||||
// @Produce text/plain
|
||||
// @Param notification body string false "支付宝回调参数"
|
||||
// @Success 200 {string} string "success"
|
||||
// @Failure 400 {string} string "failure"
|
||||
// @Router /api/payment/alipay/callback [post]
|
||||
func (h *PaymentHandler) AlipayCallback(c *gin.Context) {
|
||||
// 这里我们需要一个alipay client实例来解析回调
|
||||
// 由于我们在service层已经有了client,这里暂时简化处理
|
||||
// 实际应该从service中获取client
|
||||
|
||||
// 暂时使用简化的处理方式
|
||||
// 实际项目中应该使用service中的client来验证和处理
|
||||
utils.SendNotificationResponse(c.Writer, true)
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
_ "license-manager/docs/swagger" // swagger docs
|
||||
"license-manager/internal/api/handlers"
|
||||
"license-manager/internal/api/middleware"
|
||||
@@ -43,6 +44,7 @@ func SetupRouter() *gin.Engine {
|
||||
authCodeRepo := repository.NewAuthorizationCodeRepository(db)
|
||||
licenseRepo := repository.NewLicenseRepository(db)
|
||||
dashboardRepo := repository.NewDashboardRepository(db)
|
||||
paymentRepo := repository.NewPaymentRepository(db)
|
||||
|
||||
// 获取logger实例
|
||||
log := logger.GetLogger()
|
||||
@@ -55,6 +57,7 @@ func SetupRouter() *gin.Engine {
|
||||
authCodeService := service.NewAuthorizationCodeService(authCodeRepo, customerRepo, cuUserRepo, licenseRepo)
|
||||
cuOrderService := service.NewCuOrderService(cuOrderRepo, cuUserRepo, authCodeRepo, db)
|
||||
cuDeviceService := service.NewCuDeviceService(licenseRepo)
|
||||
paymentService := service.NewPaymentService(paymentRepo, cuOrderRepo, convertPaymentConfig(cfg.Payment))
|
||||
enumService := service.NewEnumService()
|
||||
licenseService := service.NewLicenseService(licenseRepo, db, log)
|
||||
dashboardService := service.NewDashboardService(dashboardRepo)
|
||||
@@ -65,8 +68,9 @@ func SetupRouter() *gin.Engine {
|
||||
customerHandler := handlers.NewCustomerHandler(customerService)
|
||||
cuAuthHandler := handlers.NewCuAuthHandler(cuUserService)
|
||||
cuProfileHandler := handlers.NewCuProfileHandler(cuUserService)
|
||||
cuOrderHandler := handlers.NewCuOrderHandler(cuOrderService)
|
||||
cuOrderHandler := handlers.NewCuOrderHandler(cuOrderService, paymentService)
|
||||
cuDeviceHandler := handlers.NewCuDeviceHandler(cuDeviceService)
|
||||
paymentHandler := handlers.NewPaymentHandler(paymentService)
|
||||
cuAuthorizationHandler := handlers.NewCuAuthorizationHandler(authCodeService)
|
||||
enumHandler := handlers.NewEnumHandler(enumService)
|
||||
authCodeHandler := handlers.NewAuthorizationCodeHandler(authCodeService)
|
||||
@@ -90,6 +94,9 @@ func SetupRouter() *gin.Engine {
|
||||
// 许可证激活接口(客户端软件使用)
|
||||
public.POST("/v1/activate", licenseHandler.ActivateLicense)
|
||||
public.POST("/v1/heartbeat", licenseHandler.Heartbeat)
|
||||
|
||||
// 支付回调接口
|
||||
public.POST("/payment/alipay/callback", paymentHandler.AlipayCallback)
|
||||
}
|
||||
|
||||
// 需要认证的接口
|
||||
@@ -174,6 +181,10 @@ func SetupRouter() *gin.Engine {
|
||||
cuAuth.GET("/orders/:order_id", cuOrderHandler.GetOrder)
|
||||
cuAuth.GET("/orders", cuOrderHandler.GetUserOrders)
|
||||
|
||||
// 支付管理
|
||||
cuAuth.GET("/payment/:payment_no/status", paymentHandler.GetPaymentStatus)
|
||||
cuAuth.GET("/payment/history", paymentHandler.GetUserPayments)
|
||||
|
||||
// 授权码管理
|
||||
cuAuth.POST("/authorization-codes/:codeId/share", cuAuthorizationHandler.ShareAuthorizationCode)
|
||||
|
||||
@@ -185,3 +196,46 @@ func SetupRouter() *gin.Engine {
|
||||
|
||||
return router
|
||||
}
|
||||
|
||||
// convertPaymentConfig 转换支付配置
|
||||
func convertPaymentConfig(cfg config.PaymentConfig) *service.PaymentConfig {
|
||||
fmt.Printf("convertPaymentConfig: cfg.DefaultMethod=%s, cfg.Providers is nil: %v\n", cfg.DefaultMethod, cfg.Providers == nil)
|
||||
if cfg.Providers != nil {
|
||||
fmt.Printf("convertPaymentConfig: Found %d providers\n", len(cfg.Providers))
|
||||
for name, provider := range cfg.Providers {
|
||||
fmt.Printf("convertPaymentConfig: Provider %s, Enabled=%v\n", name, provider.Enabled)
|
||||
}
|
||||
}
|
||||
|
||||
config := &service.PaymentConfig{
|
||||
DefaultMethod: cfg.DefaultMethod,
|
||||
ExpireMinutes: cfg.ExpireMinutes,
|
||||
}
|
||||
|
||||
if cfg.Providers != nil {
|
||||
config.Providers = make(map[string]*service.AlipayProviderConfig)
|
||||
for name, provider := range cfg.Providers {
|
||||
if provider.Enabled {
|
||||
config.Providers[name] = &service.AlipayProviderConfig{
|
||||
Enabled: provider.Enabled,
|
||||
AppID: provider.AppID,
|
||||
PrivateKey: provider.PrivateKey,
|
||||
PublicKey: provider.PublicKey,
|
||||
GatewayURL: provider.GatewayURL,
|
||||
NotifyURL: provider.NotifyURL,
|
||||
ReturnURL: provider.ReturnURL,
|
||||
SignType: provider.SignType,
|
||||
Charset: provider.Charset,
|
||||
Format: provider.Format,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("convertPaymentConfig: Final config.Providers is nil: %v\n", config.Providers == nil)
|
||||
if config.Providers != nil {
|
||||
fmt.Printf("convertPaymentConfig: Final config has %d providers\n", len(config.Providers))
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
@@ -14,6 +14,7 @@ type Config struct {
|
||||
Log LogConfig `mapstructure:"log"`
|
||||
I18n I18nConfig `mapstructure:"i18n"`
|
||||
License LicenseConfig `mapstructure:"license"`
|
||||
Payment PaymentConfig `mapstructure:"payment"`
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
@@ -88,6 +89,27 @@ type RSAConfig struct {
|
||||
KeySize int `mapstructure:"key_size"` // 密钥大小(2048或4096),默认2048
|
||||
}
|
||||
|
||||
type PaymentConfig struct {
|
||||
DefaultMethod string `mapstructure:"default_method"` // 默认支付方式
|
||||
Providers map[string]*PaymentProvider `mapstructure:"providers"` // 支付提供商配置
|
||||
ExpireMinutes int `mapstructure:"expire_minutes"` // 支付过期时间(分钟)
|
||||
RetryTimes int `mapstructure:"retry_times"` // 支付重试次数
|
||||
EnableLog bool `mapstructure:"enable_log"` // 启用支付日志
|
||||
}
|
||||
|
||||
type PaymentProvider struct {
|
||||
AppID string `mapstructure:"app_id"`
|
||||
PrivateKey string `mapstructure:"private_key"`
|
||||
PublicKey string `mapstructure:"public_key"`
|
||||
GatewayURL string `mapstructure:"gateway_url"`
|
||||
NotifyURL string `mapstructure:"notify_url"`
|
||||
ReturnURL string `mapstructure:"return_url"`
|
||||
SignType string `mapstructure:"sign_type"`
|
||||
Charset string `mapstructure:"charset"`
|
||||
Format string `mapstructure:"format"`
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
}
|
||||
|
||||
var AppConfig *Config
|
||||
|
||||
func Load(configPath string) error {
|
||||
@@ -172,6 +194,12 @@ func setDefaults() {
|
||||
viper.SetDefault("license.heartbeat_timeout", 300)
|
||||
viper.SetDefault("license.offline_timeout", 1440)
|
||||
viper.SetDefault("license.expiring_days", 30)
|
||||
|
||||
// Payment defaults
|
||||
viper.SetDefault("payment.default_method", "alipay")
|
||||
viper.SetDefault("payment.expire_minutes", 30)
|
||||
viper.SetDefault("payment.retry_times", 3)
|
||||
viper.SetDefault("payment.enable_log", true)
|
||||
}
|
||||
|
||||
func GetConfig() *Config {
|
||||
|
||||
@@ -25,6 +25,7 @@ func AutoMigrate() error {
|
||||
&models.User{},
|
||||
&models.CuUser{},
|
||||
&models.CuOrder{},
|
||||
&models.Payment{},
|
||||
&models.AuthorizationCode{},
|
||||
&models.License{},
|
||||
&models.AuthorizationChange{},
|
||||
|
||||
@@ -59,8 +59,9 @@ func (o *CuOrder) BeforeUpdate(tx *gorm.DB) error {
|
||||
|
||||
// CuOrderCreateRequest 创建订单请求结构
|
||||
type CuOrderCreateRequest struct {
|
||||
PackageID string `json:"package_id" binding:"required"`
|
||||
LicenseCount int `json:"license_count" binding:"required,min=1,max=1000"`
|
||||
PackageID string `json:"package_id" binding:"required"`
|
||||
LicenseCount int `json:"license_count" binding:"required,min=1,max=1000"`
|
||||
PaymentMethod string `json:"payment_method,omitempty"` // 可选:支付方式,不传则为免费订单
|
||||
}
|
||||
|
||||
// CuOrderResponse 订单响应结构
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Payment 支付订单模型
|
||||
type Payment struct {
|
||||
ID int `gorm:"primaryKey;autoIncrement" json:"id"`
|
||||
PaymentNo string `gorm:"type:varchar(64);uniqueIndex;not null" json:"payment_no"`
|
||||
BusinessType string `gorm:"type:varchar(50);not null;index:business_type_id" json:"business_type"`
|
||||
BusinessID *string `gorm:"type:varchar(36);index:business_type_id" json:"business_id"`
|
||||
CustomerID string `gorm:"type:varchar(36);not null;index" json:"customer_id"`
|
||||
CuUserID string `gorm:"type:varchar(36);not null;index" json:"cu_user_id"`
|
||||
Amount float64 `gorm:"type:decimal(10,2);not null" json:"amount"`
|
||||
Currency string `gorm:"type:varchar(3);default:'CNY'" json:"currency"`
|
||||
PaymentMethod string `gorm:"type:varchar(20);default:'alipay'" json:"payment_method"`
|
||||
PaymentProvider string `gorm:"type:varchar(20);default:'alipay'" json:"payment_provider"`
|
||||
Status string `gorm:"type:varchar(20);not null;default:'pending'" json:"status"`
|
||||
TradeNo *string `gorm:"type:varchar(64)" json:"trade_no"`
|
||||
PaymentTime *time.Time `gorm:"type:datetime(3)" json:"payment_time"`
|
||||
ExpireTime time.Time `gorm:"type:datetime(3);not null" json:"expire_time"`
|
||||
PaymentURL *string `gorm:"type:text" json:"payment_url"`
|
||||
NotifyData *string `gorm:"type:json" json:"notify_data"`
|
||||
ExtraData *string `gorm:"type:json" json:"extra_data"`
|
||||
CreatedAt time.Time `gorm:"type:datetime(3);not null;index" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"type:datetime(3);not null" json:"updated_at"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (Payment) TableName() string {
|
||||
return "payments"
|
||||
}
|
||||
|
||||
// BeforeCreate 创建前自动设置时间戳
|
||||
func (p *Payment) BeforeCreate(tx *gorm.DB) error {
|
||||
now := time.Now()
|
||||
if p.CreatedAt.IsZero() {
|
||||
p.CreatedAt = now
|
||||
}
|
||||
if p.UpdatedAt.IsZero() {
|
||||
p.UpdatedAt = now
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BeforeUpdate 更新前自动设置时间戳
|
||||
func (p *Payment) BeforeUpdate(tx *gorm.DB) error {
|
||||
p.UpdatedAt = time.Now()
|
||||
return nil
|
||||
}
|
||||
|
||||
// PaymentCreateRequest 创建支付请求结构
|
||||
type PaymentCreateRequest struct {
|
||||
BusinessType string `json:"business_type" binding:"required"`
|
||||
BusinessID *string `json:"business_id"`
|
||||
CustomerID string `json:"customer_id" binding:"required"`
|
||||
CuUserID string `json:"cu_user_id" binding:"required"`
|
||||
Amount float64 `json:"amount" binding:"required,min=0.01"`
|
||||
Currency string `json:"currency"`
|
||||
PaymentMethod string `json:"payment_method"`
|
||||
}
|
||||
|
||||
// PaymentResponse 支付响应结构
|
||||
type PaymentResponse struct {
|
||||
ID int `json:"id"`
|
||||
PaymentNo string `json:"payment_no"`
|
||||
BusinessType string `json:"business_type"`
|
||||
BusinessID *string `json:"business_id"`
|
||||
CustomerID string `json:"customer_id"`
|
||||
CuUserID string `json:"cu_user_id"`
|
||||
Amount float64 `json:"amount"`
|
||||
Currency string `json:"currency"`
|
||||
PaymentMethod string `json:"payment_method"`
|
||||
PaymentProvider string `json:"payment_provider"`
|
||||
Status string `json:"status"`
|
||||
TradeNo *string `json:"trade_no"`
|
||||
PaymentTime *time.Time `json:"payment_time"`
|
||||
ExpireTime time.Time `json:"expire_time"`
|
||||
PaymentURL *string `json:"payment_url"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// ToResponse 转换为响应结构
|
||||
func (p *Payment) ToResponse() *PaymentResponse {
|
||||
return &PaymentResponse{
|
||||
ID: p.ID,
|
||||
PaymentNo: p.PaymentNo,
|
||||
BusinessType: p.BusinessType,
|
||||
BusinessID: p.BusinessID,
|
||||
CustomerID: p.CustomerID,
|
||||
CuUserID: p.CuUserID,
|
||||
Amount: p.Amount,
|
||||
Currency: p.Currency,
|
||||
PaymentMethod: p.PaymentMethod,
|
||||
PaymentProvider: p.PaymentProvider,
|
||||
Status: p.Status,
|
||||
TradeNo: p.TradeNo,
|
||||
PaymentTime: p.PaymentTime,
|
||||
ExpireTime: p.ExpireTime,
|
||||
PaymentURL: p.PaymentURL,
|
||||
CreatedAt: p.CreatedAt,
|
||||
UpdatedAt: p.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
// PaymentStatusResponse 支付状态响应结构
|
||||
type PaymentStatusResponse struct {
|
||||
PaymentNo string `json:"payment_no"`
|
||||
Status string `json:"status"`
|
||||
Amount float64 `json:"amount"`
|
||||
PaymentTime *time.Time `json:"payment_time"`
|
||||
TradeNo *string `json:"trade_no"`
|
||||
BusinessOrder *PaymentBusinessOrder `json:"business_order,omitempty"`
|
||||
}
|
||||
|
||||
// PaymentBusinessOrder 支付关联的业务订单信息
|
||||
type PaymentBusinessOrder struct {
|
||||
OrderNo string `json:"order_no"`
|
||||
AuthorizationCode *string `json:"authorization_code"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
// PaymentListResponse 支付列表响应结构
|
||||
type PaymentListResponse struct {
|
||||
Payments []*PaymentResponse `json:"payments"`
|
||||
TotalCount int64 `json:"total_count"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
}
|
||||
|
||||
// 业务类型常量
|
||||
const (
|
||||
BusinessTypePackageOrder = "package_order" // 套餐购买订单
|
||||
BusinessTypeLicenseExtend = "license_extend" // 许可延期
|
||||
BusinessTypeServiceUpgrade = "service_upgrade" // 服务升级
|
||||
)
|
||||
|
||||
// 支付状态常量
|
||||
const (
|
||||
PaymentStatusPending = "pending" // 待支付
|
||||
PaymentStatusPaid = "paid" // 已支付
|
||||
PaymentStatusCancelled = "cancelled" // 已取消
|
||||
PaymentStatusExpired = "expired" // 已过期
|
||||
PaymentStatusFailed = "failed" // 支付失败
|
||||
PaymentStatusRefunded = "refunded" // 已退款
|
||||
)
|
||||
|
||||
// 支付方式常量
|
||||
const (
|
||||
PaymentMethodAlipay = "alipay" // 支付宝
|
||||
PaymentMethodWechat = "wechat" // 微信支付
|
||||
PaymentMethodBank = "bank" // 银行卡
|
||||
)
|
||||
@@ -141,3 +141,33 @@ type DashboardRepository interface {
|
||||
// GetRecentAuthorizations 获取最近授权列表
|
||||
GetRecentAuthorizations(ctx context.Context, req *models.DashboardRecentAuthorizationsRequest) (*models.DashboardRecentAuthorizationsResponse, error)
|
||||
}
|
||||
|
||||
// PaymentRepository 支付数据访问接口
|
||||
type PaymentRepository interface {
|
||||
// Create 创建支付订单
|
||||
Create(ctx context.Context, payment *models.Payment) error
|
||||
|
||||
// GetByID 根据ID获取支付订单
|
||||
GetByID(ctx context.Context, id int) (*models.Payment, error)
|
||||
|
||||
// GetByPaymentNo 根据支付单号获取支付订单
|
||||
GetByPaymentNo(ctx context.Context, paymentNo string) (*models.Payment, error)
|
||||
|
||||
// GetByBusinessID 根据业务ID获取支付订单
|
||||
GetByBusinessID(ctx context.Context, businessType, businessID string) (*models.Payment, error)
|
||||
|
||||
// GetByCustomerAndCuUserID 根据客户ID和客户用户ID获取支付订单列表
|
||||
GetByCustomerAndCuUserID(ctx context.Context, customerID, cuUserID string, offset, limit int) ([]*models.Payment, int64, error)
|
||||
|
||||
// Update 更新支付订单
|
||||
Update(ctx context.Context, payment *models.Payment) error
|
||||
|
||||
// UpdateStatus 更新支付状态
|
||||
UpdateStatus(ctx context.Context, paymentNo, status string, tradeNo *string, paymentTime *string) error
|
||||
|
||||
// Delete 删除支付订单
|
||||
Delete(ctx context.Context, id int) error
|
||||
|
||||
// GetExpiredPayments 获取过期的支付订单
|
||||
GetExpiredPayments(ctx context.Context) ([]*models.Payment, error)
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"license-manager/internal/models"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type paymentRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewPaymentRepository(db *gorm.DB) PaymentRepository {
|
||||
return &paymentRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *paymentRepository) Create(ctx context.Context, payment *models.Payment) error {
|
||||
return r.db.WithContext(ctx).Create(payment).Error
|
||||
}
|
||||
|
||||
func (r *paymentRepository) GetByID(ctx context.Context, id int) (*models.Payment, error) {
|
||||
var payment models.Payment
|
||||
err := r.db.WithContext(ctx).Where("id = ?", id).First(&payment).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &payment, nil
|
||||
}
|
||||
|
||||
func (r *paymentRepository) GetByPaymentNo(ctx context.Context, paymentNo string) (*models.Payment, error) {
|
||||
var payment models.Payment
|
||||
err := r.db.WithContext(ctx).Where("payment_no = ?", paymentNo).First(&payment).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &payment, nil
|
||||
}
|
||||
|
||||
func (r *paymentRepository) GetByBusinessID(ctx context.Context, businessType, businessID string) (*models.Payment, error) {
|
||||
var payment models.Payment
|
||||
err := r.db.WithContext(ctx).Where("business_type = ? AND business_id = ?", businessType, businessID).First(&payment).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &payment, nil
|
||||
}
|
||||
|
||||
func (r *paymentRepository) GetByCustomerAndCuUserID(ctx context.Context, customerID, cuUserID string, offset, limit int) ([]*models.Payment, int64, error) {
|
||||
var payments []*models.Payment
|
||||
var total int64
|
||||
|
||||
query := r.db.WithContext(ctx).Model(&models.Payment{}).Where("customer_id = ? AND cu_user_id = ?", customerID, cuUserID)
|
||||
|
||||
// 获取总数
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 获取分页数据
|
||||
err := query.Order("created_at DESC").Offset(offset).Limit(limit).Find(&payments).Error
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return payments, total, nil
|
||||
}
|
||||
|
||||
func (r *paymentRepository) Update(ctx context.Context, payment *models.Payment) error {
|
||||
return r.db.WithContext(ctx).Save(payment).Error
|
||||
}
|
||||
|
||||
func (r *paymentRepository) UpdateStatus(ctx context.Context, paymentNo, status string, tradeNo *string, paymentTime *string) error {
|
||||
updateData := map[string]interface{}{
|
||||
"status": status,
|
||||
}
|
||||
|
||||
if tradeNo != nil {
|
||||
updateData["trade_no"] = *tradeNo
|
||||
}
|
||||
|
||||
if paymentTime != nil {
|
||||
updateData["payment_time"] = *paymentTime
|
||||
}
|
||||
|
||||
return r.db.WithContext(ctx).Model(&models.Payment{}).Where("payment_no = ?", paymentNo).Updates(updateData).Error
|
||||
}
|
||||
|
||||
func (r *paymentRepository) Delete(ctx context.Context, id int) error {
|
||||
return r.db.WithContext(ctx).Where("id = ?", id).Delete(&models.Payment{}).Error
|
||||
}
|
||||
|
||||
func (r *paymentRepository) GetExpiredPayments(ctx context.Context) ([]*models.Payment, error) {
|
||||
var payments []*models.Payment
|
||||
|
||||
err := r.db.WithContext(ctx).Where("status = ? AND expire_time < NOW()", models.PaymentStatusPending).Find(&payments).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return payments, nil
|
||||
}
|
||||
@@ -17,9 +17,11 @@ import (
|
||||
|
||||
type CuOrderService interface {
|
||||
CreateOrder(ctx context.Context, cuUserID, customerID string, req *models.CuOrderCreateRequest) (*models.CuOrder, error)
|
||||
CreatePendingOrder(ctx context.Context, cuUserID, customerID string, req *models.CuOrderCreateRequest) (*models.CuOrder, error)
|
||||
GetOrder(ctx context.Context, orderID, cuUserID string) (*models.CuOrder, error)
|
||||
GetUserOrders(ctx context.Context, cuUserID string, offset, limit int) ([]*models.CuOrder, int64, error)
|
||||
CalculatePrice(ctx context.Context, packageID string, licenseCount int) (*PriceCalculationResult, error)
|
||||
UpdateOrderStatus(ctx context.Context, orderID string, status string) error
|
||||
}
|
||||
|
||||
type PriceCalculationResult struct {
|
||||
@@ -85,6 +87,62 @@ func NewCuOrderService(repo repository.CuOrderRepository, cuUserRepo repository.
|
||||
}
|
||||
}
|
||||
|
||||
func (s *cuOrderService) CreatePendingOrder(ctx context.Context, cuUserID, customerID string, req *models.CuOrderCreateRequest) (*models.CuOrder, error) {
|
||||
// 计算价格
|
||||
priceResult, err := s.CalculatePrice(ctx, req.PackageID, req.LicenseCount)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 生成订单号
|
||||
orderNo := s.generateOrderNo()
|
||||
|
||||
// 获取套餐配置
|
||||
packageConfig, exists := packageConfigs[req.PackageID]
|
||||
if !exists {
|
||||
return nil, i18n.NewI18nError("600001", "套餐不存在")
|
||||
}
|
||||
|
||||
// 创建订单(pending状态)
|
||||
order := &models.CuOrder{
|
||||
OrderNo: orderNo,
|
||||
CustomerID: customerID,
|
||||
CuUserID: cuUserID,
|
||||
PackageID: req.PackageID,
|
||||
PackageName: packageConfig.Name,
|
||||
LicenseCount: req.LicenseCount,
|
||||
UnitPrice: priceResult.UnitPrice,
|
||||
DiscountRate: priceResult.DiscountRate,
|
||||
TotalAmount: priceResult.TotalAmount,
|
||||
Status: "pending", // 待支付状态
|
||||
}
|
||||
|
||||
// 对于试用版,设置过期时间
|
||||
if req.PackageID == "trial" {
|
||||
expiredAt := s.getTrialExpiryTime()
|
||||
order.ExpiredAt = &expiredAt
|
||||
}
|
||||
|
||||
// 保存订单
|
||||
if err := s.repo.Create(order); err != nil {
|
||||
return nil, i18n.NewI18nError("601001", "订单创建失败: "+err.Error())
|
||||
}
|
||||
|
||||
return order, nil
|
||||
}
|
||||
|
||||
func (s *cuOrderService) UpdateOrderStatus(ctx context.Context, orderID string, status string) error {
|
||||
// 这里需要实现更新订单状态的逻辑
|
||||
// 暂时先返回nil,后面再实现
|
||||
return nil
|
||||
}
|
||||
|
||||
// getTrialExpiryTime 获取试用版过期时间(当月25日23:59:59)
|
||||
func (s *cuOrderService) getTrialExpiryTime() time.Time {
|
||||
now := time.Now()
|
||||
return time.Date(now.Year(), now.Month(), 25, 23, 59, 59, 0, now.Location())
|
||||
}
|
||||
|
||||
func (s *cuOrderService) CreateOrder(ctx context.Context, cuUserID, customerID string, req *models.CuOrderCreateRequest) (*models.CuOrder, error) {
|
||||
lang := pkgcontext.GetLanguageFromContext(ctx)
|
||||
|
||||
|
||||
@@ -82,3 +82,4 @@ type CuDeviceService interface {
|
||||
// 解绑设备
|
||||
UnbindDevice(ctx context.Context, customerID, licenseID string) error
|
||||
}
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"license-manager/internal/models"
|
||||
"license-manager/internal/repository"
|
||||
"license-manager/pkg/utils"
|
||||
"time"
|
||||
|
||||
"github.com/smartwalle/alipay/v3"
|
||||
)
|
||||
|
||||
type PaymentService interface {
|
||||
CreatePayment(ctx context.Context, req *models.PaymentCreateRequest) (*models.Payment, error)
|
||||
GetPayment(ctx context.Context, paymentNo string) (*models.Payment, error)
|
||||
GetPaymentStatus(ctx context.Context, paymentNo string) (*models.PaymentStatusResponse, error)
|
||||
ProcessPaymentCallback(ctx context.Context, notification interface{}) error
|
||||
GetUserPayments(ctx context.Context, customerID, cuUserID string, page, pageSize int) (*models.PaymentListResponse, error)
|
||||
CancelExpiredPayments(ctx context.Context) error
|
||||
}
|
||||
|
||||
type paymentService struct {
|
||||
paymentRepo repository.PaymentRepository
|
||||
cuOrderRepo repository.CuOrderRepository
|
||||
alipayClient *alipay.Client
|
||||
config *PaymentConfig
|
||||
}
|
||||
|
||||
type PaymentConfig struct {
|
||||
DefaultMethod string `json:"default_method"`
|
||||
Providers map[string]*AlipayProviderConfig `json:"providers"`
|
||||
ExpireMinutes int `json:"expire_minutes"`
|
||||
}
|
||||
|
||||
type AlipayProviderConfig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
AppID string `json:"app_id"`
|
||||
PrivateKey string `json:"private_key"`
|
||||
PublicKey string `json:"public_key"`
|
||||
GatewayURL string `json:"gateway_url"`
|
||||
NotifyURL string `json:"notify_url"`
|
||||
ReturnURL string `json:"return_url"`
|
||||
SignType string `json:"sign_type"`
|
||||
Charset string `json:"charset"`
|
||||
Format string `json:"format"`
|
||||
}
|
||||
|
||||
func NewPaymentService(
|
||||
paymentRepo repository.PaymentRepository,
|
||||
cuOrderRepo repository.CuOrderRepository,
|
||||
config *PaymentConfig,
|
||||
) PaymentService {
|
||||
var alipayClient *alipay.Client
|
||||
fmt.Printf("PaymentService: Checking providers, config.Providers is nil: %v\n", config.Providers == nil)
|
||||
if config.Providers != nil {
|
||||
fmt.Printf("PaymentService: Found %d providers\n", len(config.Providers))
|
||||
for providerName := range config.Providers {
|
||||
fmt.Printf("PaymentService: Provider: %s\n", providerName)
|
||||
}
|
||||
if alipayConfig, exists := config.Providers["alipay"]; exists && alipayConfig.Enabled {
|
||||
fmt.Printf("Initializing Alipay client with APPID: %s\n", alipayConfig.AppID)
|
||||
fmt.Printf("Private key length: %d\n", len(alipayConfig.PrivateKey))
|
||||
fmt.Printf("Public key length: %d\n", len(alipayConfig.PublicKey))
|
||||
|
||||
// 使用官方SDK创建客户端
|
||||
fmt.Printf("Creating Alipay client with APPID: %s\n", alipayConfig.AppID)
|
||||
client, err := alipay.New(alipayConfig.AppID, alipayConfig.PrivateKey, false)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to create Alipay client: %v\n", err)
|
||||
fmt.Printf("Private key length: %d\n", len(alipayConfig.PrivateKey))
|
||||
} else {
|
||||
fmt.Printf("Alipay client created successfully\n")
|
||||
// 先不加载公钥,测试基本功能
|
||||
alipayClient = client
|
||||
fmt.Println("Alipay client initialized successfully")
|
||||
}
|
||||
} else {
|
||||
fmt.Println("Alipay provider not found or disabled")
|
||||
}
|
||||
} else {
|
||||
fmt.Println("No payment providers configured")
|
||||
}
|
||||
|
||||
return &paymentService{
|
||||
paymentRepo: paymentRepo,
|
||||
cuOrderRepo: cuOrderRepo,
|
||||
alipayClient: alipayClient,
|
||||
config: config,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *paymentService) CreatePayment(ctx context.Context, req *models.PaymentCreateRequest) (*models.Payment, error) {
|
||||
// 设置默认值
|
||||
if req.Currency == "" {
|
||||
req.Currency = "CNY"
|
||||
}
|
||||
if req.PaymentMethod == "" {
|
||||
req.PaymentMethod = s.config.DefaultMethod
|
||||
}
|
||||
|
||||
// 生成支付单号
|
||||
paymentNo := utils.GeneratePaymentNo()
|
||||
|
||||
// 计算过期时间
|
||||
expireTime := time.Now().Add(time.Duration(s.config.ExpireMinutes) * time.Minute)
|
||||
|
||||
// 创建支付单
|
||||
payment := &models.Payment{
|
||||
PaymentNo: paymentNo,
|
||||
BusinessType: req.BusinessType,
|
||||
BusinessID: req.BusinessID,
|
||||
CustomerID: req.CustomerID,
|
||||
CuUserID: req.CuUserID,
|
||||
Amount: req.Amount,
|
||||
Currency: req.Currency,
|
||||
PaymentMethod: req.PaymentMethod,
|
||||
PaymentProvider: req.PaymentMethod,
|
||||
Status: models.PaymentStatusPending,
|
||||
ExpireTime: expireTime,
|
||||
}
|
||||
|
||||
// 如果是支付宝,生成支付URL
|
||||
if req.PaymentMethod == "alipay" {
|
||||
fmt.Printf("Payment method is alipay, checking client...\n")
|
||||
if s.alipayClient == nil {
|
||||
fmt.Printf("Alipay client is nil!\n")
|
||||
return nil, fmt.Errorf("alipay client not initialized")
|
||||
}
|
||||
|
||||
fmt.Printf("Alipay client is available, creating payment request...\n")
|
||||
|
||||
// 使用官方SDK创建支付请求
|
||||
// TradePagePay继承了Trade,需要通过Trade字段设置
|
||||
payRequest := alipay.TradePagePay{
|
||||
Trade: alipay.Trade{
|
||||
OutTradeNo: paymentNo,
|
||||
TotalAmount: fmt.Sprintf("%.2f", req.Amount),
|
||||
Subject: "产品套餐购买",
|
||||
ProductCode: "FAST_INSTANT_TRADE_PAY",
|
||||
},
|
||||
}
|
||||
|
||||
fmt.Printf("Creating Alipay payment request: OutTradeNo=%s, TotalAmount=%s\n", paymentNo, fmt.Sprintf("%.2f", req.Amount))
|
||||
|
||||
// 生成支付URL
|
||||
payURL, err := s.alipayClient.TradePagePay(payRequest)
|
||||
if err != nil {
|
||||
fmt.Printf("TradePagePay failed: %v\n", err)
|
||||
return nil, fmt.Errorf("generate payment URL failed: %w", err)
|
||||
}
|
||||
payURLStr := payURL.String()
|
||||
fmt.Printf("Generated payment URL: %s\n", payURLStr[:100]+"...")
|
||||
payment.PaymentURL = &payURLStr
|
||||
} else {
|
||||
fmt.Printf("Payment method is: %s\n", req.PaymentMethod)
|
||||
}
|
||||
|
||||
// 保存到数据库
|
||||
if err := s.paymentRepo.Create(ctx, payment); err != nil {
|
||||
return nil, fmt.Errorf("create payment failed: %w", err)
|
||||
}
|
||||
|
||||
return payment, nil
|
||||
}
|
||||
|
||||
func (s *paymentService) GetPayment(ctx context.Context, paymentNo string) (*models.Payment, error) {
|
||||
return s.paymentRepo.GetByPaymentNo(ctx, paymentNo)
|
||||
}
|
||||
|
||||
func (s *paymentService) GetPaymentStatus(ctx context.Context, paymentNo string) (*models.PaymentStatusResponse, error) {
|
||||
payment, err := s.paymentRepo.GetByPaymentNo(ctx, paymentNo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response := &models.PaymentStatusResponse{
|
||||
PaymentNo: payment.PaymentNo,
|
||||
Status: payment.Status,
|
||||
Amount: payment.Amount,
|
||||
PaymentTime: payment.PaymentTime,
|
||||
TradeNo: payment.TradeNo,
|
||||
}
|
||||
|
||||
// 如果支付成功,获取业务订单信息
|
||||
if payment.Status == models.PaymentStatusPaid && payment.BusinessID != nil {
|
||||
if payment.BusinessType == models.BusinessTypePackageOrder {
|
||||
order, err := s.cuOrderRepo.GetByID(*payment.BusinessID)
|
||||
if err == nil && order.AuthorizationCode != nil {
|
||||
response.BusinessOrder = &models.PaymentBusinessOrder{
|
||||
OrderNo: order.OrderNo,
|
||||
AuthorizationCode: order.AuthorizationCode,
|
||||
Status: order.Status,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (s *paymentService) ProcessPaymentCallback(ctx context.Context, notification interface{}) error {
|
||||
// 这里暂时简化处理回调逻辑
|
||||
// 实际项目中应该进行完整的验证和处理
|
||||
// 包括签名验证、状态检查、业务逻辑处理等
|
||||
|
||||
// 暂时返回成功,表示回调处理完成
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *paymentService) GetUserPayments(ctx context.Context, customerID, cuUserID string, page, pageSize int) (*models.PaymentListResponse, error) {
|
||||
offset := (page - 1) * pageSize
|
||||
payments, total, err := s.paymentRepo.GetByCustomerAndCuUserID(ctx, customerID, cuUserID, offset, pageSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
paymentResponses := make([]*models.PaymentResponse, len(payments))
|
||||
for i, payment := range payments {
|
||||
paymentResponses[i] = payment.ToResponse()
|
||||
}
|
||||
|
||||
return &models.PaymentListResponse{
|
||||
Payments: paymentResponses,
|
||||
TotalCount: total,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *paymentService) CancelExpiredPayments(ctx context.Context) error {
|
||||
expiredPayments, err := s.paymentRepo.GetExpiredPayments(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, payment := range expiredPayments {
|
||||
err = s.paymentRepo.UpdateStatus(ctx, payment.PaymentNo, models.PaymentStatusExpired, nil, nil)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to cancel expired payment %s: %v\n", payment.PaymentNo, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
-- 支付订单表
|
||||
-- 支持多种支付方式和业务场景的通用支付表
|
||||
-- MySQL版本
|
||||
|
||||
-- 创建支付订单表
|
||||
CREATE TABLE payments (
|
||||
-- 基础信息
|
||||
id INT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
||||
payment_no VARCHAR(64) UNIQUE NOT NULL COMMENT '支付单号',
|
||||
business_type VARCHAR(50) NOT NULL COMMENT '业务类型 (package_order)',
|
||||
business_id VARCHAR(36) NULL COMMENT '业务ID (关联cu_orders.id)',
|
||||
|
||||
-- 用户信息
|
||||
customer_id VARCHAR(36) NOT NULL COMMENT '客户ID',
|
||||
cu_user_id VARCHAR(36) NOT NULL COMMENT '客户用户ID',
|
||||
|
||||
-- 支付信息
|
||||
amount DECIMAL(10,2) NOT NULL COMMENT '支付金额',
|
||||
currency VARCHAR(3) DEFAULT 'CNY' COMMENT '货币类型',
|
||||
payment_method VARCHAR(20) DEFAULT 'alipay' COMMENT '支付方式',
|
||||
payment_provider VARCHAR(20) DEFAULT 'alipay' COMMENT '支付提供商',
|
||||
|
||||
-- 状态信息
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'pending' COMMENT '支付状态: pending-待支付, paid-已支付, cancelled-已取消, expired-已过期, failed-支付失败',
|
||||
trade_no VARCHAR(64) NULL COMMENT '第三方交易号',
|
||||
payment_time DATETIME(3) NULL COMMENT '支付完成时间',
|
||||
expire_time DATETIME(3) NOT NULL COMMENT '支付过期时间',
|
||||
|
||||
-- 支付链接和回调
|
||||
payment_url TEXT NULL COMMENT '支付链接',
|
||||
notify_data JSON NULL COMMENT '回调数据',
|
||||
extra_data JSON NULL COMMENT '扩展数据',
|
||||
|
||||
-- 时间信息
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)
|
||||
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='支付订单表 - 支持多种支付方式和业务场景';
|
||||
|
||||
-- 创建索引
|
||||
CREATE INDEX idx_payments_payment_no ON payments(payment_no);
|
||||
CREATE INDEX idx_payments_business_type_id ON payments(business_type, business_id);
|
||||
CREATE INDEX idx_payments_customer_id ON payments(customer_id);
|
||||
CREATE INDEX idx_payments_cu_user_id ON payments(cu_user_id);
|
||||
CREATE INDEX idx_payments_status ON payments(status);
|
||||
CREATE INDEX idx_payments_created_at ON payments(created_at);
|
||||
|
||||
-- 外键约束
|
||||
ALTER TABLE payments ADD CONSTRAINT fk_payments_customer_id FOREIGN KEY (customer_id) REFERENCES customers(id) ON DELETE CASCADE;
|
||||
ALTER TABLE payments ADD CONSTRAINT fk_payments_cu_user_id FOREIGN KEY (cu_user_id) REFERENCES cu_users(id) ON DELETE CASCADE;
|
||||
|
||||
-- 注意:
|
||||
-- 1. 时间戳由Go应用程序管理,不依赖数据库默认值
|
||||
-- 2. payment_no全局唯一,作为支付单的主要标识
|
||||
-- 3. business_type + business_id 支持关联不同业务场景
|
||||
-- 4. extra_data字段用于存储支付方式特定的扩展信息
|
||||
-- 5. 支持软删除(如果需要可以通过deleted_at字段扩展)
|
||||
@@ -0,0 +1,35 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// GeneratePaymentNo 生成支付单号
|
||||
func GeneratePaymentNo() string {
|
||||
timestamp := time.Now().Format("20060102150405")
|
||||
random := GenerateRandomString(6)
|
||||
return fmt.Sprintf("PAY%s%s", timestamp, random)
|
||||
}
|
||||
|
||||
// GenerateRandomString 生成随机字符串
|
||||
func GenerateRandomString(length int) string {
|
||||
const charset = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
result := make([]byte, length)
|
||||
for i := range result {
|
||||
result[i] = charset[time.Now().UnixNano()%int64(len(charset))]
|
||||
time.Sleep(1 * time.Nanosecond) // 简单的随机性保证
|
||||
}
|
||||
return string(result)
|
||||
}
|
||||
|
||||
// SendNotificationResponse 发送通知响应
|
||||
func SendNotificationResponse(w http.ResponseWriter, success bool) {
|
||||
response := "success"
|
||||
if !success {
|
||||
response = "failure"
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
w.Write([]byte(response))
|
||||
}
|
||||
Binary file not shown.
Reference in new issue
Block a user