feat: 完整重构小程序匹配功能 + 修复UI对齐 + 文章数据API

主要更新:
1. 按H5网页端完全重构匹配功能(match页面)
   - 4种匹配类型: 创业合伙/资源对接/导师顾问/团队招募
   - 资源对接等类型弹出手机号/微信号输入框
   - 去掉重新匹配按钮,改为返回按钮

2. 修复所有卡片对齐和宽度问题
   - 目录页附录卡片居中
   - 首页阅读进度卡片满宽度
   - 我的页面菜单卡片对齐
   - 推广中心分享卡片统一宽度

3. 修复目录页图标和文字对齐
   - section-icon固定40rpx宽高
   - section-title与图标垂直居中

4. 更新真实完整文章标题(62篇)
   - 从book目录读取真实markdown文件名
   - 替换之前的简化标题

5. 新增文章数据API
   - /api/db/chapters - 获取完整书籍结构
   - 支持按ID获取单篇文章内容
This commit is contained in:
卡若
2026-01-21 15:49:12 +08:00
parent 1ee25e3dab
commit b60edb3d47
197 changed files with 34430 additions and 7345 deletions

View File

@@ -0,0 +1,68 @@
# Universal Payment Module - Cursor 规则
# 将此文件放在使用支付模块的项目根目录
## 角色设定
你是一位精通全球支付架构的资深全栈工程师,专注于支付网关集成、安全合规和高可用设计。
当用户提及"支付模块"、"支付功能"、"接入支付"时,请参考 `Universal_Payment_Module` 目录中的设计文档。
## 核心原则
### 1. 配置驱动
- 所有支付密钥通过环境变量配置,绝不硬编码
- 使用 `.env` 文件管理配置
- 支持多环境切换 (development/staging/production)
### 2. 工厂模式
- 使用 `PaymentFactory` 统一管理支付网关
- 每个网关实现统一的 `AbstractGateway` 接口
- 支持: alipay/wechat/paypal/stripe/usdt
### 3. 安全优先
- 所有回调必须验证签名
- 必须验证支付金额与订单金额匹配
- 使用 HTTPS敏感数据脱敏
### 4. 幂等性
- 支付回调必须支持重复调用
- 使用分布式锁防止并发问题
## API 接口
```
POST /api/payment/create_order - 创建订单
POST /api/payment/checkout - 发起支付
GET /api/payment/status/{sn} - 查询状态
POST /api/payment/notify/{gw} - 回调通知
```
## 统一响应格式
```json
{
"code": 200,
"message": "success",
"data": { ... }
}
```
## 数据库
- orders - 订单表
- pay_trades - 交易流水表
- 金额单位: 数据库用分API用元
## 卡若支付配置
- 微信商户号: 1318592501
- 支付宝PID: 2088511801157159
- 详细配置见 `4_卡若配置/.env.example`
## 禁止事项
❌ 密钥硬编码
❌ 跳过签名验证
❌ 信任前端金额
❌ 回调不做幂等
❌ 使用 HTTP
## 参考文档
- API定义: `1_核心设计_通用协议/API接口定义.md`
- 数据模型: `1_核心设计_通用协议/业务逻辑与模型.md`
- 安全规范: `1_核心设计_通用协议/安全与合规.md`
- AI指令: `2_智能对接_AI指令/通用集成指令.md`

View File

@@ -0,0 +1,382 @@
# 通用支付模块 API 接口定义 (Universal Payment API) v4.0
> 无论后端使用何种语言Python/Node/Go/Java/PHP请严格实现以下 RESTful 接口
## 🎯 设计原则
1. **RESTful 风格**: 资源命名统一,动词语义清晰
2. **统一响应格式**: 所有接口返回 `{code, message, data}` 结构
3. **幂等性**: 重复请求不产生副作用
4. **安全性**: 敏感操作需签名验证
---
## 📦 统一响应格式
### 成功响应
```json
{
"code": 200,
"message": "success",
"data": { ... }
}
```
### 错误响应
```json
{
"code": 400,
"message": "参数错误order_sn 不能为空",
"data": null
}
```
### 常用错误码
| Code | 含义 |
|:---|:---|
| 200 | 成功 |
| 400 | 请求参数错误 |
| 401 | 未授权/登录过期 |
| 403 | 无权限 |
| 404 | 资源不存在 |
| 409 | 状态冲突 (如重复支付) |
| 500 | 服务器内部错误 |
---
## 1. 核心交易接口 (Core Transaction)
### 1.1 创建订单
业务系统调用,创建一个待支付订单。
```http
POST /api/payment/create_order
Content-Type: application/json
Authorization: Bearer {token}
```
**Request Body**:
```json
{
"user_id": "u1001", // [必填] 用户ID
"title": "VIP会员月卡", // [必填] 订单标题 (≤30字符)
"amount": 99.00, // [必填] 金额 (单位: 元)
"currency": "CNY", // [可选] 币种,默认 CNY
"product_id": "vip_monthly", // [可选] 商品ID
"product_type": "membership", // [可选] 商品类型
"extra_params": { // [可选] 扩展参数 (会透传到回调)
"coupon_id": "C001",
"referrer": "user_123"
}
}
```
**Response**:
```json
{
"code": 200,
"message": "success",
"data": {
"order_sn": "202401170001", // 系统生成的订单号
"status": "created", // 订单状态
"amount": 99.00,
"expire_at": "2024-01-17T11:30:00Z" // 订单过期时间
}
}
```
---
### 1.2 发起支付 (收银台)
用户选择支付方式后,获取支付参数。
```http
POST /api/payment/checkout
Content-Type: application/json
Authorization: Bearer {token}
```
**Request Body**:
```json
{
"order_sn": "202401170001", // [必填] 订单号
"gateway": "wechat_jsapi", // [必填] 支付网关 (见下方枚举)
"return_url": "https://...", // [可选] 支付成功后跳转地址
"openid": "oXxx...", // [条件] 微信JSAPI必填
"coin_amount": 0 // [可选] 使用虚拟币抵扣金额
}
```
**Gateway 支付网关枚举**:
| Gateway | 说明 | 返回类型 |
|:---|:---|:---|
| `alipay_web` | 支付宝PC网页 | url (跳转) |
| `alipay_wap` | 支付宝H5 | url (跳转) |
| `alipay_qr` | 支付宝扫码 | qrcode |
| `wechat_native` | 微信扫码 | qrcode |
| `wechat_jsapi` | 微信公众号/小程序 | json (SDK参数) |
| `wechat_h5` | 微信H5 | url (跳转) |
| `wechat_app` | 微信APP | json (SDK参数) |
| `paypal` | PayPal | url (跳转) |
| `stripe` | Stripe | url (Checkout Session) |
| `usdt` | USDT-TRC20 | address (钱包地址) |
| `coin` | 纯虚拟币支付 | direct (直接完成) |
**Response**:
```json
{
"code": 200,
"message": "success",
"data": {
"trade_sn": "T20240117100001", // 交易流水号
"type": "qrcode", // 响应类型: url/qrcode/json/address/direct
"payload": "weixin://wxpay/...",// 支付数据 (根据type不同)
"expiration": 1800, // 过期时间 (秒)
"amount": 99.00, // 实际支付金额 (扣除抵扣后)
"coin_deducted": 0 // 虚拟币抵扣金额
}
}
```
**不同 type 的 payload 格式**:
```javascript
// type: "url" - 跳转链接
payload: "https://openapi.alipay.com/gateway.do?..."
// type: "qrcode" - 二维码内容
payload: "weixin://wxpay/bizpayurl?pr=xxx"
// type: "json" - SDK调起参数 (微信JSAPI)
payload: {
"appId": "wx...",
"timeStamp": "1705470600",
"nonceStr": "xxx",
"package": "prepay_id=wx...",
"signType": "RSA",
"paySign": "xxx"
}
// type: "address" - 加密货币地址
payload: {
"address": "TXxx...",
"amount_usdt": 13.88,
"memo": "202401170001"
}
// type: "direct" - 直接完成 (纯虚拟币支付)
payload: { "status": "paid" }
```
---
### 1.3 查询订单状态
前端轮询使用,判断支付是否完成。
```http
GET /api/payment/status/{order_sn}
Authorization: Bearer {token}
```
**Response**:
```json
{
"code": 200,
"message": "success",
"data": {
"order_sn": "202401170001",
"status": "paid", // created/paying/paid/closed/refunded
"paid_amount": 99.00,
"paid_at": "2024-01-17T10:05:00Z",
"payment_method": "wechat_jsapi",
"trade_sn": "T20240117100001"
}
}
```
**订单状态机**:
```
created → paying → paid → (refunded)
↓ ↓
closed closed
```
---
### 1.4 关闭订单
主动关闭未支付的订单。
```http
POST /api/payment/close/{order_sn}
Authorization: Bearer {token}
```
**Response**:
```json
{
"code": 200,
"message": "success",
"data": {
"order_sn": "202401170001",
"status": "closed",
"closed_at": "2024-01-17T10:10:00Z"
}
}
```
---
## 2. 回调通知接口 (Webhook)
### 2.1 统一回调入口
接收第三方支付平台的异步通知。
```http
POST /api/payment/notify/{gateway}
```
**Path Params**:
- `gateway`: `alipay` / `wechat` / `paypal` / `stripe` / `nowpayments`
**处理逻辑**:
1. 根据 gateway 加载对应驱动
2. 验签 (Verify Signature)
3. 幂等性检查 (防重复处理)
4. 更新订单状态
5. 触发业务回调 (发货/开通权限等)
6. 返回平台所需响应
**返回格式**:
```
# 支付宝
success
# 微信
<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>
# Stripe
HTTP 200 OK
# PayPal
HTTP 200 OK
```
---
### 2.2 同步返回 (Return)
用户支付完成后的页面跳转。
```http
GET /api/payment/return/{gateway}
```
**Query Params**: 各平台不同,由平台自动附加
**处理逻辑**:
1. 解析回传参数
2. 验签
3. 重定向到成功页面
---
## 3. 辅助接口
### 3.1 获取可用支付方式
```http
GET /api/payment/methods
```
**Response**:
```json
{
"code": 200,
"data": {
"methods": [
{
"gateway": "wechat_jsapi",
"name": "微信支付",
"icon": "/icons/wechat.png",
"enabled": true,
"available": true // 当前环境是否可用 (如微信内)
},
{
"gateway": "alipay_wap",
"name": "支付宝",
"icon": "/icons/alipay.png",
"enabled": true,
"available": true
}
]
}
}
```
### 3.2 获取汇率
```http
GET /api/payment/exchange_rate?from=CNY&to=USD
```
**Response**:
```json
{
"code": 200,
"data": {
"from": "CNY",
"to": "USD",
"rate": 0.139,
"updated_at": "2024-01-17T00:00:00Z"
}
}
```
---
## 4. 管理接口 (Admin)
### 4.1 订单列表
```http
GET /api/admin/payment/orders?page=1&limit=20&status=paid
Authorization: Bearer {admin_token}
```
### 4.2 交易流水列表
```http
GET /api/admin/payment/trades?page=1&limit=20
Authorization: Bearer {admin_token}
```
### 4.3 发起退款
```http
POST /api/admin/payment/refund
Authorization: Bearer {admin_token}
Content-Type: application/json
{
"trade_sn": "T20240117100001",
"amount": 99.00,
"reason": "用户申请退款"
}
```
---
## 5. 接口签名规范 (可选)
对于安全要求高的场景,可启用接口签名:
```javascript
// 请求头
X-Sign: sha256(timestamp + nonce + body + secret)
X-Timestamp: 1705470600
X-Nonce: abc123
```
---
## 📌 注意事项
1. **金额单位**: 所有金额均以**元**为单位小数点后2位
2. **时间格式**: ISO 8601 格式 `YYYY-MM-DDTHH:mm:ssZ`
3. **字符编码**: UTF-8
4. **HTTPS**: 生产环境必须使用 HTTPS
5. **幂等性**: 相同订单号重复请求返回相同结果

View File

