PluginProbe
Pay with Vipps and MobilePay for WooCommerce / 6.0.2
Pay with Vipps and MobilePay for WooCommerce v6.0.2
6.2.3 6.2.2 6.2.1 6.2.0 6.1.10 6.1.9 6.1.8 6.1.7 6.1.6 6.1.5 6.1.4 6.1.3 6.1.2 6.1.1 6.1.0 6.0.5 6.0.4 6.0.3 6.0.2 6.0.1 6.0.0 5.4.3 5.4.2 5.4.1 5.4.0 All 185 releases
woo-vipps / payment / js / vipps-checkout.js

vipps-checkout.js in Pay with Vipps and MobilePay for WooCommerce 6.0.2, at payment/js/vipps-checkout.js

415 lines 18.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /*
2
3 This file is part of the plugin Pay with Vipps and MobilePay for WooCommerce
4 Copyright (c) 2019 WP-Hosting AS
5
6 MIT License
7
8 Copyright (c) 2019 WP-Hosting AS
9
10 Permission is hereby granted, free of charge, to any person obtaining a copy
11 of this software and associated documentation files (the "Software"), to deal
12 in the Software without restriction, including without limitation the rights
13 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14 copies of the Software, and to permit persons to whom the Software is
15 furnished to do so, subject to the following conditions:
16
17 The above copyright notice and this permission notice shall be included in all
18 copies or substantial portions of the Software.
19
20 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26 SOFTWARE.
27
28 */
29
30
31
32 jQuery( document ).ready( function() {
33 // This gets loaded conditionally when the Vipps Checkout page is used IOK 2021-08-25
34 var pollingdone=false;
35 var polling=false;
36 var sessionStarted = false;
37 var listening=false;
38 var initiating=false;
39
40 // This will hold the Vipps Checkout object
41 let VCO = null;
42
43 // Which need to be locked/unlocked before we can modify the session. And the session
44 // can only be locked from a screen like this. IOK 2025-04-24
45 function unlockSession() {
46 if (VCO) {
47 return VCO.unlock();
48 }
49 }
50 function lockSession(timeout=0) {
51 if (VCO) {
52 const lockPromise = VCO.lock();
53 if (timeout > 0) {
54 setTimeout(unlockSession, timeout);
55 }
56 return lockPromise;
57 }
58 }
59
60 // Global function defined for widgets to be able to safely do callbacks to modify the order. IOK 2025-05-15
61 function wooVippsCheckoutCallback( action, args ) {
62 let successhandler = args['success'] ? args['success'] : function (data) { console.log ("Callback Success: %j", data); };
63 let errorhandler = args['error'] ? args['error'] : function (data) { console.log ("Callback Error: %j", data); };
64 let lock_held = args['lock_held'] ? 1 : 0;
65 let callbackdata = args['data'] ? args['data'] : {};
66
67 let data = { 'action': 'vipps_checkout_callback', 'callback_action': action, 'vipps_checkout_sec' : jQuery('#vipps_checkout_sec').val(),
68 'orderid' : jQuery('#vippsorderid').val(),
69 callbackdata, lock_held};
70
71 // Abstracted out so we can pass it to the lock promise if neccessary IOK 2025-05-16
72 function doTheCall() {
73 jQuery("body").css("cursor", "progress");
74 jQuery("body").addClass('processing');
75
76 jQuery.ajax(VippsConfig['vippsajaxurl'],
77 { cache:false,
78 timeout: 0,
79 dataType:'json',
80 headers: {"Accept-Language": `${VippsConfig['vippslocale']}, *`},
81 method: 'POST',
82 data: data,
83 error: function (xhr, statustext, error) {
84 return errorhandler({error: statustext});
85 },
86 success: function (result,statustext, xhr) {
87 if (!result['success']) return errorhandler(result['data']);
88 return successhandler(result['data']);
89 },
90 complete: function (xhr, statustext) {
91 if (lock_held) unlockSession();
92 jQuery("body").css("cursor", "default");
93 jQuery("body").removeClass('processing');
94 }
95 });
96 }
97
98 if (lock_held) {
99 lockSession().then(doTheCall).catch( (error) => errorhandler({error: statustext}));
100 } else {
101 doTheCall();
102 }
103 }
104 // and "export" it.
105 window.wooVippsCheckoutCallback = wooVippsCheckoutCallback;
106
107 // Just in case we need to do this by button.
108 jQuery('.vipps_checkout_button.button').click(function (e) { initVippsCheckout() });
109 if (jQuery('.vipps_checkout_startdiv').length>0) {
110 // which we must if we don't have the visibility API
111 if (typeof document.hidden == "undefined") {
112 jQuery('.vipps_checkout_startdiv').css('visibility', 'visible');
113 } else {
114 document.addEventListener('visibilitychange', initWhenVisible, false);
115 initWhenVisible();
116 }
117 }
118
119
120 // Initialize the Vipps Checkout process
121 function initVippsCheckout () {
122 // Prevent multiple initializations
123 if (initiating) return;
124 initiating = true;
125
126 // Set visual indicators for processing state
127 jQuery("body").css("cursor", "progress");
128 jQuery("body").addClass('processing');
129
130 // Disable all Vipps checkout buttons
131 jQuery('.vipps_checkout_button.button').each(function () {
132 jQuery(this).addClass('disabled');
133 jQuery(this).css("cursor", "progress");
134 });
135
136 // Check cart total before proceeding with checkout NT-2024-09-07
137 // handle any errors in handleCheckoutError IOK 2024-09-09
138 return validateCart(proceedWithCheckout, handleCheckoutError);
139 }
140
141 // Check if the cart total meets the minimum required amount NT-2024-09-07
142 function validateCart(success, failure) {
143 jQuery.ajax(VippsConfig['vippsajaxurl'], {
144 cache: false,
145 dataType: 'json',
146 headers: {"Accept-Language": `${VippsConfig['vippslocale']}, *`},
147 data: { 'action': 'vipps_checkout_validate_cart' },
148 method: 'POST',
149 success: function(result) {
150 // If cart total is valid, proceed
151 if (result.success) {
152 success();
153 } else {
154 failure(result.data.message)
155 }
156 },
157 error: function(xhr, statustext, error) {
158 // Ignore any validation errors if ajax somehow breaks, but log the thing
159 console.log("Error validating cart: " + statustext);
160 success();
161 }
162 });
163 }
164
165 function iframeLoaded() {
166 // Safeguard. This would actually mostly affect *second tabs* opened, so we may not want to call it at all. Insted,
167 // we may want to see here if the session status has changed, and if not, close the page. IOK 2025-04-16
168 // Unfortunately, the session status change thing doesn't happen immediately, so this would need to be with a timeout.
169 jQuery("body").removeClass('processing');
170 }
171
172 // Called when we know the order used to represent the Vipps Session exists
173 function loadWidgets() {
174 jQuery.ajax(VippsConfig['vippsajaxurl'], {
175 data: {action: "vipps_checkout_get_widgets"},
176 type: "GET",
177 cache:false,
178 timeout: 0,
179 headers: {"Accept-Language": `${VippsConfig['vippslocale']}, *`},
180 dataType:'html',
181 error: function (eh) { console.log("error loading widgets"); },
182 success: function (data) {
183 jQuery('#vipps_checkout_widget_mount').html(data);
184 initializeWidgets();
185 jQuery('.vipps_checkout_widget_wrapper').show();
186 jQuery('body').trigger('woo-vipps-checkout-widgets-loaded');
187 }
188 });
189 }
190
191
192 // Common initializations for all widgets after load
193 function initializeWidgets() {
194 // widget accordion feature. LP 2025-05-07
195 jQuery('.vipps_checkout_widget_title.accordion').on('click', function() {
196 jQuery(this).toggleClass('active');
197 jQuery(this).next('.vipps_checkout_body').toggle();
198 });
199 // Coupon code widget button hover, using the css color classes instead of :hover. LP 2025-08-08
200 function togglePurple() {
201 jQuery(this).toggleClass('vippspurple2');
202 jQuery(this).toggleClass('vippspurple-light');
203 };
204 jQuery('.vipps_checkout_widget_button').on('mouseenter', togglePurple).on('mouseleave', togglePurple);
205 }
206
207 function proceedWithCheckout() {
208 // Try to start Vipps Checkout with any session provided.
209 function doVippsCheckout() {
210 if (!VippsSessionState) return false;
211 loadWidgets();
212 let args = {
213 checkoutFrontendUrl: VippsSessionState['checkoutFrontendUrl'].replace(/\/$/, ''),
214 token: VippsSessionState['token'],
215 iFrameContainerId: "vippscheckoutframe",
216 language: VippsConfig['vippslanguage'],
217 on: {
218 shipping_option_selected: function (data) { pollSessionStatus('shipping_selected', data); },
219 total_amount_changed: function (data) { pollSessionStatus('total_changed', data); },
220 session_status_changed: function (data) {
221 console.log("Session status changed %j", data);
222 sessionStarted = true; jQuery("body").removeClass('processing');
223 pollSessionStatus('status_changed', data);
224 },
225 shipping_address_changed: function (data) { pollSessionStatus('address_changed', data); } ,
226 customer_information_changed: function (data) { pollSessionStatus('customer_info_changed', data); }
227 }
228 };
229 let vippsCheckout = VippsCheckout(args);
230 VCO = vippsCheckout;
231
232 // When just loaded, with a slight delay ensure the session is unlocked, just in case it was locked in a different tab which
233 // was then closed. IOK 2025-04-24
234 setTimeout(unlockSession, 3000);
235
236 jQuery("body").css("cursor", "default");
237 jQuery('.vipps_checkout_button.button').css("cursor", "default");
238 jQuery('.vipps_checkout_startdiv').hide();
239
240 // The iframe should be *present* now, but not loaded, so we get to add an onLoad element. IOK 2025-04-16
241 jQuery('#vippscheckoutframe iframe').on("load", iframeLoaded);
242
243 return true;
244 }
245
246 if (!doVippsCheckout()) {
247 let data = {};
248 let formdata = jQuery("#vippsdata").serializeArray();
249 for(i=0;i<formdata.length;i++) {
250 let entry = formdata[i];
251 data[entry.name] = entry.value;
252 }
253 if (typeof wp !== 'undefined' && typeof wp.hooks !== 'undefined') {
254 data = wp.hooks.applyFilters('vippsCheckoutInitalizeSessionData', data);
255 }
256 data['action'] = 'vipps_checkout_start_session';
257 data['vipps_checkout_sec'] = jQuery('#vipps_checkout_sec').val();
258 data['orderid'] = jQuery('#vippsorderid').val();
259
260 jQuery.ajax(VippsConfig['vippsajaxurl'],
261 { cache:false,
262 timeout: 0,
263 dataType:'json',
264 headers: {"Accept-Language": `${VippsConfig['vippslocale']}, *`},
265 data: data,
266 method: 'POST',
267 error: function (xhr, statustext, error) {
268 jQuery("body").css("cursor", "default");
269 jQuery('.vipps_checkout_button.button').css("cursor", "default");
270 jQuery('.vipps_checkout_startdiv').hide();
271 console.log('Error initiating transaction : ' + statustext + ' : ' + error);
272 pollingdone=true;
273 jQuery("body").removeClass('processing');
274 jQuery('#vippscheckouterror').show();
275 jQuery('#vippscheckoutframe').html('<div style="display:none">Error occured</div>');
276 if (error == 'timeout') {
277 console.log('Timeout creating Checkout session at vipps');
278 }
279 },
280 'success': function (result,statustext, xhr) {
281 jQuery("body").css("cursor", "default");
282 jQuery('.vipps_checkout_button.button').css("cursor", "default");
283 jQuery('.vipps_checkout_startdiv').hide();
284
285 // Save order created or fetched
286 if ( result['data']['orderid'] ) {
287 jQuery('#vippsorderid').val(result['data']['orderid']);
288 }
289
290 if (! result['data']['ok']) {
291 console.log("Error starting Vipps MobilePay Checkout %j", result);
292 jQuery('#vippscheckouterror').show();
293 jQuery("body").removeClass('processing');
294 return;
295 }
296 if (result['data']['redirect']) {
297 window.location.replace(result['redirect']);
298 return;
299 }
300 if (result['data']['src']) {
301 VippsSessionState = { token: result['data']['token'], checkoutFrontendUrl: result['data']['src'] }
302 doVippsCheckout();
303 }
304 },
305 });
306 }
307 }
308
309 // Function to handle errors during the Vipps checkout process NT-2024-09-07
310 function handleCheckoutError(errorMessage) {
311 console.error(errorMessage);
312 jQuery("body").css("cursor", "default");
313 jQuery('.vipps_checkout_button.button').css("cursor", "default");
314 jQuery('.vipps_checkout_startdiv').hide();
315 jQuery("body").removeClass('processing');
316 jQuery('#vippscheckouterror').hide();
317 jQuery('#vippscheckoutframe').html('<div class="woocommerce-error">' + errorMessage + '</div>');
318 initiating = false;
319 }
320
321 function pollSessionStatus (type, pollData) {
322 if (polling) return;
323 polling=true;
324 locking = 0;
325 if (!type) type="none";
326 if (!pollData) pollData={};
327
328 // For these two, we need to lock the session because VAT calculations can change IOK 2025-04-24
329 if (type =="address_changed" || type=="customer_info_changed") {
330 locking=1;
331 }
332
333 if (typeof wp !== 'undefined' && typeof wp.hooks !== 'undefined') {
334 wp.hooks.doAction('vippsCheckoutPollingStart', type, pollData, VCO);
335 }
336
337 // Just a trivial errorhandler for now. IOK 2025-05-15
338 function errorhandler (error) {
339 console.log(error);
340 }
341
342 // Abstracted out so we can pass it to the lock promise if neccessary IOK 2025-05-16
343 function doTheCall() {
344 jQuery.ajax(VippsConfig['vippsajaxurl'],
345 { cache:false,
346 timeout: 0,
347 dataType:'json',
348 method: 'POST',
349 headers: {"Accept-Language": `${VippsConfig['vippslocale']}, *`},
350 data: { 'action': 'vipps_checkout_poll_session', 'lock_held' : locking, 'type': type, 'pollData': pollData, 'vipps_checkout_sec' : jQuery('#vipps_checkout_sec').val(), 'orderid' : jQuery('#vippsorderid').val() },
351 error: function (xhr, statustext, error) {
352 if (locking) setTimeout(unlockSession, 3000); // Allow backend some error recovery time.
353 // This may happen as a result of a race condition where the user is sent to Vipps
354 // when the "poll" call still hasn't returned. In this case this error doesn't actually matter,
355 // It may also be a temporary error, so we do not interrupt polling or notify the user. Just log.
356 // IOK 2022-04-06
357 if (error == 'timeout') {
358 errorhandler('Timeout polling session data hos Vipps');
359 } else {
360 errorhandler('Error polling session data hos Vipps - this may be temporary or because the user has moved on: ' + statustext + " error: " + error);
361 }
362 },
363 complete: function (xhr, statustext, error) {
364 polling = false;
365 },
366 success: function (result,statustext, xhr) {
367 if (locking) unlockSession();
368 console.log('Ok: ' + result['success'] + ' message ' + result['data']['msg'] + ' url ' + result['data']['url']);
369 if (result['data']['msg'] == 'EXPIRED') {
370 jQuery('#vippscheckoutexpired').show();
371 jQuery('#vippscheckoutframe').html('<div style="display:none">Session expired</div>');
372 pollingdone=true;
373 return;
374 }
375 if (result['data']['msg'] == 'ERROR' || result['data']['msg'] == 'FAILED') {
376 jQuery('#vippscheckouterror').show();
377 jQuery('#vippscheckoutframe').html('<div style="display:none">Error occured in backend</div>');
378 pollingdone=true;
379 return;
380 }
381 if (result['data']['url']) {
382 pollingdone = 1;
383 window.location.replace(result['data']['url']);
384 }
385 },
386 });
387 }
388
389 if (locking) {
390 lockSession().then(doTheCall).catch((error) => errorhandler(error));
391 } else {
392 doTheCall();
393 }
394
395 }
396
397
398
399 function initWhenVisible() {
400 if (typeof document.visibilityState == 'undefined') return;
401 if (initiating) return;
402 if (listening) return;
403 if (document.visibilityState == 'visible') {
404 jQuery("body").addClass('processing');
405 // Give other scripts a chance to run first
406 setTimeout(initVippsCheckout, 100);
407 } else {
408 console.log("Not visible - not starting Vipps MobilePay Checkout");
409 }
410 }
411
412 console.log("Vipps MobilePay Checkout Initialized version 115");
413 initWhenVisible();
414 });
415