PluginProbe
HyperPay Payments / 5.3.0
HyperPay Payments v5.3.0
6.6.0 trunk 1.7 1.8 1.8.1 1.8.2 1.8.3 1.8.4 1.8.5 1.9.5 1.9.6 1.9.7 1.9.8 1.9.9 2.0.0 2.1.0 2.2.0 2.3.0 2.3.1 2.3.2 2.3.3 3.0.0 3.0.5 4.0.0 4.0.2 All 34 releases
hyperpay-gateways / src / App / DefaultGateway.php

DefaultGateway.php in HyperPay Payments 5.3.0, at src/App/DefaultGateway.php

1,071 lines 35.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Hyperpay\Gateways\App;
4
5 if ( ! defined( 'ABSPATH' ) ) exit;
6
7
8 use Exception;
9 use Hyperpay\Gateways\Main;
10 use WC_Order;
11 use WC_Payment_Gateway;
12
13
14 /**
15 * Hyperpay main class created to extends from it
16 * when create a new payments Gateways
17 *
18 */
19 class DefaultGateway extends WC_Payment_Gateway
20 {
21 use HyperpayBlocks;
22 /**
23 * Gateway admin options
24 */
25 public $testMode, $title, $trans_type;
26 public $accessToken, $entityId, $brands, $order_status;
27 public $custom_style, $latin_validation;
28 public $currency, $is_arabic, $supported_network;
29 public $server_to_server = false;
30
31
32
33 /**
34 * connecter type on test mode
35 *
36 * @var string INTERNAL|EXTERNAL
37 */
38
39 public $trans_mode = 'INTERNAL';
40 public $description;
41 public $instructions;
42
43 /**
44 * if payments have direct fields on checkout page
45 *
46 * @var boolean
47 */
48 public $has_fields = false;
49
50 /**
51 * used to display the invoice id
52 * at success order page
53 * @var string|null
54 */
55 public $invoice_id;
56
57
58
59 /**
60 * Mada BlackBins
61 *
62 * @var array
63 */
64 protected $blackBins = [];
65
66 /**
67 * supported brands thats will showing on settings and checkout page
68 *
69 * @var array
70 */
71 protected $supported_brands = [];
72
73 /**
74 * displayed error msg
75 * @var string
76 */
77 protected $failed_message = '';
78
79 /**
80 * displayed success msg
81 * @var string
82 */
83 protected $success_message = '';
84
85 public $NONCE = '';
86
87 /**
88 * regular expressions
89 * @var string
90 */
91
92 public $successCodePattern = '/^(000\.000\.|000\.100\.1|000\.[36])/';
93 public $successManualReviewCodePattern = '/^(000\.400\.0|000\.400\.100)/';
94 public $pendingCodePattern = '/^(800\.400\.5|100\.400\.500)/';
95
96 /**
97 * CopyAndPay script URL
98 *
99 * @var string
100 */
101 protected $script_url = "https://eu-prod.oppwa.com/v1/paymentWidgets.js?checkoutId=";
102
103 /**
104 * CopyAndPay prepare checkout link
105 *
106 * @method POST
107 * @var string
108 */
109 protected $token_url = "https://eu-prod.oppwa.com/v1/checkouts";
110
111
112 /**
113 * get transaction status
114 * @method GET
115 * @var string
116 *
117 * ##TOKEN## will replace with transaction id when fire the request
118 */
119 protected $transaction_status_url = "https://eu-prod.oppwa.com/v1/checkouts/##TOKEN##/payment";
120
121 /**
122 * back-office end-point
123 * @method GET
124 * @var string
125 *
126 */
127 protected $server_to_server_url = "https://eu-prod.oppwa.com/v1/payments";
128
129 /**
130 * Query transaction report
131 *
132 * @method GET
133 * @var string
134 */
135 protected $query_url = "https://eu-prod.oppwa.com/v1/query";
136
137
138 protected $ACI_base_url = "https://eu-prod.oppwa.com";
139 /**
140 * payment styles that will show in settings
141 *
142 * @var array
143 *
144 */
145 protected $payment_style = [
146 'card' => 'Card',
147 'plain' => 'Plain'
148 ];
149
150
151 function __construct()
152 {
153
154 $this->init_settings(); // <== to get saved settings from database
155 $this->init_form_fields(); // <== render form inside admin panel
156 $this->is_arabic = substr(get_locale(), 0, 2) == 'ar'; // <== to get current locale
157
158 $this->testMode = $this->get_option('testmode'); // <== check if payments on test mode
159 $this->title = $this->get_option('title'); // <== get title from setting
160 $this->trans_type = $this->get_option('trans_type'); // <== get transaction type [DB / Pre-Auth] from setting
161 $this->accessToken = $this->get_option('accesstoken'); // <== get access toke from setting
162 $this->entityId = $this->getEntity(); // <== get entityId from setting
163
164 $this->brands = is_array($this->get_option('hyper_pay_brands')) ? $this->get_option('hyper_pay_brands') : [$this->get_option('hyper_pay_brands')]; // <== get brands from setting
165
166 $this->payment_style = $this->get_option('payment_style'); // <== get style from setting
167
168 $this->order_status = $this->get_option('order_status'); // <== get order status after success from setting
169 $this->custom_style = $this->get_option('custom_style'); // <== get custom style from setting
170
171 $this->description = __('All transactions are processed in a secure environment.', 'hyperpay-gateways');
172
173
174 $this->latin_validation = $this->get_option('latin_validation'); // <== get custom style from setting
175 $this->currency = get_woocommerce_currency();
176
177 $this->NONCE = \md5(wp_rand(1111111111,9999999999));
178
179
180 /**
181 * if test mode is one
182 * overwrite currents URLs ti test URLs
183 */
184 if ($this->testMode) {
185 $this->query_url = "https://eu-test.oppwa.com/v1/query";
186 $this->token_url = "https://eu-test.oppwa.com/v1/checkouts";
187 $this->script_url = "https://eu-test.oppwa.com/v1/paymentWidgets.js?checkoutId=";
188 $this->transaction_status_url = "https://eu-test.oppwa.com/v1/checkouts/##TOKEN##/payment";
189 $this->server_to_server_url = "https://eu-test.oppwa.com/v1/payments";
190 $this->ACI_base_url = "https://eu-test.oppwa.com";
191 }
192
193 $this->query_url .= "?entityId=" . $this->entityId;
194 $this->transaction_status_url .= "?entityId=" . $this->entityId;
195
196 /**
197 * default failed message
198 * @var string
199 */
200 $this->failed_message = __('Your transaction not completed .', 'hyperpay-gateways');
201 $this->success_message = __('Your payment has been processed successfully.', 'hyperpay-gateways');
202
203 /**
204 * overwrite default update function
205 *
206 * @param woocommerce_update_options_payment_gateways_<payment_id>
207 * @param array[class,function_name]
208 */
209
210 if (!has_action("woocommerce_update_options_payment_gateways_{$this->id}")) {
211 add_action('woocommerce_update_options_payment_gateways_' . $this->id, array($this, 'process_admin_options'));
212 }
213
214
215 /**
216 * prepare checkout form
217 *
218 * @param string woocommerce_receipt_<payments_id>
219 * @param array[class,function_name]
220 */
221
222 if (!has_action("woocommerce_receipt_{$this->id}")) {
223 add_action("woocommerce_receipt_{$this->id}", [$this, 'receipt_page']);
224 }
225
226
227 /**
228 * set payments icon from src/assets/images/BRAND-log.png
229 *
230 * make sure when add new image to rename image according this format BRAND_NAME-logo.svg
231 *
232 * @param string woocommerce_gateway_icon
233 * @param array[class,function_name]
234 *
235 */
236 add_filter('woocommerce_gateway_icon', [$this, 'set_icons'], 10, 2);
237
238 /**
239 * to include src/assets/js/admin.js <JavaScript>
240 *
241 * @param string admin_enqueue_scripts
242 * @param array[class,function_name]
243 *
244 */
245 add_action('admin_enqueue_scripts', [$this, 'admin_script']);
246
247 add_action("woocommerce_thankyou_order_received_text", [$this, "order_received_text"], 10, 2);
248
249 add_action('before_woocommerce_pay', [$this, 'action_before_woocommerce_pay'], 10, 0);
250
251 if (!has_action("woocommerce_order_action_capture_payment")) {
252 add_action('woocommerce_order_action_capture_payment', [$this, 'capture_payment']);
253 }
254 }
255
256
257
258 public function getEntity()
259 {
260 $available_currencies = $this->get_option('currencies_ids');
261 $current_currency = get_woocommerce_currency();
262
263 if (isset($available_currencies[$current_currency])) {
264 return $available_currencies[$current_currency];
265 }
266
267 return $this->get_option('entityId');
268 }
269
270
271 public function process_admin_options()
272 {
273 $this->init_settings();
274
275 $post_data = $this->get_post_data();
276
277 foreach ($this->get_form_fields() as $key => $field) {
278 if ('title' !== $this->get_field_type($field)) {
279 try {
280 $this->settings[$key] = $this->get_field_value($key, $field, $post_data);
281 if ('select' === $field['type'] || 'checkbox' === $field['type']) {
282 /**
283 * Notify that a non-option setting has been updated.
284 *
285 * @since 7.8.0
286 */
287 do_action(
288 'woocommerce_update_non_option_setting',
289 array(
290 'id' => $key,
291 'type' => $field['type'],
292 'value' => $this->settings[$key],
293 )
294 );
295 } elseif ('currencies_ids_field' === $key && isset($post_data['currencies_ids'])) {
296 $this->settings["currencies_ids"] = array_reduce($post_data['currencies_ids'], function ($result, $item) {
297 if ($item['value'] && $item['name'])
298 $result[$item['name']] = $item['value'];
299 return $result;
300 }, array());
301 }
302 } catch (Exception $e) {
303 $this->add_error($e->getMessage());
304 }
305 }
306 }
307
308 $option_key = $this->get_option_key();
309 do_action('woocommerce_update_option', array('id' => $option_key)); // phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
310 return update_option($option_key, apply_filters('woocommerce_settings_api_sanitized_fields_' . $this->id, $this->settings), 'yes'); // phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
311 }
312
313 public function capture_payment($order)
314 {
315
316 $uniqueId = $order->get_meta('transaction_id');
317 $url = $this->server_to_server_url . $uniqueId;
318
319 $orderAmount = number_format($order->get_total(), 2, '.', '');
320 $amount = number_format(round($orderAmount, 2), 2, '.', '');
321
322 $gateway_name = 'WC_' . ucfirst($order->get_payment_method()) . "_Gateway";
323 $gateway = new $gateway_name();
324
325 $data = [
326 'headers' => [
327 "Authorization" => "Bearer {$gateway->accessToken}"
328 ],
329 'body' => [
330 "entityId" => $gateway->entityId,
331 "amount" => $amount,
332 "currency" => $gateway->currency,
333 "paymentType" => 'CP',
334 ]
335 ];
336
337 $response = Http::post($url, $data);
338 $resultCode = $response['result']['code'] ?? '';
339
340 if (preg_match($this->successCodePattern, $resultCode) || preg_match($this->successManualReviewCodePattern, $resultCode)) {
341 $order->add_order_note("Captured Successfully");
342 $order->update_status($this->order_status);
343 } else {
344 $order->add_order_note("Captured Failed" . $resultCode['result']['description'] ?? 'Unknown reason');
345 }
346
347 $location = sanitize_url($_SERVER['HTTP_REFERER']);
348 wp_safe_redirect($location);
349 exit();
350 }
351
352 public function action_before_woocommerce_pay()
353 {
354 global $wp;
355
356 $order_id = absint($wp->query_vars['order-pay']); // The order ID
357
358 $order = wc_get_order($order_id);
359
360 if ($order->has_status('on-hold')) {
361 $order->update_status('pending');
362 } elseif ($order->has_status($this->order_status)) {
363 wp_safe_redirect($this->get_return_url($order));
364 exit();
365 }
366 }
367
368 public function order_received_text($thanks_text, $order)
369 {
370
371 $msg = $order->get_meta('gateway_note');
372 if ($order->get_payment_method() == $this->id && $order->get_status() == 'on-hold' && !empty($msg)) {
373 wc_add_notice($msg, "notice");
374 wc_print_notices();
375 } else {
376 return $thanks_text;
377 }
378 }
379
380 /**
381 * for validate settings form
382 * @return void
383 */
384
385 public function admin_script(): void
386 {
387 global $current_tab, $current_section;
388
389 /**
390 * to make sure load admin.js just when currents payments opened
391 *
392 */
393 if ($current_tab == 'checkout' && $current_section == $this->id) {
394
395 $data = [
396 'id' => $this->id,
397 'url' => $this->token_url,
398 'code_setting' => wp_enqueue_code_editor(['type' => 'text/css'])
399 ];
400
401 wp_enqueue_script('hyperpay_admin', HYPERPAY_PLUGIN_DIR . '/src/assets/js/admin.js', ['jquery'], '1.0.0', true);
402 wp_localize_script('hyperpay_admin', 'hyperpay_data', $data);
403 }
404 }
405
406
407 public function iconSrc()
408 {
409 $icons = [];
410 foreach ($this->brands as $brand) {
411 $img = HYPERPAY_PLUGIN_DIR . '/src/assets/images/default.png';
412 if (file_exists(Main::ROOT_PATH . '/assets/images/' . esc_attr($brand) . "-logo.svg"))
413 $img = HYPERPAY_PLUGIN_DIR . '/src/assets/images/' . esc_attr($brand) . "-logo.svg";
414
415 $icons[] = $img;
416 }
417 return $icons;
418 }
419
420 /**
421 * to set payment icon based on supported brands
422 *
423 * @param string $icon
424 * @param string $id current payment id
425 *
426 * @return string $icon new icon
427 *
428 */
429
430 public function set_icons($icon, $id)
431 {
432
433 if ($id == $this->id) {
434 $icons = "";
435 foreach ($this->iconSrc() as $src) {
436 $icons .= "<img style='padding:2px ; ' src='$src' >";
437 }
438 return $icons;
439 }
440
441 return $icon;
442 }
443
444 /**
445 * Here you can define all fields thats will showing in setting page
446 * @return void
447 */
448 public function init_form_fields(): void
449 {
450
451 $this->form_fields = [
452 'enabled' => [
453 'title' => __('Enable/Disable', 'hyperpay-gateways'),
454 'type' => 'checkbox',
455 'label' => __('Enable Payment Module.', 'hyperpay-gateways'),
456 'default' => 'no'
457 ],
458 'testmode' => [
459 'title' => __('Test mode', 'hyperpay-gateways'),
460 'type' => 'select',
461 'options' => ['0' => __('Off', 'hyperpay-gateways'), '1' => __('On', 'hyperpay-gateways')]
462 ],
463 'title' => [
464 'title' => __('Title:', 'hyperpay-gateways'),
465 'type' => 'text',
466 'description' => __('This controls the title which the user sees during checkout.', 'hyperpay-gateways'),
467 'default' => $this->method_title ?? __('Credit Card', 'hyperpay-gateways')
468 ],
469 'trans_type' => [
470 'title' => __('Transaction type', 'hyperpay-gateways'),
471 'type' => 'select',
472 'options' => $this->get_hyperpay_trans_type(),
473 ],
474 'accesstoken' => [
475 'title' => __('Access Token', 'hyperpay-gateways'),
476 'type' => 'text',
477 ],
478 'entityId' => [
479 'title' => __('Entity ID', 'hyperpay-gateways'),
480 'type' => 'text',
481 'description' => __('This will used as default if multi-currency not configured', 'hyperpay-gateways'),
482 ],
483 'currencies_ids_field' => [
484 'custom_attributes' => [
485 'data-currencies' => $this->get_option("currencies_ids") ? json_encode($this->get_option("currencies_ids")) : null,
486 'data-currencies_list' => json_encode(array_keys(get_woocommerce_currencies()))
487 ],
488 'title' => __('Multi-currency', 'hyperpay-gateways'),
489 'type' => 'hidden',
490 'description' => __('In case you have a multi-currency store', 'hyperpay-gateways'),
491 ],
492 'secret' => [
493 'title' => __('Webhook Key', 'hyperpay-gateways'),
494 'type' => 'text',
495 ],
496 '_webhock' => [
497 'title' => __('Webhook URL', 'hyperpay-gateways'),
498 'type' => 'text',
499 'class' => 'disabled',
500 'default' => get_site_url() . "/?rest_route=/hyperpay/v1/" . \str_replace("\\", "/", get_class($this))
501 ],
502 'hyper_pay_brands' => [
503 'title' => __('Brands', 'hyperpay-gateways'),
504 'class' => count($this->supported_brands) !== 1 ?: 'disabled',
505 'type' => count($this->supported_brands) > 1 ? 'multiselect' : 'select',
506 'options' => $this->supported_brands,
507 ],
508 'payment_style' => [
509 'title' => __('Payment Style', 'hyperpay-gateways'),
510 'type' => 'select',
511 'class' => count($this->payment_style) !== 1 ?: 'disabled',
512 'options' => $this->payment_style,
513 'default' => 'plain'
514 ],
515 'custom_style' => [
516 'title' => __('Custom Style', 'hyperpay-gateways'),
517 'type' => 'textarea',
518 'description' => 'Input custom css for payment (Optional)',
519 'class' => 'hyperpay_custom_style'
520 ],
521 'latin_validation' => [
522 'title' => __('Enable Input validation (Accept English Characters only)', 'hyperpay-gateways'),
523 'type' => 'checkbox',
524 'label' => __('Yes' , 'hyperpay-gateways'),
525 'default' => 'yes',
526 'description' => __('Disable this option may cause transaction declined by bank due to 3DSecure', 'hyperpay-gateways'),
527 ],
528 'order_status' => [
529 'title' => __('Status Of Order', 'hyperpay-gateways'),
530 'type' => 'select',
531 'options' => $this->get_order_status(),
532 'description' => __("select order status after success transaction.", 'hyperpay-gateways')
533 ],
534
535 ];
536 }
537
538
539 /**
540 * to fill order_status select fields
541 *
542 * @return array
543 */
544 function get_order_status(): array
545 {
546 $order_status = [
547
548 'processing' => __('Processing', 'hyperpay-gateways'),
549 'completed' => __('Completed', 'hyperpay-gateways')
550 ];
551
552 return $order_status;
553 }
554
555 /**
556 * to fill trans_type select fields
557 *
558 * @return array
559 */
560 function get_hyperpay_trans_type(): array
561 {
562 $hyperpay_trans_type = [
563 'DB' => 'Debit',
564 'PA' => 'Pre-Authorization'
565 ];
566
567 return $hyperpay_trans_type;
568 }
569
570
571 /**
572 * This function fire when click on Place order at checkout page
573 * @param int $order_id
574 *
575 * @return void
576 */
577 function receipt_page($order_id)
578 {
579 $order = new WC_Order($order_id);
580
581 // if we have id param that mean the page result ACI redirection
582 if (isset($_GET['resourcePath'])) {
583 $resourcePath = sanitize_text_field($_GET['resourcePath']);
584 $url = $this->ACI_base_url . $resourcePath;
585 // set header request to contain access token
586 $auth = [
587 'headers' => ['Authorization' => 'Bearer ' . $this->accessToken],
588 "body" => [
589 "entityId" => $this->entityId
590 ]
591 ];
592
593 $response = Http::get($url, $auth);
594
595
596
597 //Dynamic fire function based on status
598 $status = $this->check_status($response);
599 return $this->$status($order, $response);
600 } elseif ($result = $this->prepareCheckout($order_id)) { // process a new transaction
601 return $this->renderPaymentForm($order, $result);
602 }
603 }
604
605 private function isJson($string)
606 {
607 json_decode($string);
608 return json_last_error() === JSON_ERROR_NONE;
609 }
610
611 /**
612 *
613 * render CopyAndPay form
614 * @param WC_Order $order
615 * @param string $token
616 * @return void
617 */
618 public function renderPaymentForm(WC_Order $order, $result)
619 {
620
621 if ($this->server_to_server) {
622 $redirect = $result['response']['redirect'];
623 $extra_params = [];
624 $query_url = wp_parse_url($redirect['url'], PHP_URL_QUERY);
625 if ($query_url)
626 parse_str(wp_parse_url($redirect['url'], PHP_URL_QUERY), $extra_params);
627
628
629 return View::render('server-to-server.html', \compact('redirect', 'extra_params'));
630 }
631
632 $token = $result['response']['id'];
633 $postBackURL = $result['postBackURL'];
634
635
636 $scriptURL = $this->script_url;
637 $scriptURL .= $token;
638
639 $payment_brands = $this->brands;
640
641 if (is_array($this->brands))
642 $payment_brands = implode(' ', $this->brands);
643
644
645 $dataObj = [
646 'is_arabic' => esc_js($this->is_arabic),
647 'style' => esc_html($this->payment_style),
648 'postBackURL' => ($postBackURL),
649 'payment_brands' => esc_html($payment_brands),
650 'custom_style' => esc_html($this->custom_style),
651 'scriptURL' => esc_html($scriptURL),
652 'integrity' => esc_html($result['integrity'] ?? ''),
653 'nonce' => $this->NONCE
654 ];
655
656
657 if ($this->supported_network) {
658 $dataObj['supported_network'] = $this->supported_network;
659 }
660
661
662 add_action('wp_head', [$this, 'custom_add_to_head']);
663
664 $custom_style = $this->custom_style;
665 return View::render('copy-and-pay.html', compact('dataObj'));
666 }
667
668 public function custom_add_to_head()
669 {
670
671 $data = [
672 'url' => $this->ACI_base_url,
673 'home' => get_home_url(),
674 'nonce' => $this->NONCE
675 ];
676
677 return View::render('header.html' , \compact('data'));
678 }
679
680
681 /**
682 * Process the payment and return the result
683 * @param int $order_id
684 * @return array[redirect,token,result]
685 *
686 */
687 public function process_payment($order_id)
688 {
689
690 $order = new WC_Order($order_id);
691 /**
692 *
693 * validate data to prevent arabic character
694 */
695
696 if ($this->latin_validation == 'yes') {
697 $firstName = $order->get_billing_first_name();
698 $family = $order->get_billing_last_name();
699 $street = $order->get_billing_address_1();
700 $city = $order->get_billing_city();
701 $email = $order->get_billing_email();
702
703
704 $data_to_validate = [
705 'first name' => $firstName,
706 'last name' => $family,
707 'street' => $street,
708 'city' => $city,
709 'email' => $email,
710 ];
711
712 if ($order->get_billing_state()) {
713 $data_to_validate['state'] = $order->get_billing_state();
714 }
715
716
717 // raise a validation error if validation valid
718 $this->validate_form($data_to_validate);
719 }
720
721
722 return [
723 'result' => 'success',
724 'redirect' => $order->get_checkout_payment_url(true)
725 ];
726 }
727
728 public function getCheckoutData($order_id)
729 {
730 $order = new WC_Order($order_id);
731
732 $shipping_cost = number_format($order->get_shipping_total(), 2, '.', '');
733 $amount = number_format($order->get_total(), 2, '.', '');
734 $transactionKey = wp_rand(11111111, 99999999);
735
736 $postBackURL = $order->get_checkout_payment_url(true);
737
738
739 $postBackURL .= wp_parse_url($postBackURL, PHP_URL_QUERY) ? '&' : '?';
740 $postBackURL .= 'callback=true';
741 $postBackURL .= "&transaction-key=$transactionKey";
742
743 $data = [
744 'headers' => [
745 "Authorization" => "Bearer {$this->accessToken}"
746 ],
747 'body' => [
748 "entityId" => $this->entityId,
749 "amount" => $amount,
750 "currency" => $this->currency,
751 "paymentType" => $this->trans_type,
752 "merchantTransactionId" => $order_id . "I" . $transactionKey,
753 "customer.email" => $order->get_billing_email(),
754 "notificationUrl" => $order->get_checkout_payment_url(true),
755 "customParameters[bill_number]" => $order_id,
756 "customer.givenName" => $order->get_billing_first_name(),
757 "customer.surname" => $order->get_billing_last_name(),
758 "billing.street1" => $order->get_billing_address_1(),
759 "billing.city" => $order->get_billing_city(),
760 "billing.state" => $order->get_billing_state(),
761 "billing.country" => $order->get_billing_country(),
762 "billing.postcode" => $order->get_billing_postcode(),
763 "shipping.postcode" => $order->get_billing_postcode(),
764 "shipping.street1" => $order->get_billing_address_1(),
765 "shipping.city" => $order->get_billing_city(),
766 "shipping.state" => $order->get_billing_state(),
767 "shipping.country" => $order->get_billing_country(),
768 "shipping.cost" => $shipping_cost,
769 "customParameters[branch_id]" => '1',
770 "customParameters[teller_id]" => '1',
771 "customParameters[device_id]" => '1',
772 "customParameters[plugin]" => 'wordpress',
773 "locale" => get_locale(),
774 "integrity" => true,
775 'customParameters' => [
776 '3DS2_enrolled' => \true
777 ]
778
779 ]
780 ];
781
782
783 if ($this->testMode) {
784 $data['body']["testMode"] = $this->trans_mode;
785 }
786
787 if ($this->server_to_server) {
788 $data['body']['shopperResultUrl'] = $postBackURL;
789 $data['body']['paymentBrand'] = $this->brands[0];
790 }
791
792
793
794 // add extra parameters if exists
795 return [
796 "data" => array_replace_recursive($data, $this->setExtraData($order)),
797 "postBackURL" => $postBackURL
798 ];
799 }
800
801 public function prepareCheckout($order_id)
802 {
803
804 // set data to post
805 $url = $this->server_to_server ? $this->server_to_server_url : $this->token_url;
806
807 $checkout = $this->getCheckoutData($order_id);
808
809 // HTTP Request to oppwa to get checkout
810 $response = Http::post($url, $checkout['data']);
811
812 if (!\preg_match("/^(000\.200)/", $response['result']['code'] ?? '')) {
813 $this->handleError($response);
814 return false;
815 }
816
817 return [
818 "response" => $response,
819 "postBackURL" => $checkout['postBackURL']
820 ];
821 }
822
823
824 /**
825 * check if all data valid to post {English Characters}
826 * @param array
827 * @return void
828 */
829 function validate_form(array $data)
830 {
831 $errors = [];
832
833 $translations = [
834 'first name' => esc_html__('First Name', 'hyperpay-gateways'),
835 'last name' => esc_html__('Last Name', 'hyperpay-gateways'),
836 'street' => esc_html__('Street', 'hyperpay-gateways'),
837 'state' => esc_html__('State', 'hyperpay-gateways'),
838 'city' => esc_html__('City', 'hyperpay-gateways'),
839 'email' => esc_html__('Email', 'hyperpay-gateways'),
840 ];
841
842 foreach ($data as $key => $field) {
843 if (!preg_match("/^[a-zA-Z0-9-._!`'#%&,:;<>=@{}~\$\(\)\*\+\/\\\?\[\]\^\| +]+$/", $field) || strlen($field) < 2)
844 $errors[$key] = ($translations[$key] ?? esc_html__('Unknown status', 'hyperpay-gateways')) . ' ' . __('format error', 'hyperpay-gateways');
845 }
846
847 if (!preg_match('/^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,})$/i', $data['email'])) {
848 $errors['email'] = __('Email format not valid', 'hyperpay-gateways');
849 }
850
851
852 foreach ($errors as $msg) {
853 if (self::has_checkout_block()) {
854 throw new Exception(esc_html($msg));
855 } else {
856 wc_add_notice('<strong>' . $msg . '</strong>', 'error');
857 }
858 }
859
860 // count equal to zero then no errors and it's valid
861 return count($errors);
862 }
863
864 /**
865 *
866 * GET request to transaction report to check if transaction exists or not
867 * @param int
868 * @return array $response
869 *
870 */
871 public function queryTransactionReport(string $merchantTrxId): array
872 {
873 $url = $this->query_url . "&merchantTransactionId=$merchantTrxId";
874 return Http::get($url, ["headers" => ["Authorization" => "Bearer {$this->accessToken}"]]);
875 }
876
877
878
879 /**
880 *
881 * check the status
882 *
883 * @param array $resultJson
884 * @return string
885 */
886 public function check_status(array $resultJson): string
887 {
888
889 $status = 'failed';
890 $resultCode = $resultJson['result']['code'] ?? '';
891
892 if (preg_match($this->successCodePattern, $resultCode) || preg_match($this->successManualReviewCodePattern, $resultCode)) {
893 $status = 'success';
894 } elseif (preg_match($this->pendingCodePattern, $resultCode)) {
895 $status = "pending";
896 } elseif (isset($resultJson['card']['bin']) && $resultJson['result']['code'] == '800.300.401' && in_array($resultJson['card']['bin'], $this->blackBins)) {
897 $this->failed_message = __('Sorry! Please select "mada" payment option in order to be able to complete your purchase successfully.', 'hyperpay-gateways');
898 }
899
900 return $status;
901 }
902
903
904 /**
905 * handel failed Payments
906 * @param WC_Order $order
907 * @param string $message
908 * @return void
909 */
910 public function failed(WC_Order $order, $resultJson)
911 {
912
913 if (isset($_GET["callback"]) && isset($_GET['transaction-key'])) {
914
915 $hpOrderId = $order->get_id();
916 $transactionKey = sanitize_text_field($_GET['transaction-key']);
917 $merchantTrxId = $hpOrderId . "I" . $transactionKey;
918 $queryResponse = $this->queryTransactionReport($merchantTrxId);
919
920 if (array_key_exists("payments", $queryResponse)) {
921 return $this->processQueryResult($queryResponse, $order);
922 }
923 }
924
925 return $this->handleError($resultJson);
926 }
927
928 public function handleError($resultJson)
929 {
930 $order = wc_get_order(wc_clean(get_query_var('order-pay')));
931
932 $error_code = $resultJson["result"]["code"];
933 $error_description = $resultJson["result"]["description"];
934 $aci_msg = $error_description;
935 $error_list = [];
936
937 if ($error_code == "600.200.500") {
938 $aci_msg = "configuration error";
939 Log::write(["error" => $error_description, "response" => $resultJson]);
940 }
941
942 $order->add_order_note("{$this->failed_message} $error_code : $error_description");
943
944
945 $error_list = $this->getExtended($resultJson);
946
947 if (empty($error_list)) {
948 wc_add_notice($this->failed_message, "error");
949 wc_add_notice($aci_msg, "error");
950 }
951
952 foreach ($error_list as $error) {
953 wc_add_notice($error["error"], "error");
954 $order->add_order_note("extended description : " . $error["error"]);
955 }
956
957 $order->update_status("failed");
958
959 wc_print_notices();
960 }
961
962 public function getExtended($resultJson)
963 {
964 $error_list = [];
965 if (isset($resultJson["resultDetails"]["ExtendedDescription"])) {
966 $resultDetails = $resultJson["resultDetails"]["ExtendedDescription"];
967 if ($this->isJson($resultDetails)) {
968 $resultDetails = json_decode($resultDetails, true);
969
970 if (array_key_exists("details", $resultDetails)) {
971 $error_list[] = $resultDetails["details"];
972 } elseif (array_key_exists("message", $resultDetails)) {
973 $error_list[] = ["error" => $resultDetails['message']];
974 }
975 }
976 }
977
978 return $error_list;
979 }
980
981 /**
982 * check the result of transaction if success of failed
983 *
984 * @param array $resultJson
985 * @param WC_Order $order
986 * @return void
987 */
988 public function processQueryResult(array $resultJson, WC_Order $order)
989 {
990 unset($_GET["callback"]);
991
992 $payment = end($resultJson["payments"]); // get the last transaction
993
994 if (isset($payment["result"]["code"])) {
995 $status = $this->check_status($payment);
996 return $this->$status($order, $payment);
997 }
998 }
999
1000 /**
1001 * set customParameters of requested data
1002 * @param WC_Order $order
1003 * @return array
1004 */
1005 public function setExtraData(WC_Order $order): array
1006 {
1007 return [];
1008 }
1009
1010 /**
1011 * update success order
1012 * @param WC_Order $order
1013 * @param array $resultJson
1014 * @return void
1015 */
1016 public function success(WC_Order $order, $resultJson)
1017 {
1018 global $woocommerce;
1019
1020
1021 $woocommerce->cart->empty_cart();
1022 $uniqueId = $resultJson["id"];
1023
1024 //to add action in order details to capture the pre authorization payments
1025 if (array_key_exists('paymentType', $resultJson) && $resultJson['paymentType'] == "PA") {
1026 $order->add_meta_data("is_pre_authorization", true);
1027 $order->add_meta_data("transaction_id", $uniqueId);
1028 $order->add_order_note("pre authorization transaction, need to capture");
1029 $this->order_status = "on-hold";
1030 }
1031
1032 if (array_key_exists("invoice_id", $resultJson["resultDetails"])) {
1033 $this->invoice_id = $resultJson["resultDetails"]["invoice_id"];
1034 $order->add_meta_data("invoice_id", $this->invoice_id);
1035 $order->add_order_note("invoice id : " . $this->invoice_id);
1036 }
1037
1038 $order->add_order_note($this->success_message . __("Transaction ID: ", "hyperpay-gateways") . esc_html($uniqueId));
1039 $order->update_status($this->order_status);
1040 $order->save();
1041
1042
1043
1044 wp_safe_redirect($this->get_return_url($order));
1045 exit();
1046 }
1047
1048 /**
1049 * update pending order
1050 * @param WC_Order $order
1051 * @param array $resultJson
1052 * @return void
1053 */
1054 public function pending(WC_Order $order, $resultJson)
1055 {
1056 global $woocommerce;
1057
1058
1059 $order->update_status("on-hold");
1060 $order->add_meta_data("gateway_note", __("Transaction is pending confirmation from ", "hyperpay-gateways") . str_replace("hyperpay_", "", $order->get_payment_method()));
1061 $order->save();
1062
1063 $woocommerce->cart->empty_cart();
1064 $uniqueId = $resultJson["id"];
1065
1066 $order->add_order_note("the order waiting gateway confirmation" . __("Transaction ID: ", "hyperpay-gateways") . esc_html($uniqueId));
1067 wp_safe_redirect($this->get_return_url($order));
1068 exit();
1069 }
1070 }
1071