| @@ -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,50 @@ | ||
| 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 | + // If we are handling the 'wait for payment' action, we need to poll the order status before | |
| 319 | + // we start producing content IOK 2026-09-21 | |
| 320 | + if ($action == 'wait_for_payment') { | |
| 321 | + $this->handle_payment_poll_and_redirect(); | |
| 322 | + } | |
| 323 | + | |
| 324 | + }); | |
| 325 | + | |
| 326 | + // Add an admin interface for this page as well IOK 2026-09-11 | |
| 327 | + add_action('woocommerce_settings_pages', array($this, 'woocommerce_settings_pages')); | |
| 328 | + | |
| 275 | 329 | } |
| 276 | 330 | |
| 277 | 331 | public function admin_init () { |
| 278 | 332 | $gw = $this->gateway(); |
| @@ -392,9 +446,77 @@ | ||
| 392 | 446 | } |
| 393 | 447 | } |
| 394 | 448 | } |
| 395 | 449 | |
| 450 | + | |
| 451 | + /** Ensure we have a special page for payment flows | |
| 452 | + * | |
| 453 | + * woocommerce_loaded is too early for this because of maybe_create_vipps_pages which calls WC_Install::create_pages, | |
| 454 | + * and we hook unto this with woocommerce_create_pages. LP 2026-09-03 | |
| 455 | + **/ | |
| 456 | + public function ensure_special_page_exists() { | |
| 457 | + if (static::get_special_page_id()) return; | |
| 458 | + $this->log(__('Missing id for special page, attempting to fix.', 'woo-vipps'), 'info'); | |
| 396 | 459 | |
| 460 | + // 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 | |
| 461 | + $old_special_page_id = $this->gateway()->get_option('vippsspecialpageid'); | |
| 462 | + if ($old_special_page_id && ($special_page = get_post($old_special_page_id)) && "trash" !== $special_page->post_status) { | |
| 463 | + $this->log(__('Migrated old special page setting.', 'woo-vipps'), 'info'); | |
| 464 | + // there is no wc_set_page_id() so we update the option directly. LP 2026-09-01 | |
| 465 | + update_option('woocommerce_vipps_special_page_page_id', $old_special_page_id); | |
| 466 | + | |
| 467 | + // Ensure this page has the necessary shortcode. LP 2026-09-01 | |
| 468 | + if (!has_shortcode($special_page->post_content, 'vipps_special_page')) { | |
| 469 | + $new_content = $special_page->post_content . "\n\n<!-- wp:shortcode -->[vipps_special_page]<!-- /wp:shortcode -->"; | |
| 470 | + wp_update_post([ | |
| 471 | + 'ID' => $old_special_page_id, | |
| 472 | + 'post_content' => $new_content, | |
| 473 | + ]); | |
| 474 | + } | |
| 475 | + } else { | |
| 476 | + // Create special page if its missing. LP 2026-09-01 | |
| 477 | + $this->maybe_create_vipps_pages(); | |
| 478 | + } | |
| 479 | + } | |
| 480 | + | |
| 481 | + // Admin interface for the special page on woo/advanced/pages | |
| 482 | + public function woocommerce_settings_pages ($settings) { | |
| 483 | + $i = -1; | |
| 484 | + foreach($settings as $entry) { | |
| 485 | + $i++; | |
| 486 | + if ($entry['type'] == 'sectionend' && $entry['id'] == 'advanced_page_options') { | |
| 487 | + break; | |
| 488 | + } | |
| 489 | + } | |
| 490 | + if ($i > 0) { | |
| 491 | + $vippspagesettings = array( | |
| 492 | + array( | |
| 493 | + 'title' => sprintf(__( '%1$s Page', 'woo-vipps' ), Vipps::CompanyName()), | |
| 494 | + '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') , | |
| 495 | + 'id' => 'woocommerce_vipps_special_page_page_id', | |
| 496 | + 'type' => 'single_select_page_with_search', | |
| 497 | + 'default' => '', | |
| 498 | + 'class' => 'wc-page-search', | |
| 499 | + 'css' => 'min-width:300px;', | |
| 500 | + 'args' => array( | |
| 501 | + 'exclude' => | |
| 502 | + array( | |
| 503 | + wc_get_page_id( 'myaccount' ), | |
| 504 | + wc_get_page_id( 'checkout' ), | |
| 505 | + wc_get_page_id( 'cart' ), | |
| 506 | + ), | |
| 507 | + ), | |
| 508 | + 'desc_tip' => true, | |
| 509 | + 'autoload' => false, | |
| 510 | + )); | |
| 511 | + array_splice($settings, $i, 0, $vippspagesettings); | |
| 512 | + } | |
| 513 | + | |
| 514 | + return $settings; | |
| 515 | + } | |
| 516 | + | |
| 517 | + | |
| 518 | + | |
| 397 | 519 | // Runs on init, adds the Vipps badge feature if activated |
| 398 | 520 | public function maybe_add_vipps_badge_feature () { |
| 399 | 521 | $badge_options = get_option('vipps_badge_options'); |
| 400 | 522 | if (!$badge_options || !@$badge_options['badgeon']) return false; |
| @@ -723,8 +845,11 @@ | ||
| 723 | 845 | |
| 724 | 846 | // Get current brand and language |
| 725 | 847 | $current_brand = strtolower($this->get_payment_method_name()); |
| 726 | 848 | $current_language = $this->get_customer_language(); |
| 849 | + if ('se' === $current_language) $current_language = 'sv'; | |
| 850 | + // Looks like button and badge web components now use 'da' instead of 'dk' for danish. LP 2026-08-13 | |
| 851 | + if ('dk' === $current_language) $current_language = 'da'; | |
| 727 | 852 | |
| 728 | 853 | $variants = ['white'=> __('White', 'woo-vipps'), 'grey' => __('Grey','woo-vipps'), |
| 729 | 854 | 'filled'=> __('Filled', 'woo-vipps'), 'light'=>__('Light','woo-vipps'), |
| 730 | 855 | 'purple'=> __('Purple', 'woo-vipps')]; |
| @@ -787,9 +912,9 @@ | ||
| 787 | 912 | |
| 788 | 913 | <h2><?php _e('Shortcodes', 'woo-vipps'); ?> </h2> |
| 789 | 914 | <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 | 915 | <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> | |
| 916 | + <pre>[vipps-mobilepay-badge variant={white|filled|light|grey|purple}<br> language={en|no|fi|da|sv} ] </pre><br> | |
| 792 | 917 | <?php _e("Please refer to the documentation for the meaning of the parameters.", 'woo-vipps'); ?></br> |
| 793 | 918 | <?php _e("The brand will be automatically applied.", 'woo-vipps'); ?> |
| 794 | 919 | </p> |
| 795 | 920 | |
| @@ -816,21 +941,24 @@ | ||
| 816 | 941 | echo json_encode(array('ok'=>0,'msg'=>__('You don\'t have sufficient rights to edit this product', 'woo-vipps'))); |
| 817 | 942 | wp_die(__('You don\'t have sufficient rights to edit this product', 'woo-vipps')); |
| 818 | 943 | } |
| 819 | 944 | |
| 820 | - $options = get_option('vipps_button_options'); | |
| 821 | - if (isset($_POST['express']['variant'])) { | |
| 822 | - $options['express']['variant'] = sanitize_title($_POST['express']['variant']); | |
| 945 | + $old = get_option('vipps_button_options2', []); | |
| 946 | + $new = $old; | |
| 947 | + if (isset($_POST['express']['configs'])) { | |
| 948 | + foreach ($_POST['express']['configs'] as $ctx => $config) { | |
| 949 | + $sanitized_ctx = sanitize_title($ctx); | |
| 950 | + $sanitized_config = map_deep($config, 'sanitize_title'); | |
| 951 | + | |
| 952 | + // If nonglobal context that uses global config, just wipe the rest of the stored config. LP 2026-06-25 | |
| 953 | + if ("global" !== $sanitized_ctx && ($sanitized_config['use-global-config'] ?? false)) { | |
| 954 | + $new['express']['configs'][$sanitized_ctx] = ['use-global-config' => true]; | |
| 955 | + } else { | |
| 956 | + $new['express']['configs'][$sanitized_ctx] = $sanitized_config; | |
| 957 | + } | |
| 958 | + } | |
| 823 | 959 | } |
| 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); | |
| 960 | + update_option('vipps_button_options2', $new); | |
| 833 | 961 | wp_safe_redirect(admin_url("admin.php?page=vipps_button_menu")); |
| 834 | 962 | exit(); |
| 835 | 963 | } |
| 836 | 964 | |
| @@ -864,9 +992,12 @@ | ||
| 864 | 992 | public function vipps_mobilepay_badge_shortcode($atts) { |
| 865 | 993 | $args = shortcode_atts( array('id'=>'', 'class'=>'', 'brand' => '', 'variant' => '','language'=>''), $atts ); |
| 866 | 994 | |
| 867 | 995 | $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(); | |
| 996 | + $language = in_array($args['language'], ['en', 'no', 'sv', 'da', 'dk', 'fi']) ? $args['language'] : $this->get_customer_language(); | |
| 997 | + if ('se' === $language) $language = 'sv'; | |
| 998 | + // Looks like button and badge web components now use 'da' instead of 'dk' for danish. LP 2026-08-11 | |
| 999 | + if ('dk' === $language) $language = 'da'; | |
| 869 | 1000 | $id = sanitize_title($args['id']); |
| 870 | 1001 | $class = sanitize_text_field($args['class']); |
| 871 | 1002 | |
| 872 | 1003 | $attributes = []; |
| @@ -882,13 +1013,18 @@ | ||
| 882 | 1013 | return "<vipps-mobilepay-badge $badgeatts></vipps-mobilepay-badge>"; |
| 883 | 1014 | } |
| 884 | 1015 | |
| 885 | 1016 | // legacy vipps_badge shortcode, the new one is vipps_mobilepay_badge_shortcode. LP 19.11.2024 |
| 1017 | + // Diff: this one doesn't support brand (JUST VIPPS). LP 2026-08-11 | |
| 886 | 1018 | public function vipps_badge_shortcode($atts) { |
| 887 | 1019 | $args = shortcode_atts( array('id'=>'', 'class'=>'','variant' => '','language'=>''), $atts ); |
| 888 | 1020 | |
| 889 | 1021 | $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(); | |
| 1022 | + $language = in_array($args['language'], ['en', 'no', 'sv', 'da', 'dk', 'fi']) ? $args['language'] : $this->get_customer_language(); | |
| 1023 | + if ('se' === $language) $language = 'sv'; | |
| 1024 | + // Looks like button and badge web components now use 'da' instead of 'dk' for danish. LP 2026-08-11 | |
| 1025 | + if ('dk' === $language) $language = 'da'; | |
| 1026 | + | |
| 891 | 1027 | $id = sanitize_title($args['id']); |
| 892 | 1028 | $class = sanitize_text_field($args['class']); |
| 893 | 1029 | |
| 894 | 1030 | $attributes = []; |
| @@ -902,163 +1038,318 @@ | ||
| 902 | 1038 | |
| 903 | 1039 | return "<vipps-badge $badgeatts></vipps-badge>"; |
| 904 | 1040 | } |
| 905 | 1041 | |
| 906 | - public function get_express_logo_variants() { | |
| 1042 | + public function get_html_button_default_attrs() { | |
| 907 | 1043 | 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'), | |
| 1044 | + 'language' => 'store', | |
| 1045 | + 'variant' => 'primary', | |
| 1046 | + 'rounded' => 'false', | |
| 1047 | + 'verb' => 'buy', | |
| 1048 | + 'stretched' => 'false', | |
| 1049 | + 'compact' => 'false', | |
| 1050 | + '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 | 1051 | ]; |
| 915 | 1052 | } |
| 916 | 1053 | |
| 1054 | + public function get_html_button_attrs_for_context($context = 'global') { | |
| 1055 | + $options = get_option('vipps_button_options2', []); | |
| 1056 | + if (!is_string($context)) $context = 'global'; | |
| 1057 | + $config = $options['express']['configs'][$context] ?? []; | |
| 1058 | + if (!$config || ($config['use-global-config'] ?? false)) { | |
| 1059 | + $config = $options['express']['configs']['global'] ?? $this->get_html_button_default_attrs(); | |
| 1060 | + } | |
| 1061 | + return $config; | |
| 1062 | + } | |
| 917 | 1063 | |
| 1064 | + public function get_html_button_for_context($context = 'global') { | |
| 1065 | + return $this->get_html_button($this->get_html_button_attrs_for_context($context)); | |
| 1066 | + } | |
| 1067 | + | |
| 1068 | + // Generic Vipps/MobilePay button html, as of now a web component hosted locally. LP 2026-06-24 | |
| 1069 | + // See info and attributes at https://developer.vippsmobilepay.com/docs/knowledge-base/buttons/ | |
| 1070 | + public function get_html_button($attrs = []) { | |
| 1071 | + $payment_method = $this->get_payment_method_name(); | |
| 1072 | + $attrs = wp_parse_args($attrs, $this->get_html_button_default_attrs()); | |
| 1073 | + $attrs['brand'] = strtolower($payment_method); | |
| 1074 | + $attrs['type'] = 'button'; // static | |
| 1075 | + | |
| 1076 | + // Support using store language | |
| 1077 | + if ('store' === $attrs['language']) $attrs['language'] = $this->get_customer_language(); | |
| 1078 | + // Don't support these login verbs. LP 2026-06-04 | |
| 1079 | + if (in_array($attrs['verb'], ['login', 'register'])) $attrs['verb'] = 'buy'; | |
| 1080 | + // Looks like button and badge web components now use 'da' instead of 'dk' for danish. LP 2026-08-11 | |
| 1081 | + if ('dk' === $attrs['language']) $attrs['language'] = 'da'; | |
| 1082 | + | |
| 1083 | + $escaped_attrs = []; | |
| 1084 | + foreach($attrs as $k => $v) { | |
| 1085 | + $escaped_attrs[$k] = esc_attr($v); | |
| 1086 | + } | |
| 1087 | + | |
| 1088 | + // id attribute | |
| 1089 | + $id = $escaped_attrs['id'] ?? ''; | |
| 1090 | + $id_str = $id ? "id='$id'" : ''; | |
| 1091 | + | |
| 1092 | + // class attribute | |
| 1093 | + $class_str = ''; | |
| 1094 | + if (isset($attrs['class'])) { | |
| 1095 | + if (is_array($attrs['class'])) { | |
| 1096 | + $class_str = implode(' ', $attrs['class']); | |
| 1097 | + } else if (is_string($attrs['class'])) { | |
| 1098 | + $class_str = $attrs['class']; | |
| 1099 | + } | |
| 1100 | + } | |
| 1101 | + | |
| 1102 | + // The html | |
| 1103 | + $html = <<<EOF | |
| 1104 | +<vipps-mobilepay-button | |
| 1105 | + $id_str | |
| 1106 | + $class_str | |
| 1107 | + type="{$escaped_attrs['type']}" | |
| 1108 | + brand="{$escaped_attrs['brand']}" | |
| 1109 | + language="{$escaped_attrs['language']}" | |
| 1110 | + variant="{$escaped_attrs['variant']}" | |
| 1111 | + rounded="{$escaped_attrs['rounded']}" | |
| 1112 | + verb="{$escaped_attrs['verb']}" | |
| 1113 | + stretched="{$escaped_attrs['stretched']}" | |
| 1114 | + compact="{$escaped_attrs['compact']}" | |
| 1115 | +></vipps-mobilepay-button> | |
| 1116 | +EOF; | |
| 1117 | + return apply_filters('woo_vipps_html_button', $html, $attrs); | |
| 1118 | + } | |
| 1119 | + | |
| 918 | 1120 | public function button_menu_page() { |
| 919 | 1121 | if (!current_user_can('manage_woocommerce')) { |
| 920 | 1122 | wp_die(__('You don\'t have sufficient rights to access this page', 'woo-vipps')); |
| 921 | 1123 | } |
| 922 | - $payment_method = $this->get_payment_method_name(); | |
| 923 | - $lang = $this->get_customer_language(); | |
| 924 | - $button_options = get_option('vipps_button_options'); | |
| 1124 | + wp_enqueue_script('vipps-button-webcomponent'); | |
| 1125 | + ?> | |
| 1126 | + <div class='wrap vipps-button-settings'> | |
| 1127 | + <h1><?php echo sprintf(__('%1$s button configuration', 'woo-vipps'), Vipps::CompanyName()); ?></h1> | |
| 1128 | + <span><?php echo sprintf(__('%1$s supports different variants of buttons for you to perfect your store\'s look', 'woo-vipps'), Vipps::CompanyName()); ?></span> | |
| 1129 | + <form id="vipps-button-settings-form" class="vipps-button-settings" action="<?php echo admin_url('admin-post.php'); ?>" method="POST"> | |
| 1130 | + <input type="hidden" name="action" value="update_vipps_button_settings" /> | |
| 1131 | + <?php wp_nonce_field( 'buttonaction', 'buttonnonce'); ?> | |
| 925 | 1132 | |
| 926 | - $variants = $this->get_express_logo_variants(); | |
| 927 | - $mini_variants = array_filter($variants, fn($key) => str_ends_with($key, 'mini'), ARRAY_FILTER_USE_KEY); | |
| 1133 | + <!-- Express section --> | |
| 1134 | + <?php $this->button_menu_express_section(); ?> | |
| 928 | 1135 | |
| 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 | - ], | |
| 1136 | + <!-- submit button --> | |
| 1137 | + <div id="vipps-button-settings-save"> | |
| 1138 | + <input class="btn button primary" type="submit" value="<?php _e('Update settings', 'woo-vipps'); ?>" /> | |
| 1139 | + </div> | |
| 1140 | + </form> | |
| 1141 | + </div> | |
| 1142 | + <?php | |
| 1143 | + } | |
| 1144 | + | |
| 1145 | + private function button_menu_express_section() { | |
| 1146 | + $options = get_option('vipps_button_options2', []); | |
| 1147 | + $express = $options['express'] ?? []; | |
| 1148 | + $configs = $express['configs'] ?? []; | |
| 1149 | + $contexts = [ | |
| 1150 | + 'global' => __('Global', 'woo-vipps'), | |
| 1151 | + 'product' => __('Product', 'woo-vipps'), | |
| 1152 | + 'catalog' => __('Catalog', 'woo-vipps'), | |
| 1153 | + 'cart' => __('Cart', 'woo-vipps'), | |
| 1154 | + 'minicart' => __('Mini cart', 'woo-vipps'), | |
| 1155 | + 'checkout' => __('Checkout', 'woo-vipps'), | |
| 940 | 1156 | ]; |
| 1157 | + $init_context = 'global'; | |
| 1158 | + $init_config = $configs[$init_context] ?? []; | |
| 941 | 1159 | |
| 1160 | + // html button args | |
| 1161 | + $init_args = $init_config; | |
| 1162 | + $init_args['id'] = 'vipps-button-express-preview'; | |
| 1163 | + | |
| 942 | 1164 | ?> |
| 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"> | |
| 1165 | + <div class="vipps-button-settings-section" id="vipps-button-settings-express-container"> | |
| 1166 | + <h2> <?php _e('Express Checkout', 'woo-vipps'); ?></h2> | |
| 947 | 1167 | |
| 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'); ?> | |
| 1168 | + <!-- Context dropdown --> | |
| 1169 | + <div id="vipps-button-settings-express-context"> | |
| 1170 | + <label> | |
| 1171 | + <?php _e('Config context', 'woo-vipps'); ?> | |
| 1172 | + </label> | |
| 1173 | + <select id="context" onChange='updateContext()'> | |
| 1174 | + <?php foreach($contexts as $key => $label): ?> | |
| 1175 | + <option value="<?php echo $key; ?>" <?php if ('global' === $key) echo " selected "; ?> > | |
| 1176 | + <?php echo $label ; ?> | |
| 1177 | + </option> | |
| 1178 | + <?php endforeach; ?> | |
| 1179 | + </select> | |
| 1180 | + <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> | |
| 1181 | + </div> | |
| 1182 | + | |
| 953 | 1183 | |
| 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> | |
| 1184 | + <!-- Button paremeter inputs. These input values are put into post data express.tmpConfig temporarily. | |
| 1185 | + On context change, configs are stored in a global 'contextConfigs'. Each config is processed into new option structure before submit. LP 2026-06-24 --> | |
| 1186 | + <div class="vipps-button-settings-section" id="vipps-button-settings-express-args"> | |
| 1187 | + <fieldset> | |
| 1188 | + <label><input type="checkbox" name="express[tmpConfig][rounded]" checked=""><?php _e('Rounded', 'woo-vipps'); ?></label> | |
| 1189 | + <label><input type="checkbox" name="express[tmpConfig][compact]"><?php _e('Compact', 'woo-vipps'); ?></label> | |
| 1190 | + <label><input type="checkbox" name="express[tmpConfig][stretched]"><?php _e('Stretched', 'woo-vipps'); ?></label> | |
| 1191 | + </fieldset> | |
| 1192 | + <fieldset> | |
| 1193 | + <legend><?php _e('Language', 'woo-vipps'); ?></legend> | |
| 1194 | + <label><input type="radio" name="express[tmpConfig][language]" checked value="store"><?php _e('Store language', 'woo-vipps'); ?></label> | |
| 1195 | + <label><input type="radio" name="express[tmpConfig][language]" value="en"><?php _e('English', 'woo-vipps'); ?></label> | |
| 1196 | + <label><input type="radio" name="express[tmpConfig][language]" value="no"><?php _e('Norwegian', 'woo-vipps'); ?></label> | |
| 1197 | + <label><input type="radio" name="express[tmpConfig][language]" value="dk"><?php _e('Danish', 'woo-vipps'); ?></label> | |
| 1198 | + <label><input type="radio" name="express[tmpConfig][language]" value="sv"><?php _e('Swedish', 'woo-vipps'); ?></label> | |
| 1199 | + <?php if ($this->get_payment_method_name() === 'MobilePay'): ?> | |
| 1200 | + <label><input type="radio" disabled="" name="express[tmpConfig][language]" value="fi"><?php _e('Finnish', 'woo-vipps'); ?></label> | |
| 1201 | + <?php endif; ?> | |
| 1202 | + </fieldset> | |
| 967 | 1203 | |
| 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> | |
| 1204 | + <?php if ($this->get_payment_method_name() !== 'MobilePay'): ?> | |
| 1205 | + <p><?php printf(__('Finnish is currently only available with the %s payment method.', 'woo-vipps'), 'MobilePay'); ?></p> | |
| 1206 | + <?php endif; ?> | |
| 980 | 1207 | |
| 1208 | + <fieldset> | |
| 1209 | + <legend><?php _e('Verb', 'woo-vipps'); ?></legend> | |
| 1210 | + <label><input type="radio" name="express[tmpConfig][verb]" checked value="buy"><?php _e('Buy', 'woo-vipps'); ?></label> | |
| 1211 | + <label><input type="radio" name="express[tmpConfig][verb]" value="pay"><?php _e('Pay', 'woo-vipps'); ?></label> | |
| 1212 | + <label><input type="radio" name="express[tmpConfig][verb]" value="continue"><?php _e('Continue', 'woo-vipps'); ?></label> | |
| 1213 | + <label><input type="radio" name="express[tmpConfig][verb]" value="confirm"><?php _e('Confirm', 'woo-vipps'); ?></label> | |
| 1214 | + <label><input type="radio" name="express[tmpConfig][verb]" value="donate"><?php _e('Donate', 'woo-vipps'); ?></label> | |
| 1215 | + <label><input type="radio" name="express[tmpConfig][verb]" value="express"><?php _e('Express', 'woo-vipps'); ?></label> | |
| 1216 | + </fieldset> | |
| 1217 | + <fieldset> | |
| 1218 | + <legend><?php _e('Variant', 'woo-vipps'); ?></legend> | |
| 1219 | + <label><input type="radio" name="express[tmpConfig][variant]" checked value="primary"><?php _e('Primary', 'woo-vipps'); ?></label> | |
| 1220 | + <label><input type="radio" name="express[tmpConfig][variant]" value="dark"><?php _e('Dark (WCAG AAA)', 'woo-vipps'); ?></label> | |
| 1221 | + <label><input type="radio" name="express[tmpConfig][variant]" value="light"><?php _e('Light (WCAG AAA)', 'woo-vipps'); ?></label> | |
| 1222 | + </fieldset> | |
| 1223 | + </div> | |
| 981 | 1224 | |
| 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> | |
| 1225 | + <!-- Button preview that changes depending on the chosen parameters. LP 2026-06-24 --> | |
| 1226 | + <?php echo $this->get_html_button($init_args); ?> | |
| 1227 | + </div> | |
| 991 | 1228 | |
| 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> | |
| 1229 | + <script> | |
| 1230 | + // When inputs change, update the preview args. LP 2026-06-24 | |
| 1231 | + jQuery('#vipps-button-settings-express-args input').on('click', updatePreview); | |
| 997 | 1232 | |
| 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> | |
| 1233 | + let currentContext = '<?php echo $init_context; ?>'; | |
| 1234 | + let contextConfigs = <?php echo json_encode($configs) ?: "{}"; ?> // maps context slug to config object. LP 2026-06-24 | |
| 1003 | 1235 | |
| 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> | |
| 1236 | + // Updates the actual html inputs from given config. LP 2026-07-01 | |
| 1237 | + function setInputsFromConfig(context, config) { | |
| 1238 | + const useGlobalConfig = Boolean(config?.["use-global-config"]); | |
| 1239 | + const isGlobal = "global" === context; | |
| 1009 | 1240 | |
| 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> | |
| 1241 | + // Only show the 'use-global-config' checkbox for nonglobal context. LP 2026-06-26 | |
| 1242 | + jQuery('#use-global-config-container').toggleClass('hidden', isGlobal); | |
| 1021 | 1243 | |
| 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> | |
| 1244 | + // Nonglobal contexts with useGlobalConfig, and empty configs, should fallback to the global config. LP 2026-06-26 | |
| 1245 | + if (!config || (!isGlobal && useGlobalConfig)) { | |
| 1246 | + config = contextConfigs["global"]; | |
| 1034 | 1247 | |
| 1035 | - <!-- END EXPRESS SECTION --> | |
| 1036 | - </div> | |
| 1248 | + jQuery('input[name="express[tmpConfig][use-global-config]"]').prop("checked", true); | |
| 1037 | 1249 | |
| 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> | |
| 1250 | + // When using global config, the inputs should be disabled until its unchecked. LP 2026-06-26 | |
| 1251 | + jQuery('#vipps-button-settings-express-args input').prop("disabled", true); | |
| 1252 | + } else { | |
| 1253 | + jQuery('input[name="express[tmpConfig][use-global-config]"]').prop("checked", false); | |
| 1254 | + jQuery('#vipps-button-settings-express-args input').prop("disabled", false); | |
| 1255 | + } | |
| 1042 | 1256 | |
| 1043 | - </form> | |
| 1044 | - </div> | |
| 1257 | + Object.entries(config).forEach(([key, val]) => { | |
| 1258 | + if ("use-global-config" === key) return; | |
| 1259 | + const inputs = jQuery(`input[name="express[tmpConfig][${key}]"]`); | |
| 1260 | + const type = inputs.prop('type'); | |
| 1261 | + switch (type) { | |
| 1262 | + case "checkbox": | |
| 1263 | + inputs.prop('checked', typeof val === "boolean" ? val : "true" === val); | |
| 1264 | + break; | |
| 1265 | + case "radio": | |
| 1266 | + inputs.filter(`[value="${val}"]`).prop('checked', true); | |
| 1267 | + break; | |
| 1268 | + default: | |
| 1269 | + console.error(`woo-vipps: Unexpected input type '${type}' for button config. key=${key}, val=${val}`); | |
| 1270 | + } | |
| 1271 | + }); | |
| 1045 | 1272 | |
| 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 | - } | |
| 1273 | + updatePreview(); | |
| 1274 | + } | |
| 1275 | + // init the starting config from option. LP 2026-06-25 | |
| 1276 | + setInputsFromConfig(currentContext, contextConfigs[currentContext]); | |
| 1053 | 1277 | |
| 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> | |
| 1278 | + // Update the preview web component's attributes. LP 2026-06-24 | |
| 1279 | + function updatePreview(event) { | |
| 1280 | + const args = getPreviewArgs(); | |
| 1281 | + // 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 | |
| 1282 | + // if ('store' === args.language) args.language = '<?php echo $this->get_customer_language(); ?>'; | |
| 1283 | + if ('store' === args.language) args.language = '<?php echo substr(get_locale(), 0, 2); ?>'; | |
| 1284 | + const button = jQuery('#vipps-button-express-preview'); | |
| 1285 | + button.attr(args); | |
| 1286 | + } | |
| 1287 | + | |
| 1288 | + function getPreviewArgs() { | |
| 1289 | + const args = {}; | |
| 1290 | + jQuery('#vipps-button-settings-express-args input').each(function () { | |
| 1291 | + // inputs are put in form arrays like 'express[tmpConfig][attribute]', so extract the actual attribute name. LP 2026-06-24 | |
| 1292 | + const matches = [...this.name.matchAll(/\[([^\]]+)\]/g)]; | |
| 1293 | + const attr = matches.length ? matches[matches.length - 1][1] : null; | |
| 1294 | + if (!attr) { | |
| 1295 | + console.error("woo-vipps: Could not extract attribute name for button preview:", this); | |
| 1296 | + return; | |
| 1297 | + } | |
| 1298 | + if (this.type === 'checkbox') { | |
| 1299 | + args[attr] = this.checked; | |
| 1300 | + } else if (this.checked) { | |
| 1301 | + args[attr] = this.value; | |
| 1302 | + } | |
| 1303 | + }); | |
| 1304 | + | |
| 1305 | + return args; | |
| 1306 | + } | |
| 1307 | + | |
| 1308 | + // Stores selected config for context and switches to another (if changed). LP 2026-07-01 | |
| 1309 | + function updateContext() { | |
| 1310 | + const wasGlobal = "global" === currentContext; | |
| 1311 | + const useGlobalConfig = jQuery('#use-global-config-container input').prop("checked"); | |
| 1312 | + | |
| 1313 | + // Store config to global, unless its a non-global context that uses global config. LP 2026-06-25 | |
| 1314 | + if (wasGlobal || !useGlobalConfig) { | |
| 1315 | + contextConfigs[currentContext] = getPreviewArgs(); | |
| 1316 | + } else { | |
| 1317 | + contextConfigs[currentContext] = {'use-global-config': true}; | |
| 1318 | + } | |
| 1319 | + | |
| 1320 | + // Swap to new context: set all input fields to the stored values if exists. LP 2026-06-25 | |
| 1321 | + const newContext = jQuery("#context").val(); | |
| 1322 | + const newConfig = contextConfigs[newContext]; | |
| 1323 | + setInputsFromConfig(newContext, newConfig); | |
| 1324 | + currentContext = newContext; | |
| 1325 | + } | |
| 1326 | + | |
| 1327 | + // Before submit: delete the tmpConfig for the current selected values, and add the stored contextConfigs to the post data. LP 2026-06-24 | |
| 1328 | + jQuery('#vipps-button-settings-form').on('formdata', e => { | |
| 1329 | + const formData = e?.originalEvent?.formData; | |
| 1330 | + if (!formData) return; | |
| 1331 | + | |
| 1332 | + // run this to store current context config before posting. LP 2026-06-24 | |
| 1333 | + updateContext(); | |
| 1334 | + | |
| 1335 | + // now we can delete the current temporary config from post data. LP 2026-06-24 | |
| 1336 | + const keysToDelete = []; | |
| 1337 | + for (const [key] of formData.entries()) { | |
| 1338 | + if (key.startsWith("express[tmpConfig][")) { | |
| 1339 | + keysToDelete.push(key); | |
| 1340 | + } | |
| 1341 | + } | |
| 1342 | + keysToDelete.forEach(key => formData.delete(key)); | |
| 1343 | + | |
| 1344 | + // Now add the actual post data from the stored global contextConfigs. LP 2026-06-24 | |
| 1345 | + Object.entries(contextConfigs).forEach(([context, config]) => { | |
| 1346 | + Object.entries(config).forEach(([key, val]) => { | |
| 1347 | + formData.append(`express[configs][${context}][${key}]`, val); | |
| 1348 | + }); | |
| 1349 | + }); | |
| 1350 | + }); | |
| 1351 | + </script> | |
| 1061 | 1352 | <?php |
| 1062 | 1353 | } |
| 1063 | 1354 | |
| 1064 | 1355 | |
| @@ -1478,12 +1769,22 @@ | ||
| 1478 | 1769 | plugins_url('js/vipps-on-site-messaging.js', WC_VIPPS_PAYMENT_MAIN_FILE), |
| 1479 | 1770 | array(), |
| 1480 | 1771 | filemtime(dirname(WC_VIPPS_PAYMENT_MAIN_FILE) . '/js/vipps-on-site-messaging.js'), |
| 1481 | 1772 | [ |
| 1482 | - 'in_footer' => true, | |
| 1483 | - 'strategy' => 'async', | |
| 1773 | + 'in_footer' => true, | |
| 1774 | + 'strategy' => 'async', | |
| 1484 | 1775 | ], |
| 1485 | 1776 | ); |
| 1777 | + | |
| 1778 | + // Button web component downloaded from https://cdn.vippsmobilepay.com/js/button/button.js. LP 2026-06-24 | |
| 1779 | + wp_register_script('vipps-button-webcomponent', | |
| 1780 | + plugins_url('js/vipps-button.js', WC_VIPPS_PAYMENT_MAIN_FILE), | |
| 1781 | + array(), | |
| 1782 | + filemtime(dirname(WC_VIPPS_PAYMENT_MAIN_FILE) . '/js/vipps-button.js'), | |
| 1783 | + [ | |
| 1784 | + 'in_footer' => false | |
| 1785 | + ], | |
| 1786 | + ); | |
| 1486 | 1787 | } |
| 1487 | 1788 | |
| 1488 | 1789 | // Runs late in both wp_enqueue_scripts and admin_enqueue_scripts to make it more compatible with translation plugins IOK 2026-02-02 |
| 1489 | 1790 | public function script_add_vippslocale () { |
| @@ -1502,8 +1803,9 @@ | ||
| 1502 | 1803 | $this->script_add_vippslocale(); |
| 1503 | 1804 | |
| 1504 | 1805 | wp_enqueue_script('vipps-gw'); |
| 1505 | 1806 | wp_enqueue_style('vipps-gw',plugins_url('css/vipps.css',__FILE__),array(),filemtime(dirname(__FILE__) . "/css/vipps.css")); |
| 1807 | + wp_enqueue_script('vipps-button-webcomponent'); | |
| 1506 | 1808 | } |
| 1507 | 1809 | |
| 1508 | 1810 | |
| 1509 | 1811 | public function add_shortcodes() { |
| @@ -1515,8 +1817,11 @@ | ||
| 1515 | 1817 | // New vipps-mobilepay-badge shortcode. LP 19.11.2024 |
| 1516 | 1818 | add_shortcode('vipps-mobilepay-badge', array($this, 'vipps_mobilepay_badge_shortcode')); |
| 1517 | 1819 | // Legacy vipps-badge shortcode. LP 19.11.2024 |
| 1518 | 1820 | add_shortcode('vipps-badge', array($this, 'vipps_badge_shortcode')); |
| 1821 | + | |
| 1822 | + // special page handling, previously a fake page. LP 2026-08-25 | |
| 1823 | + add_shortcode('vipps_special_page', array($this, 'vipps_special_page_shortcode')); | |
| 1519 | 1824 | } |
| 1520 | 1825 | |
| 1521 | 1826 | |
| 1522 | 1827 | public function log ($what,$type='info') { |
| @@ -1542,8 +1847,10 @@ | ||
| 1542 | 1847 | } |
| 1543 | 1848 | |
| 1544 | 1849 | // Show express button option on checkout form. LP 2026-03-23 |
| 1545 | 1850 | public function checkout_before_customer_details_express () { |
| 1851 | + if (did_action('woo_vipps_checkout_before_customer_details_express')) return; | |
| 1852 | + do_action('woo_vipps_checkout_before_customer_details_express'); | |
| 1546 | 1853 | $gw = $this->gateway(); |
| 1547 | 1854 | if (!$gw->show_express_checkout()) return; |
| 1548 | 1855 | $this->express_checkout_section_html(); |
| 1549 | 1856 | } |
| @@ -1553,9 +1860,9 @@ | ||
| 1553 | 1860 | $header_text = __('Express Checkout', 'woo-vipps'); |
| 1554 | 1861 | $header = "<legend class='express-header'>$header_text</legend>"; |
| 1555 | 1862 | $div_classes = "legacy-checkout vipps-express-checkout $payment_method"; |
| 1556 | 1863 | echo "<fieldset class='$div_classes'>$header"; |
| 1557 | - $this->cart_express_checkout_button_html(); | |
| 1864 | + $this->checkout_express_checkout_button_html(); | |
| 1558 | 1865 | echo '</fieldset>'; |
| 1559 | 1866 | } |
| 1560 | 1867 | |
| 1561 | 1868 | public function express_checkout_banner() { |
| @@ -1582,8 +1889,27 @@ | ||
| 1582 | 1889 | <div class="<?php echo $div_classes;?>"><?php echo $message;?></div> |
| 1583 | 1890 | <?php |
| 1584 | 1891 | } |
| 1585 | 1892 | |
| 1893 | + public function checkout_express_checkout_button() { | |
| 1894 | + $gw = $this->gateway(); | |
| 1895 | + | |
| 1896 | + if ($gw->show_express_checkout()){ | |
| 1897 | + return $this->checkout_express_checkout_button_html(); | |
| 1898 | + } | |
| 1899 | + } | |
| 1900 | + | |
| 1901 | + public function checkout_express_checkout_button_html() { | |
| 1902 | + $url = $this->express_checkout_url(); | |
| 1903 | + $url = wp_nonce_url($url,'express','sec'); | |
| 1904 | + $button= apply_filters('woo_vipps_express_checkout_button', $this->get_html_button_for_context('checkout')); | |
| 1905 | + $method = $this->get_payment_method_name(); | |
| 1906 | + $title = sprintf(__('Buy now with %1$s!', 'woo-vipps'), $method); | |
| 1907 | + $html = "<a href='$url' class='vipps-express-checkout short $method' title='$title'>$button</a>"; | |
| 1908 | + $html = apply_filters('woo_vipps_cart_express_checkout_button', $html, $url); | |
| 1909 | + echo $html; | |
| 1910 | + } | |
| 1911 | + | |
| 1586 | 1912 | // Show the express button if reasonable to do so |
| 1587 | 1913 | public function cart_express_checkout_button() { |
| 1588 | 1914 | $gw = $this->gateway(); |
| 1589 | 1915 | |
| @@ -1602,22 +1928,46 @@ | ||
| 1602 | 1928 | |
| 1603 | 1929 | public function cart_express_checkout_button_html($minicart = false) { |
| 1604 | 1930 | $url = $this->express_checkout_url(); |
| 1605 | 1931 | $url = wp_nonce_url($url,'express','sec'); |
| 1606 | - $page = $minicart ? 'minicart' : 'cart'; | |
| 1607 | - $imgurl= apply_filters('woo_vipps_express_checkout_button', $this->get_payment_logo($page)); | |
| 1932 | + $context = $minicart ? 'minicart' : 'cart'; | |
| 1933 | + $button= apply_filters('woo_vipps_express_checkout_button', $this->get_html_button_for_context($context)); | |
| 1608 | 1934 | $method = $this->get_payment_method_name(); |
| 1609 | 1935 | $title = sprintf(__('Buy now with %1$s!', 'woo-vipps'), $method); |
| 1610 | - $button = "<a href='$url' class='button vipps-express-checkout short $method' title='$title'><img alt='$title' border=0 src='$imgurl'></a>"; | |
| 1611 | - $button = apply_filters('woo_vipps_cart_express_checkout_button', $button, $url); | |
| 1612 | - echo $button; | |
| 1936 | + $html = "<a href='$url' class='vipps-express-checkout short $method' title='$title'>$button</a>"; | |
| 1937 | + $html = apply_filters('woo_vipps_cart_express_checkout_button', $html, $url); | |
| 1938 | + echo $html; | |
| 1613 | 1939 | } |
| 1614 | 1940 | |
| 1615 | 1941 | // 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 |
| 1616 | 1942 | // cached. Therefore stock, purchasability etc will be done later. IOK 2018-10-02 |
| 1617 | 1943 | public function buy_now_button_shortcode ($atts) { |
| 1618 | - $args = shortcode_atts( array( 'id' => '','variant'=>'','sku' => '',), $atts ); | |
| 1619 | - return "<div class='vipps_buy_now_wrapper noloop'>". $this->get_buy_now_button($args['id'], $args['variant'], $args['sku'], false, '', 'shortcode') . "</div>"; | |
| 1944 | + // The new web component button args. LP 2026-07-02 | |
| 1945 | + $button_args = $this->get_html_button_default_attrs(); | |
| 1946 | + unset($button_args['brand']); | |
| 1947 | + | |
| 1948 | + // Variant exists for the product variant. LP 2026-07-02 | |
| 1949 | + if (isset($button_args['variant'])) $button_args['button_variant'] = $button_args['variant']; | |
| 1950 | + unset($button_args['variant']); | |
| 1951 | + | |
| 1952 | + $args = shortcode_atts( | |
| 1953 | + array(...$button_args, | |
| 1954 | + 'id' => '','variant'=> '','sku' => '', | |
| 1955 | + ), | |
| 1956 | + $atts, | |
| 1957 | + ); | |
| 1958 | + | |
| 1959 | + // Variant exists for the product variant. LP 2026-07-02 | |
| 1960 | + $button_args = $args; | |
| 1961 | + if (isset($button_args['button_variant'])) $button_args['variant'] = $button_args['button_variant']; | |
| 1962 | + unset($button_args['button_variant']); | |
| 1963 | + unset($button_args['sku']); | |
| 1964 | + unset($button_args['id']); | |
| 1965 | + // NB: the language may be incorrect for the shortcode, see web component bug at https://developer.vippsmobilepay.com/docs/knowledge-base/buttons/ | |
| 1966 | + // "Note also that there is a bug in the library, and it currently only renders one language per page." | |
| 1967 | + // 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 | |
| 1968 | + | |
| 1969 | + return "<div class='vipps_buy_now_wrapper noloop'>". $this->get_buy_now_button($args['id'], $args['variant'], $args['sku'], false, '', 'shortcode', $button_args) . "</div>"; | |
| 1620 | 1970 | } |
| 1621 | 1971 | |
| 1622 | 1972 | // 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 |
| 1623 | 1973 | public function express_checkout_button_shortcode() { |
| @@ -2286,77 +2636,69 @@ | ||
| 2286 | 2636 | return null; |
| 2287 | 2637 | } |
| 2288 | 2638 | } |
| 2289 | 2639 | |
| 2640 | + // Special pages, and some callbacks. IOK 2018-05-18 | |
| 2641 | + public function template_redirect() { | |
| 2290 | 2642 | |
| 2291 | - // If this is a special page, return true very early because we are handling this. IOK 2023-02-22 | |
| 2292 | - public function pre_handle_404($current, $query) { | |
| 2293 | - if (!is_admin()) { | |
| 2294 | - $special = $this->is_special_page(); | |
| 2295 | - if ($special) { | |
| 2296 | - // Ensure very early on that Autooptimize does not try to optimize us (if installed) IOK 2023-03-04 | |
| 2297 | - add_filter( 'autoptimize_filter_noptimize', '__return_true'); | |
| 2298 | - return true; | |
| 2299 | - } | |
| 2643 | + // Handle legacy vipps-buy-now urls that auto-start express checkout for certain product - in QR codes etc IOK 2026-09-11 | |
| 2644 | + // We redirect these to the new location. | |
| 2645 | + $path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); | |
| 2646 | + if (( ($_GET['VippsSpecialPage'] ?? '') == 'vipps-buy-product') || ($path && preg_match("!/vipps-buy-product/?$!", $path)) ) { | |
| 2647 | + $url = static::get_special_page_url(); | |
| 2648 | + $_GET['action'] = 'buy_product'; | |
| 2649 | + $q = build_query($_GET); | |
| 2650 | + wp_redirect($url . "?" . $q, 302); | |
| 2651 | + exit(); | |
| 2300 | 2652 | } |
| 2301 | - return $current; | |
| 2302 | - } | |
| 2303 | 2653 | |
| 2304 | - // Special pages, and some callbacks. IOK 2018-05-18 | |
| 2305 | - public function template_redirect() { | |
| 2306 | - global $post; | |
| 2307 | - // Handle special callbacks | |
| 2308 | - $special = $this->is_special_page() ; | |
| 2309 | - | |
| 2310 | - if ($special) { | |
| 2654 | + if (static::is_special_page()) { | |
| 2655 | + // Legacy: Stop the canonical redirect here. Unclear if still necessary. IOK 2026-09-11 | |
| 2311 | 2656 | remove_filter('template_redirect', 'redirect_canonical', 10); |
| 2312 | - do_action('woo_vipps_before_handling_special_page', $special); | |
| 2657 | + // dont cache special page. LP 2026-08-25 | |
| 2658 | + $this->nocache(); | |
| 2659 | + // Do the custom pre-load actions for these pages IOK 2026-09-11 | |
| 2660 | + do_action('woo_vipps_before_handling_special_page', ($_GET['action'] ?? "")); | |
| 2661 | + } | |
| 2662 | + } | |
| 2313 | 2663 | |
| 2314 | - // Allow above hook to actually handle special pages. It should probably call $Vipps->fakepage or a redirect; can be used | |
| 2315 | - // to intercept express checkout etc. IOK 2022-03-18 | |
| 2316 | - if (! apply_filters('woo_vipps_special_page_handled', false, $special)) { | |
| 2317 | - $this->$special(); | |
| 2318 | - } | |
| 2319 | - } | |
| 2664 | + // Dynamic special page title depending on endpoint/action, only frontend. LP 2026-09-02 | |
| 2665 | + public function vipps_special_page_endpoint_title($title, $postid = 0) { | |
| 2666 | + global $wp_query; | |
| 2667 | + // Comment from woocommerce's wc_page_endpoint_title where this logic is from: LP 2026-09-02 | |
| 2320 | 2668 | |
| 2321 | - $consentremoval = $this->is_consent_removal(); | |
| 2322 | - if ($consentremoval) { | |
| 2323 | - remove_filter('template_redirect', 'redirect_canonical', 10); | |
| 2324 | - do_action('woo_vipps_before_handling_special_page', 'consentremoval'); | |
| 2325 | - if (! apply_filters('woo_vipps_special_page_handled', false, 'consentremoval')) { | |
| 2326 | - $this->vipps_consent_removal_callback($consentremoval); | |
| 2669 | + // In block themes the whole template (header, footer, content) renders inside the main | |
| 2670 | + // loop, so `the_title` fires for any post title rendered on the page (e.g. a product in a | |
| 2671 | + // server-rendered mini-cart) - not just the page's own heading. Only replace the title of | |
| 2672 | + // the queried page so an earlier title doesn't consume this one-shot filter. | |
| 2673 | + if ( ! is_null( $wp_query ) && ! is_admin() && is_main_query() && in_the_loop() && is_page() && $postid == static::get_special_page_id() ) { | |
| 2674 | + switch ($_GET['action'] ?? '') { | |
| 2675 | + case 'wait_for_payment': | |
| 2676 | + $title = __('Processing order', 'woo-vipps'); | |
| 2677 | + break; | |
| 2678 | + case 'do_express_checkout': | |
| 2679 | + case 'buy_product': | |
| 2680 | + $title = __('Express Checkout', 'woo-vipps'); | |
| 2681 | + break; | |
| 2327 | 2682 | } |
| 2328 | 2683 | } |
| 2684 | + return $title; | |
| 2329 | 2685 | } |
| 2686 | + | |
| 2330 | 2687 | // Template handling for special pages. IOK 2018-11-21 |
| 2688 | + // 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 | |
| 2331 | 2689 | public function template_include($template) { |
| 2332 | - $special = $this->is_special_page() ; | |
| 2333 | - if ($special) { | |
| 2690 | + if (static::is_special_page()) { | |
| 2334 | 2691 | // Get any special template override from the options IOK 2020-02-18 |
| 2335 | 2692 | $specific = $this->gateway()->get_option('vippsspecialpagetemplate'); |
| 2336 | 2693 | $found = locate_template($specific,false,false); |
| 2337 | 2694 | if ($found) $template=$found; |
| 2338 | 2695 | |
| 2339 | - return apply_filters('woo_vipps_special_page_template', $template, $special); | |
| 2696 | + return apply_filters('woo_vipps_special_page_template', $template, $_GET['action'] ?? ''); | |
| 2340 | 2697 | } |
| 2341 | 2698 | return $template; |
| 2342 | 2699 | } |
| 2343 | 2700 | |
| 2344 | - | |
| 2345 | - // Can't use wc-api for this, as that does not support DELETE . IOK 2018-05-18 | |
| 2346 | - private function is_consent_removal () { | |
| 2347 | - | |
| 2348 | - if ($_SERVER['REQUEST_METHOD'] != 'DELETE') return false; | |
| 2349 | - if ( !get_option('permalink_structure')) { | |
| 2350 | - if (@$_REQUEST['vipps-consent-removal']) return @$_REQUEST['callback']; | |
| 2351 | - return false; | |
| 2352 | - } | |
| 2353 | - if (preg_match("!/vipps-consent-removal/([^/]*)!", $_SERVER['REQUEST_URI'], $matches)) { | |
| 2354 | - return @$_REQUEST['callback']; | |
| 2355 | - } | |
| 2356 | - return false; | |
| 2357 | - } | |
| 2358 | - | |
| 2359 | 2701 | // On the thank you page, we have a completed order, so we need to restore any saved cart and possibly log in |
| 2360 | 2702 | // the user if using Express Checkout IOK 2020-10-09 |
| 2361 | 2703 | public function woocommerce_before_thankyou ($orderid) { |
| 2362 | 2704 | $order = wc_get_order($orderid); |
| @@ -2361,9 +2703,9 @@ | ||
| 2361 | 2703 | public function woocommerce_before_thankyou ($orderid) { |
| 2362 | 2704 | $order = wc_get_order($orderid); |
| 2363 | 2705 | if ($order) { |
| 2364 | 2706 | // Requires that this is express checkout and that 'create users on express checkout' is chosen. IOK 2020-10-09 |
| 2365 | - // -- 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 | |
| 2707 | + // -- or the same thing for Checkout. Also, the NHG code should not be running, and there is a filter, too. IOK 2023-08-04 | |
| 2366 | 2708 | $this->maybe_log_in_user($order); |
| 2367 | 2709 | $order->delete_meta_data('_vipps_limited_session'); |
| 2368 | 2710 | $order->save(); |
| 2369 | 2711 | |
| @@ -2424,9 +2766,8 @@ | ||
| 2424 | 2766 | |
| 2425 | 2767 | // Support adding pickup locations to any shipping rate using the 'woo_vipps_shipping_method_pickup_points' filter |
| 2426 | 2768 | // IOK 2025-11-19 |
| 2427 | 2769 | add_filter('woo_vipps_modify_express_checkout_rate', array($this, 'express_add_pickup_location_options'), 10, 4); |
| 2428 | - | |
| 2429 | 2770 | } |
| 2430 | 2771 | |
| 2431 | 2772 | public function get_payment_method_name() { |
| 2432 | 2773 | return $this->gateway()->get_option('payment_method_name'); |
| @@ -2446,14 +2787,13 @@ | ||
| 2446 | 2787 | public function after_setup_theme() { |
| 2447 | 2788 | // To facilitate development, allow loading the plugin-supplied translations. Must be called here at the earliest. |
| 2448 | 2789 | $ok = Vipps::load_plugin_textdomain('woo-vipps', false, basename( dirname( dirname( __FILE__ ) ) ) . "/languages"); |
| 2449 | 2790 | |
| 2450 | - // Vipps Checkout replaces the default checkout page, and currently uses its own page for this which needs to exist | |
| 2791 | + // Checkout replaces the default checkout page, and currently uses its own page for this which needs to exist | |
| 2451 | 2792 | // Will also probably be used to maintain a real utility-page for Vipps actions later for themes where this |
| 2452 | 2793 | // is important. |
| 2453 | 2794 | add_filter('woocommerce_create_pages', array($this, 'woocommerce_create_pages'), 50, 1); |
| 2454 | 2795 | |
| 2455 | - | |
| 2456 | 2796 | // Callbacks use the Woo API IOK 2018-05-18 |
| 2457 | 2797 | add_action( 'woocommerce_api_wc_gateway_vipps', array($this,'vipps_callback')); |
| 2458 | 2798 | add_action( 'woocommerce_api_vipps_shipping_details', array($this,'vipps_shipping_details_callback')); |
| 2459 | 2799 | |
| @@ -2464,9 +2804,9 @@ | ||
| 2464 | 2804 | add_action( 'woocommerce_cart_actions', array($this, 'cart_express_checkout_button')); |
| 2465 | 2805 | add_action( 'woocommerce_widget_shopping_cart_buttons', array($this, 'minicart_express_checkout_button'), 30); |
| 2466 | 2806 | |
| 2467 | 2807 | // Previously we added an express html banner to the action 'woocommerce_before_checkout_form.', |
| 2468 | - // replaced by the new express buttons in manner more like Gutenberg. LP 2026-03-23 | |
| 2808 | + // replaced by the new express buttons in manner more like Gutenberg. for grepping: "express legacy checkout". LP 2026-03-23 | |
| 2469 | 2809 | add_action('woocommerce_checkout_before_customer_details', array($this, 'checkout_before_customer_details_express'), 5); |
| 2470 | 2810 | |
| 2471 | 2811 | add_action('woocommerce_after_add_to_cart_button', array($this, 'single_product_buy_now_button')); |
| 2472 | 2812 | add_action('woocommerce_after_shop_loop_item', array($this, 'loop_single_product_buy_now_button'), 20); |
| @@ -2471,12 +2811,10 @@ | ||
| 2471 | 2811 | add_action('woocommerce_after_add_to_cart_button', array($this, 'single_product_buy_now_button')); |
| 2472 | 2812 | add_action('woocommerce_after_shop_loop_item', array($this, 'loop_single_product_buy_now_button'), 20); |
| 2473 | 2813 | |
| 2474 | 2814 | |
| 2475 | - // Special pages and callbacks handled by template_redirect | |
| 2476 | - // We must also notify WP and other plugins that we are handling this 404-like situation. IOK 2023-02-22 | |
| 2815 | + // Special pages and callbacks handled by template_redirect. IOK 2023-02-22 | |
| 2477 | 2816 | add_action('template_redirect', array($this,'template_redirect'),1); |
| 2478 | - add_action('pre_handle_404', array($this, 'pre_handle_404'), 1, 2); | |
| 2479 | 2817 | |
| 2480 | 2818 | // Allow overriding their templates |
| 2481 | 2819 | add_filter('template_include', array($this,'template_include'), 10, 1); |
| 2482 | 2820 | |
| @@ -2583,9 +2921,8 @@ | ||
| 2583 | 2921 | $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()); |
| 2584 | 2922 | $this->vippsJSConfig['vippslanguage'] = $this->get_customer_language(); |
| 2585 | 2923 | $this->vippsJSConfig['vippslocale'] = get_locale(); |
| 2586 | 2924 | $this->vippsJSConfig['vippsexpressbuttonurl'] = $this->get_payment_method_name(); |
| 2587 | - $this->vippsJSConfig['logoSvgUrl'] = $this->get_payment_logo('buy-now-block'); | |
| 2588 | 2925 | |
| 2589 | 2926 | |
| 2590 | 2927 | // If the site supports Gutenberg Blocks, support the Checkout block IOK 2020-08-10 |
| 2591 | 2928 | if (class_exists('Automattic\WooCommerce\Blocks\Payments\Integrations\AbstractPaymentMethodType')) { |
| @@ -2763,14 +3100,14 @@ | ||
| 2763 | 3100 | |
| 2764 | 3101 | $raw_post = @file_get_contents( 'php://input' ); |
| 2765 | 3102 | $result = @json_decode($raw_post,true); |
| 2766 | 3103 | |
| 2767 | - // This handler handles both Vipps Checkout and Vipps ECom IOK 2021-09-02 | |
| 3104 | + // This handler handles both Checkout and Vipps ECom IOK 2021-09-02 | |
| 2768 | 3105 | // .. and the epayment webhooks 2023-12-19 |
| 2769 | 3106 | $ischeckout = false; |
| 2770 | 3107 | $iswebhook = false; |
| 2771 | 3108 | $callback = isset($_REQUEST['callback']) ? $_REQUEST['callback'] : ""; |
| 2772 | - // For Vipps Checkout v3 and onwards, we control the callback so the type is just this field | |
| 3109 | + // For Checkout v3 and onwards, we control the callback so the type is just this field | |
| 2773 | 3110 | if ($callback == 'checkout') { |
| 2774 | 3111 | $ischeckout = true; |
| 2775 | 3112 | } |
| 2776 | 3113 | // For the webhooks, we will add 'webhook' to the result, but we also know that 'pspReference' will be present. IOK 2023-12-19 |
| @@ -3184,10 +3521,10 @@ | ||
| 3184 | 3521 | $this->log(sprintf(__("Wrong %1\$s Orderid on shipping details callback", 'woo-vipps'), $this->get_payment_method_name()), 'warning'); |
| 3185 | 3522 | exit(); |
| 3186 | 3523 | } |
| 3187 | 3524 | |
| 3188 | - // If we are doing this for Vipps Checkout after version 3, communicate to any shipping methods with | |
| 3189 | - // special support for Vipps Checkout that this is in fact happening. IOK 2023-01-19 | |
| 3525 | + // If we are doing this for Checkout after version 3, communicate to any shipping methods with | |
| 3526 | + // special support for Checkout that this is in fact happening. IOK 2023-01-19 | |
| 3190 | 3527 | // This needs to be done before "calculate totals". |
| 3191 | 3528 | // Moved from "vipps_shipping_details_callback_handler" because we need it before restoring sessions. IOK 2025-05-06 |
| 3192 | 3529 | $ischeckout = $order->get_meta('_vipps_checkout'); |
| 3193 | 3530 | |
| @@ -3363,9 +3700,9 @@ | ||
| 3363 | 3700 | ), 'debug'); |
| 3364 | 3701 | |
| 3365 | 3702 | } |
| 3366 | 3703 | |
| 3367 | - // Add shipping tax rates to the *order* so we can calculate this correctly when using Vipps Checkouts | |
| 3704 | + // Add shipping tax rates to the *order* so we can calculate this correctly when using Checkouts | |
| 3368 | 3705 | // 'dynamic pricing' 2023-01-26 |
| 3369 | 3706 | // Which may be deprecated, but anyway, for future use IOK 2025-08-14 |
| 3370 | 3707 | $taxrate = 0; |
| 3371 | 3708 | if (is_array($shipping_tax_rates) && !empty($shipping_tax_rates)) { |
| @@ -3491,9 +3828,9 @@ | ||
| 3491 | 3828 | $vippsmethod['shippingMethod'] = $rate->get_label(); |
| 3492 | 3829 | $vippsmethod['shippingMethodId'] = $key; |
| 3493 | 3830 | $vippsmethods[]=$vippsmethod; |
| 3494 | 3831 | |
| 3495 | - // Metadata and settings stored for later use for Vipps Checkout | |
| 3832 | + // Metadata and settings stored for later use for Checkout | |
| 3496 | 3833 | // and express checkout - basically, for each *key* have the corresponding object. IOK 2025-08-15 |
| 3497 | 3834 | // In the end, this data will be serialized and stored in the Order, and used in the gateways method set_order_shipping_details to |
| 3498 | 3835 | // finalize the order. IOK 2025-08-15 |
| 3499 | 3836 | $ratemap[$key]=$rate; |
| @@ -3511,9 +3848,9 @@ | ||
| 3511 | 3848 | // This then is the old Express Checkout format, which we have exposed in filters. IOK 2025-08-14 |
| 3512 | 3849 | $return = array('addressId'=>intval($addressid), 'orderId'=>$vippsorderid, 'shippingDetails'=>$vippsmethods); |
| 3513 | 3850 | $return = apply_filters('woo_vipps_vipps_formatted_shipping_methods', $return); // Mostly for debugging |
| 3514 | 3851 | |
| 3515 | - // IOK 2021-11-16 Vipps Checkout uses a slightly different syntax and format. | |
| 3852 | + // IOK 2021-11-16 Checkout uses a slightly different syntax and format. | |
| 3516 | 3853 | // IOK 2025-08-15 and new Express yet another slightly different format. |
| 3517 | 3854 | // IOK 2025-08-15 pass the ratemap as a reference, so transforms can update them |
| 3518 | 3855 | if ($ischeckout) { |
| 3519 | 3856 | $return = VippsCheckout::instance()->format_shipping_methods($return, $ratemap, $methodmap, $order); |
| @@ -3889,17 +4226,8 @@ | ||
| 3889 | 4226 | header("X-Accel-Expires: 0"); |
| 3890 | 4227 | } |
| 3891 | 4228 | |
| 3892 | 4229 | |
| 3893 | - | |
| 3894 | - // Handle DELETE on a vipps consent removal callback | |
| 3895 | - public function vipps_consent_removal_callback ($callback) { | |
| 3896 | - Vipps::nocache(); | |
| 3897 | - // Currently, no such requests will be posted, and as this code isn't sufficiently tested,we'll just have | |
| 3898 | - // to escape here when the API is changed. IOK 2020-10-14 | |
| 3899 | - $this->log("Consent removal is non-functional pending API changes as of 2020-10-14"); print "1"; exit(); | |
| 3900 | - } | |
| 3901 | - | |
| 3902 | 4230 | public function woocommerce_payment_gateways($methods) { |
| 3903 | 4231 | require_once(dirname(__FILE__) . "/WC_Gateway_Vipps.class.php"); |
| 3904 | 4232 | require_once(dirname(__FILE__) . "/WC_Gateway_VippsCard.class.php"); |
| 3905 | 4233 | // Protect the singleton: Use the object instead of the class name IOK 2025-02-04 |
| @@ -3974,9 +4302,9 @@ | ||
| 3974 | 4302 | if ($currentstatus != 'initiated') { |
| 3975 | 4303 | $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'); |
| 3976 | 4304 | return; |
| 3977 | 4305 | } |
| 3978 | - $this->check_status_of_pending_order($o, false, false); | |
| 4306 | + $this->check_status_of_pending_order($o, false); | |
| 3979 | 4307 | } |
| 3980 | 4308 | } |
| 3981 | 4309 | |
| 3982 | 4310 | // 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 - |
| @@ -3981,30 +4309,35 @@ | ||
| 3981 | 4309 | |
| 3982 | 4310 | // 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 - |
| 3983 | 4311 | // e.g. wp-cron. IOK 2021-06-21 |
| 3984 | 4312 | // Stop restoring session in wp-cron too. IOK 2021-08-23 |
| 3985 | - public function check_status_of_pending_order($order, $maybe_restore_session=0, $allow_retry=true) { | |
| 3986 | - $express = $order->get_meta('_vipps_express_checkout'); | |
| 3987 | - $vippstatus = $order->get_meta('_vipps_status'); | |
| 3988 | - if ($express && $maybe_restore_session) { | |
| 3989 | - $this->log(sprintf(__("Restoring session of order %1\$d", 'woo-vipps'), $order->get_id()), 'debug'); | |
| 3990 | - $this->callback_restore_session($order->get_id()); | |
| 3991 | - } | |
| 4313 | + // Stop restoring session in wp-cron again(?) since we now use a rest endpoint to handle shipping. LP 2026-05-13 | |
| 4314 | + public function check_status_of_pending_order($order, $allow_retry=true) { | |
| 3992 | 4315 | $gw = $this->gateway(); |
| 3993 | 4316 | |
| 3994 | 4317 | $order_status = null; |
| 3995 | 4318 | try { |
| 3996 | 4319 | $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())); |
| 3997 | - $order_status = $gw->callback_check_order_status($order, $allow_retry); | |
| 4320 | + | |
| 4321 | + // Poll status and correct woo status. LP 2026-05-19 | |
| 4322 | + $order_data = $gw->get_payment_details($order); | |
| 4323 | + | |
| 4324 | + // If we already know the order failed, we don't need to process the order further below. LP 2026-05-19 | |
| 4325 | + if ('CANCEL' === ($order_data['state'] ?? "")) { | |
| 4326 | + /* translators: company name */ | |
| 4327 | + $order->update_status('cancelled', sprintf(__('Payment cancelled at %1$s.', 'woo-vipps'), Vipps::CompanyName())); | |
| 4328 | + return; | |
| 4329 | + } | |
| 4330 | + | |
| 4331 | + $gw->set_order_status_by_payment_details($order, $order_data, $allow_retry); | |
| 4332 | + $order = wc_get_order($order->get_id()); // refresh order if changed. LP 2026-05-13 | |
| 4333 | + $order_status = $order->get_status(); | |
| 4334 | + | |
| 3998 | 4335 | $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'); |
| 3999 | 4336 | } catch (Exception $e) { |
| 4000 | 4337 | $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'); |
| 4001 | 4338 | $this->log($e->getMessage() . "\n" . $order->get_id(), 'error'); |
| 4002 | 4339 | } |
| 4003 | - // Ensure we don't keep using an old session for more than one order here. | |
| 4004 | - if ($express && $maybe_restore_session) { | |
| 4005 | - $this->callback_destroy_session(); | |
| 4006 | - } | |
| 4007 | 4340 | return $order_status; |
| 4008 | 4341 | } |
| 4009 | 4342 | |
| 4010 | 4343 | // 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 |
| @@ -4017,20 +4350,40 @@ | ||
| 4017 | 4350 | } |
| 4018 | 4351 | } |
| 4019 | 4352 | |
| 4020 | 4353 | public function activate () { |
| 4021 | - static::maybe_add_cron_event(); | |
| 4022 | - $gw = $this->gateway(); | |
| 4354 | + static::maybe_add_cron_event(); | |
| 4355 | + $gw = $this->gateway(); | |
| 4023 | 4356 | |
| 4024 | - // If store is using the default "Woo" orderprefix, generate a new one, this time using the stores' sitename if possible. IOK 2020-05-19 | |
| 4025 | - if ($gw->get_option('orderprefix') == 'Woo') { | |
| 4026 | - $gw->update_option('orderprefix', $this->generate_order_prefix()); | |
| 4027 | - } | |
| 4028 | - // IOK 2023-12-20 for the epayment api, we need to re-initialize webhooks at this point. | |
| 4029 | - $gw->initialize_webhooks(); | |
| 4030 | - $this->payment_method_name = $gw->get_option('payment_method_name'); | |
| 4031 | - } | |
| 4357 | + // If store is using the default "Woo" orderprefix, generate a new one, this time using the stores' sitename if possible. IOK 2020-05-19 | |
| 4358 | + if ($gw->get_option('orderprefix') == 'Woo') { | |
| 4359 | + $gw->update_option('orderprefix', $this->generate_order_prefix()); | |
| 4360 | + } | |
| 4361 | + // IOK 2023-12-20 for the epayment api, we need to re-initialize webhooks at this point. | |
| 4362 | + $gw->initialize_webhooks(); | |
| 4363 | + $this->payment_method_name = $gw->get_option('payment_method_name'); | |
| 4032 | 4364 | |
| 4365 | + | |
| 4366 | + // Check if the special page is noted and actually does exist | |
| 4367 | + $special = static::get_special_page_id(); | |
| 4368 | + if ($special) { | |
| 4369 | + $special_page = get_post($special); | |
| 4370 | + if ($special_page && 'trash' !== $special_page->post_status) { | |
| 4371 | + // Ensure this page has the necessary shortcode. LP 2026-09-01 | |
| 4372 | + if (!has_shortcode($special_page->post_content, 'vipps_special_page')) { | |
| 4373 | + $new_content = $special_page->post_content . "\n\n<!-- wp:shortcode -->[vipps_special_page]<!-- /wp:shortcode -->"; | |
| 4374 | + wp_update_post([ | |
| 4375 | + 'ID' => $special, | |
| 4376 | + 'post_content' => $new_content, | |
| 4377 | + ]); | |
| 4378 | + } | |
| 4379 | + } else { | |
| 4380 | + delete_option('woocommerce_vipps_special_page_page_id'); | |
| 4381 | + } | |
| 4382 | + } | |
| 4383 | + | |
| 4384 | + } | |
| 4385 | + | |
| 4033 | 4386 | // We have added some hooks to wp-cron; remove these. IOK 2020-04-01 |
| 4034 | 4387 | public static function deactivate() { |
| 4035 | 4388 | $timestamp = wp_next_scheduled('vipps_cron_cleanup_hook'); |
| 4036 | 4389 | wp_unschedule_event($timestamp, 'vipps_cron_cleanup_hook'); |
| @@ -4043,9 +4396,9 @@ | ||
| 4043 | 4396 | // Delete all settings if checked in settings menu. LP 2025-10-06 |
| 4044 | 4397 | $should_delete = $gw->get_option( 'delete_settings_on_deactivation' ) === 'yes'; |
| 4045 | 4398 | if ($should_delete) { |
| 4046 | 4399 | // Delete options. |
| 4047 | - $options = ['woocommerce_vipps_settings', 'woocommerce_vipps_card_settings', 'woo-vipps-configured', 'vipps_badge_options', 'vipps_button_options', '_vipps_dismissed_notices', 'woo_vipps_checkout_activated']; | |
| 4400 | + $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']; | |
| 4048 | 4401 | foreach($options as $option) { |
| 4049 | 4402 | delete_option($option); |
| 4050 | 4403 | } |
| 4051 | 4404 | } |
| @@ -4083,9 +4436,10 @@ | ||
| 4083 | 4436 | // If setting is true, use Vipps as default payment. Called by the woocommrece_cart_updated hook. IOK 2018-06-06 |
| 4084 | 4437 | private function maybe_set_vipps_as_default() { |
| 4085 | 4438 | if (WC()->session->get('chosen_payment_method')) return; // User has already chosen payment method, so we're done. |
| 4086 | 4439 | $gw = $this->gateway(); |
| 4087 | - if ($gw->get_option('vippsdefault')=='yes') { | |
| 4440 | + // Do *not* default to vipps if Kustom Checkout is installed IOK 2026-09-11 | |
| 4441 | + if ($gw->get_option('vippsdefault')=='yes' && !class_exists('KCO')) { | |
| 4088 | 4442 | WC()->session->set('chosen_payment_method', $gw->id); |
| 4089 | 4443 | } |
| 4090 | 4444 | } |
| 4091 | 4445 | |
| @@ -4190,13 +4544,13 @@ | ||
| 4190 | 4544 | if (is_user_logged_in()) return; |
| 4191 | 4545 | if (!$order || ! self::is_vipps_order($order)) return; |
| 4192 | 4546 | |
| 4193 | 4547 | // We *do* want to log in express checkout customers, but not those that |
| 4194 | - // use the Vipps Checkout solution - those can change their emails in the | |
| 4548 | + // use the Checkout solution - those can change their emails in the | |
| 4195 | 4549 | // checkout screen. IOK 2021-09-03 |
| 4196 | 4550 | $do_login = $order->get_meta('_vipps_express_checkout'); |
| 4197 | 4551 | |
| 4198 | - // We will not log in Vipps Checkout users unless the option for that is true | |
| 4552 | + // We will not log in Checkout users unless the option for that is true | |
| 4199 | 4553 | if ($order->get_meta('_vipps_checkout') && 'yes' != $this->gateway()->get_option('checkoutcreateuser')) { |
| 4200 | 4554 | $do_login = false; |
| 4201 | 4555 | } |
| 4202 | 4556 | |
| @@ -4231,9 +4585,9 @@ | ||
| 4231 | 4585 | |
| 4232 | 4586 | // Both Checkout and Express Checkout have the below value set to true |
| 4233 | 4587 | if (!$order->get_meta('_vipps_express_checkout')) return; |
| 4234 | 4588 | |
| 4235 | - // Creating/logging in users are handled separately for Vipps Checkout and Express Checkout, so check the correct setting | |
| 4589 | + // Creating/logging in users are handled separately for Checkout and Express Checkout, so check the correct setting | |
| 4236 | 4590 | // IOK 2023-07-27 |
| 4237 | 4591 | $ischeckout = $order->get_meta('_vipps_checkout'); |
| 4238 | 4592 | if ($ischeckout) { |
| 4239 | 4593 | if ($this->gateway()->get_option('checkoutcreateuser') != 'yes') return null; |
| @@ -4618,9 +4972,9 @@ | ||
| 4618 | 4972 | $ok = wc()->shipping->register_shipping_method( new Automattic\WooCommerce\Blocks\Shipping\PickupLocation() ); |
| 4619 | 4973 | } |
| 4620 | 4974 | } |
| 4621 | 4975 | |
| 4622 | - // Vipps Checkout and Express Checkout allows loading specific kinds of shipping methods with non-standard APIs, such as PickupLocations. IOK 2025-05-08 | |
| 4976 | + // Checkout and Express Checkout allows loading specific kinds of shipping methods with non-standard APIs, such as PickupLocations. IOK 2025-05-08 | |
| 4623 | 4977 | // Must be called *early*. IOK 2025-05-08. Called in callback methods, and if using static shipping, in the 'start session' callback. |
| 4624 | 4978 | public function load_extra_shipping_methods($order, $addressdata, $ischeckout=false) { |
| 4625 | 4979 | // 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 |
| 4626 | 4980 | add_action('woocommerce_load_shipping_methods', function () use ($order, $addressdata) { |
| @@ -4691,46 +5045,37 @@ | ||
| 4691 | 5045 | wp_send_json(array('status'=>'error', 'msg'=> __('Unknown payment status','woo-vipps') . ' ' . $payment)); |
| 4692 | 5046 | return false; |
| 4693 | 5047 | } |
| 4694 | 5048 | |
| 4695 | - // The various return URLs for special pages of the Vipps stuff depend on settings and pretty-URLs so we supply them from here | |
| 4696 | - // These are for the "fallback URL" mostly. IOK 2018-05-18 | |
| 4697 | - private function make_vipps_url($what) { | |
| 4698 | - if ( !get_option('permalink_structure')) { | |
| 4699 | - return add_query_arg('VippsSpecialPage', $what, home_url("/", 'https')); | |
| 4700 | - } | |
| 4701 | - return trailingslashit(home_url($what, 'https')); | |
| 5049 | + // 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 | |
| 5050 | + private function make_special_page_url($action) { | |
| 5051 | + return add_query_arg('action', $action, $this->get_special_page_url()); | |
| 4702 | 5052 | } |
| 5053 | + | |
| 4703 | 5054 | public function payment_return_url() { |
| 4704 | - return apply_filters('woo_vipps_payment_return_url', $this->make_vipps_url('vipps-betaling')); | |
| 5055 | + return apply_filters('woo_vipps_payment_return_url', $this->make_special_page_url('wait_for_payment')); | |
| 4705 | 5056 | } |
| 4706 | 5057 | public function express_checkout_url() { |
| 4707 | - return $this->make_vipps_url('vipps-express-checkout'); | |
| 5058 | + return $this->make_special_page_url('do_express_checkout'); | |
| 4708 | 5059 | } |
| 4709 | 5060 | public function buy_product_url() { |
| 4710 | - return $this->make_vipps_url('vipps-buy-product'); | |
| 5061 | + return $this->make_special_page_url('buy_product'); | |
| 4711 | 5062 | } |
| 4712 | 5063 | |
| 4713 | - // Return the method in the Vipps | |
| 4714 | - public function is_special_page() { | |
| 4715 | - $specials = array('vipps-betaling' => 'vipps_wait_for_payment', 'vipps-express-checkout'=>'vipps_express_checkout', 'vipps-buy-product'=>'vipps_buy_product'); | |
| 4716 | - $method = null; | |
| 4717 | - if ( get_option('permalink_structure')) { | |
| 4718 | - foreach($specials as $special=>$specialmethod) { | |
| 4719 | - // IOK 2018-06-07 Change to add any prefix from home-url for better matching IOK 2018-06-07 | |
| 4720 | - $path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); | |
| 4721 | - if ($path && preg_match("!/$special/?$!", $path, $matches)) { | |
| 4722 | - $method = $specialmethod; break; | |
| 4723 | - } | |
| 4724 | - } | |
| 4725 | - } else { | |
| 4726 | - if (isset($_GET['VippsSpecialPage'])) { | |
| 4727 | - $method = @$specials[$_GET['VippsSpecialPage']]; | |
| 4728 | - } | |
| 4729 | - } | |
| 4730 | - return $method; | |
| 5064 | + public static function is_special_page() { | |
| 5065 | + $id = static::get_special_page_id(); | |
| 5066 | + return $id && is_page($id); | |
| 4731 | 5067 | } |
| 4732 | 5068 | |
| 5069 | + public static function get_special_page_id() { | |
| 5070 | + $id = wc_get_page_id('vipps_special_page'); // -1 if not found | |
| 5071 | + return $id > 0 ? $id : null; | |
| 5072 | + } | |
| 5073 | + | |
| 5074 | + public static function get_special_page_url() { | |
| 5075 | + return get_permalink(static::get_special_page_id()); | |
| 5076 | + } | |
| 5077 | + | |
| 4733 | 5078 | // Just create a spinner and a overlay. |
| 4734 | 5079 | public function spinner () { |
| 4735 | 5080 | $flavour = sanitize_title($this->get_payment_method_name()); |
| 4736 | 5081 | ob_start(); |
| @@ -4751,164 +5096,22 @@ | ||
| 4751 | 5096 | return apply_filters('woo_vipps_spinner', ob_get_clean()); |
| 4752 | 5097 | } |
| 4753 | 5098 | |
| 4754 | 5099 | |
| 4755 | - // Returns express logo images depending on parameters, these are the new express svgs received 2025-12-12. | |
| 4756 | - // Fallbacks to defaults for each payment method. LP 2025-12-15 | |
| 4757 | - public function get_express_logo($payment_method, $lang, $variant) { | |
| 4758 | - $base = plugins_url('img', __FILE__); | |
| 4759 | - | |
| 4760 | - // 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 | |
| 4761 | - $img_map = [ | |
| 4762 | - "vipps" => [ | |
| 4763 | - "default" => "$base/vipps/express/en/buy-now-vipps-en-rectangular.svg", | |
| 4764 | - "default-mini" => "$base/vipps/express/en/express-vipps-en-rectangular-mini.svg", | |
| 4765 | - "en" => [ | |
| 4766 | - "default" => "$base/vipps/express/en/buy-now-vipps-en-rectangular.svg", | |
| 4767 | - "default-mini" => "$base/vipps/express/en/express-vipps-en-rectangular-mini.svg", | |
| 4768 | - "buy-now-rectangular" => "$base/vipps/express/en/buy-now-vipps-en-rectangular.svg", | |
| 4769 | - "buy-now-pill" => "$base/vipps/express/en/buy-now-vipps-en-pill.svg", | |
| 4770 | - "express-rectangular" => "$base/vipps/express/en/express-vipps-en-rectangular.svg", | |
| 4771 | - "express-rectangular-mini" => "$base/vipps/express/en/express-vipps-en-rectangular-mini.svg", | |
| 4772 | - "express-pill" => "$base/vipps/express/en/express-vipps-en-pill.svg", | |
| 4773 | - "express-pill-mini" => "$base/vipps/express/en/express-vipps-en-pill-mini.svg", | |
| 4774 | - | |
| 4775 | - ], | |
| 4776 | - "no" => [ | |
| 4777 | - "default" => "$base/vipps/express/no/kjop-na-vipps-no-rectangular.svg", | |
| 4778 | - "default-mini" => "$base/vipps/express/no/ekspress-vipps-no-rectangular-mini.svg", | |
| 4779 | - "buy-now-rectangular" => "$base/vipps/express/no/kjop-na-vipps-no-rectangular.svg", | |
| 4780 | - "buy-now-pill" => "$base/vipps/express/no/kjop-na-vipps-no-pill.svg", | |
| 4781 | - "express-rectangular" => "$base/vipps/express/no/ekspress-vipps-no-rectangular.svg", | |
| 4782 | - "express-rectangular-mini" => "$base/vipps/express/no/ekspress-vipps-no-rectangular-mini.svg", | |
| 4783 | - "express-pill" => "$base/vipps/express/no/ekspress-vipps-no-pill.svg", | |
| 4784 | - "express-pill-mini" => "$base/vipps/express/no/ekspress-vipps-no-pill-mini.svg", | |
| 4785 | - ], | |
| 4786 | - "se" => [ | |
| 4787 | - "default" => "$base/vipps/express/se/kop-nu-vipps-se-rectangular.svg", | |
| 4788 | - "default-mini" => "$base/vipps/express/se/express-vipps-se-rectangular-mini.svg", | |
| 4789 | - "buy-now-rectangular" => "$base/vipps/express/se/kop-nu-vipps-se-rectangular.svg", | |
| 4790 | - "buy-now-pill" => "$base/vipps/express/se/kop-nu-vipps-se-pill.svg", | |
| 4791 | - "express-rectangular" => "$base/vipps/express/se/express-vipps-se-rectangular.svg", | |
| 4792 | - "express-rectangular-mini" => "$base/vipps/express/se/express-vipps-se-rectangular-mini.svg", | |
| 4793 | - "express-pill" => "$base/vipps/express/se/express-vipps-se-pill.svg", | |
| 4794 | - "express-pill-mini" => "$base/vipps/express/se/express-vipps-se-pill-mini.svg", | |
| 4795 | - | |
| 4796 | - ], | |
| 4797 | - ], | |
| 4798 | - "mobilepay" => [ | |
| 4799 | - "default" => "$base/mobilepay/express/en/buy-now-mp-en-rectangular.svg", | |
| 4800 | - "default-mini" => "$base/mobilepay/express/en/express-mp-en-rectangular-mini.svg", | |
| 4801 | - "en" => [ | |
| 4802 | - "default" => "$base/mobilepay/express/en/buy-now-mp-en-rectangular.svg", | |
| 4803 | - "default-mini" => "$base/mobilepay/express/en/express-mp-en-rectangular-mini.svg", | |
| 4804 | - "buy-now-rectangular" => "$base/mobilepay/express/en/buy-now-mp-en-rectangular.svg", | |
| 4805 | - "buy-now-pill" => "$base/mobilepay/express/en/buy-now-mp-en-pill.svg", | |
| 4806 | - "express-rectangular" => "$base/mobilepay/express/en/express-mp-en-rectangular.svg", | |
| 4807 | - "express-rectangular-mini" => "$base/mobilepay/express/en/express-mp-en-rectangular-mini.svg", | |
| 4808 | - "express-pill" => "$base/mobilepay/express/en/express-mp-en-pill.svg", | |
| 4809 | - "express-pill-mini" => "$base/mobilepay/express/en/express-mp-en-pill-mini.svg", | |
| 4810 | - | |
| 4811 | - ], | |
| 4812 | - "dk" => [ | |
| 4813 | - "default" => "$base/mobilepay/express/dk/kob-nu-mp-dk-rectangular.svg", | |
| 4814 | - "default-mini" => "$base/mobilepay/express/dk/express-mp-dk-rectangular-mini.svg", | |
| 4815 | - "buy-now-rectangular" => "$base/mobilepay/express/dk/kob-nu-mp-dk-rectangular.svg", | |
| 4816 | - "buy-now-pill" => "$base/mobilepay/express/dk/kob-nu-mp-dk-pill.svg", | |
| 4817 | - "express-rectangular" => "$base/mobilepay/express/dk/express-mp-dk-rectangular.svg", | |
| 4818 | - "express-rectangular-mini" => "$base/mobilepay/express/dk/express-mp-dk-rectangular-mini.svg", | |
| 4819 | - "express-pill" => "$base/mobilepay/express/dk/express-mp-dk-pill.svg", | |
| 4820 | - "express-pill-mini" => "$base/mobilepay/express/dk/express-mp-dk-pill-mini.svg", | |
| 4821 | - ], | |
| 4822 | - "fi" => [ | |
| 4823 | - "default" => "$base/mobilepay/express/fi/osta-nyt-mp-fi-rectangular.svg", | |
| 4824 | - "default-mini" => "$base/mobilepay/express/fi/express-mp-fi-rectangular-mini.svg", | |
| 4825 | - "buy-now-rectangular" => "$base/mobilepay/express/fi/osta-nyt-mp-fi-rectangular.svg", | |
| 4826 | - "buy-now-pill" => "$base/mobilepay/express/fi/osta-nyt-mp-fi-pill.svg", | |
| 4827 | - "express-rectangular" => "$base/mobilepay/express/fi/express-mp-fi-rectangular.svg", | |
| 4828 | - "express-rectangular-mini" => "$base/mobilepay/express/fi/express-mp-fi-rectangular-mini.svg", | |
| 4829 | - "express-pill" => "$base/mobilepay/express/fi/express-mp-fi-pill.svg", | |
| 4830 | - "express-pill-mini" => "$base/mobilepay/express/fi/express-mp-fi-pill-mini.svg", | |
| 4831 | - | |
| 4832 | - ], | |
| 4833 | - ], | |
| 4834 | - | |
| 4835 | - ]; | |
| 4836 | - | |
| 4837 | - $payment = strtolower($payment_method); | |
| 4838 | - if ($lang === 'store') $lang = $this->get_customer_language(); | |
| 4839 | - | |
| 4840 | - // Dont give a default if payment method not found. LP 2025-12-12 | |
| 4841 | - if (!array_key_exists($payment, $img_map)) { | |
| 4842 | - return null; | |
| 4843 | - } | |
| 4844 | - $payment_map = $img_map[$payment]; | |
| 4845 | - | |
| 4846 | - $img = null; | |
| 4847 | - if (array_key_exists($lang, $payment_map) | |
| 4848 | - && is_array($payment_map[$lang]) | |
| 4849 | - && array_key_exists($variant, $payment_map[$lang])) { | |
| 4850 | - $img = @$payment_map[$lang][$variant]; | |
| 4851 | - } | |
| 4852 | - | |
| 4853 | - // Default fallback behaviour | |
| 4854 | - if (!$img) { | |
| 4855 | - $default = str_ends_with($variant, '-mini') ? 'default-mini' : 'default'; | |
| 4856 | - | |
| 4857 | - // First try getting default for payment method + language. LP 2026-01-16 | |
| 4858 | - if (array_key_exists($lang, $payment_map) && is_array($payment_map[$lang])) { | |
| 4859 | - /* translators: %1= payment method name, %2 = language string, %3 = variant name */ | |
| 4860 | - $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'); | |
| 4861 | - $img = @$payment_map[$lang][$default]; | |
| 4862 | - } | |
| 4863 | - | |
| 4864 | - // If not found, then try global default for payment method. LP 2026-01-16 | |
| 4865 | - if (!$img) { | |
| 4866 | - $img = @$payment_map[$default]; | |
| 4867 | - } | |
| 4868 | - | |
| 4869 | - // Found no logo at all, log this. LP 2026-01-16 | |
| 4870 | - if (!$img) { | |
| 4871 | - /* translators: %1= payment method name, %2 = language string, %3 = variant name */ | |
| 4872 | - $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'); | |
| 4873 | - } | |
| 4874 | - } | |
| 4875 | - return $img; | |
| 5100 | + // DEPRECATED: Legacy function as of using new web component buttons. LP 2026-06-26 | |
| 5101 | + // NB: previously this returned the url to a svg logo. We don't do this anymore, so it returns html. LP 2026-06-30 | |
| 5102 | + public function get_express_logo($_payment_method = null, $_lang = null, $_variant = null, $context = 'global') { | |
| 5103 | + return $this->get_html_button_for_context($context); | |
| 4876 | 5104 | } |
| 4877 | 5105 | |
| 5106 | + // DEPRECATED: Legacy function as of using new web component buttons. LP 2026-06-26 | |
| 4878 | 5107 | // Get payment logo based on payment method, then language NT 2023-11-30 |
| 4879 | - // and based on custom variant setting. $page is the page origin slug, e.g 'cart', 'product'. LP 2025-12-15 | |
| 4880 | - public function get_payment_logo($page = null) { | |
| 4881 | - $lang = $this->get_customer_language(); | |
| 4882 | - $payment_method = $this->get_payment_method_name(); | |
| 4883 | - $variant = $this->get_express_logo_page_variant($page); | |
| 4884 | - $logo_url = $this->get_express_logo($payment_method, $lang, $variant); | |
| 4885 | - return $logo_url; | |
| 5108 | + // and based on custom variant setting. $context is where it is to be used, e.g 'cart', 'product'. LP 2025-12-15 | |
| 5109 | + // NB: previously this returned the url to a svg logo. We don't do this anymore, so it returns html. LP 2026-06-30 | |
| 5110 | + public function get_payment_logo($context = 'global') { | |
| 5111 | + return $this->get_express_logo(null, null, null, $context); | |
| 4886 | 5112 | } |
| 4887 | 5113 | |
| 4888 | - /** Returns the correct variant to use for the given page, found from the wp option. LP 2025-12-23 */ | |
| 4889 | - private function get_express_logo_page_variant($page = null) { | |
| 4890 | - $options = get_option('vipps_button_options'); | |
| 4891 | - | |
| 4892 | - // Init defaults, use mini version by default in below pages. LP 2025-12-17 | |
| 4893 | - $use_mini = in_array($page, ['catalog']); | |
| 4894 | - $variant = ""; | |
| 4895 | - | |
| 4896 | - // Find correct variant from button settings. LP 2025-12-17 | |
| 4897 | - if (is_array($options) && array_key_exists('express', $options)) { | |
| 4898 | - if (array_key_exists($page, $options['express']['force-mini'])) { | |
| 4899 | - $use_mini = sanitize_title($options['express']['force-mini'][$page]) === 'yes'; | |
| 4900 | - } | |
| 4901 | - $key = $use_mini ? 'mini-variant' : 'variant'; | |
| 4902 | - $variant = sanitize_title($options['express'][$key]) ?? ''; | |
| 4903 | - } | |
| 4904 | - | |
| 4905 | - if (!$variant) { | |
| 4906 | - $variant = $use_mini ? "default-mini" : "default"; | |
| 4907 | - } | |
| 4908 | - return apply_filters('woo_vipps_express_button_page_variant', $variant, $page); | |
| 4909 | - } | |
| 4910 | - | |
| 4911 | 5114 | // Get express banner logo based on payment method. LP 2025-09-03 |
| 4912 | 5115 | private function get_express_banner_logo() { |
| 4913 | 5116 | $payment_method = $this->get_payment_method_name(); |
| 4914 | 5117 | |
| @@ -4919,10 +5122,12 @@ | ||
| 4919 | 5122 | } |
| 4920 | 5123 | return null; |
| 4921 | 5124 | } |
| 4922 | 5125 | |
| 4923 | - // Get buy now button by manually selecting logo variant and language. LP 2026-01-16 | |
| 4924 | - public function get_buy_now_button_manual($product_id,$variation_id=null,$sku=null,$disabled=false, $classes='', $logo_variant=null, $logo_lang=null) { | |
| 5126 | + // Code that will generate various versions of the 'buy now with Vipps' button IOK 2018-09-27 | |
| 5127 | + // $context is slug describing where its to be used, like 'catalog', 'cart', 'product' etc. and will | |
| 5128 | + // be used unless $button_args_override is nonempty. See init_button_options() and get_html_button() LP 2026-06-26 | |
| 5129 | + public function get_buy_now_button($product_id,$variation_id=null,$sku=null,$disabled=false, $classes='', $context='global', $button_args_override = []) { | |
| 4925 | 5130 | $disabled = $disabled ? 'disabled' : ''; |
| 4926 | 5131 | $data = array(); |
| 4927 | 5132 | |
| 4928 | 5133 | // Support directly using the variant id as $product_id with no $variation_id. LP 2026-01-23 |
| @@ -4937,9 +5142,8 @@ | ||
| 4937 | 5142 | if ($sku) $data['product_sku'] = $sku; |
| 4938 | 5143 | if ($product_id) $data['product_id'] = $product_id; |
| 4939 | 5144 | if ($variation_id) $data['variation_id'] = $variation_id; |
| 4940 | 5145 | |
| 4941 | - | |
| 4942 | 5146 | $buttoncode = "<a href='javascript:void(0)' $disabled "; |
| 4943 | 5147 | foreach($data as $key=>$value) { |
| 4944 | 5148 | $value = esc_attr($value); |
| 4945 | 5149 | $buttoncode .= " data-$key='$value' "; |
| @@ -4946,14 +5150,18 @@ | ||
| 4946 | 5150 | } |
| 4947 | 5151 | |
| 4948 | 5152 | $payment_method = $this->get_payment_method_name(); |
| 4949 | 5153 | $title = sprintf(__('Buy now with %1$s', 'woo-vipps'), $payment_method); |
| 4950 | - $short = str_ends_with($logo_variant, 'mini'); | |
| 4951 | - $logo = $this->get_express_logo($payment_method, $logo_lang, $logo_variant); | |
| 4952 | 5154 | |
| 4953 | - $message =" <img border=0 src='$logo' alt='$payment_method'/>"; | |
| 5155 | + if (is_array($button_args_override) && $button_args_override) { | |
| 5156 | + $button_args = $button_args_override; | |
| 5157 | + } else { | |
| 5158 | + $button_args = $this->get_html_button_attrs_for_context($context); | |
| 5159 | + } | |
| 5160 | + $short = ($button_args['compact'] ?? 'false') === 'true'; | |
| 5161 | + $button = $this->get_html_button($button_args); | |
| 4954 | 5162 | |
| 4955 | -# Extra classes, if passed IOK 2019-02-26 | |
| 5163 | + # Extra classes, if passed IOK 2019-02-26 | |
| 4956 | 5164 | if (is_array($classes)) { |
| 4957 | 5165 | $classes = join(" ", $classes); |
| 4958 | 5166 | } |
| 4959 | 5167 | if ($classes) $classes = " $classes"; |
| @@ -4958,19 +5166,15 @@ | ||
| 4958 | 5166 | } |
| 4959 | 5167 | if ($classes) $classes = " $classes"; |
| 4960 | 5168 | if ($short) $classes = "short $classes"; |
| 4961 | 5169 | |
| 4962 | - $buttoncode .= " class='single-product button vipps-buy-now $payment_method $disabled$classes' title='$title'>$message</a>"; | |
| 5170 | + $buttoncode .= " class='single-product button vipps-buy-now $payment_method $disabled$classes' title='$title'>$button</a>"; | |
| 5171 | + | |
| 5172 | + | |
| 5173 | + | |
| 4963 | 5174 | return apply_filters('woo_vipps_buy_now_button', $buttoncode, $product_id, $variation_id, $sku, $disabled); |
| 4964 | 5175 | } |
| 4965 | 5176 | |
| 4966 | - // Code that will generate various versions of the 'buy now with Vipps' button IOK 2018-09-27 | |
| 4967 | - public function get_buy_now_button($product_id,$variation_id=null,$sku=null,$disabled=false, $classes='', $page=null) { | |
| 4968 | - $logo_lang = $this->get_customer_language(); | |
| 4969 | - $logo_variant = $this->get_express_logo_page_variant($page); | |
| 4970 | - return $this->get_buy_now_button_manual($product_id, $variation_id, $sku, $disabled, $classes, $logo_variant, $logo_lang); | |
| 4971 | - } | |
| 4972 | - | |
| 4973 | 5177 | // Display a 'buy now with express checkout' button on the product page IOK 2018-09-27 |
| 4974 | 5178 | public function single_product_buy_now_button () { |
| 4975 | 5179 | $gw = $this->gateway(); |
| 4976 | 5180 | $how = $gw->get_option('singleproductexpress'); |
| @@ -5051,43 +5255,90 @@ | ||
| 5051 | 5255 | } |
| 5052 | 5256 | |
| 5053 | 5257 | |
| 5054 | 5258 | |
| 5055 | - // Vipps Checkout replaces the default checkout page, and currently uses its own page for this which needs to exist | |
| 5259 | + // Checkout replaces the default checkout page, and currently uses its own page for this which needs to exist | |
| 5056 | 5260 | // IOK 2026-04-30 remove this when checkout is end-of-life'd |
| 5261 | + // We now also use this for the vipps special page, previously a fakepage. LP 2026-08-18 | |
| 5057 | 5262 | public function woocommerce_create_pages ($data) { |
| 5263 | + // Vipps Checkout page | |
| 5058 | 5264 | $vipps_checkout_activated = get_option('woo_vipps_checkout_activated', false); |
| 5059 | - if (!$vipps_checkout_activated) return $data; | |
| 5265 | + if ($vipps_checkout_activated) { | |
| 5266 | + $data['vipps_checkout'] = array( | |
| 5267 | + 'name' => _x( 'vipps_checkout', 'Page slug', 'woo-vipps' ), | |
| 5268 | + 'title' => _x( 'Vipps MobilePay Checkout', 'Page title', 'woo-vipps' ), | |
| 5269 | + 'content' => '<!-- wp:shortcode -->[' . 'vipps_checkout' . ']<!-- /wp:shortcode -->', | |
| 5270 | + ); | |
| 5271 | + } | |
| 5060 | 5272 | |
| 5061 | - $data['vipps_checkout'] = array( | |
| 5062 | - 'name' => _x( 'vipps_checkout', 'Page slug', 'woo-vipps' ), | |
| 5063 | - 'title' => _x( 'Vipps MobilePay Checkout', 'Page title', 'woo-vipps' ), | |
| 5064 | - 'content' => '<!-- wp:shortcode -->[' . 'vipps_checkout' . ']<!-- /wp:shortcode -->', | |
| 5065 | - ); | |
| 5066 | - | |
| 5273 | + // Vipps special page for certain payment flow actions. Previously a fake page. LP 2026-08-18 | |
| 5274 | + $data['vipps_special_page'] = [ | |
| 5275 | + 'name' => 'vipps-payment', // slug | |
| 5276 | + /* translators: company name */ | |
| 5277 | + 'title' => sprintf(__('%s special page', 'woo-vipps'), static::CompanyName()), // we hide the title frontend in template_redirect. LP 2026-08-27 | |
| 5278 | + 'content' => '<!-- wp:shortcode -->[vipps_special_page]<!-- /wp:shortcode -->', | |
| 5279 | + ]; | |
| 5067 | 5280 | return $data; |
| 5068 | 5281 | } |
| 5069 | 5282 | |
| 5070 | - // Creates any necessary Vipps pages. Will be called e.g. when activating Vipps Checkout or turning it on. | |
| 5283 | + // Creates any necessary Vipps pages. E.g vipps checkout page or vipps special page. LP 2026-09-01 | |
| 5284 | + // If a page slug already exists, then it won't overwrite or duplicate it!. LP 2026-09-02 | |
| 5071 | 5285 | public function maybe_create_vipps_pages () { |
| 5286 | + $make_pages = false; | |
| 5287 | + | |
| 5288 | + // Vipps Checkout page. LP 2026-08-18 | |
| 5072 | 5289 | $checkoutid = wc_get_page_id('vipps_checkout'); |
| 5073 | - $makeit = !$checkoutid || ! get_post_status($checkoutid); | |
| 5074 | - if ($makeit) { | |
| 5290 | + if (!$checkoutid || ! get_post_status($checkoutid)) { | |
| 5075 | 5291 | delete_option('woocommerce_vipps_checkout_page_id'); |
| 5292 | + $make_pages = true; | |
| 5076 | 5293 | } |
| 5077 | 5294 | |
| 5078 | - if ($makeit) { | |
| 5079 | - WC_Install::create_pages(); | |
| 5295 | + // vipps special page, previously a fake page. LP 2026-08-18 | |
| 5296 | + $builtin_special_page_id = static::get_special_page_id(); | |
| 5297 | + if (!$builtin_special_page_id || !get_post_status($builtin_special_page_id)) { | |
| 5298 | + delete_option('woocommerce_vipps_special_page_page_id'); | |
| 5299 | + $make_pages = true; | |
| 5080 | 5300 | } |
| 5301 | + | |
| 5302 | + if ($make_pages) { | |
| 5303 | + WC_Install::create_pages(); | |
| 5304 | + } | |
| 5081 | 5305 | } |
| 5082 | 5306 | |
| 5307 | + public function vipps_special_page_shortcode($atts, $content) { | |
| 5308 | + // No point in expanding this unless we are actually doing the special actions. LP 2026-08-25 | |
| 5309 | + if (is_admin()) return; | |
| 5310 | + if (wp_doing_ajax()) return; | |
| 5311 | + if (defined('REST_REQUEST') && REST_REQUEST) return; | |
| 5312 | + if (did_filter('woo_vipps_special_page_html')) return; // User has somehow added two shortcodes. IOK 2026-09-18 | |
| 5083 | 5313 | |
| 5314 | + $action = $_GET['action'] ?? ''; | |
| 5315 | + $html = ""; | |
| 5316 | + switch ($action) { | |
| 5317 | + case 'wait_for_payment': | |
| 5318 | + $html = $this->vipps_wait_for_payment(); | |
| 5319 | + break; | |
| 5320 | + case 'do_express_checkout': | |
| 5321 | + $html = $this->vipps_express_checkout(); | |
| 5322 | + break; | |
| 5323 | + case 'buy_product': | |
| 5324 | + $html = $this->vipps_buy_product(); | |
| 5325 | + break; | |
| 5326 | + default: | |
| 5327 | + $html = ''; | |
| 5328 | + } | |
| 5329 | + // This is mostly to avoid this shortcode evaluating twice IOK 2026-09-18 | |
| 5330 | + $html = apply_filters('woo_vipps_special_page_html', $html, $action); | |
| 5331 | + | |
| 5332 | + // Remember, this is a shortcode, so the html must be returned, not echoed IOK 2026-09-11 | |
| 5333 | + return $html; | |
| 5334 | + } | |
| 5335 | + | |
| 5336 | + | |
| 5084 | 5337 | // This URL will when accessed add a product to the cart and go directly to the express checkout page. |
| 5085 | 5338 | // The argument passed must be a shareable link created for a given product - so this in effect acts as a landing page for |
| 5086 | 5339 | // the buying thru Vipps Express Checkout of a single product linked to in for instance banners. IOK 2018-09-24 |
| 5087 | 5340 | public function vipps_buy_product() { |
| 5088 | - status_header(200,'OK'); | |
| 5089 | - Vipps::nocache(); | |
| 5090 | 5341 | |
| 5091 | 5342 | add_filter('body_class', function ($classes) { |
| 5092 | 5343 | $classes[] = 'vipps-express-checkout'; |
| 5093 | 5344 | $classes[] = 'woocommerce-checkout'; // Required by Pixel Your Site IOK 2022-11-24 |
| @@ -5123,9 +5374,9 @@ | ||
| 5123 | 5374 | |
| 5124 | 5375 | if (!$productinfo) { |
| 5125 | 5376 | $title = __("Product is no longer available",'woo-vipps'); |
| 5126 | 5377 | $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'); |
| 5127 | - return $this->fakepage($title,$content); | |
| 5378 | + return $this->special_page_html($title,$content); | |
| 5128 | 5379 | } |
| 5129 | 5380 | |
| 5130 | 5381 | // Pass the productinfo to the express checkout form |
| 5131 | 5382 | $args = array(); |
| @@ -5142,15 +5393,13 @@ | ||
| 5142 | 5393 | } |
| 5143 | 5394 | $args[sanitize_title(wp_unslash($key))] = sanitize_text_field(wp_unslash($value)); |
| 5144 | 5395 | } |
| 5145 | 5396 | |
| 5146 | - $this->print_express_checkout_page(true,'do_single_product_express_checkout',$args); | |
| 5397 | + return $this->express_checkout_page_html(true,'do_single_product_express_checkout',$args); | |
| 5147 | 5398 | } |
| 5148 | 5399 | |
| 5149 | 5400 | // 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. |
| 5150 | 5401 | public function vipps_express_checkout() { |
| 5151 | - status_header(200,'OK'); | |
| 5152 | - Vipps::nocache(); | |
| 5153 | 5402 | // We need a nonce to get here, but we should only get here when we have a cart, so this will not be cached. |
| 5154 | 5403 | // IOK 2018-05-28 |
| 5155 | 5404 | $ok = isset($_REQUEST['sec']) && wp_verify_nonce($_REQUEST['sec'],'express'); |
| 5156 | 5405 | |
| @@ -5177,9 +5426,9 @@ | ||
| 5177 | 5426 | }); |
| 5178 | 5427 | |
| 5179 | 5428 | do_action('woo_vipps_express_checkout_page'); |
| 5180 | 5429 | |
| 5181 | - $this->print_express_checkout_page(true, 'do_express_checkout'); | |
| 5430 | + return $this->express_checkout_page_html(true, 'do_express_checkout'); | |
| 5182 | 5431 | } |
| 5183 | 5432 | |
| 5184 | 5433 | // This method tries to ensure that a customer does not 'lose' the return page and |
| 5185 | 5434 | // starts ordering the same products twice. IOK 2020-01-22 |
| @@ -5274,9 +5523,10 @@ | ||
| 5274 | 5523 | return $orderspec; |
| 5275 | 5524 | } |
| 5276 | 5525 | |
| 5277 | 5526 | // Used as a landing page for launching express checkout - borh for the cart and for single products. IOK 2018-09-28 |
| 5278 | - protected function print_express_checkout_page($execute,$action,$productinfo=null) { | |
| 5527 | + // Returns the html. LP 2026-08-27 | |
| 5528 | + protected function express_checkout_page_html($execute,$action,$productinfo=null) { | |
| 5279 | 5529 | $gw = $this->gateway(); |
| 5280 | 5530 | |
| 5281 | 5531 | $expressCheckoutMessages = array(); |
| 5282 | 5532 | $expressCheckoutMessages['termsAndConditionsError'] = __( 'Please read and accept the terms and conditions to proceed with your order.', 'woocommerce' ); |
| @@ -5287,11 +5537,12 @@ | ||
| 5287 | 5537 | wp_localize_script('vipps-express-checkout', 'VippsCheckoutMessages', $expressCheckoutMessages); |
| 5288 | 5538 | wp_enqueue_script('vipps-express-checkout'); |
| 5289 | 5539 | // If we have a valid nonce when we get here, just call the 'create order' bit at once. Otherwise, make a button |
| 5290 | 5540 | // to actually perform the express checkout. |
| 5291 | - $buttonimgurl= apply_filters('woo_vipps_express_checkout_button', $this->get_payment_logo('landing')); | |
| 5541 | + $buttonhtml = apply_filters('woo_vipps_express_checkout_button', $this->get_html_button()); | |
| 5292 | 5542 | |
| 5293 | 5543 | |
| 5544 | + | |
| 5294 | 5545 | $orderspec = $this->get_orderspec_from_arguments($productinfo); |
| 5295 | 5546 | if (empty($orderspec)) { |
| 5296 | 5547 | $orderspec = $this->get_orderspec_from_cart(); |
| 5297 | 5548 | } |
| @@ -5358,10 +5609,9 @@ | ||
| 5358 | 5609 | |
| 5359 | 5610 | if ($execute) { |
| 5360 | 5611 | $content .= "<p id=waiting>" . __("Please wait while we are preparing your order", 'woo-vipps') . "</p>"; |
| 5361 | 5612 | $content .= "<div id='vipps-status-message'></div>"; |
| 5362 | - $this->fakepage(__('Order in progress','woo-vipps'), $content); | |
| 5363 | - return; | |
| 5613 | + return $this->special_page_html('', $content); | |
| 5364 | 5614 | } else { |
| 5365 | 5615 | $content .= $askForConfirmationHTML; |
| 5366 | 5616 | $content .= $extraHTML; |
| 5367 | 5617 | $content .= $termsHTML; |
| @@ -5366,23 +5616,18 @@ | ||
| 5366 | 5616 | $content .= $extraHTML; |
| 5367 | 5617 | $content .= $termsHTML; |
| 5368 | 5618 | $content .= apply_filters('woo_vipps_express_checkout_validation_elements', ''); |
| 5369 | 5619 | $title = sprintf(__('Buy now with %1$s!', 'woo-vipps'), $this->get_payment_method_name()); |
| 5370 | - $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>"; | |
| 5620 | + $content .= "<div class='vipps_buy_now_wrapper noloop'><a href='#' id='do-express-checkout' class='vipps-express-checkout' title='$title'>$buttonhtml</a></div>"; | |
| 5371 | 5621 | $content .= "<div id='vipps-status-message'></div>"; |
| 5372 | - $this->fakepage(sprintf(__('%1$s Express Checkout','woo-vipps'), $this->get_payment_method_name()), $content); | |
| 5373 | - return; | |
| 5622 | + return $this->special_page_html('', $content); | |
| 5374 | 5623 | } |
| 5375 | 5624 | } |
| 5376 | 5625 | |
| 5377 | 5626 | |
| 5378 | - | |
| 5379 | - public function vipps_wait_for_payment() { | |
| 5380 | - status_header(200,'OK'); | |
| 5381 | - Vipps::nocache(); | |
| 5382 | - | |
| 5627 | + // Called in template_redirect before we get to the wait-for-payment page IOK 2026-09-21 | |
| 5628 | + private function handle_payment_poll_and_redirect () { | |
| 5383 | 5629 | $orderid = WC()->session->get('_vipps_pending_order'); |
| 5384 | - | |
| 5385 | 5630 | $order = null; |
| 5386 | 5631 | $gw = $this->gateway(); |
| 5387 | 5632 | |
| 5388 | 5633 | // Failsafe for when the session disappears IOK 2018-11-19 |
| @@ -5394,9 +5639,9 @@ | ||
| 5394 | 5639 | // If so, we will read the order id from the GET arguments and check if the auth token is correct, |
| 5395 | 5640 | // simulating the session with that. |
| 5396 | 5641 | // IOK 2019-11-19, changed to using GET 2023-01-23 |
| 5397 | 5642 | if ($no_session && $limited_session) { |
| 5398 | - $orderid = intval(@$_GET['id']); | |
| 5643 | + $orderid = intval($_GET['id'] ?? false); | |
| 5399 | 5644 | } |
| 5400 | 5645 | if ($orderid) { |
| 5401 | 5646 | clean_post_cache($orderid); |
| 5402 | 5647 | $order = wc_get_order($orderid); |
| @@ -5415,11 +5660,8 @@ | ||
| 5415 | 5660 | $session->set('_vipps_pending_order', $orderid); |
| 5416 | 5661 | } |
| 5417 | 5662 | } |
| 5418 | 5663 | |
| 5419 | - | |
| 5420 | - do_action('woo_vipps_wait_for_payment_page',$order); | |
| 5421 | - | |
| 5422 | 5664 | $deleted_order=0; |
| 5423 | 5665 | if ($orderid && !$order) { |
| 5424 | 5666 | // If this happens, we actually did have an order, but it has been deleted, which must mean that it was cancelled. |
| 5425 | 5667 | // Concievably a hook on the 'cancel'-transition or in the callback handlers could clean that up before we get here. IOK 2019-09-26 |
| @@ -5444,9 +5686,9 @@ | ||
| 5444 | 5686 | clean_post_cache($orderid); |
| 5445 | 5687 | $order = wc_get_order($orderid); // Reload order object |
| 5446 | 5688 | } |
| 5447 | 5689 | } else { |
| 5448 | - // No need to do anyting here. IOK 2020-01-26 | |
| 5690 | + // No need to do anyting here. IOK 2020-01-26 | |
| 5449 | 5691 | } |
| 5450 | 5692 | |
| 5451 | 5693 | $payment = 'notchecked'; |
| 5452 | 5694 | if ($do_poll) { |
| @@ -5462,9 +5704,8 @@ | ||
| 5462 | 5704 | exit(); |
| 5463 | 5705 | } |
| 5464 | 5706 | |
| 5465 | 5707 | // We are done, but in failure. Don't poll. |
| 5466 | - $content = ""; | |
| 5467 | 5708 | $failure_redirect = apply_filters('woo_vipps_order_failed_redirect', '', $orderid); |
| 5468 | 5709 | |
| 5469 | 5710 | // Status is failed; still send to return url (as of now /order-recieved), the text there will depend on the status. |
| 5470 | 5711 | // For failed it shows a "Retry payment" button that takes the customer to /pay-for-order where it will be retried. LP 2026-03-17 |
| @@ -5472,8 +5713,9 @@ | ||
| 5472 | 5713 | $failure_redirect = $failure_redirect ?: $gw->get_return_url($order); |
| 5473 | 5714 | wp_redirect($failure_redirect); |
| 5474 | 5715 | exit(); |
| 5475 | 5716 | } |
| 5717 | + | |
| 5476 | 5718 | if ($status == 'cancelled' || $payment == 'cancelled') { |
| 5477 | 5719 | $this->maybe_restore_cart($orderid,'failed'); |
| 5478 | 5720 | if ($failure_redirect){ |
| 5479 | 5721 | wp_redirect($failure_redirect); |
| @@ -5478,27 +5720,45 @@ | ||
| 5478 | 5720 | if ($failure_redirect){ |
| 5479 | 5721 | wp_redirect($failure_redirect); |
| 5480 | 5722 | exit(); |
| 5481 | 5723 | } |
| 5724 | + } else { | |
| 5725 | + // If not, enqueue the status checker IOK 2026-09-21 | |
| 5726 | + 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'); | |
| 5727 | + } | |
| 5728 | + | |
| 5729 | + // Communicate this to the shortcode IOK 2026-09-21 | |
| 5730 | + add_filter('woo_vipps_wait_for_payment_status', function () use($orderid, $status, $payment) { | |
| 5731 | + return ['orderid'=>$orderid, 'status'=>$status, 'payment'=>$payment]; | |
| 5732 | + }); | |
| 5733 | + | |
| 5734 | + } | |
| 5735 | + | |
| 5736 | + public function vipps_wait_for_payment() { | |
| 5737 | + | |
| 5738 | + // This will have been computed in template_redirect, but the status will be either still pending or failed. IOK 2026-09-21 | |
| 5739 | + $data = apply_filters('woo_vipps_wait_for_payment_status', []); | |
| 5740 | + | |
| 5741 | + $orderid = $data['orderid'] ?? 0; | |
| 5742 | + $status = $data['status'] ?? ""; | |
| 5743 | + $payment = $data['payment'] ?? ""; | |
| 5744 | + | |
| 5745 | + $order = wc_get_order($orderid); | |
| 5746 | + if (!$order) wp_die(__('Unknown order', 'woo-vipps')); | |
| 5747 | + | |
| 5748 | + do_action('woo_vipps_wait_for_payment_page',$order); | |
| 5749 | + $gw = $this->gateway(); | |
| 5750 | + | |
| 5751 | + $content = ""; | |
| 5752 | + if ($status == 'cancelled' || $payment == 'cancelled') { | |
| 5482 | 5753 | $content .= "<div id=failure><p>". __('Order cancelled','woo-vipps') . '</p>'; |
| 5483 | 5754 | $content .= "<p><a href='" . home_url() . "' class='btn button'>" . __('Continue shopping','woo-vipps') . '</a></p>'; |
| 5484 | 5755 | $content .= "</div>"; |
| 5485 | - $this->fakepage(__('Order cancelled','woo-vipps'), $content); | |
| 5486 | - | |
| 5487 | - return; | |
| 5756 | + return $this->special_page_html('', $content); | |
| 5488 | 5757 | } |
| 5489 | 5758 | |
| 5490 | 5759 | // 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 |
| 5491 | - | |
| 5492 | 5760 | // Otherwise, go to a page waiting/polling for the callback. IOK 2018-05-16 |
| 5493 | - 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'); | |
| 5494 | - | |
| 5495 | - // 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 | |
| 5496 | - // and check that the order matches (and is 'pending') (and exists) | |
| 5497 | - $vippsstamp = $order->get_meta('_vipps_init_timestamp'); | |
| 5498 | - $vippsstatus = $order->get_meta('_vipps_status'); | |
| 5499 | - $message = __($order->get_meta('_vipps_confirm_message'),'woo-vipps'); | |
| 5500 | - | |
| 5501 | 5761 | $signal = $this->callbackSignal($order); |
| 5502 | 5762 | $content = ""; |
| 5503 | 5763 | $content .= "<div id='waiting'><p>" . sprintf(__('Waiting for confirmation of purchase from %1$s','woo-vipps'), $this->get_payment_method_name()); |
| 5504 | 5764 | |
| @@ -5506,15 +5766,16 @@ | ||
| 5506 | 5766 | $signalurl = $this->callbackSignalURL($signal); |
| 5507 | 5767 | |
| 5508 | 5768 | $content .= "</p></div>"; |
| 5509 | 5769 | |
| 5510 | - // We impersonate the woocommerce-checkout form here mainly to work with the Pixel Your Site plugin IOK 2022-11-24 | |
| 5511 | - $classlist = apply_filters("woo_vipps_express_checkout_form_classes", "woocommerce-checkout"); | |
| 5512 | - $content .= "<form id='vippsdata' class='" . esc_attr($classlist) . "'>"; | |
| 5770 | + $failure_redirect = apply_filters('woo_vipps_order_failed_redirect', '', $orderid); | |
| 5771 | + | |
| 5772 | + // Carry the order status to the checking script IOK 2026-09-21 | |
| 5773 | + $content .= "<form id='vippsdata'>"; | |
| 5513 | 5774 | $content .= "<input type='hidden' id='fkey' name='fkey' value='".htmlspecialchars($signalurl)."'>"; |
| 5514 | 5775 | $content .= "<input type='hidden' name='key' value='".htmlspecialchars($order->get_order_key())."'>"; |
| 5515 | 5776 | $content .= "<input type='hidden' name='action' value='check_order_status'>"; |
| 5516 | - $content .= wp_nonce_field('vippsstatus','sec',1,false); | |
| 5777 | + $content .= wp_nonce_field('vippsstatus','sec',1,false); | |
| 5517 | 5778 | $content .= "</form>"; |
| 5518 | 5779 | |
| 5519 | 5780 | |
| 5520 | 5781 | $content .= "<div id='error' style='display:none'><p>".__('Error during order confirmation','woo-vipps'). '</p>'; |
| @@ -5531,94 +5792,22 @@ | ||
| 5531 | 5792 | $content .= "<a id='continueToOrderFailed' style='display:none' href='" . $failure_redirect . "'></a>"; |
| 5532 | 5793 | $content .= "<a id='continueToOrderFailedFallback' style='display:none' href='" . $gw->get_return_url($order) . "'></a>"; |
| 5533 | 5794 | $content .= "</div>"; |
| 5534 | 5795 | |
| 5796 | + return $this->special_page_html('', $content); | |
| 5797 | + } | |
| 5535 | 5798 | |
| 5536 | - $this->fakepage(__('Waiting for your order confirmation','woo-vipps'), $content); | |
| 5799 | + // Returns formatted html for the vipps special page. LP 2026-08-27 | |
| 5800 | + public function special_page_html($header, $content) { | |
| 5801 | + $header_html = $header ? "<h2 class='vipps-special-page-title page-title'>$header</h2>" : ''; | |
| 5802 | + $html = <<<EOF | |
| 5803 | + $header_html | |
| 5804 | + <div class="vipps-special-page-content">$content</div> | |
| 5805 | + EOF; | |
| 5806 | + return apply_filters('woo_vipps_special_page_html', $html, $header, $content); | |
| 5537 | 5807 | } |
| 5538 | 5808 | |
| 5539 | 5809 | |
| 5540 | - | |
| 5541 | - public function fakepage($title,$content) { | |
| 5542 | - global $wp, $wp_query; | |
| 5543 | - // We don't want this here. | |
| 5544 | - remove_filter ('the_content', 'wpautop'); | |
| 5545 | - | |
| 5546 | - $specialid = $this->gateway()->get_option('vippsspecialpageid'); | |
| 5547 | - $wp_post = null; | |
| 5548 | - if ($specialid) { | |
| 5549 | - $wp_post = get_post($specialid); | |
| 5550 | - if ($wp_post) { | |
| 5551 | - $wp_post->post_title = $title; | |
| 5552 | - $wp_post->post_content = $content; | |
| 5553 | - // Normalize a bit | |
| 5554 | - $wp_post->filter = 'raw'; // important | |
| 5555 | - $wp_post->post_status = 'publish'; | |
| 5556 | - $wp_post->comment_status= 'closed'; | |
| 5557 | - $wp_post->ping_status= 'closed'; | |
| 5558 | - } else { | |
| 5559 | - $this->log(sprintf(__("Could not use special page with id %s - it seems not to exist.", 'woo-vipps'), $specialid), 'error'); | |
| 5560 | - } | |
| 5561 | - } | |
| 5562 | - if (!$wp_post || is_wp_error($wp_post)) { | |
| 5563 | - $post = new stdClass(); | |
| 5564 | - $post->ID = -99; | |
| 5565 | - $post->post_author = 1; | |
| 5566 | - $post->post_date = current_time( 'mysql' ); | |
| 5567 | - $post->post_date_gmt = current_time( 'mysql', 1 ); | |
| 5568 | - $post->post_title = $title; | |
| 5569 | - $post->post_content = $content; | |
| 5570 | - $post->post_status = 'publish'; | |
| 5571 | - $post->comment_status = 'closed'; | |
| 5572 | - $post->ping_status = 'closed'; | |
| 5573 | - $post->post_name = 'vippsconfirm-fake-page-name'; | |
| 5574 | - $post->post_type = 'page'; | |
| 5575 | - $post->filter = 'raw'; // important | |
| 5576 | - $wp_post = new WP_Post($post); | |
| 5577 | - wp_cache_add( -99, $wp_post, 'posts' ); | |
| 5578 | - } | |
| 5579 | - | |
| 5580 | - // Update the main query | |
| 5581 | - $wp_query->post = $wp_post; | |
| 5582 | - $wp_query->posts = array( $wp_post ); | |
| 5583 | - $wp_query->queried_object = $wp_post; | |
| 5584 | - $wp_query->queried_object_id = $wp_post->ID; | |
| 5585 | - $wp_query->found_posts = 1; | |
| 5586 | - $wp_query->post_count = 1; | |
| 5587 | - $wp_query->max_num_pages = 1; | |
| 5588 | - $wp_query->is_page = true; | |
| 5589 | - $wp_query->is_singular = true; | |
| 5590 | - $wp_query->is_single = false; | |
| 5591 | - $wp_query->is_attachment = false; | |
| 5592 | - $wp_query->is_archive = false; | |
| 5593 | - $wp_query->is_category = false; | |
| 5594 | - $wp_query->is_tag = false; | |
| 5595 | - $wp_query->is_tax = false; | |
| 5596 | - $wp_query->is_author = false; | |
| 5597 | - $wp_query->is_date = false; | |
| 5598 | - $wp_query->is_year = false; | |
| 5599 | - $wp_query->is_month = false; | |
| 5600 | - $wp_query->is_day = false; | |
| 5601 | - $wp_query->is_time = false; | |
| 5602 | - $wp_query->is_search = false; | |
| 5603 | - $wp_query->is_feed = false; | |
| 5604 | - $wp_query->is_comment_feed = false; | |
| 5605 | - $wp_query->is_trackback = false; | |
| 5606 | - $wp_query->is_home = false; | |
| 5607 | - $wp_query->is_embed = false; | |
| 5608 | - $wp_query->is_404 = false; | |
| 5609 | - $wp_query->is_paged = false; | |
| 5610 | - $wp_query->is_admin = false; | |
| 5611 | - $wp_query->is_preview = false; | |
| 5612 | - $wp_query->is_robots = false; | |
| 5613 | - $wp_query->is_posts_page = false; | |
| 5614 | - $wp_query->is_post_type_archive = false; | |
| 5615 | - // Update globals | |
| 5616 | - $GLOBALS['wp_query'] = $wp_query; | |
| 5617 | - $wp->register_globals(); | |
| 5618 | - return $wp_post; | |
| 5619 | - } | |
| 5620 | - | |
| 5621 | 5810 | // Support the interactivity API with data about our cart IOK 2026-02-23 |
| 5622 | 5811 | public function woo_vipps_store_api_cart_data() { |
| 5623 | 5812 | // 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 |
| 5624 | 5813 | |
| @@ -5624,13 +5813,15 @@ | ||
| 5624 | 5813 | |
| 5625 | 5814 | $checkout_page = $this->gateway()->vipps_checkout_available(); |
| 5626 | 5815 | $standard_checkout = get_permalink(get_option('woocommerce_checkout_page_id')); |
| 5627 | 5816 | $checkout_url = $checkout_page ? get_permalink($checkout_page) : $standard_checkout; |
| 5817 | + | |
| 5628 | 5818 | $cart_data = array( |
| 5629 | 5819 | 'cart_hide_express' => !$this->gateway()->show_express_checkout(), |
| 5630 | 5820 | 'cart_supports_checkout' => (bool) $checkout_page, |
| 5631 | 5821 | 'checkout_url' => $checkout_url, |
| 5632 | 5822 | ); |
| 5823 | + | |
| 5633 | 5824 | return $cart_data; |
| 5634 | 5825 | } |
| 5635 | 5826 | |
| 5636 | 5827 | public function woo_vipps_store_api_cart_schema() { |
| @@ -5652,8 +5843,117 @@ | ||
| 5652 | 5843 | ), |
| 5653 | 5844 | ); |
| 5654 | 5845 | } |
| 5655 | 5846 | |
| 5847 | + // Inits option 'vipps_button_options' and handles migration from older versions. LP 2026-06-26 | |
| 5848 | + // new version is stored as vipps_button_options2 to avoid breaking older versions on version revert. IOK 2026-07-15 | |
| 5849 | + private function init_button_options() { | |
| 5850 | + /* New structure as of now | |
| 5851 | + * [ | |
| 5852 | + * 'version' => x.x, | |
| 5853 | + * 'express' => [ | |
| 5854 | + * 'version' => x.x, used to migrate from previous iterations | |
| 5855 | + * 'configs' => [ different button parameters for certain contexts, falls back to global if context has no override | |
| 5856 | + * 'global' => ['compact' => ..., 'verb' => ..., ...], | |
| 5857 | + * 'cart' => [...], | |
| 5858 | + * 'product' => [...], | |
| 5859 | + * 'checkout' => [...], | |
| 5860 | + * ... | |
| 5861 | + * ], | |
| 5862 | + * 'product_configs' => [ overrides for specific products | |
| 5863 | + * 1532 => ['compact' => ..., 'verb' => ..., ...], | |
| 5864 | + * ... | |
| 5865 | + * ], | |
| 5866 | + * ], | |
| 5867 | + * ] | |
| 5868 | + */ | |
| 5869 | + $options = get_option('vipps_button_options2'); | |
| 5870 | + if (!empty($options)) return; | |
| 5871 | + | |
| 5872 | + $old_options = get_option('vipps_button_options'); | |
| 5873 | + $default_config = $this->get_html_button_default_attrs(); | |
| 5874 | + unset($default_config['brand']); // brand needs to be dynamic from payment method! LP 2026-07-01 | |
| 5875 | + $default_compact = array_replace($default_config, ['compact' => 'true']); | |
| 5876 | + | |
| 5877 | + $default_options = [ | |
| 5878 | + 'version' => $this->button_options_version, | |
| 5879 | + 'express' => [ | |
| 5880 | + 'version' => $this->button_options_express_version, | |
| 5881 | + 'configs' => [ | |
| 5882 | + 'global' => $default_config, | |
| 5883 | + // Need compact version by default for below pages. LP 2026-07-07 | |
| 5884 | + 'catalog' => $default_compact, | |
| 5885 | + 'minicart' => $default_compact, // storefront needs compact, tho 2025 theme has a lot of room. Just use compact LP 2026-07-07 | |
| 5886 | + ], | |
| 5887 | + 'product_configs' => [], | |
| 5888 | + ], | |
| 5889 | + ]; | |
| 5890 | + | |
| 5891 | + $new_options = $default_options; | |
| 5892 | + | |
| 5893 | + //Actually, we have some options from the old structure IOK 2026-07-15 | |
| 5894 | + if (!empty($old_options)) { | |
| 5895 | + $new_options['express']['configs']['global'] = $this->migrate_button_variant_to_config($old_options['express']['variant'] ?? ''); | |
| 5896 | + unset($new_options['express']['configs']['global']['brand']); // dont set brand, this needs to be dynamic. LP 2026-07-01 | |
| 5897 | + } | |
| 5898 | + | |
| 5899 | + // Migrate context/page mini override to new context config. LP 2026-06-26 | |
| 5900 | + if (is_array($old_options['express']['force-mini'] ?? null)) { | |
| 5901 | + foreach($old_options['express']['force-mini'] as $context => $use_mini) { | |
| 5902 | + if ("yes" === $use_mini) { | |
| 5903 | + $config = $this->migrate_button_variant_to_config($old_options['express']['mini-variant'] ?? ''); | |
| 5904 | + unset($config['brand']); // brand needs to be dynamic from payment method! LP 2026-07-01 | |
| 5905 | + $config['compact'] = 'true'; | |
| 5906 | + $new_options['express']['configs'][$context] = $config; | |
| 5907 | + } | |
| 5908 | + } | |
| 5909 | + } | |
| 5910 | + | |
| 5911 | + if ($this->get_payment_method_name() !== 'MobilePay') { | |
| 5912 | + // Finnish is only available in the MobilePay component right now, so reset language in any configs. LP 2026-07-01 | |
| 5913 | + foreach(($new_options['express']['configs'] ?? []) as $context => $config) { | |
| 5914 | + if ('fi' === ($config['language'] ?? '')) { | |
| 5915 | + $config['language'] = 'store'; | |
| 5916 | + $new_options['express']['configs'][$context] = $config; | |
| 5917 | + } | |
| 5918 | + } | |
| 5919 | + } | |
| 5920 | + | |
| 5921 | + /* translators: placeholders are arrays */ | |
| 5922 | + $this->log(sprintf(__('Migrating from old button options. Old: %s, new: %s', 'woo-vipps'), print_r($options, true), print_r($new_options, true)), 'debug'); | |
| 5923 | + | |
| 5924 | + | |
| 5925 | + | |
| 5926 | + update_option('vipps_button_options2', $new_options); | |
| 5927 | + } | |
| 5928 | + | |
| 5929 | + // Old variant string => new config array. LP 2026-06-26 | |
| 5930 | + public function migrate_button_variant_to_config($variant_slug) { | |
| 5931 | + if (!is_string($variant_slug)) return []; | |
| 5932 | + $config = $this->get_html_button_default_attrs(); | |
| 5933 | + $config['rounded'] = str_contains($variant_slug, 'pill') ? 'true' : 'false'; | |
| 5934 | + $config['compact'] = str_contains($variant_slug, 'mini') ? 'true' : 'false'; | |
| 5935 | + if (str_contains($variant_slug, 'buy-now')) { | |
| 5936 | + $config['verb'] = 'buy'; | |
| 5937 | + } else if (str_contains($variant_slug, 'express')) { | |
| 5938 | + $config['verb'] = 'express'; | |
| 5939 | + } | |
| 5940 | + return $config; | |
| 5941 | + } | |
| 5942 | + | |
| 5943 | + // Old legacy button logo variants. Replaced by web component. See get_html_button(). LP 2026-07-01 | |
| 5944 | + public function get_express_logo_variants() { | |
| 5945 | + return [ | |
| 5946 | + 'buy-now-rectangular' => __('Buy now rectangular', 'woo-vipps'), | |
| 5947 | + 'buy-now-pill' => __('Buy now pill', 'woo-vipps'), | |
| 5948 | + 'express-rectangular' => __('Express rectangular', 'woo-vipps'), | |
| 5949 | + 'express-pill' => __('Express pill', 'woo-vipps'), | |
| 5950 | + 'express-rectangular-mini' => __('Express rectangular mini', 'woo-vipps'), | |
| 5951 | + 'express-pill-mini' => __('Express pill mini', 'woo-vipps'), | |
| 5952 | + ]; | |
| 5953 | + } | |
| 5954 | + | |
| 5955 | + | |
| 5656 | 5956 | // Whether the order is possible to restart with a retry session at VMP. LP 2026-03-18 |
| 5657 | 5957 | public static function order_is_vipps_retryable($order_id) { |
| 5658 | 5958 | $order = wc_get_order($order_id); |
| 5659 | 5959 | if (!$order) return false; |
| @@ -5662,6 +5962,20 @@ | ||
| 5662 | 5962 | $shipping_set = $order->get_meta('_vipps_shipping_set'); |
| 5663 | 5963 | |
| 5664 | 5964 | // Express or unfinalized Checkout orders do not have shipping available, so we cant retry these in particular. LP 2026-03-18 |
| 5665 | 5965 | return $nonexpress_epayment || $shipping_set; |
| 5966 | + } | |
| 5967 | + | |
| 5968 | + /** Returns the plugin's rest api namespace including the version. | |
| 5969 | + * Use latest version ($version = 'latest') with caution, we want backwards compatible endpoints. LP 2026-03-31 */ | |
| 5970 | + public static function get_rest_namespace($version = 'latest') { | |
| 5971 | + $version = $version === 'latest' ? self::REST_CURRENT_VERSION : $version; | |
| 5972 | + return self::REST_NAMESPACE_BASE . "/$version"; | |
| 5973 | + } | |
| 5974 | + | |
| 5975 | + /** Returns the plugin's rest api url. | |
| 5976 | + * $version accepts 'latest', but you probably don't want to do that. | |
| 5977 | + * Remember root forward-slash for $route. e.g $route = '/my-route' LP 2026-03-31 */ | |
| 5978 | + public static function get_rest_url($version, $route) { | |
| 5979 | + return get_rest_url(null, static::get_rest_namespace($version) . $route, 'rest'); | |
| 5666 | 5980 | } |
| 5667 | 5981 | } |