@extends('layouts.dashboard') @section('content') @php /** * Super-admin subscription management. * * Lists every restaurant's current subscription standing and what can be done to a term: * record a payment taken outside the product, refund one, or end it early. Recording by hand is * the ONLY way a priced plan can be paid on an install with no gateway keyed — the action existed, * was deleted, and left this page's own tooltip promising it. Mirrors admin/campaigns.php: same * .qm-table pattern, shared component classes, no new CSS. * * @var array $rows one row per restaurant (latest subscription joined to plan + owner) * @var array $pager pagination metadata from paginate * @var array $platformTax the platform's own tax config for subscription invoices */ $statusPill = static function (string $s): string { return match ($s) { 'active' => 'qm-pill-green', 'pending' => 'qm-pill-amber', 'expired', 'cancelled' => 'qm-pill-red', default => 'qm-pill-grey', }; }; $today = date('Y-m-d'); $filter = $filter ?? ''; $counts = $counts ?? []; $allCount = $allCount ?? 0; // The export walks the same door the list shows: the active tab travels with it. $qs = $filter !== '' ? http_build_query(['status' => $filter]) : ''; @endphp @php /* .qm-d2: the dashboard density standard (app.css §35) */ @endphp
| {{ t_raw('common.restaurant') }} | {{ t_raw('admin.subscriptions.col_plan') }} | {{ t_raw('common.status') }} | {{ t_raw('common.period') }} | {{ t_raw('admin.subscriptions.col_amount') }} | {{ t_raw('admin.subscriptions.col_reference') }} | {{ t_raw('common.actions') }} |
|---|---|---|---|---|---|---|
|
{!! restaurant_logo_html(['logo' => $s['logo'] ?? '', 'name' => $s['restaurant_name'] ?? ''], 'md') !!}
{{ (string) $s['restaurant_name'] }}
{{ (string) ($s['owner_email'] ?? '-') }}
|
@if ($planName !== '') {!! plan_tag($planSlug, $planName) !!} @php /* The TERM's cadence first — a monthly plan sold annually must say yearly here; only rows from before annual billing fall back to the plan's own cycle. */ @endphp {{ status_label('billing_cycle', (string) (($s['term_cycle'] ?? '') ?: ($s['plan_cycle'] ?? $s['restaurant_plan_cycle'] ?? ''))) }} @else {{ t_raw('admin.subscriptions.no_plan') }} @endif | @if ($status !== '') {{ status_label('subscription', $isExpired && $status === 'active' ? 'expired' : $status) }} @else {{ status_label('subscription', 'none') }} @endif | @if (!empty($s['starts_at'])) {!! fmt_day($s['starts_at']) !!} – {!! !empty($s['ends_at']) ? fmt_day($s['ends_at']) : '' . t('admin.subscriptions.lifetime') . '' !!} @else {!! dash() !!} @endif | {!! isset($s['amount_paid']) && $s['subscription_id'] ? money($s['amount_paid']) : dash() !!} | @if (!empty($s['payment_ref'])) {{ (string) $s['payment_ref'] }} @if (!empty($s['payment_method'])) @php $m = (string) $s['payment_method']; $ico = payment_method_icon($m); @endphp · {{ $ico !== '' ? $ico : status_label('payment_method', $m) }} @endif @else {!! dash() !!} @endif | @php // Ending a term is rare and consequential, so both lifecycle states sit in the // overflow menu, and only while a term is still running — there is nothing to end // on a subscription that has already lapsed. $canEnd = !empty($s['subscription_id']) && in_array($status, ['active', 'pending'], true); // The two lifecycle actions END a term rather than sell one, so they stay behind // the overflow menu; recording a payment and refunding one sit inline, because those // are the actions an operator comes to this page to perform. $rowActions = []; /* The paper a paid term produced. Same invoice the vendor prints, reached through the admin route (no vendor ownership scope on this side). */ if (!empty($s['subscription_id']) && !empty($s['invoice_no'])) { $rowActions[] = [ 'label' => t_raw('vendor.billing.view_invoice'), 'icon' => 'fa-file-invoice', 'href' => '/admin/subscriptions/invoice/' . (int) $s['subscription_id'], 'primary' => true, ]; } if ($canEnd) { $rowActions[] = ['label' => t_raw('admin.subscriptions.mark_expired_btn'), 'icon' => 'fa-hourglass-end', 'form' => [ 'action' => '/admin/subscriptions/lifecycle', 'fields' => ['subscription_id' => (int) $s['subscription_id'], 'status' => 'expired'], 'confirm' => t_raw('admin.subscriptions.confirm_expire', [':restaurant' => (string) $s['restaurant_name']]), ]]; $rowActions[] = ['label' => t_raw('admin.subscriptions.cancel_btn'), 'icon' => 'fa-ban', 'danger' => true, 'form' => [ 'action' => '/admin/subscriptions/lifecycle', 'fields' => ['subscription_id' => (int) $s['subscription_id'], 'status' => 'cancelled'], 'confirm' => t_raw('admin.subscriptions.confirm_cancel', [':restaurant' => (string) $s['restaurant_name']]), ]]; } /* Cancelling stops the NEXT charge; it never returns the money already taken. Until PaymentGateway::refund() existed there was nothing that could — so a term paid by mistake had no remedy inside the product at all. Offered only where there is money to return and a gateway reference to return it against. */ if (!empty($s['subscription_id']) && (float) ($s['amount_paid'] ?? 0) > 0 && $status !== 'refunded' && in_array((string) ($s['payment_method'] ?? ''), ['stripe', 'paypal'], true)) { $rowActions[] = ['label' => t_raw('vendor.orders.refund_btn'), 'icon' => 'fa-undo', 'danger' => true, 'form' => [ 'action' => '/admin/subscriptions/refund', 'fields' => ['subscription_id' => (int) $s['subscription_id']], 'confirm' => t_raw('admin.subscriptions.confirm_refund', [ ':amount' => money((float) $s['amount_paid']), ':restaurant' => (string) $s['restaurant_name'], ':method' => status_label('payment_method', (string) $s['payment_method']), ]), ]]; } @endphp@php /* Record payment: money taken OUTSIDE the product — a bank transfer, a cheque, cash — or a gateway payment whose confirmation never landed. Gateway payments record themselves, so this rides in the actions menu with the other exception levers rather than fronting every row as if a settled PayPal term still needed recording. On an install with no gateway keyed it is still the only way a priced plan gets paid. */ @endphp @php $rowActions[] = [ 'label' => t_raw('admin.subscriptions.record_payment_btn'), 'icon' => 'fa-receipt', 'attrs' => [ 'data-bs-toggle' => 'modal', 'data-bs-target' => '#subPayModal', 'data-sub-pay' => '', 'data-restaurant-id' => (string) (int) $s['restaurant_id'], 'data-restaurant-name' => (string) $s['restaurant_name'], 'data-plan-id' => (string) (int) ($s['plan_id'] ?: $s['restaurant_plan_id']), ], ]; @endphp @partial('row-actions', ['inline' => 1, 'actions' => $rowActions, 'menuLabel' => t_raw('admin.subscriptions.lifecycle_menu_label')]) |