@extends('admin.layouts.app') @section('title', '订阅详情') @section('page_title', '订阅详情') @section('content') @php // 统一构造平台订单跳转链接:避免手写拼接导致编码/漏字段问题 // 注意:这里使用相对路径,避免测试/不同 APP_URL 环境下生成绝对域名导致断言与展示不一致 // 同时:自动附带 back 参数,保证“从订阅详情跳到订单列表/治理后能回到订阅详情并保留上下文” $platformOrdersBaseUrl = '/admin/platform-orders'; // back 参数用于“返回上一页(保留上下文)”,但 back 本身不应再包含 back(避免无限嵌套导致 URL 膨胀) $selfWithoutBack = \App\Support\BackUrl::selfWithoutBack(); // back 安全护栏(全页通用):用于 `{!! !!}` 输出的 href(避免 & 破坏回链) $incomingBack = (string) request()->query('back', ''); $safeBackForLinks = \App\Support\BackUrl::sanitizeForLinks($incomingBack); $makePlatformOrderUrl = function (array $query) use ($platformOrdersBaseUrl, $selfWithoutBack) { // 若调用方显式传了 back,则不覆盖;否则默认回到当前订阅详情页(剔除 back,避免嵌套) $query = $query + ['back' => $selfWithoutBack]; return $platformOrdersBaseUrl . '?' . \Illuminate\Support\Arr::query($query); }; @endphp @php // 用于构建“保留当前上下文”的订阅列表跳转链接(从订阅详情跳回列表后可一键返回本订阅详情) // 统一收敛:订阅详情页自身(剔除 back)直接复用 BackUrl::selfWithoutBack,避免手写拼接口径漂移。 $subscriptionShowSelf = \App\Support\BackUrl::selfWithoutBack(); $makeSubscriptionIndexUrl = function (array $query) use ($subscriptionShowSelf) { $url = '/admin/site-subscriptions'; if (count($query) > 0) { $url .= '?' . \Illuminate\Support\Arr::query($query); } return \App\Support\BackUrl::withBack($url, $subscriptionShowSelf); }; @endphp
订阅号
{{ $subscription->subscription_no }}
状态
{{ ($statusLabels[$subscription->status] ?? $subscription->status) }} ({{ $subscription->status }})
@csrf
治理动作:可手工修正订阅状态(仅改状态字段,不自动改到期时间/订阅同步记录)。
站点
@if($subscription->merchant) {{ $subscription->merchant->name }} @else 未关联站点 @endif
套餐
@php $planName = $subscription->plan_name ?: ($subscription->plan?->name ?? '未设置'); @endphp @if($subscription->plan) {{ $planName }} @else {{ $planName }} @endif
计费周期 / 周期(月)
{{ $subscription->billing_cycle ?: '-' }} / {{ (int) $subscription->period_months }}
金额
¥{{ number_format((float) $subscription->amount, 2) }}
开始时间
{{ optional($subscription->starts_at)->format('Y-m-d H:i:s') ?: '-' }}
到期时间
{{ optional($subscription->ends_at)->format('Y-m-d H:i:s') ?: '-' }}
到期状态(按到期时间)
{{ $expiryLabel }}
试用到期
{{ optional($subscription->trial_ends_at)->format('Y-m-d H:i:s') ?: '-' }}
生效时间
{{ optional($subscription->activated_at)->format('Y-m-d H:i:s') ?: '-' }}
取消时间
{{ optional($subscription->cancelled_at)->format('Y-m-d H:i:s') ?: '-' }}
@if($subscription->subscription_no)
{{-- 去重降噪:下方摘要卡/表格头部已提供“全部订单/可同步”等跳转入口,这里仅保留治理相关入口与下单入口 --}} 查看续费缺订阅订单(同站点/同套餐) @php $createRenewalOrderUrl = '/admin/platform-orders/create?' . \Illuminate\Support\Arr::query([ 'merchant_id' => $subscription->merchant_id, 'plan_id' => $subscription->plan_id, 'site_subscription_id' => $subscription->id, 'order_type' => 'renewal', 'require_subscription' => '1', 'quantity' => 1, 'remark' => (string) config('saasshop.platform_orders.renewal_order_remark_prefix', '来自订阅:') . $subscription->subscription_no, // 保留 back:创建订单 -> 订单详情后可一键返回本订阅详情 'back' => $selfWithoutBack, ]); @endphp 创建续费订单
@endif
@php $reconcileMismatchOrders = (int) ($summaryStats['reconcile_mismatch_orders'] ?? 0); $refundInconsistentOrders = (int) ($summaryStats['refund_inconsistent_orders'] ?? 0); @endphp @if($reconcileMismatchOrders > 0 || $refundInconsistentOrders > 0)
批量同步治理提示
当前订阅下存在 @if($reconcileMismatchOrders > 0) 对账不一致 ({{ $reconcileMismatchOrders }}) @endif @if($reconcileMismatchOrders > 0 && $refundInconsistentOrders > 0) @endif @if($refundInconsistentOrders > 0) 退款不一致 ({{ $refundInconsistentOrders }}) @endif 订单。建议先逐单治理金额/状态口径(补回执/核对退款/修正状态),再批量同步订阅。 {{-- 去重降噪:上方正文已给出筛选入口链接,这里不再重复放置“进入XX订单列表”按钮 --}}
@endif
@csrf
提示:点击后按钮会自动禁用,避免重复提交。
说明:该按钮等价于平台订单页的“批量同步订阅(当前筛选范围)”,已内置只处理可同步订单(已支付+已生效+未同步)。

关联订单总数

点击跳转:该订阅下全部平台订单

已同步

点击跳转:该订阅下「已同步」订单

同步失败

点击跳转:该订阅下「同步失败」订单

可同步(已支付+已生效+未同步)

点击跳转:该订阅下「可同步订阅」订单

未同步(无记录)

口径:meta 无 activation 且无 error;点击跳转可查看明细

有回执订单 / 回执总额

{{ $summaryStats['receipt_orders'] ?? 0 }} / ¥{{ number_format((float) ($summaryStats['total_receipt_amount'] ?? 0), 2) }}
点击订单数可跳转:该订阅下「有回执」订单

有退款订单 / 退款总额

{{ $summaryStats['refund_orders'] ?? 0 }} / ¥{{ number_format((float) ($summaryStats['total_refunded_amount'] ?? 0), 2) }}
点击订单数可跳转:该订阅下「有退款」订单
@php $refundTol = (float) config('saasshop.amounts.tolerance', 0.01); @endphp
当前容差:¥{{ number_format($refundTol, 2) }}
@if(((int) ($summaryStats['refund_inconsistent_orders'] ?? 0)) > 0)
退款不一致治理提示
提示:存在退款状态与退款总额不一致订单。 查看退款不一致订单 (建议先逐单核对退款轨迹,再在订单详情页使用「退款状态治理」修正状态;仅修口径,不会自动生成回执/退款回执)。
@endif

对账差额(回执-已付)

@php $delta = (float) ($summaryStats['reconciliation_delta'] ?? 0); @endphp
点击差额可跳转:该订阅下「对账不一致」订单
@php $tol = (float) config('saasshop.amounts.tolerance', 0.01); @endphp @if(((int) ($summaryStats['reconcile_mismatch_orders'] ?? 0)) > 0)
对账不一致治理提示
提示:存在「回执总额 vs 已付金额」不一致订单。 查看对账不一致订单 (建议先逐单核对回执轨迹与订单金额,再决定是否补回执/修正订单金额口径)。
@elseif(abs($delta) >= $tol)
对账差额提示
差额超过容差(tol={{ number_format($tol, 2) }}),可能存在回执金额与订单已付金额不一致。
@else
差额在容差范围内(tol={{ number_format($tol, 2) }},当前订阅维度)
@endif

BMPA 失败数

点击跳转:该订阅下「批量标记支付并生效失败」订单

BMPA 失败原因Top3

@php $bmpaFailedReasonStats = $bmpaFailedReasonStats ?? []; // 避免 URL 过长/特殊字符破坏 query:原因过长则不生成 keyword 链接 // 与列表页/仪表盘/订单详情保持一致:由 config 统一控制阈值。 $FAILED_REASON_KEYWORD_MAX_LEN = (int) config('saasshop.platform_orders.sync_error_keyword_link_max_len', 200); @endphp @if(count($bmpaFailedReasonStats) > 0)
@foreach($bmpaFailedReasonStats as $item) @php $reason = (string) ($item['reason'] ?? ''); $reasonUrl = ''; if ($reason !== '' && mb_strlen($reason) <= $FAILED_REASON_KEYWORD_MAX_LEN) { $reasonUrl = $makePlatformOrderUrl([ 'site_subscription_id' => $subscription->id, 'bmpa_failed_only' => '1', 'bmpa_error_keyword' => $reason, ]); } @endphp
@if($reasonUrl) {{ $reason }} @else {{ $reason }} (原因过长,请复制到筛选框) @endif ({{ $item['count'] }})
@endforeach
@else
暂无 BMPA 失败原因聚合数据
@endif

同步失败原因Top3

@php $failedReasonStats = $failedReasonStats ?? []; @endphp @if(count($failedReasonStats) > 0)
@foreach($failedReasonStats as $item) @php $reason = (string) ($item['reason'] ?? ''); $reasonUrl = ''; if ($reason !== '' && mb_strlen($reason) <= $FAILED_REASON_KEYWORD_MAX_LEN) { $reasonUrl = $makePlatformOrderUrl([ 'site_subscription_id' => $subscription->id, 'sync_status' => 'failed', 'sync_error_keyword' => $reason, ]); } @endphp
@if($reasonUrl) {{ $reason }} @else {{ $reason }} (原因过长,请复制到筛选框) @endif ({{ $item['count'] }})
@endforeach
@else
暂无同步失败原因聚合数据
@endif

关联平台订单({{ $platformOrders->total() }})

同步状态筛选: @php $cur = $summaryStats['current_order_sync_status'] ?? ''; // 重要:这里的筛选链接需要保留 back,否则点击后会丢失“返回上一页(保留上下文)”能力。 // 同时:href 中会包含多个 query 参数,必须使用 `{!! !!}` 原样输出,避免 `&` 被转义为 `&`。 // $safeBackForLinks 已在页头统一按 BackUrl::sanitizeForLinks 计算。 // 构造“订阅详情页自身”的筛选链接:统一走 BackUrl::currentPathWithQuery,减少手写拼接与口径漂移。 // 目标:保留 back(安全过滤后)+ 保留其它 query 上下文 + 覆盖/移除 order_sync_status。 $makeSelfFilterUrl = function (?string $orderSyncStatus) use ($safeBackForLinks) { $v = ($orderSyncStatus !== null && $orderSyncStatus !== '') ? $orderSyncStatus : null; return \App\Support\BackUrl::currentPathWithQuery([ 'order_sync_status' => $v, ], $safeBackForLinks); }; @endphp 全部 已同步 同步失败 未同步 可同步 在平台订单页打开 @if($cur) (当前:{{ $cur }}) @endif
@forelse($platformOrders as $order) @php $syncedId = (int) data_get($order->meta, 'subscription_activation.subscription_id', 0); $syncErr = (string) (data_get($order->meta, 'subscription_activation_error.message') ?? ''); if ($syncedId > 0) { $syncStatus = '已同步'; } elseif ($syncErr !== '') { $syncStatus = '同步失败'; } else { $syncStatus = '未同步'; } @endphp @php $orderShowUrl = \App\Support\BackUrl::withBack('/admin/platform-orders/' . $order->id, $selfWithoutBack); @endphp @empty @endforelse
ID 订单号 订单状态 支付状态 应付/已付 下单时间 生效时间 同步状态 同步时间 失败原因 操作
{{ $order->id }}{{ $order->order_no }} {{ $order->status }} {{ $order->payment_status }} ¥{{ number_format((float) $order->payable_amount, 2) }} / ¥{{ number_format((float) $order->paid_amount, 2) }} {{ optional($order->placed_at)->format('Y-m-d H:i:s') ?: '-' }} {{ optional($order->activated_at)->format('Y-m-d H:i:s') ?: '-' }} {{ $syncStatus }} {{ data_get($order->meta, 'subscription_activation.synced_at') ?: '-' }} @if($syncStatus === '同步失败') {{ mb_substr($syncErr, 0, 60) }} @else - @endif 详情
暂无关联平台订单。
{{ $platformOrders->links('pagination.admin') }}
@endsection