1.优化相关文档

2.重新设置Token验证接口
This commit is contained in:
2026-04-30 15:18:06 +08:00
parent 487016e037
commit ccdb58ea1d
5 changed files with 334 additions and 124 deletions

View File

@@ -34,6 +34,8 @@ AGENT_AUTH_JWT_SECRET=
PLAYX_SESSION_EXPIRE_SECONDS=3600 PLAYX_SESSION_EXPIRE_SECONDS=3600
# verifyToken 是否仅本地联调false=走对方远程校验) # verifyToken 是否仅本地联调false=走对方远程校验)
PLAYX_VERIFY_TOKEN_LOCAL_ONLY=false PLAYX_VERIFY_TOKEN_LOCAL_ONLY=false
# verifyToken 对方接口路径
PLAYX_TOKEN_VERIFY_URL=/api/v1/auth/verify-token
# verifyToken 仅本地联调时的默认用户ID # verifyToken 仅本地联调时的默认用户ID
PLAYX_VERIFY_TOKEN_LOCAL_DEFAULT_USER_ID=testmyr PLAYX_VERIFY_TOKEN_LOCAL_DEFAULT_USER_ID=testmyr
# verifyToken 仅本地联调时的默认用户名 # verifyToken 仅本地联调时的默认用户名

View File

