From bdbf572f195fac279fec593bdd573de112fac85a Mon Sep 17 00:00:00 2001 From: isaacclad Date: Thu, 16 Jul 2026 00:42:34 +0000 Subject: [PATCH] Bill Frontdesk Pro and Enterprise per branch. Pro is GHS 990/branch/mo and Enterprise is GHS 1990/branch/mo, with the Care-style branch selector UI, self-serve Enterprise checkout, and renewal charges scaled to billed branches. --- .../Controllers/Frontdesk/ProController.php | 112 ++++++++++++++--- app/Services/Frontdesk/PlanService.php | 67 ++++++++++- app/Services/Frontdesk/ProRenewalService.php | 24 ++-- config/frontdesk.php | 19 ++- .../frontdesk/integrations/upgrade.blade.php | 2 +- resources/views/frontdesk/pro/index.blade.php | 113 +++++++++++++++--- .../views/frontdesk/settings/edit.blade.php | 4 +- routes/web.php | 1 + tests/Feature/FrontdeskProTest.php | 14 ++- 9 files changed, 295 insertions(+), 61 deletions(-) diff --git a/app/Http/Controllers/Frontdesk/ProController.php b/app/Http/Controllers/Frontdesk/ProController.php index a15e65a..03326ec 100644 --- a/app/Http/Controllers/Frontdesk/ProController.php +++ b/app/Http/Controllers/Frontdesk/ProController.php @@ -26,6 +26,7 @@ class ProController extends Controller ? Carbon::parse($settings['plan_expires_at']) : null; $planKey = $plans->planKey($organization); + $branchCount = max(1, $plans->activeBranchCount($organization)); return view('frontdesk.pro.index', [ 'organization' => $organization, @@ -34,10 +35,17 @@ class ProController extends Controller 'isEnterprise' => $planKey === 'enterprise', 'hasPaidPlan' => $plans->hasPaidPlan($organization), 'canManage' => $canManage, - 'proPriceMinor' => $plans->proPriceMinor(), + 'proPricePerBranchMinor' => $plans->proPricePerBranchMinor(), + 'proPriceMinor' => $plans->proPriceTotalMinor($organization), + 'enterprisePricePerBranchMinor' => $plans->enterprisePricePerBranchMinor(), + 'enterprisePriceMinor' => $plans->enterprisePriceMinor($organization), + 'branchCount' => $branchCount, + 'canSubscribeEnterprise' => $plans->canSubscribeEnterprise($organization), + 'enterpriseMinBranches' => (int) config('frontdesk.enterprise.min_branches', 1), 'prepaidMonths' => (array) config('frontdesk.prepaid_months', [6, 12, 24]), 'currency' => (string) config('billing.currency', 'GHS'), 'planExpiresAt' => $expiresAt, + 'billedBranches' => (int) ($settings['billed_branches'] ?? $branchCount), 'salesUrl' => ladill_platform_url('contact-sales'), ]); } @@ -52,20 +60,60 @@ class ProController extends Controller ->with('success', 'Your organization already has an active paid plan.'); } - if ($plans->isEnterprise($organization)) { - return redirect()->route('frontdesk.pro.index') - ->with('success', 'Your organization is on Frontdesk Enterprise.'); - } + $validated = $request->validate([ + 'branches' => ['required', 'integer', 'min:1', 'max:999'], + ]); + + $branches = $plans->resolveCheckoutBranches($organization, (int) $validated['branches']); + $amountMinor = $plans->priceForBranches('pro', $branches); return $this->chargeMonthlyWallet( $organization, $billing, - $plans->proPriceMinor(), + $amountMinor, 'pro', 'frontdesk_pro', 'frontdesk-pro-'.$organization->id.'-'.now()->format('Y-m-d-His'), - 'Ladill Frontdesk Pro — monthly subscription', + 'Ladill Frontdesk Pro — '.$branches.' branch(es) monthly', 'Welcome to Frontdesk Pro — active for one month.', + $branches, + ); + } + + public function subscribeEnterprise(Request $request, PlanService $plans, BillingClient $billing): RedirectResponse + { + $this->authorizeAbility($request, 'settings.manage'); + $organization = $this->organization($request); + + if ($plans->hasPaidPlan($organization)) { + return redirect()->route('frontdesk.pro.index') + ->with('success', 'Your organization already has an active paid plan.'); + } + + if (! $plans->canSubscribeEnterprise($organization)) { + $min = (int) config('frontdesk.enterprise.min_branches', 1); + + return redirect()->route('frontdesk.pro.index') + ->with('error', "Enterprise requires at least {$min} active branch. Add a branch or contact sales."); + } + + $validated = $request->validate([ + 'branches' => ['required', 'integer', 'min:1', 'max:999'], + ]); + + $branches = $plans->resolveCheckoutBranches($organization, (int) $validated['branches']); + $amountMinor = $plans->priceForBranches('enterprise', $branches); + + return $this->chargeMonthlyWallet( + $organization, + $billing, + $amountMinor, + 'enterprise', + 'frontdesk_enterprise', + 'frontdesk-enterprise-'.$organization->id.'-'.now()->format('Y-m-d-His'), + 'Ladill Frontdesk Enterprise — '.$branches.' branch(es) monthly', + 'Welcome to Frontdesk Enterprise — active for one month.', + $branches, ); } @@ -73,27 +121,41 @@ class ProController extends Controller { $this->authorizeAbility($request, 'settings.manage'); $validated = $request->validate([ - 'plan' => ['required', 'in:pro'], + 'plan' => ['required', 'in:pro,enterprise'], 'months' => ['required', 'integer', 'in:6,12,24'], + 'branches' => ['required', 'integer', 'min:1', 'max:999'], ]); $organization = $this->organization($request); - if ($plans->hasPaidPlan($organization) || $plans->isEnterprise($organization)) { + if ($plans->hasPaidPlan($organization)) { return redirect()->route('frontdesk.pro.index') ->with('success', 'Your organization already has an active plan.'); } + $plan = $validated['plan']; $months = (int) $validated['months']; - $amountMinor = $plans->proPriceMinor() * $months; + + if ($plan === 'enterprise' && ! $plans->canSubscribeEnterprise($organization)) { + $min = (int) config('frontdesk.enterprise.min_branches', 1); + + return redirect()->route('frontdesk.pro.index') + ->with('error', "Enterprise requires at least {$min} active branch."); + } + + $branches = $plans->resolveCheckoutBranches($organization, (int) $validated['branches']); + $amountMinor = $plans->priceForBranches($plan, $branches) * $months; try { $checkout = $billing->initiatePlanCheckout( $organization->owner_ref, - 'pro', + $plan, $months, $amountMinor, route('frontdesk.pro.paystack.callback'), - ['organization_id' => $organization->id], + [ + 'organization_id' => $organization->id, + 'billed_branches' => $branches, + ], ); } catch (\Throwable) { return redirect()->route('frontdesk.pro.index') @@ -140,11 +202,18 @@ class ProController extends Controller abort(403); } + $plan = (string) ($result['plan'] ?? 'pro'); $months = (int) ($result['months'] ?? 0); - $this->activatePrepaidPlan($organization, 'pro', $months); + $branches = isset($metadata['billed_branches']) + ? (int) $metadata['billed_branches'] + : max(1, $plans->activeBranchCount($organization)); + + $this->activatePrepaidPlan($organization, $plan, $months, $branches); + + $label = $plan === 'enterprise' ? 'Frontdesk Enterprise' : 'Frontdesk Pro'; return redirect()->route('frontdesk.pro.index') - ->with('success', "Frontdesk Pro is active for {$months} months."); + ->with('success', "{$label} is active for {$months} months."); } private function chargeMonthlyWallet( @@ -156,6 +225,7 @@ class ProController extends Controller string $reference, string $description, string $successMessage, + int $billedBranches, ): RedirectResponse { try { if (! $billing->canAfford($organization->owner_ref, $priceMinor)) { @@ -184,23 +254,29 @@ class ProController extends Controller $settings['plan'] = $plan; $settings['auto_renew'] = true; $settings['billing_method'] = 'wallet_monthly'; + $settings['billed_branches'] = $billedBranches; $settings['plan_expires_at'] = now() ->addDays((int) config('frontdesk.pro.period_days', 30)) ->toIso8601String(); - unset($settings['plan_renewal_error']); + unset($settings['plan_renewal_error'], $settings['enterprise_billed_branches']); $organization->update(['settings' => $settings]); return redirect()->route('frontdesk.pro.index')->with('success', $successMessage); } - private function activatePrepaidPlan(Organization $organization, string $plan, int $months): void - { + private function activatePrepaidPlan( + Organization $organization, + string $plan, + int $months, + int $billedBranches, + ): void { $settings = $organization->settings ?? []; $settings['plan'] = $plan; $settings['auto_renew'] = false; $settings['billing_method'] = 'paystack_prepaid'; + $settings['billed_branches'] = $billedBranches; $settings['plan_expires_at'] = now()->addMonths($months)->toIso8601String(); - unset($settings['plan_renewal_error']); + unset($settings['plan_renewal_error'], $settings['enterprise_billed_branches']); $organization->update(['settings' => $settings]); } } diff --git a/app/Services/Frontdesk/PlanService.php b/app/Services/Frontdesk/PlanService.php index ae64c25..31bf6ff 100644 --- a/app/Services/Frontdesk/PlanService.php +++ b/app/Services/Frontdesk/PlanService.php @@ -2,6 +2,7 @@ namespace App\Services\Frontdesk; +use App\Models\Branch; use App\Models\Device; use App\Models\Organization; use Carbon\Carbon; @@ -32,10 +33,9 @@ class PlanService return $this->planKey($organization) === 'enterprise'; } - /** Self-serve paid plans only — enterprise is sales-led. */ public function hasPaidPlan(Organization $organization): bool { - return $this->planKey($organization) === 'pro'; + return in_array($this->planKey($organization), ['pro', 'enterprise'], true); } /** @return array */ @@ -49,7 +49,7 @@ class PlanService ]; } - /** Null means unlimited included host emails (Pro). */ + /** Null means unlimited included host emails (Pro / Enterprise). */ public function freeEmailsPerMonth(Organization $organization): ?int { $value = config('frontdesk.plans.'.$this->planKey($organization).'.free_emails_per_month', 100); @@ -57,6 +57,14 @@ class PlanService return $value === null ? null : (int) $value; } + public function activeBranchCount(Organization $organization): int + { + return Branch::query() + ->where('organization_id', $organization->id) + ->where('is_active', true) + ->count(); + } + public function canAddBranch(Organization $organization, int $currentCount): bool { $limit = config('frontdesk.plans.'.$this->planKey($organization).'.max_branches'); @@ -86,8 +94,59 @@ class PlanService return in_array($feature, $features, true); } + public function proPricePerBranchMinor(): int + { + return (int) config( + 'frontdesk.plans.pro.price_minor_per_branch', + config('frontdesk.plans.pro.price_minor', 99000), + ); + } + + /** + * Per-branch rate (legacy callers treated this as a flat monthly fee). + * + * @deprecated Prefer proPricePerBranchMinor() / priceForBranches(). + */ public function proPriceMinor(): int { - return (int) config('frontdesk.plans.pro.price_minor', 7900); + return $this->proPricePerBranchMinor(); + } + + public function enterprisePricePerBranchMinor(): int + { + return (int) config('frontdesk.plans.enterprise.price_minor_per_branch', 199000); + } + + public function proPriceTotalMinor(Organization $organization): int + { + return $this->priceForBranches('pro', $this->activeBranchCount($organization)); + } + + public function enterprisePriceMinor(Organization $organization): int + { + return $this->priceForBranches('enterprise', $this->activeBranchCount($organization)); + } + + public function priceForBranches(string $plan, int $branches): int + { + $branches = max(1, $branches); + $rate = $plan === 'enterprise' + ? $this->enterprisePricePerBranchMinor() + : $this->proPricePerBranchMinor(); + + return $branches * $rate; + } + + public function resolveCheckoutBranches(Organization $organization, int $requested): int + { + $branches = max(1, $requested); + $active = max(1, $this->activeBranchCount($organization)); + + return max($branches, $active); + } + + public function canSubscribeEnterprise(Organization $organization): bool + { + return $this->activeBranchCount($organization) >= (int) config('frontdesk.enterprise.min_branches', 1); } } diff --git a/app/Services/Frontdesk/ProRenewalService.php b/app/Services/Frontdesk/ProRenewalService.php index ce326fe..b403890 100644 --- a/app/Services/Frontdesk/ProRenewalService.php +++ b/app/Services/Frontdesk/ProRenewalService.php @@ -18,7 +18,7 @@ class ProRenewalService public function dueOrganizations(): Collection { return Organization::query() - ->where('settings->plan', 'pro') + ->whereIn('settings->plan', ['pro', 'enterprise']) ->whereNotNull('settings->plan_expires_at') ->get() ->filter(fn (Organization $organization) => $this->isDue($organization)); @@ -27,7 +27,7 @@ class ProRenewalService public function isDue(Organization $organization): bool { $settings = $organization->settings ?? []; - if (($settings['plan'] ?? '') !== 'pro') { + if (! in_array($settings['plan'] ?? '', ['pro', 'enterprise'], true)) { return false; } if (array_key_exists('auto_renew', $settings) && $settings['auto_renew'] === false) { @@ -47,24 +47,32 @@ class ProRenewalService } $settings = $organization->settings ?? []; - $price = $this->plans->proPriceMinor(); - $reference = 'frontdesk-pro-'.$organization->id.'-'.now()->format('YmdHis'); + $plan = (string) ($settings['plan'] ?? 'pro'); + $branches = max( + 1, + (int) ($settings['billed_branches'] ?? 0), + $this->plans->activeBranchCount($organization), + ); + $price = $this->plans->priceForBranches($plan, $branches); + $reference = 'frontdesk-'.$plan.'-'.$organization->id.'-'.now()->format('YmdHis'); + $label = $plan === 'enterprise' ? 'Enterprise' : 'Pro'; try { $charged = $this->billing->debit( $organization->owner_ref, $price, - 'frontdesk_pro_renewal', + 'frontdesk_'.$plan.'_renewal', $reference, - 'Ladill Frontdesk Pro — monthly renewal', + 'Ladill Frontdesk '.$label.' — '.$branches.' branch(es) monthly renewal', ); } catch (\Throwable) { $charged = false; } if ($charged) { - $settings['plan'] = 'pro'; + $settings['plan'] = $plan; $settings['auto_renew'] = true; + $settings['billed_branches'] = $branches; $settings['plan_expires_at'] = now() ->addDays((int) config('frontdesk.pro.period_days', 30)) ->toIso8601String(); @@ -78,7 +86,7 @@ class ProRenewalService $graceEnd = $expires->copy()->addDays((int) config('frontdesk.pro.grace_days', 3)); if ($graceEnd->isPast()) { $settings['plan'] = 'free'; - unset($settings['plan_expires_at']); + unset($settings['plan_expires_at'], $settings['billed_branches']); $settings['plan_renewal_error'] = 'Suspended after failed renewal.'; } else { $settings['plan_renewal_error'] = 'Renewal failed — top up your wallet.'; diff --git a/config/frontdesk.php b/config/frontdesk.php index 7390a05..e83c083 100644 --- a/config/frontdesk.php +++ b/config/frontdesk.php @@ -141,8 +141,15 @@ return [ ], 'pro' => [ 'label' => 'Pro', - // Aligned with Queue Pro (GHS 990/mo). - 'price_minor' => (int) env('FRONTDESK_PRO_PRICE_MINOR', 99000), + // GHS 990/branch/mo (legacy FRONTDESK_PRO_PRICE_MINOR still honored). + 'price_minor_per_branch' => (int) env( + 'FRONTDESK_PRO_PRICE_PER_BRANCH_MINOR', + env('FRONTDESK_PRO_PRICE_MINOR', 99000), + ), + 'price_minor' => (int) env( + 'FRONTDESK_PRO_PRICE_PER_BRANCH_MINOR', + env('FRONTDESK_PRO_PRICE_MINOR', 99000), + ), 'max_branches' => null, 'max_kiosk_devices' => null, 'free_emails_per_month' => env('FRONTDESK_PRO_FREE_EMAILS_PER_MONTH') !== null @@ -161,8 +168,8 @@ return [ ], 'enterprise' => [ 'label' => 'Enterprise', - // Aligned with Queue Enterprise (GHS 2990+/mo per branch). - 'price_minor_per_branch' => (int) env('FRONTDESK_ENTERPRISE_PRICE_PER_BRANCH_MINOR', 299000), + // GHS 1990/branch/mo. + 'price_minor_per_branch' => (int) env('FRONTDESK_ENTERPRISE_PRICE_PER_BRANCH_MINOR', 199000), 'max_branches' => null, 'max_kiosk_devices' => null, 'free_emails_per_month' => null, @@ -180,7 +187,7 @@ return [ ], 'enterprise' => [ - 'min_branches' => (int) env('FRONTDESK_ENTERPRISE_MIN_BRANCHES', 2), + 'min_branches' => (int) env('FRONTDESK_ENTERPRISE_MIN_BRANCHES', 1), ], 'prepaid_months' => [6, 12, 24], @@ -344,7 +351,7 @@ return [ 'upgrade_banner' => [ 'title' => 'Unlock Frontdesk Pro or Enterprise', - 'description' => 'Unlimited kiosks, webhooks, iCal sync & scheduled reports — from GHS 990/mo.', + 'description' => 'Unlimited kiosks, webhooks, iCal sync & scheduled reports — from GHS 990/branch/mo.', 'route' => 'frontdesk.pro.index', ], diff --git a/resources/views/frontdesk/integrations/upgrade.blade.php b/resources/views/frontdesk/integrations/upgrade.blade.php index 7f2215b..0b5a3b5 100644 --- a/resources/views/frontdesk/integrations/upgrade.blade.php +++ b/resources/views/frontdesk/integrations/upgrade.blade.php @@ -6,7 +6,7 @@

Upgrade to Pro

- GHS {{ number_format($proPriceMinor / 100, 0) }} / month from your Ladill wallet — unlimited branches and kiosks, integrations, scheduled reports, and more. + GHS {{ number_format($proPriceMinor / 100, 0) }} / branch / month from your Ladill wallet — unlimited kiosks, integrations, scheduled reports, and more.

View Pro plans

diff --git a/resources/views/frontdesk/pro/index.blade.php b/resources/views/frontdesk/pro/index.blade.php index b2e450e..0b6f885 100644 --- a/resources/views/frontdesk/pro/index.blade.php +++ b/resources/views/frontdesk/pro/index.blade.php @@ -1,9 +1,30 @@ @php - $proPrice = number_format($proPriceMinor / 100, 0); + $proPerBranch = number_format($proPricePerBranchMinor / 100, 0); + $enterprisePerBranch = number_format($enterprisePricePerBranchMinor / 100, 0); + $initialBranches = max(1, (int) $branchCount); @endphp -

+
@if (session('upsell'))
{{ session('upsell') }}
@endif @@ -17,10 +38,11 @@

Choose your Frontdesk plan

- Pay monthly from your Ladill wallet, or prepay 6/12/24 months via Paystack for Pro. - Enterprise is custom — contact sales for multi-site onboarding. + Pay monthly from your Ladill wallet, or prepay 6/12/24 months via Paystack. + Pro and Enterprise are billed per branch — set how many branches to include below. + Enterprise includes a one-time setup fee — contact sales for onboarding.

-
+
@@ -29,7 +51,20 @@ :class="billing === '{{ $months }}' ? 'bg-indigo-600 text-white' : 'bg-slate-100 text-slate-700 hover:bg-slate-200'" class="rounded-lg px-3 py-1.5 text-sm font-medium transition">{{ $months }} months · Paystack @endforeach + +
+

+ Minimum is your current active branch count ({{ $initialBranches }}). Totals update live for Pro and Enterprise. +

@@ -51,13 +86,22 @@ {{-- Pro --}}

Pro

- -

Unlimited branches & kiosks

+ +

+ branch(es) × {{ $currency }} {{ $proPerBranch }}/branch + = {{ $currency }} /mo +

  • Unlimited host email alerts
  • Webhooks & iCal feeds
  • Scheduled report exports
  • Custom badge templates
  • +
  • Unlimited kiosks (billed per branch)
@if ($planKey === 'pro') @@ -66,20 +110,27 @@ @if ($planExpiresAt) until {{ $planExpiresAt->format('d M Y') }} @endif + @if ($billedBranches > 0) + · billed for {{ $billedBranches }} {{ str('branch')->plural($billedBranches) }} + @endif .

SMS host alerts bill at platform rates from your wallet.

@elseif ($canManage)
@csrf - + +
@else @@ -91,8 +142,17 @@ {{-- Enterprise --}}

Enterprise

-

Custom

-

Multi-site · SLA · dedicated onboarding

+ +

+ branch(es) × {{ $currency }} {{ $enterprisePerBranch }}/branch + = {{ $currency }} /mo +

  • Everything in Pro
  • Custom integrations
  • @@ -106,16 +166,35 @@ @if ($planExpiresAt) until {{ $planExpiresAt->format('d M Y') }} @endif + @if ($billedBranches > 0) + · billed for {{ $billedBranches }} {{ str('branch')->plural($billedBranches) }} + @endif .

    @elseif ($canManage) - - Contact sales - -

    Includes one-time setup fee

    + @if ($canSubscribeEnterprise) +
    + @csrf + + +
    + + @else +

    Add an active branch to subscribe, or contact sales.

    + @endif + Contact sales for setup & SLA @else -

    Ask an admin to contact sales.

    +

    Ask an admin to upgrade.

    @endif
diff --git a/resources/views/frontdesk/settings/edit.blade.php b/resources/views/frontdesk/settings/edit.blade.php index d46691b..58845cd 100644 --- a/resources/views/frontdesk/settings/edit.blade.php +++ b/resources/views/frontdesk/settings/edit.blade.php @@ -9,13 +9,13 @@

@if ($isPro) - {{ $plan['label'] ?? 'Pro' }} — unlimited branches and kiosks, integrations, and unlimited host emails (SMS still billed per segment). + {{ $plan['label'] ?? 'Pro' }} — unlimited kiosks, integrations, and unlimited host emails, billed per branch (SMS still billed per segment). @else Free — one branch, one kiosk, core check-in and badges. Host alerts billed after {{ number_format($freeEmailAllowance) }} free emails per month. @endif

@if (! $isPro) - Upgrade to Pro (GHS {{ number_format($proPriceMinor / 100, 0) }}/mo) + Upgrade to Pro (GHS {{ number_format($proPriceMinor / 100, 0) }}/branch/mo) @endif
diff --git a/routes/web.php b/routes/web.php index 0199c72..82d71d9 100644 --- a/routes/web.php +++ b/routes/web.php @@ -178,6 +178,7 @@ Route::middleware(['auth', 'platform.session'])->group(function () { Route::get('/pro', [ProController::class, 'index'])->name('frontdesk.pro.index'); Route::post('/pro/subscribe', [ProController::class, 'subscribe'])->name('frontdesk.pro.subscribe'); + Route::post('/pro/subscribe-enterprise', [ProController::class, 'subscribeEnterprise'])->name('frontdesk.pro.subscribe-enterprise'); Route::post('/pro/subscribe-prepaid', [ProController::class, 'subscribePrepaid'])->name('frontdesk.pro.subscribe-prepaid'); Route::get('/pro/paystack/callback', [ProController::class, 'paystackCallback'])->name('frontdesk.pro.paystack.callback'); diff --git a/tests/Feature/FrontdeskProTest.php b/tests/Feature/FrontdeskProTest.php index c959abe..cbaf9ee 100644 --- a/tests/Feature/FrontdeskProTest.php +++ b/tests/Feature/FrontdeskProTest.php @@ -59,11 +59,11 @@ class FrontdeskProTest extends TestCase ->get(route('frontdesk.pro.index')) ->assertOk() ->assertSee('Choose your Frontdesk plan') - ->assertSee('GHS 79') - ->assertSee('Custom') - ->assertSee('Upgrade to Pro') + ->assertSee('GHS 990') + ->assertSee('/branch/mo') + ->assertSee('GHS 1,990') ->assertSee('Enterprise') - ->assertSee('Contact sales'); + ->assertSee('Branches'); } public function test_sidebar_shows_upgrade_to_pro_on_dashboard(): void @@ -84,13 +84,16 @@ class FrontdeskProTest extends TestCase ]); $this->actingAs($this->owner) - ->post(route('frontdesk.pro.subscribe')) + ->post(route('frontdesk.pro.subscribe'), [ + 'branches' => 1, + ]) ->assertRedirect(route('frontdesk.pro.index')) ->assertSessionHas('success'); $settings = $this->organization->fresh()->settings; $this->assertSame('pro', $settings['plan']); $this->assertSame('wallet_monthly', $settings['billing_method']); + $this->assertSame(1, (int) ($settings['billed_branches'] ?? 0)); } public function test_subscribe_prepaid_redirects_to_paystack(): void @@ -106,6 +109,7 @@ class FrontdeskProTest extends TestCase ->post(route('frontdesk.pro.subscribe-prepaid'), [ 'plan' => 'pro', 'months' => 12, + 'branches' => 1, ]) ->assertRedirect('https://checkout.paystack.com/test'); }