PageSourceSearch

https://travel.historictownsofamerica.com/wp-content/plugins/yatra/assets/js/stripe.js?ver=3.0.15.1789496072

js historictownsofamerica.com collected 2026-09-25 00:02:28 UTC 52,993 bytes, 1,281 lines download raw bytes

1/**
2 * Stripe Payment Integration for Yatra
3 * Supports Stripe Elements (card) and Payment Request (Google Pay / Apple Pay)
4 */
5
6const __ = (window.wp && window.wp.i18n && window.wp.i18n.__) ? window.wp.i18n.__ : (text => text);
7
8class YatraStripe {
9    constructor(apiUrl, nonce) {
10        this.apiUrl = apiUrl;
11        this.nonce = nonce;
12        this.stripe = null;
13        this.elements = null;
14        this.cardElement = null;
15        this.paymentRequest = null;
16        this.paymentRequestButton = null;
17        this.paymentRequestMounted = false;
18        this.paymentForm = document.getElementById('yatra-booking-form');
19        this.submitButton = document.getElementById('yatra-submit-booking');
20        this.originalButtonHtml = this.submitButton ? this.submitButton.innerHTML : '';
21        this.methodButtons = {};
22        this.activeMethod = null;
23        this.cardWrapper = null;
24        this.walletWrapper = null;
25        this.walletCapabilities = {
26            google_pay: false,
27            apple_pay: false,
28        };
29        this.walletMethodHints = {
30            google_pay: '',
31            apple_pay: '',
32        };
33        this.walletAvailabilityKnown = false;
34        /** Short labels for each method switcher option (tooltip base copy). */
35        this.methodHelpText = {};
36
37        this.stripeSettings = window.yatraBookingData?.stripe || {};
38        let enabledMethods = this.stripeSettings.enabledMethods || ['card'];
39        if (typeof enabledMethods === 'string') {
40            enabledMethods = enabledMethods.split(',');
41        }
42        if (!Array.isArray(enabledMethods) || enabledMethods.length === 0) {
43            enabledMethods = ['card'];
44        }
45        this.enabledMethods = enabledMethods.map((method) => method.trim().toLowerCase()).filter(Boolean);
46        if (this.enabledMethods.length === 0) {
47            this.enabledMethods = ['card'];
48        }
49
50        this.supportsCard = this.enabledMethods.includes('card');
51        this.supportsPaymentRequest = this.enabledMethods.some((method) => method === 'google_pay' || method === 'apple_pay');
52
53        this.companyCountry = window.yatraBookingData?.companyCountry || 'US';
54        this.tripTitle = window.yatraBookingData?.tripTitle || 'Trip Booking';
55
56        window.yatraStripeInstance = this;
57
58        if (document.readyState === 'loading') {
59            document.addEventListener('DOMContentLoaded', () => this.init());
60        } else {
61            this.init();
62        }
63    }
64
65    init() {
66        if (!document.querySelector('input[name="payment_gateway"][value="stripe"]')) {
67            return;
68        }
69
70        this.addStripeElementsContainer();
71
72        document.querySelectorAll('input[name="payment_gateway"]').forEach((radio) => {
73            radio.addEventListener('change', (e) => this.handlePaymentMethodChange(e));
74        });
75
76        if (typeof Stripe !== 'undefined') {
77            this.initializeStripe();
78        }
79
80        // Listen for unified booking submit event (new system)
81        document.addEventListener('yatra_booking_submit', (event) => this.handleBookingSubmit(event));
82        
83        // Listen for payment response for client-side Stripe payments
84        document.addEventListener('yatra_payment_response', (event) => this.handlePaymentResponse(event));
85        
86        // Legacy event support
87        document.addEventListener('yatraBeforeBookingSubmit', (event) => this.handleGatewayIntercept(event));
88    }
89    
90    /**
91     * Handle unified booking submit event
92     */
93    handleBookingSubmit(event) {
94        if (event.detail?.gateway !== 'stripe') {
95            return; // Let other gateways handle
96        }
97        
98        // Stripe intercepts the form submission to handle card payment
99        event.preventDefault();
100        
101        // Use the existing handleGatewayIntercept logic
102        this.handleGatewayIntercept({
103            preventDefault: () => {},
104            detail: event.detail
105        });
106    }
107    
108    /**
109     * Handle payment response for Stripe (when server returns requires_action)
110     */
111    handlePaymentResponse(event) {
112        if (event.detail?.requires_action !== 'stripe_payment') {
113            return;
114        }
115        
116        
117        this.handleStripePaymentAction(event.detail);
118    }
119    
120    /**
121     * Handle Stripe payment action from server response
122     */
123    async handleStripePaymentAction(data) {
124        try {
125            // Load Stripe if not already loaded
126            if (!this.stripe && data.publishable_key) {
127                await this.loadStripeScript();
128                this.stripe = Stripe(data.publishable_key);
129            }
130            
131            if (!this.stripe) {
132                throw new Error('Stripe not initialized');
133            }
134            
135            const elements = this.stripe.elements({
136                clientSecret: data.client_secret
137            });
138            
139            // Create payment element in a modal
140            const modal = document.createElement('div');
141            modal.id = 'yatra-stripe-modal';
142            modal.innerHTML = `
143                <div style="position: fixed; inset: 0; background: rgba(0,0,0,0.5); z-index: 9999; display: flex; align-items: center; justify-content: center;">
144                    <div style="background: white; padding: 32px; border-radius: 12px; max-width: 500px; width: 90%; box-shadow: 0 4px 20px rgba(0,0,0,0.2);">
145                        <h2 style="margin: 0 0 24px; text-align: center;">${__('Complete Your Payment', 'yatra')}</h2>
146                        <div id="stripe-payment-element"></div>
147                        <button id="stripe-submit-btn" style="width: 100%; padding: 16px; background: #635bff; color: white; border: none; border-radius: 8px; font-size: 16px; font-weight: 600; margin-top: 24px; cursor: pointer;">
148                            ${__('Pay', 'yatra')} ${data.currency} ${parseFloat(data.amount).toFixed(2)}
149                        </button>
150                        <button id="stripe-cancel-btn" style="width: 100%; padding: 12px; background: transparent; color: #666; border: 1px solid #ddd; border-radius: 8px; font-size: 14px; margin-top: 12px; cursor: pointer;">
151                            ${__('Cancel', 'yatra')}
152                        </button>
153                        <div id="stripe-error-message" style="margin-top: 16px; color: #ef4444; display: none;"></div>
154                    </div>
155                </div>
156            `;
157            document.body.appendChild(modal);
158            
159            const paymentElement = elements.create('payment');
160            paymentElement.mount('#stripe-payment-element');
161            
162            document.getElementById('stripe-submit-btn').addEventListener('click', async () => {
163                const btn = document.getElementById('stripe-submit-btn');
164                btn.disabled = true;
165                btn.textContent = __('Processing...', 'yatra');
166                
167                // Append `stripe=success` to the gateway's return URL using proper URL
168                // composition. The naive `+ '?stripe=success'` form breaks when
169                // confirmation_url already carries query params (e.g. `?balance=paid`
170                // from a remaining-balance flow) — two question marks produces an
171                // invalid URL and browsers drop the second segment, losing the flag.
172                let confirmReturnUrl = data.confirmation_url;
173                try {
174                    const u = new URL(confirmReturnUrl, window.location.origin);
175                    u.searchParams.set('stripe', 'success');
176                    confirmReturnUrl = u.toString();
177                } catch (_e) {
178                    // Fallback for malformed bases (shouldn't happen in practice)
179                    const join = confirmReturnUrl.includes('?') ? '&' : '?';
180                    confirmReturnUrl = `${confirmReturnUrl}${join}stripe=success`;
181                }
182
183                const { error } = await this.stripe.confirmPayment({
184                    elements,
185                    confirmParams: {
186                        return_url: confirmReturnUrl,
187                    },
188                });
189                
190                if (error) {
191                    document.getElementById('stripe-error-message').textContent = error.message;
192                    document.getElementById('stripe-error-message').style.display = 'block';
193                    btn.disabled = false;
194                    btn.textContent = `${__('Pay', 'yatra')} ${data.currency} ${parseFloat(data.amount).toFixed(2)}`;
195                }
196            });
197            
198            document.getElementById('stripe-cancel-btn').addEventListener('click', () => {
199                modal.remove();
200                document.dispatchEvent(new CustomEvent('yatra_payment_cancelled', {
201                    detail: { gateway: 'stripe' }
202                }));
203            });
204            
205        } catch (error) {
206            console.error('Stripe payment error:', error);
207            document.dispatchEvent(new CustomEvent('yatra_payment_failed', {
208                detail: { gateway: 'stripe', error: error.message }
209            }));
210        }
211    }
212    
213    loadStripeScript() {
214        return new Promise((resolve, reject) => {
215            if (typeof Stripe !== 'undefined') {
216                resolve();
217                return;
218            }
219            const script = document.createElement('script');
220            script.src = 'https://js.stripe.com/v3/';
221            script.onload = resolve;
222            script.onerror = reject;
223            document.head.appendChild(script);
224        });
225    }
226
227    addStripeElementsContainer() {
228        const stripeOption = document.querySelector('input[name="payment_gateway"][value="stripe"]');
229        if (!stripeOption || document.querySelector('.yatra-stripe-container')) {
230            return;
231        }
232
233        const container = document.createElement('div');
234        container.className = 'yatra-stripe-container';
235        const stripePreselected = !!stripeOption.checked;
236        container.style.display = stripePreselected ? 'block' : 'none';
237
238        this.methodSwitcher = this.createMethodSwitcher();
239        // Show the "Payment methods" chooser only when there's an actual choice
240        // (Card + Apple/Google Pay). With Card as the only enabled method the
241        // chooser is redundant chrome, so present the card field directly.
242        if (this.hasMultipleMethods) {
243            container.appendChild(this.methodSwitcher);
244        } else {
245            container.classList.add('yatra-stripe-container--single-method');
246        }
247
248        if (this.supportsPaymentRequest) {
249            const requestWrapper = document.createElement('div');
250            requestWrapper.className = 'yatra-stripe-payment-request-wrapper';
251            const walletNote = window.yatraTranslations?.stripeWalletNote || __('Pay instantly with Apple Pay or Google Pay.', 'yatra');
252            const walletTitle = __('Apple Pay & Google Pay', 'yatra');
253            requestWrapper.innerHTML = `
254                <div class="yatra-wallet-header">
255                    <div>
256                        <p class="yatra-wallet-eyebrow">${__('One-tap checkout', 'yatra')}</p>
257                        <h4 class="yatra-wallet-title">${walletTitle}</h4>
258                    </div>
259                    <div class="yatra-wallet-badges">
260                        <span class="wallet-badge wallet-badge--apple"> Pay</span>
261                        <span class="wallet-badge wallet-badge--google">G Pay</span>
262                    </div>
263                </div>
264                <p class="yatra-wallet-desc">${walletNote}</p>
265                <div id="yatra-stripe-payment-request" class="yatra-stripe-payment-request placeholder"></div>
266                <div id="yatra-stripe-wallet-status" class="yatra-wallet-status">
267                    ${__('Checking your device for wallet support…', 'yatra')}
268                </div>
269            `;
270            container.appendChild(requestWrapper);
271            this.walletWrapper = requestWrapper;
272            this.walletWrapper.style.display = 'none';
273        }
274
275        if (this.supportsCard) {
276            const cardWrapper = document.createElement('div');
277            cardWrapper.className = 'yatra-stripe-card-wrapper';
278            cardWrapper.innerHTML = `
279                <div id="yatra-stripe-card-element" class="yatra-stripe-card-element"></div>
280                <div id="yatra-stripe-card-errors" class="yatra-stripe-errors" role="alert"></div>
281            `;
282            container.appendChild(cardWrapper);
283            this.cardWrapper = cardWrapper;
284            this.cardWrapper.style.display = 'none';
285        }
286
287        const gatewayExtra = document.getElementById('yatra-gateway-extra-stripe');
288        if (gatewayExtra) {
289            gatewayExtra.appendChild(container);
290        } else {
291            let parentElement = stripeOption.closest('.yatra-payment-option')
292                || stripeOption.closest('.payment_method_stripe')
293                || stripeOption.closest('.payment-method')
294                || stripeOption.parentElement;
295
296            if (parentElement) {
297                parentElement.appendChild(container);
298            } else {
299                const paymentOptions = document.querySelector('.yatra-payment-options, .payment-methods');
300                if (paymentOptions && paymentOptions.parentNode) {
301                    paymentOptions.parentNode.insertBefore(container, paymentOptions.nextSibling);
302                }
303            }
304        }
305
306        this.setActiveMethod(this.getDefaultMethod());
307        this.syncMethodButtonsState();
308
309        if (stripePreselected && !this.stripe) {
310            this.initializeStripe();
311        }
312    }
313
314    handlePaymentMethodChange(event) {
315        const selectedMethod = event.target.value;
316        const stripeContainer = document.querySelector('.yatra-stripe-container');
317
318        if (selectedMethod === 'stripe') {
319            if (!this.stripe) {
320                this.initializeStripe();
321            } else if (stripeContainer) {
322                stripeContainer.style.display = 'block';
323            }
324        } else if (stripeContainer) {
325            stripeContainer.style.display = 'none';
326        }
327    }
328
329    initializeStripe() {
330        const publishableKey = window.yatraBookingData?.stripe?.publishableKey
331            || window.yatraBookingData?.stripe?.publishable_key;
332        if (!publishableKey) {
333            this.displayError(__('Stripe publishable key is missing. Please contact support.', 'yatra'));
334            return;
335        }
336
337        if (typeof Stripe === 'undefined') {
338            const script = document.createElement('script');
339            script.src = 'https://js.stripe.com/v3/';
340            script.onload = () => this.setupStripeElements(publishableKey);
341            script.onerror = () => {
342                this.displayError(__('Failed to load Stripe.js. Please try again or choose another payment method.', 'yatra'));
343            };
344            document.head.appendChild(script);
345        } else {
346            this.setupStripeElements(publishableKey);
347        }
348    }
349
350    setupStripeElements(publishableKey) {
351        try {
352            this.stripe = Stripe(publishableKey, { apiVersion: '2023-10-16' });
353            this.elements = this.stripe.elements({
354                fonts: [{ cssSrc: 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap' }],
355                locale: 'auto'
356            });
357
358            if (this.supportsCard) {
359                const style = {
360                    base: {
361                        color: '#32325d',
362                        fontFamily: 'Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif',
363                        fontSmoothing: 'antialiased',
364                        fontSize: '16px',
365                        '::placeholder': { color: '#aab7c4' }
366                    },
367                    invalid: {
368                        color: '#e53935',
369                        iconColor: '#e53935'
370                    }
371                };
372
373                this.cardElement = this.elements.create('card', {
374                    style,
375                    hidePostalCode: true
376                });
377
378                const cardElement = document.getElementById('yatra-stripe-card-element');
379                if (cardElement) {
380                    this.cardElement.mount(cardElement);
381                    this.cardElement.on('change', (event) => this.displayError(event.error?.message || ''));
382                }
383            }
384
385            this.initializePaymentRequestButton();
386
387            const stripeContainer = document.querySelector('.yatra-stripe-container');
388            if (stripeContainer && document.querySelector('input[name="payment_gateway"]:checked')?.value === 'stripe') {
389                stripeContainer.style.display = 'block';
390            }
391        } catch (error) {
392            console.error('Error initializing Stripe:', error);
393            this.displayError(__('Failed to initialize payment processor. Please try again or use another payment method.', 'yatra'));
394        }
395    }
396
397    async handleGatewayIntercept(event) {
398        const selectedMethod = document.querySelector('input[name="payment_gateway"]:checked')?.value;
399        if (selectedMethod !== 'stripe') {
400            return true;
401        }
402
403        event.preventDefault();
404
405        if (this.activeMethod && this.activeMethod !== 'card') {
406            this.displayError(__('Please pay using the Apple Pay or Google Pay button above.', 'yatra'));
407            return false;
408        }
409
410        if (!this.supportsCard) {
411            this.displayError(__('Please use Apple Pay or Google Pay above to complete your payment.', 'yatra'));
412            return false;
413        }
414
415        const bookingData = event?.detail?.bookingData || this.collectBookingData();
416        if (!bookingData) {
417            this.displayError(__('Please complete the booking form before continuing.', 'yatra'));
418            return false;
419        }
420
421        if (!this.stripe || !this.cardElement) {
422            this.initializeStripe();
423            this.displayError(__('Stripe is still loading. Please wait a moment and try again.', 'yatra'));
424            return false;
425        }
426
427        if (!this.originalButtonHtml && event?.detail?.originalBtnHtml) {
428            this.originalButtonHtml = event.detail.originalBtnHtml;
429        } else if (!this.originalButtonHtml && this.submitButton) {
430            this.originalButtonHtml = this.submitButton.innerHTML;
431        }
432
433        this.displayError('');
434        this.setLoadingState(true);
435
436        try {
437            const paymentContext = await this.prepareStripePayment(bookingData, event?.detail?.form || this.paymentForm);
438            const { bookingInfo, clientSecret, billingDetails, effectiveAmount } = paymentContext;
439
440            const { error, paymentIntent } = await this.stripe.confirmCardPayment(clientSecret, {
441                payment_method: {
442                    card: this.cardElement,
443                    billing_details: billingDetails
444                },
445                return_url: this.buildReturnUrl(bookingInfo)
446            });
447
448            if (error) {
449                throw error;
450            }
451
452            if (paymentIntent.status === 'succeeded') {
453                await this.notifyPaymentSuccess({
454                    bookingId: bookingInfo.booking_id,
455                    transactionId: paymentIntent.id,
456                    saveCard: !!bookingData.save_card,
457                    amount: effectiveAmount,
458                    currency: bookingInfo.currency || bookingData.currency || 'USD'
459                });
460                window.location.href = this.buildConfirmationUrlFromBookingInfo(bookingInfo);
461            } else {
462                throw new Error(__('Payment processing was not completed. Please check your payment details and try again.', 'yatra'));
463            }
464        } catch (error) {
465            console.error('Stripe payment error:', error);
466            this.displayError(error.message || __('Payment failed. Please try again or use another payment method.', 'yatra'));
467            this.setLoadingState(false);
468        }
469
470        return false;
471    }
472
473    async initializePaymentRequestButton() {
474        if (!this.supportsPaymentRequest || !this.stripe || this.paymentRequestMounted) {
475            return;
476        }
477
478        try {
479            this.paymentRequest = this.stripe.paymentRequest({
480                country: (this.companyCountry || 'US').toUpperCase(),
481                currency: (window.yatraBookingData?.currency || 'USD').toLowerCase(),
482                total: this.getPaymentRequestTotal(),
483                requestPayerName: true,
484                requestPayerEmail: true,
485            });
486
487            const result = await this.paymentRequest.canMakePayment();
488            if (!result) {
489                this.setWalletStatus(
490                    __('Apple Pay and Google Pay are not available on this browser/device.', 'yatra'),
491                    'warning'
492                );
493                this.updateWalletCapabilities(null);
494                this.showWalletPlaceholder(__('Use card payment instead or switch to a supported device.', 'yatra'));
495                this.syncMethodButtonsState();
496                return;
497            }
498
499            this.updateWalletCapabilities(result === true ? { applePay: true, googlePay: true } : result);
500            this.setWalletStatus(
501                __('Great! Apple Pay or Google Pay is available on this device.', 'yatra'),
502                'success'
503            );
504            this.syncMethodButtonsState();
505
506            this.paymentRequest.on('paymentmethod', (event) => this.handlePaymentRequestPayment(event));
507
508            this.paymentRequestButton = this.elements.create('paymentRequestButton', {
509                paymentRequest: this.paymentRequest,
510                style: {
511                    paymentRequestButton: {
512                        type: 'default',
513                        theme: 'dark',
514                        height: '48px'
515                    }
516                }
517            });
518
519            const prContainer = document.getElementById('yatra-stripe-payment-request');
520            if (prContainer) {
521                this.paymentRequestButton.mount(prContainer);
522                this.paymentRequestMounted = true;
523                prContainer.classList.remove('placeholder');
524                const stripeContainer = document.querySelector('.yatra-stripe-container');
525                if (stripeContainer && document.querySelector('input[name="payment_gateway"]:checked')?.value === 'stripe') {
526                    stripeContainer.style.display = 'block';
527                }
528            }
529        } catch (error) {
530            console.error('Failed to initialize Stripe Payment Request button:', error);
531            this.setWalletStatus(
532                __('Unable to load Apple Pay / Google Pay. Please try again later or use card payment.', 'yatra'),
533                'error'
534            );
535            this.showWalletPlaceholder(__('Wallet payment failed to load.', 'yatra'));
536            this.updateWalletCapabilities(null);
537            const errHint = __('Wallet payments could not be initialized. Use your card below, or try again after refreshing the page.', 'yatra');
538            this.walletMethodHints.google_pay = errHint;
539            this.walletMethodHints.apple_pay = errHint;
540            this.syncMethodButtonsState();
541        }
542    }
543
544    setWalletStatus(message, variant = 'info') {
545        const statusEl = document.getElementById('yatra-stripe-wallet-status');
546        if (!statusEl) {
547            return;
548        }
549        statusEl.textContent = message;
550        statusEl.classList.remove('is-success', 'is-warning', 'is-error');
551        if (variant === 'success') {
552            statusEl.classList.add('is-success');
553        } else if (variant === 'warning') {
554            statusEl.classList.add('is-warning');
555        } else if (variant === 'error') {
556            statusEl.classList.add('is-error');
557        }
558    }
559
560    showWalletPlaceholder(message) {
561        const container = document.getElementById('yatra-stripe-payment-request');
562        if (!container) {
563            return;
564        }
565        container.classList.add('wallet-placeholder');
566        container.innerHTML = `<span>${message}</span>`;
567    }
568
569    updateWalletCapabilities(result) {
570        const availability = { apple_pay: false, google_pay: false };
571
572        if (result && typeof result === 'object') {
573            availability.apple_pay = !!(result.applePay || result.apple_pay);
574            availability.google_pay = !!(result.googlePay || result.google_pay);
575        } else if (result === true) {
576            availability.apple_pay = true;
577            availability.google_pay = true;
578        }
579
580        this.walletCapabilities = availability;
581
582        const hintGoogle = !availability.google_pay && this.enabledMethods.includes('google_pay')
583            ? __('Google Pay is not available in this browser. Try Chrome or Edge on desktop or Android, use HTTPS, and ensure you have a card in your Google account.', 'yatra')
584            : '';
585        const hintApple = !availability.apple_pay && this.enabledMethods.includes('apple_pay')
586            ? __('Apple Pay is not available here. Use Safari on iPhone, iPad, or Mac, serve this page over HTTPS, and add your domain in Stripe for Apple Pay.', 'yatra')
587            : '';
588
589        this.walletMethodHints = {
590            google_pay: hintGoogle,
591            apple_pay: hintApple,
592        };
593        this.walletAvailabilityKnown = true;
594    }
595
596    createMethodSwitcher() {
597        const wrapper = document.createElement('div');
598        wrapper.className = 'yatra-method-switcher';
599
600        const availableButtons = [];
601
602        const header = document.createElement('div');
603        header.className = 'yatra-method-switcher__header';
604
605        const eyebrow = document.createElement('p');
606        eyebrow.className = 'yatra-label-eyebrow';
607        eyebrow.textContent = __('Choose a payment option', 'yatra');
608
609        const title = document.createElement('div');
610        title.className = 'yatra-method-switcher__title';
611        title.textContent = __('Payment methods', 'yatra');
612
613        const helper = document.createElement('p');
614        helper.className = 'yatra-method-switcher__helper';
615        helper.textContent = __('Pick the option that works best for you.', 'yatra');
616
617        header.appendChild(eyebrow);
618        header.appendChild(title);
619        header.appendChild(helper);
620
621        if (this.supportsCard) {
622            availableButtons.push({
623                id: 'card',
624                label: __('Card', 'yatra'),
625                description: __('Use any major debit or credit card via Stripe Elements.', 'yatra'),
626                icon: this.getMethodIconMarkup('card')
627            });
628        }
629
630        if (this.enabledMethods.includes('google_pay')) {
631            availableButtons.push({
632                id: 'google_pay',
633                label: __('Google Pay', 'yatra'),
634                description: __('Pay instantly with the cards saved to your Google account.', 'yatra'),
635                icon: this.getMethodIconMarkup('google_pay')
636            });
637        }
638
639        if (this.enabledMethods.includes('apple_pay')) {
640            availableButtons.push({
641                id: 'apple_pay',
642                label: __('Apple Pay', 'yatra'),
643                description: __('Use Apple Wallet on iPhone, iPad, or Mac to pay securely.', 'yatra'),
644                icon: this.getMethodIconMarkup('apple_pay')
645            });
646        }
647
648        // Only a real choice (Card + a wallet) warrants the chooser. With Card
649        // as the only enabled method, the render step drops this switcher and
650        // shows the card field on its own (no redundant "Payment methods" row).
651        this.hasMultipleMethods = availableButtons.length > 1;
652
653        const buttons = document.createElement('div');
654        buttons.className = 'yatra-method-switcher__buttons';
655
656        availableButtons.forEach((method) => {
657            this.methodHelpText[method.id] = method.description;
658            const btn = document.createElement('button');
659            btn.type = 'button';
660            btn.className = 'yatra-method-button';
661            btn.dataset.method = method.id;
662            btn.innerHTML = `
663                ${method.icon}
664                <div class="method-content">
665                    <span class="method-label">${method.label}</span>
666                </div>
667                <span class="method-tooltip">
668                    <svg viewBox="0 0 20 20" aria-hidden="true" focusable="false">
669                        <circle cx="10" cy="10" r="9" stroke="currentColor" stroke-width="1.3" fill="none"></circle>
670                        <circle cx="10" cy="6" r="1.2" fill="currentColor"></circle>
671                        <path d="M10 9v6" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"></path>
672                    </svg>
673                </span>
674            `;
675            this.applyMethodTooltipText(btn.querySelector('.method-tooltip'), method.description);
676            btn.addEventListener('click', () => {
677                if (btn.getAttribute('aria-disabled') === 'true') {
678                    return;
679                }
680                this.setActiveMethod(method.id);
681            });
682            this.methodButtons[method.id] = btn;
683            buttons.appendChild(btn);
684        });
685
686        wrapper.appendChild(header);
687        wrapper.appendChild(buttons);
688        return wrapper;
689    }
690
691    getMethodIconMarkup(type) {
692        const base = '<span class="method-icon method-icon--' + type + '" aria-hidden="true">';
693        const close = '</span>';
694
695        const icons = {
696            card: `${base}
697                <svg viewBox="0 0 40 24" role="presentation" focusable="false">
698                    <rect x="1" y="1" width="38" height="22" rx="6" fill="#111827" stroke="rgba(255,255,255,0.2)" stroke-width="1" />
699                    <rect x="4" y="8" width="32" height="3" fill="#4b5563"></rect>
700                    <rect x="4" y="14" width="15" height="4" fill="#f3f4f6"></rect>
701                    <circle cx="28" cy="16" r="3.5" fill="#f97316"></circle>
702                    <circle cx="33" cy="16" r="3.5" fill="#facc15"></circle>
703                </svg>
704            ${close}`,
705            google_pay: `${base}
706                <svg viewBox="0 0 40 24" role="presentation" focusable="false">
707                    <g transform="translate(8 4)">
708                        <path fill="#ea4335" d="M6.4 7.6a3.6 3.6 0 0 1 3.4-3.6c1 0 1.7.4 2.2.8L14 3.8A6 6 0 0 0 9.8 2C6.5 2 3.7 4.7 3.7 8s2.8 6 6.1 6c1.8 0 3.3-.6 4.3-1.8 1.1-1.3 1.4-3.1 1.2-4.6H9.8v2.5h3.6c-.2.9-.7 1.7-1.5 2.3-.7.6-1.7.9-2.8.9-2.3 0-4.2-1.9-4.2-4.2z"></path>
709                        <path fill="#4285f4" d="M20.5 4.8a2.8 2.8 0 0 1 2.6 1.6V5H25v9.2h-1.9V9.6a1.6 1.6 0 0 0-1.7-1.7c-1.1 0-1.7.7-1.7 1.9v4.4h-1.9V5h1.9v1.3c.4-.8 1.3-1.5 2.8-1.5z"></path>
710                        <path fill="#34a853" d="M16.7 9.2c0-1.1-.9-2-2-2s-2 .9-2 2 .9 2 2 2 2-.9 2-2zm3.9 0c0 3-2.3 5.2-5.9 5.2a5.9 5.9 0 1 1 0-11.8 5.8 5.8 0 0 1 4.1 1.7l-1.6 1.6A3.5 3.5 0 0 0 14.7 4C12.3 4 10.4 6 10.4 8.4s1.9 4.4 4.3 4.4c2 0 3.2-1.1 3.6-2.6h-3.6V8.7h5.8c.1.2.1.4.1.5z"></path>
711                    </g>
712                </svg>
713            ${close}`,
714            apple_pay: `${base}
715                <svg viewBox="0 0 40 24" role="presentation" focusable="false">
716                    <g transform="translate(7 3)" fill="currentColor">
717                        <path d="M15.1 4.8c-.7.9-1.8 1.4-2.8 1.3-.1-1 .3-2 1-2.7.7-.8 2.1-1.5 3.2-1.5.1 1.1-.3 2.2-1.4 2.9z"></path>
718                        <path d="M18.3 12.7c-.5 1.1-1.2 2.2-2.1 3.2-.9 1-1.8 1.9-3 1.9s-1.5-.6-2.9-.6-1.9.6-3 .6c-1.3 0-2.3-1.1-3.2-2.1C2.3 13.7 1 10 2.7 7.5c.8-1.2 2.2-2 3.6-2 1.4 0 2.4.9 3 .9.7 0 2-.9 3.6-.9.6 0 2.5.2 3.6 1.9.1.1 2 .7 1.8 5.3z"></path>
719                        <rect x="20.5" y="4.3" width="1.9" height="9.4" rx="0.5"></rect>
720                        <path d="M26.8 4.1c2.2 0 3.8 1.4 3.8 3.4 0 2.1-1.6 3.4-3.9 3.4h-2V14h-2V4.1zm-2 5.4h1.7c1.2 0 2-.7 2-1.9s-.8-1.9-2-1.9h-1.7z"></path>
721                    </g>
722                </svg>
723            ${close}`
724        };
725
726        return icons[type] || `${base}</span>`;
727    }
728
729    getDefaultMethod() {
730        if (this.supportsCard) {
731            return 'card';
732        }
733
734        if (this.enabledMethods.includes('google_pay')) {
735            return 'google_pay';
736        }
737
738        if (this.enabledMethods.includes('apple_pay')) {
739            return 'apple_pay';
740        }
741
742        return 'card';
743    }
744
745    setActiveMethod(method) {
746        if (!method) {
747            return;
748        }
749
750        // Guard against selecting unsupported methods
751        if (!this.isMethodSelectable(method)) {
752            method = this.getDefaultMethod();
753        }
754
755        if (this.activeMethod === method) {
756            return;
757        }
758
759        this.activeMethod = method;
760
761        if (this.cardWrapper) {
762            this.cardWrapper.style.display = method === 'card' ? 'block' : 'none';
763        }
764
765        if (this.walletWrapper) {
766            const shouldShowWallet = method === 'google_pay' || method === 'apple_pay';
767            this.walletWrapper.style.display = shouldShowWallet ? 'block' : 'none';
768        }
769
770        this.syncMethodButtonsState();
771    }
772
773    syncMethodButtonsState() {
774        Object.entries(this.methodButtons).forEach(([method, button]) => {
775            if (!button) {
776                return;
777            }
778
779            const isActive = method === this.activeMethod;
780            button.classList.toggle('is-active', isActive);
781            button.classList.remove('is-disabled', 'is-pending');
782            button.removeAttribute('aria-disabled');
783
784            const tooltipEl = button.querySelector('.method-tooltip');
785            const baseHelp = this.methodHelpText[method] || '';
786
787            if (this.isWalletMethod(method)) {
788                if (!this.walletAvailabilityKnown) {
789                    button.classList.add('is-pending');
790                    button.setAttribute('aria-disabled', 'true');
791                    this.applyMethodTooltipText(
792                        tooltipEl,
793                        __('Checking if Google Pay or Apple Pay is available on this device…', 'yatra')
794                    );
795                } else {
796                    const isSupported = !!this.walletCapabilities[method];
797                    if (!isSupported) {
798                        button.classList.add('is-disabled');
799                        button.setAttribute('aria-disabled', 'true');
800                        const why = this.walletMethodHints[method] || __('This wallet is not available on this device or browser. Use card payment instead.', 'yatra');
801                        this.applyMethodTooltipText(tooltipEl, why);
802                        if (isActive) {
803                            this.setActiveMethod('card');
804                        }
805                    } else {
806                        this.applyMethodTooltipText(tooltipEl, baseHelp);
807                    }
808                }
809            } else {
810                this.applyMethodTooltipText(tooltipEl, baseHelp);
811            }
812        });
813
814        // Ensure at least one method is visible if everything else disabled
815        const activeBtn = this.activeMethod ? this.methodButtons[this.activeMethod] : null;
816        const activeBlocked = activeBtn && activeBtn.getAttribute('aria-disabled') === 'true';
817        if (!this.activeMethod || activeBlocked) {
818            const fallback = this.supportsCard ? 'card'
819                : (this.walletCapabilities.google_pay ? 'google_pay'
820                    : (this.walletCapabilities.apple_pay ? 'apple_pay' : null));
821            if (fallback) {
822                this.setActiveMethod(fallback);
823            }
824        }
825    }
826
827    /**
828     * @param {HTMLElement | null} el
829     * @param {string} text
830     */
831    applyMethodTooltipText(el, text) {
832        if (!el) {
833            return;
834        }
835        const t = String(text);
836        el.setAttribute('data-tooltip', t);
837        el.setAttribute('aria-label', t);
838        el.setAttribute('title', t);
839    }
840
841    isWalletMethod(method) {
842        return method === 'google_pay' || method === 'apple_pay';
843    }
844
845    isMethodSelectable(method) {
846        if (method === 'card') {
847            return !!this.supportsCard;
848        }
849
850        if (this.isWalletMethod(method)) {
851            if (!this.walletAvailabilityKnown) {
852                return false;
853            }
854            return !!this.walletCapabilities[method];
855        }
856
857        return false;
858    }
859
860    async handlePaymentRequestPayment(event) {
861        try {
862            const bookingData = this.collectBookingData();
863            if (!bookingData) {
864                throw new Error(__('Please complete the booking form before using Apple Pay or Google Pay.', 'yatra'));
865            }
866
867            this.setLoadingState(true);
868            const paymentContext = await this.prepareStripePayment(bookingData, this.paymentForm);
869            const { bookingInfo, clientSecret, effectiveAmount } = paymentContext;
870
871            const confirmation = await this.stripe.confirmCardPayment(clientSecret, {
872                payment_method: event.paymentMethod.id,
873                return_url: this.buildReturnUrl(bookingInfo)
874            });
875
876            if (confirmation.error) {
877                event.complete('fail');
878                throw confirmation.error;
879            }
880
881            event.complete('success');
882
883            if (confirmation.paymentIntent?.status === 'requires_action') {
884                return;
885            }
886
887            if (confirmation.paymentIntent?.status === 'succeeded') {
888                await this.notifyPaymentSuccess({
889                    bookingId: bookingInfo.booking_id,
890                    transactionId: confirmation.paymentIntent.id,
891                    saveCard: !!bookingData.save_card,
892                    amount: effectiveAmount,
893                    currency: bookingInfo.currency || bookingData.currency || 'USD'
894                });
895            }
896
897            window.location.href = this.buildConfirmationUrlFromBookingInfo(bookingInfo);
898        } catch (error) {
899            console.error('Stripe payment request error:', error);
900            if (event && typeof event.complete === 'function') {
901                event.complete('fail');
902            }
903            this.displayError(error.message || __('Payment failed. Please try again or use another payment method.', 'yatra'));
904            this.setLoadingState(false);
905        }
906    }
907
908    async prepareStripePayment(bookingData, formElement) {
909        // /booking/create requires BOTH the standard WP REST nonce
910        // (X-WP-Nonce) AND a booking-scoped action nonce
911        // (X-Yatra-Booking-Nonce). The booking-scoped one is what
912        // gates the actual write; the endpoint's public
913        // permission_callback intentionally bypasses WP's default
914        // cookie/nonce check so guest checkouts work, and falls back
915        // to the booking-scoped nonce as the real CSRF defence.
916        // Missing it returns { code: 'invalid_nonce' } 403.
917        const bookingNonce =
918            (typeof window !== 'undefined' && window.yatraBookingData && window.yatraBookingData.bookingNonce)
919            || (typeof document !== 'undefined'
920                ? (document.querySelector('input[name="yatra_booking_nonce"]') || {}).value
921                : '')
922            || '';
923
924        // Always call the same endpoint - server decides based on session type
925        const bookingResponse = await fetch(`${this.apiUrl}/booking/create`, {
926            method: 'POST',
927            headers: {
928                'Content-Type': 'application/json',
929                'X-WP-Nonce': this.nonce,
930                'X-Yatra-Booking-Nonce': bookingNonce
931            },
932            body: JSON.stringify(bookingData)
933        });
934
935        const bookingResult = await bookingResponse.json();
936
937        if (!bookingResult.success) {
938            throw new Error(bookingResult.message || __('Failed to process request. Please try again.', 'yatra'));
939        }
940
941        const bookingInfo = bookingResult.data;
942        const effectiveAmount = this.getEffectiveAmount(bookingData, bookingInfo);
943
944        if (bookingInfo.client_secret) {
945            const customerEmail = bookingInfo.customer_email || bookingData.contact_email || bookingData.email;
946            const customerName = bookingInfo.customer_name ||
947                [bookingData.contact_first_name, bookingData.contact_last_name]
948                    .filter(Boolean)
949                    .join(' ') || bookingData.full_name;
950
951            if (!customerEmail) {
952                throw new Error(__('Email address is required.', 'yatra'));
953            }
954
955            const billingDetails = this.getBillingDetails(bookingData, formElement);
956
957            return {
958                bookingInfo,
959                clientSecret: bookingInfo.client_secret,
960                billingDetails,
961                effectiveAmount,
962            };
963        }
964
965        const customerEmail = bookingInfo.customer_email || bookingData.contact_email || bookingData.email;
966        const customerName = bookingInfo.customer_name ||
967            [bookingData.contact_first_name, bookingData.contact_last_name]
968                .filter(Boolean)
969                .join(' ') || bookingData.full_name;
970
971        if (!customerEmail) {
972            throw new Error(__('Email address is required.', 'yatra'));
973        }
974
975        const paymentIntentResponse = await fetch(`${this.apiUrl}/payment/create-intent`, {
976            method: 'POST',
977            headers: {
978                'Content-Type': 'application/json',
979                'X-WP-Nonce': this.nonce
980            },
981            body: JSON.stringify({
982                gateway: 'stripe',
983                booking_id: bookingInfo.booking_id,
984                amount: effectiveAmount,
985                currency: bookingInfo.currency,
986                trip_id: bookingInfo.trip_id || bookingData.trip_id || null,
987                trip_date: bookingInfo.trip_date || bookingData.travel_date || '',
988                customer_email: customerEmail,
989                customer_name: customerName,
990                return_url: this.buildReturnUrl(bookingInfo)
991            })
992        });
993
994        const intentPayload = await paymentIntentResponse.json();
995        if (!paymentIntentResponse.ok) {
996            throw new Error(intentPayload?.message || __('Unable to process payment. Please try again.', 'yatra'));
997        }
998        const clientSecret = intentPayload.client_secret;
999
1000        if (!clientSecret) {
1001            throw new Error(intentPayload?.error?.message || __('Unable to process payment. Please try again.', 'yatra'));
1002        }
1003
1004        const billingDetails = this.getBillingDetails(bookingData, formElement);
1005
1006        return {
1007            bookingInfo,
1008            clientSecret,
1009            billingDetails,
1010            effectiveAmount,
1011        };
1012    }
1013
1014    getBillingDetails(bookingData, formElement) {
1015        const getField = (keys) => {
1016            const candidates = Array.isArray(keys) ? keys : [keys];
1017            for (const key of candidates) {
1018                if (bookingData && bookingData[key]) {
1019                    return bookingData[key];
1020                }
1021                if (formElement) {
1022                    const input = formElement.querySelector(`[name="${key}"]`);
1023                    if (input && input.value) {
1024                        return input.value;
1025                    }
1026                }
1027            }
1028            return '';
1029        };
1030
1031        const firstName = getField(['billing_first_name', 'contact_first_name', 'first_name']);
1032        const lastName = getField(['billing_last_name', 'contact_last_name', 'last_name']);
1033        const email = getField(['billing_email', 'contact_email', 'email']);
1034        const phone = getField(['billing_phone', 'contact_phone', 'phone']);
1035        const line1 = getField(['billing_address_1', 'contact_address']);
1036        const city = getField(['billing_city', 'contact_city']);
1037        const country = getField(['billing_country', 'contact_country']);
1038        const postalCode = getField(['billing_postcode', 'contact_postcode', 'contact_zip']);
1039
1040        const billingDetails = {};
1041        const name = `${firstName} ${lastName}`.trim();
1042        if (name) {
1043            billingDetails.name = name;
1044        }
1045        if (email) {
1046            billingDetails.email = email;
1047        }
1048        if (phone) {
1049            billingDetails.phone = phone;
1050        }
1051
1052        const address = {};
1053        if (line1) {
1054            address.line1 = line1;
1055        }
1056        if (city) {
1057            address.city = city;
1058        }
1059        if (country) {
1060            const normalizedCountry = country.trim().toUpperCase();
1061            if (normalizedCountry.length === 2) {
1062                address.country = normalizedCountry;
1063            }
1064        }
1065        if (postalCode) {
1066            address.postal_code = postalCode;
1067        }
1068
1069        if (Object.keys(address).length > 0) {
1070            billingDetails.address = address;
1071        }
1072
1073        return billingDetails;
1074    }
1075
1076    collectBookingData() {
1077        if (!this.paymentForm) {
1078            return null;
1079        }
1080
1081        const formData = new FormData(this.paymentForm);
1082        const bookingData = {};
1083
1084        for (const [key, value] of formData.entries()) {
1085            const matches = key.match(/^([^\[]+)(?:\[([^\]]*)\])?(?:\[([^\]]*)\])?$/);
1086
1087            if (matches) {
1088                const [, base, index, field] = matches;
1089
1090                if (index !== undefined && field !== undefined) {
1091                    bookingData[base] = bookingData[base] || {};
1092                    bookingData[base][index] = bookingData[base][index] || {};
1093                    bookingData[base][index][field] = value;
1094                } else if (index !== undefined) {
1095                    bookingData[base] = bookingData[base] || [];
1096                    bookingData[base][index] = value;
1097                } else {
1098                    bookingData[key] = value;
1099                }
1100            } else {
1101                bookingData[key] = value;
1102            }
1103        }
1104
1105        bookingData.accept_terms = this.paymentForm.querySelector('input[name="accept_terms"]')?.checked || false;
1106        bookingData.accept_privacy = this.paymentForm.querySelector('input[name="accept_privacy"]')?.checked || false;
1107        bookingData.subscribe_newsletter = this.paymentForm.querySelector('input[name="subscribe_newsletter"]')?.checked || false;
1108        bookingData.payment_due = parseFloat(this.paymentForm.getAttribute('data-payment-due'))
1109            || window.yatraBookingData?.paymentDue
1110            || null;
1111        bookingData.currency = window.yatraBookingData?.currency
1112            || bookingData.currency
1113            || this.paymentForm.querySelector('input[name="currency"]')?.value
1114            || 'USD';
1115        bookingData.payment_gateway = 'stripe';
1116
1117        return bookingData;
1118    }
1119
1120    displayError(message) {
1121        const errorElement = document.getElementById('yatra-stripe-card-errors');
1122        if (errorElement) {
1123            errorElement.textContent = message || '';
1124            errorElement.style.display = message ? 'block' : 'none';
1125            if (message) {
1126                errorElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
1127            }
1128        } else if (message) {
1129            console.error(message);
1130        }
1131    }
1132
1133    setLoadingState(isLoading) {
1134        if (!this.submitButton) {
1135            return;
1136        }
1137
1138        if (isLoading) {
1139            this.submitButton.disabled = true;
1140            // Spinner markup is fixed; only the user-visible text is
1141            // wrapped so it ends up in the .pot with a clean msgid.
1142            this.submitButton.innerHTML =
1143                '<span class="yatra-spinner"></span> ' + __('Processing Payment...', 'yatra');
1144        } else {
1145            this.submitButton.disabled = false;
1146            this.submitButton.innerHTML = this.originalButtonHtml;
1147        }
1148    }
1149
1150    getEffectiveAmount(bookingData, bookingInfo) {
1151        // After /booking/create, server-calculated charge (deposit / partial / full) is authoritative.
1152        // `amount` = charge for this checkout (matches gateway). Never fall back to `total_amount` (full order).
1153        const serverFirst = [
1154            parseFloat(bookingInfo?.amount),
1155            parseFloat(bookingInfo?.amount_due)
1156        ];
1157        for (let i = 0; i < serverFirst.length; i++) {
1158            const v = serverFirst[i];
1159            if (!isNaN(v) && v > 0) {
1160                return v;
1161            }
1162        }
1163
1164        const summaryDue = parseFloat(window.yatraBookingSummary?.amountDue);
1165        if (!isNaN(summaryDue) && summaryDue > 0) {
1166            return summaryDue;
1167        }
1168
1169        const candidates = [
1170            parseFloat(bookingData?.amount_due),
1171            parseFloat(bookingData?.payment_due),
1172            parseFloat(this.paymentForm?.dataset.paymentDue),
1173            parseFloat(window.yatraBookingData?.paymentDue)
1174        ].map((val) => (isNaN(val) ? 0 : val));
1175
1176        const picked = candidates.find((val) => val > 0) ?? 0;
1177        return picked > 0 ? picked : 0;
1178    }
1179
1180    getCurrentPaymentDue() {
1181        const summaryDue = parseFloat(window.yatraBookingSummary?.amountDue);
1182        if (!isNaN(summaryDue) && summaryDue > 0) {
1183            return summaryDue;
1184        }
1185
1186        const candidates = [
1187            parseFloat(this.paymentForm?.dataset.paymentDue),
1188            parseFloat(window.yatraBookingData?.paymentDue)
1189        ].map((val) => (isNaN(val) ? 0 : val));
1190
1191        return candidates.find((val) => val > 0) ?? 0;
1192    }
1193
1194    getPaymentRequestTotal() {
1195        return {
1196            label: this.tripTitle,
1197            amount: this.getAmountInCents(this.getCurrentPaymentDue())
1198        };
1199    }
1200
1201    getAmountInCents(amount) {
1202        const value = isNaN(amount) ? 0 : amount;
1203        return Math.max(50, Math.round(value * 100));
1204    }
1205
1206    /**
1207     * Pageless confirmation URLs use the same base as Settings → Default Booking URL Base
1208     * (e.g. book → /book/confirmation/{reference}/). Never hard-code /booking-confirmation/.
1209     */
1210    buildConfirmationUrlFromBookingInfo(bookingInfo) {
1211        if (bookingInfo.redirect_url) {
1212            return bookingInfo.redirect_url;
1213        }
1214        const d = window.yatraBookingData || {};
1215        const site = (d.siteUrl || window.location.origin || '').replace(/\/$/, '');
1216        const ref = String(bookingInfo.reference || '').trim();
1217        const id = bookingInfo.booking_id;
1218        const segment = ref || (id != null && id !== '' ? String(id) : '');
1219
1220        if (!segment) {
1221            return `${site}/`;
1222        }
1223
1224        if (d.permalinkStructure === 'plain') {
1225            return `${site}/?yatra_booking_confirmation=${encodeURIComponent(segment)}`;
1226        }
1227
1228        const bb = String(d.bookingBase || 'book').replace(/^\/|\/$/g, '');
1229        return `${site}/${bb}/confirmation/${encodeURIComponent(segment)}/`;
1230    }
1231
1232    buildReturnUrl(bookingInfo) {
1233        const base = this.buildConfirmationUrlFromBookingInfo(bookingInfo);
1234        try {
1235            const url = new URL(base, window.location.origin);
1236            url.searchParams.set('stripe_return', '1');
1237            return url.toString();
1238        } catch (e) {
1239            const join = base.includes('?') ? '&' : '?';
1240            return `${base}${join}stripe_return=1`;
1241        }
1242    }
1243
1244    async notifyPaymentSuccess({ bookingId, transactionId, saveCard, amount, currency }) {
1245        try {
1246            const response = await fetch(`${this.apiUrl}/payment/confirm`, {
1247                method: 'POST',
1248                headers: {
1249                    'Content-Type': 'application/json',
1250                    'X-WP-Nonce': this.nonce,
1251                },
1252                body: JSON.stringify({
1253                    gateway: 'stripe',
1254                    booking_id: bookingId,
1255                    transaction_id: transactionId,
1256                    save_card: !!saveCard,
1257                    amount,
1258                    currency,
1259                }),
1260            });
1261
1262            const payload = await response.json();
1263            if (!response.ok || payload?.success === false) {
1264                throw new Error(payload?.message || __('Failed to record payment.', 'yatra'));
1265            }
1266
1267            return payload;
1268        } catch (error) {
1269            console.error('Failed to notify backend about payment success:', error);
1270            throw error;
1271        }
1272    }
1273}
1274
1275document.addEventListener('DOMContentLoaded', () => {
1276    if (document.getElementById('yatra-booking-form')) {
1277        const apiUrl = window.yatraBookingData?.apiUrl || '/wp-json/yatra/v1';
1278        const nonce = window.yatraBookingData?.nonce || '';
1279        new YatraStripe(apiUrl, nonce);
1280    }
1281});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.