diff --git a/.github-upload-rules.md b/.github-upload-rules.md new file mode 100644 index 00000000..fc927623 --- /dev/null +++ b/.github-upload-rules.md @@ -0,0 +1,389 @@ +# GitHub 上传规则 + +## 仓库信息 + +**仓库地址**: https://github.com/fnvtk/Mycontent +**分支**: soul-content +**完整地址**: https://github.com/fnvtk/Mycontent/tree/soul-content + +--- + +## 快速上传命令 + +### 一键上传(推荐) +```bash +cd "/Users/karuo/Documents/开发/3、自营项目/一场soul的创业实验" +git add -A +git commit -m "更新内容" +git push origin soul-content +``` + +### 详细上传步骤 +```bash +# 1. 进入项目目录 +cd "/Users/karuo/Documents/开发/3、自营项目/一场soul的创业实验" + +# 2. 查看状态 +git status + +# 3. 添加所有更改 +git add -A + +# 4. 提交更改(修改提交信息) +git commit -m "feat: 更新说明" + +# 5. 推送到GitHub +git push origin soul-content +``` + +--- + +## 常用提交信息模板 + +### 功能更新 +```bash +git commit -m "feat: 添加新功能 + +- 功能1 +- 功能2 +- 功能3" +``` + +### Bug修复 +```bash +git commit -m "fix: 修复问题 + +- 修复问题1 +- 修复问题2" +``` + +### 界面优化 +```bash +git commit -m "style: 界面优化 + +- 优化首页 +- 优化匹配页面 +- 统一配色" +``` + +### 文档更新 +```bash +git commit -m "docs: 更新文档 + +- 更新README +- 添加使用说明" +``` + +### 性能优化 +```bash +git commit -m "perf: 性能优化 + +- 优化加载速度 +- 优化API响应" +``` + +--- + +## Talk功能上传规则 + +### Talk内容目录 +``` +book/ +├── 附录/ +│ └── 附录1|Soul派对房精选对话.md +└── talk/ (如果有独立的talk目录) +``` + +### 上传Talk内容 +```bash +# 1. 添加talk相关文件 +cd "/Users/karuo/Documents/开发/3、自营项目/一场soul的创业实验" +git add book/附录/附录1|Soul派对房精选对话.md +git add book/talk/* # 如果有talk目录 + +# 2. 提交 +git commit -m "feat: 更新Talk内容 + +- 添加新的对话记录 +- 更新精选对话" + +# 3. 推送 +git push origin soul-content +``` + +--- + +## 分支管理 + +### 查看当前分支 +```bash +git branch +``` + +### 切换分支 +```bash +git checkout soul-content +``` + +### 创建新分支 +```bash +git checkout -b new-branch-name +``` + +--- + +## 同步最新代码 + +### 拉取最新代码 +```bash +cd "/Users/karuo/Documents/开发/3、自营项目/一场soul的创业实验" +git pull origin soul-content +``` + +### 查看远程仓库 +```bash +git remote -v +``` + +--- + +## 回滚操作 + +### 撤销未提交的更改 +```bash +git checkout -- <文件名> +``` + +### 撤销已提交但未推送的提交 +```bash +git reset --soft HEAD^ +``` + +### 查看提交历史 +```bash +git log --oneline +``` + +--- + +## 忽略文件 + +### .gitignore 常用配置 +``` +# 依赖 +node_modules/ +.pnp +.pnp.js + +# 测试 +coverage/ + +# 生产构建 +.next/ +out/ +build/ +dist/ + +# 环境变量 +.env +.env.local +.env.production.local +.env.development.local + +# 调试日志 +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# 操作系统 +.DS_Store +Thumbs.db + +# IDE +.vscode/ +.idea/ +*.swp +*.swo + +# 小程序 +miniprogram/node_modules/ +miniprogram/.tea/ + +# 临时文件 +*.log +*.tmp +``` + +--- + +## 紧急情况处理 + +### 推送失败(冲突) +```bash +# 1. 拉取最新代码 +git pull origin soul-content + +# 2. 解决冲突后 +git add -A +git commit -m "fix: 解决冲突" +git push origin soul-content +``` + +### 强制推送(慎用) +```bash +git push -f origin soul-content +``` + +--- + +## 标签管理 + +### 创建版本标签 +```bash +git tag -a v1.3.1 -m "完美版本:首页对齐H5,64章精准数据" +git push origin v1.3.1 +``` + +### 查看所有标签 +```bash +git tag +``` + +--- + +## 自动化脚本 + +### 快速上传脚本 (quick-push.sh) +```bash +#!/bin/bash + +cd "/Users/karuo/Documents/开发/3、自营项目/一场soul的创业实验" + +echo "🚀 开始上传到GitHub..." + +# 添加所有更改 +git add -A + +# 获取提交信息 +echo "请输入提交信息(留空则使用默认信息):" +read commit_msg + +if [ -z "$commit_msg" ]; then + commit_msg="update: 更新内容 $(date '+%Y-%m-%d %H:%M:%S')" +fi + +# 提交 +git commit -m "$commit_msg" + +# 推送 +git push origin soul-content + +echo "✅ 上传完成!" +echo "📝 提交信息:$commit_msg" +echo "🔗 查看:https://github.com/fnvtk/Mycontent/tree/soul-content" +``` + +### 使用方法 +```bash +# 1. 赋予执行权限 +chmod +x quick-push.sh + +# 2. 执行脚本 +./quick-push.sh +``` + +--- + +## 常见问题 + +### Q: 推送时要求输入用户名密码? +**A**: 使用GitHub个人访问令牌(Personal Access Token) +```bash +# 设置远程仓库URL(使用token) +git remote set-url origin https://@github.com/fnvtk/Mycontent.git +``` + +### Q: 文件太大无法推送? +**A**: 使用Git LFS +```bash +git lfs install +git lfs track "*.大文件" +git add .gitattributes +``` + +### Q: 如何删除远程分支? +**A**: +```bash +git push origin --delete branch-name +``` + +--- + +## 项目结构 + +``` +一场soul的创业实验/ +├── app/ # Next.js应用 +├── book/ # 书籍内容(64章) +│ ├── 序言|... +│ ├── 第一篇|真实的人/ +│ ├── 第二篇|真实的行业/ +│ ├── 第三篇|真实的错误/ +│ ├── 第四篇|真实的赚钱/ +│ ├── 第五篇|真实的社会/ +│ ├── 尾声|... +│ └── 附录/ +│ └── 附录1|Soul派对房精选对话.md ← Talk内容 +├── miniprogram/ # 微信小程序 +├── components/ # 组件 +├── lib/ # 工具库 +├── public/ # 静态资源 +│ └── book-chapters.json # 64章数据 +├── scripts/ # 脚本 +│ └── sync-book-content.js +├── .gitignore # Git忽略配置 +├── package.json # 项目配置 +└── README.md # 项目说明 +``` + +--- + +## 下次上传流程 + +### 简单三步: +```bash +# 1. 进入目录 +cd "/Users/karuo/Documents/开发/3、自营项目/一场soul的创业实验" + +# 2. 添加并提交 +git add -A && git commit -m "更新内容" + +# 3. 推送 +git push origin soul-content +``` + +### 或使用别名(更快) +```bash +# 添加到 ~/.zshrc 或 ~/.bashrc +alias soul-push='cd "/Users/karuo/Documents/开发/3、自营项目/一场soul的创业实验" && git add -A && git commit -m "update: 更新内容" && git push origin soul-content' + +# 使用 +soul-push +``` + +--- + +## 重要提示 + +1. ⚠️ **推送前检查**:确保没有敏感信息(密钥、密码等) +2. 📝 **提交信息**:写清楚每次更改的内容 +3. 🔄 **定期备份**:重要节点创建标签 +4. 🚫 **不要推送**:node_modules、.env等文件 +5. ✅ **推送后验证**:访问GitHub确认更新成功 + +--- + +**仓库地址**: https://github.com/fnvtk/Mycontent/tree/soul-content + +**创建时间**: 2026年1月14日 +**最后更新**: v1.3.1 diff --git a/README.md b/README.md index abb72cd5..ae009582 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,16 @@ -# AI phone branding +# 一场SOUL的创业实验 -*Automatically synced with your [v0.app](https://v0.app) deployments* +## 项目简介 +这是一个基于 Soul 派对房真实商业故事开发的知识付费与分销系统。 -[![Deployed on Vercel](https://img.shields.io/badge/Deployed%20on-Vercel-black?style=for-the-badge&logo=vercel)](https://vercel.com/fnvtks-projects/v0--ap) -[![Built with v0](https://img.shields.io/badge/Built%20with-v0.app-black?style=for-the-badge)](https://v0.app/chat/tPF15XbLAKD) +### 核心功能 +- 📚 **电子书阅读**: 每日更新的真实商业案例。 +- 🤝 **找伙伴**: 匹配志同道合的创业合伙人。 +- 💰 **分销系统**: 90% 高额佣金,推广赚收益。 +- 👤 **个人中心**: 账号绑定、收益提现、阅读足迹。 -## Overview +## 技术开发 +本项目由 **卡若** 开发,核心逻辑与私域系统由 **存客宝** 提供技术支持。 -This repository will stay in sync with your deployed chats on [v0.app](https://v0.app). -Any changes you make to your deployed app will be automatically pushed to this repository from [v0.app](https://v0.app). - -## Deployment - -Your project is live at: - -**[https://vercel.com/fnvtks-projects/v0--ap](https://vercel.com/fnvtks-projects/v0--ap)** - -## Build your app - -Continue building your app on: - -**[https://v0.app/chat/tPF15XbLAKD](https://v0.app/chat/tPF15XbLAKD)** - -## How It Works - -1. Create and modify your project using [v0.app](https://v0.app) -2. Deploy your chats from the v0 interface -3. Changes are automatically pushed to this repository -4. Vercel deploys the latest version from this repository +--- +*版权所有 © 2026 卡若 & 存客宝* diff --git a/addons/Universal_Payment_Module copy/.cursorrules b/addons/Universal_Payment_Module copy/.cursorrules new file mode 100644 index 00000000..e9610e90 --- /dev/null +++ b/addons/Universal_Payment_Module copy/.cursorrules @@ -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` diff --git a/addons/Universal_Payment_Module copy/1_核心设计_通用协议/API接口定义.md b/addons/Universal_Payment_Module copy/1_核心设计_通用协议/API接口定义.md new file mode 100644 index 00000000..9487549d --- /dev/null +++ b/addons/Universal_Payment_Module copy/1_核心设计_通用协议/API接口定义.md @@ -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 + +# 微信 + + +# 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. **幂等性**: 相同订单号重复请求返回相同结果 diff --git a/addons/Universal_Payment_Module copy/1_核心设计_通用协议/业务逻辑与模型.md b/addons/Universal_Payment_Module copy/1_核心设计_通用协议/业务逻辑与模型.md new file mode 100644 index 00000000..b93a2250 --- /dev/null +++ b/addons/Universal_Payment_Module copy/1_核心设计_通用协议/业务逻辑与模型.md @@ -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. 退款幂等 +- 同一笔交易只能退款一次 (或限制总退款金额) diff --git a/addons/Universal_Payment_Module copy/1_核心设计_通用协议/安全与合规.md b/addons/Universal_Payment_Module copy/1_核心设计_通用协议/安全与合规.md new file mode 100644 index 00000000..009567de --- /dev/null +++ b/addons/Universal_Payment_Module copy/1_核心设计_通用协议/安全与合规.md @@ -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 白名单验证 (可选) +- [ ] 幂等性处理 +- [ ] 防重放攻击 +``` diff --git a/addons/Universal_Payment_Module copy/1_核心设计_通用协议/标准配置模板.yaml b/addons/Universal_Payment_Module copy/1_核心设计_通用协议/标准配置模板.yaml new file mode 100644 index 00000000..225a7af1 --- /dev/null +++ b/addons/Universal_Payment_Module copy/1_核心设计_通用协议/标准配置模板.yaml @@ -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 diff --git a/addons/Universal_Payment_Module copy/2_智能对接_AI指令/Cursor规则.md b/addons/Universal_Payment_Module copy/2_智能对接_AI指令/Cursor规则.md new file mode 100644 index 00000000..ad7d0ddc --- /dev/null +++ b/addons/Universal_Payment_Module copy/2_智能对接_AI指令/Cursor规则.md @@ -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 +- 回调: POST,form 表单格式 + +### 微信支付 Wechat +- SDK: `wechatpay-python-v3` / `wechatpay-java` +- 签名: HMAC-SHA256 / RSA (v3) +- 回调: POST,XML 格式 + +### PayPal +- SDK: `paypal-rest-sdk` +- 认证: OAuth 2.0 +- 回调: Webhook,JSON 格式 + +### Stripe +- SDK: `stripe` +- 认证: API Key +- 回调: Webhook,JSON 格式 + +--- + +## 代码生成模板 + +### 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; + abstract verifySign(data: Record): boolean; + abstract parseNotify(data: Record): NotifyResult; +} + +export class PaymentFactory { + private static gateways: Map 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 + └── 后端源码/ +``` diff --git a/addons/Universal_Payment_Module copy/2_智能对接_AI指令/通用集成指令.md b/addons/Universal_Payment_Module copy/2_智能对接_AI指令/通用集成指令.md new file mode 100644 index 00000000..a886e178 --- /dev/null +++ b/addons/Universal_Payment_Module copy/2_智能对接_AI指令/通用集成指令.md @@ -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 +- 作者: 卡若 diff --git a/addons/Universal_Payment_Module copy/3_逻辑参考_通用实现/前端收银台Demo.html b/addons/Universal_Payment_Module copy/3_逻辑参考_通用实现/前端收银台Demo.html new file mode 100644 index 00000000..060af045 --- /dev/null +++ b/addons/Universal_Payment_Module copy/3_逻辑参考_通用实现/前端收银台Demo.html @@ -0,0 +1,748 @@ + + + + + + 通用收银台 v4.0 + + + + +
+ +
+
+
VIP会员月卡
+
+ ¥ + 99.00 +
+
+ 订单号: 202401170001 + 29:59 +
+
+ + +
+

