diff --git a/backend/app/Resources/Event/EventResourcePublic.php b/backend/app/Resources/Event/EventResourcePublic.php index da969e58f5..d505aa830d 100644 --- a/backend/app/Resources/Event/EventResourcePublic.php +++ b/backend/app/Resources/Event/EventResourcePublic.php @@ -3,6 +3,7 @@ namespace HiEvents\Resources\Event; use HiEvents\DomainObjects\EventDomainObject; +use HiEvents\DomainObjects\OrderDomainObject; use HiEvents\Resources\BaseResource; use HiEvents\Resources\Image\ImageResource; use HiEvents\Resources\Organizer\OrganizerResourcePublic; @@ -17,9 +18,12 @@ class EventResourcePublic extends BaseResource { private readonly bool $includePostCheckoutData; + private readonly ?OrderDomainObject $orderContext; + public function __construct( mixed $resource, mixed $includePostCheckoutData = false, + ?OrderDomainObject $orderContext = null, ) { // This is a hacky workaround to handle when this resource is instantiated @@ -28,6 +32,7 @@ public function __construct( $this->includePostCheckoutData = is_bool($includePostCheckoutData) ? $includePostCheckoutData : false; + $this->orderContext = $orderContext; parent::__construct($resource); } @@ -56,7 +61,9 @@ public function toArray(Request $request): array condition: !is_null($this->getEventSettings()), value: fn() => new EventSettingsResourcePublic( $this->getEventSettings(), - $this->includePostCheckoutData + $this->includePostCheckoutData, + $this->resource instanceof EventDomainObject ? $this->resource : null, + $this->orderContext, ), ), // @TODO - public question resource diff --git a/backend/app/Resources/Event/EventSettingsResourcePublic.php b/backend/app/Resources/Event/EventSettingsResourcePublic.php index 822d6a0cc3..c5144e6d45 100644 --- a/backend/app/Resources/Event/EventSettingsResourcePublic.php +++ b/backend/app/Resources/Event/EventSettingsResourcePublic.php @@ -2,8 +2,14 @@ namespace HiEvents\Resources\Event; +use HiEvents\DomainObjects\EventDomainObject; use HiEvents\DomainObjects\EventSettingDomainObject; +use HiEvents\DomainObjects\OrderDomainObject; +use HiEvents\Services\Domain\Email\EmailTokenContextBuilder; +use HiEvents\Services\Infrastructure\Email\LiquidTemplateRenderer; +use HiEvents\Services\Infrastructure\HtmlPurifier\HtmlPurifierService; use Illuminate\Http\Resources\Json\JsonResource; +use RuntimeException; /** * @mixin EventSettingDomainObject @@ -11,8 +17,10 @@ class EventSettingsResourcePublic extends JsonResource { public function __construct( - mixed $resource, - private readonly bool $includePostCheckoutData = false, + mixed $resource, + private readonly bool $includePostCheckoutData = false, + private readonly ?EventDomainObject $eventContext = null, + private readonly ?OrderDomainObject $orderContext = null, ) { parent::__construct($resource); @@ -67,7 +75,7 @@ public function toArray($request): array // Payment settings 'payment_providers' => $this->getPaymentProviders(), - 'offline_payment_instructions' => $this->getOfflinePaymentInstructions(), + 'offline_payment_instructions' => $this->getOfflinePaymentInstructionsForOrder(), 'allow_orders_awaiting_offline_payment_to_check_in' => $this->getAllowOrdersAwaitingOfflinePaymentToCheckIn(), // Invoice settings @@ -94,4 +102,28 @@ public function toArray($request): array 'waitlist_offer_timeout_minutes' => $this->getWaitlistOfferTimeoutMinutes(), ]; } + + private function getOfflinePaymentInstructionsForOrder(): ?string + { + $instructions = $this->getOfflinePaymentInstructions(); + $organizer = $this->eventContext?->getOrganizer(); + + if (!$instructions || !$this->eventContext || !$this->orderContext || !$organizer) { + return $instructions; + } + + try { + $context = app(EmailTokenContextBuilder::class)->buildOrderConfirmationContext( + order: $this->orderContext, + event: $this->eventContext, + organizer: $organizer, + eventSettings: $this->resource, + ); + $rendered = app(LiquidTemplateRenderer::class)->render($instructions, $context); + + return app(HtmlPurifierService::class)->purify($rendered); + } catch (RuntimeException) { + return $instructions; + } + } } diff --git a/backend/app/Resources/Order/OrderResourcePublic.php b/backend/app/Resources/Order/OrderResourcePublic.php index 7880d22ed5..f6973c7fdf 100644 --- a/backend/app/Resources/Order/OrderResourcePublic.php +++ b/backend/app/Resources/Order/OrderResourcePublic.php @@ -45,6 +45,7 @@ public function toArray(Request $request): array fn() => new EventResourcePublic( resource: $this->getEvent(), includePostCheckoutData: $this->getStatus() === OrderStatus::COMPLETED->name, + orderContext: $this->resource instanceof OrderDomainObject ? $this->resource : null, ), ), 'latest_invoice' => $this->when( diff --git a/backend/tests/Unit/Resources/Event/EventSettingsResourcePublicTest.php b/backend/tests/Unit/Resources/Event/EventSettingsResourcePublicTest.php index 96fa0dccaa..4257f9715e 100644 --- a/backend/tests/Unit/Resources/Event/EventSettingsResourcePublicTest.php +++ b/backend/tests/Unit/Resources/Event/EventSettingsResourcePublicTest.php @@ -2,7 +2,13 @@ namespace Tests\Unit\Resources\Event; +use HiEvents\DomainObjects\Enums\PaymentProviders; +use HiEvents\DomainObjects\EventDomainObject; use HiEvents\DomainObjects\EventSettingDomainObject; +use HiEvents\DomainObjects\OrderDomainObject; +use HiEvents\DomainObjects\OrganizerDomainObject; +use HiEvents\DomainObjects\Status\OrderPaymentStatus; +use HiEvents\DomainObjects\Status\OrderStatus; use HiEvents\Resources\Event\EventSettingsResourcePublic; use Illuminate\Http\Request; use Tests\TestCase; @@ -31,4 +37,79 @@ public function test_public_resource_exposes_allow_copy_details_when_disabled(): $this->assertFalse($resource['allow_copy_details_to_all_attendees']); } + + public function test_offline_payment_instructions_render_order_tokens(): void + { + $resource = $this->makeResource( + '
Use {{ order.number }} for {{ event.title }}
', + orderFirstName: 'Jane', + ); + + $data = $resource->toArray(Request::create('/')); + + $this->assertSame( + 'Use ORD-12345 for Summer Session
', + $data['offline_payment_instructions'], + ); + } + + public function test_rendered_offline_payment_instructions_are_purified(): void + { + $resource = $this->makeResource( + 'Reference {{ order.first_name }}
', + orderFirstName: 'Jane', + ); + + $data = $resource->toArray(Request::create('/')); + + $this->assertStringNotContainsString('