22 KiB
playX 接口文档(按调用方向拆分)
说明:本文档严格依据当前代码 app/api/controller/v1/Playx.php、app/api/controller/v1/Auth.php(临时登录)、config/playx.php 与定时任务 app/process/PlayxJobs.php 整理。
按调用方向分为三类(避免与历史章节标题混淆):
| 方向 | 含义 | 本文位置 |
|---|---|---|
| playX → 积分商城 | playX(或上游批处理)主动 HTTP 调用商城开放接口 | §1(如 Daily Push) |
| 积分商城 → playX | 商城 Worker / 后台 主动 HTTP 调用 playX / Cash Market 提供的接口 | 不展开于本文;交付 playX 的说明见 docs/playX-接口待完善清单.md |
| 积分商城 → H5 | H5 / 内嵌页 调用商城 的会员与积分业务接口 | §3 |
1. playX → 积分商城(外部系统调用商城开放接口)
1.1 Daily Push API
- 方法:
POST - 路径:
/api/v1/mall/dailyPush
Header(多语言,可选)
lang:zh/zh-cn返回中文(默认);en返回英文
Header(签名校验:HMAC 必填)
当 playX.daily_push_secret 配置非空时,需要携带(HMAC):
X-Request-Id:请求 IDX-Timestamp:时间戳X-Signature:签名(HMAC_SHA256)
服务端签名计算:
canonical = X-Timestamp + "\n" + X-Request-Id + "\nPOST\n/api/v1/mall/dailyPush\n" + sha256(json_body)expected = hash_hmac('sha256', canonical, daily_push_secret)- 校验:
hash_equals(expected, X-Signature)
说明:
- 本项目对接方案为 仅启用 HMAC,不使用
Authorization头做校验。
Body
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
request_id |
string | 是 | 外部推送请求号(原样返回) |
date |
string(YYYY-MM-DD) | 是 | 业务日期(入库到 mall_daily_push.date) |
user_id |
string | 是 | playX 用户 ID(用于幂等;入库 mall_daily_push.user_id 等;服务端会按 user_id/username 确保存在 mall_user_asset 资产行) |
username |
string | 否 | 展示冗余(同步到商城用户侧逻辑时使用) |
yesterday_win_loss_net |
number | 否 | 昨日净输赢(仅当 < 0 时计算新增保障金) |
yesterday_total_deposit |
number | 否 | 昨日总充值(用于计算今日可领取上限) |
lifetime_total_deposit |
number | 否 | 历史总充值 |
lifetime_total_withdraw |
number | 否 | 历史总提现 |
格式 B:新版批量上报(兼容你截图)
新版 body 形如:
{
"report_date": "1700000000",
"member": [
{
"member_id": "123456",
"login": "john",
"lty_deposit": 15230.75,
"lty_withdrawal": 12400.50,
"yesterday_total_w": -320.25,
"yesterday_total_deposit": 500.00
}
]
}
字段映射(服务端内部会转换成旧字段再计算):
report_date->date(若为 Unix 秒则转为YYYY-MM-DD)member[].member_id->user_idmember[].login->usernamemember[].yesterday_total_w->yesterday_win_loss_netmember[].yesterday_total_deposit->yesterday_total_depositmember[].lty_deposit->lifetime_total_depositmember[].lty_withdrawal->lifetime_total_withdraw
返回补充:
- 批量模式会在
data里增加results[],每个成员一条结果(是否deduped)。
幂等规则
- 幂等键:
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/mall/dailyPush' \
-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
}
示例(新版批量上报)
请求:
curl -X POST 'http://localhost:1818/api/v1/mall/dailyPush' \
-H 'Content-Type: application/json' \
-d '{
"report_date": "1700000000",
"member": [
{
"member_id": "123456",
"login": "john",
"lty_deposit": 15230.75,
"lty_withdrawal": 12400.50,
"yesterday_total_w": -320.25,
"yesterday_total_deposit": 500.00
}
]
}'
返回(首次写入至少一个成员时的示例):
{
"code": 1,
"msg": "",
"time": 0,
"data": {
"request_id": "report_2023-11-14",
"accepted": true,
"deduped": false,
"message": "Ok",
"results": [
{
"user_id": "123456",
"accepted": true,
"deduped": false,
"message": "Ok"
}
]
}
}
2. 积分商城 → playX(贵方需提供的 HTTP 接口)
商城在验 Token、红利发放、交易轮询、Angpow 导入等场景会 主动请求 playX / Cash Market。
完整 URL、请求/响应字段、成功判定、与 Angpush 双路径关系、联调待办 已单独整理,便于 直接转发给 playX 平台:
docs/playX-接口待完善清单.md
本文 §1 仅描述「谁调用商城」;§3 描述「H5 调用商城」。
3. 积分商城 → H5(服务端提供给 H5 的接口)
3.0 数据模型说明(与代码一致)
- 积分商城用户资产主表:
mall_user_asset(账号、积分、playx_user_id等;H5 临时登录temLogin直接创建/复用该表行,不依赖独立会员user表)。 - 会话缓存:
mall_session(字段含session_id、user_id(此处存 playX 侧用户标识字符串,与mall_user_asset.playx_user_id一致)、expire_time等)。 - 统一订单:
mall_order(红利/实物/提现订单;user_id字段为playx_user_id字符串)。 - 对外业务 ID:订单与推送中的
user_id多为 playX 用户 ID(playx_user_id)。临时登录场景下,资产表会生成占位 ID,形如mall_{mall_user_asset.id}(见MallUserAsset::ensureForUsername)。
3.1 鉴权解析规则(resolvePlayxAssetIdFromRequest)
以下接口在服务端最终都会解析出 mall_user_asset.id(整型,资产表主键),再按该 ID 加载资产与关联数据。
优先级(由高到低):
session_id(post优先,get兼容)- 在
mall_session中存在且未过期:用会话里的user_id(playx_user_id字符串)在mall_user_asset按playx_user_id查找,得到资产主键。 - 若会话无效:兼容把
session_id参数误当作 商城 token 再试一次(UUID 形态 token)。
- 在
token(post/get或请求头ba-token/token)- 校验
token表:类型为会员user或商城临时muser。未过期时,user_id字段为mall_user_asset.id(muser)或会员体系约定 ID(user)。
- 校验
user_id(post/get兼容)- 纯数字:视为
mall_user_asset.id。 - 非纯数字:视为
playx_user_id,在mall_user_asset按该字段查找主键。
- 纯数字:视为
注意:请求参数的取值方式是
post()优先,get()兼容(即同字段既可传 post 也可传 get)。
3.2 临时登录(获取商城 token)
- 方法:
GET(推荐)或POST - 路径:
/api/v1/temLogin - 开关:
config/buildadmin.php→agent_auth.temp_login_enable为true;有效期agent_auth.temp_login_expire(秒)。
请求参数
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
username |
string | 是 | 登录名(唯一);不存在则自动创建 mall_user_asset 行 |
行为说明
- 若用户名不存在:
MallUserAsset::ensureForUsername创建资产行(随机密码等),并将playx_user_id更新为mall_{id}形式(与真实 playX ID 区分)。 - 签发 商城 token(类型
muser,token表内user_id=mall_user_asset.id),并签发muser-refresh刷新令牌。
返回(成功 data.userInfo)
| 字段 | 类型 | 说明 |
|---|---|---|
id |
int | mall_user_asset.id |
username |
string | 用户名 |
nickname |
string | 同 username |
playx_user_id |
string | 资产表中的 playx_user_id(如 mall_12) |
token |
string | 访问 H5 接口时携带 |
refresh_token |
string | 调用 /api/common/refreshToken 时使用(类型 muser-refresh) |
expires_in |
int | token 有效秒数 |
示例
curl -G 'http://localhost:1818/api/v1/temLogin' --data-urlencode 'username=demo_h5'
用户名含 + 等号时需 URL 编码(如 %2B60123456789)。
3.3 Token 验证(换 session)
- 方法:
POST(推荐GET传token亦可) - 路径:
/api/v1/mall/verifyToken
配置:本地验证 vs 远程 playX
- 配置项:
config/playx.php→verify_token_local_only(环境变量PLAYX_VERIFY_TOKEN_LOCAL_ONLY,未设置时默认为 **1/ 开启本地验证)。 verify_token_local_only = true(默认)- 不请求 playX HTTP。
- 仅接受商城临时登录 token(类型
muser),校验token表后写入mall_session。 - 返回的
data.user_id为playx_user_id(与资产表一致)。
verify_token_local_only = false(生产对接 playX)- 需配置
PLAYX_TOKEN_VERIFY_URL(完整https回调或相对路径)、PLAYX_ANGPOW_MERCHANT_CODE、PLAYX_ANGPOW_IMPORT_AUTH_KEY等(见docs/PlayX-对接文档(积分商城).md§5.2)。 - 相对路径时尚需
PLAYX_ANGPOW_IMPORT_BASE_URL;未配置则返回未配置类错误。
- 需配置
请求参数
必填其一:
token(Body 优先;session兼容字段;Query 也可传token)
可选 lang:zh / ZH / zh-cn 中文 msg(默认),en / EN 英文;可通过请求头、Query 或与 Body 同传的字段指定。
返回(成功 data)
| 字段 | 类型 | 说明 |
|---|---|---|
session_id |
string | 写入 mall_session |
user_id |
string | playX 用户 ID(即 playx_user_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/mall/verifyToken' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'token=上一步TemLogin返回的token'
3.x 收货地址(mall_address)
下面接口用于 H5 维护收货地址。鉴权同本章其他接口:携带
session_id或token或user_id。
3.x.1 地址列表
- 方法:
GET - 路径:
/api/v1/mall/addressList
返回:data.list 为地址数组。
3.x.2 添加地址
- 方法:
POST - 路径:
/api/v1/mall/addressAdd
Body:
| 字段 | 必填 | 说明 |
|---|---|---|
receiver_name |
是 | 收货人 |
phone |
是 | 电话 |
region |
是 | 地区(数组或逗号分隔字符串) |
detail_address |
是 | 详细地址 |
default_setting |
否 | 1 设为默认地址 |
3.x.3 修改地址(含设为默认)
- 方法:
POST - 路径:
/api/v1/mall/addressEdit
Body:id 必填,其余字段按需传入更新。
3.x.4 删除地址
- 方法:
POST - 路径:
/api/v1/mall/addressDelete
Body:id 必填。若删除默认地址,服务端会自动挑选一条剩余地址设为默认(如存在)。
3.4 用户资产(Assets)
- 方法:
GET - 路径:
/api/v1/mall/assets
请求参数(鉴权)
以下任选其一即可(与 3.1 鉴权解析规则 一致):
session_idtoken(或请求头ba-token/token)user_id(纯数字为mall_user_asset.id,否则为playx_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/mall/assets' --data-urlencode 'token=上一步temLogin返回的token'
响应(示例):
{
"code": 1,
"msg": "",
"time": 1712345678,
"data": {
"locked_points": 100,
"available_points": 50,
"today_limit": 200,
"today_claimed": 80,
"withdrawable_cash": 5.2
}
}
3.5 领取(Claim)
- 方法:
POST - 路径:
/api/v1/mall/claim
请求 Body
必填:
claim_request_id:幂等键(string,唯一)
鉴权:同 3.1(session_id / token / user_id)
返回(成功 data)
与 用户资产 返回字段一致(资产快照)。
幂等:
claim_request_id已存在:不会重复入账,直接返回当前资产快照
示例
curl -X POST 'http://localhost:1818/api/v1/mall/claim' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'claim_request_id=claim_001' \
--data-urlencode 'token=上一步temLogin返回的token'
响应(首次领取,示例):
{
"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.6 商品列表
- 方法:
GET - 路径:
/api/v1/mall/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/mall/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.7 红利兑换(Bonus Redeem)
- 方法:
POST - 路径:
/api/v1/mall/bonusRedeem
请求 Body
必填:
item_id:商品 ID(要求mall_item.type=BONUS且status=1) 鉴权:同 3.1(session_id/token/user_id)
返回(成功)
msg:Redeem submitted, please wait about 10 minutesdata.order_id:订单 IDdata.status:PENDING
示例
curl -X POST 'http://localhost:1818/api/v1/mall/bonusRedeem' \
-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.8 实物兑换(Physical Redeem)
- 方法:
POST - 路径:
/api/v1/mall/physicalRedeem
请求 Body
必填:
item_id:商品 ID(要求mall_item.type=PHYSICAL且status=1)address_id:收货地址 ID(mall_address.id,须属于当前用户资产;下单时写入mall_order.mall_address_id,并将该地址快照写入receiver_name/receiver_phone/receiver_address) 鉴权:同 3.1(session_id/token/user_id)
返回(成功)
msg:Redeem successdata:null
示例
curl -X POST 'http://localhost:1818/api/v1/mall/physicalRedeem' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'item_id=200' \
--data-urlencode 'address_id=10' \
--data-urlencode 'session_id=7b1c....'
响应(示例):
{
"code": 1,
"msg": "Redeem success",
"time": 1712345687,
"data": null
}
3.9 提现申请(Withdraw Apply)
- 方法:
POST - 路径:
/api/v1/mall/withdrawApply
请求 Body
必填:
item_id:商品 ID(要求mall_item.type=WITHDRAW且status=1) 鉴权:同 3.1(session_id/token/user_id)
返回(成功)
msg:Withdraw submitted, please wait about 10 minutesdata.order_id:订单 IDdata.status:PENDING
示例
curl -X POST 'http://localhost:1818/api/v1/mall/withdrawApply' \
-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.10 订单列表
- 方法:
GET - 路径:
/api/v1/mall/orders
请求参数(鉴权)
同 3.1(session_id / token / user_id)。
返回(成功 data)
list:订单列表(最多 100 条),并包含关联的mallItem(关系对象)- 列表项中的
user_id为 playX 侧playx_user_id(字符串),与mall_order.user_id一致
示例
请求:
curl -G 'http://localhost:1818/api/v1/mall/orders' --data-urlencode 'token=上一步temLogin返回的token'
响应(示例,简化):
{
"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.11 积分流水(Points Logs)
- 方法:
GET - 路径:
/api/v1/mall/pointsLogs
请求参数(鉴权)
同 3.1(session_id / token / user_id)。
Query 参数
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
limit |
int | 否 | 每页条数,默认 20,最大 100 |
cursor |
string | 否 | 游标(上一页返回 next_cursor) |
direction |
string | 否 | IN 仅入账 / OUT 仅扣减;不传返回全部 |
返回(成功 data)
| 字段 | 类型 | 说明 |
|---|---|---|
data.list |
array | 流水数组(时间倒序) |
data.next_cursor |
string|null | 下一页游标(本页最后一条记录的游标) |
list 每一项字段:
| 字段 | 类型 | 说明 |
|---|---|---|
biz_type |
string | CLAIM/REDEEM_BONUS/REDEEM_PHYSICAL/REDEEM_WITHDRAW/REFUND |
direction |
string | IN/OUT |
points |
int | 积分变动值(正数,方向由 direction 表示) |
ts |
int | Unix 秒 |
ref_id |
string | 领取为 claim_request_id;订单为 external_transaction_id |
order_no |
string | 订单号(非订单类为空) |
order_status |
string | 订单状态(非订单类为空) |
mallItem |
object|null | 商品信息(非订单类为 null) |
item_id |
int | 兼容字段:商品ID(非订单类为 0) |
item_title |
string | 兼容字段:商品标题(非订单类为空) |
item_type |
int | 兼容字段:商品类型(非订单类为 0;1=BONUS 2=PHYSICAL 3=WITHDRAW) |
item_score |
int | 兼容字段:商品所需积分(非订单类为 0) |
cursor |
string | 当前记录游标 |
mallItem 字段(非隐私):
| 字段 | 类型 | 说明 |
|---|---|---|
id |
int | mall_item.id |
title |
string | 商品名 |
type |
int | 商品类型 |
score |
int | 所需积分 |
amount |
number | 现金面值 |
multiplier |
int | 流水倍数 |
category |
string | 红利业务类别 |
category_title |
string | 类别展示名 |
示例
curl -G 'http://localhost:1818/api/v1/mall/pointsLogs' \
--data-urlencode 'token=上一步temLogin返回的token' \
--data-urlencode 'limit=20'
3.12 同步额度(可选)
当前代码未实现并未注册路由:/api/v1/mall/syncLimit。