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();