Просмотр исходного кода

后台实时推送:人工充值订单推送到代收订单频道

人工充值(payment_orders type=3)此前没有映射到任何频道,relay 解析后直接丢弃。
人工充值审查页请求的是 /admin/paymentOrder?channel=rgcz,因此推送到
admin.orders.payment,行数据按同一口径解析;前端需按 data.type 区分本页类型。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
doge 21 часов назад
Родитель
Сommit
db181f472c

+ 5 - 1
app/Services/AdminRealtime/AdminRealtimeRows.php

@@ -104,7 +104,7 @@ final class AdminRealtimeRows
         return $row === null ? [] : [self::message($channel, ['id' => self::castId($id)], $row)];
     }
 
-    /** 代收(type=1)进「代收订单」与「三方充值」两张列表,代付(type=2/4)进「人民币提现」 */
+    /** 代收(type=1)进「代收订单」与「三方充值」两张列表,人工充值(type=3)进「代收订单」频道,代付(type=2/4)进「人民币提现」 */
     private static function paymentOrders(string $id, string $action): array
     {
         $key = ['id' => self::castId($id)];
@@ -119,6 +119,10 @@ final class AdminRealtimeRows
             if ($row !== null) $messages[] = self::message('admin.orders.payment', $key, $row);
             $row = self::pick(ThirdPartyDepositService::list(['id' => $id])['data'] ?? [], 'id', $id);
             if ($row !== null) $messages[] = self::message('admin.orders.third_party_deposit', $key, $row);
+        } elseif ($type === PaymentOrderService::TYPE_SELF_PAY) {
+            // 人工充值审查页同样请求 /admin/paymentOrder,以 channel=rgcz 切换到 type=3
+            $row = self::pick(PaymentOrderService::paginate(['type' => PaymentOrderService::TYPE_SELF_PAY, 'channel' => 'rgcz', 'id' => $id])['data'] ?? [], 'id', $id);
+            if ($row !== null) $messages[] = self::message('admin.orders.payment', $key, $row);
         } elseif (in_array($type, [PaymentOrderService::TYPE_PAYOUT, PaymentOrderService::TYPE_SELF_PAYOUT], true)) {
             $row = self::pick(self::viaController(Withdraw::class, 'rmb', ['id' => $id, 'type' => $type]), 'id', $id);
             if ($row !== null) $messages[] = self::message('admin.orders.payment_withdraw', $key, $row);

+ 3 - 2
docs/前端交付/bot-28/后台/后台实时推送-前端接入.md

@@ -113,7 +113,7 @@ echo.private('admin.orders.withdraw')
 | `admin.orders.third_game` | `/admin/thirdGame/orders` | `id` | 同上 |
 | `admin.orders.bet` | `/admin/bet` | `id` | 同上 |
 | `admin.orders.recharge` | `/admin/wallet` | `id` | 同上 |
-| `admin.orders.payment` | `/admin/paymentOrder`(代收,`type=1`) | `id` | 同上 |
+| `admin.orders.payment` | `/admin/paymentOrder`(代收 `type=1`;人工充值审查 `channel=rgcz`,即 `type=3`) | `id` | 同上 |
 | `admin.orders.third_party_deposit` | `/admin/thirdPartyDeposit`(三方充值,`type=1`) | `id` | 同上 |
 | `admin.orders.payment_withdraw` | `/admin/withdraw/rmb`(`type=2/4`) | `id` | 同上 |
 | `admin.orders.withdraw` | `/admin/withdraw` | `id` | 同上 |
@@ -173,6 +173,7 @@ Echo 回调收到的对象:
 
 - `seq` 是持久保存、单调递增且不复用的广播序号,用于去重和补发;`id` 是事件表编号,仅供排查;`key.id` 或 `key.member_id` 才是行主键,三者不能混用。
 - 用 **`seq + channel`** 去重,并限制去重缓存大小。同一笔代收订单会同时推给代收与三方充值两个频道,`seq` 相同。
+- `admin.orders.payment` 同时承载代收(`type=1`)与人工充值(`type=3`)订单,代收列表页和人工充值审查页都订阅它,收到后须按 `data.type` 只处理本页的类型;`admin.orders.payment_withdraw` 同理区分 `type=2/4`。
 - 每条记录还要按 `channel + key` 保存已应用的最大 `seq`;删除、下线同样保留版本标记,拒绝较旧的补发覆盖新状态。完整列表重新同步或离开频道后可清理该频道的版本标记。
 - 只有第一页、默认倒序且符合筛选时才直接把新行放到顶部;其它分页提示有新数据,保留管理员当前浏览位置。
 - 合计、总数和按端在线统计没有随行同步推送,需要时节流重新请求对应 HTTP 接口;推送不可用时继续提供原有的刷新方式。
@@ -264,7 +265,7 @@ Echo 回调收到的对象:
 | `admin.orders.third_game` | `/admin/thirdGame/orders` 的 `list[]` 单行 | `id`、`user_id`、`member_id`、`username`、`first_name`、`player_id`、`platform`、`currency`、`game_type`、`game_type_text`、`game_name`、`round`、`table`、`seat`、`bet_amount`、`valid_amount`、`settled_amount`、`bet_content`、`status`、`status_text`、`game_order_id`、`bet_time`、`last_update_time` |
 | `admin.orders.bet` | `/admin/bet` 单行 | `bets` 字段、`user`、`is_winner`、`winning_array`,`created_at` 为 `Y-m-d H:i` |
 | `admin.orders.recharge` | `/admin/wallet` 单行 | `recharges` 字段、`member`(会员信息) |
-| `admin.orders.payment` | `/admin/paymentOrder` 单行 | `payment_orders` 字段、`first_name`、`user_admin_note` |
+| `admin.orders.payment` | `/admin/paymentOrder` 单行(人工充值按 `channel=rgcz` 口径) | `payment_orders` 字段、`first_name`、`user_admin_note` |
 | `admin.orders.third_party_deposit` | `/admin/thirdPartyDeposit` 单行 | `id`、`member_id`、`username`、`first_name`、`payment_company`、`payment_company_name`、`payment_method`、`channel_code`、`amount`、`fee`、`before_balance`、`after_balance`、`order_no`、`gateway_order_no`、`submitted_at`、`credited_at`、`platform`、`status`、`status_text` |
 | `admin.orders.payment_withdraw` | `/admin/withdraw/rmb` 单行(按订单的 `type` 取 2 或 4) | `payment_orders` 字段、`first_name`、`user_admin_note` |
 | `admin.orders.manual_audit` | `/admin/manualAudit` 单行 | `id`、`request_id`、`admin_id`、`member_id`、`username`、`first_name`、`transaction_type(_text)`、`change_type`、`level_before/after(_name)`、`amount`、`signed_amount`、`before_balance`、`after_balance`、`status(_text)`、`remark`、`failure_reason`、`created_at` |

+ 3 - 2
docs/总后台/后台实时推送-前端接入.md

@@ -113,7 +113,7 @@ echo.private('admin.orders.withdraw')
 | `admin.orders.third_game` | `/admin/thirdGame/orders` | `id` | 同上 |
 | `admin.orders.bet` | `/admin/bet` | `id` | 同上 |
 | `admin.orders.recharge` | `/admin/wallet` | `id` | 同上 |
-| `admin.orders.payment` | `/admin/paymentOrder`(代收,`type=1`) | `id` | 同上 |
+| `admin.orders.payment` | `/admin/paymentOrder`(代收 `type=1`;人工充值审查 `channel=rgcz`,即 `type=3`) | `id` | 同上 |
 | `admin.orders.third_party_deposit` | `/admin/thirdPartyDeposit`(三方充值,`type=1`) | `id` | 同上 |
 | `admin.orders.payment_withdraw` | `/admin/withdraw/rmb`(`type=2/4`) | `id` | 同上 |
 | `admin.orders.withdraw` | `/admin/withdraw` | `id` | 同上 |
@@ -173,6 +173,7 @@ Echo 回调收到的对象:
 
 - `seq` 是持久保存、单调递增且不复用的广播序号,用于去重和补发;`id` 是事件表编号,仅供排查;`key.id` 或 `key.member_id` 才是行主键,三者不能混用。
 - 用 **`seq + channel`** 去重,并限制去重缓存大小。同一笔代收订单会同时推给代收与三方充值两个频道,`seq` 相同。
+- `admin.orders.payment` 同时承载代收(`type=1`)与人工充值(`type=3`)订单,代收列表页和人工充值审查页都订阅它,收到后须按 `data.type` 只处理本页的类型;`admin.orders.payment_withdraw` 同理区分 `type=2/4`。
 - 每条记录还要按 `channel + key` 保存已应用的最大 `seq`;删除、下线同样保留版本标记,拒绝较旧的补发覆盖新状态。完整列表重新同步或离开频道后可清理该频道的版本标记。
 - 只有第一页、默认倒序且符合筛选时才直接把新行放到顶部;其它分页提示有新数据,保留管理员当前浏览位置。
 - 合计、总数和按端在线统计没有随行同步推送,需要时节流重新请求对应 HTTP 接口;推送不可用时继续提供原有的刷新方式。
@@ -264,7 +265,7 @@ Echo 回调收到的对象:
 | `admin.orders.third_game` | `/admin/thirdGame/orders` 的 `list[]` 单行 | `id`、`user_id`、`member_id`、`username`、`first_name`、`player_id`、`platform`、`currency`、`game_type`、`game_type_text`、`game_name`、`round`、`table`、`seat`、`bet_amount`、`valid_amount`、`settled_amount`、`bet_content`、`status`、`status_text`、`game_order_id`、`bet_time`、`last_update_time` |
 | `admin.orders.bet` | `/admin/bet` 单行 | `bets` 字段、`user`、`is_winner`、`winning_array`,`created_at` 为 `Y-m-d H:i` |
 | `admin.orders.recharge` | `/admin/wallet` 单行 | `recharges` 字段、`member`(会员信息) |
-| `admin.orders.payment` | `/admin/paymentOrder` 单行 | `payment_orders` 字段、`first_name`、`user_admin_note` |
+| `admin.orders.payment` | `/admin/paymentOrder` 单行(人工充值按 `channel=rgcz` 口径) | `payment_orders` 字段、`first_name`、`user_admin_note` |
 | `admin.orders.third_party_deposit` | `/admin/thirdPartyDeposit` 单行 | `id`、`member_id`、`username`、`first_name`、`payment_company`、`payment_company_name`、`payment_method`、`channel_code`、`amount`、`fee`、`before_balance`、`after_balance`、`order_no`、`gateway_order_no`、`submitted_at`、`credited_at`、`platform`、`status`、`status_text` |
 | `admin.orders.payment_withdraw` | `/admin/withdraw/rmb` 单行(按订单的 `type` 取 2 或 4) | `payment_orders` 字段、`first_name`、`user_admin_note` |
 | `admin.orders.manual_audit` | `/admin/manualAudit` 单行 | `id`、`request_id`、`admin_id`、`member_id`、`username`、`first_name`、`transaction_type(_text)`、`change_type`、`level_before/after(_name)`、`amount`、`signed_amount`、`before_balance`、`after_balance`、`status(_text)`、`remark`、`failure_reason`、`created_at` |

+ 24 - 0
tests/Integration/AdminRealtimeTest.php

@@ -2,7 +2,9 @@
 
 namespace Tests\Integration;
 
+use App\Http\Controllers\admin\PaymentOrder as PaymentOrderController;
 use App\Http\Controllers\admin\Withdraw as WithdrawController;
+use App\Models\PaymentOrder;
 use App\Models\User;
 use App\Models\Withdraw;
 use App\Services\AdminRealtime\AdminRealtime;
@@ -181,6 +183,28 @@ class AdminRealtimeTest extends TestCase
             'SQLite 不支持在线判定 SQL,扫描失败只记日志不影响广播');
     }
 
