| 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 |
// Hook vippsInit to woocommerce/product-collection render event. LP 29.11.2024 |
| 31 |
// IOK 2026-01-14 available from woo 9.4 - so the buy now block will not be available until that version |
| 32 |
document.body.addEventListener('wc-blocks_product_list_rendered', |
| 33 |
function (event) { |
| 34 |
document.body.dispatchEvent(new Event('vippsInit')); |
| 35 |
} |
| 36 |
); |
| 37 |
|
| 38 |
|
| 39 |
jQuery( document ).ready( function() { |
| 40 |
|
| 41 |
// This is for WooCommerce Product Bundles, which potentially have *several* variant-products or configurable products, so |
| 42 |
// the standard Woo hooks don't apply. Luckily, we have other, simpler events to use. |
| 43 |
// For this to work, we also have to force the compat-mode thing on. IOK 2020-04-21 |
| 44 |
jQuery('.cart.bundle_data').on('woocommerce-product-bundle-hide', function (e, variant) { |
| 45 |
jQuery('form .button.single-product.vipps-buy-now').removeClass('variation-found'); |
| 46 |
jQuery('form .button.single-product.vipps-buy-now').attr('disabled','disabled'); |
| 47 |
jQuery('form .button.single-product.vipps-buy-now').addClass('disabled'); |
| 48 |
// We don't know why the button is hidden, so ensure we use compatibility-mode IOK 2020-04-21 |
| 49 |
jQuery('.button.single-product.vipps-buy-now').addClass('compat-mode'); |
| 50 |
removeErrorMessages(); |
| 51 |
}); |
| 52 |
jQuery('.cart.bundle_data').on('woocommerce-product-bundle-show', function (e, variant) { |
| 53 |
jQuery('form .button.single-product.vipps-buy-now').addClass('variation-found'); |
| 54 |
jQuery('form .button.single-product.vipps-buy-now').removeAttr('disabled'); |
| 55 |
jQuery('form .button.single-product.vipps-buy-now').removeClass('disabled'); |
| 56 |
removeErrorMessages(); |
| 57 |
}); |
| 58 |
|
| 59 |
// Hooks for the 'buy now with vipps' button on product pages etc |
| 60 |
jQuery('body').on('found_variation', function (e,variation) { |
| 61 |
const form = jQuery(e.target); |
| 62 |
const target = form.find(".button.single-product.vipps-buy-now.variable-product"); |
| 63 |
|
| 64 |
var purchasable=true; |
| 65 |
if ( ! variation.is_purchasable || ! variation.is_in_stock || ! variation.variation_is_visible ) { |
| 66 |
purchasable = false; |
| 67 |
} |
| 68 |
target.addClass('variation-found'); |
| 69 |
if (purchasable) { |
| 70 |
target.removeAttr('disabled'); |
| 71 |
target.removeClass('disabled'); |
| 72 |
removeErrorMessages(); |
| 73 |
} else { |
| 74 |
target.attr('disabled','disabled'); |
| 75 |
target.addClass('disabled'); |
| 76 |
removeErrorMessages(); |
| 77 |
} |
| 78 |
}); |
| 79 |
jQuery('body').on('reset_data', function (e) { |
| 80 |
const form = jQuery(e.target) |
| 81 |
const target = form.find(".button.single-product.vipps-buy-now.variable-product"); |
| 82 |
target.removeClass('variation-found'); |
| 83 |
target.attr('disabled','disabled'); |
| 84 |
target.addClass('disabled'); |
| 85 |
removeErrorMessages(); |
| 86 |
}); |
| 87 |
// If this is triggered, somebody just loaded a variation form, so we need to redo the button init scripts |
| 88 |
jQuery('body').on('wc_variation_form', function () { |
| 89 |
vippsInit(); |
| 90 |
}); |
| 91 |
// Allow other guys to do this too |
| 92 |
jQuery('body').on('vippsInit', function () { |
| 93 |
vippsInit(); |
| 94 |
}); |
| 95 |
|
| 96 |
|
| 97 |
// Using ajax, get a session and a key and redirect to the "buy single product using express checkout" page. |
| 98 |
function buySingleProduct (event) { |
| 99 |
event.preventDefault(); |
| 100 |
|
| 101 |
var element = jQuery(this); |
| 102 |
if (jQuery('body').hasClass('processing')) return; // Trying to stop doublecclickers. IOK 2018-09-27 |
| 103 |
|
| 104 |
if (jQuery(element).hasClass('disabled')) { |
| 105 |
if (typeof wc_add_to_cart_variation_params != 'undefined') { |
| 106 |
if (jQuery(element).hasClass('variation-found')) { |
| 107 |
window.alert(wc_add_to_cart_variation_params.i18n_unavailable_text); |
| 108 |
} else { |
| 109 |
window.alert(wc_add_to_cart_variation_params.i18n_make_a_selection_text); |
| 110 |
} |
| 111 |
} else { |
| 112 |
console.log("Missing the wc_add_to_cart_variation_params"); |
| 113 |
} |
| 114 |
return false; |
| 115 |
} |
| 116 |
|
| 117 |
jQuery('body').addClass('processing'); |
| 118 |
removeErrorMessages(); |
| 119 |
jQuery(element).attr('disabled','disabled'); |
| 120 |
jQuery(element).attr('inactive','inactive'); |
| 121 |
jQuery(element).addClass('disabled'); |
| 122 |
jQuery(element).addClass('loading'); |
| 123 |
|
| 124 |
if (typeof wp !== 'undefined' && typeof wp.hooks !== 'undefined') { |
| 125 |
wp.hooks.doAction('vippsBuySingleProduct', element, event); |
| 126 |
} |
| 127 |
|
| 128 |
var compatMode = jQuery(element).hasClass('compat-mode'); |
| 129 |
if (typeof wp !== 'undefined' && typeof wp.hooks !== 'undefined') { |
| 130 |
compatMode = wp.hooks.applyFilters('vippsBuySingleProductCompatMode', compatMode, element, event); |
| 131 |
} |
| 132 |
|
| 133 |
// In compatibility mode, we delegate to the existing buy now button instead of doing the logic |
| 134 |
// ourselves. This allows more filters and actions in existing plugins to run. IOK 2019-02-26 |
| 135 |
if (compatMode) { |
| 136 |
var form = jQuery(element).closest('form'); |
| 137 |
var otherbutton = form.find('.single_add_to_cart_button').first(); |
| 138 |
var compatAction = function () { |
| 139 |
form.prepend('<input type=hidden id="vipps_compat_mode" name="vipps_compat_mode" value="1">'); |
| 140 |
if (otherbutton.length>0) otherbutton.click(); |
| 141 |
} |
| 142 |
// If your theme or product is weird enough, you may need this |
| 143 |
if (typeof wp !== 'undefined' && typeof wp.hooks !== 'undefined') { |
| 144 |
compatAction = wp.hooks.applyFilters('vippsBuySingleProductCompatModeAction', compatAction, element, event); |
| 145 |
} |
| 146 |
compatAction(); |
| 147 |
return false; |
| 148 |
} |
| 149 |
|
| 150 |
var data = {}; |
| 151 |
if (element.data('product_id') || element.data('product_sku')) { |
| 152 |
data = element.data(); |
| 153 |
} else { |
| 154 |
var form = element.closest('form.cart'); |
| 155 |
if (!form) { |
| 156 |
jQuery(element).removeClass('disabled'); |
| 157 |
jQuery(element).removeClass('loading'); |
| 158 |
jQuery(element).removeAttr('disabled'); |
| 159 |
jQuery(element).removeAttr('inactive'); |
| 160 |
jQuery('body').removeClass('processing'); |
| 161 |
addErrorMessage('Cannot add product - unknown error'); |
| 162 |
return false; |
| 163 |
} |
| 164 |
// Initialize with the entire content of the form if we have it. |
| 165 |
var serialized = form.serializeArray(); |
| 166 |
for (var i=0;i<serialized.length;i++) { |
| 167 |
if (serialized[i]['name'] != 'add-to-cart') { |
| 168 |
data[serialized[i]['name']] = serialized[i]['value']; |
| 169 |
} |
| 170 |
} |
| 171 |
var prodid = jQuery(form).find('input[name="product_id"]'); |
| 172 |
var varid = jQuery(form).find('input[name="variation_id"]'); |
| 173 |
var quantity = jQuery(form).find('input[name="quantity"]'); |
| 174 |
data['quantity'] = (quantity.length>0) ? quantity.val() : 1; |
| 175 |
data['product_id'] = (prodid.length>0) ? prodid.val() : 0; |
| 176 |
data['variation_id'] = (varid.length>0) ? varid.val() : 0; |
| 177 |
// Earlier versions, no variation: |
| 178 |
if (prodid.length == 0) { |
| 179 |
prodid = jQuery(form).find('button[name="add-to-cart"]'); |
| 180 |
if (prodid.length > 0) { |
| 181 |
data['product_id'] = prodid.val(); |
| 182 |
} else { |
| 183 |
// Some weird themes are even more incompatible |
| 184 |
prodid = jQuery(form).find('button[data-product_id]'); |
| 185 |
data['product_id'] = prodid.length > 0 ? prodid.data('product_id') : 0; |
| 186 |
} |
| 187 |
} |
| 188 |
} |
| 189 |
// Finally, create a hook for even weirder themes. |
| 190 |
data = wp.hooks.applyFilters('vippsBuySingleProductData', data, element, event); |
| 191 |
data['action'] = 'vipps_buy_single_product'; |
| 192 |
|
| 193 |
jQuery.ajax(VippsConfig['vippsajaxurl'], { |
| 194 |
"method": "POST", |
| 195 |
"data":data, |
| 196 |
"cache":false, |
| 197 |
"headers": {"Accept-Language": `${VippsConfig['vippslocale']}, *`}, |
| 198 |
"dataType": "json", |
| 199 |
"error": function (xhr, statustext, error) { |
| 200 |
console.log("Error creating express checkout:" + statustext + " " + error); |
| 201 |
addErrorMessage(error, element); |
| 202 |
jQuery(element).prop('disabled',true); |
| 203 |
jQuery(element).prop('inactive',true); |
| 204 |
jQuery('body').removeClass('processing'); |
| 205 |
}, |
| 206 |
"success": function (result, statustext, xhr) { |
| 207 |
if (result["ok"]) { |
| 208 |
console.log("We created the order!"); |
| 209 |
/* In case user presses the back button */ |
| 210 |
setTimeout(function () { |
| 211 |
jQuery(element).removeClass('disabled'); |
| 212 |
jQuery(element).removeClass('loading'); |
| 213 |
jQuery(element).removeAttr('disabled'); |
| 214 |
jQuery(element).removeAttr('inactive'); |
| 215 |
jQuery('body').removeClass('processing'); |
| 216 |
} |
| 217 |
, 1500); |
| 218 |
window.location.assign(result["url"]); |
| 219 |
} else { |
| 220 |
console.log("Failure!"); |
| 221 |
jQuery(element).removeClass('disabled'); |
| 222 |
jQuery(element).removeClass('loading'); |
| 223 |
jQuery(element).removeAttr('disabled'); |
| 224 |
jQuery(element).removeAttr('inactive'); |
| 225 |
addErrorMessage(result['msg'],element); |
| 226 |
jQuery('body').removeClass('processing'); |
| 227 |
} |
| 228 |
}, |
| 229 |
"timeout": 0 |
| 230 |
}); |
| 231 |
|
| 232 |
} |
| 233 |
|
| 234 |
|
| 235 |
|
| 236 |
// Remove old error messages |
| 237 |
function removeErrorMessages () { |
| 238 |
jQuery('.woocommerce-error.vipps-error').fadeOut(300, function () { jQuery(this).remove(); }); |
| 239 |
jQuery(document).trigger('woo-vipps-remove-errors'); |
| 240 |
if (typeof wp !== 'undefined' && typeof wp.hooks !== 'undefined') { |
| 241 |
wp.hooks.doAction('vippsRemoveErrorMessages'); |
| 242 |
} |
| 243 |
} |
| 244 |
|
| 245 |
// And add new ones |
| 246 |
function addErrorMessage(msg,element) { |
| 247 |
// Allow developers to customize error message by hiding vipps-default-error-message and hooking woo-vipps-error-message <messsage>,<element> |
| 248 |
var msg = "<p><ul class='woocommerce-error vipps-error vipps-default-error-message vipps-buy-now-error'><li>"+msg+"!</li></ul></p>"; |
| 249 |
if (typeof wp !== 'undefined' && typeof wp.hooks !== 'undefined') { |
| 250 |
msg = wp.hooks.applyFilters('vippsErrorMessage',msg, element); |
| 251 |
} |
| 252 |
jQuery(document).trigger('woo-vipps-error-message',[msg, element]); |
| 253 |
if (typeof wp !== 'undefined' && typeof wp.hooks !== 'undefined') { |
| 254 |
wp.hooks.doAction('vippsAddErrorMessage', msg, element); |
| 255 |
} |
| 256 |
|
| 257 |
jQuery(msg).hide().insertAfter(element).fadeIn(300); |
| 258 |
jQuery('.woocommerce-error.vipps-error').click(removeErrorMessages); |
| 259 |
} |
| 260 |
|
| 261 |
// Hooks for the button itself |
| 262 |
function vippsInit() { |
| 263 |
jQuery('.button.single-product.vipps-buy-now:not(.initialized)').click(buySingleProduct); |
| 264 |
jQuery('.button.single-product.vipps-buy-now').addClass('initialized'); |
| 265 |
if (typeof wp !== 'undefined' && typeof wp.hooks !== 'undefined') { |
| 266 |
wp.hooks.doAction('vippsInit'); |
| 267 |
} |
| 268 |
} |
| 269 |
|
| 270 |
// For modern wp/woo we want to subscribe to events for the cart especially. IOK 2026-02-24 |
| 271 |
if( window.wp?.data) { |
| 272 |
// The interactivity based minicart is only loaded once; so if the cart changes wrt supporting Vipps Checkout we must handle that |
| 273 |
// by subscribing to the cart data. IOK 2026-02-23 |
| 274 |
let prevCheckoutUrl = ""; |
| 275 |
function handleCartChanges () { |
| 276 |
const minicartbutton = document.querySelector('a.wp-block-woocommerce-mini-cart-checkout-button-block'); |
| 277 |
if (!minicartbutton) return; |
| 278 |
|
| 279 |
const cart = wp.data.select("wc/store/cart")?.getCartData?.(); |
| 280 |
if (!cart) return; |
| 281 |
const vippsdata = cart?.extensions?.['woo-vipps']; |
| 282 |
if (!vippsdata) return; |
| 283 |
if (vippsdata['checkout_url'] && prevCheckoutUrl != vippsdata['checkout_url']) { |
| 284 |
prevCheckoutUrl = vippsdata['checkout_url']; |
| 285 |
minicartbutton.href = vippsdata['checkout_url']; |
| 286 |
} |
| 287 |
} |
| 288 |
wp.data.subscribe(handleCartChanges); |
| 289 |
} |
| 290 |
|
| 291 |
|
| 292 |
|
| 293 |
vippsInit(); |
| 294 |
}); |
| 295 |
|
| 296 |
|