| @@ -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 | } |
| @@ -114,12 +121,17 @@ | ||
| 114 | 121 | add_action('init',array($Vipps,'init')); |
| 115 | 122 | add_action( 'woocommerce_loaded', array($Vipps,'woocommerce_loaded')); |
| 116 | 123 | add_filter( 'woocommerce_available_payment_gateways', array($Vipps, 'payment_gateway_filter')); |
| 117 | 124 | add_action( 'woocommerce_blocks_loaded', [$Vipps, 'woocommerce_blocks_loaded']); |
| 118 | - // 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 | |
| 119 | 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 |
| 120 | 127 | // stored in the database since we support this for both Vipps MobilePay checkokut and Express. IOK 2026-02-25 |
| 121 | 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); | |
| 122 | 134 | } |
| 123 | 135 | |
| 124 | 136 | // Register woocommerce store api endpoint to use in buy-now minicart block. LP 2026-02-10 |
| 125 | 137 | public function woocommerce_blocks_loaded() { |
| @@ -234,9 +246,9 @@ | ||
| 234 | 246 | add_action('wp_ajax_woo_vipps_order_action', array($this, 'order_handle_vipps_action')); |
| 235 | 247 | |
| 236 | 248 | // Fetch wc products, but filter those only purchasable by VMP express checkout. LP 2026-01-22 |
| 237 | 249 | add_action('rest_api_init', function() { |
| 238 | - register_rest_route('woo-vipps/v1', '/express-products', [ | |
| 250 | + register_rest_route(self::get_rest_namespace('v1'), '/express-products', [ | |
| 239 | 251 | 'methods' => 'GET', |
| 240 | 252 | 'callback' => [$this, 'rest_express_checkout_products'], |
| 241 | 253 | 'permission_callback' => '__return_true', |
| 242 | 254 | ]); |
| @@ -271,8 +283,43 @@ | ||
| 271 | 283 | add_filter('woo_vipps_lock_order', array($this,'flock_lock_order')); |
| 272 | 284 | add_action('woo_vipps_unlock_order', array($this, 'flock_unlock_order')); |
| 273 | 285 | } |
| 274 | 286 | |
| 287 | + // Set default button options, migrating any older setup IOK 2026-07-15 | |
| 288 | + $this->init_button_options(); | |
| 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. | |
| 294 | + | |
| 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'); | |
| 304 | + } | |
| 305 | + }; | |
| 306 | + add_action('delete_post', $delete_special_page_id, 10, 2); | |
| 307 | + add_action('wp_trash_post', $delete_special_page_id, 10, 2); | |
| 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 | + | |
| 275 | 322 | } |
| 276 | 323 | |
| 277 | 324 | public function admin_init () { |
| 278 | 325 | $gw = $this->gateway(); |
| @@ -392,9 +439,77 @@ | ||
| 392 | 439 | } |
| 393 | 440 | } |
| 394 | 441 | } |
| 395 | 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'); | |
| 396 | 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 | + | |
| 397 | 512 | // Runs on init, adds the Vipps badge feature if activated |
| 398 | 513 | public function maybe_add_vipps_badge_feature () { |
| 399 | 514 | $badge_options = get_option('vipps_badge_options'); |
| 400 | 515 | if (!$badge_options || !@$badge_options['badgeon']) return false; |
| @@ -723,8 +838,11 @@ | ||
| 723 | 838 | |
| 724 | 839 | // Get current brand and language |
| 725 | 840 | $current_brand = strtolower($this->get_payment_method_name()); |
| 726 | 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'; | |
| 727 | 845 | |
| 728 | 846 | $variants = ['white'=> __('White', 'woo-vipps'), 'grey' => __('Grey','woo-vipps'), |
| 729 | 847 | 'filled'=> __('Filled', 'woo-vipps'), 'light'=>__('Light','woo-vipps'), |
| 730 | 848 | 'purple'=> __('Purple', 'woo-vipps')]; |
| @@ -787,9 +905,9 @@ | ||
| 787 | 905 | |
| 788 | 906 | <h2><?php _e('Shortcodes', 'woo-vipps'); ?> </h2> |
| 789 | 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> |
| 790 | 908 | <br><?php _e("The shortcode looks like this:", 'woo-vipps')?><br> |
| 791 | - <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> | |
| 792 | 910 | <?php _e("Please refer to the documentation for the meaning of the parameters.", 'woo-vipps'); ?></br> |
| 793 | 911 | <?php _e("The brand will be automatically applied.", 'woo-vipps'); ?> |
| 794 | 912 | </p> |
| 795 | 913 | |
| @@ -816,21 +934,24 @@ | ||
| 816 | 934 | echo json_encode(array('ok'=>0,'msg'=>__('You don\'t have sufficient rights to edit this product', 'woo-vipps'))); |
| 817 | 935 | wp_die(__('You don\'t have sufficient rights to edit this product', 'woo-vipps')); |
| 818 | 936 | } |
| 819 | 937 | |
| 820 | - $options = get_option('vipps_button_options'); | |
| 821 | - if (isset($_POST['express']['variant'])) { | |
| 822 | - $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 | + } | |
| 823 | 952 | } |
| 824 | - if (isset($_POST['express']['mini-variant'])) { | |
| 825 | - $options['express']['mini-variant'] = sanitize_title($_POST['express']['mini-variant']); | |
| 826 | - } | |
| 827 | - if (isset($_POST['express']['force-mini']) && is_array($_POST['express']['force-mini'])) { | |
| 828 | - foreach($_POST['express']['force-mini'] as $key => $val) | |
| 829 | - $options['express']['force-mini'][$key] = sanitize_title($val); | |
| 830 | - } | |
| 831 | - | |
| 832 | - update_option('vipps_button_options', $options); | |
| 953 | + update_option('vipps_button_options2', $new); | |
| 833 | 954 | wp_safe_redirect(admin_url("admin.php?page=vipps_button_menu")); |
| 834 | 955 | exit(); |
| 835 | 956 | } |
| 836 | 957 | |
| @@ -864,9 +985,12 @@ | ||
| 864 | 985 | public function vipps_mobilepay_badge_shortcode($atts) { |
| 865 | 986 | $args = shortcode_atts( array('id'=>'', 'class'=>'', 'brand' => '', 'variant' => '','language'=>''), $atts ); |
| 866 | 987 | |
| 867 | 988 | $variant = in_array($args['variant'], ['orange', 'light-orange', 'grey','white', 'purple', 'filled', 'light']) ? $args['variant'] : ""; |
| 868 | - $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'; | |
| 869 | 993 | $id = sanitize_title($args['id']); |
| 870 | 994 | $class = sanitize_text_field($args['class']); |
| 871 | 995 | |
| 872 | 996 | $attributes = []; |
| @@ -882,13 +1006,18 @@ | ||
| 882 | 1006 | return "<vipps-mobilepay-badge $badgeatts></vipps-mobilepay-badge>"; |
| 883 | 1007 | } |
| 884 | 1008 | |
| 885 | 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 | |
| 886 | 1011 | public function vipps_badge_shortcode($atts) { |
| 887 | 1012 | $args = shortcode_atts( array('id'=>'', 'class'=>'','variant' => '','language'=>''), $atts ); |
| 888 | 1013 | |
| 889 | 1014 | $variant = in_array($args['variant'], ['orange', 'light-orange', 'grey','white', 'purple']) ? $args['variant'] : ""; |
| 890 | - $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 | + | |
| 891 | 1020 | $id = sanitize_title($args['id']); |
| 892 | 1021 | $class = sanitize_text_field($args['class']); |
| 893 | 1022 | |
| 894 | 1023 | $attributes = []; |
| @@ -902,163 +1031,318 @@ | ||
| 902 | 1031 | |
| 903 | 1032 | return "<vipps-badge $badgeatts></vipps-badge>"; |
| 904 | 1033 | } |
| 905 | 1034 | |
| 906 | - public function get_express_logo_variants() { | |
| 1035 | + public function get_html_button_default_attrs() { | |
| 907 | 1036 | return [ |
| 908 | - 'buy-now-rectangular' => __('Buy now rectangular', 'woo-vipps'), | |
| 909 | - 'buy-now-pill' => __('Buy now pill', 'woo-vipps'), | |
| 910 | - 'express-rectangular' => __('Express rectangular', 'woo-vipps'), | |
| 911 | - 'express-pill' => __('Express pill', 'woo-vipps'), | |
| 912 | - 'express-rectangular-mini' => __('Express rectangular mini', 'woo-vipps'), | |
| 913 | - '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 | |
| 914 | 1044 | ]; |
| 915 | 1045 | } |
| 916 | 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 | + } | |
| 917 | 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 | + | |
| 918 | 1113 | public function button_menu_page() { |
| 919 | 1114 | if (!current_user_can('manage_woocommerce')) { |
| 920 | 1115 | wp_die(__('You don\'t have sufficient rights to access this page', 'woo-vipps')); |
| 921 | 1116 | } |
| 922 | - $payment_method = $this->get_payment_method_name(); | |
| 923 | - $lang = $this->get_customer_language(); | |
| 924 | - $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'); ?> | |
| 925 | 1125 | |
| 926 | - $variants = $this->get_express_logo_variants(); | |
| 927 | - $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(); ?> | |
| 928 | 1128 | |
| 929 | - $init_states = [ | |
| 930 | - 'express' => [ | |
| 931 | - 'variant' => array_key_exists(@$button_options['express']['variant'], $variants) ? $button_options['express']['variant'] : 'buy-now-rectangular', | |
| 932 | - 'mini-variant' => array_key_exists(@$button_options['express']['mini-variant'], $mini_variants) ? $button_options['express']['mini-variant'] : 'express-rectangular-mini', | |
| 933 | - 'force-mini' => [ | |
| 934 | - 'product' => @$button_options['express']['force-mini']['product'] ?? 'no', | |
| 935 | - 'catalog' => @$button_options['express']['force-mini']['catalog'] ?? 'yes', | |
| 936 | - 'cart' => @$button_options['express']['force-mini']['cart'] ?? 'no', | |
| 937 | - 'minicart' => @$button_options['express']['force-mini']['minicart'] ?? 'no', | |
| 938 | - ], | |
| 939 | - ], | |
| 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'), | |
| 940 | 1149 | ]; |
| 1150 | + $init_context = 'global'; | |
| 1151 | + $init_config = $configs[$init_context] ?? []; | |
| 941 | 1152 | |
| 1153 | + // html button args | |
| 1154 | + $init_args = $init_config; | |
| 1155 | + $init_args['id'] = 'vipps-button-express-preview'; | |
| 1156 | + | |
| 942 | 1157 | ?> |
| 943 | - <div class='wrap vipps-button-settings'> | |
| 944 | - <h1><?php echo sprintf(__('%1$s button configuration', 'woo-vipps'), Vipps::CompanyName()); ?></h1> | |
| 945 | - <span><?php echo sprintf(__('%1$s supports different variants of buttons for you to perfect your store\'s look', 'woo-vipps'), Vipps::CompanyName()); ?></span> | |
| 946 | - <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> | |
| 947 | 1160 | |
| 948 | - <!-- EXPRESS SECTION --> | |
| 949 | - <div id="vipps-button-settings-express-container"> | |
| 950 | - <h2> <?php _e('Express Checkout', 'woo-vipps'); ?></h2> | |
| 951 | - <input type="hidden" name="action" value="update_vipps_button_settings" /> | |
| 952 | - <?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 | + | |
| 953 | 1176 | |
| 954 | - <!-- variant --> | |
| 955 | - <div class="vipps-button-settings-section"> | |
| 956 | - <!-- variant dropdown --> | |
| 957 | - <div class="vipps-button-settings-express-demo-container"> | |
| 958 | - <label for="vippsButtonVariant"><?php _e('Choose variant', 'woo-vipps'); ?></label> | |
| 959 | - <select id="vippsButtonVariant" name="express[variant]" onChange='changeExpressVariant()'> | |
| 960 | - <?php foreach($variants as $key=>$name): ?> | |
| 961 | - <option value="<?php echo $key; ?>" <?php if ($init_states['express']['variant'] === $key) echo " selected "; ?> > | |
| 962 | - <?php echo $name ; ?> | |
| 963 | - </option> | |
| 964 | - <?php endforeach; ?> | |
| 965 | - </select> | |
| 966 | - </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> | |
| 967 | 1196 | |
| 968 | - <!-- Preload all variant images. Javascript will show the active one. LP 2025-12-16 --> | |
| 969 | - <div class="vipps-button-settings-express-demo-container vipps-button-settings-img-container"> | |
| 970 | - <?php foreach(array_keys($variants) as $variant): ?> | |
| 971 | - <img | |
| 972 | - class="vipps-button-settings-express-demo" | |
| 973 | - id="vipps-button-settings-express-demo-<?php echo $variant; ?>" | |
| 974 | - src="<?php echo $this->get_express_logo($payment_method, $lang, $variant); ?>" | |
| 975 | - style="display: <?php echo ($variant === $init_states['express']['variant'] ? 'block' : 'none') ;?>;" | |
| 976 | - > | |
| 977 | - <?php endforeach; ?> | |
| 978 | - </div> | |
| 979 | - </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; ?> | |
| 980 | 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> | |
| 981 | 1217 | |
| 982 | - <!-- mini variant section --> | |
| 983 | - <div class="vipps-button-settings-section"> | |
| 984 | - <!-- Checkboxes "Use mini version for x page" --> | |
| 985 | - <label><?php _e('Force mini variant in these contexts:', 'woo-vipps'); ?></label> | |
| 986 | - <div class="vipps-button-settings-express-force-mini-container"> | |
| 987 | - <label class="vipps-button-settings-express-force-mini" id="vipps-button-settings-express-force-mini-product"><?php _e('Product page', 'woo-vipps'); ?></label> | |
| 988 | - <input name="express[force-mini][product]" type="hidden" value="no"> | |
| 989 | - <input name="express[force-mini][product]" type="checkbox" value="yes" <?php if ($init_states['express']['force-mini']['product'] == "yes") echo "checked";?>> | |
| 990 | - </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> | |
| 991 | 1221 | |
| 992 | - <div class="vipps-button-settings-express-force-mini-container"> | |
| 993 | - <label class="vipps-button-settings-express-force-mini" id="vipps-button-settings-express-force-mini-catalog"><?php _e('Catalog page', 'woo-vipps'); ?></label> | |
| 994 | - <input name="express[force-mini][catalog]" type="hidden" value="no"> | |
| 995 | - <input name="express[force-mini][catalog]" type="checkbox" value="yes" <?php if ($init_states['express']['force-mini']['catalog'] == "yes") echo "checked";?>> | |
| 996 | - </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); | |
| 997 | 1225 | |
| 998 | - <div class="vipps-button-settings-express-force-mini-container"> | |
| 999 | - <label class="vipps-button-settings-express-force-mini" id="vipps-button-settings-express-force-mini-cart"><?php _e('Cart', 'woo-vipps'); ?></label> | |
| 1000 | - <input name="express[force-mini][cart]" type="hidden" value="no"> | |
| 1001 | - <input name="express[force-mini][cart]" type="checkbox" value="yes" <?php if ($init_states['express']['force-mini']['cart'] == "yes") echo "checked";?>> | |
| 1002 | - </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 | |
| 1003 | 1228 | |
| 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-minicart"><?php _e('Mini cart', 'woo-vipps'); ?></label> | |
| 1006 | - <input name="express[force-mini][minicart]" type="hidden" value="no"> | |
| 1007 | - <input name="express[force-mini][minicart]" type="checkbox" value="yes" <?php if ($init_states['express']['force-mini']['minicart'] == "yes") echo "checked";?>> | |
| 1008 | - </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; | |
| 1009 | 1233 | |
| 1010 | - <!-- mini variant dropdown --> | |
| 1011 | - <div class="vipps-button-settings-express-mini-demo-container"> | |
| 1012 | - <label for="vippsButtonMiniVariant"><?php _e('Choose variant to use in mini contexts', 'woo-vipps'); ?></label> | |
| 1013 | - <select id="vippsButtonMiniVariant" name="express[mini-variant]" onChange='changeExpressMiniVariant()'> | |
| 1014 | - <?php foreach($mini_variants as $key=>$name): ?> | |
| 1015 | - <option value="<?php echo $key; ?>" <?php if ($init_states['express']['mini-variant'] === $key) echo " selected "; ?> > | |
| 1016 | - <?php echo $name ; ?> | |
| 1017 | - </option> | |
| 1018 | - <?php endforeach; ?> | |
| 1019 | - </select> | |
| 1020 | - </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); | |
| 1021 | 1236 | |
| 1022 | - <!-- Preload mini variant imgs. LP 2025-12-17 --> | |
| 1023 | - <div class="vipps-button-settings-express-mini-demo-container vipps-button-settings-img-container"> | |
| 1024 | - <?php foreach(array_keys($mini_variants) as $variant): ?> | |
| 1025 | - <img | |
| 1026 | - class="vipps-button-settings-express-mini-demo" | |
| 1027 | - id="vipps-button-settings-express-mini-demo-<?php echo $variant; ?>" | |
| 1028 | - src="<?php echo $this->get_express_logo($payment_method, $lang, $variant); ?>" | |
| 1029 | - style="display: <?php echo ($variant === $init_states['express']['mini-variant'] ? 'block' : 'none') ;?>;" | |
| 1030 | - > | |
| 1031 | - <?php endforeach; ?> | |
| 1032 | - </div> | |
| 1033 | - </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"]; | |
| 1034 | 1240 | |
| 1035 | - <!-- END EXPRESS SECTION --> | |
| 1036 | - </div> | |
| 1241 | + jQuery('input[name="express[tmpConfig][use-global-config]"]').prop("checked", true); | |
| 1037 | 1242 | |
| 1038 | - <!-- Save button --> | |
| 1039 | - <div id="vipps-button-settings-save"> | |
| 1040 | - <input class="btn button primary" type="submit" value="<?php _e('Update settings', 'woo-vipps'); ?>" /> | |
| 1041 | - </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 | + } | |
| 1042 | 1249 | |
| 1043 | - </form> | |
| 1044 | - </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 | + }); | |
| 1045 | 1265 | |
| 1046 | - <script> | |
| 1047 | - function changeExpressVariant() { | |
| 1048 | - const variant = jQuery('#vippsButtonVariant').val().trim(); | |
| 1049 | - // Show the one selected, hide all others. LP 2025-12-16 | |
| 1050 | - jQuery('.vipps-button-settings-express-demo').hide(); | |
| 1051 | - jQuery(`#vipps-button-settings-express-demo-${variant}`).show(); | |
| 1052 | - } | |
| 1266 | + updatePreview(); | |
| 1267 | + } | |
| 1268 | + // init the starting config from option. LP 2026-06-25 | |
| 1269 | + setInputsFromConfig(currentContext, contextConfigs[currentContext]); | |
| 1053 | 1270 | |
| 1054 | - function changeExpressMiniVariant() { | |
| 1055 | - const variant = jQuery('#vippsButtonMiniVariant').val().trim(); | |
| 1056 | - // Show the one selected, hide all others. LP 2025-12-16 | |
| 1057 | - jQuery('.vipps-button-settings-express-mini-demo').hide(); | |
| 1058 | - jQuery(`#vipps-button-settings-express-mini-demo-${variant}`).show(); | |
| 1059 | - } | |
| 1060 | - </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> | |
| 1061 | 1345 | <?php |
| 1062 | 1346 | } |
| 1063 | 1347 | |
| 1064 | 1348 | |
| @@ -1473,14 +1757,27 @@ | ||
| 1473 | 1757 | } |
| 1474 | 1758 | wp_register_script('vipps-gw',plugins_url('js/vipps.js',__FILE__),array('jquery','wp-hooks'),filemtime(dirname(__FILE__) . "/js/vipps.js"), 'true'); |
| 1475 | 1759 | |
| 1476 | 1760 | // Badges - web components provided by Vipps MobilePay to display payment options in-store. |
| 1477 | - wp_register_script('vipps-onsite-messageing','https://checkout.vipps.no/on-site-messaging/v1/vipps-osm.js',array(),WOO_VIPPS_VERSION, | |
| 1478 | - 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 | + [ | |
| 1479 | 1766 | 'in_footer' => true, |
| 1480 | 1767 | 'strategy' => 'async', |
| 1481 | - )); | |
| 1768 | + ], | |
| 1769 | + ); | |
| 1482 | 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 | + ); | |
| 1483 | 1780 | } |
| 1484 | 1781 | |
| 1485 | 1782 | // Runs late in both wp_enqueue_scripts and admin_enqueue_scripts to make it more compatible with translation plugins IOK 2026-02-02 |
| 1486 | 1783 | public function script_add_vippslocale () { |
| @@ -1499,8 +1796,9 @@ | ||
| 1499 | 1796 | $this->script_add_vippslocale(); |
| 1500 | 1797 | |
| 1501 | 1798 | wp_enqueue_script('vipps-gw'); |
| 1502 | 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'); | |
| 1503 | 1801 | } |
| 1504 | 1802 | |
| 1505 | 1803 | |
| 1506 | 1804 | public function add_shortcodes() { |
| @@ -1512,8 +1810,11 @@ | ||
| 1512 | 1810 | // New vipps-mobilepay-badge shortcode. LP 19.11.2024 |
| 1513 | 1811 | add_shortcode('vipps-mobilepay-badge', array($this, 'vipps_mobilepay_badge_shortcode')); |
| 1514 | 1812 | // Legacy vipps-badge shortcode. LP 19.11.2024 |
| 1515 | 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')); | |
| 1516 | 1817 | } |
| 1517 | 1818 | |
| 1518 | 1819 | |
| 1519 | 1820 | public function log ($what,$type='info') { |
| @@ -1539,8 +1840,10 @@ | ||
| 1539 | 1840 | } |
| 1540 | 1841 | |
| 1541 | 1842 | // Show express button option on checkout form. LP 2026-03-23 |
| 1542 | 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'); | |
| 1543 | 1846 | $gw = $this->gateway(); |
| 1544 | 1847 | if (!$gw->show_express_checkout()) return; |
| 1545 | 1848 | $this->express_checkout_section_html(); |
| 1546 | 1849 | } |
| @@ -1550,9 +1853,9 @@ | ||
| 1550 | 1853 | $header_text = __('Express Checkout', 'woo-vipps'); |
| 1551 | 1854 | $header = "<legend class='express-header'>$header_text</legend>"; |
| 1552 | 1855 | $div_classes = "legacy-checkout vipps-express-checkout $payment_method"; |
| 1553 | 1856 | echo "<fieldset class='$div_classes'>$header"; |
| 1554 | - $this->cart_express_checkout_button_html(); | |
| 1857 | + $this->checkout_express_checkout_button_html(); | |
| 1555 | 1858 | echo '</fieldset>'; |
| 1556 | 1859 | } |
| 1557 | 1860 | |
| 1558 | 1861 | public function express_checkout_banner() { |
| @@ -1579,8 +1882,27 @@ | ||
| 1579 | 1882 | <div class="<?php echo $div_classes;?>"><?php echo $message;?></div> |
| 1580 | 1883 | <?php |
| 1581 | 1884 | } |
| 1582 | 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 | + | |
| 1583 | 1905 | // Show the express button if reasonable to do so |
| 1584 | 1906 | public function cart_express_checkout_button() { |
| 1585 | 1907 | $gw = $this->gateway(); |
| 1586 | 1908 | |
| @@ -1599,22 +1921,46 @@ | ||
| 1599 | 1921 | |
| 1600 | 1922 | public function cart_express_checkout_button_html($minicart = false) { |
| 1601 | 1923 | $url = $this->express_checkout_url(); |
| 1602 | 1924 | $url = wp_nonce_url($url,'express','sec'); |
| 1603 | - $page = $minicart ? 'minicart' : 'cart'; | |
| 1604 | - $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)); | |
| 1605 | 1927 | $method = $this->get_payment_method_name(); |
| 1606 | 1928 | $title = sprintf(__('Buy now with %1$s!', 'woo-vipps'), $method); |
| 1607 | - $button = "<a href='$url' class='button vipps-express-checkout short $method' title='$title'><img alt='$title' border=0 src='$imgurl'></a>"; | |
| 1608 | - $button = apply_filters('woo_vipps_cart_express_checkout_button', $button, $url); | |
| 1609 | - 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; | |
| 1610 | 1932 | } |
| 1611 | 1933 | |
| 1612 | 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 |
| 1613 | 1935 | // cached. Therefore stock, purchasability etc will be done later. IOK 2018-10-02 |
| 1614 | 1936 | public function buy_now_button_shortcode ($atts) { |
| 1615 | - $args = shortcode_atts( array( 'id' => '','variant'=>'','sku' => '',), $atts ); | |
| 1616 | - 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>"; | |
| 1617 | 1963 | } |
| 1618 | 1964 | |
| 1619 | 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 |
| 1620 | 1966 | public function express_checkout_button_shortcode() { |
| @@ -2283,77 +2629,69 @@ | ||
| 2283 | 2629 | return null; |
| 2284 | 2630 | } |
| 2285 | 2631 | } |
| 2286 | 2632 | |
| 2633 | + // Special pages, and some callbacks. IOK 2018-05-18 | |
| 2634 | + public function template_redirect() { | |
| 2287 | 2635 | |
| 2288 | - // If this is a special page, return true very early because we are handling this. IOK 2023-02-22 | |
| 2289 | - public function pre_handle_404($current, $query) { | |
| 2290 | - if (!is_admin()) { | |
| 2291 | - $special = $this->is_special_page(); | |
| 2292 | - if ($special) { | |
| 2293 | - // Ensure very early on that Autooptimize does not try to optimize us (if installed) IOK 2023-03-04 | |
| 2294 | - add_filter( 'autoptimize_filter_noptimize', '__return_true'); | |
| 2295 | - return true; | |
| 2296 | - } | |
| 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(); | |
| 2297 | 2645 | } |
| 2298 | - return $current; | |
| 2299 | - } | |
| 2300 | 2646 | |
| 2301 | - // Special pages, and some callbacks. IOK 2018-05-18 | |
| 2302 | - public function template_redirect() { | |
| 2303 | - global $post; | |
| 2304 | - // Handle special callbacks | |
| 2305 | - $special = $this->is_special_page() ; | |
| 2306 | - | |
| 2307 | - if ($special) { | |
| 2647 | + if (static::is_special_page()) { | |
| 2648 | + // Legacy: Stop the canonical redirect here. Unclear if still necessary. IOK 2026-09-11 | |
| 2308 | 2649 | remove_filter('template_redirect', 'redirect_canonical', 10); |
| 2309 | - 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 | + } | |
| 2310 | 2656 | |
| 2311 | - // Allow above hook to actually handle special pages. It should probably call $Vipps->fakepage or a redirect; can be used | |
| 2312 | - // to intercept express checkout etc. IOK 2022-03-18 | |
| 2313 | - if (! apply_filters('woo_vipps_special_page_handled', false, $special)) { | |
| 2314 | - $this->$special(); | |
| 2315 | - } | |
| 2316 | - } | |
| 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 | |
| 2317 | 2661 | |
| 2318 | - $consentremoval = $this->is_consent_removal(); | |
| 2319 | - if ($consentremoval) { | |
| 2320 | - remove_filter('template_redirect', 'redirect_canonical', 10); | |
| 2321 | - do_action('woo_vipps_before_handling_special_page', 'consentremoval'); | |
| 2322 | - if (! apply_filters('woo_vipps_special_page_handled', false, 'consentremoval')) { | |
| 2323 | - $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; | |
| 2324 | 2675 | } |
| 2325 | 2676 | } |
| 2677 | + return $title; | |
| 2326 | 2678 | } |
| 2679 | + | |
| 2327 | 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 | |
| 2328 | 2682 | public function template_include($template) { |
| 2329 | - $special = $this->is_special_page() ; | |
| 2330 | - if ($special) { | |
| 2683 | + if (static::is_special_page()) { | |
| 2331 | 2684 | // Get any special template override from the options IOK 2020-02-18 |
| 2332 | 2685 | $specific = $this->gateway()->get_option('vippsspecialpagetemplate'); |
| 2333 | 2686 | $found = locate_template($specific,false,false); |
| 2334 | 2687 | if ($found) $template=$found; |
| 2335 | 2688 | |
| 2336 | - return apply_filters('woo_vipps_special_page_template', $template, $special); | |
| 2689 | + return apply_filters('woo_vipps_special_page_template', $template, $_GET['action'] ?? ''); | |
| 2337 | 2690 | } |
| 2338 | 2691 | return $template; |
| 2339 | 2692 | } |
| 2340 | 2693 | |
| 2341 | - | |
| 2342 | - // Can't use wc-api for this, as that does not support DELETE . IOK 2018-05-18 | |
| 2343 | - private function is_consent_removal () { | |
| 2344 | - | |
| 2345 | - if ($_SERVER['REQUEST_METHOD'] != 'DELETE') return false; | |
| 2346 | - if ( !get_option('permalink_structure')) { | |
| 2347 | - if (@$_REQUEST['vipps-consent-removal']) return @$_REQUEST['callback']; | |
| 2348 | - return false; | |
| 2349 | - } | |
| 2350 | - if (preg_match("!/vipps-consent-removal/([^/]*)!", $_SERVER['REQUEST_URI'], $matches)) { | |
| 2351 | - return @$_REQUEST['callback']; | |
| 2352 | - } | |
| 2353 | - return false; | |
| 2354 | - } | |
| 2355 | - | |
| 2356 | 2694 | // On the thank you page, we have a completed order, so we need to restore any saved cart and possibly log in |
| 2357 | 2695 | // the user if using Express Checkout IOK 2020-10-09 |
| 2358 | 2696 | public function woocommerce_before_thankyou ($orderid) { |
| 2359 | 2697 | $order = wc_get_order($orderid); |
| @@ -2358,9 +2696,9 @@ | ||
| 2358 | 2696 | public function woocommerce_before_thankyou ($orderid) { |
| 2359 | 2697 | $order = wc_get_order($orderid); |
| 2360 | 2698 | if ($order) { |
| 2361 | 2699 | // Requires that this is express checkout and that 'create users on express checkout' is chosen. IOK 2020-10-09 |
| 2362 | - // -- 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 | |
| 2363 | 2701 | $this->maybe_log_in_user($order); |
| 2364 | 2702 | $order->delete_meta_data('_vipps_limited_session'); |
| 2365 | 2703 | $order->save(); |
| 2366 | 2704 | |
| @@ -2421,9 +2759,8 @@ | ||
| 2421 | 2759 | |
| 2422 | 2760 | // Support adding pickup locations to any shipping rate using the 'woo_vipps_shipping_method_pickup_points' filter |
| 2423 | 2761 | // IOK 2025-11-19 |
| 2424 | 2762 | add_filter('woo_vipps_modify_express_checkout_rate', array($this, 'express_add_pickup_location_options'), 10, 4); |
| 2425 | - | |
| 2426 | 2763 | } |
| 2427 | 2764 | |
| 2428 | 2765 | public function get_payment_method_name() { |
| 2429 | 2766 | return $this->gateway()->get_option('payment_method_name'); |
| @@ -2443,14 +2780,13 @@ | ||
| 2443 | 2780 | public function after_setup_theme() { |
| 2444 | 2781 | // To facilitate development, allow loading the plugin-supplied translations. Must be called here at the earliest. |
| 2445 | 2782 | $ok = Vipps::load_plugin_textdomain('woo-vipps', false, basename( dirname( dirname( __FILE__ ) ) ) . "/languages"); |
| 2446 | 2783 | |
| 2447 | - // 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 | |
| 2448 | 2785 | // Will also probably be used to maintain a real utility-page for Vipps actions later for themes where this |
| 2449 | 2786 | // is important. |
| 2450 | 2787 | add_filter('woocommerce_create_pages', array($this, 'woocommerce_create_pages'), 50, 1); |
| 2451 | 2788 | |
| 2452 | - | |
| 2453 | 2789 | // Callbacks use the Woo API IOK 2018-05-18 |
| 2454 | 2790 | add_action( 'woocommerce_api_wc_gateway_vipps', array($this,'vipps_callback')); |
| 2455 | 2791 | add_action( 'woocommerce_api_vipps_shipping_details', array($this,'vipps_shipping_details_callback')); |
| 2456 | 2792 | |
| @@ -2461,9 +2797,9 @@ | ||
| 2461 | 2797 | add_action( 'woocommerce_cart_actions', array($this, 'cart_express_checkout_button')); |
| 2462 | 2798 | add_action( 'woocommerce_widget_shopping_cart_buttons', array($this, 'minicart_express_checkout_button'), 30); |
| 2463 | 2799 | |
| 2464 | 2800 | // Previously we added an express html banner to the action 'woocommerce_before_checkout_form.', |
| 2465 | - // 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 | |
| 2466 | 2802 | add_action('woocommerce_checkout_before_customer_details', array($this, 'checkout_before_customer_details_express'), 5); |
| 2467 | 2803 | |
| 2468 | 2804 | add_action('woocommerce_after_add_to_cart_button', array($this, 'single_product_buy_now_button')); |
| 2469 | 2805 | add_action('woocommerce_after_shop_loop_item', array($this, 'loop_single_product_buy_now_button'), 20); |
| @@ -2468,12 +2804,10 @@ | ||
| 2468 | 2804 | add_action('woocommerce_after_add_to_cart_button', array($this, 'single_product_buy_now_button')); |
| 2469 | 2805 | add_action('woocommerce_after_shop_loop_item', array($this, 'loop_single_product_buy_now_button'), 20); |
| 2470 | 2806 | |
| 2471 | 2807 | |
| 2472 | - // Special pages and callbacks handled by template_redirect | |
| 2473 | - // 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 | |
| 2474 | 2809 | add_action('template_redirect', array($this,'template_redirect'),1); |
| 2475 | - add_action('pre_handle_404', array($this, 'pre_handle_404'), 1, 2); | |
| 2476 | 2810 | |
| 2477 | 2811 | // Allow overriding their templates |
| 2478 | 2812 | add_filter('template_include', array($this,'template_include'), 10, 1); |
| 2479 | 2813 | |
| @@ -2580,9 +2914,8 @@ | ||
| 2580 | 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()); |
| 2581 | 2915 | $this->vippsJSConfig['vippslanguage'] = $this->get_customer_language(); |
| 2582 | 2916 | $this->vippsJSConfig['vippslocale'] = get_locale(); |
| 2583 | 2917 | $this->vippsJSConfig['vippsexpressbuttonurl'] = $this->get_payment_method_name(); |
| 2584 | - $this->vippsJSConfig['logoSvgUrl'] = $this->get_payment_logo('buy-now-block'); | |
| 2585 | 2918 | |
| 2586 | 2919 | |
| 2587 | 2920 | // If the site supports Gutenberg Blocks, support the Checkout block IOK 2020-08-10 |
| 2588 | 2921 | if (class_exists('Automattic\WooCommerce\Blocks\Payments\Integrations\AbstractPaymentMethodType')) { |
| @@ -2731,9 +3064,10 @@ | ||
| 2731 | 3064 | return; |
| 2732 | 3065 | } |
| 2733 | 3066 | |
| 2734 | 3067 | $captured = intval($order->get_meta('_vipps_captured')); |
| 2735 | - $capremain = intval($order->get_meta('_vipps_capture_remaining')); | |
| 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')); | |
| 2736 | 3070 | if ($captured && (!$capremain || $capremain < 2)) { |
| 2737 | 3071 | print "<div><strong>" . sprintf(__("The entire amount has been captured at %1\$s", 'woo-vipps'), $this->get_payment_method_name()) . "</strong></div>"; |
| 2738 | 3072 | return; |
| 2739 | 3073 | } |
| @@ -2759,14 +3093,14 @@ | ||
| 2759 | 3093 | |
| 2760 | 3094 | $raw_post = @file_get_contents( 'php://input' ); |
| 2761 | 3095 | $result = @json_decode($raw_post,true); |
| 2762 | 3096 | |
| 2763 | - // 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 | |
| 2764 | 3098 | // .. and the epayment webhooks 2023-12-19 |
| 2765 | 3099 | $ischeckout = false; |
| 2766 | 3100 | $iswebhook = false; |
| 2767 | 3101 | $callback = isset($_REQUEST['callback']) ? $_REQUEST['callback'] : ""; |
| 2768 | - // 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 | |
| 2769 | 3103 | if ($callback == 'checkout') { |
| 2770 | 3104 | $ischeckout = true; |
| 2771 | 3105 | } |
| 2772 | 3106 | // For the webhooks, we will add 'webhook' to the result, but we also know that 'pspReference' will be present. IOK 2023-12-19 |
| @@ -3180,10 +3514,10 @@ | ||
| 3180 | 3514 | $this->log(sprintf(__("Wrong %1\$s Orderid on shipping details callback", 'woo-vipps'), $this->get_payment_method_name()), 'warning'); |
| 3181 | 3515 | exit(); |
| 3182 | 3516 | } |
| 3183 | 3517 | |
| 3184 | - // If we are doing this for Vipps Checkout after version 3, communicate to any shipping methods with | |
| 3185 | - // 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 | |
| 3186 | 3520 | // This needs to be done before "calculate totals". |
| 3187 | 3521 | // Moved from "vipps_shipping_details_callback_handler" because we need it before restoring sessions. IOK 2025-05-06 |
| 3188 | 3522 | $ischeckout = $order->get_meta('_vipps_checkout'); |
| 3189 | 3523 | |
| @@ -3359,9 +3693,9 @@ | ||
| 3359 | 3693 | ), 'debug'); |
| 3360 | 3694 | |
| 3361 | 3695 | } |
| 3362 | 3696 | |
| 3363 | - // 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 | |
| 3364 | 3698 | // 'dynamic pricing' 2023-01-26 |
| 3365 | 3699 | // Which may be deprecated, but anyway, for future use IOK 2025-08-14 |
| 3366 | 3700 | $taxrate = 0; |
| 3367 | 3701 | if (is_array($shipping_tax_rates) && !empty($shipping_tax_rates)) { |
| @@ -3487,9 +3821,9 @@ | ||
| 3487 | 3821 | $vippsmethod['shippingMethod'] = $rate->get_label(); |
| 3488 | 3822 | $vippsmethod['shippingMethodId'] = $key; |
| 3489 | 3823 | $vippsmethods[]=$vippsmethod; |
| 3490 | 3824 | |
| 3491 | - // Metadata and settings stored for later use for Vipps Checkout | |
| 3825 | + // Metadata and settings stored for later use for Checkout | |
| 3492 | 3826 | // and express checkout - basically, for each *key* have the corresponding object. IOK 2025-08-15 |
| 3493 | 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 |
| 3494 | 3828 | // finalize the order. IOK 2025-08-15 |
| 3495 | 3829 | $ratemap[$key]=$rate; |
| @@ -3507,9 +3841,9 @@ | ||
| 3507 | 3841 | // This then is the old Express Checkout format, which we have exposed in filters. IOK 2025-08-14 |
| 3508 | 3842 | $return = array('addressId'=>intval($addressid), 'orderId'=>$vippsorderid, 'shippingDetails'=>$vippsmethods); |
| 3509 | 3843 | $return = apply_filters('woo_vipps_vipps_formatted_shipping_methods', $return); // Mostly for debugging |
| 3510 | 3844 | |
| 3511 | - // 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. | |
| 3512 | 3846 | // IOK 2025-08-15 and new Express yet another slightly different format. |
| 3513 | 3847 | // IOK 2025-08-15 pass the ratemap as a reference, so transforms can update them |
| 3514 | 3848 | if ($ischeckout) { |
| 3515 | 3849 | $return = VippsCheckout::instance()->format_shipping_methods($return, $ratemap, $methodmap, $order); |
| @@ -3885,17 +4219,8 @@ | ||
| 3885 | 4219 | header("X-Accel-Expires: 0"); |
| 3886 | 4220 | } |
| 3887 | 4221 | |
| 3888 | 4222 | |
| 3889 | - | |
| 3890 | - // Handle DELETE on a vipps consent removal callback | |
| 3891 | - public function vipps_consent_removal_callback ($callback) { | |
| 3892 | - Vipps::nocache(); | |
| 3893 | - // Currently, no such requests will be posted, and as this code isn't sufficiently tested,we'll just have | |
| 3894 | - // to escape here when the API is changed. IOK 2020-10-14 | |
| 3895 | - $this->log("Consent removal is non-functional pending API changes as of 2020-10-14"); print "1"; exit(); | |
| 3896 | - } | |
| 3897 | - | |
| 3898 | 4223 | public function woocommerce_payment_gateways($methods) { |
| 3899 | 4224 | require_once(dirname(__FILE__) . "/WC_Gateway_Vipps.class.php"); |
| 3900 | 4225 | require_once(dirname(__FILE__) . "/WC_Gateway_VippsCard.class.php"); |
| 3901 | 4226 | // Protect the singleton: Use the object instead of the class name IOK 2025-02-04 |
| @@ -3970,9 +4295,9 @@ | ||
| 3970 | 4295 | if ($currentstatus != 'initiated') { |
| 3971 | 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'); |
| 3972 | 4297 | return; |
| 3973 | 4298 | } |
| 3974 | - $this->check_status_of_pending_order($o, false, false); | |
| 4299 | + $this->check_status_of_pending_order($o, false); | |
| 3975 | 4300 | } |
| 3976 | 4301 | } |
| 3977 | 4302 | |
| 3978 | 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 - |
| @@ -3977,30 +4302,35 @@ | ||
| 3977 | 4302 | |
| 3978 | 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 - |
| 3979 | 4304 | // e.g. wp-cron. IOK 2021-06-21 |
| 3980 | 4305 | // Stop restoring session in wp-cron too. IOK 2021-08-23 |
| 3981 | - public function check_status_of_pending_order($order, $maybe_restore_session=0, $allow_retry=true) { | |
| 3982 | - $express = $order->get_meta('_vipps_express_checkout'); | |
| 3983 | - $vippstatus = $order->get_meta('_vipps_status'); | |
| 3984 | - if ($express && $maybe_restore_session) { | |
| 3985 | - $this->log(sprintf(__("Restoring session of order %1\$d", 'woo-vipps'), $order->get_id()), 'debug'); | |
| 3986 | - $this->callback_restore_session($order->get_id()); | |
| 3987 | - } | |
| 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) { | |
| 3988 | 4308 | $gw = $this->gateway(); |
| 3989 | 4309 | |
| 3990 | 4310 | $order_status = null; |
| 3991 | 4311 | try { |
| 3992 | 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())); |
| 3993 | - $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 | + | |
| 3994 | 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'); |
| 3995 | 4329 | } catch (Exception $e) { |
| 3996 | 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'); |
| 3997 | 4331 | $this->log($e->getMessage() . "\n" . $order->get_id(), 'error'); |
| 3998 | 4332 | } |
| 3999 | - // Ensure we don't keep using an old session for more than one order here. | |
| 4000 | - if ($express && $maybe_restore_session) { | |
| 4001 | - $this->callback_destroy_session(); | |
| 4002 | - } | |
| 4003 | 4333 | return $order_status; |
| 4004 | 4334 | } |
| 4005 | 4335 | |
| 4006 | 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 |
| @@ -4013,20 +4343,40 @@ | ||
| 4013 | 4343 | } |
| 4014 | 4344 | } |
| 4015 | 4345 | |
| 4016 | 4346 | public function activate () { |
| 4017 | - static::maybe_add_cron_event(); | |
| 4018 | - $gw = $this->gateway(); | |
| 4347 | + static::maybe_add_cron_event(); | |
| 4348 | + $gw = $this->gateway(); | |
| 4019 | 4349 | |
| 4020 | - // If store is using the default "Woo" orderprefix, generate a new one, this time using the stores' sitename if possible. IOK 2020-05-19 | |
| 4021 | - if ($gw->get_option('orderprefix') == 'Woo') { | |
| 4022 | - $gw->update_option('orderprefix', $this->generate_order_prefix()); | |
| 4023 | - } | |
| 4024 | - // IOK 2023-12-20 for the epayment api, we need to re-initialize webhooks at this point. | |
| 4025 | - $gw->initialize_webhooks(); | |
| 4026 | - $this->payment_method_name = $gw->get_option('payment_method_name'); | |
| 4027 | - } | |
| 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'); | |
| 4028 | 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 | + | |
| 4029 | 4379 | // We have added some hooks to wp-cron; remove these. IOK 2020-04-01 |
| 4030 | 4380 | public static function deactivate() { |
| 4031 | 4381 | $timestamp = wp_next_scheduled('vipps_cron_cleanup_hook'); |
| 4032 | 4382 | wp_unschedule_event($timestamp, 'vipps_cron_cleanup_hook'); |
| @@ -4039,9 +4389,9 @@ | ||
| 4039 | 4389 | // Delete all settings if checked in settings menu. LP 2025-10-06 |
| 4040 | 4390 | $should_delete = $gw->get_option( 'delete_settings_on_deactivation' ) === 'yes'; |
| 4041 | 4391 | if ($should_delete) { |
| 4042 | 4392 | // Delete options. |
| 4043 | - $options = ['woocommerce_vipps_settings', 'woocommerce_vipps_card_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']; | |
| 4044 | 4394 | foreach($options as $option) { |
| 4045 | 4395 | delete_option($option); |
| 4046 | 4396 | } |
| 4047 | 4397 | } |
| @@ -4079,9 +4429,10 @@ | ||
| 4079 | 4429 | // If setting is true, use Vipps as default payment. Called by the woocommrece_cart_updated hook. IOK 2018-06-06 |
| 4080 | 4430 | private function maybe_set_vipps_as_default() { |
| 4081 | 4431 | if (WC()->session->get('chosen_payment_method')) return; // User has already chosen payment method, so we're done. |
| 4082 | 4432 | $gw = $this->gateway(); |
| 4083 | - 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')) { | |
| 4084 | 4435 | WC()->session->set('chosen_payment_method', $gw->id); |
| 4085 | 4436 | } |
| 4086 | 4437 | } |
| 4087 | 4438 | |
| @@ -4186,13 +4537,13 @@ | ||
| 4186 | 4537 | if (is_user_logged_in()) return; |
| 4187 | 4538 | if (!$order || ! self::is_vipps_order($order)) return; |
| 4188 | 4539 | |
| 4189 | 4540 | // We *do* want to log in express checkout customers, but not those that |
| 4190 | - // use the Vipps Checkout solution - those can change their emails in the | |
| 4541 | + // use the Checkout solution - those can change their emails in the | |
| 4191 | 4542 | // checkout screen. IOK 2021-09-03 |
| 4192 | 4543 | $do_login = $order->get_meta('_vipps_express_checkout'); |
| 4193 | 4544 | |
| 4194 | - // 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 | |
| 4195 | 4546 | if ($order->get_meta('_vipps_checkout') && 'yes' != $this->gateway()->get_option('checkoutcreateuser')) { |
| 4196 | 4547 | $do_login = false; |
| 4197 | 4548 | } |
| 4198 | 4549 | |
| @@ -4227,9 +4578,9 @@ | ||
| 4227 | 4578 | |
| 4228 | 4579 | // Both Checkout and Express Checkout have the below value set to true |
| 4229 | 4580 | if (!$order->get_meta('_vipps_express_checkout')) return; |
| 4230 | 4581 | |
| 4231 | - // 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 | |
| 4232 | 4583 | // IOK 2023-07-27 |
| 4233 | 4584 | $ischeckout = $order->get_meta('_vipps_checkout'); |
| 4234 | 4585 | if ($ischeckout) { |
| 4235 | 4586 | if ($this->gateway()->get_option('checkoutcreateuser') != 'yes') return null; |
| @@ -4614,9 +4965,9 @@ | ||
| 4614 | 4965 | $ok = wc()->shipping->register_shipping_method( new Automattic\WooCommerce\Blocks\Shipping\PickupLocation() ); |
| 4615 | 4966 | } |
| 4616 | 4967 | } |
| 4617 | 4968 | |
| 4618 | - // 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 | |
| 4619 | 4970 | // Must be called *early*. IOK 2025-05-08. Called in callback methods, and if using static shipping, in the 'start session' callback. |
| 4620 | 4971 | public function load_extra_shipping_methods($order, $addressdata, $ischeckout=false) { |
| 4621 | 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 |
| 4622 | 4973 | add_action('woocommerce_load_shipping_methods', function () use ($order, $addressdata) { |
| @@ -4687,46 +5038,37 @@ | ||
| 4687 | 5038 | wp_send_json(array('status'=>'error', 'msg'=> __('Unknown payment status','woo-vipps') . ' ' . $payment)); |
| 4688 | 5039 | return false; |
| 4689 | 5040 | } |
| 4690 | 5041 | |
| 4691 | - // The various return URLs for special pages of the Vipps stuff depend on settings and pretty-URLs so we supply them from here | |
| 4692 | - // These are for the "fallback URL" mostly. IOK 2018-05-18 | |
| 4693 | - private function make_vipps_url($what) { | |
| 4694 | - if ( !get_option('permalink_structure')) { | |
| 4695 | - return add_query_arg('VippsSpecialPage', $what, home_url("/", 'https')); | |
| 4696 | - } | |
| 4697 | - 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()); | |
| 4698 | 5045 | } |
| 5046 | + | |
| 4699 | 5047 | public function payment_return_url() { |
| 4700 | - 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')); | |
| 4701 | 5049 | } |
| 4702 | 5050 | public function express_checkout_url() { |
| 4703 | - return $this->make_vipps_url('vipps-express-checkout'); | |
| 5051 | + return $this->make_special_page_url('do_express_checkout'); | |
| 4704 | 5052 | } |
| 4705 | 5053 | public function buy_product_url() { |
| 4706 | - return $this->make_vipps_url('vipps-buy-product'); | |
| 5054 | + return $this->make_special_page_url('buy_product'); | |
| 4707 | 5055 | } |
| 4708 | 5056 | |
| 4709 | - // Return the method in the Vipps | |
| 4710 | - public function is_special_page() { | |
| 4711 | - $specials = array('vipps-betaling' => 'vipps_wait_for_payment', 'vipps-express-checkout'=>'vipps_express_checkout', 'vipps-buy-product'=>'vipps_buy_product'); | |
| 4712 | - $method = null; | |
| 4713 | - if ( get_option('permalink_structure')) { | |
| 4714 | - foreach($specials as $special=>$specialmethod) { | |
| 4715 | - // IOK 2018-06-07 Change to add any prefix from home-url for better matching IOK 2018-06-07 | |
| 4716 | - $path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); | |
| 4717 | - if ($path && preg_match("!/$special/?$!", $path, $matches)) { | |
| 4718 | - $method = $specialmethod; break; | |
| 4719 | - } | |
| 4720 | - } | |
| 4721 | - } else { | |
| 4722 | - if (isset($_GET['VippsSpecialPage'])) { | |
| 4723 | - $method = @$specials[$_GET['VippsSpecialPage']]; | |
| 4724 | - } | |
| 4725 | - } | |
| 4726 | - return $method; | |
| 5057 | + public static function is_special_page() { | |
| 5058 | + $id = static::get_special_page_id(); | |
| 5059 | + return $id && is_page($id); | |
| 4727 | 5060 | } |
| 4728 | 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 | + | |
| 4729 | 5071 | // Just create a spinner and a overlay. |
| 4730 | 5072 | public function spinner () { |
| 4731 | 5073 | $flavour = sanitize_title($this->get_payment_method_name()); |
| 4732 | 5074 | ob_start(); |
| @@ -4747,164 +5089,22 @@ | ||
| 4747 | 5089 | return apply_filters('woo_vipps_spinner', ob_get_clean()); |
| 4748 | 5090 | } |
| 4749 | 5091 | |
| 4750 | 5092 | |
| 4751 | - // Returns express logo images depending on parameters, these are the new express svgs received 2025-12-12. | |
| 4752 | - // Fallbacks to defaults for each payment method. LP 2025-12-15 | |
| 4753 | - public function get_express_logo($payment_method, $lang, $variant) { | |
| 4754 | - $base = plugins_url('img', __FILE__); | |
| 4755 | - | |
| 4756 | - // 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 | |
| 4757 | - $img_map = [ | |
| 4758 | - "vipps" => [ | |
| 4759 | - "default" => "$base/vipps/express/en/buy-now-vipps-en-rectangular.svg", | |
| 4760 | - "default-mini" => "$base/vipps/express/en/express-vipps-en-rectangular-mini.svg", | |
| 4761 | - "en" => [ | |
| 4762 | - "default" => "$base/vipps/express/en/buy-now-vipps-en-rectangular.svg", | |
| 4763 | - "default-mini" => "$base/vipps/express/en/express-vipps-en-rectangular-mini.svg", | |
| 4764 | - "buy-now-rectangular" => "$base/vipps/express/en/buy-now-vipps-en-rectangular.svg", | |
| 4765 | - "buy-now-pill" => "$base/vipps/express/en/buy-now-vipps-en-pill.svg", | |
| 4766 | - "express-rectangular" => "$base/vipps/express/en/express-vipps-en-rectangular.svg", | |
| 4767 | - "express-rectangular-mini" => "$base/vipps/express/en/express-vipps-en-rectangular-mini.svg", | |
| 4768 | - "express-pill" => "$base/vipps/express/en/express-vipps-en-pill.svg", | |
| 4769 | - "express-pill-mini" => "$base/vipps/express/en/express-vipps-en-pill-mini.svg", | |
| 4770 | - | |
| 4771 | - ], | |
| 4772 | - "no" => [ | |
| 4773 | - "default" => "$base/vipps/express/no/kjop-na-vipps-no-rectangular.svg", | |
| 4774 | - "default-mini" => "$base/vipps/express/no/ekspress-vipps-no-rectangular-mini.svg", | |
| 4775 | - "buy-now-rectangular" => "$base/vipps/express/no/kjop-na-vipps-no-rectangular.svg", | |
| 4776 | - "buy-now-pill" => "$base/vipps/express/no/kjop-na-vipps-no-pill.svg", | |
| 4777 | - "express-rectangular" => "$base/vipps/express/no/ekspress-vipps-no-rectangular.svg", | |
| 4778 | - "express-rectangular-mini" => "$base/vipps/express/no/ekspress-vipps-no-rectangular-mini.svg", | |
| 4779 | - "express-pill" => "$base/vipps/express/no/ekspress-vipps-no-pill.svg", | |
| 4780 | - "express-pill-mini" => "$base/vipps/express/no/ekspress-vipps-no-pill-mini.svg", | |
| 4781 | - ], | |
| 4782 | - "se" => [ | |
| 4783 | - "default" => "$base/vipps/express/se/kop-nu-vipps-se-rectangular.svg", | |
| 4784 | - "default-mini" => "$base/vipps/express/se/express-vipps-se-rectangular-mini.svg", | |
| 4785 | - "buy-now-rectangular" => "$base/vipps/express/se/kop-nu-vipps-se-rectangular.svg", | |
| 4786 | - "buy-now-pill" => "$base/vipps/express/se/kop-nu-vipps-se-pill.svg", | |
| 4787 | - "express-rectangular" => "$base/vipps/express/se/express-vipps-se-rectangular.svg", | |
| 4788 | - "express-rectangular-mini" => "$base/vipps/express/se/express-vipps-se-rectangular-mini.svg", | |
| 4789 | - "express-pill" => "$base/vipps/express/se/express-vipps-se-pill.svg", | |
| 4790 | - "express-pill-mini" => "$base/vipps/express/se/express-vipps-se-pill-mini.svg", | |
| 4791 | - | |
| 4792 | - ], | |
| 4793 | - ], | |
| 4794 | - "mobilepay" => [ | |
| 4795 | - "default" => "$base/mobilepay/express/en/buy-now-mp-en-rectangular.svg", | |
| 4796 | - "default-mini" => "$base/mobilepay/express/en/express-mp-en-rectangular-mini.svg", | |
| 4797 | - "en" => [ | |
| 4798 | - "default" => "$base/mobilepay/express/en/buy-now-mp-en-rectangular.svg", | |
| 4799 | - "default-mini" => "$base/mobilepay/express/en/express-mp-en-rectangular-mini.svg", | |
| 4800 | - "buy-now-rectangular" => "$base/mobilepay/express/en/buy-now-mp-en-rectangular.svg", | |
| 4801 | - "buy-now-pill" => "$base/mobilepay/express/en/buy-now-mp-en-pill.svg", | |
| 4802 | - "express-rectangular" => "$base/mobilepay/express/en/express-mp-en-rectangular.svg", | |
| 4803 | - "express-rectangular-mini" => "$base/mobilepay/express/en/express-mp-en-rectangular-mini.svg", | |
| 4804 | - "express-pill" => "$base/mobilepay/express/en/express-mp-en-pill.svg", | |
| 4805 | - "express-pill-mini" => "$base/mobilepay/express/en/express-mp-en-pill-mini.svg", | |
| 4806 | - | |
| 4807 | - ], | |
| 4808 | - "dk" => [ | |
| 4809 | - "default" => "$base/mobilepay/express/dk/kob-nu-mp-dk-rectangular.svg", | |
| 4810 | - "default-mini" => "$base/mobilepay/express/dk/express-mp-dk-rectangular-mini.svg", | |
| 4811 | - "buy-now-rectangular" => "$base/mobilepay/express/dk/kob-nu-mp-dk-rectangular.svg", | |
| 4812 | - "buy-now-pill" => "$base/mobilepay/express/dk/kob-nu-mp-dk-pill.svg", | |
| 4813 | - "express-rectangular" => "$base/mobilepay/express/dk/express-mp-dk-rectangular.svg", | |
| 4814 | - "express-rectangular-mini" => "$base/mobilepay/express/dk/express-mp-dk-rectangular-mini.svg", | |
| 4815 | - "express-pill" => "$base/mobilepay/express/dk/express-mp-dk-pill.svg", | |
| 4816 | - "express-pill-mini" => "$base/mobilepay/express/dk/express-mp-dk-pill-mini.svg", | |
| 4817 | - ], | |
| 4818 | - "fi" => [ | |
| 4819 | - "default" => "$base/mobilepay/express/fi/osta-nyt-mp-fi-rectangular.svg", | |
| 4820 | - "default-mini" => "$base/mobilepay/express/fi/express-mp-fi-rectangular-mini.svg", | |
| 4821 | - "buy-now-rectangular" => "$base/mobilepay/express/fi/osta-nyt-mp-fi-rectangular.svg", | |
| 4822 | - "buy-now-pill" => "$base/mobilepay/express/fi/osta-nyt-mp-fi-pill.svg", | |
| 4823 | - "express-rectangular" => "$base/mobilepay/express/fi/express-mp-fi-rectangular.svg", | |
| 4824 | - "express-rectangular-mini" => "$base/mobilepay/express/fi/express-mp-fi-rectangular-mini.svg", | |
| 4825 | - "express-pill" => "$base/mobilepay/express/fi/express-mp-fi-pill.svg", | |
| 4826 | - "express-pill-mini" => "$base/mobilepay/express/fi/express-mp-fi-pill-mini.svg", | |
| 4827 | - | |
| 4828 | - ], | |
| 4829 | - ], | |
| 4830 | - | |
| 4831 | - ]; | |
| 4832 | - | |
| 4833 | - $payment = strtolower($payment_method); | |
| 4834 | - if ($lang === 'store') $lang = $this->get_customer_language(); | |
| 4835 | - | |
| 4836 | - // Dont give a default if payment method not found. LP 2025-12-12 | |
| 4837 | - if (!array_key_exists($payment, $img_map)) { | |
| 4838 | - return null; | |
| 4839 | - } | |
| 4840 | - $payment_map = $img_map[$payment]; | |
| 4841 | - | |
| 4842 | - $img = null; | |
| 4843 | - if (array_key_exists($lang, $payment_map) | |
| 4844 | - && is_array($payment_map[$lang]) | |
| 4845 | - && array_key_exists($variant, $payment_map[$lang])) { | |
| 4846 | - $img = @$payment_map[$lang][$variant]; | |
| 4847 | - } | |
| 4848 | - | |
| 4849 | - // Default fallback behaviour | |
| 4850 | - if (!$img) { | |
| 4851 | - $default = str_ends_with($variant, '-mini') ? 'default-mini' : 'default'; | |
| 4852 | - | |
| 4853 | - // First try getting default for payment method + language. LP 2026-01-16 | |
| 4854 | - if (array_key_exists($lang, $payment_map) && is_array($payment_map[$lang])) { | |
| 4855 | - /* translators: %1= payment method name, %2 = language string, %3 = variant name */ | |
| 4856 | - $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'); | |
| 4857 | - $img = @$payment_map[$lang][$default]; | |
| 4858 | - } | |
| 4859 | - | |
| 4860 | - // If not found, then try global default for payment method. LP 2026-01-16 | |
| 4861 | - if (!$img) { | |
| 4862 | - $img = @$payment_map[$default]; | |
| 4863 | - } | |
| 4864 | - | |
| 4865 | - // Found no logo at all, log this. LP 2026-01-16 | |
| 4866 | - if (!$img) { | |
| 4867 | - /* translators: %1= payment method name, %2 = language string, %3 = variant name */ | |
| 4868 | - $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'); | |
| 4869 | - } | |
| 4870 | - } | |
| 4871 | - 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); | |
| 4872 | 5097 | } |
| 4873 | 5098 | |
| 5099 | + // DEPRECATED: Legacy function as of using new web component buttons. LP 2026-06-26 | |
| 4874 | 5100 | // Get payment logo based on payment method, then language NT 2023-11-30 |
| 4875 | - // and based on custom variant setting. $page is the page origin slug, e.g 'cart', 'product'. LP 2025-12-15 | |
| 4876 | - public function get_payment_logo($page = null) { | |
| 4877 | - $lang = $this->get_customer_language(); | |
| 4878 | - $payment_method = $this->get_payment_method_name(); | |
| 4879 | - $variant = $this->get_express_logo_page_variant($page); | |
| 4880 | - $logo_url = $this->get_express_logo($payment_method, $lang, $variant); | |
| 4881 | - 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); | |
| 4882 | 5105 | } |
| 4883 | 5106 | |
| 4884 | - /** Returns the correct variant to use for the given page, found from the wp option. LP 2025-12-23 */ | |
| 4885 | - private function get_express_logo_page_variant($page = null) { | |
| 4886 | - $options = get_option('vipps_button_options'); | |
| 4887 | - | |
| 4888 | - // Init defaults, use mini version by default in below pages. LP 2025-12-17 | |
| 4889 | - $use_mini = in_array($page, ['catalog']); | |
| 4890 | - $variant = ""; | |
| 4891 | - | |
| 4892 | - // Find correct variant from button settings. LP 2025-12-17 | |
| 4893 | - if (is_array($options) && array_key_exists('express', $options)) { | |
| 4894 | - if (array_key_exists($page, $options['express']['force-mini'])) { | |
| 4895 | - $use_mini = sanitize_title($options['express']['force-mini'][$page]) === 'yes'; | |
| 4896 | - } | |
| 4897 | - $key = $use_mini ? 'mini-variant' : 'variant'; | |
| 4898 | - $variant = sanitize_title($options['express'][$key]) ?? ''; | |
| 4899 | - } | |
| 4900 | - | |
| 4901 | - if (!$variant) { | |
| 4902 | - $variant = $use_mini ? "default-mini" : "default"; | |
| 4903 | - } | |
| 4904 | - return apply_filters('woo_vipps_express_button_page_variant', $variant, $page); | |
| 4905 | - } | |
| 4906 | - | |
| 4907 | 5107 | // Get express banner logo based on payment method. LP 2025-09-03 |
| 4908 | 5108 | private function get_express_banner_logo() { |
| 4909 | 5109 | $payment_method = $this->get_payment_method_name(); |
| 4910 | 5110 | |
| @@ -4915,10 +5115,12 @@ | ||
| 4915 | 5115 | } |
| 4916 | 5116 | return null; |
| 4917 | 5117 | } |
| 4918 | 5118 | |
| 4919 | - // Get buy now button by manually selecting logo variant and language. LP 2026-01-16 | |
| 4920 | - 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 = []) { | |
| 4921 | 5123 | $disabled = $disabled ? 'disabled' : ''; |
| 4922 | 5124 | $data = array(); |
| 4923 | 5125 | |
| 4924 | 5126 | // Support directly using the variant id as $product_id with no $variation_id. LP 2026-01-23 |
| @@ -4933,9 +5135,8 @@ | ||
| 4933 | 5135 | if ($sku) $data['product_sku'] = $sku; |
| 4934 | 5136 | if ($product_id) $data['product_id'] = $product_id; |
| 4935 | 5137 | if ($variation_id) $data['variation_id'] = $variation_id; |
| 4936 | 5138 | |
| 4937 | - | |
| 4938 | 5139 | $buttoncode = "<a href='javascript:void(0)' $disabled "; |
| 4939 | 5140 | foreach($data as $key=>$value) { |
| 4940 | 5141 | $value = esc_attr($value); |
| 4941 | 5142 | $buttoncode .= " data-$key='$value' "; |
| @@ -4942,14 +5143,18 @@ | ||
| 4942 | 5143 | } |
| 4943 | 5144 | |
| 4944 | 5145 | $payment_method = $this->get_payment_method_name(); |
| 4945 | 5146 | $title = sprintf(__('Buy now with %1$s', 'woo-vipps'), $payment_method); |
| 4946 | - $short = str_ends_with($logo_variant, 'mini'); | |
| 4947 | - $logo = $this->get_express_logo($payment_method, $logo_lang, $logo_variant); | |
| 4948 | 5147 | |
| 4949 | - $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); | |
| 4950 | 5155 | |
| 4951 | -# Extra classes, if passed IOK 2019-02-26 | |
| 5156 | + # Extra classes, if passed IOK 2019-02-26 | |
| 4952 | 5157 | if (is_array($classes)) { |
| 4953 | 5158 | $classes = join(" ", $classes); |
| 4954 | 5159 | } |
| 4955 | 5160 | if ($classes) $classes = " $classes"; |
| @@ -4954,19 +5159,15 @@ | ||
| 4954 | 5159 | } |
| 4955 | 5160 | if ($classes) $classes = " $classes"; |
| 4956 | 5161 | if ($short) $classes = "short $classes"; |
| 4957 | 5162 | |
| 4958 | - $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 | + | |
| 4959 | 5167 | return apply_filters('woo_vipps_buy_now_button', $buttoncode, $product_id, $variation_id, $sku, $disabled); |
| 4960 | 5168 | } |
| 4961 | 5169 | |
| 4962 | - // Code that will generate various versions of the 'buy now with Vipps' button IOK 2018-09-27 | |
| 4963 | - public function get_buy_now_button($product_id,$variation_id=null,$sku=null,$disabled=false, $classes='', $page=null) { | |
| 4964 | - $logo_lang = $this->get_customer_language(); | |
| 4965 | - $logo_variant = $this->get_express_logo_page_variant($page); | |
| 4966 | - return $this->get_buy_now_button_manual($product_id, $variation_id, $sku, $disabled, $classes, $logo_variant, $logo_lang); | |
| 4967 | - } | |
| 4968 | - | |
| 4969 | 5170 | // Display a 'buy now with express checkout' button on the product page IOK 2018-09-27 |
| 4970 | 5171 | public function single_product_buy_now_button () { |
| 4971 | 5172 | $gw = $this->gateway(); |
| 4972 | 5173 | $how = $gw->get_option('singleproductexpress'); |
| @@ -5047,43 +5248,90 @@ | ||
| 5047 | 5248 | } |
| 5048 | 5249 | |
| 5049 | 5250 | |
| 5050 | 5251 | |
| 5051 | - // 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 | |
| 5052 | 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 | |
| 5053 | 5255 | public function woocommerce_create_pages ($data) { |
| 5256 | + // Vipps Checkout page | |
| 5054 | 5257 | $vipps_checkout_activated = get_option('woo_vipps_checkout_activated', false); |
| 5055 | - 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 | + } | |
| 5056 | 5265 | |
| 5057 | - $data['vipps_checkout'] = array( | |
| 5058 | - 'name' => _x( 'vipps_checkout', 'Page slug', 'woo-vipps' ), | |
| 5059 | - 'title' => _x( 'Vipps MobilePay Checkout', 'Page title', 'woo-vipps' ), | |
| 5060 | - 'content' => '<!-- wp:shortcode -->[' . 'vipps_checkout' . ']<!-- /wp:shortcode -->', | |
| 5061 | - ); | |
| 5062 | - | |
| 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 | + ]; | |
| 5063 | 5273 | return $data; |
| 5064 | 5274 | } |
| 5065 | 5275 | |
| 5066 | - // 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 | |
| 5067 | 5278 | public function maybe_create_vipps_pages () { |
| 5279 | + $make_pages = false; | |
| 5280 | + | |
| 5281 | + // Vipps Checkout page. LP 2026-08-18 | |
| 5068 | 5282 | $checkoutid = wc_get_page_id('vipps_checkout'); |
| 5069 | - $makeit = !$checkoutid || ! get_post_status($checkoutid); | |
| 5070 | - if ($makeit) { | |
| 5283 | + if (!$checkoutid || ! get_post_status($checkoutid)) { | |
| 5071 | 5284 | delete_option('woocommerce_vipps_checkout_page_id'); |
| 5285 | + $make_pages = true; | |
| 5072 | 5286 | } |
| 5073 | 5287 | |
| 5074 | - if ($makeit) { | |
| 5075 | - 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; | |
| 5076 | 5293 | } |
| 5294 | + | |
| 5295 | + if ($make_pages) { | |
| 5296 | + WC_Install::create_pages(); | |
| 5297 | + } | |
| 5077 | 5298 | } |
| 5078 | 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 | |
| 5079 | 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 | + | |
| 5080 | 5330 | // This URL will when accessed add a product to the cart and go directly to the express checkout page. |
| 5081 | 5331 | // The argument passed must be a shareable link created for a given product - so this in effect acts as a landing page for |
| 5082 | 5332 | // the buying thru Vipps Express Checkout of a single product linked to in for instance banners. IOK 2018-09-24 |
| 5083 | 5333 | public function vipps_buy_product() { |
| 5084 | - status_header(200,'OK'); | |
| 5085 | - Vipps::nocache(); | |
| 5086 | 5334 | |
| 5087 | 5335 | add_filter('body_class', function ($classes) { |
| 5088 | 5336 | $classes[] = 'vipps-express-checkout'; |
| 5089 | 5337 | $classes[] = 'woocommerce-checkout'; // Required by Pixel Your Site IOK 2022-11-24 |
| @@ -5119,9 +5367,9 @@ | ||
| 5119 | 5367 | |
| 5120 | 5368 | if (!$productinfo) { |
| 5121 | 5369 | $title = __("Product is no longer available",'woo-vipps'); |
| 5122 | 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'); |
| 5123 | - return $this->fakepage($title,$content); | |
| 5371 | + return $this->special_page_html($title,$content); | |
| 5124 | 5372 | } |
| 5125 | 5373 | |
| 5126 | 5374 | // Pass the productinfo to the express checkout form |
| 5127 | 5375 | $args = array(); |
| @@ -5138,15 +5386,13 @@ | ||
| 5138 | 5386 | } |
| 5139 | 5387 | $args[sanitize_title(wp_unslash($key))] = sanitize_text_field(wp_unslash($value)); |
| 5140 | 5388 | } |
| 5141 | 5389 | |
| 5142 | - $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); | |
| 5143 | 5391 | } |
| 5144 | 5392 | |
| 5145 | 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. |
| 5146 | 5394 | public function vipps_express_checkout() { |
| 5147 | - status_header(200,'OK'); | |
| 5148 | - Vipps::nocache(); | |
| 5149 | 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. |
| 5150 | 5396 | // IOK 2018-05-28 |
| 5151 | 5397 | $ok = isset($_REQUEST['sec']) && wp_verify_nonce($_REQUEST['sec'],'express'); |
| 5152 | 5398 | |
| @@ -5173,9 +5419,9 @@ | ||
| 5173 | 5419 | }); |
| 5174 | 5420 | |
| 5175 | 5421 | do_action('woo_vipps_express_checkout_page'); |
| 5176 | 5422 | |
| 5177 | - $this->print_express_checkout_page(true, 'do_express_checkout'); | |
| 5423 | + return $this->express_checkout_page_html(true, 'do_express_checkout'); | |
| 5178 | 5424 | } |
| 5179 | 5425 | |
| 5180 | 5426 | // This method tries to ensure that a customer does not 'lose' the return page and |
| 5181 | 5427 | // starts ordering the same products twice. IOK 2020-01-22 |
| @@ -5270,9 +5516,10 @@ | ||
| 5270 | 5516 | return $orderspec; |
| 5271 | 5517 | } |
| 5272 | 5518 | |
| 5273 | 5519 | // Used as a landing page for launching express checkout - borh for the cart and for single products. IOK 2018-09-28 |
| 5274 | - 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) { | |
| 5275 | 5522 | $gw = $this->gateway(); |
| 5276 | 5523 | |
| 5277 | 5524 | $expressCheckoutMessages = array(); |
| 5278 | 5525 | $expressCheckoutMessages['termsAndConditionsError'] = __( 'Please read and accept the terms and conditions to proceed with your order.', 'woocommerce' ); |
| @@ -5283,11 +5530,12 @@ | ||
| 5283 | 5530 | wp_localize_script('vipps-express-checkout', 'VippsCheckoutMessages', $expressCheckoutMessages); |
| 5284 | 5531 | wp_enqueue_script('vipps-express-checkout'); |
| 5285 | 5532 | // If we have a valid nonce when we get here, just call the 'create order' bit at once. Otherwise, make a button |
| 5286 | 5533 | // to actually perform the express checkout. |
| 5287 | - $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()); | |
| 5288 | 5535 | |
| 5289 | 5536 | |
| 5537 | + | |
| 5290 | 5538 | $orderspec = $this->get_orderspec_from_arguments($productinfo); |
| 5291 | 5539 | if (empty($orderspec)) { |
| 5292 | 5540 | $orderspec = $this->get_orderspec_from_cart(); |
| 5293 | 5541 | } |
| @@ -5354,10 +5602,9 @@ | ||
| 5354 | 5602 | |
| 5355 | 5603 | if ($execute) { |
| 5356 | 5604 | $content .= "<p id=waiting>" . __("Please wait while we are preparing your order", 'woo-vipps') . "</p>"; |
| 5357 | 5605 | $content .= "<div id='vipps-status-message'></div>"; |
| 5358 | - $this->fakepage(__('Order in progress','woo-vipps'), $content); | |
| 5359 | - return; | |
| 5606 | + return $this->special_page_html('', $content); | |
| 5360 | 5607 | } else { |
| 5361 | 5608 | $content .= $askForConfirmationHTML; |
| 5362 | 5609 | $content .= $extraHTML; |
| 5363 | 5610 | $content .= $termsHTML; |
| @@ -5362,12 +5609,11 @@ | ||
| 5362 | 5609 | $content .= $extraHTML; |
| 5363 | 5610 | $content .= $termsHTML; |
| 5364 | 5611 | $content .= apply_filters('woo_vipps_express_checkout_validation_elements', ''); |
| 5365 | 5612 | $title = sprintf(__('Buy now with %1$s!', 'woo-vipps'), $this->get_payment_method_name()); |
| 5366 | - $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>"; | |
| 5367 | 5614 | $content .= "<div id='vipps-status-message'></div>"; |
| 5368 | - $this->fakepage(sprintf(__('%1$s Express Checkout','woo-vipps'), $this->get_payment_method_name()), $content); | |
| 5369 | - return; | |
| 5615 | + return $this->special_page_html('', $content); | |
| 5370 | 5616 | } |
| 5371 | 5617 | } |
| 5372 | 5618 | |
| 5373 | 5619 | |
| @@ -5372,11 +5618,8 @@ | ||
| 5372 | 5618 | |
| 5373 | 5619 | |
| 5374 | 5620 | |
| 5375 | 5621 | public function vipps_wait_for_payment() { |
| 5376 | - status_header(200,'OK'); | |
| 5377 | - Vipps::nocache(); | |
| 5378 | - | |
| 5379 | 5622 | $orderid = WC()->session->get('_vipps_pending_order'); |
| 5380 | 5623 | |
| 5381 | 5624 | $order = null; |
| 5382 | 5625 | $gw = $this->gateway(); |
| @@ -5411,9 +5654,8 @@ | ||
| 5411 | 5654 | $session->set('_vipps_pending_order', $orderid); |
| 5412 | 5655 | } |
| 5413 | 5656 | } |
| 5414 | 5657 | |
| 5415 | - | |
| 5416 | 5658 | do_action('woo_vipps_wait_for_payment_page',$order); |
| 5417 | 5659 | |
| 5418 | 5660 | $deleted_order=0; |
| 5419 | 5661 | if ($orderid && !$order) { |
| @@ -5477,11 +5719,9 @@ | ||
| 5477 | 5719 | } |
| 5478 | 5720 | $content .= "<div id=failure><p>". __('Order cancelled','woo-vipps') . '</p>'; |
| 5479 | 5721 | $content .= "<p><a href='" . home_url() . "' class='btn button'>" . __('Continue shopping','woo-vipps') . '</a></p>'; |
| 5480 | 5722 | $content .= "</div>"; |
| 5481 | - $this->fakepage(__('Order cancelled','woo-vipps'), $content); | |
| 5482 | - | |
| 5483 | - return; | |
| 5723 | + return $this->special_page_html('', $content); | |
| 5484 | 5724 | } |
| 5485 | 5725 | |
| 5486 | 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 |
| 5487 | 5727 | |
| @@ -5487,14 +5727,8 @@ | ||
| 5487 | 5727 | |
| 5488 | 5728 | // Otherwise, go to a page waiting/polling for the callback. IOK 2018-05-16 |
| 5489 | 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'); |
| 5490 | 5730 | |
| 5491 | - // 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 | |
| 5492 | - // and check that the order matches (and is 'pending') (and exists) | |
| 5493 | - $vippsstamp = $order->get_meta('_vipps_init_timestamp'); | |
| 5494 | - $vippsstatus = $order->get_meta('_vipps_status'); | |
| 5495 | - $message = __($order->get_meta('_vipps_confirm_message'),'woo-vipps'); | |
| 5496 | - | |
| 5497 | 5731 | $signal = $this->callbackSignal($order); |
| 5498 | 5732 | $content = ""; |
| 5499 | 5733 | $content .= "<div id='waiting'><p>" . sprintf(__('Waiting for confirmation of purchase from %1$s','woo-vipps'), $this->get_payment_method_name()); |
| 5500 | 5734 | |
| @@ -5527,94 +5761,22 @@ | ||
| 5527 | 5761 | $content .= "<a id='continueToOrderFailed' style='display:none' href='" . $failure_redirect . "'></a>"; |
| 5528 | 5762 | $content .= "<a id='continueToOrderFailedFallback' style='display:none' href='" . $gw->get_return_url($order) . "'></a>"; |
| 5529 | 5763 | $content .= "</div>"; |
| 5530 | 5764 | |
| 5765 | + return $this->special_page_html('', $content); | |
| 5766 | + } | |
| 5531 | 5767 | |
| 5532 | - $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); | |
| 5533 | 5776 | } |
| 5534 | 5777 | |
| 5535 | 5778 | |
| 5536 | - | |
| 5537 | - public function fakepage($title,$content) { | |
| 5538 | - global $wp, $wp_query; | |
| 5539 | - // We don't want this here. | |
| 5540 | - remove_filter ('the_content', 'wpautop'); | |
| 5541 | - | |
| 5542 | - $specialid = $this->gateway()->get_option('vippsspecialpageid'); | |
| 5543 | - $wp_post = null; | |
| 5544 | - if ($specialid) { | |
| 5545 | - $wp_post = get_post($specialid); | |
| 5546 | - if ($wp_post) { | |
| 5547 | - $wp_post->post_title = $title; | |
| 5548 | - $wp_post->post_content = $content; | |
| 5549 | - // Normalize a bit | |
| 5550 | - $wp_post->filter = 'raw'; // important | |
| 5551 | - $wp_post->post_status = 'publish'; | |
| 5552 | - $wp_post->comment_status= 'closed'; | |
| 5553 | - $wp_post->ping_status= 'closed'; | |
| 5554 | - } else { | |
| 5555 | - $this->log(sprintf(__("Could not use special page with id %s - it seems not to exist.", 'woo-vipps'), $specialid), 'error'); | |
| 5556 | - } | |
| 5557 | - } | |
| 5558 | - if (!$wp_post || is_wp_error($wp_post)) { | |
| 5559 | - $post = new stdClass(); | |
| 5560 | - $post->ID = -99; | |
| 5561 | - $post->post_author = 1; | |
| 5562 | - $post->post_date = current_time( 'mysql' ); | |
| 5563 | - $post->post_date_gmt = current_time( 'mysql', 1 ); | |
| 5564 | - $post->post_title = $title; | |
| 5565 | - $post->post_content = $content; | |
| 5566 | - $post->post_status = 'publish'; | |
| 5567 | - $post->comment_status = 'closed'; | |
| 5568 | - $post->ping_status = 'closed'; | |
| 5569 | - $post->post_name = 'vippsconfirm-fake-page-name'; | |
| 5570 | - $post->post_type = 'page'; | |
| 5571 | - $post->filter = 'raw'; // important | |
| 5572 | - $wp_post = new WP_Post($post); | |
| 5573 | - wp_cache_add( -99, $wp_post, 'posts' ); | |
| 5574 | - } | |
| 5575 | - | |
| 5576 | - // Update the main query | |
| 5577 | - $wp_query->post = $wp_post; | |
| 5578 | - $wp_query->posts = array( $wp_post ); | |
| 5579 | - $wp_query->queried_object = $wp_post; | |
| 5580 | - $wp_query->queried_object_id = $wp_post->ID; | |
| 5581 | - $wp_query->found_posts = 1; | |
| 5582 | - $wp_query->post_count = 1; | |
| 5583 | - $wp_query->max_num_pages = 1; | |
| 5584 | - $wp_query->is_page = true; | |
| 5585 | - $wp_query->is_singular = true; | |
| 5586 | - $wp_query->is_single = false; | |
| 5587 | - $wp_query->is_attachment = false; | |
| 5588 | - $wp_query->is_archive = false; | |
| 5589 | - $wp_query->is_category = false; | |
| 5590 | - $wp_query->is_tag = false; | |
| 5591 | - $wp_query->is_tax = false; | |
| 5592 | - $wp_query->is_author = false; | |
| 5593 | - $wp_query->is_date = false; | |
| 5594 | - $wp_query->is_year = false; | |
| 5595 | - $wp_query->is_month = false; | |
| 5596 | - $wp_query->is_day = false; | |
| 5597 | - $wp_query->is_time = false; | |
| 5598 | - $wp_query->is_search = false; | |
| 5599 | - $wp_query->is_feed = false; | |
| 5600 | - $wp_query->is_comment_feed = false; | |
| 5601 | - $wp_query->is_trackback = false; | |
| 5602 | - $wp_query->is_home = false; | |
| 5603 | - $wp_query->is_embed = false; | |
| 5604 | - $wp_query->is_404 = false; | |
| 5605 | - $wp_query->is_paged = false; | |
| 5606 | - $wp_query->is_admin = false; | |
| 5607 | - $wp_query->is_preview = false; | |
| 5608 | - $wp_query->is_robots = false; | |
| 5609 | - $wp_query->is_posts_page = false; | |
| 5610 | - $wp_query->is_post_type_archive = false; | |
| 5611 | - // Update globals | |
| 5612 | - $GLOBALS['wp_query'] = $wp_query; | |
| 5613 | - $wp->register_globals(); | |
| 5614 | - return $wp_post; | |
| 5615 | - } | |
| 5616 | - | |
| 5617 | 5779 | // Support the interactivity API with data about our cart IOK 2026-02-23 |
| 5618 | 5780 | public function woo_vipps_store_api_cart_data() { |
| 5619 | 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 |
| 5620 | 5782 | |
| @@ -5620,13 +5782,15 @@ | ||
| 5620 | 5782 | |
| 5621 | 5783 | $checkout_page = $this->gateway()->vipps_checkout_available(); |
| 5622 | 5784 | $standard_checkout = get_permalink(get_option('woocommerce_checkout_page_id')); |
| 5623 | 5785 | $checkout_url = $checkout_page ? get_permalink($checkout_page) : $standard_checkout; |
| 5786 | + | |
| 5624 | 5787 | $cart_data = array( |
| 5625 | 5788 | 'cart_hide_express' => !$this->gateway()->show_express_checkout(), |
| 5626 | 5789 | 'cart_supports_checkout' => (bool) $checkout_page, |
| 5627 | 5790 | 'checkout_url' => $checkout_url, |
| 5628 | 5791 | ); |
| 5792 | + | |
| 5629 | 5793 | return $cart_data; |
| 5630 | 5794 | } |
| 5631 | 5795 | |
| 5632 | 5796 | public function woo_vipps_store_api_cart_schema() { |
| @@ -5648,8 +5812,117 @@ | ||
| 5648 | 5812 | ), |
| 5649 | 5813 | ); |
| 5650 | 5814 | } |
| 5651 | 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 | + | |
| 5652 | 5925 | // Whether the order is possible to restart with a retry session at VMP. LP 2026-03-18 |
| 5653 | 5926 | public static function order_is_vipps_retryable($order_id) { |
| 5654 | 5927 | $order = wc_get_order($order_id); |
| 5655 | 5928 | if (!$order) return false; |
| @@ -5658,6 +5931,20 @@ | ||
| 5658 | 5931 | $shipping_set = $order->get_meta('_vipps_shipping_set'); |
| 5659 | 5932 | |
| 5660 | 5933 | // Express or unfinalized Checkout orders do not have shipping available, so we cant retry these in particular. LP 2026-03-18 |
| 5661 | 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'); | |
| 5662 | 5949 | } |
| 5663 | 5950 | } |