From 57d7a716bc51c41d664a05ad9fe263a3a3344277 Mon Sep 17 00:00:00 2001 From: Konstantinos Arvanitakis Date: Mon, 28 Sep 2026 23:54:24 +0300 Subject: [PATCH] Chore: Updates to Notifications --- src/Checkout/Services/CheckoutService.php | 17 ++++++++++++++++ src/Notification/NotificationRegistry.php | 11 ++++++++++ .../OrderCapturedNotification.php | 20 +++++++++++++++++++ .../OrderCompletedNotification.php | 1 + .../OrderDeliveredNotification.php | 1 + .../OrderDispatchedNotification.php | 1 + .../OrderPickupReadyNotification.php | 1 + .../Notifications/OrderPlacedNotification.php | 1 + .../OrderRefundedNotification.php | 1 + .../OrderStatusUpdatedNotification.php | 1 + src/Store/Services/StoreDetailsService.php | 20 +++++++++++++++++++ 11 files changed, 75 insertions(+) diff --git a/src/Checkout/Services/CheckoutService.php b/src/Checkout/Services/CheckoutService.php index 1d33b24..843a2b1 100644 --- a/src/Checkout/Services/CheckoutService.php +++ b/src/Checkout/Services/CheckoutService.php @@ -5,6 +5,7 @@ namespace Modules\Core\Checkout\Services; use Lunar\Exceptions\FingerprintMismatchException; use Lunar\Exceptions\Carts\CartException; use Illuminate\Support\Collection; +use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\Event; use Lunar\Base\Addressable; use Lunar\DataTypes\ShippingOption; @@ -431,6 +432,22 @@ class CheckoutService 'terms_accepted' => true, 'terms_accepted_at' => now()->toIso8601String(), 'terms_accepted_policy_version' => $policyVersion, + // Order.locale doesn't exist as a column — saved here so every + // order notification (OrderPaymentResolutionService/ + // MarkOrderPlacedOnDeferredPayment fire OrderPlaced synchronously + // in THIS request, but a later one — e.g. a Stripe 3-D Secure + // webhook, or a staff status change from Filament — has no + // request-scoped locale of its own) can render in the locale the + // shopper actually checked out in, not whatever locale (or none) + // happens to be active when the notification is sent. See + // NotificationRegistry::register(), which reads this back. + 'locale' => App::getLocale(), + // Lunar copies the discount onto Order.discount_breakdown but not + // the coupon CODE itself — that stays on the cart row, which may + // since have been cleared/reused for a new cart. Copied here so + // the confirmation email always shows what code THIS order was + // actually placed with. + 'coupon_code' => $cart->coupon_code, ]; $order->save(); diff --git a/src/Notification/NotificationRegistry.php b/src/Notification/NotificationRegistry.php index a73c4e9..040707f 100644 --- a/src/Notification/NotificationRegistry.php +++ b/src/Notification/NotificationRegistry.php @@ -44,6 +44,17 @@ class NotificationRegistry if (isset($event->delaySeconds) && $event->delaySeconds > 0) { $notification->delay($event->delaySeconds); } + // Every order-notification event carries ->order, whose + // ->meta['locale'] was saved at checkout (Checkout\ + // Services\CheckoutService::initiatePayment()) — the + // request that actually sends this may have no locale of + // its own (a payment webhook, a queued job, a staff + // action from Filament), so this is the only reliable + // source. Falls back to the app default for an order + // placed before this existed. + if (isset($event->order) && $locale = $event->order->meta['locale'] ?? null) { + $notification->locale($locale); + } $notification->notifiable()->notify($notification); } catch (Throwable $e) { report($e); diff --git a/src/Order/Notifications/OrderCapturedNotification.php b/src/Order/Notifications/OrderCapturedNotification.php index 0d2a444..353ee17 100644 --- a/src/Order/Notifications/OrderCapturedNotification.php +++ b/src/Order/Notifications/OrderCapturedNotification.php @@ -7,8 +7,23 @@ use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Support\Facades\Notification as NotificationFacade; use Modules\Core\Notification\BaseNotification; use Modules\Core\Order\Events\OrderCaptured; +use Modules\Core\Order\Services\OrderStatusFlow; use Modules\Core\Order\Support\OrderReferenceDisplay; +/** + * "Payment captured" is only a meaningful, distinct update for a bank + * transfer order — placing that order and paying for it are genuinely two + * separate moments (Order::paid stays false at checkout; a shopper is + * told their order is confirmed and awaiting a wire, then separately told + * once staff mark it paid). For every other payment method (card, + * authorize-then-capture, COD), placing the order already means the + * shopper did everything they need to on their end — a card payment is + * captured in the very same request that places the order, so a second + * "payment captured" email would just repeat what OrderPlacedNotification + * already said. See OrderStatusFlow's own docblock for why payment method + * never affects the order's status SEQUENCE, only this kind of business + * decision about which events actually matter to the shopper. + */ class OrderCapturedNotification extends BaseNotification { public function __construct(private readonly OrderCaptured $event) {} @@ -25,6 +40,10 @@ class OrderCapturedNotification extends BaseNotification public function via(object $notifiable): array { + if (! app(OrderStatusFlow::class)->isBankTransfer($this->event->order)) { + return []; + } + return ['mail']; } @@ -46,6 +65,7 @@ class OrderCapturedNotification extends BaseNotification return (new MailMessage) ->subject(__('Payment captured for your order :reference', ['reference' => $reference])) ->view('core::order.notifications.captured', [ + 'order' => $order, 'reference' => $reference, 'amount' => $this->event->transaction->amount->formatted, ]); diff --git a/src/Order/Notifications/OrderCompletedNotification.php b/src/Order/Notifications/OrderCompletedNotification.php index 7be46e0..02f94b8 100644 --- a/src/Order/Notifications/OrderCompletedNotification.php +++ b/src/Order/Notifications/OrderCompletedNotification.php @@ -46,6 +46,7 @@ class OrderCompletedNotification extends BaseNotification return (new MailMessage) ->subject(__('Your order :reference is complete', ['reference' => $reference])) ->view('core::order.notifications.completed', [ + 'order' => $order, 'reference' => $reference, ]); } diff --git a/src/Order/Notifications/OrderDeliveredNotification.php b/src/Order/Notifications/OrderDeliveredNotification.php index 84931e8..daf6ccf 100644 --- a/src/Order/Notifications/OrderDeliveredNotification.php +++ b/src/Order/Notifications/OrderDeliveredNotification.php @@ -46,6 +46,7 @@ class OrderDeliveredNotification extends BaseNotification return (new MailMessage) ->subject(__('Your order :reference has been delivered', ['reference' => $reference])) ->view('core::order.notifications.delivered', [ + 'order' => $order, 'reference' => $reference, ]); } diff --git a/src/Order/Notifications/OrderDispatchedNotification.php b/src/Order/Notifications/OrderDispatchedNotification.php index ac32ed8..fc58a8e 100644 --- a/src/Order/Notifications/OrderDispatchedNotification.php +++ b/src/Order/Notifications/OrderDispatchedNotification.php @@ -51,6 +51,7 @@ class OrderDispatchedNotification extends BaseNotification return (new MailMessage) ->subject(__('Your order :reference is on its way', ['reference' => $reference])) ->view('core::order.notifications.dispatched', [ + 'order' => $order, 'reference' => $reference, ]); } diff --git a/src/Order/Notifications/OrderPickupReadyNotification.php b/src/Order/Notifications/OrderPickupReadyNotification.php index b3b108b..f5aed5e 100644 --- a/src/Order/Notifications/OrderPickupReadyNotification.php +++ b/src/Order/Notifications/OrderPickupReadyNotification.php @@ -56,6 +56,7 @@ class OrderPickupReadyNotification extends BaseNotification return (new MailMessage) ->subject(__('Your order :reference is ready for pickup', ['reference' => $reference])) ->view('core::order.notifications.pickup-ready', [ + 'order' => $order, 'reference' => $reference, ]); } diff --git a/src/Order/Notifications/OrderPlacedNotification.php b/src/Order/Notifications/OrderPlacedNotification.php index c9d32ba..fef81d4 100644 --- a/src/Order/Notifications/OrderPlacedNotification.php +++ b/src/Order/Notifications/OrderPlacedNotification.php @@ -57,6 +57,7 @@ class OrderPlacedNotification extends BaseNotification return (new MailMessage) ->subject(__('Your order :reference is confirmed', ['reference' => $reference])) ->view('core::order.notifications.placed', [ + 'order' => $order, 'reference' => $reference, 'total' => $order->total->formatted, 'lines' => $order->lines, diff --git a/src/Order/Notifications/OrderRefundedNotification.php b/src/Order/Notifications/OrderRefundedNotification.php index d494e5f..c446d04 100644 --- a/src/Order/Notifications/OrderRefundedNotification.php +++ b/src/Order/Notifications/OrderRefundedNotification.php @@ -46,6 +46,7 @@ class OrderRefundedNotification extends BaseNotification return (new MailMessage) ->subject(__('A refund has been issued for your order :reference', ['reference' => $reference])) ->view('core::order.notifications.refunded', [ + 'order' => $order, 'reference' => $reference, 'amount' => $this->event->transaction->amount->formatted, ]); diff --git a/src/Order/Notifications/OrderStatusUpdatedNotification.php b/src/Order/Notifications/OrderStatusUpdatedNotification.php index ac7dcf9..d51a85d 100644 --- a/src/Order/Notifications/OrderStatusUpdatedNotification.php +++ b/src/Order/Notifications/OrderStatusUpdatedNotification.php @@ -58,6 +58,7 @@ class OrderStatusUpdatedNotification extends BaseNotification return (new MailMessage) ->subject(__('Your order :reference has been updated', ['reference' => $reference])) ->view('core::order.notifications.status-updated', [ + 'order' => $order, 'reference' => $reference, 'statusLabel' => config("lunar.orders.statuses.{$order->status}.label", $order->status), ]); diff --git a/src/Store/Services/StoreDetailsService.php b/src/Store/Services/StoreDetailsService.php index 8ff2e11..322fdb1 100644 --- a/src/Store/Services/StoreDetailsService.php +++ b/src/Store/Services/StoreDetailsService.php @@ -5,6 +5,8 @@ namespace Modules\Core\Store\Services; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Event; use Lunar\Models\Language; +use Lunar\Models\Order; +use Modules\Core\Order\Services\OrderStatusFlow; use Modules\Core\Store\Events\StoreDetailsUpdated; use Modules\Core\Store\Models\StoreDetails; @@ -35,6 +37,24 @@ class StoreDetailsService ); } + /** + * Null for any non-bank-transfer order — the confirmation email only + * shows this block when there's actually a wire to send (see + * BankTransferPaymentDriver's own docblock for why a bank transfer + * order stays at 'awaiting_payment' until staff confirm the wire + * arrived). Null also when the store hasn't filled the field in for + * $locale, so the caller's own @if($bankTransferInstructions) guard + * covers both cases identically. + */ + public function bankTransferInstructionsFor(Order $order, string $locale): ?string + { + if (! app(OrderStatusFlow::class)->isBankTransfer($order)) { + return null; + } + + return $this->current()->translate('bank_transfer_instructions', $locale) ?: null; + } + public function update(array $attributes): StoreDetails { $storeDetails = $this->firstOrCreate();