PluginProbe
PayPlug for WooCommerce (Official) / 3.1.0
PayPlug for WooCommerce (Official) v3.1.0
3.1.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 All 102 releases
← All changes | src/PayplugWoocommerceRequest.php +157 -27 2.18.0 → 3.1.0 View file →
@@ -3,8 +3,9 @@
3 3 namespace Payplug\PayplugWoocommerce;
4 4
5 5 // Exit if accessed directly
6 6 use Automattic\WooCommerce\Utilities\OrderUtil;
7 +use Payplug\Exception\HttpException;
7 8 use Payplug\PayplugWoocommerce\Gateway\PayplugAddressData;
8 9 use Payplug\PayplugWoocommerce\Gateway\PayplugGateway;
9 10 use Payplug\PayplugWoocommerce\Traits\ServiceGetter;
10 11
@@ -51,8 +52,9 @@
51 52 add_action('wc_ajax_payplug_create_order', [$this, 'ajax_create_order']);
52 53 add_action('wc_ajax_applepay_update_payment', [$this, 'applepay_update_payment']);
53 54 add_action('wc_ajax_applepay_get_order_totals', [$this, 'applepay_get_order_totals']);
54 55 add_action('wc_ajax_payplug_order_review_url', [$this, 'ajax_create_payment']);
56 + add_action('wc_ajax_payplug_apple_pay_create_order_pay', [$this, 'ajax_apple_pay_create_order_pay']);
55 57 add_action('wc_ajax_payplug_check_payment', [$this, 'check_payment']);
56 58 add_action('wc_ajax_payplug_create_intent', [$this, 'create_payment_intent']);
57 59 }
58 60
@@ -59,9 +61,9 @@
59 61 /**
60 62 * Sets the WC customer session if one is not set.
61 63 * This is needed so nonces can be verified by AJAX Request.
62 64 */
63 - public function set_session()
65 + public function set_session(): void
64 66 {
65 67 if (!is_product() || (isset(WC()->session) && WC()->session->has_session())) {
66 68 return;
67 69 }
@@ -78,9 +80,9 @@
78 80
79 81 /**
80 82 * Create the woocommerce order in the BO
81 83 */
82 - public function ajax_create_order()
84 + public function ajax_create_order(): void
83 85 {
84 86 if (WC()->cart->is_empty()) {
85 87 wp_send_json_error(__('Empty cart', 'payplug'));
86 88 }
@@ -94,15 +96,100 @@
94 96 die(0);
95 97 }
96 98
97 99 /**
100 + * Process Apple Pay payment for an existing order on the order-pay page.
101 + */
102 + public function ajax_apple_pay_create_order_pay(): void
103 + {
104 + if (!check_ajax_referer('woocommerce-process_checkout', 'woocommerce-process-checkout-nonce', false)) {
105 + wp_send_json([
106 + 'result' => 'failure',
107 + 'messages' => '<ul class="woocommerce-error"><li>' . __('Invalid order.', 'payplug') . '</li></ul>',
108 + ]);
109 +
110 + return;
111 + }
112 +
113 + $order_id = isset($_POST['order_id']) ? absint(wp_unslash($_POST['order_id'])) : 0;
114 + $order_key = isset($_POST['order_key']) ? wc_clean(wp_unslash($_POST['order_key'])) : '';
115 +
116 + $order = $order_id ? wc_get_order($order_id) : null;
117 + if (!$order || !hash_equals($order->get_order_key(), $order_key)) {
118 + wp_send_json([
119 + 'result' => 'failure',
120 + 'messages' => '<ul class="woocommerce-error"><li>' . __('Invalid order.', 'payplug') . '</li></ul>',
121 + ]);
122 +
123 + return;
124 + }
125 +
126 + // This AJAX request's own URL never carries the order-pay query var (only the page
127 + // that triggered it does), but WC_Payment_Gateway::get_order_total() - used by this
128 + // plugin's own check_gateway() filter on woocommerce_available_payment_gateways to
129 + // enforce per-method amount permissions - reads that query var to know whether to use
130 + // the order's total or the (here empty, on order-pay) cart's. Left unset, it falls
131 + // back to a cart total of 0, which the amount-permission check then rejects, making
132 + // Apple Pay appear unavailable below. Setting it restores the normal, fully validated
133 + // availability check (API key, requirements, amount permissions, etc.).
134 + global $wp_query;
135 + $wp_query->set('order-pay', $order_id);
136 +
137 + $available_gateways = WC()->payment_gateways->get_available_payment_gateways();
138 + if (!isset($available_gateways['apple_pay'])) {
139 + wp_send_json([
140 + 'result' => 'failure',
141 + 'messages' => '<ul class="woocommerce-error"><li>' . __('Apple Pay not available.', 'payplug') . '</li></ul>',
142 + ]);
143 +
144 + return;
145 + }
146 +
147 + try {
148 + $result = $available_gateways['apple_pay']->process_payment($order_id);
149 + wp_send_json($result);
150 + } catch (\Exception $e) {
151 + wp_send_json([
152 + 'result' => 'failure',
153 + 'messages' => '<ul class="woocommerce-error"><li>' . esc_html($e->getMessage()) . '</li></ul>',
154 + ]);
155 + }
156 + }
157 +
158 + /**
98 159 * Create the woocommerce order in the BO
99 160 */
100 - public function ajax_create_payment()
161 + public function ajax_create_payment(): void
101 162 {
102 163 global $wp;
103 164
104 - if (WC()->cart->is_empty()) {
165 + $https_referer = wc_clean(wp_unslash($_POST['_wp_http_referer'] ?? ''));
166 + $path = wp_parse_url($https_referer) ?: [];
167 + $output = [];
168 + if (!empty($path['query'])) {
169 + wp_parse_str($path['query'], $output);
170 + }
171 +
172 + if (isset($output['order-pay'])) {
173 + $order_id = absint($output['order-pay']);
174 + } else {
175 + preg_match('/(?<=order-pay\/)\d*/', $path['path'] ?? '', $matches);
176 + $order_id = !empty($matches[0]) ? absint($matches[0]) : 0;
177 + }
178 +
179 + // The referer is client-supplied and can be spoofed: only trust it as an order-pay
180 + // request once the order it names is confirmed real and the key matches, exactly
181 + // like the order-pay AJAX flows below already require (create_payment_intent,
182 + // ajax_apple_pay_create_order_pay). Otherwise fall through as a regular checkout.
183 + $order = $order_id ? wc_get_order($order_id) : false;
184 + if (!$order instanceof \WC_Order || !hash_equals($order->get_order_key(), wc_clean(wp_unslash($output['key'] ?? '')))) {
185 + $order_id = 0;
186 + }
187 +
188 + // Order-pay repays an existing order, whose line items live on the order itself,
189 + // not the session cart - which is legitimately empty here (the customer already
190 + // completed checkout for it), so only require a non-empty cart on a fresh checkout.
191 + if (empty($order_id) && WC()->cart->is_empty()) {
105 192 wp_send_json_error(__('Empty cart', 'payplug'));
106 193 }
107 194
108 195 if (!defined('WOOCOMMERCE_CHECKOUT')) {
@@ -119,19 +206,8 @@
119 206 } else {
120 207 $this->ajax_create_order();
121 208 }
122 209
123 - $https_referer = $_POST['_wp_http_referer'];
124 - $path = parse_url($https_referer);
125 - wp_parse_str($path['query'], $output);
126 -
127 - if (isset($output['order-pay'])) {
128 - $order_id = $output['order-pay'];
129 - } else {
130 - preg_match('/(?<=order-pay\/)\d*/', $path['path'], $matches);
131 - $order_id = $matches[0];
132 - }
133 -
134 210 $this->process_order_payment($order_id, $payment_method);
135 211 }
136 212
137 213 /**
@@ -144,10 +220,21 @@
144 220 *
145 221 * @param int $order_id Order ID.
146 222 * @param string $payment_method Payment method.
147 223 */
148 - protected function process_order_payment($order_id, $payment_method)
224 + protected function process_order_payment($order_id, $payment_method): void
149 225 {
226 + // This AJAX request's own URL never carries the order-pay query var (only the page
227 + // that triggered it does), but WC_Payment_Gateway::get_order_total() - used by this
228 + // plugin's own check_gateway() filter on woocommerce_available_payment_gateways to
229 + // enforce per-method amount permissions - reads that query var to know whether to use
230 + // the order's total or the (here empty, on order-pay) cart's. Left unset, it falls
231 + // back to a cart total of 0, which the amount-permission check then rejects, making
232 + // every gateway appear unavailable below. Setting it restores the normal, fully
233 + // validated availability check (API key, requirements, amount permissions, etc.).
234 + global $wp_query;
235 + $wp_query->set('order-pay', $order_id);
236 +
150 237 $available_gateways = WC()->payment_gateways->get_available_payment_gateways();
151 238
152 239 if (!isset($available_gateways[$payment_method])) {
153 240 return;
@@ -177,9 +264,9 @@
177 264
178 265 /**
179 266 * Update Payplug API Payment for Apple Pay
180 267 */
181 - public function applepay_update_payment()
268 + public function applepay_update_payment(): void
182 269 {
183 270 $payment_id = $_POST['payment_id'];
184 271 $apiService = new \Payplug\PayplugWoocommerce\Service\Api();
185 272 $mode = PayplugWoocommerceHelper::check_mode() ? 'live' : 'test';
@@ -222,9 +309,9 @@
222 309 $update = $payment->update($data);
223 310 wp_send_json_success(['result' => $update->is_paid]);
224 311 }
225 312
226 - public function applepay_get_order_totals()
313 + public function applepay_get_order_totals(): void
227 314 {
228 315 try {
229 316 wp_send_json_success(WC()->cart->total);
230 317 } catch (\Exception $e) {
@@ -235,9 +322,9 @@
235 322
236 323 /**
237 324 * Empty cart for Apple Pay on product page
238 325 */
239 - public function applepay_empty_cart()
326 + public function applepay_empty_cart(): void
240 327 {
241 328 try {
242 329 WC()->cart->empty_cart();
243 330 wp_send_json_success();
@@ -250,9 +337,9 @@
250 337
251 338 /**
252 339 * Add the product on the current page to the cart for Apple Pay on product page
253 340 */
254 - public function applepay_add_to_cart()
341 + public function applepay_add_to_cart(): void
255 342 {
256 343 try {
257 344 if (!empty($_POST['product_id'])) {
258 345 $product_id = $_POST['product_id'];
@@ -285,9 +372,9 @@
285 372 {
286 373 return (strlen($value) > $maxlength) ? substr($value, 0, $maxlength) : $value;
287 374 }
288 375
289 - public function check_payment()
376 + public function check_payment(): void
290 377 {
291 378 global $wpdb;
292 379 $payment_id = $_POST['payment_id'];
293 380 if (empty($payment_id)) {
@@ -430,13 +517,44 @@
430 517
431 518 return $order_id;
432 519 }
433 520
434 - public function create_payment_intent()
521 + public function create_payment_intent(): void
435 522 {
436 - $order_id = $_POST['order_id'];
437 - $this->gateway = $this->get_payplug_gateway($_POST['gateway']);
523 + if (!check_ajax_referer('woocommerce-process_checkout', 'woocommerce-process-checkout-nonce', false)) {
524 + wp_send_json_error(__('Invalid order.', 'payplug'), 403);
525 +
526 + return;
527 + }
528 +
529 + $order_id = isset($_POST['order_id']) ? absint(wp_unslash($_POST['order_id'])) : 0;
530 + $this->gateway = $this->get_payplug_gateway(isset($_POST['gateway']) ? wc_clean(wp_unslash($_POST['gateway'])) : '');
438 531 $order = wc_get_order($order_id);
532 +
533 + if (!$order instanceof \WC_Order || !$this->gateway) {
534 + wp_send_json_error(__('Invalid order.', 'payplug'));
535 +
536 + return;
537 + }
538 +
539 + // On order-pay, closing the payment sheet should keep the customer on the order-pay
540 + // page, not cancel the order and send them to the cart like a fresh checkout attempt
541 + // would.
542 + $is_order_pay = is_wc_endpoint_url('order-pay') || !empty($_POST['order_pay_key']);
543 +
544 + if (!empty($_POST['order_pay_key'])) {
545 + $order_pay_key = wc_clean(wp_unslash($_POST['order_pay_key']));
546 + if (!hash_equals($order->get_order_key(), $order_pay_key)) {
547 + wp_send_json_error(__('Invalid order.', 'payplug'));
548 +
549 + return;
550 + }
551 + }
552 +
553 + $cancel_url = $is_order_pay
554 + ? esc_url_raw($order->get_checkout_payment_url())
555 + : esc_url_raw($order->get_cancel_order_url_raw());
556 +
439 557 $customer_id = PayplugWoocommerceHelper::is_pre_30() ? $order->customer_user : $order->get_customer_id();
440 558 $return_url = esc_url_raw($order->get_checkout_order_received_url());
441 559 $address_data = PayplugAddressData::from_order($order);
442 560 $amount = (int) PayplugWoocommerceHelper::get_payplug_amount($order->get_total());
@@ -476,9 +594,9 @@
476 594 'apple_pay_domain' => $this->gateway->domain_name,
477 595 ])),
478 596 ],
479 597 ];
480 - $payment_data['hosted_payment']['cancel_url'] = esc_url_raw($order->get_cancel_order_url_raw());
598 + $payment_data['hosted_payment']['cancel_url'] = $cancel_url;
481 599 $payment_data['metadata']['applepay_workflow'] = 'checkout';
482 600 }
483 601
484 602 $method = $this->get_configuration()->get_option('payment_methods.configuration.payplug.embedded_mode');
@@ -502,10 +620,22 @@
502 620 * @param PayplugAddressData $address_data
503 621 */
504 622 $payment_data = apply_filters('payplug_gateway_payment_data', $payment_data, $order_id, [], $address_data);
505 623
506 - $payment = $this->gateway->payplug_api->payment_create($payment_data);
624 + try {
625 + $payment = $this->gateway->payplug_api->payment_create($payment_data);
626 + } catch (HttpException $e) {
627 + PayplugGateway::log(sprintf('Error while processing order #%s : %s', $order_id, wc_print_r($e->getErrorObject(), true)), 'error');
628 + wp_send_json_error(__('Payment processing failed. Please retry.', 'payplug'));
507 629
630 + return;
631 + } catch (\Exception $e) {
632 + PayplugGateway::log(sprintf('Error while processing order #%s : %s', $order_id, $e->getMessage()), 'error');
633 + wp_send_json_error(__('Payment processing failed. Please retry.', 'payplug'));
634 +
635 + return;
636 + }
637 +
508 638 // Save transaction id on the order
509 639 PayplugWoocommerceHelper::is_pre_30() ? update_post_meta($order_id, '_transaction_id', $payment->id) : $order->set_transaction_id($payment->id);
510 640
511 641 if (is_callable([$order, 'save'])) {
@@ -518,9 +648,9 @@
518 648 wp_send_json_success([
519 649 'payment_id' => $payment->id,
520 650 'merchant_session' => isset($payment->payment_method['merchant_session']) ? $payment->payment_method['merchant_session'] : null,
521 651 'redirect' => !empty($payment->hosted_payment->payment_url) ? $payment->hosted_payment->payment_url : $return_url,
522 - 'cancel' => esc_url_raw($order->get_cancel_order_url_raw()),
652 + 'cancel' => $cancel_url,
523 653 ]);
524 654 }
525 655
526 656 /**