| @@ -8,66 +8,278 @@ | ||
| 8 | 8 | |
| 9 | 9 | namespace Automattic\WooCommerce\Blocks\Payments\Integrations; |
| 10 | 10 | |
| 11 | 11 | final class Vipps extends AbstractPaymentMethodType { |
| 12 | - private $localized=0; | |
| 12 | + private $localized = 0; | |
| 13 | + private static $store_api_payment_processor = null; | |
| 13 | 14 | protected $name = 'vipps'; |
| 14 | - protected $payment_method_name = "Vipps"; | |
| 15 | + protected $payment_method_name = 'Vipps'; | |
| 15 | 16 | |
| 17 | + /** | |
| 18 | + * Load gateway settings and register the Store API bridge when Blocks initializes this integration. | |
| 19 | + */ | |
| 16 | 20 | public function initialize() { |
| 17 | 21 | $this->settings = get_option( 'woocommerce_vipps_settings', [] ); |
| 18 | - $this->payment_method_name = \WC_Gateway_Vipps::instance()->get_payment_method_name(); | |
| 22 | + $this->payment_method_name = \WC_Gateway_Vipps::instance()->get_payment_method_name(); | |
| 23 | + $this->register_store_api_payment_processor(); | |
| 19 | 24 | } |
| 20 | 25 | |
| 21 | - // Register this payment method IOK 2020-08-10 | |
| 22 | - public static function register() { | |
| 23 | - add_action( 'woocommerce_blocks_payment_method_type_registration', | |
| 24 | - function ($registry) { | |
| 25 | - $registry->register(new static()); | |
| 26 | - }); | |
| 27 | - } | |
| 26 | + /** | |
| 27 | + * Register once at priority 10. Setting a result status here lets WooCommerce's | |
| 28 | + * legacy fallback at priority 999 skip calling process_payment a second time. | |
| 29 | + */ | |
| 30 | + protected function register_store_api_payment_processor() { | |
| 31 | + if ( self::$store_api_payment_processor ) { | |
| 32 | + return; | |
| 33 | + } | |
| 28 | 34 | |
| 35 | + self::$store_api_payment_processor = $this; | |
| 36 | + | |
| 37 | + add_action( | |
| 38 | + 'woocommerce_rest_checkout_process_payment_with_context', | |
| 39 | + array( $this, 'process_gutenberg_checkout_payment' ), | |
| 40 | + 10, | |
| 41 | + 2 | |
| 42 | + ); | |
| 43 | + } | |
| 44 | + | |
| 45 | + // Register this payment method IOK 2020-08-10 | |
| 46 | + /** | |
| 47 | + * Register the integration with the Blocks payment-method registry. | |
| 48 | + */ | |
| 49 | + public static function register() { | |
| 50 | + add_action( | |
| 51 | + 'woocommerce_blocks_payment_method_type_registration', | |
| 52 | + function ( $registry ) { | |
| 53 | + $registry->register( new static() ); | |
| 54 | + } | |
| 55 | + ); | |
| 56 | + } | |
| 57 | + | |
| 58 | + /** | |
| 59 | + * Expose the method to Blocks only when it is enabled in the gateway settings. | |
| 60 | + */ | |
| 29 | 61 | public function is_active() { |
| 30 | 62 | return filter_var( $this->get_setting( 'enabled', false ), FILTER_VALIDATE_BOOLEAN ); |
| 31 | 63 | } |
| 64 | + | |
| 65 | + /** | |
| 66 | + * Load the checkout component after shared gateway code; filemtime invalidates cached JavaScript. | |
| 67 | + */ | |
| 32 | 68 | public function get_payment_method_script_handles() { |
| 33 | 69 | |
| 34 | - $version = filemtime(dirname(__FILE__) . "/js/wc-payment-method-vipps.js"); | |
| 35 | - $path = plugins_url('js/wc-payment-method-vipps.js', __FILE__); | |
| 36 | - $handle = 'wc-payment-method-vipps'; | |
| 37 | - $dependencies = array('wp-hooks', 'vipps-gw'); | |
| 70 | + $version = filemtime( dirname( __FILE__ ) . '/js/wc-payment-method-vipps.js' ); | |
| 71 | + $path = plugins_url( 'js/wc-payment-method-vipps.js', __FILE__ ); | |
| 72 | + $handle = 'wc-payment-method-vipps'; | |
| 73 | + $dependencies = array( 'wp-hooks', 'wp-element', 'vipps-gw' ); | |
| 38 | 74 | |
| 39 | - wp_register_script($handle, $path, $dependencies,$version,true); | |
| 75 | + wp_register_script( $handle, $path, $dependencies, $version, true ); | |
| 40 | 76 | |
| 77 | + // Will not use wp_set_script_translations yet, it seems to be not fully compatible with Loco Translate etc. Instead use | |
| 78 | + // old-fashioned localize-script IOK 2020-08-11 | |
| 79 | + // wp_set_script_translations( 'wc-payment-method-vipps', 'woo-vipps' ); | |
| 80 | + // VippsLocale is localized for vipps-gw instead of this, as it seems it does no longer work to do localize-script at this point. | |
| 81 | + // IOK 2022-12-13 | |
| 41 | 82 | |
| 42 | - // Will not use wp_set_script_translations yet, it seems to be not fully compatible with Loco Translate etc. Instead use | |
| 43 | - // old-fashioned localize-script IOK 2020-08-11 | |
| 44 | - // wp_set_script_translations( 'wc-payment-method-vipps', 'woo-vipps' ); | |
| 45 | - // VippsLocale is localized for vipps-gw instead of this, as it seems it does no longer work to do localize-script at this point. | |
| 46 | - // IOK 2022-12-13 | |
| 47 | - | |
| 48 | 83 | return [ 'wc-payment-method-vipps' ]; |
| 49 | 84 | } |
| 50 | 85 | |
| 86 | + /** | |
| 87 | + * Reuse the gateway shortcode so express checkout retains its existing markup and data attributes. | |
| 88 | + */ | |
| 51 | 89 | public function get_express_checkout_button () { |
| 52 | - $button = \Vipps::instance()->express_checkout_button_shortcode(); | |
| 90 | + $context = 'checkout_gutenberg'; | |
| 91 | + if (is_cart()) { | |
| 92 | + $context='cart_gutenberg'; | |
| 93 | + } | |
| 94 | + | |
| 95 | + ob_start(); | |
| 96 | + \Vipps::instance()->cart_express_checkout_button_html($context); | |
| 97 | + $button = ob_get_clean(); | |
| 53 | 98 | return $button; |
| 54 | 99 | } |
| 55 | - public function show_express_checkout_button () { | |
| 56 | - $gw = \Vipps::instance()->gateway(); | |
| 57 | - if (!$gw->cart_supports_express_checkout()) return false; | |
| 58 | - return $gw->show_express_checkout(); | |
| 59 | - } | |
| 60 | 100 | |
| 101 | + /** | |
| 102 | + * Check cart support before exposing the merchant's express-checkout setting. | |
| 103 | + */ | |
| 104 | + public function show_express_checkout_button() { | |
| 105 | + $gw = \Vipps::instance()->gateway(); | |
| 106 | + if ( ! $gw->cart_supports_express_checkout() ) return false; | |
| 107 | + return $gw->show_express_checkout(); | |
| 108 | + } | |
| 109 | + | |
| 110 | + /** | |
| 111 | + * Process an existing Store API order through the gateway and expose its URL as | |
| 112 | + * widget payment details. The pre-process filter may supply a result instead of | |
| 113 | + * calling the gateway; subsequent filters may adapt the result, URL and reference. | |
| 114 | + * Exceptions are surfaced by WooCommerce as checkout errors. A successful result | |
| 115 | + * means payment initiation succeeded, not that payment has been captured. | |
| 116 | + * The browser suppresses the normal redirect only after accepting the widget handoff. | |
| 117 | + */ | |
| 118 | + public function process_gutenberg_checkout_payment( $context, $result ) { | |
| 119 | + | |
| 120 | + if ( empty( $context->payment_method ) || 'vipps' !== $context->payment_method ) { | |
| 121 | + return; | |
| 122 | + } | |
| 123 | + | |
| 124 | + $order = isset( $context->order ) ? $context->order : null; | |
| 125 | + | |
| 126 | + if ( ! $order || ! is_callable( array( $order, 'get_id' ) ) ) { | |
| 127 | + throw new \Exception( $this->get_payment_method_error_message( __( '%s could not process the order.', 'woo-vipps' ) ) ); | |
| 128 | + } | |
| 129 | + | |
| 130 | + $gateway = $this->get_gutenberg_checkout_gateway(); | |
| 131 | + | |
| 132 | + if ( ! $gateway ) { | |
| 133 | + throw new \Exception( $this->get_payment_method_error_message( __( '%s is not available.', 'woo-vipps' ) ) ); | |
| 134 | + } | |
| 135 | + | |
| 136 | + $payment_result = apply_filters( | |
| 137 | + 'woo_vipps_gutenberg_checkout_process_payment_result', | |
| 138 | + null, | |
| 139 | + $context, | |
| 140 | + $result, | |
| 141 | + $gateway | |
| 142 | + ); | |
| 143 | + | |
| 144 | + if ( null === $payment_result ) { | |
| 145 | + if ( ! is_callable( array( $gateway, 'process_payment' ) ) ) { | |
| 146 | + throw new \Exception( $this->get_payment_method_error_message( __( '%s could not start the payment.', 'woo-vipps' ) ) ); | |
| 147 | + } | |
| 148 | + | |
| 149 | + $payment_result = $gateway->process_payment( $order->get_id() ); | |
| 150 | + } | |
| 151 | + | |
| 152 | + $payment_result = apply_filters( | |
| 153 | + 'woo_vipps_gutenberg_checkout_processed_payment_result', | |
| 154 | + $payment_result, | |
| 155 | + $context, | |
| 156 | + $result, | |
| 157 | + $gateway | |
| 158 | + ); | |
| 159 | + | |
| 160 | + if ( ! is_array( $payment_result ) ) { | |
| 161 | + throw new \Exception( $this->get_payment_method_error_message( __( '%s returned an invalid payment response.', 'woo-vipps' ) ) ); | |
| 162 | + } | |
| 163 | + | |
| 164 | + if ( isset( $payment_result['result'] ) && 'success' !== $payment_result['result'] ) { | |
| 165 | + $message = ! empty( $payment_result['message'] ) | |
| 166 | + ? $payment_result['message'] | |
| 167 | + : $this->get_payment_method_error_message( __( '%s could not start the payment.', 'woo-vipps' ) ); | |
| 168 | + throw new \Exception( wp_strip_all_tags( $message ) ); | |
| 169 | + } | |
| 170 | + | |
| 171 | + $payment_url = apply_filters( | |
| 172 | + 'woo_vipps_gutenberg_checkout_payment_url', | |
| 173 | + isset( $payment_result['redirect'] ) ? $payment_result['redirect'] : '', | |
| 174 | + $payment_result, | |
| 175 | + $context, | |
| 176 | + $result, | |
| 177 | + $gateway | |
| 178 | + ); | |
| 179 | + | |
| 180 | + if ( ! $payment_url ) { | |
| 181 | + throw new \Exception( $this->get_payment_method_error_message( __( '%s did not return a payment URL.', 'woo-vipps' ) ) ); | |
| 182 | + } | |
| 183 | + | |
| 184 | + $payment_reference = apply_filters( | |
| 185 | + 'woo_vipps_gutenberg_checkout_payment_reference', | |
| 186 | + $this->get_gutenberg_checkout_payment_reference( $payment_result, $order ), | |
| 187 | + $payment_result, | |
| 188 | + $context, | |
| 189 | + $result, | |
| 190 | + $gateway | |
| 191 | + ); | |
| 192 | + | |
| 193 | + $payment_details = array( | |
| 194 | + 'vippsPaymentUrl' => esc_url_raw( $payment_url ), | |
| 195 | + 'vippsPaymentReference' => $payment_reference ? sanitize_text_field( $payment_reference ) : '', | |
| 196 | + ); | |
| 197 | + | |
| 198 | + if ( isset( $result->payment_details ) && is_array( $result->payment_details ) ) { | |
| 199 | + $payment_details = array_merge( $result->payment_details, $payment_details ); | |
| 200 | + } | |
| 201 | + | |
| 202 | + $payment_details = apply_filters( | |
| 203 | + 'woo_vipps_gutenberg_checkout_payment_details', | |
| 204 | + $payment_details, | |
| 205 | + $payment_result, | |
| 206 | + $context, | |
| 207 | + $result, | |
| 208 | + $gateway | |
| 209 | + ); | |
| 210 | + | |
| 211 | + $result->set_status( 'success' ); | |
| 212 | + $result->set_payment_details( $payment_details ); | |
| 213 | + } | |
| 214 | + | |
| 215 | + /** | |
| 216 | + * Resolve the gateway singleton, allowing the main plugin accessor as a compatibility fallback. | |
| 217 | + */ | |
| 218 | + protected function get_gutenberg_checkout_gateway() { | |
| 219 | + if ( class_exists( '\WC_Gateway_Vipps' ) && is_callable( array( '\WC_Gateway_Vipps', 'instance' ) ) ) { | |
| 220 | + return \WC_Gateway_Vipps::instance(); | |
| 221 | + } | |
| 222 | + | |
| 223 | + if ( class_exists( '\Vipps' ) && is_callable( array( '\Vipps', 'instance' ) ) ) { | |
| 224 | + $vipps = \Vipps::instance(); | |
| 225 | + | |
| 226 | + if ( is_callable( array( $vipps, 'gateway' ) ) ) { | |
| 227 | + return $vipps->gateway(); | |
| 228 | + } | |
| 229 | + } | |
| 230 | + | |
| 231 | + return null; | |
| 232 | + } | |
| 233 | + | |
| 234 | + /** | |
| 235 | + * Prefer the returned payment reference, then known order metadata; a missing reference is optional. | |
| 236 | + */ | |
| 237 | + protected function get_gutenberg_checkout_payment_reference( $payment_result, $order ) { | |
| 238 | + foreach ( array( 'paymentReference', 'reference', 'vippsPaymentReference' ) as $key ) { | |
| 239 | + if ( ! empty( $payment_result[ $key ] ) ) { | |
| 240 | + return $payment_result[ $key ]; | |
| 241 | + } | |
| 242 | + } | |
| 243 | + | |
| 244 | + foreach ( array( '_vipps_payment_reference', '_vipps_reference', 'vipps_payment_reference' ) as $key ) { | |
| 245 | + $reference = $order->get_meta( $key, true ); | |
| 246 | + | |
| 247 | + if ( $reference ) { | |
| 248 | + return $reference; | |
| 249 | + } | |
| 250 | + } | |
| 251 | + | |
| 252 | + return ''; | |
| 253 | + } | |
| 254 | + | |
| 255 | + /** | |
| 256 | + * Format customer-facing errors using the configured payment method name. | |
| 257 | + */ | |
| 258 | + protected function get_payment_method_error_message( $message ) { | |
| 259 | + return sprintf( $message, $this->payment_method_name ); | |
| 260 | + } | |
| 261 | + | |
| 262 | + /** | |
| 263 | + * Expose localized labels, branding and express eligibility as vipps_data for the JS component. | |
| 264 | + */ | |
| 61 | 265 | public function get_payment_method_data() { |
| 266 | + $logo = ( $this->payment_method_name == 'Vipps' ) ? plugins_url( '../../img/vipps-mark.svg', __FILE__ ) : plugins_url( '../../img/mobilepay-mark.png', __FILE__ ); | |
| 267 | + $brand = ( $this->payment_method_name == 'Vipps' ) ? 'vipps' : 'mobilepay'; | |
| 268 | + $language = substr( determine_locale(), 0, 2 ); | |
| 269 | + if ( ! in_array( $language, array( 'no', 'en', 'da', 'fi', 'sv' ), true ) ) { | |
| 270 | + $language = 'en'; | |
| 271 | + } | |
| 62 | 272 | |
| 63 | - $logo = ($this->payment_method_name == "Vipps") ? plugins_url('../../img/vipps-mark.svg', __FILE__) : plugins_url('../../img/mobilepay-mark.png', __FILE__); | |
| 273 | + $data = [ | |
| 274 | + 'title' => $this->payment_method_name, | |
| 275 | + 'description' => $this->get_setting( 'description' ), | |
| 276 | + 'iconsrc' => apply_filters( 'woo_vipps_block_logo_url', $logo ), | |
| 277 | + 'brand' => apply_filters( 'woo_vipps_block_widget_brand', $brand ), | |
| 278 | + 'language' => apply_filters( 'woo_vipps_block_widget_language', $language ), | |
| 279 | + 'show_express_checkout' => $this->show_express_checkout_button(), | |
| 280 | + 'expressbutton' => $this->get_express_checkout_button(), | |
| 281 | + ]; | |
| 64 | 282 | |
| 65 | - return [ | |
| 66 | - 'title' => $this->payment_method_name, | |
| 67 | - 'description' => $this->get_setting( 'description' ), | |
| 68 | - 'iconsrc' => apply_filters('woo_vipps_block_logo_url', $logo), | |
| 69 | - 'show_express_checkout' => $this->show_express_checkout_button(), | |
| 70 | - 'expressbutton' => $this->get_express_checkout_button() | |
| 71 | - ]; | |
| 283 | + return $data; | |
| 72 | 284 | } |
| 73 | 285 | } |