PluginProbe
PayPlug for WooCommerce (Official) / 3.0.0
PayPlug for WooCommerce (Official) v3.0.0
3.0.0 2.18.0 1.0.17 1.0.18 1.0.19 1.0.20 1.0.21 1.0.22 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.1.0 1.10.0 1.10.1 1.2.1 1.2.10 1.2.11 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 All 101 releases
payplug / src / Gateway / PayplugGateway.php

PayplugGateway.php in PayPlug for WooCommerce (Official) 3.0.0, at src/Gateway/PayplugGateway.php

1,373 lines 50.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Payplug\PayplugWoocommerce\Gateway;
4
5 // Exit if accessed directly
6 if (!defined('ABSPATH')) {
7 exit;
8 }
9
10 use Payplug\Authentication;
11 use Payplug\Exception\ConfigurationException;
12 use Payplug\Exception\HttpException;
13 use Payplug\Payplug;
14 use Payplug\PayplugWoocommerce\Helper\Lock;
15 use Payplug\PayplugWoocommerce\PayplugWoocommerceHelper;
16 use Payplug\PayplugWoocommerce\Traits\ServiceGetter;
17 use Payplug\Resource\Payment as PaymentResource;
18 use Payplug\Resource\Refund as RefundResource;
19 use WC_Payment_Gateway_CC;
20 use WC_Payment_Tokens;
21
22 /**
23 * PayPlug WooCommerce Gateway.
24 */
25 class PayplugGateway extends WC_Payment_Gateway_CC
26 {
27 use ServiceGetter;
28
29 public const OPTION_NAME = 'payplug_config';
30
31 /**
32 * @var string
33 */
34 public $mode;
35 /**
36 * @var bool
37 */
38 public $debug;
39 /**
40 * @var string
41 */
42 public $email;
43 /**
44 * @var string
45 */
46 public $embedded_mode;
47 /**
48 * @var bool
49 */
50 public $save_card;
51
52 /**
53 * @var string
54 */
55 public $oney_type;
56
57 /**
58 * @var string
59 */
60 public $oney_product_animation;
61
62 /**
63 * @var PayplugGatewayRequirements
64 */
65 private $requirements;
66
67 /**
68 * @var PayplugPermissions
69 */
70 private $permissions;
71
72 /**
73 * @var PayplugResponse
74 */
75 public $response;
76
77 /**
78 * @var PayplugApi
79 */
80 public $payplug_api;
81
82 /**
83 * @var \WC_Logger
84 */
85 protected static $log;
86
87 /**
88 * @var bool
89 */
90 protected static $log_enabled;
91
92 /**
93 * @var float
94 */
95 public const MIN_AMOUNT = 0.99;
96
97 /**
98 * @var float
99 */
100 public const MAX_AMOUNT = 20000;
101
102 /**
103 * @var string
104 */
105 private $payplug_merchant_country = 'FR';
106
107 protected $oney_response;
108 public $min_oney_price;
109 public $oney_thresholds_min;
110 public $max_oney_price;
111 public $oney_thresholds_max;
112
113 public const ENABLE_ON_TEST_MODE = true;
114
115 /**
116 * Logging method.
117 *
118 * @param string $message Log message.
119 * @param string $level Optional. Default 'info'.
120 * emergency|alert|critical|error|warning|notice|info|debug
121 */
122 public static function log($message, $level = 'info'): void
123 {
124 if (!self::$log_enabled) {
125 return;
126 }
127
128 if (empty(self::$log)) {
129 self::$log = PayplugWoocommerceHelper::is_pre_30() ? new \WC_Logger() : wc_get_logger();
130 }
131
132 PayplugWoocommerceHelper::is_pre_30()
133 ? self::$log->add('payplug_gateway', $message)
134 : self::$log->log($level, $message, ['source' => 'payplug_gateway']);
135 }
136
137 /**
138 * Construct method
139 *
140 * @return void
141 */
142 public function __construct()
143 {
144 //required plugin id
145 $this->id = 'payplug';
146 $this->supports = [
147 'products',
148 'refunds',
149 'tokenization',
150 ];
151
152 $payplug_gateways = ['payplug', 'american_express', 'apple_pay', 'bancontact', 'oney_x3_with_fees', 'oney_x3_without_fees', 'oney_x4_with_fees', 'oney_x4_without_fees', 'satispay', 'ideal', 'mybank', 'wero', 'bizum', 'scalapay'];
153
154 //save buttom admin
155 if ((!empty($_GET['section'])) && (in_array($_GET['section'], $payplug_gateways))) {
156 $GLOBALS['hide_save_button'] = true;
157 }
158
159 $this->init_settings();
160 $this->requirements = new PayplugGatewayRequirements($this);
161 if ($this->user_logged_in()) {
162 $this->init_payplug();
163 }
164
165 $this->mode = (bool) $this->get_configuration()->get_option('mode') ? 'live' : 'test';
166 $this->debug = (bool) $this->get_configuration()->get_option('debug');
167 $this->email = (string) $this->get_configuration()->get_option('email');
168
169 //admin form
170 $this->init_form_fields();
171
172 add_filter('woocommerce_get_customer_payment_tokens', [$this, 'filter_tokens'], 10, 3);
173
174 self::$log_enabled = $this->debug;
175
176 add_filter('woocommerce_get_order_item_totals', [$this, 'customize_gateway_title'], 10, 2);
177 add_action('woocommerce_thankyou', [$this, 'validate_payment']);
178 add_action('the_post', [$this, 'validate_payment']);
179 add_action('woocommerce_available_payment_gateways', [$this, 'check_gateway']);
180 }
181
182 /**
183 * @param $option
184 * @param $value
185 *
186 * @return bool|void
187 */
188 public function update_option($option, $value = '')
189 {
190 if ($this->needs_setup()) {
191 wp_send_json_error('needs_setup');
192 wp_die();
193 }
194
195 parent::update_option($option, $value);
196 }
197
198 /**
199 * this payment gateway cannot be updated on the wooco payment settings
200 *
201 * @return bool
202 */
203 public function needs_setup()
204 {
205 return true;
206 }
207
208 /**
209 * Customize gateway title in emails.
210 *
211 * @param array $total_rows
212 * @param \WC_Order $order
213 *
214 * @return array
215 *
216 * @author Clément Boirie
217 */
218 public function customize_gateway_title($total_rows, $order)
219 {
220 $get_payment_method = $this->id;
221 if (method_exists($order, 'get_payment_method')) {
222 $get_payment_method = $order->get_payment_method();
223 }
224
225 $payment_method = PayplugWoocommerceHelper::is_pre_30() ? $order->payment_method : $get_payment_method;
226 if (
227 $this->id !== $payment_method
228 || !isset($total_rows['payment_method'])
229 ) {
230 return $total_rows;
231 }
232
233 $total_rows['payment_method']['value'] = __('Credit card', 'payplug');
234
235 return $total_rows;
236 }
237
238 /**
239 * Validate order payment when the user is redirected to the success confirmation page.
240 *
241 * @throws \WC_Data_Exception
242 */
243 public function validate_payment($id = null, $save_request = true, $ipn = false): void
244 {
245 global $wp;
246
247 if (!$ipn) {
248 if (!is_wc_endpoint_url('order-received') || (empty($_GET['key']) && empty($id))) {
249 return;
250 }
251 }
252
253 if (!empty($_GET['order-received'])) {
254 $order_id = (int) ($_GET['order-received']);
255 } elseif (!empty($id) && !is_object($id)) {
256 $order_id = (int) $id;
257 }
258
259 if (empty($order_id) && (isset($wp->query_vars['order-received']))) {
260 $order_id = apply_filters(
261 'woocommerce_thankyou_order_id',
262 absint($wp->query_vars['order-received'])
263 );
264 }
265
266 if (empty($order_id)) {
267 if (empty($_GET['key']) && empty($id) && !is_object($id)) {
268 return;
269 }
270 $order_id = wc_get_order_id_by_order_key(wc_clean((!empty($_GET['key']) ? $_GET['key'] : (int) $id)));
271 }
272
273 if (empty($order_id)) {
274 return;
275 }
276
277 $order = wc_get_order($order_id);
278 if (!$order instanceof \WC_Order) {
279 return;
280 }
281
282 $payment_method = PayplugWoocommerceHelper::is_pre_30() ? $order->payment_method : $order->get_payment_method();
283 if (!in_array($payment_method, ['payplug', 'oney_x3_with_fees', 'oney_x4_with_fees', 'oney_x3_without_fees', 'oney_x4_without_fees', 'bancontact', 'apple_pay', 'american_express', 'satispay', 'mybank', 'ideal', 'wero', 'bizum', 'scalapay'])) {
284 return;
285 }
286
287 if ($payment_method === $this->id) {
288 $transaction_id = PayplugWoocommerceHelper::is_pre_30() ? get_post_meta($order_id, '_transaction_id', true) : $order->get_transaction_id();
289 if (empty($transaction_id)) {
290 self::log(sprintf('Order #%s : Missing transaction id.', $order_id), 'error');
291
292 return;
293 }
294
295 $lock_id = Lock::handle_insert($save_request, $transaction_id);
296 if (!$lock_id) {
297 return;
298 }
299
300 try {
301 $payment = $this->payplug_api->payment_retrieve($transaction_id);
302 } catch (\Exception $e) {
303 self::log(
304 sprintf(
305 'Order #%s : An error occurred while retrieving the payment data with the message : %s',
306 $order_id,
307 $e->getMessage()
308 )
309 );
310
311 return;
312 }
313
314 $this->response->process_payment($payment);
315
316 \Payplug\PayplugWoocommerce\Model\Lock::delete_lock($lock_id);
317 $waiting_requests = \Payplug\PayplugWoocommerce\Model\Lock::get_lock_by_payment_id($transaction_id);
318
319 if ($waiting_requests) {
320 \Payplug\PayplugWoocommerce\Model\Lock::delete_lock_by_payment_id($transaction_id);
321 $this->validate_payment($order_id, false);
322 }
323 }
324 }
325
326 /**
327 * Check if this gateway is enabled
328 */
329 public function is_available()
330 {
331 if ('yes' == $this->enabled) {
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;
341 }
342
343 return parent::is_available();
344 }
345
346 /**
347 * Load gateway settings.
348 */
349 public function init_settings(): void
350 {
351 parent::init_settings();
352 $enabled = !empty($this->settings['enabled']) && (bool) $this->settings['enabled'];
353 $this->enabled = $enabled ? 'yes' : 'no';
354 }
355
356 /**
357 * Register gateway settings.
358 */
359 public function init_form_fields(): void
360 {
361 $anchor = esc_html_x(__('More informations', 'payplug'), 'modal', 'payplug');
362 $domain = __('support.payplug.com/hc/fr/articles/4408142346002', 'payplug');
363 $link = sprintf(' <a href="https://%s" target="_blank">%s</a>', $domain, $anchor);
364
365 $anchor_bancontact = esc_html_x(__('payplug_bancontact_activation_request', 'payplug'), 'modal', 'payplug');
366 $domain_bancontact = __('payplug_bancontact_activation_url', 'payplug');
367 $bancontact_call_to_action = sprintf(' <a id="bancontact_call_to_action" href="https://%s" target="_blank">%s</a>', $domain_bancontact, $anchor_bancontact);
368
369 $oney_cfg = $this->get_configuration()->get_option('payment_methods.configuration.oney');
370 if (!empty($oney_cfg)) {
371 $oney_amount = json_decode($oney_cfg['custom_amounts'], true);
372 $oney_amount['min'] = (float) $oney_amount['min'] / 100;
373 $oney_amount['max'] = (float) $oney_amount['max'] / 100;
374 } else {
375 $oney_amount = [
376 'min' => 100,
377 'max' => 3000,
378 ];
379 }
380 $fields = [
381 'enabled' => [
382 'title' => __('Enable/Disable', 'payplug'),
383 'type' => 'checkbox',
384 'label' => __('Enable PayPlug', 'payplug'),
385 'description' => __('Only Euro payments can be processed with PayPlug.', 'payplug'),
386 'default' => false,
387 ],
388 'title' => [
389 'title' => __('Title', 'payplug'),
390 'type' => 'text',
391 'description' => __('The payment solution title displayed to your customers during checkout', 'payplug'),
392 'default' => _x('Credit card checkout', 'Default gateway title', 'payplug'),
393 'desc_tip' => false,
394 ],
395 'description' => [
396 'title' => __('Description', 'payplug'),
397 'type' => 'text',
398 'description' => __('The payment solution description displayed to your customers during checkout', 'payplug'),
399 'default' => '',
400 'desc_tip' => false,
401 ],
402 'title_connexion' => [
403 'title' => __('Connection', 'payplug'),
404 'type' => 'title',
405 ],
406 'email' => [
407 'type' => 'hidden',
408 'default' => '',
409 ],
410 'login' => [
411 'type' => 'login',
412 'default' => '',
413 ],
414 'title_testmode' => [
415 'title' => __('Mode', 'payplug'),
416 'type' => 'title',
417 ],
418 'mode' => [
419 'title' => '',
420 'label' => '',
421 'type' => 'yes_no',
422 'yes' => 'Live',
423 'no' => 'Test',
424 'description' => __('In TEST mode, all payments will be simulations and will not generate real transactions.', 'payplug'),
425 'default' => false,
426 'hide_label' => true,
427 ],
428 'title_settings' => [
429 'title' => __('Settings', 'payplug'),
430 'type' => 'title',
431 ],
432 'payment_method' => [
433 'title' => __('Payment page', 'payplug'),
434 'type' => 'radio',
435 'options' => [
436 'redirect' => __('Redirect', 'payplug'),
437 'embedded' => __('Integrated', 'payplug'),
438 ],
439 'description' => __('Customers will be redirected to a PayPlug payment page to finalize the transaction, or payments will be performed in an embeddable payment form on your website.', 'payplug'),
440 'default' => 'redirect',
441 'desc_tip' => false,
442 ],
443 'debug' => [
444 'title' => __('Debug', 'payplug'),
445 'type' => 'checkbox',
446 'description' => __('Debug mode saves additional information on your server for each operation done via the PayPlug plugin (Developer setting).', 'payplug'),
447 'label' => __('Activate debug mode', 'payplug'),
448 'default' => true,
449 'desc_tip' => false,
450 ],
451 'title_advanced_settings' => [
452 'title' => __('payplug_advanced_settings', 'payplug'),
453 'description' => __(
454 'Your current offer does not allow this option. Try it on TEST mode. More information <a href="https://www.payplug.com/pricing" target="_blank">here.</a>',
455 'payplug'
456 ),
457 'type' => 'title',
458 ],
459 'save_card' => [
460 'title' => __('One Click Payment', 'payplug'),
461 'type' => 'checkbox',
462 'label' => __('Activate', 'payplug'),
463 'description' => __('Allow your customers to save their credit card information for later purchases.', 'payplug'),
464 'default' => false,
465 'desc_tip' => false,
466 ],
467 'bancontact' => [
468 'title' => __('payplug_bancontact_activate_title', 'payplug'),
469 'type' => 'checkbox',
470 'label' => __('Activate', 'payplug'),
471 'description' => '<p class="description" id="bancontact_test_mode_description"> ' . __('payplug_bancontact_testmode_description', 'payplug') . ' </p>' .
472 '<p class="description" id="bancontact_live_mode_description_disabled"> ' . __('payplug_bancontact_livemode_description_disabled', 'payplug') . ' </p>' .
473 $bancontact_call_to_action,
474 'default' => false,
475 ],
476 'apple_pay' => [
477 'title' => __('payplug_apple_pay_activate_title', 'payplug'),
478 'type' => 'checkbox',
479 'label' => __('Activate', 'payplug'),
480 'description' => '<p class="description" id="apple_pay_test_mode_description"> ' . __('payplug_apple_pay_testmode_description', 'payplug') . ' </p>' .
481 '<p class="description" id="apple_pay_live_mode_description"> ' . __('payplug_apple_pay_livemode_description', 'payplug') . ' </p>',
482 'default' => false,
483 ],
484 'american_express' => [
485 'title' => __('payplug_amex_title', 'payplug'),
486 'type' => 'checkbox',
487 'label' => __('payplug_amex_activate', 'payplug'),
488 'description' => '<p class="description" id="amex_test_mode_description"> ' . __('payplug_amex_testmode_description', 'payplug') . ' </p>' .
489 '<p class="description" id="amex_live_mode_description"> ' . __('payplug_amex_livemode_description', 'payplug') . ' </p>',
490 'default' => false,
491 ],
492 'oney' => [
493 'title' => __('3x 4x Oney payments', 'payplug'),
494 'type' => 'checkbox',
495 'label' => __('Activate', 'payplug'),
496 // TRAD
497 'description' => sprintf(__('Allow your customers to split payments into 3 or 4 installments, for orders between %s€ and %s€', 'payplug'), $this->min_oney_price, $this->max_oney_price) . $link,
498 'default' => false,
499 'desc_tip' => false,
500 ],
501 'oney_type' => [
502 'title' => '',
503 'type' => 'oney_type',
504 'options' => [
505 'with_fees' => __('Oney with fees', 'payplug'),
506 'without_fees' => __('Oney without fees', 'payplug'),
507 ],
508 'descriptions' => [
509 'with_fees' => __('The fees are split between you and your customers', 'payplug'),
510 'without_fees' => __('You pay the fees', 'payplug'),
511 ],
512 'description' => '',
513 'default' => 'with_fees',
514 'desc_tip' => false,
515 ],
516 'oney_thresholds' => [
517 'title' => '',
518 'type' => 'oney_thresholds',
519 'description' => sprintf(
520 __('I would like to offer guaranteed payment in installments for amounts between %s€ and %s€.', 'payplug'),
521 '<b class="min">' . $oney_amount['min'] . '</b>',
522 '<b class="max">' . $oney_amount['max'] . '</b>'
523 ),
524 'desc_tip' => false,
525 ],
526 'oney_product_animation' => [
527 'title' => __('oney_installments_pop_up', 'payplug'),
528 'description' => __('display_the_oney_installments_pop_up_on_the_product_page', 'payplug'),
529 'label' => __('Activate', 'payplug'),
530 'default' => false,
531 'desc_tip' => false,
532 'type' => 'oney_product_animation',
533 ],
534 ];
535
536 if ($this->user_logged_in()) {
537 if ($this->permissions->has_permissions(PayplugPermissions::SAVE_CARD)) {
538 unset($fields['title_advanced_settings']);
539 } elseif ('live' === $this->get_current_mode()) {
540 $fields['save_card']['disabled'] = true;
541 }
542 }
543
544 /**
545 * Filter PayPlug gateway settings.
546 *
547 * @param array $fields
548 */
549 $fields = apply_filters('payplug_gateway_settings', $fields);
550 $this->form_fields = $fields;
551 }
552
553 /**
554 * Set global configuration for PayPlug instance.
555 */
556 public function init_payplug(): void
557 {
558 $this->payplug_api = new PayplugApi($this);
559 $this->payplug_api->init();
560
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());
564 $this->response = new PayplugResponse($this);
565
566 // Register IPN handler
567 new PayplugIpnResponse($this);
568 }
569
570 /**
571 * Filter saved tokens for the gateway.
572 *
573 * A token will be removed if :
574 * - it doesn't match the current merchant logged in,
575 * - or it doesn't match the current gateway mode,
576 * - or it is expired.
577 *
578 * @param array $tokens
579 * @param int $user_id
580 * @param string $gateway_id
581 *
582 * @return array
583 */
584 public function filter_tokens($tokens, $user_id, $gateway_id)
585 {
586 if (!is_user_logged_in() || !class_exists('WC_Payment_Gateway_CC')) {
587 return $tokens;
588 }
589
590 $saved_card = $this->get_configuration()->get_option('payment_methods.configuration.payplug.save_card')
591 && is_user_logged_in();
592
593 if (!$saved_card) {
594 foreach ($tokens as $token_id => $token) {
595 if ('payplug' == (string) $token->get_gateway_id()) {
596 unset($tokens[$token_id]);
597 }
598 }
599
600 return $tokens;
601 }
602
603 /* @var \WC_Payment_Token_CC $token */
604 foreach ($tokens as $k => $token) {
605 if ($this->id !== $token->get_gateway_id()) {
606 continue;
607 }
608
609 // check if token is associated with a merchant id and if it match the current one
610 $token_merchant_id = $token->get_meta('payplug_account', true);
611 if (empty($token_merchant_id) || $this->get_merchant_id() !== $token_merchant_id) {
612 unset($tokens[$k]);
613 continue;
614 }
615
616 // check if token is available for the current gateway mode
617 if ($this->mode != $token->get_meta('mode', true)) {
618 unset($tokens[$k]);
619 continue;
620 }
621
622 // check if token is not expired
623 $current_month = \absint(date('n'));
624 $current_year = \absint(date('Y'));
625 if ($current_year > (int) $token->get_expiry_year()) {
626 unset($tokens[$k]);
627 continue;
628 }
629
630 if ($current_year === (int) $token->get_expiry_year() && $current_month > (int) $token->get_expiry_month()) {
631 unset($tokens[$k]);
632 continue;
633 }
634 }
635
636 return $tokens;
637 }
638
639 /**
640 * extra payment fields
641 */
642 public function payment_fields(): void
643 {
644 $description = $this->get_description();
645
646 if (!empty($description)) {
647 echo wpautop(wptexturize($description));
648 }
649
650 if ($this->save_card_available()) {
651 $this->tokenization_script();
652 $this->saved_payment_methods();
653 }
654 }
655
656 /**
657 * Handle admin display.
658 */
659 public function admin_options(): void
660 {
661 /************ VUE Code *************/
662 wp_enqueue_script('chunk-vendors.js', PAYPLUG_GATEWAY_PLUGIN_URL . 'assets/dist/js/chunk-vendors-' . PAYPLUG_GATEWAY_VERSION . '.js', [], PAYPLUG_GATEWAY_VERSION);
663 wp_enqueue_script('app.js', PAYPLUG_GATEWAY_PLUGIN_URL . 'assets/dist/js/app-' . PAYPLUG_GATEWAY_VERSION . '.js', [], PAYPLUG_GATEWAY_VERSION);
664 wp_enqueue_style('app.css', PAYPLUG_GATEWAY_PLUGIN_URL . 'assets/dist/css/app-' . PAYPLUG_GATEWAY_VERSION . '.css', [], PAYPLUG_GATEWAY_VERSION);
665 wp_localize_script(
666 'app.js',
667 'payplug_admin_config',
668 [
669 'img_path' => esc_url(PAYPLUG_GATEWAY_PLUGIN_URL . 'assets/dist/'),
670 'ajax_url' => get_home_url(),
671 'rest_url' => get_home_url() . '/?rest_route=/payplug_api/',
672 ]
673 ); ?>
674 <script>window.get_data_url = "<?php echo rest_url('payplug/data'); ?>"</script>
675 <script>window.set_data_url = "<?php echo rest_url('payplug/save_data'); ?>"</script>
676 <div id="payplug_admin"></div>
677
678 <?php
679
680 /*********** End VUE Code ***********/
681 }
682
683 /**
684 * Process payment.
685 *
686 * @param int $order_id
687 *
688 * @throws \Exception
689 *
690 * @return array
691 */
692 public function process_payment($order_id)
693 {
694 self::log(sprintf('Processing payment for order #%s', $order_id));
695
696 $order = wc_get_order($order_id);
697 if (!$order instanceof \WC_Order) {
698 self::log(sprintf('Order #%s not found.', $order_id), 'error');
699 throw new \Exception(__('Order not found.', 'payplug'));
700 }
701 $customer_id = PayplugWoocommerceHelper::is_pre_30() ? $order->customer_user : $order->get_customer_id();
702 $amount = (int) PayplugWoocommerceHelper::get_payplug_amount($order->get_total());
703 $amount = $this->validate_order_amount($amount);
704
705 if (is_wp_error($amount)) {
706 self::log(sprintf('Invalid amount %s for the order.', $order->get_total()), 'error');
707 throw new \Exception($amount->get_error_message());
708 }
709
710 $payment_token_id = (isset($_POST['wc-' . $this->id . '-payment-token']) && 'new' !== $_POST['wc-' . $this->id . '-payment-token'])
711 ? wc_clean($_POST['wc-' . $this->id . '-payment-token'])
712 : false;
713
714 if ($payment_token_id && (int) $customer_id > 0) {
715 self::log(sprintf('Payment token found.', $amount));
716
717 return $this->process_payment_with_token($order, $amount, $customer_id, $payment_token_id);
718 }
719
720 return $this->process_standard_payment($order, $amount, $customer_id);
721 }
722
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 /**
748 * if payment was generated by an intend, we shouldn't generate another one and try to pay it, this would generate duplications
749 *
750 * @param $order
751 *
752 * @throws \Exception
753 *
754 * @return array|null
755 */
756 private function process_standard_intent_payment($order)
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
763 //no order-pay page, no ajax_on_order_review_page
764 if (!$is_order_pay &&
765 PayplugWoocommerceHelper::is_checkout_block() &&
766 (
767 ('payplug' == $this->id && in_array($this->embedded_mode, ['integrated', 'popup'])) ||
768 ('american_express' == $this->id && 'popup' == $this->embedded_mode)
769 ) &&
770 ($_GET['wc-ajax'] ?? '') !== 'payplug_order_review_url' &&
771 !empty($order->get_transaction_id())
772 ) {
773 $order_id = PayplugWoocommerceHelper::is_pre_30() ? $order->id : $order->get_id();
774
775 try {
776 $payment = $this->payplug_api->payment_retrieve($order->get_transaction_id());
777 if (ob_get_length() > 0) {
778 ob_clean();
779 }
780
781 // Save transaction id for the order
782 PayplugWoocommerceHelper::is_pre_30()
783 ? update_post_meta($order_id, '_transaction_id', $payment->id)
784 : $order->set_transaction_id($payment->id);
785
786 if ($payment->is_paid) {
787 $finished_status = wc_get_is_paid_statuses();
788 $order->set_status($finished_status[0]);
789 }
790
791 if (is_callable([$order, 'save'])) {
792 $order->save();
793 }
794
795 /**
796 * Fires once a payment has been created.
797 *
798 * @param int $order_id Order ID
799 * @param PaymentResource $payment Payment resource
800 */
801 \do_action('payplug_gateway_payment_created', $order_id, $payment);
802
803 $metadata = PayplugWoocommerceHelper::extract_transaction_metadata($payment);
804 PayplugWoocommerceHelper::save_transaction_metadata($order, $metadata);
805
806 self::log(sprintf('Payment intent created for order #%s', $order_id));
807
808 $return_url = esc_url_raw($order->get_checkout_order_received_url());
809
810 $result = [
811 'payment_id' => $payment->id,
812 'result' => 'success',
813 'redirect' => !empty($payment->hosted_payment->payment_url) ? $payment->hosted_payment->payment_url : $return_url,
814 'cancel' => !empty($payment->hosted_payment->cancel_url) ? $payment->hosted_payment->cancel_url : null,
815 ];
816
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;
826 } catch (HttpException $e) {
827 self::log(sprintf('Error while processing order #%s : %s', $order_id, wc_print_r($e->getErrorObject(), true)), 'error');
828 throw new \Exception(__('Payment processing failed. Please retry.', 'payplug'));
829 } catch (\Exception $e) {
830 self::log(sprintf('Error while processing order #%s : %s', $order_id, $e->getMessage()), 'error');
831 throw new \Exception(__('Payment processing failed. Please retry.', 'payplug'));
832 }
833 }
834
835 return null;
836 }
837
838 /**
839 * @param \WC_Order $order
840 * @param int $amount
841 * @param int $customer_id
842 *
843 * @throws \Exception
844 *
845 * @return array
846 */
847 public function process_standard_payment($order, $amount, $customer_id)
848 {
849 $order_id = PayplugWoocommerceHelper::is_pre_30() ? $order->id : $order->get_id();
850
851 $intent = $this->process_standard_intent_payment($order);
852 if (!empty($intent)) {
853 return $intent;
854 }
855
856 try {
857 $address_data = PayplugAddressData::from_order($order);
858
859 $return_url = esc_url_raw($order->get_checkout_order_received_url());
860
861 if (!(substr($return_url, 0, 4) === 'http')) {
862 $return_url = get_site_url() . $return_url;
863 }
864
865 $payment_data = [
866 'amount' => $amount,
867 'currency' => get_woocommerce_currency(),
868 'allow_save_card' => $this->save_card_available() && (int) $customer_id > 0,
869 'billing' => $address_data->get_billing(),
870 'shipping' => $address_data->get_shipping(),
871 'hosted_payment' => [
872 'return_url' => $return_url,
873 'cancel_url' => esc_url_raw($order->get_cancel_order_url_raw()),
874 ],
875 'notification_url' => esc_url_raw(WC()->api_request_url('PayplugGateway')),
876 'metadata' => [
877 'order_id' => $order_id,
878 'customer_id' => ((int) $customer_id > 0) ? $customer_id : 'guest',
879 'domain' => $this->limit_length(esc_url_raw(home_url()), 500),
880 ],
881 ];
882
883 if (PayplugWoocommerceHelper::is_checkout_block() && is_checkout()) {
884 $payment_data['metadata']['woocommerce_block'] = 'CHECKOUT';
885 } elseif (PayplugWoocommerceHelper::is_cart_block() && is_cart()) {
886 $payment_data['metadata']['woocommerce_block'] = 'CART';
887 }
888
889 //IP request required variables
890 if ('integrated' == $this->embedded_mode) {
891 $payment_data['initiator'] = 'PAYER';
892 $payment_data['integration'] = 'INTEGRATED_PAYMENT';
893 unset($payment_data['hosted_payment']['cancel_url']);
894 }
895
896 //for subscriptions the card needs to be saved
897 $is_subscription = PayplugWoocommerceHelper::is_subscription();
898 if (!empty($is_subscription) && $is_subscription === true) {
899 $payment_data['allow_save_card'] = false;
900 $payment_data['save_card'] = true;
901 $payment_data['force_3ds'] = true;
902 $payment_data['metadata']['subscription'] = 'subscription';
903 }
904
905 /**
906 * Filter the payment data before it's used
907 *
908 * @param array $payment_data
909 * @param int $order_id
910 * @param array $customer_details
911 * @param PayplugAddressData $address_data
912 */
913 $payment_data = apply_filters('payplug_gateway_payment_data', $payment_data, $order_id, [], $address_data);
914 $payment = $this->payplug_api->payment_create($payment_data);
915
916 // Save transaction id for the order
917 PayplugWoocommerceHelper::is_pre_30()
918 ? update_post_meta($order_id, '_transaction_id', $payment->id)
919 : $order->set_transaction_id($payment->id);
920
921 $order->set_payment_method($this->id);
922 $order->set_payment_method_title($this->method_title);
923
924 if (is_callable([$order, 'save'])) {
925 $order->save();
926 }
927
928 /**
929 * Fires once a payment has been created.
930 *
931 * @param int $order_id Order ID
932 * @param PaymentResource $payment Payment resource
933 */
934 \do_action('payplug_gateway_payment_created', $order_id, $payment);
935
936 $metadata = PayplugWoocommerceHelper::extract_transaction_metadata($payment);
937 PayplugWoocommerceHelper::save_transaction_metadata($order, $metadata);
938
939 self::log(sprintf('Payment creation complete for order #%s', $order_id));
940
941 if (ob_get_length() > 0) {
942 ob_clean();
943 }
944
945 return [
946 'payment_id' => $payment->id,
947 'result' => 'success',
948 'redirect' => !empty($payment->hosted_payment->payment_url) ? $payment->hosted_payment->payment_url : $return_url,
949 'cancel' => !empty($payment->hosted_payment->cancel_url) ? $payment->hosted_payment->cancel_url : null,
950 ];
951 } catch (HttpException $e) {
952 self::log(sprintf('Error while processing order #%s : %s', $order_id, wc_print_r($e->getErrorObject(), true)), 'error');
953 throw new \Exception(__('Payment processing failed. Please retry.', 'payplug'));
954 } catch (\Exception $e) {
955 self::log(sprintf('Error while processing order #%s : %s', $order_id, $e->getMessage()), 'error');
956 throw new \Exception(__('Payment processing failed. Please retry.', 'payplug'));
957 }
958 }
959
960 /**
961 * @param \WC_Order $order
962 * @param int $amount
963 * @param int $customer_id
964 * @param string $token_id
965 *
966 * @throws \Exception
967 *
968 * @return array
969 */
970 public function process_payment_with_token($order, $amount, $customer_id, $token_id)
971 {
972 $order_id = PayplugWoocommerceHelper::is_pre_30() ? $order->id : $order->get_id();
973 $payment_token = WC_Payment_Tokens::get($token_id);
974 if (!$payment_token || (int) $customer_id !== (int) $payment_token->get_user_id()) {
975 self::log('Could not find the payment token or the payment doesn\'t belong to the current user.', 'error');
976 throw new \Exception(__('Invalid payment method.', 'payplug'));
977 }
978
979 try {
980 $address_data = PayplugAddressData::from_order($order);
981
982 $return_url = esc_url_raw($order->get_checkout_order_received_url());
983
984 if (!(substr($return_url, 0, 4) === 'http')) {
985 $return_url = get_site_url() . $return_url;
986 }
987
988 $payment_data = [
989 'amount' => $amount,
990 'currency' => get_woocommerce_currency(),
991 'payment_method' => $payment_token->get_token(),
992 'allow_save_card' => false,
993 'billing' => $address_data->get_billing(),
994 'shipping' => $address_data->get_shipping(),
995 'initiator' => 'PAYER',
996 'hosted_payment' => [
997 'return_url' => $return_url,
998 'cancel_url' => esc_url_raw($order->get_cancel_order_url_raw()),
999 ],
1000 'notification_url' => esc_url_raw(WC()->api_request_url('PayplugGateway')),
1001 'metadata' => [
1002 'order_id' => $order_id,
1003 'customer_id' => ((int) $customer_id > 0) ? $customer_id : 'guest',
1004 'domain' => $this->limit_length(esc_url_raw(home_url()), 500),
1005 'woocommerce_block' => \WC_Blocks_Utils::has_block_in_page(wc_get_page_id('checkout'), 'woocommerce/checkout'),
1006 ],
1007 ];
1008
1009 $is_subscription = PayplugWoocommerceHelper::is_subscription();
1010 if (!empty($is_subscription) && $is_subscription === true) {
1011 $payment_data['metadata']['subscription'] = 'subscription';
1012 }
1013
1014 /** This filter is documented in src/Gateway/PayplugGateway */
1015 $payment_data = apply_filters('payplug_gateway_payment_data', $payment_data, $order_id, [], $address_data);
1016 $payment = $this->payplug_api->payment_create($payment_data);
1017
1018 // Save transaction id for the order
1019 PayplugWoocommerceHelper::is_pre_30()
1020 ? update_post_meta($order_id, '_transaction_id', $payment->id)
1021 : $order->set_transaction_id($payment->id);
1022
1023 if (is_callable([$order, 'save'])) {
1024 $order->save();
1025 }
1026
1027 /** This action is documented in src/Gateway/PayplugGateway */
1028 \do_action('payplug_gateway_payment_created', $order_id, $payment);
1029
1030 $metadata = PayplugWoocommerceHelper::extract_transaction_metadata($payment);
1031 PayplugWoocommerceHelper::save_transaction_metadata($order, $metadata);
1032
1033 $this->response->process_payment($payment, true);
1034 if (($payment->__get('is_paid'))) {
1035 $redirect = $order->get_checkout_order_received_url();
1036 } elseif (isset($payment->__get('hosted_payment')->payment_url)) {
1037 $redirect = $payment->__get('hosted_payment')->payment_url;
1038 } else {
1039 $redirect = $return_url;
1040 }
1041
1042 return [
1043 'payment_id' => $payment->id,
1044 'result' => 'success',
1045 'is_paid' => $payment->__get('is_paid'), // Use for path redirect before DSP2
1046 'redirect' => $redirect,
1047 ];
1048 } catch (HttpException $e) {
1049 self::log(sprintf('Error while processing order #%s : %s', $order_id, wc_print_r($e->getErrorObject(), true)), 'error');
1050 throw new \Exception(__('Payment processing failed. Please retry.', 'payplug'));
1051 } catch (\Exception $e) {
1052 self::log(sprintf('Error while processing order #%s : %s', $order_id, $e->getMessage()), 'error');
1053 throw new \Exception(__('Payment processing failed. Please retry.', 'payplug'));
1054 }
1055 }
1056
1057 /**
1058 * Process refund for an order paid with PayPlug gateway.
1059 *
1060 * @param int $order_id
1061 * @param null $amount
1062 * @param string $reason
1063 *
1064 * @return bool|\WP_Error
1065 */
1066 public function process_refund($order_id, $amount = null, $reason = '')
1067 {
1068 self::log(sprintf('Processing refund for order #%s', $order_id));
1069
1070 if (!$this->user_logged_in()) {
1071 self::log(__('You must be logged in with your PayPlug account.', 'payplug'), 'error');
1072
1073 return new \WP_Error('process_refund_error', __('You must be logged in with your PayPlug account.', 'payplug'));
1074 }
1075
1076 $order = wc_get_order($order_id);
1077 if (!$order instanceof \WC_Order) {
1078 self::log(sprintf('The order #%s does not exist.', $order_id), 'error');
1079
1080 return new \WP_Error('process_refund_error', sprintf(__('The order %s does not exist.', 'payplug'), $order_id));
1081 }
1082
1083 if ($order->get_status() === 'cancelled') {
1084 self::log(sprintf('The order #%s cannot be refund.', $order_id), 'error');
1085
1086 return new \WP_Error('process_refund_error', sprintf(__('The order %s cannot be refund.', 'payplug'), $order_id));
1087 }
1088
1089 $transaction_id = PayplugWoocommerceHelper::is_pre_30() ? get_post_meta($order_id, '_transaction_id', true) : $order->get_transaction_id();
1090 if (empty($transaction_id)) {
1091 self::log(sprintf('The order #%s does not have PayPlug transaction ID associated with it.', $order_id), 'error');
1092
1093 return new \WP_Error('process_refund_error', __('No PayPlug transaction was found for this order. The refund could not be processed.', 'payplug'));
1094 }
1095
1096 $customer_id = PayplugWoocommerceHelper::is_pre_30() ? $order->customer_user : $order->get_customer_id();
1097
1098 $data = [
1099 'metadata' => [
1100 'order_id' => $order_id,
1101 'customer_id' => ((int) $customer_id > 0) ? $customer_id : 'guest',
1102 'refund_from' => 'woocommerce',
1103 ],
1104 ];
1105
1106 if (!is_null($amount)) {
1107 $data['amount'] = PayplugWoocommerceHelper::get_payplug_amount($amount);
1108 }
1109
1110 if (!empty($reason)) {
1111 $data['metadata']['reason'] = $reason;
1112 }
1113
1114 /**
1115 * Filter the refund data before it's used.
1116 *
1117 * @param array $data
1118 * @param int $order_id
1119 * @param string $transaction_id
1120 */
1121 $data = apply_filters('payplug_gateway_refund_data', $data, $order_id, $transaction_id);
1122
1123 try {
1124 $refund = $this->payplug_api->refund_create($transaction_id, $data);
1125
1126 /**
1127 * Fires once a refund has been created.
1128 *
1129 * @param int $order_id Order ID
1130 * @param RefundResource $refund Refund resource
1131 * @param string $transaction_id Transaction id
1132 */
1133 \do_action('payplug_gateway_refund_created', $order_id, $refund, $transaction_id);
1134
1135 $refund_meta_key = sprintf('_pr_%s', wc_clean($refund->id));
1136 if (PayplugWoocommerceHelper::is_pre_30()) {
1137 update_post_meta($order_id, $refund_meta_key, $refund->id);
1138 } else {
1139 $order->add_meta_data($refund_meta_key, $refund->id, true);
1140 $order->save();
1141 }
1142
1143 $note = sprintf(__('Refund %s : Refunded %s', 'payplug'), wc_clean($refund->id), wc_price(((int) $refund->amount) / 100));
1144 if (!empty($refund->metadata['reason'])) {
1145 $note .= sprintf(' (%s)', esc_html($refund->metadata['reason']));
1146 }
1147 $order->add_order_note($note);
1148
1149 try {
1150 $payment = $this->payplug_api->payment_retrieve($transaction_id);
1151 $metadata = PayplugWoocommerceHelper::extract_transaction_metadata($payment);
1152 PayplugWoocommerceHelper::save_transaction_metadata($order, $metadata);
1153 } catch (\Exception $e) {
1154 }
1155
1156 self::log('Refund process complete for the order.');
1157
1158 return true;
1159 } catch (HttpException $e) {
1160 self::log(sprintf('Refund request error for the order %s from PayPlug API : %s', $order_id, wc_print_r($e->getErrorObject(), true)), 'error');
1161
1162 return new \WP_Error('process_refund_error', __('The transaction could not be refunded. Please try again.', 'payplug'));
1163 } catch (\Exception $e) {
1164 self::log(sprintf('Refund request error for the order %s : %s', $order_id, wc_clean($e->getMessage())), 'error');
1165
1166 return new \WP_Error('process_refund_error', __('The transaction could not be refunded. Please try again.', 'payplug'));
1167 }
1168 }
1169
1170 /**
1171 * Check the order amount to ensure it's on the allowed range.
1172 *
1173 * @param int $amount
1174 *
1175 * @return int|\WP_Error
1176 */
1177 public function validate_order_amount($amount)
1178 {
1179 if (
1180 $amount < PayplugWoocommerceHelper::get_minimum_amount()
1181 || $amount > PayplugWoocommerceHelper::get_maximum_amount()
1182 ) {
1183 return new \WP_Error(
1184 'invalid order amount',
1185 sprintf(__('Payments for this amount (%s) are not authorised with this payment gateway.', 'payplug'), \wc_price($amount / 100))
1186 );
1187 }
1188
1189 return $amount;
1190 }
1191
1192 /**
1193 * Limit string length.
1194 *
1195 * @param string $value
1196 * @param int $maxlength
1197 *
1198 * @return string
1199 */
1200 public function limit_length($value, $maxlength = 100)
1201 {
1202 return (strlen($value) > $maxlength) ? substr($value, 0, $maxlength) : $value;
1203 }
1204
1205 /**
1206 * Get user's keys.
1207 *
1208 * @param string $email
1209 * @param string $password
1210 *
1211 * @return array|\WP_Error
1212 */
1213 public function retrieve_user_api_keys($email, $password)
1214 {
1215 if (empty($email) || empty($password)) {
1216 return new \WP_Error('missing_login_data', __('Please fill all login fields', 'payplug'));
1217 }
1218
1219 try {
1220 $response = Authentication::getKeysByLogin($email, $password);
1221 if (empty($response) || !isset($response['httpResponse']) && 'payplug' === $this->id) {
1222 return new \WP_Error('invalid_credentials', __('Invalid credentials.', 'payplug'));
1223 }
1224
1225 return $response['httpResponse']['secret_keys'];
1226 } catch (HttpException $e) {
1227 if ('payplug' === $this->id) {
1228 return new \WP_Error('invalid_credentials', __('Invalid credentials.', 'payplug'));
1229 }
1230 }
1231 }
1232
1233 /**
1234 * Get user merchant id.
1235 *
1236 * This method might be called during the login process before the global PayPlug
1237 * configuration is set. In that case you can pass a valid token to make the request.
1238 *
1239 * @param string|null $key
1240 *
1241 * @return string
1242 */
1243 public function retrieve_merchant_id($key = null)
1244 {
1245 $merchant_id = '';
1246 try {
1247 $response = !is_null($key) && !empty($key) ? Authentication::getAccount(new Payplug($key)) : Authentication::getAccount();
1248 PayplugWoocommerceHelper::set_transient_data($response);
1249 $merchant_id = isset($response['httpResponse']['id']) ? $response['httpResponse']['id'] : '';
1250 } catch (ConfigurationException $e) {
1251 self::log(sprintf('Missing API key for PayPlug client : %s', wc_print_r($e->getMessage(), true)), 'error');
1252 } catch (HttpException $e) {
1253 self::log(sprintf('Account request error from PayPlug API : %s', wc_print_r($e->getErrorObject(), true)), 'error');
1254 PayplugWoocommerceHelper::exception_handler_400_logout($e->getCode(), '', sprintf('Account request error from PayPlug API : %s', wc_print_r($e->getMessage(), true)));
1255 } catch (\Exception $e) {
1256 self::log(sprintf('Account request error : %s', wc_clean($e->getMessage())), 'error');
1257 }
1258
1259 return $merchant_id;
1260 }
1261
1262 /**
1263 * Get PayPlug gateway mode.
1264 *
1265 * @return string
1266 */
1267 public function get_current_mode()
1268 {
1269 return $this->get_configuration()->get_option('mode');
1270 }
1271
1272 /**
1273 * Get user API key.
1274 *
1275 * @param string $mode
1276 *
1277 * @return string
1278 */
1279 public function get_api_key($mode = 'test')
1280 {
1281 return $this->get_api()->get_bearer_token($mode);
1282 }
1283
1284 /**
1285 * Get current merchant id.
1286 *
1287 * @return string
1288 */
1289 public function get_merchant_id()
1290 {
1291 return $this->get_configuration()->get_option('company_id');
1292 }
1293
1294 /**
1295 * Check if user is logged in and we have an API key for TEST mode.
1296 *
1297 * @return bool
1298 */
1299 public function user_logged_in()
1300 {
1301 $options = $this->get_configuration()->get_options();
1302 if (empty($options) || !isset($options['api_key']) || !isset($options['jwt'])) {
1303 return false;
1304 }
1305
1306 $jwt = json_decode($options['jwt'], true);
1307 if (!empty($jwt) && isset($jwt['test']) && isset($jwt['test']['access_token'])) {
1308 return true;
1309 }
1310
1311 $api_key = json_decode($options['api_key'], true);
1312
1313 return !empty($api_key) && isset($api_key['test']) && !empty($api_key['test']);
1314 }
1315
1316 /**
1317 * Check if oneclick payment is activated and merchant can use it.
1318 *
1319 * @return bool
1320 */
1321 public function save_card_available()
1322 {
1323 return 'payplug' == $this->id && $this->user_logged_in()
1324 && $this->save_card
1325 && $this->permissions->has_permissions(PayplugPermissions::SAVE_CARD);
1326 }
1327
1328 /**
1329 * Check if the gatteway is allowed for the order amount
1330 *
1331 * @param array
1332 *
1333 * @return array
1334 */
1335 public function check_gateway($gateways)
1336 {
1337 if (!empty(WC()->cart) && isset($gateways[$this->id]) && $gateways[$this->id]->id == $this->id) {
1338 $order_amount = $this->get_order_total();
1339 foreach ($gateways[$this->id]->settings['payment_methods']['permissions'] as $key => &$permission) {
1340 $method_amounts = json_decode($permission['amounts'], true);
1341 if ($order_amount < $method_amounts['min']['EUR'] / 100 || $order_amount > $method_amounts['max']['EUR'] / 100) {
1342 unset($gateways[$key]);
1343 }
1344 }
1345 }
1346
1347 if ((bool) $this->get_configuration()->get_option('payment_methods.configuration.oney.with_fees')) {
1348 unset($gateways['oney_x3_without_fees']);
1349 unset($gateways['oney_x4_without_fees']);
1350 } else {
1351 unset($gateways['oney_x3_with_fees']);
1352 unset($gateways['oney_x4_with_fees']);
1353 }
1354
1355 return $gateways;
1356 }
1357
1358 /**
1359 * Can the order be refunded via this gateway?
1360 *
1361 *
1362 * @param WC_Order $order Order object.
1363 *
1364 * @return bool If false, the automatic refund button is hidden in the UI.
1365 */
1366 public function can_refund_order($order)
1367 {
1368 $status = $order->get_status();
1369
1370 return $order && $this->supports('refunds') && $status !== 'cancelled' && $status !== 'failed';
1371 }
1372 }
1373