PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.17
Yatra – Travel Booking & Tour Operator Software v3.0.17
3.0.17 3.0.16 3.0.15 3.0.14 3.0.14.1 3.0.14.2 3.0.12 3.0.13 3.0.11 3.0.10 3.0.9 3.0.8 3.0.7 3.0.6 3.0.5 3.0.5.1 3.0.4 3.0.3 3.0.2.9 3.0.2.7 3.0.2.8 3.0.2.6 trunk 1.0.0 2.0.0 All 85 releases
← All changes | app/Services/EmailTemplateDefaults.php +367 -20 3.0.3 → 3.0.17 View file →
@@ -16,13 +16,15 @@
16 16 * @return array<string, string>
17 17 */
18 18 public static function settingsOptionDefaults(): array
19 19 {
20 - return [
20 + $defaults = [
21 21 'email_tpl_booking_subject' => '✈️ [{{site_name}}] Booking update · {{booking_reference}}',
22 22 'email_tpl_booking_body' => self::htmlBookingCustomer(),
23 23 'email_tpl_payment_subject' => '✅ [{{site_name}}] Payment received · {{booking_reference}}',
24 24 'email_tpl_payment_body' => self::htmlPaymentCustomer(),
25 + 'email_tpl_partial_payment_subject' => '💳 [{{site_name}}] Part payment received · {{booking_reference}}',
26 + 'email_tpl_partial_payment_body' => self::htmlPartialPaymentCustomer(),
25 27 'email_tpl_cancellation_subject' => '📋 [{{site_name}}] Booking cancelled · {{booking_reference}}',
26 28 'email_tpl_cancellation_body' => self::htmlCancellationCustomer(),
27 29 'email_tpl_reminder_subject' => '🗓️ [{{site_name}}] Your trip is coming up · {{booking_reference}}',
28 30 'email_tpl_reminder_body' => self::htmlReminderCustomer(),
@@ -35,8 +37,22 @@
35 37 'email_tpl_trip_consent_subject' => '📝 [{{site_name}}] Action required · {{form_name}}',
36 38 'email_tpl_trip_consent_body' => self::htmlTripConsentRequest(),
37 39 'email_tpl_customer_verification_subject' => '✉️ [{{site_name}}] Verify your email address',
38 40 'email_tpl_customer_verification_body' => self::htmlCustomerEmailVerification(),
41 + // Guest-checkout verification (distinct from account registration above).
42 + // Subject mirrors the render's hardcoded fallback; body reuses the
43 + // customer-verification HTML so the email is identical until an operator
44 + // customizes it. Registering these keys here is required so the settings
45 + // save accepts them (SettingsController whitelists by default_settings)
46 + // and the admin template editor pre-fills them.
47 + 'email_tpl_guest_verification_subject' => '✉️ [{{site_name}}] Verify your email to complete your booking',
48 + 'email_tpl_guest_verification_body' => self::htmlCustomerEmailVerification(),
49 + // Account email change: confirmation link to the NEW address, and a
50 + // security notice to the OLD address once the change is confirmed.
51 + 'email_tpl_account_email_change_subject' => '✉️ [{{site_name}}] Confirm your new email address',
52 + 'email_tpl_account_email_change_body' => self::htmlAccountEmailChangeRequest(),
53 + 'email_tpl_account_email_changed_subject' => '🔔 [{{site_name}}] Your email address was changed',
54 + 'email_tpl_account_email_changed_body' => self::htmlAccountEmailChanged(),
39 55 'email_tpl_booking_completed_subject' => '🌟 [{{site_name}}] Thanks for traveling · {{booking_reference}}',
40 56 'email_tpl_booking_completed_body' => self::htmlTripCompleted(),
41 57 'email_tpl_booking_expired_customer_subject' => '⏱️ [{{site_name}}] Booking expired · {{booking_reference}}',
42 58 'email_tpl_booking_expired_customer_body' => self::htmlBookingExpiredCustomer(),
@@ -57,11 +73,33 @@
57 73 'email_tpl_enquiry_response_subject' => '💬 [{{site_name}}] Re: your enquiry',
58 74 'email_tpl_enquiry_response_body' => self::htmlEnquiryResponse(),
59 75 'email_tpl_review_request_subject' => '⭐ [{{site_name}}] How was {{trip_name}}?',
60 76 'email_tpl_review_request_body' => self::htmlReviewRequest(),
61 - 'email_tpl_abandoned_booking_recovery_subject' => '🛒 [{{site_name}}] Complete your booking',
62 - 'email_tpl_abandoned_booking_recovery_body' => self::htmlAbandonedBookingRecovery(),
77 + 'email_tpl_abandoned_booking_recovery_first_subject' => '🛒 [{{site_name}}] Complete your booking',
78 + 'email_tpl_abandoned_booking_recovery_first_body' => self::htmlAbandonedBookingRecoveryFirst(),
79 + 'email_tpl_abandoned_booking_recovery_second_subject' => '⏳ [{{site_name}}] Still interested? Your booking is waiting',
80 + 'email_tpl_abandoned_booking_recovery_second_body' => self::htmlAbandonedBookingRecoverySecond(),
81 + 'email_tpl_abandoned_booking_recovery_final_subject' => '⚠️ [{{site_name}}] Final reminder: complete your booking',
82 + 'email_tpl_abandoned_booking_recovery_final_body' => self::htmlAbandonedBookingRecoveryFinal(),
63 83 ];
84 +
85 + // Every template also gets an opt-in Bcc / Cc option, derived from its
86 + // own subject key so the two lists can never drift apart (and so a
87 + // template type added later picks them up for free). Empty by default:
88 + // no address configured means no copy is sent and nothing changes for
89 + // existing sites. Registered here because this array doubles as the
90 + // whitelist of settings the REST API will persist.
91 + foreach (array_keys($defaults) as $key) {
92 + if (substr($key, -8) !== '_subject') {
93 + continue;
94 + }
95 +
96 + $base = substr($key, 0, -8);
97 + $defaults[$base . '_bcc'] = '';
98 + $defaults[$base . '_cc'] = '';
99 + }
100 +
101 + return $defaults;
64 102 }
65 103
66 104 /**
67 105 * Full subject + body for Pro system templates (DB). Null = no bundled default for that key.
@@ -94,8 +132,12 @@
94 132 'booking_cancelled' => [
95 133 'subject' => '📋 Booking cancelled · {{booking_reference}}',
96 134 'body' => self::htmlCancellationCustomer(),
97 135 ],
136 + 'admin_booking_cancelled' => [
137 + 'subject' => '📋 Booking cancelled · {{booking_reference}} (#{{booking_id}})',
138 + 'body' => self::htmlAdminBookingCancelled(),
139 + ],
98 140 'booking_completed' => [
99 141 'subject' => '🌟 Thanks for traveling with us · {{booking_reference}}',
100 142 'body' => self::htmlTripCompleted(),
101 143 ],
@@ -102,8 +144,16 @@
102 144 'payment_received' => [
103 145 'subject' => '✅ Payment received · {{booking_reference}}',
104 146 'body' => self::htmlPaymentCustomer(),
105 147 ],
148 + 'admin_payment_received' => [
149 + 'subject' => '✅ Payment received · {{booking_reference}} (#{{booking_id}})',
150 + 'body' => self::htmlAdminPaymentReceived(),
151 + ],
152 + 'partial_payment_received' => [
153 + 'subject' => '💳 Part payment received · {{booking_reference}}',
154 + 'body' => self::htmlPartialPaymentCustomer(),
155 + ],
106 156 'payment_reminder' => [
107 157 'subject' => '💳 Payment reminder · {{booking_reference}}',
108 158 'body' => self::htmlPaymentReminder(),
109 159 ],
@@ -134,8 +184,24 @@
134 184 'customer_email_verification' => [
135 185 'subject' => '✉️ [{{site_name}}] Verify your email address',
136 186 'body' => self::htmlCustomerEmailVerification(),
137 187 ],
188 + 'guest_email_verification' => [
189 + // Guest-checkout verification reuses the customer-verification
190 + // body — the flow is identical (click a magic link to prove
191 + // ownership) and the booking-specific copy is injected at
192 + // send-time via {{intro_paragraph}} / {{footer_note}}.
193 + 'subject' => '✉️ [{{site_name}}] Verify your email to complete your booking',
194 + 'body' => self::htmlCustomerEmailVerification(),
195 + ],
196 + 'account_email_change_request' => [
197 + 'subject' => '✉️ [{{site_name}}] Confirm your new email address',
198 + 'body' => self::htmlAccountEmailChangeRequest(),
199 + ],
200 + 'account_email_changed' => [
201 + 'subject' => '🔔 [{{site_name}}] Your email address was changed',
202 + 'body' => self::htmlAccountEmailChanged(),
203 + ],
138 204 'booking_expired_customer' => [
139 205 'subject' => '⏱️ [{{site_name}}] Booking expired · {{booking_reference}}',
140 206 'body' => self::htmlBookingExpiredCustomer(),
141 207 ],
@@ -158,12 +224,20 @@
158 224 'admin_scheduled_payment_failed' => [
159 225 'subject' => '⚠️ [{{site_name}}] Scheduled payment failed · {{booking_reference}}',
160 226 'body' => self::htmlAdminScheduledPaymentFailed(),
161 227 ],
162 - 'abandoned_booking_recovery' => [
228 + 'abandoned_booking_recovery_first' => [
163 229 'subject' => '🛒 [{{site_name}}] Complete your booking',
164 - 'body' => self::htmlAbandonedBookingRecovery(),
230 + 'body' => self::htmlAbandonedBookingRecoveryFirst(),
165 231 ],
232 + 'abandoned_booking_recovery_second' => [
233 + 'subject' => '⏳ [{{site_name}}] Still interested? Your booking is waiting',
234 + 'body' => self::htmlAbandonedBookingRecoverySecond(),
235 + ],
236 + 'abandoned_booking_recovery_final' => [
237 + 'subject' => '⚠️ [{{site_name}}] Final reminder: complete your booking',
238 + 'body' => self::htmlAbandonedBookingRecoveryFinal(),
239 + ],
166 240 ];
167 241
168 242 return $map[$templateKey] ?? null;
169 243 }
@@ -213,8 +287,9 @@
213 287 }));
214 288
215 289 $inner = '<p style="margin:0 0 16px;font-size:17px;color:#0f172a;">'
216 290 . sprintf(
291 + /* translators: %s: customer name */
217 292 esc_html__('Hello %s,', 'yatra'),
218 293 $name
219 294 )
220 295 . '</p>'
@@ -250,9 +325,13 @@
250 325 return $row['value'] !== '';
251 326 }));
252 327
253 328 $inner = '<p style="margin:0 0 16px;font-size:17px;color:#0f172a;">'
254 - . sprintf(esc_html__('Dear %s,', 'yatra'), $name)
329 + . sprintf(
330 + /* translators: %s: customer name */
331 + esc_html__('Dear %s,', 'yatra'),
332 + $name
333 + )
255 334 . '</p>'
256 335 . '<p style="margin:0 0 8px;color:#475569;">'
257 336 . esc_html__('We’ve received your payment — thank you! Here’s a quick summary.', 'yatra')
258 337 . '</p>'
@@ -267,10 +346,61 @@
267 346 );
268 347 }
269 348
270 349 /**
350 + * Part-payment received — a balance is still outstanding.
351 + *
352 + * Wording differs from the full-payment email on purpose: it confirms what
353 + * was received AND states what is still owed, which is the whole reason this
354 + * template is separate.
355 + *
271 356 * @param array<string, string> $v
272 357 */
358 + public static function fallbackTransactionalPartialPayment(array $v): string
359 + {
360 + // Null-coalescing rather than `?:` so this renders cleanly even when a
361 + // caller passes a sparse variable set — this fallback runs on sites that
362 + // upgraded before the template's body option was seeded.
363 + $name = esc_html(
364 + ($v['customer_first_name'] ?? '') ?: ($v['customer_name'] ?? '') ?: __('Customer', 'yatra')
365 + );
366 + $site = esc_html($v['site_name'] ?? get_bloginfo('name'));
367 + $rows = array_values(array_filter([
368 + ['label' => __('Booking reference', 'yatra'), 'value' => esc_html($v['booking_reference'] ?? '')],
369 + ['label' => __('Amount received', 'yatra'), 'value' => esc_html($v['payment_amount_formatted'] ?? '')],
370 + ['label' => __('Paid so far', 'yatra'), 'value' => esc_html($v['amount_paid_formatted'] ?? '')],
371 + ['label' => __('Balance remaining', 'yatra'), 'value' => esc_html($v['amount_due_formatted'] ?? '')],
372 + ['label' => __('Booking total', 'yatra'), 'value' => esc_html($v['total_amount_formatted'] ?? '')],
373 + ['label' => __('Method', 'yatra'), 'value' => esc_html($v['payment_method'] ?? '')],
374 + ['label' => __('Transaction ID', 'yatra'), 'value' => esc_html($v['transaction_id'] ?? '')],
375 + ], static function (array $row): bool {
376 + return $row['value'] !== '';
377 + }));
378 +
379 + $inner = '<p style="margin:0 0 16px;font-size:17px;color:#0f172a;">'
380 + . sprintf(
381 + /* translators: %s: customer name */
382 + esc_html__('Dear %s,', 'yatra'),
383 + $name
384 + )
385 + . '</p>'
386 + . '<p style="margin:0 0 8px;color:#475569;">'
387 + . esc_html__('Thank you — we’ve received your part payment. Your booking is not yet paid in full, so the remaining balance is shown below.', 'yatra')
388 + . '</p>'
389 + . EmailTemplateLayout::detailCard($rows);
390 +
391 + return EmailTemplateLayout::customer(
392 + '💳',
393 + __('Part payment received', 'yatra'),
394 + $inner,
395 + $site,
396 + __('Payment update', 'yatra')
397 + );
398 + }
399 +
400 + /**
401 + * @param array<string, string> $v
402 + */
273 403 public static function fallbackTransactionalCancellation(array $v): string
274 404 {
275 405 $name = esc_html($v['customer_first_name'] ?: $v['customer_name'] ?: __('Customer', 'yatra'));
276 406 $site = esc_html($v['site_name'] ?? get_bloginfo('name'));
@@ -282,9 +412,13 @@
282 412 return $row['value'] !== '';
283 413 }));
284 414
285 415 $inner = '<p style="margin:0 0 16px;font-size:17px;color:#0f172a;">'
286 - . sprintf(esc_html__('Hello %s,', 'yatra'), $name)
416 + . sprintf(
417 + /* translators: %s: customer name */
418 + esc_html__('Hello %s,', 'yatra'),
419 + $name
420 + )
287 421 . '</p>'
288 422 . '<p style="margin:0 0 20px;color:#475569;">'
289 423 . esc_html__('Your booking has been cancelled as requested. If you have questions, just reply to this email.', 'yatra')
290 424 . '</p>'
@@ -324,9 +458,13 @@
324 458 $rows = array_merge($rows, $balanceRow);
325 459 }
326 460
327 461 $inner = '<p style="margin:0 0 16px;font-size:17px;color:#0f172a;">'
328 - . sprintf(esc_html__('Dear %s,', 'yatra'), $name)
462 + . sprintf(
463 + /* translators: %s: customer name */
464 + esc_html__('Dear %s,', 'yatra'),
465 + $name
466 + )
329 467 . '</p>'
330 468 . '<p style="margin:0 0 20px;color:#475569;">'
331 469 . ($days !== ''
332 470 ? sprintf(
@@ -380,9 +518,13 @@
380 518 : '';
381 519
382 520 $inner = $testBlock
383 521 . '<p style="margin:0 0 16px;font-size:17px;color:#0f172a;">'
384 - . sprintf(esc_html__('Hello %s,', 'yatra'), $recipient)
522 + . sprintf(
523 + /* translators: %s: customer name */
524 + esc_html__('Hello %s,', 'yatra'),
525 + $recipient
526 + )
385 527 . '</p>'
386 528 . '<p style="margin:0 0 20px;color:#475569;">'
387 529 . sprintf(
388 530 /* translators: %s: consent form title */
@@ -434,9 +576,13 @@
434 576 . $expiryInner
435 577 . '</div>';
436 578
437 579 $inner = '<p style="margin:0 0 16px;font-size:17px;color:#0f172a;">'
438 - . sprintf(esc_html__('Hello %s,', 'yatra'), $first)
580 + . sprintf(
581 + /* translators: %s: customer name */
582 + esc_html__('Hello %s,', 'yatra'),
583 + $first
584 + )
439 585 . '</p>'
440 586 . ($intro !== '' ? '<p style="margin:0 0 20px;color:#475569;">' . $intro . '</p>' : '')
441 587 . EmailTemplateLayout::button($link, __('Verify email address', 'yatra'))
442 588 . $expiry
@@ -455,8 +601,90 @@
455 601 );
456 602 }
457 603
458 604 /**
605 + * Fallback HTML for the account email-change confirmation (empty saved template).
606 + *
607 + * @param array<string, string> $v
608 + */
609 + public static function fallbackTransactionalAccountEmailChangeRequest(array $v): string
610 + {
611 + $firstRaw = (string) ($v['customer_first_name'] ?? '');
612 + if ($firstRaw === '') {
613 + $firstRaw = (string) ($v['customer_name'] ?? '');
614 + }
615 + $first = esc_html($firstRaw !== '' ? $firstRaw : __('there', 'yatra'));
616 + $site = esc_html($v['site_name'] ?? get_bloginfo('name'));
617 + $link = esc_url((string) ($v['verification_link'] ?? home_url('/')));
618 + $newEmail = esc_html((string) ($v['new_email'] ?? ''));
619 + $intro = isset($v['intro_paragraph']) ? esc_html((string) $v['intro_paragraph']) : '';
620 + $footer = isset($v['footer_note']) ? esc_html((string) $v['footer_note']) : '';
621 +
622 + $newBlock = $newEmail !== ''
623 + ? '<div style="margin:0 0 20px;padding:12px 16px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:10px;font-size:14px;color:#475569;">'
624 + . esc_html__('New email address:', 'yatra') . ' <strong>' . $newEmail . '</strong></div>'
625 + : '';
626 +
627 + $inner = '<p style="margin:0 0 16px;font-size:17px;color:#0f172a;">'
628 + . sprintf(/* translators: %s: customer name */ esc_html__('Hello %s,', 'yatra'), $first)
629 + . '</p>'
630 + . ($intro !== '' ? '<p style="margin:0 0 20px;color:#475569;">' . $intro . '</p>' : '')
631 + . $newBlock
632 + . EmailTemplateLayout::button($link, __('Confirm email change', 'yatra'))
633 + . ($footer !== '' ? '<p style="margin:24px 0 0;font-size:14px;color:#64748b;">' . $footer . '</p>' : '')
634 + . '<p style="margin:20px 0 0;font-size:13px;color:#64748b;">'
635 + . esc_html__('If the button does not work, copy this link into your browser:', 'yatra')
636 + . '</p>'
637 + . '<p style="margin:8px 0 0;font-size:13px;word-break:break-all;"><a href="' . $link . '" style="color:#0d9488;">' . $link . '</a></p>';
638 +
639 + return EmailTemplateLayout::customer(
640 + '✉️',
641 + __('Confirm your new email', 'yatra'),
642 + $inner,
643 + $site,
644 + __('Account security', 'yatra')
645 + );
646 + }
647 +
648 + /**
649 + * Fallback HTML for the "email changed" security notice (empty saved template).
650 + *
651 + * @param array<string, string> $v
652 + */
653 + public static function fallbackTransactionalAccountEmailChanged(array $v): string
654 + {
655 + $firstRaw = (string) ($v['customer_first_name'] ?? '');
656 + if ($firstRaw === '') {
657 + $firstRaw = (string) ($v['customer_name'] ?? '');
658 + }
659 + $first = esc_html($firstRaw !== '' ? $firstRaw : __('there', 'yatra'));
660 + $site = esc_html($v['site_name'] ?? get_bloginfo('name'));
661 + $newEmail = esc_html((string) ($v['new_email'] ?? ''));
662 + $intro = isset($v['intro_paragraph']) ? esc_html((string) $v['intro_paragraph']) : '';
663 + $footer = isset($v['footer_note']) ? esc_html((string) $v['footer_note']) : '';
664 +
665 + $newBlock = $newEmail !== ''
666 + ? '<div style="margin:0 0 20px;padding:12px 16px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:10px;font-size:14px;color:#475569;">'
667 + . esc_html__('New email address:', 'yatra') . ' <strong>' . $newEmail . '</strong></div>'
668 + : '';
669 +
670 + $inner = '<p style="margin:0 0 16px;font-size:17px;color:#0f172a;">'
671 + . sprintf(/* translators: %s: customer name */ esc_html__('Hello %s,', 'yatra'), $first)
672 + . '</p>'
673 + . ($intro !== '' ? '<p style="margin:0 0 20px;color:#475569;">' . $intro . '</p>' : '')
674 + . $newBlock
675 + . ($footer !== '' ? '<p style="margin:24px 0 0;font-size:14px;color:#64748b;">' . $footer . '</p>' : '');
676 +
677 + return EmailTemplateLayout::customer(
678 + '🔔',
679 + __('Your email address was changed', 'yatra'),
680 + $inner,
681 + $site,
682 + __('Account security', 'yatra')
683 + );
684 + }
685 +
686 + /**
459 687 * Fallback HTML for admin new-booking notice (empty saved template).
460 688 *
461 689 * @param array<string, string> $v
462 690 */
@@ -548,9 +776,9 @@
548 776 ['label' => __('Phone', 'yatra'), 'value' => esc_html($v['customer_phone'] ?? '')],
549 777 ['label' => __('Trip', 'yatra'), 'value' => esc_html($v['trip_name'] ?? '')],
550 778 ['label' => __('Travel date', 'yatra'), 'value' => esc_html($v['travel_date'] ?? '')],
551 779 ['label' => __('Travelers', 'yatra'), 'value' => esc_html($v['travelers_count'] ?? '')],
552 - ['label' => __('Total', 'yatra'), 'value' => trim(esc_html($v['total_amount_formatted'] ?? '') . ' ' . esc_html($v['currency'] ?? ''))],
780 + ['label' => __('Total', 'yatra'), 'value' => esc_html($v['total_amount_formatted'] ?? '')],
553 781 ['label' => __('Booking status', 'yatra'), 'value' => esc_html($v['booking_status'] ?? '')],
554 782 ['label' => __('Payment status', 'yatra'), 'value' => esc_html($v['payment_status'] ?? '')],
555 783 ], static function (array $row): bool {
556 784 return trim((string) ($row['value'] ?? '')) !== '';
@@ -695,9 +923,9 @@
695 923 ['label' => 'Phone', 'value' => '{{customer_phone}}'],
696 924 ['label' => 'Trip', 'value' => '{{trip_name}}'],
697 925 ['label' => 'Travel date', 'value' => '{{travel_date}}'],
698 926 ['label' => 'Travelers', 'value' => '{{travelers_count}}'],
699 - ['label' => 'Total', 'value' => '{{total_amount_formatted}} {{currency}}'],
927 + ['label' => 'Total', 'value' => '{{total_amount_formatted}}'],
700 928 ['label' => 'Booking status', 'value' => '{{booking_status}}'],
701 929 ['label' => 'Payment status', 'value' => '{{payment_status}}'],
702 930 ])
703 931 . '<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="margin:28px 0 0;"><tr>'
@@ -738,8 +966,35 @@
738 966 'Payment received'
739 967 );
740 968 }
741 969
970 + /**
971 + * Editor pre-fill for the part-payment template ({{merge tags}} intact).
972 + */
973 + private static function htmlPartialPaymentCustomer(): string
974 + {
975 + $inner = '<p style="margin:0 0 16px;font-size:17px;color:#0f172a;">Dear {{customer_name}},</p>'
976 + . '<p style="margin:0 0 20px;color:#475569;">'
977 + . esc_html__('Thank you — we’ve received your part payment. The remaining balance for your booking is shown below.', 'yatra')
978 + . '</p>'
979 + . EmailTemplateLayout::detailCard([
980 + ['label' => 'Booking reference', 'value' => '{{booking_reference}}'],
981 + ['label' => 'Amount received', 'value' => '{{payment_amount_formatted}}'],
982 + ['label' => 'Paid so far', 'value' => '{{amount_paid_formatted}}'],
983 + ['label' => 'Balance remaining', 'value' => '{{amount_due_formatted}}'],
984 + ['label' => 'Booking total', 'value' => '{{total_amount_formatted}}'],
985 + ['label' => 'Method', 'value' => '{{payment_method}}'],
986 + ]);
987 +
988 + return EmailTemplateLayout::customer(
989 + '💳',
990 + 'Part payment received',
991 + $inner,
992 + '{{site_name}}',
993 + 'Payment update'
994 + );
995 + }
996 +
742 997 private static function htmlCancellationCustomer(): string
743 998 {
744 999 $inner = '<p style="margin:0 0 16px;font-size:17px;color:#0f172a;">Hello {{customer_name}},</p>'
745 1000 . '<p style="margin:0 0 20px;color:#475569;">'
@@ -871,8 +1126,55 @@
871 1126 __('Account verification', 'yatra')
872 1127 );
873 1128 }
874 1129
1130 + private static function htmlAccountEmailChangeRequest(): string
1131 + {
1132 + $cta = '<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="margin:28px 0;"><tr>'
1133 + . '<td style="border-radius:10px;background:#0d9488;">'
1134 + . '<a href="{{verification_link}}" style="display:inline-block;padding:14px 28px;font-family:\'Segoe UI\',Roboto,Arial,sans-serif;font-size:15px;font-weight:600;color:#ffffff;text-decoration:none;border-radius:10px;">'
1135 + . esc_html__('Confirm email change', 'yatra')
1136 + . '</a></td></tr></table>';
1137 +
1138 + $inner = '<p style="margin:0 0 16px;font-size:17px;color:#0f172a;">Hello {{customer_first_name}},</p>'
1139 + . '<p style="margin:0 0 20px;color:#475569;">{{intro_paragraph}}</p>'
1140 + . '<div style="margin:0 0 20px;padding:12px 16px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:10px;font-size:14px;color:#475569;">'
1141 + . esc_html__('New email address:', 'yatra') . ' <strong>{{new_email}}</strong>'
1142 + . '</div>'
1143 + . $cta
1144 + . '<p style="margin:24px 0 0;font-size:14px;color:#64748b;">{{footer_note}}</p>'
1145 + . '<p style="margin:20px 0 0;font-size:13px;color:#64748b;">'
1146 + . esc_html__('If the button does not work, copy this link into your browser:', 'yatra')
1147 + . '</p>'
1148 + . '<p style="margin:8px 0 0;font-size:13px;word-break:break-all;"><a href="{{verification_link}}" style="color:#0d9488;">{{verification_link}}</a></p>';
1149 +
1150 + return EmailTemplateLayout::customer(
1151 + '✉️',
1152 + __('Confirm your new email', 'yatra'),
1153 + $inner,
1154 + '{{site_name}}',
1155 + __('Account security', 'yatra')
1156 + );
1157 + }
1158 +
1159 + private static function htmlAccountEmailChanged(): string
1160 + {
1161 + $inner = '<p style="margin:0 0 16px;font-size:17px;color:#0f172a;">Hello {{customer_first_name}},</p>'
1162 + . '<p style="margin:0 0 20px;color:#475569;">{{intro_paragraph}}</p>'
1163 + . '<div style="margin:0 0 20px;padding:12px 16px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:10px;font-size:14px;color:#475569;">'
1164 + . esc_html__('New email address:', 'yatra') . ' <strong>{{new_email}}</strong>'
1165 + . '</div>'
1166 + . '<p style="margin:24px 0 0;font-size:14px;color:#64748b;">{{footer_note}}</p>';
1167 +
1168 + return EmailTemplateLayout::customer(
1169 + '🔔',
1170 + __('Your email address was changed', 'yatra'),
1171 + $inner,
1172 + '{{site_name}}',
1173 + __('Account security', 'yatra')
1174 + );
1175 + }
1176 +
875 1177 private static function htmlPaymentReminder(): string
876 1178 {
877 1179 $inner = '<p style="margin:0 0 16px;font-size:17px;color:#0f172a;">Hello {{customer_name}},</p>'
878 1180 . '<p style="margin:0 0 20px;color:#475569;">'
@@ -1041,20 +1343,31 @@
1041 1343 }
1042 1344
1043 1345 private static function htmlScheduledPaymentReminder(): string
1044 1346 {
1347 + // "Pay your balance now" CTA. The href is the {{balance_payment_url}}
1348 + // merge tag (a secure, guest-accessible balance link supplied by Pro
1349 + // Scheduled Payments); it is built manually rather than via
1350 + // EmailTemplateLayout::button() because that esc_url()s the href, which
1351 + // would strip the merge-tag braces. Pro passes an already-escaped URL.
1352 + $cta = '<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="margin:28px 0;"><tr>'
1353 + . '<td style="border-radius:10px;background:#0d9488;">'
1354 + . '<a href="{{balance_payment_url}}" style="display:inline-block;padding:14px 28px;font-family:\'Segoe UI\',Roboto,Arial,sans-serif;font-size:15px;font-weight:600;color:#ffffff;text-decoration:none;border-radius:10px;">'
1355 + . esc_html__('Pay your balance now', 'yatra') . '</a></td></tr></table>';
1356 +
1045 1357 $inner = '<p style="margin:0 0 16px;font-size:17px;color:#0f172a;">Hello {{customer_first_name}},</p>'
1046 1358 . '<p style="margin:0 0 20px;color:#475569;">'
1047 - . esc_html__('This is a reminder that a scheduled payment is coming up for your booking.', 'yatra')
1359 + . esc_html__('This is a reminder that the remaining balance for your booking is coming up.', 'yatra')
1048 1360 . '</p>'
1049 1361 . EmailTemplateLayout::detailCard([
1050 1362 ['label' => 'Reference', 'value' => '{{booking_reference}}'],
1051 1363 ['label' => 'Amount', 'value' => '{{scheduled_amount_formatted}}'],
1052 - ['label' => 'Scheduled date', 'value' => '{{scheduled_date_formatted}}'],
1364 + ['label' => 'Due date', 'value' => '{{scheduled_date_formatted}}'],
1053 1365 ['label' => 'Type', 'value' => '{{payment_type_label}}'],
1054 1366 ])
1367 + . $cta
1055 1368 . '<p style="margin:24px 0 0;font-size:14px;color:#64748b;">'
1056 - . esc_html__('If you need to update your payment method, contact us before the charge date.', 'yatra')
1369 + . esc_html__('If you have any questions about this payment, please contact us before the due date.', 'yatra')
1057 1370 . '</p>';
1058 1371
1059 1372 return EmailTemplateLayout::customer(
1060 1373 '💳',
@@ -1134,9 +1447,9 @@
1134 1447 '{{site_name}} · ' . esc_html__('Admin', 'yatra')
1135 1448 );
1136 1449 }
1137 1450
1138 - private static function htmlAbandonedBookingRecovery(): string
1451 + private static function htmlAbandonedBookingRecoveryBase(string $headline, string $subLabel): string
1139 1452 {
1140 1453 $inner = '<p style="margin:0 0 16px;font-size:17px;color:#0f172a;">Hello {{customer_name}},</p>'
1141 1454 . '<p style="margin:0 0 20px;color:#475569;">{{recovery_intro_html}}</p>'
1142 1455 . EmailTemplateLayout::detailCard([
@@ -1153,15 +1466,39 @@
1153 1466 . '</p>';
1154 1467
1155 1468 return EmailTemplateLayout::customer(
1156 1469 '🛒',
1157 - __('Complete your booking', 'yatra'),
1470 + $headline,
1158 1471 $inner,
1159 1472 '{{site_name}}',
1160 - __('Abandoned checkout', 'yatra')
1473 + $subLabel
1161 1474 );
1162 1475 }
1163 1476
1477 + private static function htmlAbandonedBookingRecoveryFirst(): string
1478 + {
1479 + return self::htmlAbandonedBookingRecoveryBase(
1480 + __('Complete your booking', 'yatra'),
1481 + __('Abandoned checkout · First reminder', 'yatra')
1482 + );
1483 + }
1484 +
1485 + private static function htmlAbandonedBookingRecoverySecond(): string
1486 + {
1487 + return self::htmlAbandonedBookingRecoveryBase(
1488 + __('Still interested?', 'yatra'),
1489 + __('Abandoned checkout · Second reminder', 'yatra')
1490 + );
1491 + }
1492 +
1493 + private static function htmlAbandonedBookingRecoveryFinal(): string
1494 + {
1495 + return self::htmlAbandonedBookingRecoveryBase(
1496 + __('Final reminder', 'yatra'),
1497 + __('Abandoned checkout · Final reminder', 'yatra')
1498 + );
1499 + }
1500 +
1164 1501 public static function fallbackTransactionalBookingCompleted(array $v): string
1165 1502 {
1166 1503 return TransactionalEmailTemplateService::parseMergeTags(self::htmlTripCompleted(), TransactionalEmailTemplateService::mergeTemplateVariables($v));
1167 1504 }
@@ -1215,11 +1552,21 @@
1215 1552 {
1216 1553 return TransactionalEmailTemplateService::parseMergeTags(self::htmlReviewRequest(), TransactionalEmailTemplateService::mergeTemplateVariables($v));
1217 1554 }
1218 1555
1219 - public static function fallbackTransactionalAbandonedBookingRecovery(array $v): string
1556 + public static function fallbackTransactionalAbandonedBookingRecoveryFirst(array $v): string
1220 1557 {
1221 - return TransactionalEmailTemplateService::parseMergeTags(self::htmlAbandonedBookingRecovery(), TransactionalEmailTemplateService::mergeTemplateVariables($v));
1558 + return TransactionalEmailTemplateService::parseMergeTags(self::htmlAbandonedBookingRecoveryFirst(), TransactionalEmailTemplateService::mergeTemplateVariables($v));
1559 + }
1560 +
1561 + public static function fallbackTransactionalAbandonedBookingRecoverySecond(array $v): string
1562 + {
1563 + return TransactionalEmailTemplateService::parseMergeTags(self::htmlAbandonedBookingRecoverySecond(), TransactionalEmailTemplateService::mergeTemplateVariables($v));
1564 + }
1565 +
1566 + public static function fallbackTransactionalAbandonedBookingRecoveryFinal(array $v): string
1567 + {
1568 + return TransactionalEmailTemplateService::parseMergeTags(self::htmlAbandonedBookingRecoveryFinal(), TransactionalEmailTemplateService::mergeTemplateVariables($v));
1222 1569 }
1223 1570
1224 1571 /**
1225 1572 * Merge-tag variables for enquiry templates (core wp_mail path when Pro does not own the channel).