diff --git a/resources/views/app.blade.php b/resources/views/app.blade.php index 7284121..78b2430 100644 --- a/resources/views/app.blade.php +++ b/resources/views/app.blade.php @@ -13,7 +13,7 @@ - Checkout · {{ config('checkout.merchant') ?: config('app.name') }} + Checkout · {{ config('lunar.checkout.merchant') ?: config('app.name') }} {{-- Tab icon (CheckoutTheme::favicon). The checkout is its own document, so without this the tab shows the browser's blank globe. --}} diff --git a/spec/0008-checkout-ui-and-theming.md b/spec/0008-checkout-ui-and-theming.md index e32a7ea..2fdd014 100644 --- a/spec/0008-checkout-ui-and-theming.md +++ b/spec/0008-checkout-ui-and-theming.md @@ -201,7 +201,7 @@ from the current channel. Same seam, runtime-resolved. **Favicon and tab title (added in run 1 of the master test plan).** `CheckoutTheme::favicon` is a URL/path validated by the same chokepoint as `logo` and `stylesheet`, projected as `favicon` and rendered by the root view as ``. The tab title is -"Checkout · {merchant}" from `checkout.merchant`, falling back to the app name. Without +"Checkout · {merchant}" from `lunar.checkout.merchant`, falling back to the app name. Without either, the checkout's own document showed the browser's blank globe and a bare "Checkout". ### E. Three customisation tiers (light → heavy) diff --git a/src/Http/Controllers/CheckoutController.php b/src/Http/Controllers/CheckoutController.php index f6e0f5a..0bdf6ee 100644 --- a/src/Http/Controllers/CheckoutController.php +++ b/src/Http/Controllers/CheckoutController.php @@ -386,7 +386,7 @@ private function projectCheckout(CheckoutDriver $driver, CheckoutSessionModel $s return [ 'uuid' => $session->uuid, - 'merchant' => config('checkout.merchant') ?: config('app.name'), + 'merchant' => $this->merchant(), 'currency' => $snapshot->currencyCode, 'items' => array_map(fn (array $line): array => [ 'id' => $line['identifier'], @@ -451,6 +451,15 @@ private function projectCheckout(CheckoutDriver $driver, CheckoutSessionModel $s ]; } + /** + * The store name in the checkout header: the configured merchant, falling + * back to the application name. The root view's tab title reads the same. + */ + private function merchant(): string + { + return (string) (config('lunar.checkout.merchant') ?: config('app.name')); + } + /** * The session's write/navigation endpoints the checkout Vue app (and the * host-page express mount's JSON start response) drive it through. @@ -1243,7 +1252,7 @@ public function processing(CheckoutSessionModel $session, ReconcilesCheckoutSess return Inertia::render('Processing', [ 'pollUrl' => route('lunar.checkout.processing', $session->uuid), - 'merchant' => config('app.name', 'Store'), + 'merchant' => $this->merchant(), 'theme' => $theme->tokens(), 'branding' => $theme->branding(), 'stylesheet' => $theme->stylesheet(), diff --git a/tests/Feature/CheckoutRouteTest.php b/tests/Feature/CheckoutRouteTest.php index d67982b..dd486c7 100644 --- a/tests/Feature/CheckoutRouteTest.php +++ b/tests/Feature/CheckoutRouteTest.php @@ -98,7 +98,7 @@ function routeTestCart(): Cart }); it('projects the configured merchant name into the header prop', function () { - config(['checkout.merchant' => 'Edwardes Bros']); + config(['lunar.checkout.merchant' => 'Edwardes Bros']); // Needs lines: show() bounces a session whose cart is empty. $cart = CheckoutCart::orderable(); @@ -114,7 +114,7 @@ function routeTestCart(): Cart }); it('falls back to the app name when no merchant is configured', function () { - config(['checkout.merchant' => null, 'app.name' => 'Lunar Store']); + config(['lunar.checkout.merchant' => null, 'app.name' => 'Lunar Store']); // Needs lines: show() bounces a session whose cart is empty. $cart = CheckoutCart::orderable(); diff --git a/tests/Feature/CheckoutThemeBrandingTest.php b/tests/Feature/CheckoutThemeBrandingTest.php index 53cd285..e2a8e20 100644 --- a/tests/Feature/CheckoutThemeBrandingTest.php +++ b/tests/Feature/CheckoutThemeBrandingTest.php @@ -13,7 +13,7 @@ */ it('renders the theme favicon and the merchant name in the root view head', function () { app()->bind(CheckoutTheme::class, fn (): CheckoutTheme => CheckoutTheme::tender()->with(favicon: '/favicon.svg')); - config()->set('checkout.merchant', 'Edwardes Bros'); + config()->set('lunar.checkout.merchant', 'Edwardes Bros'); $cart = CheckoutCart::orderable(); $session = CheckoutCart::session($cart); diff --git a/tests/Feature/PaymentMethodAvailabilityTest.php b/tests/Feature/PaymentMethodAvailabilityTest.php index 27b204c..e9a1ba5 100644 --- a/tests/Feature/PaymentMethodAvailabilityTest.php +++ b/tests/Feature/PaymentMethodAvailabilityTest.php @@ -193,6 +193,23 @@ public function unavailableReason(Cart $cart): ?string expect($registry->unavailableReasons($large))->toBe([]); }); +it('renders the checkout before any payment method is registered', function () { + $cart = CheckoutCart::orderable(); + $session = CheckoutCart::session($cart); + + $this->get(route('lunar.checkout.show', $session->uuid), ['X-Inertia' => 'true']) + ->assertOk() + ->assertJsonCount(0, 'props.checkout.paymentMethods') + ->assertJsonPath('props.checkout.paymentUnavailable', []); + + $this->postJson(route('lunar.checkout.pay', $session->uuid), [ + 'fingerprint' => CheckoutCart::fingerprint($session), + 'payment_method' => 'offline', + ])->assertStatus(422); + + expect(Order::query()->count())->toBe(0); +}); + it('projects why the payment region is empty', function () { app(PaymentMethodRegistry::class)->add(MinimumSpendMethod::class); diff --git a/tests/Feature/PaymentReleaseTest.php b/tests/Feature/PaymentReleaseTest.php index 9ceaf27..640b969 100644 --- a/tests/Feature/PaymentReleaseTest.php +++ b/tests/Feature/PaymentReleaseTest.php @@ -221,6 +221,17 @@ function pinnedSession(array $overrides = []) expect($session->refresh()->status)->toBeInstanceOf(PaymentProcessing::class); }); +it('processing shows the same merchant name as the checkout', function () { + ReleaseTestGateway::$status = PaymentIntentStatus::Pending; + config(['lunar.checkout.merchant' => 'Edwardes Bros', 'app.name' => 'Lunar Store']); + + $session = pinnedSession(); + + $this->get(route('lunar.checkout.processing', $session->uuid), ['X-Inertia' => 'true']) + ->assertOk() + ->assertJsonPath('props.merchant', 'Edwardes Bros'); +}); + it('processing redirects an already completed session straight to success', function () { $session = pinnedSession(); $session->forceFill(['status' => Completed::$name, 'success_url' => 'https://store.test/thanks'])->save();