PaymentCenter/README.md

394 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# PaymentCenter 支付中心
一个基于 Go + Gin 的统一支付网关系统,为商户提供下单、退款、查询、收银台等支付能力,底层对接微信支付、支付宝等第三方支付平台。
> 代码位置:后端接口位于 `app/`,收银台页面模板位于 `front/templates/`。
---
## 1. 技术栈
| 组件 | 选型 |
|------|------|
| 语言 | Go |
| Web 框架 | [Gin](https://github.com/gin-gonic/gin) |
| ORM | [xorm](https://xorm.io/) |
| 第三方支付 | [go-pay](https://github.com/go-pay/gopay) |
| JSON | [bytedance/sonic](https://github.com/bytedance/sonic) |
| 参数校验 | go-playground/validator.v9 |
| 缓存/分布式锁 | Redissnow-core/redis |
| 数据库 | MySQL |
| 加密算法 | RSA / SM2 / SM4 / AES / DES3 |
---
## 2. 目录结构
```
PaymentCenter/
├── app/
│ ├── constants/ # 常量定义错误码、公共枚举、pojo
│ │ ├── common/common.go # 订单状态、支付渠道、路由前缀
│ │ └── errorcode/ # 错误码与中文消息
│ ├── http/
│ │ ├── routes/route.go # 路由注册入口
│ │ ├── requestmapping/ # 路由→请求结构体映射
│ │ ├── middlewares/ # 中间件CORS、鉴权、加解密、参数校验
│ │ ├── controllers/
│ │ │ ├── base.go # 公共Success/Error/GenRequest/ApiRes
│ │ │ ├── front/ # C 端:支付 API、收银台、回调
│ │ │ └── backend/ # 管理后台
│ │ └── entities/ # 请求/响应结构体
│ ├── services/
│ │ ├── api_request_valid.go # App 校验 & IP 白名单
│ │ ├── order.go # 订单 CRUD & 状态检查
│ │ ├── pay_page.go # 收银台渠道列表、订单状态检查
│ │ ├── apicrypt/ # RSA/SM2/SM4 加解密实现
│ │ └── thirdpay/ # 第三方支付业务封装
│ │ ├── pay.go # 下单/退款/关闭主流程
│ │ ├── pay_page.go # 收银台业务V2 预下单、查询
│ │ ├── pay_query.go # 三方查询
│ │ ├── do/ # 渠道策略层PayCheck、Pay、PayWay 映射)
│ │ └── thirdpay_notify/ # 回调异步通知处理
│ ├── third/paymentService/ # go-pay 封装(微信/支付宝底层调用)
│ ├── models/ # xorm 实体orders、app、merchant、pay_channel…
│ ├── data/ # Repository 层OrderRepo、PayChannelRepo 等)
│ ├── utils/ # snowflake、JWT、HTTP client、useragent、DES
│ │ └── encrypt/ # AES、RSA、SM2、SM4
│ ├── caches/ # 缓存
│ └── mq/ # 消息队列
└── front/
└── templates/
├── payPage.html # 收银台主页面
├── payTemplateDefault.html # 渠道跳转模板
├── success.html # 支付成功JSAPI 场景)
└── fail.html # 支付失败
```
---
## 3. 架构分层与调用链
```
Gin 路由
MiddlewareCORS → ValidateRequest / ValidatePayRequest → 加解密)
Controllerfront/ 或 backend/
services/(业务编排 + services/thirdpay/
services/thirdpay/do/PayCheck → Pay → PayWay 策略)
third/paymentService/go-pay 底层调用)→ 微信 / 支付宝
data/Repository→ models/xorm ORM→ MySQL
```
- **控制器层**:只做 HTTP 层的事(绑定请求、调用 service、封装返回
- **服务层**:核心业务(状态机、幂等、分布式锁、渠道路由)
- **Repositorydata/**:只做数据库 CRUD不包含业务逻辑
---
## 4. C 端接口清单
> 接口前缀:`/pay/front/api/v1` 或 `/pay/front/api/v2`
| 方法 | 路由 | 中间件 | 功能 |
|------|------|--------|------|
| POST | `/notify/wx/:payChannelId` | - | 微信支付回调 |
| POST | `/notify/ali/:payChannelId` | - | 支付宝支付回调 |
| POST | `/pay/url` (V1) | ValidatePayRequest | 下单,同步返回支付 URL / JsInfo |
| POST | `/pay/url` (V2) | ValidatePayRequest | 预下单,仅创建订单,返回收银台 URL |
| POST | `/pay/refund` | ValidatePayRequest | 退款 |
| POST | `/pay/query` | ValidatePayRequest | 查询订单状态 |
| POST | `/pay/close` | ValidatePayRequest | 关闭订单 |
| GET | `/payPage?no={id}` | - | 收银台 HTML 页面入口 |
| POST | `/payPage/list` | ValidateRequest | 获取可用支付渠道列表 |
| GET | `/payPage/submit` | ValidateRequest | 获取支付跳转链接(渲染 payTemplateDefault.html|
| POST | `/payPage/query` | - | 收银台页面轮询订单状态 |
| POST | `/ali/getOauth` | ValidateRequest | 支付宝小程序获取 openId |
| POST/GET | `/wx/*` | ValidateRequest | 微信授权 / openId / JSAPI / 小程序支付 |
- 路由定义参见 [app/http/routes/route.go](file:///e:/goproject/goproject/PaymentCenter/app/http/routes/route.go)
- 路由→结构体映射参见 [app/http/requestmapping/front.go](file:///e:/goproject/goproject/PaymentCenter/app/http/requestmapping/front.go)
---
## 5. 两种下单模式
### 5.1 模式一V1 直接下单(`POST /pay/front/api/v1/pay/url`
适用于商户自己管理收银台页面:
1. 商户构造请求 `RequestBody{ app_id, timestamp, data, key }`,加密后发起
2. 中间件 `ValidatePayRequest()`:查 App 配置 → 解密 → 反序列化为 `PayReqs`
3. 控制器调用 `thirdpay.ThirdPayInfoCheck()``thirdpay.ThirdPayUrl()`
4. 同步返回 `ApiResponse{ Order, Url, JsInfo }`
### 5.2 模式二V2 预下单 + 收银台(推荐)
适用于商户跳转至我方收银台页面:
1. 商户调用 `POST /pay/front/api/v2/pay/url`
2. `PayUrlV2Service()`
- Redis 分布式锁(`payUrl:{out_trade_no}`3 秒)
- 检查商户是否有可用支付渠道
-`out_trade_no + app_id` 查询订单;不存在则新建订单(状态 WAITPAY
- 返回收银台 URL`https://pay-host/pay/front/api/v1/payPage?no={orders.id}`
3. 商户将用户浏览器跳转到收银台
4. **收银台页面**`payPage.html`
- 读取 URL 参数 `no`**订单自增 ID不是 out_trade_no**
- `POST /payPage/list?id=xxx` 拉取渠道列表
- 单渠道直接跳转 `/payPage/submit`,多渠道让用户选择
- 选择后跳转到第三方支付页面
- 支付完成后轮询 `/payPage/query`,成功后跳转到商户 `return_url`
---
## 6. 收银台页面流程
```
商户后台
│ POST /v2/pay/url ─────┐
│ ▼
│ 支付中心创建订单
│ │
│ ▼
│ 返回 payPage?no={orderId}
└── 用户浏览器 ───► GET payPage.html
├── JS 调 POST /payPage/list 获取渠道
├── GET /payPage/submit 获取跳转链接
│ └──► payTemplateDefault.html window.location
├── POST /payPage/query 轮询订单状态
└── 支付成功 → 跳转 return_url
```
页面文件:
- [payPage.html](file:///e:/goproject/goproject/PaymentCenter/front/templates/payPage.html) — 收银台主页(含加载态、渠道选择、状态确认弹窗)
- [payTemplateDefault.html](file:///e:/goproject/goproject/PaymentCenter/front/templates/payTemplateDefault.html) — 第三方支付跳转
- [success.html](file:///e:/goproject/goproject/PaymentCenter/front/templates/success.html) — 微信 JSAPI 成功
- [fail.html](file:///e:/goproject/goproject/PaymentCenter/front/templates/fail.html) — 支付失败
前端注意:`payPage.html` 中的 `API_BASE_URL` 默认写死为 `http://localhost:7081`,部署时需要替换为生产地址。
---
## 7. 请求加解密体系
### 7.1 两个核心中间件
| 中间件 | 路由 | 说明 |
|--------|------|------|
| `ValidateRequest()` | 收银台列表、授权、小程序等 | 普通参数解析form/json+ validator 校验 + 可选日志 |
| `ValidatePayRequest()` | `/pay/url`、`/pay/refund`、`/pay/query`、`/pay/close` | 支付专用:`app_id` 查表 → 按 `key_type` 解密 data → 二次解析校验 |
中间件代码:[app/http/middlewares/base.go](file:///e:/goproject/goproject/PaymentCenter/app/http/middlewares/base.go)
### 7.2 支付请求结构(加密封装)
```
{
"app_id": 123, // 应用 ID查表得 KeyType
"timestamp": 1700000000, // 时间戳
"data": "...", // 真实业务参数的密文PayReqs JSON → 加密 → Base64
"key": "..." // 部分算法所需对称密钥密文
}
```
解密后 `data` 对应的业务结构,在 `requestmapping.FrontRequestMap` 中按路由映射。
### 7.3 加密算法(由 `app.key_type` 决定)
| 类型 | 实现文件 |
|------|---------|
| NO_CRYPT | 明文(不加密) |
| RSA | [services/apicrypt/rsa.go](file:///e:/goproject/goproject/PaymentCenter/app/services/apicrypt/rsa.go) |
| SM2 | [services/apicrypt/sm2.go](file:///e:/goproject/goproject/PaymentCenter/app/services/apicrypt/sm2.go) |
| SM4 | [services/apicrypt/sm4.go](file:///e:/goproject/goproject/PaymentCenter/app/services/apicrypt/sm4.go) |
注册处:[services/apicrypt/types.go](file:///e:/goproject/goproject/PaymentCenter/app/services/apicrypt/types.go) 中的 `ApiCryptMap`
### 7.4 响应加密
- 非生产环境:`controllers.Success/Error` 直接返回 `{ code, message, data }` JSON
- 生产环境(`config.GetConf().Env == "production"`):整体 JSON 用 DES3 + Base64 加密后以纯文本返回
- 对应方法:`controllers.EncriptJson()`
---
## 8. 订单模型 & 状态机
### 8.1 Orders 表
字段定义:[app/models/ordersmodel/orders.go](file:///e:/goproject/goproject/PaymentCenter/app/models/ordersmodel/orders.go)
| 字段 | 类型 | 说明 |
|------|------|------|
| id | int64 | 主键snowflake 生成 |
| merchant_id | int64 | 商户 |
| pay_channel_id | int64 | 支付渠道实例 |
| app_id | int64 | 应用 |
| out_trade_no | string(50) | 外部/商户订单号 |
| order_type | tinyint | 1=支付, 2=退款 |
| refund_order_id | int64 | 退款关联的原订单 |
| amount | int | 订单金额,单位:**分** |
| actual_amount | int | 实际金额,单位:**分** |
| payer_total | int | 付款方金额,单位:**分** |
| ext_json | string(1024) | 扩展 JSON |
| desc | string(100) | 商品描述 |
| status | tinyint | 订单状态(见下) |
| create_time / update_time | datetime | 自动维护 |
### 8.2 订单状态(`constants/common/common.go`
| 常量 | 值 | 说明 |
|------|----|------|
| ORDER_STATUS_WAITPAY | 1 | 待支付(刚创建) |
| ORDER_STATUS_PAYING | 2 | 支付中(已调第三方下单) |
| ORDER_STATUS_PAYED | 3 | 支付成功 |
| ORDER_STATUS_FAILED | 4 | 支付失败 |
| ORDER_STATUS_CLOSE | 5 | 已关闭 |
### 8.3 状态流转
```
WAITPAY(1) ──► PAYING(2) ──┬──► PAYED(3)
│ │
└─────► CLOSE(5) └──► FAILED(4)
```
状态检查方法:`services.OrderStatusCheck(order)`,位置 [app/services/pay_page.go](file:///e:/goproject/goproject/PaymentCenter/app/services/pay_page.go#L89-L103)
---
## 9. 支付渠道体系
### 9.1 渠道类型枚举
定义在 `constants/common/common.go`
| 常量 | 值 | 含义 |
|------|----|------|
| PAY_CHANNEL_WECHAT_JSAPI | 1 | 微信公众号 JSAPI |
| PAY_CHANNEL_WECHAT_H5 | 2 | 微信 H5 |
| PAY_CHANNEL_WECHAT_APP | 3 | 微信 APP |
| PAY_CHANNEL_WECHAT_NATIVE | 4 | 微信 Native 扫码 |
| PAY_CHANNEL_WECHAT_MINI | 5 | 微信小程序 |
| PAY_CHANNEL_ALIPAY_WEB | 6 | 支付宝网页/移动应用 |
| PAY_CHANNEL_ALIPAY_MINI | 7 | 支付宝小程序 |
| PAY_CHANNEL_ALIPAY_JSAPI | 8 | 支付宝 JSAPI |
| PAY_CHANNEL_ALIPAY_PC | 9 | 支付宝 PC 网站 |
### 9.2 策略映射(渠道 → 实现方法)
- 下单:`services/thirdpay/do/pay_way.go` 的 `PayWayList[channelType]`
- 退款:`services/thirdpay/do/refund_way.go` 的 `RefundWayList[channelType]`
- 底层统一入口:`third/paymentService/payment_service.go` 的 `PaymentService(ctx, req)`
- 根据 `req.ChannelType` 路由到 `AliPayService()``WechatPayService()`
### 9.3 客户端环境 → 可用渠道
- 微信浏览器内 → JSAPI需要 openId收银台会自动先走授权
- PC 浏览器 → 微信 Native、支付宝 PC
- 手机浏览器(非微信内)→ 微信 H5、支付宝 Web
实现:`services.PayPageChannelList()` + `services.ClientEnvCheck(ua)`
---
## 10. 新增功能的操作清单
### 10.1 新增一个 C 端 API
1.`app/http/entities/front/` 定义 Request/Response 结构体
2.`app/http/requestmapping/front.go` 的两个 map 中添加路由 → 结构体映射
3.`app/http/routes/route.go` 注册路由,挂上合适的中间件
4.`app/http/controllers/front/` 对应文件新增 handler`controllers.GetRequest(c).(*front.XXX)` 取参数)
5.`app/services/``app/services/thirdpay/` 实现业务逻辑
### 10.2 新增一个支付渠道
1.`constants/common/common.go` 增加 `PAY_CHANNEL_XXX` 常量和 `PayChannelName`/`PayChannelList` 条目
2.`services/thirdpay/do/pay_way.go``PayWayList` 注册实现方法
3.`third/paymentService/wechat_service.go``ali_service.go` 增加对应分支
4. 若需区分客户端环境,更新 `OpenInPayChannelMap`
### 10.3 新增一个页面模板
1.`front/templates/` 下新增 `.html`
2. 路由里通过 `c.HTML(http.StatusOK, "文件名.html", gin.H{...})` 渲染
3. `router.LoadHTMLGlob("./front/templates/*")` 已在 routes 中注册
### 10.4 新增一个错误码
1.`constants/errorcode/error_code.go` 定义 const并在 `ErrCodeMap` 注册中文消息
2. 使用:`controllers.Error(c, errorcode.XXX)` 或 `controllers.ApiRes(c, nil, code)`
### 10.5 新增数据模型 / Repository
1.`app/models/xxxmodel/` 定义结构体xorm tag + TableName + GetInstance 单例)
2.`app/data/` 新增 `XxxRepo` 封装 xorm 操作(推荐配合 `builder.Cond` 拼装条件)
3.`app/services/` 新增 `XxxFindOne` / `XxxList` 等服务方法
---
## 11. 编码规范与注意事项
- **金额单位**:全链路使用 **分**int前端展示时 `/100` 转元;不得出现 float64 金额
- **订单 ID vs out_trade_no**
- `orders.id` = 雪花自增 ID支付中心内部、收银台查询使用
- `out_trade_no` = 商户外部订单号(商户下单、查询使用)
- **不要混用**
- **幂等性**V2 下单通过 Redis 分布式锁key=`payUrl:{out_trade_no}`3 秒)+ 数据库 `(out_trade_no, app_id)` 唯一约束保证
- **中间件选型**
- 加密支付接口 → `ValidatePayRequest()`
- 收银台、授权、小程序等 → `ValidateRequest()`
- 回调接口 → 直接在 controller 解析(走第三方验签)
- **统一返回**`controllers.Success/Error/ApiRes/HandCodeRes`,不要手写 JSON
- **错误码来源**`constants/errorcode/error_code.go`,不要硬编码数字
- **日志**`utils.Log(ctx, tag, ...)`;支付链路关键节点必须有日志
- **单元测试**工具类、加解密、snowflake 等均有 `_test.go`,新工具函数建议配套
---
## 12. 关键文件速查
| 功能 | 文件 |
|------|------|
| 路由注册 | [app/http/routes/route.go](file:///e:/goproject/goproject/PaymentCenter/app/http/routes/route.go) |
| 路由→结构体映射 | [app/http/requestmapping/front.go](file:///e:/goproject/goproject/PaymentCenter/app/http/requestmapping/front.go) |
| 中间件 | [app/http/middlewares/base.go](file:///e:/goproject/goproject/PaymentCenter/app/http/middlewares/base.go) |
| 公共返回/参数解析 | [app/http/controllers/base.go](file:///e:/goproject/goproject/PaymentCenter/app/http/controllers/base.go) |
| 支付接口 controller | [app/http/controllers/front/api.go](file:///e:/goproject/goproject/PaymentCenter/app/http/controllers/front/api.go) |
| 收银台 controller | [app/http/controllers/front/pay_page.go](file:///e:/goproject/goproject/PaymentCenter/app/http/controllers/front/pay_page.go) |
| 回调/微信/支付宝 controller | [app/http/controllers/front/payment_controller.go](file:///e:/goproject/goproject/PaymentCenter/app/http/controllers/front/payment_controller.go) |
| 请求结构体 | [app/http/entities/front/pay.go](file:///e:/goproject/goproject/PaymentCenter/app/http/entities/front/pay.go) |
| 订单服务 | [app/services/order.go](file:///e:/goproject/goproject/PaymentCenter/app/services/order.go) |
| 收银台服务(渠道列表/状态检查) | [app/services/pay_page.go](file:///e:/goproject/goproject/PaymentCenter/app/services/pay_page.go) |
| App 校验 | [app/services/api_request_valid.go](file:///e:/goproject/goproject/PaymentCenter/app/services/api_request_valid.go) |
| 第三方支付主流程 | [app/services/thirdpay/pay.go](file:///e:/goproject/goproject/PaymentCenter/app/services/thirdpay/pay.go) |
| 收银台/V2 下单 | [app/services/thirdpay/pay_page.go](file:///e:/goproject/goproject/PaymentCenter/app/services/thirdpay/pay_page.go) |
| 加解密类型注册 | [app/services/apicrypt/types.go](file:///e:/goproject/goproject/PaymentCenter/app/services/apicrypt/types.go) |
| 错误码 | [app/constants/errorcode/error_code.go](file:///e:/goproject/goproject/PaymentCenter/app/constants/errorcode/error_code.go) |
| 公共常量 | [app/constants/common/common.go](file:///e:/goproject/goproject/PaymentCenter/app/constants/common/common.go) |
| Orders 模型 | [app/models/ordersmodel/orders.go](file:///e:/goproject/goproject/PaymentCenter/app/models/ordersmodel/orders.go) |
| Order Repository | [app/data/orders.go](file:///e:/goproject/goproject/PaymentCenter/app/data/orders.go) |
| 底层 go-pay 调用入口 | app/third/paymentService/payment_service.go |
| 收银台页面 | [front/templates/payPage.html](file:///e:/goproject/goproject/PaymentCenter/front/templates/payPage.html) |
| 跳转模板 | [front/templates/payTemplateDefault.html](file:///e:/goproject/goproject/PaymentCenter/front/templates/payTemplateDefault.html) |
| 雪花 ID 生成 | [app/utils/snowflake/snow_flake.go](file:///e:/goproject/goproject/PaymentCenter/app/utils/snowflake/snow_flake.go) |