docs文档
This commit is contained in:
624
docs/PlayX-接口文档.md
Normal file
624
docs/PlayX-接口文档.md
Normal file
@@ -0,0 +1,624 @@
|
||||
# PlayX 接口文档(按调用方向拆分)
|
||||
|
||||
说明:本文档严格依据当前代码 `app/api/controller/v1/Playx.php` 与定时任务 `app/process/PlayxJobs.php` 整理。
|
||||
|
||||
三类接口分别为:
|
||||
- `积分商城 -> PlayX`(PlayX 调用商城)
|
||||
- `PlayX -> 积分商城`(商城调用 PlayX)
|
||||
- `积分商城 -> H5`(H5 调用商城)
|
||||
|
||||
---
|
||||
|
||||
## 1. 积分商城 -> PlayX(PlayX 调用商城)
|
||||
|
||||
### 1.1 Daily Push API
|
||||
* 方法:`POST`
|
||||
* 路径:`/api/v1/playx/daily-push`
|
||||
|
||||
#### Header(签名校验:可选)
|
||||
当 `playx.daily_push_secret` 配置非空时,需要携带:
|
||||
- `X-Request-Id`:请求 ID
|
||||
- `X-Timestamp`:时间戳
|
||||
- `X-Signature`:签名(HMAC_SHA256)
|
||||
|
||||
服务端签名计算:
|
||||
- `canonical = X-Timestamp + "\n" + X-Request-Id + "\nPOST\n/api/v1/playx/daily-push\n" + sha256(json_body)`
|
||||
- `expected = hash_hmac('sha256', canonical, daily_push_secret)`
|
||||
- 校验:`hash_equals(expected, X-Signature)`
|
||||
|
||||
#### Body
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `request_id` | string | 是 | 外部推送请求号(原样返回) |
|
||||
| `date` | string(YYYY-MM-DD) | 是 | 业务日期(入库到 `mall_playx_daily_push.date`) |
|
||||
| `user_id` | string | 是 | PlayX 用户 ID(用于幂等) |
|
||||
| `username` | string | 否 | 展示冗余 |
|
||||
| `yesterday_win_loss_net` | number | 否 | 昨日净输赢(仅当 `< 0` 时计算新增保障金) |
|
||||
| `yesterday_total_deposit` | number | 否 | 昨日总充值(用于计算今日可领取上限) |
|
||||
| `lifetime_total_deposit` | number | 否 | 历史总充值 |
|
||||
| `lifetime_total_withdraw` | number | 否 | 历史总提现 |
|
||||
|
||||
#### 幂等规则
|
||||
* 幂等键:`user_id + date`
|
||||
* 重复推送:不会重复入账,返回 `data.deduped=true`
|
||||
|
||||
#### 返回(Response)
|
||||
外层通用返回结构:`{ code, msg, time, data }`
|
||||
|
||||
成功(首次入库):
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `data.request_id` | string | 原样返回 |
|
||||
| `data.accepted` | boolean | `true` |
|
||||
| `data.deduped` | boolean | `false` |
|
||||
| `data.message` | string | `ok` |
|
||||
|
||||
成功(重复推送):
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `data.request_id` | string | 原样返回 |
|
||||
| `data.accepted` | boolean | `true` |
|
||||
| `data.deduped` | boolean | `true` |
|
||||
| `data.message` | string | `duplicate input` |
|
||||
|
||||
失败:
|
||||
* 当缺少必填字段:code=0,msg 为缺少字段错误
|
||||
* 当签名不正确:HTTP 401,code=0,msg 为 `INVALID_SIGNATURE`
|
||||
|
||||
#### 示例(未开启签名校验)
|
||||
请求:
|
||||
```bash
|
||||
curl -X POST 'http://localhost:1818/api/v1/playx/daily-push' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"request_id":"req_1001",
|
||||
"date":"2026-03-18",
|
||||
"user_id":"U123",
|
||||
"username":"demo_user",
|
||||
"yesterday_win_loss_net":-120.5,
|
||||
"yesterday_total_deposit":50,
|
||||
"lifetime_total_deposit":5000,
|
||||
"lifetime_total_withdraw":2000
|
||||
}'
|
||||
```
|
||||
|
||||
响应(首次):
|
||||
```json
|
||||
{
|
||||
"code": 1,
|
||||
"msg": "",
|
||||
"data": {
|
||||
"request_id": "req_1001",
|
||||
"accepted": true,
|
||||
"deduped": false,
|
||||
"message": "ok"
|
||||
},
|
||||
"time": 0
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. PlayX -> 积分商城(商城调用 PlayX)
|
||||
|
||||
> 下面这些接口由 PlayX 提供。商城侧仅按“请求参数 + 期望返回判定条件”发起调用与处理结果。
|
||||
|
||||
### 2.1 Token Verification API
|
||||
* 方法:`POST`
|
||||
* URL:`${playx.api.base_url}${playx.api.token_verify_url}`
|
||||
* 默认:`/api/v1/auth/verify-token`
|
||||
|
||||
#### 请求 Body(商城侧发送)
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `request_id` | string | 是 | 形如 `mall_{uniqid}` |
|
||||
| `token` | string | 是 | 前端传入的 PlayX token |
|
||||
|
||||
#### 返回(期望)
|
||||
商城侧校验:
|
||||
* HTTP 状态码必须为 `200`
|
||||
* 且响应体中必须包含 `user_id`
|
||||
|
||||
期望字段(示例):
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `user_id` | string | 必选 |
|
||||
| `username` | string | 可选 |
|
||||
| `token_expire_at` | string | 可选(能被 `strtotime` 解析) |
|
||||
|
||||
示例(成功):
|
||||
```json
|
||||
{
|
||||
"user_id": "U123",
|
||||
"username": "demo_user",
|
||||
"token_expire_at": "2026-04-01T12:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
示例(失败):
|
||||
```json
|
||||
{
|
||||
"message": "invalid token"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.2 Bonus Grant API
|
||||
* 方法:`POST`
|
||||
* URL:`${playx.api.base_url}${playx.api.bonus_grant_url}`
|
||||
* 默认:`/api/v1/bonus/grant`
|
||||
|
||||
#### 请求 Body(商城侧发送)
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `request_id` | string | 是 | 形如 `mall_bonus_{uniqid}` |
|
||||
| `externalTransactionId` | string | 是 | `MallPlayxOrder.external_transaction_id` |
|
||||
| `user_id` | string | 是 | PlayX 用户 ID |
|
||||
| `amount` | number | 是 | `MallPlayxOrder.amount` |
|
||||
| `rewardName` | string | 是 | `mall_item.title` |
|
||||
| `category` | string | 是 | `mall_item.category`(默认 `daily`) |
|
||||
| `categoryTitle` | string | 是 | `mall_item.category_title` |
|
||||
| `multiplier` | int | 是 | `MallPlayxOrder.multiplier` |
|
||||
|
||||
#### 返回(期望)
|
||||
商城侧判定:
|
||||
* HTTP 状态码 `200`
|
||||
* 且 `data.status === "accepted"`
|
||||
|
||||
成功时读取:
|
||||
* `data.playx_transaction_id`
|
||||
|
||||
失败时读取:
|
||||
* `data.message` 写入订单 `fail_reason`
|
||||
|
||||
示例(accepted):
|
||||
```json
|
||||
{
|
||||
"status": "accepted",
|
||||
"playx_transaction_id": "PX_TX_001"
|
||||
}
|
||||
```
|
||||
|
||||
示例(rejected):
|
||||
```json
|
||||
{
|
||||
"status": "rejected",
|
||||
"message": "insufficient balance"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.3 Balance Credit API
|
||||
* 方法:`POST`
|
||||
* URL:`${playx.api.base_url}${playx.api.balance_credit_url}`
|
||||
* 默认:`/api/v1/balance/credit`
|
||||
|
||||
#### 请求 Body(商城侧发送)
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `request_id` | string | 是 | 形如 `mall_withdraw_{uniqid}` |
|
||||
| `externalTransactionId` | string | 是 | `MallPlayxOrder.external_transaction_id` |
|
||||
| `user_id` | string | 是 | PlayX 用户 ID |
|
||||
| `amount` | number | 是 | `MallPlayxOrder.amount` |
|
||||
| `multiplier` | int | 是 | `MallPlayxOrder.multiplier` |
|
||||
|
||||
#### 返回(期望)
|
||||
与 Bonus Grant 一致:
|
||||
* `data.status === "accepted"` -> 读取 `playx_transaction_id`
|
||||
* 否则 -> 读取 `message` 写入 `fail_reason`
|
||||
|
||||
示例(accepted):
|
||||
```json
|
||||
{
|
||||
"status": "accepted",
|
||||
"playx_transaction_id": "PX_TX_002"
|
||||
}
|
||||
```
|
||||
|
||||
示例(rejected):
|
||||
```json
|
||||
{
|
||||
"status": "rejected",
|
||||
"message": "insufficient balance"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.4 Transaction Status Query API(交易终态查询)
|
||||
* 方法:`GET`
|
||||
* URL:`${playx.api.base_url}${playx.api.transaction_status_url}`
|
||||
* 默认:`/api/v1/transaction/status`
|
||||
|
||||
#### Query 参数
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `externalTransactionId` | string | 是 | 订单幂等键 `external_transaction_id` |
|
||||
|
||||
#### 返回(期望)
|
||||
定时任务读取 `data.status`:
|
||||
* `COMPLETED`:商城将订单 `status` 更新为 `COMPLETED`
|
||||
* `FAILED` 或 `REJECTED`:商城将订单 `status=REJECTED`、`grant_status=FAILED_FINAL`,并退回积分
|
||||
* 失败信息取 `data.message` 写入订单 `fail_reason`
|
||||
|
||||
示例(completed):
|
||||
```json
|
||||
{ "status": "COMPLETED" }
|
||||
```
|
||||
|
||||
示例(failed):
|
||||
```json
|
||||
{ "status": "FAILED", "message": "grant rejected by PlayX" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 积分商城 -> H5(服务端提供给 H5 的接口)
|
||||
|
||||
说明:鉴权与用户解析规则由 `resolveUserIdFromRequest()` 决定。
|
||||
* 优先使用 `session_id`(在 `mall_playx_session` 查到且未过期)
|
||||
* 其次使用 `user_id`
|
||||
|
||||
公共鉴权字段:
|
||||
* `session_id`:字符串
|
||||
* `user_id`:字符串
|
||||
|
||||
> 注意:请求参数的取值方式是 `post()` 优先,`get()` 兼容(即同字段既可传 post 也可传 get)。
|
||||
|
||||
---
|
||||
|
||||
### 3.1 Token 验证
|
||||
* 方法:`POST`
|
||||
* 路径:`/api/v1/playx/verify-token`
|
||||
|
||||
#### 请求 Body
|
||||
必填其一:
|
||||
* `token`(优先读取)
|
||||
* `session`(兼容字段,当 `token` 为空时会被当作 token)
|
||||
|
||||
#### 返回(成功 data)
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `session_id` | string | 写入 `mall_playx_session` |
|
||||
| `user_id` | string | PlayX 用户 ID |
|
||||
| `username` | string | 用户名 |
|
||||
| `token_expire_at` | string | ISO 字符串(服务端 `date('c', expireAt)`) |
|
||||
|
||||
失败:
|
||||
* token 为空:HTTP 401,msg=`INVALID_TOKEN`
|
||||
* PlayX 未配置:msg=`PlayX API not configured`
|
||||
|
||||
#### 示例
|
||||
请求:
|
||||
```bash
|
||||
curl -X POST 'http://localhost:1818/api/v1/playx/verify-token' \
|
||||
-H 'Content-Type: application/x-www-form-urlencoded' \
|
||||
--data-urlencode 'token=PLAYX_TOKEN_XXX'
|
||||
```
|
||||
|
||||
响应(成功示例):
|
||||
```json
|
||||
{
|
||||
"code": 1,
|
||||
"msg": "",
|
||||
"data": {
|
||||
"session_id": "7b1c....",
|
||||
"user_id": "U123",
|
||||
"username": "demo_user",
|
||||
"token_expire_at": "2026-04-01T12:00:00+00:00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.2 用户资产
|
||||
* 方法:`GET`
|
||||
* 路径:`/api/v1/playx/assets`
|
||||
|
||||
#### 请求参数(鉴权)
|
||||
* `session_id`(优先)
|
||||
* `user_id`(兼容)
|
||||
|
||||
#### 返回(成功 data)
|
||||
若未找到资产:返回 0。
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `locked_points` | int | 待领取积分 |
|
||||
| `available_points` | int | 可用积分 |
|
||||
| `today_limit` | int | 今日可领取上限 |
|
||||
| `today_claimed` | int | 今日已领取 |
|
||||
| `withdrawable_cash` | number(2) | `available_points * points_to_cash_ratio`(保留 2 位) |
|
||||
|
||||
#### 示例
|
||||
```bash
|
||||
curl -G 'http://localhost:1818/api/v1/playx/assets' --data-urlencode 'session_id=7b1c....'
|
||||
```
|
||||
|
||||
响应(示例):
|
||||
```json
|
||||
{
|
||||
"code": 1,
|
||||
"msg": "",
|
||||
"time": 1712345678,
|
||||
"data": {
|
||||
"locked_points": 100,
|
||||
"available_points": 50,
|
||||
"today_limit": 200,
|
||||
"today_claimed": 80,
|
||||
"withdrawable_cash": 5.2
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.3 领取(Claim)
|
||||
* 方法:`POST`
|
||||
* 路径:`/api/v1/playx/claim`
|
||||
|
||||
#### 请求 Body
|
||||
必填:
|
||||
* `claim_request_id`:幂等键(string,唯一)
|
||||
鉴权:
|
||||
* `session_id` 或 `user_id`
|
||||
|
||||
#### 返回(成功 data)
|
||||
与 `用户资产` 返回字段一致(资产快照)。
|
||||
|
||||
幂等:
|
||||
* `claim_request_id` 已存在:不会重复入账,直接返回当前资产快照
|
||||
|
||||
#### 示例
|
||||
```bash
|
||||
curl -X POST 'http://localhost:1818/api/v1/playx/claim' \
|
||||
-H 'Content-Type: application/x-www-form-urlencoded' \
|
||||
--data-urlencode 'claim_request_id=claim_001' \
|
||||
--data-urlencode 'session_id=7b1c....'
|
||||
```
|
||||
|
||||
响应(首次领取,示例):
|
||||
```json
|
||||
{
|
||||
"code": 1,
|
||||
"msg": "Claim success",
|
||||
"time": 1712345679,
|
||||
"data": {
|
||||
"locked_points": 60,
|
||||
"available_points": 90,
|
||||
"today_limit": 200,
|
||||
"today_claimed": 120,
|
||||
"withdrawable_cash": 9.0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
响应(幂等重复,示例:可能 msg 为空):
|
||||
```json
|
||||
{
|
||||
"code": 1,
|
||||
"msg": "",
|
||||
"time": 1712345680,
|
||||
"data": {
|
||||
"locked_points": 60,
|
||||
"available_points": 90,
|
||||
"today_limit": 200,
|
||||
"today_claimed": 120,
|
||||
"withdrawable_cash": 9.0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.4 商品列表
|
||||
* 方法:`GET`
|
||||
* 路径:`/api/v1/playx/items`
|
||||
|
||||
#### 请求参数
|
||||
* `type`(可选):`BONUS` | `PHYSICAL` | `WITHDRAW`
|
||||
|
||||
不传或空:返回 `mall_item.status=1` 且不过滤 type 的商品列表。
|
||||
|
||||
#### 返回(成功 data)
|
||||
* `list`:商品列表(直接返回 `MallItem` 的字段数组;包含扩展字段:`amount/multiplier/category/category_title` 等)
|
||||
|
||||
#### 示例
|
||||
请求:
|
||||
```bash
|
||||
curl -G 'http://localhost:1818/api/v1/playx/items' --data-urlencode 'type=WITHDRAW'
|
||||
```
|
||||
|
||||
响应(示例):
|
||||
```json
|
||||
{
|
||||
"code": 1,
|
||||
"msg": "",
|
||||
"time": 1712345685,
|
||||
"data": {
|
||||
"list": [
|
||||
{
|
||||
"id": 321,
|
||||
"title": "提现档位A",
|
||||
"type": 3,
|
||||
"score": 1000,
|
||||
"amount": 100.0,
|
||||
"multiplier": 1,
|
||||
"category": "withdraw",
|
||||
"category_title": "提现"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.5 红利兑换(Bonus Redeem)
|
||||
* 方法:`POST`
|
||||
* 路径:`/api/v1/playx/bonus/redeem`
|
||||
|
||||
#### 请求 Body
|
||||
必填:
|
||||
* `item_id`:商品 ID(要求 `mall_item.type=BONUS` 且 `status=1`)
|
||||
鉴权:
|
||||
* `session_id` 或 `user_id`
|
||||
|
||||
#### 返回(成功)
|
||||
* `msg`:`Redeem submitted, please wait about 10 minutes`
|
||||
* `data.order_id`:订单 ID
|
||||
* `data.status`:`PENDING`
|
||||
|
||||
#### 示例
|
||||
```bash
|
||||
curl -X POST 'http://localhost:1818/api/v1/playx/bonus/redeem' \
|
||||
-H 'Content-Type: application/x-www-form-urlencoded' \
|
||||
--data-urlencode 'item_id=123' \
|
||||
--data-urlencode 'session_id=7b1c....'
|
||||
```
|
||||
|
||||
响应(示例):
|
||||
```json
|
||||
{
|
||||
"code": 1,
|
||||
"msg": "Redeem submitted, please wait about 10 minutes",
|
||||
"time": 1712345686,
|
||||
"data": {
|
||||
"order_id": 456,
|
||||
"status": "PENDING"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.6 实物兑换(Physical Redeem)
|
||||
* 方法:`POST`
|
||||
* 路径:`/api/v1/playx/physical/redeem`
|
||||
|
||||
#### 请求 Body
|
||||
必填:
|
||||
* `item_id`:商品 ID(要求 `mall_item.type=PHYSICAL` 且 `status=1`)
|
||||
* `receiver_name`:收货人
|
||||
* `receiver_phone`:收货电话
|
||||
* `receiver_address`:收货地址
|
||||
鉴权:
|
||||
* `session_id` 或 `user_id`
|
||||
|
||||
#### 返回(成功)
|
||||
* `msg`:`Redeem success`
|
||||
* `data`:`null`
|
||||
|
||||
#### 示例
|
||||
```bash
|
||||
curl -X POST 'http://localhost:1818/api/v1/playx/physical/redeem' \
|
||||
-H 'Content-Type: application/x-www-form-urlencoded' \
|
||||
--data-urlencode 'item_id=200' \
|
||||
--data-urlencode 'receiver_name=张三' \
|
||||
--data-urlencode 'receiver_phone=18800001111' \
|
||||
--data-urlencode 'receiver_address=北京市朝阳区XX路XX号' \
|
||||
--data-urlencode 'session_id=7b1c....'
|
||||
```
|
||||
|
||||
响应(示例):
|
||||
```json
|
||||
{
|
||||
"code": 1,
|
||||
"msg": "Redeem success",
|
||||
"time": 1712345687,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.7 提现申请(Withdraw Apply)
|
||||
* 方法:`POST`
|
||||
* 路径:`/api/v1/playx/withdraw/apply`
|
||||
|
||||
#### 请求 Body
|
||||
必填:
|
||||
* `item_id`:商品 ID(要求 `mall_item.type=WITHDRAW` 且 `status=1`)
|
||||
鉴权:
|
||||
* `session_id` 或 `user_id`
|
||||
|
||||
#### 返回(成功)
|
||||
* `msg`:`Withdraw submitted, please wait about 10 minutes`
|
||||
* `data.order_id`:订单 ID
|
||||
* `data.status`:`PENDING`
|
||||
|
||||
#### 示例
|
||||
```bash
|
||||
curl -X POST 'http://localhost:1818/api/v1/playx/withdraw/apply' \
|
||||
-H 'Content-Type: application/x-www-form-urlencoded' \
|
||||
--data-urlencode 'item_id=321' \
|
||||
--data-urlencode 'session_id=7b1c....'
|
||||
```
|
||||
|
||||
响应(示例):
|
||||
```json
|
||||
{
|
||||
"code": 1,
|
||||
"msg": "Withdraw submitted, please wait about 10 minutes",
|
||||
"time": 1712345688,
|
||||
"data": {
|
||||
"order_id": 789,
|
||||
"status": "PENDING"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.8 订单列表
|
||||
* 方法:`GET`
|
||||
* 路径:`/api/v1/playx/orders`
|
||||
|
||||
#### 请求参数
|
||||
* `session_id` 或 `user_id`
|
||||
|
||||
#### 返回(成功 data)
|
||||
* `list`:订单列表(最多 100 条),并包含关联的 `mallItem`(关系对象)
|
||||
|
||||
#### 示例
|
||||
请求:
|
||||
```bash
|
||||
curl -G 'http://localhost:1818/api/v1/playx/orders' --data-urlencode 'session_id=7b1c....'
|
||||
```
|
||||
|
||||
响应(示例,简化):
|
||||
```json
|
||||
{
|
||||
"code": 1,
|
||||
"msg": "",
|
||||
"time": 1712345689,
|
||||
"data": {
|
||||
"list": [
|
||||
{
|
||||
"id": 456,
|
||||
"user_id": "U123",
|
||||
"type": "BONUS",
|
||||
"status": "PENDING",
|
||||
"mall_item_id": 123,
|
||||
"points_cost": 100,
|
||||
"amount": 10.0,
|
||||
"external_transaction_id": "BONUS_ORD2026....",
|
||||
"grant_status": "NOT_SENT",
|
||||
"mallItem": {
|
||||
"id": 123,
|
||||
"title": "每日红利",
|
||||
"type": 1
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.9 同步额度(可选)
|
||||
当前代码未实现并未注册路由:`/api/v1/playx/sync-limit`。
|
||||
|
||||
Reference in New Issue
Block a user