PageSourceSearch

https://theperfectworld.com/wp-content/plugins/wp-payment-gateway/assets/js/frontend.js?ver=1.1.0

js theperfectworld.com collected 2026-09-24 23:29:06 UTC 21,519 bytes, 512 lines download raw bytes

1jQuery(document).ready(function($) {
2    const form = $('#wpg-payment-form');
3    if (!form.length) {
4        return;
5    }
6
7    const formCurrency = (form.data('currency') || wpgParams.currency || 'SEK').toString().toUpperCase();
8    const submitBtn = $('#wpg-submit-btn');
9    const submitBtnDefaultText = $.trim(submitBtn.text());
10    const errorDiv = $('#wpg-error-message');
11    const successDiv = $('#wpg-success-message');
12
13    function setProgress(step) { return step; }
14
15    function updateAmountSummary() {
16        const amountVal = $('#wpg-custom-amount').val() || $('.wpg-amount-btn.active').data('amount');
17        if (amountVal) {
18            $('#wpg-selected-amount').text(amountVal + ' ' + formCurrency);
19            $('.wpg-amount-summary').show();
20        } else {
21            $('.wpg-amount-summary').hide();
22        }
23    }
24
25    let stripe;
26    let elements;
27    let paymentElement;
28
29    // Payment Element (deferred intent) kräver ett belopp i minsta enhet (ören)
30    // redan vid initiering; 10000 är bara en platshållare tills givaren valt belopp.
31    const PLACEHOLDER_AMOUNT_MINOR = 10000;
32
33    function getAmountMinor() {
34        const raw = $('#wpg-custom-amount').val() || $('.wpg-amount-btn.active').data('amount');
35        const amount = parseFloat(raw);
36        return (amount > 0) ? Math.round(amount * 100) : null;
37    }
38
39    function isRecurringSelected() {
40        const checkbox = document.getElementById('wpg-recurring');
41        return !!(checkbox && checkbox.checked);
42    }
43
44    // Elements-konfigurationen måste matcha PaymentIntent som skapas på servern
45    // (belopp och setup_future_usage), annars avvisar confirmPayment betalningen.
46    function syncStripeElements() {
47        if (!elements) {
48            return;
49        }
50        elements.update({
51            amount: getAmountMinor() || PLACEHOLDER_AMOUNT_MINOR,
52            setupFutureUsage: isRecurringSelected() ? 'off_session' : null
53        });
54    }
55
56    if (typeof Stripe !== 'undefined' && wpgParams.stripeEnabled && wpgParams.publishableKey) {
57        try {
58            stripe = Stripe(wpgParams.publishableKey);
59            elements = stripe.elements({
60                mode: 'payment',
61                amount: getAmountMinor() || PLACEHOLDER_AMOUNT_MINOR,
62                currency: formCurrency.toLowerCase(),
63                paymentMethodTypes: ['card'],
64                setupFutureUsage: isRecurringSelected() ? 'off_session' : undefined,
65                appearance: {
66                    variables: {
67                        fontSizeBase: '16px',
68                        colorText: '#32325d',
69                        colorDanger: '#fa755a'
70                    }
71                }
72            });
73
74            // Namn och e-post samlas in i vårt eget formulär och skickas
75            // via confirmParams i stället för att dubbleras i elementet.
76            paymentElement = elements.create('payment', {
77                fields: {
78                    billingDetails: {
79                        name: 'never',
80                        email: 'never'
81                    }
82                }
83            });
84
85            paymentElement.on('change', function(event) {
86                const displayError = $('#stripe-card-errors');
87                if (event.error) {
88                    displayError.text(event.error.message).show();
89                } else {
90                    displayError.text('').hide();
91                }
92            });
93
94            if ($('input[name="payment_method"]:checked').val() === 'stripe') {
95                paymentElement.mount('#stripe-card-element');
96            }
97        } catch (error) {
98            console.error('Error initializing Stripe:', error);
99        }
100    }
101
102    $('#wpg-recurring').on('change', syncStripeElements);
103
104    function toggleSwishFields() {
105        const paymentMethod = $('input[name="payment_method"]:checked').val();
106        if (paymentMethod === 'swish') {
107            $('.wpg-swish-fields').stop(true, true).slideDown();
108            $('#wpg-swish-phone').prop('required', true);
109        } else {
110            $('.wpg-swish-fields').stop(true, true).slideUp();
111            $('#wpg-swish-phone').prop('required', false);
112        }
113        highlightPaymentMethod();
114    }
115
116    function highlightPaymentMethod() {
117        $('.wpg-payment-method').removeClass('active');
118        const checkedRadio = $('input[name="payment_method"]:checked');
119        if (checkedRadio.length) {
120            checkedRadio.closest('.wpg-payment-method').addClass('active');
121        }
122    }
123
124    function showError(message) {
125        errorDiv.html(message).show();
126        successDiv.hide();
127        $('html, body').animate({
128            scrollTop: errorDiv.offset().top - 100
129        }, 500);
130    }
131
132    function showSuccess(message) {
133        successDiv.html(message).show();
134        errorDiv.hide();
135        $('html, body').animate({
136            scrollTop: successDiv.offset().top - 100
137        }, 500);
138    }
139
140    function validateEmail(email) {
141        return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
142    }
143
144    function fmt(str) {
145        const args = Array.prototype.slice.call(arguments, 1);
146        let i = 0;
147        return String(str).replace(/%[sd]/g, function() {
148            const val = args[i++];
149            return val !== undefined ? val : '';
150        });
151    }
152
153    form.on('submit', async function(e) {
154        e.preventDefault();
155        errorDiv.hide().empty();
156        successDiv.hide().empty();
157
158        const amount = $('#wpg-custom-amount').val() || $('.wpg-amount-btn.active').data('amount');
159        const fullName = $('#wpg-full-name').val().trim();
160        const email = $('#wpg-email').val();
161        const paymentMethod = $('input[name="payment_method"]:checked').val();
162
163        if (!amount) {
164            showError('Please select or enter an amount.');
165            return;
166        }
167
168        if (!fullName || !validateEmail(email)) {
169            showError('Please fill in your name and a valid email before paying.');
170            return;
171        }
172
173        submitBtn
174            .text((wpgParams.i18n && wpgParams.i18n.processing_payment) ? wpgParams.i18n.processing_payment : 'Processing payment...')
175            .addClass('loading')
176            .prop('disabled', true);
177
178        const formId = $('#wpg-payment-form input[name="form_id"]').val();
179        const donationPurpose = $('#wpg-donation-purpose').val();
180        const customerComment = $('#wpg-customer-comment').val();
181        const formType = $('#wpg-payment-form input[name="form_type"]').val() || 'standard';
182        const phone = $('#wpg-phone').val() || '';
183        const donorMessage = $('#wpg-donor-message').val() || '';
184
185        const paymentData = {
186            amount: amount,
187            full_name: fullName,
188            email: email,
189            phone: phone,
190            donor_message: donorMessage,
191            payment_method: paymentMethod,
192            form_id: formId,
193            form_type: formType,
194            currency: formCurrency,
195            action: 'wpg_process_payment',
196            nonce: wpgParams.nonce
197        };
198
199        if (donationPurpose) {
200            paymentData.donation_purpose = donationPurpose;
201        }
202
203        if (customerComment) {
204            paymentData.customer_comment = customerComment;
205        }
206
207        if (paymentMethod === 'swish') {
208            const swishPhone = $('#wpg-swish-phone').val();
209            if (!swishPhone) {
210                showError(wpgParams.i18n ? wpgParams.i18n.swish_phone_required : 'Swish phone number is required');
211                submitBtn.text(submitBtnDefaultText).removeClass('loading').prop('disabled', false);
212                return;
213            }
214            paymentData.swish_phone = swishPhone;
215        }
216
217        try {
218            if (paymentMethod === 'stripe') {
219                if (!paymentElement) {
220                    throw new Error(wpgParams.i18n ? wpgParams.i18n.stripe_init_error : 'Stripe card element not initialized');
221                }
222
223                const isRecurring = isRecurringSelected();
224
225                if (isRecurring) {
226                    paymentData.recurring = '1';
227                    paymentData.action = 'wpg_create_subscription';
228                }
229
230                // Elements måste spegla valt belopp/återkommande innan submit + confirm
231                syncStripeElements();
232
233                const submitResult = await elements.submit();
234                if (submitResult.error) {
235                    throw new Error(submitResult.error.message);
236                }
237
238                const response = await $.ajax({
239                    url: wpgParams.ajaxUrl,
240                    method: 'POST',
241                    data: paymentData
242                });
243
244                if (!response.success || !response.data.client_secret) {
245                    const fallbackMsg = wpgParams.i18n ? wpgParams.i18n.could_not_init_payment : 'Could not initialize payment';
246                    throw new Error(response.data.error || fallbackMsg);
247                }
248
249                const returnUrlBase = response.data.redirect_url || window.location.href;
250                const returnUrl = returnUrlBase + (returnUrlBase.includes('?') ? '&' : '?') + 'payment=success';
251
252                const stripeResult = await stripe.confirmPayment({
253                    elements: elements,
254                    clientSecret: response.data.client_secret,
255                    confirmParams: {
256                        return_url: returnUrl,
257                        payment_method_data: {
258                            billing_details: {
259                                name: fullName,
260                                email: email
261                            }
262                        }
263                    },
264                    redirect: 'if_required'
265                });
266
267                const stripeError = stripeResult.error;
268                const paymentIntent = stripeResult.paymentIntent;
269
270                if (stripeError || !paymentIntent || paymentIntent.status !== 'succeeded') {
271                    await $.ajax({
272                        url: wpgParams.ajaxUrl,
273                        method: 'POST',
274                        data: {
275                            action: 'wpg_update_payment_status',
276                            payment_id: response.data.payment_id,
277                            status: 'failed',
278                            error_message: stripeError ? stripeError.message : 'Payment was not successful',
279                            nonce: wpgParams.nonce
280                        }
281                    });
282
283                    const fallbackMsg = wpgParams.i18n ? wpgParams.i18n.payment_not_successful : 'Payment was not successful. Please try again.';
284                    throw new Error(stripeError ? stripeError.message : fallbackMsg);
285                }
286
287                try {
288                    await $.ajax({
289                        url: wpgParams.ajaxUrl,
290                        method: 'POST',
291                        data: {
292                            action: 'wpg_update_payment_status',
293                            payment_id: response.data.payment_id,
294                            status: 'completed',
295                            nonce: wpgParams.nonce
296                        }
297                    });
298                } catch (statusErr) {
299                    console.error('Failed to update payment status to completed:', statusErr);
300                }
301
302                const statusHtml = `
303                    <div class="payment-status">
304                        <p><strong>${wpgParams.i18n ? wpgParams.i18n.payment_status_successful : 'Payment Status: Successful'}</strong></p>
305                        <p><strong>${wpgParams.i18n ? wpgParams.i18n.amount_label : 'Amount:'}</strong> ${amount} ${formCurrency}</p>
306                        <p><strong>${wpgParams.i18n ? wpgParams.i18n.payment_method_label : 'Payment Method:'}</strong> ${wpgParams.i18n ? wpgParams.i18n.payment_method_stripe : 'Credit Card (Stripe)'}</p>
307                        <p>${fmt(wpgParams.i18n ? wpgParams.i18n.redirecting_in_seconds : 'You will be redirected to the confirmation page in %d seconds...', 5)}</p>
308                    </div>
309                `;
310                showSuccess(statusHtml);
311
312                let secondsLeft = 5;
313                const countdownInterval = setInterval(() => {
314                    secondsLeft--;
315                    if (secondsLeft > 0) {
316                        $('.payment-status p:last').text(fmt(wpgParams.i18n ? wpgParams.i18n.redirecting_in_seconds : 'You will be redirected to the confirmation page in %d seconds...', secondsLeft));
317                    } else {
318                        clearInterval(countdownInterval);
319                        const redirectUrl = response.data.redirect_url;
320                        const separator = redirectUrl.includes('?') ? '&' : '?';
321                        window.location.href = redirectUrl + separator + 'payment=success';
322                    }
323                }, 1000);
324            } else if (paymentMethod === 'swish') {
325                const result = await processSwishPayment(paymentData);
326
327                if (result.success) {
328                    const isMobile = isMobileDevice();
329                    let statusHtml = `
330                        <div class="payment-status">
331                            <p><strong>${wpgParams.i18n && wpgParams.i18n.payment_status_waiting ? wpgParams.i18n.payment_status_waiting : 'Payment Status: Waiting for confirmation'}</strong></p>
332                            <p><strong>${wpgParams.i18n ? wpgParams.i18n.amount_label : 'Amount:'}</strong> ${amount} ${formCurrency}</p>
333                            <p><strong>${wpgParams.i18n ? wpgParams.i18n.payment_method_label : 'Payment Method:'}</strong> Swish</p>
334                    `;
335
336                    if (isMobile) {
337                        statusHtml += `
338                            <p>${wpgParams.i18n ? wpgParams.i18n.swish_approve_instructions : 'Please open your Swish app and approve the payment.'}</p>
339                            <p><button type="button" class="wpg-button wpg-open-swish-btn" style="margin: 15px 0; padding: 12px 24px; font-size: 16px; background: #f5821f; color: white; border: none; border-radius: 4px; cursor: pointer;">
340                                Oppna Swish-appen
341                            </button></p>
342                        `;
343                    } else {
344                        statusHtml += `<p>${wpgParams.i18n ? wpgParams.i18n.swish_approve_instructions : 'Please open your Swish app and approve the payment.'}</p>`;
345                    }
346
347                    statusHtml += `
348                            <p class="wpg-swish-wait-msg">${wpgParams.i18n ? wpgParams.i18n.payment_waiting : 'Waiting for payment...'}</p>
349                        </div>
350                    `;
351
352                    showSuccess(statusHtml);
353
354                    $(document).off('click.wpgSwish').on('click.wpgSwish', '.wpg-open-swish-btn', function() {
355                        $('.wpg-swish-wait-msg').html('<span class="spinner"></span> ' + (wpgParams.i18n && wpgParams.i18n.checking_payment ? wpgParams.i18n.checking_payment : 'Checking payment status...'));
356                        openSwishApp();
357                    });
358
359                    startSwishPolling(result.payment_id, result.redirect_url);
360                } else {
361                    throw new Error(result.error || (wpgParams.i18n ? wpgParams.i18n.payment_failed_generic : 'Payment failed'));
362                }
363            }
364        } catch (error) {
365            showError(error.message || (wpgParams.i18n ? wpgParams.i18n.processing_error_generic : 'An error occurred while processing your payment'));
366        } finally {
367            submitBtn.text(submitBtnDefaultText).removeClass('loading').prop('disabled', false);
368        }
369    });
370
371    $('.wpg-amount-btn').on('click', function() {
372        $('.wpg-amount-btn').removeClass('active');
373        $(this).addClass('active');
374        $('#wpg-custom-amount').val('');
375        updateAmountSummary();
376        syncStripeElements();
377        setProgress(2);
378    });
379
380    $('#wpg-custom-amount').on('input', function() {
381        $('.wpg-amount-btn').removeClass('active');
382        updateAmountSummary();
383        syncStripeElements();
384        setProgress(2);
385    });
386
387    $('#wpg-full-name, #wpg-email').on('input', function() {
388        const nameVal = $('#wpg-full-name').val().trim();
389        const emailVal = $('#wpg-email').val().trim();
390        if (nameVal && validateEmail(emailVal)) {
391            setProgress(3);
392        } else {
393            setProgress(2);
394        }
395    });
396
397    $('input[name="payment_method"]').on('change', function() {
398        const method = $(this).val();
399        const cardElement = $('#stripe-card-element');
400
401        if (method === 'stripe') {
402            cardElement.show();
403            if (paymentElement && typeof paymentElement.mount === 'function') {
404                try {
405                    paymentElement.mount('#stripe-card-element');
406                } catch (e) {
407                    console.error('Error mounting Stripe payment element:', e);
408                }
409            }
410        } else {
411            if (paymentElement && typeof paymentElement.unmount === 'function') {
412                try {
413                    paymentElement.unmount();
414                } catch (e) {
415                    console.error('Error unmounting Stripe payment element:', e);
416                }
417            }
418            cardElement.hide();
419        }
420
421        highlightPaymentMethod();
422        setProgress(3);
423    });
424
425    if (stripe && $('input[name="payment_method"]:checked').val() === 'stripe') {
426        const cardElement = $('#stripe-card-element');
427        cardElement.show();
428        if (paymentElement && typeof paymentElement.mount === 'function') {
429            try {
430                paymentElement.mount('#stripe-card-element');
431            } catch (e) {
432                console.error('Error mounting Stripe payment element:', e);
433            }
434        }
435    }
436
437    function isMobileDevice() {
438        return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
439    }
440
441    function openSwishApp() {
442        window.location.href = 'swish://';
443    }
444
445    window.startSwishPolling = function(paymentId, redirectUrl) {
446        const pollInterval = 3000;
447        const timeoutDuration = 180 * 1000;
448        const startTime = Date.now();
449        const intervalId = setInterval(async () => {
450            try {
451                if (Date.now() - startTime > timeoutDuration) {
452                    clearInterval(intervalId);
453                    try {
454                        await $.ajax({
455                            url: wpgParams.ajaxUrl,
456                            method: 'POST',
457                            data: {
458                                action: 'wpg_update_payment_status',
459                                payment_id: paymentId,
460                                status: 'cancelled',
461                                error_message: wpgParams.i18n ? wpgParams.i18n.payment_timed_out : 'Payment timed out',
462                                nonce: wpgParams.nonce
463                            }
464                        });
465                    } catch (timeoutErr) {
466                        console.error('Error updating payment status on timeout:', timeoutErr);
467                    }
468                    showError(wpgParams.i18n ? wpgParams.i18n.payment_timed_out : 'Payment timed out. Please try again.');
469                    return;
470                }
471
472                const statusResponse = await $.ajax({
473                    url: wpgParams.ajaxUrl,
474                    method: 'POST',
475                    data: {
476                        action: 'wpg_check_swish_status',
477                        payment_id: paymentId,
478                        nonce: wpgParams.nonce
479                    }
480                });
481
482                if (statusResponse.success) {
483                    const status = statusResponse.data.status;
484                    if (status === 'paid' || status === 'completed') {
485                        clearInterval(intervalId);
486                        $('.wpg-swish-wait-msg').text(wpgParams.i18n ? wpgParams.i18n.payment_confirmed_redirecting : 'Payment confirmed! Redirecting...');
487                        setTimeout(() => {
488                            if (redirectUrl) {
489                                const separator = redirectUrl.includes('?') ? '&' : '?';
490                                window.location.href = redirectUrl + separator + 'payment=success';
491                            } else {
492                                const currentUrl = new URL(window.location.href);
493                                currentUrl.searchParams.set('payment', 'success');
494                                window.location.href = currentUrl.toString();
495                            }
496                        }, 2000);
497                    } else if (status === 'failed' || status === 'cancelled') {
498                        clearInterval(intervalId);
499                        showError(fmt(wpgParams.i18n ? wpgParams.i18n.payment_not_completed : 'Payment was not completed (status: %s). Please try again.', status));
500                    }
501                }
502            } catch (err) {
503                console.error('Polling error:', err);
504            }
505        }, pollInterval);
506    };
507
508    toggleSwishFields();
509    highlightPaymentMethod();
510    updateAmountSummary();
511    setProgress(1);
512});

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.