@@ -21,7 +21,7 @@ class EmailTokenContextBuilderTest extends TestCase
2121 protected function setUp (): void
2222 {
2323 parent ::setUp ();
24- $ this ->contextBuilder = new EmailTokenContextBuilder ( );
24+ $ this ->contextBuilder = app (EmailTokenContextBuilder::class );
2525 }
2626
2727 public function test_builds_order_confirmation_context (): void
@@ -101,6 +101,70 @@ public function test_builds_attendee_ticket_context(): void
101101 $ this ->assertEquals ('Great Organizer ' , $ context ['organizer ' ]['name ' ]);
102102 }
103103
104+ public function test_offline_payment_instructions_tokens_are_rendered_into_context (): void
105+ {
106+ $ order = $ this ->createMockOrder ();
107+ $ event = $ this ->createMockEvent ();
108+ $ organizer = $ this ->createMockOrganizer ();
109+ $ eventSettings = $ this ->createMockEventSettings (
110+ offlinePaymentInstructions: '<p>Use {{ order.number }} for {{ event.title }}</p> ' ,
111+ );
112+
113+ $ context = $ this ->contextBuilder ->buildOrderConfirmationContext (
114+ $ order ,
115+ $ event ,
116+ $ organizer ,
117+ $ eventSettings
118+ );
119+
120+ $ this ->assertSame (
121+ '<p>Use ORD-123456 for Amazing Event</p> ' ,
122+ $ context ['settings ' ]['offline_payment_instructions ' ],
123+ );
124+ }
125+
126+ public function test_rendered_offline_payment_instructions_are_purified (): void
127+ {
128+ $ order = $ this ->createMockOrder (firstName: '<script>alert("xss")</script>John ' );
129+ $ event = $ this ->createMockEvent ();
130+ $ organizer = $ this ->createMockOrganizer ();
131+ $ eventSettings = $ this ->createMockEventSettings (
132+ offlinePaymentInstructions: '<p>Reference {{ order.first_name }}</p> ' ,
133+ );
134+
135+ $ context = $ this ->contextBuilder ->buildOrderConfirmationContext (
136+ $ order ,
137+ $ event ,
138+ $ organizer ,
139+ $ eventSettings
140+ );
141+
142+ $ this ->assertStringNotContainsString ('<script> ' , $ context ['settings ' ]['offline_payment_instructions ' ]);
143+ $ this ->assertStringContainsString ('John ' , $ context ['settings ' ]['offline_payment_instructions ' ]);
144+ }
145+
146+ public function test_unrenderable_offline_payment_instructions_fall_back_to_raw_value (): void
147+ {
148+ $ order = $ this ->createMockOrder ();
149+ $ event = $ this ->createMockEvent ();
150+ $ organizer = $ this ->createMockOrganizer ();
151+ $ eventSettings = $ this ->createMockEventSettings (
152+ offlinePaymentInstructions: '<p>{% unknown_tag %}</p> ' ,
153+ );
154+
155+ $ context = $ this ->contextBuilder ->buildOrderConfirmationContext (
156+ $ order ,
157+ $ event ,
158+ $ organizer ,
159+ $ eventSettings
160+ );
161+
162+ $ this ->assertSame (
163+ '<p>{% unknown_tag %}</p> ' ,
164+ $ context ['settings ' ]['offline_payment_instructions ' ],
165+ );
166+ }
167+
104168 public function test_whitelists_only_allowed_tokens_for_order_confirmation (): void
105169 {
106170 $ order = $ this ->createMockOrder ();
@@ -155,7 +219,7 @@ public function test_whitelists_only_allowed_tokens_for_attendee_ticket(): void
155219 $ this ->assertArrayHasKey ('title ' , $ context ['event ' ]);
156220 }
157221
158- private function createMockOrder (): OrderDomainObject
222+ private function createMockOrder (string $ firstName = ' John ' ): OrderDomainObject
159223 {
160224 $ orderItem = Mockery::mock (OrderItemDomainObject::class, [
161225 'getProductPriceId ' => 123 ,
@@ -168,7 +232,7 @@ private function createMockOrder(): OrderDomainObject
168232 return Mockery::mock (OrderDomainObject::class, [
169233 'getPublicId ' => 'ORD-123456 ' ,
170234 'getTotalGross ' => 9999 ,
171- 'getFirstName ' => ' John ' ,
235+ 'getFirstName ' => $ firstName ,
172236 'getLastName ' => 'Doe ' ,
173237 'getEmail ' => 'john@example.com ' ,
174238 'getCreatedAt ' => '2024-01-15 10:30:00 ' ,
@@ -202,11 +266,11 @@ private function createMockOrganizer(): OrganizerDomainObject
202266 ]);
203267 }
204268
205- private function createMockEventSettings (): EventSettingDomainObject
269+ private function createMockEventSettings (string $ offlinePaymentInstructions = ' Pay by bank transfer ' ): EventSettingDomainObject
206270 {
207271 return Mockery::mock (EventSettingDomainObject::class, [
208272 'getSupportEmail ' => 'support@event.com ' ,
209- 'getOfflinePaymentInstructions ' => ' Pay by bank transfer ' ,
273+ 'getOfflinePaymentInstructions ' => $ offlinePaymentInstructions ,
210274 'getPostCheckoutMessage ' => 'Thank you for your purchase! ' ,
211275 'getLocationDetails ' => null ,
212276 ]);
0 commit comments