| @@ -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 | } |
| @@ -110,16 +117,22 @@ | ||
| 110 | 117 | add_action('wp_footer', array($Vipps,'footer')); |
| 111 | 118 | } |
| 112 | 119 | add_action( 'plugins_loaded', array($Vipps,'plugins_loaded')); |
| 113 | 120 | add_action( 'after_setup_theme', array($Vipps,'after_setup_theme')); |
| 114 | - add_action('init',array($Vipps,'init')); | |
| 121 | + add_action( 'init',array($Vipps,'init')); | |
| 122 | + add_action( 'rest_api_init', array($Vipps, 'rest_api_init')); | |
| 115 | 123 | add_action( 'woocommerce_loaded', array($Vipps,'woocommerce_loaded')); |
| 116 | 124 | add_filter( 'woocommerce_available_payment_gateways', array($Vipps, 'payment_gateway_filter')); |
| 117 | 125 | 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 | |
| 126 | + // Express Checkout and Checkout supports the new pickup_location shipping method, but the admin interface for this may | |
| 119 | 127 | // 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 | 128 | // stored in the database since we support this for both Vipps MobilePay checkokut and Express. IOK 2026-02-25 |
| 121 | 129 | add_action('woocommerce_load_shipping_methods', array($Vipps, 'maybe_load_pickup_locations'), 90); |
| 130 | + | |
| 131 | + // Vipps Checkout replaces the default checkout page, and currently uses its own page for this which needs to exist | |
| 132 | + // Will also probably be used to maintain a real utility-page for Vipps actions later for themes where this | |
| 133 | + // is important. | |
| 134 | + add_filter('woocommerce_create_pages', array($Vipps, 'woocommerce_create_pages'), 50, 1); | |
| 122 | 135 | } |
| 123 | 136 | |
| 124 | 137 | // Register woocommerce store api endpoint to use in buy-now minicart block. LP 2026-02-10 |
| 125 | 138 | public function woocommerce_blocks_loaded() { |
| @@ -215,8 +228,9 @@ | ||
| 215 | 228 | // Register certain scripts in wp_loaded because they will be added to the backend as well - the gutenberg checkout block |
| 216 | 229 | // needs these to be defined in the backend. IOK 2024-04-16 |
| 217 | 230 | add_action('wp_loaded', array($this, 'wp_register_scripts')); |
| 218 | 231 | add_action('wp_enqueue_scripts', array($this, 'wp_enqueue_scripts')); |
| 232 | + add_action('wp_enqueue_scripts', array($this, 'enqueue_classic_checkout_scripts'), 20); | |
| 219 | 233 | |
| 220 | 234 | // Remove the possibility of restarting failed orders etc. This will be fixed in the future. IOK 2023-05-26 |
| 221 | 235 | add_filter('woocommerce_my_account_my_orders_actions', array($this,'woocommerce_my_account_my_orders_actions'), 10, 2); |
| 222 | 236 | |
| @@ -232,16 +246,8 @@ | ||
| 232 | 246 | |
| 233 | 247 | // Extra order actions on the order screen, now using ajax to be compatible with HPOS. IOK 2022-12-02 |
| 234 | 248 | add_action('wp_ajax_woo_vipps_order_action', array($this, 'order_handle_vipps_action')); |
| 235 | 249 | |
| 236 | - // Fetch wc products, but filter those only purchasable by VMP express checkout. LP 2026-01-22 | |
| 237 | - add_action('rest_api_init', function() { | |
| 238 | - register_rest_route('woo-vipps/v1', '/express-products', [ | |
| 239 | - 'methods' => 'GET', | |
| 240 | - 'callback' => [$this, 'rest_express_checkout_products'], | |
| 241 | - 'permission_callback' => '__return_true', | |
| 242 | - ]); | |
| 243 | - }); | |
| 244 | 250 | |
| 245 | 251 | // We need a 5-minute scheduled event for the handler for missed callbacks. Using the |
| 246 | 252 | // action scheduler would be better, but we can't do that just yet because of backwards |
| 247 | 253 | // compatibility. At some point, support for older woo-versions should be dropped; then this |
| @@ -271,10 +277,65 @@ | ||
| 271 | 277 | add_filter('woo_vipps_lock_order', array($this,'flock_lock_order')); |
| 272 | 278 | add_action('woo_vipps_unlock_order', array($this, 'flock_unlock_order')); |
| 273 | 279 | } |
| 274 | 280 | |
| 281 | + // Set default button options, migrating any older setup IOK 2026-07-15 | |
| 282 | + $this->init_button_options(); | |
| 283 | + | |
| 284 | + /* | |
| 285 | + From version 6.2.x we create a real physical page to handle the "special" vipps pages, | |
| 286 | + where we earlier used just a fake page with no real page id, unless especially configured. | |
| 287 | + We therefore need to add code to maintain this special page. | |
| 288 | + | |
| 289 | + woocommerce_loaded is too early for this because of maybe_create_vipps_pages which calls WC_Install::create_pages, | |
| 290 | + and we hook unto this with woocommerce_create_pages. LP 2026-09-03 | |
| 291 | + */ | |
| 292 | + | |
| 293 | + // Delete special page id option when its deleted or trashed, so that we dont have to load | |
| 294 | + // in the post to check status in woocommerce_loaded when we ensure the special page exists. LP 2026-09-03 | |
| 295 | + $delete_special_page_id = function($post_id, $post = null) { | |
| 296 | + if (static::get_special_page_id() === $post_id) { | |
| 297 | + delete_option('woocommerce_vipps_special_page_page_id'); | |
| 298 | + } | |
| 299 | + }; | |
| 300 | + add_action('delete_post', $delete_special_page_id, 10, 2); | |
| 301 | + add_action('wp_trash_post', $delete_special_page_id, 10, 2); | |
| 302 | + | |
| 303 | + $this->ensure_special_page_exists(); | |
| 304 | + | |
| 305 | + | |
| 306 | + // We want this special page to have a certain title and maybe special scripts and so on, | |
| 307 | + // this gets run in template redirect for these pages. | |
| 308 | + add_action('woo_vipps_before_handling_special_page', array($this, 'pre_special_page_actions')); | |
| 309 | + | |
| 310 | + // Add an admin interface for this page as well IOK 2026-09-11 | |
| 311 | + add_action('woocommerce_settings_pages', array($this, 'woocommerce_settings_pages')); | |
| 275 | 312 | } |
| 276 | 313 | |
| 314 | + | |
| 315 | + public function rest_api_init () { | |
| 316 | + | |
| 317 | + // Fetch wc products, but filter those only purchasable by VMP express checkout. LP 2026-01-22 | |
| 318 | + register_rest_route(self::get_rest_namespace('v1'), '/express-products', [ | |
| 319 | + 'methods' => 'GET', | |
| 320 | + 'callback' => [$this, 'rest_express_checkout_products'], | |
| 321 | + 'permission_callback' => '__return_true', | |
| 322 | + ]); | |
| 323 | + | |
| 324 | + // Start a single product express checkout process. IOK 2026-08-25 | |
| 325 | + register_rest_route(self::get_rest_namespace('v1'), '/express_checkout_single', [ | |
| 326 | + 'methods' => 'POST', | |
| 327 | + 'callback' => [$this, 'rest_do_single_product_express_checkout'], | |
| 328 | + 'permission_callback' => '__return_true', | |
| 329 | + ]); | |
| 330 | + // And one for the cart. IOK 2026-09-04 | |
| 331 | + register_rest_route(self::get_rest_namespace('v1'), '/express_checkout', [ | |
| 332 | + 'methods' => 'POST', | |
| 333 | + 'callback' => [$this, 'rest_do_express_checkout'], | |
| 334 | + 'permission_callback' => '__return_true', | |
| 335 | + ]); | |
| 336 | + } | |
| 337 | + | |
| 277 | 338 | public function admin_init () { |
| 278 | 339 | $gw = $this->gateway(); |
| 279 | 340 | require_once(dirname(__FILE__) . "/admin/settings/VippsAdminSettings.class.php"); |
| 280 | 341 | $adminSettings = VippsAdminSettings::instance(); |
| @@ -301,10 +362,8 @@ | ||
| 301 | 362 | // Styling etc |
| 302 | 363 | add_action('admin_head', array($this, 'admin_head')); |
| 303 | 364 | |
| 304 | 365 | // Scripts |
| 305 | - $this->vippsJSConfig['vippssecnonce'] = wp_create_nonce('vippssecnonce'); | |
| 306 | - wp_localize_script('vipps-gw', 'VippsConfig', $this->vippsJSConfig); | |
| 307 | 366 | add_action('admin_enqueue_scripts', array($this,'admin_enqueue_scripts')); |
| 308 | 367 | |
| 309 | 368 | // IOK 2026-05-26 redirect the old Woo-generated settings-screen to our own settings page. |
| 310 | 369 | add_action('current_screen', function ($screen) { |
| @@ -392,9 +451,77 @@ | ||
| 392 | 451 | } |
| 393 | 452 | } |
| 394 | 453 | } |
| 395 | 454 | |
| 455 | + | |
| 456 | + /** Ensure we have a special page for payment flows | |
| 457 | + * | |
| 458 | + * woocommerce_loaded is too early for this because of maybe_create_vipps_pages which calls WC_Install::create_pages, | |
| 459 | + * and we hook unto this with woocommerce_create_pages. LP 2026-09-03 | |
| 460 | + **/ | |
| 461 | + public function ensure_special_page_exists() { | |
| 462 | + if (static::get_special_page_id()) return; | |
| 463 | + $this->log(__('Missing id for special page, attempting to fix.', 'woo-vipps'), 'info'); | |
| 396 | 464 | |
| 465 | + // 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 | |
| 466 | + $old_special_page_id = $this->gateway()->get_option('vippsspecialpageid'); | |
| 467 | + if ($old_special_page_id && ($special_page = get_post($old_special_page_id)) && "trash" !== $special_page->post_status) { | |
| 468 | + $this->log(__('Migrated old special page setting.', 'woo-vipps'), 'info'); | |
| 469 | + // there is no wc_set_page_id() so we update the option directly. LP 2026-09-01 | |
| 470 | + update_option('woocommerce_vipps_special_page_page_id', $old_special_page_id); | |
| 471 | + | |
| 472 | + // Ensure this page has the necessary shortcode. LP 2026-09-01 | |
| 473 | + if (!has_shortcode($special_page->post_content, 'vipps_special_page')) { | |
| 474 | + $new_content = $special_page->post_content . "\n\n<!-- wp:shortcode -->[vipps_special_page]<!-- /wp:shortcode -->"; | |
| 475 | + wp_update_post([ | |
| 476 | + 'ID' => $old_special_page_id, | |
| 477 | + 'post_content' => $new_content, | |
| 478 | + ]); | |
| 479 | + } | |
| 480 | + } else { | |
| 481 | + // Create special page if its missing. LP 2026-09-01 | |
| 482 | + $this->maybe_create_vipps_pages(); | |
| 483 | + } | |
| 484 | + } | |
| 485 | + | |
| 486 | + // Admin interface for the special page on woo/advanced/pages | |
| 487 | + public function woocommerce_settings_pages ($settings) { | |
| 488 | + $i = -1; | |
| 489 | + foreach($settings as $entry) { | |
| 490 | + $i++; | |
| 491 | + if ($entry['type'] == 'sectionend' && $entry['id'] == 'advanced_page_options') { | |
| 492 | + break; | |
| 493 | + } | |
| 494 | + } | |
| 495 | + if ($i > 0) { | |
| 496 | + $vippspagesettings = array( | |
| 497 | + array( | |
| 498 | + 'title' => sprintf(__( '%1$s Page', 'woo-vipps' ), Vipps::CompanyName()), | |
| 499 | + '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') , | |
| 500 | + 'id' => 'woocommerce_vipps_special_page_page_id', | |
| 501 | + 'type' => 'single_select_page_with_search', | |
| 502 | + 'default' => '', | |
| 503 | + 'class' => 'wc-page-search', | |
| 504 | + 'css' => 'min-width:300px;', | |
| 505 | + 'args' => array( | |
| 506 | + 'exclude' => | |
| 507 | + array( | |
| 508 | + wc_get_page_id( 'myaccount' ), | |
| 509 | + wc_get_page_id( 'checkout' ), | |
| 510 | + wc_get_page_id( 'cart' ), | |
| 511 | + ), | |
| 512 | + ), | |
| 513 | + 'desc_tip' => true, | |
| 514 | + 'autoload' => false, | |
| 515 | + )); | |
| 516 | + array_splice($settings, $i, 0, $vippspagesettings); | |
| 517 | + } | |
| 518 | + | |
| 519 | + return $settings; | |
| 520 | + } | |
| 521 | + | |
| 522 | + | |
| 523 | + | |
| 397 | 524 | // Runs on init, adds the Vipps badge feature if activated |
| 398 | 525 | public function maybe_add_vipps_badge_feature () { |
| 399 | 526 | $badge_options = get_option('vipps_badge_options'); |
| 400 | 527 | if (!$badge_options || !@$badge_options['badgeon']) return false; |
| @@ -723,8 +850,11 @@ | ||
| 723 | 850 | |
| 724 | 851 | // Get current brand and language |
| 725 | 852 | $current_brand = strtolower($this->get_payment_method_name()); |
| 726 | 853 | $current_language = $this->get_customer_language(); |
| 854 | + if ('se' === $current_language) $current_language = 'sv'; | |
| 855 | + // Looks like button and badge web components now use 'da' instead of 'dk' for danish. LP 2026-08-13 | |
| 856 | + if ('dk' === $current_language) $current_language = 'da'; | |
| 727 | 857 | |
| 728 | 858 | $variants = ['white'=> __('White', 'woo-vipps'), 'grey' => __('Grey','woo-vipps'), |
| 729 | 859 | 'filled'=> __('Filled', 'woo-vipps'), 'light'=>__('Light','woo-vipps'), |
| 730 | 860 | 'purple'=> __('Purple', 'woo-vipps')]; |
| @@ -787,9 +917,9 @@ | ||
| 787 | 917 | |
| 788 | 918 | <h2><?php _e('Shortcodes', 'woo-vipps'); ?> </h2> |
| 789 | 919 | <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 | 920 | <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> | |
| 921 | + <pre>[vipps-mobilepay-badge variant={white|filled|light|grey|purple}<br> language={en|no|fi|da|sv} ] </pre><br> | |
| 792 | 922 | <?php _e("Please refer to the documentation for the meaning of the parameters.", 'woo-vipps'); ?></br> |
| 793 | 923 | <?php _e("The brand will be automatically applied.", 'woo-vipps'); ?> |
| 794 | 924 | </p> |
| 795 | 925 | |
| @@ -816,21 +946,24 @@ | ||
| 816 | 946 | echo json_encode(array('ok'=>0,'msg'=>__('You don\'t have sufficient rights to edit this product', 'woo-vipps'))); |
| 817 | 947 | wp_die(__('You don\'t have sufficient rights to edit this product', 'woo-vipps')); |
| 818 | 948 | } |
| 819 | 949 | |
| 820 | - $options = get_option('vipps_button_options'); | |
| 821 | - if (isset($_POST['express']['variant'])) { | |
| 822 | - $options['express']['variant'] = sanitize_title($_POST['express']['variant']); | |
| 950 | + $old = get_option('vipps_button_options2', []); | |
| 951 | + $new = $old; | |
| 952 | + if (isset($_POST['express']['configs'])) { | |
| 953 | + foreach ($_POST['express']['configs'] as $ctx => $config) { | |
| 954 | + $sanitized_ctx = sanitize_title($ctx); | |
| 955 | + $sanitized_config = map_deep($config, 'sanitize_title'); | |
| 956 | + | |
| 957 | + // If nonglobal context that uses global config, just wipe the rest of the stored config. LP 2026-06-25 | |
| 958 | + if ("global" !== $sanitized_ctx && ($sanitized_config['use-global-config'] ?? false)) { | |
| 959 | + $new['express']['configs'][$sanitized_ctx] = ['use-global-config' => true]; | |
| 960 | + } else { | |
| 961 | + $new['express']['configs'][$sanitized_ctx] = $sanitized_config; | |
| 962 | + } | |
| 963 | + } | |
| 823 | 964 | } |
| 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); | |
| 965 | + update_option('vipps_button_options2', $new); | |
| 833 | 966 | wp_safe_redirect(admin_url("admin.php?page=vipps_button_menu")); |
| 834 | 967 | exit(); |
| 835 | 968 | } |
| 836 | 969 | |
| @@ -864,9 +997,12 @@ | ||
| 864 | 997 | public function vipps_mobilepay_badge_shortcode($atts) { |
| 865 | 998 | $args = shortcode_atts( array('id'=>'', 'class'=>'', 'brand' => '', 'variant' => '','language'=>''), $atts ); |
| 866 | 999 | |
| 867 | 1000 | $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(); | |
| 1001 | + $language = in_array($args['language'], ['en', 'no', 'sv', 'da', 'dk', 'fi']) ? $args['language'] : $this->get_customer_language(); | |
| 1002 | + if ('se' === $language) $language = 'sv'; | |
| 1003 | + // Looks like button and badge web components now use 'da' instead of 'dk' for danish. LP 2026-08-11 | |
| 1004 | + if ('dk' === $language) $language = 'da'; | |
| 869 | 1005 | $id = sanitize_title($args['id']); |
| 870 | 1006 | $class = sanitize_text_field($args['class']); |
| 871 | 1007 | |
| 872 | 1008 | $attributes = []; |
| @@ -882,13 +1018,18 @@ | ||
| 882 | 1018 | return "<vipps-mobilepay-badge $badgeatts></vipps-mobilepay-badge>"; |
| 883 | 1019 | } |
| 884 | 1020 | |
| 885 | 1021 | // legacy vipps_badge shortcode, the new one is vipps_mobilepay_badge_shortcode. LP 19.11.2024 |
| 1022 | + // Diff: this one doesn't support brand (JUST VIPPS). LP 2026-08-11 | |
| 886 | 1023 | public function vipps_badge_shortcode($atts) { |
| 887 | 1024 | $args = shortcode_atts( array('id'=>'', 'class'=>'','variant' => '','language'=>''), $atts ); |
| 888 | 1025 | |
| 889 | 1026 | $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(); | |
| 1027 | + $language = in_array($args['language'], ['en', 'no', 'sv', 'da', 'dk', 'fi']) ? $args['language'] : $this->get_customer_language(); | |
| 1028 | + if ('se' === $language) $language = 'sv'; | |
| 1029 | + // Looks like button and badge web components now use 'da' instead of 'dk' for danish. LP 2026-08-11 | |
| 1030 | + if ('dk' === $language) $language = 'da'; | |
| 1031 | + | |
| 891 | 1032 | $id = sanitize_title($args['id']); |
| 892 | 1033 | $class = sanitize_text_field($args['class']); |
| 893 | 1034 | |
| 894 | 1035 | $attributes = []; |
| @@ -902,163 +1043,339 @@ | ||
| 902 | 1043 | |
| 903 | 1044 | return "<vipps-badge $badgeatts></vipps-badge>"; |
| 904 | 1045 | } |
| 905 | 1046 | |
| 906 | - public function get_express_logo_variants() { | |
| 1047 | + public function get_html_button_default_attrs() { | |
| 907 | 1048 | 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'), | |
| 1049 | + 'language' => 'store', | |
| 1050 | + 'variant' => 'primary', | |
| 1051 | + 'rounded' => 'false', | |
| 1052 | + 'verb' => 'buy', | |
| 1053 | + 'stretched' => 'false', | |
| 1054 | + 'compact' => 'false', | |
| 1055 | + '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 | 1056 | ]; |
| 915 | 1057 | } |
| 916 | 1058 | |
| 1059 | + public function get_html_button_attrs_for_context($context = 'global') { | |
| 1060 | + $options = get_option('vipps_button_options2', []); | |
| 1061 | + if (!is_string($context)) $context = 'global'; | |
| 1062 | + | |
| 1063 | + // Gutenberg express checkout buttons really want to be stretched, so we'll treat them somewhat differently. | |
| 1064 | + $gutenberg = false; | |
| 1065 | + if ($context == 'checkout_gutenberg') { | |
| 1066 | + $context = 'checkout'; | |
| 1067 | + $gutenberg = true; | |
| 1068 | + } | |
| 1069 | + if ($context == 'cart_gutenberg') { | |
| 1070 | + $context = 'cart'; | |
| 1071 | + $gutenberg = true; | |
| 1072 | + } | |
| 917 | 1073 | |
| 918 | - public function button_menu_page() { | |
| 919 | - if (!current_user_can('manage_woocommerce')) { | |
| 920 | - wp_die(__('You don\'t have sufficient rights to access this page', 'woo-vipps')); | |
| 1074 | + $config = $options['express']['configs'][$context] ?? []; | |
| 1075 | + $use_global = !$config || ($config['use-global-config'] ?? false); | |
| 1076 | + if ($use_global) { | |
| 1077 | + $config = $options['express']['configs']['global'] ?? $this->get_html_button_default_attrs(); | |
| 921 | 1078 | } |
| 1079 | + | |
| 1080 | + // see above. | |
| 1081 | + if ($gutenberg) { | |
| 1082 | + $config['stretched']='true'; | |
| 1083 | + } | |
| 1084 | + return $config; | |
| 1085 | + } | |
| 1086 | + | |
| 1087 | + public function get_html_button_for_context($context = 'global') { | |
| 1088 | + return $this->get_html_button($this->get_html_button_attrs_for_context($context)); | |
| 1089 | + } | |
| 1090 | + | |
| 1091 | + // Generic Vipps/MobilePay button html, as of now a web component hosted locally. LP 2026-06-24 | |
| 1092 | + // See info and attributes at https://developer.vippsmobilepay.com/docs/knowledge-base/buttons/ | |
| 1093 | + public function get_html_button($attrs = []) { | |
| 922 | 1094 | $payment_method = $this->get_payment_method_name(); |
| 923 | - $lang = $this->get_customer_language(); | |
| 924 | - $button_options = get_option('vipps_button_options'); | |
| 1095 | + $attrs = wp_parse_args($attrs, $this->get_html_button_default_attrs()); | |
| 1096 | + $attrs['brand'] = strtolower($payment_method); | |
| 1097 | + $attrs['type'] = 'button'; // static | |
| 925 | 1098 | |
| 926 | - $variants = $this->get_express_logo_variants(); | |
| 927 | - $mini_variants = array_filter($variants, fn($key) => str_ends_with($key, 'mini'), ARRAY_FILTER_USE_KEY); | |
| 1099 | + // Support using store language | |
| 1100 | + if ('store' === $attrs['language']) $attrs['language'] = $this->get_customer_language(); | |
| 1101 | + // Don't support these login verbs. LP 2026-06-04 | |
| 1102 | + if (in_array($attrs['verb'], ['login', 'register'])) $attrs['verb'] = 'buy'; | |
| 1103 | + // Looks like button and badge web components now use 'da' instead of 'dk' for danish. LP 2026-08-11 | |
| 1104 | + if ('dk' === $attrs['language']) $attrs['language'] = 'da'; | |
| 928 | 1105 | |
| 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 | - ], | |
| 940 | - ]; | |
| 1106 | + $escaped_attrs = []; | |
| 1107 | + foreach($attrs as $k => $v) { | |
| 1108 | + $escaped_attrs[$k] = esc_attr($v); | |
| 1109 | + } | |
| 941 | 1110 | |
| 1111 | + // id attribute | |
| 1112 | + $id = $escaped_attrs['id'] ?? ''; | |
| 1113 | + $id_str = $id ? "id='$id'" : ''; | |
| 1114 | + | |
| 1115 | + // class attribute | |
| 1116 | + $class_str = ''; | |
| 1117 | + if (isset($attrs['class'])) { | |
| 1118 | + if (is_array($attrs['class'])) { | |
| 1119 | + $class_str = implode(' ', $attrs['class']); | |
| 1120 | + } else if (is_string($attrs['class'])) { | |
| 1121 | + $class_str = $attrs['class']; | |
| 1122 | + } | |
| 1123 | + } | |
| 1124 | + | |
| 1125 | + // The html | |
| 1126 | + $html = <<<EOF | |
| 1127 | +<vipps-mobilepay-button | |
| 1128 | + $id_str | |
| 1129 | + $class_str | |
| 1130 | + type="{$escaped_attrs['type']}" | |
| 1131 | + brand="{$escaped_attrs['brand']}" | |
| 1132 | + language="{$escaped_attrs['language']}" | |
| 1133 | + variant="{$escaped_attrs['variant']}" | |
| 1134 | + rounded="{$escaped_attrs['rounded']}" | |
| 1135 | + verb="{$escaped_attrs['verb']}" | |
| 1136 | + stretched="{$escaped_attrs['stretched']}" | |
| 1137 | + compact="{$escaped_attrs['compact']}" | |
| 1138 | +></vipps-mobilepay-button> | |
| 1139 | +EOF; | |
| 1140 | + return apply_filters('woo_vipps_html_button', $html, $attrs); | |
| 1141 | + } | |
| 1142 | + | |
| 1143 | + public function button_menu_page() { | |
| 1144 | + if (!current_user_can('manage_woocommerce')) { | |
| 1145 | + wp_die(__('You don\'t have sufficient rights to access this page', 'woo-vipps')); | |
| 1146 | + } | |
| 1147 | + wp_enqueue_script('vipps-button-webcomponent'); | |
| 942 | 1148 | ?> |
| 943 | 1149 | <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"> | |
| 1150 | + <h1><?php echo sprintf(__('%1$s button configuration', 'woo-vipps'), Vipps::CompanyName()); ?></h1> | |
| 1151 | + <span><?php echo sprintf(__('%1$s supports different variants of buttons for you to perfect your store\'s look', 'woo-vipps'), Vipps::CompanyName()); ?></span> | |
| 1152 | + <form id="vipps-button-settings-form" class="vipps-button-settings" action="<?php echo admin_url('admin-post.php'); ?>" method="POST"> | |
| 1153 | + <input type="hidden" name="action" value="update_vipps_button_settings" /> | |
| 1154 | + <?php wp_nonce_field( 'buttonaction', 'buttonnonce'); ?> | |
| 947 | 1155 | |
| 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'); ?> | |
| 1156 | + <!-- Express section --> | |
| 1157 | + <?php $this->button_menu_express_section(); ?> | |
| 953 | 1158 | |
| 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> | |
| 1159 | + <!-- submit button --> | |
| 1160 | + <div id="vipps-button-settings-save"> | |
| 1161 | + <input class="btn button primary" type="submit" value="<?php _e('Update settings', 'woo-vipps'); ?>" /> | |
| 966 | 1162 | </div> |
| 1163 | + </form> | |
| 1164 | + </div> | |
| 1165 | + <?php | |
| 1166 | + } | |
| 967 | 1167 | |
| 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> | |
| 1168 | + private function button_menu_express_section() { | |
| 1169 | + $options = get_option('vipps_button_options2', []); | |
| 1170 | + $express = $options['express'] ?? []; | |
| 1171 | + $configs = $express['configs'] ?? []; | |
| 980 | 1172 | |
| 981 | 1173 | |
| 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> | |
| 1174 | + $contexts = [ | |
| 1175 | + 'global' => __('Global', 'woo-vipps'), | |
| 1176 | + 'product' => __('Product', 'woo-vipps'), | |
| 1177 | + 'catalog' => __('Catalog', 'woo-vipps'), | |
| 1178 | + 'cart' => __('Cart', 'woo-vipps'), | |
| 1179 | + 'minicart' => __('Mini cart', 'woo-vipps'), | |
| 1180 | + 'checkout' => __('Checkout', 'woo-vipps'), | |
| 1181 | + ]; | |
| 1182 | + $init_context = 'global'; | |
| 1183 | + $init_config = $configs[$init_context] ?? []; | |
| 991 | 1184 | |
| 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> | |
| 1185 | + // html button args | |
| 1186 | + $init_args = $init_config; | |
| 1187 | + $init_args['id'] = 'vipps-button-express-preview'; | |
| 997 | 1188 | |
| 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> | |
| 1189 | + ?> | |
| 1190 | + <div class="vipps-button-settings-section" id="vipps-button-settings-express-container"> | |
| 1191 | + <h2> <?php _e('Express Checkout', 'woo-vipps'); ?></h2> | |
| 1003 | 1192 | |
| 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> | |
| 1193 | + <!-- Context dropdown --> | |
| 1194 | + <div id="vipps-button-settings-express-context"> | |
| 1195 | + <label> | |
| 1196 | + <?php _e('Config context', 'woo-vipps'); ?> | |
| 1197 | + </label> | |
| 1198 | + <select id="context" onChange='updateContext()'> | |
| 1199 | + <?php foreach($contexts as $key => $label): ?> | |
| 1200 | + <option value="<?php echo $key; ?>" <?php if ('global' === $key) echo " selected "; ?> > | |
| 1201 | + <?php echo $label ; ?> | |
| 1202 | + </option> | |
| 1203 | + <?php endforeach; ?> | |
| 1204 | + </select> | |
| 1205 | + <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> | |
| 1206 | + </div> | |
| 1207 | + | |
| 1009 | 1208 | |
| 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> | |
| 1209 | + <!-- Button paremeter inputs. These input values are put into post data express.tmpConfig temporarily. | |
| 1210 | + On context change, configs are stored in a global 'contextConfigs'. Each config is processed into new option structure before submit. LP 2026-06-24 --> | |
| 1211 | + <div class="vipps-button-settings-section" id="vipps-button-settings-express-args"> | |
| 1212 | + <fieldset> | |
| 1213 | + <label><input type="checkbox" name="express[tmpConfig][rounded]" checked=""><?php _e('Rounded', 'woo-vipps'); ?></label> | |
| 1214 | + <label><input type="checkbox" name="express[tmpConfig][compact]"><?php _e('Compact', 'woo-vipps'); ?></label> | |
| 1215 | + <label><input type="checkbox" name="express[tmpConfig][stretched]"><?php _e('Stretched', 'woo-vipps'); ?></label> | |
| 1216 | + </fieldset> | |
| 1217 | + <fieldset> | |
| 1218 | + <legend><?php _e('Language', 'woo-vipps'); ?></legend> | |
| 1219 | + <label><input type="radio" name="express[tmpConfig][language]" checked value="store"><?php _e('Store language', 'woo-vipps'); ?></label> | |
| 1220 | + <label><input type="radio" name="express[tmpConfig][language]" value="en"><?php _e('English', 'woo-vipps'); ?></label> | |
| 1221 | + <label><input type="radio" name="express[tmpConfig][language]" value="no"><?php _e('Norwegian', 'woo-vipps'); ?></label> | |
| 1222 | + <label><input type="radio" name="express[tmpConfig][language]" value="dk"><?php _e('Danish', 'woo-vipps'); ?></label> | |
| 1223 | + <label><input type="radio" name="express[tmpConfig][language]" value="sv"><?php _e('Swedish', 'woo-vipps'); ?></label> | |
| 1224 | + <?php if ($this->get_payment_method_name() === 'MobilePay'): ?> | |
| 1225 | + <label><input type="radio" disabled="" name="express[tmpConfig][language]" value="fi"><?php _e('Finnish', 'woo-vipps'); ?></label> | |
| 1226 | + <?php endif; ?> | |
| 1227 | + </fieldset> | |
| 1021 | 1228 | |
| 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> | |
| 1229 | + <?php if ($this->get_payment_method_name() !== 'MobilePay'): ?> | |
| 1230 | + <p><?php printf(__('Finnish is currently only available with the %s payment method.', 'woo-vipps'), 'MobilePay'); ?></p> | |
| 1231 | + <?php endif; ?> | |
| 1034 | 1232 | |
| 1035 | - <!-- END EXPRESS SECTION --> | |
| 1233 | + <fieldset> | |
| 1234 | + <legend><?php _e('Verb', 'woo-vipps'); ?></legend> | |
| 1235 | + <label><input type="radio" name="express[tmpConfig][verb]" checked value="buy"><?php _e('Buy', 'woo-vipps'); ?></label> | |
| 1236 | + <label><input type="radio" name="express[tmpConfig][verb]" value="pay"><?php _e('Pay', 'woo-vipps'); ?></label> | |
| 1237 | + <label><input type="radio" name="express[tmpConfig][verb]" value="continue"><?php _e('Continue', 'woo-vipps'); ?></label> | |
| 1238 | + <label><input type="radio" name="express[tmpConfig][verb]" value="confirm"><?php _e('Confirm', 'woo-vipps'); ?></label> | |
| 1239 | + <label><input type="radio" name="express[tmpConfig][verb]" value="donate"><?php _e('Donate', 'woo-vipps'); ?></label> | |
| 1240 | + <label><input type="radio" name="express[tmpConfig][verb]" value="express"><?php _e('Express', 'woo-vipps'); ?></label> | |
| 1241 | + </fieldset> | |
| 1242 | + <fieldset> | |
| 1243 | + <legend><?php _e('Variant', 'woo-vipps'); ?></legend> | |
| 1244 | + <label><input type="radio" name="express[tmpConfig][variant]" checked value="primary"><?php _e('Primary', 'woo-vipps'); ?></label> | |
| 1245 | + <label><input type="radio" name="express[tmpConfig][variant]" value="dark"><?php _e('Dark (WCAG AAA)', 'woo-vipps'); ?></label> | |
| 1246 | + <label><input type="radio" name="express[tmpConfig][variant]" value="light"><?php _e('Light (WCAG AAA)', 'woo-vipps'); ?></label> | |
| 1247 | + </fieldset> | |
| 1036 | 1248 | </div> |
| 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> | |
| 1042 | - | |
| 1043 | - </form> | |
| 1250 | + <!-- Button preview that changes depending on the chosen parameters. LP 2026-06-24 --> | |
| 1251 | + <?php echo $this->get_html_button($init_args); ?> | |
| 1044 | 1252 | </div> |
| 1045 | 1253 | |
| 1046 | 1254 | <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 | - } | |
| 1255 | + // When inputs change, update the preview args. LP 2026-06-24 | |
| 1256 | + jQuery('#vipps-button-settings-express-args input').on('click', updatePreview); | |
| 1053 | 1257 | |
| 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> | |
| 1258 | + let currentContext = '<?php echo $init_context; ?>'; | |
| 1259 | + let contextConfigs = <?php echo json_encode($configs) ?: "{}"; ?> // maps context slug to config object. LP 2026-06-24 | |
| 1260 | + | |
| 1261 | + // Updates the actual html inputs from given config. LP 2026-07-01 | |
| 1262 | + function setInputsFromConfig(context, config) { | |
| 1263 | + const useGlobalConfig = Boolean(config?.["use-global-config"]); | |
| 1264 | + const isGlobal = "global" === context; | |
| 1265 | + | |
| 1266 | + // Only show the 'use-global-config' checkbox for nonglobal context. LP 2026-06-26 | |
| 1267 | + jQuery('#use-global-config-container').toggleClass('hidden', isGlobal); | |
| 1268 | + | |
| 1269 | + // Nonglobal contexts with useGlobalConfig, and empty configs, should fallback to the global config. LP 2026-06-26 | |
| 1270 | + if (!config || (!isGlobal && useGlobalConfig)) { | |
| 1271 | + config = contextConfigs["global"]; | |
| 1272 | + | |
| 1273 | + jQuery('input[name="express[tmpConfig][use-global-config]"]').prop("checked", true); | |
| 1274 | + | |
| 1275 | + // When using global config, the inputs should be disabled until its unchecked. LP 2026-06-26 | |
| 1276 | + jQuery('#vipps-button-settings-express-args input').prop("disabled", true); | |
| 1277 | + } else { | |
| 1278 | + jQuery('input[name="express[tmpConfig][use-global-config]"]').prop("checked", false); | |
| 1279 | + jQuery('#vipps-button-settings-express-args input').prop("disabled", false); | |
| 1280 | + } | |
| 1281 | + | |
| 1282 | + Object.entries(config).forEach(([key, val]) => { | |
| 1283 | + if ("use-global-config" === key) return; | |
| 1284 | + const inputs = jQuery(`input[name="express[tmpConfig][${key}]"]`); | |
| 1285 | + const type = inputs.prop('type'); | |
| 1286 | + switch (type) { | |
| 1287 | + case "checkbox": | |
| 1288 | + inputs.prop('checked', typeof val === "boolean" ? val : "true" === val); | |
| 1289 | + break; | |
| 1290 | + case "radio": | |
| 1291 | + inputs.filter(`[value="${val}"]`).prop('checked', true); | |
| 1292 | + break; | |
| 1293 | + default: | |
| 1294 | + console.error(`woo-vipps: Unexpected input type '${type}' for button config. key=${key}, val=${val}`); | |
| 1295 | + } | |
| 1296 | + }); | |
| 1297 | + | |
| 1298 | + updatePreview(); | |
| 1299 | + } | |
| 1300 | + // init the starting config from option. LP 2026-06-25 | |
| 1301 | + setInputsFromConfig(currentContext, contextConfigs[currentContext]); | |
| 1302 | + | |
| 1303 | + // Update the preview web component's attributes. LP 2026-06-24 | |
| 1304 | + function updatePreview(event) { | |
| 1305 | + const args = getPreviewArgs(); | |
| 1306 | + // 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 | |
| 1307 | + // if ('store' === args.language) args.language = '<?php echo $this->get_customer_language(); ?>'; | |
| 1308 | + if ('store' === args.language) args.language = '<?php echo substr(get_locale(), 0, 2); ?>'; | |
| 1309 | + const button = jQuery('#vipps-button-express-preview'); | |
| 1310 | + button.attr(args); | |
| 1311 | + } | |
| 1312 | + | |
| 1313 | + function getPreviewArgs() { | |
| 1314 | + const args = {}; | |
| 1315 | + jQuery('#vipps-button-settings-express-args input').each(function () { | |
| 1316 | + // inputs are put in form arrays like 'express[tmpConfig][attribute]', so extract the actual attribute name. LP 2026-06-24 | |
| 1317 | + const matches = [...this.name.matchAll(/\[([^\]]+)\]/g)]; | |
| 1318 | + const attr = matches.length ? matches[matches.length - 1][1] : null; | |
| 1319 | + if (!attr) { | |
| 1320 | + console.error("woo-vipps: Could not extract attribute name for button preview:", this); | |
| 1321 | + return; | |
| 1322 | + } | |
| 1323 | + if (this.type === 'checkbox') { | |
| 1324 | + args[attr] = this.checked; | |
| 1325 | + } else if (this.checked) { | |
| 1326 | + args[attr] = this.value; | |
| 1327 | + } | |
| 1328 | + }); | |
| 1329 | + | |
| 1330 | + return args; | |
| 1331 | + } | |
| 1332 | + | |
| 1333 | + // Stores selected config for context and switches to another (if changed). LP 2026-07-01 | |
| 1334 | + function updateContext() { | |
| 1335 | + const wasGlobal = "global" === currentContext; | |
| 1336 | + const useGlobalConfig = jQuery('#use-global-config-container input').prop("checked"); | |
| 1337 | + | |
| 1338 | + // Store config to global, unless its a non-global context that uses global config. LP 2026-06-25 | |
| 1339 | + if (wasGlobal || !useGlobalConfig) { | |
| 1340 | + contextConfigs[currentContext] = getPreviewArgs(); | |
| 1341 | + } else { | |
| 1342 | + contextConfigs[currentContext] = {'use-global-config': true}; | |
| 1343 | + } | |
| 1344 | + | |
| 1345 | + // Swap to new context: set all input fields to the stored values if exists. LP 2026-06-25 | |
| 1346 | + const newContext = jQuery("#context").val(); | |
| 1347 | + const newConfig = contextConfigs[newContext]; | |
| 1348 | + | |
| 1349 | + setInputsFromConfig(newContext, newConfig); | |
| 1350 | + currentContext = newContext; | |
| 1351 | + } | |
| 1352 | + | |
| 1353 | + // Before submit: delete the tmpConfig for the current selected values, and add the stored contextConfigs to the post data. LP 2026-06-24 | |
| 1354 | + jQuery('#vipps-button-settings-form').on('formdata', e => { | |
| 1355 | + const formData = e?.originalEvent?.formData; | |
| 1356 | + if (!formData) return; | |
| 1357 | + | |
| 1358 | + // run this to store current context config before posting. LP 2026-06-24 | |
| 1359 | + updateContext(); | |
| 1360 | + | |
| 1361 | + // now we can delete the current temporary config from post data. LP 2026-06-24 | |
| 1362 | + const keysToDelete = []; | |
| 1363 | + for (const [key] of formData.entries()) { | |
| 1364 | + if (key.startsWith("express[tmpConfig][")) { | |
| 1365 | + keysToDelete.push(key); | |
| 1366 | + } | |
| 1367 | + } | |
| 1368 | + keysToDelete.forEach(key => formData.delete(key)); | |
| 1369 | + | |
| 1370 | + // Now add the actual post data from the stored global contextConfigs. LP 2026-06-24 | |
| 1371 | + Object.entries(contextConfigs).forEach(([context, config]) => { | |
| 1372 | + Object.entries(config).forEach(([key, val]) => { | |
| 1373 | + formData.append(`express[configs][${context}][${key}]`, val); | |
| 1374 | + }); | |
| 1375 | + }); | |
| 1376 | + }); | |
| 1377 | + </script> | |
| 1061 | 1378 | <?php |
| 1062 | 1379 | } |
| 1063 | 1380 | |
| 1064 | 1381 | |
| @@ -1392,12 +1709,14 @@ | ||
| 1392 | 1709 | <?php |
| 1393 | 1710 | } |
| 1394 | 1711 | // Scripts used in the backend |
| 1395 | 1712 | public function admin_enqueue_scripts($hook) { |
| 1713 | + | |
| 1714 | + wp_register_script('vipps-admin',plugins_url('js/admin.js',__FILE__),array('jquery'),filemtime(dirname(__FILE__) . "/js/admin.js"), 'all'); | |
| 1715 | + $this->vippsJSConfig['vippssecnonce'] = wp_create_nonce('vippssecnonce'); | |
| 1716 | + wp_localize_script('vipps-admin', 'VippsConfig', $this->vippsJSConfig); | |
| 1396 | 1717 | // Add certain translations very late so translation plugins get a chance to work. IOK 2026-02-02 |
| 1397 | - $this->script_add_vippslocale(); | |
| 1398 | - | |
| 1399 | - wp_register_script('vipps-admin',plugins_url('js/admin.js',__FILE__),array('jquery','vipps-gw'),filemtime(dirname(__FILE__) . "/js/admin.js"), 'all'); | |
| 1718 | + $this->script_add_vippslocale('vipps-admin'); | |
| 1400 | 1719 | wp_enqueue_script('vipps-admin'); |
| 1401 | 1720 | |
| 1402 | 1721 | wp_enqueue_style('vipps-admin-style',plugins_url('css/admin.css',__FILE__),array(),filemtime(dirname(__FILE__) . "/css/admin.css"), 'all'); |
| 1403 | 1722 | wp_enqueue_style('vipps-fonts'); |
| @@ -1467,12 +1786,9 @@ | ||
| 1467 | 1786 | |
| 1468 | 1787 | public function wp_register_scripts () { |
| 1469 | 1788 | // We are going to use the 'hooks' library introduced by WP 5.1, but we still support WP 4.7. So if this isn't enqueues |
| 1470 | 1789 | // (which it only is if Gutenberg is active) or not provided at all, add it now. |
| 1471 | - if (!wp_script_is( 'wp-hooks', 'registered')) { | |
| 1472 | - wp_register_script('wp-hooks', plugins_url('/compat/hooks.min.js', __FILE__)); | |
| 1473 | - } | |
| 1474 | - wp_register_script('vipps-gw',plugins_url('js/vipps.js',__FILE__),array('jquery','wp-hooks'),filemtime(dirname(__FILE__) . "/js/vipps.js"), 'true'); | |
| 1790 | + wp_register_script('vipps-gw',plugins_url('js/vipps.js',__FILE__),array('jquery','wp-hooks', 'wp-api-fetch','vipps-widget-sdk'),filemtime(dirname(__FILE__) . "/js/vipps.js"), true); | |
| 1475 | 1791 | |
| 1476 | 1792 | // Badges - web components provided by Vipps MobilePay to display payment options in-store. |
| 1477 | 1793 | wp_register_script('vipps-onsite-messageing', |
| 1478 | 1794 | plugins_url('js/vipps-on-site-messaging.js', WC_VIPPS_PAYMENT_MAIN_FILE), |
| @@ -1482,35 +1798,120 @@ | ||
| 1482 | 1798 | 'in_footer' => true, |
| 1483 | 1799 | 'strategy' => 'async', |
| 1484 | 1800 | ], |
| 1485 | 1801 | ); |
| 1802 | + | |
| 1803 | + add_filter( 'script_loader_tag', function($tag, $handle,$src) { | |
| 1804 | + if ($handle == 'vipps-widget-sdk') { | |
| 1805 | + $tag = preg_replace("!^<script!", "<script data-vipps-widget-sdk ", $tag); | |
| 1806 | + return $tag; | |
| 1807 | + } | |
| 1808 | + return $tag; | |
| 1809 | + },10,3); | |
| 1810 | + | |
| 1811 | + wp_register_script('vipps-widget-sdk', "https://cdn.vippsmobilepay.com/js/widget-sdk/vipps-widget.js", | |
| 1812 | + array('vipps-button-webcomponent'), | |
| 1813 | + filemtime(dirname(WC_VIPPS_PAYMENT_MAIN_FILE) . '/js/vipps.js'), | |
| 1814 | + ['in_footer' => true] | |
| 1815 | + ); | |
| 1816 | + | |
| 1817 | + // Button web component downloaded from https://cdn.vippsmobilepay.com/js/button/button.js. LP 2026-06-24 | |
| 1818 | + wp_register_script('vipps-button-webcomponent', | |
| 1819 | + plugins_url('js/vipps-button.js', WC_VIPPS_PAYMENT_MAIN_FILE), | |
| 1820 | + array(), | |
| 1821 | + filemtime(dirname(WC_VIPPS_PAYMENT_MAIN_FILE) . '/js/vipps-button.js'), | |
| 1822 | + [ | |
| 1823 | + 'in_footer' => false | |
| 1824 | + ] | |
| 1825 | + ); | |
| 1486 | 1826 | } |
| 1487 | 1827 | |
| 1488 | 1828 | // Runs late in both wp_enqueue_scripts and admin_enqueue_scripts to make it more compatible with translation plugins IOK 2026-02-02 |
| 1489 | - public function script_add_vippslocale () { | |
| 1829 | + public function script_add_vippslocale ($handle) { | |
| 1490 | 1830 | // This is actually for the payment block, where localize script has started to not-work in certain contexts. IOK 2022-12-13 |
| 1831 | + $name = $this->get_payment_method_name(); | |
| 1491 | 1832 | $strings = array( |
| 1492 | - 'Continue with Vipps'=>sprintf(__('Continue with %1$s', 'woo-vipps'), $this->get_payment_method_name()), | |
| 1493 | - 'Vipps'=> sprintf(__('%1$s', 'woo-vipps'), $this->get_payment_method_name()), | |
| 1494 | - 'pay_with_card' => sprintf(__('Pay with card through %1$s', 'woo-vipps'), $this->get_payment_method_name()), | |
| 1833 | + 'Continue with Vipps'=>sprintf(__('Continue with %1$s', 'woo-vipps'), $name), | |
| 1834 | + 'Vipps'=> sprintf(__('%1$s', 'woo-vipps'), $name), | |
| 1835 | + 'pay_with_card' => sprintf(__('Pay with card through %1$s', 'woo-vipps'), $name), | |
| 1836 | + 'termsAndConditionsError' => __( 'Please read and accept the terms and conditions to proceed with your order.', 'woocommerce' ), | |
| 1837 | + 'temporaryError' => sprintf(__('%1$s is temporarily unavailable.','woo-vipps'),$name), | |
| 1838 | + 'successMessage' => sprintf(__('To the %1$s app!','woo-vipps'), $name), | |
| 1839 | + 'cancel'=> __("Cancel", 'woo-vipps'), | |
| 1840 | + 'close'=> __("Close", 'woo-vipps'), | |
| 1841 | + 'missingPaymentUrl'=> __("Successful checkout response has no payment URL", 'woo-vipps'), | |
| 1842 | + 'expressCheckoutFailed'=> __("Express checkout failed", 'woo-vipps'), | |
| 1843 | + 'unexpectedCheckoutResponse'=> __("Unexpected express checkout response", 'woo-vipps'), | |
| 1844 | + 'vippsCheckoutFailed'=> __("Vipps Mobilepay checkout failed", 'woo-vipps'), | |
| 1845 | + 'correctHighlightedFields'=> __("Please correct the highlighted fields.", 'woo-vipps'), | |
| 1846 | + 'checkFormBeforeContinuing'=> __("Please check the form before continuing.", 'woo-vipps'), | |
| 1847 | + 'cartCheckoutUnavailable'=> __("Cannot start express checkout: cart checkout is unavailable", 'woo-vipps'), | |
| 1848 | + 'productIdentifiersMissing'=> __("Cannot buy product: product id, variation id and sku are missing", 'woo-vipps'), | |
| 1849 | + 'productFormNotFound'=> __("Cannot buy product: product form not found", 'woo-vipps'), | |
| 1850 | + 'paymentSuccessfulRedirecting' => __("Payment successful. Redirecting…", 'woo-vipps'), | |
| 1495 | 1851 | ); |
| 1496 | - wp_localize_script('vipps-gw', 'VippsLocale', $strings); | |
| 1852 | + wp_localize_script($handle, 'VippsLocale', $strings); | |
| 1497 | 1853 | } |
| 1498 | 1854 | |
| 1499 | 1855 | public function wp_enqueue_scripts() { |
| 1856 | + // Add late: if this value isn't 'yes' we wil not add order attribution to express orders. IOK 2026-09-10 | |
| 1857 | + $this->vippsJSConfig['expressOrderAttribution'] = $this->gateway()->get_option('vippsorderattribution'); | |
| 1500 | 1858 | wp_localize_script('vipps-gw', 'VippsConfig', $this->vippsJSConfig); |
| 1501 | 1859 | // Add certain translations very late so translation plugins get a chance to work. IOK 2026-02-02 |
| 1502 | - $this->script_add_vippslocale(); | |
| 1860 | + $this->script_add_vippslocale('vipps-gw'); | |
| 1503 | 1861 | |
| 1504 | 1862 | wp_enqueue_script('vipps-gw'); |
| 1505 | 1863 | wp_enqueue_style('vipps-gw',plugins_url('css/vipps.css',__FILE__),array(),filemtime(dirname(__FILE__) . "/css/vipps.css")); |
| 1864 | + wp_enqueue_script('vipps-button-webcomponent'); | |
| 1506 | 1865 | } |
| 1507 | 1866 | |
| 1867 | + // These scripts should be loaded only on the checkout screen and is used only for the classic shortcode checkout and | |
| 1868 | + // the pay-for-order screen. IOK 2026-09-15 | |
| 1869 | + public function enqueue_classic_checkout_scripts () { | |
| 1870 | + if ( ! function_exists( 'is_checkout' ) || ! is_checkout() || is_order_received_page() ) { | |
| 1871 | + return; | |
| 1872 | + } | |
| 1873 | + // Order-pay is rendered by the classic form even with a Blocks checkout page. | |
| 1874 | + // It must bypass the check for the parent checkout page's block content. | |
| 1875 | + if ( ! is_checkout_pay_page() ) { | |
| 1876 | + $utils = '\\Automattic\\WooCommerce\\Blocks\\Utils\\CartCheckoutUtils'; | |
| 1877 | + $uses_checkout_block = is_callable( array( $utils, 'is_checkout_block_default' ) ) | |
| 1878 | + ? $utils::is_checkout_block_default() | |
| 1879 | + : has_block( 'woocommerce/checkout', wc_get_page_id( 'checkout' ) ); | |
| 1508 | 1880 | |
| 1881 | + if ( $uses_checkout_block ) { | |
| 1882 | + return; | |
| 1883 | + } | |
| 1884 | + } | |
| 1885 | + | |
| 1886 | + // This script uses jQuery because the classic checkout screen does too. IOK 2026-09-15 | |
| 1887 | + $relative_path = 'js/vipps-classic-checkout.js'; | |
| 1888 | + wp_enqueue_script( | |
| 1889 | + 'vipps-classic-checkout', | |
| 1890 | + plugins_url( $relative_path, __FILE__ ), | |
| 1891 | + array( 'jquery', 'wc-checkout', 'vipps-gw' ), | |
| 1892 | + filemtime( plugin_dir_path( __FILE__ ) . $relative_path ), | |
| 1893 | + true | |
| 1894 | + ); | |
| 1895 | + | |
| 1896 | + if ( is_checkout_pay_page() ) { | |
| 1897 | + $order = wc_get_order( absint( get_query_var( 'order-pay' ) ) ); | |
| 1898 | + wp_add_inline_script( 'vipps-classic-checkout', 'window.VippsOrderPayConfig = ' . wp_json_encode( array( | |
| 1899 | + 'orderId' => $order ? $order->get_id() : 0, | |
| 1900 | + 'orderKey' => $order ? $order->get_order_key() : '', | |
| 1901 | + 'billingEmail' => $order ? $order->get_billing_email() : '', | |
| 1902 | + 'endpoint' => $order ? rest_url( 'wc/store/v1/checkout/' . $order->get_id() ) : '', | |
| 1903 | + 'nonce' => wp_create_nonce( 'wc_store_api' ), | |
| 1904 | + 'billingAddress' => $order ? $order->get_address( 'billing' ) : array(), | |
| 1905 | + 'shippingAddress' => $order ? $order->get_address( 'shipping' ) : array(), | |
| 1906 | + ) ) . ';', 'before' ); | |
| 1907 | + } | |
| 1908 | + } | |
| 1909 | + | |
| 1910 | + | |
| 1509 | 1911 | public function add_shortcodes() { |
| 1510 | 1912 | add_shortcode('woo_vipps_buy_now', array($this, 'buy_now_button_shortcode')); |
| 1511 | 1913 | add_shortcode('woo_vipps_express_checkout_button', array($this, 'express_checkout_button_shortcode')); |
| 1512 | - add_shortcode('woo_vipps_express_checkout_banner', array($this, 'express_checkout_banner_shortcode')); | |
| 1513 | 1914 | |
| 1514 | 1915 | // Badges, if using shortcodes |
| 1515 | 1916 | // New vipps-mobilepay-badge shortcode. LP 19.11.2024 |
| 1516 | 1917 | add_shortcode('vipps-mobilepay-badge', array($this, 'vipps_mobilepay_badge_shortcode')); |
| @@ -1515,8 +1916,11 @@ | ||
| 1515 | 1916 | // New vipps-mobilepay-badge shortcode. LP 19.11.2024 |
| 1516 | 1917 | add_shortcode('vipps-mobilepay-badge', array($this, 'vipps_mobilepay_badge_shortcode')); |
| 1517 | 1918 | // Legacy vipps-badge shortcode. LP 19.11.2024 |
| 1518 | 1919 | add_shortcode('vipps-badge', array($this, 'vipps_badge_shortcode')); |
| 1920 | + | |
| 1921 | + // special page handling, previously a fake page. LP 2026-08-25 | |
| 1922 | + add_shortcode('vipps_special_page', array($this, 'vipps_special_page_shortcode')); | |
| 1519 | 1923 | } |
| 1520 | 1924 | |
| 1521 | 1925 | |
| 1522 | 1926 | public function log ($what,$type='info') { |
| @@ -1542,8 +1946,10 @@ | ||
| 1542 | 1946 | } |
| 1543 | 1947 | |
| 1544 | 1948 | // Show express button option on checkout form. LP 2026-03-23 |
| 1545 | 1949 | public function checkout_before_customer_details_express () { |
| 1950 | + if (did_action('woo_vipps_checkout_before_customer_details_express')) return; | |
| 1951 | + do_action('woo_vipps_checkout_before_customer_details_express'); | |
| 1546 | 1952 | $gw = $this->gateway(); |
| 1547 | 1953 | if (!$gw->show_express_checkout()) return; |
| 1548 | 1954 | $this->express_checkout_section_html(); |
| 1549 | 1955 | } |
| @@ -1553,37 +1959,12 @@ | ||
| 1553 | 1959 | $header_text = __('Express Checkout', 'woo-vipps'); |
| 1554 | 1960 | $header = "<legend class='express-header'>$header_text</legend>"; |
| 1555 | 1961 | $div_classes = "legacy-checkout vipps-express-checkout $payment_method"; |
| 1556 | 1962 | echo "<fieldset class='$div_classes'>$header"; |
| 1557 | - $this->cart_express_checkout_button_html(); | |
| 1963 | + $this->checkout_express_checkout_button_html(); | |
| 1558 | 1964 | echo '</fieldset>'; |
| 1559 | 1965 | } |
| 1560 | 1966 | |
| 1561 | - public function express_checkout_banner() { | |
| 1562 | - $gw = $this->gateway(); | |
| 1563 | - if (!$gw->show_express_checkout()) return; | |
| 1564 | - return $this->express_checkout_banner_html(); | |
| 1565 | - } | |
| 1566 | - | |
| 1567 | - public function express_checkout_banner_html() { | |
| 1568 | - $url = $this->express_checkout_url(); | |
| 1569 | - $url = wp_nonce_url($url,'express','sec'); | |
| 1570 | - $text = __('Skip entering your address and just checkout using', 'woo-vipps'); | |
| 1571 | - $linktext = 'Express'; // dont translate. LP 2025-09-03 | |
| 1572 | - $logo = $this->get_express_banner_logo(); | |
| 1573 | - $payment_method = $this->get_payment_method_name(); | |
| 1574 | - | |
| 1575 | - $img_classes = 'express-banner-logo inline negative ' . strtolower($payment_method) . '-logo'; | |
| 1576 | - $div_classes = 'woocommerce-info ' . strtolower($payment_method) . '-info'; | |
| 1577 | - $a_classes = 'express-banner-link ' . strtolower($payment_method) . '-link'; | |
| 1578 | - | |
| 1579 | - $message = $text . "<a href='$url' class='$a_classes'><img class='$img_classes' border=0 src='$logo' alt='$payment_method'/>$linktext!</a>"; | |
| 1580 | - $message = apply_filters('woo_vipps_express_checkout_banner', $message, $url, $payment_method); | |
| 1581 | - ?> | |
| 1582 | - <div class="<?php echo $div_classes;?>"><?php echo $message;?></div> | |
| 1583 | - <?php | |
| 1584 | - } | |
| 1585 | - | |
| 1586 | 1967 | // Show the express button if reasonable to do so |
| 1587 | 1968 | public function cart_express_checkout_button() { |
| 1588 | 1969 | $gw = $this->gateway(); |
| 1589 | 1970 | |
| @@ -1595,29 +1976,71 @@ | ||
| 1595 | 1976 | public function minicart_express_checkout_button() { |
| 1596 | 1977 | $gw = $this->gateway(); |
| 1597 | 1978 | |
| 1598 | 1979 | if ($gw->show_express_checkout()){ |
| 1599 | - return $this->cart_express_checkout_button_html(true); | |
| 1980 | + return $this->cart_express_checkout_button_html('minicart'); | |
| 1600 | 1981 | } |
| 1601 | 1982 | } |
| 1602 | 1983 | |
| 1603 | - public function cart_express_checkout_button_html($minicart = false) { | |
| 1604 | - $url = $this->express_checkout_url(); | |
| 1605 | - $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)); | |
| 1984 | + // This is for the Vipps SDK button used instead of the normal "pay for order" and "confirm order" buttons | |
| 1985 | + // on the classic checkout and pay-for-order pages. It gets swapped in when the user selects vipps, and swapped out otherwise. | |
| 1986 | + public function add_checkout_button_for_classic () { | |
| 1987 | + $button = $this->get_html_button_for_context('checkout'); | |
| 1988 | + $submit = "<div class='vipps-classic-checkout-container'><button id='vipps-classic-checkout-submit' class='hidden vipps-submit-wrapper' type='submit'>$button</button></div>"; | |
| 1989 | + echo $submit; | |
| 1990 | + } | |
| 1991 | + | |
| 1992 | + public function cart_express_checkout_button_html($context= 'cart') { | |
| 1993 | + $button = apply_filters('woo_vipps_express_checkout_button', $this->get_html_button_for_context($context)); | |
| 1608 | 1994 | $method = $this->get_payment_method_name(); |
| 1609 | 1995 | $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; | |
| 1996 | + $url = "#"; | |
| 1997 | + $sec = wp_create_nonce('express'); | |
| 1998 | + $html = "<a href='#' class='vipps-express-checkout short " . esc_attr($method) . "' title='" . esc_attr($title) . "' data-sec='" . esc_attr($sec) . "'>$button</a>"; | |
| 1999 | + $html = apply_filters('woo_vipps_cart_express_checkout_button', $html, $url); | |
| 2000 | + echo $html; | |
| 1613 | 2001 | } |
| 1614 | 2002 | |
| 2003 | + public function checkout_express_checkout_button_html() { | |
| 2004 | + $button = apply_filters('woo_vipps_express_checkout_button', $this->get_html_button_for_context('checkout')); | |
| 2005 | + $method = $this->get_payment_method_name(); | |
| 2006 | + $title = sprintf(__('Buy now with %1$s!', 'woo-vipps'), $method); | |
| 2007 | + $url = "#"; | |
| 2008 | + $sec = wp_create_nonce('express'); | |
| 2009 | + $html = "<a href='#' class='vipps-express-checkout short " . esc_attr($method) . "' title='" . esc_attr($title) . "' data-sec='" . esc_attr($sec) . "'>$button</a>"; | |
| 2010 | + $html = apply_filters('woo_vipps_cart_express_checkout_button', $html, $url); | |
| 2011 | + echo $html; | |
| 2012 | + } | |
| 2013 | + | |
| 1615 | 2014 | // 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 | 2015 | // cached. Therefore stock, purchasability etc will be done later. IOK 2018-10-02 |
| 1617 | 2016 | 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>"; | |
| 2017 | + // The new web component button args. LP 2026-07-02 | |
| 2018 | + $button_args = $this->get_html_button_default_attrs(); | |
| 2019 | + unset($button_args['brand']); | |
| 2020 | + | |
| 2021 | + // Variant exists for the product variant. LP 2026-07-02 | |
| 2022 | + if (isset($button_args['variant'])) $button_args['button_variant'] = $button_args['variant']; | |
| 2023 | + unset($button_args['variant']); | |
| 2024 | + | |
| 2025 | + $args = shortcode_atts( | |
| 2026 | + array(...$button_args, | |
| 2027 | + 'id' => '','variant'=> '','sku' => '', | |
| 2028 | + ), | |
| 2029 | + $atts, | |
| 2030 | + ); | |
| 2031 | + | |
| 2032 | + // Variant exists for the product variant. LP 2026-07-02 | |
| 2033 | + $button_args = $args; | |
| 2034 | + if (isset($button_args['button_variant'])) $button_args['variant'] = $button_args['button_variant']; | |
| 2035 | + unset($button_args['button_variant']); | |
| 2036 | + unset($button_args['sku']); | |
| 2037 | + unset($button_args['id']); | |
| 2038 | + // NB: the language may be incorrect for the shortcode, see web component bug at https://developer.vippsmobilepay.com/docs/knowledge-base/buttons/ | |
| 2039 | + // "Note also that there is a bug in the library, and it currently only renders one language per page." | |
| 2040 | + // 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 | |
| 2041 | + | |
| 2042 | + 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 | 2043 | } |
| 1621 | 2044 | |
| 1622 | 2045 | // 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 | 2046 | public function express_checkout_button_shortcode() { |
| @@ -1623,19 +2046,11 @@ | ||
| 1623 | 2046 | public function express_checkout_button_shortcode() { |
| 1624 | 2047 | $gw = $this->gateway(); |
| 1625 | 2048 | if (!$gw->cart_supports_express_checkout()) return; |
| 1626 | 2049 | ob_start(); |
| 1627 | - $this->cart_express_checkout_button_html('shortcode'); | |
| 2050 | + $this->cart_express_checkout_button_html('cart'); | |
| 1628 | 2051 | return ob_get_clean(); |
| 1629 | 2052 | } |
| 1630 | - // Show a banner normally shown for non-logged-in-users at the checkout page. It does not need to check if we are to show the button, obviously, but needs to see if the cart works | |
| 1631 | - public function express_checkout_banner_shortcode() { | |
| 1632 | - $gw = $this->gateway(); | |
| 1633 | - if (!$gw->cart_supports_express_checkout()) return; | |
| 1634 | - ob_start(); | |
| 1635 | - $this->express_checkout_banner_html(); | |
| 1636 | - return ob_get_clean(); | |
| 1637 | - } | |
| 1638 | 2053 | |
| 1639 | 2054 | // Manage the various product meta fields |
| 1640 | 2055 | public function process_product_meta ($id, $post) { |
| 1641 | 2056 | // This is for the 'buy now' button |
| @@ -2286,77 +2701,93 @@ | ||
| 2286 | 2701 | return null; |
| 2287 | 2702 | } |
| 2288 | 2703 | } |
| 2289 | 2704 | |
| 2705 | + // Special pages, and some callbacks. IOK 2018-05-18 | |
| 2706 | + public function template_redirect() { | |
| 2290 | 2707 | |
| 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 | - } | |
| 2708 | + // Handle legacy vipps-buy-now urls that auto-start express checkout for certain product - in QR codes etc IOK 2026-09-11 | |
| 2709 | + // We redirect these to the new location. | |
| 2710 | + $path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); | |
| 2711 | + if (( ($_GET['VippsSpecialPage'] ?? '') == 'vipps-buy-product') || ($path && preg_match("!/vipps-buy-product/?$!", $path)) ) { | |
| 2712 | + $url = static::get_special_page_url(); | |
| 2713 | + $_GET['action'] = 'buy_product'; | |
| 2714 | + $q = build_query($_GET); | |
| 2715 | + wp_redirect($url . "?" . $q, 302); | |
| 2716 | + exit(); | |
| 2300 | 2717 | } |
| 2301 | - return $current; | |
| 2718 | + | |
| 2719 | + if (static::is_special_page()) { | |
| 2720 | + // Legacy: Stop the canonical redirect here. Unclear if still necessary. IOK 2026-09-11 | |
| 2721 | + remove_filter('template_redirect', 'redirect_canonical', 10); | |
| 2722 | + // dont cache special page. LP 2026-08-25 | |
| 2723 | + $this->nocache(); | |
| 2724 | + // Do the custom pre-load actions for these pages IOK 2026-09-11 | |
| 2725 | + do_action('woo_vipps_before_handling_special_page', ($_GET['action'] ?? "")); | |
| 2726 | + } | |
| 2302 | 2727 | } |
| 2303 | 2728 | |
| 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() ; | |
| 2729 | + // Ran in template redirect for the special page. IOK 2026-09-2 | |
| 2730 | + public function pre_special_page_actions ($action) { | |
| 2731 | + // Change title dynamically depending on action. LP 2026-09-02 | |
| 2732 | + add_filter('the_title', [$this, 'vipps_special_page_endpoint_title'], 10, 2); | |
| 2309 | 2733 | |
| 2310 | - if ($special) { | |
| 2311 | - remove_filter('template_redirect', 'redirect_canonical', 10); | |
| 2312 | - do_action('woo_vipps_before_handling_special_page', $special); | |
| 2734 | + // If we are handling the 'wait for payment' action, we need to poll the order status before | |
| 2735 | + // we start producing content IOK 2026-09-21 | |
| 2736 | + if ($action == 'wait_for_payment') { | |
| 2737 | + $this->handle_payment_poll_and_redirect(); | |
| 2738 | + } | |
| 2313 | 2739 | |
| 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 | - } | |
| 2740 | + // Some validation is required for this action | |
| 2741 | + if ($action == 'do_express_checkout') { | |
| 2742 | + $this->vipps_express_checkout_consistency_check(); | |
| 2319 | 2743 | } |
| 2744 | + // These two actions require an extra script | |
| 2745 | + if (in_array($action, ['buy_product','do_express_checkout'])) { | |
| 2746 | + wp_enqueue_script('vipps-purchase', plugins_url('js/vipps-purchase.js',__FILE__), ['vipps-gw'], | |
| 2747 | + filemtime(dirname(__FILE__) . "/js/vipps-purchase.js"), | |
| 2748 | + ['in_footer'=>true] | |
| 2749 | + ); | |
| 2750 | + } | |
| 2751 | + } | |
| 2320 | 2752 | |
| 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); | |
| 2753 | + // Dynamic special page title depending on endpoint/action, only frontend. LP 2026-09-02 | |
| 2754 | + public function vipps_special_page_endpoint_title($title, $postid = 0) { | |
| 2755 | + global $wp_query; | |
| 2756 | + // Comment from woocommerce's wc_page_endpoint_title where this logic is from: LP 2026-09-02 | |
| 2757 | + | |
| 2758 | + // In block themes the whole template (header, footer, content) renders inside the main | |
| 2759 | + // loop, so `the_title` fires for any post title rendered on the page (e.g. a product in a | |
| 2760 | + // server-rendered mini-cart) - not just the page's own heading. Only replace the title of | |
| 2761 | + // the queried page so an earlier title doesn't consume this one-shot filter. | |
| 2762 | + if ( ! is_null( $wp_query ) && ! is_admin() && is_main_query() && in_the_loop() && is_page() && $postid == static::get_special_page_id() ) { | |
| 2763 | + switch ($_GET['action'] ?? '') { | |
| 2764 | + case 'wait_for_payment': | |
| 2765 | + $title = __('Processing order', 'woo-vipps'); | |
| 2766 | + break; | |
| 2767 | + case 'do_express_checkout': | |
| 2768 | + case 'buy_product': | |
| 2769 | + $title = __('Express Checkout', 'woo-vipps'); | |
| 2770 | + break; | |
| 2327 | 2771 | } |
| 2328 | 2772 | } |
| 2773 | + return $title; | |
| 2329 | 2774 | } |
| 2775 | + | |
| 2330 | 2776 | // Template handling for special pages. IOK 2018-11-21 |
| 2777 | + // 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 | 2778 | public function template_include($template) { |
| 2332 | - $special = $this->is_special_page() ; | |
| 2333 | - if ($special) { | |
| 2779 | + if (static::is_special_page()) { | |
| 2334 | 2780 | // Get any special template override from the options IOK 2020-02-18 |
| 2335 | 2781 | $specific = $this->gateway()->get_option('vippsspecialpagetemplate'); |
| 2336 | 2782 | $found = locate_template($specific,false,false); |
| 2337 | 2783 | if ($found) $template=$found; |
| 2338 | 2784 | |
| 2339 | - return apply_filters('woo_vipps_special_page_template', $template, $special); | |
| 2785 | + return apply_filters('woo_vipps_special_page_template', $template, $_GET['action'] ?? ''); | |
| 2340 | 2786 | } |
| 2341 | 2787 | return $template; |
| 2342 | 2788 | } |
| 2343 | 2789 | |
| 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 | 2790 | // On the thank you page, we have a completed order, so we need to restore any saved cart and possibly log in |
| 2360 | 2791 | // the user if using Express Checkout IOK 2020-10-09 |
| 2361 | 2792 | public function woocommerce_before_thankyou ($orderid) { |
| 2362 | 2793 | $order = wc_get_order($orderid); |
| @@ -2361,9 +2792,9 @@ | ||
| 2361 | 2792 | public function woocommerce_before_thankyou ($orderid) { |
| 2362 | 2793 | $order = wc_get_order($orderid); |
| 2363 | 2794 | if ($order) { |
| 2364 | 2795 | // 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 | |
| 2796 | + // -- 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 | 2797 | $this->maybe_log_in_user($order); |
| 2367 | 2798 | $order->delete_meta_data('_vipps_limited_session'); |
| 2368 | 2799 | $order->save(); |
| 2369 | 2800 | |
| @@ -2424,9 +2855,8 @@ | ||
| 2424 | 2855 | |
| 2425 | 2856 | // Support adding pickup locations to any shipping rate using the 'woo_vipps_shipping_method_pickup_points' filter |
| 2426 | 2857 | // IOK 2025-11-19 |
| 2427 | 2858 | add_filter('woo_vipps_modify_express_checkout_rate', array($this, 'express_add_pickup_location_options'), 10, 4); |
| 2428 | - | |
| 2429 | 2859 | } |
| 2430 | 2860 | |
| 2431 | 2861 | public function get_payment_method_name() { |
| 2432 | 2862 | return $this->gateway()->get_option('payment_method_name'); |
| @@ -2446,14 +2876,13 @@ | ||
| 2446 | 2876 | public function after_setup_theme() { |
| 2447 | 2877 | // To facilitate development, allow loading the plugin-supplied translations. Must be called here at the earliest. |
| 2448 | 2878 | $ok = Vipps::load_plugin_textdomain('woo-vipps', false, basename( dirname( dirname( __FILE__ ) ) ) . "/languages"); |
| 2449 | 2879 | |
| 2450 | - // Vipps Checkout replaces the default checkout page, and currently uses its own page for this which needs to exist | |
| 2880 | + // Checkout replaces the default checkout page, and currently uses its own page for this which needs to exist | |
| 2451 | 2881 | // Will also probably be used to maintain a real utility-page for Vipps actions later for themes where this |
| 2452 | 2882 | // is important. |
| 2453 | 2883 | add_filter('woocommerce_create_pages', array($this, 'woocommerce_create_pages'), 50, 1); |
| 2454 | 2884 | |
| 2455 | - | |
| 2456 | 2885 | // Callbacks use the Woo API IOK 2018-05-18 |
| 2457 | 2886 | add_action( 'woocommerce_api_wc_gateway_vipps', array($this,'vipps_callback')); |
| 2458 | 2887 | add_action( 'woocommerce_api_vipps_shipping_details', array($this,'vipps_shipping_details_callback')); |
| 2459 | 2888 | |
| @@ -2464,19 +2893,21 @@ | ||
| 2464 | 2893 | add_action( 'woocommerce_cart_actions', array($this, 'cart_express_checkout_button')); |
| 2465 | 2894 | add_action( 'woocommerce_widget_shopping_cart_buttons', array($this, 'minicart_express_checkout_button'), 30); |
| 2466 | 2895 | |
| 2467 | 2896 | // 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 | |
| 2897 | + // replaced by the new express buttons in manner more like Gutenberg. for grepping: "express legacy checkout". LP 2026-03-23 | |
| 2469 | 2898 | add_action('woocommerce_checkout_before_customer_details', array($this, 'checkout_before_customer_details_express'), 5); |
| 2470 | 2899 | |
| 2471 | 2900 | add_action('woocommerce_after_add_to_cart_button', array($this, 'single_product_buy_now_button')); |
| 2472 | 2901 | add_action('woocommerce_after_shop_loop_item', array($this, 'loop_single_product_buy_now_button'), 20); |
| 2473 | 2902 | |
| 2903 | + // For the classic checkout page and pay-for-order page, use a custom submit button when payment method | |
| 2904 | + // is Vipps | |
| 2905 | + add_action('woocommerce_review_order_after_submit', array($this, 'add_checkout_button_for_classic')); | |
| 2906 | + add_action('woocommerce_pay_order_after_submit', array($this, 'add_checkout_button_for_classic')); | |
| 2474 | 2907 | |
| 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 | |
| 2908 | + // Special pages and callbacks handled by template_redirect. IOK 2023-02-22 | |
| 2477 | 2909 | add_action('template_redirect', array($this,'template_redirect'),1); |
| 2478 | - add_action('pre_handle_404', array($this, 'pre_handle_404'), 1, 2); | |
| 2479 | 2910 | |
| 2480 | 2911 | // Allow overriding their templates |
| 2481 | 2912 | add_filter('template_include', array($this,'template_include'), 10, 1); |
| 2482 | 2913 | |
| @@ -2483,21 +2914,8 @@ | ||
| 2483 | 2914 | // Ajax endpoints for checking the order status while waiting for confirmation |
| 2484 | 2915 | add_action('wp_ajax_nopriv_check_order_status', array($this, 'ajax_check_order_status')); |
| 2485 | 2916 | add_action('wp_ajax_check_order_status', array($this, 'ajax_check_order_status')); |
| 2486 | 2917 | |
| 2487 | - | |
| 2488 | - // Buying a single product directly using express checkout IOK 2018-09-28 | |
| 2489 | - add_action('wp_ajax_nopriv_vipps_buy_single_product', array($this, 'ajax_vipps_buy_single_product')); | |
| 2490 | - add_action('wp_ajax_vipps_buy_single_product', array($this, 'ajax_vipps_buy_single_product')); | |
| 2491 | - | |
| 2492 | - // This is for express checkout which we will also do asynchronously IOK 2018-05-28 | |
| 2493 | - add_action('wp_ajax_nopriv_do_express_checkout', array($this, 'ajax_do_express_checkout')); | |
| 2494 | - add_action('wp_ajax_do_express_checkout', array($this, 'ajax_do_express_checkout')); | |
| 2495 | - | |
| 2496 | - // Same thing, but for single products IOK 2018-05-28 | |
| 2497 | - add_action('wp_ajax_nopriv_do_single_product_express_checkout', array($this, 'ajax_do_single_product_express_checkout')); | |
| 2498 | - add_action('wp_ajax_do_single_product_express_checkout', array($this, 'ajax_do_single_product_express_checkout')); | |
| 2499 | - | |
| 2500 | 2918 | // Handle the cancel unpaid order action when the "hold stock" times out. |
| 2501 | 2919 | // For *normal* vipps orders, we run another cronjob every 5. minute which checks order status, |
| 2502 | 2920 | // therefore here it suffices to check if the order is 'cancelled' at Vipps, and if so we return. |
| 2503 | 2921 | // For Checkout the rules are different though. |
| @@ -2576,9 +2994,8 @@ | ||
| 2576 | 2994 | $this->vippsJSConfig = array(); |
| 2577 | 2995 | $this->vippsJSConfig['vippsajaxurl'] = admin_url('admin-ajax.php'); |
| 2578 | 2996 | $this->vippsJSConfig['BuyNowWith'] = __('Buy now with', 'woo-vipps'); |
| 2579 | 2997 | $this->vippsJSConfig['BuyNowWithVipps'] = sprintf(__('Buy now with %1$s', 'woo-vipps'), $this->get_payment_method_name()); |
| 2580 | - $this->vippsJSConfig['vippslogourl'] = plugins_url('img/vipps_logo_negativ_rgb_transparent.png',__FILE__); | |
| 2581 | 2998 | $this->vippsJSConfig['vippssmileurl'] = plugins_url('img/vmp-logo.png',__FILE__); |
| 2582 | 2999 | $this->vippsJSConfig['vippsbuynowbutton'] = sprintf(__( '%1$s Buy Now button', 'woo-vipps' ), $this->get_payment_method_name()); |
| 2583 | 3000 | $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 | 3001 | $this->vippsJSConfig['vippslanguage'] = $this->get_customer_language(); |
| @@ -2583,9 +3000,10 @@ | ||
| 2583 | 3000 | $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 | 3001 | $this->vippsJSConfig['vippslanguage'] = $this->get_customer_language(); |
| 2585 | 3002 | $this->vippsJSConfig['vippslocale'] = get_locale(); |
| 2586 | 3003 | $this->vippsJSConfig['vippsexpressbuttonurl'] = $this->get_payment_method_name(); |
| 2587 | - $this->vippsJSConfig['logoSvgUrl'] = $this->get_payment_logo('buy-now-block'); | |
| 3004 | + $this->vippsJSConfig['paymentMethodSlug'] = sanitize_title($this->get_payment_method_name()); | |
| 3005 | + $this->vippsJSConfig['paymentMethodName'] = $this->get_payment_method_name(); | |
| 2588 | 3006 | |
| 2589 | 3007 | |
| 2590 | 3008 | // If the site supports Gutenberg Blocks, support the Checkout block IOK 2020-08-10 |
| 2591 | 3009 | if (class_exists('Automattic\WooCommerce\Blocks\Payments\Integrations\AbstractPaymentMethodType')) { |
| @@ -2763,14 +3181,14 @@ | ||
| 2763 | 3181 | |
| 2764 | 3182 | $raw_post = @file_get_contents( 'php://input' ); |
| 2765 | 3183 | $result = @json_decode($raw_post,true); |
| 2766 | 3184 | |
| 2767 | - // This handler handles both Vipps Checkout and Vipps ECom IOK 2021-09-02 | |
| 3185 | + // This handler handles both Checkout and Vipps ECom IOK 2021-09-02 | |
| 2768 | 3186 | // .. and the epayment webhooks 2023-12-19 |
| 2769 | 3187 | $ischeckout = false; |
| 2770 | 3188 | $iswebhook = false; |
| 2771 | 3189 | $callback = isset($_REQUEST['callback']) ? $_REQUEST['callback'] : ""; |
| 2772 | - // For Vipps Checkout v3 and onwards, we control the callback so the type is just this field | |
| 3190 | + // For Checkout v3 and onwards, we control the callback so the type is just this field | |
| 2773 | 3191 | if ($callback == 'checkout') { |
| 2774 | 3192 | $ischeckout = true; |
| 2775 | 3193 | } |
| 2776 | 3194 | // 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 +3602,10 @@ | ||
| 3184 | 3602 | $this->log(sprintf(__("Wrong %1\$s Orderid on shipping details callback", 'woo-vipps'), $this->get_payment_method_name()), 'warning'); |
| 3185 | 3603 | exit(); |
| 3186 | 3604 | } |
| 3187 | 3605 | |
| 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 | |
| 3606 | + // If we are doing this for Checkout after version 3, communicate to any shipping methods with | |
| 3607 | + // special support for Checkout that this is in fact happening. IOK 2023-01-19 | |
| 3190 | 3608 | // This needs to be done before "calculate totals". |
| 3191 | 3609 | // Moved from "vipps_shipping_details_callback_handler" because we need it before restoring sessions. IOK 2025-05-06 |
| 3192 | 3610 | $ischeckout = $order->get_meta('_vipps_checkout'); |
| 3193 | 3611 | |
| @@ -3363,9 +3781,9 @@ | ||
| 3363 | 3781 | ), 'debug'); |
| 3364 | 3782 | |
| 3365 | 3783 | } |
| 3366 | 3784 | |
| 3367 | - // Add shipping tax rates to the *order* so we can calculate this correctly when using Vipps Checkouts | |
| 3785 | + // Add shipping tax rates to the *order* so we can calculate this correctly when using Checkouts | |
| 3368 | 3786 | // 'dynamic pricing' 2023-01-26 |
| 3369 | 3787 | // Which may be deprecated, but anyway, for future use IOK 2025-08-14 |
| 3370 | 3788 | $taxrate = 0; |
| 3371 | 3789 | if (is_array($shipping_tax_rates) && !empty($shipping_tax_rates)) { |
| @@ -3491,9 +3909,9 @@ | ||
| 3491 | 3909 | $vippsmethod['shippingMethod'] = $rate->get_label(); |
| 3492 | 3910 | $vippsmethod['shippingMethodId'] = $key; |
| 3493 | 3911 | $vippsmethods[]=$vippsmethod; |
| 3494 | 3912 | |
| 3495 | - // Metadata and settings stored for later use for Vipps Checkout | |
| 3913 | + // Metadata and settings stored for later use for Checkout | |
| 3496 | 3914 | // and express checkout - basically, for each *key* have the corresponding object. IOK 2025-08-15 |
| 3497 | 3915 | // 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 | 3916 | // finalize the order. IOK 2025-08-15 |
| 3499 | 3917 | $ratemap[$key]=$rate; |
| @@ -3511,9 +3929,9 @@ | ||
| 3511 | 3929 | // This then is the old Express Checkout format, which we have exposed in filters. IOK 2025-08-14 |
| 3512 | 3930 | $return = array('addressId'=>intval($addressid), 'orderId'=>$vippsorderid, 'shippingDetails'=>$vippsmethods); |
| 3513 | 3931 | $return = apply_filters('woo_vipps_vipps_formatted_shipping_methods', $return); // Mostly for debugging |
| 3514 | 3932 | |
| 3515 | - // IOK 2021-11-16 Vipps Checkout uses a slightly different syntax and format. | |
| 3933 | + // IOK 2021-11-16 Checkout uses a slightly different syntax and format. | |
| 3516 | 3934 | // IOK 2025-08-15 and new Express yet another slightly different format. |
| 3517 | 3935 | // IOK 2025-08-15 pass the ratemap as a reference, so transforms can update them |
| 3518 | 3936 | if ($ischeckout) { |
| 3519 | 3937 | $return = VippsCheckout::instance()->format_shipping_methods($return, $ratemap, $methodmap, $order); |
| @@ -3803,9 +4221,9 @@ | ||
| 3803 | 4221 | WC()->cart->calculate_totals(); |
| 3804 | 4222 | WC()->cart->set_session(); |
| 3805 | 4223 | return true; |
| 3806 | 4224 | } catch (Exception $e) { |
| 3807 | - $this->log(sprintf(__("Error regenerating cart from order %1\$d: %2\$s", 'woo-vipps'), $order_id, $e->get_message()), 'error'); | |
| 4225 | + $this->log(sprintf(__("Error regenerating cart from order %1\$d: %2\$s", 'woo-vipps'), $order_id, $e->getMessage()), 'error'); | |
| 3808 | 4226 | return false; |
| 3809 | 4227 | } |
| 3810 | 4228 | } |
| 3811 | 4229 | |
| @@ -3889,17 +4307,8 @@ | ||
| 3889 | 4307 | header("X-Accel-Expires: 0"); |
| 3890 | 4308 | } |
| 3891 | 4309 | |
| 3892 | 4310 | |
| 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 | 4311 | public function woocommerce_payment_gateways($methods) { |
| 3903 | 4312 | require_once(dirname(__FILE__) . "/WC_Gateway_Vipps.class.php"); |
| 3904 | 4313 | require_once(dirname(__FILE__) . "/WC_Gateway_VippsCard.class.php"); |
| 3905 | 4314 | // Protect the singleton: Use the object instead of the class name IOK 2025-02-04 |
| @@ -3924,9 +4333,11 @@ | ||
| 3924 | 4333 | if ( empty($_REQUEST['add-to-cart']) || ! is_numeric($_REQUEST['add-to-cart']) || empty($_REQUEST['vipps_compat_mode']) || !$_REQUEST['vipps_compat_mode']) { |
| 3925 | 4334 | return $url; |
| 3926 | 4335 | } |
| 3927 | 4336 | $url = $this->express_checkout_url(); |
| 3928 | - $url = wp_nonce_url($url,'express','sec'); | |
| 4337 | + // At this point, there is always a query argument here. IOK 2026-09-21 | |
| 4338 | + $nonce = wp_create_nonce('express'); | |
| 4339 | + $url = $url . "&sec=$nonce"; | |
| 3929 | 4340 | |
| 3930 | 4341 | return $url; |
| 3931 | 4342 | } |
| 3932 | 4343 | |
| @@ -3974,9 +4385,9 @@ | ||
| 3974 | 4385 | if ($currentstatus != 'initiated') { |
| 3975 | 4386 | $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 | 4387 | return; |
| 3977 | 4388 | } |
| 3978 | - $this->check_status_of_pending_order($o, false, false); | |
| 4389 | + $this->check_status_of_pending_order($o, false); | |
| 3979 | 4390 | } |
| 3980 | 4391 | } |
| 3981 | 4392 | |
| 3982 | 4393 | // 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 +4392,35 @@ | ||
| 3981 | 4392 | |
| 3982 | 4393 | // 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 | 4394 | // e.g. wp-cron. IOK 2021-06-21 |
| 3984 | 4395 | // 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 | - } | |
| 4396 | + // Stop restoring session in wp-cron again(?) since we now use a rest endpoint to handle shipping. LP 2026-05-13 | |
| 4397 | + public function check_status_of_pending_order($order, $allow_retry=true) { | |
| 3992 | 4398 | $gw = $this->gateway(); |
| 3993 | 4399 | |
| 3994 | 4400 | $order_status = null; |
| 3995 | 4401 | try { |
| 3996 | 4402 | $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); | |
| 4403 | + | |
| 4404 | + // Poll status and correct woo status. LP 2026-05-19 | |
| 4405 | + $order_data = $gw->get_payment_details($order); | |
| 4406 | + | |
| 4407 | + // If we already know the order failed, we don't need to process the order further below. LP 2026-05-19 | |
| 4408 | + if ('CANCEL' === ($order_data['state'] ?? "")) { | |
| 4409 | + /* translators: company name */ | |
| 4410 | + $order->update_status('cancelled', sprintf(__('Payment cancelled at %1$s.', 'woo-vipps'), Vipps::CompanyName())); | |
| 4411 | + return; | |
| 4412 | + } | |
| 4413 | + | |
| 4414 | + $gw->set_order_status_by_payment_details($order, $order_data, $allow_retry); | |
| 4415 | + $order = wc_get_order($order->get_id()); // refresh order if changed. LP 2026-05-13 | |
| 4416 | + $order_status = $order->get_status(); | |
| 4417 | + | |
| 3998 | 4418 | $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 | 4419 | } catch (Exception $e) { |
| 4000 | 4420 | $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 | 4421 | $this->log($e->getMessage() . "\n" . $order->get_id(), 'error'); |
| 4002 | 4422 | } |
| 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 | 4423 | return $order_status; |
| 4008 | 4424 | } |
| 4009 | 4425 | |
| 4010 | 4426 | // 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 +4433,40 @@ | ||
| 4017 | 4433 | } |
| 4018 | 4434 | } |
| 4019 | 4435 | |
| 4020 | 4436 | public function activate () { |
| 4021 | - static::maybe_add_cron_event(); | |
| 4022 | - $gw = $this->gateway(); | |
| 4437 | + static::maybe_add_cron_event(); | |
| 4438 | + $gw = $this->gateway(); | |
| 4023 | 4439 | |
| 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 | - } | |
| 4440 | + // If store is using the default "Woo" orderprefix, generate a new one, this time using the stores' sitename if possible. IOK 2020-05-19 | |
| 4441 | + if ($gw->get_option('orderprefix') == 'Woo') { | |
| 4442 | + $gw->update_option('orderprefix', $this->generate_order_prefix()); | |
| 4443 | + } | |
| 4444 | + // IOK 2023-12-20 for the epayment api, we need to re-initialize webhooks at this point. | |
| 4445 | + $gw->initialize_webhooks(); | |
| 4446 | + $this->payment_method_name = $gw->get_option('payment_method_name'); | |
| 4032 | 4447 | |
| 4448 | + | |
| 4449 | + // Check if the special page is noted and actually does exist | |
| 4450 | + $special = static::get_special_page_id(); | |
| 4451 | + if ($special) { | |
| 4452 | + $special_page = get_post($special); | |
| 4453 | + if ($special_page && 'trash' !== $special_page->post_status) { | |
| 4454 | + // Ensure this page has the necessary shortcode. LP 2026-09-01 | |
| 4455 | + if (!has_shortcode($special_page->post_content, 'vipps_special_page')) { | |
| 4456 | + $new_content = $special_page->post_content . "\n\n<!-- wp:shortcode -->[vipps_special_page]<!-- /wp:shortcode -->"; | |
| 4457 | + wp_update_post([ | |
| 4458 | + 'ID' => $special, | |
| 4459 | + 'post_content' => $new_content, | |
| 4460 | + ]); | |
| 4461 | + } | |
| 4462 | + } else { | |
| 4463 | + delete_option('woocommerce_vipps_special_page_page_id'); | |
| 4464 | + } | |
| 4465 | + } | |
| 4466 | + | |
| 4467 | + } | |
| 4468 | + | |
| 4033 | 4469 | // We have added some hooks to wp-cron; remove these. IOK 2020-04-01 |
| 4034 | 4470 | public static function deactivate() { |
| 4035 | 4471 | $timestamp = wp_next_scheduled('vipps_cron_cleanup_hook'); |
| 4036 | 4472 | wp_unschedule_event($timestamp, 'vipps_cron_cleanup_hook'); |
| @@ -4043,9 +4479,9 @@ | ||
| 4043 | 4479 | // Delete all settings if checked in settings menu. LP 2025-10-06 |
| 4044 | 4480 | $should_delete = $gw->get_option( 'delete_settings_on_deactivation' ) === 'yes'; |
| 4045 | 4481 | if ($should_delete) { |
| 4046 | 4482 | // 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']; | |
| 4483 | + $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 | 4484 | foreach($options as $option) { |
| 4049 | 4485 | delete_option($option); |
| 4050 | 4486 | } |
| 4051 | 4487 | } |
| @@ -4083,9 +4519,10 @@ | ||
| 4083 | 4519 | // If setting is true, use Vipps as default payment. Called by the woocommrece_cart_updated hook. IOK 2018-06-06 |
| 4084 | 4520 | private function maybe_set_vipps_as_default() { |
| 4085 | 4521 | if (WC()->session->get('chosen_payment_method')) return; // User has already chosen payment method, so we're done. |
| 4086 | 4522 | $gw = $this->gateway(); |
| 4087 | - if ($gw->get_option('vippsdefault')=='yes') { | |
| 4523 | + // Do *not* default to vipps if Kustom Checkout is installed IOK 2026-09-11 | |
| 4524 | + if ($gw->get_option('vippsdefault')=='yes' && !class_exists('KCO')) { | |
| 4088 | 4525 | WC()->session->set('chosen_payment_method', $gw->id); |
| 4089 | 4526 | } |
| 4090 | 4527 | } |
| 4091 | 4528 | |
| @@ -4097,28 +4534,12 @@ | ||
| 4097 | 4534 | $order = wc_get_order($order->get_id()); |
| 4098 | 4535 | $order_status = $order->get_status(); |
| 4099 | 4536 | |
| 4100 | 4537 | if ($order_status != 'pending') return $order_status; |
| 4101 | - // No callback has occured yet. If this has been going on for a while, check directly with Vipps | |
| 4102 | - // We can't use the vipps init timestamp here, because that may be in the past for Checkout at least. IOK 2025-08-13 | |
| 4103 | - if ($order_status == 'pending') { | |
| 4104 | - if (WC()->session) { | |
| 4105 | - $now = time(); | |
| 4106 | - $then = WC()->session->get('_vipps_check_' . $order->get_id()); | |
| 4107 | - if (!$then) { | |
| 4108 | - $then = $now; | |
| 4109 | - WC()->session->set('_vipps_check_' . $order->get_id(), $then); | |
| 4110 | - } | |
| 4111 | - if (($then + (1 * 30)) > $now) { // more than half a minute? Start checking at Vipps | |
| 4112 | - return $order_status; | |
| 4113 | - } | |
| 4114 | - } else { | |
| 4115 | - // No session shouldn't be possible, but if it is.. | |
| 4116 | - return $order_status; | |
| 4117 | - } | |
| 4118 | - } | |
| 4538 | + | |
| 4539 | + $gw = $this->gateway(); | |
| 4119 | 4540 | $this->log("Checking order status on Vipps for order id: " . $order->get_id(), 'info'); |
| 4120 | - return $this->check_status_of_pending_order($order); | |
| 4541 | + $newstatus = $gw->poll_and_check_order_status($order); | |
| 4121 | 4542 | } |
| 4122 | 4543 | |
| 4123 | 4544 | // In some situations we have to empty the cart when the user goes to Vipps, so |
| 4124 | 4545 | // we store it in the session and restore it if the users cancels. IOK 2018-05-07 |
| @@ -4186,17 +4607,18 @@ | ||
| 4186 | 4607 | |
| 4187 | 4608 | // Maybe log in user |
| 4188 | 4609 | // It is done on the thank-you page of the order, and only for express checkout. |
| 4189 | 4610 | function maybe_log_in_user ($order) { |
| 4611 | + | |
| 4190 | 4612 | if (is_user_logged_in()) return; |
| 4191 | 4613 | if (!$order || ! self::is_vipps_order($order)) return; |
| 4192 | 4614 | |
| 4193 | 4615 | // 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 | |
| 4616 | + // use the Checkout solution - those can change their emails in the | |
| 4195 | 4617 | // checkout screen. IOK 2021-09-03 |
| 4196 | 4618 | $do_login = $order->get_meta('_vipps_express_checkout'); |
| 4197 | 4619 | |
| 4198 | - // We will not log in Vipps Checkout users unless the option for that is true | |
| 4620 | + // We will not log in Checkout users unless the option for that is true | |
| 4199 | 4621 | if ($order->get_meta('_vipps_checkout') && 'yes' != $this->gateway()->get_option('checkoutcreateuser')) { |
| 4200 | 4622 | $do_login = false; |
| 4201 | 4623 | } |
| 4202 | 4624 | |
| @@ -4231,9 +4653,9 @@ | ||
| 4231 | 4653 | |
| 4232 | 4654 | // Both Checkout and Express Checkout have the below value set to true |
| 4233 | 4655 | if (!$order->get_meta('_vipps_express_checkout')) return; |
| 4234 | 4656 | |
| 4235 | - // Creating/logging in users are handled separately for Vipps Checkout and Express Checkout, so check the correct setting | |
| 4657 | + // Creating/logging in users are handled separately for Checkout and Express Checkout, so check the correct setting | |
| 4236 | 4658 | // IOK 2023-07-27 |
| 4237 | 4659 | $ischeckout = $order->get_meta('_vipps_checkout'); |
| 4238 | 4660 | if ($ischeckout) { |
| 4239 | 4661 | if ($this->gateway()->get_option('checkoutcreateuser') != 'yes') return null; |
| @@ -4333,129 +4755,233 @@ | ||
| 4333 | 4755 | } |
| 4334 | 4756 | if (!$o) return; |
| 4335 | 4757 | if (!$o->get_meta('_vipps_single_product_express')) return; |
| 4336 | 4758 | if ($failed && !apply_filters('woo_vipps_restore_cart_on_express_checkout_failure', true, $o)) return; |
| 4337 | - if ($failed) WC()->cart->empty_cart(); | |
| 4759 | + // Restoring cart! But clear it first so we dont add this single product to the restored cart. LP 2026-09-22 | |
| 4760 | + WC()->cart->empty_cart(); | |
| 4338 | 4761 | $this->restore_cart($o); |
| 4339 | 4762 | } |
| 4340 | 4763 | |
| 4341 | 4764 | |
| 4342 | - public function ajax_vipps_buy_single_product () { | |
| 4343 | - Vipps::nocache(); | |
| 4344 | - static::set_locale_if_in_header(); | |
| 4345 | - // We're not checking ajax referer here, because what we do is creating a session and redirecting to the | |
| 4346 | - // 'create order' page wherein we'll do the actual work. IOK 2018-09-28 | |
| 4347 | - $session = WC()->session; | |
| 4348 | - if (!$session->has_session()) { | |
| 4349 | - $session->set_customer_session_cookie(true); | |
| 4765 | + // Actually create a express checkout order object, with no shipping or personal information, returning information about | |
| 4766 | + // the result. The order should at this point be in a/the cart. For single product purchases, this is a different cart than | |
| 4767 | + // the main one; for cart purchases, it's just the WC()->cart object. IOK 2026-08-25 | |
| 4768 | + private function create_and_process_express_order() { | |
| 4769 | + $result = null; | |
| 4770 | + $gw = $this->gateway(); | |
| 4771 | + try { | |
| 4772 | + $orderid = $gw->create_partial_order(); | |
| 4773 | + do_action('woo_vipps_ajax_do_express_checkout', $orderid); | |
| 4774 | + } catch (Exception $e) { | |
| 4775 | + $result = array('ok'=>0, 'orderid'=>0, 'msg'=>__('Could not create order','woo-vipps') . ': ' . $e->getMessage(), 'url'=>false); | |
| 4776 | + return $result; | |
| 4777 | + } | |
| 4778 | + if (!$orderid) { | |
| 4779 | + $result = array('ok'=>0, 'orderid'=>0, 'msg'=>__('Could not create order','woo-vipps'), 'url'=>false); | |
| 4780 | + return $result; | |
| 4350 | 4781 | } |
| 4351 | - $session->set('__vipps_buy_product', json_encode($_REQUEST)); | |
| 4352 | 4782 | |
| 4353 | - // Incredibly, some caches will cache this page even with cookies set and no-cache headers set. So we try to | |
| 4354 | - // add yet another way to inform caches that this is, in fact, not cacheable. IOK 2023-06-12 | |
| 4355 | - $url = add_query_arg('nc', sha1(uniqid(WC()->session->get_customer_id(),true)), $this->buy_product_url()); | |
| 4783 | + try { | |
| 4784 | + $this->maybe_add_static_shipping($gw,$orderid); | |
| 4785 | + } catch (Exception $e) { | |
| 4786 | + $this->log(__("Error calculating static shipping", 'woo-vipps'), 'error'); | |
| 4787 | + $this->log($e->getMessage(),'error'); | |
| 4788 | + $result = array('ok'=>0, 'orderid'=>0, 'msg'=>__('Could not create order','woo-vipps'), 'url'=>false); | |
| 4789 | + return $result; | |
| 4790 | + } | |
| 4356 | 4791 | |
| 4357 | - $result = array('ok'=>1, 'msg'=>__('Processing order... ','woo-vipps'), 'url'=> $url); | |
| 4358 | - wp_send_json($result); | |
| 4359 | - exit(); | |
| 4792 | + // Now pass this to the Woo gateway and get a redirect URL back IOK 2026-08-25 | |
| 4793 | + $ok = $gw->process_payment($orderid); | |
| 4794 | + if ($ok && $ok['result'] == 'success') { | |
| 4795 | + $result = array('ok'=>1, 'orderid'=>$orderid, 'msg'=>'', 'url'=>$ok['redirect']); | |
| 4796 | + return $result; | |
| 4797 | + } | |
| 4798 | + $result = array('ok'=>0, 'orderid'=>$orderid, 'msg'=> sprintf(__('%1$s is temporarily unavailable.','woo-vipps'), $this->get_payment_method_name()), 'url'=>''); | |
| 4799 | + return $result; | |
| 4360 | 4800 | } |
| 4361 | 4801 | |
| 4362 | - public function ajax_do_express_checkout () { | |
| 4363 | - check_ajax_referer('do_express','sec'); | |
| 4364 | - Vipps::nocache(); | |
| 4365 | - static::set_locale_if_in_header(); | |
| 4802 | + // This creates a simple hash for the 'current order' which we will store in the session if we proceed to checkout. We use this to | |
| 4803 | + // avoid/warn the user of duplicate purchases. IOK 2026-09-09 | |
| 4804 | + public function create_order_hash($args=null) { | |
| 4805 | + // If we have no arguments, we'll hash the cart. | |
| 4806 | + if (empty($args)) { | |
| 4807 | + $cartitems = WC()->cart->get_cart(); | |
| 4808 | + $orderspec = array(); | |
| 4809 | + foreach($cartitems as $item => $values) { | |
| 4810 | + $orderspec[] = array('sku'=> ($values['sku'] ?? ""), 'product_id'=>($values['product_id'] ?? 0), 'variation_id'=>($values['variation_id'] ?? 0), 'quantity'=>($values['quantity'] ?? 1)); | |
| 4811 | + } | |
| 4812 | + $args = $orderspec; | |
| 4813 | + } | |
| 4814 | + return md5(serialize($args)); | |
| 4815 | + } | |
| 4816 | + | |
| 4817 | + | |
| 4818 | + // This method may provide HTML form elements to ask a user questions after starting | |
| 4819 | + // express checkout. It is used to detect duplicate orders, possibly for terms and conditions, and user-definiable customizations. IOK 2026-09-09 | |
| 4820 | + // NULL productinfo means use the cart; the "current hash" is used to detect duplicates, and is calculated by the caller. | |
| 4821 | + public function express_order_needs_confirmation($args, $productinfo, $current_hash) { | |
| 4822 | + $elements = []; | |
| 4823 | + $html = ""; | |
| 4824 | + | |
| 4825 | + // First, let's check if we need to confirm the purchase. | |
| 4826 | + $last_express_purchase_hash = WC()->session->get('woo_vipps_last_express'); | |
| 4827 | + if ($last_express_purchase_hash) { | |
| 4828 | + list($hash, $orderid, $stamp) = explode(":", $last_express_purchase_hash); | |
| 4829 | + $cutoff = $stamp + apply_filters('woo_vipps_recent_order_cutoff', (3*60)); | |
| 4830 | + if ($hash == $current_hash && (time() <= $cutoff )) { | |
| 4831 | + $order = wc_get_order($orderid); | |
| 4832 | + $status = $order ? $order->get_status() : false; | |
| 4833 | + // IOK TODO/FIXME actually, if the order is pending/failed/cancelled and *identical* to our current productinfo, we could plausibly do a restart here. Would probably require careful checking though, and | |
| 4834 | + // a different flow. IOK 2026-09-17 | |
| 4835 | + if (in_array($status, ['on-hold', 'processing', 'completed'])) { | |
| 4836 | + $header = __("Are you sure?",'woo-vipps'); | |
| 4837 | + $body = __("You recently completed an order with exactly the same products as you are buying now. There should be an email in your inbox from the previous purchase. Are you sure you want to order again?",'woo-vipps'); | |
| 4838 | + $elements['possible_duplicate'] = "<h1>$header</h1><p>$body</p>"; | |
| 4839 | + $this->log(__("It seems a customer is trying to re-order product(s) recently bought in the same session, asking user for confirmation", 'woo-vipps'), 'info'); | |
| 4840 | + } | |
| 4841 | + } | |
| 4842 | + } | |
| 4843 | + | |
| 4366 | 4844 | $gw = $this->gateway(); |
| 4845 | + $askForTerms = function_exists('wc_terms_and_conditions_checkbox_enabled') ? wc_terms_and_conditions_checkbox_enabled() : true; | |
| 4846 | + $askForTerms = $askForTerms && ($gw->get_option('expresscheckout_termscheckbox') == 'yes'); | |
| 4847 | + $askForTerms = apply_filters('woo_vipps_express_checkout_terms_and_conditions_checkbox_enabled', $askForTerms); | |
| 4367 | 4848 | |
| 4368 | - if (!$gw->express_checkout_available() || !$gw->cart_supports_express_checkout()) { | |
| 4369 | - $result = array('ok'=>0, 'msg'=>sprintf(__('%1$s is not available for this order','woo-vipps'), Vipps::ExpressCheckoutName()), 'url'=>false); | |
| 4370 | - wp_send_json($result); | |
| 4371 | - exit(); | |
| 4849 | + if ($askForTerms) { | |
| 4850 | + $termsHTML = ''; | |
| 4851 | + // Include shop terms | |
| 4852 | + ob_start(); | |
| 4853 | + wc_get_template('checkout/terms.php'); | |
| 4854 | + $termsHTML = ob_get_clean(); | |
| 4855 | + $termsHTML = apply_filters('woo_vipps_express_checkout_terms_and_conditions_html',$termsHTML); | |
| 4856 | + $elements['terms'] = $termsHTML; | |
| 4372 | 4857 | } |
| 4373 | 4858 | |
| 4859 | + // Custom fields | |
| 4860 | + ob_start(); | |
| 4861 | + do_action('woo_vipps_express_checkout_orderspec_form', $productinfo, $args); | |
| 4862 | + $extra_fields = ob_get_clean(); | |
| 4863 | + if (!empty($extra_fields)) { | |
| 4864 | + $elements['extra'] = $extra_fields; | |
| 4865 | + } | |
| 4374 | 4866 | |
| 4375 | - | |
| 4867 | + if (!empty($elements)) { | |
| 4868 | + $html = join("\n", array_values($elements)); | |
| 4869 | + $msg = join(",", array_keys($elements)); | |
| 4870 | + return ['ok'=>2, 'msg'=>$msg, 'html'=>$html, 'url'=>'']; | |
| 4871 | + } | |
| 4376 | 4872 | |
| 4873 | + return false; | |
| 4874 | + | |
| 4875 | + } | |
| 4876 | + | |
| 4877 | + public function rest_do_express_checkout ($request) { | |
| 4878 | + Vipps::nocache(); | |
| 4879 | + check_ajax_referer('express', 'sec'); | |
| 4880 | + static::set_locale_if_in_header(); | |
| 4881 | + $args = $request->get_json_params(); | |
| 4882 | + if (!$args) { | |
| 4883 | + return new WP_Error('no_data', __('No data passed to express checkout', 'woo-vipps'), ['status' => 400]); | |
| 4884 | + } | |
| 4885 | + | |
| 4886 | + // Since this is the REST api, we need to load the cart manually here. IOK 2026-08-27 | |
| 4887 | + if ( is_null( WC()->cart ) ) { | |
| 4888 | + WC()->frontend_includes(); | |
| 4889 | + if ( ! WC()->session instanceof WC_Session ) { | |
| 4890 | + WC()->session = new WC_Session_Handler(); | |
| 4891 | + WC()->session->init(); | |
| 4892 | + } | |
| 4893 | + if (is_null( WC()->customer)) { | |
| 4894 | + WC()->customer = new WC_Customer( get_current_user_id(), true ); | |
| 4895 | + } | |
| 4896 | + WC()->cart = new WC_Cart(); | |
| 4897 | + WC()->cart->get_cart_from_session(); | |
| 4898 | + } | |
| 4899 | + | |
| 4900 | + | |
| 4901 | + $gw = $this->gateway(); | |
| 4902 | + if (!$gw->express_checkout_available() || !$gw->cart_supports_express_checkout()) { | |
| 4903 | + $result = array('ok'=>0, 'msg'=>sprintf(__('%1$s is not available for this order','woo-vipps'), Vipps::ExpressCheckoutName()), 'url'=>false); | |
| 4904 | + return $result; | |
| 4905 | + } | |
| 4377 | 4906 | // Validate cart going forward using same logic as WC_Cart->check_cart() but not adding notices. |
| 4378 | 4907 | $toolate = false; |
| 4379 | 4908 | $msg = ""; |
| 4380 | 4909 | $valid = WC()->cart->check_cart_item_validity(); |
| 4381 | 4910 | if ( is_wp_error( $valid) ) { |
| 4382 | - $toolate = true; | |
| 4383 | - $msg = "<br>" . $valid->get_error_message(); | |
| 4911 | + $toolate = true; | |
| 4912 | + $msg = "<br>" . $valid->get_error_message(); | |
| 4384 | 4913 | } |
| 4385 | 4914 | $stock = WC()->cart->check_cart_item_stock(); |
| 4386 | - if ( is_wp_error( $stock) ) { | |
| 4387 | - $toolate = true; | |
| 4388 | - $msg = "<br>" . $stock->get_error_message(); | |
| 4389 | - } | |
| 4915 | + if ( is_wp_error( $stock) ) { | |
| 4916 | + $toolate = true; | |
| 4917 | + $msg = "<br>" . $stock->get_error_message(); | |
| 4918 | + } | |
| 4390 | 4919 | |
| 4391 | 4920 | if ($toolate) { |
| 4392 | 4921 | $result = array('ok'=>0, 'msg'=>sprintf(__('Some of the products in your cart are no longer available in the quantities you have ordered. Please <a href="%1$s">edit your order</a> before continuing the checkout','woo-vipps'), wc_get_cart_url()) . $msg, 'url'=>false); |
| 4393 | - wp_send_json($result); | |
| 4394 | - exit(); | |
| 4922 | + return $result; | |
| 4395 | 4923 | } |
| 4396 | 4924 | |
| 4397 | - try { | |
| 4398 | - $orderid = $gw->create_partial_order(); | |
| 4399 | - do_action('woo_vipps_ajax_do_express_checkout', $orderid); | |
| 4400 | - } catch (Exception $e) { | |
| 4401 | - $this->log($e->getMessage(),'error'); | |
| 4402 | - $result = array('ok'=>0, 'msg'=>__('Could not create order','woo-vipps') . ': ' . $e->getMessage(), 'url'=>false); | |
| 4403 | - wp_send_json($result); | |
| 4404 | - exit(); | |
| 4405 | - } | |
| 4406 | - if (!$orderid) { | |
| 4407 | - $result = array('ok'=>0, 'msg'=>__('Could not create order','woo-vipps'), 'url'=>false); | |
| 4408 | - wp_send_json($result); | |
| 4409 | - exit(); | |
| 4925 | + // Then the cookies. These would be the _ga and sbjs_ cookies typically, but we'll let users handle these themselves. | |
| 4926 | + // These are passed as arguments from the javascript, since proxies are likely to strip them. This should allow | |
| 4927 | + // systems like MonsterInsights that look for the _GA cookie to succeed. IOK 2026-08-30 | |
| 4928 | + $cookies = $args['cookies'] ?? []; | |
| 4929 | + foreach($cookies as $key => $value) { | |
| 4930 | + if (!isset($_COOKIE[$key])) { | |
| 4931 | + $_COOKIE[$key] = $value; | |
| 4932 | + } | |
| 4410 | 4933 | } |
| 4934 | + // There might be extra values here now, which would typically have been posted as POST arguments, in a form. | |
| 4935 | + // User-defined stuff and so on. We'll initiate the POST value with these to simulate this for backwards compatibility. | |
| 4936 | + $others =$args['post'] ?? []; | |
| 4937 | + foreach($args['post'] as $key=>$value) { | |
| 4938 | + $_POST[$key] = $value; | |
| 4939 | + } | |
| 4411 | 4940 | |
| 4412 | - try { | |
| 4413 | - $this->maybe_add_static_shipping($gw,$orderid); | |
| 4414 | - } catch (Exception $e) { | |
| 4415 | - $this->log(__("Error calculating static shipping", 'woo-vipps'), 'error'); | |
| 4416 | - $this->log($e->getMessage(),'error'); | |
| 4417 | - $result = array('ok'=>0, 'msg'=>__('Could not create order','woo-vipps'), 'url'=>false); | |
| 4418 | - wp_send_json($result); | |
| 4419 | - exit(); | |
| 4941 | + // Try to avoid re-purchasing the same order repeatedly. IOK 2026-09-02 | |
| 4942 | + $current_hash = $this->create_order_hash(); | |
| 4943 | + $confirmation = (bool) intval(($others['confirmed'] ?? 0)); | |
| 4944 | + if (!$confirmation) { | |
| 4945 | + $result = $this->express_order_needs_confirmation($args, null, $current_hash); | |
| 4946 | + if (!empty($result)) { | |
| 4947 | + return $result; | |
| 4948 | + } | |
| 4420 | 4949 | } |
| 4421 | - | |
| 4422 | - $ok = $gw->process_payment($orderid); | |
| 4423 | - if ($ok && $ok['result'] == 'success') { | |
| 4424 | - $result = array('ok'=>1, 'msg'=>'', 'url'=>$ok['redirect']); | |
| 4425 | - wp_send_json($result); | |
| 4426 | - exit(); | |
| 4950 | + | |
| 4951 | + $result = $this->create_and_process_express_order(); | |
| 4952 | + if ($result['ok'] == 1) { | |
| 4953 | + $orderid = $result['orderid']; | |
| 4954 | + WC()->session->set('woo_vipps_last_express', "$current_hash:$orderid:" . time()); | |
| 4955 | + WC()->session->save_data(); | |
| 4427 | 4956 | } |
| 4428 | - $result = array('ok'=>0, 'msg'=> sprintf(__('%1$s is temporarily unavailable.','woo-vipps'), $this->get_payment_method_name()), 'url'=>''); | |
| 4429 | - wp_send_json($result); | |
| 4430 | - exit(); | |
| 4957 | + return $result; | |
| 4958 | + | |
| 4431 | 4959 | } |
| 4432 | 4960 | |
| 4433 | - // Same as ajax_do_express_checkout, but for a single product/variation. Duplicate code because we want to manipulate the cart differently here. IOK 2018-09-25 | |
| 4434 | - public function ajax_do_single_product_express_checkout() { | |
| 4435 | - check_ajax_referer('do_express','sec'); | |
| 4436 | - Vipps::nocache(); | |
| 4961 | + | |
| 4962 | + // Rest handler for single product express checkout. Expects arguments as JSON. IOK 2026-08-25 | |
| 4963 | + public function rest_do_single_product_express_checkout ($request) { | |
| 4964 | + Vipps::nocache(); | |
| 4437 | 4965 | static::set_locale_if_in_header(); |
| 4438 | - require_once(dirname(__FILE__) . "/WC_Gateway_Vipps.class.php"); | |
| 4439 | - $gw = $this->gateway(); | |
| 4440 | - | |
| 4441 | - if (!$gw->express_checkout_available()) { | |
| 4442 | - $result = array('ok'=>0, 'msg'=>sprintf(__('%1$s is not available for this order','woo-vipps'), Vipps::ExpressCheckoutName()), 'url'=>false); | |
| 4443 | - wp_send_json($result); | |
| 4444 | - exit(); | |
| 4966 | + $args = $request->get_json_params(); | |
| 4967 | + if (!$args) { | |
| 4968 | + return new WP_Error('no_data', __('No data passed to express checkout', 'woo-vipps'), ['status' => 400]); | |
| 4445 | 4969 | } |
| 4970 | + $result = ['ok' => 0, 'msg'=>'', 'orderid'=>0, 'url'=>'']; | |
| 4446 | 4971 | |
| 4972 | + // We receive the varid, prodid, sku and quantity directly. One of these. The sku is the dominant one. IOK 2026-08-27 | |
| 4973 | + $varid = intval($args['variation_id'] ?? 0); | |
| 4974 | + $prodid = intval($args['product_id'] ?? 0); | |
| 4975 | + $sku = sanitize_text_field($args['sku'] ?? ""); | |
| 4976 | + $quantity = max(1, intval($args['quantity'] ?? 0)); | |
| 4447 | 4977 | |
| 4448 | - // Here we will either have a product-id, a variant-id and a product-id, or just a SKU. The product-id will not be a variant - but | |
| 4449 | - // we'll double-check just in case. Also if we somehow *just* get a variant-id we should fix that too. But a SKU trumps all. IOK 2018-10-02 | |
| 4450 | - $varid = intval(@$_POST['variation_id']); | |
| 4451 | - $prodid = intval(@$_POST['product_id']); | |
| 4452 | - $sku = sanitize_text_field(@$_POST['sku']); | |
| 4453 | - $quant = intval(@$_POST['quantity']); | |
| 4454 | 4978 | |
| 4455 | - // Get any attributes posted for variable products (where one of the dimensions is "any" for instance) | |
| 4456 | - $variations = array(); | |
| 4457 | - foreach ($_POST as $key => $value ) { | |
| 4979 | + // We expect the variations - that is, the fields named "attribute_..." to be sent as post fields. | |
| 4980 | + // We just need to sanitize them. | |
| 4981 | + $variations = []; | |
| 4982 | + $invars = $args['post'] ?? []; | |
| 4983 | + foreach ($invars as $key => $value) { | |
| 4458 | 4984 | if ( 'attribute_' !== substr( $key, 0, 10 ) ) { |
| 4459 | 4985 | continue; |
| 4460 | 4986 | } |
| 4461 | 4987 | $variations[ sanitize_title( wp_unslash( $key ) ) ] = wp_unslash( $value ); |
| @@ -4460,15 +4986,94 @@ | ||
| 4460 | 4986 | } |
| 4461 | 4987 | $variations[ sanitize_title( wp_unslash( $key ) ) ] = wp_unslash( $value ); |
| 4462 | 4988 | } |
| 4463 | 4989 | |
| 4464 | - $product = null; | |
| 4465 | - $variant = null; | |
| 4466 | - $parent = null; | |
| 4467 | - $parentid = null; | |
| 4468 | - $quantity = 1; | |
| 4469 | - if ($quant && $quant>1) $quantity=$quant; | |
| 4990 | + // Then the cookies. These would be the _ga and sbjs_ cookies typically, but we'll let users handle these themselves. | |
| 4991 | + // These are passed as arguments from the javascript, since proxies are likely to strip them. This should allow | |
| 4992 | + // systems like MonsterInsights that look for the _GA cookie to succeed. IOK 2026-08-30 | |
| 4993 | + $cookies = $args['cookies'] ?? []; | |
| 4994 | + foreach($cookies as $key => $value) { | |
| 4995 | + if (!isset($_COOKIE[$key])) { | |
| 4996 | + $_COOKIE[$key] = $value; | |
| 4997 | + } | |
| 4998 | + } | |
| 4470 | 4999 | |
| 5000 | + // There might be extra values here now, which would typically have been posted as POST arguments, in a form. | |
| 5001 | + // User-defined stuff and so on. We'll initiate the POST value with these to simulate this for backwards compatibility. | |
| 5002 | + $others =$args['post'] ?? []; | |
| 5003 | + foreach($args['post'] as $key=>$value) { | |
| 5004 | + $_POST[$key] = $value; | |
| 5005 | + } | |
| 5006 | + | |
| 5007 | + // Since this is the REST api, we need to load the cart manually here. *Not* loading the cart could be an option but unpredictable. IOK 2026-08-27 | |
| 5008 | + if ( is_null( WC()->cart ) ) { | |
| 5009 | + WC()->frontend_includes(); | |
| 5010 | + if ( ! WC()->session instanceof WC_Session ) { | |
| 5011 | + WC()->session = new WC_Session_Handler(); | |
| 5012 | + WC()->session->init(); | |
| 5013 | + | |
| 5014 | + // If we don't have a session cookie, we need to set it, and also initialize the $_COOKIE value. IOK 2026-09-29 | |
| 5015 | + if (! WC()->session->get_session_cookie()) { | |
| 5016 | + $store_session_cookie = function ( $options, $name, $value ) { $_COOKIE[$name] = $value; return $options;}; | |
| 5017 | + add_filter('woocommerce_set_cookie_options', $store_session_cookie, 10, 3); | |
| 5018 | + try { | |
| 5019 | + WC()->session->set_customer_session_cookie( true ); // We have to explicitly set the cookie if this session is fresh. IOK 2026-09-29 | |
| 5020 | + } finally { | |
| 5021 | + remove_filter('woocommerce_set_cookie_options', $store_session_cookie, 10); | |
| 5022 | + } | |
| 5023 | + } | |
| 5024 | + } | |
| 5025 | + if (is_null( WC()->customer)) { | |
| 5026 | + WC()->customer = new WC_Customer( get_current_user_id(), true ); | |
| 5027 | + } | |
| 5028 | + WC()->cart = new WC_Cart(); | |
| 5029 | + WC()->cart->get_cart_from_session(); | |
| 5030 | + } | |
| 5031 | + | |
| 5032 | + // Try to avoid re-purchasing the same order repeatedly. IOK 2026-09-02 | |
| 5033 | + // We calculate this here so we can add it to the session later. IOK 2026-09-09 | |
| 5034 | + $orderspec = array('sku'=> $sku, 'product_id'=>$prodid, 'variation_id'=>$varid, 'quantity'=>$quantity); | |
| 5035 | + $current_hash = $this->create_order_hash($orderspec); | |
| 5036 | + | |
| 5037 | + // Now to handle "extra questions" for an order, including terms + conditions and "possible duplicate order" IOK 2026-09-09 | |
| 5038 | + $confirmation = (bool) intval(($others['confirmed'] ?? 0)); | |
| 5039 | + if (!$confirmation) { | |
| 5040 | + $result = $this->express_order_needs_confirmation($args, $orderspec, $current_hash); | |
| 5041 | + if (!empty($result)) { | |
| 5042 | + $response = new WP_REST_Response($result); | |
| 5043 | + $response->set_status(200); | |
| 5044 | + return $response; | |
| 5045 | + } | |
| 5046 | + } | |
| 5047 | + | |
| 5048 | + // Basically always return 200 after this, and always return an object with an 'ok' and a 'msg' value, possibly 'orderid' and 'url'. | |
| 5049 | + $result = $this->really_do_single_product_express_checkout($prodid, $varid, $sku, $quantity, $variations); | |
| 5050 | + // And if we're going to express now so let's note the order. IOK 2026-08-27. Now this assumes success, but *basically* I think this is ok. | |
| 5051 | + // We'll reset it on order failure I think. IOK 2026-08-20 FIXME | |
| 5052 | + if ($result['ok'] == 1) { | |
| 5053 | + $orderid = $result['orderid']; | |
| 5054 | + WC()->session->set('woo_vipps_last_express', "$current_hash:$orderid:" . time()); | |
| 5055 | + WC()->session->save_data(); | |
| 5056 | + } | |
| 5057 | + | |
| 5058 | + $response = new WP_REST_Response($result); | |
| 5059 | + $response->set_status(200); | |
| 5060 | + | |
| 5061 | + return $response; | |
| 5062 | + } | |
| 5063 | + | |
| 5064 | + // Common private method to do single product express checkout, used by the new REST express. IOK 2026-08-25 | |
| 5065 | + private function really_do_single_product_express_checkout($prodid, $varid, $sku, $quantity=1, $variations=[]) { | |
| 5066 | + require_once(dirname(__FILE__) . "/WC_Gateway_Vipps.class.php"); | |
| 5067 | + $gw = $this->gateway(); | |
| 5068 | + | |
| 5069 | + if (!$gw->express_checkout_available()) { | |
| 5070 | + $result = array('ok'=>0, 'msg'=>sprintf(__('%1$s is not available for this order','woo-vipps'), Vipps::ExpressCheckoutName()), 'url'=>false); | |
| 5071 | + return $result; | |
| 5072 | + } | |
| 5073 | + // Here we will either have a product-id, a variant-id and a product-id, or just a SKU. The product-id will not be a variant - but | |
| 5074 | + // we'll double-check just in case. Also if we somehow *just* get a variant-id we should fix that too. But a SKU trumps all. IOK 2018-10-02 | |
| 5075 | + | |
| 4471 | 5076 | // Find the product, or variation, and get everything in order so we can check existence, availability etc. IOK 2018-10-02 |
| 4472 | 5077 | // Moved rules around as the _sku variant broke in 3.6.1 for stores that didn't bother to update the database IOK 2019-04-24 |
| 4473 | 5078 | // This broke single-product purchases for variable products; fixed IOK 2019-05-21 thanks to Gaute Terland Nilsen @ Easyweb for the report |
| 4474 | 5079 | try { |
| @@ -4481,17 +5086,14 @@ | ||
| 4481 | 5086 | $product = wc_get_product($skuid); |
| 4482 | 5087 | } |
| 4483 | 5088 | } catch (Exception $e) { |
| 4484 | 5089 | $result = array('ok'=>0, 'msg'=>__('Error finding product - cannot create order','woo-vipps'), 'url'=>false); |
| 4485 | - wp_send_json($result); | |
| 4486 | - exit(); | |
| 5090 | + return $result; | |
| 4487 | 5091 | } |
| 4488 | 5092 | |
| 4489 | - | |
| 4490 | 5093 | if (!$product) { |
| 4491 | 5094 | $result = array('ok'=>0, 'msg'=>__('Unknown product, cannot create order','woo-vipps'), 'url'=>false); |
| 4492 | - wp_send_json($result); | |
| 4493 | - exit(); | |
| 5095 | + return $result; | |
| 4494 | 5096 | } |
| 4495 | 5097 | |
| 4496 | 5098 | $parentid = $product ? $product->get_parent_id() : null; // If the product is a variation, then the parent product is the parentid. |
| 4497 | 5099 | $parent = $parentid ? wc_get_product($parentid) : null; |
| @@ -4498,34 +5100,30 @@ | ||
| 4498 | 5100 | |
| 4499 | 5101 | // This can't really happen, but if it did.. |
| 4500 | 5102 | if ($prodid && $parentid && ($prodid != $parentid)) { |
| 4501 | 5103 | $result = array('ok'=>0, 'msg'=>__('Selected product variant is not available','woo-vipps'), 'url'=>false); |
| 4502 | - wp_send_json($result); | |
| 4503 | - exit(); | |
| 5104 | + return $result; | |
| 4504 | 5105 | } |
| 4505 | 5106 | if (!$gw->product_supports_express_checkout($product)) { |
| 4506 | 5107 | $result = array('ok'=>0, 'msg'=>sprintf(__('%1$s is not available for this order','woo-vipps'), Vipps::ExpressCheckoutName()), 'url'=>false); |
| 4507 | - wp_send_json($result); | |
| 4508 | - exit(); | |
| 5108 | + return $result; | |
| 4509 | 5109 | } |
| 4510 | 5110 | |
| 4511 | 5111 | // Somebody addded the wrong SKU |
| 4512 | 5112 | if ($product->get_type() == 'variable'){ |
| 4513 | 5113 | $result = array('ok'=>0, 'msg'=>__('Selected product variant is not available for purchase','woo-vipps'), 'url'=>false); |
| 4514 | - wp_send_json($result); | |
| 4515 | - exit(); | |
| 5114 | + return $result; | |
| 4516 | 5115 | } |
| 4517 | 5116 | // Final check of availability |
| 4518 | 5117 | if (!$product->is_purchasable() || !$product->is_in_stock()) { |
| 4519 | 5118 | $result = array('ok'=>0, 'msg'=>__('Your product is temporarily no longer available for purchase','woo-vipps'), 'url'=>false); |
| 4520 | - wp_send_json($result); | |
| 4521 | - exit(); | |
| 5119 | + return $result; | |
| 4522 | 5120 | } |
| 4523 | 5121 | |
| 4524 | 5122 | // Now it should be safe to continue to the checkout process. IOK 2018-10-02 |
| 4525 | - | |
| 4526 | 5123 | // Create a new temporary cart for this order. We need to get (and save) the real session cart, |
| 4527 | 5124 | // because some plugins actually override this. |
| 5125 | + // NB: Please note the cart must have been loaded here, be aware when doing REST. IOK 2026-08-27 | |
| 4528 | 5126 | $current_cart = clone WC()->cart; |
| 4529 | 5127 | WC()->cart->empty_cart(); |
| 4530 | 5128 | |
| 4531 | 5129 | if ($parent && $parent->get_type() == 'variable') { |
| @@ -4532,50 +5130,22 @@ | ||
| 4532 | 5130 | WC()->cart->add_to_cart($parent->get_id(),$quantity,$product->get_id(), $variations); |
| 4533 | 5131 | } else { |
| 4534 | 5132 | WC()->cart->add_to_cart($product->get_id(),$quantity); |
| 4535 | 5133 | } |
| 5134 | + WC()->session->save_data(); | |
| 4536 | 5135 | |
| 4537 | - try { | |
| 4538 | - $orderid = $gw->create_partial_order(); | |
| 4539 | - do_action('woo_vipps_ajax_do_express_checkout', $orderid); | |
| 4540 | - } catch (Exception $e) { | |
| 4541 | - $result = array('ok'=>0, 'msg'=>__('Could not create order','woo-vipps') . ': ' . $e->getMessage(), 'url'=>false); | |
| 4542 | - wp_send_json($result); | |
| 4543 | - exit(); | |
| 4544 | - } | |
| 5136 | + $result = $this->create_and_process_express_order(); | |
| 4545 | 5137 | |
| 4546 | - if (!$orderid) { | |
| 4547 | - $result = array('ok'=>0, 'msg'=>__('Could not create order','woo-vipps'), 'url'=>false); | |
| 4548 | - wp_send_json($result); | |
| 4549 | - exit(); | |
| 5138 | + if ($result['ok'] ?? false) { | |
| 5139 | + // Single product purchase, so save any contents of the real cart | |
| 5140 | + $orderid = $result['orderid']; | |
| 5141 | + $order = wc_get_order($orderid); | |
| 5142 | + $order->update_meta_data('_vipps_single_product_express',true); | |
| 5143 | + $order->save(); | |
| 5144 | + $this->save_cart($order,$current_cart); | |
| 4550 | 5145 | } |
| 4551 | 5146 | |
| 4552 | - try { | |
| 4553 | - $this->maybe_add_static_shipping($gw,$orderid); | |
| 4554 | - } catch (Exception $e) { | |
| 4555 | - $this->log(__("Error calculating static shipping", 'woo-vipps'), 'error'); | |
| 4556 | - $this->log($e->getMessage(),'error'); | |
| 4557 | - $result = array('ok'=>0, 'msg'=>__('Could not create order','woo-vipps'), 'url'=>false); | |
| 4558 | - wp_send_json($result); | |
| 4559 | - exit(); | |
| 4560 | - } | |
| 4561 | - | |
| 4562 | - | |
| 4563 | - // Single product purchase, so save any contents of the real cart | |
| 4564 | - $order = wc_get_order($orderid); | |
| 4565 | - $order->update_meta_data('_vipps_single_product_express',true); | |
| 4566 | - $order->save(); | |
| 4567 | - $this->save_cart($order,$current_cart); | |
| 4568 | - | |
| 4569 | - $ok = $gw->process_payment($orderid); | |
| 4570 | - if ($ok && $ok['result'] == 'success') { | |
| 4571 | - $result = array('ok'=>1, 'msg'=>'', 'url'=>$ok['redirect']); | |
| 4572 | - wp_send_json($result); | |
| 4573 | - exit(); | |
| 4574 | - } | |
| 4575 | - $result = array('ok'=>0, 'msg'=> sprintf(__('%1$s is temporarily unavailable.','woo-vipps'), $this->get_payment_method_name()), 'url'=>''); | |
| 4576 | - wp_send_json($result); | |
| 4577 | - exit(); | |
| 5147 | + return $result; | |
| 4578 | 5148 | } |
| 4579 | 5149 | |
| 4580 | 5150 | // This calculates and adds static shipping info to a partial order for express checkout if merchant has enabled this. IOK 2020-03-19 |
| 4581 | 5151 | // Made visible for consistency with add_static_shipping. IOK 2021-10-22 |
| @@ -4618,9 +5188,9 @@ | ||
| 4618 | 5188 | $ok = wc()->shipping->register_shipping_method( new Automattic\WooCommerce\Blocks\Shipping\PickupLocation() ); |
| 4619 | 5189 | } |
| 4620 | 5190 | } |
| 4621 | 5191 | |
| 4622 | - // Vipps Checkout and Express Checkout allows loading specific kinds of shipping methods with non-standard APIs, such as PickupLocations. IOK 2025-05-08 | |
| 5192 | + // Checkout and Express Checkout allows loading specific kinds of shipping methods with non-standard APIs, such as PickupLocations. IOK 2025-05-08 | |
| 4623 | 5193 | // Must be called *early*. IOK 2025-05-08. Called in callback methods, and if using static shipping, in the 'start session' callback. |
| 4624 | 5194 | public function load_extra_shipping_methods($order, $addressdata, $ischeckout=false) { |
| 4625 | 5195 | // 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 | 5196 | add_action('woocommerce_load_shipping_methods', function () use ($order, $addressdata) { |
| @@ -4641,9 +5211,9 @@ | ||
| 4641 | 5211 | $transaction = sanitize_text_field(@$_POST['transaction']); |
| 4642 | 5212 | |
| 4643 | 5213 | $sessionorders= WC()->session->get('_vipps_session_orders'); |
| 4644 | 5214 | if (!isset($sessionorders[$orderid])) { |
| 4645 | - wp_send_json(array('status'=>'error', 'msg'=>__('Not an order','woo-vipps'))); | |
| 5215 | + wp_send_json(array('status'=>'error', 'msg'=>__('Not a session order','woo-vipps'))); | |
| 4646 | 5216 | } |
| 4647 | 5217 | |
| 4648 | 5218 | $order = wc_get_order($orderid); |
| 4649 | 5219 | if (!$order) { |
| @@ -4691,46 +5261,37 @@ | ||
| 4691 | 5261 | wp_send_json(array('status'=>'error', 'msg'=> __('Unknown payment status','woo-vipps') . ' ' . $payment)); |
| 4692 | 5262 | return false; |
| 4693 | 5263 | } |
| 4694 | 5264 | |
| 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')); | |
| 5265 | + // 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 | |
| 5266 | + private function make_special_page_url($action) { | |
| 5267 | + return add_query_arg('action', $action, $this->get_special_page_url()); | |
| 4702 | 5268 | } |
| 5269 | + | |
| 4703 | 5270 | public function payment_return_url() { |
| 4704 | - return apply_filters('woo_vipps_payment_return_url', $this->make_vipps_url('vipps-betaling')); | |
| 5271 | + return apply_filters('woo_vipps_payment_return_url', $this->make_special_page_url('wait_for_payment')); | |
| 4705 | 5272 | } |
| 4706 | 5273 | public function express_checkout_url() { |
| 4707 | - return $this->make_vipps_url('vipps-express-checkout'); | |
| 5274 | + return $this->make_special_page_url('do_express_checkout'); | |
| 4708 | 5275 | } |
| 4709 | 5276 | public function buy_product_url() { |
| 4710 | - return $this->make_vipps_url('vipps-buy-product'); | |
| 5277 | + return $this->make_special_page_url('buy_product'); | |
| 4711 | 5278 | } |
| 4712 | 5279 | |
| 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; | |
| 5280 | + public static function is_special_page() { | |
| 5281 | + $id = static::get_special_page_id(); | |
| 5282 | + return $id && is_page($id); | |
| 4731 | 5283 | } |
| 4732 | 5284 | |
| 5285 | + public static function get_special_page_id() { | |
| 5286 | + $id = wc_get_page_id('vipps_special_page'); // -1 if not found | |
| 5287 | + return $id > 0 ? $id : null; | |
| 5288 | + } | |
| 5289 | + | |
| 5290 | + public static function get_special_page_url() { | |
| 5291 | + return get_permalink(static::get_special_page_id()); | |
| 5292 | + } | |
| 5293 | + | |
| 4733 | 5294 | // Just create a spinner and a overlay. |
| 4734 | 5295 | public function spinner () { |
| 4735 | 5296 | $flavour = sanitize_title($this->get_payment_method_name()); |
| 4736 | 5297 | ob_start(); |
| @@ -4751,164 +5312,22 @@ | ||
| 4751 | 5312 | return apply_filters('woo_vipps_spinner', ob_get_clean()); |
| 4752 | 5313 | } |
| 4753 | 5314 | |
| 4754 | 5315 | |
| 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; | |
| 5316 | + // DEPRECATED: Legacy function as of using new web component buttons. LP 2026-06-26 | |
| 5317 | + // NB: previously this returned the url to a svg logo. We don't do this anymore, so it returns html. LP 2026-06-30 | |
| 5318 | + public function get_express_logo($_payment_method = null, $_lang = null, $_variant = null, $context = 'global') { | |
| 5319 | + return $this->get_html_button_for_context($context); | |
| 4876 | 5320 | } |
| 4877 | 5321 | |
| 5322 | + // DEPRECATED: Legacy function as of using new web component buttons. LP 2026-06-26 | |
| 4878 | 5323 | // 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; | |
| 5324 | + // and based on custom variant setting. $context is where it is to be used, e.g 'cart', 'product'. LP 2025-12-15 | |
| 5325 | + // NB: previously this returned the url to a svg logo. We don't do this anymore, so it returns html. LP 2026-06-30 | |
| 5326 | + public function get_payment_logo($context = 'global') { | |
| 5327 | + return $this->get_express_logo(null, null, null, $context); | |
| 4886 | 5328 | } |
| 4887 | 5329 | |
| 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 | 5330 | // Get express banner logo based on payment method. LP 2025-09-03 |
| 4912 | 5331 | private function get_express_banner_logo() { |
| 4913 | 5332 | $payment_method = $this->get_payment_method_name(); |
| 4914 | 5333 | |
| @@ -4919,10 +5338,12 @@ | ||
| 4919 | 5338 | } |
| 4920 | 5339 | return null; |
| 4921 | 5340 | } |
| 4922 | 5341 | |
| 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) { | |
| 5342 | + // Code that will generate various versions of the 'buy now with Vipps' button IOK 2018-09-27 | |
| 5343 | + // $context is slug describing where its to be used, like 'catalog', 'cart', 'product' etc. and will | |
| 5344 | + // be used unless $button_args_override is nonempty. See init_button_options() and get_html_button() LP 2026-06-26 | |
| 5345 | + public function get_buy_now_button($product_id,$variation_id=null,$sku=null,$disabled=false, $classes='', $context='global', $button_args_override = []) { | |
| 4925 | 5346 | $disabled = $disabled ? 'disabled' : ''; |
| 4926 | 5347 | $data = array(); |
| 4927 | 5348 | |
| 4928 | 5349 | // Support directly using the variant id as $product_id with no $variation_id. LP 2026-01-23 |
| @@ -4937,9 +5358,8 @@ | ||
| 4937 | 5358 | if ($sku) $data['product_sku'] = $sku; |
| 4938 | 5359 | if ($product_id) $data['product_id'] = $product_id; |
| 4939 | 5360 | if ($variation_id) $data['variation_id'] = $variation_id; |
| 4940 | 5361 | |
| 4941 | - | |
| 4942 | 5362 | $buttoncode = "<a href='javascript:void(0)' $disabled "; |
| 4943 | 5363 | foreach($data as $key=>$value) { |
| 4944 | 5364 | $value = esc_attr($value); |
| 4945 | 5365 | $buttoncode .= " data-$key='$value' "; |
| @@ -4946,14 +5366,18 @@ | ||
| 4946 | 5366 | } |
| 4947 | 5367 | |
| 4948 | 5368 | $payment_method = $this->get_payment_method_name(); |
| 4949 | 5369 | $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 | 5370 | |
| 4953 | - $message =" <img border=0 src='$logo' alt='$payment_method'/>"; | |
| 5371 | + if (is_array($button_args_override) && $button_args_override) { | |
| 5372 | + $button_args = $button_args_override; | |
| 5373 | + } else { | |
| 5374 | + $button_args = $this->get_html_button_attrs_for_context($context); | |
| 5375 | + } | |
| 5376 | + $short = ($button_args['compact'] ?? 'false') === 'true'; | |
| 5377 | + $button = $this->get_html_button($button_args); | |
| 4954 | 5378 | |
| 4955 | -# Extra classes, if passed IOK 2019-02-26 | |
| 5379 | + # Extra classes, if passed IOK 2019-02-26 | |
| 4956 | 5380 | if (is_array($classes)) { |
| 4957 | 5381 | $classes = join(" ", $classes); |
| 4958 | 5382 | } |
| 4959 | 5383 | if ($classes) $classes = " $classes"; |
| @@ -4958,19 +5382,15 @@ | ||
| 4958 | 5382 | } |
| 4959 | 5383 | if ($classes) $classes = " $classes"; |
| 4960 | 5384 | if ($short) $classes = "short $classes"; |
| 4961 | 5385 | |
| 4962 | - $buttoncode .= " class='single-product button vipps-buy-now $payment_method $disabled$classes' title='$title'>$message</a>"; | |
| 5386 | + $buttoncode .= " class='single-product button vipps-buy-now $payment_method $disabled$classes' title='$title'>$button</a>"; | |
| 5387 | + | |
| 5388 | + | |
| 5389 | + | |
| 4963 | 5390 | return apply_filters('woo_vipps_buy_now_button', $buttoncode, $product_id, $variation_id, $sku, $disabled); |
| 4964 | 5391 | } |
| 4965 | 5392 | |
| 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 | 5393 | // Display a 'buy now with express checkout' button on the product page IOK 2018-09-27 |
| 4974 | 5394 | public function single_product_buy_now_button () { |
| 4975 | 5395 | $gw = $this->gateway(); |
| 4976 | 5396 | $how = $gw->get_option('singleproductexpress'); |
| @@ -5050,51 +5470,90 @@ | ||
| 5050 | 5470 | echo "<div class='vipps_buy_now_wrapper loop'>$button</div>"; |
| 5051 | 5471 | } |
| 5052 | 5472 | |
| 5053 | 5473 | |
| 5054 | - | |
| 5055 | - // Vipps Checkout replaces the default checkout page, and currently uses its own page for this which needs to exist | |
| 5474 | + // Checkout replaces the default checkout page, and currently uses its own page for this which needs to exist | |
| 5056 | 5475 | // IOK 2026-04-30 remove this when checkout is end-of-life'd |
| 5476 | + // We now also use this for the vipps special page, previously a fakepage. LP 2026-08-18 | |
| 5057 | 5477 | public function woocommerce_create_pages ($data) { |
| 5478 | + // Vipps Checkout page | |
| 5058 | 5479 | $vipps_checkout_activated = get_option('woo_vipps_checkout_activated', false); |
| 5059 | - if (!$vipps_checkout_activated) return $data; | |
| 5480 | + if ($vipps_checkout_activated) { | |
| 5481 | + $data['vipps_checkout'] = array( | |
| 5482 | + 'name' => _x( 'vipps_checkout', 'Page slug', 'woo-vipps' ), | |
| 5483 | + 'title' => _x( 'Vipps MobilePay Checkout', 'Page title', 'woo-vipps' ), | |
| 5484 | + 'content' => '<!-- wp:shortcode -->[' . 'vipps_checkout' . ']<!-- /wp:shortcode -->', | |
| 5485 | + ); | |
| 5486 | + } | |
| 5060 | 5487 | |
| 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 | - | |
| 5488 | + // Vipps special page for certain payment flow actions. Previously a fake page. LP 2026-08-18 | |
| 5489 | + $data['vipps_special_page'] = [ | |
| 5490 | + 'name' => 'vipps-payment', // slug | |
| 5491 | + /* translators: company name */ | |
| 5492 | + 'title' => sprintf(__('%s special page', 'woo-vipps'), static::CompanyName()), // we hide the title frontend in template_redirect. LP 2026-08-27 | |
| 5493 | + 'content' => '<!-- wp:shortcode -->[vipps_special_page]<!-- /wp:shortcode -->', | |
| 5494 | + ]; | |
| 5067 | 5495 | return $data; |
| 5068 | 5496 | } |
| 5069 | 5497 | |
| 5070 | - // Creates any necessary Vipps pages. Will be called e.g. when activating Vipps Checkout or turning it on. | |
| 5498 | + // Creates any necessary Vipps pages. E.g vipps checkout page or vipps special page. LP 2026-09-01 | |
| 5499 | + // If a page slug already exists, then it won't overwrite or duplicate it!. LP 2026-09-02 | |
| 5071 | 5500 | public function maybe_create_vipps_pages () { |
| 5501 | + $make_pages = false; | |
| 5502 | + | |
| 5503 | + // Vipps Checkout page. LP 2026-08-18 | |
| 5072 | 5504 | $checkoutid = wc_get_page_id('vipps_checkout'); |
| 5073 | - $makeit = !$checkoutid || ! get_post_status($checkoutid); | |
| 5074 | - if ($makeit) { | |
| 5505 | + if (!$checkoutid || ! get_post_status($checkoutid)) { | |
| 5075 | 5506 | delete_option('woocommerce_vipps_checkout_page_id'); |
| 5507 | + $make_pages = true; | |
| 5076 | 5508 | } |
| 5077 | 5509 | |
| 5078 | - if ($makeit) { | |
| 5079 | - WC_Install::create_pages(); | |
| 5510 | + // vipps special page, previously a fake page. LP 2026-08-18 | |
| 5511 | + $builtin_special_page_id = static::get_special_page_id(); | |
| 5512 | + if (!$builtin_special_page_id || !get_post_status($builtin_special_page_id)) { | |
| 5513 | + delete_option('woocommerce_vipps_special_page_page_id'); | |
| 5514 | + $make_pages = true; | |
| 5080 | 5515 | } |
| 5516 | + | |
| 5517 | + if ($make_pages) { | |
| 5518 | + WC_Install::create_pages(); | |
| 5519 | + } | |
| 5081 | 5520 | } |
| 5082 | 5521 | |
| 5522 | + public function vipps_special_page_shortcode($atts, $content) { | |
| 5523 | + // No point in expanding this unless we are actually doing the special actions. LP 2026-08-25 | |
| 5524 | + if (is_admin()) return; | |
| 5525 | + if (wp_doing_ajax()) return; | |
| 5526 | + if (defined('REST_REQUEST') && REST_REQUEST) return; | |
| 5527 | + if (did_filter('woo_vipps_special_page_html')) return; // User has somehow added two shortcodes. IOK 2026-09-18 | |
| 5083 | 5528 | |
| 5529 | + $action = $_GET['action'] ?? ''; | |
| 5530 | + $html = ""; | |
| 5531 | + switch ($action) { | |
| 5532 | + case 'wait_for_payment': | |
| 5533 | + $html = $this->vipps_wait_for_payment(); | |
| 5534 | + break; | |
| 5535 | + case 'do_express_checkout': | |
| 5536 | + $html = $this->vipps_express_checkout(); | |
| 5537 | + break; | |
| 5538 | + case 'buy_product': | |
| 5539 | + $html = $this->vipps_buy_product(); | |
| 5540 | + break; | |
| 5541 | + default: | |
| 5542 | + $html = ''; | |
| 5543 | + } | |
| 5544 | + // This is mostly to avoid this shortcode evaluating twice IOK 2026-09-18 | |
| 5545 | + $html = apply_filters('woo_vipps_special_page_html', $html, $action); | |
| 5546 | + | |
| 5547 | + // Remember, this is a shortcode, so the html must be returned, not echoed IOK 2026-09-11 | |
| 5548 | + return $html; | |
| 5549 | + } | |
| 5550 | + | |
| 5551 | + | |
| 5084 | 5552 | // This URL will when accessed add a product to the cart and go directly to the express checkout page. |
| 5085 | 5553 | // The argument passed must be a shareable link created for a given product - so this in effect acts as a landing page for |
| 5086 | 5554 | // the buying thru Vipps Express Checkout of a single product linked to in for instance banners. IOK 2018-09-24 |
| 5087 | 5555 | public function vipps_buy_product() { |
| 5088 | - status_header(200,'OK'); | |
| 5089 | - Vipps::nocache(); | |
| 5090 | - | |
| 5091 | - add_filter('body_class', function ($classes) { | |
| 5092 | - $classes[] = 'vipps-express-checkout'; | |
| 5093 | - $classes[] = 'woocommerce-checkout'; // Required by Pixel Your Site IOK 2022-11-24 | |
| 5094 | - return apply_filters('woo_vipps_express_checkout_body_class', $classes); | |
| 5095 | - }); | |
| 5096 | - | |
| 5097 | 5556 | do_action('woo_vipps_express_checkout_page'); |
| 5098 | 5557 | |
| 5099 | 5558 | $session = WC()->session; |
| 5100 | 5559 | $posted = $session->get('__vipps_buy_product'); |
| @@ -5123,39 +5582,38 @@ | ||
| 5123 | 5582 | |
| 5124 | 5583 | if (!$productinfo) { |
| 5125 | 5584 | $title = __("Product is no longer available",'woo-vipps'); |
| 5126 | 5585 | $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); | |
| 5586 | + return $this->special_page_html($title,$content); | |
| 5128 | 5587 | } |
| 5129 | 5588 | |
| 5130 | 5589 | // Pass the productinfo to the express checkout form |
| 5131 | 5590 | $args = array(); |
| 5132 | - $args['quantity'] = 1; | |
| 5133 | - if (array_key_exists('product_id',$productinfo)) $args['product_id'] = intval($productinfo['product_id']); | |
| 5134 | - if (array_key_exists('variation_id',$productinfo)) $args['variation_id'] = intval($productinfo['variation_id']); | |
| 5135 | - if (array_key_exists('product_sku',$productinfo)) $args['sku'] = sanitize_text_field($productinfo['product_sku']); | |
| 5136 | - if (array_key_exists('quantity',$productinfo)) $args['quantity'] = intval($productinfo['quantity']); | |
| 5591 | + $args['product_id'] = esc_attr(intval($productinfo['product_id'] ?? 0)); | |
| 5592 | + $args['variation_id'] = esc_attr(intval($productinfo['variation_id'] ?? 0)); | |
| 5593 | + $args['sku'] = esc_attr(sanitize_text_field($productinfo['product_sku'] ?? "")); | |
| 5594 | + $args['quantity'] = esc_attr(max(1, intval($productinfo['quantity'] ?? 0))); | |
| 5137 | 5595 | |
| 5138 | - // For variable products where some of the attributes are "any", we need to add these as well. This is from woos form-handler for these. | |
| 5139 | - foreach ($productinfo as $key => $value) { | |
| 5140 | - if ( 'attribute_' !== substr( $key, 0, 10 ) ) { | |
| 5141 | - continue; | |
| 5142 | - } | |
| 5143 | - $args[sanitize_title(wp_unslash($key))] = sanitize_text_field(wp_unslash($value)); | |
| 5144 | - } | |
| 5596 | + $payment_method = $this->get_payment_method_name(); | |
| 5597 | + $btitle = esc_attr(sprintf(__('Buy now with %1$s', 'woo-vipps'), $payment_method)); | |
| 5598 | + $bclass = esc_attr($payment_method); | |
| 5145 | 5599 | |
| 5146 | - $this->print_express_checkout_page(true,'do_single_product_express_checkout',$args); | |
| 5600 | + $content = "<p id=waiting>" . __("Please wait while we are preparing your order", 'woo-vipps') . "...</p>"; | |
| 5601 | + $content .= "<div class='vipps-qr-purchase' style='visibility:hidden'>"; | |
| 5602 | + $content .= "<a href='javascript:void(0)' class='single-product button vipps-buy-now $bclass' data-vipps-autostart='true' data-vipps-purchase='single' data-product_id='{$args['product_id']}' data-variation_id='{$args['variation_id']}' data-product_sku='{$args['sku']}' data-quantity='{$args['quantity']}' title='{$btitle}'; | |
| 5603 | + >"; | |
| 5604 | + $content .= $this->get_html_button_for_context('global'); | |
| 5605 | + $content .= "</a>"; | |
| 5606 | + $content .= "</div>"; | |
| 5607 | + | |
| 5608 | + return $content; | |
| 5147 | 5609 | } |
| 5148 | 5610 | |
| 5149 | - // 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 | - public function vipps_express_checkout() { | |
| 5151 | - status_header(200,'OK'); | |
| 5152 | - Vipps::nocache(); | |
| 5611 | + public function vipps_express_checkout_consistency_check() { | |
| 5153 | 5612 | // 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 | 5613 | // IOK 2018-05-28 |
| 5155 | 5614 | $ok = isset($_REQUEST['sec']) && wp_verify_nonce($_REQUEST['sec'],'express'); |
| 5156 | 5615 | |
| 5157 | - | |
| 5158 | 5616 | $backurl = wp_validate_redirect(@$_SERVER['HTTP_REFERER']); |
| 5159 | 5617 | if (!$backurl) $backurl = home_url(); |
| 5160 | 5618 | |
| 5161 | 5619 | if (!$ok) { |
| @@ -5169,218 +5627,39 @@ | ||
| 5169 | 5627 | wp_redirect($backurl); |
| 5170 | 5628 | exit(); |
| 5171 | 5629 | } |
| 5172 | 5630 | |
| 5173 | - add_filter('body_class', function ($classes) { | |
| 5174 | - $classes[] = 'vipps-express-checkout'; | |
| 5175 | - $classes[] = 'woocommerce-checkout'; // Required by Pixel Your Site IOK 2022-11-24 | |
| 5176 | - return apply_filters('woo_vipps_express_checkout_body_class', $classes); | |
| 5177 | - }); | |
| 5178 | - | |
| 5179 | - do_action('woo_vipps_express_checkout_page'); | |
| 5180 | - | |
| 5181 | - $this->print_express_checkout_page(true, 'do_express_checkout'); | |
| 5631 | + add_filter('woo_vipps_express_checkout_consistent', '__return_true'); | |
| 5182 | 5632 | } |
| 5183 | 5633 | |
| 5184 | - // This method tries to ensure that a customer does not 'lose' the return page and | |
| 5185 | - // starts ordering the same products twice. IOK 2020-01-22 | |
| 5186 | - protected function validate_express_checkout_orderspec ($orderspec) { | |
| 5187 | - if (empty($orderspec)) return true; // It's not a duplicate, it's nothing. | |
| 5188 | - | |
| 5189 | - // First build for the current order an array of hash-tables keyed by prodid, varid and quantity. | |
| 5190 | - $orderset = array(); | |
| 5191 | - foreach($orderspec as $entry) $orderset[] = join(':', $entry); | |
| 5192 | - | |
| 5193 | - // Then get open orders | |
| 5194 | - $sessionorders = array(); | |
| 5195 | - $sessionorderdata = WC()->session->get('_vipps_session_orders'); | |
| 5196 | - if ($sessionorderdata) { | |
| 5197 | - foreach(array_keys($sessionorderdata) as $oid) { | |
| 5198 | - $orderobject = wc_get_order($oid); | |
| 5199 | - // Check to see that this hasn't been deleted yet IOK 2020-01-07 | |
| 5200 | - if ($orderobject instanceof WC_Order) { | |
| 5201 | - $sessionorders[] = $orderobject; | |
| 5202 | - } | |
| 5203 | - } | |
| 5634 | + // This is a landing page for the express checkout of the normal cart - it is done like this because this could take time on slower hosts. | |
| 5635 | + // IOK 2026-09-09 - nowadays this is only used for compatibility mode. It will automatically start express checkout of the current cart when reached. | |
| 5636 | + public function vipps_express_checkout() { | |
| 5637 | + // Some checks are made in template_redirect, we check here if they are ok IOK 2026-09-21 | |
| 5638 | + if (!apply_filters('woo_vipps_express_checkout_consistent', false)) { | |
| 5639 | + $content = __('Link expired, please try again', 'woo-vipps'); | |
| 5640 | + return $content; | |
| 5204 | 5641 | } |
| 5205 | - // Nothing more to do here | |
| 5206 | - if (empty($sessionorders)) return true; | |
| 5642 | + | |
| 5643 | + do_action('woo_vipps_express_checkout_page'); | |
| 5207 | 5644 | |
| 5208 | - // And create a similar hash table for each of the open orders | |
| 5209 | - $openorderdata = array(); | |
| 5210 | - foreach ($sessionorders as $open_order) { | |
| 5211 | - $status = $open_order->get_status(); | |
| 5212 | - if ($status == 'cancelled' || $status == 'pending') continue; | |
| 5213 | - $when = strtotime($open_order->get_date_modified()); | |
| 5214 | - $cutoff = $when + apply_filters('woo_vipps_recent_order_cutoff', (5*60)); | |
| 5215 | - if (time() > $cutoff) { | |
| 5216 | - continue; | |
| 5217 | - } | |
| 5218 | - $orderdata = array(); | |
| 5219 | - foreach($open_order->get_items() as $item) { | |
| 5220 | - $productspec = $item->get_product_id() . ':' . $item->get_variation_id() . ':' . $item->get_quantity(); | |
| 5221 | - $orderdata[] = $productspec; | |
| 5222 | - } | |
| 5223 | - $openorderdata[]=$orderdata; | |
| 5224 | - } | |
| 5645 | + $payment_method = $this->get_payment_method_name(); | |
| 5646 | + $btitle = esc_attr(sprintf(__('Buy now with %1$s', 'woo-vipps'), $payment_method)); | |
| 5647 | + $bclass = esc_attr($payment_method); | |
| 5648 | + $sec = esc_attr($_REQUEST['sec']); | |
| 5649 | + $content = ""; | |
| 5650 | + $content .= "<p id=waiting>" . __("Please wait while we are preparing your order", 'woo-vipps') . "...</p>"; | |
| 5651 | + $content .= '<div class="vipps-cart-purchase" style="visibility:hidden">"'; | |
| 5652 | + $content .= "<a href='javascript:void(0)' class='vipps-express-checkout short $bclass' data-vipps-autostart='true' data-sec='$sec' title='$btitle'>"; | |
| 5653 | + $content .= $this->get_html_button_for_context('global'); | |
| 5654 | + $content .="</a>"; | |
| 5655 | + $content .="</div>"; | |
| 5225 | 5656 | |
| 5226 | - // Now: For each entry in the orderhash, check if there is an order that has a) all of them and b) not any more of them. | |
| 5227 | - foreach($openorderdata as $prevorder) { | |
| 5228 | - $a = array_diff($prevorder, $orderset); | |
| 5229 | - $b = array_diff($orderset, $prevorder); | |
| 5230 | - if (empty($a) && empty($b)) { | |
| 5231 | - $this->log(__("It seems a customer is trying to re-order product(s) recently bought in the same session, asking user for confirmation", 'woo-vipps'), 'info'); | |
| 5232 | - return false; | |
| 5233 | - } | |
| 5234 | - } | |
| 5235 | - // Else, order is good. | |
| 5236 | - return true; | |
| 5657 | + return $content; | |
| 5237 | 5658 | } |
| 5238 | 5659 | |
| 5239 | - // Returns a triple of productid, variantid and quantity from an array of arguments which can pass either these or a SKU value. | |
| 5240 | - // Return value is like in a cart. | |
| 5241 | - // Used to create an order in express checkout, and to see that this order isn't a repeat. IOK 2020-01-22 | |
| 5242 | - protected function get_orderspec_from_arguments ($productinfo) { | |
| 5243 | - if (!$productinfo) return array(); | |
| 5244 | - $variantid = 0; | |
| 5245 | - $productid = 0; | |
| 5246 | - $quantity = intval(@$productinfo['quantity']); | |
| 5247 | - if (!$quantity) $quantity = 1; | |
| 5248 | - if (isset($productinfo['sku']) && $productinfo['sku']) { | |
| 5249 | - $sku = $productinfo['sku']; | |
| 5250 | - $skuid = wc_get_product_id_by_sku($sku); | |
| 5251 | - $product = wc_get_product($skuid); | |
| 5252 | - $parentid = $product ? $product->get_parent_id() : null; | |
| 5253 | - if ($product) { | |
| 5254 | - if ($parentid) { | |
| 5255 | - $variantid = $skuid; $productid = $parentid; | |
| 5256 | - } else { | |
| 5257 | - $productid = $skuid; | |
| 5258 | - } | |
| 5259 | - } | |
| 5260 | - } else if (isset($productinfo['product_id']) && $productinfo['product_id']) { | |
| 5261 | - $productid = intval($productinfo['product_id']); | |
| 5262 | - $variantid = intval(@$productinfo['variation_id']); | |
| 5263 | - } | |
| 5264 | - if ($productid) return array(array('product_id'=>$productid, 'variation_id'=>$variantid, 'quantity'=>$quantity)); | |
| 5265 | - return array(); | |
| 5266 | - } | |
| 5267 | - // If no productinfo, this will produce an orderspec from the current cart IOK 2020-01-24 | |
| 5268 | - protected function get_orderspec_from_cart () { | |
| 5269 | - $cartitems = WC()->cart->get_cart(); | |
| 5270 | - $orderspec = array(); | |
| 5271 | - foreach($cartitems as $item => $values) { | |
| 5272 | - $orderspec[] = array('product_id'=>$values['product_id'], 'variation_id'=>$values['variation_id'], 'quantity'=>$values['quantity']); | |
| 5273 | - } | |
| 5274 | - return $orderspec; | |
| 5275 | - } | |
| 5276 | - | |
| 5277 | - // 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) { | |
| 5279 | - $gw = $this->gateway(); | |
| 5280 | - | |
| 5281 | - $expressCheckoutMessages = array(); | |
| 5282 | - $expressCheckoutMessages['termsAndConditionsError'] = __( 'Please read and accept the terms and conditions to proceed with your order.', 'woocommerce' ); | |
| 5283 | - $expressCheckoutMessages['temporaryError'] = sprintf(__('%1$s is temporarily unavailable.','woo-vipps'), $this->get_payment_method_name()); | |
| 5284 | - $expressCheckoutMessages['successMessage'] = sprintf(__('To the %1$s app!','woo-vipps'), $this->get_payment_method_name()); | |
| 5285 | - | |
| 5286 | - wp_register_script('vipps-express-checkout',plugins_url('js/express-checkout.js',__FILE__),array('jquery','wp-hooks'),filemtime(dirname(__FILE__) . "/js/express-checkout.js"), 'true'); | |
| 5287 | - wp_localize_script('vipps-express-checkout', 'VippsCheckoutMessages', $expressCheckoutMessages); | |
| 5288 | - wp_enqueue_script('vipps-express-checkout'); | |
| 5289 | - // If we have a valid nonce when we get here, just call the 'create order' bit at once. Otherwise, make a button | |
| 5290 | - // to actually perform the express checkout. | |
| 5291 | - $buttonimgurl= apply_filters('woo_vipps_express_checkout_button', $this->get_payment_logo('landing')); | |
| 5292 | - | |
| 5293 | - | |
| 5294 | - $orderspec = $this->get_orderspec_from_arguments($productinfo); | |
| 5295 | - if (empty($orderspec)) { | |
| 5296 | - $orderspec = $this->get_orderspec_from_cart(); | |
| 5297 | - } | |
| 5298 | - $orderisOK = $this->validate_express_checkout_orderspec($orderspec); | |
| 5299 | - $orderisOK = apply_filters('woo_vipps_validate_express_checkout_orderspec', $orderisOK, $orderspec); | |
| 5300 | - | |
| 5301 | - $askForTerms = function_exists('wc_terms_and_conditions_checkbox_enabled') ? wc_terms_and_conditions_checkbox_enabled() : true; | |
| 5302 | - $askForTerms = $askForTerms && ($gw->get_option('expresscheckout_termscheckbox') == 'yes'); | |
| 5303 | - $askForTerms = apply_filters('woo_vipps_express_checkout_terms_and_conditions_checkbox_enabled', $askForTerms); | |
| 5304 | - | |
| 5305 | - $askForConfirmationHTML = ''; | |
| 5306 | - if (!$orderisOK) { | |
| 5307 | - $header = __("Are you sure?",'woo-vipps'); | |
| 5308 | - $body = __("You recently completed an order with exactly the same products as you are buying now. There should be an email in your inbox from the previous purchase. Are you sure you want to order again?",'woo-vipps'); | |
| 5309 | - $askForConfirmationHTML = apply_filters('woo_vipps_ask_user_to_confirm_repurchase', "<h2 class='confirmVippsExpressCheckoutHeader'>$header</h2><p>$body</p>"); | |
| 5310 | - } | |
| 5311 | - // Should we go directly to checkout, or do we need to stop and ask the user something (for instance?) IOK 2010-01-20 | |
| 5312 | - $execute = $execute && $orderisOK && !$askForTerms; | |
| 5313 | - $execute = apply_filters('woo_vipps_checkout_directly_to_vipps', $execute, $productinfo); | |
| 5314 | - | |
| 5315 | - $content = $this->spinner(); | |
| 5316 | - | |
| 5317 | - // We impersonate the woocommerce-checkout form here mainly to work with the Pixel Your Site plugin IOK 2022-11-24 | |
| 5318 | - // The form data below is sent on order creation; the sec is also used to poll session status | |
| 5319 | - $classlist = apply_filters("woo_vipps_express_checkout_form_classes", "woocommerce-checkout"); | |
| 5320 | - $content .= "<form id='vippsdata' class='" . esc_attr($classlist) . "'>"; | |
| 5321 | - $content .= "<input type='hidden' name='action' value='" . esc_attr($action) ."'>"; | |
| 5322 | - if ($this->gateway()->get_option('vippsorderattribution') == 'yes') { | |
| 5323 | - // This is for the new order attribution feature of woo. IOK 2024-01-09 | |
| 5324 | - $content .= '<input type="hidden" id="vippsorderattribution" value="1" />'; | |
| 5325 | - ob_start(); | |
| 5326 | - do_action( 'woocommerce_after_order_notes'); | |
| 5327 | - $content .= ob_get_clean(); | |
| 5328 | - } | |
| 5329 | - $content .= wp_nonce_field('do_express','sec',1,false); | |
| 5330 | - | |
| 5331 | - $termsHTML = ''; | |
| 5332 | - if ($askForTerms) { | |
| 5333 | - // Include shop terms | |
| 5334 | - ob_start(); | |
| 5335 | - wc_get_template('checkout/terms.php'); | |
| 5336 | - $termsHTML = ob_get_clean(); | |
| 5337 | - $termsHTML = apply_filters('woo_vipps_express_checkout_terms_and_conditions_html',$termsHTML); | |
| 5338 | - } | |
| 5339 | - $termsHTML = apply_filters('woo_vipps_express_checkout_terms_and_conditions_html',$termsHTML); | |
| 5340 | - | |
| 5341 | - if ($productinfo) { | |
| 5342 | - foreach($productinfo as $key=>$value) { | |
| 5343 | - $k = esc_attr($key); | |
| 5344 | - $v = esc_attr($value); | |
| 5345 | - $content .= "<input type='hidden' name='$k' value='$v' />"; | |
| 5346 | - } | |
| 5347 | - } | |
| 5348 | - ob_start(); | |
| 5349 | - $content .= do_action('woo_vipps_express_checkout_orderspec_form', $productinfo); | |
| 5350 | - $content .= ob_get_clean(); | |
| 5351 | - $content .= "</form>"; | |
| 5352 | - | |
| 5353 | - $extraHTML = apply_filters('woo_vipps_express_checkout_final_html', '', $termsHTML,$askForConfirmationHTML); | |
| 5354 | - $pressTheButtonHTML = ""; | |
| 5355 | - if (empty($termsHTML) && empty($askForConfirmationHTML) && empty($extraHTML)) { | |
| 5356 | - $pressTheButtonHTML = "<p id=waiting>" . sprintf(__('Ready for %1$s - press the button', 'woo-vipps'), Vipps::ExpressCheckoutName()) . "</p>"; | |
| 5357 | - } | |
| 5358 | - | |
| 5359 | - if ($execute) { | |
| 5360 | - $content .= "<p id=waiting>" . __("Please wait while we are preparing your order", 'woo-vipps') . "</p>"; | |
| 5361 | - $content .= "<div id='vipps-status-message'></div>"; | |
| 5362 | - $this->fakepage(__('Order in progress','woo-vipps'), $content); | |
| 5363 | - return; | |
| 5364 | - } else { | |
| 5365 | - $content .= $askForConfirmationHTML; | |
| 5366 | - $content .= $extraHTML; | |
| 5367 | - $content .= $termsHTML; | |
| 5368 | - $content .= apply_filters('woo_vipps_express_checkout_validation_elements', ''); | |
| 5369 | - $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>"; | |
| 5371 | - $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; | |
| 5374 | - } | |
| 5375 | - } | |
| 5376 | - | |
| 5377 | - | |
| 5378 | - | |
| 5379 | - public function vipps_wait_for_payment() { | |
| 5380 | - status_header(200,'OK'); | |
| 5381 | - Vipps::nocache(); | |
| 5382 | - | |
| 5660 | + // Called in template_redirect before we get to the wait-for-payment page IOK 2026-09-21 | |
| 5661 | + private function handle_payment_poll_and_redirect () { | |
| 5383 | 5662 | $orderid = WC()->session->get('_vipps_pending_order'); |
| 5384 | 5663 | |
| 5385 | 5664 | $order = null; |
| 5386 | 5665 | $gw = $this->gateway(); |
| @@ -5394,9 +5673,9 @@ | ||
| 5394 | 5673 | // If so, we will read the order id from the GET arguments and check if the auth token is correct, |
| 5395 | 5674 | // simulating the session with that. |
| 5396 | 5675 | // IOK 2019-11-19, changed to using GET 2023-01-23 |
| 5397 | 5676 | if ($no_session && $limited_session) { |
| 5398 | - $orderid = intval(@$_GET['id']); | |
| 5677 | + $orderid = intval($_GET['id'] ?? false); | |
| 5399 | 5678 | } |
| 5400 | 5679 | if ($orderid) { |
| 5401 | 5680 | clean_post_cache($orderid); |
| 5402 | 5681 | $order = wc_get_order($orderid); |
| @@ -5411,20 +5690,22 @@ | ||
| 5411 | 5690 | $session = WC()->session; |
| 5412 | 5691 | if (!$session->has_session()) { |
| 5413 | 5692 | $session->set_customer_session_cookie(true); |
| 5414 | 5693 | } |
| 5694 | + | |
| 5695 | + $sessionorders= WC()->session->get('_vipps_session_orders'); | |
| 5696 | + $sessionorders[$orderid] = 1; | |
| 5697 | + WC()->session->set('_vipps_session_orders',$sessionorders); | |
| 5415 | 5698 | $session->set('_vipps_pending_order', $orderid); |
| 5699 | + WC()->session->save_data(); | |
| 5416 | 5700 | } |
| 5417 | 5701 | } |
| 5418 | 5702 | |
| 5419 | - | |
| 5420 | - do_action('woo_vipps_wait_for_payment_page',$order); | |
| 5421 | - | |
| 5422 | 5703 | $deleted_order=0; |
| 5423 | 5704 | if ($orderid && !$order) { |
| 5424 | 5705 | // If this happens, we actually did have an order, but it has been deleted, which must mean that it was cancelled. |
| 5425 | 5706 | // Concievably a hook on the 'cancel'-transition or in the callback handlers could clean that up before we get here. IOK 2019-09-26 |
| 5426 | - $this->log(__("In order return: The order %1\$d seems to be deleted", 'woo-vipps'), 'debug'); | |
| 5707 | + $this->log(sprintf(__("In order return: The order %1\$d seems to be deleted", 'woo-vipps'), $orderid), 'debug'); | |
| 5427 | 5708 | $deleted_order=1; |
| 5428 | 5709 | } |
| 5429 | 5710 | |
| 5430 | 5711 | if (!$order && !$deleted_order) wp_die(__('Unknown order', 'woo-vipps')); |
| @@ -5434,12 +5715,13 @@ | ||
| 5434 | 5715 | |
| 5435 | 5716 | // This is for debugging only - set to false to ensure we wait for the callback. IOK 2023-08-04 |
| 5436 | 5717 | $do_poll = true; |
| 5437 | 5718 | |
| 5438 | - // Still pending, no callback. Make a call to the server as the order might not have been created. IOK 2018-05-16 | |
| 5719 | + // Do a single poll here to check and set the order status at Woo using the order status at Vipps IOK 2026-09-29 | |
| 5439 | 5720 | if ($do_poll && $status == 'pending') { |
| 5440 | - // Just in case the callback hasn't come yet, do a quick check of the order status at Vipps. | |
| 5441 | - $newstatus = $gw->callback_check_order_status($order); | |
| 5721 | + // We will do *one* poll before waiting for the callback (for a while, at least.) IOK 2026-09-29 | |
| 5722 | + $newstatus = $gw->poll_and_check_order_status($order); | |
| 5723 | + $this->log(sprintf(__("In order return: Order status of %1\$d is %2\$s", 'woo-vipps'), $orderid, $newstatus), 'info'); | |
| 5442 | 5724 | if ($status != $newstatus) { |
| 5443 | 5725 | $status = $newstatus; |
| 5444 | 5726 | clean_post_cache($orderid); |
| 5445 | 5727 | $order = wc_get_order($orderid); // Reload order object |
| @@ -5444,11 +5726,13 @@ | ||
| 5444 | 5726 | clean_post_cache($orderid); |
| 5445 | 5727 | $order = wc_get_order($orderid); // Reload order object |
| 5446 | 5728 | } |
| 5447 | 5729 | } else { |
| 5448 | - // No need to do anyting here. IOK 2020-01-26 | |
| 5730 | + // No need to do anyting here. IOK 2020-01-26 | |
| 5449 | 5731 | } |
| 5450 | 5732 | |
| 5733 | + // Actually, this may cause a second poll if the first left us pending. Should be rewritten - but *mostly* it will just check | |
| 5734 | + // the payment status at Vipps without polls, which will tell us if the payment succeeded in case people use custom order statuses and so on. IOK 2026-09-29 | |
| 5451 | 5735 | $payment = 'notchecked'; |
| 5452 | 5736 | if ($do_poll) { |
| 5453 | 5737 | $payment = $deleted_order ? 'cancelled' : $gw->check_payment_status($order); |
| 5454 | 5738 | } |
| @@ -5462,9 +5746,8 @@ | ||
| 5462 | 5746 | exit(); |
| 5463 | 5747 | } |
| 5464 | 5748 | |
| 5465 | 5749 | // We are done, but in failure. Don't poll. |
| 5466 | - $content = ""; | |
| 5467 | 5750 | $failure_redirect = apply_filters('woo_vipps_order_failed_redirect', '', $orderid); |
| 5468 | 5751 | |
| 5469 | 5752 | // Status is failed; still send to return url (as of now /order-recieved), the text there will depend on the status. |
| 5470 | 5753 | // 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 +5755,9 @@ | ||
| 5472 | 5755 | $failure_redirect = $failure_redirect ?: $gw->get_return_url($order); |
| 5473 | 5756 | wp_redirect($failure_redirect); |
| 5474 | 5757 | exit(); |
| 5475 | 5758 | } |
| 5759 | + | |
| 5476 | 5760 | if ($status == 'cancelled' || $payment == 'cancelled') { |
| 5477 | 5761 | $this->maybe_restore_cart($orderid,'failed'); |
| 5478 | 5762 | if ($failure_redirect){ |
| 5479 | 5763 | wp_redirect($failure_redirect); |
| @@ -5478,27 +5762,46 @@ | ||
| 5478 | 5762 | if ($failure_redirect){ |
| 5479 | 5763 | wp_redirect($failure_redirect); |
| 5480 | 5764 | exit(); |
| 5481 | 5765 | } |
| 5766 | + } else { | |
| 5767 | + // If not, enqueue the status checker IOK 2026-09-21 | |
| 5768 | + 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'); | |
| 5769 | + } | |
| 5770 | + | |
| 5771 | + $this->log(sprintf(__("Order status of %1\$d not ready in order return: payment status %2\$s", 'woo-vipps'), $orderid, $payment), 'info'); | |
| 5772 | + | |
| 5773 | + // Communicate this to the shortcode IOK 2026-09-21 | |
| 5774 | + add_filter('woo_vipps_wait_for_payment_status', function () use($orderid, $status, $payment) { | |
| 5775 | + return ['orderid'=>$orderid, 'status'=>$status, 'payment'=>$payment]; | |
| 5776 | + }); | |
| 5777 | + | |
| 5778 | + } | |
| 5779 | + | |
| 5780 | + public function vipps_wait_for_payment() { | |
| 5781 | + // This will have been computed in template_redirect, but the status will be either still pending or failed. IOK 2026-09-21 | |
| 5782 | + $data = apply_filters('woo_vipps_wait_for_payment_status', []); | |
| 5783 | + | |
| 5784 | + $orderid = $data['orderid'] ?? 0; | |
| 5785 | + $status = $data['status'] ?? ""; | |
| 5786 | + $payment = $data['payment'] ?? ""; | |
| 5787 | + | |
| 5788 | + $order = wc_get_order($orderid); | |
| 5789 | + if (!$order) wp_die(__('Unknown order', 'woo-vipps')); | |
| 5790 | + | |
| 5791 | + do_action('woo_vipps_wait_for_payment_page',$order); | |
| 5792 | + $gw = $this->gateway(); | |
| 5793 | + | |
| 5794 | + $content = ""; | |
| 5795 | + if ($status == 'cancelled' || $payment == 'cancelled') { | |
| 5482 | 5796 | $content .= "<div id=failure><p>". __('Order cancelled','woo-vipps') . '</p>'; |
| 5483 | 5797 | $content .= "<p><a href='" . home_url() . "' class='btn button'>" . __('Continue shopping','woo-vipps') . '</a></p>'; |
| 5484 | 5798 | $content .= "</div>"; |
| 5485 | - $this->fakepage(__('Order cancelled','woo-vipps'), $content); | |
| 5486 | - | |
| 5487 | - return; | |
| 5799 | + return $this->special_page_html('', $content); | |
| 5488 | 5800 | } |
| 5489 | 5801 | |
| 5490 | 5802 | // 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 | 5803 | // 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 | 5804 | $signal = $this->callbackSignal($order); |
| 5502 | 5805 | $content = ""; |
| 5503 | 5806 | $content .= "<div id='waiting'><p>" . sprintf(__('Waiting for confirmation of purchase from %1$s','woo-vipps'), $this->get_payment_method_name()); |
| 5504 | 5807 | |
| @@ -5506,18 +5809,18 @@ | ||
| 5506 | 5809 | $signalurl = $this->callbackSignalURL($signal); |
| 5507 | 5810 | |
| 5508 | 5811 | $content .= "</p></div>"; |
| 5509 | 5812 | |
| 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) . "'>"; | |
| 5813 | + $failure_redirect = apply_filters('woo_vipps_order_failed_redirect', '', $orderid); | |
| 5814 | + | |
| 5815 | + // Carry the order status to the checking script IOK 2026-09-21 | |
| 5816 | + $content .= "<form id='vippsdata'>"; | |
| 5513 | 5817 | $content .= "<input type='hidden' id='fkey' name='fkey' value='".htmlspecialchars($signalurl)."'>"; |
| 5514 | 5818 | $content .= "<input type='hidden' name='key' value='".htmlspecialchars($order->get_order_key())."'>"; |
| 5515 | 5819 | $content .= "<input type='hidden' name='action' value='check_order_status'>"; |
| 5516 | - $content .= wp_nonce_field('vippsstatus','sec',1,false); | |
| 5820 | + $content .= wp_nonce_field('vippsstatus','sec',1,false); | |
| 5517 | 5821 | $content .= "</form>"; |
| 5518 | 5822 | |
| 5519 | - | |
| 5520 | 5823 | $content .= "<div id='error' style='display:none'><p>".__('Error during order confirmation','woo-vipps'). '</p>'; |
| 5521 | 5824 | $content .= "<p>" . __('An error occured during order confirmation. The error has been logged. Please contact us to determine the status of your order', 'woo-vipps') . "</p>"; |
| 5522 | 5825 | $content .= "<p><a href='" . home_url() . "' class='btn button'>" . __('Continue shopping','woo-vipps') . '</a></p>'; |
| 5523 | 5826 | $content .= "</div>"; |
| @@ -5531,94 +5834,22 @@ | ||
| 5531 | 5834 | $content .= "<a id='continueToOrderFailed' style='display:none' href='" . $failure_redirect . "'></a>"; |
| 5532 | 5835 | $content .= "<a id='continueToOrderFailedFallback' style='display:none' href='" . $gw->get_return_url($order) . "'></a>"; |
| 5533 | 5836 | $content .= "</div>"; |
| 5534 | 5837 | |
| 5838 | + return $this->special_page_html('', $content); | |
| 5839 | + } | |
| 5535 | 5840 | |
| 5536 | - $this->fakepage(__('Waiting for your order confirmation','woo-vipps'), $content); | |
| 5841 | + // Returns formatted html for the vipps special page. LP 2026-08-27 | |
| 5842 | + public function special_page_html($header, $content) { | |
| 5843 | + $header_html = $header ? "<h2 class='vipps-special-page-title page-title'>$header</h2>" : ''; | |
| 5844 | + $html = <<<EOF | |
| 5845 | + $header_html | |
| 5846 | + <div class="vipps-special-page-content">$content</div> | |
| 5847 | + EOF; | |
| 5848 | + return apply_filters('woo_vipps_special_page_html', $html, $header, $content); | |
| 5537 | 5849 | } |
| 5538 | 5850 | |
| 5539 | 5851 | |
| 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 | 5852 | // Support the interactivity API with data about our cart IOK 2026-02-23 |
| 5622 | 5853 | public function woo_vipps_store_api_cart_data() { |
| 5623 | 5854 | // 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 | 5855 | |
| @@ -5624,13 +5855,15 @@ | ||
| 5624 | 5855 | |
| 5625 | 5856 | $checkout_page = $this->gateway()->vipps_checkout_available(); |
| 5626 | 5857 | $standard_checkout = get_permalink(get_option('woocommerce_checkout_page_id')); |
| 5627 | 5858 | $checkout_url = $checkout_page ? get_permalink($checkout_page) : $standard_checkout; |
| 5859 | + | |
| 5628 | 5860 | $cart_data = array( |
| 5629 | 5861 | 'cart_hide_express' => !$this->gateway()->show_express_checkout(), |
| 5630 | 5862 | 'cart_supports_checkout' => (bool) $checkout_page, |
| 5631 | 5863 | 'checkout_url' => $checkout_url, |
| 5632 | 5864 | ); |
| 5865 | + | |
| 5633 | 5866 | return $cart_data; |
| 5634 | 5867 | } |
| 5635 | 5868 | |
| 5636 | 5869 | public function woo_vipps_store_api_cart_schema() { |
| @@ -5652,8 +5885,117 @@ | ||
| 5652 | 5885 | ), |
| 5653 | 5886 | ); |
| 5654 | 5887 | } |
| 5655 | 5888 | |
| 5889 | + // Inits option 'vipps_button_options' and handles migration from older versions. LP 2026-06-26 | |
| 5890 | + // new version is stored as vipps_button_options2 to avoid breaking older versions on version revert. IOK 2026-07-15 | |
| 5891 | + private function init_button_options() { | |
| 5892 | + /* New structure as of now | |
| 5893 | + * [ | |
| 5894 | + * 'version' => x.x, | |
| 5895 | + * 'express' => [ | |
| 5896 | + * 'version' => x.x, used to migrate from previous iterations | |
| 5897 | + * 'configs' => [ different button parameters for certain contexts, falls back to global if context has no override | |
| 5898 | + * 'global' => ['compact' => ..., 'verb' => ..., ...], | |
| 5899 | + * 'cart' => [...], | |
| 5900 | + * 'product' => [...], | |
| 5901 | + * 'checkout' => [...], | |
| 5902 | + * ... | |
| 5903 | + * ], | |
| 5904 | + * 'product_configs' => [ overrides for specific products | |
| 5905 | + * 1532 => ['compact' => ..., 'verb' => ..., ...], | |
| 5906 | + * ... | |
| 5907 | + * ], | |
| 5908 | + * ], | |
| 5909 | + * ] | |
| 5910 | + */ | |
| 5911 | + $options = get_option('vipps_button_options2'); | |
| 5912 | + if (!empty($options)) return; | |
| 5913 | + | |
| 5914 | + $old_options = get_option('vipps_button_options'); | |
| 5915 | + $default_config = $this->get_html_button_default_attrs(); | |
| 5916 | + unset($default_config['brand']); // brand needs to be dynamic from payment method! LP 2026-07-01 | |
| 5917 | + $default_compact = array_replace($default_config, ['compact' => 'true']); | |
| 5918 | + | |
| 5919 | + $default_options = [ | |
| 5920 | + 'version' => $this->button_options_version, | |
| 5921 | + 'express' => [ | |
| 5922 | + 'version' => $this->button_options_express_version, | |
| 5923 | + 'configs' => [ | |
| 5924 | + 'global' => $default_config, | |
| 5925 | + // Need compact version by default for below pages. LP 2026-07-07 | |
| 5926 | + 'catalog' => $default_compact, | |
| 5927 | + 'minicart' => $default_compact, // storefront needs compact, tho 2025 theme has a lot of room. Just use compact LP 2026-07-07 | |
| 5928 | + ], | |
| 5929 | + 'product_configs' => [], | |
| 5930 | + ], | |
| 5931 | + ]; | |
| 5932 | + | |
| 5933 | + $new_options = $default_options; | |
| 5934 | + | |
| 5935 | + //Actually, we have some options from the old structure IOK 2026-07-15 | |
| 5936 | + if (!empty($old_options)) { | |
| 5937 | + $new_options['express']['configs']['global'] = $this->migrate_button_variant_to_config($old_options['express']['variant'] ?? ''); | |
| 5938 | + unset($new_options['express']['configs']['global']['brand']); // dont set brand, this needs to be dynamic. LP 2026-07-01 | |
| 5939 | + } | |
| 5940 | + | |
| 5941 | + // Migrate context/page mini override to new context config. LP 2026-06-26 | |
| 5942 | + if (is_array($old_options['express']['force-mini'] ?? null)) { | |
| 5943 | + foreach($old_options['express']['force-mini'] as $context => $use_mini) { | |
| 5944 | + if ("yes" === $use_mini) { | |
| 5945 | + $config = $this->migrate_button_variant_to_config($old_options['express']['mini-variant'] ?? ''); | |
| 5946 | + unset($config['brand']); // brand needs to be dynamic from payment method! LP 2026-07-01 | |
| 5947 | + $config['compact'] = 'true'; | |
| 5948 | + $new_options['express']['configs'][$context] = $config; | |
| 5949 | + } | |
| 5950 | + } | |
| 5951 | + } | |
| 5952 | + | |
| 5953 | + if ($this->get_payment_method_name() !== 'MobilePay') { | |
| 5954 | + // Finnish is only available in the MobilePay component right now, so reset language in any configs. LP 2026-07-01 | |
| 5955 | + foreach(($new_options['express']['configs'] ?? []) as $context => $config) { | |
| 5956 | + if ('fi' === ($config['language'] ?? '')) { | |
| 5957 | + $config['language'] = 'store'; | |
| 5958 | + $new_options['express']['configs'][$context] = $config; | |
| 5959 | + } | |
| 5960 | + } | |
| 5961 | + } | |
| 5962 | + | |
| 5963 | + /* translators: placeholders are arrays */ | |
| 5964 | + $this->log(sprintf(__('Migrating from old button options. Old: %s, new: %s', 'woo-vipps'), print_r($options, true), print_r($new_options, true)), 'debug'); | |
| 5965 | + | |
| 5966 | + | |
| 5967 | + | |
| 5968 | + update_option('vipps_button_options2', $new_options); | |
| 5969 | + } | |
| 5970 | + | |
| 5971 | + // Old variant string => new config array. LP 2026-06-26 | |
| 5972 | + public function migrate_button_variant_to_config($variant_slug) { | |
| 5973 | + if (!is_string($variant_slug)) return []; | |
| 5974 | + $config = $this->get_html_button_default_attrs(); | |
| 5975 | + $config['rounded'] = str_contains($variant_slug, 'pill') ? 'true' : 'false'; | |
| 5976 | + $config['compact'] = str_contains($variant_slug, 'mini') ? 'true' : 'false'; | |
| 5977 | + if (str_contains($variant_slug, 'buy-now')) { | |
| 5978 | + $config['verb'] = 'buy'; | |
| 5979 | + } else if (str_contains($variant_slug, 'express')) { | |
| 5980 | + $config['verb'] = 'express'; | |
| 5981 | + } | |
| 5982 | + return $config; | |
| 5983 | + } | |
| 5984 | + | |
| 5985 | + // Old legacy button logo variants. Replaced by web component. See get_html_button(). LP 2026-07-01 | |
| 5986 | + public function get_express_logo_variants() { | |
| 5987 | + return [ | |
| 5988 | + 'buy-now-rectangular' => __('Buy now rectangular', 'woo-vipps'), | |
| 5989 | + 'buy-now-pill' => __('Buy now pill', 'woo-vipps'), | |
| 5990 | + 'express-rectangular' => __('Express rectangular', 'woo-vipps'), | |
| 5991 | + 'express-pill' => __('Express pill', 'woo-vipps'), | |
| 5992 | + 'express-rectangular-mini' => __('Express rectangular mini', 'woo-vipps'), | |
| 5993 | + 'express-pill-mini' => __('Express pill mini', 'woo-vipps'), | |
| 5994 | + ]; | |
| 5995 | + } | |
| 5996 | + | |
| 5997 | + | |
| 5656 | 5998 | // Whether the order is possible to restart with a retry session at VMP. LP 2026-03-18 |
| 5657 | 5999 | public static function order_is_vipps_retryable($order_id) { |
| 5658 | 6000 | $order = wc_get_order($order_id); |
| 5659 | 6001 | if (!$order) return false; |
| @@ -5662,6 +6004,20 @@ | ||
| 5662 | 6004 | $shipping_set = $order->get_meta('_vipps_shipping_set'); |
| 5663 | 6005 | |
| 5664 | 6006 | // Express or unfinalized Checkout orders do not have shipping available, so we cant retry these in particular. LP 2026-03-18 |
| 5665 | 6007 | return $nonexpress_epayment || $shipping_set; |
| 6008 | + } | |
| 6009 | + | |
| 6010 | + /** Returns the plugin's rest api namespace including the version. | |
| 6011 | + * Use latest version ($version = 'latest') with caution, we want backwards compatible endpoints. LP 2026-03-31 */ | |
| 6012 | + public static function get_rest_namespace($version = 'latest') { | |
| 6013 | + $version = $version === 'latest' ? self::REST_CURRENT_VERSION : $version; | |
| 6014 | + return self::REST_NAMESPACE_BASE . "/$version"; | |
| 6015 | + } | |
| 6016 | + | |
| 6017 | + /** Returns the plugin's rest api url. | |
| 6018 | + * $version accepts 'latest', but you probably don't want to do that. | |
| 6019 | + * Remember root forward-slash for $route. e.g $route = '/my-route' LP 2026-03-31 */ | |
| 6020 | + public static function get_rest_url($version, $route) { | |
| 6021 | + return get_rest_url(null, static::get_rest_namespace($version) . $route, 'rest'); | |
| 5666 | 6022 | } |
| 5667 | 6023 | } |