From 8ec421a6ace73b6e490f891d91d1baf0f5c33f7a Mon Sep 17 00:00:00 2001 From: Dave Earley Date: Mon, 26 Jan 2026 21:39:30 +0000 Subject: [PATCH] Feature: Invoice design improvements --- .../PaymentIntentSucceededHandler.php | 33 ++- backend/resources/views/invoice.blade.php | 265 ++++++++++++------ 2 files changed, 205 insertions(+), 93 deletions(-) diff --git a/backend/app/Services/Domain/Payment/Stripe/EventHandlers/PaymentIntentSucceededHandler.php b/backend/app/Services/Domain/Payment/Stripe/EventHandlers/PaymentIntentSucceededHandler.php index 43e7d7fb96..c9e608d56b 100644 --- a/backend/app/Services/Domain/Payment/Stripe/EventHandlers/PaymentIntentSucceededHandler.php +++ b/backend/app/Services/Domain/Payment/Stripe/EventHandlers/PaymentIntentSucceededHandler.php @@ -8,6 +8,8 @@ use Brick\Money\Exception\UnknownCurrencyException; use Carbon\Carbon; use HiEvents\DomainObjects\Enums\PaymentProviders; +use HiEvents\DomainObjects\EventSettingDomainObject; +use HiEvents\DomainObjects\Generated\EventSettingDomainObjectAbstract; use HiEvents\DomainObjects\Generated\OrderDomainObjectAbstract; use HiEvents\DomainObjects\Generated\StripePaymentDomainObjectAbstract; use HiEvents\DomainObjects\OrderDomainObject; @@ -23,6 +25,7 @@ use HiEvents\Repository\Eloquent\Value\Relationship; use HiEvents\Repository\Interfaces\AffiliateRepositoryInterface; use HiEvents\Repository\Interfaces\AttendeeRepositoryInterface; +use HiEvents\Repository\Interfaces\EventSettingsRepositoryInterface; use HiEvents\Repository\Interfaces\OrderRepositoryInterface; use HiEvents\Services\Domain\Order\OrderApplicationFeeService; use HiEvents\Services\Domain\Payment\Stripe\StripeRefundExpiredOrderService; @@ -40,17 +43,18 @@ class PaymentIntentSucceededHandler { public function __construct( - private readonly OrderRepositoryInterface $orderRepository, - private readonly StripePaymentsRepository $stripePaymentsRepository, - private readonly AffiliateRepositoryInterface $affiliateRepository, - private readonly ProductQuantityUpdateService $quantityUpdateService, - private readonly StripeRefundExpiredOrderService $refundExpiredOrderService, - private readonly AttendeeRepositoryInterface $attendeeRepository, - private readonly DatabaseManager $databaseManager, - private readonly LoggerInterface $logger, - private readonly Repository $cache, - private readonly DomainEventDispatcherService $domainEventDispatcherService, - private readonly OrderApplicationFeeService $orderApplicationFeeService, + private readonly OrderRepositoryInterface $orderRepository, + private readonly StripePaymentsRepository $stripePaymentsRepository, + private readonly AffiliateRepositoryInterface $affiliateRepository, + private readonly ProductQuantityUpdateService $quantityUpdateService, + private readonly StripeRefundExpiredOrderService $refundExpiredOrderService, + private readonly AttendeeRepositoryInterface $attendeeRepository, + private readonly DatabaseManager $databaseManager, + private readonly LoggerInterface $logger, + private readonly Repository $cache, + private readonly DomainEventDispatcherService $domainEventDispatcherService, + private readonly OrderApplicationFeeService $orderApplicationFeeService, + private readonly EventSettingsRepositoryInterface $eventSettingsRepository, ) { } @@ -94,7 +98,12 @@ public function handleEvent(PaymentIntent $paymentIntent): void $this->quantityUpdateService->updateQuantitiesFromOrder($updatedOrder); - OrderStatusChangedEvent::dispatch($updatedOrder); + /** @var EventSettingDomainObject $eventSettings */ + $eventSettings = $this->eventSettingsRepository->findFirstWhere([ + EventSettingDomainObjectAbstract::EVENT_ID => $updatedOrder->getEventId(), + ]); + + event(new OrderStatusChangedEvent($updatedOrder, createInvoice: $eventSettings->getEnableInvoicing())); $this->domainEventDispatcherService->dispatch( new OrderEvent( diff --git a/backend/resources/views/invoice.blade.php b/backend/resources/views/invoice.blade.php index 86aa461ad7..9db23e8990 100644 --- a/backend/resources/views/invoice.blade.php +++ b/backend/resources/views/invoice.blade.php @@ -1,11 +1,16 @@ @php use Carbon\Carbon; @endphp @php use HiEvents\Helper\Currency; @endphp +@php use HiEvents\DomainObjects\Status\InvoiceStatus; @endphp @php /** @var \HiEvents\DomainObjects\EventDomainObject $event */ @endphp @php /** @var \HiEvents\DomainObjects\EventSettingDomainObject $eventSettings */ @endphp @php /** @var \HiEvents\DomainObjects\OrderDomainObject $order */ @endphp @php /** @var \HiEvents\DomainObjects\InvoiceDomainObject $invoice */ @endphp +@php + $isPaid = $invoice->getStatus() === InvoiceStatus::PAID->name; + $isVoid = $invoice->getStatus() === InvoiceStatus::VOID->name; +@endphp - + @@ -21,40 +26,75 @@ body { font-family: 'DejaVu Sans', Arial, sans-serif; font-size: 12px; - line-height: 1.4; + line-height: 1.5; color: #1a1a1a; padding: 20px 30px; } - .header { - margin-bottom: 30px; - min-height: 100px; - display: block; + table.header-table { + width: 100%; + margin-bottom: 25px; + } + + table.header-table td { + vertical-align: top; } .logo-title { - font-size: 36px; - font-weight: normal; + font-family: 'DejaVu Sans', Arial, sans-serif; + font-size: 28px; + font-weight: 700; color: #1a1a1a; + margin: 0 0 2px 0; + letter-spacing: -0.5px; + } + + .header-event-name { + font-family: 'DejaVu Sans', Arial, sans-serif; + font-size: 12px; + color: #666; margin: 0; - float: left; - width: 50%; } .company-details { - float: right; text-align: right; line-height: 1.6; - width: 45%; + color: #555; } - .company-details > div { - margin-bottom: 3px; + .company-name { + font-weight: bold; + color: #1a1a1a; } - .invoice-info-container { - clear: both; - padding-top: 20px; + .status-badge { + display: inline-block; + padding: 4px 14px; + margin-top: 6px; + border-radius: 4px; + font-family: 'DejaVu Sans', Arial, sans-serif; + font-size: 11px; + font-weight: bold; + letter-spacing: 0.5px; + text-transform: uppercase; + } + + .status-paid { + background-color: #e6f9ee; + color: #1a7d42; + border: 1px solid #b8e6cc; + } + + .status-unpaid { + background-color: #fff3e0; + color: #b36b00; + border: 1px solid #ffe0b2; + } + + .status-void { + background-color: #f5f5f5; + color: #888; + border: 1px solid #ddd; } .invoice-info-grid { @@ -65,26 +105,30 @@ } .invoice-info-grid td { - padding: 10px; - width: 25%; + padding: 12px 14px; vertical-align: top; } .info-label { - color: #8a6bc0; - font-size: 12px; + font-family: 'DejaVu Sans', Arial, sans-serif; + color: #888; + font-size: 10px; font-weight: bold; display: block; margin-bottom: 4px; + text-transform: uppercase; + letter-spacing: 0.3px; } .info-value { + font-family: 'DejaVu Sans', Arial, sans-serif; font-size: 13px; + font-weight: 500; } .billing-section { margin-bottom: 20px; - background: #f8f8f8; + background: #f9f9fb; padding: 15px; border-radius: 4px; width: 48%; @@ -92,10 +136,17 @@ } .billing-title { - color: #8a6bc0; - font-size: 12px; + color: #888; + font-size: 10px; font-weight: bold; margin-bottom: 8px; + text-transform: uppercase; + letter-spacing: 0.3px; + } + + .billing-name { + font-weight: bold; + margin-bottom: 2px; } table.items { @@ -106,29 +157,31 @@ } .items th { - background: #8a6bc0; - color: white; + background: #f9f9fb; + color: #555; text-align: left; - padding: 12px 15px; + padding: 10px 15px; font-weight: bold; - font-size: 12px; - letter-spacing: 0.5px; + font-size: 10px; + letter-spacing: 0.3px; + text-transform: uppercase; + border-bottom: 2px solid #e6e6e6; } .items td { padding: 12px 15px; - border-bottom: 1px solid #e6e6e6; + border-bottom: 1px solid #f0f0f0; vertical-align: middle; } .item-description { - color: #666; + color: #888; font-size: 11px; margin-top: 4px; } .item-price-original { - color: #666; + color: #999; text-decoration: line-through; font-size: 11px; margin-bottom: 2px; @@ -141,39 +194,52 @@ .totals { width: 350px; margin-left: auto; - margin-top: 20px; + margin-top: 10px; } .totals td { - padding: 8px 10px; + padding: 6px 10px; text-align: right; } .total-line td { - border-top: 2px solid #8a6bc0; + border-top: 2px solid #1a1a1a; font-weight: bold; font-size: 14px; padding-top: 12px; - background: #f8f8f8; + } + + .amount-paid-line td { + color: #1a7d42; + font-size: 12px; + padding-top: 8px; + } + + .balance-due-line td { + border-top: 1px solid #e6e6e6; + font-weight: bold; + font-size: 14px; + padding-top: 10px; } .subtotal td { font-weight: bold; - padding-top: 15px; + padding-top: 12px; } .breakdown td { - color: #666; + color: #888; font-size: 11px; } .invoice-notes { margin: 30px 0; padding: 15px; - background-color: #f8f8f8; + background-color: #f9f9fb; border-radius: 4px; line-height: 1.6; clear: both; + color: #555; } .invoice-footer { @@ -183,17 +249,18 @@ text-align: center; line-height: 1.6; clear: both; + color: #888; + font-size: 11px; } .tax-info { - margin-top: 20px; - padding-top: 15px; + margin-top: 15px; + padding-top: 12px; border-top: 1px dashed #e6e6e6; font-size: 11px; - color: #666; + color: #888; } - /* Specific column widths for items table */ .col-description { width: 55%; } @@ -214,50 +281,75 @@ body { padding: 15px; } + + .status-paid { + background-color: #e6f9ee !important; + -webkit-print-color-adjust: exact; + print-color-adjust: exact; + } } -
-

{{ $eventSettings->getInvoiceLabel() ?? __('Invoice') }}

-
-
{{ $eventSettings->getOrganizationName() }}
-
{!! $eventSettings->getOrganizationAddress() !!}
- @if($eventSettings->getSupportEmail()) -
{{ $eventSettings->getSupportEmail() }}
- @endif -
-
-
- - - - - @if($invoice->getDueDate()) - +
- {{ __('Invoice Number') }} - #{{ $invoice->getInvoiceNumber() }} - - {{ __('Date Issued') }} - {{ Carbon::parse($order->getCreatedAt())->format('d/m/Y') }} - - {{ __('Due Date') }} - {{ Carbon::parse($invoice->getDueDate())->format('d/m/Y') }} -
+ + + + +
+

{{ $eventSettings->getInvoiceLabel() ?? __('Invoice') }}

+

{{ $event->getTitle() }}

+
+
{{ $eventSettings->getOrganizationName() }}
+
{!! $eventSettings->getOrganizationAddress() !!}
+ @if($eventSettings->getSupportEmail()) +
{{ $eventSettings->getSupportEmail() }}
@endif +
+ + + + + + @if(!$isPaid && $invoice->getDueDate()) - -
+ {{ __('Invoice Number') }} + #{{ $invoice->getInvoiceNumber() }} + + {{ __('Date Issued') }} + {{ Carbon::parse($invoice->getIssueDate())->format('d/m/Y') }} + - {{ __('Amount Due') }} - {{ Currency::format($order->getTotalGross(), $order->getCurrency()) }} + {{ __('Due Date') }} + {{ Carbon::parse($invoice->getDueDate())->format('d/m/Y') }}
-
+ @endif + + @if($isPaid) + {{ __('Amount Paid') }} + @else + {{ __('Amount Due') }} + @endif + {{ Currency::format($order->getTotalGross(), $order->getCurrency()) }} + + + {{ __('Status') }} + + @if($isPaid) + {{ __('Paid') }} + @elseif($isVoid) + {{ __('Void') }} + @else + {{ __('Unpaid') }} + @endif + + + +
{{ __('Billed To') }}
-
{{ $order->getFullName() }}
+
{{ $order->getFullName() }}
{{ $order->getEmail() }}
@if($order->getAddress())
{{ $order->getBillingAddressString() }}
@@ -267,10 +359,10 @@ class="info-value">{{ Carbon::parse($invoice->getDueDate())->format('d/m/Y') }}< - - - - + + + + @@ -364,9 +456,20 @@ class="item-price-discounted">{{ Currency::format($orderItem['total_before_addit @endif - + + + @if($isPaid) + + + + + + + + + @endif
{{ __('DESCRIPTION') }}{{ __('RATE') }}{{ __('QTY') }}{{ __('AMOUNT') }}{{ __('Description') }}{{ __('Rate') }}{{ __('Qty') }}{{ __('Amount') }}
{{ __('Total Amount') }}{{ __('Total') }} {{ Currency::format($order->getTotalGross(), $order->getCurrency()) }}
{{ __('Amount Paid') }}-{{ Currency::format($order->getTotalGross(), $order->getCurrency()) }}
{{ __('Balance Due') }}{{ Currency::format(0, $order->getCurrency()) }}
@if($eventSettings->getInvoiceNotes())