PageSourceSearch

https://greatfirevpn.com/en/payment

html greatfirevpn.com collected 2026-10-02 00:29:55 UTC 57,971 bytes, 1,617 lines download raw bytes

1<!DOCTYPE html>
2<html lang="en">
3<head>
4    <meta charset="UTF-8">
5    <meta name="viewport" content="width=device-width, initial-scale=1.0">
6    <title>Payment & Top Up Balance - GreatFireVPN</title>
7    <meta name="description" content="GreatFireVPN payment page — top up your balance. Pay with Visa/Mastercard, cryptocurrency, or other payment methods. Instant activation.">
8    <link rel="canonical" href="https://greatfirevpn.com/en/payment" />
9    <link rel="alternate" hreflang="x-default" href="https://greatfirevpn.com/en/payment" />
10    <link rel="alternate" hreflang="en" href="https://greatfirevpn.com/en/payment" />
11    <link rel="alternate" hreflang="ru" href="https://greatfirevpn.com/ru/payment" />
12    <link rel="alternate" hreflang="zh-Hans" href="https://greatfirevpn.com/zh/payment" />
13    <link rel="alternate" hreflang="fa" href="https://greatfirevpn.com/fa/payment" />
14    <link rel="alternate" hreflang="vi" href="https://greatfirevpn.com/vi/payment" />
15    <link rel="alternate" hreflang="my" href="https://greatfirevpn.com/my/payment" />
16    <link rel="alternate" hreflang="tr" href="https://greatfirevpn.com/tr/payment" />
17    <link rel="alternate" hreflang="es" href="https://greatfirevpn.com/es/payment" />
18    <meta property="og:type" content="website">
19    <meta property="og:title" content="Payment & Top Up Balance - GreatFireVPN">
20    <meta property="og:description" content="GreatFireVPN payment page — top up your balance. Pay with Visa/Mastercard, cryptocurrency, or other payment methods. Instant activation.">
21    <meta property="og:url" content="https://greatfirevpn.com/en/payment">
22    <meta property="og:image" content="https://greatfirevpn.com/assets/og-image.png">
23    <meta name="twitter:card" content="summary_large_image">
24    <meta name="twitter:title" content="Payment & Top Up Balance - GreatFireVPN">
25    <meta name="twitter:description" content="GreatFireVPN payment page — top up your balance. Pay with Visa/Mastercard, cryptocurrency, or other payment methods. Instant activation.">
26    <meta name="twitter:image" content="https://greatfirevpn.com/assets/og-image.png">
27    <link rel="icon" type="image/svg+xml" href="/favicon.svg">
28    <link rel="icon" type="image/png" sizes="32x32" href="/images/favicon.png">
29    <link rel="apple-touch-icon" href="/images/webclip.png">
30
31    <!-- Google tag (gtag.js) -->
vendor: 68 bytes, lines 31-32
31
32    <script async src="https://www.googletagmanager.com/gtag/js?id=
32G-0K1MNBSGKK
vendor: 16 bytes, lines 32-33
32"></script>
33    
33<script>
34        
vendor: 152 bytes, lines 34-38
34window.dataLayer = window.dataLayer || [];
35        function gtag(){dataLayer.push(arguments);}
36        gtag('js', new Date());
37
38        gtag('config', '
38G-0K1MNBSGKK
vendor: 8 bytes, lines 38-39
38');
39    
39</script>
39
40
41    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
42    <style>
43        :root {
44            --primary-red: #E70E02;
45            --primary-orange: #FF7F11;
46            --primary-blue: #011140;
47            --accent-blue: #1E90FF;
48            --text-color: #333;
49            --bg-light: #f8f9fa;
50            --bg-dark: #0a0a0a;
51            --transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
52            --card-shadow: 0 4px 20px rgba(0,0,0,0.08);
53            --hover-shadow: 0 15px 35px rgba(0,0,0,0.15);
54        }
55
56        .sr-only {
57            position: absolute;
58            width: 1px;
59            height: 1px;
60            padding: 0;
61            margin: -1px;
62            overflow: hidden;
63            clip: rect(0, 0, 0, 0);
64            white-space: nowrap;
65            border: 0;
66        }
67
68        body {
69            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
70            margin: 0;
71            padding: 20px;
72            background: var(--bg-light);
73            color: var(--text-color);
74            line-height: 1.6;
75        }
76
77        .container {
78            max-width: 600px;
79            margin: 0 auto;
80            background: white;
81            padding: 20px;
82            border-radius: 20px;
83            box-shadow: var(--card-shadow);
84        }
85
86        .logo {
87            text-align: center;
88            margin-bottom: 20px;
89        }
90
91        .logo img {
92            max-width: 250px;
93            height: auto;
94            transition: var(--transition);
95        }
96
97        .logo img:hover {
98            transform: scale(1.05);
99            filter: brightness(1.1);
100        }
101
102        .user-info {
103            text-align: center;
104            margin-bottom: 20px;
105            padding: 15px;
106            background: var(--bg-light);
107            border-radius: 10px;
108            border: 1px solid rgba(0,0,0,0.05);
109        }
110
111        .logout-link {
112            display: inline-block;
113            margin-left: 10px;
114            font-size: 0.8em;
115            color: #999;
116            text-decoration: none;
117            cursor: pointer;
118            font-weight: normal;
119        }
120
121        .logout-link:hover {
122            color: var(--primary-orange);
123            text-decoration: underline;
124        }
125
126        .price-tag {
127            background: linear-gradient(135deg, rgba(255,127,17,0.1), rgba(231,14,2,0.1));
128            border-radius: 12px;
129            padding: 15px 20px;
130            margin: 0 auto 30px;
131            max-width: 250px;
132            text-align: center;
133            font-weight: bold;
134            color: var(--primary-blue);
135            font-size: 24px;
136            border: 1px solid rgba(255,127,17,0.2);
137            transition: var(--transition);
138        }
139
140        .package-grid {
141            display: grid;
142            grid-template-columns: repeat(2, 1fr);
143            gap: 10px;
144            margin: 20px 0;
145        }
146
147        .package-card {
148            background: white;
149            border-radius: 15px;
150            padding: 12px;
151            text-align: center;
152            cursor: pointer;
153            transition: var(--transition);
154            border: 2px solid transparent;
155            box-shadow: var(--card-shadow);
156            position: relative;
157        }
158
159        .package-card:hover {
160            transform: translateY(-3px);
161            box-shadow: var(--hover-shadow);
162        }
163
164        .package-card.selected {
165            border-color: var(--primary-orange);
166            background: linear-gradient(135deg, rgba(255,127,17,0.05), rgba(231,14,2,0.05));
167        }
168
169        .package-duration {
170            font-size: 20px;
171            font-weight: bold;
172            color: var(--primary-blue);
173            margin-bottom: 6px;
174        }
175
176        .package-price {
177            font-size: 24px;
178            font-weight: bold;
179            color: var(--primary-orange);
180            margin-bottom: 4px;
181        }
182
183        .package-price-rub {
184            font-size: 14px;
185            color: #666;
186            margin-bottom: 8px;
187        }
188
189        .package-price-cny {
190            font-size: 14px;
191            color: #666;
192            margin-bottom: 8px;
193        }
194
195        .best-value {
196            position: absolute;
197            top: -8px;
198            right: -8px;
199            background: linear-gradient(135deg, var(--primary-orange), var(--primary-red));
200            color: white;
201            padding: 3px 10px;
202            border-radius: 15px;
203            font-size: 11px;
204            font-weight: bold;
205            box-shadow: var(--card-shadow);
206        }
207
208        .payment-options {
209            display: grid;
210            gap: 15px;
211            margin-top: 20px;
212        }
213
214        .payment-button {
215            padding: 12px 20px;
216            border: none;
217            border-radius: 12px;
218            font-size: 18px;
219            font-weight: 500;
220            cursor: pointer;
221            transition: var(--transition);
222            background: linear-gradient(45deg, var(--primary-blue), #1a2980);
223            color: white;
224            display: flex;
225            align-items: center;
226            justify-content: center;
227            position: relative;
228            box-shadow: var(--card-shadow);
229        }
230
231        .payment-button:hover {
232            transform: translateY(-2px);
233            box-shadow: var(--hover-shadow);
234        }
235
236        .payment-button:active {
237            transform: translateY(1px);
238        }
239
240        .payment-button i {
241            margin-right: 12px;
242        }
243
244
245        .payment-button.payment-button-unavailable,
246        .payment-button.payment-button-unavailable:hover,
247        .payment-button.payment-button-unavailable:active {
248            background: #cfd4dc;
249            color: #8a8f99;
250            cursor: not-allowed;
251            transform: none;
252            box-shadow: none;
253            opacity: 0.85;
254        }
255
256        .message {
257            padding: 15px;
258            margin: 15px 0;
259            border-radius: 10px;
260            text-align: center;
261            display: none;
262            transition: var(--transition);
263        }
264
265        .success {
266            background: linear-gradient(135deg, rgba(40, 167, 69, 0.1), rgba(40, 167, 69, 0.2));
267            color: #155724;
268            border: 1px solid rgba(40, 167, 69, 0.3);
269        }
270
271        .error {
272            background: linear-gradient(135deg, rgba(231, 14, 2, 0.1), rgba(231, 14, 2, 0.2));
273            color: #721c24;
274            border: 1px solid rgba(231, 14, 2, 0.3);
275        }
276
277        .loading .payment-button {
278            opacity: 0.7;
279            cursor: not-allowed;
280        }
281
282        .loading .payment-button::after {
283            content: '';
284            position: absolute;
285            width: 20px;
286            height: 20px;
287            border: 2px solid rgba(255,255,255,0.3);
288            border-radius: 50%;
289            border-top-color: #ffffff;
290            animation: spin 1s ease-in-out infinite;
291            margin-left: 10px;
292        }
293
294        @keyframes spin {
295            to { transform: rotate(360deg); }
296        }
297
298        /* Account lookup form styles */
299        .account-lookup {
300            display: none;
301            margin-bottom: 30px;
302            animation: fadeIn 0.5s ease;
303        }
304
305        .account-lookup h2 {
306            color: var(--primary-blue);
307            margin-bottom: 15px;
308            text-align: center;
309        }
310
311        .account-lookup p {
312            color: #666;
313            text-align: center;
314            margin-bottom: 20px;
315        }
316
317        .account-lookup input {
318            width: calc(100% - 30px);
319            padding: 15px;
320            margin: 10px auto;
321            border: 1px solid rgba(0,0,0,0.1);
322            border-radius: 10px;
323            font-size: 16px;
324            transition: var(--transition);
325            background: var(--bg-light);
326            box-sizing: border-box;
327            display: block;
328        }
329
330        .account-lookup input:focus {
331            outline: none;
332            border-color: var(--primary-orange);
333            box-shadow: 0 0 0 3px rgba(255,127,17,0.1);
334        }
335
336        .account-lookup button {
337            width: calc(100% - 30px);
338            padding: 15px;
339            background: linear-gradient(45deg, var(--primary-blue), #1a2980);
340            color: white;
341            border: none;
342            border-radius: 10px;
343            font-size: 16px;
344            font-weight: 500;
345            cursor: pointer;
346            transition: var(--transition);
347            margin: 10px auto;
348            box-sizing: border-box;
349            display: block;
350        }
351
352        #lookup-error-message {
353            width: calc(100% - 30px);
354            margin: 10px auto;
355            box-sizing: border-box;
356        }
357
358        .account-lookup button:hover {
359            transform: translateY(-2px);
360            box-shadow: var(--hover-shadow);
361        }
362
363        .account-lookup button:disabled {
364            background: #ccc;
365            cursor: not-allowed;
366            transform: none;
367            box-shadow: none;
368        }
369
370        @media (min-width: 480px) {
371            .package-grid {
372                gap: 15px;
373            }
374
375            .package-card {
376                padding: 15px;
377            }
378
379            .package-duration {
380                font-size: 22px;
381                margin-bottom: 8px;
382            }
383
384            .package-price {
385                font-size: 26px;
386                margin-bottom: 5px;
387            }
388
389            .package-price-rub {
390                font-size: 16px;
391                margin-bottom: 10px;
392            }
393
394            .package-price-cny {
395                font-size: 16px;
396                margin-bottom: 10px;
397            }
398
399            .best-value {
400                top: -10px;
401                right: -10px;
402                padding: 4px 12px;
403                font-size: 12px;
404            }
405        }
406
407        /* Promo code styles */
408        .promo-section {
409            margin: 15px 0;
410            padding: 15px;
411            background: var(--bg-light);
412            border-radius: 12px;
413            border: 1px solid rgba(0,0,0,0.05);
414            overflow: hidden;
415            box-sizing: border-box;
416        }
417
418        .promo-input-group {
419            display: flex;
420            gap: 10px;
421            align-items: center;
422            max-width: 100%;
423        }
424
425        .promo-input-group input {
426            flex: 1;
427            min-width: 0;
428            padding: 12px 15px;
429            border: 1px solid rgba(0,0,0,0.1);
430            border-radius: 10px;
431            font-size: 15px;
432            text-transform: uppercase;
433            letter-spacing: 1px;
434            transition: var(--transition);
435            background: white;
436            box-sizing: border-box;
437        }
438
439        .promo-input-group input:focus {
440            outline: none;
441            border-color: var(--primary-orange);
442            box-shadow: 0 0 0 3px rgba(255,127,17,0.1);
443        }
444
445        .promo-input-group button {
446            padding: 12px 20px;
447            background: linear-gradient(45deg, var(--primary-blue), #1a2980);
448            color: white;
449            border: none;
450            border-radius: 10px;
451            font-size: 15px;
452            font-weight: 500;
453            cursor: pointer;
454            transition: var(--transition);
455            white-space: nowrap;
456            flex-shrink: 0;
457            box-sizing: border-box;
458        }
459
460        .promo-input-group button:hover {
461            transform: translateY(-1px);
462            box-shadow: var(--card-shadow);
463        }
464
465        .promo-input-group button:disabled {
466            opacity: 0.6;
467            cursor: not-allowed;
468            transform: none;
469        }
470
471        .promo-status {
472            margin-top: 10px;
473            padding: 10px 15px;
474            border-radius: 8px;
475            font-size: 14px;
476            display: none;
477        }
478
479        .promo-status.promo-success {
480            background: linear-gradient(135deg, rgba(40, 167, 69, 0.1), rgba(40, 167, 69, 0.2));
481            color: #155724;
482            border: 1px solid rgba(40, 167, 69, 0.3);
483            display: block;
484        }
485
486        .promo-status.promo-error {
487            background: linear-gradient(135deg, rgba(231, 14, 2, 0.1), rgba(231, 14, 2, 0.2));
488            color: #721c24;
489            border: 1px solid rgba(231, 14, 2, 0.3);
490            display: block;
491        }
492
493        .bonus-badge {
494            display: inline-block;
495            background: linear-gradient(135deg, #28a745, #20c997);
496            color: white;
497            padding: 2px 8px;
498            border-radius: 10px;
499            font-size: 12px;
500            font-weight: bold;
501            margin-top: 4px;
502            animation: fadeIn 0.3s ease;
503        }
504
505        @keyframes fadeIn {
506            from { opacity: 0; transform: translateY(20px); }
507            to { opacity: 1; transform: translateY(0); }
508        }
509
510        .language-selector {
511            position: fixed;
512            top: 20px;
513            right: 20px;
514            z-index: 1000;
515        }
516
517        .language-selector select {
518            padding: 8px 12px;
519            border: 2px solid var(--primary-blue);
520            border-radius: 20px;
521            background: white;
522            color: var(--primary-blue);
523            font-weight: 500;
524            cursor: pointer;
525            transition: var(--transition);
526        }
527
528        .language-selector select:hover {
529            border-color: var(--primary-orange);
530        }
531
532        @media (max-width: 768px) {
533            .language-selector {
534                position: relative;
535                text-align: center;
536                margin: 30px auto 20px;
537                right: auto;
538                top: auto;
539                left: auto;
540                transform: none;
541                background: transparent;
542                padding: 0;
543                box-shadow: none;
544            }
545        }
546
547        /* Freekassa API method picker (test page only) */
548        .fk-method-panel {
549            display: none;
550            margin-top: -5px;
551            padding: 16px;
552            background: var(--bg-light);
553            border-radius: 12px;
554            border: 1px solid rgba(0,0,0,0.05);
555            animation: fadeIn 0.3s ease;
556        }
557
558        .fk-method-panel.open { display: block; }
559
560        .fk-method-tiles {
561            display: grid;
562            /* 2 columns on small screens (3rd tile wraps); 3 columns from 480px up */
563            grid-template-columns: repeat(2, 1fr);
564            gap: 10px;
565            margin-bottom: 16px;
566        }
567
568        @media (min-width: 480px) {
569            .fk-method-tiles {
570                grid-template-columns: repeat(3, 1fr);
571            }
572        }
573
574        .fk-method-tile {
575            background: white;
576            border: 2px solid transparent;
577            border-radius: 12px;
578            padding: 14px 8px;
579            text-align: center;
580            cursor: pointer;
581            transition: var(--transition);
582            box-shadow: var(--card-shadow);
583        }
584
585        .fk-method-tile:hover {
586            transform: translateY(-2px);
587            box-shadow: var(--hover-shadow);
588        }
589
590        .fk-method-tile.selected {
591            border-color: var(--primary-orange);
592            background: linear-gradient(135deg, rgba(255,127,17,0.05), rgba(231,14,2,0.05));
593        }
594
595        .fk-method-tile.disabled {
596            opacity: 0.4;
597            cursor: not-allowed;
598            background: #f3f4f6;
599            box-shadow: none;
600        }
601
602        .fk-method-tile.disabled:hover {
603            transform: none;
604            box-shadow: none;
605        }
606
607        .fk-method-tile.disabled .fk-method-name::after {
608            content: " (unavailable)";
609            font-size: 11px;
610            font-weight: 400;
611            color: #999;
612        }
613
614        .fk-method-icon {
615            font-size: 26px;
616            margin-bottom: 6px;
617            color: var(--primary-blue);
618        }
619
620        .fk-method-name {
621            font-weight: 600;
622            font-size: 15px;
623            color: var(--primary-blue);
624            margin-bottom: 2px;
625        }
626
627        .fk-method-desc {
628            font-size: 12px;
629            color: #666;
630        }
631
632        .fk-email-group {
633            margin-bottom: 14px;
634        }
635
636        .fk-email-group label {
637            display: block;
638            font-size: 13px;
639            color: #555;
640            margin-bottom: 6px;
641        }
642
643        .fk-email-group input {
644            width: 100%;
645            padding: 12px 14px;
646            border: 1px solid rgba(0,0,0,0.1);
647            border-radius: 10px;
648            font-size: 15px;
649            background: white;
650            box-sizing: border-box;
651            transition: var(--transition);
652        }
653
654        .fk-email-group input:focus {
655            outline: none;
656            border-color: var(--primary-orange);
657            box-shadow: 0 0 0 3px rgba(255,127,17,0.1);
658        }
659
660        .fk-email-group input.invalid {
661            border-color: var(--primary-red);
662            box-shadow: 0 0 0 3px rgba(231,14,2,0.1);
663        }
664
665        .fk-continue-button {
666            width: 100%;
667            background: linear-gradient(135deg, var(--primary-orange), var(--primary-red));
668            font-weight: 600;
669            font-size: 17px;
670            letter-spacing: 0.3px;
671            box-shadow: 0 6px 18px rgba(231, 14, 2, 0.25);
672        }
673
674        .fk-continue-button:hover:not(:disabled) {
675            background: linear-gradient(135deg, #ff8a2a, #ff2a1c);
676            transform: translateY(-2px);
677            box-shadow: 0 10px 24px rgba(231, 14, 2, 0.35);
678        }
679
680        .fk-continue-button .fk-continue-arrow {
681            margin-left: 10px;
682            transition: var(--transition);
683        }
684
685        .fk-continue-button:hover:not(:disabled) .fk-continue-arrow {
686            transform: translateX(4px);
687        }
688
689        .fk-continue-button:disabled {
690            background: #cfd4dc;
691            color: #8a8f99;
692            cursor: not-allowed;
693            transform: none;
694            box-shadow: none;
695            font-weight: 500;
696        }
697
698        .fk-continue-button:disabled .fk-continue-arrow {
699            opacity: 0.6;
700        }
701
702        .fk-test-badge {
703            display: inline-block;
704            margin-left: 8px;
705            padding: 2px 8px;
706            background: linear-gradient(135deg, var(--primary-orange), var(--primary-red));
707            color: white;
708            border-radius: 10px;
709            font-size: 10px;
710            font-weight: bold;
711            letter-spacing: 0.5px;
712            vertical-align: middle;
713        }
714    </style>
715</head>
716<body>
717<div class="container">
718    <h1 class="sr-only">Payment & Top Up Balance - GreatFireVPN</h1>
719    <div class="logo">
720        <a href="/en/">
721            <img id="logo-image" src="/assets/logo-blue.png" alt="GreatFire VPN Logo">
722        </a>
723    </div>
724
725    <!-- Account lookup form (shown when no userId is provided) -->
726    <div id="account-lookup" class="account-lookup" style="display: none;">
727        <h2>Enter Your Account Name</h2>
728        <p>Please enter your account name to continue with payment.<br>Download the app to register.</p>
729        <input type="text" id="account-name" placeholder="Enter your account name">
730        <div id="lookup-error-message" class="message error"></div>
731        <button id="lookup-button">Continue</button>
732    </div>
733
734    <!-- Payment section (shown after account lookup or when userId is provided) -->
735    <div id="payment-section" style="display: none;">
736        <div class="user-info">
737            <p><span id="topup-text">Top up balance for:</span> <strong id="username">Loading...</strong><a class="logout-link" onclick="logoutAccount()">(switch account)</a></p>
738        </div>
739
740        <div class="package-grid">
741            <!-- Package grid will be populated by loadPackages function -->
742        </div>
743
744        <div class="promo-section">
745            <div class="promo-input-group">
746                <input type="text" id="promo-code-input" placeholder="Enter promo code">
747                <button id="apply-promo-btn" onclick="applyPromoCode()">Apply</button>
748            </div>
749            <div id="promo-status" class="promo-status"></div>
750        </div>
751
752        <div id="success-message" class="message success"></div>
753        <div id="error-message" class="message error"></div>
754        <div class="payment-options" id="payment-options">
755            <button class="payment-button payment-button-unavailable" id="stripe-button" type="button" disabled aria-disabled="true" title="Currently unavailable">
756                <i class="fab fa-stripe"></i>
757                <span id="stripe-text">Pay with Visa/Mastercard (currently unavailable)</span>
758            </button>
759            <button class="payment-button" onclick="addBalance('crypto')">
760                <i class="fab fa-bitcoin"></i>
761                <span id="crypto-text">Pay with Cryptocurrency</span>
762            </button>
763            <!--
764                Legacy SCI handler (kept for revert): onclick="addBalance('russian')"
765                Now overridden at runtime by initFkApiFlow() -> toggleFkPanel().
766            -->
767            <button class="payment-button" id="russian-button" onclick="addBalance('russian')">
768                <i class="fas fa-credit-card"></i>
769                <span id="russian-card-text">Pay from Russia</span>
770                <!-- TEST badge removed for production; preserved for revert:
771                <span id="fk-test-badge" class="fk-test-badge" style="display:none;">TEST</span>
772                -->
773            </button>
774            <div class="fk-method-panel" id="fk-method-panel">
775                <div class="fk-method-tiles" id="fk-method-tiles">
776                    <div class="fk-method-tile" data-method-id="44" onclick="onFkTileClick(this)">
777                        <div class="fk-method-icon"><i class="fas fa-bolt"></i></div>
778                        <div class="fk-method-name">СБП</div>
779                        <div class="fk-method-desc">QR / Faster Payments</div>
780                    </div>
781                    <div class="fk-method-tile" data-method-id="43" onclick="onFkTileClick(this)">
782                        <div class="fk-method-icon"><i class="fas fa-mobile-screen-button"></i></div>
783                        <div class="fk-method-name">SberPay</div>
784                        <div class="fk-method-desc">Sberbank app</div>
785                    </div>
786                    <div class="fk-method-tile" data-method-id="36" onclick="onFkTileClick(this)">
787                        <div class="fk-method-icon"><i class="fas fa-credit-card"></i></div>
788                        <div class="fk-method-name">Bank Card</div>
789                        <div class="fk-method-desc">Visa / MC / МИР</div>
790                    </div>
791                </div>
792                <div class="fk-email-group">
793                    <label for="fk-email">Email for receipt</label>
794                    <input type="email" id="fk-email" placeholder="[email protected]" autocomplete="email" inputmode="email">
795                </div>
796                <button class="payment-button fk-continue-button" id="fk-continue" onclick="submitFkPayment()" disabled>
797                    <span>Continue to payment</span>
798                    <i class="fas fa-arrow-right fk-continue-arrow" aria-hidden="true"></i>
799                </button>
800                <div id="fk-error" class="message error" style="display:none; margin-top: 12px;"></div>
801            </div>
802            <button class="payment-button" onclick="addBalance('chinese1')">
803                <i class="fas fa-yen-sign"></i>
804                <span id="chinese1-text">Pay from China</span>
805            </button>
806        </div>
807    </div>
808
809    <div class="language-selector">
810        <select id="language-select">
811            <option value="en" selected>English</option>
812            <option value="zh">中文</option>
813            <option value="ru">Русский</option>
814            <option value="fa">فارسی</option>
815            <option value="vi">Tiếng Việt</option>
816            <option value="my">မြန်မာ</option>
817            <option value="tr">Türkçe</option>
818            <option value="es">Español</option>
819        </select>
820    </div>
821    <p style="text-align:center;margin-top:20px;font-size:14px;color:#666">Paying from inside Iran? <a href="/en/pay-from-iran" style="color:var(--primary-orange);font-weight:600;text-decoration:none">Read our payment guide &rarr;</a></p>
822</div>
823
824<script>
825    const API_URL = 'https://greatfirevpn.com/gfpayments';
826
827    // Freekassa API flow.
828    // Previously gated to the test page only (kept commented for fast revert):
829    // const FK_API_ENABLED = window.location.pathname.startsWith('/test-payment');
830    // Now enabled everywhere — production uses the same worker contract.
831    const FK_API_ENABLED = true;
832
833    const urlParams = new URLSearchParams(window.location.search);
834    let userId = urlParams.get('user_id');
835    let displayName = urlParams.get('display_name');
836
837    // Handle MERCHANT_ORDER_ID parameter if present
838    // That's Freekassa-specific for the return URL
839    const merchantOrderId = urlParams.get('MERCHANT_ORDER_ID');
840    if (merchantOrderId && !userId) {
841        // Extract user_id from MERCHANT_ORDER_ID (format: gfvpn_userid_days_timestamp)
842        const parts = merchantOrderId.split('_');
843        if (parts.length >= 2) {
844            userId = parts[1]; // The user_id is the second part
845        }
846    }
847
848    // Force locale to match the page language
849    const locale = 'en';
850
851    // Static translations for this page
852    const lang = {
853        success: 'Successfully updated your balance!',
854        error: 'Error processing payment. Please try again later.',
855        select_package: 'Please select a package first',
856        best_value: 'Best Value',
857        days: { '30': '30 Days', '90': '90 Days', '180': '180 Days', '365': '365 Days' },
858        lookup_error: 'Account not found. Please check your account name and try again.',
859        lookup_button: 'Continue',
860        promo_empty: 'Please enter a promo code',
861        promo_invalid: 'Promo code not found',
862        promo_expired: 'This promo code has expired',
863        promo_removed: 'Promo code removed',
864        promo_bonus_days: 'bonus days',
865        promo_bonus: 'PROMO',
866        days_unit: 'days',
867        promo_apply: 'Apply',
868        fk_choose_method: 'Please choose a payment method',
869        fk_invalid_email: 'Please enter a valid email',
870        fk_continue: 'Continue to payment',
871        fk_processing: 'Processing...'
872    };
873
874    let selectedDays = null;
875    let packages = [];
876    let activePromo = null;
877    let selectedFkMethodId = null;
878
879    // GA4 event tracking helper
880    function gfTrack(eventName, params) {
881        if (typeof gtag === 'function') {
882            gtag('event', eventName, params || {});
883        }
884    }
885
886    // Load packages from JSON file
887    async function loadPackages() {
888        try {
889            const response = await fetch('/packages.json');
890            const data = await response.json();
891            packages = data.packages;
892            generatePackageGrid();
893        } catch (error) {
894            console.error('Failed to load packages:', error);
895            // Fallback to hardcoded packages
896            packages = [
897                { days: 30, price_usd: 4.99, price_rub: 399, price_cny: 39, featured: false },
898                { days: 90, price_usd: 13.99, price_rub: 1199, price_cny: 99, featured: false },
899                { days: 180, price_usd: 24.99, price_rub: 1999, price_cny: 179, featured: false },
900                { days: 365, price_usd: 39.99, price_rub: 3499, price_cny: 279, featured: true }
901            ];
902            generatePackageGrid();
903        }
904    }
905
906    // Generate package grid from loaded data
907    function generatePackageGrid() {
908        const packageGrid = document.querySelector('.package-grid');
909        if (!packageGrid) return;
910
911        packageGrid.innerHTML = '';
912        
913        packages.forEach(package => {
914            const packageCard = document.createElement('div');
915            packageCard.className = 'package-card';
916            packageCard.dataset.days = package.days;
917            packageCard.onclick = () => selectPackage(packageCard);
918            
919            if (package.featured) {
920                packageCard.innerHTML = `
921                    <div class="best-value">${lang.best_value}</div>
922                    <div class="package-duration">${lang.days[package.days]}</div>
923                    <div class="package-price">$${package.price_usd}</div>
924                    <div class="package-price-rub" style="display: none;">${package.price_rub} RUB</div>
925                    <div class="package-price-cny" style="display: none;">Â¥${package.price_cny}</div>
926                `;
927            } else {
928                packageCard.innerHTML = `
929                    <div class="package-duration">${lang.days[package.days]}</div>
930                    <div class="package-price">$${package.price_usd}</div>
931                    <div class="package-price-rub" style="display: none;">${package.price_rub} RUB</div>
932                    <div class="package-price-cny" style="display: none;">Â¥${package.price_cny}</div>
933                `;
934            }
935            
936            packageGrid.appendChild(packageCard);
937        });
938    }
939
940    function selectPackage(element) {
941        // Remove selected class from all packages
942        document.querySelectorAll('.package-card').forEach(card => {
943            card.classList.remove('selected');
944        });
945        
946        // Add selected class to clicked package
947        element.classList.add('selected');
948        
949        // Store selected days
950        selectedDays = parseInt(element.dataset.days);
951
952        const pkg = packages.find(p => p.days === selectedDays);
953        gfTrack('package_select', {
954            event_category: 'engagement',
955            days: selectedDays,
956            price_usd: pkg ? pkg.price_usd : null,
957            promo_code: activePromo ? activePromo.code : 'none'
958        });
959    }
960
961    // Promo code functions
962    async function applyPromoCode() {
963        const input = document.getElementById('promo-code-input');
964        const status = document.getElementById('promo-status');
965        const btn = document.getElementById('apply-promo-btn');
966        const code = input.value.trim().toUpperCase();
967
968        if (!code) {
969            status.className = 'promo-status promo-error';
970            status.textContent = lang.promo_empty;
971            gfTrack('promo_code_apply', { event_category: 'engagement', result: 'empty' });
972            return;
973        }
974
975        // If same promo is already active, remove it
976        if (activePromo && activePromo.code === code) {
977            activePromo = null;
978            status.className = 'promo-status promo-error';
979            status.textContent = lang.promo_removed;
980            input.value = '';
981            updatePackageDisplay();
982            gfTrack('promo_code_apply', { event_category: 'engagement', result: 'removed', promo_code: code });
983            setTimeout(() => { status.className = 'promo-status'; }, 3000);
984            return;
985        }
986
987        try {
988            btn.disabled = true;
989            btn.textContent = '...';
990
991            const response = await fetch(`${API_URL}
991/validate-promo`, {
992                method: 'POST',
993                headers: { 'Content-Type': 'application/json' },
994                body: JSON.stringify({ promo_code: code })
995            });
996
997            // Handle 404 or other errors gracefully (endpoint not configured yet)
998            if (!response.ok) {
999                status.className = 'promo-status promo-error';
1000                status.textContent = lang.promo_invalid;
1001                activePromo = null;
1002                updatePackageDisplay();
1003                gfTrack('promo_code_apply', { event_category: 'engagement', result: 'invalid', promo_code: code });
1004                return;
1005            }
1006
1007            const data = await response.json();
1008
1009            if (!data.valid) {
1010                status.className = 'promo-status promo-error';
1011                status.textContent = lang.promo_invalid;
1012                activePromo = null;
1013                updatePackageDisplay();
1014                gfTrack('promo_code_apply', { event_category: 'engagement', result: 'invalid', promo_code: code });
1015                return;
1016            }
1017
1018            // Valid promo
1019            activePromo = {
1020                code: code,
1021                bonus_days: data.bonus_days,
1022                bonus_percent: data.bonus_percent
1023            };
1024
1025            let bonusDesc;
1026            if (data.bonus_days) {
1027                bonusDesc = '+' + data.bonus_days + ' ' + lang.promo_bonus_days;
1028            } else if (data.bonus_percent) {
1029                bonusDesc = '+' + data.bonus_percent + '% ' + lang.promo_bonus_days;
1030            }
1031
1032            status.className = 'promo-status promo-success';
1033            status.textContent = '✓ ' + code + ' — ' + bonusDesc;
1034            updatePackageDisplay();
1035            gfTrack('promo_code_apply', {
1036                event_category: 'engagement',
1037                result: 'success',
1038                promo_code: code,
1039                bonus_days: data.bonus_days || null,
1040                bonus_percent: data.bonus_percent || null
1041            });
1042
1043        } catch (error) {
1044            // Network errors or endpoint not found - show error
1045            status.className = 'promo-status promo-error';
1046            status.textContent = lang.promo_invalid;
1047            activePromo = null;
1048            updatePackageDisplay();
1049            gfTrack('promo_code_apply', { event_category: 'engagement', result: 'error', promo_code: code });
1050        } finally {
1051            btn.disabled = false;
1052            btn.textContent = lang.promo_apply;
1053        }
1054    }
1055
1056    function getBonusDays(baseDays) {
1057        if (!activePromo) return 0;
1058        if (activePromo.bonus_days) return activePromo.bonus_days;
1059        if (activePromo.bonus_percent) return Math.floor(baseDays * activePromo.bonus_percent / 100);
1060        return 0;
1061    }
1062
1063    function updatePackageDisplay() {
1064        const cards = document.querySelectorAll('.package-card');
1065        cards.forEach(card => {
1066            const days = parseInt(card.dataset.days);
1067            const durationEl = card.querySelector('.package-duration');
1068            const existingBadge = card.querySelector('.bonus-badge');
1069            if (existingBadge) existingBadge.remove();
1070
1071            // Restore original label
1072            durationEl.textContent = lang.days[days];
1073
1074            if (activePromo) {
1075                const bonus = getBonusDays(days);
1076                if (bonus > 0) {
1077                    durationEl.textContent = days + ' + ' + bonus + ' ' + lang.days_unit;
1078                    const badge = document.createElement('div');
1079                    badge.className = 'bonus-badge';
1080                    badge.textContent = '🎁 ' + lang.promo_bonus;
1081                    durationEl.after(badge);
1082                }
1083            }
1084        });
1085    }
1086
1087    // Function to fetch account name from user ID
1088    async function fetchAccountName(userId) {
1089        try {
1090            const response = await fetch(`${API_URL}/get-account-name`, {
1091                method: 'POST',
1092                headers: {
1093                    'Content-Type': 'application/json',
1094                },
1095                body: JSON.stringify({ user_id: userId })
1096            });
1097
1098            if (!response.ok) {
1099                throw new Error('Failed to fetch account name');
1100            }
1101
1102            const data = await response.json();
1103            return data;
1104        } catch (error) {
1105            return null;
1106        }
1107    }
1108
1109    // Function to fetch account ID from account name
1110    // For non-email account names, returns its own UUID
1111    // For email account names, returns the (linked) primary account UUID
1112    async function fetchAccountId(accountName) {
1113        try {
1114            const response = await fetch(`${API_URL}
1114/get-primary-account-uuid`, {
1115                method: 'POST',
1116                headers: {
1117                    'Content-Type': 'application/json',
1118                },
1119                body: JSON.stringify({
1120                    account_name: accountName
1121                })
1122            });
1123
1124            const data = await response.json();
1125            
1126            if (!response.ok) {
1127                throw new Error(data.error || 'Account lookup failed');
1128            }
1129
1130            return data;
1131        } catch (error) {
1132            throw error;
1133        }
1134    }
1135
1136    // Initialize the page
1137    async function initializePage() {
1138        // Use absolute path only on the main domain
1139        if (window.location.hostname === 'greatfirevpn.com') {
1140            document.getElementById('logo-image').src = '/assets/logo-blue.png';
1141        }
1142
1143        // Load packages first
1144        await loadPackages();
1145
1146        // English page: Show USD prices (default, already visible)
1147
1148        // Show/hide sections based on whether userId is provided
1149        const accountLookup = document.getElementById('account-lookup');
1150        const paymentSection = document.getElementById('payment-section');
1151        
1152        if (!userId) {
1153            // Show account lookup, hide payment section
1154            accountLookup.style.display = 'block';
1155            paymentSection.style.display = 'none';
1156            
1157            // Add event listener for account lookup
1158            document.getElementById('lookup-button').addEventListener('click', lookupAccount);
1159        } else {
1160            // Show payment section and hide account lookup
1161            accountLookup.style.display = 'none';
1162            paymentSection.style.display = 'block';
1163            
1164            // If display name is not provided, fetch it
1165            if (!displayName) {
1166                const accountName = await fetchAccountName(userId);
1167                displayName = accountName || userId;
1168            }
1169            
1170            document.getElementById('username').textContent = displayName;
1171        }
1172
1173        initFkApiFlow();
1174    }
1175
1176    // Logout / switch account
1177    function logoutAccount() {
1178        userId = null;
1179        displayName = null;
1180        document.getElementById('account-name').value = '';
1181        document.getElementById('lookup-error-message').style.display = 'none';
1182        document.getElementById('account-lookup').style.display = 'block';
1183        document.getElementById('payment-section').style.display = 'none';
1184        const newUrl = new URL(window.location.href);
1185        newUrl.searchParams.delete('user_id');
1186        newUrl.searchParams.delete('display_name');
1187        window.history.pushState({}, '', newUrl);
1188    }
1189
1190    // Account lookup function
1191    async function lookupAccount() {
1192        const accountName = document.getElementById('account-name').value.trim();
1193        const lookupButton = document.getElementById('lookup-button');
1194        const errorMsg = document.getElementById('lookup-error-message');
1195        
1196        if (!accountName) {
1197            errorMsg.textContent = lang.lookup_error;
1198            errorMsg.style.display = 'block';
1199            gfTrack('account_lookup_submit', { event_category: 'engagement', result: 'empty' });
1200            return;
1201        }
1202
1203        try {
1204            lookupButton.disabled = true;
1205            lookupButton.textContent = 'Looking up...';
1206            errorMsg.style.display = 'none'; // Clear any previous error
1207
1208            const accountId = await fetchAccountId(accountName);
1209
1210            if (!accountId || typeof accountId !== 'string' || accountId.length !== 36) {
1211                throw new Error('Invalid account ID received');
1212            }
1213
1214            userId = accountId;
1215            displayName = accountName;
1216            gfTrack('account_lookup_submit', { event_category: 'engagement', result: 'success' });
1217            
1218            // Update UI
1219            document.getElementById('account-lookup').style.display = 'none';
1220            document.getElementById('payment-section').style.display = 'block';
1221            document.getElementById('username').textContent = displayName;
1222            
1223            // Update URL without reloading
1224            const newUrl = new URL(window.location.href);
1225            newUrl.searchParams.set('user_id', userId);
1226            newUrl.searchParams.set('display_name', displayName);
1227            window.history.pushState({}, '', newUrl);
1228
1229        } catch (error) {
1230            errorMsg.textContent = lang.lookup_error;
1231            errorMsg.style.display = 'block';
1232            gfTrack('account_lookup_submit', { event_category: 'engagement', result: 'not_found' });
1233        } finally {
1234            lookupButton.disabled = false;
1235            lookupButton.textContent = lang.lookup_button;
1236        }
1237    }
1238
1239    // ===== Freekassa API method picker (test page only) =====
1240
1241    // Centralised error reporter: keeps the actual message in the dev tools
1242    // (and GA), returns the generic user-facing string for display.
1243    // Use for any payment-related error that we don't want to leak verbatim.
1244    function reportPaymentError(context, error, extra) {
1245        const detail = (error && (error.message || error.toString())) || 'unknown';
1246        // eslint-disable-next-line no-console
1247        console.error(`[payment:${context}]`, error, extra || '');
1248        gfTrack('payment_error', {
1249            event_category: 'error',
1250            context,
1251            error: detail
1252        });
1253        return lang.error;
1254    }
1255
1256    function isValidEmail(email) {
1257        return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
1258    }
1259
1260    function updateFkContinueButton() {
1261        const btn = document.getElementById('fk-continue');
1262        if (!btn) return;
1263        const email = (document.getElementById('fk-email').value || '').trim();
1264        btn.disabled = !(selectedFkMethodId && isValidEmail(email));
1265    }
1266
1267    function onFkTileClick(tileEl) {
1268        if (tileEl.classList.contains('disabled')) return;
1269        const id = parseInt(tileEl.dataset.methodId, 10);
1270        if (!id) return;
1271        selectFkMethod(id);
1272    }
1273
1274    function selectFkMethod(methodId) {
1275        selectedFkMethodId = methodId;
1276        document.querySelectorAll('.fk-method-tile').forEach(tile => {
1277            tile.classList.toggle('selected', parseInt(tile.dataset.methodId, 10) === methodId);
1278        });
1279        updateFkContinueButton();
1280        gfTrack('fk_method_select', {
1281            event_category: 'engagement',
1282            paymentSystemId: methodId,
1283            days: selectedDays
1284        });
1285    }
1286
1287    async function loadFkPaymentSystems() {
1288        try {
1289            const resp = await fetch(`${API_URL}/freekassa-payment-systems`);
1290            if (!resp.ok) throw new Error('HTTP ' + resp.status);
1291            const data = await resp.json();
1292            if (!data || !data.success || !Array.isArray(data.currencies)) {
1293                throw new Error('bad response');
1294            }
1295            const activeIds = new Set(
1296                data.currencies
1297                    .filter(c => c && c.is_enabled === true)
1298                    .map(c => c.id)
1299            );
1300            applyFkAvailability(activeIds);
1301            gfTrack('fk_methods_loaded', {
1302                event_category: 'engagement',
1303                count: activeIds.size
1304            });
1305        } catch (e) {
1306            // Fail open: leave tiles enabled with their hardcoded ids.
1307            // The worker will reject the order if the id is actually inactive.
1308            // eslint-disable-next-line no-console
1309            console.warn('[payment:fk_methods_load]', e);
1310            gfTrack('fk_methods_error', {
1311                event_category: 'engagement',
1312                error: (e && e.message) || 'unknown'
1313            });
1314        }
1315    }
1316
1317    // Toggle each tile's .disabled class based on the merchant's active id list.
1318    // Tiles keep their fixed data-method-id (44 / 36); we never remap.
1319    function applyFkAvailability(activeIds) {
1320        document.querySelectorAll('.fk-method-tile').forEach(tile => {
1321            const id = parseInt(tile.dataset.methodId, 10);
1322            const available = activeIds.has(id);
1323            tile.classList.toggle('disabled', !available);
1324
1325            // If the user had this tile selected and it just became unavailable, deselect.
1326            if (!available && selectedFkMethodId === id) {
1327                selectedFkMethodId = null;
1328                tile.classList.remove('selected');
1329                updateFkContinueButton();
1330            }
1331        });
1332    }
1333
1334    function toggleFkPanel() {
1335        const panel = document.getElementById('fk-method-panel');
1336        const errorMsg = document.getElementById('error-message');
1337
1338        // Require a package first - mirror addBalance() behaviour
1339        if (!selectedDays) {
1340            errorMsg.textContent = lang.select_package;
1341            errorMsg.style.display = 'block';
1342            gfTrack('payment_method_click', {
1343                event_category: 'conversion',
1344                payment_method: 'russian_api',
1345                result: 'no_package_selected'
1346            });
1347            return;
1348        }
1349        errorMsg.style.display = 'none';
1350
1351        const wasOpen = panel.classList.contains('open');
1352        if (wasOpen) {
1353            panel.classList.remove('open');
1354            gfTrack('fk_panel_close', { event_category: 'engagement' });
1355        } else {
1356            panel.classList.add('open');
1357            // Always reflect the real state on open (disabled until method + valid email)
1358            updateFkContinueButton();
1359            gfTrack('fk_panel_open', {
1360                event_category: 'engagement',
1361                days: selectedDays,
1362                promo_code: activePromo ? activePromo.code : 'none'
1363            });
1364            // Smooth-scroll the panel into view on small screens
1365            setTimeout(() => panel.scrollIntoView({ behavior: 'smooth', block: 'nearest' }), 50);
1366        }
1367    }
1368
1369    async function submitFkPayment() {
1370        const continueBtn = document.getElementById('fk-continue');
1371        const errorEl = document.getElementById('fk-error');
1372        const emailInput = document.getElementById('fk-email');
1373        const email = emailInput.value.trim();
1374
1375        errorEl.style.display = 'none';
1376        emailInput.classList.remove('invalid');
1377
1378        if (!selectedDays) {
1379            errorEl.textContent = lang.select_package;
1380            errorEl.style.display = 'block';
1381            return;
1382        }
1383        if (!selectedFkMethodId) {
1384            errorEl.textContent = lang.fk_choose_method;
1385            errorEl.style.display = 'block';
1386            return;
1387        }
1388        if (!isValidEmail(email)) {
1389            emailInput.classList.add('invalid');
1390            errorEl.textContent = lang.fk_invalid_email;
1391            errorEl.style.display = 'block';
1392            emailInput.focus();
1393            return;
1394        }
1395
1396        const requestBody = {
1397            userId,
1398            paymentMethod: 'russian_api',
1399            paymentSystemId: selectedFkMethodId,
1400            email,
1401            days: selectedDays
1402        };
1403        if (activePromo) requestBody.promo_code = activePromo.code;
1404
1405        gfTrack('fk_create_order_attempt', {
1406            event_category: 'conversion',
1407            paymentSystemId: selectedFkMethodId,
1408            days: selectedDays,
1409            promo_code: activePromo ? activePromo.code : 'none'
1410        });
1411
1412        try {
1413            continueBtn.disabled = true;
1414            const originalLabel = continueBtn.querySelector('span').textContent;
1415            continueBtn.querySelector('span').textContent = lang.fk_processing;
1416
1417            const response = await fetch(`${API_URL}
1417/handle-payment`, {
1418                method: 'POST',
1419                headers: { 'Content-Type': 'application/json' },
1420                body: JSON.stringify(requestBody)
1421            });
1422
1423            let data = null;
1424            try { data = await response.json(); } catch (_) { /* non-JSON body */ }
1425
1426            if (!response.ok || !data || !data.url) {
1427                throw new Error((data && data.error) || ('HTTP ' + response.status));
1428            }
1429
1430            gfTrack('fk_create_order_success', {
1431                event_category: 'conversion',
1432                paymentSystemId: selectedFkMethodId,
1433                days: selectedDays
1434            });
1435
1436            window.location.href = data.url;
1437            // Keep button disabled while we navigate away
1438        } catch (error) {
1439            errorEl.textContent = reportPaymentError('fk_create_order', error, {
1440                paymentSystemId: selectedFkMethodId,
1441                days: selectedDays
1442            });
1443            errorEl.style.display = 'block';
1444            continueBtn.disabled = false;
1445            continueBtn.querySelector('span').textContent = lang.fk_continue;
1446        }
1447    }
1448
1449    function initFkApiFlow() {
1450        if (!FK_API_ENABLED) return;
1451
1452        const russianBtn = document.getElementById('russian-button');
1453        if (russianBtn) {
1454            // Override the SCI handler with the panel toggle
1455            russianBtn.onclick = toggleFkPanel;
1456        }
1457
1458        // TEST badge removed for production rollout (kept for revert):
1459        // const badge = document.getElementById('fk-test-badge');
1460        // if (badge) badge.style.display = 'inline-block';
1461
1462        const emailInput = document.getElementById('fk-email');
1463        if (emailInput) {
1464            emailInput.addEventListener('input', () => {
1465                emailInput.classList.remove('invalid');
1466                updateFkContinueButton();
1467            });
1468        }
1469
1470        // Discover which methods are actually enabled on the merchant.
1471        // Non-blocking — tiles render with their hardcoded ids first, then get
1472        // greyed out when the response arrives if their id isn't in `is_enabled`.
1473        loadFkPaymentSystems();
1474    }
1475
1476    async function addBalance(paymentMethod) {
1477        const container = document.getElementById('payment-options');
1478        const successMsg = document.getElementById('success-message');
1479        const errorMsg = document.getElementById('error-message');
1480        const buttons = document.querySelectorAll('.payment-button');
1481
1482        if (!selectedDays) {
1483            errorMsg.textContent = lang.select_package;
1484            errorMsg.style.display = 'block';
1485            gfTrack('payment_method_click', {
1486                event_category: 'conversion',
1487                payment_method: paymentMethod,
1488                result: 'no_package_selected'
1489            });
1490            return;
1491        }
1492
1493        gfTrack('payment_method_click', {
1494            event_category: 'conversion',
1495            payment_method: paymentMethod,
1496            days: selectedDays,
1497            promo_code: activePromo ? activePromo.code : 'none'
1498        });
1499
1500        try {
1501            container.classList.add('loading');
1502            buttons.forEach(button => button.disabled = true);
1503            successMsg.style.display = 'none';
1504            errorMsg.style.display = 'none';
1505
1506            // Prepare base request body
1507            const requestBody = {
1508                userId,
1509                paymentMethod,
1510                days: selectedDays
1511            };
1512
1513            // Include promo code if active
1514            if (activePromo) {
1515                requestBody.promo_code = activePromo.code;
1516            }
1517
1518            const response = await fetch(`${API_URL}/handle-payment`, {
1519                method: 'POST',
1520                headers: {
1521                    'Content-Type': 'application/json',
1522                },
1523                body: JSON.stringify(requestBody)
1524            });
1525
1526            const data = await response.json();
1527
1528            if (!response.ok) {
1529                throw new Error(data.error || 'Payment processing failed');
1530            }
1531
1532            // Handle response based on payment method
1533            if (paymentMethod === 'crypto' && data.url) {
1534                // Redirect to the crypto payment page
1535                window.location.href = data.url;
1536                return;
1537            } else if (paymentMethod === 'stripe' && data.checkoutUrl) {
1538                // Just redirect to Stripe's checkout URL as is
1539                window.location.href = data.checkoutUrl;
1540                return;
1541            } else if (paymentMethod === 'russian' && data.url) {
1542                // Redirect to the Russian payment page
1543                window.location.href = data.url;
1544                return;
1545            } else if (paymentMethod === 'chinese1' && data.url) {
1546                // Redirect to the Chinese payment page
1547                window.location.href = data.url;
1548                return;
1549            }
1550
1551            // Show success message for other payment methods which directly increase balance
1552            successMsg.textContent = lang.success;
1553            successMsg.style.display = 'block';
1554
1555            setTimeout(() => {
1556                window.close();
1557            }, 3000);
1558
1559        } catch (error) {
1560            errorMsg.textContent = error.message || lang.error;
1561            errorMsg.style.display = 'block';
1562        } finally {
1563            container.classList.remove('loading');
1564            buttons.forEach(button => { if (!button.classList.contains('payment-button-unavailable')) button.disabled = false; });
1565        }
1566    }
1567
1568    // Handle back button/close
1569    window.addEventListener('pageshow', function(event) {
1570        const container = document.getElementById('payment-options');
1571        const buttons = document.querySelectorAll('.payment-button');
1572        container.classList.remove('loading');
1573        buttons.forEach(button => { if (!button.classList.contains('payment-button-unavailable')) button.disabled = false; });
1574    });
1575
1576    // Initialize the page when DOM is loaded
1577    document.addEventListener('DOMContentLoaded', initializePage);
1578
1579    // Handle payment success/cancel URLs
1580    const path = window.location.pathname;
1581    const successMsg = document.getElementById('success-message');
1582    const errorMsg = document.getElementById('error-message');
1583
1584    if (path.includes('/payment-success')) {
1585        // Show success message
1586        successMsg.textContent = lang.success;
1587        successMsg.style.display = 'block';
1588        
1589        // Optional: Redirect back to app after short delay
1590        setTimeout(() => {
1591            window.close();
1592        }, 3000);
1593    } else if (path.includes('/payment-cancel')) {
1594        // Show cancel message
1595        errorMsg.textContent = lang.error;
1596        errorMsg.style.display = 'block';
1597    }
1598</script>
1598
1599
1600<script>
1601    // Language selector functionality
1602    const languageSelect = document.getElementById('language-select');
1603    languageSelect.addEventListener('change', function(e) {
1604        const newLang = e.target.value;
1605        gfTrack('language_change', {
1606            event_category: 'engagement',
1607            language_from: locale,
1608            language_to: newLang
1609        });
1610        // Preserve the current URL parameters (userId, etc.)
1611        const urlParams = new URLSearchParams(window.location.search);
1612        const newUrl = '/' + newLang + '/payment' + (urlParams.toString() ? '?' + urlParams.toString() : '');
1613        window.location.href = newUrl;
1614    });
1615</script>
1615
1616</body>
1617</html> 

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.