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.