From 02701ca0f506d028de622af46bb717d2efd1999a Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Tue, 10 Sep 2024 15:06:15 +0200 Subject: [PATCH 01/16] =?UTF-8?q?=E2=9C=A8=20Add=20ProcessPaymentTrait=20t?= =?UTF-8?q?o=20AxoGateway?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We’ll use this Trait later for error handling; the trait requires the SessionHandler instance. --- modules/ppcp-axo/services.php | 1 + modules/ppcp-axo/src/Gateway/AxoGateway.php | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-axo/services.php b/modules/ppcp-axo/services.php index 0519e9209..3458f080e 100644 --- a/modules/ppcp-axo/services.php +++ b/modules/ppcp-axo/services.php @@ -75,6 +75,7 @@ return array( $container->get( 'wcgateway.settings.render' ), $container->get( 'wcgateway.settings' ), $container->get( 'wcgateway.url' ), + $container->get( 'session.handler' ), $container->get( 'wcgateway.order-processor' ), $container->get( 'axo.card_icons' ), $container->get( 'axo.card_icons.axo' ), diff --git a/modules/ppcp-axo/src/Gateway/AxoGateway.php b/modules/ppcp-axo/src/Gateway/AxoGateway.php index 4fa80cd4a..ffa2ce0a0 100644 --- a/modules/ppcp-axo/src/Gateway/AxoGateway.php +++ b/modules/ppcp-axo/src/Gateway/AxoGateway.php @@ -26,12 +26,14 @@ use WooCommerce\PayPalCommerce\WcGateway\Gateway\TransactionUrlProvider; use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderMetaTrait; use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderProcessor; use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsRenderer; +use WooCommerce\PayPalCommerce\WcGateway\Gateway\ProcessPaymentTrait; +use WooCommerce\PayPalCommerce\Session\SessionHandler; /** * Class AXOGateway. */ class AxoGateway extends WC_Payment_Gateway { - use OrderMetaTrait, GatewaySettingsRendererTrait; + use OrderMetaTrait, GatewaySettingsRendererTrait, ProcessPaymentTrait; const ID = 'ppcp-axo-gateway'; @@ -119,12 +121,20 @@ class AxoGateway extends WC_Payment_Gateway { */ protected $logger; + /** + * The Session Handler. + * + * @var SessionHandler + */ + protected $session_handler; + /** * AXOGateway constructor. * * @param SettingsRenderer $settings_renderer The settings renderer. * @param ContainerInterface $ppcp_settings The settings. * @param string $wcgateway_module_url The WcGateway module URL. + * @param SessionHandler $session_handler The session handler. * @param OrderProcessor $order_processor The Order processor. * @param array $card_icons The card icons. * @param array $card_icons_axo The card icons. @@ -139,6 +149,7 @@ class AxoGateway extends WC_Payment_Gateway { SettingsRenderer $settings_renderer, ContainerInterface $ppcp_settings, string $wcgateway_module_url, + SessionHandler $session_handler, OrderProcessor $order_processor, array $card_icons, array $card_icons_axo, @@ -154,6 +165,7 @@ class AxoGateway extends WC_Payment_Gateway { $this->settings_renderer = $settings_renderer; $this->ppcp_settings = $ppcp_settings; $this->wcgateway_module_url = $wcgateway_module_url; + $this->session_handler = $session_handler; $this->order_processor = $order_processor; $this->card_icons = $card_icons; $this->card_icons_axo = $card_icons_axo; @@ -245,6 +257,7 @@ class AxoGateway extends WC_Payment_Gateway { $payment_source_properties ); + // TODO - this request fails in block checkout. Need to investigate. $order = $this->order_endpoint->create( array( $purchase_unit ), $shipping_preference, From a3d4a3e04fbaac0074bbf591c88155b9ea389d54 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Tue, 10 Sep 2024 19:42:20 +0200 Subject: [PATCH 02/16] =?UTF-8?q?=F0=9F=A5=85=20Add=20generic=20error=20ha?= =?UTF-8?q?ndling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-axo/src/Gateway/AxoGateway.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/modules/ppcp-axo/src/Gateway/AxoGateway.php b/modules/ppcp-axo/src/Gateway/AxoGateway.php index ffa2ce0a0..59cfd7742 100644 --- a/modules/ppcp-axo/src/Gateway/AxoGateway.php +++ b/modules/ppcp-axo/src/Gateway/AxoGateway.php @@ -10,6 +10,7 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Axo\Gateway; use Psr\Log\LoggerInterface; +use Exception; use WC_Order; use WC_Payment_Gateway; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint; @@ -27,6 +28,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderMetaTrait; use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderProcessor; use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsRenderer; use WooCommerce\PayPalCommerce\WcGateway\Gateway\ProcessPaymentTrait; +use WooCommerce\PayPalCommerce\WcGateway\Exception\GatewayGenericException; use WooCommerce\PayPalCommerce\Session\SessionHandler; /** @@ -230,6 +232,13 @@ class AxoGateway extends WC_Payment_Gateway { public function process_payment( $order_id ) { $wc_order = wc_get_order( $order_id ); + if ( ! is_a( $wc_order, WC_Order::class ) ) { + return $this->handle_payment_failure( + null, + new GatewayGenericException( new Exception( 'WC order was not found.' ) ) + ); + } + // phpcs:ignore WordPress.Security.NonceVerification.Missing $fastlane_member = wc_clean( wp_unslash( $_POST['fastlane_member'] ?? '' ) ); if ( $fastlane_member ) { From 9c86bc23e1bed60f93d293833120bf1924831a35 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Tue, 10 Sep 2024 19:46:07 +0200 Subject: [PATCH 03/16] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Move=20code=20into?= =?UTF-8?q?=20try-catch=20block?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-axo/src/Gateway/AxoGateway.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/modules/ppcp-axo/src/Gateway/AxoGateway.php b/modules/ppcp-axo/src/Gateway/AxoGateway.php index 59cfd7742..3d7351b15 100644 --- a/modules/ppcp-axo/src/Gateway/AxoGateway.php +++ b/modules/ppcp-axo/src/Gateway/AxoGateway.php @@ -239,20 +239,20 @@ class AxoGateway extends WC_Payment_Gateway { ); } - // phpcs:ignore WordPress.Security.NonceVerification.Missing - $fastlane_member = wc_clean( wp_unslash( $_POST['fastlane_member'] ?? '' ) ); - if ( $fastlane_member ) { - $payment_method_title = __( 'Debit & Credit Cards (via Fastlane by PayPal)', 'woocommerce-paypal-payments' ); - $wc_order->set_payment_method_title( $payment_method_title ); - $wc_order->save(); - } + try { + // phpcs:ignore WordPress.Security.NonceVerification.Missing + $fastlane_member = wc_clean( wp_unslash( $_POST['fastlane_member'] ?? '' ) ); + if ( $fastlane_member ) { + $payment_method_title = __( 'Debit & Credit Cards (via Fastlane by PayPal)', 'woocommerce-paypal-payments' ); + $wc_order->set_payment_method_title( $payment_method_title ); + $wc_order->save(); + } $purchase_unit = $this->purchase_unit_factory->from_wc_order( $wc_order ); // phpcs:ignore WordPress.Security.NonceVerification.Missing $nonce = wc_clean( wp_unslash( $_POST['axo_nonce'] ?? '' ) ); - try { $shipping_preference = $this->shipping_preference_factory->from_state( $purchase_unit, 'checkout' From c011e05ed897764dffa4b84ac407301c1866377d Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Tue, 10 Sep 2024 19:50:15 +0200 Subject: [PATCH 04/16] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Extract=20order-crea?= =?UTF-8?q?tion=20to=20own=20method?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-axo/src/Gateway/AxoGateway.php | 74 +++++++++++++-------- 1 file changed, 45 insertions(+), 29 deletions(-) diff --git a/modules/ppcp-axo/src/Gateway/AxoGateway.php b/modules/ppcp-axo/src/Gateway/AxoGateway.php index 3d7351b15..222cc923e 100644 --- a/modules/ppcp-axo/src/Gateway/AxoGateway.php +++ b/modules/ppcp-axo/src/Gateway/AxoGateway.php @@ -20,6 +20,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory; +use WooCommerce\PayPalCommerce\ApiClient\Entity\Order; use WooCommerce\PayPalCommerce\Onboarding\Environment; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\WcGateway\Gateway\GatewaySettingsRendererTrait; @@ -248,36 +249,12 @@ class AxoGateway extends WC_Payment_Gateway { $wc_order->save(); } - $purchase_unit = $this->purchase_unit_factory->from_wc_order( $wc_order ); - // phpcs:ignore WordPress.Security.NonceVerification.Missing - $nonce = wc_clean( wp_unslash( $_POST['axo_nonce'] ?? '' ) ); + // The `axo_nonce` is not a WP nonce, but a single use token generated by the JS SDK. + // phpcs:ignore WordPress.Security.NonceVerification.Missing + $token = wc_clean( wp_unslash( $_POST['axo_nonce'] ?? '' ) ); - $shipping_preference = $this->shipping_preference_factory->from_state( - $purchase_unit, - 'checkout' - ); - - $payment_source_properties = new \stdClass(); - $payment_source_properties->single_use_token = $nonce; - - $payment_source = new PaymentSource( - 'card', - $payment_source_properties - ); - - // TODO - this request fails in block checkout. Need to investigate. - $order = $this->order_endpoint->create( - array( $purchase_unit ), - $shipping_preference, - null, - null, - '', - ApplicationContext::USER_ACTION_CONTINUE, - '', - array(), - $payment_source - ); + $order = $this->create_paypal_order( $wc_order, $token ); $this->order_processor->process_captured_and_authorized( $wc_order, $order ); @@ -307,8 +284,47 @@ class AxoGateway extends WC_Payment_Gateway { 'result' => 'success', 'redirect' => $this->get_return_url( $wc_order ), ); + } - return $result; + /** + * Create a new PayPal order from the existing WC_Order instance. + * + * @param WC_Order $wc_order The WooCommerce order to use as a base. + * @param string $single_use_token The single use token, generated by the JS SDK. + * + * @return Order The PayPal order. + */ + protected function create_paypal_order( WC_Order $wc_order, string $single_use_token ) : Order { + $purchase_unit = $this->purchase_unit_factory->from_wc_order( $wc_order ); + + $shipping_preference = $this->shipping_preference_factory->from_state( + $purchase_unit, + 'checkout' + ); + + $payment_source_properties = (object) array( + 'single_use_token' => $single_use_token, + ); + + $payment_source = new PaymentSource( + 'card', + $payment_source_properties + ); + + // TODO - this request fails in block checkout. Need to investigate. + $order = $this->order_endpoint->create( + array( $purchase_unit ), + $shipping_preference, + null, + null, + '', + ApplicationContext::USER_ACTION_CONTINUE, + '', + array(), + $payment_source + ); + + return $order; } /** From d41b967e9019429982522934f1eff8e2f850c567 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Tue, 10 Sep 2024 19:51:35 +0200 Subject: [PATCH 05/16] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Reuse=20existing=20e?= =?UTF-8?q?rror=20handling=20logic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-axo/src/Gateway/AxoGateway.php | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/modules/ppcp-axo/src/Gateway/AxoGateway.php b/modules/ppcp-axo/src/Gateway/AxoGateway.php index 222cc923e..c2a101582 100644 --- a/modules/ppcp-axo/src/Gateway/AxoGateway.php +++ b/modules/ppcp-axo/src/Gateway/AxoGateway.php @@ -257,25 +257,8 @@ class AxoGateway extends WC_Payment_Gateway { $order = $this->create_paypal_order( $wc_order, $token ); $this->order_processor->process_captured_and_authorized( $wc_order, $order ); - - } catch ( RuntimeException $exception ) { - $error = $exception->getMessage(); - if ( is_a( $exception, PayPalApiException::class ) ) { - $error = $exception->get_details( $error ); - } - - $this->logger->error( $error ); - wc_add_notice( $error, 'error' ); - - $wc_order->update_status( - 'failed', - $error - ); - - return array( - 'result' => 'failure', - 'redirect' => wc_get_checkout_url(), - ); + } catch ( Exception $exception ) { + return $this->handle_payment_failure( $wc_order, $exception ); } WC()->cart->empty_cart(); From a4231ab77c3294cd7abd4dd826345205330ce587 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Tue, 10 Sep 2024 19:52:40 +0200 Subject: [PATCH 06/16] =?UTF-8?q?=E2=9C=A8=20Support=20PayPalApiExceptions?= =?UTF-8?q?=20in=20error=20logging?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/Gateway/ProcessPaymentTrait.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php b/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php index 3cf401b29..ed940db78 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php +++ b/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php @@ -13,6 +13,7 @@ use Exception; use Throwable; use WC_Order; use WooCommerce\PayPalCommerce\WcGateway\Exception\GatewayGenericException; +use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; /** * Trait ProcessPaymentTrait @@ -74,8 +75,13 @@ trait ProcessPaymentTrait { * @param Throwable $exception The exception to format. * @return string */ - protected function format_exception( Throwable $exception ): string { - $output = $exception->getMessage() . ' ' . basename( $exception->getFile() ) . ':' . $exception->getLine(); + protected function format_exception( Throwable $exception ) : string { + $message = $exception->getMessage(); + if ( is_a( $exception, PayPalApiException::class ) ) { + $message = $exception->get_details( $message ); + } + + $output = $message . ' ' . basename( $exception->getFile() ) . ':' . $exception->getLine(); $prev = $exception->getPrevious(); if ( ! $prev ) { return $output; From 9564fd8d561fc79036b23e1d893693c4a8168f32 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Tue, 10 Sep 2024 19:58:23 +0200 Subject: [PATCH 07/16] =?UTF-8?q?=F0=9F=8E=A8=20Apply=20phpcs=20rules,=20m?= =?UTF-8?q?inor=20cleanup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-axo/src/Gateway/AxoGateway.php | 37 ++++++++++----------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/modules/ppcp-axo/src/Gateway/AxoGateway.php b/modules/ppcp-axo/src/Gateway/AxoGateway.php index c2a101582..60ad6322f 100644 --- a/modules/ppcp-axo/src/Gateway/AxoGateway.php +++ b/modules/ppcp-axo/src/Gateway/AxoGateway.php @@ -5,7 +5,7 @@ * @package WooCommerce\PayPalCommerce\WcGateway\Gateway */ -declare(strict_types=1); +declare( strict_types = 1 ); namespace WooCommerce\PayPalCommerce\Axo\Gateway; @@ -16,8 +16,6 @@ use WC_Payment_Gateway; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Entity\ApplicationContext; use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSource; -use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; -use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory; use WooCommerce\PayPalCommerce\ApiClient\Entity\Order; @@ -134,19 +132,19 @@ class AxoGateway extends WC_Payment_Gateway { /** * AXOGateway constructor. * - * @param SettingsRenderer $settings_renderer The settings renderer. - * @param ContainerInterface $ppcp_settings The settings. - * @param string $wcgateway_module_url The WcGateway module URL. - * @param SessionHandler $session_handler The session handler. - * @param OrderProcessor $order_processor The Order processor. - * @param array $card_icons The card icons. - * @param array $card_icons_axo The card icons. - * @param OrderEndpoint $order_endpoint The order endpoint. - * @param PurchaseUnitFactory $purchase_unit_factory The purchase unit factory. - * @param ShippingPreferenceFactory $shipping_preference_factory The shipping preference factory. - * @param TransactionUrlProvider $transaction_url_provider The transaction url provider. - * @param Environment $environment The environment. - * @param LoggerInterface $logger The logger. + * @param SettingsRenderer $settings_renderer The settings renderer. + * @param ContainerInterface $ppcp_settings The settings. + * @param string $wcgateway_module_url The WcGateway module URL. + * @param SessionHandler $session_handler The session handler. + * @param OrderProcessor $order_processor The Order processor. + * @param array $card_icons The card icons. + * @param array $card_icons_axo The card icons. + * @param OrderEndpoint $order_endpoint The order endpoint. + * @param PurchaseUnitFactory $purchase_unit_factory The purchase unit factory. + * @param ShippingPreferenceFactory $shipping_preference_factory Shipping preference factory. + * @param TransactionUrlProvider $transaction_url_provider The transaction url provider. + * @param Environment $environment The environment. + * @param LoggerInterface $logger The logger. */ public function __construct( SettingsRenderer $settings_renderer, @@ -228,6 +226,7 @@ class AxoGateway extends WC_Payment_Gateway { * Processes the order. * * @param int $order_id The WC order ID. + * * @return array */ public function process_payment( $order_id ) { @@ -263,7 +262,7 @@ class AxoGateway extends WC_Payment_Gateway { WC()->cart->empty_cart(); - $result = array( + return array( 'result' => 'success', 'redirect' => $this->get_return_url( $wc_order ), ); @@ -349,7 +348,7 @@ class AxoGateway extends WC_Payment_Gateway { * * @return string */ - public function get_transaction_url( $order ): string { + public function get_transaction_url( $order ) : string { $this->view_transaction_url = $this->transaction_url_provider->get_transaction_url_base( $order ); return parent::get_transaction_url( $order ); @@ -383,7 +382,7 @@ class AxoGateway extends WC_Payment_Gateway { * * @return SettingsRenderer */ - protected function settings_renderer(): SettingsRenderer { + protected function settings_renderer() : SettingsRenderer { return $this->settings_renderer; } } From 07d9ec5f889be434b19aa9d10b1de3a212e3fcfa Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Tue, 10 Sep 2024 19:59:06 +0200 Subject: [PATCH 08/16] =?UTF-8?q?=F0=9F=9A=A7=20Document=20bug=20with=20bl?= =?UTF-8?q?ock=20checkout?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-axo/src/Gateway/AxoGateway.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/ppcp-axo/src/Gateway/AxoGateway.php b/modules/ppcp-axo/src/Gateway/AxoGateway.php index 60ad6322f..40d0df86a 100644 --- a/modules/ppcp-axo/src/Gateway/AxoGateway.php +++ b/modules/ppcp-axo/src/Gateway/AxoGateway.php @@ -248,6 +248,10 @@ class AxoGateway extends WC_Payment_Gateway { $wc_order->save(); } + /* + * TODO - in block checkout this is empty, which causes an API error. + * Expected a 36-char UUID, like "8216a7c6-63be-1760-7451-80a858d571be" + */ // The `axo_nonce` is not a WP nonce, but a single use token generated by the JS SDK. // phpcs:ignore WordPress.Security.NonceVerification.Missing From 9234a833ddf5ea31346a5cfa218eedfe23251cac Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 11 Sep 2024 12:50:05 +0200 Subject: [PATCH 09/16] =?UTF-8?q?=E2=9C=A8=20Submit=20axo=5Fnonce?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-axo-block/resources/js/index.js | 26 ++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/modules/ppcp-axo-block/resources/js/index.js b/modules/ppcp-axo-block/resources/js/index.js index bf7c5a6d4..89e45c367 100644 --- a/modules/ppcp-axo-block/resources/js/index.js +++ b/modules/ppcp-axo-block/resources/js/index.js @@ -76,6 +76,32 @@ const Axo = ( props ) => { card, ] ); + useEffect( () => { + const unsubscribe = onPaymentSetup( async () => { + // Validate payment options and emit response. + + // Note: This response supports the Ryan flow (payment via saved card-token) + return { + type: emitResponse.responseTypes.SUCCESS, + meta: { + paymentMethodData: { + axo_nonce: card?.id, + }, + }, + }; + } ); + + // Unsubscribes when this component is unmounted. + return () => { + unsubscribe(); + }; + }, [ + emitResponse.responseTypes.ERROR, + emitResponse.responseTypes.SUCCESS, + onPaymentSetup, + card, + ] ); + const { setIsAxoActive, setIsGuest, setIsAxoScriptLoaded } = useDispatch( STORE_NAME ); From e9a3c0bb6d2f18d1e479a63415249313d9c44ed1 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 11 Sep 2024 16:04:08 +0200 Subject: [PATCH 10/16] =?UTF-8?q?=E2=9C=A8=20New=20hook=20to=20return=20AX?= =?UTF-8?q?O=20billing=20details?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/hooks/useCustomerData.js | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/modules/ppcp-axo-block/resources/js/hooks/useCustomerData.js b/modules/ppcp-axo-block/resources/js/hooks/useCustomerData.js index 1d513f0d1..6d98196c3 100644 --- a/modules/ppcp-axo-block/resources/js/hooks/useCustomerData.js +++ b/modules/ppcp-axo-block/resources/js/hooks/useCustomerData.js @@ -1,5 +1,6 @@ import { useCallback, useMemo } from '@wordpress/element'; import { useDispatch, useSelect } from '@wordpress/data'; +import { useMemo } from '@wordpress/element'; export const useCustomerData = () => { const customerData = useSelect( ( select ) => @@ -40,3 +41,48 @@ export const useCustomerData = () => { ] ); }; + +export const useTokenizeCustomerData = () => { + const customerData = useSelect( ( select ) => + select( 'wc/store/cart' ).getCustomerData() + ); + + const isValidAddress = ( address ) => { + // At least one name must be present. + if ( ! address.first_name && ! address.last_name ) { + return false; + } + + // Street, city, postcode, country are mandatory; state is optional. + return ( + address.address_1 && + address.city && + address.postcode && + address.country + ); + }; + + // Memoize the customer data to avoid unnecessary re-renders (and potential infinite loops). + return useMemo( () => { + const { billingAddress, shippingAddress } = customerData; + + // Prefer billing address, but fallback to shipping address if billing address is not valid. + const mainAddress = isValidAddress( billingAddress ) + ? billingAddress + : shippingAddress; + + return { + cardholderName: { + fullName: `${ mainAddress.first_name } ${ mainAddress.last_name }`, + }, + billingAddress: { + addressLine1: mainAddress.address_1, + addressLine2: mainAddress.address_2, + adminArea1: mainAddress.state, + adminArea2: mainAddress.city, + postalCode: mainAddress.postcode, + countryCode: mainAddress.country, + }, + }; + }, [ customerData ] ); +}; From 7e707d5aa1ae5ebab79086c9d22a4c84063b5675 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 11 Sep 2024 16:08:59 +0200 Subject: [PATCH 11/16] =?UTF-8?q?=E2=9C=A8=20Add=20dependencies=20for=20Ga?= =?UTF-8?q?ry=20checkout?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Conflicts: # modules/ppcp-axo-block/resources/js/index.js --- modules/ppcp-axo-block/resources/js/index.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/modules/ppcp-axo-block/resources/js/index.js b/modules/ppcp-axo-block/resources/js/index.js index 89e45c367..85a521a6f 100644 --- a/modules/ppcp-axo-block/resources/js/index.js +++ b/modules/ppcp-axo-block/resources/js/index.js @@ -7,7 +7,10 @@ import { loadPaypalScript } from '../../../ppcp-button/resources/js/modules/Help // Hooks import useFastlaneSdk from './hooks/useFastlaneSdk'; -import { useCustomerData } from './hooks/useCustomerData'; +import { + useCustomerData, + useTokenizeCustomerData, +} from './hooks/useCustomerData'; import { useShippingAddressChange } from './hooks/useShippingAddressChange'; import { useCardChange } from './hooks/useCardChange'; @@ -46,6 +49,8 @@ const Axo = ( props ) => { const [ paypalLoaded, setPaypalLoaded ] = useState( false ); const [ shippingAddress, setShippingAddress ] = useState( null ); const [ card, setCard ] = useState( null ); + const [ paymentComponent, setPaymentComponent ] = useState( null ); + const tokenizedCustomerData = useTokenizeCustomerData(); const fastlaneSdk = useFastlaneSdk( axoConfig, ppcpConfig ); console.log( 'Axo component rendering' ); @@ -206,8 +211,8 @@ const Axo = ( props ) => { }; }, [] ); - const handlePaymentLoad = useCallback( ( paymentComponent ) => { - console.log( 'Payment component loaded', paymentComponent ); + const handlePaymentLoad = useCallback( ( component ) => { + setPaymentComponent( component ); }, [] ); const handleChange = ( selectedCard ) => { From 389860c739e13560532ff2515ec38ca08e57f3a9 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 11 Sep 2024 16:17:12 +0200 Subject: [PATCH 12/16] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Re-organize=20paymen?= =?UTF-8?q?t-setup-integration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Performance update, and preparation for Gary-flow changes --- modules/ppcp-axo-block/resources/js/index.js | 39 +++++++++++--------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/modules/ppcp-axo-block/resources/js/index.js b/modules/ppcp-axo-block/resources/js/index.js index 85a521a6f..4dcbe7419 100644 --- a/modules/ppcp-axo-block/resources/js/index.js +++ b/modules/ppcp-axo-block/resources/js/index.js @@ -81,29 +81,22 @@ const Axo = ( props ) => { card, ] ); - useEffect( () => { - const unsubscribe = onPaymentSetup( async () => { - // Validate payment options and emit response. + const handlePaymentSetup = useCallback( async () => { + const isRyanFlow = !! card?.id; + const cardToken = card?.id; - // Note: This response supports the Ryan flow (payment via saved card-token) - return { - type: emitResponse.responseTypes.SUCCESS, - meta: { - paymentMethodData: { - axo_nonce: card?.id, - }, + return { + type: emitResponse.responseTypes.SUCCESS, + meta: { + paymentMethodData: { + fastlane_member: isRyanFlow, + axo_nonce: cardToken, }, - }; - } ); - - // Unsubscribes when this component is unmounted. - return () => { - unsubscribe(); + }, }; }, [ emitResponse.responseTypes.ERROR, emitResponse.responseTypes.SUCCESS, - onPaymentSetup, card, ] ); @@ -117,6 +110,18 @@ const Axo = ( props ) => { setBillingAddress: updateWooBillingAddress, } = useCustomerData(); + /** + * `onPaymentSetup()` fires when we enter the "PROCESSING" state in the checkout flow. + * It pre-processes the payment details and returns data for server-side processing. + */ + useEffect( () => { + const unsubscribe = onPaymentSetup( handlePaymentSetup ); + + return () => { + unsubscribe(); + }; + }, [ onPaymentSetup, handlePaymentSetup ] ); + useEffect( () => { console.log( 'Initializing class toggles' ); initializeClassToggles(); From db6b860b5f64c4f98c3cff50b7672278b6783130 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 11 Sep 2024 16:18:08 +0200 Subject: [PATCH 13/16] =?UTF-8?q?=E2=9C=A8=20Integrate=20Gary=20flow=20in?= =?UTF-8?q?=20AXO=20block=20checkout?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-axo-block/resources/js/index.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-axo-block/resources/js/index.js b/modules/ppcp-axo-block/resources/js/index.js index 4dcbe7419..a1023b03a 100644 --- a/modules/ppcp-axo-block/resources/js/index.js +++ b/modules/ppcp-axo-block/resources/js/index.js @@ -83,7 +83,20 @@ const Axo = ( props ) => { const handlePaymentSetup = useCallback( async () => { const isRyanFlow = !! card?.id; - const cardToken = card?.id; + let cardToken = card?.id; + + if ( ! cardToken && paymentComponent ) { + cardToken = await paymentComponent + .getPaymentToken( tokenizedCustomerData ) + .then( ( response ) => response.id ); + } + + if ( ! cardToken ) { + return { + type: emitResponse.responseTypes.ERROR, + message: 'Could not process the payment (tokenization error)', + }; + } return { type: emitResponse.responseTypes.SUCCESS, @@ -98,6 +111,8 @@ const Axo = ( props ) => { emitResponse.responseTypes.ERROR, emitResponse.responseTypes.SUCCESS, card, + paymentComponent, + tokenizedCustomerData, ] ); const { setIsAxoActive, setIsGuest, setIsAxoScriptLoaded } = From a0d0a06dfdc3c0303b453498a3b6b40a519cf27a Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 11 Sep 2024 16:21:05 +0200 Subject: [PATCH 14/16] =?UTF-8?q?=F0=9F=92=A1=20Remove=20dev-comments,=20c?= =?UTF-8?q?lean=20up?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-axo/src/Gateway/AxoGateway.php | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/modules/ppcp-axo/src/Gateway/AxoGateway.php b/modules/ppcp-axo/src/Gateway/AxoGateway.php index 40d0df86a..dc54c0e9e 100644 --- a/modules/ppcp-axo/src/Gateway/AxoGateway.php +++ b/modules/ppcp-axo/src/Gateway/AxoGateway.php @@ -248,12 +248,7 @@ class AxoGateway extends WC_Payment_Gateway { $wc_order->save(); } - /* - * TODO - in block checkout this is empty, which causes an API error. - * Expected a 36-char UUID, like "8216a7c6-63be-1760-7451-80a858d571be" - */ - - // The `axo_nonce` is not a WP nonce, but a single use token generated by the JS SDK. + // The `axo_nonce` is not a WP nonce, but a card-token generated by the JS SDK. // phpcs:ignore WordPress.Security.NonceVerification.Missing $token = wc_clean( wp_unslash( $_POST['axo_nonce'] ?? '' ) ); @@ -275,12 +270,12 @@ class AxoGateway extends WC_Payment_Gateway { /** * Create a new PayPal order from the existing WC_Order instance. * - * @param WC_Order $wc_order The WooCommerce order to use as a base. - * @param string $single_use_token The single use token, generated by the JS SDK. + * @param WC_Order $wc_order The WooCommerce order to use as a base. + * @param string $payment_token The payment token, generated by the JS SDK. * * @return Order The PayPal order. */ - protected function create_paypal_order( WC_Order $wc_order, string $single_use_token ) : Order { + protected function create_paypal_order( WC_Order $wc_order, string $payment_token ) : Order { $purchase_unit = $this->purchase_unit_factory->from_wc_order( $wc_order ); $shipping_preference = $this->shipping_preference_factory->from_state( @@ -289,7 +284,7 @@ class AxoGateway extends WC_Payment_Gateway { ); $payment_source_properties = (object) array( - 'single_use_token' => $single_use_token, + 'single_use_token' => $payment_token, ); $payment_source = new PaymentSource( @@ -297,8 +292,7 @@ class AxoGateway extends WC_Payment_Gateway { $payment_source_properties ); - // TODO - this request fails in block checkout. Need to investigate. - $order = $this->order_endpoint->create( + return $this->order_endpoint->create( array( $purchase_unit ), $shipping_preference, null, @@ -309,8 +303,6 @@ class AxoGateway extends WC_Payment_Gateway { array(), $payment_source ); - - return $order; } /** From 0cdfe13102ef7910f016e33e204c3fe72b988ae9 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Thu, 12 Sep 2024 16:38:27 +0200 Subject: [PATCH 15/16] =?UTF-8?q?=E2=9C=A8=20Enforce=20Fastlane=20to=20be?= =?UTF-8?q?=20the=20first=20payment=20method?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-axo/src/AxoModule.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/modules/ppcp-axo/src/AxoModule.php b/modules/ppcp-axo/src/AxoModule.php index 069860550..a215fc7db 100644 --- a/modules/ppcp-axo/src/AxoModule.php +++ b/modules/ppcp-axo/src/AxoModule.php @@ -28,6 +28,8 @@ use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; use WooCommerce\PayPalCommerce\WcGateway\Helper\CartCheckoutDetector; use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsListener; use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper; +use WC_Payment_Gateways; + /** * Class AxoModule */ @@ -130,6 +132,20 @@ class AxoModule implements ServiceModule, ExtendingModule, ExecutableModule { } ); + // Enforce Fastlane to always be the first payment method in the list. + add_action( + 'wc_payment_gateways_initialized', + function ( WC_Payment_Gateways $gateways ) { + foreach ( $gateways->payment_gateways as $key => $gateway ) { + if ( $gateway->id === AxoGateway::ID ) { + unset( $gateways->payment_gateways[ $key ] ); + array_unshift( $gateways->payment_gateways, $gateway ); + break; + } + } + } + ); + // Force 'cart-block' and 'cart' Smart Button locations in the settings. add_action( 'admin_init', From 63ff90f061dca95c700d368b0e796c125aae42e2 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Thu, 12 Sep 2024 16:48:20 +0200 Subject: [PATCH 16/16] =?UTF-8?q?=F0=9F=A9=B9=20Do=20not=20modify=20gatewa?= =?UTF-8?q?y=20sort=20order=20in=20admin?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-axo/src/AxoModule.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/ppcp-axo/src/AxoModule.php b/modules/ppcp-axo/src/AxoModule.php index a215fc7db..1ff7ae712 100644 --- a/modules/ppcp-axo/src/AxoModule.php +++ b/modules/ppcp-axo/src/AxoModule.php @@ -136,6 +136,9 @@ class AxoModule implements ServiceModule, ExtendingModule, ExecutableModule { add_action( 'wc_payment_gateways_initialized', function ( WC_Payment_Gateways $gateways ) { + if ( is_admin() ) { + return; + } foreach ( $gateways->payment_gateways as $key => $gateway ) { if ( $gateway->id === AxoGateway::ID ) { unset( $gateways->payment_gateways[ $key ] );