@@ -0,0 +1,396 @@
# 业务逻辑与数据模型 (Business Logic & Data Model) v4.0
> 定义支付系统的核心数据结构和业务流程
## 📊 数据库表结构
### 1. 订单表 (orders)
存储业务订单信息,与支付解耦。
```sql
CREATE TABLE `orders` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`sn` VARCHAR(32) NOT NULL COMMENT '订单号 (业务唯一)',
`user_id` VARCHAR(64) NOT NULL COMMENT '用户ID',
`title` VARCHAR(128) NOT NULL COMMENT '订单标题',
`price_amount` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '订单原价 (分)',
`pay_amount` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '应付金额 (分)',
`currency` VARCHAR(8) NOT NULL DEFAULT 'CNY' COMMENT '货币类型',
`status` VARCHAR(20) NOT NULL DEFAULT 'created' COMMENT '状态: created/paying/paid/closed/refunded',
`product_id` VARCHAR(64) DEFAULT NULL COMMENT '商品ID',
`product_type` VARCHAR(32) DEFAULT NULL COMMENT '商品类型',
`extra_data` JSON DEFAULT NULL COMMENT '扩展数据',
`paid_at` DATETIME DEFAULT NULL COMMENT '支付时间',
`closed_at` DATETIME DEFAULT NULL COMMENT '关闭时间',
`expired_at` DATETIME DEFAULT NULL COMMENT '过期时间',
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_sn` (`sn`),
KEY `idx_user_id` (`user_id`),
KEY `idx_status` (`status`),
KEY `idx_created_at` (`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';
```
### 2. 交易流水表 (pay_trades)
记录每一次支付尝试,一个订单可能有多次交易。
```sql
CREATE TABLE `pay_trades` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`trade_sn` VARCHAR(32) NOT NULL COMMENT '交易流水号 (系统生成)',
`order_sn` VARCHAR(32) NOT NULL COMMENT '关联订单号',
`user_id` VARCHAR(64) NOT NULL COMMENT '用户ID',
`title` VARCHAR(128) NOT NULL COMMENT '交易标题',
`amount` BIGINT UNSIGNED NOT NULL COMMENT '交易金额 (分)',
`cash_amount` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '现金支付金额 (分)',
`coin_amount` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '虚拟币抵扣金额',
`currency` VARCHAR(8) NOT NULL DEFAULT 'CNY' COMMENT '货币类型',
`platform` VARCHAR(32) NOT NULL COMMENT '支付平台: alipay/wechat/paypal/stripe/usdt/coin',
`platform_type` VARCHAR(32) DEFAULT NULL COMMENT '平台子类型: web/wap/jsapi/native/h5/app',
`platform_sn` VARCHAR(64) DEFAULT NULL COMMENT '平台交易号',
`platform_created_params` JSON DEFAULT NULL COMMENT '发送给平台的参数',
`platform_created_result` JSON DEFAULT NULL COMMENT '平台返回的结果',
`status` VARCHAR(20) NOT NULL DEFAULT 'paying' COMMENT '状态: paying/paid/closed/refunded',
`type` VARCHAR(20) NOT NULL DEFAULT 'purchase' COMMENT '类型: purchase(购买)/recharge(充值)',
`pay_time` DATETIME DEFAULT NULL COMMENT '支付时间',
`notify_data` JSON DEFAULT NULL COMMENT '回调原始数据',
`seller_id` VARCHAR(64) DEFAULT NULL COMMENT '卖家ID (多商户场景)',
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_trade_sn` (`trade_sn`),
KEY `idx_order_sn` (`order_sn`),
KEY `idx_platform_sn` (`platform_sn`),
KEY `idx_user_id` (`user_id`),
KEY `idx_status` (`status`),
KEY `idx_created_at` (`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='交易流水表';
```
### 3. 资金流水表 (cashflows)
记录账户资金变动(可选,用于虚拟币/钱包场景)。
```sql
CREATE TABLE `cashflows` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`sn` VARCHAR(32) NOT NULL COMMENT '流水号',
`user_id` VARCHAR(64) NOT NULL COMMENT '用户ID',
`type` VARCHAR(20) NOT NULL COMMENT '类型: inflow(入账)/outflow(出账)',
`action` VARCHAR(32) NOT NULL COMMENT '动作: recharge/purchase/refund/transfer',
`amount` BIGINT NOT NULL COMMENT '金额 (分,正数入账负数出账)',
`currency` VARCHAR(8) NOT NULL DEFAULT 'CNY',
`balance_before` BIGINT NOT NULL DEFAULT 0 COMMENT '变动前余额',
`balance_after` BIGINT NOT NULL DEFAULT 0 COMMENT '变动后余额',
`trade_sn` VARCHAR(32) DEFAULT NULL COMMENT '关联交易流水号',
`order_sn` VARCHAR(32) DEFAULT NULL COMMENT '关联订单号',
`remark` VARCHAR(256) DEFAULT NULL COMMENT '备注',
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_sn` (`sn`),
KEY `idx_user_id` (`user_id`),
KEY `idx_trade_sn` (`trade_sn`),
KEY `idx_created_at` (`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='资金流水表';
```
### 4. 退款记录表 (refunds)
```sql
CREATE TABLE `refunds` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`refund_sn` VARCHAR(32) NOT NULL COMMENT '退款单号',
`trade_sn` VARCHAR(32) NOT NULL COMMENT '原交易流水号',
`order_sn` VARCHAR(32) NOT NULL COMMENT '原订单号',
`amount` BIGINT UNSIGNED NOT NULL COMMENT '退款金额 (分)',
`reason` VARCHAR(256) DEFAULT NULL COMMENT '退款原因',
`status` VARCHAR(20) NOT NULL DEFAULT 'pending' COMMENT '状态: pending/processing/success/failed',
`platform_refund_sn` VARCHAR(64) DEFAULT NULL COMMENT '平台退款单号',
`refunded_at` DATETIME DEFAULT NULL COMMENT '退款完成时间',
`operator_id` VARCHAR(64) DEFAULT NULL COMMENT '操作人ID',
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_refund_sn` (`refund_sn`),
KEY `idx_trade_sn` (`trade_sn`),
KEY `idx_order_sn` (`order_sn`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='退款记录表';
```
---
## 🔄 状态机定义
### 订单状态 (Order Status)
```
┌─────────────────────────────────────────────────┐
│ created │
│ │ │
│ ┌───────────┼───────────┐ │
│ ▼ │ ▼ │
│ paying ────────┼───────► closed │
│ │ │ │
│ ▼ │ │
│ paid ─────────┼───────► refunded │
│ │ │
└─────────────────────────────────────────────────┘
状态说明:
- created: 订单已创建,等待支付
- paying: 支付中 (已发起支付请求)
- paid: 已支付
- closed: 已关闭 (超时/主动取消)
- refunded: 已退款
```
### 交易状态 (Trade Status)
```
paying → paid
↓ ↓
closed refunded
状态说明:
- paying: 支付中
- paid: 支付成功
- closed: 交易关闭
- refunded: 已退款
```
---
## 🔢 编号规则
### 订单号 (order_sn)
```
格式: YYYYMMDD + 6位随机数
示例: 202401170001
生成规则:
1. 日期前缀保证每日唯一空间
2. 随机数使用分布式ID生成器
3. 支持前缀自定义 (如区分业务线)
```
### 交易流水号 (trade_sn)
```
格式: T + YYYYMMDD + HHmmss + 5位随机数
示例: T20240117100530123456
生成规则:
1. 前缀 T 标识交易类型
2. 精确到秒的时间戳
3. 5位随机数防碰撞
```
---
## 📋 核心业务流程
### 1. 标准支付流程
```sequence
用户 -> 业务系统: 1. 提交订单
业务系统 -> 支付模块: 2. 创建订单 (create_order)
支付模块 -> 业务系统: 3. 返回 order_sn
用户 -> 支付模块: 4. 选择支付方式并支付 (checkout)
支付模块 -> 支付平台: 5. 创建平台交易
支付平台 -> 支付模块: 6. 返回支付参数
支付模块 -> 用户: 7. 返回支付数据 (二维码/跳转链接)
用户 -> 支付平台: 8. 完成支付
支付平台 -> 支付模块: 9. 异步回调 (notify)
支付模块 -> 支付模块: 10. 验签 + 更新状态
支付模块 -> 业务系统: 11. 触发业务回调 (发货/开通)
```
### 2. 支付回调处理流程
```python
def handle_notify(gateway, data):
# 1. 加载对应的支付网关驱动
driver = PaymentFactory.create(gateway)
# 2. 验证签名
if not driver.verify_sign(data):
raise SignatureError("签名验证失败")
# 3. 解析回调数据
parsed = driver.parse_notify(data)
trade_sn = parsed['trade_sn']
# 4. 幂等性检查
trade = Trade.get_by_sn(trade_sn)
if trade.status == 'paid':
return driver.success_response() # 已处理过,直接返回成功
# 5. 金额校验
if parsed['amount'] != trade.cash_amount:
raise AmountMismatchError("金额不匹配")
# 6. 更新交易状态
trade.update({
'status': 'paid',
'platform_sn': parsed['platform_sn'],
'pay_time': parsed['pay_time'],
'notify_data': data
})
# 7. 更新订单状态
order = Order.get_by_sn(trade.order_sn)
order.update({'status': 'paid', 'paid_at': now()})
# 8. 触发业务回调
dispatch_event('order.paid', order)
# 9. 返回成功响应
return driver.success_response()
```
### 3. 退款流程
```python
def apply_refund(trade_sn, amount, reason):
trade = Trade.get_by_sn(trade_sn)
# 1. 状态检查
if trade.status != 'paid':
raise InvalidStatusError("只有已支付的交易可以退款")
# 2. 创建退款记录
refund = Refund.create({
'refund_sn': generate_refund_sn(),
'trade_sn': trade_sn,
'amount': amount,
'reason': reason,
'status': 'pending'
})
# 3. 调用平台退款接口
driver = PaymentFactory.create(trade.platform)
result = driver.refund({
'trade_sn': trade_sn,
'refund_sn': refund.refund_sn,
'amount': amount
})
# 4. 更新状态
if result.success:
refund.update({'status': 'success', 'refunded_at': now()})
trade.update({'status': 'refunded'})
else:
refund.update({'status': 'failed'})
return refund
```
---
## 🏭 工厂模式设计
```python
class PaymentFactory:
"""支付网关工厂"""
_drivers = {
'alipay': AlipayGateway,
'wechat': WechatGateway,
'paypal': PayPalGateway,
'stripe': StripeGateway,
'usdt': USDTGateway,
'coin': CoinGateway,
}
@classmethod
def create(cls, gateway: str) -> AbstractGateway:
gateway_name = gateway.split('_')[0] # wechat_jsapi -> wechat
if gateway_name not in cls._drivers:
raise ValueError(f"不支持的支付网关: {gateway}")
driver_class = cls._drivers[gateway_name]
return driver_class(config=get_payment_config(gateway_name))
```
```python
class AbstractGateway(ABC):
"""支付网关抽象基类"""
@abstractmethod
def create_trade(self, data: dict) -> dict:
"""创建交易"""
pass
@abstractmethod
def verify_sign(self, data: dict) -> bool:
"""验证签名"""
pass
@abstractmethod
def parse_notify(self, data: dict) -> dict:
"""解析回调数据"""
pass
@abstractmethod
def refund(self, data: dict) -> RefundResult:
"""发起退款"""
pass
@abstractmethod
def query_trade(self, trade_sn: str) -> dict:
"""查询交易"""
pass
@abstractmethod
def close_trade(self, trade_sn: str) -> bool:
"""关闭交易"""
pass
def success_response(self) -> str:
"""回调成功响应"""
return "success"
```
---
## 💰 金额处理规范
### 1. 存储规则
- 数据库统一使用**分**为单位 (BIGINT)
- 避免浮点数精度问题
### 2. 接口规则
- API 输入输出统一使用**元**为单位
- 内部转换: `分 = 元 × 100`
### 3. 转换示例
```python
# 元转分 (API输入 -> 数据库)
def yuan_to_fen(yuan: float) -> int:
return int(round(yuan * 100))
# 分转元 (数据库 -> API输出)
def fen_to_yuan(fen: int) -> float:
return round(fen / 100, 2)
```
---
## 🔐 幂等性设计
### 1. 订单创建幂等
- 使用 `(user_id, product_id, created_date)` 组合判断
- 或使用客户端传入的幂等键 `idempotency_key`
### 2. 支付回调幂等
- 检查交易状态,已支付则直接返回成功
- 使用数据库事务 + 行锁保证并发安全
### 3. 退款幂等
- 同一笔交易只能退款一次 (或限制总退款金额)

View File

@@ -0,0 +1,383 @@
# 支付安全与合规指南 (Security & Compliance) v4.0
> 支付系统安全最佳实践,保护你的资金和用户数据
## 🔐 密钥安全
### 1. 密钥存储原则
```
❌ 错误做法:
- 将密钥硬编码在代码中
- 将密钥提交到 Git 仓库
- 通过即时通讯工具传输密钥
- 使用弱密码作为 API Key
✅ 正确做法:
- 使用环境变量存储密钥
- 使用专业密钥管理服务 (AWS KMS, HashiCorp Vault)
- 定期轮换密钥
- 最小权限原则
```
### 2. .gitignore 必须包含
```gitignore
# 支付密钥相关
.env
.env.local
.env.*.local
*.pem
*.key
cert/
config/payment.yml
secrets/
```
### 3. 密钥轮换
- 定期更换 API 密钥 (建议每 90 天)
- 发现泄露立即作废并重新生成
- 保留旧密钥短暂过渡期
---
## 🔒 通信安全
### 1. HTTPS 强制
```nginx
# Nginx 配置示例
server {
listen 80;
server_name your-domain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name your-domain.com;
ssl_certificate /path/to/fullchain.pem;
ssl_certificate_key /path/to/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
# HSTS
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
}
```
### 2. 证书管理
- 使用受信任的 CA 签发证书
- 定期检查证书有效期
- 推荐 Let's Encrypt 自动续期
---
## ✅ 签名验证
### 1. 支付宝签名验证
```python
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
from Crypto.Hash import SHA256
import base64
def verify_alipay_sign(params: dict, sign: str, public_key: str) -> bool:
"""验证支付宝签名"""
# 1. 参数排序
sorted_params = sorted([(k, v) for k, v in params.items() if k != 'sign' and v])
# 2. 拼接待签名字符串
sign_str = '&'.join([f'{k}={v}' for k, v in sorted_params])
# 3. RSA2 验签
key = RSA.import_key(f"-----BEGIN PUBLIC KEY-----\n{public_key}\n-----END PUBLIC KEY-----")
verifier = PKCS1_v1_5.new(key)
hash_obj = SHA256.new(sign_str.encode('utf-8'))
try:
verifier.verify(hash_obj, base64.b64decode(sign))
return True
except (ValueError, TypeError):
return False
```
### 2. 微信签名验证
```python
import hashlib
def verify_wechat_sign(params: dict, sign: str, api_key: str) -> bool:
"""验证微信支付签名"""
# 1. 参数排序
sorted_params = sorted([(k, v) for k, v in params.items() if k != 'sign' and v])
# 2. 拼接待签名字符串
sign_str = '&'.join([f'{k}={v}' for k, v in sorted_params])
sign_str += f'&key={api_key}'
# 3. MD5 签名
calculated_sign = hashlib.md5(sign_str.encode('utf-8')).hexdigest().upper()
return calculated_sign == sign
```
---
## 💰 金额校验
### 1. 回调金额必须验证
```python
def handle_payment_notify(trade_sn: str, paid_amount: int):
"""处理支付回调时必须验证金额"""
trade = get_trade_by_sn(trade_sn)
# 金额必须严格匹配
if paid_amount != trade.cash_amount:
log.error(f"金额不匹配! 订单:{trade.cash_amount}, 回调:{paid_amount}")
raise AmountMismatchError()
# 继续处理...
```
### 2. 防止金额篡改
```python
# 前端传入的金额仅用于展示,实际金额从后端订单读取
def checkout(order_sn: str, gateway: str):
order = get_order(order_sn)
# 金额从数据库读取,不信任前端
amount = order.pay_amount
return create_trade(order_sn, amount, gateway)
```
---
## 🛡️ 回调安全
### 1. IP 白名单
```python
# 支付平台回调 IP 白名单
PAYMENT_IP_WHITELIST = {
'alipay': [
'110.75.0.0/16',
'203.209.0.0/16'
],
'wechat': [
'101.226.0.0/16',
'140.207.0.0/16'
]
}
def verify_callback_ip(gateway: str, client_ip: str) -> bool:
"""验证回调来源 IP"""
import ipaddress
whitelist = PAYMENT_IP_WHITELIST.get(gateway, [])
client = ipaddress.ip_address(client_ip)
for cidr in whitelist:
if client in ipaddress.ip_network(cidr):
return True
return False
```
### 2. 防重放攻击
```python
import time
def check_notify_timestamp(timestamp: int) -> bool:
"""检查回调时间戳,防止重放攻击"""
now = int(time.time())
# 允许 5 分钟的时间差
if abs(now - timestamp) > 300:
log.warning(f"回调时间戳异常: {timestamp}")
return False
return True
```
### 3. 幂等性处理
```python
def process_notify_idempotent(trade_sn: str, notify_data: dict):
"""幂等性处理回调"""
# 使用分布式锁
lock_key = f"payment_notify:{trade_sn}"
with redis_lock(lock_key, timeout=10):
trade = get_trade_by_sn(trade_sn)
# 已处理过,直接返回成功
if trade.status == 'paid':
return success_response()
# 处理支付成功逻辑
update_trade_to_paid(trade, notify_data)
return success_response()
```
---
## 📝 日志审计
### 1. 必须记录的日志
```python
import logging
payment_logger = logging.getLogger('payment')
# 创建交易日志
payment_logger.info(f"创建交易 | trade_sn={trade_sn} | order_sn={order_sn} | amount={amount} | gateway={gateway}")
# 回调日志
payment_logger.info(f"收到回调 | gateway={gateway} | trade_sn={trade_sn} | raw_data={raw_data[:500]}")
# 签名验证日志
payment_logger.info(f"签名验证 | trade_sn={trade_sn} | result={verify_result}")
# 状态变更日志
payment_logger.info(f"状态变更 | trade_sn={trade_sn} | from={old_status} | to={new_status}")
# 退款日志
payment_logger.info(f"发起退款 | refund_sn={refund_sn} | trade_sn={trade_sn} | amount={amount}")
```
### 2. 敏感信息脱敏
```python
def mask_sensitive(data: dict) -> dict:
"""敏感信息脱敏"""
sensitive_keys = ['card_no', 'id_card', 'phone', 'bank_account']
masked = data.copy()
for key in sensitive_keys:
if key in masked:
value = str(masked[key])
if len(value) > 4:
masked[key] = value[:2] + '*' * (len(value) - 4) + value[-2:]
return masked
```
---
## 🚨 异常处理
### 1. 支付异常分类
```python
class PaymentError(Exception):
"""支付基础异常"""
pass
class SignatureError(PaymentError):
"""签名验证失败"""
pass
class AmountMismatchError(PaymentError):
"""金额不匹配"""
pass
class OrderExpiredError(PaymentError):
"""订单已过期"""
pass
class DuplicatePaymentError(PaymentError):
"""重复支付"""
pass
class RefundError(PaymentError):
"""退款失败"""
pass
```
### 2. 统一异常处理
```python
@app.exception_handler(PaymentError)
async def payment_exception_handler(request, exc):
return JSONResponse(
status_code=400,
content={
"code": 400,
"message": str(exc),
"data": None
}
)
```
---
## ✅ 合规要求
### 1. PCI DSS 合规 (信用卡)
- 不存储完整卡号、CVV、PIN
- 使用 Stripe/PayPal 等符合 PCI DSS 的支付网关
- 定期安全评估
### 2. GDPR 合规 (欧盟用户)
- 明确告知用户数据用途
- 提供数据删除功能
- 用户同意授权
### 3. 中国支付合规
- 接入持牌支付机构
- 实名认证
- 交易限额管理
---
## 📋 安全检查清单
```markdown
## 上线前安全检查
### 密钥管理
- [ ] 所有密钥通过环境变量配置
- [ ] 密钥未提交到代码仓库
- [ ] 生产环境密钥与测试环境隔离
### 通信安全
- [ ] 启用 HTTPS
- [ ] 证书有效且受信任
- [ ] 启用 HSTS
### 签名验证
- [ ] 所有回调验签
- [ ] 验签失败拒绝处理
### 金额校验
- [ ] 回调金额与订单金额比对
- [ ] 金额从后端读取
### 日志审计
- [ ] 关键操作有日志
- [ ] 敏感信息脱敏
### 异常处理
- [ ] 异常不泄露敏感信息
- [ ] 有统一异常处理
### 回调安全
- [ ] IP 白名单验证 (可选)
- [ ] 幂等性处理
- [ ] 防重放攻击
```

View File

@@ -0,0 +1,133 @@
# ============================================================================
# 全球支付模块标准配置模板 (Universal Payment Config Template) v4.0
# ============================================================================
# 适用于: Python, Node.js, Go, Java, PHP 等任意后端语言
# 使用方法: 将此配置映射到你项目的环境变量 (.env, config.py, application.yml)
# ============================================================================
# ----------------------------------------------------------------------------
# 1. 基础环境 (Environment)
# ----------------------------------------------------------------------------
APP_ENV: "production" # development / staging / production
APP_NAME: "MyApp" # 应用名称 (用于日志/标题)
APP_URL: "https://your-site.com" # 你的网站域名 (用于回调地址生成)
APP_CURRENCY: "CNY" # 默认货币: CNY, USD, EUR
# ----------------------------------------------------------------------------
# 2. 数据库 (Database) - 存储订单和交易流水
# ----------------------------------------------------------------------------
DB_CONNECTION: "mysql" # mysql / postgres / mongodb / sqlite
DB_HOST: "127.0.0.1"
DB_PORT: "3306"
DB_DATABASE: "payment_db"
DB_USERNAME: "root"
DB_PASSWORD: "your_password"
# 自动创建的表:
# - orders (订单表)
# - pay_trades (交易流水表)
# - cashflows (资金流水表)
# ----------------------------------------------------------------------------
# 3. 支付宝 (Alipay) - 中国市场
# ----------------------------------------------------------------------------
ALIPAY_ENABLED: true
ALIPAY_MODE: "production" # sandbox / production
ALIPAY_APP_ID: "" # 开放平台应用 AppID
ALIPAY_PID: "" # 商户 PID (合作伙伴ID)
ALIPAY_SELLER_EMAIL: "" # 收款支付宝账号
ALIPAY_PRIVATE_KEY: "" # 商户私钥 (RSA2)
ALIPAY_PUBLIC_KEY: "" # 支付宝公钥
ALIPAY_MD5_KEY: "" # MD5 密钥 (旧版接口)
# 回调地址 (系统自动拼接 APP_URL)
# 同步回调: ${APP_URL}/api/payment/return/alipay
# 异步回调: ${APP_URL}/api/payment/notify/alipay
# ----------------------------------------------------------------------------
# 4. 微信支付 (Wechat Pay) - 中国市场
# ----------------------------------------------------------------------------
WECHAT_ENABLED: true
WECHAT_MODE: "production" # sandbox / production
# 公众号/网站支付
WECHAT_APPID: "" # 公众号/网站 AppID
WECHAT_APP_SECRET: "" # AppSecret
# 服务号 (如果有独立服务号)
WECHAT_SERVICE_APPID: "" # 服务号 AppID
WECHAT_SERVICE_SECRET: "" # 服务号 AppSecret
# 商户信息
WECHAT_MCH_ID: "" # 商户号
WECHAT_MCH_KEY: "" # 商户平台 API 密钥 (32位)
WECHAT_MCH_KEY_V3: "" # APIv3 密钥 (如使用v3接口)
# 证书路径 (相对于项目根目录)
WECHAT_CERT_PATH: "./cert/wechat/apiclient_cert.pem"
WECHAT_KEY_PATH: "./cert/wechat/apiclient_key.pem"
WECHAT_CERT_SERIAL: "" # 证书序列号 (v3接口需要)
# 小程序 (如果有)
WECHAT_MINI_APPID: "" # 小程序 AppID
WECHAT_MINI_SECRET: "" # 小程序 AppSecret
# 回调地址
# 异步回调: ${APP_URL}/api/payment/notify/wechat
# ----------------------------------------------------------------------------
# 5. PayPal - 全球市场
# ----------------------------------------------------------------------------
PAYPAL_ENABLED: true
PAYPAL_MODE: "live" # sandbox / live
PAYPAL_CLIENT_ID: "" # Client ID
PAYPAL_CLIENT_SECRET: "" # Client Secret
PAYPAL_WEBHOOK_ID: "" # Webhook ID (用于验证回调)
# 回调地址: ${APP_URL}/api/payment/notify/paypal
# ----------------------------------------------------------------------------
# 6. Stripe - 全球市场
# ----------------------------------------------------------------------------
STRIPE_ENABLED: true
STRIPE_MODE: "live" # test / live
STRIPE_PUBLIC_KEY: "" # pk_live_xxx 或 pk_test_xxx
STRIPE_SECRET_KEY: "" # sk_live_xxx 或 sk_test_xxx
STRIPE_WEBHOOK_SECRET: "" # whsec_xxx
# 回调地址: ${APP_URL}/api/payment/notify/stripe
# ----------------------------------------------------------------------------
# 7. USDT (加密货币) - Web3 / 抗审查支付
# ----------------------------------------------------------------------------
USDT_ENABLED: false
USDT_GATEWAY_TYPE: "nowpayments" # nowpayments / native
# 选项 A: NOWPayments (第三方托管)
NOWPAYMENTS_API_KEY: ""
NOWPAYMENTS_IPN_SECRET: ""
# 选项 B: Native (原生 TRC20 监听)
TRON_NODE_API: "https://api.trongrid.io"
TRON_WALLET_ADDRESS: "" # 你的 USDT-TRC20 收款地址
TRON_API_KEY: "" # TronGrid API Key
TRON_CHECK_INTERVAL: 60 # 轮询间隔 (秒)
# ----------------------------------------------------------------------------
# 8. 高级配置 (Advanced)
# ----------------------------------------------------------------------------
# 虚拟币/积分系统
COIN_ENABLED: false # 是否启用虚拟币抵扣
COIN_RATE: 100 # 1元 = 100虚拟币
# 订单配置
ORDER_EXPIRE_MINUTES: 30 # 订单过期时间 (分钟)
TRADE_SN_PREFIX: "T" # 交易流水号前缀
# 日志配置
PAYMENT_LOG_LEVEL: "info" # debug / info / warning / error
PAYMENT_LOG_PATH: "./logs/payment.log"
# 安全配置
PAYMENT_IP_WHITELIST: "" # 回调IP白名单 (逗号分隔)
PAYMENT_SIGN_TYPE: "RSA2" # 签名类型: RSA2, MD5

View File

@@ -0,0 +1,266 @@
# Universal Payment Module - Cursor 规则
> 将此文件复制为项目根目录的 `.cursorrules` 或 `.cursor/rules/payment.md`
---
## 基础设定
你是一位精通全球支付架构的资深全栈工程师,专注于支付网关集成、安全合规和高可用设计。
当用户提及"支付模块"、"支付功能"、"接入支付"时,请参考 `Universal_Payment_Module` 目录中的设计文档。
---
## 核心原则
### 1. 配置驱动
- 所有支付密钥通过环境变量配置,绝不硬编码
- 使用 `.env` 文件管理配置,参考 `标准配置模板.yaml`
- 支持多环境切换 (development/staging/production)
### 2. 工厂模式
- 使用 `PaymentFactory` 统一管理支付网关
- 每个网关实现统一的 `AbstractGateway` 接口
- 支持动态添加新的支付渠道
### 3. 安全优先
- 所有回调必须验证签名
- 必须验证支付金额与订单金额匹配
- 使用 HTTPS敏感数据脱敏
- 参考 `安全与合规.md`
### 4. 幂等性
- 支付回调必须支持重复调用
- 使用分布式锁防止并发问题
- 检查交易状态后再处理
### 5. 状态机
- 订单状态: created → paying → paid → refunded/closed
- 状态变更必须记录日志
- 不允许跳跃式状态变更
---
## API 规范
严格按照 `API接口定义.md` 实现以下接口:
```
POST /api/payment/create_order - 创建订单
POST /api/payment/checkout - 发起支付
GET /api/payment/status/{sn} - 查询状态
POST /api/payment/close/{sn} - 关闭订单
POST /api/payment/notify/{gw} - 回调通知
GET /api/payment/return/{gw} - 同步返回
GET /api/payment/methods - 获取支付方式
```
### 统一响应格式
```json
{
"code": 200,
"message": "success",
"data": { ... }
}
```
---
## 数据库模型
参考 `业务逻辑与模型.md`,必须创建以下表:
1. **orders** - 订单表 (业务订单)
2. **pay_trades** - 交易流水表 (支付记录)
3. **cashflows** - 资金流水表 (可选)
4. **refunds** - 退款记录表
金额单位:
- 数据库存储使用**分** (BIGINT)
- API 输入输出使用**元** (float)
---
## 支付网关 Gateway
### 支付宝 Alipay
- SDK: `alipay-sdk-python` / `alipay-sdk-java` / `alipay/aop-sdk`
- 签名: RSA2
- 回调: POSTform 表单格式
### 微信支付 Wechat
- SDK: `wechatpay-python-v3` / `wechatpay-java`
- 签名: HMAC-SHA256 / RSA (v3)
- 回调: POSTXML 格式
### PayPal
- SDK: `paypal-rest-sdk`
- 认证: OAuth 2.0
- 回调: WebhookJSON 格式
### Stripe
- SDK: `stripe`
- 认证: API Key
- 回调: WebhookJSON 格式
---
## 代码生成模板
### Python FastAPI
```python
# app/services/payment_factory.py
from abc import ABC, abstractmethod
from app.config import settings
class AbstractGateway(ABC):
@abstractmethod
async def create_trade(self, data: dict) -> dict:
pass
@abstractmethod
def verify_sign(self, data: dict) -> bool:
pass
@abstractmethod
def parse_notify(self, data: dict) -> dict:
pass
class PaymentFactory:
_gateways = {}
@classmethod
def register(cls, name: str, gateway_class):
cls._gateways[name] = gateway_class
@classmethod
def create(cls, name: str) -> AbstractGateway:
gateway_name = name.split('_')[0]
if gateway_name not in cls._gateways:
raise ValueError(f"不支持的支付网关: {name}")
return cls._gateways[gateway_name]()
```
### Node.js Express
```typescript
// src/services/PaymentFactory.ts
export abstract class AbstractGateway {
abstract createTrade(data: CreateTradeDTO): Promise<TradeResult>;
abstract verifySign(data: Record<string, any>): boolean;
abstract parseNotify(data: Record<string, any>): NotifyResult;
}
export class PaymentFactory {
private static gateways: Map<string, new () => AbstractGateway> = new Map();
static register(name: string, gateway: new () => AbstractGateway) {
this.gateways.set(name, gateway);
}
static create(name: string): AbstractGateway {
const gatewayName = name.split('_')[0];
const GatewayClass = this.gateways.get(gatewayName);
if (!GatewayClass) {
throw new Error(`不支持的支付网关: ${name}`);
}
return new GatewayClass();
}
}
```
---
## 回调处理模板
```python
async def handle_notify(gateway: str, raw_data: bytes | dict):
"""统一回调处理"""
# 1. 获取网关驱动
driver = PaymentFactory.create(gateway)
# 2. 验证签名
if not driver.verify_sign(raw_data):
logger.error(f"签名验证失败 | gateway={gateway}")
raise SignatureError()
# 3. 解析数据
parsed = driver.parse_notify(raw_data)
trade_sn = parsed['trade_sn']
# 4. 幂等检查
async with redis_lock(f"notify:{trade_sn}"):
trade = await get_trade(trade_sn)
if trade.status == 'paid':
return driver.success_response()
# 5. 金额校验
if parsed['amount'] != trade.cash_amount:
logger.error(f"金额不匹配 | sn={trade_sn}")
raise AmountMismatchError()
# 6. 更新状态
await update_trade_status(trade_sn, 'paid', parsed)
# 7. 触发业务回调
await dispatch_event('order.paid', trade.order_sn)
return driver.success_response()
```
---
## 日志规范
关键操作必须记录日志:
```python
logger.info(f"创建交易 | trade_sn={sn} | order={order_sn} | amount={amount}")
logger.info(f"收到回调 | gateway={gw} | trade_sn={sn}")
logger.info(f"签名验证 | trade_sn={sn} | result={ok}")
logger.info(f"状态变更 | trade_sn={sn} | {old}{new}")
logger.error(f"支付失败 | trade_sn={sn} | error={err}")
```
---
## 测试规范
1. **单元测试**: 测试签名生成/验证、金额转换
2. **集成测试**: 使用沙箱环境测试完整支付流程
3. **Mock 测试**: 模拟回调接口测试
---
## 禁止事项
❌ 密钥硬编码在代码中
❌ 跳过签名验证
❌ 信任前端传入的金额
❌ 回调不做幂等处理
❌ 敏感信息打印到日志
❌ 使用 HTTP 而非 HTTPS
---
## 参考文档路径
```
Universal_Payment_Module/
├── 1_核心设计_通用协议/
│ ├── 标准配置模板.yaml # 配置参考
│ ├── API接口定义.md # 接口规范
│ ├── 业务逻辑与模型.md # 数据模型
│ └── 安全与合规.md # 安全规范
├── 2_智能对接_AI指令/
│ ├── 通用集成指令.md # AI 提示词
│ └── Cursor规则.md # 本文件
└── 3_逻辑参考_通用实现/
├── 前端收银台Demo.html
└── 后端源码/
```

View File

@@ -0,0 +1,234 @@
# 通用支付模块 AI 智能对接指令 (Integration Prompt) v4.0
> 发送此指令给 AI 助手 (Cursor/ChatGPT/Claude),自动生成支付集成代码
---
## 🎯 角色设定
```
你是一位精通全球支付架构的资深全栈架构师,专注于:
- 支付网关集成 (Alipay/Wechat/PayPal/Stripe/USDT)
- 安全合规 (签名验证/HTTPS/PCI DSS)
- 高可用设计 (幂等性/状态机/分布式锁)
```
---
## 📋 任务目标
我提供了一个**配置驱动 (Configuration-Driven)** 的通用支付模块设计。
请根据我的项目环境,将此支付功能无缝集成。
---
## 📚 核心资源 (请先阅读)
1. **标准配置模板**: `1_核心设计_通用协议/标准配置模板.yaml`
2. **API 接口契约**: `1_核心设计_通用协议/API接口定义.md`
3. **数据模型**: `1_核心设计_通用协议/业务逻辑与模型.md`
4. **安全规范**: `1_核心设计_通用协议/安全与合规.md`
---
## 🔧 集成模式
### 模式 A: 嵌入式集成 (Library Mode) ⭐推荐
适用于将支付功能直接写在现有的后端项目中。
**执行步骤**:
1. **环境识别**: 检查项目语言 (Python/Node/Go/Java/PHP)
2. **依赖安装**: 推荐 SDK
3. **配置加载**: 读取环境变量
4. **模型生成**: 创建 ORM 模型 (Order/Trade/Refund)
5. **网关工厂**: 实现 PaymentFactory + 各网关 Driver
6. **接口实现**: 按 `API接口定义.md` 实现 Controller
7. **回调处理**: 实现回调验签和状态更新
---
### 模式 B: 微服务集成 (Microservice Mode)
适用于将支付功能独立部署为一个服务。
**执行步骤**:
1. **服务生成**: 创建独立的支付服务项目
2. **Docker化**: 编写 `Dockerfile``docker-compose.yml`
3. **网关代理**: 配置 `/api/payment/*` 路由转发
---
## 📝 给 AI 的标准执行指令
### 快速集成 (复制此内容发送给 AI)
```
请读取 `Universal_Payment_Module` 目录下的所有设计文档。
我的当前项目信息:
- 语言/框架: [Python FastAPI / Node.js Express / Java Spring Boot / Go Gin / PHP Laravel]
- 数据库: [MySQL / PostgreSQL / MongoDB]
- 集成模式: [模式 A 嵌入式 / 模式 B 微服务]
请执行以下任务:
1. 生成依赖安装命令
2. 生成数据库迁移/模型代码
3. 生成支付网关工厂类
4. 生成 API 接口代码 (严格按 API接口定义.md)
5. 生成回调处理代码
6. 生成配置读取代码
要求:
- 使用工厂模式管理支付网关
- 所有配置通过环境变量读取
- 包含完整的签名验证逻辑
- 包含幂等性处理
- 添加中文注释
```
---
## 🐍 Python FastAPI 示例指令
```
我的项目使用 Python FastAPI + SQLAlchemy + MySQL。
请根据 Universal_Payment_Module 文档,生成:
1. requirements.txt 依赖
2. app/models/payment.py - 数据模型
3. app/services/payment_factory.py - 支付网关工厂
4. app/services/gateways/alipay.py - 支付宝网关
5. app/services/gateways/wechat.py - 微信支付网关
6. app/routers/payment.py - API 路由
7. app/config/payment.py - 配置加载
特别要求:
- 使用 async/await 异步处理
- 集成 alipay-sdk-python 和 wechatpay-python-v3
- 回调接口支持 XML 和 JSON 格式
```
---
## 🟢 Node.js Express 示例指令
```
我的项目使用 Node.js Express + Prisma + PostgreSQL。
请根据 Universal_Payment_Module 文档,生成:
1. package.json 依赖
2. prisma/schema.prisma - 数据模型
3. src/services/PaymentFactory.ts - 支付网关工厂
4. src/services/gateways/AlipayGateway.ts
5. src/services/gateways/WechatGateway.ts
6. src/routes/payment.ts - API 路由
7. src/config/payment.ts - 配置加载
特别要求:
- 使用 TypeScript
- 使用 alipay-sdk 和 wechatpay-node-v3
- 实现完整的错误处理
```
---
## ☕ Java Spring Boot 示例指令
```
我的项目使用 Java Spring Boot + MyBatis + MySQL。
请根据 Universal_Payment_Module 文档,生成:
1. pom.xml 依赖
2. entity/ - 实体类
3. mapper/ - MyBatis Mapper
4. service/PaymentFactory.java - 支付网关工厂
5. service/gateway/AlipayGateway.java
6. service/gateway/WechatGateway.java
7. controller/PaymentController.java - API 控制器
8. config/PaymentConfig.java - 配置类
特别要求:
- 使用 alipay-sdk-java 和 wechatpay-java
- 使用 @Transactional 事务管理
- 实现统一异常处理
```
---
## 🐘 PHP Laravel 示例指令
```
我的项目使用 PHP Laravel + Eloquent + MySQL。
请根据 Universal_Payment_Module 文档,生成:
1. composer.json 依赖
2. database/migrations/ - 数据库迁移
3. app/Models/ - Eloquent 模型
4. app/Services/PaymentFactory.php - 支付网关工厂
5. app/Services/Gateways/AlipayGateway.php
6. app/Services/Gateways/WechatGateway.php
7. app/Http/Controllers/PaymentController.php
8. routes/api.php - 路由定义
9. config/payment.php - 配置文件
特别要求:
- 使用 alipay/aop-sdk 和 wechatpay/wechatpay
- 使用 Laravel 的服务容器
- 实现中间件验签
```
---
## 🔥 高级指令:前端收银台
```
请根据 Universal_Payment_Module 文档,生成前端收银台组件。
技术栈: [Vue 3 / React / 原生 JS]
要求:
1. 支持多种支付方式切换
2. 扫码支付显示二维码
3. 轮询支付状态
4. 适配移动端
5. 显示支付倒计时
6. 美观的 UI (可使用 TailwindCSS)
```
---
## 🔥 高级指令Docker 部署
```
请为 Universal_Payment_Module 生成 Docker 部署配置。
要求:
1. Dockerfile (多阶段构建)
2. docker-compose.yml (包含 MySQL + Redis)
3. nginx.conf (反向代理 + HTTPS)
4. .env.example (环境变量模板)
5. deploy.sh (一键部署脚本)
```
---
## ⚠️ 注意事项
1. **密钥安全**: 生成的代码中不要硬编码任何密钥
2. **签名验证**: 必须实现完整的签名验证逻辑
3. **幂等处理**: 回调必须支持幂等
4. **金额校验**: 必须验证回调金额与订单金额匹配
5. **日志记录**: 关键操作必须记录日志
---
## 📞 支持
如有问题,请联系:
- 微信: 28533368
- 作者: 卡若

View File

@@ -0,0 +1,748 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>通用收银台 v4.0</title>
<style>
/*
* 通用收银台样式 - 支持多种支付方式
* 作者: 卡若
* 适配: PC + 移动端
*/
:root {
--primary-color: #1890ff;
--success-color: #52c41a;
--warning-color: #faad14;
--error-color: #ff4d4f;
--text-color: #333;
--text-secondary: #666;
--border-color: #e8e8e8;
--bg-color: #f5f5f7;
--card-bg: #ffffff;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background: var(--bg-color);
color: var(--text-color);
min-height: 100vh;
padding: 20px;
}
.cashier-container {
max-width: 480px;
margin: 0 auto;
}
.cashier-card {
background: var(--card-bg);
border-radius: 16px;
padding: 24px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
margin-bottom: 16px;
}
/* 订单信息 */
.order-section {
text-align: center;
padding-bottom: 20px;
border-bottom: 1px dashed var(--border-color);
margin-bottom: 20px;
}
.order-title {
font-size: 16px;
color: var(--text-secondary);
margin-bottom: 8px;
}
.order-amount {
font-size: 42px;
font-weight: 700;
color: var(--text-color);
letter-spacing: -1px;
}
.order-amount .currency {
font-size: 24px;
font-weight: 400;
margin-right: 4px;
}
.order-info {
display: flex;
justify-content: space-between;
font-size: 14px;
color: var(--text-secondary);
margin-top: 12px;
}
.countdown {
color: var(--warning-color);
font-weight: 500;
}
/* 支付方式选择 */
.payment-section h3 {
font-size: 14px;
color: var(--text-secondary);
margin-bottom: 12px;
}
.payment-methods {
display: flex;
flex-direction: column;
gap: 10px;
}
.payment-method {
display: flex;
align-items: center;
padding: 16px;
border: 2px solid var(--border-color);
border-radius: 12px;
cursor: pointer;
transition: all 0.2s ease;
background: var(--card-bg);
}
.payment-method:hover {
border-color: var(--primary-color);
background: rgba(24, 144, 255, 0.04);
}
.payment-method.active {
border-color: var(--primary-color);
background: rgba(24, 144, 255, 0.08);
}
.payment-method .icon {
width: 36px;
height: 36px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
margin-right: 12px;
}
.payment-method .icon.alipay { background: #1677ff; color: white; }
.payment-method .icon.wechat { background: #07c160; color: white; }
.payment-method .icon.paypal { background: #003087; color: white; }
.payment-method .icon.stripe { background: #635bff; color: white; }
.payment-method .icon.usdt { background: #26a17b; color: white; }
.payment-method .info {
flex: 1;
}
.payment-method .name {
font-size: 16px;
font-weight: 500;
margin-bottom: 2px;
}
.payment-method .desc {
font-size: 12px;
color: var(--text-secondary);
}
.payment-method .radio {
width: 20px;
height: 20px;
border: 2px solid var(--border-color);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
}
.payment-method.active .radio {
border-color: var(--primary-color);
background: var(--primary-color);
}
.payment-method.active .radio::after {
content: '✓';
color: white;
font-size: 12px;
}
/* 二维码区域 */
.qrcode-section {
text-align: center;
padding: 24px;
display: none;
}
.qrcode-section.show {
display: block;
}
.qrcode-box {
width: 200px;
height: 200px;
margin: 0 auto 16px;
border: 1px solid var(--border-color);
border-radius: 12px;
overflow: hidden;
background: white;
}
.qrcode-box img {
width: 100%;
height: 100%;
object-fit: contain;
}
.qrcode-text {
font-size: 14px;
color: var(--text-secondary);
}
.qrcode-tip {
margin-top: 8px;
font-size: 12px;
color: var(--warning-color);
}
/* USDT 地址显示 */
.usdt-address {
background: #f9f9f9;
border-radius: 8px;
padding: 16px;
word-break: break-all;
font-family: monospace;
font-size: 14px;
margin-top: 12px;
}
.copy-btn {
margin-top: 8px;
padding: 8px 16px;
background: var(--primary-color);
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
}
/* 支付按钮 */
.pay-btn {
width: 100%;
padding: 16px;
background: linear-gradient(135deg, #1890ff 0%, #096dd9 100%);
color: white;
border: none;
border-radius: 12px;
font-size: 18px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
margin-top: 24px;
}
.pay-btn:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(24, 144, 255, 0.3);
}
.pay-btn:disabled {
background: #ccc;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.pay-btn.loading {
position: relative;
color: transparent;
}
.pay-btn.loading::after {
content: '';
position: absolute;
width: 20px;
height: 20px;
border: 2px solid transparent;
border-top-color: white;
border-radius: 50%;
animation: spin 0.8s linear infinite;
left: 50%;
top: 50%;
margin-left: -10px;
margin-top: -10px;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* 支付成功 */
.success-section {
text-align: center;
padding: 40px 20px;
display: none;
}
.success-section.show {
display: block;
}
.success-icon {
width: 80px;
height: 80px;
border-radius: 50%;
background: var(--success-color);
color: white;
font-size: 48px;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 20px;
animation: scaleIn 0.5s ease;
}
@keyframes scaleIn {
0% { transform: scale(0); }
50% { transform: scale(1.2); }
100% { transform: scale(1); }
}
.success-section h2 {
font-size: 24px;
margin-bottom: 8px;
}
.success-section p {
color: var(--text-secondary);
}
.return-btn {
margin-top: 24px;
padding: 12px 32px;
background: var(--primary-color);
color: white;
border: none;
border-radius: 8px;
font-size: 16px;
cursor: pointer;
}
/* 安全提示 */
.security-tips {
text-align: center;
padding: 12px;
font-size: 12px;
color: var(--text-secondary);
}
.security-tips span {
display: inline-flex;
align-items: center;
gap: 4px;
}
/* 响应式 */
@media (max-width: 480px) {
body {
padding: 0;
}
.cashier-container {
max-width: 100%;
}
.cashier-card {
border-radius: 0;
padding: 20px 16px;
}
.order-amount {
font-size: 36px;
}
}
</style>
</head>
<body>
<div class="cashier-container">
<!-- 订单信息卡片 -->
<div class="cashier-card" id="orderCard">
<div class="order-section">
<div class="order-title" id="orderTitle">VIP会员月卡</div>
<div class="order-amount">
<span class="currency">¥</span>
<span id="displayAmount">99.00</span>
</div>
<div class="order-info">
<span>订单号: <span id="orderSn">202401170001</span></span>
<span class="countdown" id="countdown">29:59</span>
</div>
</div>
<!-- 支付方式选择 -->
<div class="payment-section" id="paymentSection">
<h3>选择支付方式</h3>
<div class="payment-methods" id="paymentMethods">
<!-- 支付宝 -->
<div class="payment-method" data-gateway="alipay_wap" onclick="selectMethod(this)">
<div class="icon alipay">💙</div>
<div class="info">
<div class="name">支付宝</div>
<div class="desc">推荐有支付宝账户的用户使用</div>
</div>
<div class="radio"></div>
</div>
<!-- 微信支付 -->
<div class="payment-method" data-gateway="wechat_native" onclick="selectMethod(this)">
<div class="icon wechat">💚</div>
<div class="info">
<div class="name">微信支付</div>
<div class="desc">扫码支付,微信用户首选</div>
</div>
<div class="radio"></div>
</div>
<!-- PayPal -->
<div class="payment-method" data-gateway="paypal" onclick="selectMethod(this)">
<div class="icon paypal">🅿️</div>
<div class="info">
<div class="name">PayPal</div>
<div class="desc">支持信用卡,海外用户推荐</div>
</div>
<div class="radio"></div>
</div>
<!-- USDT -->
<div class="payment-method" data-gateway="usdt" onclick="selectMethod(this)">
<div class="icon usdt"></div>
<div class="info">
<div class="name">USDT (TRC20)</div>
<div class="desc">加密货币支付</div>
</div>
<div class="radio"></div>
</div>
</div>
</div>
<!-- 二维码区域 -->
<div class="qrcode-section" id="qrcodeSection">
<div class="qrcode-box">
<img id="qrcodeImg" src="" alt="支付二维码">
</div>
<div class="qrcode-text" id="qrcodeText">请使用微信扫描二维码支付</div>
<div class="qrcode-tip">二维码有效期 30 分钟,请尽快支付</div>
<!-- USDT 地址 -->
<div class="usdt-address" id="usdtAddress" style="display: none;"></div>
<button class="copy-btn" id="copyBtn" style="display: none;" onclick="copyAddress()">复制地址</button>
</div>
<!-- 支付按钮 -->
<button class="pay-btn" id="payBtn" onclick="doPay()">确认支付 ¥99.00</button>
</div>
<!-- 支付成功 -->
<div class="cashier-card success-section" id="successSection">
<div class="success-icon"></div>
<h2>支付成功</h2>
<p>感谢您的购买,订单已完成</p>
<button class="return-btn" onclick="goBack()">返回商户</button>
</div>
<!-- 安全提示 -->
<div class="security-tips">
<span>🔒 安全支付由卡若私域提供技术支持</span>
</div>
</div>
<script>
/**
* 通用收银台 JavaScript v4.0
* 作者: 卡若
*
* 使用方法:
* 1. 修改 API_BASE 为你的后端地址
* 2. 通过 URL 参数传入订单信息: ?order_sn=xxx&amount=99.00&title=商品名称
*/
// 配置
const API_BASE = '/api/payment'; // 你的后端接口地址
const POLL_INTERVAL = 3000; // 轮询间隔 (毫秒)
const ORDER_TIMEOUT = 30 * 60; // 订单超时时间 (秒)
// 状态
let selectedGateway = null;
let orderSn = '';
let orderAmount = 0;
let countdownTimer = null;
let pollTimer = null;
let remainingSeconds = ORDER_TIMEOUT;
// 初始化
document.addEventListener('DOMContentLoaded', function() {
// 从 URL 解析订单信息
const params = new URLSearchParams(window.location.search);
orderSn = params.get('order_sn') || '202401170001';
orderAmount = parseFloat(params.get('amount')) || 99.00;
const title = params.get('title') || 'VIP会员月卡';
// 更新UI
document.getElementById('orderSn').textContent = orderSn;
document.getElementById('orderTitle').textContent = title;
document.getElementById('displayAmount').textContent = orderAmount.toFixed(2);
document.getElementById('payBtn').textContent = `确认支付 ¥${orderAmount.toFixed(2)}`;
// 启动倒计时
startCountdown();
// 检测微信环境
if (isWechat()) {
// 微信环境默认选择JSAPI
const wechatMethod = document.querySelector('[data-gateway="wechat_native"]');
if (wechatMethod) {
wechatMethod.dataset.gateway = 'wechat_jsapi';
wechatMethod.querySelector('.desc').textContent = '微信内直接支付';
}
}
});
// 选择支付方式
function selectMethod(element) {
// 移除其他选中状态
document.querySelectorAll('.payment-method').forEach(el => {
el.classList.remove('active');
});
// 设置选中状态
element.classList.add('active');
selectedGateway = element.dataset.gateway;
// 隐藏二维码区域
document.getElementById('qrcodeSection').classList.remove('show');
document.getElementById('paymentSection').style.display = 'block';
document.getElementById('payBtn').style.display = 'block';
// 更新金额显示 (USDT显示美元)
if (selectedGateway === 'usdt') {
const usdtAmount = (orderAmount / 7.2).toFixed(2); // 简单汇率转换
document.getElementById('displayAmount').textContent = usdtAmount;
document.querySelector('.currency').textContent = '₮';
document.getElementById('payBtn').textContent = `确认支付 ₮${usdtAmount}`;
} else if (selectedGateway === 'paypal') {
const usdAmount = (orderAmount / 7.2).toFixed(2);
document.getElementById('displayAmount').textContent = usdAmount;
document.querySelector('.currency').textContent = '$';
document.getElementById('payBtn').textContent = `确认支付 $${usdAmount}`;
} else {
document.getElementById('displayAmount').textContent = orderAmount.toFixed(2);
document.querySelector('.currency').textContent = '¥';
document.getElementById('payBtn').textContent = `确认支付 ¥${orderAmount.toFixed(2)}`;
}
}
// 发起支付
async function doPay() {
if (!selectedGateway) {
alert('请选择支付方式');
return;
}
const payBtn = document.getElementById('payBtn');
payBtn.classList.add('loading');
payBtn.disabled = true;
try {
// 调用后端接口
const response = await fetch(`${API_BASE}/checkout`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
order_sn: orderSn,
gateway: selectedGateway,
return_url: window.location.href
})
});
const result = await response.json();
if (result.code !== 200) {
throw new Error(result.message || '支付发起失败');
}
const { type, payload, trade_sn } = result.data;
switch (type) {
case 'url':
// 跳转支付 (支付宝H5, PayPal, Stripe)
window.location.href = payload;
break;
case 'qrcode':
// 显示二维码 (微信Native, 支付宝扫码)
showQrcode(payload, selectedGateway);
startPolling(trade_sn);
break;
case 'json':
// 调起SDK (微信JSAPI)
callWechatPay(payload);
break;
case 'address':
// 显示钱包地址 (USDT)
showUsdtAddress(payload);
startPolling(trade_sn);
break;
case 'direct':
// 直接完成 (虚拟币支付)
showSuccess();
break;
}
} catch (error) {
console.error('支付失败:', error);
alert(error.message || '支付发起失败,请重试');
} finally {
payBtn.classList.remove('loading');
payBtn.disabled = false;
}
}
// 显示二维码
function showQrcode(content, gateway) {
document.getElementById('paymentSection').style.display = 'none';
document.getElementById('payBtn').style.display = 'none';
document.getElementById('qrcodeSection').classList.add('show');
// 使用在线服务生成二维码
const qrcodeUrl = `https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(content)}`;
document.getElementById('qrcodeImg').src = qrcodeUrl;
// 更新提示文字
const texts = {
'wechat_native': '请使用微信扫描二维码支付',
'alipay_qr': '请使用支付宝扫描二维码支付'
};
document.getElementById('qrcodeText').textContent = texts[gateway] || '请扫描二维码支付';
}
// 显示USDT地址
function showUsdtAddress(addressInfo) {
document.getElementById('paymentSection').style.display = 'none';
document.getElementById('payBtn').style.display = 'none';
document.getElementById('qrcodeSection').classList.add('show');
const { address, amount_usdt, memo } = addressInfo;
document.getElementById('qrcodeImg').src = `https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(address)}`;
document.getElementById('qrcodeText').textContent = `请向以下地址转账 ${amount_usdt} USDT (TRC20)`;
document.getElementById('usdtAddress').textContent = address;
document.getElementById('usdtAddress').style.display = 'block';
document.getElementById('copyBtn').style.display = 'inline-block';
}
// 复制地址
function copyAddress() {
const address = document.getElementById('usdtAddress').textContent;
navigator.clipboard.writeText(address).then(() => {
alert('地址已复制');
});
}
// 调用微信JSAPI支付
function callWechatPay(params) {
if (typeof WeixinJSBridge === 'undefined') {
alert('请在微信中打开此页面');
return;
}
WeixinJSBridge.invoke('getBrandWCPayRequest', params, function(res) {
if (res.err_msg === 'get_brand_wcpay_request:ok') {
showSuccess();
} else {
alert('支付取消或失败');
}
});
}
// 轮询支付状态
function startPolling(tradeSn) {
if (pollTimer) clearInterval(pollTimer);
pollTimer = setInterval(async () => {
try {
const response = await fetch(`${API_BASE}/status/${orderSn}`);
const result = await response.json();
if (result.data && result.data.status === 'paid') {
clearInterval(pollTimer);
showSuccess();
}
} catch (error) {
console.error('查询状态失败:', error);
}
}, POLL_INTERVAL);
}
// 显示支付成功
function showSuccess() {
document.getElementById('orderCard').style.display = 'none';
document.getElementById('successSection').classList.add('show');
// 停止轮询和倒计时
if (pollTimer) clearInterval(pollTimer);
if (countdownTimer) clearInterval(countdownTimer);
}
// 返回商户
function goBack() {
const returnUrl = new URLSearchParams(window.location.search).get('return_url');
if (returnUrl) {
window.location.href = returnUrl;
} else {
window.history.back();
}
}
// 倒计时
function startCountdown() {
countdownTimer = setInterval(() => {
remainingSeconds--;
if (remainingSeconds <= 0) {
clearInterval(countdownTimer);
alert('订单已超时,请重新下单');
window.history.back();
return;
}
const minutes = Math.floor(remainingSeconds / 60);
const seconds = remainingSeconds % 60;
document.getElementById('countdown').textContent =
`${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
}, 1000);
}
// 检测微信环境
function isWechat() {
return /MicroMessenger/i.test(navigator.userAgent);
}
</script>
</body>
</html>

View File

@@ -0,0 +1,294 @@
"""
支付宝网关实现 (Alipay Gateway)
基于 www.lytiao.com 项目提取
作者: 卡若
版本: v4.0
"""
import json
import base64
import hashlib
import logging
from typing import Dict, Any, Optional
from urllib.parse import urlencode, parse_qs
try:
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
from Crypto.Hash import SHA256
except ImportError:
print("请安装 pycryptodome: pip install pycryptodome")
from payment_factory import (
AbstractGateway, CreateTradeData, TradeResult, NotifyResult,
SignatureError, logger
)
class AlipayGateway(AbstractGateway):
"""
支付宝网关
支持:
- 电脑网站支付 (platform_type='web')
- 手机网站支付 (platform_type='wap')
- 扫码支付 (platform_type='qr')
"""
GATEWAY_URL = 'https://openapi.alipay.com/gateway.do'
SANDBOX_URL = 'https://openapi.alipaydev.com/gateway.do'
def __init__(self, config: Dict[str, Any]):
super().__init__(config)
self.app_id = config.get('app_id', '')
self.pid = config.get('pid', '')
self.seller_email = config.get('seller_email', '')
self.private_key = config.get('private_key', '')
self.public_key = config.get('public_key', '')
self.md5_key = config.get('md5_key', '')
def create_trade(self, data: CreateTradeData) -> TradeResult:
"""创建支付宝交易"""
platform_type = data.platform_type.capitalize()
if platform_type == 'Web':
return self._create_web_trade(data)
elif platform_type == 'Wap':
return self._create_wap_trade(data)
elif platform_type == 'Qr':
return self._create_qr_trade(data)
else:
raise ValueError(f"不支持的支付类型: {platform_type}")
def _create_web_trade(self, data: CreateTradeData) -> TradeResult:
"""电脑网站支付"""
biz_content = {
'subject': data.goods_title[:256],
'out_trade_no': data.trade_sn,
'total_amount': str(data.amount / 100), # 分转元
'product_code': 'FAST_INSTANT_TRADE_PAY',
'body': data.goods_detail[:128] if data.goods_detail else '',
'passback_params': json.dumps(data.attach) if data.attach else '',
}
params = self._build_params('alipay.trade.page.pay', biz_content, data.return_url, data.notify_url)
# 生成签名
sign = self._generate_sign(params)
params['sign'] = sign
# 构建跳转URL
pay_url = f"{self.GATEWAY_URL}?{urlencode(params)}"
return TradeResult(
type='url',
payload=pay_url,
trade_sn=data.trade_sn
)
def _create_wap_trade(self, data: CreateTradeData) -> TradeResult:
"""手机网站支付"""
biz_content = {
'subject': data.goods_title[:256],
'out_trade_no': data.trade_sn,
'total_amount': str(data.amount / 100),
'product_code': 'QUICK_WAP_WAY',
'body': data.goods_detail[:128] if data.goods_detail else '',
'passback_params': json.dumps(data.attach) if data.attach else '',
}
params = self._build_params('alipay.trade.wap.pay', biz_content, data.return_url, data.notify_url)
sign = self._generate_sign(params)
params['sign'] = sign
pay_url = f"{self.GATEWAY_URL}?{urlencode(params)}"
return TradeResult(
type='url',
payload=pay_url,
trade_sn=data.trade_sn
)
def _create_qr_trade(self, data: CreateTradeData) -> TradeResult:
"""扫码支付 (当面付)"""
biz_content = {
'subject': data.goods_title[:256],
'out_trade_no': data.trade_sn,
'total_amount': str(data.amount / 100),
'body': data.goods_detail[:128] if data.goods_detail else '',
}
params = self._build_params('alipay.trade.precreate', biz_content, '', data.notify_url)
sign = self._generate_sign(params)
params['sign'] = sign
# 调用接口获取二维码
import requests
response = requests.post(self.GATEWAY_URL, data=params)
result = response.json()
if 'alipay_trade_precreate_response' in result:
resp = result['alipay_trade_precreate_response']
if resp.get('code') == '10000':
return TradeResult(
type='qrcode',
payload=resp['qr_code'],
trade_sn=data.trade_sn
)
raise Exception(f"创建支付宝扫码支付失败: {result}")
def _build_params(self, method: str, biz_content: dict, return_url: str, notify_url: str) -> dict:
"""构建公共参数"""
import time
params = {
'app_id': self.app_id,
'method': method,
'charset': 'utf-8',
'sign_type': 'RSA2',
'timestamp': time.strftime('%Y-%m-%d %H:%M:%S'),
'version': '1.0',
'biz_content': json.dumps(biz_content, ensure_ascii=False),
}
if return_url:
params['return_url'] = return_url
if notify_url:
params['notify_url'] = notify_url
return params
def _generate_sign(self, params: dict) -> str:
"""生成RSA2签名"""
# 排序并拼接
sorted_params = sorted([(k, v) for k, v in params.items() if v])
sign_str = '&'.join([f'{k}={v}' for k, v in sorted_params])
# RSA2签名
key = RSA.import_key(f"-----BEGIN RSA PRIVATE KEY-----\n{self.private_key}\n-----END RSA PRIVATE KEY-----")
signer = PKCS1_v1_5.new(key)
hash_obj = SHA256.new(sign_str.encode('utf-8'))
signature = signer.sign(hash_obj)
return base64.b64encode(signature).decode('utf-8')
def verify_sign(self, data: Dict[str, Any]) -> bool:
"""验证支付宝签名"""
sign = data.pop('sign', '')
sign_type = data.pop('sign_type', 'RSA2')
if not sign:
return False
# 排序并拼接
sorted_params = sorted([(k, v) for k, v in data.items() if v and k != 'sign_type'])
sign_str = '&'.join([f'{k}={v}' for k, v in sorted_params])
try:
key = RSA.import_key(f"-----BEGIN PUBLIC KEY-----\n{self.public_key}\n-----END PUBLIC KEY-----")
verifier = PKCS1_v1_5.new(key)
hash_obj = SHA256.new(sign_str.encode('utf-8'))
verifier.verify(hash_obj, base64.b64decode(sign))
return True
except (ValueError, TypeError) as e:
logger.error(f"支付宝签名验证失败: {e}")
return False
def parse_notify(self, data: Dict[str, Any]) -> NotifyResult:
"""解析支付宝回调"""
trade_status = data.get('trade_status', '')
if trade_status in ['TRADE_SUCCESS', 'TRADE_FINISHED']:
status = 'paid'
else:
status = 'failed'
# 解析透传参数
attach = {}
passback = data.get('passback_params', '')
if passback:
try:
attach = json.loads(passback)
except:
pass
# 解析支付时间
import time
gmt_payment = data.get('gmt_payment', '')
if gmt_payment:
pay_time = int(time.mktime(time.strptime(gmt_payment, '%Y-%m-%d %H:%M:%S')))
else:
pay_time = int(time.time())
return NotifyResult(
status=status,
trade_sn=data.get('out_trade_no', ''),
platform_sn=data.get('trade_no', ''),
pay_amount=int(float(data.get('total_amount', 0)) * 100),
pay_time=pay_time,
currency='CNY',
attach=attach,
raw_data=data
)
def close_trade(self, trade_sn: str) -> bool:
"""关闭交易"""
biz_content = {
'out_trade_no': trade_sn,
}
params = self._build_params('alipay.trade.close', biz_content, '', '')
sign = self._generate_sign(params)
params['sign'] = sign
import requests
response = requests.post(self.GATEWAY_URL, data=params)
result = response.json()
resp = result.get('alipay_trade_close_response', {})
return resp.get('code') == '10000'
def query_trade(self, trade_sn: str) -> Optional[NotifyResult]:
"""查询交易"""
biz_content = {
'out_trade_no': trade_sn,
}
params = self._build_params('alipay.trade.query', biz_content, '', '')
sign = self._generate_sign(params)
params['sign'] = sign
import requests
response = requests.post(self.GATEWAY_URL, data=params)
result = response.json()
resp = result.get('alipay_trade_query_response', {})
if resp.get('code') == '10000' and resp.get('trade_status') in ['TRADE_SUCCESS', 'TRADE_FINISHED']:
return self.parse_notify(resp)
return None
def refund(self, trade_sn: str, refund_sn: str, amount: int, reason: str = '') -> bool:
"""发起退款"""
biz_content = {
'out_trade_no': trade_sn,
'out_request_no': refund_sn,
'refund_amount': str(amount / 100),
'refund_reason': reason or '用户申请退款',
}
params = self._build_params('alipay.trade.refund', biz_content, '', '')
sign = self._generate_sign(params)
params['sign'] = sign
import requests
response = requests.post(self.GATEWAY_URL, data=params)
result = response.json()
resp = result.get('alipay_trade_refund_response', {})
return resp.get('code') == '10000'
def success_response(self) -> str:
return 'success'

View File

@@ -0,0 +1,339 @@
"""
支付网关工厂 (Payment Gateway Factory)
基于 www.lytiao.com 项目提取的支付逻辑,适配 Python FastAPI
作者: 卡若
版本: v4.0
"""
import os
import time
import hashlib
import logging
from abc import ABC, abstractmethod
from typing import Dict, Any, Optional, Tuple
from dataclasses import dataclass
from enum import Enum
logger = logging.getLogger('payment')
class PaymentPlatform(Enum):
"""支付平台枚举"""
ALIPAY = 'alipay'
WECHAT = 'wechat'
PAYPAL = 'paypal'
STRIPE = 'stripe'
USDT = 'usdt'
COIN = 'coin'
class TradeType(Enum):
"""交易类型"""
PURCHASE = 'purchase' # 购买
RECHARGE = 'recharge' # 充值
class TradeStatus(Enum):
"""交易状态"""
PAYING = 'paying'
PAID = 'paid'
CLOSED = 'closed'
REFUNDED = 'refunded'
@dataclass
class CreateTradeData:
"""创建交易请求数据"""
goods_title: str
goods_detail: str
trade_sn: str
order_sn: str
amount: int # 金额,单位:分
notify_url: str
return_url: str = ''
platform_type: str = 'web' # web/wap/jsapi/native/h5/app
create_ip: str = ''
open_id: str = '' # 微信JSAPI支付需要
attach: Dict[str, Any] = None
@dataclass
class TradeResult:
"""交易结果"""
type: str # url/qrcode/json/address/direct
payload: Any # 支付数据
trade_sn: str
expiration: int = 1800 # 过期时间(秒)
@dataclass
class NotifyResult:
"""回调解析结果"""
status: str
trade_sn: str
platform_sn: str
pay_amount: int # 分
pay_time: int # 时间戳
currency: str = 'CNY'
attach: Dict[str, Any] = None
raw_data: Dict[str, Any] = None
class PaymentException(Exception):
"""支付异常基类"""
pass
class SignatureError(PaymentException):
"""签名验证失败"""
pass
class AmountMismatchError(PaymentException):
"""金额不匹配"""
pass
class GatewayNotFoundError(PaymentException):
"""支付网关不存在"""
pass
class AbstractGateway(ABC):
"""
支付网关抽象基类
所有支付网关必须实现此接口
"""
def __init__(self, config: Dict[str, Any] = None):
self.config = config or {}
@abstractmethod
def create_trade(self, data: CreateTradeData) -> TradeResult:
"""
创建交易
Args:
data: 交易数据
Returns:
TradeResult: 包含支付参数的结果
"""
pass
@abstractmethod
def verify_sign(self, data: Dict[str, Any]) -> bool:
"""
验证签名
Args:
data: 回调原始数据
Returns:
bool: 签名是否有效
"""
pass
@abstractmethod
def parse_notify(self, data: Any) -> NotifyResult:
"""
解析回调数据
Args:
data: 回调原始数据 (可能是dict或xml字符串)
Returns:
NotifyResult: 解析后的结果
"""
pass
@abstractmethod
def close_trade(self, trade_sn: str) -> bool:
"""
关闭交易
Args:
trade_sn: 交易流水号
Returns:
bool: 是否关闭成功
"""
pass
@abstractmethod
def query_trade(self, trade_sn: str) -> Optional[NotifyResult]:
"""
查询交易状态
Args:
trade_sn: 交易流水号
Returns:
NotifyResult: 交易状态未支付返回None
"""
pass
@abstractmethod
def refund(self, trade_sn: str, refund_sn: str, amount: int, reason: str = '') -> bool:
"""
发起退款
Args:
trade_sn: 原交易流水号
refund_sn: 退款单号
amount: 退款金额(分)
reason: 退款原因
Returns:
bool: 是否成功
"""
pass
def success_response(self) -> str:
"""回调成功响应"""
return 'success'
def fail_response(self) -> str:
"""回调失败响应"""
return 'fail'
class PaymentFactory:
"""
支付网关工厂
使用示例:
# 注册网关
PaymentFactory.register('alipay', AlipayGateway)
PaymentFactory.register('wechat', WechatGateway)
# 创建网关实例
gateway = PaymentFactory.create('wechat_jsapi')
result = gateway.create_trade(data)
"""
_gateways: Dict[str, type] = {}
@classmethod
def register(cls, name: str, gateway_class: type):
"""注册支付网关"""
cls._gateways[name] = gateway_class
logger.info(f"注册支付网关: {name}")
@classmethod
def create(cls, gateway: str) -> AbstractGateway:
"""
创建支付网关实例
Args:
gateway: 网关名称,格式如 'wechat_jsapi',会取下划线前的部分
Returns:
AbstractGateway: 网关实例
"""
gateway_name = gateway.split('_')[0]
if gateway_name not in cls._gateways:
raise GatewayNotFoundError(f"不支持的支付网关: {gateway}")
gateway_class = cls._gateways[gateway_name]
config = cls._get_gateway_config(gateway_name)
return gateway_class(config)
@classmethod
def _get_gateway_config(cls, gateway_name: str) -> Dict[str, Any]:
"""获取网关配置"""
config_map = {
'alipay': {
'app_id': os.getenv('ALIPAY_APP_ID', ''),
'pid': os.getenv('ALIPAY_PID', ''),
'seller_email': os.getenv('ALIPAY_SELLER_EMAIL', ''),
'private_key': os.getenv('ALIPAY_PRIVATE_KEY', ''),
'public_key': os.getenv('ALIPAY_PUBLIC_KEY', ''),
'md5_key': os.getenv('ALIPAY_MD5_KEY', ''),
},
'wechat': {
'appid': os.getenv('WECHAT_APPID', ''),
'app_secret': os.getenv('WECHAT_APP_SECRET', ''),
'mch_id': os.getenv('WECHAT_MCH_ID', ''),
'mch_key': os.getenv('WECHAT_MCH_KEY', ''),
'cert_path': os.getenv('WECHAT_CERT_PATH', ''),
'key_path': os.getenv('WECHAT_KEY_PATH', ''),
},
'paypal': {
'client_id': os.getenv('PAYPAL_CLIENT_ID', ''),
'client_secret': os.getenv('PAYPAL_CLIENT_SECRET', ''),
'mode': os.getenv('PAYPAL_MODE', 'sandbox'),
},
'stripe': {
'public_key': os.getenv('STRIPE_PUBLIC_KEY', ''),
'secret_key': os.getenv('STRIPE_SECRET_KEY', ''),
'webhook_secret': os.getenv('STRIPE_WEBHOOK_SECRET', ''),
},
}
return config_map.get(gateway_name, {})
@classmethod
def get_enabled_gateways(cls) -> list:
"""获取已启用的支付网关列表"""
enabled = []
if os.getenv('ALIPAY_ENABLED', 'false').lower() == 'true':
enabled.append({
'gateway': 'alipay',
'name': '支付宝',
'icon': '/icons/alipay.png',
'types': ['web', 'wap', 'qr']
})
if os.getenv('WECHAT_ENABLED', 'false').lower() == 'true':
enabled.append({
'gateway': 'wechat',
'name': '微信支付',
'icon': '/icons/wechat.png',
'types': ['native', 'jsapi', 'h5', 'app']
})
if os.getenv('PAYPAL_ENABLED', 'false').lower() == 'true':
enabled.append({
'gateway': 'paypal',
'name': 'PayPal',
'icon': '/icons/paypal.png',
'types': ['redirect']
})
if os.getenv('STRIPE_ENABLED', 'false').lower() == 'true':
enabled.append({
'gateway': 'stripe',
'name': 'Stripe',
'icon': '/icons/stripe.png',
'types': ['redirect']
})
return enabled
def generate_trade_sn(prefix: str = 'T') -> str:
"""
生成交易流水号
格式: 前缀 + 年月日时分秒 + 5位随机数
示例: T2024011710053012345
"""
import random
timestamp = time.strftime('%Y%m%d%H%M%S')
random_num = random.randint(10000, 99999)
return f"{prefix}{timestamp}{random_num}"
def yuan_to_fen(yuan: float) -> int:
"""元转分"""
return int(round(yuan * 100))
def fen_to_yuan(fen: int) -> float:
"""分转元"""
return round(fen / 100, 2)

View File

@@ -0,0 +1,317 @@
"""
微信支付网关实现 (Wechat Pay Gateway)
基于 www.lytiao.com 项目提取
作者: 卡若
版本: v4.0
"""
import json
import time
import hashlib
import logging
import xml.etree.ElementTree as ET
from typing import Dict, Any, Optional
from urllib.parse import urlencode
import uuid
from payment_factory import (
AbstractGateway, CreateTradeData, TradeResult, NotifyResult,
SignatureError, logger
)
class WechatGateway(AbstractGateway):
"""
微信支付网关
支持:
- Native扫码支付 (platform_type='native')
- JSAPI公众号/小程序支付 (platform_type='jsapi')
- H5支付 (platform_type='h5')
- APP支付 (platform_type='app')
"""
UNIFIED_ORDER_URL = 'https://api.mch.weixin.qq.com/pay/unifiedorder'
ORDER_QUERY_URL = 'https://api.mch.weixin.qq.com/pay/orderquery'
CLOSE_ORDER_URL = 'https://api.mch.weixin.qq.com/pay/closeorder'
REFUND_URL = 'https://api.mch.weixin.qq.com/secapi/pay/refund'
def __init__(self, config: Dict[str, Any]):
super().__init__(config)
self.appid = config.get('appid', '')
self.app_secret = config.get('app_secret', '')
self.mch_id = config.get('mch_id', '')
self.mch_key = config.get('mch_key', '')
self.cert_path = config.get('cert_path', '')
self.key_path = config.get('key_path', '')
def create_trade(self, data: CreateTradeData) -> TradeResult:
"""创建微信支付交易"""
platform_type = data.platform_type.upper()
# 构建统一下单参数
params = {
'appid': self.appid,
'mch_id': self.mch_id,
'nonce_str': self._generate_nonce(),
'body': data.goods_title[:128],
'out_trade_no': data.trade_sn,
'total_fee': str(data.amount), # 微信以分为单位
'spbill_create_ip': data.create_ip or '127.0.0.1',
'notify_url': data.notify_url,
'trade_type': platform_type,
'attach': json.dumps(data.attach) if data.attach else '',
}
# JSAPI需要openid
if platform_type == 'JSAPI':
if not data.open_id:
raise ValueError("微信JSAPI支付需要提供 openid")
params['openid'] = data.open_id
# H5支付需要scene_info
if platform_type == 'MWEB':
params['scene_info'] = json.dumps({
'h5_info': {
'type': 'Wap',
'wap_url': data.return_url,
'wap_name': data.goods_title[:32]
}
})
# 生成签名
params['sign'] = self._generate_sign(params)
# 调用统一下单接口
import requests
xml_data = self._dict_to_xml(params)
response = requests.post(self.UNIFIED_ORDER_URL, data=xml_data.encode('utf-8'))
result = self._xml_to_dict(response.text)
if result.get('return_code') != 'SUCCESS':
raise Exception(f"微信支付统一下单失败: {result.get('return_msg')}")
if result.get('result_code') != 'SUCCESS':
raise Exception(f"微信支付统一下单失败: {result.get('err_code_des')}")
# 根据类型返回不同格式
if platform_type == 'NATIVE':
# 扫码支付返回二维码链接
return TradeResult(
type='qrcode',
payload=result['code_url'],
trade_sn=data.trade_sn
)
elif platform_type == 'JSAPI':
# 公众号支付返回JS SDK参数
prepay_id = result['prepay_id']
js_params = {
'appId': self.appid,
'timeStamp': str(int(time.time())),
'nonceStr': self._generate_nonce(),
'package': f'prepay_id={prepay_id}',
'signType': 'MD5',
}
js_params['paySign'] = self._generate_sign(js_params)
return TradeResult(
type='json',
payload=js_params,
trade_sn=data.trade_sn
)
elif platform_type == 'MWEB':
# H5支付返回跳转链接
mweb_url = result['mweb_url']
if data.return_url:
mweb_url += f"&redirect_url={urlencode({'': data.return_url})[1:]}"
return TradeResult(
type='url',
payload=mweb_url,
trade_sn=data.trade_sn
)
elif platform_type == 'APP':
# APP支付返回SDK参数
prepay_id = result['prepay_id']
app_params = {
'appid': self.appid,
'partnerid': self.mch_id,
'prepayid': prepay_id,
'package': 'Sign=WXPay',
'noncestr': self._generate_nonce(),
'timestamp': str(int(time.time())),
}
app_params['sign'] = self._generate_sign(app_params)
return TradeResult(
type='json',
payload=app_params,
trade_sn=data.trade_sn
)
else:
raise ValueError(f"不支持的微信支付类型: {platform_type}")
def verify_sign(self, data: Dict[str, Any]) -> bool:
"""验证微信签名"""
sign = data.pop('sign', '')
if not sign:
return False
calculated_sign = self._generate_sign(data)
return calculated_sign == sign
def parse_notify(self, data: Any) -> NotifyResult:
"""解析微信回调"""
# 如果是XML字符串先转换为dict
if isinstance(data, str):
data = self._xml_to_dict(data)
# 验证签名
if not self.verify_sign(data.copy()):
raise SignatureError("微信签名验证失败")
result_code = data.get('result_code', '')
if result_code == 'SUCCESS':
status = 'paid'
else:
status = 'failed'
# 解析透传参数
attach = {}
attach_str = data.get('attach', '')
if attach_str:
try:
attach = json.loads(attach_str)
except:
pass
# 解析支付时间 (格式: 20240117100530)
time_end = data.get('time_end', '')
if time_end:
pay_time = int(time.mktime(time.strptime(time_end, '%Y%m%d%H%M%S')))
else:
pay_time = int(time.time())
return NotifyResult(
status=status,
trade_sn=data.get('out_trade_no', ''),
platform_sn=data.get('transaction_id', ''),
pay_amount=int(data.get('cash_fee', 0)),
pay_time=pay_time,
currency=data.get('fee_type', 'CNY'),
attach=attach,
raw_data=data
)
def close_trade(self, trade_sn: str) -> bool:
"""关闭微信交易"""
params = {
'appid': self.appid,
'mch_id': self.mch_id,
'out_trade_no': trade_sn,
'nonce_str': self._generate_nonce(),
}
params['sign'] = self._generate_sign(params)
import requests
xml_data = self._dict_to_xml(params)
response = requests.post(self.CLOSE_ORDER_URL, data=xml_data.encode('utf-8'))
result = self._xml_to_dict(response.text)
return result.get('result_code') == 'SUCCESS'
def query_trade(self, trade_sn: str) -> Optional[NotifyResult]:
"""查询微信交易"""
params = {
'appid': self.appid,
'mch_id': self.mch_id,
'out_trade_no': trade_sn,
'nonce_str': self._generate_nonce(),
}
params['sign'] = self._generate_sign(params)
import requests
xml_data = self._dict_to_xml(params)
response = requests.post(self.ORDER_QUERY_URL, data=xml_data.encode('utf-8'))
result = self._xml_to_dict(response.text)
if result.get('trade_state') == 'SUCCESS':
return self.parse_notify(result)
return None
def refund(self, trade_sn: str, refund_sn: str, amount: int, reason: str = '') -> bool:
"""微信退款 (需要证书)"""
# 先查询原交易获取金额
query_result = self.query_trade(trade_sn)
if not query_result:
return False
params = {
'appid': self.appid,
'mch_id': self.mch_id,
'nonce_str': self._generate_nonce(),
'out_trade_no': trade_sn,
'out_refund_no': refund_sn,
'total_fee': str(query_result.pay_amount),
'refund_fee': str(amount),
'refund_desc': reason or '用户申请退款',
}
params['sign'] = self._generate_sign(params)
import requests
xml_data = self._dict_to_xml(params)
# 退款需要证书
response = requests.post(
self.REFUND_URL,
data=xml_data.encode('utf-8'),
cert=(self.cert_path, self.key_path)
)
result = self._xml_to_dict(response.text)
return result.get('result_code') == 'SUCCESS'
def success_response(self) -> str:
"""微信回调成功响应"""
return '<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>'
def fail_response(self) -> str:
"""微信回调失败响应"""
return '<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[ERROR]]></return_msg></xml>'
def _generate_nonce(self) -> str:
"""生成随机字符串"""
return uuid.uuid4().hex
def _generate_sign(self, params: dict) -> str:
"""生成MD5签名"""
# 排序并拼接
sorted_params = sorted([(k, v) for k, v in params.items() if v and k != 'sign'])
sign_str = '&'.join([f'{k}={v}' for k, v in sorted_params])
sign_str += f'&key={self.mch_key}'
# MD5签名
return hashlib.md5(sign_str.encode('utf-8')).hexdigest().upper()
def _dict_to_xml(self, data: dict) -> str:
"""字典转XML"""
xml = ['<xml>']
for k, v in data.items():
if isinstance(v, str):
xml.append(f'<{k}><![CDATA[{v}]]></{k}>')
else:
xml.append(f'<{k}>{v}</{k}>')
xml.append('</xml>')
return ''.join(xml)
def _xml_to_dict(self, xml_str: str) -> dict:
"""XML转字典"""
root = ET.fromstring(xml_str)
return {child.tag: child.text for child in root}

View File

@@ -0,0 +1,101 @@
# ============================================================================
# 卡若私域支付配置 (Karuo Payment Config)
# ============================================================================
# ⚠️ 警告: 此文件包含敏感信息,请勿提交到 Git
# 使用方法: 复制此文件为 .env 并填入真实值
# ============================================================================
# ----------------------------------------------------------------------------
# 1. 基础环境
# ----------------------------------------------------------------------------
APP_ENV=production
APP_NAME=卡若私域
APP_URL=https://www.lytiao.com
APP_CURRENCY=CNY
# ----------------------------------------------------------------------------
# 2. 数据库 (卡若私域数据库)
# ----------------------------------------------------------------------------
DB_CONNECTION=mysql
DB_HOST=10.88.182.62
DB_PORT=3306
DB_DATABASE=payment_db
DB_USERNAME=root
DB_PASSWORD=Vtka(agu)-1
# ----------------------------------------------------------------------------
# 3. 支付宝 (Alipay)
# ----------------------------------------------------------------------------
ALIPAY_ENABLED=true
ALIPAY_MODE=production
ALIPAY_APP_ID=
ALIPAY_PID=2088511801157159
ALIPAY_SELLER_EMAIL=zhengzhiqun@vip.qq.com
ALIPAY_PRIVATE_KEY=
ALIPAY_PUBLIC_KEY=
ALIPAY_MD5_KEY=lz6ey1h3kl9zqkgtjz3avb5gk37wzbrp
# ----------------------------------------------------------------------------
# 4. 微信支付 (Wechat Pay)
# ----------------------------------------------------------------------------
WECHAT_ENABLED=true
WECHAT_MODE=production
# 网站/H5支付
WECHAT_APPID=wx432c93e275548671
WECHAT_APP_SECRET=25b7e7fdb7998e5107e242ebb6ddabd0
# 服务号 (JSAPI)
WECHAT_SERVICE_APPID=wx7c0dbf34ddba300d
WECHAT_SERVICE_SECRET=f865ef18c43dfea6cbe3b1f1aebdb82e
# 商户信息
WECHAT_MCH_ID=1318592501
WECHAT_MCH_KEY=wx3e31b068be59ddc131b068be59ddc2
# 证书路径
WECHAT_CERT_PATH=./cert/wechat/apiclient_cert.pem
WECHAT_KEY_PATH=./cert/wechat/apiclient_key.pem
# MP文件验证码
WECHAT_MP_VERIFY=SP8AfZJyAvprRORT
# ----------------------------------------------------------------------------
# 5. PayPal (暂未启用)
# ----------------------------------------------------------------------------
PAYPAL_ENABLED=false
PAYPAL_MODE=sandbox
PAYPAL_CLIENT_ID=
PAYPAL_CLIENT_SECRET=
# ----------------------------------------------------------------------------
# 6. Stripe (暂未启用)
# ----------------------------------------------------------------------------
STRIPE_ENABLED=false
STRIPE_MODE=test
STRIPE_PUBLIC_KEY=
STRIPE_SECRET_KEY=
STRIPE_WEBHOOK_SECRET=
# ----------------------------------------------------------------------------
# 7. USDT (暂未启用)
# ----------------------------------------------------------------------------
USDT_ENABLED=false
USDT_GATEWAY_TYPE=nowpayments
NOWPAYMENTS_API_KEY=
NOWPAYMENTS_IPN_SECRET=
# ----------------------------------------------------------------------------
# 8. 高级配置
# ----------------------------------------------------------------------------
# 虚拟币/积分
COIN_ENABLED=false
COIN_RATE=100
# 订单配置
ORDER_EXPIRE_MINUTES=30
TRADE_SN_PREFIX=T
# 日志
PAYMENT_LOG_LEVEL=info
PAYMENT_LOG_PATH=./logs/payment.log

View File

@@ -0,0 +1,142 @@
# 🌐 全球通用支付模块 (Universal Payment Module) v4.0
> **配置驱动 (Configuration-Driven)** | **API 优先 (API-First)** | **AI 智能对接**
>
> 让任何语言的项目在 5 分钟内接入支付宝、微信支付、PayPal、Stripe 和 USDT
## 📂 模块结构
```
Universal_Payment_Module/
├── 1_核心设计_通用协议/ # [灵魂] 定义了支付的"法律"
│ ├── 标准配置模板.yaml # 填空即可配置所有支付参数
│ ├── API接口定义.md # 无论用什么语言,接口都长这样
│ ├── 业务逻辑与模型.md # 数据库表结构设计 (Order/PayTrade)
│ └── 安全与合规.md # 支付安全最佳实践
├── 2_智能对接_AI指令/ # [工具] AI 编译器
│ ├── 通用集成指令.md # 发给 AI自动生成代码
│ └── Cursor规则.md # Cursor IDE 专用规则
├── 3_逻辑参考_通用实现/ # [参考] 可直接复用的代码
│ ├── 前端收银台Demo.html # 原生 JS 实现的通用收银台
│ ├── 后端源码/ # 多语言参考实现
│ │ ├── php/ # PHP (Laravel/Symfony)
│ │ ├── python/ # Python (FastAPI/Django)
│ │ ├── nodejs/ # Node.js (Express/NestJS)
│ │ └── java/ # Java (Spring Boot)
│ └── 前端模板/ # Vue/React/原生JS 模板
├── 4_卡若配置/ # [私有] 卡若的支付密钥 (勿提交Git)
│ └── .env.example # 配置示例
└── README.md # 本说明文档
```
## 🚀 极速对接 (3步完成)
### 第一步:配置 (Config)
```bash
# 1. 复制配置模板到你的项目
cp 1_核心设计_通用协议/标准配置模板.yaml your-project/.env
# 2. 填入你的支付密钥
```
### 第二步:生成代码 (Generate with AI)
```
发送给 Cursor/ChatGPT
"请读取 Universal_Payment_Module 目录,我的项目是 Python FastAPI
采用嵌入式集成,帮我生成支付模块代码。"
```
### 第三步:前端接入 (Frontend)
```javascript
// 只需调用一个 API
const result = await fetch('/api/payment/checkout', {
method: 'POST',
body: JSON.stringify({ order_sn: '202401170001', gateway: 'wechat_jsapi' })
});
```
## 🌍 支持的支付渠道
| 渠道 | 能力 | 场景 | 状态 |
|:---|:---|:---|:---|
| **支付宝 Alipay** | 扫码/H5/APP/小程序 | 中国市场 (CNY) | ✅ 已实现 |
| **微信支付 Wechat** | JSAPI/Native/H5/APP/小程序 | 中国市场 (CNY) | ✅ 已实现 |
| **PayPal** | 信用卡/订阅 | 全球市场 (USD/EUR) | ✅ 已实现 |
| **Stripe** | 信用卡/订阅/Apple Pay | 全球市场 | ✅ 已实现 |
| **USDT (TRC20)** | 链上转账/监听 | Web3/抗审查 | ✅ 已实现 |
## ✨ v4.0 核心特性
### 1. 配置驱动 (Zero-Code Config)
- 所有密钥通过环境变量注入,无需改动代码
- 支持多环境切换 (development/production)
### 2. 工厂模式 (Payment Factory)
```python
# 所有支付网关统一接口
payment = PaymentFactory.create('wechat')
result = payment.create_trade(order)
```
### 3. 幂等性保障 (Idempotency)
- 回调通知自动去重
- 订单状态机管理
### 4. AI 智能生成
- 提供 Cursor/Copilot 专用提示词
- 一键生成任意语言的完整实现
## 📖 快速参考
### 创建订单
```http
POST /api/payment/create_order
Content-Type: application/json
{
"user_id": "u1001",
"title": "VIP会员",
"amount": 99.00,
"currency": "CNY",
"product_id": "vip_monthly"
}
```
### 发起支付
```http
POST /api/payment/checkout
Content-Type: application/json
{
"order_sn": "202401170001",
"gateway": "wechat_jsapi",
"openid": "oXxx..."
}
```
### 支付状态查询
```http
GET /api/payment/status/202401170001
```
## 🔐 安全须知
1. **密钥安全**: 所有密钥存放在 `.env`**绝不提交到 Git**
2. **HTTPS 强制**: 生产环境必须启用 HTTPS
3. **签名验证**: 所有回调必须验签
4. **金额校验**: 支付金额必须与订单金额匹配
## 📚 相关文档
- [API 接口定义](./1_核心设计_通用协议/API接口定义.md)
- [数据库模型](./1_核心设计_通用协议/业务逻辑与模型.md)
- [AI 集成指令](./2_智能对接_AI指令/通用集成指令.md)
- [Cursor 规则](./2_智能对接_AI指令/Cursor规则.md)
---
**作者**: 卡若 | **联系**: 28533368 (微信) | **更新**: 2026-01-17