Files
webman-buildadmin-mall/docs/PlayX-接口文档.md
2026-03-20 18:11:00 +08:00

625 lines
15 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.
# PlayX 接口文档(按调用方向拆分)
说明:本文档严格依据当前代码 `app/api/controller/v1/Playx.php` 与定时任务 `app/process/PlayxJobs.php` 整理。
三类接口分别为:
- `积分商城 -> PlayX`PlayX 调用商城)
- `PlayX -> 积分商城`(商城调用 PlayX
- `积分商城 -> H5`H5 调用商城)
---
## 1. 积分商城 -> PlayXPlayX 调用商城)
### 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=0msg 为缺少字段错误
* 当签名不正确HTTP 401code=0msg 为 `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 401msg=`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`