set_custom_path( $domain, $path );
return load_textdomain( $domain, $path . '/' . $mofile, $locale );
}
}
public static function register_hooks() {
$Vipps = static::instance();
register_activation_hook(WC_VIPPS_MAIN_FILE, array($Vipps,'activate'));
register_deactivation_hook(WC_VIPPS_MAIN_FILE,array('Vipps','deactivate'));
if (is_admin()) {
add_action('admin_init',array($Vipps,'admin_init'));
add_action('admin_menu',array($Vipps,'admin_menu'));
} else {
add_action('wp_footer', array($Vipps,'footer'));
}
add_action( 'plugins_loaded', array($Vipps,'plugins_loaded'));
add_action( 'after_setup_theme', array($Vipps,'after_setup_theme'));
add_action('init',array($Vipps,'init'));
add_action( 'woocommerce_loaded', array($Vipps,'woocommerce_loaded'));
add_filter( 'woocommerce_available_payment_gateways', array($Vipps, 'payment_gateway_filter'));
add_action( 'woocommerce_blocks_loaded', [$Vipps, 'woocommerce_blocks_loaded']);
// Express Checkout and Vipps Checkout supports the new pickup_location shipping method, but the admin interface for this may
// not have loaded if the default checkout solution isn't the Checkout block. We'll load it anyway if the user has any local pickup locations
// stored in the database since we support this for both Vipps MobilePay checkokut and Express. IOK 2026-02-25
add_action('woocommerce_load_shipping_methods', array($Vipps, 'maybe_load_pickup_locations'), 90);
}
// Register woocommerce store api endpoint to use in buy-now minicart block. LP 2026-02-10
public function woocommerce_blocks_loaded() {
if ( ! function_exists( 'woocommerce_store_api_register_endpoint_data' ) ) {
return;
}
woocommerce_store_api_register_endpoint_data(
array(
'endpoint' => Automattic\WooCommerce\StoreApi\Schemas\V1\CartSchema::IDENTIFIER,
'namespace' => 'woo-vipps',
'data_callback' => [$this, 'woo_vipps_store_api_cart_data'],
'schema_callback' => [$this, 'woo_vipps_store_api_cart_schema'],
'schema_type' => ARRAY_A,
)
);
}
// Some different bits and pieces: If we are on the pay-for-order page, we cannot provide Vipps for an order that has been at Vipps. IOK 2024-05-17
// Since we now support Vipps restart sessions, we *may* now provide Vipps a payment option on this page even if the order has been at Vipps. LP 2026-03-10
public function payment_gateway_filter ($gateways) {
if (is_checkout_pay_page()) {
$orderid = absint(get_query_var( 'order-pay'));
$order = $orderid ? wc_get_order($orderid) : null;
if (is_a($order, 'WC_Order')) {
// Existing override that allows repayment. IOK 2024-06-04
// i.e a third party plugin that implemented payment retrying for our plugin, we used to enable repayment only if this plugin was found.
// $allow_repayment = class_exists('\Site\Plugins\WooVipps\WooVippsPayForOrder');
// However, now we implement payment retrying ourselves. LP 2026-03-18
$vipps_status = $order->get_meta('_vipps_status');
$retry_count = $order->get_meta('_vipps_retry_count');
$retry_enabled = apply_filters('woo_vipps_enable_payment_retry', true, $order, $vipps_status, $retry_count);
$order_is_retryable = static::order_is_vipps_retryable($order->get_id());
// by default enable repayment if we can retry the order. LP 2026-03-18
$allow_repayment = apply_filters('woo_vipps_allow_repayment', $retry_enabled && $order_is_retryable, $order); // legacy filter
if (!$allow_repayment) unset($gateways['vipps']);
}
}
return $gateways;
}
// Get the singleton WC_GatewayVipps instance
public function gateway() {
if (class_exists('WC_Payment_Gateway')) {
require_once(dirname(__FILE__) . "/WC_Gateway_Vipps.class.php");
return WC_Gateway_Vipps::instance();
} else {
$this->log(__("Error: Cannot instantiate payment gateway, because WooCommerce is not loaded! This can happen when WooCommerce updates itself; but if it didn't, please activate WooCommerce again", 'woo-vipps'), 'error');
return null;
}
}
// These are strings that should be available for translation possibly at some future point. Partly to be easier to work with translate.wordpress.org
// Other usages are to translate any dynamic strings that may come from APIs etc. IOK 2021-03-18
private function translatable_strings() {
// Nothing here right now
return false;
}
// True iff support for HPOS has been activated IOK 2022-12-07
public function useHPOS() {
if ($this->HPOSActive == null) {
// Current way of checking IOK 2023-12-19
if (class_exists('Automattic\WooCommerce\Utilities\OrderUtil')) {
if (Automattic\WooCommerce\Utilities\OrderUtil::custom_orders_table_usage_is_enabled()) {
$this->HPOSActive = true;
} else {
$this->HPOSActive = false;
}
return $this->HPOSActive;
}
// This works in the backend, so ensures we are good with the meta fields etc.
if (function_exists('wc_get_container') && // 4.4.0
function_exists('wc_get_page_screen_id') && // Part of HPOS, not yet released
class_exists("Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController") &&
wc_get_container()->get( Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController::class )->custom_orders_table_usage_is_enabled() ) {
$this->HPOSActive = true;
} else {
$this->HPOSActive = false;
}
}
return $this->HPOSActive;
}
public function init () {
// Register certain scripts in wp_loaded because they will be added to the backend as well - the gutenberg checkout block
// needs these to be defined in the backend. IOK 2024-04-16
add_action('wp_loaded', array($this, 'wp_register_scripts'));
add_action('wp_enqueue_scripts', array($this, 'wp_enqueue_scripts'));
// Remove the possibility of restarting failed orders etc. This will be fixed in the future. IOK 2023-05-26
add_filter('woocommerce_my_account_my_orders_actions', array($this,'woocommerce_my_account_my_orders_actions'), 10, 2);
// Used in 'compat mode' only to add products to the cart
add_filter('woocommerce_add_to_cart_redirect', array($this, 'woocommerce_add_to_cart_redirect'), 10, 1);
$this->add_shortcodes();
$this->maybe_add_vipps_badge_feature();
// Handle the asynch call to send Order Management data on payment complete - this will push order data to the users' Vipps app
add_action('admin_post_nopriv_woo_vipps_order_management', array($this, 'do_order_management'));
add_action('admin_post_woo_vipps_order_management', array($this, 'do_order_management'));
// Extra order actions on the order screen, now using ajax to be compatible with HPOS. IOK 2022-12-02
add_action('wp_ajax_woo_vipps_order_action', array($this, 'order_handle_vipps_action'));
// Fetch wc products, but filter those only purchasable by VMP express checkout. LP 2026-01-22
add_action('rest_api_init', function() {
register_rest_route('woo-vipps/v1', '/express-products', [
'methods' => 'GET',
'callback' => [$this, 'rest_express_checkout_products'],
'permission_callback' => '__return_true',
]);
});
// We need a 5-minute scheduled event for the handler for missed callbacks. Using the
// action scheduler would be better, but we can't do that just yet because of backwards
// compatibility. At some point, support for older woo-versions should be dropped; then this
// should use the action scheduler instead. IOK 2021-06-21
add_filter('cron_schedules', function ($schedules) {
if(!isset($schedules["5min"])){
$schedules["5min"] = array(
'interval' => 5*60,
'display' => __('Once every 5 minutes'));
}
return $schedules;
});
// Offload work to wp-cron so it can be done in the background on sites with heavy load IOK 2020-04-01
add_action('vipps_cron_cleanup_hook', array($this, 'cron_cleanup_hook'));
// Check periodically for orders that are stuck pending with no callback IOK 2021-06-21
add_action('vipps_cron_missing_callback_hook', array($this, 'cron_check_for_missing_callbacks'));
// For the rest, we need to read the payment gateways setting, and the payment gateway may not actually
// exist at this point. This is because for it to exist, WooCommerce must have loaded, and if it hasn't, for instance
// because it is self-updating or because it has been deactivated just now or something, we won't have access to it.
// Therefore test it first. IOK 2022-12-08
$gw = $this->gateway();
// This is a developer-mode level feature because flock() is not portable. This ensures callbacks and shopreturns do not
// simultaneously update the orders, in particular not the express checkout order lines wrt shipping. IOK 2020-05-19
if ($gw && $gw->get_option('use_flock') == 'yes') {
add_filter('woo_vipps_lock_order', array($this,'flock_lock_order'));
add_action('woo_vipps_unlock_order', array($this, 'flock_unlock_order'));
}
}
// IOK 2022-12-02 This is currently used in two places: In the code that finds orders marked "to be deleted", and
// in the getOrderIdByVippsOrderId function. This is for the old-style Woo order tables, and do nothing for HPOS right now.
// This should however be replaced by its own table so it can be done more efficiently.
public static function add_wc_order_meta_key_support() {
if (did_action('woo_vipps_add_order_meta_key_support')) return;
do_action('woo_vipps_add_order_meta_key_support');
add_filter('woocommerce_order_data_store_cpt_get_orders_query', function ($query, $query_vars) {
if (isset($query_vars['meta_vipps_orderid']) && $query_vars['meta_vipps_orderid'] ) {
if (!isset($query['meta_query'])) $query['meta_query'] = array();
$query['meta_query'][] = array(
'key' => '_vipps_orderid',
'value' => $query_vars['meta_vipps_orderid']
);
}
if (isset($query_vars['meta_vipps_delendum']) && $query_vars['meta_vipps_delendum'] ) {
if (!isset($query['meta_query'])) $query['meta_query'] = array();
$query['meta_query'][] = array(
'key' => '_vipps_delendum',
'value' => 1
);
}
return $query;
}, 10, 2);
}
public function admin_init () {
$gw = $this->gateway();
require_once(dirname(__FILE__) . "/admin/settings/VippsAdminSettings.class.php");
$adminSettings = VippsAdminSettings::instance();
// Stuff for the Order screen
add_action('woocommerce_order_item_add_action_buttons', array($this, 'order_item_add_action_buttons'), 10, 1);
// Don't allow deletion of refunds made through Vipps IOK 2025-11-17
add_action('woocommerce_after_order_refund_item_name', function ($refund) {
$orderid = $refund->get_parent_id();
$order = wc_get_order($orderid);
if (is_a($order, 'WC_Order') && $order->get_payment_method() == 'vipps') {
$id = $refund->get_id();
$gw = $refund->get_refunded_payment();
if ($gw) {
$msg = sprintf(__('Refunded through %1$s', 'woo-vipps'), $this->get_payment_method_name());
echo "";
echo "" . esc_html($msg) . "";
}
}});
require_once(dirname(__FILE__) . "/VippsDismissibleAdminBanners.class.php");
VippsDismissibleAdminBanners::add();
// Styling etc
add_action('admin_head', array($this, 'admin_head'));
// Scripts
$this->vippsJSConfig['vippssecnonce'] = wp_create_nonce('vippssecnonce');
wp_localize_script('vipps-gw', 'VippsConfig', $this->vippsJSConfig);
add_action('admin_enqueue_scripts', array($this,'admin_enqueue_scripts'));
// IOK 2026-05-26 redirect the old Woo-generated settings-screen to our own settings page.
add_action('current_screen', function ($screen) {
if (!is_admin() || !$screen || $screen->id !== 'woocommerce_page_wc-settings') return;
if ($_GET['tab'] != 'checkout' || $_GET['section'] != 'vipps') return;
wp_safe_redirect(admin_url('admin.php?page=vipps_settings_menu'));
exit();
});
// Custom product properties
// IOK 2024-01-17 temporary: The special product properties are currenlty only active for Vipps
// IOK 2025-09-01 now available for all
add_filter('woocommerce_product_data_tabs', array($this,'woocommerce_product_data_tabs'),99);
add_action('woocommerce_product_data_panels', array($this,'woocommerce_product_data_panels'),99);
add_action('woocommerce_process_product_meta', array($this, 'process_product_meta'), 10, 2);
add_action('add_meta_boxes', array($this, 'add_meta_boxes'));
// Keep admin notices during redirects IOK 2018-05-07
add_action('admin_notices',array($this,'stored_admin_notices'));
// Ajax just for the backend
add_action('wp_ajax_vipps_create_shareable_link', array($this, 'ajax_vipps_create_shareable_link'));
add_action('wp_ajax_vipps_payment_details', array($this, 'ajax_vipps_payment_details'));
add_action('wp_ajax_vipps_update_admin_settings', array($adminSettings, 'ajax_vipps_update_admin_settings'));
// POST actions for the backend
add_action('admin_post_update_vipps_badge_settings', array($this, 'update_badge_settings'));
add_action('admin_post_update_vipps_button_settings', array($this, 'update_button_settings'));
add_action('admin_post_vipps_delete_webhook', array($this, 'vipps_delete_webhook'));
add_action('admin_post_vipps_add_webhook', array($this, 'vipps_add_webhook'));
// Link to the settings page from the plugin list
add_filter( 'plugin_action_links_'.plugin_basename(WC_VIPPS_MAIN_FILE ), array($this, 'plugin_action_links'));
if ($gw->enabled == 'yes' && $gw->is_test_mode()) {
$what = sprintf(__('%1$s is currently in test mode - no real transactions will occur', 'woo-vipps'), Vipps::CompanyName());
$this->add_vipps_admin_notice($what,'info', '', 'test-mode');
}
// This requires merchants using the old shipping callback filter to choose between this or the new shipping method mechanism. IOK 2020-02-17
if (has_action('woo_vipps_shipping_methods')) {
$option = $gw->get_option('newshippingcallback');
if ($option != 'old' && $option != 'new') {
$what = __('Your theme or a plugin is currently overriding the \'woo_vipps_shipping_methods\' filter to customize your shipping alternatives. While this works, this disables the newer Express Checkout shipping system, which is neccessary if your shipping is to include metadata. You can do this, or stop this message, from the settings page', 'woo-vipps');
$this->add_vipps_admin_notice($what,'info');
}
}
// IOK 2020-04-01 If the plugin is updated, the normal 'activate' hook may not run. Add the scheduled events if not present.
// Normal updates will not need this, but if updates are 'sideloaded', it is neccessary still. This call will only do work if the
// jobs are not scheduled. We'll ensure the action is active first time an admin logs in.
if (!defined('DOING_AJAX') || !DOING_AJAX) {
static::maybe_add_cron_event();
if (!get_option('woo-vipps-configured')) {
list($ok, $msg) = $gw->check_connection();
if (!$ok){
if ($msg) {
$this->add_vipps_admin_notice(sprintf(__("
%1\$s not yet correctly configured: please go to the %1\$s settings to complete your setup:
%3\$s
%1\$s not yet configured: please go to the %1\$s settings to complete your setup!
", 'woo-vipps'), Vipps::CompanyName(), admin_url('/admin.php?page=vipps_settings_menu'))); } } } // If we are configured, but we don't have any webhooks yet, initialize them for the epayment api. IOK 2023-12-20 // if we do have them, check them for consistency if (get_option('woo-vipps-configured')) { if (empty(get_option('_woo_vipps_webhooks'))) { $gw->initialize_webhooks(); } else { $ok = $gw->check_webhooks(); if (!$ok) { $gw->initialize_webhooks(); }; } } } } // Runs on init, adds the Vipps badge feature if activated public function maybe_add_vipps_badge_feature () { $badge_options = get_option('vipps_badge_options'); if (!$badge_options || !@$badge_options['badgeon']) return false; add_action('wp_enqueue_scripts', function () { wp_enqueue_script('vipps-onsite-messageing'); }); add_action('woocommerce_before_add_to_cart_form', function () use ($badge_options) { global $product; if (!is_a($product, 'WC_Product')) return; $show = intval(@$badge_options['defaultall']); $forthis = $product->get_meta('_vipps_show_badge', true); $dontshow = ($forthis == 'none'); $doshow = !$dontshow && ($show || ($forthis && $forthis != 'none')); if (!apply_filters('woo_vipps_show_vipps_badge_for_product', $doshow, $product)) { return; } $attr = ""; if ($forthis != 'none' || isset($badge_options['variant'])) { $variant = ($forthis && $forthis != 'none') ? $forthis : $badge_options['variant']; $attr .= " variant='" . sanitize_title($variant) . "' "; } $lang = $this->get_customer_language(); if ($lang) { $attr .= " language='". $lang . "' "; } $brand = $this->get_payment_method_name(); if ($brand) $attr .= " brand='". strtolower($brand) . "' "; $badge = ""; printf(__('Whenever an event like a payment or a cancellation occurs on a %1$s account, you can be notified of this using a webhook. This is used by this plugin to get noticed of payments by users even when they do not return to your store.', 'woo-vipps'), Vipps::CompanyName()); echo "
"; echo ""; __('To do this, the plugin will automatically add webhooks for the MSN - Merchant Serial Numbers - configured on this site', 'woo-vipps'); echo "
"; echo ""; __('If your MSN has registered other callbacks, for instance for another website, you can manage these here - and you can also add your own hooks that will be notified of payment events to any other URL you enter.', 'woo-vipps'); echo "
"; echo ""; printf(__('Implementing a webhook is not trivial, so you will probably need a developer for this. You can read more about what is required here. ', 'woo-vipps'), $webhookapi); printf(__('Please note that there is normally a limit of 5 webhooks per MSN - contact %1$s if you need more', 'woo-vipps'), Vipps::CompanyName()); echo "
"; echo ""; print __('The following is a listing of your webhooks. If you have changed your website name, you may see some hooks that you do not recognize - these should be deleted', 'woo-vipps'); echo "
"; $keyset = $this->gateway()->get_keyset(); $recurrings = $this->gateway()->get_keyset(); foreach($recurrings as $msn=> $keys) { if (!isset($keyset[$msn])) { $keyset[$msn] = $keys; } } $allhooks = $this->gateway()->initialize_webhooks(); $localhooks = get_option('_woo_vipps_webhooks'); echo ""; foreach ($keyset as $msn => $data) { $testmode = $data['testmode'] ?? false; echo "| " . __('Webhook', 'woo-vipps') . " | " . __('Action', 'woo-vipps') . " | " . "
|---|---|
| " . esc_html($url) . " | "; echo ""; echo "[" . __('View', 'woo-vipps') . "] "; if (!$local) { echo " [" . __('Delete', 'woo-vipps') . "]"; } else { echo " ". __('Created for this site', 'woo-vipps') . ""; } echo " | "; echo "
" . sprintf(__('Ready for %1$s - press the button', 'woo-vipps'), Vipps::ExpressCheckoutName()) . "
"; } if ($execute) { $content .= "" . __("Please wait while we are preparing your order", 'woo-vipps') . "
"; $content .= ""; $this->fakepage(__('Order in progress','woo-vipps'), $content); return; } else { $content .= $askForConfirmationHTML; $content .= $extraHTML; $content .= $termsHTML; $content .= apply_filters('woo_vipps_express_checkout_validation_elements', ''); $title = sprintf(__('Buy now with %1$s!', 'woo-vipps'), $this->get_payment_method_name()); $content .= ""; $content .= ""; $this->fakepage(sprintf(__('%1$s Express Checkout','woo-vipps'), $this->get_payment_method_name()), $content); return; } } public function vipps_wait_for_payment() { status_header(200,'OK'); Vipps::nocache(); $orderid = WC()->session->get('_vipps_pending_order'); $order = null; $gw = $this->gateway(); // Failsafe for when the session disappears IOK 2018-11-19 $no_session = $orderid ? false : true; $limited_session = sanitize_text_field(@$_GET['ls']); // Now we *should* have a session at this point, but the session may have been deleted, // or the session may be in another browser, because we get here by the Vipps app opening the app. // If so, we will read the order id from the GET arguments and check if the auth token is correct, // simulating the session with that. // IOK 2019-11-19, changed to using GET 2023-01-23 if ($no_session && $limited_session) { $orderid = intval(@$_GET['id']); } if ($orderid) { clean_post_cache($orderid); $order = wc_get_order($orderid); } // if we came here with no session, check to see if we are allowed to do stuff with the order. if ($order && $no_session) { if (!$order->get_meta('_vipps_limited_session') || (!wp_check_password($limited_session, $order->get_meta('_vipps_limited_session')))) { $this->log("Wrong order session id on Vipps payment return url", 'error'); $order = null; $orderid=0; } else { $session = WC()->session; if (!$session->has_session()) { $session->set_customer_session_cookie(true); } $session->set('_vipps_pending_order', $orderid); } } do_action('woo_vipps_wait_for_payment_page',$order); $deleted_order=0; if ($orderid && !$order) { // If this happens, we actually did have an order, but it has been deleted, which must mean that it was cancelled. // Concievably a hook on the 'cancel'-transition or in the callback handlers could clean that up before we get here. IOK 2019-09-26 $this->log(__("In order return: The order %1\$d seems to be deleted", 'woo-vipps'), 'debug'); $deleted_order=1; } if (!$order && !$deleted_order) wp_die(__('Unknown order', 'woo-vipps')); // If we are done, we are done, so go directly to the end. IOK 2018-05-16 $status = $deleted_order ? 'cancelled' : $order->get_status(); // This is for debugging only - set to false to ensure we wait for the callback. IOK 2023-08-04 $do_poll = true; // Still pending, no callback. Make a call to the server as the order might not have been created. IOK 2018-05-16 if ($do_poll && $status == 'pending') { // Just in case the callback hasn't come yet, do a quick check of the order status at Vipps. $newstatus = $gw->callback_check_order_status($order); if ($status != $newstatus) { $status = $newstatus; clean_post_cache($orderid); $order = wc_get_order($orderid); // Reload order object } } else { // No need to do anyting here. IOK 2020-01-26 } $payment = 'notchecked'; if ($do_poll) { $payment = $deleted_order ? 'cancelled' : $gw->check_payment_status($order); } // All these payment statuses are successes so go to the thankyou page. if ($payment == 'authorized' || $payment == 'complete') { // IOK 2023-07-17 this used to be called in the woocommerce_thankyou hook, now we do it here instead, since // we may need to be logged in to be able to get to that hook. $this->woocommerce_before_thankyou($order->get_id()); wp_redirect($gw->get_return_url($order)); exit(); } // We are done, but in failure. Don't poll. $content = ""; $failure_redirect = apply_filters('woo_vipps_order_failed_redirect', '', $orderid); // Status is failed; still send to return url (as of now /order-recieved), the text there will depend on the status. // For failed it shows a "Retry payment" button that takes the customer to /pay-for-order where it will be retried. LP 2026-03-17 if ('failed' == $status) { $failure_redirect = $failure_redirect ?: $gw->get_return_url($order); wp_redirect($failure_redirect); exit(); } if ($status == 'cancelled' || $payment == 'cancelled') { $this->maybe_restore_cart($orderid,'failed'); if ($failure_redirect){ wp_redirect($failure_redirect); exit(); } $content .= "". __('Order cancelled','woo-vipps') . '
'; $content .= "'; $content .= "" . sprintf(__('Waiting for confirmation of purchase from %1$s','woo-vipps'), $this->get_payment_method_name()); if ($signal && !is_file($signal)) $signal = ''; $signalurl = $this->callbackSignalURL($signal); $content .= "