选择支付方式

+
+ +
+
💙
+
+
支付宝
+
推荐有支付宝账户的用户使用
+
+
+
+ + +
+
💚
+
+
微信支付
+
扫码支付,微信用户首选
+
+
+
+ + +
+
🅿️
+
+
PayPal
+
支持信用卡,海外用户推荐
+
+
+
+ + +
+
+
+
USDT (TRC20)
+
加密货币支付
+
+
+
+
+
+ + +
+
+ 支付二维码 +
+
请使用微信扫描二维码支付
+
二维码有效期 30 分钟,请尽快支付
+ + + + +
+ + + +
+ + +
+
+

支付成功

+

感谢您的购买,订单已完成

+ +
+ + +
+ 🔒 安全支付由卡若私域提供技术支持 +
+
+ + + + + diff --git a/addons/Universal_Payment_Module copy/3_逻辑参考_通用实现/后端源码/python/alipay_gateway.py b/addons/Universal_Payment_Module copy/3_逻辑参考_通用实现/后端源码/python/alipay_gateway.py new file mode 100644 index 00000000..e17e637d --- /dev/null +++ b/addons/Universal_Payment_Module copy/3_逻辑参考_通用实现/后端源码/python/alipay_gateway.py @@ -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' diff --git a/addons/Universal_Payment_Module copy/3_逻辑参考_通用实现/后端源码/python/payment_factory.py b/addons/Universal_Payment_Module copy/3_逻辑参考_通用实现/后端源码/python/payment_factory.py new file mode 100644 index 00000000..bc1791bd --- /dev/null +++ b/addons/Universal_Payment_Module copy/3_逻辑参考_通用实现/后端源码/python/payment_factory.py @@ -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) diff --git a/addons/Universal_Payment_Module copy/3_逻辑参考_通用实现/后端源码/python/wechat_gateway.py b/addons/Universal_Payment_Module copy/3_逻辑参考_通用实现/后端源码/python/wechat_gateway.py new file mode 100644 index 00000000..b6d09eb9 --- /dev/null +++ b/addons/Universal_Payment_Module copy/3_逻辑参考_通用实现/后端源码/python/wechat_gateway.py @@ -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 '' + + def fail_response(self) -> str: + """微信回调失败响应""" + return '' + + 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 = [''] + for k, v in data.items(): + if isinstance(v, str): + xml.append(f'<{k}>') + else: + xml.append(f'<{k}>{v}') + xml.append('') + 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} diff --git a/addons/Universal_Payment_Module copy/4_卡若配置/env.example.txt b/addons/Universal_Payment_Module copy/4_卡若配置/env.example.txt new file mode 100644 index 00000000..345aba6e --- /dev/null +++ b/addons/Universal_Payment_Module copy/4_卡若配置/env.example.txt @@ -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 diff --git a/addons/Universal_Payment_Module copy/README.md b/addons/Universal_Payment_Module copy/README.md new file mode 100644 index 00000000..be151a97 --- /dev/null +++ b/addons/Universal_Payment_Module copy/README.md @@ -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 diff --git a/addons/Universal_Payment_Module/1_核心设计_通用协议/API接口定义.md b/addons/Universal_Payment_Module/1_核心设计_通用协议/API接口定义.md deleted file mode 100644 index 10008242..00000000 --- a/addons/Universal_Payment_Module/1_核心设计_通用协议/API接口定义.md +++ /dev/null @@ -1,94 +0,0 @@ -# 通用支付模块 API 接口定义 (Universal Payment API) - -无论后端使用何种语言(Python/Node/Go),请严格实现以下 RESTful 接口。 - -## 1. 核心交易接口 (Core Transaction) - -### 1.1 创建订单 -* **URL**: `POST /api/payment/create_order` -* **Description**: 业务系统通知支付模块创建一个待支付订单。 -* **Request Body**: - \`\`\`json - { - "user_id": "u1001", // 用户ID - "title": "VIP Membership", // 订单标题 - "amount": 99.00, // 金额 (法币单位: 元 / 美元) - "currency": "CNY", // 币种: CNY, USD - "product_id": "vip_01", // 商品ID - "extra_params": {} // 扩展参数 - } - \`\`\` -* **Response**: - \`\`\`json - { - "code": 200, - "data": { - "order_sn": "202310270001", // 支付系统生成的唯一订单号 - "status": "created" - } - } - \`\`\` - -### 1.2 发起支付 (收银台) -* **URL**: `POST /api/payment/checkout` -* **Description**: 用户选择支付方式后,获取支付参数。 -* **Request Body**: - \`\`\`json - { - "order_sn": "202310270001", - "gateway": "alipay_qr", // 支付方式: alipay_qr, wechat_jsapi, paypal, usdt, stripe - "return_url": "https://...", // 支付成功后前端跳转地址 - "openid": "..." // 微信JSAPI支付必填 - } - \`\`\` -* **Response**: - \`\`\`json - { - "code": 200, - "data": { - "type": "url", // url (跳转), qrcode (扫码), json (SDK参数), address (USDT) - "payload": "https://...", // 具体内容 - "expiration": 1800 // 过期时间(秒) - } - } - \`\`\` - -### 1.3 查询订单状态 -* **URL**: `GET /api/payment/status/{order_sn}` -* **Description**: 前端轮询使用。 -* **Response**: - \`\`\`json - { - "code": 200, - "data": { - "status": "paid", // created, paying, paid, closed - "paid_amount": 99.00, - "paid_at": "2023-10-27 10:00:00" - } - } - \`\`\` - ---- - -## 2. 回调通知接口 (Webhook) - -### 2.1 统一回调入口 -* **URL**: `POST /api/payment/notify/{gateway}` -* **Description**: 接收第三方支付平台的异步通知。 -* **Path Params**: - * `gateway`: `alipay`, `wechat`, `paypal`, `stripe`, `nowpayments` -* **Logic**: - 1. 根据 gateway 加载对应驱动。 - 2. 验签 (Verify Signature)。 - 3. 幂等性检查 (Idempotency Check)。 - 4. 更新订单状态。 - 5. 返回平台所需的响应字符串 (e.g. `success`, `200 OK`). - ---- - -## 3. 辅助接口 - -### 3.1 获取汇率 -* **URL**: `GET /api/payment/exchange_rate` -* **Params**: `from=USD&to=CNY` -* **Response**: `{"rate": 7.21}` diff --git a/addons/Universal_Payment_Module/1_核心设计_通用协议/标准配置模板.yaml b/addons/Universal_Payment_Module/1_核心设计_通用协议/标准配置模板.yaml deleted file mode 100644 index 03dd6fe6..00000000 --- a/addons/Universal_Payment_Module/1_核心设计_通用协议/标准配置模板.yaml +++ /dev/null @@ -1,70 +0,0 @@ -# 全球支付模块标准配置模板 (Standard Config) - -# 无论你使用 Python, Node.js, Go 还是 Java,请将此配置映射到你的环境(如 .env, config.py, config.js) - -# ------------------------------------------------------------------- -# 1. 基础环境 (Environment) -# ------------------------------------------------------------------- -APP_ENV: "production" # development / production -APP_URL: "https://your-site.com" # 你的网站域名 (用于回调) - -# ------------------------------------------------------------------- -# 2. 数据库 (Database) -# ------------------------------------------------------------------- -# 系统会自动生成 order 和 pay_trade 表 -DB_CONNECTION: "mysql" # mysql / postgres / sqlite -DB_HOST: "127.0.0.1" -DB_PORT: "3306" -DB_DATABASE: "payment_db" -DB_USERNAME: "root" -DB_PASSWORD: "password" - -# ------------------------------------------------------------------- -# 3. 支付宝 (Alipay) - CN -# ------------------------------------------------------------------- -ALIPAY_ENABLED: true -ALIPAY_APP_ID: "20210001..." -ALIPAY_PRIVATE_KEY: "MIIETv..." # 商户私钥 -ALIPAY_PUBLIC_KEY: "MIIBIj..." # 支付宝公钥 - -# ------------------------------------------------------------------- -# 4. 微信支付 (Wechat Pay) - CN -# ------------------------------------------------------------------- -WECHAT_ENABLED: true -WECHAT_APP_ID: "wx123456..." # 公众号/小程序 AppID -WECHAT_MCH_ID: "1234567890" # 商户号 -WECHAT_API_V3_KEY: "abcdef..." # APIv3 密钥 (32位) -WECHAT_CERT_SERIAL: "45F59C..." # 证书序列号 -WECHAT_PRIVATE_KEY_PATH: "./cert/apiclient_key.pem" -WECHAT_CERT_PATH: "./cert/apiclient_cert.pem" - -# ------------------------------------------------------------------- -# 5. PayPal - Global -# ------------------------------------------------------------------- -PAYPAL_ENABLED: true -PAYPAL_MODE: "live" # sandbox / live -PAYPAL_CLIENT_ID: "Af7s8..." -PAYPAL_CLIENT_SECRET: "EKd9..." - -# ------------------------------------------------------------------- -# 6. Stripe - Global -# ------------------------------------------------------------------- -STRIPE_ENABLED: true -STRIPE_PUBLIC_KEY: "pk_live_..." -STRIPE_SECRET_KEY: "sk_live_..." -STRIPE_WEBHOOK_SECRET: "whsec_..." - -# ------------------------------------------------------------------- -# 7. USDT (Crypto) - Web3 -# ------------------------------------------------------------------- -USDT_ENABLED: true -USDT_GATEWAY_TYPE: "nowpayments" # nowpayments / native (原生监听) - -# 选项 A: NOWPayments (第三方网关) -NOWPAYMENTS_API_KEY: "R1G..." -NOWPAYMENTS_IPN_SECRET: "secret..." - -# 选项 B: Native (原生监听 - TRC20) -TRON_NODE_API: "https://api.trongrid.io" -TRON_WALLET_ADDRESS: "T9yD14Nj9..." # 你的收款地址 -TRON_CHECK_INTERVAL: 60 # 轮询间隔 (秒) diff --git a/addons/Universal_Payment_Module/2_智能对接_AI指令/通用集成指令.md b/addons/Universal_Payment_Module/2_智能对接_AI指令/通用集成指令.md deleted file mode 100644 index f89dc28b..00000000 --- a/addons/Universal_Payment_Module/2_智能对接_AI指令/通用集成指令.md +++ /dev/null @@ -1,44 +0,0 @@ -# 通用支付模块智能对接指令 (AI Integration Prompt) v3.0 - -**角色设定**: 你是一位精通全球支付架构(Alipay/Wechat/PayPal/Stripe/USDT)的资深全栈架构师。 - -**任务目标**: -我提供了一个**完全配置驱动 (Configuration-Driven)** 的通用支付模块设计。 -请你根据我的目标项目环境,将此支付功能无缝集成进去。 - -**核心资源 (Input)**: -1. **标准配置模板**: `1_核心设计_通用协议/标准配置模板.yaml` (所有支付参数的 Key) -2. **API 接口契约**: `1_核心设计_通用协议/API接口定义.md` (标准 RESTful 接口) -3. **核心业务模型**: `1_核心设计_通用协议/业务逻辑与模型.md` (数据库表结构) - -**集成模式 (选择一种)**: - -### 模式 A: 嵌入式集成 (Library Mode) - *推荐* -适用于将支付功能直接写在现有的后端项目中 (如 Django app, NestJS module)。 - -**步骤**: -1. **环境识别**: 检查我的项目语言 (Python/Node/Go/Java)。 -2. **依赖安装**: 根据语言推荐 SDK (e.g. `alipay-sdk-python`, `stripe`). -3. **配置加载**: 创建代码读取 `标准配置模板.yaml` 中的环境变量。 -4. **模型生成**: 根据 `业务逻辑与模型.md` 生成 ORM 代码 (User/Order/PayTrade)。 -5. **接口实现**: 严格按照 `API接口定义.md` 实现 Controller/View。 - * *要求*: 使用工厂模式 (`PaymentFactory`) 管理不同网关。 - -### 模式 B: 微服务集成 (Microservice Mode) -适用于将支付功能独立部署为一个 Docker 容器。 - -**步骤**: -1. **服务生成**: 用 Go (Gin) 或 Node.js (Express) 生成一个独立服务。 -2. **Docker化**: 编写 `Dockerfile` 和 `docker-compose.yml`。 -3. **网关代理**: 配置 Nginx 或 API Gateway 将 `/api/payment` 转发给此服务。 - ---- - -**给 AI 的执行指令 (Prompt)**: - -> "请读取 `Universal_Payment_Module` 目录下的所有设计文档。 -> 我的当前项目是基于 **[你的语言/框架]** 的。 -> 请采用 **[模式 A / 模式 B]** 为我集成支付功能。 -> 1. 首先生成依赖安装命令。 -> 2. 然后生成数据库模型代码。 -> 3. 最后实现符合 `API接口定义.md` 的核心接口代码。" diff --git a/addons/Universal_Payment_Module/3_逻辑参考_通用实现/前端收银台Demo.html b/addons/Universal_Payment_Module/3_逻辑参考_通用实现/前端收银台Demo.html deleted file mode 100644 index 3a68421f..00000000 --- a/addons/Universal_Payment_Module/3_逻辑参考_通用实现/前端收银台Demo.html +++ /dev/null @@ -1,141 +0,0 @@ - - - - - - 通用收银台 Demo - - - - -
-
-
订单支付
-
¥ 99.00
-
订单号: 202310270001
-
- -
- -
- 🔵 支付宝 (Alipay) - -
- -
- 🟢 微信支付 (Wechat) - -
- -
- 🅿️ PayPal (USD) - -
- -
- 🪙 USDT (TRC20) - -
-
- -
- QRCode -

