394 lines
18 KiB
Markdown
394 lines
18 KiB
Markdown
# 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 |
|
||
| 缓存/分布式锁 | Redis(snow-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 路由
|
||
│
|
||
▼
|
||
Middleware(CORS → ValidateRequest / ValidatePayRequest → 加解密)
|
||
│
|
||
▼
|
||
Controller(front/ 或 backend/)
|
||
│
|
||
▼
|
||
services/(业务编排 + services/thirdpay/)
|
||
│
|
||
▼
|
||
services/thirdpay/do/(PayCheck → Pay → PayWay 策略)
|
||
│
|
||
▼
|
||
third/paymentService/(go-pay 底层调用)→ 微信 / 支付宝
|
||
│
|
||
▼
|
||
data/(Repository)→ models/(xorm ORM)→ MySQL
|
||
```
|
||
|
||
- **控制器层**:只做 HTTP 层的事(绑定请求、调用 service、封装返回)
|
||
- **服务层**:核心业务(状态机、幂等、分布式锁、渠道路由)
|
||
- **Repository(data/)**:只做数据库 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) |
|