| @@ -36,8 +36,12 @@ | ||
| 36 | 36 | } |
| 37 | 37 | require_once(dirname(__FILE__) . "/VippsAPIException.class.php"); |
| 38 | 38 | |
| 39 | 39 | class Vipps { |
| 40 | + /* Rest api consts. LP 2026-03-30 */ | |
| 41 | + private const REST_NAMESPACE_BASE = 'woo-vipps'; | |
| 42 | + private const REST_CURRENT_VERSION = 'v1'; // don't use this directly, use methods get_rest_namespace() and get_rest_url(). LP 2026-04-22 | |
| 43 | + | |
| 40 | 44 | private static $instance = null; |
| 41 | 45 | |
| 42 | 46 | /* Used to interact with other payment gateways if neccessary (for 'external payment gateways') IOK 2024-05-27 */ |
| 43 | 47 | public static $installed_gateways = []; |
| @@ -55,8 +59,11 @@ | ||
| 55 | 59 | private $lockKey = null; |
| 56 | 60 | |
| 57 | 61 | public $vippsJSConfig = array(); |
| 58 | 62 | |
| 63 | + public $button_options_version = '2.0'; | |
| 64 | + public $button_options_express_version = '2.0'; | |
| 65 | + | |
| 59 | 66 | // IOK 2023-11-29 Vipps merging with MobilePay causes some challenges which we solve by abstraction |
| 60 | 67 | public static function CompanyName() { |
| 61 | 68 | return __("Vipps MobilePay", 'woo-vipps'); |
| 62 | 69 | } |
| @@ -63,9 +70,9 @@ | ||
| 63 | 70 | public static function CheckoutName($order=null) { |
| 64 | 71 | return "Vipps MobilePay Checkout"; // Do not translate |
| 65 | 72 | } |
| 66 | 73 | public static function ExpressCheckoutName($order=null) { |
| 67 | - return __("Vipps Express Checkout", 'woo-vipps'); | |
| 74 | + return __("Vipps MobilePay Express Checkout", 'woo-vipps'); | |
| 68 | 75 | } |
| 69 | 76 | public static function LoginName() { |
| 70 | 77 | return __("Login with Vipps", 'woo-vipps'); |
| 71 | 78 | } |
| @@ -74,8 +81,15 @@ | ||
| 74 | 81 | if (!static::$instance) static::$instance = new Vipps(); |
| 75 | 82 | return static::$instance; |
| 76 | 83 | } |
| 77 | 84 | |
| 85 | + // recognize our own gateways or gateway ids IOK 2026-05-26 | |
| 86 | + // param is either order or order's payment method id string. LP 2026-05-28 | |
| 87 | + public static function is_vipps_order($order_or_string) { | |
| 88 | + $id = is_string($order_or_string) ? $order_or_string : $order_or_string->get_payment_method(); | |
| 89 | + return in_array($id , ['vipps', 'vipps_card']); | |
| 90 | + } | |
| 91 | + | |
| 78 | 92 | // To simplify development, we load translations from the plugins' own .mos on development branches. IOK 2023-11-28 |
| 79 | 93 | public static function load_plugin_textdomain( $domain, $deprecated = false, $plugin_rel_path = false ) { |
| 80 | 94 | $development = apply_filters('woo_vipps_use_plugin_translations', false); |
| 81 | 95 | if (!$development) { |
| @@ -107,12 +121,17 @@ | ||
| 107 | 121 | add_action('init',array($Vipps,'init')); |
| 108 | 122 | add_action( 'woocommerce_loaded', array($Vipps,'woocommerce_loaded')); |
| 109 | 123 | add_filter( 'woocommerce_available_payment_gateways', array($Vipps, 'payment_gateway_filter')); |
| 110 | 124 | add_action( 'woocommerce_blocks_loaded', [$Vipps, 'woocommerce_blocks_loaded']); |
| 111 | - // Express Checkout and Vipps Checkout supports the new pickup_location shipping method, but the admin interface for this may | |
| 125 | + // Express Checkout and Checkout supports the new pickup_location shipping method, but the admin interface for this may | |
| 112 | 126 | // 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 |
| 113 | 127 | // stored in the database since we support this for both Vipps MobilePay checkokut and Express. IOK 2026-02-25 |
| 114 | 128 | add_action('woocommerce_load_shipping_methods', array($Vipps, 'maybe_load_pickup_locations'), 90); |
| 129 | + | |
| 130 | + // Vipps Checkout replaces the default checkout page, and currently uses its own page for this which needs to exist | |
| 131 | + // Will also probably be used to maintain a real utility-page for Vipps actions later for themes where this | |
| 132 | + // is important. | |
| 133 | + add_filter('woocommerce_create_pages', array($Vipps, 'woocommerce_create_pages'), 50, 1); | |
| 115 | 134 | } |
| 116 | 135 | |
| 117 | 136 | // Register woocommerce store api endpoint to use in buy-now minicart block. LP 2026-02-10 |
| 118 | 137 | public function woocommerce_blocks_loaded() { |
| @@ -135,9 +154,11 @@ | ||
| 135 | 154 | public function payment_gateway_filter ($gateways) { |
| 136 | 155 | if (is_checkout_pay_page()) { |
| 137 | 156 | $orderid = absint(get_query_var( 'order-pay')); |
| 138 | 157 | $order = $orderid ? wc_get_order($orderid) : null; |
| 139 | - if (is_a($order, 'WC_Order')) { | |
| 158 | + if (is_a($order, 'WC_Order') | |
| 159 | + && $order->get_meta('_vipps_init_timestamp') // allow vipps payment for new orders, like when creating an order from backend. LP 2026-05-28 | |
| 160 | + ) { | |
| 140 | 161 | // Existing override that allows repayment. IOK 2024-06-04 |
| 141 | 162 | // i.e a third party plugin that implemented payment retrying for our plugin, we used to enable repayment only if this plugin was found. |
| 142 | 163 | // $allow_repayment = class_exists('\Site\Plugins\WooVipps\WooVippsPayForOrder'); |
| 143 | 164 | // However, now we implement payment retrying ourselves. LP 2026-03-18 |
| @@ -225,9 +246,9 @@ | ||
| 225 | 246 | add_action('wp_ajax_woo_vipps_order_action', array($this, 'order_handle_vipps_action')); |
| 226 | 247 | |
| 227 | 248 | // Fetch wc products, but filter those only purchasable by VMP express checkout. LP 2026-01-22 |
| 228 | 249 | add_action('rest_api_init', function() { |
| 229 | - register_rest_route('woo-vipps/v1', '/express-products', [ | |
| 250 | + register_rest_route(self::get_rest_namespace('v1'), '/express-products', [ | |
| 230 | 251 | 'methods' => 'GET', |
| 231 | 252 | 'callback' => [$this, 'rest_express_checkout_products'], |
| 232 | 253 | 'permission_callback' => '__return_true', |
| 233 | 254 | ]); |
| @@ -262,39 +283,46 @@ | ||
| 262 | 283 | add_filter('woo_vipps_lock_order', array($this,'flock_lock_order')); |
| 263 | 284 | add_action('woo_vipps_unlock_order', array($this, 'flock_unlock_order')); |
| 264 | 285 | } |
| 265 | 286 | |
| 266 | - } | |
| 287 | + // Set default button options, migrating any older setup IOK 2026-07-15 | |
| 288 | + $this->init_button_options(); | |
| 267 | 289 | |
| 290 | + /* | |
| 291 | + From version 6.2.x we create a real physical page to handle the "special" vipps pages, | |
| 292 | + where we earlier used just a fake page with no real page id, unless especially configured. | |
| 293 | + We therefore need to add code to maintain this special page. | |
| 268 | 294 | |
| 269 | - // IOK 2022-12-02 This is currently used in two places: In the code that finds orders marked "to be deleted", and | |
| 270 | - // in the getOrderIdByVippsOrderId function. This is for the old-style Woo order tables, and do nothing for HPOS right now. | |
| 271 | - // This should however be replaced by its own table so it can be done more efficiently. | |
| 272 | - public static function add_wc_order_meta_key_support() { | |
| 273 | - if (did_action('woo_vipps_add_order_meta_key_support')) return; | |
| 274 | - do_action('woo_vipps_add_order_meta_key_support'); | |
| 275 | - add_filter('woocommerce_order_data_store_cpt_get_orders_query', function ($query, $query_vars) { | |
| 276 | - if (isset($query_vars['meta_vipps_orderid']) && $query_vars['meta_vipps_orderid'] ) { | |
| 277 | - if (!isset($query['meta_query'])) $query['meta_query'] = array(); | |
| 278 | - $query['meta_query'][] = array( | |
| 279 | - 'key' => '_vipps_orderid', | |
| 280 | - 'value' => $query_vars['meta_vipps_orderid'] | |
| 281 | - ); | |
| 295 | + woocommerce_loaded is too early for this because of maybe_create_vipps_pages which calls WC_Install::create_pages, | |
| 296 | + and we hook unto this with woocommerce_create_pages. LP 2026-09-03 | |
| 297 | + */ | |
| 298 | + | |
| 299 | + // Delete special page id option when its deleted or trashed, so that we dont have to load | |
| 300 | + // in the post to check status in woocommerce_loaded when we ensure the special page exists. LP 2026-09-03 | |
| 301 | + $delete_special_page_id = function($post_id, $post = null) { | |
| 302 | + if (static::get_special_page_id() === $post_id) { | |
| 303 | + delete_option('woocommerce_vipps_special_page_page_id'); | |
| 282 | 304 | } |
| 283 | - if (isset($query_vars['meta_vipps_delendum']) && $query_vars['meta_vipps_delendum'] ) { | |
| 284 | - if (!isset($query['meta_query'])) $query['meta_query'] = array(); | |
| 285 | - $query['meta_query'][] = array( | |
| 286 | - 'key' => '_vipps_delendum', | |
| 287 | - 'value' => 1 | |
| 288 | - ); | |
| 289 | - } | |
| 290 | - return $query; | |
| 291 | - }, 10, 2); | |
| 305 | + }; | |
| 306 | + add_action('delete_post', $delete_special_page_id, 10, 2); | |
| 307 | + add_action('wp_trash_post', $delete_special_page_id, 10, 2); | |
| 292 | 308 | |
| 309 | + $this->ensure_special_page_exists(); | |
| 310 | + | |
| 311 | + | |
| 312 | + // We want this special page to have a certain title and maybe special scripts and so on, | |
| 313 | + // this gets run in template redirect for these pages. | |
| 314 | + add_action('woo_vipps_before_handling_special_page', function ($action) { | |
| 315 | + // Change title dynamically depending on action. LP 2026-09-02 | |
| 316 | + add_filter('the_title', [$this, 'vipps_special_page_endpoint_title'], 10, 2); | |
| 317 | + }); | |
| 318 | + | |
| 319 | + // Add an admin interface for this page as well IOK 2026-09-11 | |
| 320 | + add_action('woocommerce_settings_pages', array($this, 'woocommerce_settings_pages')); | |
| 321 | + | |
| 293 | 322 | } |
| 294 | 323 | |
| 295 | 324 | public function admin_init () { |
| 296 | - | |
| 297 | 325 | $gw = $this->gateway(); |
| 298 | 326 | require_once(dirname(__FILE__) . "/admin/settings/VippsAdminSettings.class.php"); |
| 299 | 327 | $adminSettings = VippsAdminSettings::instance(); |
| 300 | 328 | // Stuff for the Order screen |
| @@ -303,9 +331,9 @@ | ||
| 303 | 331 | // Don't allow deletion of refunds made through Vipps IOK 2025-11-17 |
| 304 | 332 | add_action('woocommerce_after_order_refund_item_name', function ($refund) { |
| 305 | 333 | $orderid = $refund->get_parent_id(); |
| 306 | 334 | $order = wc_get_order($orderid); |
| 307 | - if (is_a($order, 'WC_Order') && $order->get_payment_method() == 'vipps') { | |
| 335 | + if (is_a($order, 'WC_Order') && self::is_vipps_order($order)) { | |
| 308 | 336 | $id = $refund->get_id(); |
| 309 | 337 | $gw = $refund->get_refunded_payment(); |
| 310 | 338 | if ($gw) { |
| 311 | 339 | $msg = sprintf(__('Refunded through %1$s', 'woo-vipps'), $this->get_payment_method_name()); |
| @@ -324,20 +352,21 @@ | ||
| 324 | 352 | $this->vippsJSConfig['vippssecnonce'] = wp_create_nonce('vippssecnonce'); |
| 325 | 353 | wp_localize_script('vipps-gw', 'VippsConfig', $this->vippsJSConfig); |
| 326 | 354 | add_action('admin_enqueue_scripts', array($this,'admin_enqueue_scripts')); |
| 327 | 355 | |
| 328 | - // Redirect the default WooCommerce settings page to our own | |
| 329 | - add_action( 'woocommerce_settings_start', function () { | |
| 330 | - add_filter('admin_url', function ($url, $path) { | |
| 331 | - if (strpos($path, "tab=checkout§ion=vipps") === false) return $url; | |
| 332 | - $qs = parse_url($path, PHP_URL_QUERY); | |
| 333 | - if (!$qs) return $url; | |
| 334 | - $args = []; | |
| 335 | - parse_str($qs, $args); | |
| 336 | - $ok = (($args['page']??false) == 'wc-settings') && (($args['tab']??false) == 'checkout') && (($args['section']??false) == 'vipps'); | |
| 337 | - if (!$ok) return $url; | |
| 338 | - return admin_url("/admin.php?page=vipps_settings_menu"); | |
| 339 | - }, 10, 2); | |
| 356 | + // IOK 2026-05-26 redirect the old Woo-generated settings-screen to our own settings page. | |
| 357 | + add_action('current_screen', function ($screen) { | |
| 358 | + if (!is_admin() || !$screen || $screen->id !== 'woocommerce_page_wc-settings') return; | |
| 359 | + $section = ($_GET['section'] ?? ""); | |
| 360 | + if (($_GET['tab'] ?? "")!= 'checkout' | |
| 361 | + || !in_array($section, ['vipps', 'vipps_card']) | |
| 362 | + ) return; | |
| 363 | + | |
| 364 | + $settings_tab = ''; | |
| 365 | + if ('vipps_card' === $section) $settings_tab = '#Card payments'; | |
| 366 | + | |
| 367 | + wp_safe_redirect(admin_url("admin.php?page=vipps_settings_menu$settings_tab")); | |
| 368 | + exit(); | |
| 340 | 369 | }); |
| 341 | 370 | |
| 342 | 371 | // Custom product properties |
| 343 | 372 | // IOK 2024-01-17 temporary: The special product properties are currenlty only active for Vipps |
| @@ -410,9 +439,77 @@ | ||
| 410 | 439 | } |
| 411 | 440 | } |
| 412 | 441 | } |
| 413 | 442 | |
| 443 | + | |
| 444 | + /** Ensure we have a special page for payment flows | |
| 445 | + * | |
| 446 | + * woocommerce_loaded is too early for this because of maybe_create_vipps_pages which calls WC_Install::create_pages, | |
| 447 | + * and we hook unto this with woocommerce_create_pages. LP 2026-09-03 | |
| 448 | + **/ | |
| 449 | + public function ensure_special_page_exists() { | |
| 450 | + if (static::get_special_page_id()) return; | |
| 451 | + $this->log(__('Missing id for special page, attempting to fix.', 'woo-vipps'), 'info'); | |
| 414 | 452 | |
| 453 | + // If user had in previous version overriden the fake page with a real one: migrate this page to be the special page. LP 2026-09-01 | |
| 454 | + $old_special_page_id = $this->gateway()->get_option('vippsspecialpageid'); | |
| 455 | + if ($old_special_page_id && ($special_page = get_post($old_special_page_id)) && "trash" !== $special_page->post_status) { | |
| 456 | + $this->log(__('Migrated old special page setting.', 'woo-vipps'), 'info'); | |
| 457 | + // there is no wc_set_page_id() so we update the option directly. LP 2026-09-01 | |
| 458 | + update_option('woocommerce_vipps_special_page_page_id', $old_special_page_id); | |
| 459 | + | |
| 460 | + // Ensure this page has the necessary shortcode. LP 2026-09-01 | |
| 461 | + if (!has_shortcode($special_page->post_content, 'vipps_special_page')) { | |
| 462 | + $new_content = $special_page->post_content . "\n\n<!-- wp:shortcode -->[vipps_special_page]<!-- /wp:shortcode -->"; | |
| 463 | + wp_update_post([ | |
| 464 | + 'ID' => $old_special_page_id, | |
| 465 | + 'post_content' => $new_content, | |
| 466 | + ]); | |
| 467 | + } | |
| 468 | + } else { | |
| 469 | + // Create special page if its missing. LP 2026-09-01 | |
| 470 | + $this->maybe_create_vipps_pages(); | |
| 471 | + } | |
| 472 | + } | |
| 473 | + | |
| 474 | + // Admin interface for the special page on woo/advanced/pages | |
| 475 | + public function woocommerce_settings_pages ($settings) { | |
| 476 | + $i = -1; | |
| 477 | + foreach($settings as $entry) { | |
| 478 | + $i++; | |
| 479 | + if ($entry['type'] == 'sectionend' && $entry['id'] == 'advanced_page_options') { | |
| 480 | + break; | |
| 481 | + } | |
| 482 | + } | |
| 483 | + if ($i > 0) { | |
| 484 | + $vippspagesettings = array( | |
| 485 | + array( | |
| 486 | + 'title' => sprintf(__( '%1$s Page', 'woo-vipps' ), Vipps::CompanyName()), | |
| 487 | + 'desc' => sprintf(__('This page is used for various special pages used by %1$s', 'woo-vipps'), Vipps::CompanyName()) . sprintf( __( 'Page contents: [%1$s]', 'woocommerce' ), 'vipps_special_page') , | |
| 488 | + 'id' => 'woocommerce_vipps_special_page_page_id', | |
| 489 | + 'type' => 'single_select_page_with_search', | |
| 490 | + 'default' => '', | |
| 491 | + 'class' => 'wc-page-search', | |
| 492 | + 'css' => 'min-width:300px;', | |
| 493 | + 'args' => array( | |
| 494 | + 'exclude' => | |
| 495 | + array( | |
| 496 | + wc_get_page_id( 'myaccount' ), | |
| 497 | + wc_get_page_id( 'checkout' ), | |
| 498 | + wc_get_page_id( 'cart' ), | |
| 499 | + ), | |
| 500 | + ), | |
| 501 | + 'desc_tip' => true, | |
| 502 | + 'autoload' => false, | |
| 503 | + )); | |
| 504 | + array_splice($settings, $i, 0, $vippspagesettings); | |
| 505 | + } | |
| 506 | + | |
| 507 | + return $settings; | |
| 508 | + } | |
| 509 | + | |
| 510 | + | |
| 511 | + | |
| 415 | 512 | // Runs on init, adds the Vipps badge feature if activated |
| 416 | 513 | public function maybe_add_vipps_badge_feature () { |
| 417 | 514 | $badge_options = get_option('vipps_badge_options'); |
| 418 | 515 | if (!$badge_options || !@$badge_options['badgeon']) return false; |
| @@ -741,8 +838,11 @@ | ||
| 741 | 838 | |
| 742 | 839 | // Get current brand and language |
| 743 | 840 | $current_brand = strtolower($this->get_payment_method_name()); |
| 744 | 841 | $current_language = $this->get_customer_language(); |
| 842 | + if ('se' === $current_language) $current_language = 'sv'; | |
| 843 | + // Looks like button and badge web components now use 'da' instead of 'dk' for danish. LP 2026-08-13 | |
| 844 | + if ('dk' === $current_language) $current_language = 'da'; | |
| 745 | 845 | |
| 746 | 846 | $variants = ['white'=> __('White', 'woo-vipps'), 'grey' => __('Grey','woo-vipps'), |
| 747 | 847 | 'filled'=> __('Filled', 'woo-vipps'), 'light'=>__('Light','woo-vipps'), |
| 748 | 848 | 'purple'=> __('Purple', 'woo-vipps')]; |
| @@ -805,9 +905,9 @@ | ||
| 805 | 905 | |
| 806 | 906 | <h2><?php _e('Shortcodes', 'woo-vipps'); ?> </h2> |
| 807 | 907 | <p><?php echo sprintf(__('If you need to add a %1$s badge on a specific page, footer, header and so on, and you cannot use the Gutenberg Block provided for this, you can either add the %1$s Badge manually (as <a href="%2$s" nofollow rel=nofollow target=_blank>documented here</a>) or you can use the shortcode.', 'woo-vipps'), Vipps::CompanyName(), "https://developer.vippsmobilepay.com/docs/knowledge-base/design-guidelines/on-site-messaging/"); ?></p> |
| 808 | 908 | <br><?php _e("The shortcode looks like this:", 'woo-vipps')?><br> |
| 809 | - <pre>[vipps-mobilepay-badge variant={white|filled|light|grey|purple}<br> language={en|no|fi|dk} ] </pre><br> | |
| 909 | + <pre>[vipps-mobilepay-badge variant={white|filled|light|grey|purple}<br> language={en|no|fi|da|sv} ] </pre><br> | |
| 810 | 910 | <?php _e("Please refer to the documentation for the meaning of the parameters.", 'woo-vipps'); ?></br> |
| 811 | 911 | <?php _e("The brand will be automatically applied.", 'woo-vipps'); ?> |
| 812 | 912 | </p> |
| 813 | 913 | |
| @@ -834,21 +934,24 @@ | ||
| 834 | 934 | echo json_encode(array('ok'=>0,'msg'=>__('You don\'t have sufficient rights to edit this product', 'woo-vipps'))); |
| 835 | 935 | wp_die(__('You don\'t have sufficient rights to edit this product', 'woo-vipps')); |
| 836 | 936 | } |
| 837 | 937 | |
| 838 | - $options = get_option('vipps_button_options'); | |
| 839 | - if (isset($_POST['express']['variant'])) { | |
| 840 | - $options['express']['variant'] = sanitize_title($_POST['express']['variant']); | |
| 938 | + $old = get_option('vipps_button_options2', []); | |
| 939 | + $new = $old; | |
| 940 | + if (isset($_POST['express']['configs'])) { | |
| 941 | + foreach ($_POST['express']['configs'] as $ctx => $config) { | |
| 942 | + $sanitized_ctx = sanitize_title($ctx); | |
| 943 | + $sanitized_config = map_deep($config, 'sanitize_title'); | |
| 944 | + | |
| 945 | + // If nonglobal context that uses global config, just wipe the rest of the stored config. LP 2026-06-25 | |
| 946 | + if ("global" !== $sanitized_ctx && ($sanitized_config['use-global-config'] ?? false)) { | |
| 947 | + $new['express']['configs'][$sanitized_ctx] = ['use-global-config' => true]; | |
| 948 | + } else { | |
| 949 | + $new['express']['configs'][$sanitized_ctx] = $sanitized_config; | |
| 950 | + } | |
| 951 | + } | |
| 841 | 952 | } |
| 842 | - if (isset($_POST['express']['mini-variant'])) { | |
| 843 | - $options['express']['mini-variant'] = sanitize_title($_POST['express']['mini-variant']); | |
| 844 | - } | |
| 845 | - if (isset($_POST['express']['force-mini']) && is_array($_POST['express']['force-mini'])) { | |
| 846 | - foreach($_POST['express']['force-mini'] as $key => $val) | |
| 847 | - $options['express']['force-mini'][$key] = sanitize_title($val); | |
| 848 | - } | |
| 849 | - | |
| 850 | - update_option('vipps_button_options', $options); | |
| 953 | + update_option('vipps_button_options2', $new); | |
| 851 | 954 | wp_safe_redirect(admin_url("admin.php?page=vipps_button_menu")); |
| 852 | 955 | exit(); |
| 853 | 956 | } |
| 854 | 957 | |
| @@ -882,9 +985,12 @@ | ||
| 882 | 985 | public function vipps_mobilepay_badge_shortcode($atts) { |
| 883 | 986 | $args = shortcode_atts( array('id'=>'', 'class'=>'', 'brand' => '', 'variant' => '','language'=>''), $atts ); |
| 884 | 987 | |
| 885 | 988 | $variant = in_array($args['variant'], ['orange', 'light-orange', 'grey','white', 'purple', 'filled', 'light']) ? $args['variant'] : ""; |
| 886 | - $language = in_array($args['language'], ['en','no', 'fi', 'dk']) ? $args['language'] : $this->get_customer_language(); | |
| 989 | + $language = in_array($args['language'], ['en', 'no', 'sv', 'da', 'dk', 'fi']) ? $args['language'] : $this->get_customer_language(); | |
| 990 | + if ('se' === $language) $language = 'sv'; | |
| 991 | + // Looks like button and badge web components now use 'da' instead of 'dk' for danish. LP 2026-08-11 | |
| 992 | + if ('dk' === $language) $language = 'da'; | |
| 887 | 993 | $id = sanitize_title($args['id']); |
| 888 | 994 | $class = sanitize_text_field($args['class']); |
| 889 | 995 | |
| 890 | 996 | $attributes = []; |
| @@ -900,13 +1006,18 @@ | ||
| 900 | 1006 | return "<vipps-mobilepay-badge $badgeatts></vipps-mobilepay-badge>"; |
| 901 | 1007 | } |
| 902 | 1008 | |
| 903 | 1009 | // legacy vipps_badge shortcode, the new one is vipps_mobilepay_badge_shortcode. LP 19.11.2024 |
| 1010 | + // Diff: this one doesn't support brand (JUST VIPPS). LP 2026-08-11 | |
| 904 | 1011 | public function vipps_badge_shortcode($atts) { |
| 905 | 1012 | $args = shortcode_atts( array('id'=>'', 'class'=>'','variant' => '','language'=>''), $atts ); |
| 906 | 1013 | |
| 907 | 1014 | $variant = in_array($args['variant'], ['orange', 'light-orange', 'grey','white', 'purple']) ? $args['variant'] : ""; |
| 908 | - $language = in_array($args['language'], ['en','no', 'dk', 'fi']) ? $args['language'] : $this->get_customer_language(); | |
| 1015 | + $language = in_array($args['language'], ['en', 'no', 'sv', 'da', 'dk', 'fi']) ? $args['language'] : $this->get_customer_language(); | |
| 1016 | + if ('se' === $language) $language = 'sv'; | |
| 1017 | + // Looks like button and badge web components now use 'da' instead of 'dk' for danish. LP 2026-08-11 | |
| 1018 | + if ('dk' === $language) $language = 'da'; | |
| 1019 | + | |
| 909 | 1020 | $id = sanitize_title($args['id']); |
| 910 | 1021 | $class = sanitize_text_field($args['class']); |
| 911 | 1022 | |
| 912 | 1023 | $attributes = []; |
| @@ -920,163 +1031,318 @@ | ||
| 920 | 1031 | |
| 921 | 1032 | return "<vipps-badge $badgeatts></vipps-badge>"; |
| 922 | 1033 | } |
| 923 | 1034 | |
| 924 | - public function get_express_logo_variants() { | |
| 1035 | + public function get_html_button_default_attrs() { | |
| 925 | 1036 | return [ |
| 926 | - 'buy-now-rectangular' => __('Buy now rectangular', 'woo-vipps'), | |
| 927 | - 'buy-now-pill' => __('Buy now pill', 'woo-vipps'), | |
| 928 | - 'express-rectangular' => __('Express rectangular', 'woo-vipps'), | |
| 929 | - 'express-pill' => __('Express pill', 'woo-vipps'), | |
| 930 | - 'express-rectangular-mini' => __('Express rectangular mini', 'woo-vipps'), | |
| 931 | - 'express-pill-mini' => __('Express pill mini', 'woo-vipps'), | |
| 1037 | + 'language' => 'store', | |
| 1038 | + 'variant' => 'primary', | |
| 1039 | + 'rounded' => 'false', | |
| 1040 | + 'verb' => 'buy', | |
| 1041 | + 'stretched' => 'false', | |
| 1042 | + 'compact' => 'false', | |
| 1043 | + 'brand' => strtolower($this->get_payment_method_name()), // NB: if setting wp option, you need to remember to unset this value so it's dynamic. LP 2026-07-01 | |
| 932 | 1044 | ]; |
| 933 | 1045 | } |
| 934 | 1046 | |
| 1047 | + public function get_html_button_attrs_for_context($context = 'global') { | |
| 1048 | + $options = get_option('vipps_button_options2', []); | |
| 1049 | + if (!is_string($context)) $context = 'global'; | |
| 1050 | + $config = $options['express']['configs'][$context] ?? []; | |
| 1051 | + if (!$config || ($config['use-global-config'] ?? false)) { | |
| 1052 | + $config = $options['express']['configs']['global'] ?? $this->get_html_button_default_attrs(); | |
| 1053 | + } | |
| 1054 | + return $config; | |
| 1055 | + } | |
| 935 | 1056 | |
| 1057 | + public function get_html_button_for_context($context = 'global') { | |
| 1058 | + return $this->get_html_button($this->get_html_button_attrs_for_context($context)); | |
| 1059 | + } | |
| 1060 | + | |
| 1061 | + // Generic Vipps/MobilePay button html, as of now a web component hosted locally. LP 2026-06-24 | |
| 1062 | + // See info and attributes at https://developer.vippsmobilepay.com/docs/knowledge-base/buttons/ | |
| 1063 | + public function get_html_button($attrs = []) { | |
| 1064 | + $payment_method = $this->get_payment_method_name(); | |
| 1065 | + $attrs = wp_parse_args($attrs, $this->get_html_button_default_attrs()); | |
| 1066 | + $attrs['brand'] = strtolower($payment_method); | |
| 1067 | + $attrs['type'] = 'button'; // static | |
| 1068 | + | |
| 1069 | + // Support using store language | |
| 1070 | + if ('store' === $attrs['language']) $attrs['language'] = $this->get_customer_language(); | |
| 1071 | + // Don't support these login verbs. LP 2026-06-04 | |
| 1072 | + if (in_array($attrs['verb'], ['login', 'register'])) $attrs['verb'] = 'buy'; | |
| 1073 | + // Looks like button and badge web components now use 'da' instead of 'dk' for danish. LP 2026-08-11 | |
| 1074 | + if ('dk' === $attrs['language']) $attrs['language'] = 'da'; | |
| 1075 | + | |
| 1076 | + $escaped_attrs = []; | |
| 1077 | + foreach($attrs as $k => $v) { | |
| 1078 | + $escaped_attrs[$k] = esc_attr($v); | |
| 1079 | + } | |
| 1080 | + | |
| 1081 | + // id attribute | |
| 1082 | + $id = $escaped_attrs['id'] ?? ''; | |
| 1083 | + $id_str = $id ? "id='$id'" : ''; | |
| 1084 | + | |
| 1085 | + // class attribute | |
| 1086 | + $class_str = ''; | |
| 1087 | + if (isset($attrs['class'])) { | |
| 1088 | + if (is_array($attrs['class'])) { | |
| 1089 | + $class_str = implode(' ', $attrs['class']); | |
| 1090 | + } else if (is_string($attrs['class'])) { | |
| 1091 | + $class_str = $attrs['class']; | |
| 1092 | + } | |
| 1093 | + } | |
| 1094 | + | |
| 1095 | + // The html | |
| 1096 | + $html = <<<EOF | |
| 1097 | +<vipps-mobilepay-button | |
| 1098 | + $id_str | |
| 1099 | + $class_str | |
| 1100 | + type="{$escaped_attrs['type']}" | |
| 1101 | + brand="{$escaped_attrs['brand']}" | |
| 1102 | + language="{$escaped_attrs['language']}" | |
| 1103 | + variant="{$escaped_attrs['variant']}" | |
| 1104 | + rounded="{$escaped_attrs['rounded']}" | |
| 1105 | + verb="{$escaped_attrs['verb']}" | |
| 1106 | + stretched="{$escaped_attrs['stretched']}" | |
| 1107 | + compact="{$escaped_attrs['compact']}" | |
| 1108 | +></vipps-mobilepay-button> | |
| 1109 | +EOF; | |
| 1110 | + return apply_filters('woo_vipps_html_button', $html, $attrs); | |
| 1111 | + } | |
| 1112 | + | |
| 936 | 1113 | public function button_menu_page() { |
| 937 | 1114 | if (!current_user_can('manage_woocommerce')) { |
| 938 | 1115 | wp_die(__('You don\'t have sufficient rights to access this page', 'woo-vipps')); |
| 939 | 1116 | } |
| 940 | - $payment_method = $this->get_payment_method_name(); | |
| 941 | - $lang = $this->get_customer_language(); | |
| 942 | - $button_options = get_option('vipps_button_options'); | |
| 1117 | + wp_enqueue_script('vipps-button-webcomponent'); | |
| 1118 | + ?> | |
| 1119 | + <div class='wrap vipps-button-settings'> | |
| 1120 | + <h1><?php echo sprintf(__('%1$s button configuration', 'woo-vipps'), Vipps::CompanyName()); ?></h1> | |
| 1121 | + <span><?php echo sprintf(__('%1$s supports different variants of buttons for you to perfect your store\'s look', 'woo-vipps'), Vipps::CompanyName()); ?></span> | |
| 1122 | + <form id="vipps-button-settings-form" class="vipps-button-settings" action="<?php echo admin_url('admin-post.php'); ?>" method="POST"> | |
| 1123 | + <input type="hidden" name="action" value="update_vipps_button_settings" /> | |
| 1124 | + <?php wp_nonce_field( 'buttonaction', 'buttonnonce'); ?> | |
| 943 | 1125 | |
| 944 | - $variants = $this->get_express_logo_variants(); | |
| 945 | - $mini_variants = array_filter($variants, fn($key) => str_ends_with($key, 'mini'), ARRAY_FILTER_USE_KEY); | |
| 1126 | + <!-- Express section --> | |
| 1127 | + <?php $this->button_menu_express_section(); ?> | |
| 946 | 1128 | |
| 947 | - $init_states = [ | |
| 948 | - 'express' => [ | |
| 949 | - 'variant' => array_key_exists(@$button_options['express']['variant'], $variants) ? $button_options['express']['variant'] : 'buy-now-rectangular', | |
| 950 | - 'mini-variant' => array_key_exists(@$button_options['express']['mini-variant'], $mini_variants) ? $button_options['express']['mini-variant'] : 'express-rectangular-mini', | |
| 951 | - 'force-mini' => [ | |
| 952 | - 'product' => @$button_options['express']['force-mini']['product'] ?? 'no', | |
| 953 | - 'catalog' => @$button_options['express']['force-mini']['catalog'] ?? 'yes', | |
| 954 | - 'cart' => @$button_options['express']['force-mini']['cart'] ?? 'no', | |
| 955 | - 'minicart' => @$button_options['express']['force-mini']['minicart'] ?? 'no', | |
| 956 | - ], | |
| 957 | - ], | |
| 1129 | + <!-- submit button --> | |
| 1130 | + <div id="vipps-button-settings-save"> | |
| 1131 | + <input class="btn button primary" type="submit" value="<?php _e('Update settings', 'woo-vipps'); ?>" /> | |
| 1132 | + </div> | |
| 1133 | + </form> | |
| 1134 | + </div> | |
| 1135 | + <?php | |
| 1136 | + } | |
| 1137 | + | |
| 1138 | + private function button_menu_express_section() { | |
| 1139 | + $options = get_option('vipps_button_options2', []); | |
| 1140 | + $express = $options['express'] ?? []; | |
| 1141 | + $configs = $express['configs'] ?? []; | |
| 1142 | + $contexts = [ | |
| 1143 | + 'global' => __('Global', 'woo-vipps'), | |
| 1144 | + 'product' => __('Product', 'woo-vipps'), | |
| 1145 | + 'catalog' => __('Catalog', 'woo-vipps'), | |
| 1146 | + 'cart' => __('Cart', 'woo-vipps'), | |
| 1147 | + 'minicart' => __('Mini cart', 'woo-vipps'), | |
| 1148 | + 'checkout' => __('Checkout', 'woo-vipps'), | |
| 958 | 1149 | ]; |
| 1150 | + $init_context = 'global'; | |
| 1151 | + $init_config = $configs[$init_context] ?? []; | |
| 959 | 1152 | |
| 1153 | + // html button args | |
| 1154 | + $init_args = $init_config; | |
| 1155 | + $init_args['id'] = 'vipps-button-express-preview'; | |
| 1156 | + | |
| 960 | 1157 | ?> |
| 961 | - <div class='wrap vipps-button-settings'> | |
| 962 | - <h1><?php echo sprintf(__('%1$s button configuration', 'woo-vipps'), Vipps::CompanyName()); ?></h1> | |
| 963 | - <span><?php echo sprintf(__('%1$s supports different variants of buttons for you to perfect your store\'s look', 'woo-vipps'), Vipps::CompanyName()); ?></span> | |
| 964 | - <form class="vipps-button-settings" action="<?php echo admin_url('admin-post.php'); ?>" method="POST"> | |
| 1158 | + <div class="vipps-button-settings-section" id="vipps-button-settings-express-container"> | |
| 1159 | + <h2> <?php _e('Express Checkout', 'woo-vipps'); ?></h2> | |
| 965 | 1160 | |
| 966 | - <!-- EXPRESS SECTION --> | |
| 967 | - <div id="vipps-button-settings-express-container"> | |
| 968 | - <h2> <?php _e('Express Checkout', 'woo-vipps'); ?></h2> | |
| 969 | - <input type="hidden" name="action" value="update_vipps_button_settings" /> | |
| 970 | - <?php wp_nonce_field( 'buttonaction', 'buttonnonce'); ?> | |
| 1161 | + <!-- Context dropdown --> | |
| 1162 | + <div id="vipps-button-settings-express-context"> | |
| 1163 | + <label> | |
| 1164 | + <?php _e('Config context', 'woo-vipps'); ?> | |
| 1165 | + </label> | |
| 1166 | + <select id="context" onChange='updateContext()'> | |
| 1167 | + <?php foreach($contexts as $key => $label): ?> | |
| 1168 | + <option value="<?php echo $key; ?>" <?php if ('global' === $key) echo " selected "; ?> > | |
| 1169 | + <?php echo $label ; ?> | |
| 1170 | + </option> | |
| 1171 | + <?php endforeach; ?> | |
| 1172 | + </select> | |
| 1173 | + <label class="hidden" id="use-global-config-container"><input onchange="updateContext()" type="checkbox" name="express[tmpConfig][use-global-config]" checked><?php _e('Use global config', 'woo-vipps'); ?></label> | |
| 1174 | + </div> | |
| 1175 | + | |
| 971 | 1176 | |
| 972 | - <!-- variant --> | |
| 973 | - <div class="vipps-button-settings-section"> | |
| 974 | - <!-- variant dropdown --> | |
| 975 | - <div class="vipps-button-settings-express-demo-container"> | |
| 976 | - <label for="vippsButtonVariant"><?php _e('Choose variant', 'woo-vipps'); ?></label> | |
| 977 | - <select id="vippsButtonVariant" name="express[variant]" onChange='changeExpressVariant()'> | |
| 978 | - <?php foreach($variants as $key=>$name): ?> | |
| 979 | - <option value="<?php echo $key; ?>" <?php if ($init_states['express']['variant'] === $key) echo " selected "; ?> > | |
| 980 | - <?php echo $name ; ?> | |
| 981 | - </option> | |
| 982 | - <?php endforeach; ?> | |
| 983 | - </select> | |
| 984 | - </div> | |
| 1177 | + <!-- Button paremeter inputs. These input values are put into post data express.tmpConfig temporarily. | |
| 1178 | + On context change, configs are stored in a global 'contextConfigs'. Each config is processed into new option structure before submit. LP 2026-06-24 --> | |
| 1179 | + <div class="vipps-button-settings-section" id="vipps-button-settings-express-args"> | |
| 1180 | + <fieldset> | |
| 1181 | + <label><input type="checkbox" name="express[tmpConfig][rounded]" checked=""><?php _e('Rounded', 'woo-vipps'); ?></label> | |
| 1182 | + <label><input type="checkbox" name="express[tmpConfig][compact]"><?php _e('Compact', 'woo-vipps'); ?></label> | |
| 1183 | + <label><input type="checkbox" name="express[tmpConfig][stretched]"><?php _e('Stretched', 'woo-vipps'); ?></label> | |
| 1184 | + </fieldset> | |
| 1185 | + <fieldset> | |
| 1186 | + <legend><?php _e('Language', 'woo-vipps'); ?></legend> | |
| 1187 | + <label><input type="radio" name="express[tmpConfig][language]" checked value="store"><?php _e('Store language', 'woo-vipps'); ?></label> | |
| 1188 | + <label><input type="radio" name="express[tmpConfig][language]" value="en"><?php _e('English', 'woo-vipps'); ?></label> | |
| 1189 | + <label><input type="radio" name="express[tmpConfig][language]" value="no"><?php _e('Norwegian', 'woo-vipps'); ?></label> | |
| 1190 | + <label><input type="radio" name="express[tmpConfig][language]" value="dk"><?php _e('Danish', 'woo-vipps'); ?></label> | |
| 1191 | + <label><input type="radio" name="express[tmpConfig][language]" value="sv"><?php _e('Swedish', 'woo-vipps'); ?></label> | |
| 1192 | + <?php if ($this->get_payment_method_name() === 'MobilePay'): ?> | |
| 1193 | + <label><input type="radio" disabled="" name="express[tmpConfig][language]" value="fi"><?php _e('Finnish', 'woo-vipps'); ?></label> | |
| 1194 | + <?php endif; ?> | |
| 1195 | + </fieldset> | |
| 985 | 1196 | |
| 986 | - <!-- Preload all variant images. Javascript will show the active one. LP 2025-12-16 --> | |
| 987 | - <div class="vipps-button-settings-express-demo-container vipps-button-settings-img-container"> | |
| 988 | - <?php foreach(array_keys($variants) as $variant): ?> | |
| 989 | - <img | |
| 990 | - class="vipps-button-settings-express-demo" | |
| 991 | - id="vipps-button-settings-express-demo-<?php echo $variant; ?>" | |
| 992 | - src="<?php echo $this->get_express_logo($payment_method, $lang, $variant); ?>" | |
| 993 | - style="display: <?php echo ($variant === $init_states['express']['variant'] ? 'block' : 'none') ;?>;" | |
| 994 | - > | |
| 995 | - <?php endforeach; ?> | |
| 996 | - </div> | |
| 997 | - </div> | |
| 1197 | + <?php if ($this->get_payment_method_name() !== 'MobilePay'): ?> | |
| 1198 | + <p><?php printf(__('Finnish is currently only available with the %s payment method.', 'woo-vipps'), 'MobilePay'); ?></p> | |
| 1199 | + <?php endif; ?> | |
| 998 | 1200 | |
| 1201 | + <fieldset> | |
| 1202 | + <legend><?php _e('Verb', 'woo-vipps'); ?></legend> | |
| 1203 | + <label><input type="radio" name="express[tmpConfig][verb]" checked value="buy"><?php _e('Buy', 'woo-vipps'); ?></label> | |
| 1204 | + <label><input type="radio" name="express[tmpConfig][verb]" value="pay"><?php _e('Pay', 'woo-vipps'); ?></label> | |
| 1205 | + <label><input type="radio" name="express[tmpConfig][verb]" value="continue"><?php _e('Continue', 'woo-vipps'); ?></label> | |
| 1206 | + <label><input type="radio" name="express[tmpConfig][verb]" value="confirm"><?php _e('Confirm', 'woo-vipps'); ?></label> | |
| 1207 | + <label><input type="radio" name="express[tmpConfig][verb]" value="donate"><?php _e('Donate', 'woo-vipps'); ?></label> | |
| 1208 | + <label><input type="radio" name="express[tmpConfig][verb]" value="express"><?php _e('Express', 'woo-vipps'); ?></label> | |
| 1209 | + </fieldset> | |
| 1210 | + <fieldset> | |
| 1211 | + <legend><?php _e('Variant', 'woo-vipps'); ?></legend> | |
| 1212 | + <label><input type="radio" name="express[tmpConfig][variant]" checked value="primary"><?php _e('Primary', 'woo-vipps'); ?></label> | |
| 1213 | + <label><input type="radio" name="express[tmpConfig][variant]" value="dark"><?php _e('Dark (WCAG AAA)', 'woo-vipps'); ?></label> | |
| 1214 | + <label><input type="radio" name="express[tmpConfig][variant]" value="light"><?php _e('Light (WCAG AAA)', 'woo-vipps'); ?></label> | |
| 1215 | + </fieldset> | |
| 1216 | + </div> | |
| 999 | 1217 | |
| 1000 | - <!-- mini variant section --> | |
| 1001 | - <div class="vipps-button-settings-section"> | |
| 1002 | - <!-- Checkboxes "Use mini version for x page" --> | |
| 1003 | - <label><?php _e('Force mini variant in these contexts:', 'woo-vipps'); ?></label> | |
| 1004 | - <div class="vipps-button-settings-express-force-mini-container"> | |
| 1005 | - <label class="vipps-button-settings-express-force-mini" id="vipps-button-settings-express-force-mini-product"><?php _e('Product page', 'woo-vipps'); ?></label> | |
| 1006 | - <input name="express[force-mini][product]" type="hidden" value="no"> | |
| 1007 | - <input name="express[force-mini][product]" type="checkbox" value="yes" <?php if ($init_states['express']['force-mini']['product'] == "yes") echo "checked";?>> | |
| 1008 | - </div> | |
| 1218 | + <!-- Button preview that changes depending on the chosen parameters. LP 2026-06-24 --> | |
| 1219 | + <?php echo $this->get_html_button($init_args); ?> | |
| 1220 | + </div> | |
| 1009 | 1221 | |
| 1010 | - <div class="vipps-button-settings-express-force-mini-container"> | |
| 1011 | - <label class="vipps-button-settings-express-force-mini" id="vipps-button-settings-express-force-mini-catalog"><?php _e('Catalog page', 'woo-vipps'); ?></label> | |
| 1012 | - <input name="express[force-mini][catalog]" type="hidden" value="no"> | |
| 1013 | - <input name="express[force-mini][catalog]" type="checkbox" value="yes" <?php if ($init_states['express']['force-mini']['catalog'] == "yes") echo "checked";?>> | |
| 1014 | - </div> | |
| 1222 | + <script> | |
| 1223 | + // When inputs change, update the preview args. LP 2026-06-24 | |
| 1224 | + jQuery('#vipps-button-settings-express-args input').on('click', updatePreview); | |
| 1015 | 1225 | |
| 1016 | - <div class="vipps-button-settings-express-force-mini-container"> | |
| 1017 | - <label class="vipps-button-settings-express-force-mini" id="vipps-button-settings-express-force-mini-cart"><?php _e('Cart', 'woo-vipps'); ?></label> | |
| 1018 | - <input name="express[force-mini][cart]" type="hidden" value="no"> | |
| 1019 | - <input name="express[force-mini][cart]" type="checkbox" value="yes" <?php if ($init_states['express']['force-mini']['cart'] == "yes") echo "checked";?>> | |
| 1020 | - </div> | |
| 1226 | + let currentContext = '<?php echo $init_context; ?>'; | |
| 1227 | + let contextConfigs = <?php echo json_encode($configs) ?: "{}"; ?> // maps context slug to config object. LP 2026-06-24 | |
| 1021 | 1228 | |
| 1022 | - <div class="vipps-button-settings-express-force-mini-container"> | |
| 1023 | - <label class="vipps-button-settings-express-force-mini" id="vipps-button-settings-express-force-mini-minicart"><?php _e('Mini cart', 'woo-vipps'); ?></label> | |
| 1024 | - <input name="express[force-mini][minicart]" type="hidden" value="no"> | |
| 1025 | - <input name="express[force-mini][minicart]" type="checkbox" value="yes" <?php if ($init_states['express']['force-mini']['minicart'] == "yes") echo "checked";?>> | |
| 1026 | - </div> | |
| 1229 | + // Updates the actual html inputs from given config. LP 2026-07-01 | |
| 1230 | + function setInputsFromConfig(context, config) { | |
| 1231 | + const useGlobalConfig = Boolean(config?.["use-global-config"]); | |
| 1232 | + const isGlobal = "global" === context; | |
| 1027 | 1233 | |
| 1028 | - <!-- mini variant dropdown --> | |
| 1029 | - <div class="vipps-button-settings-express-mini-demo-container"> | |
| 1030 | - <label for="vippsButtonMiniVariant"><?php _e('Choose variant to use in mini contexts', 'woo-vipps'); ?></label> | |
| 1031 | - <select id="vippsButtonMiniVariant" name="express[mini-variant]" onChange='changeExpressMiniVariant()'> | |
| 1032 | - <?php foreach($mini_variants as $key=>$name): ?> | |
| 1033 | - <option value="<?php echo $key; ?>" <?php if ($init_states['express']['mini-variant'] === $key) echo " selected "; ?> > | |
| 1034 | - <?php echo $name ; ?> | |
| 1035 | - </option> | |
| 1036 | - <?php endforeach; ?> | |
| 1037 | - </select> | |
| 1038 | - </div> | |
| 1234 | + // Only show the 'use-global-config' checkbox for nonglobal context. LP 2026-06-26 | |
| 1235 | + jQuery('#use-global-config-container').toggleClass('hidden', isGlobal); | |
| 1039 | 1236 | |
| 1040 | - <!-- Preload mini variant imgs. LP 2025-12-17 --> | |
| 1041 | - <div class="vipps-button-settings-express-mini-demo-container vipps-button-settings-img-container"> | |
| 1042 | - <?php foreach(array_keys($mini_variants) as $variant): ?> | |
| 1043 | - <img | |
| 1044 | - class="vipps-button-settings-express-mini-demo" | |
| 1045 | - id="vipps-button-settings-express-mini-demo-<?php echo $variant; ?>" | |
| 1046 | - src="<?php echo $this->get_express_logo($payment_method, $lang, $variant); ?>" | |
| 1047 | - style="display: <?php echo ($variant === $init_states['express']['mini-variant'] ? 'block' : 'none') ;?>;" | |
| 1048 | - > | |
| 1049 | - <?php endforeach; ?> | |
| 1050 | - </div> | |
| 1051 | - </div> | |
| 1237 | + // Nonglobal contexts with useGlobalConfig, and empty configs, should fallback to the global config. LP 2026-06-26 | |
| 1238 | + if (!config || (!isGlobal && useGlobalConfig)) { | |
| 1239 | + config = contextConfigs["global"]; | |
| 1052 | 1240 | |
| 1053 | - <!-- END EXPRESS SECTION --> | |
| 1054 | - </div> | |
| 1241 | + jQuery('input[name="express[tmpConfig][use-global-config]"]').prop("checked", true); | |
| 1055 | 1242 | |
| 1056 | - <!-- Save button --> | |
| 1057 | - <div id="vipps-button-settings-save"> | |
| 1058 | - <input class="btn button primary" type="submit" value="<?php _e('Update settings', 'woo-vipps'); ?>" /> | |
| 1059 | - </div> | |
| 1243 | + // When using global config, the inputs should be disabled until its unchecked. LP 2026-06-26 | |
| 1244 | + jQuery('#vipps-button-settings-express-args input').prop("disabled", true); | |
| 1245 | + } else { | |
| 1246 | + jQuery('input[name="express[tmpConfig][use-global-config]"]').prop("checked", false); | |
| 1247 | + jQuery('#vipps-button-settings-express-args input').prop("disabled", false); | |
| 1248 | + } | |
| 1060 | 1249 | |
| 1061 | - </form> | |
| 1062 | - </div> | |
| 1250 | + Object.entries(config).forEach(([key, val]) => { | |
| 1251 | + if ("use-global-config" === key) return; | |
| 1252 | + const inputs = jQuery(`input[name="express[tmpConfig][${key}]"]`); | |
| 1253 | + const type = inputs.prop('type'); | |
| 1254 | + switch (type) { | |
| 1255 | + case "checkbox": | |
| 1256 | + inputs.prop('checked', typeof val === "boolean" ? val : "true" === val); | |
| 1257 | + break; | |
| 1258 | + case "radio": | |
| 1259 | + inputs.filter(`[value="${val}"]`).prop('checked', true); | |
| 1260 | + break; | |
| 1261 | + default: | |
| 1262 | + console.error(`woo-vipps: Unexpected input type '${type}' for button config. key=${key}, val=${val}`); | |
| 1263 | + } | |
| 1264 | + }); | |
| 1063 | 1265 | |
| 1064 | - <script> | |
| 1065 | - function changeExpressVariant() { | |
| 1066 | - const variant = jQuery('#vippsButtonVariant').val().trim(); | |
| 1067 | - // Show the one selected, hide all others. LP 2025-12-16 | |
| 1068 | - jQuery('.vipps-button-settings-express-demo').hide(); | |
| 1069 | - jQuery(`#vipps-button-settings-express-demo-${variant}`).show(); | |
| 1070 | - } | |
| 1266 | + updatePreview(); | |
| 1267 | + } | |
| 1268 | + // init the starting config from option. LP 2026-06-25 | |
| 1269 | + setInputsFromConfig(currentContext, contextConfigs[currentContext]); | |
| 1071 | 1270 | |
| 1072 | - function changeExpressMiniVariant() { | |
| 1073 | - const variant = jQuery('#vippsButtonMiniVariant').val().trim(); | |
| 1074 | - // Show the one selected, hide all others. LP 2025-12-16 | |
| 1075 | - jQuery('.vipps-button-settings-express-mini-demo').hide(); | |
| 1076 | - jQuery(`#vipps-button-settings-express-mini-demo-${variant}`).show(); | |
| 1077 | - } | |
| 1078 | - </script> | |
| 1271 | + // Update the preview web component's attributes. LP 2026-06-24 | |
| 1272 | + function updatePreview(event) { | |
| 1273 | + const args = getPreviewArgs(); | |
| 1274 | + // FIXME: when i use get_customer_language() here it gives me my user language, but on frontend it gives the site language, i.e not the same value. So this preview will be wrong language. so use get_locale for now. LP 2026-07-02 | |
| 1275 | + // if ('store' === args.language) args.language = '<?php echo $this->get_customer_language(); ?>'; | |
| 1276 | + if ('store' === args.language) args.language = '<?php echo substr(get_locale(), 0, 2); ?>'; | |
| 1277 | + const button = jQuery('#vipps-button-express-preview'); | |
| 1278 | + button.attr(args); | |
| 1279 | + } | |
| 1280 | + | |
| 1281 | + function getPreviewArgs() { | |
| 1282 | + const args = {}; | |
| 1283 | + jQuery('#vipps-button-settings-express-args input').each(function () { | |
| 1284 | + // inputs are put in form arrays like 'express[tmpConfig][attribute]', so extract the actual attribute name. LP 2026-06-24 | |
| 1285 | + const matches = [...this.name.matchAll(/\[([^\]]+)\]/g)]; | |
| 1286 | + const attr = matches.length ? matches[matches.length - 1][1] : null; | |
| 1287 | + if (!attr) { | |
| 1288 | + console.error("woo-vipps: Could not extract attribute name for button preview:", this); | |
| 1289 | + return; | |
| 1290 | + } | |
| 1291 | + if (this.type === 'checkbox') { | |
| 1292 | + args[attr] = this.checked; | |
| 1293 | + } else if (this.checked) { | |
| 1294 | + args[attr] = this.value; | |
| 1295 | + } | |
| 1296 | + }); | |
| 1297 | + | |
| 1298 | + return args; | |
| 1299 | + } | |
| 1300 | + | |
| 1301 | + // Stores selected config for context and switches to another (if changed). LP 2026-07-01 | |
| 1302 | + function updateContext() { | |
| 1303 | + const wasGlobal = "global" === currentContext; | |
| 1304 | + const useGlobalConfig = jQuery('#use-global-config-container input').prop("checked"); | |
| 1305 | + | |
| 1306 | + // Store config to global, unless its a non-global context that uses global config. LP 2026-06-25 | |
| 1307 | + if (wasGlobal || !useGlobalConfig) { | |
| 1308 | + contextConfigs[currentContext] = getPreviewArgs(); | |
| 1309 | + } else { | |
| 1310 | + contextConfigs[currentContext] = {'use-global-config': true}; | |
| 1311 | + } | |
| 1312 | + | |
| 1313 | + // Swap to new context: set all input fields to the stored values if exists. LP 2026-06-25 | |
| 1314 | + const newContext = jQuery("#context").val(); | |
| 1315 | + const newConfig = contextConfigs[newContext]; | |
| 1316 | + setInputsFromConfig(newContext, newConfig); | |
| 1317 | + currentContext = newContext; | |
| 1318 | + } | |
| 1319 | + | |
| 1320 | + // Before submit: delete the tmpConfig for the current selected values, and add the stored contextConfigs to the post data. LP 2026-06-24 | |
| 1321 | + jQuery('#vipps-button-settings-form').on('formdata', e => { | |
| 1322 | + const formData = e?.originalEvent?.formData; | |
| 1323 | + if (!formData) return; | |
| 1324 | + | |
| 1325 | + // run this to store current context config before posting. LP 2026-06-24 | |
| 1326 | + updateContext(); | |
| 1327 | + | |
| 1328 | + // now we can delete the current temporary config from post data. LP 2026-06-24 | |
| 1329 | + const keysToDelete = []; | |
| 1330 | + for (const [key] of formData.entries()) { | |
| 1331 | + if (key.startsWith("express[tmpConfig][")) { | |
| 1332 | + keysToDelete.push(key); | |
| 1333 | + } | |
| 1334 | + } | |
| 1335 | + keysToDelete.forEach(key => formData.delete(key)); | |
| 1336 | + | |
| 1337 | + // Now add the actual post data from the stored global contextConfigs. LP 2026-06-24 | |
| 1338 | + Object.entries(contextConfigs).forEach(([context, config]) => { | |
| 1339 | + Object.entries(config).forEach(([key, val]) => { | |
| 1340 | + formData.append(`express[configs][${context}][${key}]`, val); | |
| 1341 | + }); | |
| 1342 | + }); | |
| 1343 | + }); | |
| 1344 | + </script> | |
| 1079 | 1345 | <?php |
| 1080 | 1346 | } |
| 1081 | 1347 | |
| 1082 | 1348 | |
| @@ -1329,22 +1595,35 @@ | ||
| 1329 | 1595 | public function delete_old_cancelled_orders() { |
| 1330 | 1596 | $limit = 30; |
| 1331 | 1597 | $cutoff = time() - 600; // Ten minutes old orders: Delete them |
| 1332 | 1598 | $oldorders = time() - (60*60*24*7); // Very old orders: Ignore them to make this work on sites with enormous order databases |
| 1333 | - // Ensure the old order table understands the meta query IOK 2022-12-02 | |
| 1334 | - static::add_wc_order_meta_key_support(); | |
| 1335 | - $args = array( | |
| 1599 | + $delenda = []; | |
| 1600 | + | |
| 1601 | + if ($this->useHPOS()) { | |
| 1602 | + $args = array( | |
| 1336 | 1603 | 'status' => 'cancelled', |
| 1337 | 1604 | 'limit' => $limit, |
| 1338 | 1605 | 'date_modified' => "$oldorders...$cutoff", |
| 1339 | - 'meta_vipps_delendum' => 1); | |
| 1340 | - if ($this->useHPOS()) { | |
| 1341 | - /* The above, with the filter, is for the old orders table, the below is for the new IOK 2022-12-02 */ | |
| 1342 | - $args['meta_query'] = [[ 'key' => '_vipps_delendum', 'value' => 1 ]]; | |
| 1606 | + 'meta_query' => [[ 'key' => '_vipps_delendum', 'value' => 1 ]] | |
| 1607 | + ); | |
| 1608 | + $delenda = wc_get_orders($args); | |
| 1609 | + } else { | |
| 1610 | + // Old-style orders, we'll just use SQL | |
| 1611 | + global $wpdb; | |
| 1612 | + $sql = $wpdb->prepare("SELECT p.ID FROM {$wpdb->posts} p JOIN {$wpdb->postmeta} pm on (pm.post_id = p.ID AND pm.meta_key = '_vipps_delendum') WHERE p.post_type = 'shop_order' AND p.post_status = 'wc-cancelled' AND p.post_modified_gmt >= %s AND p.post_modified_gmt <= %s AND pm.meta_value = 1 LIMIT %d", | |
| 1613 | + gmdate( 'Y-m-d H:i:s', $oldorders ), | |
| 1614 | + gmdate( 'Y-m-d H:i:s', $cutoff ), | |
| 1615 | + $limit | |
| 1616 | + ); | |
| 1617 | + $order_ids = $wpdb->get_col($sql); | |
| 1618 | + foreach($order_ids as $did) { | |
| 1619 | + $d = wc_get_order($did); | |
| 1620 | + if ($d && !is_wp_error($d)) { | |
| 1621 | + $delenda[] = $d; | |
| 1622 | + } | |
| 1623 | + } | |
| 1343 | 1624 | } |
| 1344 | 1625 | |
| 1345 | - $delenda = wc_get_orders($args); | |
| 1346 | - | |
| 1347 | 1626 | foreach ($delenda as $del) { |
| 1348 | 1627 | // Delete only if there is no customer info for the order IOK 2022-10-12 |
| 1349 | 1628 | if (!$del->get_billing_email()) { |
| 1350 | 1629 | $del->delete(true); |
| @@ -1460,9 +1739,9 @@ | ||
| 1460 | 1739 | $orderid = isset($_REQUEST['id']) ? $_REQUEST['id'] : 0; |
| 1461 | 1740 | $order = wc_get_order($orderid); |
| 1462 | 1741 | } |
| 1463 | 1742 | } |
| 1464 | - if (is_a($order, 'WC_Order') && $order->get_payment_method() == 'vipps') { | |
| 1743 | + if (is_a($order, 'WC_Order') && self::is_vipps_order($order)) { | |
| 1465 | 1744 | $vippsorder = true; |
| 1466 | 1745 | } |
| 1467 | 1746 | |
| 1468 | 1747 | if ($vippsorder) { |
| @@ -1478,20 +1757,37 @@ | ||
| 1478 | 1757 | } |
| 1479 | 1758 | wp_register_script('vipps-gw',plugins_url('js/vipps.js',__FILE__),array('jquery','wp-hooks'),filemtime(dirname(__FILE__) . "/js/vipps.js"), 'true'); |
| 1480 | 1759 | |
| 1481 | 1760 | // Badges - web components provided by Vipps MobilePay to display payment options in-store. |
| 1482 | - wp_register_script('vipps-onsite-messageing','https://checkout.vipps.no/on-site-messaging/v1/vipps-osm.js',array(),WOO_VIPPS_VERSION, | |
| 1483 | - array( | |
| 1761 | + wp_register_script('vipps-onsite-messageing', | |
| 1762 | + plugins_url('js/vipps-on-site-messaging.js', WC_VIPPS_PAYMENT_MAIN_FILE), | |
| 1763 | + array(), | |
| 1764 | + filemtime(dirname(WC_VIPPS_PAYMENT_MAIN_FILE) . '/js/vipps-on-site-messaging.js'), | |
| 1765 | + [ | |
| 1484 | 1766 | 'in_footer' => true, |
| 1485 | 1767 | 'strategy' => 'async', |
| 1486 | - )); | |
| 1768 | + ], | |
| 1769 | + ); | |
| 1487 | 1770 | |
| 1771 | + // Button web component downloaded from https://cdn.vippsmobilepay.com/js/button/button.js. LP 2026-06-24 | |
| 1772 | + wp_register_script('vipps-button-webcomponent', | |
| 1773 | + plugins_url('js/vipps-button.js', WC_VIPPS_PAYMENT_MAIN_FILE), | |
| 1774 | + array(), | |
| 1775 | + filemtime(dirname(WC_VIPPS_PAYMENT_MAIN_FILE) . '/js/vipps-button.js'), | |
| 1776 | + [ | |
| 1777 | + 'in_footer' => false | |
| 1778 | + ], | |
| 1779 | + ); | |
| 1488 | 1780 | } |
| 1489 | 1781 | |
| 1490 | 1782 | // Runs late in both wp_enqueue_scripts and admin_enqueue_scripts to make it more compatible with translation plugins IOK 2026-02-02 |
| 1491 | 1783 | public function script_add_vippslocale () { |
| 1492 | 1784 | // This is actually for the payment block, where localize script has started to not-work in certain contexts. IOK 2022-12-13 |
| 1493 | - $strings = array('Continue with Vipps'=>sprintf(__('Continue with %1$s', 'woo-vipps'), $this->get_payment_method_name()),'Vipps'=> sprintf(__('%1$s', 'woo-vipps'), $this->get_payment_method_name())); | |
| 1785 | + $strings = array( | |
| 1786 | + 'Continue with Vipps'=>sprintf(__('Continue with %1$s', 'woo-vipps'), $this->get_payment_method_name()), | |
| 1787 | + 'Vipps'=> sprintf(__('%1$s', 'woo-vipps'), $this->get_payment_method_name()), | |
| 1788 | + 'pay_with_card' => sprintf(__('Pay with card through %1$s', 'woo-vipps'), $this->get_payment_method_name()), | |
| 1789 | + ); | |
| 1494 | 1790 | wp_localize_script('vipps-gw', 'VippsLocale', $strings); |
| 1495 | 1791 | } |
| 1496 | 1792 | |
| 1497 | 1793 | public function wp_enqueue_scripts() { |
| @@ -1500,8 +1796,9 @@ | ||
| 1500 | 1796 | $this->script_add_vippslocale(); |
| 1501 | 1797 | |
| 1502 | 1798 | wp_enqueue_script('vipps-gw'); |
| 1503 | 1799 | wp_enqueue_style('vipps-gw',plugins_url('css/vipps.css',__FILE__),array(),filemtime(dirname(__FILE__) . "/css/vipps.css")); |
| 1800 | + wp_enqueue_script('vipps-button-webcomponent'); | |
| 1504 | 1801 | } |
| 1505 | 1802 | |
| 1506 | 1803 | |
| 1507 | 1804 | public function add_shortcodes() { |
| @@ -1513,8 +1810,11 @@ | ||
| 1513 | 1810 | // New vipps-mobilepay-badge shortcode. LP 19.11.2024 |
| 1514 | 1811 | add_shortcode('vipps-mobilepay-badge', array($this, 'vipps_mobilepay_badge_shortcode')); |
| 1515 | 1812 | // Legacy vipps-badge shortcode. LP 19.11.2024 |
| 1516 | 1813 | add_shortcode('vipps-badge', array($this, 'vipps_badge_shortcode')); |
| 1814 | + | |
| 1815 | + // special page handling, previously a fake page. LP 2026-08-25 | |
| 1816 | + add_shortcode('vipps_special_page', array($this, 'vipps_special_page_shortcode')); | |
| 1517 | 1817 | } |
| 1518 | 1818 | |
| 1519 | 1819 | |
| 1520 | 1820 | public function log ($what,$type='info') { |
| @@ -1540,8 +1840,10 @@ | ||
| 1540 | 1840 | } |
| 1541 | 1841 | |
| 1542 | 1842 | // Show express button option on checkout form. LP 2026-03-23 |
| 1543 | 1843 | public function checkout_before_customer_details_express () { |
| 1844 | + if (did_action('woo_vipps_checkout_before_customer_details_express')) return; | |
| 1845 | + do_action('woo_vipps_checkout_before_customer_details_express'); | |
| 1544 | 1846 | $gw = $this->gateway(); |
| 1545 | 1847 | if (!$gw->show_express_checkout()) return; |
| 1546 | 1848 | $this->express_checkout_section_html(); |
| 1547 | 1849 | } |
| @@ -1548,13 +1850,13 @@ | ||
| 1548 | 1850 | |
| 1549 | 1851 | public function express_checkout_section_html() { |
| 1550 | 1852 | $payment_method = $this->get_payment_method_name(); |
| 1551 | 1853 | $header_text = __('Express Checkout', 'woo-vipps'); |
| 1552 | - $header = "<div class='express-header'>$header_text</div>"; | |
| 1854 | + $header = "<legend class='express-header'>$header_text</legend>"; | |
| 1553 | 1855 | $div_classes = "legacy-checkout vipps-express-checkout $payment_method"; |
| 1554 | - echo "<div class='$div_classes'>$header"; | |
| 1555 | - $this->cart_express_checkout_button_html(); | |
| 1556 | - echo '</div>'; | |
| 1856 | + echo "<fieldset class='$div_classes'>$header"; | |
| 1857 | + $this->checkout_express_checkout_button_html(); | |
| 1858 | + echo '</fieldset>'; | |
| 1557 | 1859 | } |
| 1558 | 1860 | |
| 1559 | 1861 | public function express_checkout_banner() { |
| 1560 | 1862 | $gw = $this->gateway(); |
| @@ -1580,8 +1882,27 @@ | ||
| 1580 | 1882 | <div class="<?php echo $div_classes;?>"><?php echo $message;?></div> |
| 1581 | 1883 | <?php |
| 1582 | 1884 | } |
| 1583 | 1885 | |
| 1886 | + public function checkout_express_checkout_button() { | |
| 1887 | + $gw = $this->gateway(); | |
| 1888 | + | |
| 1889 | + if ($gw->show_express_checkout()){ | |
| 1890 | + return $this->checkout_express_checkout_button_html(); | |
| 1891 | + } | |
| 1892 | + } | |
| 1893 | + | |
| 1894 | + public function checkout_express_checkout_button_html() { | |
| 1895 | + $url = $this->express_checkout_url(); | |
| 1896 | + $url = wp_nonce_url($url,'express','sec'); | |
| 1897 | + $button= apply_filters('woo_vipps_express_checkout_button', $this->get_html_button_for_context('checkout')); | |
| 1898 | + $method = $this->get_payment_method_name(); | |
| 1899 | + $title = sprintf(__('Buy now with %1$s!', 'woo-vipps'), $method); | |
| 1900 | + $html = "<a href='$url' class='vipps-express-checkout short $method' title='$title'>$button</a>"; | |
| 1901 | + $html = apply_filters('woo_vipps_cart_express_checkout_button', $html, $url); | |
| 1902 | + echo $html; | |
| 1903 | + } | |
| 1904 | + | |
| 1584 | 1905 | // Show the express button if reasonable to do so |
| 1585 | 1906 | public function cart_express_checkout_button() { |
| 1586 | 1907 | $gw = $this->gateway(); |
| 1587 | 1908 | |
| @@ -1600,22 +1921,46 @@ | ||
| 1600 | 1921 | |
| 1601 | 1922 | public function cart_express_checkout_button_html($minicart = false) { |
| 1602 | 1923 | $url = $this->express_checkout_url(); |
| 1603 | 1924 | $url = wp_nonce_url($url,'express','sec'); |
| 1604 | - $page = $minicart ? 'minicart' : 'cart'; | |
| 1605 | - $imgurl= apply_filters('woo_vipps_express_checkout_button', $this->get_payment_logo($page)); | |
| 1925 | + $context = $minicart ? 'minicart' : 'cart'; | |
| 1926 | + $button= apply_filters('woo_vipps_express_checkout_button', $this->get_html_button_for_context($context)); | |
| 1606 | 1927 | $method = $this->get_payment_method_name(); |
| 1607 | 1928 | $title = sprintf(__('Buy now with %1$s!', 'woo-vipps'), $method); |
| 1608 | - $button = "<a href='$url' class='button vipps-express-checkout short $method' title='$title'><img alt='$title' border=0 src='$imgurl'></a>"; | |
| 1609 | - $button = apply_filters('woo_vipps_cart_express_checkout_button', $button, $url); | |
| 1610 | - echo $button; | |
| 1929 | + $html = "<a href='$url' class='vipps-express-checkout short $method' title='$title'>$button</a>"; | |
| 1930 | + $html = apply_filters('woo_vipps_cart_express_checkout_button', $html, $url); | |
| 1931 | + echo $html; | |
| 1611 | 1932 | } |
| 1612 | 1933 | |
| 1613 | 1934 | // A shortcode for a single buy now button. Express checkout must be active; but I don't check for this here, as this button may be |
| 1614 | 1935 | // cached. Therefore stock, purchasability etc will be done later. IOK 2018-10-02 |
| 1615 | 1936 | public function buy_now_button_shortcode ($atts) { |
| 1616 | - $args = shortcode_atts( array( 'id' => '','variant'=>'','sku' => '',), $atts ); | |
| 1617 | - return "<div class='vipps_buy_now_wrapper noloop'>". $this->get_buy_now_button($args['id'], $args['variant'], $args['sku'], false, '', 'shortcode') . "</div>"; | |
| 1937 | + // The new web component button args. LP 2026-07-02 | |
| 1938 | + $button_args = $this->get_html_button_default_attrs(); | |
| 1939 | + unset($button_args['brand']); | |
| 1940 | + | |
| 1941 | + // Variant exists for the product variant. LP 2026-07-02 | |
| 1942 | + if (isset($button_args['variant'])) $button_args['button_variant'] = $button_args['variant']; | |
| 1943 | + unset($button_args['variant']); | |
| 1944 | + | |
| 1945 | + $args = shortcode_atts( | |
| 1946 | + array(...$button_args, | |
| 1947 | + 'id' => '','variant'=> '','sku' => '', | |
| 1948 | + ), | |
| 1949 | + $atts, | |
| 1950 | + ); | |
| 1951 | + | |
| 1952 | + // Variant exists for the product variant. LP 2026-07-02 | |
| 1953 | + $button_args = $args; | |
| 1954 | + if (isset($button_args['button_variant'])) $button_args['variant'] = $button_args['button_variant']; | |
| 1955 | + unset($button_args['button_variant']); | |
| 1956 | + unset($button_args['sku']); | |
| 1957 | + unset($button_args['id']); | |
| 1958 | + // NB: the language may be incorrect for the shortcode, see web component bug at https://developer.vippsmobilepay.com/docs/knowledge-base/buttons/ | |
| 1959 | + // "Note also that there is a bug in the library, and it currently only renders one language per page." | |
| 1960 | + // it seems like get_html_button() runs once before this shortcode code (whic gets default attrs including language), so I think this is why language bug appears. LP 2026-07-02 | |
| 1961 | + | |
| 1962 | + return "<div class='vipps_buy_now_wrapper noloop'>". $this->get_buy_now_button($args['id'], $args['variant'], $args['sku'], false, '', 'shortcode', $button_args) . "</div>"; | |
| 1618 | 1963 | } |
| 1619 | 1964 | |
| 1620 | 1965 | // The express checkout shortcode implementation. It does not need to check if we are to show the button, obviously, but needs to see if the cart works |
| 1621 | 1966 | public function express_checkout_button_shortcode() { |
| @@ -1891,9 +2236,9 @@ | ||
| 1891 | 2236 | public function add_vipps_metabox ($post_or_order_object) { |
| 1892 | 2237 | $order = ( $post_or_order_object instanceof WP_Post ) ? wc_get_order( $post_or_order_object->ID ) : $post_or_order_object; |
| 1893 | 2238 | $order = wc_get_order($post_or_order_object); |
| 1894 | 2239 | $pm = $order->get_payment_method(); |
| 1895 | - if ($pm != 'vipps') return; | |
| 2240 | + if (!self::is_vipps_order($pm)) return; | |
| 1896 | 2241 | $orderid=$order->get_id(); |
| 1897 | 2242 | |
| 1898 | 2243 | $init = intval($order->get_meta('_vipps_init_timestamp')); |
| 1899 | 2244 | $callback = intval($order->get_meta('_vipps_callback_timestamp')); |
| @@ -1989,9 +2334,9 @@ | ||
| 1989 | 2334 | print "<p>" . __("Unknown order", 'woo-vipps') . "</p>"; |
| 1990 | 2335 | exit(); |
| 1991 | 2336 | } |
| 1992 | 2337 | $pm = $order->get_payment_method(); |
| 1993 | - if ($pm != 'vipps') { | |
| 2338 | + if (!self::is_vipps_order($pm)) { | |
| 1994 | 2339 | print "<p>" . sprintf(__("The order is not a %1\$s order", 'woo-vipps'), $this->get_payment_method_name()) . "</p>"; |
| 1995 | 2340 | exit(); |
| 1996 | 2341 | } |
| 1997 | 2342 | |
| @@ -2235,24 +2580,28 @@ | ||
| 2235 | 2580 | |
| 2236 | 2581 | |
| 2237 | 2582 | // Because the prefix used to create the Vipps order id is editable |
| 2238 | 2583 | // by the user, we will store that as a meta and use this for callbacks etc. |
| 2239 | - // IOK: This needs to be replaced by a separate table, but in the meantime, we will use | |
| 2240 | - // wc_get_orders and not $wpdb directly, so it should work with HPOS too. | |
| 2241 | 2584 | // IOK 2023-01-23 this function is no longer used, and kept only for backwards compatibility with |
| 2242 | 2585 | // debug filters and similar. |
| 2586 | + // IOK 2026-05-27 rewritten to avoid wc_get_orders for pre-HPOS. Still not used. | |
| 2243 | 2587 | public function getOrderIdByVippsOrderId($vippsorderid) { |
| 2244 | - // Ensure the old order table understands the meta query IOK 2022-12-02 | |
| 2245 | - static::add_wc_order_meta_key_support(); | |
| 2246 | - $result = wc_get_orders( array( | |
| 2247 | - 'limit' => 1, | |
| 2248 | - 'return' => 'ids', | |
| 2249 | - 'meta_vipps_orderid' => $vippsorderid, | |
| 2250 | - /* The above, with the filter, is for the old orders table, the below is for the new IOK 2022-12-02 */ | |
| 2251 | - 'meta_query' => [[ 'key' => '_vipps_orderid', 'value' => $vippsorderid ]] | |
| 2252 | - )); | |
| 2253 | - if ($result && is_array($result)) return $result[0]; | |
| 2254 | - | |
| 2588 | + $result = false; | |
| 2589 | + if ($this->useHPOS()) { | |
| 2590 | + $result = wc_get_orders( array( | |
| 2591 | + 'limit' => 1, | |
| 2592 | + 'return' => 'ids', | |
| 2593 | + 'meta_query' => [[ 'key' => '_vipps_orderid', 'value' => $vippsorderid ]] | |
| 2594 | + )); | |
| 2595 | + if ($result && is_array($result)) return $result[0]; | |
| 2596 | + } else { | |
| 2597 | + // Pre-HPOS did not support meta_query, so we're doing it with direct access to the database. IOK 2026-05-27 | |
| 2598 | + global $wpdb; | |
| 2599 | + $q = $wpdb->prepare("SELECT p.ID from `{$wpdb->posts}` p JOIN `{$wpdb->postmeta}` m ON (m.post_id = p.ID and m.meta_key = '_vipps_orderid') WHERE p.post_type = 'shop_order' AND m.meta_value = %s LIMIT 1", $vippsorderid); | |
| 2600 | + $res = $wpdb->get_results($q, ARRAY_A); | |
| 2601 | + if (empty($res)) return 0; | |
| 2602 | + return $res[0]['ID']; | |
| 2603 | + } | |
| 2255 | 2604 | return 0; |
| 2256 | 2605 | } |
| 2257 | 2606 | |
| 2258 | 2607 | // This is like getOrderByVipsOrderId, but only fetches pending orders. |
| @@ -2263,9 +2612,8 @@ | ||
| 2263 | 2612 | $result = wc_get_orders( array( |
| 2264 | 2613 | 'limit' => 1, |
| 2265 | 2614 | 'status' => 'wc-pending', |
| 2266 | 2615 | 'type' => 'shop_order', |
| 2267 | - 'payment_method' => 'vipps', | |
| 2268 | 2616 | 'date_created' => '>' . $sevendaysago, |
| 2269 | 2617 | 'return' => 'objects', |
| 2270 | 2618 | 'meta_query' => [[ 'key' => '_vipps_orderid', 'value' => $vippsorderid ]] |
| 2271 | 2619 | )); |
| @@ -2281,77 +2629,69 @@ | ||
| 2281 | 2629 | return null; |
| 2282 | 2630 | } |
| 2283 | 2631 | } |
| 2284 | 2632 | |
| 2633 | + // Special pages, and some callbacks. IOK 2018-05-18 | |
| 2634 | + public function template_redirect() { | |
| 2285 | 2635 | |
| 2286 | - // If this is a special page, return true very early because we are handling this. IOK 2023-02-22 | |
| 2287 | - public function pre_handle_404($current, $query) { | |
| 2288 | - if (!is_admin()) { | |
| 2289 | - $special = $this->is_special_page(); | |
| 2290 | - if ($special) { | |
| 2291 | - // Ensure very early on that Autooptimize does not try to optimize us (if installed) IOK 2023-03-04 | |
| 2292 | - add_filter( 'autoptimize_filter_noptimize', '__return_true'); | |
| 2293 | - return true; | |
| 2294 | - } | |
| 2636 | + // Handle legacy vipps-buy-now urls that auto-start express checkout for certain product - in QR codes etc IOK 2026-09-11 | |
| 2637 | + // We redirect these to the new location. | |
| 2638 | + $path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); | |
| 2639 | + if (( ($_GET['VippsSpecialPage'] ?? '') == 'vipps-buy-product') || ($path && preg_match("!/vipps-buy-product/?$!", $path)) ) { | |
| 2640 | + $url = static::get_special_page_url(); | |
| 2641 | + $_GET['action'] = 'buy_product'; | |
| 2642 | + $q = build_query($_GET); | |
| 2643 | + wp_redirect($url . "?" . $q, 302); | |
| 2644 | + exit(); | |
| 2295 | 2645 | } |
| 2296 | - return $current; | |
| 2297 | - } | |
| 2298 | 2646 | |
| 2299 | - // Special pages, and some callbacks. IOK 2018-05-18 | |
| 2300 | - public function template_redirect() { | |
| 2301 | - global $post; | |
| 2302 | - // Handle special callbacks | |
| 2303 | - $special = $this->is_special_page() ; | |
| 2304 | - | |
| 2305 | - if ($special) { | |
| 2647 | + if (static::is_special_page()) { | |
| 2648 | + // Legacy: Stop the canonical redirect here. Unclear if still necessary. IOK 2026-09-11 | |
| 2306 | 2649 | remove_filter('template_redirect', 'redirect_canonical', 10); |
| 2307 | - do_action('woo_vipps_before_handling_special_page', $special); | |
| 2650 | + // dont cache special page. LP 2026-08-25 | |
| 2651 | + $this->nocache(); | |
| 2652 | + // Do the custom pre-load actions for these pages IOK 2026-09-11 | |
| 2653 | + do_action('woo_vipps_before_handling_special_page', $_GET['action']); | |
| 2654 | + } | |
| 2655 | + } | |
| 2308 | 2656 | |
| 2309 | - // Allow above hook to actually handle special pages. It should probably call $Vipps->fakepage or a redirect; can be used | |
| 2310 | - // to intercept express checkout etc. IOK 2022-03-18 | |
| 2311 | - if (! apply_filters('woo_vipps_special_page_handled', false, $special)) { | |
| 2312 | - $this->$special(); | |
| 2313 | - } | |
| 2314 | - } | |
| 2657 | + // Dynamic special page title depending on endpoint/action, only frontend. LP 2026-09-02 | |
| 2658 | + public function vipps_special_page_endpoint_title($title, $postid = 0) { | |
| 2659 | + global $wp_query; | |
| 2660 | + // Comment from woocommerce's wc_page_endpoint_title where this logic is from: LP 2026-09-02 | |
| 2315 | 2661 | |
| 2316 | - $consentremoval = $this->is_consent_removal(); | |
| 2317 | - if ($consentremoval) { | |
| 2318 | - remove_filter('template_redirect', 'redirect_canonical', 10); | |
| 2319 | - do_action('woo_vipps_before_handling_special_page', 'consentremoval'); | |
| 2320 | - if (! apply_filters('woo_vipps_special_page_handled', false, 'consentremoval')) { | |
| 2321 | - $this->vipps_consent_removal_callback($consentremoval); | |
| 2662 | + // In block themes the whole template (header, footer, content) renders inside the main | |
| 2663 | + // loop, so `the_title` fires for any post title rendered on the page (e.g. a product in a | |
| 2664 | + // server-rendered mini-cart) - not just the page's own heading. Only replace the title of | |
| 2665 | + // the queried page so an earlier title doesn't consume this one-shot filter. | |
| 2666 | + if ( ! is_null( $wp_query ) && ! is_admin() && is_main_query() && in_the_loop() && is_page() && $postid == static::get_special_page_id() ) { | |
| 2667 | + switch ($_GET['action'] ?? '') { | |
| 2668 | + case 'wait_for_payment': | |
| 2669 | + $title = __('Processing order', 'woo-vipps'); | |
| 2670 | + break; | |
| 2671 | + case 'do_express_checkout': | |
| 2672 | + case 'buy_product': | |
| 2673 | + $title = __('Express Checkout', 'woo-vipps'); | |
| 2674 | + break; | |
| 2322 | 2675 | } |
| 2323 | 2676 | } |
| 2677 | + return $title; | |
| 2324 | 2678 | } |
| 2679 | + | |
| 2325 | 2680 | // Template handling for special pages. IOK 2018-11-21 |
| 2681 | + // This is legacy - the special page is now a real page, so it can have a special template using standard WP methods. IOK 2026-09-11 | |
| 2326 | 2682 | public function template_include($template) { |
| 2327 | - $special = $this->is_special_page() ; | |
| 2328 | - if ($special) { | |
| 2683 | + if (static::is_special_page()) { | |
| 2329 | 2684 | // Get any special template override from the options IOK 2020-02-18 |
| 2330 | 2685 | $specific = $this->gateway()->get_option('vippsspecialpagetemplate'); |
| 2331 | 2686 | $found = locate_template($specific,false,false); |
| 2332 | 2687 | if ($found) $template=$found; |
| 2333 | 2688 | |
| 2334 | - return apply_filters('woo_vipps_special_page_template', $template, $special); | |
| 2689 | + return apply_filters('woo_vipps_special_page_template', $template, $_GET['action'] ?? ''); | |
| 2335 | 2690 | } |
| 2336 | 2691 | return $template; |
| 2337 | 2692 | } |
| 2338 | 2693 | |
| 2339 | - | |
| 2340 | - // Can't use wc-api for this, as that does not support DELETE . IOK 2018-05-18 | |
| 2341 | - private function is_consent_removal () { | |
| 2342 | - | |
| 2343 | - if ($_SERVER['REQUEST_METHOD'] != 'DELETE') return false; | |
| 2344 | - if ( !get_option('permalink_structure')) { | |
| 2345 | - if (@$_REQUEST['vipps-consent-removal']) return @$_REQUEST['callback']; | |
| 2346 | - return false; | |
| 2347 | - } | |
| 2348 | - if (preg_match("!/vipps-consent-removal/([^/]*)!", $_SERVER['REQUEST_URI'], $matches)) { | |
| 2349 | - return @$_REQUEST['callback']; | |
| 2350 | - } | |
| 2351 | - return false; | |
| 2352 | - } | |
| 2353 | - | |
| 2354 | 2694 | // On the thank you page, we have a completed order, so we need to restore any saved cart and possibly log in |
| 2355 | 2695 | // the user if using Express Checkout IOK 2020-10-09 |
| 2356 | 2696 | public function woocommerce_before_thankyou ($orderid) { |
| 2357 | 2697 | $order = wc_get_order($orderid); |
| @@ -2356,9 +2696,9 @@ | ||
| 2356 | 2696 | public function woocommerce_before_thankyou ($orderid) { |
| 2357 | 2697 | $order = wc_get_order($orderid); |
| 2358 | 2698 | if ($order) { |
| 2359 | 2699 | // Requires that this is express checkout and that 'create users on express checkout' is chosen. IOK 2020-10-09 |
| 2360 | - // -- or the same thing for Vipps Checkout. Also, the NHG code should not be running, and there is a filter, too. IOK 2023-08-04 | |
| 2700 | + // -- or the same thing for Checkout. Also, the NHG code should not be running, and there is a filter, too. IOK 2023-08-04 | |
| 2361 | 2701 | $this->maybe_log_in_user($order); |
| 2362 | 2702 | $order->delete_meta_data('_vipps_limited_session'); |
| 2363 | 2703 | $order->save(); |
| 2364 | 2704 | |
| @@ -2390,9 +2730,9 @@ | ||
| 2390 | 2730 | |
| 2391 | 2731 | // If local pickup has been added to express/checkout by filters, add this to emails/confirmation pages. IOK 2025-08-15 |
| 2392 | 2732 | add_filter('woocommerce_order_shipping_to_display', function($shipping, $order, $tax_display) { |
| 2393 | 2733 | if (!is_a($order, 'WC_Order')) return $shipping; |
| 2394 | - if ($order->get_payment_method() != 'vipps') return $shipping; | |
| 2734 | + if (! self::is_vipps_order($order)) return $shipping; | |
| 2395 | 2735 | $shipping_method = current( $order->get_shipping_methods() ); |
| 2396 | 2736 | |
| 2397 | 2737 | if (empty($shipping_method)) return $shipping; |
| 2398 | 2738 | |
| @@ -2419,9 +2759,8 @@ | ||
| 2419 | 2759 | |
| 2420 | 2760 | // Support adding pickup locations to any shipping rate using the 'woo_vipps_shipping_method_pickup_points' filter |
| 2421 | 2761 | // IOK 2025-11-19 |
| 2422 | 2762 | add_filter('woo_vipps_modify_express_checkout_rate', array($this, 'express_add_pickup_location_options'), 10, 4); |
| 2423 | - | |
| 2424 | 2763 | } |
| 2425 | 2764 | |
| 2426 | 2765 | public function get_payment_method_name() { |
| 2427 | 2766 | return $this->gateway()->get_option('payment_method_name'); |
| @@ -2441,14 +2780,13 @@ | ||
| 2441 | 2780 | public function after_setup_theme() { |
| 2442 | 2781 | // To facilitate development, allow loading the plugin-supplied translations. Must be called here at the earliest. |
| 2443 | 2782 | $ok = Vipps::load_plugin_textdomain('woo-vipps', false, basename( dirname( dirname( __FILE__ ) ) ) . "/languages"); |
| 2444 | 2783 | |
| 2445 | - // Vipps Checkout replaces the default checkout page, and currently uses its own page for this which needs to exist | |
| 2784 | + // Checkout replaces the default checkout page, and currently uses its own page for this which needs to exist | |
| 2446 | 2785 | // Will also probably be used to maintain a real utility-page for Vipps actions later for themes where this |
| 2447 | 2786 | // is important. |
| 2448 | 2787 | add_filter('woocommerce_create_pages', array($this, 'woocommerce_create_pages'), 50, 1); |
| 2449 | 2788 | |
| 2450 | - | |
| 2451 | 2789 | // Callbacks use the Woo API IOK 2018-05-18 |
| 2452 | 2790 | add_action( 'woocommerce_api_wc_gateway_vipps', array($this,'vipps_callback')); |
| 2453 | 2791 | add_action( 'woocommerce_api_vipps_shipping_details', array($this,'vipps_shipping_details_callback')); |
| 2454 | 2792 | |
| @@ -2459,9 +2797,9 @@ | ||
| 2459 | 2797 | add_action( 'woocommerce_cart_actions', array($this, 'cart_express_checkout_button')); |
| 2460 | 2798 | add_action( 'woocommerce_widget_shopping_cart_buttons', array($this, 'minicart_express_checkout_button'), 30); |
| 2461 | 2799 | |
| 2462 | 2800 | // Previously we added an express html banner to the action 'woocommerce_before_checkout_form.', |
| 2463 | - // replaced by the new express buttons in manner more like Gutenberg. LP 2026-03-23 | |
| 2801 | + // replaced by the new express buttons in manner more like Gutenberg. for grepping: "express legacy checkout". LP 2026-03-23 | |
| 2464 | 2802 | add_action('woocommerce_checkout_before_customer_details', array($this, 'checkout_before_customer_details_express'), 5); |
| 2465 | 2803 | |
| 2466 | 2804 | add_action('woocommerce_after_add_to_cart_button', array($this, 'single_product_buy_now_button')); |
| 2467 | 2805 | add_action('woocommerce_after_shop_loop_item', array($this, 'loop_single_product_buy_now_button'), 20); |
| @@ -2466,12 +2804,10 @@ | ||
| 2466 | 2804 | add_action('woocommerce_after_add_to_cart_button', array($this, 'single_product_buy_now_button')); |
| 2467 | 2805 | add_action('woocommerce_after_shop_loop_item', array($this, 'loop_single_product_buy_now_button'), 20); |
| 2468 | 2806 | |
| 2469 | 2807 | |
| 2470 | - // Special pages and callbacks handled by template_redirect | |
| 2471 | - // We must also notify WP and other plugins that we are handling this 404-like situation. IOK 2023-02-22 | |
| 2808 | + // Special pages and callbacks handled by template_redirect. IOK 2023-02-22 | |
| 2472 | 2809 | add_action('template_redirect', array($this,'template_redirect'),1); |
| 2473 | - add_action('pre_handle_404', array($this, 'pre_handle_404'), 1, 2); | |
| 2474 | 2810 | |
| 2475 | 2811 | // Allow overriding their templates |
| 2476 | 2812 | add_filter('template_include', array($this,'template_include'), 10, 1); |
| 2477 | 2813 | |
| @@ -2501,12 +2837,12 @@ | ||
| 2501 | 2837 | // If we can't cancel for some other reason, don't. |
| 2502 | 2838 | if (!$cancel) return $cancel; |
| 2503 | 2839 | |
| 2504 | 2840 | // Only check Vipps orders |
| 2505 | - if ($order->get_payment_method() != 'vipps') return $cancel; | |
| 2841 | + if (! self::is_vipps_order($order)) return $cancel; | |
| 2506 | 2842 | |
| 2507 | - // For Vipps, all unpaid orders must be pending. IOK FIXME ADD FAILED | |
| 2508 | - if ($order->get_status() != 'pending') return $cancel; | |
| 2843 | + // For Vipps, all unpaid orders must be pending. | |
| 2844 | + if ($order->get_status() != 'pending' && $order->get_status() != 'failed') return $cancel; | |
| 2509 | 2845 | |
| 2510 | 2846 | // Handle this separately, in the Checkout class. IOK 2025-10-08 |
| 2511 | 2847 | $checkout_session = $order->get_meta('_vipps_checkout_session'); |
| 2512 | 2848 | if ($checkout_session) { |
| @@ -2578,15 +2914,21 @@ | ||
| 2578 | 2914 | $this->vippsJSConfig['vippsbuynowdescription'] = sprintf(__( 'Add a %1$s Buy Now-button to the product block or choose a product manually', 'woo-vipps'), $this->get_payment_method_name()); |
| 2579 | 2915 | $this->vippsJSConfig['vippslanguage'] = $this->get_customer_language(); |
| 2580 | 2916 | $this->vippsJSConfig['vippslocale'] = get_locale(); |
| 2581 | 2917 | $this->vippsJSConfig['vippsexpressbuttonurl'] = $this->get_payment_method_name(); |
| 2582 | - $this->vippsJSConfig['logoSvgUrl'] = $this->get_payment_logo('buy-now-block'); | |
| 2583 | 2918 | |
| 2584 | 2919 | |
| 2585 | 2920 | // If the site supports Gutenberg Blocks, support the Checkout block IOK 2020-08-10 |
| 2586 | 2921 | if (class_exists('Automattic\WooCommerce\Blocks\Payments\Integrations\AbstractPaymentMethodType')) { |
| 2922 | + // Ensure gateways are loaded at this point IOK 2026-05-27 | |
| 2923 | + require_once(dirname(__FILE__) . '/WC_Gateway_VippsCard.class.php'); | |
| 2924 | + require_once(dirname(__FILE__) . '/WC_Gateway_Vipps.class.php'); | |
| 2925 | + | |
| 2926 | + // Then the payment blocks | |
| 2587 | 2927 | require_once(dirname(__FILE__) . "/Blocks/Payment/Vipps.class.php"); |
| 2928 | + require_once(dirname(__FILE__) . "/Blocks/Payment/VippsCard.class.php"); | |
| 2588 | 2929 | Automattic\WooCommerce\Blocks\Payments\Integrations\Vipps::register(); |
| 2930 | + Automattic\WooCommerce\Blocks\Payments\Integrations\VippsCard::register(); | |
| 2589 | 2931 | } |
| 2590 | 2932 | |
| 2591 | 2933 | // Used for e.g. labels of product/shipping metadata. IOK 2025-05-07 |
| 2592 | 2934 | add_filter('woocommerce_attribute_label', function ($label, $name, $product) { |
| @@ -2642,9 +2984,9 @@ | ||
| 2642 | 2984 | static::set_locale_if_in_header(); |
| 2643 | 2985 | $order = wc_get_order(intval($_REQUEST['orderid'])); |
| 2644 | 2986 | if (!is_a($order, 'WC_Order')) return; |
| 2645 | 2987 | $pm = $order->get_payment_method(); |
| 2646 | - if ($pm != 'vipps') return; | |
| 2988 | + if (!self::is_vipps_order($pm)) return; | |
| 2647 | 2989 | |
| 2648 | 2990 | $action = isset($_REQUEST['do']) ? sanitize_title($_REQUEST['do']) : 'none'; |
| 2649 | 2991 | |
| 2650 | 2992 | if ($action == 'do_capture') { |
| @@ -2713,9 +3055,9 @@ | ||
| 2713 | 3055 | } |
| 2714 | 3056 | |
| 2715 | 3057 | public function order_item_add_capture_button ($order) { |
| 2716 | 3058 | $pm = $order->get_payment_method(); |
| 2717 | - if ($pm != 'vipps') return; | |
| 3059 | + if (!self::is_vipps_order($pm)) return; | |
| 2718 | 3060 | $status = $order->get_status(); |
| 2719 | 3061 | |
| 2720 | 3062 | $show_capture_button = ($status == 'on-hold' || $status == 'processing'); |
| 2721 | 3063 | if (!apply_filters('woo_vipps_show_capture_button', $show_capture_button, $order)) { |
| @@ -2722,10 +3064,11 @@ | ||
| 2722 | 3064 | return; |
| 2723 | 3065 | } |
| 2724 | 3066 | |
| 2725 | 3067 | $captured = intval($order->get_meta('_vipps_captured')); |
| 2726 | - $capremain = intval($order->get_meta('_vipps_capture_remaining')); | |
| 2727 | - if ($captured && !$capremain || $capremain < 2) { | |
| 3068 | + // noncapturable should never be greater than capture remaining, so this *should* not be negative. LP 2026-06-12 | |
| 3069 | + $capremain = intval($order->get_meta('_vipps_capture_remaining')) - intval($order->get_meta('_vipps_noncapturable')); | |
| 3070 | + if ($captured && (!$capremain || $capremain < 2)) { | |
| 2728 | 3071 | print "<div><strong>" . sprintf(__("The entire amount has been captured at %1\$s", 'woo-vipps'), $this->get_payment_method_name()) . "</strong></div>"; |
| 2729 | 3072 | return; |
| 2730 | 3073 | } |
| 2731 | 3074 | |
| @@ -2750,14 +3093,14 @@ | ||
| 2750 | 3093 | |
| 2751 | 3094 | $raw_post = @file_get_contents( 'php://input' ); |
| 2752 | 3095 | $result = @json_decode($raw_post,true); |
| 2753 | 3096 | |
| 2754 | - // This handler handles both Vipps Checkout and Vipps ECom IOK 2021-09-02 | |
| 3097 | + // This handler handles both Checkout and Vipps ECom IOK 2021-09-02 | |
| 2755 | 3098 | // .. and the epayment webhooks 2023-12-19 |
| 2756 | 3099 | $ischeckout = false; |
| 2757 | 3100 | $iswebhook = false; |
| 2758 | 3101 | $callback = isset($_REQUEST['callback']) ? $_REQUEST['callback'] : ""; |
| 2759 | - // For Vipps Checkout v3 and onwards, we control the callback so the type is just this field | |
| 3102 | + // For Checkout v3 and onwards, we control the callback so the type is just this field | |
| 2760 | 3103 | if ($callback == 'checkout') { |
| 2761 | 3104 | $ischeckout = true; |
| 2762 | 3105 | } |
| 2763 | 3106 | // For the webhooks, we will add 'webhook' to the result, but we also know that 'pspReference' will be present. IOK 2023-12-19 |
| @@ -3151,9 +3494,9 @@ | ||
| 3151 | 3494 | print "Unknown order"; |
| 3152 | 3495 | $this->log(__('Could not find Woo order with id:', 'woo-vipps') . " " . $orderid, 'error'); |
| 3153 | 3496 | exit(); |
| 3154 | 3497 | } |
| 3155 | - if ($order->get_payment_method() != 'vipps') { | |
| 3498 | + if (!self::is_vipps_order($order)) { | |
| 3156 | 3499 | status_header(400, "Invalid order"); |
| 3157 | 3500 | print "Invalid order"; |
| 3158 | 3501 | $this->log(__('Invalid order for shipping callback:', 'woo-vipps') . " " . $orderid, 'error'); |
| 3159 | 3502 | exit(); |
| @@ -3171,10 +3514,10 @@ | ||
| 3171 | 3514 | $this->log(sprintf(__("Wrong %1\$s Orderid on shipping details callback", 'woo-vipps'), $this->get_payment_method_name()), 'warning'); |
| 3172 | 3515 | exit(); |
| 3173 | 3516 | } |
| 3174 | 3517 | |
| 3175 | - // If we are doing this for Vipps Checkout after version 3, communicate to any shipping methods with | |
| 3176 | - // special support for Vipps Checkout that this is in fact happening. IOK 2023-01-19 | |
| 3518 | + // If we are doing this for Checkout after version 3, communicate to any shipping methods with | |
| 3519 | + // special support for Checkout that this is in fact happening. IOK 2023-01-19 | |
| 3177 | 3520 | // This needs to be done before "calculate totals". |
| 3178 | 3521 | // Moved from "vipps_shipping_details_callback_handler" because we need it before restoring sessions. IOK 2025-05-06 |
| 3179 | 3522 | $ischeckout = $order->get_meta('_vipps_checkout'); |
| 3180 | 3523 | |
| @@ -3350,9 +3693,9 @@ | ||
| 3350 | 3693 | ), 'debug'); |
| 3351 | 3694 | |
| 3352 | 3695 | } |
| 3353 | 3696 | |
| 3354 | - // Add shipping tax rates to the *order* so we can calculate this correctly when using Vipps Checkouts | |
| 3697 | + // Add shipping tax rates to the *order* so we can calculate this correctly when using Checkouts | |
| 3355 | 3698 | // 'dynamic pricing' 2023-01-26 |
| 3356 | 3699 | // Which may be deprecated, but anyway, for future use IOK 2025-08-14 |
| 3357 | 3700 | $taxrate = 0; |
| 3358 | 3701 | if (is_array($shipping_tax_rates) && !empty($shipping_tax_rates)) { |
| @@ -3478,9 +3821,9 @@ | ||
| 3478 | 3821 | $vippsmethod['shippingMethod'] = $rate->get_label(); |
| 3479 | 3822 | $vippsmethod['shippingMethodId'] = $key; |
| 3480 | 3823 | $vippsmethods[]=$vippsmethod; |
| 3481 | 3824 | |
| 3482 | - // Metadata and settings stored for later use for Vipps Checkout | |
| 3825 | + // Metadata and settings stored for later use for Checkout | |
| 3483 | 3826 | // and express checkout - basically, for each *key* have the corresponding object. IOK 2025-08-15 |
| 3484 | 3827 | // In the end, this data will be serialized and stored in the Order, and used in the gateways method set_order_shipping_details to |
| 3485 | 3828 | // finalize the order. IOK 2025-08-15 |
| 3486 | 3829 | $ratemap[$key]=$rate; |
| @@ -3498,9 +3841,9 @@ | ||
| 3498 | 3841 | // This then is the old Express Checkout format, which we have exposed in filters. IOK 2025-08-14 |
| 3499 | 3842 | $return = array('addressId'=>intval($addressid), 'orderId'=>$vippsorderid, 'shippingDetails'=>$vippsmethods); |
| 3500 | 3843 | $return = apply_filters('woo_vipps_vipps_formatted_shipping_methods', $return); // Mostly for debugging |
| 3501 | 3844 | |
| 3502 | - // IOK 2021-11-16 Vipps Checkout uses a slightly different syntax and format. | |
| 3845 | + // IOK 2021-11-16 Checkout uses a slightly different syntax and format. | |
| 3503 | 3846 | // IOK 2025-08-15 and new Express yet another slightly different format. |
| 3504 | 3847 | // IOK 2025-08-15 pass the ratemap as a reference, so transforms can update them |
| 3505 | 3848 | if ($ischeckout) { |
| 3506 | 3849 | $return = VippsCheckout::instance()->format_shipping_methods($return, $ratemap, $methodmap, $order); |
| @@ -3876,19 +4219,11 @@ | ||
| 3876 | 4219 | header("X-Accel-Expires: 0"); |
| 3877 | 4220 | } |
| 3878 | 4221 | |
| 3879 | 4222 | |
| 3880 | - | |
| 3881 | - // Handle DELETE on a vipps consent removal callback | |
| 3882 | - public function vipps_consent_removal_callback ($callback) { | |
| 3883 | - Vipps::nocache(); | |
| 3884 | - // Currently, no such requests will be posted, and as this code isn't sufficiently tested,we'll just have | |
| 3885 | - // to escape here when the API is changed. IOK 2020-10-14 | |
| 3886 | - $this->log("Consent removal is non-functional pending API changes as of 2020-10-14"); print "1"; exit(); | |
| 3887 | - } | |
| 3888 | - | |
| 3889 | 4223 | public function woocommerce_payment_gateways($methods) { |
| 3890 | 4224 | require_once(dirname(__FILE__) . "/WC_Gateway_Vipps.class.php"); |
| 4225 | + require_once(dirname(__FILE__) . "/WC_Gateway_VippsCard.class.php"); | |
| 3891 | 4226 | // Protect the singleton: Use the object instead of the class name IOK 2025-02-04 |
| 3892 | 4227 | $gateway = $this->gateway(); |
| 3893 | 4228 | if ($gateway) { |
| 3894 | 4229 | $methods[] = $gateway; |
| @@ -3894,8 +4229,11 @@ | ||
| 3894 | 4229 | $methods[] = $gateway; |
| 3895 | 4230 | } else { |
| 3896 | 4231 | $methods[] = 'WC_Gateway_Vipps'; |
| 3897 | 4232 | } |
| 4233 | + | |
| 4234 | + $methods[] = 'WC_Gateway_VippsCard'; | |
| 4235 | + | |
| 3898 | 4236 | return $methods; |
| 3899 | 4237 | } |
| 3900 | 4238 | |
| 3901 | 4239 | // Runs after set_session, so if the session is just created, we'll get called. IOK 2018-06-06 |
| @@ -3918,9 +4256,9 @@ | ||
| 3918 | 4256 | // But for express checkout this breaks because there is no shipping method or address, and of course, |
| 3919 | 4257 | // the order id is unique too.. IOK 2018-11-21 |
| 3920 | 4258 | public function woocommerce_my_account_my_orders_actions($actions, $order ) { |
| 3921 | 4259 | $pm = $order->get_payment_method(); |
| 3922 | - if ($pm != 'vipps') return $actions; | |
| 4260 | + if (!self::is_vipps_order($pm)) return $actions; | |
| 3923 | 4261 | |
| 3924 | 4262 | if (!static::order_is_vipps_retryable($order->get_id())) { |
| 3925 | 4263 | unset($actions['pay']); |
| 3926 | 4264 | } |
| @@ -3938,10 +4276,14 @@ | ||
| 3938 | 4276 | // to the store and the Vipps callback fails for whatever reason. IOK 2021-06-21 |
| 3939 | 4277 | public function cron_check_for_missing_callbacks() { |
| 3940 | 4278 | $eightminutesago = time() - (60*8); |
| 3941 | 4279 | $sevendaysago = time() - (60*60*24*7); |
| 3942 | - $pending = wc_get_orders( | |
| 3943 | - array('limit'=>-1, 'status'=>'pending', 'payment_method' => 'vipps', 'date_created' => '>' . $sevendaysago )); | |
| 4280 | + | |
| 4281 | + // This is compatible with both HPOS and old style order management. IOK 2026-05-27 | |
| 4282 | + $pending_app = wc_get_orders( array('limit'=>-1, 'status'=>'pending', 'payment_method' => 'vipps', 'date_created' => '>' . $sevendaysago )); | |
| 4283 | + $pending_cards = wc_get_orders( array('limit'=>-1, 'status'=>'pending', 'payment_method' => 'vipps_card', 'date_created' => '>' . $sevendaysago )); | |
| 4284 | + $pending = array_merge($pending_app, $pending_cards); | |
| 4285 | + | |
| 3944 | 4286 | if (empty($pending)) return; |
| 3945 | 4287 | foreach ($pending as $o) { |
| 3946 | 4288 | $then = $o->get_meta('_vipps_init_timestamp'); |
| 3947 | 4289 | if (! $then) continue; # Race condition! We may not have set the timestamp yet. IOK 2022-03-24 |
| @@ -3953,9 +4295,9 @@ | ||
| 3953 | 4295 | if ($currentstatus != 'initiated') { |
| 3954 | 4296 | $this->log(sprintf(__("Order %2\$d is 'pending' but its %1\$s order status is '%3\$s' - this means that the order has been erroneously set to 'pending' after completion or cancellation. Will not process further, please check status of order at %1\$s and set to correct status in WooCommerce", 'woo-vipps'), $this->get_payment_method_name(), $o->get_id(), $currentstatus), 'debug'); |
| 3955 | 4297 | return; |
| 3956 | 4298 | } |
| 3957 | - $this->check_status_of_pending_order($o, false, false); | |
| 4299 | + $this->check_status_of_pending_order($o, false); | |
| 3958 | 4300 | } |
| 3959 | 4301 | } |
| 3960 | 4302 | |
| 3961 | 4303 | // Check and possibly update the status of a pending order at Vipps. We only restore session if we know this is called from a context with no session - |
| @@ -3960,30 +4302,35 @@ | ||
| 3960 | 4302 | |
| 3961 | 4303 | // Check and possibly update the status of a pending order at Vipps. We only restore session if we know this is called from a context with no session - |
| 3962 | 4304 | // e.g. wp-cron. IOK 2021-06-21 |
| 3963 | 4305 | // Stop restoring session in wp-cron too. IOK 2021-08-23 |
| 3964 | - public function check_status_of_pending_order($order, $maybe_restore_session=0, $allow_retry=true) { | |
| 3965 | - $express = $order->get_meta('_vipps_express_checkout'); | |
| 3966 | - $vippstatus = $order->get_meta('_vipps_status'); | |
| 3967 | - if ($express && $maybe_restore_session) { | |
| 3968 | - $this->log(sprintf(__("Restoring session of order %1\$d", 'woo-vipps'), $order->get_id()), 'debug'); | |
| 3969 | - $this->callback_restore_session($order->get_id()); | |
| 3970 | - } | |
| 4306 | + // Stop restoring session in wp-cron again(?) since we now use a rest endpoint to handle shipping. LP 2026-05-13 | |
| 4307 | + public function check_status_of_pending_order($order, $allow_retry=true) { | |
| 3971 | 4308 | $gw = $this->gateway(); |
| 3972 | 4309 | |
| 3973 | 4310 | $order_status = null; |
| 3974 | 4311 | try { |
| 3975 | 4312 | $order->add_order_note(sprintf(__("Callback from %1\$s delayed or never happened; order status checked by periodic job", 'woo-vipps'), $this->get_payment_method_name())); |
| 3976 | - $order_status = $gw->callback_check_order_status($order, $allow_retry); | |
| 4313 | + | |
| 4314 | + // Poll status and correct woo status. LP 2026-05-19 | |
| 4315 | + $order_data = $gw->get_payment_details($order); | |
| 4316 | + | |
| 4317 | + // If we already know the order failed, we don't need to process the order further below. LP 2026-05-19 | |
| 4318 | + if ('CANCEL' === ($order_data['state'] ?? "")) { | |
| 4319 | + /* translators: company name */ | |
| 4320 | + $order->update_status('cancelled', sprintf(__('Payment cancelled at %1$s.', 'woo-vipps'), Vipps::CompanyName())); | |
| 4321 | + return; | |
| 4322 | + } | |
| 4323 | + | |
| 4324 | + $gw->set_order_status_by_payment_details($order, $order_data, $allow_retry); | |
| 4325 | + $order = wc_get_order($order->get_id()); // refresh order if changed. LP 2026-05-13 | |
| 4326 | + $order_status = $order->get_status(); | |
| 4327 | + | |
| 3977 | 4328 | $this->log(sprintf(__("For order %2\$d order status at %1\$s is %3\$s", 'woo-vipps'), $this->get_payment_method_name(), $order->get_id(), $order_status), 'debug'); |
| 3978 | 4329 | } catch (Exception $e) { |
| 3979 | 4330 | $this->log(sprintf(__("Error getting order status at %1\$s for order %2\$d", 'woo-vipps'), $this->get_payment_method_name(), $order->get_id()), 'error'); |
| 3980 | 4331 | $this->log($e->getMessage() . "\n" . $order->get_id(), 'error'); |
| 3981 | 4332 | } |
| 3982 | - // Ensure we don't keep using an old session for more than one order here. | |
| 3983 | - if ($express && $maybe_restore_session) { | |
| 3984 | - $this->callback_destroy_session(); | |
| 3985 | - } | |
| 3986 | 4333 | return $order_status; |
| 3987 | 4334 | } |
| 3988 | 4335 | |
| 3989 | 4336 | // This will probably be run in activate, but if the plugin is updated in other ways, will also be run on after_setup_theme. IOK 2020-04-01 |
| @@ -3996,20 +4343,40 @@ | ||
| 3996 | 4343 | } |
| 3997 | 4344 | } |
| 3998 | 4345 | |
| 3999 | 4346 | public function activate () { |
| 4000 | - static::maybe_add_cron_event(); | |
| 4001 | - $gw = $this->gateway(); | |
| 4347 | + static::maybe_add_cron_event(); | |
| 4348 | + $gw = $this->gateway(); | |
| 4002 | 4349 | |
| 4003 | - // If store is using the default "Woo" orderprefix, generate a new one, this time using the stores' sitename if possible. IOK 2020-05-19 | |
| 4004 | - if ($gw->get_option('orderprefix') == 'Woo') { | |
| 4005 | - $gw->update_option('orderprefix', $this->generate_order_prefix()); | |
| 4006 | - } | |
| 4007 | - // IOK 2023-12-20 for the epayment api, we need to re-initialize webhooks at this point. | |
| 4008 | - $gw->initialize_webhooks(); | |
| 4009 | - $this->payment_method_name = $gw->get_option('payment_method_name'); | |
| 4010 | - } | |
| 4350 | + // If store is using the default "Woo" orderprefix, generate a new one, this time using the stores' sitename if possible. IOK 2020-05-19 | |
| 4351 | + if ($gw->get_option('orderprefix') == 'Woo') { | |
| 4352 | + $gw->update_option('orderprefix', $this->generate_order_prefix()); | |
| 4353 | + } | |
| 4354 | + // IOK 2023-12-20 for the epayment api, we need to re-initialize webhooks at this point. | |
| 4355 | + $gw->initialize_webhooks(); | |
| 4356 | + $this->payment_method_name = $gw->get_option('payment_method_name'); | |
| 4011 | 4357 | |
| 4358 | + | |
| 4359 | + // Check if the special page is noted and actually does exist | |
| 4360 | + $special = static::get_special_page_id(); | |
| 4361 | + if ($special) { | |
| 4362 | + $special_page = get_post($special); | |
| 4363 | + if ($special_page && 'trash' !== $special_page->post_status) { | |
| 4364 | + // Ensure this page has the necessary shortcode. LP 2026-09-01 | |
| 4365 | + if (!has_shortcode($special_page->post_content, 'vipps_special_page')) { | |
| 4366 | + $new_content = $special_page->post_content . "\n\n<!-- wp:shortcode -->[vipps_special_page]<!-- /wp:shortcode -->"; | |
| 4367 | + wp_update_post([ | |
| 4368 | + 'ID' => $special, | |
| 4369 | + 'post_content' => $new_content, | |
| 4370 | + ]); | |
| 4371 | + } | |
| 4372 | + } else { | |
| 4373 | + delete_option('woocommerce_vipps_special_page_page_id'); | |
| 4374 | + } | |
| 4375 | + } | |
| 4376 | + | |
| 4377 | + } | |
| 4378 | + | |
| 4012 | 4379 | // We have added some hooks to wp-cron; remove these. IOK 2020-04-01 |
| 4013 | 4380 | public static function deactivate() { |
| 4014 | 4381 | $timestamp = wp_next_scheduled('vipps_cron_cleanup_hook'); |
| 4015 | 4382 | wp_unschedule_event($timestamp, 'vipps_cron_cleanup_hook'); |
| @@ -4020,13 +4387,12 @@ | ||
| 4020 | 4387 | $gw->delete_all_webhooks(); |
| 4021 | 4388 | |
| 4022 | 4389 | // Delete all settings if checked in settings menu. LP 2025-10-06 |
| 4023 | 4390 | $should_delete = $gw->get_option( 'delete_settings_on_deactivation' ) === 'yes'; |
| 4024 | - if ( ($should_delete)) { | |
| 4391 | + if ($should_delete) { | |
| 4025 | 4392 | // Delete options. |
| 4026 | - $options = ['woocommerce_vipps_settings', 'woo-vipps-configured', 'vipps_badge_options', 'vipps_button_options', '_vipps_dismissed_notices', 'woo_vipps_checkout_activated']; | |
| 4393 | + $options = ['woocommerce_vipps_settings', 'woocommerce_vipps_card_settings', 'woo-vipps-configured', 'vipps_badge_options', 'vipps_button_options', 'vipps_button_options2', '_vipps_dismissed_notices', 'woo_vipps_checkout_activated']; | |
| 4027 | 4394 | foreach($options as $option) { |
| 4028 | - error_log("Deleting woo-vipps option: $option"); | |
| 4029 | 4395 | delete_option($option); |
| 4030 | 4396 | } |
| 4031 | 4397 | } |
| 4032 | 4398 | |
| @@ -4063,9 +4429,10 @@ | ||
| 4063 | 4429 | // If setting is true, use Vipps as default payment. Called by the woocommrece_cart_updated hook. IOK 2018-06-06 |
| 4064 | 4430 | private function maybe_set_vipps_as_default() { |
| 4065 | 4431 | if (WC()->session->get('chosen_payment_method')) return; // User has already chosen payment method, so we're done. |
| 4066 | 4432 | $gw = $this->gateway(); |
| 4067 | - if ($gw->get_option('vippsdefault')=='yes') { | |
| 4433 | + // Do *not* default to vipps if Kustom Checkout is installed IOK 2026-09-11 | |
| 4434 | + if ($gw->get_option('vippsdefault')=='yes' && !class_exists('KCO')) { | |
| 4068 | 4435 | WC()->session->set('chosen_payment_method', $gw->id); |
| 4069 | 4436 | } |
| 4070 | 4437 | } |
| 4071 | 4438 | |
| @@ -4167,16 +4534,16 @@ | ||
| 4167 | 4534 | // Maybe log in user |
| 4168 | 4535 | // It is done on the thank-you page of the order, and only for express checkout. |
| 4169 | 4536 | function maybe_log_in_user ($order) { |
| 4170 | 4537 | if (is_user_logged_in()) return; |
| 4171 | - if (!$order || $order->get_payment_method()!= 'vipps' ) return; | |
| 4538 | + if (!$order || ! self::is_vipps_order($order)) return; | |
| 4172 | 4539 | |
| 4173 | 4540 | // We *do* want to log in express checkout customers, but not those that |
| 4174 | - // use the Vipps Checkout solution - those can change their emails in the | |
| 4541 | + // use the Checkout solution - those can change their emails in the | |
| 4175 | 4542 | // checkout screen. IOK 2021-09-03 |
| 4176 | 4543 | $do_login = $order->get_meta('_vipps_express_checkout'); |
| 4177 | 4544 | |
| 4178 | - // We will not log in Vipps Checkout users unless the option for that is true | |
| 4545 | + // We will not log in Checkout users unless the option for that is true | |
| 4179 | 4546 | if ($order->get_meta('_vipps_checkout') && 'yes' != $this->gateway()->get_option('checkoutcreateuser')) { |
| 4180 | 4547 | $do_login = false; |
| 4181 | 4548 | } |
| 4182 | 4549 | |
| @@ -4204,9 +4571,9 @@ | ||
| 4204 | 4571 | |
| 4205 | 4572 | // Get the customer that corresponds to the current order, maybe creating the customer if it does not exist yet and |
| 4206 | 4573 | // the settings allow it. |
| 4207 | 4574 | function express_checkout_get_vipps_customer($order) { |
| 4208 | - if (!$order || $order->get_payment_method() != 'vipps' ) return null; | |
| 4575 | + if (!$order || ! self::is_vipps_order($order)) return null; | |
| 4209 | 4576 | // specific code for this by netthandelsgruppen if the below function exists |
| 4210 | 4577 | if (function_exists('create_assign_user_on_vipps_callback')) return null; |
| 4211 | 4578 | |
| 4212 | 4579 | // Both Checkout and Express Checkout have the below value set to true |
| @@ -4211,9 +4578,9 @@ | ||
| 4211 | 4578 | |
| 4212 | 4579 | // Both Checkout and Express Checkout have the below value set to true |
| 4213 | 4580 | if (!$order->get_meta('_vipps_express_checkout')) return; |
| 4214 | 4581 | |
| 4215 | - // Creating/logging in users are handled separately for Vipps Checkout and Express Checkout, so check the correct setting | |
| 4582 | + // Creating/logging in users are handled separately for Checkout and Express Checkout, so check the correct setting | |
| 4216 | 4583 | // IOK 2023-07-27 |
| 4217 | 4584 | $ischeckout = $order->get_meta('_vipps_checkout'); |
| 4218 | 4585 | if ($ischeckout) { |
| 4219 | 4586 | if ($this->gateway()->get_option('checkoutcreateuser') != 'yes') return null; |
| @@ -4598,9 +4965,9 @@ | ||
| 4598 | 4965 | $ok = wc()->shipping->register_shipping_method( new Automattic\WooCommerce\Blocks\Shipping\PickupLocation() ); |
| 4599 | 4966 | } |
| 4600 | 4967 | } |
| 4601 | 4968 | |
| 4602 | - // Vipps Checkout and Express Checkout allows loading specific kinds of shipping methods with non-standard APIs, such as PickupLocations. IOK 2025-05-08 | |
| 4969 | + // Checkout and Express Checkout allows loading specific kinds of shipping methods with non-standard APIs, such as PickupLocations. IOK 2025-05-08 | |
| 4603 | 4970 | // Must be called *early*. IOK 2025-05-08. Called in callback methods, and if using static shipping, in the 'start session' callback. |
| 4604 | 4971 | public function load_extra_shipping_methods($order, $addressdata, $ischeckout=false) { |
| 4605 | 4972 | // If we need to add more shipping methods *before* the shipping callback starts, it must be done before we load the session. IOK 2025-05-06 |
| 4606 | 4973 | add_action('woocommerce_load_shipping_methods', function () use ($order, $addressdata) { |
| @@ -4671,46 +5038,37 @@ | ||
| 4671 | 5038 | wp_send_json(array('status'=>'error', 'msg'=> __('Unknown payment status','woo-vipps') . ' ' . $payment)); |
| 4672 | 5039 | return false; |
| 4673 | 5040 | } |
| 4674 | 5041 | |
| 4675 | - // The various return URLs for special pages of the Vipps stuff depend on settings and pretty-URLs so we supply them from here | |
| 4676 | - // These are for the "fallback URL" mostly. IOK 2018-05-18 | |
| 4677 | - private function make_vipps_url($what) { | |
| 4678 | - if ( !get_option('permalink_structure')) { | |
| 4679 | - return add_query_arg('VippsSpecialPage', $what, home_url("/", 'https')); | |
| 4680 | - } | |
| 4681 | - return trailingslashit(home_url($what, 'https')); | |
| 5042 | + // The various return URLs for special pages of the Vipps stuff. Previously used a fake page and had to check permalink_structure. LP 2026-08-26 | |
| 5043 | + private function make_special_page_url($action) { | |
| 5044 | + return add_query_arg('action', $action, $this->get_special_page_url()); | |
| 4682 | 5045 | } |
| 5046 | + | |
| 4683 | 5047 | public function payment_return_url() { |
| 4684 | - return apply_filters('woo_vipps_payment_return_url', $this->make_vipps_url('vipps-betaling')); | |
| 5048 | + return apply_filters('woo_vipps_payment_return_url', $this->make_special_page_url('wait_for_payment')); | |
| 4685 | 5049 | } |
| 4686 | 5050 | public function express_checkout_url() { |
| 4687 | - return $this->make_vipps_url('vipps-express-checkout'); | |
| 5051 | + return $this->make_special_page_url('do_express_checkout'); | |
| 4688 | 5052 | } |
| 4689 | 5053 | public function buy_product_url() { |
| 4690 | - return $this->make_vipps_url('vipps-buy-product'); | |
| 5054 | + return $this->make_special_page_url('buy_product'); | |
| 4691 | 5055 | } |
| 4692 | 5056 | |
| 4693 | - // Return the method in the Vipps | |
| 4694 | - public function is_special_page() { | |
| 4695 | - $specials = array('vipps-betaling' => 'vipps_wait_for_payment', 'vipps-express-checkout'=>'vipps_express_checkout', 'vipps-buy-product'=>'vipps_buy_product'); | |
| 4696 | - $method = null; | |
| 4697 | - if ( get_option('permalink_structure')) { | |
| 4698 | - foreach($specials as $special=>$specialmethod) { | |
| 4699 | - // IOK 2018-06-07 Change to add any prefix from home-url for better matching IOK 2018-06-07 | |
| 4700 | - $path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); | |
| 4701 | - if ($path && preg_match("!/$special/?$!", $path, $matches)) { | |
| 4702 | - $method = $specialmethod; break; | |
| 4703 | - } | |
| 4704 | - } | |
| 4705 | - } else { | |
| 4706 | - if (isset($_GET['VippsSpecialPage'])) { | |
| 4707 | - $method = @$specials[$_GET['VippsSpecialPage']]; | |
| 4708 | - } | |
| 4709 | - } | |
| 4710 | - return $method; | |
| 5057 | + public static function is_special_page() { | |
| 5058 | + $id = static::get_special_page_id(); | |
| 5059 | + return $id && is_page($id); | |
| 4711 | 5060 | } |
| 4712 | 5061 | |
| 5062 | + public static function get_special_page_id() { | |
| 5063 | + $id = wc_get_page_id('vipps_special_page'); // -1 if not found | |
| 5064 | + return $id > 0 ? $id : null; | |
| 5065 | + } | |
| 5066 | + | |
| 5067 | + public static function get_special_page_url() { | |
| 5068 | + return get_permalink(static::get_special_page_id()); | |
| 5069 | + } | |
| 5070 | + | |
| 4713 | 5071 | // Just create a spinner and a overlay. |
| 4714 | 5072 | public function spinner () { |
| 4715 | 5073 | $flavour = sanitize_title($this->get_payment_method_name()); |
| 4716 | 5074 | ob_start(); |
| @@ -4731,164 +5089,22 @@ | ||
| 4731 | 5089 | return apply_filters('woo_vipps_spinner', ob_get_clean()); |
| 4732 | 5090 | } |
| 4733 | 5091 | |
| 4734 | 5092 | |
| 4735 | - // Returns express logo images depending on parameters, these are the new express svgs received 2025-12-12. | |
| 4736 | - // Fallbacks to defaults for each payment method. LP 2025-12-15 | |
| 4737 | - public function get_express_logo($payment_method, $lang, $variant) { | |
| 4738 | - $base = plugins_url('img', __FILE__); | |
| 4739 | - | |
| 4740 | - // A much more concise approach could be to name the variant files directly and do a oneliner, but harder to grep after the files' usage. LP 2025-12-12 | |
| 4741 | - $img_map = [ | |
| 4742 | - "vipps" => [ | |
| 4743 | - "default" => "$base/vipps/express/en/buy-now-vipps-en-rectangular.svg", | |
| 4744 | - "default-mini" => "$base/vipps/express/en/express-vipps-en-rectangular-mini.svg", | |
| 4745 | - "en" => [ | |
| 4746 | - "default" => "$base/vipps/express/en/buy-now-vipps-en-rectangular.svg", | |
| 4747 | - "default-mini" => "$base/vipps/express/en/express-vipps-en-rectangular-mini.svg", | |
| 4748 | - "buy-now-rectangular" => "$base/vipps/express/en/buy-now-vipps-en-rectangular.svg", | |
| 4749 | - "buy-now-pill" => "$base/vipps/express/en/buy-now-vipps-en-pill.svg", | |
| 4750 | - "express-rectangular" => "$base/vipps/express/en/express-vipps-en-rectangular.svg", | |
| 4751 | - "express-rectangular-mini" => "$base/vipps/express/en/express-vipps-en-rectangular-mini.svg", | |
| 4752 | - "express-pill" => "$base/vipps/express/en/express-vipps-en-pill.svg", | |
| 4753 | - "express-pill-mini" => "$base/vipps/express/en/express-vipps-en-pill-mini.svg", | |
| 4754 | - | |
| 4755 | - ], | |
| 4756 | - "no" => [ | |
| 4757 | - "default" => "$base/vipps/express/no/kjop-na-vipps-no-rectangular.svg", | |
| 4758 | - "default-mini" => "$base/vipps/express/no/ekspress-vipps-no-rectangular-mini.svg", | |
| 4759 | - "buy-now-rectangular" => "$base/vipps/express/no/kjop-na-vipps-no-rectangular.svg", | |
| 4760 | - "buy-now-pill" => "$base/vipps/express/no/kjop-na-vipps-no-pill.svg", | |
| 4761 | - "express-rectangular" => "$base/vipps/express/no/ekspress-vipps-no-rectangular.svg", | |
| 4762 | - "express-rectangular-mini" => "$base/vipps/express/no/ekspress-vipps-no-rectangular-mini.svg", | |
| 4763 | - "express-pill" => "$base/vipps/express/no/ekspress-vipps-no-pill.svg", | |
| 4764 | - "express-pill-mini" => "$base/vipps/express/no/ekspress-vipps-no-pill-mini.svg", | |
| 4765 | - ], | |
| 4766 | - "se" => [ | |
| 4767 | - "default" => "$base/vipps/express/se/kop-nu-vipps-se-rectangular.svg", | |
| 4768 | - "default-mini" => "$base/vipps/express/se/express-vipps-se-rectangular-mini.svg", | |
| 4769 | - "buy-now-rectangular" => "$base/vipps/express/se/kop-nu-vipps-se-rectangular.svg", | |
| 4770 | - "buy-now-pill" => "$base/vipps/express/se/kop-nu-vipps-se-pill.svg", | |
| 4771 | - "express-rectangular" => "$base/vipps/express/se/express-vipps-se-rectangular.svg", | |
| 4772 | - "express-rectangular-mini" => "$base/vipps/express/se/express-vipps-se-rectangular-mini.svg", | |
| 4773 | - "express-pill" => "$base/vipps/express/se/express-vipps-se-pill.svg", | |
| 4774 | - "express-pill-mini" => "$base/vipps/express/se/express-vipps-se-pill-mini.svg", | |
| 4775 | - | |
| 4776 | - ], | |
| 4777 | - ], | |
| 4778 | - "mobilepay" => [ | |
| 4779 | - "default" => "$base/mobilepay/express/en/buy-now-mp-en-rectangular.svg", | |
| 4780 | - "default-mini" => "$base/mobilepay/express/en/express-mp-en-rectangular-mini.svg", | |
| 4781 | - "en" => [ | |
| 4782 | - "default" => "$base/mobilepay/express/en/buy-now-mp-en-rectangular.svg", | |
| 4783 | - "default-mini" => "$base/mobilepay/express/en/express-mp-en-rectangular-mini.svg", | |
| 4784 | - "buy-now-rectangular" => "$base/mobilepay/express/en/buy-now-mp-en-rectangular.svg", | |
| 4785 | - "buy-now-pill" => "$base/mobilepay/express/en/buy-now-mp-en-pill.svg", | |
| 4786 | - "express-rectangular" => "$base/mobilepay/express/en/express-mp-en-rectangular.svg", | |
| 4787 | - "express-rectangular-mini" => "$base/mobilepay/express/en/express-mp-en-rectangular-mini.svg", | |
| 4788 | - "express-pill" => "$base/mobilepay/express/en/express-mp-en-pill.svg", | |
| 4789 | - "express-pill-mini" => "$base/mobilepay/express/en/express-mp-en-pill-mini.svg", | |
| 4790 | - | |
| 4791 | - ], | |
| 4792 | - "dk" => [ | |
| 4793 | - "default" => "$base/mobilepay/express/dk/kob-nu-mp-dk-rectangular.svg", | |
| 4794 | - "default-mini" => "$base/mobilepay/express/dk/express-mp-dk-rectangular-mini.svg", | |
| 4795 | - "buy-now-rectangular" => "$base/mobilepay/express/dk/kob-nu-mp-dk-rectangular.svg", | |
| 4796 | - "buy-now-pill" => "$base/mobilepay/express/dk/kob-nu-mp-dk-pill.svg", | |
| 4797 | - "express-rectangular" => "$base/mobilepay/express/dk/express-mp-dk-rectangular.svg", | |
| 4798 | - "express-rectangular-mini" => "$base/mobilepay/express/dk/express-mp-dk-rectangular-mini.svg", | |
| 4799 | - "express-pill" => "$base/mobilepay/express/dk/express-mp-dk-pill.svg", | |
| 4800 | - "express-pill-mini" => "$base/mobilepay/express/dk/express-mp-dk-pill-mini.svg", | |
| 4801 | - ], | |
| 4802 | - "fi" => [ | |
| 4803 | - "default" => "$base/mobilepay/express/fi/osta-nyt-mp-fi-rectangular.svg", | |
| 4804 | - "default-mini" => "$base/mobilepay/express/fi/express-mp-fi-rectangular-mini.svg", | |
| 4805 | - "buy-now-rectangular" => "$base/mobilepay/express/fi/osta-nyt-mp-fi-rectangular.svg", | |
| 4806 | - "buy-now-pill" => "$base/mobilepay/express/fi/osta-nyt-mp-fi-pill.svg", | |
| 4807 | - "express-rectangular" => "$base/mobilepay/express/fi/express-mp-fi-rectangular.svg", | |
| 4808 | - "express-rectangular-mini" => "$base/mobilepay/express/fi/express-mp-fi-rectangular-mini.svg", | |
| 4809 | - "express-pill" => "$base/mobilepay/express/fi/express-mp-fi-pill.svg", | |
| 4810 | - "express-pill-mini" => "$base/mobilepay/express/fi/express-mp-fi-pill-mini.svg", | |
| 4811 | - | |
| 4812 | - ], | |
| 4813 | - ], | |
| 4814 | - | |
| 4815 | - ]; | |
| 4816 | - | |
| 4817 | - $payment = strtolower($payment_method); | |
| 4818 | - if ($lang === 'store') $lang = $this->get_customer_language(); | |
| 4819 | - | |
| 4820 | - // Dont give a default if payment method not found. LP 2025-12-12 | |
| 4821 | - if (!array_key_exists($payment, $img_map)) { | |
| 4822 | - return null; | |
| 4823 | - } | |
| 4824 | - $payment_map = $img_map[$payment]; | |
| 4825 | - | |
| 4826 | - $img = null; | |
| 4827 | - if (array_key_exists($lang, $payment_map) | |
| 4828 | - && is_array($payment_map[$lang]) | |
| 4829 | - && array_key_exists($variant, $payment_map[$lang])) { | |
| 4830 | - $img = @$payment_map[$lang][$variant]; | |
| 4831 | - } | |
| 4832 | - | |
| 4833 | - // Default fallback behaviour | |
| 4834 | - if (!$img) { | |
| 4835 | - $default = str_ends_with($variant, '-mini') ? 'default-mini' : 'default'; | |
| 4836 | - | |
| 4837 | - // First try getting default for payment method + language. LP 2026-01-16 | |
| 4838 | - if (array_key_exists($lang, $payment_map) && is_array($payment_map[$lang])) { | |
| 4839 | - /* translators: %1= payment method name, %2 = language string, %3 = variant name */ | |
| 4840 | - $this->log(sprintf(__('Could not find chosen express logo for payment method %1$s, language %2$s, and variant %3$s, attempting to fall back on language and payment method, else only language.', 'woo-vipps'), $payment_method, $lang, $variant), 'error'); | |
| 4841 | - $img = @$payment_map[$lang][$default]; | |
| 4842 | - } | |
| 4843 | - | |
| 4844 | - // If not found, then try global default for payment method. LP 2026-01-16 | |
| 4845 | - if (!$img) { | |
| 4846 | - $img = @$payment_map[$default]; | |
| 4847 | - } | |
| 4848 | - | |
| 4849 | - // Found no logo at all, log this. LP 2026-01-16 | |
| 4850 | - if (!$img) { | |
| 4851 | - /* translators: %1= payment method name, %2 = language string, %3 = variant name */ | |
| 4852 | - $this->log(sprintf(__('Found no express logo fallback for payment method %1$s, language %2$s, and variant %3$s.', 'woo-vipps'), $payment_method, $lang, $variant), 'error'); | |
| 4853 | - } | |
| 4854 | - } | |
| 4855 | - return $img; | |
| 5093 | + // DEPRECATED: Legacy function as of using new web component buttons. LP 2026-06-26 | |
| 5094 | + // NB: previously this returned the url to a svg logo. We don't do this anymore, so it returns html. LP 2026-06-30 | |
| 5095 | + public function get_express_logo($_payment_method = null, $_lang = null, $_variant = null, $context = 'global') { | |
| 5096 | + return $this->get_html_button_for_context($context); | |
| 4856 | 5097 | } |
| 4857 | 5098 | |
| 5099 | + // DEPRECATED: Legacy function as of using new web component buttons. LP 2026-06-26 | |
| 4858 | 5100 | // Get payment logo based on payment method, then language NT 2023-11-30 |
| 4859 | - // and based on custom variant setting. $page is the page origin slug, e.g 'cart', 'product'. LP 2025-12-15 | |
| 4860 | - public function get_payment_logo($page = null) { | |
| 4861 | - $lang = $this->get_customer_language(); | |
| 4862 | - $payment_method = $this->get_payment_method_name(); | |
| 4863 | - $variant = $this->get_express_logo_page_variant($page); | |
| 4864 | - $logo_url = $this->get_express_logo($payment_method, $lang, $variant); | |
| 4865 | - return $logo_url; | |
| 5101 | + // and based on custom variant setting. $context is where it is to be used, e.g 'cart', 'product'. LP 2025-12-15 | |
| 5102 | + // NB: previously this returned the url to a svg logo. We don't do this anymore, so it returns html. LP 2026-06-30 | |
| 5103 | + public function get_payment_logo($context = 'global') { | |
| 5104 | + return $this->get_express_logo(null, null, null, $context); | |
| 4866 | 5105 | } |
| 4867 | 5106 | |
| 4868 | - /** Returns the correct variant to use for the given page, found from the wp option. LP 2025-12-23 */ | |
| 4869 | - private function get_express_logo_page_variant($page = null) { | |
| 4870 | - $options = get_option('vipps_button_options'); | |
| 4871 | - | |
| 4872 | - // Init defaults, use mini version by default in below pages. LP 2025-12-17 | |
| 4873 | - $use_mini = in_array($page, ['catalog']); | |
| 4874 | - $variant = ""; | |
| 4875 | - | |
| 4876 | - // Find correct variant from button settings. LP 2025-12-17 | |
| 4877 | - if (is_array($options) && array_key_exists('express', $options)) { | |
| 4878 | - if (array_key_exists($page, $options['express']['force-mini'])) { | |
| 4879 | - $use_mini = sanitize_title($options['express']['force-mini'][$page]) === 'yes'; | |
| 4880 | - } | |
| 4881 | - $key = $use_mini ? 'mini-variant' : 'variant'; | |
| 4882 | - $variant = sanitize_title($options['express'][$key]) ?? ''; | |
| 4883 | - } | |
| 4884 | - | |
| 4885 | - if (!$variant) { | |
| 4886 | - $variant = $use_mini ? "default-mini" : "default"; | |
| 4887 | - } | |
| 4888 | - return apply_filters('woo_vipps_express_button_page_variant', $variant, $page); | |
| 4889 | - } | |
| 4890 | - | |
| 4891 | 5107 | // Get express banner logo based on payment method. LP 2025-09-03 |
| 4892 | 5108 | private function get_express_banner_logo() { |
| 4893 | 5109 | $payment_method = $this->get_payment_method_name(); |
| 4894 | 5110 | |
| @@ -4899,10 +5115,12 @@ | ||
| 4899 | 5115 | } |
| 4900 | 5116 | return null; |
| 4901 | 5117 | } |
| 4902 | 5118 | |
| 4903 | - // Get buy now button by manually selecting logo variant and language. LP 2026-01-16 | |
| 4904 | - public function get_buy_now_button_manual($product_id,$variation_id=null,$sku=null,$disabled=false, $classes='', $logo_variant=null, $logo_lang=null) { | |
| 5119 | + // Code that will generate various versions of the 'buy now with Vipps' button IOK 2018-09-27 | |
| 5120 | + // $context is slug describing where its to be used, like 'catalog', 'cart', 'product' etc. and will | |
| 5121 | + // be used unless $button_args_override is nonempty. See init_button_options() and get_html_button() LP 2026-06-26 | |
| 5122 | + public function get_buy_now_button($product_id,$variation_id=null,$sku=null,$disabled=false, $classes='', $context='global', $button_args_override = []) { | |
| 4905 | 5123 | $disabled = $disabled ? 'disabled' : ''; |
| 4906 | 5124 | $data = array(); |
| 4907 | 5125 | |
| 4908 | 5126 | // Support directly using the variant id as $product_id with no $variation_id. LP 2026-01-23 |
| @@ -4917,9 +5135,8 @@ | ||
| 4917 | 5135 | if ($sku) $data['product_sku'] = $sku; |
| 4918 | 5136 | if ($product_id) $data['product_id'] = $product_id; |
| 4919 | 5137 | if ($variation_id) $data['variation_id'] = $variation_id; |
| 4920 | 5138 | |
| 4921 | - | |
| 4922 | 5139 | $buttoncode = "<a href='javascript:void(0)' $disabled "; |
| 4923 | 5140 | foreach($data as $key=>$value) { |
| 4924 | 5141 | $value = esc_attr($value); |
| 4925 | 5142 | $buttoncode .= " data-$key='$value' "; |
| @@ -4926,14 +5143,18 @@ | ||
| 4926 | 5143 | } |
| 4927 | 5144 | |
| 4928 | 5145 | $payment_method = $this->get_payment_method_name(); |
| 4929 | 5146 | $title = sprintf(__('Buy now with %1$s', 'woo-vipps'), $payment_method); |
| 4930 | - $short = str_ends_with($logo_variant, 'mini'); | |
| 4931 | - $logo = $this->get_express_logo($payment_method, $logo_lang, $logo_variant); | |
| 4932 | 5147 | |
| 4933 | - $message =" <img border=0 src='$logo' alt='$payment_method'/>"; | |
| 5148 | + if (is_array($button_args_override) && $button_args_override) { | |
| 5149 | + $button_args = $button_args_override; | |
| 5150 | + } else { | |
| 5151 | + $button_args = $this->get_html_button_attrs_for_context($context); | |
| 5152 | + } | |
| 5153 | + $short = ($button_args['compact'] ?? 'false') === 'true'; | |
| 5154 | + $button = $this->get_html_button($button_args); | |
| 4934 | 5155 | |
| 4935 | -# Extra classes, if passed IOK 2019-02-26 | |
| 5156 | + # Extra classes, if passed IOK 2019-02-26 | |
| 4936 | 5157 | if (is_array($classes)) { |
| 4937 | 5158 | $classes = join(" ", $classes); |
| 4938 | 5159 | } |
| 4939 | 5160 | if ($classes) $classes = " $classes"; |
| @@ -4938,19 +5159,15 @@ | ||
| 4938 | 5159 | } |
| 4939 | 5160 | if ($classes) $classes = " $classes"; |
| 4940 | 5161 | if ($short) $classes = "short $classes"; |
| 4941 | 5162 | |
| 4942 | - $buttoncode .= " class='single-product button vipps-buy-now $payment_method $disabled$classes' title='$title'>$message</a>"; | |
| 5163 | + $buttoncode .= " class='single-product button vipps-buy-now $payment_method $disabled$classes' title='$title'>$button</a>"; | |
| 5164 | + | |
| 5165 | + | |
| 5166 | + | |
| 4943 | 5167 | return apply_filters('woo_vipps_buy_now_button', $buttoncode, $product_id, $variation_id, $sku, $disabled); |
| 4944 | 5168 | } |
| 4945 | 5169 | |
| 4946 | - // Code that will generate various versions of the 'buy now with Vipps' button IOK 2018-09-27 | |
| 4947 | - public function get_buy_now_button($product_id,$variation_id=null,$sku=null,$disabled=false, $classes='', $page=null) { | |
| 4948 | - $logo_lang = $this->get_customer_language(); | |
| 4949 | - $logo_variant = $this->get_express_logo_page_variant($page); | |
| 4950 | - return $this->get_buy_now_button_manual($product_id, $variation_id, $sku, $disabled, $classes, $logo_variant, $logo_lang); | |
| 4951 | - } | |
| 4952 | - | |
| 4953 | 5170 | // Display a 'buy now with express checkout' button on the product page IOK 2018-09-27 |
| 4954 | 5171 | public function single_product_buy_now_button () { |
| 4955 | 5172 | $gw = $this->gateway(); |
| 4956 | 5173 | $how = $gw->get_option('singleproductexpress'); |
| @@ -5031,43 +5248,90 @@ | ||
| 5031 | 5248 | } |
| 5032 | 5249 | |
| 5033 | 5250 | |
| 5034 | 5251 | |
| 5035 | - // Vipps Checkout replaces the default checkout page, and currently uses its own page for this which needs to exist | |
| 5252 | + // Checkout replaces the default checkout page, and currently uses its own page for this which needs to exist | |
| 5036 | 5253 | // IOK 2026-04-30 remove this when checkout is end-of-life'd |
| 5254 | + // We now also use this for the vipps special page, previously a fakepage. LP 2026-08-18 | |
| 5037 | 5255 | public function woocommerce_create_pages ($data) { |
| 5256 | + // Vipps Checkout page | |
| 5038 | 5257 | $vipps_checkout_activated = get_option('woo_vipps_checkout_activated', false); |
| 5039 | - if (!$vipps_checkout_activated) return $data; | |
| 5258 | + if ($vipps_checkout_activated) { | |
| 5259 | + $data['vipps_checkout'] = array( | |
| 5260 | + 'name' => _x( 'vipps_checkout', 'Page slug', 'woo-vipps' ), | |
| 5261 | + 'title' => _x( 'Vipps MobilePay Checkout', 'Page title', 'woo-vipps' ), | |
| 5262 | + 'content' => '<!-- wp:shortcode -->[' . 'vipps_checkout' . ']<!-- /wp:shortcode -->', | |
| 5263 | + ); | |
| 5264 | + } | |
| 5040 | 5265 | |
| 5041 | - $data['vipps_checkout'] = array( | |
| 5042 | - 'name' => _x( 'vipps_checkout', 'Page slug', 'woo-vipps' ), | |
| 5043 | - 'title' => _x( 'Vipps MobilePay Checkout', 'Page title', 'woo-vipps' ), | |
| 5044 | - 'content' => '<!-- wp:shortcode -->[' . 'vipps_checkout' . ']<!-- /wp:shortcode -->', | |
| 5045 | - ); | |
| 5046 | - | |
| 5266 | + // Vipps special page for certain payment flow actions. Previously a fake page. LP 2026-08-18 | |
| 5267 | + $data['vipps_special_page'] = [ | |
| 5268 | + 'name' => 'vipps-payment', // slug | |
| 5269 | + /* translators: company name */ | |
| 5270 | + 'title' => sprintf(__('%s special page', 'woo-vipps'), static::CompanyName()), // we hide the title frontend in template_redirect. LP 2026-08-27 | |
| 5271 | + 'content' => '<!-- wp:shortcode -->[vipps_special_page]<!-- /wp:shortcode -->', | |
| 5272 | + ]; | |
| 5047 | 5273 | return $data; |
| 5048 | 5274 | } |
| 5049 | 5275 | |
| 5050 | - // Creates any necessary Vipps pages. Will be called e.g. when activating Vipps Checkout or turning it on. | |
| 5276 | + // Creates any necessary Vipps pages. E.g vipps checkout page or vipps special page. LP 2026-09-01 | |
| 5277 | + // If a page slug already exists, then it won't overwrite or duplicate it!. LP 2026-09-02 | |
| 5051 | 5278 | public function maybe_create_vipps_pages () { |
| 5279 | + $make_pages = false; | |
| 5280 | + | |
| 5281 | + // Vipps Checkout page. LP 2026-08-18 | |
| 5052 | 5282 | $checkoutid = wc_get_page_id('vipps_checkout'); |
| 5053 | - $makeit = !$checkoutid || ! get_post_status($checkoutid); | |
| 5054 | - if ($makeit) { | |
| 5283 | + if (!$checkoutid || ! get_post_status($checkoutid)) { | |
| 5055 | 5284 | delete_option('woocommerce_vipps_checkout_page_id'); |
| 5285 | + $make_pages = true; | |
| 5056 | 5286 | } |
| 5057 | 5287 | |
| 5058 | - if ($makeit) { | |
| 5059 | - WC_Install::create_pages(); | |
| 5288 | + // vipps special page, previously a fake page. LP 2026-08-18 | |
| 5289 | + $builtin_special_page_id = static::get_special_page_id(); | |
| 5290 | + if (!$builtin_special_page_id || !get_post_status($builtin_special_page_id)) { | |
| 5291 | + delete_option('woocommerce_vipps_special_page_page_id'); | |
| 5292 | + $make_pages = true; | |
| 5060 | 5293 | } |
| 5294 | + | |
| 5295 | + if ($make_pages) { | |
| 5296 | + WC_Install::create_pages(); | |
| 5297 | + } | |
| 5061 | 5298 | } |
| 5062 | 5299 | |
| 5300 | + public function vipps_special_page_shortcode($atts, $content) { | |
| 5301 | + // No point in expanding this unless we are actually doing the special actions. LP 2026-08-25 | |
| 5302 | + if (is_admin()) return; | |
| 5303 | + if (wp_doing_ajax()) return; | |
| 5304 | + if (defined('REST_REQUEST') && REST_REQUEST) return; | |
| 5305 | + if (did_filter('woo_vipps_special_page_html')) return; // User has somehow added two shortcodes. IOK 2026-09-18 | |
| 5063 | 5306 | |
| 5307 | + $action = $_GET['action'] ?? ''; | |
| 5308 | + $html = ""; | |
| 5309 | + switch ($action) { | |
| 5310 | + case 'wait_for_payment': | |
| 5311 | + $html = $this->vipps_wait_for_payment(); | |
| 5312 | + break; | |
| 5313 | + case 'do_express_checkout': | |
| 5314 | + $html = $this->vipps_express_checkout(); | |
| 5315 | + break; | |
| 5316 | + case 'buy_product': | |
| 5317 | + $html = $this->vipps_buy_product(); | |
| 5318 | + break; | |
| 5319 | + default: | |
| 5320 | + $html = ''; | |
| 5321 | + } | |
| 5322 | + // This is mostly to avoid this shortcode evaluating twice IOK 2026-09-18 | |
| 5323 | + $html = apply_filters('woo_vipps_special_page_html', $html, $action); | |
| 5324 | + | |
| 5325 | + // Remember, this is a shortcode, so the html must be returned, not echoed IOK 2026-09-11 | |
| 5326 | + return $html; | |
| 5327 | + } | |
| 5328 | + | |
| 5329 | + | |
| 5064 | 5330 | // This URL will when accessed add a product to the cart and go directly to the express checkout page. |
| 5065 | 5331 | // The argument passed must be a shareable link created for a given product - so this in effect acts as a landing page for |
| 5066 | 5332 | // the buying thru Vipps Express Checkout of a single product linked to in for instance banners. IOK 2018-09-24 |
| 5067 | 5333 | public function vipps_buy_product() { |
| 5068 | - status_header(200,'OK'); | |
| 5069 | - Vipps::nocache(); | |
| 5070 | 5334 | |
| 5071 | 5335 | add_filter('body_class', function ($classes) { |
| 5072 | 5336 | $classes[] = 'vipps-express-checkout'; |
| 5073 | 5337 | $classes[] = 'woocommerce-checkout'; // Required by Pixel Your Site IOK 2022-11-24 |
| @@ -5103,9 +5367,9 @@ | ||
| 5103 | 5367 | |
| 5104 | 5368 | if (!$productinfo) { |
| 5105 | 5369 | $title = __("Product is no longer available",'woo-vipps'); |
| 5106 | 5370 | $content = __("The link you have followed is for a product that is no longer available at this location. Please return to the store and try again",'woo-vipps'); |
| 5107 | - return $this->fakepage($title,$content); | |
| 5371 | + return $this->special_page_html($title,$content); | |
| 5108 | 5372 | } |
| 5109 | 5373 | |
| 5110 | 5374 | // Pass the productinfo to the express checkout form |
| 5111 | 5375 | $args = array(); |
| @@ -5122,15 +5386,13 @@ | ||
| 5122 | 5386 | } |
| 5123 | 5387 | $args[sanitize_title(wp_unslash($key))] = sanitize_text_field(wp_unslash($value)); |
| 5124 | 5388 | } |
| 5125 | 5389 | |
| 5126 | - $this->print_express_checkout_page(true,'do_single_product_express_checkout',$args); | |
| 5390 | + return $this->express_checkout_page_html(true,'do_single_product_express_checkout',$args); | |
| 5127 | 5391 | } |
| 5128 | 5392 | |
| 5129 | 5393 | // This is a landing page for the express checkout of then normal cart - it is done like this because this could take time on slower hosts. |
| 5130 | 5394 | public function vipps_express_checkout() { |
| 5131 | - status_header(200,'OK'); | |
| 5132 | - Vipps::nocache(); | |
| 5133 | 5395 | // We need a nonce to get here, but we should only get here when we have a cart, so this will not be cached. |
| 5134 | 5396 | // IOK 2018-05-28 |
| 5135 | 5397 | $ok = isset($_REQUEST['sec']) && wp_verify_nonce($_REQUEST['sec'],'express'); |
| 5136 | 5398 | |
| @@ -5157,9 +5419,9 @@ | ||
| 5157 | 5419 | }); |
| 5158 | 5420 | |
| 5159 | 5421 | do_action('woo_vipps_express_checkout_page'); |
| 5160 | 5422 | |
| 5161 | - $this->print_express_checkout_page(true, 'do_express_checkout'); | |
| 5423 | + return $this->express_checkout_page_html(true, 'do_express_checkout'); | |
| 5162 | 5424 | } |
| 5163 | 5425 | |
| 5164 | 5426 | // This method tries to ensure that a customer does not 'lose' the return page and |
| 5165 | 5427 | // starts ordering the same products twice. IOK 2020-01-22 |
| @@ -5254,9 +5516,10 @@ | ||
| 5254 | 5516 | return $orderspec; |
| 5255 | 5517 | } |
| 5256 | 5518 | |
| 5257 | 5519 | // Used as a landing page for launching express checkout - borh for the cart and for single products. IOK 2018-09-28 |
| 5258 | - protected function print_express_checkout_page($execute,$action,$productinfo=null) { | |
| 5520 | + // Returns the html. LP 2026-08-27 | |
| 5521 | + protected function express_checkout_page_html($execute,$action,$productinfo=null) { | |
| 5259 | 5522 | $gw = $this->gateway(); |
| 5260 | 5523 | |
| 5261 | 5524 | $expressCheckoutMessages = array(); |
| 5262 | 5525 | $expressCheckoutMessages['termsAndConditionsError'] = __( 'Please read and accept the terms and conditions to proceed with your order.', 'woocommerce' ); |
| @@ -5267,11 +5530,12 @@ | ||
| 5267 | 5530 | wp_localize_script('vipps-express-checkout', 'VippsCheckoutMessages', $expressCheckoutMessages); |
| 5268 | 5531 | wp_enqueue_script('vipps-express-checkout'); |
| 5269 | 5532 | // If we have a valid nonce when we get here, just call the 'create order' bit at once. Otherwise, make a button |
| 5270 | 5533 | // to actually perform the express checkout. |
| 5271 | - $buttonimgurl= apply_filters('woo_vipps_express_checkout_button', $this->get_payment_logo('landing')); | |
| 5534 | + $buttonhtml = apply_filters('woo_vipps_express_checkout_button', $this->get_html_button()); | |
| 5272 | 5535 | |
| 5273 | 5536 | |
| 5537 | + | |
| 5274 | 5538 | $orderspec = $this->get_orderspec_from_arguments($productinfo); |
| 5275 | 5539 | if (empty($orderspec)) { |
| 5276 | 5540 | $orderspec = $this->get_orderspec_from_cart(); |
| 5277 | 5541 | } |
| @@ -5338,10 +5602,9 @@ | ||
| 5338 | 5602 | |
| 5339 | 5603 | if ($execute) { |
| 5340 | 5604 | $content .= "<p id=waiting>" . __("Please wait while we are preparing your order", 'woo-vipps') . "</p>"; |
| 5341 | 5605 | $content .= "<div id='vipps-status-message'></div>"; |
| 5342 | - $this->fakepage(__('Order in progress','woo-vipps'), $content); | |
| 5343 | - return; | |
| 5606 | + return $this->special_page_html('', $content); | |
| 5344 | 5607 | } else { |
| 5345 | 5608 | $content .= $askForConfirmationHTML; |
| 5346 | 5609 | $content .= $extraHTML; |
| 5347 | 5610 | $content .= $termsHTML; |
| @@ -5346,12 +5609,11 @@ | ||
| 5346 | 5609 | $content .= $extraHTML; |
| 5347 | 5610 | $content .= $termsHTML; |
| 5348 | 5611 | $content .= apply_filters('woo_vipps_express_checkout_validation_elements', ''); |
| 5349 | 5612 | $title = sprintf(__('Buy now with %1$s!', 'woo-vipps'), $this->get_payment_method_name()); |
| 5350 | - $content .= "<div class='vipps_buy_now_wrapper noloop'><a href='#' id='do-express-checkout' class='button vipps-express-checkout' title='$title'><img alt='$title' border=0 src='$buttonimgurl'></a></div>"; | |
| 5613 | + $content .= "<div class='vipps_buy_now_wrapper noloop'><a href='#' id='do-express-checkout' class='vipps-express-checkout' title='$title'>$buttonhtml</a></div>"; | |
| 5351 | 5614 | $content .= "<div id='vipps-status-message'></div>"; |
| 5352 | - $this->fakepage(sprintf(__('%1$s Express Checkout','woo-vipps'), $this->get_payment_method_name()), $content); | |
| 5353 | - return; | |
| 5615 | + return $this->special_page_html('', $content); | |
| 5354 | 5616 | } |
| 5355 | 5617 | } |
| 5356 | 5618 | |
| 5357 | 5619 | |
| @@ -5356,11 +5618,8 @@ | ||
| 5356 | 5618 | |
| 5357 | 5619 | |
| 5358 | 5620 | |
| 5359 | 5621 | public function vipps_wait_for_payment() { |
| 5360 | - status_header(200,'OK'); | |
| 5361 | - Vipps::nocache(); | |
| 5362 | - | |
| 5363 | 5622 | $orderid = WC()->session->get('_vipps_pending_order'); |
| 5364 | 5623 | |
| 5365 | 5624 | $order = null; |
| 5366 | 5625 | $gw = $this->gateway(); |
| @@ -5395,9 +5654,8 @@ | ||
| 5395 | 5654 | $session->set('_vipps_pending_order', $orderid); |
| 5396 | 5655 | } |
| 5397 | 5656 | } |
| 5398 | 5657 | |
| 5399 | - | |
| 5400 | 5658 | do_action('woo_vipps_wait_for_payment_page',$order); |
| 5401 | 5659 | |
| 5402 | 5660 | $deleted_order=0; |
| 5403 | 5661 | if ($orderid && !$order) { |
| @@ -5461,11 +5719,9 @@ | ||
| 5461 | 5719 | } |
| 5462 | 5720 | $content .= "<div id=failure><p>". __('Order cancelled','woo-vipps') . '</p>'; |
| 5463 | 5721 | $content .= "<p><a href='" . home_url() . "' class='btn button'>" . __('Continue shopping','woo-vipps') . '</a></p>'; |
| 5464 | 5722 | $content .= "</div>"; |
| 5465 | - $this->fakepage(__('Order cancelled','woo-vipps'), $content); | |
| 5466 | - | |
| 5467 | - return; | |
| 5723 | + return $this->special_page_html('', $content); | |
| 5468 | 5724 | } |
| 5469 | 5725 | |
| 5470 | 5726 | // Still pending and order is supposed to exist, so wait for Vipps. This happens all the time, so logging is removed. IOK 2018-09-27 |
| 5471 | 5727 | |
| @@ -5471,14 +5727,8 @@ | ||
| 5471 | 5727 | |
| 5472 | 5728 | // Otherwise, go to a page waiting/polling for the callback. IOK 2018-05-16 |
| 5473 | 5729 | wp_enqueue_script('check-vipps',plugins_url('js/check-order-status.js',__FILE__),array('jquery','vipps-gw'),filemtime(dirname(__FILE__) . "/js/check-order-status.js"), 'true'); |
| 5474 | 5730 | |
| 5475 | - // Check that order exists and belongs to our session. Can use WC()->session->get() I guess - set the orderid or a hash value in the session | |
| 5476 | - // and check that the order matches (and is 'pending') (and exists) | |
| 5477 | - $vippsstamp = $order->get_meta('_vipps_init_timestamp'); | |
| 5478 | - $vippsstatus = $order->get_meta('_vipps_status'); | |
| 5479 | - $message = __($order->get_meta('_vipps_confirm_message'),'woo-vipps'); | |
| 5480 | - | |
| 5481 | 5731 | $signal = $this->callbackSignal($order); |
| 5482 | 5732 | $content = ""; |
| 5483 | 5733 | $content .= "<div id='waiting'><p>" . sprintf(__('Waiting for confirmation of purchase from %1$s','woo-vipps'), $this->get_payment_method_name()); |
| 5484 | 5734 | |
| @@ -5511,94 +5761,22 @@ | ||
| 5511 | 5761 | $content .= "<a id='continueToOrderFailed' style='display:none' href='" . $failure_redirect . "'></a>"; |
| 5512 | 5762 | $content .= "<a id='continueToOrderFailedFallback' style='display:none' href='" . $gw->get_return_url($order) . "'></a>"; |
| 5513 | 5763 | $content .= "</div>"; |
| 5514 | 5764 | |
| 5765 | + return $this->special_page_html('', $content); | |
| 5766 | + } | |
| 5515 | 5767 | |
| 5516 | - $this->fakepage(__('Waiting for your order confirmation','woo-vipps'), $content); | |
| 5768 | + // Returns formatted html for the vipps special page. LP 2026-08-27 | |
| 5769 | + public function special_page_html($header, $content) { | |
| 5770 | + $header_html = $header ? "<h2 class='vipps-special-page-title page-title'>$header</h2>" : ''; | |
| 5771 | + $html = <<<EOF | |
| 5772 | + $header_html | |
| 5773 | + <div class="vipps-special-page-content">$content</div> | |
| 5774 | + EOF; | |
| 5775 | + return apply_filters('woo_vipps_special_page_html', $html, $header, $content); | |
| 5517 | 5776 | } |
| 5518 | 5777 | |
| 5519 | 5778 | |
| 5520 | - | |
| 5521 | - public function fakepage($title,$content) { | |
| 5522 | - global $wp, $wp_query; | |
| 5523 | - // We don't want this here. | |
| 5524 | - remove_filter ('the_content', 'wpautop'); | |
| 5525 | - | |
| 5526 | - $specialid = $this->gateway()->get_option('vippsspecialpageid'); | |
| 5527 | - $wp_post = null; | |
| 5528 | - if ($specialid) { | |
| 5529 | - $wp_post = get_post($specialid); | |
| 5530 | - if ($wp_post) { | |
| 5531 | - $wp_post->post_title = $title; | |
| 5532 | - $wp_post->post_content = $content; | |
| 5533 | - // Normalize a bit | |
| 5534 | - $wp_post->filter = 'raw'; // important | |
| 5535 | - $wp_post->post_status = 'publish'; | |
| 5536 | - $wp_post->comment_status= 'closed'; | |
| 5537 | - $wp_post->ping_status= 'closed'; | |
| 5538 | - } else { | |
| 5539 | - $this->log(sprintf(__("Could not use special page with id %s - it seems not to exist.", 'woo-vipps'), $specialid), 'error'); | |
| 5540 | - } | |
| 5541 | - } | |
| 5542 | - if (!$wp_post || is_wp_error($wp_post)) { | |
| 5543 | - $post = new stdClass(); | |
| 5544 | - $post->ID = -99; | |
| 5545 | - $post->post_author = 1; | |
| 5546 | - $post->post_date = current_time( 'mysql' ); | |
| 5547 | - $post->post_date_gmt = current_time( 'mysql', 1 ); | |
| 5548 | - $post->post_title = $title; | |
| 5549 | - $post->post_content = $content; | |
| 5550 | - $post->post_status = 'publish'; | |
| 5551 | - $post->comment_status = 'closed'; | |
| 5552 | - $post->ping_status = 'closed'; | |
| 5553 | - $post->post_name = 'vippsconfirm-fake-page-name'; | |
| 5554 | - $post->post_type = 'page'; | |
| 5555 | - $post->filter = 'raw'; // important | |
| 5556 | - $wp_post = new WP_Post($post); | |
| 5557 | - wp_cache_add( -99, $wp_post, 'posts' ); | |
| 5558 | - } | |
| 5559 | - | |
| 5560 | - // Update the main query | |
| 5561 | - $wp_query->post = $wp_post; | |
| 5562 | - $wp_query->posts = array( $wp_post ); | |
| 5563 | - $wp_query->queried_object = $wp_post; | |
| 5564 | - $wp_query->queried_object_id = $wp_post->ID; | |
| 5565 | - $wp_query->found_posts = 1; | |
| 5566 | - $wp_query->post_count = 1; | |
| 5567 | - $wp_query->max_num_pages = 1; | |
| 5568 | - $wp_query->is_page = true; | |
| 5569 | - $wp_query->is_singular = true; | |
| 5570 | - $wp_query->is_single = false; | |
| 5571 | - $wp_query->is_attachment = false; | |
| 5572 | - $wp_query->is_archive = false; | |
| 5573 | - $wp_query->is_category = false; | |
| 5574 | - $wp_query->is_tag = false; | |
| 5575 | - $wp_query->is_tax = false; | |
| 5576 | - $wp_query->is_author = false; | |
| 5577 | - $wp_query->is_date = false; | |
| 5578 | - $wp_query->is_year = false; | |
| 5579 | - $wp_query->is_month = false; | |
| 5580 | - $wp_query->is_day = false; | |
| 5581 | - $wp_query->is_time = false; | |
| 5582 | - $wp_query->is_search = false; | |
| 5583 | - $wp_query->is_feed = false; | |
| 5584 | - $wp_query->is_comment_feed = false; | |
| 5585 | - $wp_query->is_trackback = false; | |
| 5586 | - $wp_query->is_home = false; | |
| 5587 | - $wp_query->is_embed = false; | |
| 5588 | - $wp_query->is_404 = false; | |
| 5589 | - $wp_query->is_paged = false; | |
| 5590 | - $wp_query->is_admin = false; | |
| 5591 | - $wp_query->is_preview = false; | |
| 5592 | - $wp_query->is_robots = false; | |
| 5593 | - $wp_query->is_posts_page = false; | |
| 5594 | - $wp_query->is_post_type_archive = false; | |
| 5595 | - // Update globals | |
| 5596 | - $GLOBALS['wp_query'] = $wp_query; | |
| 5597 | - $wp->register_globals(); | |
| 5598 | - return $wp_post; | |
| 5599 | - } | |
| 5600 | - | |
| 5601 | 5779 | // Support the interactivity API with data about our cart IOK 2026-02-23 |
| 5602 | 5780 | public function woo_vipps_store_api_cart_data() { |
| 5603 | 5781 | // Reverting the condition with the directive data-wp-bind--hidden does not work, so we need the flipped bool here (hide instead of show). LP 2026-02-10 |
| 5604 | 5782 | |
| @@ -5604,13 +5782,15 @@ | ||
| 5604 | 5782 | |
| 5605 | 5783 | $checkout_page = $this->gateway()->vipps_checkout_available(); |
| 5606 | 5784 | $standard_checkout = get_permalink(get_option('woocommerce_checkout_page_id')); |
| 5607 | 5785 | $checkout_url = $checkout_page ? get_permalink($checkout_page) : $standard_checkout; |
| 5786 | + | |
| 5608 | 5787 | $cart_data = array( |
| 5609 | 5788 | 'cart_hide_express' => !$this->gateway()->show_express_checkout(), |
| 5610 | 5789 | 'cart_supports_checkout' => (bool) $checkout_page, |
| 5611 | 5790 | 'checkout_url' => $checkout_url, |
| 5612 | 5791 | ); |
| 5792 | + | |
| 5613 | 5793 | return $cart_data; |
| 5614 | 5794 | } |
| 5615 | 5795 | |
| 5616 | 5796 | public function woo_vipps_store_api_cart_schema() { |
| @@ -5632,8 +5812,117 @@ | ||
| 5632 | 5812 | ), |
| 5633 | 5813 | ); |
| 5634 | 5814 | } |
| 5635 | 5815 | |
| 5816 | + // Inits option 'vipps_button_options' and handles migration from older versions. LP 2026-06-26 | |
| 5817 | + // new version is stored as vipps_button_options2 to avoid breaking older versions on version revert. IOK 2026-07-15 | |
| 5818 | + private function init_button_options() { | |
| 5819 | + /* New structure as of now | |
| 5820 | + * [ | |
| 5821 | + * 'version' => x.x, | |
| 5822 | + * 'express' => [ | |
| 5823 | + * 'version' => x.x, used to migrate from previous iterations | |
| 5824 | + * 'configs' => [ different button parameters for certain contexts, falls back to global if context has no override | |
| 5825 | + * 'global' => ['compact' => ..., 'verb' => ..., ...], | |
| 5826 | + * 'cart' => [...], | |
| 5827 | + * 'product' => [...], | |
| 5828 | + * 'checkout' => [...], | |
| 5829 | + * ... | |
| 5830 | + * ], | |
| 5831 | + * 'product_configs' => [ overrides for specific products | |
| 5832 | + * 1532 => ['compact' => ..., 'verb' => ..., ...], | |
| 5833 | + * ... | |
| 5834 | + * ], | |
| 5835 | + * ], | |
| 5836 | + * ] | |
| 5837 | + */ | |
| 5838 | + $options = get_option('vipps_button_options2'); | |
| 5839 | + if (!empty($options)) return; | |
| 5840 | + | |
| 5841 | + $old_options = get_option('vipps_button_options'); | |
| 5842 | + $default_config = $this->get_html_button_default_attrs(); | |
| 5843 | + unset($default_config['brand']); // brand needs to be dynamic from payment method! LP 2026-07-01 | |
| 5844 | + $default_compact = array_replace($default_config, ['compact' => 'true']); | |
| 5845 | + | |
| 5846 | + $default_options = [ | |
| 5847 | + 'version' => $this->button_options_version, | |
| 5848 | + 'express' => [ | |
| 5849 | + 'version' => $this->button_options_express_version, | |
| 5850 | + 'configs' => [ | |
| 5851 | + 'global' => $default_config, | |
| 5852 | + // Need compact version by default for below pages. LP 2026-07-07 | |
| 5853 | + 'catalog' => $default_compact, | |
| 5854 | + 'minicart' => $default_compact, // storefront needs compact, tho 2025 theme has a lot of room. Just use compact LP 2026-07-07 | |
| 5855 | + ], | |
| 5856 | + 'product_configs' => [], | |
| 5857 | + ], | |
| 5858 | + ]; | |
| 5859 | + | |
| 5860 | + $new_options = $default_options; | |
| 5861 | + | |
| 5862 | + //Actually, we have some options from the old structure IOK 2026-07-15 | |
| 5863 | + if (!empty($old_options)) { | |
| 5864 | + $new_options['express']['configs']['global'] = $this->migrate_button_variant_to_config($old_options['express']['variant'] ?? ''); | |
| 5865 | + unset($new_options['express']['configs']['global']['brand']); // dont set brand, this needs to be dynamic. LP 2026-07-01 | |
| 5866 | + } | |
| 5867 | + | |
| 5868 | + // Migrate context/page mini override to new context config. LP 2026-06-26 | |
| 5869 | + if (is_array($old_options['express']['force-mini'] ?? null)) { | |
| 5870 | + foreach($old_options['express']['force-mini'] as $context => $use_mini) { | |
| 5871 | + if ("yes" === $use_mini) { | |
| 5872 | + $config = $this->migrate_button_variant_to_config($old_options['express']['mini-variant'] ?? ''); | |
| 5873 | + unset($config['brand']); // brand needs to be dynamic from payment method! LP 2026-07-01 | |
| 5874 | + $config['compact'] = 'true'; | |
| 5875 | + $new_options['express']['configs'][$context] = $config; | |
| 5876 | + } | |
| 5877 | + } | |
| 5878 | + } | |
| 5879 | + | |
| 5880 | + if ($this->get_payment_method_name() !== 'MobilePay') { | |
| 5881 | + // Finnish is only available in the MobilePay component right now, so reset language in any configs. LP 2026-07-01 | |
| 5882 | + foreach(($new_options['express']['configs'] ?? []) as $context => $config) { | |
| 5883 | + if ('fi' === ($config['language'] ?? '')) { | |
| 5884 | + $config['language'] = 'store'; | |
| 5885 | + $new_options['express']['configs'][$context] = $config; | |
| 5886 | + } | |
| 5887 | + } | |
| 5888 | + } | |
| 5889 | + | |
| 5890 | + /* translators: placeholders are arrays */ | |
| 5891 | + $this->log(sprintf(__('Migrating from old button options. Old: %s, new: %s', 'woo-vipps'), print_r($options, true), print_r($new_options, true)), 'debug'); | |
| 5892 | + | |
| 5893 | + | |
| 5894 | + | |
| 5895 | + update_option('vipps_button_options2', $new_options); | |
| 5896 | + } | |
| 5897 | + | |
| 5898 | + // Old variant string => new config array. LP 2026-06-26 | |
| 5899 | + public function migrate_button_variant_to_config($variant_slug) { | |
| 5900 | + if (!is_string($variant_slug)) return []; | |
| 5901 | + $config = $this->get_html_button_default_attrs(); | |
| 5902 | + $config['rounded'] = str_contains($variant_slug, 'pill') ? 'true' : 'false'; | |
| 5903 | + $config['compact'] = str_contains($variant_slug, 'mini') ? 'true' : 'false'; | |
| 5904 | + if (str_contains($variant_slug, 'buy-now')) { | |
| 5905 | + $config['verb'] = 'buy'; | |
| 5906 | + } else if (str_contains($variant_slug, 'express')) { | |
| 5907 | + $config['verb'] = 'express'; | |
| 5908 | + } | |
| 5909 | + return $config; | |
| 5910 | + } | |
| 5911 | + | |
| 5912 | + // Old legacy button logo variants. Replaced by web component. See get_html_button(). LP 2026-07-01 | |
| 5913 | + public function get_express_logo_variants() { | |
| 5914 | + return [ | |
| 5915 | + 'buy-now-rectangular' => __('Buy now rectangular', 'woo-vipps'), | |
| 5916 | + 'buy-now-pill' => __('Buy now pill', 'woo-vipps'), | |
| 5917 | + 'express-rectangular' => __('Express rectangular', 'woo-vipps'), | |
| 5918 | + 'express-pill' => __('Express pill', 'woo-vipps'), | |
| 5919 | + 'express-rectangular-mini' => __('Express rectangular mini', 'woo-vipps'), | |
| 5920 | + 'express-pill-mini' => __('Express pill mini', 'woo-vipps'), | |
| 5921 | + ]; | |
| 5922 | + } | |
| 5923 | + | |
| 5924 | + | |
| 5636 | 5925 | // Whether the order is possible to restart with a retry session at VMP. LP 2026-03-18 |
| 5637 | 5926 | public static function order_is_vipps_retryable($order_id) { |
| 5638 | 5927 | $order = wc_get_order($order_id); |
| 5639 | 5928 | if (!$order) return false; |
| @@ -5642,6 +5931,20 @@ | ||
| 5642 | 5931 | $shipping_set = $order->get_meta('_vipps_shipping_set'); |
| 5643 | 5932 | |
| 5644 | 5933 | // Express or unfinalized Checkout orders do not have shipping available, so we cant retry these in particular. LP 2026-03-18 |
| 5645 | 5934 | return $nonexpress_epayment || $shipping_set; |
| 5935 | + } | |
| 5936 | + | |
| 5937 | + /** Returns the plugin's rest api namespace including the version. | |
| 5938 | + * Use latest version ($version = 'latest') with caution, we want backwards compatible endpoints. LP 2026-03-31 */ | |
| 5939 | + public static function get_rest_namespace($version = 'latest') { | |
| 5940 | + $version = $version === 'latest' ? self::REST_CURRENT_VERSION : $version; | |
| 5941 | + return self::REST_NAMESPACE_BASE . "/$version"; | |
| 5942 | + } | |
| 5943 | + | |
| 5944 | + /** Returns the plugin's rest api url. | |
| 5945 | + * $version accepts 'latest', but you probably don't want to do that. | |
| 5946 | + * Remember root forward-slash for $route. e.g $route = '/my-route' LP 2026-03-31 */ | |
| 5947 | + public static function get_rest_url($version, $route) { | |
| 5948 | + return get_rest_url(null, static::get_rest_namespace($version) . $route, 'rest'); | |
| 5646 | 5949 | } |
| 5647 | 5950 | } |