+    public function test_manual_recharge_orders_push_to_the_payment_order_channel(): void
+    {
+        Schema::create('payment_orders', function (Blueprint $t) {
+            $t->id(); $t->integer('type'); $t->string('order_no')->default(''); $t->string('member_id'); $t->decimal('amount', 20, 2)->default(0);
+            $t->decimal('fee', 20, 2)->default(0); $t->string('channel')->default(''); $t->integer('payment_type')->default(0); $t->integer('status')->default(0);
+            $t->text('pay_data')->nullable(); $t->string('remark')->nullable(); $t->text('recharge_bonus_snapshot')->nullable();
+            $t->integer('is_send')->default(0); $t->integer('is_locked')->default(0); $t->timestamps();
+        });
+        $order = PaymentOrder::create(['type' => 3, 'order_no' => 'rgcz_1', 'member_id' => '9001', 'amount' => 50, 'channel' => 'rgcz',
+            'payment_type' => 1, 'status' => 0, 'recharge_bonus_snapshot' => null]);
+        $order->update(['status' => 5]);
+
+        $events = $this->consume(0)['events'];
+        $this->assertSame([['channel' => 'admin.orders.payment', 'action' => 'created', 'key' => ['id' => 1]]],
+            array_map(fn ($e) => array_intersect_key($e, array_flip(['channel', 'action', 'key'])), $events), '人工充值只推「代收订单」频道');
+
+        $this->app->instance('request', Request::create('/admin/paymentOrder', 'GET', ['channel' => 'rgcz']));
+        $list = (new PaymentOrderController())->index()->getData(true)['data']['data'];
+        $this->assertSame(collect($list)->firstWhere('id', 1), $events[0]['data'], '推送行与人工充值审查列表的行一致');
+        $this->assertSame(5, $events[0]['data']['status']);
+    }
+
     public function test_feed_delivers_late_commits_with_a_later_seq(): void
     {
         $insert = fn (int $id, string $at) => DB::table('admin_realtime_events')->insert([