请扫描二维码支付

-
- - -
- - - - - diff --git a/addons/Universal_Payment_Module/README.md b/addons/Universal_Payment_Module/README.md deleted file mode 100644 index 06c4ae2a..00000000 --- a/addons/Universal_Payment_Module/README.md +++ /dev/null @@ -1,58 +0,0 @@ -# 全球通用支付模块 (Universal Payment Module) v3.0 - -这是一个**配置驱动 (Configuration-Driven)**、**API 优先 (API-First)** 的全球支付解决方案包。 -它通过标准化的协议和 AI 指令,让任何语言的项目都能在 5 分钟内接入支付宝、微信、PayPal、Stripe 和 USDT。 - -## 📂 模块结构 (Directory Structure) - -\`\`\` -Universal_Payment_Module/ -├── 1_核心设计_通用协议/ # [灵魂] 定义了支付的“法律” -│ ├── 标准配置模板.yaml # [新增] 填空即可配置所有支付参数 -│ ├── API接口定义.md # [新增] 无论用什么语言,接口都长这样 -│ ├── 业务逻辑与模型.md # 数据库表结构设计 (Order/PayTrade) -│ └── 接口注册指南.md # 申请 Key 的教程 -│ -├── 2_智能对接_AI指令/ # [工具] AI 编译器 -│ └── 通用集成指令.md # 发给 AI,自动生成代码 -│ -├── 3_逻辑参考_通用实现/ # [参考] -│ ├── 前端收银台Demo.html # [新增] 原生 JS 实现的通用收银台 -│ ├── 后端源码/ # PHP 参考实现 -│ └── 前端模板/ # Twig 参考模板 -│ -└── README.md # 本说明文档 -\`\`\` - -## 🚀 极速对接 (Integration Guide) - -### 第一步:配置 (Config) -1. 打开 `1_核心设计_通用协议/标准配置模板.yaml`。 -2. 将文件内容复制到你项目的配置文件中(如 `.env` 或 `config.py`)。 -3. 填入你申请到的 `APP_ID`, `PRIVATE_KEY` 等参数。 - -### 第二步:生成代码 (Generate) -1. 复制 `2_智能对接_AI指令/通用集成指令.md` 的内容。 -2. 打开 AI 助手,发送指令: - > "我的项目是用 **Python FastAPI** 写的。请根据上述文档,采用 **模式 A (嵌入式)** 为我集成支付功能。" -3. AI 会为你生成: - * `pip install ...` 命令 - * `models.py` (数据库模型) - * `payment_router.py` (API 接口) - -### 第三步:前端接入 (Frontend) -1. 参考 `3_逻辑参考_通用实现/前端收银台Demo.html`。 -2. 将其中的 `API_BASE` 替换为你后端实际的 API 地址。 -3. 即可拥有一个支持 **扫码、跳转、加密货币支付** 的全功能收银台。 - -## 🌍 支持能力 -| 渠道 | 能力 | 适用场景 | -| :--- | :--- | :--- | -| **Alipay / Wechat** | 扫码 / H5 / APP | 中国市场 (CNY) | -| **PayPal / Stripe** | 信用卡 / 订阅 | 全球市场 (USD/EUR...) | -| **USDT (TRC20)** | 链上转账 / 监听 | Web3 / 抗审查支付 | - -## ✨ v3.0 优化亮点 -* **配置驱动**: 不再需要改代码里的硬编码,所有参数通过配置文件注入。 -* **API 契约**: 明确了输入输出格式,前后端对接不再扯皮。 -* **前端 Demo**: 提供了一个不依赖任何框架的原生 JS 收银台,复制即用。 diff --git a/api/soul导师顾问接口.md b/api/soul导师顾问接口.md new file mode 100644 index 00000000..7a1c2636 --- /dev/null +++ b/api/soul导师顾问接口.md @@ -0,0 +1,413 @@ +# 对外获客线索上报接口文档(V1) + +## 一、接口概述 + +- **接口名称**:对外获客线索上报接口 +- **接口用途**:供第三方系统向【存客宝】上报客户线索(手机号 / 微信号等),用于后续的跟进、标签管理和画像分析。 +- **接口协议**:HTTP +- **请求方式**:`POST` +- **请求地址**: `https://ckbapi.quwanzhi.com/v1/api/scenarios` + +> 具体 URL 以实际环境配置为准。 + +- **数据格式**: + - 推荐:`application/json` + - 兼容:`application/x-www-form-urlencoded` +- **字符编码**:`UTF-8` + +--- + +## 二、鉴权与签名 + +### 2.1 必填鉴权字段 + +| 字段名 | 类型 | 必填 | 说明 | +|-------------|--------|------|---------------------------------------| +| `apiKey` | string | 是 | 分配给第三方的接口密钥(每个任务唯一)| +| `sign` | string | 是 | 签名值 | +| `timestamp` | int | 是 | 秒级时间戳(与服务器时间差不超过 5 分钟) | + +### 2.2 时间戳校验 + +服务器会校验 `timestamp` 是否在当前时间前后 **5 分钟** 内: + +- 通过条件:`|server_time - timestamp| <= 300` +- 超出范围则返回:`请求已过期` + +### 2.3 签名生成规则 + +接口采用自定义签名机制。**签名字段为 `sign`,生成步骤如下:** + +假设本次请求的所有参数为 `params`,其中包括业务参数 + `apiKey` + `timestamp` + `sign` + 可能存在的 `portrait` 对象。 + +#### 第一步:移除特定字段 + +从 `params` 中移除以下字段: + +- `sign` —— 自身不参与签名 +- `apiKey` —— 不参与参数拼接,仅在最后一步参与二次 MD5 +- `portrait` —— 整个画像对象不参与签名(即使内部还有子字段) + +> 说明:`portrait` 通常是一个 JSON 对象,字段较多,为避免签名实现复杂且双方难以对齐,统一不参与签名。 + +#### 第二步:移除空值字段 + +从剩余参数中,移除值为: + +- `null` +- 空字符串 `''` + +的字段,这些字段不参与签名。 + +#### 第三步:按参数名升序排序 + +对剩余参数按**参数名(键名)升序排序**,排序规则为标准的 ASCII 升序: + +```text +例如: name, phone, source, timestamp +``` + +#### 第四步:拼接参数值 + +将排序后的参数 **只取“值”**,按顺序直接拼接为一个字符串,中间不加任何分隔符: + +- 示例: + 排序后参数为: + + ```text + name = 张三 + phone = 13800000000 + source = 微信广告 + timestamp = 1710000000 + ``` + + 则拼接: + + ```text + stringToSign = "张三13800000000微信广告1710000000" + ``` + +#### 第五步:第一次 MD5 + +对上一步拼接得到的字符串做一次 MD5: + +\[ +\text{firstMd5} = \text{MD5}(\text{stringToSign}) +\] + +#### 第六步:拼接 apiKey 再次 MD5 + +将第一步的结果与 `apiKey` 直接拼接,再做一次 MD5,得到最终签名值: + +\[ +\text{sign} = \text{MD5}(\text{firstMd5} + \text{apiKey}) +\] + +#### 第七步:放入请求 + +将第六步得到的 `sign` 填入请求参数中的 `sign` 字段即可。 + +> 建议: +> - 使用小写 MD5 字符串(双方约定统一即可)。 +> - 请确保参与签名的参数与最终请求发送的参数一致(包括是否传空值)。 + +### 2.4 签名示例(PHP 伪代码) + +```php +$params = [ + 'apiKey' => 'YOUR_API_KEY', + 'timestamp' => '1710000000', + 'phone' => '13800000000', + 'name' => '张三', + 'source' => '微信广告', + 'remark' => '通过H5落地页留资', + // 'portrait' => [...], // 如有画像,这里会存在,但不参与签名 + // 'sign' => '待生成', +]; + +// 1. 去掉 sign、apiKey、portrait +unset($params['sign'], $params['apiKey'], $params['portrait']); + +// 2. 去掉空值 +$params = array_filter($params, function($value) { + return !is_null($value) && $value !== ''; +}); + +// 3. 按键名升序排序 +ksort($params); + +// 4. 拼接参数值 +$stringToSign = implode('', array_values($params)); + +// 5. 第一次 MD5 +$firstMd5 = md5($stringToSign); + +// 6. 第二次 MD5(拼接 apiKey) +$apiKey = 'YOUR_API_KEY'; +$sign = md5($firstMd5 . $apiKey); + +// 将 $sign 作为字段发送 +$params['sign'] = $sign; +``` + +--- + +## 三、请求参数说明 + +### 3.1 主标识字段(至少传一个) + +| 字段名 | 类型 | 必填 | 说明 | +|-----------|--------|------|-------------------------------------------| +| `wechatId`| string | 否 | 微信号,存在时优先作为主标识 | +| `phone` | string | 否 | 手机号,当 `wechatId` 为空时用作主标识 | + +### 3.2 基础信息字段 + +| 字段名 | 类型 | 必填 | 说明 | +|------------|--------|------|-------------------------| +| `name` | string | 否 | 客户姓名 | +| `source` | string | 否 | 线索来源描述,如“百度推广”、“抖音直播间” | +| `remark` | string | 否 | 备注信息 | +| `tags` | string | 否 | 逗号分隔的“微信标签”,如:`"高意向,电商,女装"` | +| `siteTags` | string | 否 | 逗号分隔的“站内标签”,用于站内进一步细分 | + + +### 3.3 用户画像字段 `portrait`(可选) + +`portrait` 为一个对象(JSON),用于记录用户的行为画像数据。 + +#### 3.3.1 基本示例 + +```json +"portrait": { + "type": 1, + "source": 1, + "sourceData": { + "age": 28, + "gender": "female", + "city": "上海", + "productId": "P12345", + "pageUrl": "https://example.com/product/123" + }, + "remark": "画像-基础属性", + "uniqueId": "user_13800000000_20250301_001" +} +``` + +#### 3.3.2 字段详细说明 + +| 字段名 | 类型 | 必填 | 说明 | +|-----------------------|--------|------|----------------------------------------| +| `portrait.type` | int | 否 | 画像类型,枚举值:
0-浏览
1-点击
2-下单/购买
3-注册
4-互动
默认值:0 | +| `portrait.source` | int | 否 | 画像来源,枚举值:
0-本站
1-老油条
2-老坑爹
默认值:0 | +| `portrait.sourceData` | object | 否 | 画像明细数据(键值对,会存储为 JSON 格式)
可包含任意业务相关的键值对,如:年龄、性别、城市、商品ID、页面URL等 | +| `portrait.remark` | string | 否 | 画像备注信息,最大长度100字符 | +| `portrait.uniqueId` | string | 否 | 画像去重用唯一 ID
用于防止重复记录,相同 `uniqueId` 的画像数据在半小时内会被合并统计(count字段累加)
建议格式:`{来源标识}_{用户标识}_{时间戳}_{序号}` | + +#### 3.3.3 画像类型(type)说明 + +| 值 | 类型 | 说明 | 适用场景 | +|---|------|------|---------| +| 0 | 浏览 | 用户浏览了页面或内容 | 页面访问、商品浏览、文章阅读等 | +| 1 | 点击 | 用户点击了某个元素 | 按钮点击、链接点击、广告点击等 | +| 2 | 下单/购买 | 用户完成了购买行为 | 订单提交、支付完成等 | +| 3 | 注册 | 用户完成了注册 | 账号注册、会员注册等 | +| 4 | 互动 | 用户进行了互动行为 | 点赞、评论、分享、咨询等 | + +#### 3.3.4 画像来源(source)说明 + +| 值 | 来源 | 说明 | +|---|------|------| +| 0 | 本站 | 来自本站的数据 | +| 1 | 老油条 | 来自"老油条"系统的数据 | +| 2 | 老坑爹 | 来自"老坑爹"系统的数据 | + +#### 3.3.5 sourceData 数据格式说明 + +`sourceData` 是一个 JSON 对象,可以包含任意业务相关的键值对。常见字段示例: + +```json +{ + "age": 28, + "gender": "female", + "city": "上海", + "province": "上海市", + "productId": "P12345", + "productName": "商品名称", + "category": "女装", + "price": 299.00, + "pageUrl": "https://example.com/product/123", + "referrer": "https://www.baidu.com", + "device": "mobile", + "browser": "WeChat" +} +``` + +> **注意**: +> - `sourceData` 中的数据类型可以是字符串、数字、布尔值等 +> - 嵌套对象会被序列化为 JSON 字符串存储 +> - 建议根据实际业务需求定义字段结构 + +#### 3.3.6 uniqueId 去重机制说明 + +- **作用**:防止重复记录相同的画像数据 +- **规则**:相同 `uniqueId` 的画像数据在 **半小时内** 会被合并统计,`count` 字段会自动累加 +- **建议格式**:`{来源标识}_{用户标识}_{时间戳}_{序号}` + - 示例:`site_13800000000_1710000000_001` + - 示例:`wechat_wxid_abc123_1710000000_001` +- **注意事项**: + - 如果不传 `uniqueId`,系统会为每条画像数据创建新记录 + - 如果需要在半小时内多次统计同一行为,应使用相同的 `uniqueId` + - 如果需要在半小时后重新统计,应使用不同的 `uniqueId`(建议修改时间戳部分) + +> **重要提示**:`portrait` **整体不参与签名计算**,但会参与业务处理。系统会根据 `uniqueId` 自动处理去重和统计。 + +--- + +## 四、请求示例 + +### 4.1 JSON 请求示例(无画像) + +```json +{ + "apiKey": "YOUR_API_KEY", + "timestamp": 1710000000, + "phone": "13800000000", + "name": "张三", + "source": "微信广告", + "remark": "通过H5落地页留资", + "tags": "高意向,电商", + "siteTags": "新客,女装", + "sign": "根据签名规则生成的MD5字符串" +} +``` + +### 4.2 JSON 请求示例(带微信号与画像) + +```json +{ + "apiKey": "YOUR_API_KEY", + "timestamp": 1710000000, + "wechatId": "wxid_abcdefg123", + "phone": "13800000001", + "name": "李四", + "source": "小程序落地页", + "remark": "点击【立即咨询】按钮", + "tags": "中意向,直播", + "siteTags": "复购,高客单", + "portrait": { + "type": 1, + "source": 0, + "sourceData": { + "age": 28, + "gender": "female", + "city": "上海", + "pageUrl": "https://example.com/product/123", + "productId": "P12345" + }, + "remark": "画像-点击行为", + "uniqueId": "site_13800000001_1710000000_001" + }, + "sign": "根据签名规则生成的MD5字符串" +} +``` + +### 4.3 JSON 请求示例(多种画像类型) + +#### 4.3.1 浏览行为画像 + +```json +{ + "apiKey": "YOUR_API_KEY", + "timestamp": 1710000000, + "phone": "13800000002", + "name": "王五", + "source": "百度推广", + "portrait": { + "type": 0, + "source": 0, + "sourceData": { + "pageUrl": "https://example.com/product/456", + "productName": "商品名称", + "category": "女装", + "stayTime": 120, + "device": "mobile" + }, + "remark": "商品浏览", + "uniqueId": "site_13800000002_1710000000_001" + }, + "sign": "根据签名规则生成的MD5字符串" +} +``` + + +``` + +--- + +## 五、响应说明 + +### 5.1 成功响应 + +**1)新增线索成功** + +```json +{ + "code": 200, + "message": "新增成功", + "data": "13800000000" +} +``` + +**2)线索已存在** + +```json +{ + "code": 200, + "message": "已存在", + "data": "13800000000" +} +``` + +> `data` 字段返回本次线索的主标识 `wechatId` 或 `phone`。 + +### 5.2 常见错误响应 + +```json +{ "code": 400, "message": "apiKey不能为空", "data": null } +{ "code": 400, "message": "sign不能为空", "data": null } +{ "code": 400, "message": "timestamp不能为空", "data": null } +{ "code": 400, "message": "请求已过期", "data": null } + +{ "code": 401, "message": "无效的apiKey", "data": null } +{ "code": 401, "message": "签名验证失败", "data": null } + +{ "code": 500, "message": "系统错误: 具体错误信息", "data": null } +``` + +--- + + +## 六、常见问题(FAQ) + +### Q1: 如果同一个用户多次上报相同的行为,会如何处理? + +**A**: 如果使用相同的 `uniqueId`,系统会在半小时内合并统计,`count` 字段会累加。如果使用不同的 `uniqueId`,会创建多条记录。 + +### Q2: portrait 字段是否必须传递? + +**A**: 不是必须的。`portrait` 字段是可选的,只有在需要记录用户画像数据时才传递。 + +### Q3: sourceData 中可以存储哪些类型的数据? + +**A**: `sourceData` 是一个 JSON 对象,可以存储任意键值对。支持字符串、数字、布尔值等基本类型,嵌套对象会被序列化为 JSON 字符串。 + +### Q4: uniqueId 的作用是什么? + +**A**: `uniqueId` 用于防止重复记录。相同 `uniqueId` 的画像数据在半小时内会被合并统计,避免重复数据。 + +### Q5: 画像数据如何与用户关联? + +**A**: 系统会根据请求中的 `wechatId` 或 `phone` 自动匹配 `traffic_pool` 表中的用户,并将画像数据关联到对应的 `trafficPoolId`。 + +--- diff --git a/api/soul资源对接接口 copy.md b/api/soul资源对接接口 copy.md new file mode 100644 index 00000000..7a1c2636 --- /dev/null +++ b/api/soul资源对接接口 copy.md @@ -0,0 +1,413 @@ +# 对外获客线索上报接口文档(V1) + +## 一、接口概述 + +- **接口名称**:对外获客线索上报接口 +- **接口用途**:供第三方系统向【存客宝】上报客户线索(手机号 / 微信号等),用于后续的跟进、标签管理和画像分析。 +- **接口协议**:HTTP +- **请求方式**:`POST` +- **请求地址**: `https://ckbapi.quwanzhi.com/v1/api/scenarios` + +> 具体 URL 以实际环境配置为准。 + +- **数据格式**: + - 推荐:`application/json` + - 兼容:`application/x-www-form-urlencoded` +- **字符编码**:`UTF-8` + +--- + +## 二、鉴权与签名 + +### 2.1 必填鉴权字段 + +| 字段名 | 类型 | 必填 | 说明 | +|-------------|--------|------|---------------------------------------| +| `apiKey` | string | 是 | 分配给第三方的接口密钥(每个任务唯一)| +| `sign` | string | 是 | 签名值 | +| `timestamp` | int | 是 | 秒级时间戳(与服务器时间差不超过 5 分钟) | + +### 2.2 时间戳校验 + +服务器会校验 `timestamp` 是否在当前时间前后 **5 分钟** 内: + +- 通过条件:`|server_time - timestamp| <= 300` +- 超出范围则返回:`请求已过期` + +### 2.3 签名生成规则 + +接口采用自定义签名机制。**签名字段为 `sign`,生成步骤如下:** + +假设本次请求的所有参数为 `params`,其中包括业务参数 + `apiKey` + `timestamp` + `sign` + 可能存在的 `portrait` 对象。 + +#### 第一步:移除特定字段 + +从 `params` 中移除以下字段: + +- `sign` —— 自身不参与签名 +- `apiKey` —— 不参与参数拼接,仅在最后一步参与二次 MD5 +- `portrait` —— 整个画像对象不参与签名(即使内部还有子字段) + +> 说明:`portrait` 通常是一个 JSON 对象,字段较多,为避免签名实现复杂且双方难以对齐,统一不参与签名。 + +#### 第二步:移除空值字段 + +从剩余参数中,移除值为: + +- `null` +- 空字符串 `''` + +的字段,这些字段不参与签名。 + +#### 第三步:按参数名升序排序 + +对剩余参数按**参数名(键名)升序排序**,排序规则为标准的 ASCII 升序: + +```text +例如: name, phone, source, timestamp +``` + +#### 第四步:拼接参数值 + +将排序后的参数 **只取“值”**,按顺序直接拼接为一个字符串,中间不加任何分隔符: + +- 示例: + 排序后参数为: + + ```text + name = 张三 + phone = 13800000000 + source = 微信广告 + timestamp = 1710000000 + ``` + + 则拼接: + + ```text + stringToSign = "张三13800000000微信广告1710000000" + ``` + +#### 第五步:第一次 MD5 + +对上一步拼接得到的字符串做一次 MD5: + +\[ +\text{firstMd5} = \text{MD5}(\text{stringToSign}) +\] + +#### 第六步:拼接 apiKey 再次 MD5 + +将第一步的结果与 `apiKey` 直接拼接,再做一次 MD5,得到最终签名值: + +\[ +\text{sign} = \text{MD5}(\text{firstMd5} + \text{apiKey}) +\] + +#### 第七步:放入请求 + +将第六步得到的 `sign` 填入请求参数中的 `sign` 字段即可。 + +> 建议: +> - 使用小写 MD5 字符串(双方约定统一即可)。 +> - 请确保参与签名的参数与最终请求发送的参数一致(包括是否传空值)。 + +### 2.4 签名示例(PHP 伪代码) + +```php +$params = [ + 'apiKey' => 'YOUR_API_KEY', + 'timestamp' => '1710000000', + 'phone' => '13800000000', + 'name' => '张三', + 'source' => '微信广告', + 'remark' => '通过H5落地页留资', + // 'portrait' => [...], // 如有画像,这里会存在,但不参与签名 + // 'sign' => '待生成', +]; + +// 1. 去掉 sign、apiKey、portrait +unset($params['sign'], $params['apiKey'], $params['portrait']); + +// 2. 去掉空值 +$params = array_filter($params, function($value) { + return !is_null($value) && $value !== ''; +}); + +// 3. 按键名升序排序 +ksort($params); + +// 4. 拼接参数值 +$stringToSign = implode('', array_values($params)); + +// 5. 第一次 MD5 +$firstMd5 = md5($stringToSign); + +// 6. 第二次 MD5(拼接 apiKey) +$apiKey = 'YOUR_API_KEY'; +$sign = md5($firstMd5 . $apiKey); + +// 将 $sign 作为字段发送 +$params['sign'] = $sign; +``` + +--- + +## 三、请求参数说明 + +### 3.1 主标识字段(至少传一个) + +| 字段名 | 类型 | 必填 | 说明 | +|-----------|--------|------|-------------------------------------------| +| `wechatId`| string | 否 | 微信号,存在时优先作为主标识 | +| `phone` | string | 否 | 手机号,当 `wechatId` 为空时用作主标识 | + +### 3.2 基础信息字段 + +| 字段名 | 类型 | 必填 | 说明 | +|------------|--------|------|-------------------------| +| `name` | string | 否 | 客户姓名 | +| `source` | string | 否 | 线索来源描述,如“百度推广”、“抖音直播间” | +| `remark` | string | 否 | 备注信息 | +| `tags` | string | 否 | 逗号分隔的“微信标签”,如:`"高意向,电商,女装"` | +| `siteTags` | string | 否 | 逗号分隔的“站内标签”,用于站内进一步细分 | + + +### 3.3 用户画像字段 `portrait`(可选) + +`portrait` 为一个对象(JSON),用于记录用户的行为画像数据。 + +#### 3.3.1 基本示例 + +```json +"portrait": { + "type": 1, + "source": 1, + "sourceData": { + "age": 28, + "gender": "female", + "city": "上海", + "productId": "P12345", + "pageUrl": "https://example.com/product/123" + }, + "remark": "画像-基础属性", + "uniqueId": "user_13800000000_20250301_001" +} +``` + +#### 3.3.2 字段详细说明 + +| 字段名 | 类型 | 必填 | 说明 | +|-----------------------|--------|------|----------------------------------------| +| `portrait.type` | int | 否 | 画像类型,枚举值:
0-浏览
1-点击
2-下单/购买
3-注册
4-互动
默认值:0 | +| `portrait.source` | int | 否 | 画像来源,枚举值:
0-本站
1-老油条
2-老坑爹
默认值:0 | +| `portrait.sourceData` | object | 否 | 画像明细数据(键值对,会存储为 JSON 格式)
可包含任意业务相关的键值对,如:年龄、性别、城市、商品ID、页面URL等 | +| `portrait.remark` | string | 否 | 画像备注信息,最大长度100字符 | +| `portrait.uniqueId` | string | 否 | 画像去重用唯一 ID
用于防止重复记录,相同 `uniqueId` 的画像数据在半小时内会被合并统计(count字段累加)
建议格式:`{来源标识}_{用户标识}_{时间戳}_{序号}` | + +#### 3.3.3 画像类型(type)说明 + +| 值 | 类型 | 说明 | 适用场景 | +|---|------|------|---------| +| 0 | 浏览 | 用户浏览了页面或内容 | 页面访问、商品浏览、文章阅读等 | +| 1 | 点击 | 用户点击了某个元素 | 按钮点击、链接点击、广告点击等 | +| 2 | 下单/购买 | 用户完成了购买行为 | 订单提交、支付完成等 | +| 3 | 注册 | 用户完成了注册 | 账号注册、会员注册等 | +| 4 | 互动 | 用户进行了互动行为 | 点赞、评论、分享、咨询等 | + +#### 3.3.4 画像来源(source)说明 + +| 值 | 来源 | 说明 | +|---|------|------| +| 0 | 本站 | 来自本站的数据 | +| 1 | 老油条 | 来自"老油条"系统的数据 | +| 2 | 老坑爹 | 来自"老坑爹"系统的数据 | + +#### 3.3.5 sourceData 数据格式说明 + +`sourceData` 是一个 JSON 对象,可以包含任意业务相关的键值对。常见字段示例: + +```json +{ + "age": 28, + "gender": "female", + "city": "上海", + "province": "上海市", + "productId": "P12345", + "productName": "商品名称", + "category": "女装", + "price": 299.00, + "pageUrl": "https://example.com/product/123", + "referrer": "https://www.baidu.com", + "device": "mobile", + "browser": "WeChat" +} +``` + +> **注意**: +> - `sourceData` 中的数据类型可以是字符串、数字、布尔值等 +> - 嵌套对象会被序列化为 JSON 字符串存储 +> - 建议根据实际业务需求定义字段结构 + +#### 3.3.6 uniqueId 去重机制说明 + +- **作用**:防止重复记录相同的画像数据 +- **规则**:相同 `uniqueId` 的画像数据在 **半小时内** 会被合并统计,`count` 字段会自动累加 +- **建议格式**:`{来源标识}_{用户标识}_{时间戳}_{序号}` + - 示例:`site_13800000000_1710000000_001` + - 示例:`wechat_wxid_abc123_1710000000_001` +- **注意事项**: + - 如果不传 `uniqueId`,系统会为每条画像数据创建新记录 + - 如果需要在半小时内多次统计同一行为,应使用相同的 `uniqueId` + - 如果需要在半小时后重新统计,应使用不同的 `uniqueId`(建议修改时间戳部分) + +> **重要提示**:`portrait` **整体不参与签名计算**,但会参与业务处理。系统会根据 `uniqueId` 自动处理去重和统计。 + +--- + +## 四、请求示例 + +### 4.1 JSON 请求示例(无画像) + +```json +{ + "apiKey": "YOUR_API_KEY", + "timestamp": 1710000000, + "phone": "13800000000", + "name": "张三", + "source": "微信广告", + "remark": "通过H5落地页留资", + "tags": "高意向,电商", + "siteTags": "新客,女装", + "sign": "根据签名规则生成的MD5字符串" +} +``` + +### 4.2 JSON 请求示例(带微信号与画像) + +```json +{ + "apiKey": "YOUR_API_KEY", + "timestamp": 1710000000, + "wechatId": "wxid_abcdefg123", + "phone": "13800000001", + "name": "李四", + "source": "小程序落地页", + "remark": "点击【立即咨询】按钮", + "tags": "中意向,直播", + "siteTags": "复购,高客单", + "portrait": { + "type": 1, + "source": 0, + "sourceData": { + "age": 28, + "gender": "female", + "city": "上海", + "pageUrl": "https://example.com/product/123", + "productId": "P12345" + }, + "remark": "画像-点击行为", + "uniqueId": "site_13800000001_1710000000_001" + }, + "sign": "根据签名规则生成的MD5字符串" +} +``` + +### 4.3 JSON 请求示例(多种画像类型) + +#### 4.3.1 浏览行为画像 + +```json +{ + "apiKey": "YOUR_API_KEY", + "timestamp": 1710000000, + "phone": "13800000002", + "name": "王五", + "source": "百度推广", + "portrait": { + "type": 0, + "source": 0, + "sourceData": { + "pageUrl": "https://example.com/product/456", + "productName": "商品名称", + "category": "女装", + "stayTime": 120, + "device": "mobile" + }, + "remark": "商品浏览", + "uniqueId": "site_13800000002_1710000000_001" + }, + "sign": "根据签名规则生成的MD5字符串" +} +``` + + +``` + +--- + +## 五、响应说明 + +### 5.1 成功响应 + +**1)新增线索成功** + +```json +{ + "code": 200, + "message": "新增成功", + "data": "13800000000" +} +``` + +**2)线索已存在** + +```json +{ + "code": 200, + "message": "已存在", + "data": "13800000000" +} +``` + +> `data` 字段返回本次线索的主标识 `wechatId` 或 `phone`。 + +### 5.2 常见错误响应 + +```json +{ "code": 400, "message": "apiKey不能为空", "data": null } +{ "code": 400, "message": "sign不能为空", "data": null } +{ "code": 400, "message": "timestamp不能为空", "data": null } +{ "code": 400, "message": "请求已过期", "data": null } + +{ "code": 401, "message": "无效的apiKey", "data": null } +{ "code": 401, "message": "签名验证失败", "data": null } + +{ "code": 500, "message": "系统错误: 具体错误信息", "data": null } +``` + +--- + + +## 六、常见问题(FAQ) + +### Q1: 如果同一个用户多次上报相同的行为,会如何处理? + +**A**: 如果使用相同的 `uniqueId`,系统会在半小时内合并统计,`count` 字段会累加。如果使用不同的 `uniqueId`,会创建多条记录。 + +### Q2: portrait 字段是否必须传递? + +**A**: 不是必须的。`portrait` 字段是可选的,只有在需要记录用户画像数据时才传递。 + +### Q3: sourceData 中可以存储哪些类型的数据? + +**A**: `sourceData` 是一个 JSON 对象,可以存储任意键值对。支持字符串、数字、布尔值等基本类型,嵌套对象会被序列化为 JSON 字符串。 + +### Q4: uniqueId 的作用是什么? + +**A**: `uniqueId` 用于防止重复记录。相同 `uniqueId` 的画像数据在半小时内会被合并统计,避免重复数据。 + +### Q5: 画像数据如何与用户关联? + +**A**: 系统会根据请求中的 `wechatId` 或 `phone` 自动匹配 `traffic_pool` 表中的用户,并将画像数据关联到对应的 `trafficPoolId`。 + +--- diff --git a/api/soul资源对接接口.md b/api/soul资源对接接口.md new file mode 100644 index 00000000..7a1c2636 --- /dev/null +++ b/api/soul资源对接接口.md @@ -0,0 +1,413 @@ +# 对外获客线索上报接口文档(V1) + +## 一、接口概述 + +- **接口名称**:对外获客线索上报接口 +- **接口用途**:供第三方系统向【存客宝】上报客户线索(手机号 / 微信号等),用于后续的跟进、标签管理和画像分析。 +- **接口协议**:HTTP +- **请求方式**:`POST` +- **请求地址**: `https://ckbapi.quwanzhi.com/v1/api/scenarios` + +> 具体 URL 以实际环境配置为准。 + +- **数据格式**: + - 推荐:`application/json` + - 兼容:`application/x-www-form-urlencoded` +- **字符编码**:`UTF-8` + +--- + +## 二、鉴权与签名 + +### 2.1 必填鉴权字段 + +| 字段名 | 类型 | 必填 | 说明 | +|-------------|--------|------|---------------------------------------| +| `apiKey` | string | 是 | 分配给第三方的接口密钥(每个任务唯一)| +| `sign` | string | 是 | 签名值 | +| `timestamp` | int | 是 | 秒级时间戳(与服务器时间差不超过 5 分钟) | + +### 2.2 时间戳校验 + +服务器会校验 `timestamp` 是否在当前时间前后 **5 分钟** 内: + +- 通过条件:`|server_time - timestamp| <= 300` +- 超出范围则返回:`请求已过期` + +### 2.3 签名生成规则 + +接口采用自定义签名机制。**签名字段为 `sign`,生成步骤如下:** + +假设本次请求的所有参数为 `params`,其中包括业务参数 + `apiKey` + `timestamp` + `sign` + 可能存在的 `portrait` 对象。 + +#### 第一步:移除特定字段 + +从 `params` 中移除以下字段: + +- `sign` —— 自身不参与签名 +- `apiKey` —— 不参与参数拼接,仅在最后一步参与二次 MD5 +- `portrait` —— 整个画像对象不参与签名(即使内部还有子字段) + +> 说明:`portrait` 通常是一个 JSON 对象,字段较多,为避免签名实现复杂且双方难以对齐,统一不参与签名。 + +#### 第二步:移除空值字段 + +从剩余参数中,移除值为: + +- `null` +- 空字符串 `''` + +的字段,这些字段不参与签名。 + +#### 第三步:按参数名升序排序 + +对剩余参数按**参数名(键名)升序排序**,排序规则为标准的 ASCII 升序: + +```text +例如: name, phone, source, timestamp +``` + +#### 第四步:拼接参数值 + +将排序后的参数 **只取“值”**,按顺序直接拼接为一个字符串,中间不加任何分隔符: + +- 示例: + 排序后参数为: + + ```text + name = 张三 + phone = 13800000000 + source = 微信广告 + timestamp = 1710000000 + ``` + + 则拼接: + + ```text + stringToSign = "张三13800000000微信广告1710000000" + ``` + +#### 第五步:第一次 MD5 + +对上一步拼接得到的字符串做一次 MD5: + +\[ +\text{firstMd5} = \text{MD5}(\text{stringToSign}) +\] + +#### 第六步:拼接 apiKey 再次 MD5 + +将第一步的结果与 `apiKey` 直接拼接,再做一次 MD5,得到最终签名值: + +\[ +\text{sign} = \text{MD5}(\text{firstMd5} + \text{apiKey}) +\] + +#### 第七步:放入请求 + +将第六步得到的 `sign` 填入请求参数中的 `sign` 字段即可。 + +> 建议: +> - 使用小写 MD5 字符串(双方约定统一即可)。 +> - 请确保参与签名的参数与最终请求发送的参数一致(包括是否传空值)。 + +### 2.4 签名示例(PHP 伪代码) + +```php +$params = [ + 'apiKey' => 'YOUR_API_KEY', + 'timestamp' => '1710000000', + 'phone' => '13800000000', + 'name' => '张三', + 'source' => '微信广告', + 'remark' => '通过H5落地页留资', + // 'portrait' => [...], // 如有画像,这里会存在,但不参与签名 + // 'sign' => '待生成', +]; + +// 1. 去掉 sign、apiKey、portrait +unset($params['sign'], $params['apiKey'], $params['portrait']); + +// 2. 去掉空值 +$params = array_filter($params, function($value) { + return !is_null($value) && $value !== ''; +}); + +// 3. 按键名升序排序 +ksort($params); + +// 4. 拼接参数值 +$stringToSign = implode('', array_values($params)); + +// 5. 第一次 MD5 +$firstMd5 = md5($stringToSign); + +// 6. 第二次 MD5(拼接 apiKey) +$apiKey = 'YOUR_API_KEY'; +$sign = md5($firstMd5 . $apiKey); + +// 将 $sign 作为字段发送 +$params['sign'] = $sign; +``` + +--- + +## 三、请求参数说明 + +### 3.1 主标识字段(至少传一个) + +| 字段名 | 类型 | 必填 | 说明 | +|-----------|--------|------|-------------------------------------------| +| `wechatId`| string | 否 | 微信号,存在时优先作为主标识 | +| `phone` | string | 否 | 手机号,当 `wechatId` 为空时用作主标识 | + +### 3.2 基础信息字段 + +| 字段名 | 类型 | 必填 | 说明 | +|------------|--------|------|-------------------------| +| `name` | string | 否 | 客户姓名 | +| `source` | string | 否 | 线索来源描述,如“百度推广”、“抖音直播间” | +| `remark` | string | 否 | 备注信息 | +| `tags` | string | 否 | 逗号分隔的“微信标签”,如:`"高意向,电商,女装"` | +| `siteTags` | string | 否 | 逗号分隔的“站内标签”,用于站内进一步细分 | + + +### 3.3 用户画像字段 `portrait`(可选) + +`portrait` 为一个对象(JSON),用于记录用户的行为画像数据。 + +#### 3.3.1 基本示例 + +```json +"portrait": { + "type": 1, + "source": 1, + "sourceData": { + "age": 28, + "gender": "female", + "city": "上海", + "productId": "P12345", + "pageUrl": "https://example.com/product/123" + }, + "remark": "画像-基础属性", + "uniqueId": "user_13800000000_20250301_001" +} +``` + +#### 3.3.2 字段详细说明 + +| 字段名 | 类型 | 必填 | 说明 | +|-----------------------|--------|------|----------------------------------------| +| `portrait.type` | int | 否 | 画像类型,枚举值:
0-浏览
1-点击
2-下单/购买
3-注册
4-互动
默认值:0 | +| `portrait.source` | int | 否 | 画像来源,枚举值:
0-本站
1-老油条
2-老坑爹
默认值:0 | +| `portrait.sourceData` | object | 否 | 画像明细数据(键值对,会存储为 JSON 格式)
可包含任意业务相关的键值对,如:年龄、性别、城市、商品ID、页面URL等 | +| `portrait.remark` | string | 否 | 画像备注信息,最大长度100字符 | +| `portrait.uniqueId` | string | 否 | 画像去重用唯一 ID
用于防止重复记录,相同 `uniqueId` 的画像数据在半小时内会被合并统计(count字段累加)
建议格式:`{来源标识}_{用户标识}_{时间戳}_{序号}` | + +#### 3.3.3 画像类型(type)说明 + +| 值 | 类型 | 说明 | 适用场景 | +|---|------|------|---------| +| 0 | 浏览 | 用户浏览了页面或内容 | 页面访问、商品浏览、文章阅读等 | +| 1 | 点击 | 用户点击了某个元素 | 按钮点击、链接点击、广告点击等 | +| 2 | 下单/购买 | 用户完成了购买行为 | 订单提交、支付完成等 | +| 3 | 注册 | 用户完成了注册 | 账号注册、会员注册等 | +| 4 | 互动 | 用户进行了互动行为 | 点赞、评论、分享、咨询等 | + +#### 3.3.4 画像来源(source)说明 + +| 值 | 来源 | 说明 | +|---|------|------| +| 0 | 本站 | 来自本站的数据 | +| 1 | 老油条 | 来自"老油条"系统的数据 | +| 2 | 老坑爹 | 来自"老坑爹"系统的数据 | + +#### 3.3.5 sourceData 数据格式说明 + +`sourceData` 是一个 JSON 对象,可以包含任意业务相关的键值对。常见字段示例: + +```json +{ + "age": 28, + "gender": "female", + "city": "上海", + "province": "上海市", + "productId": "P12345", + "productName": "商品名称", + "category": "女装", + "price": 299.00, + "pageUrl": "https://example.com/product/123", + "referrer": "https://www.baidu.com", + "device": "mobile", + "browser": "WeChat" +} +``` + +> **注意**: +> - `sourceData` 中的数据类型可以是字符串、数字、布尔值等 +> - 嵌套对象会被序列化为 JSON 字符串存储 +> - 建议根据实际业务需求定义字段结构 + +#### 3.3.6 uniqueId 去重机制说明 + +- **作用**:防止重复记录相同的画像数据 +- **规则**:相同 `uniqueId` 的画像数据在 **半小时内** 会被合并统计,`count` 字段会自动累加 +- **建议格式**:`{来源标识}_{用户标识}_{时间戳}_{序号}` + - 示例:`site_13800000000_1710000000_001` + - 示例:`wechat_wxid_abc123_1710000000_001` +- **注意事项**: + - 如果不传 `uniqueId`,系统会为每条画像数据创建新记录 + - 如果需要在半小时内多次统计同一行为,应使用相同的 `uniqueId` + - 如果需要在半小时后重新统计,应使用不同的 `uniqueId`(建议修改时间戳部分) + +> **重要提示**:`portrait` **整体不参与签名计算**,但会参与业务处理。系统会根据 `uniqueId` 自动处理去重和统计。 + +--- + +## 四、请求示例 + +### 4.1 JSON 请求示例(无画像) + +```json +{ + "apiKey": "YOUR_API_KEY", + "timestamp": 1710000000, + "phone": "13800000000", + "name": "张三", + "source": "微信广告", + "remark": "通过H5落地页留资", + "tags": "高意向,电商", + "siteTags": "新客,女装", + "sign": "根据签名规则生成的MD5字符串" +} +``` + +### 4.2 JSON 请求示例(带微信号与画像) + +```json +{ + "apiKey": "YOUR_API_KEY", + "timestamp": 1710000000, + "wechatId": "wxid_abcdefg123", + "phone": "13800000001", + "name": "李四", + "source": "小程序落地页", + "remark": "点击【立即咨询】按钮", + "tags": "中意向,直播", + "siteTags": "复购,高客单", + "portrait": { + "type": 1, + "source": 0, + "sourceData": { + "age": 28, + "gender": "female", + "city": "上海", + "pageUrl": "https://example.com/product/123", + "productId": "P12345" + }, + "remark": "画像-点击行为", + "uniqueId": "site_13800000001_1710000000_001" + }, + "sign": "根据签名规则生成的MD5字符串" +} +``` + +### 4.3 JSON 请求示例(多种画像类型) + +#### 4.3.1 浏览行为画像 + +```json +{ + "apiKey": "YOUR_API_KEY", + "timestamp": 1710000000, + "phone": "13800000002", + "name": "王五", + "source": "百度推广", + "portrait": { + "type": 0, + "source": 0, + "sourceData": { + "pageUrl": "https://example.com/product/456", + "productName": "商品名称", + "category": "女装", + "stayTime": 120, + "device": "mobile" + }, + "remark": "商品浏览", + "uniqueId": "site_13800000002_1710000000_001" + }, + "sign": "根据签名规则生成的MD5字符串" +} +``` + + +``` + +--- + +## 五、响应说明 + +### 5.1 成功响应 + +**1)新增线索成功** + +```json +{ + "code": 200, + "message": "新增成功", + "data": "13800000000" +} +``` + +**2)线索已存在** + +```json +{ + "code": 200, + "message": "已存在", + "data": "13800000000" +} +``` + +> `data` 字段返回本次线索的主标识 `wechatId` 或 `phone`。 + +### 5.2 常见错误响应 + +```json +{ "code": 400, "message": "apiKey不能为空", "data": null } +{ "code": 400, "message": "sign不能为空", "data": null } +{ "code": 400, "message": "timestamp不能为空", "data": null } +{ "code": 400, "message": "请求已过期", "data": null } + +{ "code": 401, "message": "无效的apiKey", "data": null } +{ "code": 401, "message": "签名验证失败", "data": null } + +{ "code": 500, "message": "系统错误: 具体错误信息", "data": null } +``` + +--- + + +## 六、常见问题(FAQ) + +### Q1: 如果同一个用户多次上报相同的行为,会如何处理? + +**A**: 如果使用相同的 `uniqueId`,系统会在半小时内合并统计,`count` 字段会累加。如果使用不同的 `uniqueId`,会创建多条记录。 + +### Q2: portrait 字段是否必须传递? + +**A**: 不是必须的。`portrait` 字段是可选的,只有在需要记录用户画像数据时才传递。 + +### Q3: sourceData 中可以存储哪些类型的数据? + +**A**: `sourceData` 是一个 JSON 对象,可以存储任意键值对。支持字符串、数字、布尔值等基本类型,嵌套对象会被序列化为 JSON 字符串。 + +### Q4: uniqueId 的作用是什么? + +**A**: `uniqueId` 用于防止重复记录。相同 `uniqueId` 的画像数据在半小时内会被合并统计,避免重复数据。 + +### Q5: 画像数据如何与用户关联? + +**A**: 系统会根据请求中的 `wechatId` 或 `phone` 自动匹配 `traffic_pool` 表中的用户,并将画像数据关联到对应的 `trafficPoolId`。 + +--- diff --git a/app/about/page.tsx b/app/about/page.tsx index 54b6c246..bcb94105 100644 --- a/app/about/page.tsx +++ b/app/about/page.tsx @@ -26,12 +26,12 @@ export default function AboutPage() { ] const milestones = [ - { year: "2012", event: "开始做游戏推广,从魔兽世界外挂代理起步" }, - { year: "2015", event: "转型电商,做天猫虚拟充值,月流水380万" }, - { year: "2017", event: "团队扩张到200人,年流水3000万" }, - { year: "2018", event: "公司破产,负债数百万,开始全国旅行反思" }, - { year: "2019", event: "重新出发,专注私域运营和个人IP" }, - { year: "2024", event: "在Soul派对房每日直播,分享真实商业故事" }, + { year: "2007-2014", event: "游戏电竞创业历程,从魔兽世界代练起步" }, + { year: "2015", event: "转型电商,做天猫虚拟充值" }, + { year: "2016-2019", event: "深耕电商领域,团队扩张到200人,年流水3000万" }, + { year: "2019-2020", event: "公司变故,重整旗鼓" }, + { year: "2020-2025", event: "电竞、地摊、大健康、私域多领域探索" }, + { year: "2025.10.15", event: "在Soul派对房开启每日分享,记录真实商业案例" }, ] return ( diff --git a/app/admin/content/page.tsx b/app/admin/content/page.tsx index 2da17e9b..3b0b44fa 100644 --- a/app/admin/content/page.tsx +++ b/app/admin/content/page.tsx @@ -1,6 +1,6 @@ "use client" -import { useState } from "react" +import { useState, useRef } from "react" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" @@ -22,6 +22,10 @@ import { X, RefreshCw, Link2, + Download, + Upload, + Eye, + Database, } from "lucide-react" interface EditingSection { @@ -29,14 +33,22 @@ interface EditingSection { title: string price: number content?: string + filePath?: string } export default function ContentPage() { const [expandedParts, setExpandedParts] = useState(["part-1"]) const [editingSection, setEditingSection] = useState(null) const [isSyncing, setIsSyncing] = useState(false) + const [isExporting, setIsExporting] = useState(false) + const [isImporting, setIsImporting] = useState(false) + const [isInitializing, setIsInitializing] = useState(false) const [feishuDocUrl, setFeishuDocUrl] = useState("") const [showFeishuModal, setShowFeishuModal] = useState(false) + const [showImportModal, setShowImportModal] = useState(false) + const [importData, setImportData] = useState("") + const [isLoadingContent, setIsLoadingContent] = useState(false) + const fileInputRef = useRef(null) const togglePart = (partId: string) => { setExpandedParts((prev) => (prev.includes(partId) ? prev.filter((id) => id !== partId) : [...prev, partId])) @@ -47,21 +59,257 @@ export default function ContentPage() { 0, ) - const handleEditSection = (section: { id: string; title: string; price: number }) => { - setEditingSection({ - id: section.id, - title: section.title, - price: section.price, - content: "", - }) + // 读取章节内容 + const handleReadSection = async (section: { id: string; title: string; price: number; filePath: string }) => { + setIsLoadingContent(true) + try { + const res = await fetch(`/api/db/book?action=read&id=${section.id}`) + const data = await res.json() + + if (data.success) { + setEditingSection({ + id: section.id, + title: section.title, + price: section.price, + content: data.section.content || "", + filePath: section.filePath, + }) + } else { + // 如果API失败,设置空内容 + setEditingSection({ + id: section.id, + title: section.title, + price: section.price, + content: "", + filePath: section.filePath, + }) + alert("无法读取文件内容: " + (data.error || "未知错误")) + } + } catch (error) { + console.error("Read section error:", error) + setEditingSection({ + id: section.id, + title: section.title, + price: section.price, + content: "", + filePath: section.filePath, + }) + } finally { + setIsLoadingContent(false) + } } - const handleSaveSection = () => { - if (editingSection) { - // 保存到本地存储或API - console.log("[v0] Saving section:", editingSection) - alert(`已保存章节: ${editingSection.title}`) - setEditingSection(null) + // 保存章节 + const handleSaveSection = async () => { + if (!editingSection) return + + try { + const res = await fetch('/api/db/book', { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + id: editingSection.id, + title: editingSection.title, + price: editingSection.price, + content: editingSection.content, + saveToFile: true, // 同时保存到文件系统 + }) + }) + + const data = await res.json() + if (data.success) { + alert(`已保存章节: ${editingSection.title}`) + setEditingSection(null) + } else { + alert("保存失败: " + (data.error || "未知错误")) + } + } catch (error) { + console.error("Save section error:", error) + alert("保存失败") + } + } + + // 同步到数据库 + const handleSyncToDatabase = async () => { + setIsSyncing(true) + try { + const res = await fetch('/api/db/book', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ action: 'sync' }) + }) + + const data = await res.json() + if (data.success) { + alert(data.message) + } else { + alert("同步失败: " + (data.error || "未知错误")) + } + } catch (error) { + console.error("Sync error:", error) + alert("同步失败") + } finally { + setIsSyncing(false) + } + } + + // 导出所有章节 + const handleExport = async () => { + setIsExporting(true) + try { + const res = await fetch('/api/db/book?action=export') + const blob = await res.blob() + + const url = window.URL.createObjectURL(blob) + const a = document.createElement('a') + a.href = url + a.download = `book_sections_${new Date().toISOString().split('T')[0]}.json` + document.body.appendChild(a) + a.click() + window.URL.revokeObjectURL(url) + document.body.removeChild(a) + + alert("导出成功") + } catch (error) { + console.error("Export error:", error) + alert("导出失败") + } finally { + setIsExporting(false) + } + } + + // 导入章节 + const handleImport = async () => { + if (!importData) { + alert("请输入或上传JSON数据") + return + } + + setIsImporting(true) + try { + const data = JSON.parse(importData) + const res = await fetch('/api/db/book', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ action: 'import', data }) + }) + + const result = await res.json() + if (result.success) { + alert(result.message) + setShowImportModal(false) + setImportData("") + } else { + alert("导入失败: " + (result.error || "未知错误")) + } + } catch (error) { + console.error("Import error:", error) + alert("导入失败: JSON格式错误") + } finally { + setIsImporting(false) + } + } + + // 文件上传 + const handleFileUpload = (e: React.ChangeEvent) => { + const file = e.target.files?.[0] + if (!file) return + + const reader = new FileReader() + reader.onload = (event) => { + const content = event.target?.result as string + const fileName = file.name.toLowerCase() + + // 根据文件类型处理 + if (fileName.endsWith('.json')) { + // JSON文件直接使用 + setImportData(content) + } else if (fileName.endsWith('.txt') || fileName.endsWith('.md') || fileName.endsWith('.markdown')) { + // TXT/MD文件自动解析为JSON格式 + const parsedData = parseTxtToJson(content, file.name) + setImportData(JSON.stringify(parsedData, null, 2)) + } else { + setImportData(content) + } + } + reader.readAsText(file) + } + + // 解析TXT/MD文件为JSON格式 + const parseTxtToJson = (content: string, fileName: string) => { + const lines = content.split('\n') + const sections: any[] = [] + let currentSection: any = null + let currentContent: string[] = [] + let sectionIndex = 1 + + for (const line of lines) { + // 检测标题行(以#开头或数字+点开头) + const titleMatch = line.match(/^#+\s+(.+)$/) || line.match(/^(\d+[\.\、]\s*.+)$/) + + if (titleMatch) { + // 保存前一个章节 + if (currentSection) { + currentSection.content = currentContent.join('\n').trim() + if (currentSection.content) { + sections.push(currentSection) + } + } + + // 开始新章节 + currentSection = { + id: `import-${sectionIndex}`, + title: titleMatch[1].replace(/^#+\s*/, '').trim(), + price: 1, + is_free: sectionIndex <= 3, // 前3章免费 + } + currentContent = [] + sectionIndex++ + } else if (currentSection) { + currentContent.push(line) + } else if (line.trim()) { + // 没有标题但有内容,创建默认章节 + currentSection = { + id: `import-${sectionIndex}`, + title: fileName.replace(/\.(txt|md|markdown)$/i, ''), + price: 1, + is_free: true, + } + currentContent.push(line) + sectionIndex++ + } + } + + // 保存最后一个章节 + if (currentSection) { + currentSection.content = currentContent.join('\n').trim() + if (currentSection.content) { + sections.push(currentSection) + } + } + + return sections + } + + // 初始化数据库 + const handleInitDatabase = async () => { + if (!confirm("确定要初始化数据库吗?这将创建所有必需的表结构。")) return + + setIsInitializing(true) + try { + const res = await fetch('/api/db/init', { method: 'POST' }) + const data = await res.json() + + if (data.success) { + alert(data.message) + } else { + alert("初始化失败: " + (data.error || "未知错误")) + } + } catch (error) { + console.error("Init database error:", error) + alert("初始化失败") + } finally { + setIsInitializing(false) } } @@ -71,7 +319,6 @@ export default function ContentPage() { return } setIsSyncing(true) - // 模拟同步过程 await new Promise((resolve) => setTimeout(resolve, 2000)) setIsSyncing(false) setShowFeishuModal(false) @@ -87,12 +334,123 @@ export default function ContentPage() { 共 {bookData.length} 篇 · {totalSections} 节内容

- +
+ + + + + +
+ {/* 导入弹窗 */} + + + + + + 导入章节数据 + + +
+
+ + + +

+ • JSON格式: 直接导入章节数据
+ • TXT/MD格式: 自动解析为章节内容 +

+
+
+ +