20 KiB
积分商城 PlayX 对接实施方案
基于《积分商城-内部对接与流程说明.md》和《PlayX-对接文档(积分商城).md》整理,结合当前项目结构给出具体落地方案。
一、接口创建
1.1 商城需对外提供的接口(PlayX 调用商城)
Daily Push API
接收 PlayX 每日 T+1 数据推送。
- 方法:
POST - 路径:
/api/v1/mall/dailyPush
请求(Header)
当配置了 playx.daily_push_secret(Daily Push 签名校验)时,需要携带:
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)
请求(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):
data.request_id:原样返回data.accepted:truedata.deduped:是否幂等命中(false=首次入库,true=重复推送)data.message:首次为ok,重复为duplicate input
示例
无签名校验(PLAYX_DAILY_PUSH_SECRET 为空):
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"
}
}
1.2 商城需调用的 PlayX 接口(外部,由 PlayX 提供)
以下为商城侧调用(由 PlayX 提供)。
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(必选)username(可选)token_expire_at(可选,能被strtotime解析)
示例
{
"request_id": "mall_abc123",
"token": "PLAYX_TOKEN_XXX"
}
{
"user_id": "U123",
"username": "demo_user",
"token_expire_at": "2026-04-01T12:00:00Z"
}
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 | 是 | 订单幂等键: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 |
示例(请求)
{
"request_id": "mall_bonus_abc123",
"externalTransactionId": "BONUS_ORD2026....",
"user_id": "U123",
"amount": 10.0,
"rewardName": "每日红利",
"category": "daily",
"categoryTitle": "每日",
"multiplier": 1
}
返回(期望)
商城侧以 data.status 判断:
status = "accepted":读取playx_transaction_id- 否则:读取
message写入fail_reason
示例(accepted):
{
"status": "accepted",
"playx_transaction_id": "PX_TX_001"
}
示例(reject):
{
"status": "rejected",
"message": "insufficient balance"
}
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 | 是 | 订单幂等键:external_transaction_id |
user_id |
string | 是 | PlayX 用户 ID |
amount |
number | 是 | 订单金额:MallPlayxOrder.amount |
multiplier |
int | 是 | MallPlayxOrder.multiplier |
返回(期望)
与 Bonus Grant 一致:
- 成功:
status="accepted"+playx_transaction_id - 失败:
message写入fail_reason
示例
请求(示意,由商城侧发起,实际 URL 以配置为准):
{
"request_id": "mall_withdraw_abc123",
"externalTransactionId": "WITHDRAW_ORD2026....",
"user_id": "U123",
"amount": 100.0,
"multiplier": 1
}
响应(accepted):
{
"status": "accepted",
"playx_transaction_id": "PX_TX_002"
}
响应(rejected):
{
"status": "rejected",
"message": "insufficient balance"
}
Transaction Status Query API(交易终态查询)
- 方法:
GET - URL:
${playx.api.base_url}${playx.api.transaction_status_url}(默认/api/v1/transaction/status)
Query
externalTransactionId:订单幂等键
示例(请求)
curl -G '${playx.api.base_url}/api/v1/transaction/status' \
--data-urlencode 'externalTransactionId=BONUS_ORD2026....'
返回(期望)
商城读取 data.status:
COMPLETED:设置订单status=COMPLETEDFAILED或REJECTED:设置订单status=REJECTED、grant_status=FAILED_FINAL,并退回积分;失败信息取data.message
示例(completed):
{ "status": "COMPLETED" }
示例(failed):
{ "status": "FAILED", "message": "grant rejected by PlayX" }
1.3 商城内部 API(供 H5 前端调用)
Token 验证
- 方法:
POST - 路径:
/api/v1/mall/verifyToken
请求(Body):
token(必填,优先读取)- 兼容:
session(当token为空时当作 token 使用)
成功返回(data):
session_iduser_idusernametoken_expire_at(ISO 时间字符串)
示例:
curl -X POST 'http://localhost:1818/api/v1/mall/verifyToken' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'token=PLAYX_TOKEN_XXX'
用户资产
- 方法:
GET - 路径:
/api/v1/mall/assets
请求参数(二选一):
session_id(优先):从mall_playx_session查 user_id(并校验过期)user_id:直接使用(兼容)
成功返回(data):
locked_pointsavailable_pointstoday_limittoday_claimedwithdrawable_cash(available_points * points_to_cash_ratio,保留 2 位)
示例
curl -G 'http://localhost:1818/api/v1/mall/assets' --data-urlencode 'session_id=7b1c....'
{
"code": 1,
"msg": "",
"data": {
"locked_points": 100,
"available_points": 50,
"today_limit": 200,
"today_claimed": 80,
"withdrawable_cash": 5.2
}
}
领取(Claim)
- 方法:
POST - 路径:
/api/v1/mall/claim
请求:
claim_request_id:幂等键(string,必填且唯一)- 鉴权:
session_id或user_id
成功返回(data):与资产接口一致(locked_points/available_points/today_limit/today_claimed/withdrawable_cash)
示例
(首次领取成功,可能返回 msg=Claim success;若幂等重复,msg 可能为空)
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 'session_id=7b1c....'
{
"code": 1,
"msg": "Claim success",
"data": {
"locked_points": 60,
"available_points": 90,
"today_limit": 200,
"today_claimed": 120,
"withdrawable_cash": 9.0
}
}
商品列表
- 方法:
GET - 路径:
/api/v1/mall/items
请求(可选):
type=BONUS|PHYSICAL|WITHDRAW
成功返回(data):
list:mall_item列表(包含amount/multiplier/category/category_title等字段)
示例
curl -G 'http://localhost:1818/api/v1/mall/items' --data-urlencode 'type=BONUS'
{
"code": 1,
"msg": "",
"data": {
"list": [
{
"id": 123,
"title": "每日红利",
"type": 1,
"score": 100,
"amount": 10.0,
"multiplier": 1,
"category": "daily",
"category_title": "每日"
}
]
}
}
红利兑换(Bonus Redeem)
- 方法:
POST - 路径:
/api/v1/mall/bonusRedeem
请求:
item_id:商品 ID(BONUS)- 鉴权:
session_id或user_id
成功返回(data):
order_idstatus: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",
"data": {
"order_id": 456,
"status": "PENDING"
}
}
实物兑换(Physical Redeem)
- 方法:
POST - 路径:
/api/v1/mall/physicalRedeem
请求:
item_id:商品 ID(PHYSICAL)address_id:mall_address.id(当前用户资产下地址;订单写入mall_address_id与收货快照)- 鉴权:
session_id或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",
"data": null
}
提现申请(Withdraw Apply)
- 方法:
POST - 路径:
/api/v1/mall/withdrawApply
请求:
item_id:商品 ID(WITHDRAW)- 鉴权:
session_id或user_id
成功返回(data):
order_idstatus: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",
"data": {
"order_id": 789,
"status": "PENDING"
}
}
订单列表
- 方法:
GET - 路径:
/api/v1/mall/orders
请求:
session_id或user_id
成功返回(data):
list:订单列表(最多 100 条,包含关联的mallItem)
示例
curl -G 'http://localhost:1818/api/v1/mall/orders' --data-urlencode 'session_id=7b1c....'
{
"code": 1,
"msg": "",
"data": {
"list": [
{
"id": 456,
"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 }
}
]
}
}
同步额度
当前代码未实现并未注册路由:/api/v1/mall/syncLimit。
如需补齐,请在接口设计阶段新增对应实现与 PlayX API 对接。
二、后台修改
2.1 商品管理(mall_item)
- 类型:需与文档一致
1= BONUS(红利)2= PHYSICAL(实物)3= WITHDRAW(提现档位)
- 新增字段(红利/提现档位):
amount:现金面值(元)multiplier:流水倍数category:红利业务类别(如 daily)category_title:类别展示名
- 库存:实物需
stock;红利/提现可不限制或按业务配置
2.2 订单管理(统一订单表)
- 订单类型:BONUS / PHYSICAL / WITHDRAW
- 订单状态:PENDING / COMPLETED / SHIPPED / REJECTED
- 实物订单:
- 发货:录入物流公司、单号 →
SHIPPED - 驳回:录入驳回原因 →
REJECTED,自动退回积分
- 发货:录入物流公司、单号 →
- 红利/提现订单:
- 展示
external_transaction_id、playx_transaction_id、推送playx - 手动重试:仅对
FAILED_RETRYABLE状态,记录retry_request_id、操作者、原因
- 展示
2.3 用户资产与人工调账
- 用户资产:按
user_id展示locked_points、available_points、today_limit、today_claimed - 人工调账:针对 T+1 推送异常或客诉,支持对
locked_points、available_points手动加减,并记录审计日志
2.4 每日推送数据
- 后台可查看
mall_playx_daily_push表数据,按user_id、date查询,便于排查异常
三、数据库修改
3.1 用户资产表(改造 mall_player 或新建)
方案 A:改造 mall_player
- 将
user_id作为主键(PlayX 的 user_id)或与现有 id 并存 - 新增字段:
locked_points(待领取积分)available_points(可用积分)today_limit(今日可领取上限)today_claimed(今日已领取)today_limit_date(今日上限所属日期,用于每日重置)
方案 B:新建 mall_playx_user_asset
user_id(PlayX 用户 ID,主键或唯一)username(冗余展示)locked_points、available_points、today_limit、today_claimed、today_limit_datecreate_time、update_time
3.2 每日推送数据表(新建)
表名:mall_playx_daily_push
| 字段 | 类型 | 说明 |
|---|---|---|
| id | int | 主键 |
| user_id | varchar(64) | 玩家 ID |
| date | date | 业务日期 |
| username | varchar(100) | 展示名 |
| yesterday_win_loss_net | decimal(15,2) | 昨日净输赢 |
| yesterday_total_deposit | decimal(15,2) | 昨日总充值 |
| lifetime_total_deposit | decimal(15,2) | 历史总充值 |
| lifetime_total_withdraw | decimal(15,2) | 历史总提现 |
| create_time | bigint | 创建时间 |
唯一索引:(user_id, date) 幂等去重
3.3 领取记录表(幂等)
表名:mall_playx_claim_log
| 字段 | 类型 | 说明 |
|---|---|---|
| id | int | 主键 |
| claim_request_id | varchar(64) | 幂等键,唯一 |
| user_id | varchar(64) | 用户 ID |
| claimed_amount | int | 领取积分 |
| create_time | bigint | 创建时间 |
唯一索引:claim_request_id
3.4 统一订单表(改造或新建)
表名:mall_playx_order(或统一改造 mall_pints_order / mall_redemption_order)
| 字段 | 类型 | 说明 |
|---|---|---|
| id | int | 主键 |
| user_id | varchar(64) | 用户 ID |
| type | enum | BONUS / PHYSICAL / WITHDRAW |
| status | enum | PENDING / COMPLETED / SHIPPED / REJECTED |
| mall_item_id | int | 商品 ID |
| points_cost | int | 消耗积分 |
| amount | decimal(15,2) | 现金面值(红利/提现) |
| multiplier | int | 流水倍数 |
| external_transaction_id | varchar(64) | 订单号 |
| playx_transaction_id | varchar(64) | PlayX 流水号 |
| grant_status | enum | NOT_SENT / SENT_PENDING / ACCEPTED / FAILED_RETRYABLE / FAILED_FINAL |
| fail_reason | text | 失败原因 |
| retry_count | int | 重试次数 |
| reject_reason | varchar(255) | 驳回原因(实物) |
| shipping_company | varchar(50) | 物流公司 |
| shipping_no | varchar(64) | 物流单号 |
| mall_address_id | int unsigned, NULL | 实物兑换所选 mall_address.id(快照仍见 receiver_*) |
| receiver_name | varchar(50) | 收货人 |
| receiver_phone | varchar(20) | 收货电话 |
| receiver_address | text | 收货地址 |
| create_time | bigint | 创建时间 |
| update_time | bigint | 更新时间 |
索引:user_id、external_transaction_id、type、status
3.5 商品表(mall_item)扩展
新增字段(若不存在):
| 字段 | 类型 | 说明 |
|---|---|---|
| amount | decimal(15,2) | 现金面值(红利/提现档位) |
| multiplier | int | 流水倍数 |
| category | varchar(32) | 红利业务类别 |
| category_title | varchar(64) | 类别展示名 |
四、业务规则
4.1 计算规则(需配置)
- 返还比例:
新增保障金 = ABS(yesterday_win_loss_net) * 返还比例(仅yesterday_win_loss_net < 0时) - 解锁比例:
今日可领取上限 = yesterday_total_deposit * 解锁比例 - 提现折算:积分 → 现金(如 10 分 = 1 元),用于前端展示
4.2 每日重置
today_claimed与today_limit按业务日重置(today_limit_date变化时)
4.3 发放重试
- 自动重试:1min / 5min / 15min,最多 3 次,使用同一
externalTransactionId - 仅对
NOT_SENT、FAILED_RETRYABLE重试 - 收到
accepted后不再重试,改轮询交易终态查询 API
五、实施顺序建议
- 数据库:新增迁移(
mall_playx_daily_push、mall_playx_claim_log、mall_playx_order),扩展mall_item、mall_player(或新建资产表) - 模型:
MallPlayxDailyPush、MallPlayxClaimLog、MallPlayxOrder、扩展MallItem、MallPlayer - 接口:Daily Push API(含签名校验)→ Token 验证 → 资产/领取 → 商品列表 → 红利/实物/提现 → 订单列表
- 后台:商品扩展、订单管理(含发货/驳回/重试)、人工调账、每日推送数据查看
- 定时任务:轮询交易终态、自动重试失败发放
六、待确认事项
- PlayX 提供的 Token Verification API、Bonus Grant API、Balance Credit API、交易终态查询 API 的 URL、鉴权方式、字段最终表
date的时区定义(如 UTC+8)- 返还比例、解锁比例、提现折算的具体数值
- 是否启用「同步额度」功能(需 PlayX 提供对应 API)