| @@ -25,9 +25,9 @@ | ||
| 25 | 25 | class PayplugGateway extends WC_Payment_Gateway_CC |
| 26 | 26 | { |
| 27 | 27 | use ServiceGetter; |
| 28 | 28 | |
| 29 | - const OPTION_NAME = 'payplug_config'; | |
| 29 | + public const OPTION_NAME = 'payplug_config'; | |
| 30 | 30 | |
| 31 | 31 | /** |
| 32 | 32 | * @var string |
| 33 | 33 | */ |
| @@ -91,14 +91,14 @@ | ||
| 91 | 91 | |
| 92 | 92 | /** |
| 93 | 93 | * @var float |
| 94 | 94 | */ |
| 95 | - const MIN_AMOUNT = 0.99; | |
| 95 | + public const MIN_AMOUNT = 0.99; | |
| 96 | 96 | |
| 97 | 97 | /** |
| 98 | 98 | * @var float |
| 99 | 99 | */ |
| 100 | - const MAX_AMOUNT = 20000; | |
| 100 | + public const MAX_AMOUNT = 20000; | |
| 101 | 101 | |
| 102 | 102 | /** |
| 103 | 103 | * @var string |
| 104 | 104 | */ |
| @@ -109,9 +109,9 @@ | ||
| 109 | 109 | public $oney_thresholds_min; |
| 110 | 110 | public $max_oney_price; |
| 111 | 111 | public $oney_thresholds_max; |
| 112 | 112 | |
| 113 | - const ENABLE_ON_TEST_MODE = true; | |
| 113 | + public const ENABLE_ON_TEST_MODE = true; | |
| 114 | 114 | |
| 115 | 115 | /** |
| 116 | 116 | * Logging method. |
| 117 | 117 | * |
| @@ -118,9 +118,9 @@ | ||
| 118 | 118 | * @param string $message Log message. |
| 119 | 119 | * @param string $level Optional. Default 'info'. |
| 120 | 120 | * emergency|alert|critical|error|warning|notice|info|debug |
| 121 | 121 | */ |
| 122 | - public static function log($message, $level = 'info') | |
| 122 | + public static function log($message, $level = 'info'): void | |
| 123 | 123 | { |
| 124 | 124 | if (!self::$log_enabled) { |
| 125 | 125 | return; |
| 126 | 126 | } |
| @@ -239,9 +239,9 @@ | ||
| 239 | 239 | * Validate order payment when the user is redirected to the success confirmation page. |
| 240 | 240 | * |
| 241 | 241 | * @throws \WC_Data_Exception |
| 242 | 242 | */ |
| 243 | - public function validate_payment($id = null, $save_request = true, $ipn = false) | |
| 243 | + public function validate_payment($id = null, $save_request = true, $ipn = false): void | |
| 244 | 244 | { |
| 245 | 245 | global $wp; |
| 246 | 246 | |
| 247 | 247 | if (!$ipn) { |
| @@ -328,9 +328,17 @@ | ||
| 328 | 328 | */ |
| 329 | 329 | public function is_available() |
| 330 | 330 | { |
| 331 | 331 | if ('yes' == $this->enabled) { |
| 332 | - return $this->requirements->satisfy_requirements() && !empty($this->get_api_key($this->get_current_mode())); | |
| 332 | + $available = $this->requirements->satisfy_requirements() && !empty($this->get_api_key($this->get_current_mode())); | |
| 333 | + | |
| 334 | + // $this->enabled only reflects the country/context check as it was at gateway construction time, | |
| 335 | + // so re-run it here to catch context that only becomes known later in the request (e.g. order-pay). | |
| 336 | + if ($available && method_exists($this, 'checkGateway')) { | |
| 337 | + $available = $this->checkGateway(); | |
| 338 | + } | |
| 339 | + | |
| 340 | + return $available; | |
| 333 | 341 | } |
| 334 | 342 | |
| 335 | 343 | return parent::is_available(); |
| 336 | 344 | } |
| @@ -337,9 +345,9 @@ | ||
| 337 | 345 | |
| 338 | 346 | /** |
| 339 | 347 | * Load gateway settings. |
| 340 | 348 | */ |
| 341 | - public function init_settings() | |
| 349 | + public function init_settings(): void | |
| 342 | 350 | { |
| 343 | 351 | parent::init_settings(); |
| 344 | 352 | $enabled = !empty($this->settings['enabled']) && (bool) $this->settings['enabled']; |
| 345 | 353 | $this->enabled = $enabled ? 'yes' : 'no'; |
| @@ -347,9 +355,9 @@ | ||
| 347 | 355 | |
| 348 | 356 | /** |
| 349 | 357 | * Register gateway settings. |
| 350 | 358 | */ |
| 351 | - public function init_form_fields() | |
| 359 | + public function init_form_fields(): void | |
| 352 | 360 | { |
| 353 | 361 | $anchor = esc_html_x(__('More informations', 'payplug'), 'modal', 'payplug'); |
| 354 | 362 | $domain = __('support.payplug.com/hc/fr/articles/4408142346002', 'payplug'); |
| 355 | 363 | $link = sprintf(' <a href="https://%s" target="_blank">%s</a>', $domain, $anchor); |
| @@ -544,14 +552,16 @@ | ||
| 544 | 552 | |
| 545 | 553 | /** |
| 546 | 554 | * Set global configuration for PayPlug instance. |
| 547 | 555 | */ |
| 548 | - public function init_payplug() | |
| 556 | + public function init_payplug(): void | |
| 549 | 557 | { |
| 550 | 558 | $this->payplug_api = new PayplugApi($this); |
| 551 | 559 | $this->payplug_api->init(); |
| 552 | 560 | |
| 553 | - $this->permissions = new PayplugPermissions($this); | |
| 561 | + // init() just resolved this for the same mode - reuse it instead of asking | |
| 562 | + // Service\Api::get_bearer_token() to do so again right after. | |
| 563 | + $this->permissions = new PayplugPermissions($this, $this->payplug_api->get_current_bearer_token()); | |
| 554 | 564 | $this->response = new PayplugResponse($this); |
| 555 | 565 | |
| 556 | 566 | // Register IPN handler |
| 557 | 567 | new PayplugIpnResponse($this); |
| @@ -628,9 +638,9 @@ | ||
| 628 | 638 | |
| 629 | 639 | /** |
| 630 | 640 | * extra payment fields |
| 631 | 641 | */ |
| 632 | - public function payment_fields() | |
| 642 | + public function payment_fields(): void | |
| 633 | 643 | { |
| 634 | 644 | $description = $this->get_description(); |
| 635 | 645 | |
| 636 | 646 | if (!empty($description)) { |
| @@ -645,9 +655,9 @@ | ||
| 645 | 655 | |
| 646 | 656 | /** |
| 647 | 657 | * Handle admin display. |
| 648 | 658 | */ |
| 649 | - public function admin_options() | |
| 659 | + public function admin_options(): void | |
| 650 | 660 | { |
| 651 | 661 | /************ VUE Code *************/ |
| 652 | 662 | wp_enqueue_script('chunk-vendors.js', PAYPLUG_GATEWAY_PLUGIN_URL . 'assets/dist/js/chunk-vendors-' . PAYPLUG_GATEWAY_VERSION . '.js', [], PAYPLUG_GATEWAY_VERSION); |
| 653 | 663 | wp_enqueue_script('app.js', PAYPLUG_GATEWAY_PLUGIN_URL . 'assets/dist/js/app-' . PAYPLUG_GATEWAY_VERSION . '.js', [], PAYPLUG_GATEWAY_VERSION); |
| @@ -710,8 +720,32 @@ | ||
| 710 | 720 | return $this->process_standard_payment($order, $amount, $customer_id); |
| 711 | 721 | } |
| 712 | 722 | |
| 713 | 723 | /** |
| 724 | + * Whether the current request is repaying an existing order (order-pay page, or one of | |
| 725 | + * the order-pay AJAX flows). The order_key/order_pay_key posted by those AJAX flows must | |
| 726 | + * match the order's own key: their mere presence isn't proof of anything, since any | |
| 727 | + * request can set them. | |
| 728 | + * | |
| 729 | + * @param \WC_Order|null $order | |
| 730 | + * | |
| 731 | + * @return bool | |
| 732 | + */ | |
| 733 | + protected function is_order_pay_request($order): bool | |
| 734 | + { | |
| 735 | + if (is_wc_endpoint_url('order-pay')) { | |
| 736 | + return true; | |
| 737 | + } | |
| 738 | + | |
| 739 | + $posted_key = $_POST['order_pay_key'] ?? $_POST['order_key'] ?? ''; | |
| 740 | + if (empty($posted_key)) { | |
| 741 | + return false; | |
| 742 | + } | |
| 743 | + | |
| 744 | + return $order instanceof \WC_Order && hash_equals($order->get_order_key(), wc_clean(wp_unslash($posted_key))); | |
| 745 | + } | |
| 746 | + | |
| 747 | + /** | |
| 714 | 748 | * if payment was generated by an intend, we shouldn't generate another one and try to pay it, this would generate duplications |
| 715 | 749 | * |
| 716 | 750 | * @param $order |
| 717 | 751 | * |
| @@ -720,16 +754,22 @@ | ||
| 720 | 754 | * @return array|null |
| 721 | 755 | */ |
| 722 | 756 | private function process_standard_intent_payment($order) |
| 723 | 757 | { |
| 758 | + // This can run from AJAX endpoints whose own request URL never carries the order-pay | |
| 759 | + // query var, so is_wc_endpoint_url() alone can't detect that context here: fall back | |
| 760 | + // to the order_key/order_pay_key sent by the order-pay AJAX flows. | |
| 761 | + $is_order_pay = $this->is_order_pay_request($order); | |
| 762 | + | |
| 724 | 763 | //no order-pay page, no ajax_on_order_review_page |
| 725 | - if (!is_wc_endpoint_url('order-pay') && | |
| 764 | + if (!$is_order_pay && | |
| 726 | 765 | PayplugWoocommerceHelper::is_checkout_block() && |
| 727 | 766 | ( |
| 728 | 767 | ('payplug' == $this->id && in_array($this->embedded_mode, ['integrated', 'popup'])) || |
| 729 | 768 | ('american_express' == $this->id && 'popup' == $this->embedded_mode) |
| 730 | 769 | ) && |
| 731 | - $_GET['wc-ajax'] !== 'payplug_order_review_url' | |
| 770 | + ($_GET['wc-ajax'] ?? '') !== 'payplug_order_review_url' && | |
| 771 | + !empty($order->get_transaction_id()) | |
| 732 | 772 | ) { |
| 733 | 773 | $order_id = PayplugWoocommerceHelper::is_pre_30() ? $order->id : $order->get_id(); |
| 734 | 774 | |
| 735 | 775 | try { |
| @@ -766,16 +806,24 @@ | ||
| 766 | 806 | self::log(sprintf('Payment intent created for order #%s', $order_id)); |
| 767 | 807 | |
| 768 | 808 | $return_url = esc_url_raw($order->get_checkout_order_received_url()); |
| 769 | 809 | |
| 770 | - wp_send_json_success([ | |
| 810 | + $result = [ | |
| 771 | 811 | 'payment_id' => $payment->id, |
| 772 | 812 | 'result' => 'success', |
| 773 | 813 | 'redirect' => !empty($payment->hosted_payment->payment_url) ? $payment->hosted_payment->payment_url : $return_url, |
| 774 | 814 | 'cancel' => !empty($payment->hosted_payment->cancel_url) ? $payment->hosted_payment->cancel_url : null, |
| 775 | - ]); | |
| 815 | + ]; | |
| 776 | 816 | |
| 777 | - return ['stt' => 'OK']; | |
| 817 | + // wp_send_json_success() calls die(), which is only safe for the classic | |
| 818 | + // wc-ajax request this was written for: the Store API checkout flow (used by | |
| 819 | + // the checkout block) calls process_payment() through the REST framework, | |
| 820 | + // and killing the process mid-request there produces a broken response. | |
| 821 | + if (wp_doing_ajax()) { | |
| 822 | + wp_send_json_success($result); | |
| 823 | + } | |
| 824 | + | |
| 825 | + return $result; | |
| 778 | 826 | } catch (HttpException $e) { |
| 779 | 827 | self::log(sprintf('Error while processing order #%s : %s', $order_id, wc_print_r($e->getErrorObject(), true)), 'error'); |
| 780 | 828 | throw new \Exception(__('Payment processing failed. Please retry.', 'payplug')); |
| 781 | 829 | } catch (\Exception $e) { |