PluginProbe
PayPlug for WooCommerce (Official) / 2.18.0
PayPlug for WooCommerce (Official) v2.18.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) 2.18.0, at src/Gateway/PayplugGateway.php

1,325 lines 48.0 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 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 const MIN_AMOUNT = 0.99;
96
97 /**
98 * @var float
99 */
100 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 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')
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)
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 return $this->requirements->satisfy_requirements() && !empty($this->get_api_key($this->get_current_mode()));
333 }
334
335 return parent::is_available();
336 }
337
338 /**
339 * Load gateway settings.
340 */
341 public function init_settings()
342 {
343 parent::init_settings();
344 $enabled = !empty($this->settings['enabled']) && (bool) $this->settings['enabled'];
345 $this->enabled = $enabled ? 'yes' : 'no';
346 }
347
348 /**
349 * Register gateway settings.
350 */
351 public function init_form_fields()
352 {
353 $anchor = esc_html_x(__('More informations', 'payplug'), 'modal', 'payplug');
354 $domain = __('support.payplug.com/hc/fr/articles/4408142346002', 'payplug');
355 $link = sprintf(' <a href="https://%s" target="_blank">%s</a>', $domain, $anchor);
356
357 $anchor_bancontact = esc_html_x(__('payplug_bancontact_activation_request', 'payplug'), 'modal', 'payplug');
358 $domain_bancontact = __('payplug_bancontact_activation_url', 'payplug');
359 $bancontact_call_to_action = sprintf(' <a id="bancontact_call_to_action" href="https://%s" target="_blank">%s</a>', $domain_bancontact, $anchor_bancontact);
360
361 $oney_cfg = $this->get_configuration()->get_option('payment_methods.configuration.oney');
362 if (!empty($oney_cfg)) {
363 $oney_amount = json_decode($oney_cfg['custom_amounts'], true);
364 $oney_amount['min'] = (float) $oney_amount['min'] / 100;
365 $oney_amount['max'] = (float) $oney_amount['max'] / 100;
366 } else {
367 $oney_amount = [
368 'min' => 100,
369 'max' => 3000,
370 ];
371 }
372 $fields = [
373 'enabled' => [
374 'title' => __('Enable/Disable', 'payplug'),
375 'type' => 'checkbox',
376 'label' => __('Enable PayPlug', 'payplug'),
377 'description' => __('Only Euro payments can be processed with PayPlug.', 'payplug'),
378 'default' => false,
379 ],
380 'title' => [
381 'title' => __('Title', 'payplug'),
382 'type' => 'text',
383 'description' => __('The payment solution title displayed to your customers during checkout', 'payplug'),
384 'default' => _x('Credit card checkout', 'Default gateway title', 'payplug'),
385 'desc_tip' => false,
386 ],
387 'description' => [
388 'title' => __('Description', 'payplug'),
389 'type' => 'text',
390 'description' => __('The payment solution description displayed to your customers during checkout', 'payplug'),
391 'default' => '',
392 'desc_tip' => false,
393 ],
394 'title_connexion' => [
395 'title' => __('Connection', 'payplug'),
396 'type' => 'title',
397 ],
398 'email' => [
399 'type' => 'hidden',
400 'default' => '',
401 ],
402 'login' => [
403 'type' => 'login',
404 'default' => '',
405 ],
406 'title_testmode' => [
407 'title' => __('Mode', 'payplug'),
408 'type' => 'title',
409 ],
410 'mode' => [
411 'title' => '',
412 'label' => '',
413 'type' => 'yes_no',
414 'yes' => 'Live',
415 'no' => 'Test',
416 'description' => __('In TEST mode, all payments will be simulations and will not generate real transactions.', 'payplug'),
417 'default' => false,
418 'hide_label' => true,
419 ],
420 'title_settings' => [
421 'title' => __('Settings', 'payplug'),
422 'type' => 'title',
423 ],
424 'payment_method' => [
425 'title' => __('Payment page', 'payplug'),
426 'type' => 'radio',
427 'options' => [
428 'redirect' => __('Redirect', 'payplug'),
429 'embedded' => __('Integrated', 'payplug'),
430 ],
431 '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'),
432 'default' => 'redirect',
433 'desc_tip' => false,
434 ],
435 'debug' => [
436 'title' => __('Debug', 'payplug'),
437 'type' => 'checkbox',
438 'description' => __('Debug mode saves additional information on your server for each operation done via the PayPlug plugin (Developer setting).', 'payplug'),
439 'label' => __('Activate debug mode', 'payplug'),
440 'default' => true,
441 'desc_tip' => false,
442 ],
443 'title_advanced_settings' => [
444 'title' => __('payplug_advanced_settings', 'payplug'),
445 'description' => __(
446 '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>',
447 'payplug'
448 ),
449 'type' => 'title',
450 ],
451 'save_card' => [
452 'title' => __('One Click Payment', 'payplug'),
453 'type' => 'checkbox',
454 'label' => __('Activate', 'payplug'),
455 'description' => __('Allow your customers to save their credit card information for later purchases.', 'payplug'),
456 'default' => false,
457 'desc_tip' => false,
458 ],
459 'bancontact' => [
460 'title' => __('payplug_bancontact_activate_title', 'payplug'),
461 'type' => 'checkbox',
462 'label' => __('Activate', 'payplug'),
463 'description' => '<p class="description" id="bancontact_test_mode_description"> ' . __('payplug_bancontact_testmode_description', 'payplug') . ' </p>' .
464 '<p class="description" id="bancontact_live_mode_description_disabled"> ' . __('payplug_bancontact_livemode_description_disabled', 'payplug') . ' </p>' .
465 $bancontact_call_to_action,
466 'default' => false,
467 ],
468 'apple_pay' => [
469 'title' => __('payplug_apple_pay_activate_title', 'payplug'),
470 'type' => 'checkbox',
471 'label' => __('Activate', 'payplug'),
472 'description' => '<p class="description" id="apple_pay_test_mode_description"> ' . __('payplug_apple_pay_testmode_description', 'payplug') . ' </p>' .
473 '<p class="description" id="apple_pay_live_mode_description"> ' . __('payplug_apple_pay_livemode_description', 'payplug') . ' </p>',
474 'default' => false,
475 ],
476 'american_express' => [
477 'title' => __('payplug_amex_title', 'payplug'),
478 'type' => 'checkbox',
479 'label' => __('payplug_amex_activate', 'payplug'),
480 'description' => '<p class="description" id="amex_test_mode_description"> ' . __('payplug_amex_testmode_description', 'payplug') . ' </p>' .
481 '<p class="description" id="amex_live_mode_description"> ' . __('payplug_amex_livemode_description', 'payplug') . ' </p>',
482 'default' => false,
483 ],
484 'oney' => [
485 'title' => __('3x 4x Oney payments', 'payplug'),
486 'type' => 'checkbox',
487 'label' => __('Activate', 'payplug'),
488 // TRAD
489 '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,
490 'default' => false,
491 'desc_tip' => false,
492 ],
493 'oney_type' => [
494 'title' => '',
495 'type' => 'oney_type',
496 'options' => [
497 'with_fees' => __('Oney with fees', 'payplug'),
498 'without_fees' => __('Oney without fees', 'payplug'),
499 ],
500 'descriptions' => [
501 'with_fees' => __('The fees are split between you and your customers', 'payplug'),
502 'without_fees' => __('You pay the fees', 'payplug'),
503 ],
504 'description' => '',
505 'default' => 'with_fees',
506 'desc_tip' => false,
507 ],
508 'oney_thresholds' => [
509 'title' => '',
510 'type' => 'oney_thresholds',
511 'description' => sprintf(
512 __('I would like to offer guaranteed payment in installments for amounts between %s€ and %s€.', 'payplug'),
513 '<b class="min">' . $oney_amount['min'] . '</b>',
514 '<b class="max">' . $oney_amount['max'] . '</b>'
515 ),
516 'desc_tip' => false,
517 ],
518 'oney_product_animation' => [
519 'title' => __('oney_installments_pop_up', 'payplug'),
520 'description' => __('display_the_oney_installments_pop_up_on_the_product_page', 'payplug'),
521 'label' => __('Activate', 'payplug'),
522 'default' => false,
523 'desc_tip' => false,
524 'type' => 'oney_product_animation',
525 ],
526 ];
527
528 if ($this->user_logged_in()) {
529 if ($this->permissions->has_permissions(PayplugPermissions::SAVE_CARD)) {
530 unset($fields['title_advanced_settings']);
531 } elseif ('live' === $this->get_current_mode()) {
532 $fields['save_card']['disabled'] = true;
533 }
534 }
535
536 /**
537 * Filter PayPlug gateway settings.
538 *
539 * @param array $fields
540 */
541 $fields = apply_filters('payplug_gateway_settings', $fields);
542 $this->form_fields = $fields;
543 }
544
545 /**
546 * Set global configuration for PayPlug instance.
547 */
548 public function init_payplug()
549 {
550 $this->payplug_api = new PayplugApi($this);
551 $this->payplug_api->init();
552
553 $this->permissions = new PayplugPermissions($this);
554 $this->response = new PayplugResponse($this);
555
556 // Register IPN handler
557 new PayplugIpnResponse($this);
558 }
559
560 /**
561 * Filter saved tokens for the gateway.
562 *
563 * A token will be removed if :
564 * - it doesn't match the current merchant logged in,
565 * - or it doesn't match the current gateway mode,
566 * - or it is expired.
567 *
568 * @param array $tokens
569 * @param int $user_id
570 * @param string $gateway_id
571 *
572 * @return array
573 */
574 public function filter_tokens($tokens, $user_id, $gateway_id)
575 {
576 if (!is_user_logged_in() || !class_exists('WC_Payment_Gateway_CC')) {
577 return $tokens;
578 }
579
580 $saved_card = $this->get_configuration()->get_option('payment_methods.configuration.payplug.save_card')
581 && is_user_logged_in();
582
583 if (!$saved_card) {
584 foreach ($tokens as $token_id => $token) {
585 if ('payplug' == (string) $token->get_gateway_id()) {
586 unset($tokens[$token_id]);
587 }
588 }
589
590 return $tokens;
591 }
592
593 /* @var \WC_Payment_Token_CC $token */
594 foreach ($tokens as $k => $token) {
595 if ($this->id !== $token->get_gateway_id()) {
596 continue;
597 }
598
599 // check if token is associated with a merchant id and if it match the current one
600 $token_merchant_id = $token->get_meta('payplug_account', true);
601 if (empty($token_merchant_id) || $this->get_merchant_id() !== $token_merchant_id) {
602 unset($tokens[$k]);
603 continue;
604 }
605
606 // check if token is available for the current gateway mode
607 if ($this->mode != $token->get_meta('mode', true)) {
608 unset($tokens[$k]);
609 continue;
610 }
611
612 // check if token is not expired
613 $current_month = \absint(date('n'));
614 $current_year = \absint(date('Y'));
615 if ($current_year > (int) $token->get_expiry_year()) {
616 unset($tokens[$k]);
617 continue;
618 }
619
620 if ($current_year === (int) $token->get_expiry_year() && $current_month > (int) $token->get_expiry_month()) {
621 unset($tokens[$k]);
622 continue;
623 }
624 }
625
626 return $tokens;
627 }
628
629 /**
630 * extra payment fields
631 */
632 public function payment_fields()
633 {
634 $description = $this->get_description();
635
636 if (!empty($description)) {
637 echo wpautop(wptexturize($description));
638 }
639
640 if ($this->save_card_available()) {
641 $this->tokenization_script();
642 $this->saved_payment_methods();
643 }
644 }
645
646 /**
647 * Handle admin display.
648 */
649 public function admin_options()
650 {
651 /************ VUE Code *************/
652 wp_enqueue_script('chunk-vendors.js', PAYPLUG_GATEWAY_PLUGIN_URL . 'assets/dist/js/chunk-vendors-' . PAYPLUG_GATEWAY_VERSION . '.js', [], PAYPLUG_GATEWAY_VERSION);
653 wp_enqueue_script('app.js', PAYPLUG_GATEWAY_PLUGIN_URL . 'assets/dist/js/app-' . PAYPLUG_GATEWAY_VERSION . '.js', [], PAYPLUG_GATEWAY_VERSION);
654 wp_enqueue_style('app.css', PAYPLUG_GATEWAY_PLUGIN_URL . 'assets/dist/css/app-' . PAYPLUG_GATEWAY_VERSION . '.css', [], PAYPLUG_GATEWAY_VERSION);
655 wp_localize_script(
656 'app.js',
657 'payplug_admin_config',
658 [
659 'img_path' => esc_url(PAYPLUG_GATEWAY_PLUGIN_URL . 'assets/dist/'),
660 'ajax_url' => get_home_url(),
661 'rest_url' => get_home_url() . '/?rest_route=/payplug_api/',
662 ]
663 ); ?>
664 <script>window.get_data_url = "<?php echo rest_url('payplug/data'); ?>"</script>
665 <script>window.set_data_url = "<?php echo rest_url('payplug/save_data'); ?>"</script>
666 <div id="payplug_admin"></div>
667
668 <?php
669
670 /*********** End VUE Code ***********/
671 }
672
673 /**
674 * Process payment.
675 *
676 * @param int $order_id
677 *
678 * @throws \Exception
679 *
680 * @return array
681 */
682 public function process_payment($order_id)
683 {
684 self::log(sprintf('Processing payment for order #%s', $order_id));
685
686 $order = wc_get_order($order_id);
687 if (!$order instanceof \WC_Order) {
688 self::log(sprintf('Order #%s not found.', $order_id), 'error');
689 throw new \Exception(__('Order not found.', 'payplug'));
690 }
691 $customer_id = PayplugWoocommerceHelper::is_pre_30() ? $order->customer_user : $order->get_customer_id();
692 $amount = (int) PayplugWoocommerceHelper::get_payplug_amount($order->get_total());
693 $amount = $this->validate_order_amount($amount);
694
695 if (is_wp_error($amount)) {
696 self::log(sprintf('Invalid amount %s for the order.', $order->get_total()), 'error');
697 throw new \Exception($amount->get_error_message());
698 }
699
700 $payment_token_id = (isset($_POST['wc-' . $this->id . '-payment-token']) && 'new' !== $_POST['wc-' . $this->id . '-payment-token'])
701 ? wc_clean($_POST['wc-' . $this->id . '-payment-token'])
702 : false;
703
704 if ($payment_token_id && (int) $customer_id > 0) {
705 self::log(sprintf('Payment token found.', $amount));
706
707 return $this->process_payment_with_token($order, $amount, $customer_id, $payment_token_id);
708 }
709
710 return $this->process_standard_payment($order, $amount, $customer_id);
711 }
712
713 /**
714 * if payment was generated by an intend, we shouldn't generate another one and try to pay it, this would generate duplications
715 *
716 * @param $order
717 *
718 * @throws \Exception
719 *
720 * @return array|null
721 */
722 private function process_standard_intent_payment($order)
723 {
724 //no order-pay page, no ajax_on_order_review_page
725 if (!is_wc_endpoint_url('order-pay') &&
726 PayplugWoocommerceHelper::is_checkout_block() &&
727 (
728 ('payplug' == $this->id && in_array($this->embedded_mode, ['integrated', 'popup'])) ||
729 ('american_express' == $this->id && 'popup' == $this->embedded_mode)
730 ) &&
731 $_GET['wc-ajax'] !== 'payplug_order_review_url'
732 ) {
733 $order_id = PayplugWoocommerceHelper::is_pre_30() ? $order->id : $order->get_id();
734
735 try {
736 $payment = $this->payplug_api->payment_retrieve($order->get_transaction_id());
737 if (ob_get_length() > 0) {
738 ob_clean();
739 }
740
741 // Save transaction id for the order
742 PayplugWoocommerceHelper::is_pre_30()
743 ? update_post_meta($order_id, '_transaction_id', $payment->id)
744 : $order->set_transaction_id($payment->id);
745
746 if ($payment->is_paid) {
747 $finished_status = wc_get_is_paid_statuses();
748 $order->set_status($finished_status[0]);
749 }
750
751 if (is_callable([$order, 'save'])) {
752 $order->save();
753 }
754
755 /**
756 * Fires once a payment has been created.
757 *
758 * @param int $order_id Order ID
759 * @param PaymentResource $payment Payment resource
760 */
761 \do_action('payplug_gateway_payment_created', $order_id, $payment);
762
763 $metadata = PayplugWoocommerceHelper::extract_transaction_metadata($payment);
764 PayplugWoocommerceHelper::save_transaction_metadata($order, $metadata);
765
766 self::log(sprintf('Payment intent created for order #%s', $order_id));
767
768 $return_url = esc_url_raw($order->get_checkout_order_received_url());
769
770 wp_send_json_success([
771 'payment_id' => $payment->id,
772 'result' => 'success',
773 'redirect' => !empty($payment->hosted_payment->payment_url) ? $payment->hosted_payment->payment_url : $return_url,
774 'cancel' => !empty($payment->hosted_payment->cancel_url) ? $payment->hosted_payment->cancel_url : null,
775 ]);
776
777 return ['stt' => 'OK'];
778 } catch (HttpException $e) {
779 self::log(sprintf('Error while processing order #%s : %s', $order_id, wc_print_r($e->getErrorObject(), true)), 'error');
780 throw new \Exception(__('Payment processing failed. Please retry.', 'payplug'));
781 } catch (\Exception $e) {
782 self::log(sprintf('Error while processing order #%s : %s', $order_id, $e->getMessage()), 'error');
783 throw new \Exception(__('Payment processing failed. Please retry.', 'payplug'));
784 }
785 }
786
787 return null;
788 }
789
790 /**
791 * @param \WC_Order $order
792 * @param int $amount
793 * @param int $customer_id
794 *
795 * @throws \Exception
796 *
797 * @return array
798 */
799 public function process_standard_payment($order, $amount, $customer_id)
800 {
801 $order_id = PayplugWoocommerceHelper::is_pre_30() ? $order->id : $order->get_id();
802
803 $intent = $this->process_standard_intent_payment($order);
804 if (!empty($intent)) {
805 return $intent;
806 }
807
808 try {
809 $address_data = PayplugAddressData::from_order($order);
810
811 $return_url = esc_url_raw($order->get_checkout_order_received_url());
812
813 if (!(substr($return_url, 0, 4) === 'http')) {
814 $return_url = get_site_url() . $return_url;
815 }
816
817 $payment_data = [
818 'amount' => $amount,
819 'currency' => get_woocommerce_currency(),
820 'allow_save_card' => $this->save_card_available() && (int) $customer_id > 0,
821 'billing' => $address_data->get_billing(),
822 'shipping' => $address_data->get_shipping(),
823 'hosted_payment' => [
824 'return_url' => $return_url,
825 'cancel_url' => esc_url_raw($order->get_cancel_order_url_raw()),
826 ],
827 'notification_url' => esc_url_raw(WC()->api_request_url('PayplugGateway')),
828 'metadata' => [
829 'order_id' => $order_id,
830 'customer_id' => ((int) $customer_id > 0) ? $customer_id : 'guest',
831 'domain' => $this->limit_length(esc_url_raw(home_url()), 500),
832 ],
833 ];
834
835 if (PayplugWoocommerceHelper::is_checkout_block() && is_checkout()) {
836 $payment_data['metadata']['woocommerce_block'] = 'CHECKOUT';
837 } elseif (PayplugWoocommerceHelper::is_cart_block() && is_cart()) {
838 $payment_data['metadata']['woocommerce_block'] = 'CART';
839 }
840
841 //IP request required variables
842 if ('integrated' == $this->embedded_mode) {
843 $payment_data['initiator'] = 'PAYER';
844 $payment_data['integration'] = 'INTEGRATED_PAYMENT';
845 unset($payment_data['hosted_payment']['cancel_url']);
846 }
847
848 //for subscriptions the card needs to be saved
849 $is_subscription = PayplugWoocommerceHelper::is_subscription();
850 if (!empty($is_subscription) && $is_subscription === true) {
851 $payment_data['allow_save_card'] = false;
852 $payment_data['save_card'] = true;
853 $payment_data['force_3ds'] = true;
854 $payment_data['metadata']['subscription'] = 'subscription';
855 }
856
857 /**
858 * Filter the payment data before it's used
859 *
860 * @param array $payment_data
861 * @param int $order_id
862 * @param array $customer_details
863 * @param PayplugAddressData $address_data
864 */
865 $payment_data = apply_filters('payplug_gateway_payment_data', $payment_data, $order_id, [], $address_data);
866 $payment = $this->payplug_api->payment_create($payment_data);
867
868 // Save transaction id for the order
869 PayplugWoocommerceHelper::is_pre_30()
870 ? update_post_meta($order_id, '_transaction_id', $payment->id)
871 : $order->set_transaction_id($payment->id);
872
873 $order->set_payment_method($this->id);
874 $order->set_payment_method_title($this->method_title);
875
876 if (is_callable([$order, 'save'])) {
877 $order->save();
878 }
879
880 /**
881 * Fires once a payment has been created.
882 *
883 * @param int $order_id Order ID
884 * @param PaymentResource $payment Payment resource
885 */
886 \do_action('payplug_gateway_payment_created', $order_id, $payment);
887
888 $metadata = PayplugWoocommerceHelper::extract_transaction_metadata($payment);
889 PayplugWoocommerceHelper::save_transaction_metadata($order, $metadata);
890
891 self::log(sprintf('Payment creation complete for order #%s', $order_id));
892
893 if (ob_get_length() > 0) {
894 ob_clean();
895 }
896
897 return [
898 'payment_id' => $payment->id,
899 'result' => 'success',
900 'redirect' => !empty($payment->hosted_payment->payment_url) ? $payment->hosted_payment->payment_url : $return_url,
901 'cancel' => !empty($payment->hosted_payment->cancel_url) ? $payment->hosted_payment->cancel_url : null,
902 ];
903 } catch (HttpException $e) {
904 self::log(sprintf('Error while processing order #%s : %s', $order_id, wc_print_r($e->getErrorObject(), true)), 'error');
905 throw new \Exception(__('Payment processing failed. Please retry.', 'payplug'));
906 } catch (\Exception $e) {
907 self::log(sprintf('Error while processing order #%s : %s', $order_id, $e->getMessage()), 'error');
908 throw new \Exception(__('Payment processing failed. Please retry.', 'payplug'));
909 }
910 }
911
912 /**
913 * @param \WC_Order $order
914 * @param int $amount
915 * @param int $customer_id
916 * @param string $token_id
917 *
918 * @throws \Exception
919 *
920 * @return array
921 */
922 public function process_payment_with_token($order, $amount, $customer_id, $token_id)
923 {
924 $order_id = PayplugWoocommerceHelper::is_pre_30() ? $order->id : $order->get_id();
925 $payment_token = WC_Payment_Tokens::get($token_id);
926 if (!$payment_token || (int) $customer_id !== (int) $payment_token->get_user_id()) {
927 self::log('Could not find the payment token or the payment doesn\'t belong to the current user.', 'error');
928 throw new \Exception(__('Invalid payment method.', 'payplug'));
929 }
930
931 try {
932 $address_data = PayplugAddressData::from_order($order);
933
934 $return_url = esc_url_raw($order->get_checkout_order_received_url());
935
936 if (!(substr($return_url, 0, 4) === 'http')) {
937 $return_url = get_site_url() . $return_url;
938 }
939
940 $payment_data = [
941 'amount' => $amount,
942 'currency' => get_woocommerce_currency(),
943 'payment_method' => $payment_token->get_token(),
944 'allow_save_card' => false,
945 'billing' => $address_data->get_billing(),
946 'shipping' => $address_data->get_shipping(),
947 'initiator' => 'PAYER',
948 'hosted_payment' => [
949 'return_url' => $return_url,
950 'cancel_url' => esc_url_raw($order->get_cancel_order_url_raw()),
951 ],
952 'notification_url' => esc_url_raw(WC()->api_request_url('PayplugGateway')),
953 'metadata' => [
954 'order_id' => $order_id,
955 'customer_id' => ((int) $customer_id > 0) ? $customer_id : 'guest',
956 'domain' => $this->limit_length(esc_url_raw(home_url()), 500),
957 'woocommerce_block' => \WC_Blocks_Utils::has_block_in_page(wc_get_page_id('checkout'), 'woocommerce/checkout'),
958 ],
959 ];
960
961 $is_subscription = PayplugWoocommerceHelper::is_subscription();
962 if (!empty($is_subscription) && $is_subscription === true) {
963 $payment_data['metadata']['subscription'] = 'subscription';
964 }
965
966 /** This filter is documented in src/Gateway/PayplugGateway */
967 $payment_data = apply_filters('payplug_gateway_payment_data', $payment_data, $order_id, [], $address_data);
968 $payment = $this->payplug_api->payment_create($payment_data);
969
970 // Save transaction id for the order
971 PayplugWoocommerceHelper::is_pre_30()
972 ? update_post_meta($order_id, '_transaction_id', $payment->id)
973 : $order->set_transaction_id($payment->id);
974
975 if (is_callable([$order, 'save'])) {
976 $order->save();
977 }
978
979 /** This action is documented in src/Gateway/PayplugGateway */
980 \do_action('payplug_gateway_payment_created', $order_id, $payment);
981
982 $metadata = PayplugWoocommerceHelper::extract_transaction_metadata($payment);
983 PayplugWoocommerceHelper::save_transaction_metadata($order, $metadata);
984
985 $this->response->process_payment($payment, true);
986 if (($payment->__get('is_paid'))) {
987 $redirect = $order->get_checkout_order_received_url();
988 } elseif (isset($payment->__get('hosted_payment')->payment_url)) {
989 $redirect = $payment->__get('hosted_payment')->payment_url;
990 } else {
991 $redirect = $return_url;
992 }
993
994 return [
995 'payment_id' => $payment->id,
996 'result' => 'success',
997 'is_paid' => $payment->__get('is_paid'), // Use for path redirect before DSP2
998 'redirect' => $redirect,
999 ];
1000 } catch (HttpException $e) {
1001 self::log(sprintf('Error while processing order #%s : %s', $order_id, wc_print_r($e->getErrorObject(), true)), 'error');
1002 throw new \Exception(__('Payment processing failed. Please retry.', 'payplug'));
1003 } catch (\Exception $e) {
1004 self::log(sprintf('Error while processing order #%s : %s', $order_id, $e->getMessage()), 'error');
1005 throw new \Exception(__('Payment processing failed. Please retry.', 'payplug'));
1006 }
1007 }
1008
1009 /**
1010 * Process refund for an order paid with PayPlug gateway.
1011 *
1012 * @param int $order_id
1013 * @param null $amount
1014 * @param string $reason
1015 *
1016 * @return bool|\WP_Error
1017 */
1018 public function process_refund($order_id, $amount = null, $reason = '')
1019 {
1020 self::log(sprintf('Processing refund for order #%s', $order_id));
1021
1022 if (!$this->user_logged_in()) {
1023 self::log(__('You must be logged in with your PayPlug account.', 'payplug'), 'error');
1024
1025 return new \WP_Error('process_refund_error', __('You must be logged in with your PayPlug account.', 'payplug'));
1026 }
1027
1028 $order = wc_get_order($order_id);
1029 if (!$order instanceof \WC_Order) {
1030 self::log(sprintf('The order #%s does not exist.', $order_id), 'error');
1031
1032 return new \WP_Error('process_refund_error', sprintf(__('The order %s does not exist.', 'payplug'), $order_id));
1033 }
1034
1035 if ($order->get_status() === 'cancelled') {
1036 self::log(sprintf('The order #%s cannot be refund.', $order_id), 'error');
1037
1038 return new \WP_Error('process_refund_error', sprintf(__('The order %s cannot be refund.', 'payplug'), $order_id));
1039 }
1040
1041 $transaction_id = PayplugWoocommerceHelper::is_pre_30() ? get_post_meta($order_id, '_transaction_id', true) : $order->get_transaction_id();
1042 if (empty($transaction_id)) {
1043 self::log(sprintf('The order #%s does not have PayPlug transaction ID associated with it.', $order_id), 'error');
1044
1045 return new \WP_Error('process_refund_error', __('No PayPlug transaction was found for this order. The refund could not be processed.', 'payplug'));
1046 }
1047
1048 $customer_id = PayplugWoocommerceHelper::is_pre_30() ? $order->customer_user : $order->get_customer_id();
1049
1050 $data = [
1051 'metadata' => [
1052 'order_id' => $order_id,
1053 'customer_id' => ((int) $customer_id > 0) ? $customer_id : 'guest',
1054 'refund_from' => 'woocommerce',
1055 ],
1056 ];
1057
1058 if (!is_null($amount)) {
1059 $data['amount'] = PayplugWoocommerceHelper::get_payplug_amount($amount);
1060 }
1061
1062 if (!empty($reason)) {
1063 $data['metadata']['reason'] = $reason;
1064 }
1065
1066 /**
1067 * Filter the refund data before it's used.
1068 *
1069 * @param array $data
1070 * @param int $order_id
1071 * @param string $transaction_id
1072 */
1073 $data = apply_filters('payplug_gateway_refund_data', $data, $order_id, $transaction_id);
1074
1075 try {
1076 $refund = $this->payplug_api->refund_create($transaction_id, $data);
1077
1078 /**
1079 * Fires once a refund has been created.
1080 *
1081 * @param int $order_id Order ID
1082 * @param RefundResource $refund Refund resource
1083 * @param string $transaction_id Transaction id
1084 */
1085 \do_action('payplug_gateway_refund_created', $order_id, $refund, $transaction_id);
1086
1087 $refund_meta_key = sprintf('_pr_%s', wc_clean($refund->id));
1088 if (PayplugWoocommerceHelper::is_pre_30()) {
1089 update_post_meta($order_id, $refund_meta_key, $refund->id);
1090 } else {
1091 $order->add_meta_data($refund_meta_key, $refund->id, true);
1092 $order->save();
1093 }
1094
1095 $note = sprintf(__('Refund %s : Refunded %s', 'payplug'), wc_clean($refund->id), wc_price(((int) $refund->amount) / 100));
1096 if (!empty($refund->metadata['reason'])) {
1097 $note .= sprintf(' (%s)', esc_html($refund->metadata['reason']));
1098 }
1099 $order->add_order_note($note);
1100
1101 try {
1102 $payment = $this->payplug_api->payment_retrieve($transaction_id);
1103 $metadata = PayplugWoocommerceHelper::extract_transaction_metadata($payment);
1104 PayplugWoocommerceHelper::save_transaction_metadata($order, $metadata);
1105 } catch (\Exception $e) {
1106 }
1107
1108 self::log('Refund process complete for the order.');
1109
1110 return true;
1111 } catch (HttpException $e) {
1112 self::log(sprintf('Refund request error for the order %s from PayPlug API : %s', $order_id, wc_print_r($e->getErrorObject(), true)), 'error');
1113
1114 return new \WP_Error('process_refund_error', __('The transaction could not be refunded. Please try again.', 'payplug'));
1115 } catch (\Exception $e) {
1116 self::log(sprintf('Refund request error for the order %s : %s', $order_id, wc_clean($e->getMessage())), 'error');
1117
1118 return new \WP_Error('process_refund_error', __('The transaction could not be refunded. Please try again.', 'payplug'));
1119 }
1120 }
1121
1122 /**
1123 * Check the order amount to ensure it's on the allowed range.
1124 *
1125 * @param int $amount
1126 *
1127 * @return int|\WP_Error
1128 */
1129 public function validate_order_amount($amount)
1130 {
1131 if (
1132 $amount < PayplugWoocommerceHelper::get_minimum_amount()
1133 || $amount > PayplugWoocommerceHelper::get_maximum_amount()
1134 ) {
1135 return new \WP_Error(
1136 'invalid order amount',
1137 sprintf(__('Payments for this amount (%s) are not authorised with this payment gateway.', 'payplug'), \wc_price($amount / 100))
1138 );
1139 }
1140
1141 return $amount;
1142 }
1143
1144 /**
1145 * Limit string length.
1146 *
1147 * @param string $value
1148 * @param int $maxlength
1149 *
1150 * @return string
1151 */
1152 public function limit_length($value, $maxlength = 100)
1153 {
1154 return (strlen($value) > $maxlength) ? substr($value, 0, $maxlength) : $value;
1155 }
1156
1157 /**
1158 * Get user's keys.
1159 *
1160 * @param string $email
1161 * @param string $password
1162 *
1163 * @return array|\WP_Error
1164 */
1165 public function retrieve_user_api_keys($email, $password)
1166 {
1167 if (empty($email) || empty($password)) {
1168 return new \WP_Error('missing_login_data', __('Please fill all login fields', 'payplug'));
1169 }
1170
1171 try {
1172 $response = Authentication::getKeysByLogin($email, $password);
1173 if (empty($response) || !isset($response['httpResponse']) && 'payplug' === $this->id) {
1174 return new \WP_Error('invalid_credentials', __('Invalid credentials.', 'payplug'));
1175 }
1176
1177 return $response['httpResponse']['secret_keys'];
1178 } catch (HttpException $e) {
1179 if ('payplug' === $this->id) {
1180 return new \WP_Error('invalid_credentials', __('Invalid credentials.', 'payplug'));
1181 }
1182 }
1183 }
1184
1185 /**
1186 * Get user merchant id.
1187 *
1188 * This method might be called during the login process before the global PayPlug
1189 * configuration is set. In that case you can pass a valid token to make the request.
1190 *
1191 * @param string|null $key
1192 *
1193 * @return string
1194 */
1195 public function retrieve_merchant_id($key = null)
1196 {
1197 $merchant_id = '';
1198 try {
1199 $response = !is_null($key) && !empty($key) ? Authentication::getAccount(new Payplug($key)) : Authentication::getAccount();
1200 PayplugWoocommerceHelper::set_transient_data($response);
1201 $merchant_id = isset($response['httpResponse']['id']) ? $response['httpResponse']['id'] : '';
1202 } catch (ConfigurationException $e) {
1203 self::log(sprintf('Missing API key for PayPlug client : %s', wc_print_r($e->getMessage(), true)), 'error');
1204 } catch (HttpException $e) {
1205 self::log(sprintf('Account request error from PayPlug API : %s', wc_print_r($e->getErrorObject(), true)), 'error');
1206 PayplugWoocommerceHelper::exception_handler_400_logout($e->getCode(), '', sprintf('Account request error from PayPlug API : %s', wc_print_r($e->getMessage(), true)));
1207 } catch (\Exception $e) {
1208 self::log(sprintf('Account request error : %s', wc_clean($e->getMessage())), 'error');
1209 }
1210
1211 return $merchant_id;
1212 }
1213
1214 /**
1215 * Get PayPlug gateway mode.
1216 *
1217 * @return string
1218 */
1219 public function get_current_mode()
1220 {
1221 return $this->get_configuration()->get_option('mode');
1222 }
1223
1224 /**
1225 * Get user API key.
1226 *
1227 * @param string $mode
1228 *
1229 * @return string
1230 */
1231 public function get_api_key($mode = 'test')
1232 {
1233 return $this->get_api()->get_bearer_token($mode);
1234 }
1235
1236 /**
1237 * Get current merchant id.
1238 *
1239 * @return string
1240 */
1241 public function get_merchant_id()
1242 {
1243 return $this->get_configuration()->get_option('company_id');
1244 }
1245
1246 /**
1247 * Check if user is logged in and we have an API key for TEST mode.
1248 *
1249 * @return bool
1250 */
1251 public function user_logged_in()
1252 {
1253 $options = $this->get_configuration()->get_options();
1254 if (empty($options) || !isset($options['api_key']) || !isset($options['jwt'])) {
1255 return false;
1256 }
1257
1258 $jwt = json_decode($options['jwt'], true);
1259 if (!empty($jwt) && isset($jwt['test']) && isset($jwt['test']['access_token'])) {
1260 return true;
1261 }
1262
1263 $api_key = json_decode($options['api_key'], true);
1264
1265 return !empty($api_key) && isset($api_key['test']) && !empty($api_key['test']);
1266 }
1267
1268 /**
1269 * Check if oneclick payment is activated and merchant can use it.
1270 *
1271 * @return bool
1272 */
1273 public function save_card_available()
1274 {
1275 return 'payplug' == $this->id && $this->user_logged_in()
1276 && $this->save_card
1277 && $this->permissions->has_permissions(PayplugPermissions::SAVE_CARD);
1278 }
1279
1280 /**
1281 * Check if the gatteway is allowed for the order amount
1282 *
1283 * @param array
1284 *
1285 * @return array
1286 */
1287 public function check_gateway($gateways)
1288 {
1289 if (!empty(WC()->cart) && isset($gateways[$this->id]) && $gateways[$this->id]->id == $this->id) {
1290 $order_amount = $this->get_order_total();
1291 foreach ($gateways[$this->id]->settings['payment_methods']['permissions'] as $key => &$permission) {
1292 $method_amounts = json_decode($permission['amounts'], true);
1293 if ($order_amount < $method_amounts['min']['EUR'] / 100 || $order_amount > $method_amounts['max']['EUR'] / 100) {
1294 unset($gateways[$key]);
1295 }
1296 }
1297 }
1298
1299 if ((bool) $this->get_configuration()->get_option('payment_methods.configuration.oney.with_fees')) {
1300 unset($gateways['oney_x3_without_fees']);
1301 unset($gateways['oney_x4_without_fees']);
1302 } else {
1303 unset($gateways['oney_x3_with_fees']);
1304 unset($gateways['oney_x4_with_fees']);
1305 }
1306
1307 return $gateways;
1308 }
1309
1310 /**
1311 * Can the order be refunded via this gateway?
1312 *
1313 *
1314 * @param WC_Order $order Order object.
1315 *
1316 * @return bool If false, the automatic refund button is hidden in the UI.
1317 */
1318 public function can_refund_order($order)
1319 {
1320 $status = $order->get_status();
1321
1322 return $order && $this->supports('refunds') && $status !== 'cancelled' && $status !== 'failed';
1323 }
1324 }
1325