| 1 |
/* global jQuery */ |
| 2 |
/** |
| 3 |
* SDK adapter for the classic checkout form, using its existing submit button. |
| 4 |
* Load after wc-checkout and vipps-gw. PHP enqueue instructions: Payment/README.md. |
| 5 |
* jQuery is intentional: both classic checkout and pay-for-order use WooCommerce's |
| 6 |
* legacy jQuery form/events, including pay-for-order pages reached from Blocks. |
| 7 |
* The order-pay branch uses WooCommerce's existing-order Store API route. Never |
| 8 |
* send an order-pay form to the cart checkout endpoint. |
| 9 |
*/ |
| 10 |
jQuery(($) => { |
| 11 |
'use strict'; |
| 12 |
const boundSubmitButtons = new WeakSet(); |
| 13 |
|
| 14 |
/** Keep the native WooCommerce submit control as the non-Vipps fallback. */ |
| 15 |
function updateVippsButton($form) { |
| 16 |
const isVipps = $form.find('input[name="payment_method"]:checked').val() === 'vipps'; |
| 17 |
const $vippsButton = $form.find('#vipps-classic-checkout-submit').first(); |
| 18 |
const $normalButton = $form.find('#place_order').first(); |
| 19 |
if (!$vippsButton.length || !$normalButton.length) return; |
| 20 |
$vippsButton.toggleClass('hidden', !isVipps) |
| 21 |
.prop('disabled', !isVipps) |
| 22 |
.attr('aria-hidden', isVipps ? 'false' : 'true') |
| 23 |
.attr('tabindex', isVipps ? '0' : '-1'); |
| 24 |
$normalButton.toggleClass('hidden', isVipps) |
| 25 |
.attr('aria-hidden', isVipps ? 'true' : 'false'); |
| 26 |
} |
| 27 |
|
| 28 |
function bindVippsButton($form) { |
| 29 |
const refresh = () => { |
| 30 |
updateVippsButton($form); |
| 31 |
const button = $form.find('#vipps-classic-checkout-submit').first()[0]; |
| 32 |
if (!button || boundSubmitButtons.has(button)) return; |
| 33 |
|
| 34 |
// A click inside the nested web component may not activate the |
| 35 |
// outer native submit button in every browser. Capture that click |
| 36 |
// and use one explicit form submission instead of its default action. |
| 37 |
button.addEventListener('click', (event) => { |
| 38 |
if (button.disabled) return; |
| 39 |
event.preventDefault(); |
| 40 |
const form = button.form || $form[0]; |
| 41 |
if (typeof form.requestSubmit === 'function') { |
| 42 |
form.requestSubmit(button.form === form ? button : undefined); |
| 43 |
} else { |
| 44 |
$form.trigger('submit'); |
| 45 |
} |
| 46 |
}, true); |
| 47 |
boundSubmitButtons.add(button); |
| 48 |
}; |
| 49 |
$form.on('change.vippsButtons', 'input[name="payment_method"]', refresh); |
| 50 |
$(document.body).on('payment_method_selected.vippsButtons updated_checkout.vippsButtons', refresh); |
| 51 |
refresh(); |
| 52 |
} |
| 53 |
|
| 54 |
if (document.body.classList.contains('woocommerce-order-pay')) { |
| 55 |
initOrderPay($); |
| 56 |
return; |
| 57 |
} |
| 58 |
|
| 59 |
/** |
| 60 |
* Pay an existing order through POST /wc/store/v1/checkout/{id}. PHP must |
| 61 |
* localize VippsOrderPayConfig with an authorized order id, addresses, and |
| 62 |
* a Store API nonce. The order key/email are sent for guest authorization. |
| 63 |
*/ |
| 64 |
function initOrderPay($) { |
| 65 |
const config = window.VippsOrderPayConfig || {}; |
| 66 |
const $form = $('#order_review').first(); |
| 67 |
if (!$form.length || !config.orderId || !config.endpoint || |
| 68 |
!config.billingAddress || !config.shippingAddress || |
| 69 |
!window.vipps?.trigger || !window.vipps?.host || |
| 70 |
typeof window.ensureVippsWidgetHostStarted !== 'function' || |
| 71 |
typeof window.setVippsPaymentBusy !== 'function' || |
| 72 |
$form.data('vippsOrderPay')) { |
| 73 |
return; |
| 74 |
} |
| 75 |
$form.data('vippsOrderPay', true); |
| 76 |
bindVippsButton($form); |
| 77 |
|
| 78 |
const owner = 'order-pay'; |
| 79 |
let attempt = null; |
| 80 |
|
| 81 |
function message(key, fallback) { |
| 82 |
return window.VippsLocale?.[key] || fallback; |
| 83 |
} |
| 84 |
|
| 85 |
function showWooNotice(text) { |
| 86 |
const $wrapper = $form.closest('.woocommerce').find('.woocommerce-notices-wrapper').first(); |
| 87 |
const $notice = $('<ul>').addClass('woocommerce-error').attr('role', 'alert') |
| 88 |
.append($('<li>').text(text)); |
| 89 |
$form.find('.woocommerce-NoticeGroup-checkout, .vipps-order-pay-notice').remove(); |
| 90 |
const $group = $('<div>').addClass('woocommerce-NoticeGroup woocommerce-NoticeGroup-checkout vipps-order-pay-notice') |
| 91 |
.append($notice); |
| 92 |
($wrapper.length ? $wrapper : $form).first().prepend($group); |
| 93 |
$group.trigger('focus'); |
| 94 |
if (typeof $.scroll_to_notices === 'function') { |
| 95 |
$.scroll_to_notices($group); |
| 96 |
} else if ($group.offset()) { |
| 97 |
$('html, body').stop(true).animate({ scrollTop: $group.offset().top - 100 }, 400); |
| 98 |
} |
| 99 |
} |
| 100 |
|
| 101 |
function responseMessage(xhr, fallback) { |
| 102 |
const response = xhr?.responseJSON || xhr?.response || xhr; |
| 103 |
return response?.message || response?.data?.message || response?.messages || fallback; |
| 104 |
} |
| 105 |
|
| 106 |
function setBusy(busy) { |
| 107 |
window.setVippsPaymentBusy(busy, owner); |
| 108 |
$form.toggleClass('processing', busy).attr('aria-busy', busy ? 'true' : 'false'); |
| 109 |
if (!busy && typeof $form.unblock === 'function') { |
| 110 |
$form.unblock(); |
| 111 |
} |
| 112 |
$form.find(':input').prop('disabled', busy); |
| 113 |
} |
| 114 |
|
| 115 |
function urlFromResponse(response) { |
| 116 |
const result = response?.payment_result || response?.paymentResult || {}; |
| 117 |
const details = result.payment_details || result.paymentDetails || []; |
| 118 |
const normalized = Array.isArray(details) |
| 119 |
? details.reduce((out, item) => { if (item?.key) out[item.key] = item.value; return out; }, {}) |
| 120 |
: details; |
| 121 |
return normalized.vippsPaymentUrl || normalized.vipps_payment_url || |
| 122 |
result.redirect_url || result.redirectUrl || response?.redirect || ''; |
| 123 |
} |
| 124 |
|
| 125 |
function validUrl(value) { |
| 126 |
try { |
| 127 |
const url = new URL(value, window.location.href); |
| 128 |
return ['https:', 'http:'].includes(url.protocol) ? url.href : ''; |
| 129 |
} catch { |
| 130 |
return ''; |
| 131 |
} |
| 132 |
} |
| 133 |
|
| 134 |
function release() { |
| 135 |
if (!attempt) return; |
| 136 |
attempt = null; |
| 137 |
setBusy(false); |
| 138 |
$form.find(':input').prop('disabled', false); |
| 139 |
$form.removeClass('processing').removeAttr('aria-busy'); |
| 140 |
} |
| 141 |
|
| 142 |
function navigate(url, successful = false) { |
| 143 |
if (successful && url) { |
| 144 |
// The widget is about to close, but this page remains visible until navigation. |
| 145 |
window.showVippsSuccessRedirectOverlay('order-pay-redirect'); |
| 146 |
window.setVippsPaymentBusy(false, owner); |
| 147 |
} else { |
| 148 |
setBusy(false); |
| 149 |
} |
| 150 |
window.location.assign(url || window.location.href); |
| 151 |
} |
| 152 |
|
| 153 |
function requestPayment(current) { |
| 154 |
const paymentData = current.paymentData; |
| 155 |
|
| 156 |
return $.ajax({ |
| 157 |
type: 'POST', |
| 158 |
url: config.endpoint, |
| 159 |
data: JSON.stringify({ |
| 160 |
key: config.orderKey || undefined, |
| 161 |
billing_email: config.billingEmail || undefined, |
| 162 |
billing_address: config.billingAddress, |
| 163 |
shipping_address: config.shippingAddress, |
| 164 |
payment_method: 'vipps', |
| 165 |
payment_data: paymentData |
| 166 |
}), |
| 167 |
contentType: 'application/json', |
| 168 |
dataType: 'json', |
| 169 |
headers: config.nonce ? { Nonce: config.nonce } : undefined |
| 170 |
}).then((response) => { |
| 171 |
if (attempt !== current) return null; |
| 172 |
const url = validUrl(urlFromResponse(response)); |
| 173 |
if (!url) { |
| 174 |
release(); |
| 175 |
showWooNotice(message('missingPaymentUrl', 'The order did not return a payment URL.')); |
| 176 |
return null; |
| 177 |
} |
| 178 |
current.phase = 'payment'; |
| 179 |
current.paymentUrl = url; |
| 180 |
setBusy(false); |
| 181 |
if (current.closed) { |
| 182 |
navigate(''); |
| 183 |
return null; |
| 184 |
} |
| 185 |
return url; |
| 186 |
}).catch((xhr) => { |
| 187 |
if (attempt !== current) return null; |
| 188 |
release(); |
| 189 |
showWooNotice(responseMessage(xhr, message('orderPayFailed', 'Could not start payment for this order. Please try again.'))); |
| 190 |
return null; |
| 191 |
}); |
| 192 |
} |
| 193 |
|
| 194 |
function submit(event) { |
| 195 |
if (attempt) return false; |
| 196 |
if ($form.find('[name="payment_method"]:checked').val() !== 'vipps') return true; |
| 197 |
event.preventDefault(); |
| 198 |
|
| 199 |
if ($form.find('[name="terms-field"]').length && !$form.find('[name="terms"]:checked').length) { |
| 200 |
setBusy(false); |
| 201 |
$form.removeClass('processing').removeAttr('aria-busy'); |
| 202 |
showWooNotice(message('termsRequired', 'Please read and accept the terms and conditions to proceed with your order.')); |
| 203 |
$form.find('[name="terms"]').first().trigger('focus'); |
| 204 |
return false; |
| 205 |
} |
| 206 |
|
| 207 |
const paymentData = $form.serializeArray() |
| 208 |
.filter(({ name }) => !['payment_method', 'woocommerce-pay-nonce', '_wpnonce'].includes(name)) |
| 209 |
.map(({ name, value }) => ({ key: name, value })); |
| 210 |
paymentData.push({ key: 'vipps_checkout_widget', value: '1' }); |
| 211 |
attempt = { phase: 'submitting', closed: false, paymentUrl: '', paymentData }; |
| 212 |
const current = attempt; |
| 213 |
setBusy(true); |
| 214 |
try { |
| 215 |
window.ensureVippsWidgetHostStarted(); |
| 216 |
current.trigger = window.vipps.trigger(() => requestPayment(current)) |
| 217 |
.on('success', (close, url) => { |
| 218 |
current.phase = 'leaving'; |
| 219 |
navigate(url || current.paymentUrl, Boolean(url)); |
| 220 |
close(); |
| 221 |
}) |
| 222 |
.on('cancel', (close, url) => { close(); navigate(url || window.location.href); }) |
| 223 |
.on('close', () => { |
| 224 |
if (attempt !== current) return; |
| 225 |
if (current.phase === 'leaving') return; |
| 226 |
if (current.phase === 'submitting') { |
| 227 |
current.closed = true; |
| 228 |
} else { |
| 229 |
navigate(''); |
| 230 |
} |
| 231 |
}) |
| 232 |
.on('error', () => { |
| 233 |
if (current.paymentUrl) navigate(current.paymentUrl); |
| 234 |
else if (current.phase === 'submitting') current.closed = true; |
| 235 |
else release(); |
| 236 |
}); |
| 237 |
Promise.resolve(current.trigger.open()).catch(() => { |
| 238 |
if (attempt === current && !current.paymentUrl) { |
| 239 |
release(); |
| 240 |
showWooNotice(message('widgetStartFailed', 'Could not start the payment. Please try again.')); |
| 241 |
} |
| 242 |
}); |
| 243 |
} catch { |
| 244 |
release(); |
| 245 |
showWooNotice(message('widgetStartFailed', 'Could not start the payment. Please try again.')); |
| 246 |
} |
| 247 |
return false; |
| 248 |
} |
| 249 |
|
| 250 |
$form.on('submit.vippsOrderPay', submit); |
| 251 |
// Returning through browser history restores the covered page and its |
| 252 |
// disabled controls; reload to reconcile the order's current status. |
| 253 |
window.addEventListener('pageshow', (event) => { |
| 254 |
if (event.persisted && attempt?.phase === 'leaving') { |
| 255 |
window.location.reload(); |
| 256 |
} |
| 257 |
}); |
| 258 |
} |
| 259 |
|
| 260 |
const params = window.wc_checkout_params; |
| 261 |
const $form = $('form.checkout').first(); |
| 262 |
if (!params?.checkout_url || !$form.length || document.querySelector('.wc-block-checkout')) { |
| 263 |
return; |
| 264 |
} |
| 265 |
if ($form.data('vippsClassicCheckout')) { |
| 266 |
return; |
| 267 |
} |
| 268 |
$form.data('vippsClassicCheckout', true); |
| 269 |
bindVippsButton($form); |
| 270 |
|
| 271 |
const owner = 'classic-checkout'; |
| 272 |
let activeAttempt = null; |
| 273 |
let updatingCheckout = false; |
| 274 |
let handedOff = false; |
| 275 |
|
| 276 |
/** Keep new messages in the gateway's existing localization dictionary. */ |
| 277 |
function translate(key, fallback) { |
| 278 |
return window.VippsLocale?.[key] || fallback; |
| 279 |
} |
| 280 |
|
| 281 |
/** Only server-provided checkout notices are HTML; local messages are escaped. */ |
| 282 |
function errorMarkup(message) { |
| 283 |
return $('<div>').addClass('woocommerce-error').text(message).prop('outerHTML'); |
| 284 |
} |
| 285 |
|
| 286 |
/** Use WooCommerce's notice/focus handling when supplied by its submit hook. */ |
| 287 |
function showError(attempt, messages) { |
| 288 |
if (typeof attempt.checkout?.submit_error === 'function') { |
| 289 |
attempt.checkout.submit_error(messages); |
| 290 |
} else { |
| 291 |
$form.find('.woocommerce-NoticeGroup-checkout').remove(); |
| 292 |
const $notice = $('<div>') |
| 293 |
.addClass('woocommerce-NoticeGroup woocommerce-NoticeGroup-checkout') |
| 294 |
.attr({ role: 'alert', tabindex: '-1' }) |
| 295 |
.html(messages); |
| 296 |
$form.prepend($notice); |
| 297 |
$notice.trigger('focus'); |
| 298 |
$(document.body).trigger('checkout_error', [messages]); |
| 299 |
} |
| 300 |
} |
| 301 |
|
| 302 |
/** |
| 303 |
* Snapshot before disabling fields. Keep them locked through the SDK session |
| 304 |
* so edits or another payment method cannot submit a competing order/payment. |
| 305 |
* Remember prior disabled states, including fields replaced by checkout updates. |
| 306 |
*/ |
| 307 |
function lockForm(attempt) { |
| 308 |
$form.addClass('processing').attr('aria-busy', 'true'); |
| 309 |
$form.find(':input').each(function () { |
| 310 |
if (!attempt.fields.has(this)) { |
| 311 |
attempt.fields.set(this, this.disabled); |
| 312 |
} |
| 313 |
this.disabled = true; |
| 314 |
}); |
| 315 |
} |
| 316 |
|
| 317 |
/** Release the shared overlay independently of the form's submission guard. */ |
| 318 |
function stopSpinner() { |
| 319 |
window.setVippsPaymentBusy(false, owner); |
| 320 |
$form.removeAttr('aria-busy'); |
| 321 |
} |
| 322 |
|
| 323 |
/** Known checkout failures can be retried without discarding form values. */ |
| 324 |
function release(attempt) { |
| 325 |
if (activeAttempt !== attempt) { |
| 326 |
return; |
| 327 |
} |
| 328 |
activeAttempt = null; |
| 329 |
stopSpinner(); |
| 330 |
$form.removeClass('processing'); |
| 331 |
attempt.fields.forEach((disabled, field) => { |
| 332 |
field.disabled = disabled; |
| 333 |
}); |
| 334 |
// Clear our reference first: close() may synchronously emit another event. |
| 335 |
attempt.trigger?.close(); |
| 336 |
} |
| 337 |
|
| 338 |
/** |
| 339 |
* A timeout/malformed success may hide an already-created payment. Do not |
| 340 |
* automatically POST again or silently enable another submission. Reloading |
| 341 |
* lets WooCommerce restore its session/order state before the user retries. |
| 342 |
*/ |
| 343 |
function requireReload(attempt, message) { |
| 344 |
if (activeAttempt !== attempt) { |
| 345 |
return; |
| 346 |
} |
| 347 |
attempt.phase = 'uncertain'; |
| 348 |
stopSpinner(); |
| 349 |
const link = $('<a>').attr('href', window.location.href) |
| 350 |
.text(translate('reloadCheckout', 'Reload checkout')); |
| 351 |
showError(attempt, errorMarkup(message) + link.prop('outerHTML')); |
| 352 |
// submit_error removes processing; restore the guard for this uncertain result. |
| 353 |
lockForm(attempt); |
| 354 |
$form.removeAttr('aria-busy'); |
| 355 |
attempt.trigger?.close(); |
| 356 |
} |
| 357 |
|
| 358 |
/** Navigate once, retaining locked state until the page has actually left. */ |
| 359 |
function navigate(attempt, url, successful = false) { |
| 360 |
if (activeAttempt !== attempt || attempt.phase === 'leaving') { |
| 361 |
return; |
| 362 |
} |
| 363 |
attempt.phase = 'leaving'; |
| 364 |
handedOff = true; |
| 365 |
if (successful && url) { |
| 366 |
// Closing the widget exposes this page while the completed order loads. |
| 367 |
window.showVippsSuccessRedirectOverlay('classic-redirect'); |
| 368 |
} |
| 369 |
stopSpinner(); |
| 370 |
if (url) { |
| 371 |
window.location.assign(url); |
| 372 |
} else { |
| 373 |
window.location.reload(); |
| 374 |
} |
| 375 |
} |
| 376 |
|
| 377 |
/** A close during submission waits for the outstanding request, not another POST. */ |
| 378 |
function finishWidget(attempt, close, redirectUrl, successful = false) { |
| 379 |
if (activeAttempt !== attempt || ['uncertain', 'leaving'].includes(attempt.phase)) { |
| 380 |
return; |
| 381 |
} |
| 382 |
if (attempt.phase === 'submitting') { |
| 383 |
attempt.closed = true; |
| 384 |
if (close) { |
| 385 |
close(); |
| 386 |
} |
| 387 |
return; |
| 388 |
} |
| 389 |
// Set the phase before close(), which may emit a nested close event. |
| 390 |
navigate(attempt, redirectUrl, successful); |
| 391 |
if (close) { |
| 392 |
close(); |
| 393 |
} |
| 394 |
} |
| 395 |
|
| 396 |
/** |
| 397 |
* If the SDK fails after a request was sent, wait for its result. A known URL |
| 398 |
* can use full-page fallback without creating a second order or payment. |
| 399 |
*/ |
| 400 |
function sdkError(attempt) { |
| 401 |
if (activeAttempt !== attempt || ['uncertain', 'leaving'].includes(attempt.phase)) { |
| 402 |
return; |
| 403 |
} |
| 404 |
if (attempt.paymentUrl) { |
| 405 |
navigate(attempt, attempt.paymentUrl); |
| 406 |
} else if (attempt.requestSent) { |
| 407 |
attempt.sdkFailed = true; |
| 408 |
} else { |
| 409 |
release(attempt); |
| 410 |
showError(attempt, errorMarkup(translate('widgetStartFailed', 'Could not start the payment. Please try again.'))); |
| 411 |
} |
| 412 |
} |
| 413 |
|
| 414 |
/** The response URL is supplied by our gateway; reject unusable URL schemes. */ |
| 415 |
function getPaymentUrl(result) { |
| 416 |
const value = result.vippsPaymentUrl || result.redirect; |
| 417 |
if (typeof value !== 'string' || !value.trim()) { |
| 418 |
return null; |
| 419 |
} |
| 420 |
try { |
| 421 |
const url = new URL(value, window.location.href); |
| 422 |
return ['https:', 'http:'].includes(url.protocol) ? url.href : null; |
| 423 |
} catch { |
| 424 |
return null; |
| 425 |
} |
| 426 |
} |
| 427 |
|
| 428 |
/** |
| 429 |
* The SDK calls this resolver from open(). WooCommerce still creates/resumes |
| 430 |
* and validates the order; only the browser handling of the result is replaced. |
| 431 |
*/ |
| 432 |
async function createSession(attempt) { |
| 433 |
let result; |
| 434 |
try { |
| 435 |
attempt.requestSent = true; |
| 436 |
result = await $.ajax({ |
| 437 |
type: 'POST', |
| 438 |
url: params.checkout_url, |
| 439 |
data: attempt.formData, |
| 440 |
dataType: 'json' |
| 441 |
}); |
| 442 |
} catch { |
| 443 |
requireReload(attempt, translate('checkoutResultUnknown', |
| 444 |
'The checkout result could not be confirmed. Check your order before trying again.')); |
| 445 |
return null; |
| 446 |
} |
| 447 |
|
| 448 |
if (activeAttempt !== attempt) { |
| 449 |
return null; |
| 450 |
} |
| 451 |
if (result?.result === 'failure') { |
| 452 |
release(attempt); |
| 453 |
if (result.reload === true) { |
| 454 |
window.location.reload(); |
| 455 |
return null; |
| 456 |
} |
| 457 |
showError(attempt, typeof result.messages === 'string' && result.messages |
| 458 |
? result.messages |
| 459 |
: errorMarkup(translate('checkoutFailed', 'Checkout failed. Please check your details and try again.'))); |
| 460 |
if (result.refresh === true) { |
| 461 |
$(document.body).trigger('update_checkout'); |
| 462 |
} |
| 463 |
return null; |
| 464 |
} |
| 465 |
|
| 466 |
const paymentUrl = result?.result === 'success' ? getPaymentUrl(result) : null; |
| 467 |
if (!paymentUrl) { |
| 468 |
requireReload(attempt, translate('missingPaymentUrl', |
| 469 |
'Checkout did not return a payment URL. Reload checkout to check your order before trying again.')); |
| 470 |
return null; |
| 471 |
} |
| 472 |
|
| 473 |
attempt.paymentUrl = paymentUrl; |
| 474 |
attempt.phase = 'payment'; |
| 475 |
handedOff = true; |
| 476 |
stopSpinner(); |
| 477 |
if (attempt.closed || attempt.sdkFailed) { |
| 478 |
navigate(attempt, attempt.closed ? null : paymentUrl); |
| 479 |
return null; |
| 480 |
} |
| 481 |
return paymentUrl; |
| 482 |
} |
| 483 |
|
| 484 |
/** |
| 485 |
* This must return false synchronously. Returning a Promise would allow core |
| 486 |
* checkout.js to also POST and redirect. Do not intercept its success event: |
| 487 |
* returning false there enters core's generic checkout error path. |
| 488 |
*/ |
| 489 |
function submitVipps(event, checkout) { |
| 490 |
if (event.result === false) { |
| 491 |
return false; |
| 492 |
} |
| 493 |
if (activeAttempt) { |
| 494 |
event.stopImmediatePropagation(); |
| 495 |
return false; |
| 496 |
} |
| 497 |
if ($form.find('input[name="payment_method"]:checked').val() !== 'vipps' || |
| 498 |
!window.vipps?.trigger || !window.vipps?.host || |
| 499 |
typeof window.ensureVippsWidgetHostStarted !== 'function' || |
| 500 |
typeof window.setVippsPaymentBusy !== 'function') { |
| 501 |
return; |
| 502 |
} |
| 503 |
|
| 504 |
event.stopImmediatePropagation(); |
| 505 |
if (updatingCheckout || (checkout?.xhr && checkout.xhr.readyState !== 4)) { |
| 506 |
showError({ checkout }, errorMarkup(translate('checkoutUpdating', |
| 507 |
'Checkout totals are updating. Please wait, then place your order again.'))); |
| 508 |
// Core's submit handler clears its update timer before invoking us. |
| 509 |
// Restart it so an update that had been queued is not lost. |
| 510 |
$(document.body).trigger('update_checkout'); |
| 511 |
return false; |
| 512 |
} |
| 513 |
|
| 514 |
const fields = $form.serializeArray().filter(({ name }) => name !== 'vipps_checkout_widget'); |
| 515 |
fields.push({ name: 'vipps_checkout_widget', value: '1' }); |
| 516 |
const attempt = { |
| 517 |
checkout, |
| 518 |
formData: $.param(fields).replace(/'/g, '%27'), |
| 519 |
fields: new Map(), |
| 520 |
phase: 'submitting', |
| 521 |
requestSent: false, |
| 522 |
trigger: null |
| 523 |
}; |
| 524 |
activeAttempt = attempt; |
| 525 |
lockForm(attempt); |
| 526 |
$form.find('.woocommerce-NoticeGroup-checkout').remove(); |
| 527 |
|
| 528 |
try { |
| 529 |
window.setVippsPaymentBusy(true, owner); |
| 530 |
window.ensureVippsWidgetHostStarted(); |
| 531 |
attempt.trigger = window.vipps.trigger(() => createSession(attempt)) |
| 532 |
.on('success', (close, url) => finishWidget(attempt, close, url, true)) |
| 533 |
.on('cancel', (close, url) => finishWidget(attempt, close, url)) |
| 534 |
.on('close', () => finishWidget(attempt)) |
| 535 |
.on('error', () => sdkError(attempt)); |
| 536 |
Promise.resolve(attempt.trigger.open()).catch(() => sdkError(attempt)); |
| 537 |
} catch { |
| 538 |
sdkError(attempt); |
| 539 |
} |
| 540 |
return false; |
| 541 |
} |
| 542 |
|
| 543 |
$form.on('checkout_place_order_vipps.vippsClassic', submitVipps); |
| 544 |
$(document.body) |
| 545 |
.on('update_checkout.vippsClassic', () => { updatingCheckout = true; }) |
| 546 |
.on('updated_checkout.vippsClassic', () => { |
| 547 |
updatingCheckout = false; |
| 548 |
if (activeAttempt) { |
| 549 |
lockForm(activeAttempt); |
| 550 |
if (activeAttempt.phase !== 'submitting') { |
| 551 |
$form.removeAttr('aria-busy'); |
| 552 |
} |
| 553 |
} |
| 554 |
}); |
| 555 |
|
| 556 |
// A full navigation reconstructs the page normally. A bfcache restoration |
| 557 |
// retains this controller, so reload once to reconcile the pending/paid order. |
| 558 |
window.addEventListener('pageshow', (event) => { |
| 559 |
if (event.persisted && (activeAttempt || handedOff)) { |
| 560 |
window.location.reload(); |
| 561 |
} |
| 562 |
}); |
| 563 |
}); |
| 564 |
|