diff --git a/app/admin/controller/Dashboard.php b/app/admin/controller/Dashboard.php
index 4843c56..9cd6773 100644
--- a/app/admin/controller/Dashboard.php
+++ b/app/admin/controller/Dashboard.php
@@ -66,7 +66,7 @@ class Dashboard extends Backend
$pendingPhysicalToShip = MallOrder::where('type', MallOrder::TYPE_PHYSICAL)
->where('status', MallOrder::STATUS_PENDING)
->count();
- $grantFailedRetryableCount = MallOrder::whereIn('type', [MallOrder::TYPE_BONUS, MallOrder::TYPE_WITHDRAW])
+ $grantFailedRetryableCount = MallOrder::where('type', MallOrder::TYPE_BONUS)
->where('grant_status', MallOrder::GRANT_FAILED_RETRYABLE)
->count();
diff --git a/app/admin/controller/mall/Order.php b/app/admin/controller/mall/Order.php
index 6d6cd6d..c926c4a 100644
--- a/app/admin/controller/mall/Order.php
+++ b/app/admin/controller/mall/Order.php
@@ -202,7 +202,7 @@ class Order extends Backend
$id = $data['id'] ?? 0;
$rejectReason = $data['reject_reason'] ?? '';
- if (!$id || $rejectReason === '') {
+ if (!$id) {
return $this->error(__('Missing required fields'));
}
@@ -214,6 +214,10 @@ class Order extends Backend
return $this->error(__('Order status must be PENDING'));
}
+ if ($order->type === MallOrder::TYPE_PHYSICAL && $rejectReason === '') {
+ return $this->error(__('Missing required fields'));
+ }
+
Db::startTrans();
try {
$asset = MallUserAsset::where('playx_user_id', $order->user_id ?? '')->find();
@@ -229,7 +233,11 @@ class Order extends Backend
$order->status = MallOrder::STATUS_REJECTED;
$order->reject_reason = $rejectReason;
- $order->grant_status = MallOrder::GRANT_FAILED_FINAL;
+ if ($order->type === MallOrder::TYPE_BONUS) {
+ $order->grant_status = MallOrder::GRANT_FAILED_FINAL;
+ } else {
+ $order->grant_status = MallOrder::GRANT_NOT_APPLICABLE;
+ }
$order->update_time = time();
$order->save();
@@ -243,7 +251,7 @@ class Order extends Backend
}
/**
- * 手动重试(仅红利/提现,且必须 FAILED_RETRYABLE)
+ * 手动重试(仅红利推送失败可重试)
*/
public function retry(Request $request): Response
{
@@ -265,8 +273,8 @@ class Order extends Backend
if (!$order) {
return $this->error(__('Record not found'));
}
- if (!in_array($order->type, [MallOrder::TYPE_BONUS, MallOrder::TYPE_WITHDRAW], true)) {
- return $this->error(__('Only BONUS/WITHDRAW can retry'));
+ if ($order->type !== MallOrder::TYPE_BONUS) {
+ return $this->error(__('Only BONUS can retry'));
}
if ($order->grant_status !== MallOrder::GRANT_FAILED_RETRYABLE) {
return $this->error(__('Only FAILED_RETRYABLE can retry'));
diff --git a/app/api/controller/v1/Playx.php b/app/api/controller/v1/Playx.php
index 12b076c..47f1563 100644
--- a/app/api/controller/v1/Playx.php
+++ b/app/api/controller/v1/Playx.php
@@ -1016,6 +1016,7 @@ class Playx extends Api
'receiver_name' => $snapshot['receiver_name'],
'receiver_phone' => $snapshot['receiver_phone'],
'receiver_address' => $snapshot['receiver_address'],
+ 'grant_status' => MallOrder::GRANT_NOT_APPLICABLE,
'create_time' => time(),
'update_time' => time(),
]);
@@ -1082,7 +1083,7 @@ class Playx extends Api
'amount' => $amount,
'multiplier' => $multiplier,
'external_transaction_id' => $orderNo,
- 'grant_status' => MallOrder::GRANT_NOT_SENT,
+ 'grant_status' => MallOrder::GRANT_NOT_APPLICABLE,
'create_time' => time(),
'update_time' => time(),
]);
@@ -1093,11 +1094,6 @@ class Playx extends Api
return $this->error($e->getMessage());
}
- $baseUrl = config('playx.api.base_url', '');
- if ($baseUrl !== '') {
- $this->callPlayxBalanceCredit($order, $playxUserId);
- }
-
return $this->success(__('Withdraw submitted, please wait about 10 minutes'), [
'order_id' => $order->id,
'status' => 'PENDING',
@@ -1143,39 +1139,4 @@ class Playx extends Api
}
}
- private function callPlayxBalanceCredit(MallOrder $order, string $userId): void
- {
- $baseUrl = rtrim(config('playx.api.base_url', ''), '/');
- $url = config('playx.api.balance_credit_url', '/api/v1/balance/credit');
- if ($baseUrl === '') {
- return;
- }
-
- try {
- $client = new \GuzzleHttp\Client(['timeout' => 15]);
- $res = $client->post($baseUrl . $url, [
- 'json' => [
- 'request_id' => 'mall_withdraw_' . uniqid(),
- 'externalTransactionId' => $order->external_transaction_id,
- 'user_id' => $userId,
- 'amount' => $order->amount,
- 'multiplier' => $order->multiplier,
- ],
- ]);
- $data = json_decode(strval($res->getBody()), true);
- if ($res->getStatusCode() === 200 && ($data['status'] ?? '') === 'accepted') {
- $order->playx_transaction_id = $data['playx_transaction_id'] ?? '';
- $order->grant_status = MallOrder::GRANT_ACCEPTED;
- $order->save();
- } else {
- $order->grant_status = MallOrder::GRANT_FAILED_RETRYABLE;
- $order->fail_reason = $data['message'] ?? 'unknown';
- $order->save();
- }
- } catch (\Throwable $e) {
- $order->grant_status = MallOrder::GRANT_FAILED_RETRYABLE;
- $order->fail_reason = $e->getMessage();
- $order->save();
- }
- }
}
diff --git a/app/common/model/MallOrder.php b/app/common/model/MallOrder.php
index 3e2ebb8..5f3b521 100644
--- a/app/common/model/MallOrder.php
+++ b/app/common/model/MallOrder.php
@@ -51,6 +51,9 @@ class MallOrder extends Model
public const GRANT_FAILED_RETRYABLE = 'FAILED_RETRYABLE';
public const GRANT_FAILED_FINAL = 'FAILED_FINAL';
+ /** 非红利订单不参与 PlayX/Angpow 推送,固定为该占位值 */
+ public const GRANT_NOT_APPLICABLE = '---';
+
protected array $type = [
'create_time' => 'integer',
'update_time' => 'integer',
diff --git a/app/process/PlayxJobs.php b/app/process/PlayxJobs.php
index 975e546..db4fcb2 100644
--- a/app/process/PlayxJobs.php
+++ b/app/process/PlayxJobs.php
@@ -47,7 +47,8 @@ class PlayxJobs
$path = strval(config('playx.api.transaction_status_url', '/api/v1/transaction/status'));
$url = rtrim($baseUrl, '/') . $path;
- $list = MallOrder::where('grant_status', MallOrder::GRANT_ACCEPTED)
+ $list = MallOrder::where('type', MallOrder::TYPE_BONUS)
+ ->where('grant_status', MallOrder::GRANT_ACCEPTED)
->where('status', MallOrder::STATUS_PENDING)
->order('id', 'desc')
->limit(50)
@@ -99,12 +100,11 @@ class PlayxJobs
}
$bonusPath = strval(config('playx.api.bonus_grant_url', '/api/v1/bonus/grant'));
- $withdrawPath = strval(config('playx.api.balance_credit_url', '/api/v1/balance/credit'));
$bonusUrl = rtrim($baseUrl, '/') . $bonusPath;
- $withdrawUrl = rtrim($baseUrl, '/') . $withdrawPath;
$maxRetry = 3;
- $list = MallOrder::whereIn('grant_status', [
+ $list = MallOrder::where('type', MallOrder::TYPE_BONUS)
+ ->whereIn('grant_status', [
MallOrder::GRANT_NOT_SENT,
MallOrder::GRANT_FAILED_RETRYABLE,
])
@@ -124,7 +124,7 @@ class PlayxJobs
$order->retry_count = intval($order->retry_count ?? 0) + 1;
try {
- $this->sendGrantByOrder($order, $bonusUrl, $withdrawUrl, $maxRetry);
+ $this->sendGrantByOrder($order, $bonusUrl, $maxRetry);
} catch (\Throwable $e) {
$order->fail_reason = $e->getMessage();
if (intval($order->retry_count) >= $maxRetry) {
@@ -167,7 +167,7 @@ class PlayxJobs
return false;
}
- private function sendGrantByOrder(MallOrder $order, string $bonusUrl, string $withdrawUrl, int $maxRetry): void
+ private function sendGrantByOrder(MallOrder $order, string $bonusUrl, int $maxRetry): void
{
$item = null;
if ($order->mallItem) {
@@ -221,45 +221,7 @@ class PlayxJobs
return;
}
- if ($order->type === MallOrder::TYPE_WITHDRAW) {
- $multiplier = intval($order->multiplier ?? 0);
- if ($multiplier <= 0) {
- $multiplier = 1;
- }
- $requestId = 'mall_retry_withdraw_' . uniqid();
- $res = $this->http->post($withdrawUrl, [
- 'json' => [
- 'request_id' => $requestId,
- 'externalTransactionId' => $order->external_transaction_id,
- 'user_id' => $order->user_id,
- 'amount' => $order->amount,
- 'multiplier' => $multiplier,
- ],
- ]);
-
- $data = json_decode(strval($res->getBody()), true) ?? [];
- if ($res->getStatusCode() === 200 && ($data['status'] ?? '') === 'accepted') {
- $order->grant_status = MallOrder::GRANT_ACCEPTED;
- $order->playx_transaction_id = strval($data['playx_transaction_id'] ?? '');
- $order->save();
- return;
- }
-
- $order->fail_reason = strval($data['message'] ?? 'PlayX balance credit not accepted');
- if (intval($order->retry_count) >= $maxRetry) {
- $order->grant_status = MallOrder::GRANT_FAILED_FINAL;
- $order->status = MallOrder::STATUS_REJECTED;
- $order->save();
- $this->refundPoints($order);
- return;
- }
-
- $order->grant_status = MallOrder::GRANT_FAILED_RETRYABLE;
- $order->save();
- return;
- }
-
- // PHYSICAL 目前由后台手工发货/驳回,不参与 PlayX 发放重试
+ // 非 BONUS 订单不参与 PlayX 发放重试(提现/实物由后台流程处理)
}
private function refundPoints(MallOrder $order): void
diff --git a/docs/积分商城-PlayX对接实施方案.md b/docs/积分商城-PlayX对接实施方案.md
index 693071b..be42de9 100644
--- a/docs/积分商城-PlayX对接实施方案.md
+++ b/docs/积分商城-PlayX对接实施方案.md
@@ -526,7 +526,7 @@ curl -G 'http://localhost:1818/api/v1/mall/orders' --data-urlencode 'session_id=
- 发货:录入物流公司、单号 → `SHIPPED`
- 驳回:录入驳回原因 → `REJECTED`,自动退回积分
- **红利/提现订单**:
- - 展示 `external_transaction_id`、`playx_transaction_id`、发放子状态
+ - 展示 `external_transaction_id`、`playx_transaction_id`、推送playx
- 手动重试:仅对 `FAILED_RETRYABLE` 状态,记录 `retry_request_id`、操作者、原因
### 2.3 用户资产与人工调账
diff --git a/web/src/lang/backend/en/mall/order.ts b/web/src/lang/backend/en/mall/order.ts
index 5661cab..18845ef 100644
--- a/web/src/lang/backend/en/mall/order.ts
+++ b/web/src/lang/backend/en/mall/order.ts
@@ -23,6 +23,7 @@ export default {
'grant_status ACCEPTED': 'ACCEPTED',
'grant_status FAILED_RETRYABLE': 'FAILED_RETRYABLE',
'grant_status FAILED_FINAL': 'FAILED_FINAL',
+ 'grant_status ---': '---',
fail_reason: 'fail_reason',
reject_reason: 'reject_reason',
shipping_company: 'shipping_company',
diff --git a/web/src/lang/backend/en/mall/playxOrder.ts b/web/src/lang/backend/en/mall/playxOrder.ts
index ff648ee..9ede71e 100644
--- a/web/src/lang/backend/en/mall/playxOrder.ts
+++ b/web/src/lang/backend/en/mall/playxOrder.ts
@@ -23,6 +23,7 @@ export default {
'grant_status ACCEPTED': 'ACCEPTED',
'grant_status FAILED_RETRYABLE': 'FAILED_RETRYABLE',
'grant_status FAILED_FINAL': 'FAILED_FINAL',
+ 'grant_status ---': '---',
fail_reason: 'fail_reason',
reject_reason: 'reject_reason',
shipping_company: 'shipping_company',
diff --git a/web/src/lang/backend/zh-cn/mall/order.ts b/web/src/lang/backend/zh-cn/mall/order.ts
index c771e60..badffef 100644
--- a/web/src/lang/backend/zh-cn/mall/order.ts
+++ b/web/src/lang/backend/zh-cn/mall/order.ts
@@ -17,12 +17,13 @@ export default {
multiplier: '流水倍数',
external_transaction_id: '订单号',
playx_transaction_id: 'PlayX流水号',
- grant_status: '发放子状态',
+ grant_status: '推送playx状态',
'grant_status NOT_SENT': '未发送',
'grant_status SENT_PENDING': '已发送排队',
'grant_status ACCEPTED': '已接收(accepted)',
'grant_status FAILED_RETRYABLE': '失败可重试',
'grant_status FAILED_FINAL': '失败最终',
+ 'grant_status ---': '---',
fail_reason: '失败原因',
reject_reason: '驳回原因',
shipping_company: '物流公司',
diff --git a/web/src/lang/backend/zh-cn/mall/playxCenter.ts b/web/src/lang/backend/zh-cn/mall/playxCenter.ts
index 822f813..0f61436 100644
--- a/web/src/lang/backend/zh-cn/mall/playxCenter.ts
+++ b/web/src/lang/backend/zh-cn/mall/playxCenter.ts
@@ -1,6 +1,6 @@
export default {
title: 'PlayX 对接中心',
- desc: '集中管理积分商城与 PlayX 的订单、推送、领取与资产数据。建议优先处理“发放子状态=失败可重试”的订单。',
+ desc: '集中管理积分商城与 PlayX 的订单、推送、领取与资产数据。建议优先处理“推送playx状态=失败可重试”的订单。',
orders: '统一订单',
dailyPush: '每日推送',
claimLog: '领取记录',
diff --git a/web/src/lang/backend/zh-cn/mall/playxOrder.ts b/web/src/lang/backend/zh-cn/mall/playxOrder.ts
index b4bfef3..187f240 100644
--- a/web/src/lang/backend/zh-cn/mall/playxOrder.ts
+++ b/web/src/lang/backend/zh-cn/mall/playxOrder.ts
@@ -17,12 +17,13 @@ export default {
multiplier: '流水倍数',
external_transaction_id: '订单号',
playx_transaction_id: 'PlayX流水号',
- grant_status: '发放子状态',
+ grant_status: '推送playx状态',
'grant_status NOT_SENT': '未发送',
'grant_status SENT_PENDING': '已发送排队',
'grant_status ACCEPTED': '已接收(accepted)',
'grant_status FAILED_RETRYABLE': '失败可重试',
'grant_status FAILED_FINAL': '失败最终',
+ 'grant_status ---': '---',
fail_reason: '失败原因',
reject_reason: '驳回原因',
shipping_company: '物流公司',
diff --git a/web/src/views/backend/mall/order/index.vue b/web/src/views/backend/mall/order/index.vue
index 8dcec94..dc58ea6 100644
--- a/web/src/views/backend/mall/order/index.vue
+++ b/web/src/views/backend/mall/order/index.vue
@@ -125,6 +125,15 @@ const baTable = new baTableClass(
label: t('mall.order.grant_status'),
prop: 'grant_status',
align: 'center',
+ minWidth: 100,
+ custom: {
+ NOT_SENT: 'info',
+ SENT_PENDING: 'primary',
+ ACCEPTED: 'primary',
+ FAILED_RETRYABLE: 'error',
+ FAILED_FINAL: 'error',
+ '---': 'info',
+ },
operator: 'eq',
sortable: false,
render: 'tag',
@@ -134,6 +143,7 @@ const baTable = new baTableClass(
ACCEPTED: t('mall.order.grant_status ACCEPTED'),
FAILED_RETRYABLE: t('mall.order.grant_status FAILED_RETRYABLE'),
FAILED_FINAL: t('mall.order.grant_status FAILED_FINAL'),
+ '---': t('mall.order.grant_status ---'),
},
},
{
@@ -237,8 +247,7 @@ const baTable = new baTableClass(
text: '手动重试',
type: 'warning',
icon: '',
- display: (row: TableRow) =>
- (row.type === 'BONUS' || row.type === 'WITHDRAW') && row.grant_status === 'FAILED_RETRYABLE' && row.status === 'PENDING',
+ display: (row: TableRow) => row.type === 'BONUS' && row.grant_status === 'FAILED_RETRYABLE' && row.status === 'PENDING',
popconfirm: {
title: '确认将该订单加入重试队列?',
confirmButtonText: '确认',
diff --git a/web/src/views/backend/mall/order/popupForm.vue b/web/src/views/backend/mall/order/popupForm.vue
index 1d09d49..87b057e 100644
--- a/web/src/views/backend/mall/order/popupForm.vue
+++ b/web/src/views/backend/mall/order/popupForm.vue
@@ -57,7 +57,7 @@
-
+
+
+ 确认后将驳回该订单并退回积分(红利/提现订单无需填写驳回原因)。
+
@@ -97,29 +101,31 @@
:input-attr="{ content: { PENDING: t('mall.order.status PENDING'), COMPLETED: t('mall.order.status COMPLETED'), SHIPPED: t('mall.order.status SHIPPED'), REJECTED: t('mall.order.status REJECTED') } }"
:placeholder="t('Please select field', { field: t('mall.order.status') })"
/>
-
-
-
+
+
+
+
+
@@ -159,7 +165,7 @@
diff --git a/web/src/views/backend/mall/playxOrder/index.vue b/web/src/views/backend/mall/playxOrder/index.vue
index f75db2c..8336b53 100644
--- a/web/src/views/backend/mall/playxOrder/index.vue
+++ b/web/src/views/backend/mall/playxOrder/index.vue
@@ -83,6 +83,7 @@ const baTable = new baTableClass(
ACCEPTED: t('mall.playxOrder.grant_status ACCEPTED'),
FAILED_RETRYABLE: t('mall.playxOrder.grant_status FAILED_RETRYABLE'),
FAILED_FINAL: t('mall.playxOrder.grant_status FAILED_FINAL'),
+ '---': t('mall.playxOrder.grant_status ---'),
},
},
{
@@ -115,8 +116,7 @@ const baTable = new baTableClass(
text: '手动重试',
type: 'warning',
icon: '',
- display: (row: TableRow) =>
- (row.type === 'BONUS' || row.type === 'WITHDRAW') && row.grant_status === 'FAILED_RETRYABLE' && row.status === 'PENDING',
+ display: (row: TableRow) => row.type === 'BONUS' && row.grant_status === 'FAILED_RETRYABLE' && row.status === 'PENDING',
popconfirm: {
title: '确认将该订单加入重试队列?',
confirmButtonText: '确认',