| @@ -920,8 +920,30 @@ | ||
| 920 | 920 | ? (document.querySelector('input[name="yatra_booking_nonce"]') || {}).value |
| 921 | 921 | : '') |
| 922 | 922 | || ''; |
| 923 | 923 | |
| 924 | + | |
| 925 | + // reCAPTCHA v3: this gateway posts to /booking/create itself, so it must | |
| 926 | + // attach its own token — the shared submit path in booking.js never runs | |
| 927 | + // for an intercepted gateway submit. Without this the server receives an | |
| 928 | + // empty token and rejects the booking with "reCAPTCHA verification | |
| 929 | + // failed", which no score threshold can get past. | |
| 930 | + // | |
| 931 | + // A fresh token every attempt: v3 tokens are single-use and expire after | |
| 932 | + // ~2 minutes, so a retry after a declined card must not reuse the old one. | |
| 933 | + try { | |
| 934 | + const yatraRc = (typeof window !== 'undefined') ? window.yatraRecaptcha : null; | |
| 935 | + if (yatraRc && typeof yatraRc.protects === 'function' && yatraRc.protects('booking') | |
| 936 | + && typeof yatraRc.execute === 'function') { | |
| 937 | + const recaptchaToken = await yatraRc.execute('booking'); | |
| 938 | + if (recaptchaToken) { | |
| 939 | + bookingData.recaptcha_token = recaptchaToken; | |
| 940 | + } | |
| 941 | + } | |
| 942 | + } catch (e) { | |
| 943 | + // Never block checkout on the helper itself; the server still decides. | |
| 944 | + } | |
| 945 | + | |
| 924 | 946 | // Always call the same endpoint - server decides based on session type |
| 925 | 947 | const bookingResponse = await fetch(`${this.apiUrl}/booking/create`, { |
| 926 | 948 | method: 'POST', |
| 927 | 949 | headers: { |