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/Controller/ApplePay.php +116 -30 2.18.0 → 3.1.0 View file →
@@ -3,8 +3,9 @@
3 3 namespace Payplug\PayplugWoocommerce\Controller;
4 4
5 5 use function is_cart;
6 6 use function is_product;
7 +
7 8 use Payplug\Exception\HttpException;
8 9 use Payplug\PayplugWoocommerce\Gateway\PayplugAddressData;
9 10 use Payplug\PayplugWoocommerce\Gateway\PayplugGateway;
10 11 use Payplug\PayplugWoocommerce\PayplugWoocommerceHelper;
@@ -22,9 +23,9 @@
22 23 protected $checkout = false;
23 24
24 25 protected $carriers = [];
25 26
26 - const ENABLE_ON_TEST_MODE = false;
27 + public const ENABLE_ON_TEST_MODE = false;
27 28
28 29 public $image = 'apple-pay-checkout.svg';
29 30
30 31 protected $product = false;
@@ -42,9 +43,9 @@
42 43 $this->has_fields = false;
43 44
44 45 $this->title = __('payplug_apple_pay_title', 'payplug');
45 46 $this->description = '<div id="apple-pay-button-wrapper"><apple-pay-button buttonstyle="black" type="pay" locale="' . get_locale() . '"></apple-pay-button></div>';
46 - $this->domain_name = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : parse_url(home_url(), PHP_URL_HOST);
47 + $this->domain_name = wp_parse_url(home_url(), PHP_URL_HOST);
47 48 $this->enabled = 'no';
48 49
49 50 if ($this->checkApplePay() && is_admin()) {
50 51 $this->enabled = 'yes';
@@ -53,11 +54,13 @@
53 54 $this->enabled = 'yes';
54 55 }
55 56
56 57 if (!is_admin()) {
57 - if (!PayplugWoocommerceHelper::is_checkout_block() && $this->get_button_checkout()) {
58 - $this->add_apple_pay_css();
59 - add_action('wp_enqueue_scripts', [$this, 'add_apple_pay_js']);
58 + // Gateways are constructed before WordPress finishes parsing the request, so
59 + // is_wc_endpoint_url('order-pay') can't be trusted yet here: defer that check
60 + // to wp_enqueue_scripts, once routing has completed.
61 + if ($this->get_button_checkout()) {
62 + add_action('wp_enqueue_scripts', [$this, 'maybe_add_apple_pay_checkout_assets']);
60 63 }
61 64
62 65 if ($this->get_button_cart() && !PayplugWoocommerceHelper::is_cart_block() && !PayplugWoocommerceHelper::is_subscription()) {
63 66 $this->enabled = 'yes';
@@ -160,9 +163,9 @@
160 163 * Outputs the payment fields on the checkout page.
161 164 *
162 165 * @return void
163 166 */
164 - public function payment_fields()
167 + public function payment_fields(): void
165 168 {
166 169 $description = $this->get_description();
167 170
168 171 if (!empty($description)) {
@@ -184,9 +187,9 @@
184 187 * Enqueues Apple Pay scripts for the cart page.
185 188 *
186 189 * @return void
187 190 */
188 - public function add_apple_pay_cart_js()
191 + public function add_apple_pay_cart_js(): void
189 192 {
190 193 wp_enqueue_script('apple-pay-sdk', 'https://applepay.cdn-apple.com/jsapi/1.latest/apple-pay-sdk.js', [], false, true);
191 194 wp_enqueue_script('payplug-apple-pay-cart', PAYPLUG_GATEWAY_PLUGIN_URL . 'assets/js/payplug-apple-pay-cart.js', ['jquery', 'apple-pay-sdk'], PAYPLUG_GATEWAY_VERSION, true);
192 195 wp_localize_script(
@@ -217,9 +220,9 @@
217 220 * Enqueues Apple Pay scripts for the product page.
218 221 *
219 222 * @return void
220 223 */
221 - public function add_apple_pay_product_js()
224 + public function add_apple_pay_product_js(): void
222 225 {
223 226 global $product;
224 227 // Only dispay ApplePay on product page for simple and variable products
225 228 if ($product->get_type() != 'simple' && $product->get_type() != 'variable') {
@@ -238,9 +241,9 @@
238 241 'is_virtual' => $product->is_virtual(),
239 242 'cart_shipping' => WC()->cart->get_shipping_total(),
240 243 'countryCode' => WC()->customer->get_billing_country(),
241 244 'currencyCode' => get_woocommerce_currency(),
242 - 'apple_pay_domain' => $_SERVER['HTTP_HOST'],
245 + 'apple_pay_domain' => $this->domain_name,
243 246 ];
244 247 wp_enqueue_script('apple-pay-sdk', 'https://applepay.cdn-apple.com/jsapi/1.latest/apple-pay-sdk.js', [], false, true);
245 248 wp_enqueue_script('payplug-apple-pay-product', PAYPLUG_GATEWAY_PLUGIN_URL . 'assets/js/payplug-apple-pay-product.js', ['jquery', 'apple-pay-sdk'], PAYPLUG_GATEWAY_VERSION, true);
246 249 wp_localize_script('payplug-apple-pay-product', 'apple_pay_params', $apple_pay_params);
@@ -274,10 +277,13 @@
274 277 $available_rates = !empty($package['rates']) ? $package['rates'] : [];
275 278 if (!empty($available_rates)) {
276 279 foreach ($available_rates as $method) {
277 280 if (in_array($method->get_method_id(), $apple_carriers)) {
278 - if ($chosen_method === $method->get_method_id() . ':' . $method->get_instance_id()) {
279 - $allowed = true;
281 + // On cart page, show the button if any eligible carrier is available —
282 + // the actual shipping selection happens inside the Apple Pay modal.
283 + // On checkout, restrict to the currently chosen shipping method.
284 + if (is_cart() || $chosen_method === $method->get_method_id() . ':' . $method->get_instance_id()) {
285 + return true;
280 286 }
281 287 }
282 288 }
283 289 }
@@ -316,9 +322,9 @@
316 322 * Display unauthorized error
317 323 *
318 324 * @return void
319 325 */
320 - public static function display_notice()
326 + public static function display_notice(): void
321 327 {
322 328 ?>
323 329 <div class="notice notice-error is-dismissible">
324 330 <p><?php echo __('payplug_apple_pay_unauthorized_error', 'payplug'); ?></p>
@@ -352,19 +358,36 @@
352 358 * Enqueues Apple Pay CSS styles.
353 359 *
354 360 * @return void
355 361 */
356 - public function add_apple_pay_css()
362 + public function add_apple_pay_css(): void
357 363 {
358 364 wp_enqueue_style('payplug-apple-pay', PAYPLUG_GATEWAY_PLUGIN_URL . 'assets/css/payplug-apple-pay.css', [], PAYPLUG_GATEWAY_VERSION);
359 365 }
360 366
361 367 /**
368 + * Enqueues the classic Apple Pay checkout assets, unless the checkout page uses the
369 + * Cart & Checkout blocks (the order-pay page always renders the classic payment form,
370 + * even then, so it still needs the classic assets).
371 + *
372 + * @return void
373 + */
374 + public function maybe_add_apple_pay_checkout_assets(): void
375 + {
376 + if (PayplugWoocommerceHelper::is_checkout_block() && !is_wc_endpoint_url('order-pay')) {
377 + return;
378 + }
379 +
380 + $this->add_apple_pay_css();
381 + $this->add_apple_pay_js();
382 + }
383 +
384 + /**
362 385 * Enqueues Apple Pay JavaScript for the checkout page.
363 386 *
364 387 * @return void
365 388 */
366 - public function add_apple_pay_js()
389 + public function add_apple_pay_js(): void
367 390 {
368 391 wp_enqueue_script('apple-pay-sdk', 'https://applepay.cdn-apple.com/jsapi/1.latest/apple-pay-sdk.js', [], false, true);
369 392 wp_enqueue_script(
370 393 'payplug-apple-pay',
@@ -382,12 +405,17 @@
382 405 [
383 406 'ajax_url_payplug_create_order' => \WC_AJAX::get_endpoint('payplug_create_order'),
384 407 'ajax_url_applepay_update_payment' => \WC_AJAX::get_endpoint('applepay_update_payment'),
385 408 'ajax_url_applepay_get_order_totals' => \WC_AJAX::get_endpoint('applepay_get_order_totals'),
409 + 'ajax_url_payplug_apple_pay_create_order_pay' => \WC_AJAX::get_endpoint('payplug_apple_pay_create_order_pay'),
386 410 'countryCode' => WC()->customer->get_billing_country(),
387 411 'currencyCode' => get_woocommerce_currency(),
388 412 'total' => WC()->cart->total,
389 413 'is_checkout' => is_checkout(),
414 + 'is_order_pay' => is_wc_endpoint_url('order-pay'),
415 + 'order_pay_id' => is_wc_endpoint_url('order-pay') ? (int) get_query_var('order-pay') : 0,
416 + 'order_pay_key' => is_wc_endpoint_url('order-pay') ? wc_clean(wp_unslash($_GET['key'] ?? '')) : '',
417 + 'wp_nonce' => wp_create_nonce('woocommerce-process_checkout'),
390 418 'apple_pay_domain' => $this->domain_name,
391 419 ]
392 420 );
393 421 }
@@ -421,9 +449,15 @@
421 449 * @return array|null
422 450 */
423 451 private function process_standard_intent_payment($order)
424 452 {
425 - if (!is_wc_endpoint_url('order-pay') &&
453 + // This runs from the payplug_apple_pay_create_order_pay AJAX endpoint too, whose own
454 + // request URL never carries the order-pay query var, so is_wc_endpoint_url() alone
455 + // can't detect that context here: fall back to the order_key/order_pay_key sent by
456 + // that endpoint and by the checkout-block create_payment_intent endpoint.
457 + $is_order_pay = $this->is_order_pay_request($order);
458 +
459 + if (!$is_order_pay &&
426 460 PayplugWoocommerceHelper::is_checkout_block() &&
427 461 !empty($order->get_transaction_id())) {
428 462 $order_id = PayplugWoocommerceHelper::is_pre_30() ? $order->id : $order->get_id();
429 463
@@ -433,19 +467,43 @@
433 467 ob_clean();
434 468 }
435 469
436 470 $return_url = esc_url_raw($order->get_checkout_order_received_url());
471 + $cancel_url = !empty($payment->hosted_payment->cancel_url) ? $payment->hosted_payment->cancel_url : esc_url_raw(wc_get_checkout_url());
437 472
438 - wp_send_json_success(
439 - [
440 - 'payment_id' => $payment->id,
441 - 'result' => 'success',
442 - 'redirect' => !empty($payment->hosted_payment->payment_url) ? $payment->hosted_payment->payment_url : $return_url,
443 - 'cancel' => !empty($payment->hosted_payment->cancel_url) ? $payment->hosted_payment->cancel_url : null,
444 - ]
445 - );
473 + // Same payload shape as process_standard_payment(): the frontend's
474 + // BeginSessionFromPaymentDetails() reads merchant_session/cancel_url/return_url
475 + // off of it regardless of which of the two methods produced it.
476 + // payment_method is a write-once attribute (merchant_session is tied to the
477 + // ApplePaySession that created the payment): a retrieved payment may not carry
478 + // it at all, and $payment->payment_method would throw UndefinedAttributeException
479 + // rather than just being null/missing, unlike a plain array access.
480 + $merchant_session = null;
481 + if (isset($payment->payment_method) && is_array($payment->payment_method)) {
482 + $merchant_session = $payment->payment_method['merchant_session'] ?? null;
483 + }
446 484
447 - return ['stt' => 'OK'];
485 + if (defined('REST_REQUEST') && REST_REQUEST) {
486 + $merchant_session = wp_json_encode($merchant_session);
487 + }
488 +
489 + $result = [
490 + 'result' => 'success',
491 + 'merchant_session' => $merchant_session,
492 + 'payment_id' => $payment->id,
493 + 'cancel_url' => $cancel_url,
494 + 'return_url' => $return_url,
495 + ];
496 +
497 + // wp_send_json_success() calls die(), which is only safe for the classic
498 + // wc-ajax request this was written for: the Store API checkout flow (used by
499 + // the checkout block) calls process_payment() through the REST framework,
500 + // and killing the process mid-request there produces a broken response.
501 + if (wp_doing_ajax()) {
502 + wp_send_json_success($result);
503 + }
504 +
505 + return $result;
448 506 } catch (HttpException $e) {
449 507 PayplugGateway::log(sprintf('Error while processing order #%s : %s', $order_id, wc_print_r($e->getErrorObject(), true)), 'error');
450 508 throw new \Exception(__('Payment processing failed. Please retry.', 'payplug'));
451 509 } catch (\Exception $e) {
@@ -475,8 +533,12 @@
475 533 if (!empty($intent)) {
476 534 return $intent;
477 535 }
478 536
537 + // Same detection as process_standard_intent_payment(): this can run from AJAX
538 + // endpoints whose own request URL never carries the order-pay query var.
539 + $is_order_pay = $this->is_order_pay_request($order);
540 +
479 541 $order_id = PayplugWoocommerceHelper::is_pre_30() ? $order->id : $order->get_id();
480 542 try {
481 543 $address_data = PayplugAddressData::from_order($order);
482 544
@@ -557,13 +619,37 @@
557 619 PayplugWoocommerceHelper::save_transaction_metadata($order, $metadata);
558 620
559 621 PayplugGateway::log(sprintf('Payment creation complete for order #%s', $order_id));
560 622
623 + // On order-pay, closing the Apple Pay sheet should keep the customer on the
624 + // order-pay page. On a regular checkout submission (classic or Blocks), it should
625 + // keep them on checkout too, so they can pick another payment method - only the
626 + // classic cart/product page flows (workflow 'cart'/'product') actually want the
627 + // order-cancelled/cart redirect, since that's where those customers started.
628 + if ($is_order_pay) {
629 + $cancel_url = esc_url_raw($order->get_checkout_payment_url());
630 + } elseif ('checkout' === $workflow) {
631 + $cancel_url = esc_url_raw(wc_get_checkout_url());
632 + } else {
633 + $cancel_url = esc_url_raw($order->get_cancel_order_url_raw());
634 + }
635 +
636 + // When process_payment() is invoked through WooCommerce Blocks' Store API (a REST
637 + // request), this array is forwarded to the client as `payment_details`, which
638 + // coerces every value to a string - an array value would become the literal,
639 + // useless string "Array". The classic AJAX flows that call this method directly
640 + // (order-pay, cart/product Apple Pay) JSON-encode/decode the whole response
641 + // transparently instead, so they need the raw merchant session object.
642 + $merchant_session = $payment->payment_method['merchant_session'];
643 + if (defined('REST_REQUEST') && REST_REQUEST) {
644 + $merchant_session = wp_json_encode($merchant_session);
645 + }
646 +
561 647 return [
562 648 'result' => 'success',
563 - 'merchant_session' => $payment->payment_method['merchant_session'],
649 + 'merchant_session' => $merchant_session,
564 650 'payment_id' => $payment->id,
565 - 'cancel_url' => esc_url_raw($order->get_cancel_order_url_raw()),
651 + 'cancel_url' => $cancel_url,
566 652 'return_url' => $return_url,
567 653 ];
568 654 } catch (HttpException $e) {
569 655 PayplugGateway::log(sprintf('Error while processing order #%s : %s', $order_id, wc_print_r($e->getErrorObject(), true)), 'error');
@@ -594,9 +680,9 @@
594 680 * @param bool $status
595 681 *
596 682 * @return void
597 683 */
598 - private function set_button_checkout($status)
684 + private function set_button_checkout($status): void
599 685 {
600 686 $this->checkout = $status;
601 687 }
602 688
@@ -606,9 +692,9 @@
606 692 * @param bool $status
607 693 *
608 694 * @return void
609 695 */
610 - private function set_button_cart($status)
696 + private function set_button_cart($status): void
611 697 {
612 698 $this->cart = $status;
613 699 }
614 700
@@ -618,9 +704,9 @@
618 704 * @param bool $status
619 705 *
620 706 * @return void
621 707 */
622 - private function set_button_product($status)
708 + private function set_button_product($status): void
623 709 {
624 710 $this->product = $status;
625 711 }
626 712
@@ -670,9 +756,9 @@
670 756 * @param array $carriers
671 757 *
672 758 * @return void
673 759 */
674 - private function set_carriers($carriers)
760 + private function set_carriers($carriers): void
675 761 {
676 762 $this->carriers = $carriers;
677 763 }
678 764 }