@@ -413,6 +413,10 @@ class Playx extends Api
$baseUrl = config('playx.angpow_import.base_url', ''); $baseUrl = config('playx.angpow_import.base_url', '');
$verifyUrl = config('playx.api.token_verify_url', '/api/v1/auth/verify-token'); $verifyUrl = config('playx.api.token_verify_url', '/api/v1/auth/verify-token');
$verifyPath = ltrim(strval($verifyUrl), '/');
if ($verifyPath === '') {
return $this->error(__('PlayX API not configured'));
}
if ($baseUrl === '') { if ($baseUrl === '') {
return $this->error(__('PlayX API not configured')); return $this->error(__('PlayX API not configured'));
} }
@@ -425,7 +429,7 @@ class Playx extends Api
} }
$requestId = 'mall_' . uniqid(); $requestId = 'mall_' . uniqid();
$requestDate = gmdate('Y-m-d H:i:s'); $requestDate = strval(time());
$signatureInput = 'merchant_code=' . $merchantCode $signatureInput = 'merchant_code=' . $merchantCode
. '&request_date=' . $requestDate . '&request_date=' . $requestDate
. '&request_id=' . $requestId . '&request_id=' . $requestId
@@ -453,18 +457,12 @@ class Playx extends Api
'request_id' => $requestId, 'request_id' => $requestId,
'token' => $token, 'token' => $token,
]; ];
$res = $client->post($verifyUrl, [ $res = $client->post($verifyPath, [
'headers' => $headers, 'headers' => $headers,
'json' => $payload, 'json' => $payload,
]); ]);
if ($res->getStatusCode() === 405) {
$res = $client->get($verifyUrl, [
'headers' => $headers,
'query' => $payload,
]);
}
$code = $res->getStatusCode();
$data = json_decode(strval($res->getBody()), true); $data = json_decode(strval($res->getBody()), true);
$code = $res->getStatusCode();
if ($code !== 200 || empty($data['user_id'])) { if ($code !== 200 || empty($data['user_id'])) {
$remoteMsg = ''; $remoteMsg = '';
if (is_array($data)) { if (is_array($data)) {

View File

@@ -32,7 +32,7 @@ return [
'api' => [ 'api' => [
'base_url' => strval(env('PLAYX_API_BASE_URL', '')), 'base_url' => strval(env('PLAYX_API_BASE_URL', '')),
'secret_key' => strval(env('PLAYX_API_SECRET_KEY', '')), 'secret_key' => strval(env('PLAYX_API_SECRET_KEY', '')),
'token_verify_url' => '/api/v1/auth/verify-token', 'token_verify_url' => strval(env('PLAYX_TOKEN_VERIFY_URL', '/api/v1/auth/verify-token')),
'bonus_grant_url' => '/api/v1/bonus/grant', 'bonus_grant_url' => '/api/v1/bonus/grant',
'balance_credit_url' => '/api/v1/balance/credit', 'balance_credit_url' => '/api/v1/balance/credit',
'transaction_status_url' => '/api/v1/transaction/status', 'transaction_status_url' => '/api/v1/transaction/status',

View File

@@ -71,15 +71,15 @@ flowchart LR
1. 用户在 playX 内打开积分商城入口iframe 1. 用户在 playX 内打开积分商城入口iframe
2. playX 前端通过 postMessage 将 **playX 下发的 token**(及必要上下文)传给商城 H5。 2. playX 前端通过 postMessage 将 **playX 下发的 token**(及必要上下文)传给商城 H5。
3. 商城 H5 调用商城后端 **`POST /api/v1/mall/verifyToken`**,由商城向 playX 的 **Token Verification API**`playX.api.base_url` + `playX.api.token_verify_url`)发起校验。 3. 商城 H5 调用商城后端 **`POST /api/v1/mall/verifyToken`**(前端只传 `token`,由商城后端向 playX 的 **Token Verification API**`PLAYX_ANGPOW_IMPORT_BASE_URL` + `PLAYX_TOKEN_VERIFY_URL`)发起校验。
4. **前提**:配置 **`playX.verify_token_local_only = false`**,且 **`playX.api.base_url`** 已配置为可访问的 playX 基地址。 4. **前提**:配置 **`playX.verify_token_local_only = false`**,且 **`PLAYX_ANGPOW_IMPORT_BASE_URL`** 已配置为可访问的 playX 基地址。
5. playX 返回 **`user_id``username`**(及可选会话过期时间等)。 5. playX 返回 **`user_id``username`**(及可选会话过期时间等)。
6. 商城写入 **`mall_playx_session`**`session_id` + 上述 `user_id`/`username` + 过期时间),后续 H5 可用 **`session_id`** 或 **`token`(商城临时 token见模式 B** 调用资产/领取等接口。 6. 商城写入 **`mall_playx_session`**`session_id` + 上述 `user_id`/`username` + 过期时间),后续 H5 可用 **`session_id`** 或 **`token`(商城临时 token见模式 B** 调用资产/领取等接口。
幂等与安全: 幂等与安全:
- H5 **不要**把 playX 的 `user_id` 当作唯一可信凭据直传下单;**以 token 换 session** 或由商城签发 token 的流程为准。 - H5 **不要**把 playX 的 `user_id` 当作唯一可信凭据直传下单;**以 token 换 session** 或由商城签发 token 的流程为准。
- playX 侧 Token Verification API 的鉴权/签名若有按双方约定可参考《playX-接口文档》§2.1 - playX 侧 Token Verification API 的鉴权/签名由商城后端完成(`merchant_code/request_date/request_id` + `X-Request-Signature`H5 不参与签名计算
#### 4.1.3 模式 B本地 / 无 playX 环境(商城自校验,不请求 playX #### 4.1.3 模式 B本地 / 无 playX 环境(商城自校验,不请求 playX
@@ -212,19 +212,29 @@ flowchart LR
### 5.2 Mall → playXToken Verification API ### 5.2 Mall → playXToken Verification API
- **目的**:商城后端校验 token/session获取可信 `user_id``username` - **目的**:商城后端校验 token/session获取可信 `user_id``username`
- **Method/Path示例占位**`POST /api/v1/auth/verify-token` - **Method**`POST`
- **Base URL**`PLAYX_ANGPOW_IMPORT_BASE_URL`(当前联调:`https://plx-uat2.ttwd3.com/en-my/mall`
- **Path**`/api/v1/auth/verify-token`
- **完整 URL**`{PLAYX_ANGPOW_IMPORT_BASE_URL}/api/v1/auth/verify-token`
- **签名 Header**`X-Request-Signature``Base64(HMAC-SHA1(canonical, key))`
- **签名明文 canonical**`merchant_code={merchant_code}&request_date={request_date}&request_id={request_id}&token={token}`
- **request_date 格式**Unix 时间戳字符串(秒)
请求字段说明(建议 请求字段说明(当前实现
| 字段名 | 类型 | 必填 | 说明 | | 字段名 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| `merchant_code` | String | 是 | 商户编码,默认 `plx`。 |
| `request_date` | String | 是 | 请求时间戳(秒)。 |
| `request_id` | String | 是 | 商城系统生成的唯一请求流水号。 | | `request_id` | String | 是 | 商城系统生成的唯一请求流水号。 |
| `token``session` | String | 是 | 从带有商城的 Iframe `postMessage` 接收到的用户加密登录散列或临时会话凭证。 | | `token` | String | 是 | 从带有商城的 Iframe `postMessage` 接收到的用户登录凭证。 |
请求示例: 请求示例:
```json ```json
{ {
"merchant_code": "plx",
"request_date": "1700000000",
"request_id": "mall_20260319_9f1b6d", "request_id": "mall_20260319_9f1b6d",
"token": "eyJhbGciOi..." "token": "eyJhbGciOi..."
} }

View File

@@ -271,159 +271,356 @@ curl -X POST 'https://{商城域名}/api/v1/mall/dailyPush' \
--- ---
## 5. 其他接口一览(摘要 ## 5. 其他接口一览(前端联调版
> 下列均为 **BuildAdmin 通用 `code/msg/time/data` 结构**;成功时 `code=1`。 > 下列接口统一返回 `code/msg/time/data`;成功通常为 `code=1`。
> 除 `verifyToken` 外,其余用户接口均需携带 `session_id` 或 `token`(见 §4
### 5.0 `POST /api/v1/mall/dailyPush`(后端对后端)
**用途**playX 按日推送用户资产基础数据到商城(前端一般不直接调用)。
**请求示例:**
```json
{
"request_id": "report_20260430",
"rows": [
{
"user_id": "U10001",
"username": "demo_user",
"yesterday_total_deposit": 1000,
"yesterday_win_loss_net": -500
}
]
}
```
### 5.1 `POST /api/v1/mall/verifyToken` ### 5.1 `POST /api/v1/mall/verifyToken`
**用途**:把 playX token 换成商城 `session_id`
用于将 **playX token**(或本地联调 token**商城 `session_id`** **前端入参约定(重要)**
- 前端/客户端调用本接口时,**只需要传 `token`**(或兼容传 `session`)。
- `merchant_code``request_date``request_id``X-Request-Signature` 由商城后端生成并带给 playX前端无需参与签名。
- 若仅传 `token` 仍校验失败,通常是 token 本身无效/过期或对方网关路径未放通,并非前端少传字段。
| 参数位置 | 名称 | 说明 | **请求示例:**
|----------|------|------| ```json
| POST/GET | `token``session` | playX 或商城 token | {
"token": "eyJhbGciOi..."
}
```
**说明:**`playX.verify_token_local_only=true`(默认),商城**仅本地校验** token不请求 playX 远程接口;远程模式需配置 `PLAYX_API_BASE_URL` 等。 **成功响应示例:**
```json
**成功 `data` 示例:** {
"code": 1,
| 字段 | 说明 | "msg": "Success",
|------|------| "time": 1777533000,
| `session_id` | 后续接口可带此字段 | "data": {
| `user_id` | playX 用户 ID 或映射后的标识 | "session_id": "fc7f3e3f0d0f4cb29f66e4c8fbab4f66",
| `username` | 用户名 | "user_id": "U10001",
| `token_expire_at` | ISO8601 过期时间 | "username": "demo_user",
"token_expire_at": "2026-04-30T16:20:00+08:00"
--- }
}
```
### 5.2 `GET /api/v1/mall/assets` ### 5.2 `GET /api/v1/mall/assets`
**用途**:查询当前用户积分资产。
查询积分资产;需 **§4** 身份。 **请求示例:**
```http
GET /api/v1/mall/assets?session_id=fc7f3e3f0d0f4cb29f66e4c8fbab4f66
```
**成功 `data`** **成功响应示例**
```json
| 字段 | 说明 | {
|------|------| "code": 1,
| `locked_points` | 待领取积分 | "msg": "Success",
| `available_points` | 可用积分 | "time": 1777533000,
| `today_limit` | 今日可领取上限 | "data": {
| `today_claimed` | 今日已领取 | "locked_points": 120,
| `withdrawable_cash` | 可提现现金(由积分×配置比例换算,保留小数) | "available_points": 350,
"today_limit": 200,
--- "today_claimed": 80,
"withdrawable_cash": 35
}
}
```
### 5.3 `POST /api/v1/mall/claim` ### 5.3 `POST /api/v1/mall/claim`
**用途**:领取积分(幂等)。
领取积分;需 **§4** 身份。 **请求示例:**
```json
{
"session_id": "fc7f3e3f0d0f4cb29f66e4c8fbab4f66",
"claim_request_id": "claim_20260430_0001"
}
```
| 参数 | 必填 | 说明 | **成功响应示例:**
|------|------|------| ```json
| `claim_request_id` | 是 | 幂等请求号 | {
"code": 1,
**成功 `data`** 与资产结构一致(含 `locked_points``available_points``today_limit``today_claimed``withdrawable_cash` 等)。 "msg": "Success",
"time": 1777533000,
--- "data": {
"locked_points": 60,
"available_points": 410,
"today_limit": 200,
"today_claimed": 140,
"withdrawable_cash": 41
}
}
```
### 5.4 `GET /api/v1/mall/items` ### 5.4 `GET /api/v1/mall/items`
**用途**:获取商城商品列表(可按类型筛选)。
商品列表。 **请求示例:**
```http
GET /api/v1/mall/items?type=BONUS
```
| 参数 | 必填 | 说明 | **成功响应示例:**
|------|------|------| ```json
| `type` | 否 | `BONUS` / `PHYSICAL` / `WITHDRAW`,筛选类型 | {
"code": 1,
**成功 `data`** `{ "list": [ ... ] }` "msg": "Success",
"time": 1777533000,
--- "data": {
"list": [
{
"id": 101,
"title": "10 MYR Bonus",
"type": "BONUS",
"points_cost": 1000
}
]
}
}
```
### 5.5 `POST /api/v1/mall/bonusRedeem` ### 5.5 `POST /api/v1/mall/bonusRedeem`
**用途**:兑换红利商品。
红利兑换;需 **§4** 身份。 **请求示例:**
```json
{
"session_id": "fc7f3e3f0d0f4cb29f66e4c8fbab4f66",
"item_id": 101
}
```
| 参数 | 必填 | 说明 | **成功响应示例:**
|------|------|------| ```json
| `item_id` | 是 | 商品 ID | {
"code": 1,
**成功 `data`**`order_id``status`(如 `PENDING`)等。 "msg": "Success",
"time": 1777533000,
--- "data": {
"order_id": "ORD202604300001",
"status": "PENDING"
}
}
```
### 5.6 `POST /api/v1/mall/physicalRedeem` ### 5.6 `POST /api/v1/mall/physicalRedeem`
**用途**:兑换实物商品(需要地址)。
实物兑换;需 **§4** 身份。 **请求示例:**
```json
{
"session_id": "fc7f3e3f0d0f4cb29f66e4c8fbab4f66",
"item_id": 202,
"address_id": 12
}
```
| 参数 | 必填 | 说明 | **成功响应示例:**
|------|------|------| ```json
| `item_id` | 是 | 实物商品 ID | {
| `address_id` | 是 | `mall_address.id`(当前用户下地址);订单保存 `mall_address_id` 与地址快照 | "code": 1,
"msg": "Success",
--- "time": 1777533000,
"data": {
"order_id": "ORD202604300002",
"status": "PENDING"
}
}
```
### 5.7 `POST /api/v1/mall/withdrawApply` ### 5.7 `POST /api/v1/mall/withdrawApply`
**用途**:发起提现档位兑换申请。
提现类兑换申请;需 **§4** 身份。 **请求示例:**
```json
{
"session_id": "fc7f3e3f0d0f4cb29f66e4c8fbab4f66",
"item_id": 303
}
```
| 参数 | 必填 | 说明 | **成功响应示例:**
|------|------|------| ```json
| `item_id` | 是 | 提现档位商品 ID | {
"code": 1,
--- "msg": "Success",
"time": 1777533000,
"data": {
"order_id": "ORD202604300003",
"status": "PENDING"
}
}
```
### 5.8 `GET /api/v1/mall/orders` ### 5.8 `GET /api/v1/mall/orders`
**用途**:查询当前用户订单列表。
订单列表;需 **§4** 身份。 **请求示例:**
```http
GET /api/v1/mall/orders?session_id=fc7f3e3f0d0f4cb29f66e4c8fbab4f66
```
**成功 `data`** `{ "list": [ ... ] }`(含关联商品等,以实际返回为准)。 **成功响应示例:**
```json
{
"code": 1,
"msg": "Success",
"time": 1777533000,
"data": {
"list": [
{
"order_id": "ORD202604300001",
"status": "PENDING",
"item_title": "10 MYR Bonus"
}
]
}
}
```
--- ### 5.9 `GET /api/v1/mall/pointsLogs`
**用途**:查询积分变动日志。
### 5.9 收货地址(`mall_address` **请求示例:**
```http
GET /api/v1/mall/pointsLogs?session_id=fc7f3e3f0d0f4cb29f66e4c8fbab4f66
```
> 下列接口均需携带 **§4 身份参数**`session_id` / `token` / `user_id` 之一)。 **成功响应示例:**
```json
{
"code": 1,
"msg": "Success",
"time": 1777533000,
"data": {
"list": [
{
"id": 9001,
"change_points": 50,
"type": "CLAIM",
"remark": "daily claim"
}
]
}
}
```
#### 5.9.1 获取收货地址列表 ### 5.10 收货地址(`mall_address`
- **方法**`GET` **用途**:用户收货地址 CRUD。
- **路径**`/api/v1/mall/addressList`
返回 `data.list`:地址数组(按 `default_setting` 优先,其次 id 倒序)。 #### 5.10.1 `GET /api/v1/mall/addressList`
**请求示例:**
```http
GET /api/v1/mall/addressList?session_id=fc7f3e3f0d0f4cb29f66e4c8fbab4f66
```
#### 5.9.2 添加收货地址 **成功响应示例:**
- **方法**`POST` ```json
- **路径**`/api/v1/mall/addressAdd` {
"code": 1,
"msg": "Success",
"time": 1777533000,
"data": {
"list": [
{
"id": 12,
"receiver_name": "Tom",
"phone": "0123456789",
"detail_address": "KLCC",
"default_setting": 1
}
]
}
}
```
Body表单或 JSON 均可,建议 JSON #### 5.10.2 `POST /api/v1/mall/addressAdd`
| 字段 | 必填 | 说明 | **请求示例:**
|------|------|------| ```json
| `receiver_name` | 是 | 收货人 | {
| `phone` | 是 | 联系电话 | "session_id": "fc7f3e3f0d0f4cb29f66e4c8fbab4f66",
| `region` | 是 | 地区(可传数组或逗号分隔字符串) | "receiver_name": "Tom",
| `detail_address` | 是 | 详细地址(短文本) | "phone": "0123456789",
| `default_setting` | 否 | `1` 设为默认地址;`0` 或不传为非默认 | "region": "Kuala Lumpur,KLCC",
"detail_address": "Tower A, 8F",
"default_setting": 1
}
```
成功返回:`data.id` 为新地址 id。 **成功响应示例:**
```json
{
"code": 1,
"msg": "Success",
"time": 1777533000,
"data": {
"id": 13
}
}
```
#### 5.9.3 修改收货地址(含设置默认) #### 5.10.3 `POST /api/v1/mall/addressEdit`
- **方法**`POST` **请求示例:**
- **路径**`/api/v1/mall/addressEdit` ```json
{
"session_id": "fc7f3e3f0d0f4cb29f66e4c8fbab4f66",
"id": 13,
"detail_address": "Tower B, 10F",
"default_setting": 1
}
```
Body **成功响应示例:**
| 字段 | 必填 | 说明 | ```json
|------|------|------| {
| `id` | 是 | 地址 id | "code": 1,
| `receiver_name/phone/region/detail_address/default_setting` | 否 | 需要修改的字段(只更新传入项) | "msg": "Success",
"time": 1777533000,
"data": null
}
```
`default_setting=1`:会自动把该用户其他地址的 `default_setting` 置为 0。 #### 5.10.4 `POST /api/v1/mall/addressDelete`
**请求示例:**
```json
{
"session_id": "fc7f3e3f0d0f4cb29f66e4c8fbab4f66",
"id": 13
}
```
#### 5.9.4 删除收货地址 **成功响应示例:**
- **方法**`POST` ```json
- **路径**`/api/v1/mall/addressDelete` {
"code": 1,
Body "msg": "Success",
| 字段 | 必填 | 说明 | "time": 1777533000,
|------|------|------| "data": null
| `id` | 是 | 地址 id | }
```
若删除的是默认地址:服务端会将剩余地址里 id 最大的一条自动设为默认(若存在)。
--- ---
@@ -433,7 +630,10 @@ Body
|-----------------|------| |-----------------|------|
| `PLAYX_DAILY_PUSH_SECRET` | 非空则 Daily Push 必须带合法 HMAC 头 | | `PLAYX_DAILY_PUSH_SECRET` | 非空则 Daily Push 必须带合法 HMAC 头 |
| `PLAYX_VERIFY_TOKEN_LOCAL_ONLY` | 为 true 时 verifyToken 不请求 playX 远程 | | `PLAYX_VERIFY_TOKEN_LOCAL_ONLY` | 为 true 时 verifyToken 不请求 playX 远程 |
| `PLAYX_API_BASE_URL` | 商城调用 playX 接口时使用与「playX 调商城」方向相反 | | `PLAYX_ANGPOW_IMPORT_BASE_URL` | 远程 `verify-token` 基础地址(当前联调可含站点前缀路径 |
| `PLAYX_TOKEN_VERIFY_URL` | 远程 `verify-token` 路径(默认 `/api/v1/auth/verify-token` |
| `PLAYX_ANGPOW_MERCHANT_CODE` | `verify-token` 请求体 `merchant_code` |
| `PLAYX_ANGPOW_IMPORT_AUTH_KEY` | `verify-token` 签名密钥HMAC-SHA1Base64 输出) |
--- ---