15 KiB
15 KiB
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:请求 IDX-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
示例(未开启签名校验)
请求:
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
}'
响应(首次):
{
"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 解析) |
示例(成功):
{
"user_id": "U123",
"username": "demo_user",
"token_expire_at": "2026-04-01T12:00:00Z"
}
示例(失败):
{
"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):
{
"status": "accepted",
"playx_transaction_id": "PX_TX_001"
}
示例(rejected):
{
"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):
{
"status": "accepted",
"playx_transaction_id": "PX_TX_002"
}
示例(rejected):
{
"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更新为COMPLETEDFAILED或REJECTED:商城将订单status=REJECTED、grant_status=FAILED_FINAL,并退回积分- 失败信息取
data.message写入订单fail_reason
示例(completed):
{ "status": "COMPLETED" }
示例(failed):
{ "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
示例
请求:
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'
响应(成功示例):
{
"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 位) |
示例
curl -G 'http://localhost:1818/api/v1/playx/assets' --data-urlencode 'session_id=7b1c....'
响应(示例):
{
"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已存在:不会重复入账,直接返回当前资产快照
示例
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....'
响应(首次领取,示例):
{
"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 为空):
{
"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等)
示例
请求:
curl -G 'http://localhost:1818/api/v1/playx/items' --data-urlencode 'type=WITHDRAW'
响应(示例):
{
"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 minutesdata.order_id:订单 IDdata.status:PENDING
示例
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....'
响应(示例):
{
"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 successdata:null
示例
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....'
响应(示例):
{
"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 minutesdata.order_id:订单 IDdata.status:PENDING
示例
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....'
响应(示例):
{
"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(关系对象)
示例
请求:
curl -G 'http://localhost:1818/api/v1/playx/orders' --data-urlencode 'session_id=7b1c....'
响应(示例,简化):
{
"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。