From de48bcbb9e0954e3fb72f313f9f99fec27e921ee Mon Sep 17 00:00:00 2001 From: dinamiko Date: Wed, 22 Dec 2021 12:46:48 +0100 Subject: [PATCH 01/19] Display payment gateways for guest users when subscription in the cart --- modules/ppcp-wc-gateway/services.php | 3 +-- .../src/Checkout/DisableGateways.php | 17 +---------------- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index cb6d3b604..1a34c36ef 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -120,8 +120,7 @@ return array( 'wcgateway.disabler' => static function ( ContainerInterface $container ): DisableGateways { $session_handler = $container->get( 'session.handler' ); $settings = $container->get( 'wcgateway.settings' ); - $subscription_helper = $container->get( 'subscription.helper' ); - return new DisableGateways( $session_handler, $settings, $subscription_helper ); + return new DisableGateways( $session_handler, $settings); }, 'wcgateway.is-wc-payments-page' => static function ( ContainerInterface $container ): bool { $page = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : ''; diff --git a/modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php b/modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php index cf4a7291d..8d3123865 100644 --- a/modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php +++ b/modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php @@ -10,7 +10,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\WcGateway\Checkout; use WooCommerce\PayPalCommerce\Session\SessionHandler; -use WooCommerce\PayPalCommerce\Subscription\Helper\SubscriptionHelper; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use Psr\Container\ContainerInterface; @@ -34,29 +33,19 @@ class DisableGateways { */ private $settings; - /** - * The subscription helper - * - * @var SubscriptionHelper - */ - private $subscription_helper; - /** * DisableGateways constructor. * * @param SessionHandler $session_handler The Session Handler. * @param ContainerInterface $settings The Settings. - * @param SubscriptionHelper $subscription_helper The subscription helper. */ public function __construct( SessionHandler $session_handler, - ContainerInterface $settings, - SubscriptionHelper $subscription_helper + ContainerInterface $settings ) { $this->session_handler = $session_handler; $this->settings = $settings; - $this->subscription_helper = $subscription_helper; } /** @@ -110,10 +99,6 @@ class DisableGateways { return true; } - if ( $this->subscription_helper->cart_contains_subscription() && ! is_user_logged_in() ) { - return true; - } - return false; } From ece3ef95d32886f7927c355caba9f767791f8cb5 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Wed, 22 Dec 2021 16:00:29 +0100 Subject: [PATCH 02/19] Display payment gateways for guest users when subscription in the cart --- modules/ppcp-button/src/Assets/SmartButton.php | 7 ------- 1 file changed, 7 deletions(-) diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index 62ec1aa90..aaa8cc809 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -177,10 +177,6 @@ class SmartButton implements SmartButtonInterface { */ public function render_wrapper(): bool { - if ( ! $this->can_save_vault_token() && $this->has_subscriptions() ) { - return false; - } - if ( $this->settings->has( 'enabled' ) && $this->settings->get( 'enabled' ) ) { $this->render_button_wrapper_registrar(); $this->render_message_wrapper_registrar(); @@ -391,9 +387,6 @@ class SmartButton implements SmartButtonInterface { if ( ! is_checkout() && ! $buttons_enabled ) { return false; } - if ( ! $this->can_save_vault_token() && $this->has_subscriptions() ) { - return false; - } $load_script = false; if ( is_checkout() && $this->settings->has( 'dcc_enabled' ) && $this->settings->get( 'dcc_enabled' ) ) { From d69f1b5a9c02942b38d3e42326d6e5541d1c732a Mon Sep 17 00:00:00 2001 From: dinamiko Date: Fri, 24 Dec 2021 12:37:06 +0100 Subject: [PATCH 03/19] Add guest customer id (WIP) --- .../src/Endpoint/IdentityToken.php | 10 +- .../src/Endpoint/PaymentTokenEndpoint.php | 5 + .../ppcp-button/src/Assets/SmartButton.php | 4 +- modules/ppcp-vaulting/src/VaultingModule.php | 4 + .../src/Gateway/ProcessPaymentTrait.php | 91 ------------------- 5 files changed, 18 insertions(+), 96 deletions(-) diff --git a/modules/ppcp-api-client/src/Endpoint/IdentityToken.php b/modules/ppcp-api-client/src/Endpoint/IdentityToken.php index 2e73b1af9..740219a3c 100644 --- a/modules/ppcp-api-client/src/Endpoint/IdentityToken.php +++ b/modules/ppcp-api-client/src/Endpoint/IdentityToken.php @@ -95,11 +95,15 @@ class IdentityToken { ), ); if ( - $customer_id - && ( $this->settings->has( 'vault_enabled' ) && $this->settings->get( 'vault_enabled' ) ) + ( $this->settings->has( 'vault_enabled' ) && $this->settings->get( 'vault_enabled' ) ) && defined( 'PPCP_FLAG_SUBSCRIPTION' ) && PPCP_FLAG_SUBSCRIPTION ) { - $args['body'] = wp_json_encode( array( 'customer_id' => $this->prefix . $customer_id ) ); + if($customer_id === 0) { + $customer_id = uniqid(); + WC()->session->set('ppcp_guest_customer_id', $customer_id); + } + + $args['body'] = wp_json_encode( array( 'customer_id' => $this->prefix . $customer_id) ); } $response = $this->request( $url, $args ); diff --git a/modules/ppcp-api-client/src/Endpoint/PaymentTokenEndpoint.php b/modules/ppcp-api-client/src/Endpoint/PaymentTokenEndpoint.php index e3c8ed903..60750fa44 100644 --- a/modules/ppcp-api-client/src/Endpoint/PaymentTokenEndpoint.php +++ b/modules/ppcp-api-client/src/Endpoint/PaymentTokenEndpoint.php @@ -93,6 +93,11 @@ class PaymentTokenEndpoint { $bearer = $this->bearer->bearer(); $customer_id = $this->prefix . $id; + $guest_customer_id_meta = get_user_meta( $id, 'ppcp_guest_customer_id', true ); + if($guest_customer_id_meta) { + $customer_id = $this->prefix . $guest_customer_id_meta; + } + $url = trailingslashit( $this->host ) . 'v2/vault/payment-tokens/?customer_id=' . $customer_id; $args = array( 'method' => 'GET', diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index aaa8cc809..e7b9bd348 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -590,7 +590,7 @@ class SmartButton implements SmartButtonInterface { return false; } - return is_user_logged_in(); + return true; } /** @@ -637,7 +637,7 @@ class SmartButton implements SmartButtonInterface { $localize = array( 'script_attributes' => $this->attributes(), 'data_client_id' => array( - 'set_attribute' => ( is_checkout() && $this->dcc_is_enabled() ) || $this->can_save_vault_token(), + 'set_attribute' => $this->can_save_vault_token(), 'endpoint' => home_url( \WC_AJAX::get_endpoint( DataClientIdEndpoint::ENDPOINT ) ), 'nonce' => wp_create_nonce( DataClientIdEndpoint::nonce() ), 'user' => get_current_user_id(), diff --git a/modules/ppcp-vaulting/src/VaultingModule.php b/modules/ppcp-vaulting/src/VaultingModule.php index a2090a2c1..0b2e039cb 100644 --- a/modules/ppcp-vaulting/src/VaultingModule.php +++ b/modules/ppcp-vaulting/src/VaultingModule.php @@ -98,6 +98,10 @@ class VaultingModule implements ModuleInterface { } ); + add_action('woocommerce_created_customer', function($customer_id) { + update_user_meta($customer_id, 'ppcp_guest_customer_id', WC()->session->get('ppcp_guest_customer_id')); + }); + $asset_loader = $container->get( 'vaulting.assets.myaccount-payments' ); add_action( 'wp_enqueue_scripts', diff --git a/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php b/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php index 7e974664c..944594f36 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php +++ b/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php @@ -174,97 +174,6 @@ trait ProcessPaymentTrait { try { if ( $this->order_processor->process( $wc_order ) ) { - if ( $this->subscription_helper->has_subscription( $order_id ) ) { - $this->logger->info( "Trying to save payment for subscription parent order #{$order_id}." ); - - $tokens = $this->payment_token_repository->all_for_user_id( $wc_order->get_customer_id() ); - if ( $tokens ) { - $this->logger->info( "Payment for subscription parent order #{$order_id} was saved correctly." ); - - if ( $this->config->has( 'intent' ) && strtoupper( (string) $this->config->get( 'intent' ) ) === 'CAPTURE' ) { - $this->authorized_payments_processor->capture_authorized_payment( $wc_order ); - } - - $this->session_handler->destroy_session_data(); - - return array( - 'result' => 'success', - 'redirect' => $this->get_return_url( $wc_order ), - ); - } - - $this->logger->error( "Payment for subscription parent order #{$order_id} was not saved." ); - - $paypal_order_id = $wc_order->get_meta( PayPalGateway::ORDER_ID_META_KEY ); - if ( ! $paypal_order_id ) { - throw new RuntimeException( 'PayPal order ID not found in meta.' ); - } - $order = $this->order_endpoint->order( $paypal_order_id ); - - $purchase_units = $order->purchase_units(); - if ( ! $purchase_units ) { - throw new RuntimeException( 'No purchase units.' ); - } - - $payments = $purchase_units[0]->payments(); - if ( ! $payments ) { - throw new RuntimeException( 'No payments.' ); - } - - $this->logger->debug( - sprintf( - 'Trying to void order %1$s, payments: %2$s.', - $order->id(), - wp_json_encode( $payments->to_array() ) - ) - ); - - $voidable_authorizations = array_filter( - $payments->authorizations(), - function ( Authorization $authorization ): bool { - return $authorization->is_voidable(); - } - ); - if ( ! $voidable_authorizations ) { - throw new RuntimeException( 'No voidable authorizations.' ); - } - - foreach ( $voidable_authorizations as $authorization ) { - $this->payments_endpoint->void( $authorization ); - } - - $this->logger->debug( - sprintf( - 'Order %1$s voided successfully.', - $order->id() - ) - ); - - $error_message = __( 'Could not process order because it was not possible to save the payment.', 'woocommerce-paypal-payments' ); - $wc_order->update_status( 'failed', $error_message ); - - $subscriptions = wcs_get_subscriptions_for_order( $order_id ); - foreach ( $subscriptions as $key => $subscription ) { - if ( $subscription->get_parent_id() === $order_id ) { - try { - $subscription->update_status( 'cancelled' ); - break; - } catch ( Exception $exception ) { - $this->logger->error( "Could not update cancelled status on subscription #{$subscription->get_id()} " . $exception->getMessage() ); - } - } - } - - // Adds retry counter meta to avoid duplicate invoice id error on consequent tries. - $wc_order->update_meta_data( 'ppcp-retry', (int) $wc_order->get_meta( 'ppcp-retry' ) + 1 ); - $wc_order->save_meta_data(); - - $this->session_handler->destroy_session_data(); - wc_add_notice( $error_message, 'error' ); - - return $failure_data; - } - WC()->cart->empty_cart(); $this->session_handler->destroy_session_data(); From d33ca00ac7cd7e0bd6cd12723c372956eb6a237f Mon Sep 17 00:00:00 2001 From: dinamiko Date: Mon, 3 Jan 2022 15:15:33 +0100 Subject: [PATCH 04/19] Fix CI errors --- .../ppcp-api-client/src/Endpoint/IdentityToken.php | 6 +++--- .../src/Endpoint/PaymentTokenEndpoint.php | 12 ++++++------ modules/ppcp-vaulting/src/VaultingModule.php | 13 ++++++++++--- modules/ppcp-wc-gateway/services.php | 2 +- .../src/Checkout/DisableGateways.php | 4 ++-- .../ApiClient/Endpoint/PaymentTokenEndpointTest.php | 11 +++++++++++ 6 files changed, 33 insertions(+), 15 deletions(-) diff --git a/modules/ppcp-api-client/src/Endpoint/IdentityToken.php b/modules/ppcp-api-client/src/Endpoint/IdentityToken.php index 740219a3c..ae12fdfca 100644 --- a/modules/ppcp-api-client/src/Endpoint/IdentityToken.php +++ b/modules/ppcp-api-client/src/Endpoint/IdentityToken.php @@ -98,12 +98,12 @@ class IdentityToken { ( $this->settings->has( 'vault_enabled' ) && $this->settings->get( 'vault_enabled' ) ) && defined( 'PPCP_FLAG_SUBSCRIPTION' ) && PPCP_FLAG_SUBSCRIPTION ) { - if($customer_id === 0) { + if ( 0 === $customer_id ) { $customer_id = uniqid(); - WC()->session->set('ppcp_guest_customer_id', $customer_id); + WC()->session->set( 'ppcp_guest_customer_id', $customer_id ); } - $args['body'] = wp_json_encode( array( 'customer_id' => $this->prefix . $customer_id) ); + $args['body'] = wp_json_encode( array( 'customer_id' => $this->prefix . $customer_id ) ); } $response = $this->request( $url, $args ); diff --git a/modules/ppcp-api-client/src/Endpoint/PaymentTokenEndpoint.php b/modules/ppcp-api-client/src/Endpoint/PaymentTokenEndpoint.php index 60750fa44..8b27ec969 100644 --- a/modules/ppcp-api-client/src/Endpoint/PaymentTokenEndpoint.php +++ b/modules/ppcp-api-client/src/Endpoint/PaymentTokenEndpoint.php @@ -92,14 +92,14 @@ class PaymentTokenEndpoint { public function for_user( int $id ): array { $bearer = $this->bearer->bearer(); - $customer_id = $this->prefix . $id; - $guest_customer_id_meta = get_user_meta( $id, 'ppcp_guest_customer_id', true ); - if($guest_customer_id_meta) { - $customer_id = $this->prefix . $guest_customer_id_meta; + $customer_id = $this->prefix . $id; + $guest_customer_id = get_user_meta( $id, 'ppcp_guest_customer_id', true ); + if ( $guest_customer_id ) { + $customer_id = $this->prefix . $guest_customer_id; } - $url = trailingslashit( $this->host ) . 'v2/vault/payment-tokens/?customer_id=' . $customer_id; - $args = array( + $url = trailingslashit( $this->host ) . 'v2/vault/payment-tokens/?customer_id=' . $customer_id; + $args = array( 'method' => 'GET', 'headers' => array( 'Authorization' => 'Bearer ' . $bearer->token(), diff --git a/modules/ppcp-vaulting/src/VaultingModule.php b/modules/ppcp-vaulting/src/VaultingModule.php index 0b2e039cb..bedaec1bc 100644 --- a/modules/ppcp-vaulting/src/VaultingModule.php +++ b/modules/ppcp-vaulting/src/VaultingModule.php @@ -98,9 +98,16 @@ class VaultingModule implements ModuleInterface { } ); - add_action('woocommerce_created_customer', function($customer_id) { - update_user_meta($customer_id, 'ppcp_guest_customer_id', WC()->session->get('ppcp_guest_customer_id')); - }); + $subscription_helper = $container->get( 'subscription.helper' ); + add_action( + 'woocommerce_created_customer', + function( $customer_id ) use ( $subscription_helper ) { + $guest_customer_id = WC()->session->get( 'ppcp_guest_customer_id' ); + if ( $guest_customer_id && $subscription_helper->cart_contains_subscription() ) { + update_user_meta( $customer_id, 'ppcp_guest_customer_id', WC()->session->get( 'ppcp_guest_customer_id' ) ); + } + } + ); $asset_loader = $container->get( 'vaulting.assets.myaccount-payments' ); add_action( diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index 1a34c36ef..5de2ff87e 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -120,7 +120,7 @@ return array( 'wcgateway.disabler' => static function ( ContainerInterface $container ): DisableGateways { $session_handler = $container->get( 'session.handler' ); $settings = $container->get( 'wcgateway.settings' ); - return new DisableGateways( $session_handler, $settings); + return new DisableGateways( $session_handler, $settings ); }, 'wcgateway.is-wc-payments-page' => static function ( ContainerInterface $container ): bool { $page = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : ''; diff --git a/modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php b/modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php index 8d3123865..64520b38c 100644 --- a/modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php +++ b/modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php @@ -44,8 +44,8 @@ class DisableGateways { ContainerInterface $settings ) { - $this->session_handler = $session_handler; - $this->settings = $settings; + $this->session_handler = $session_handler; + $this->settings = $settings; } /** diff --git a/tests/PHPUnit/ApiClient/Endpoint/PaymentTokenEndpointTest.php b/tests/PHPUnit/ApiClient/Endpoint/PaymentTokenEndpointTest.php index 7f1b8fd7c..032da9793 100644 --- a/tests/PHPUnit/ApiClient/Endpoint/PaymentTokenEndpointTest.php +++ b/tests/PHPUnit/ApiClient/Endpoint/PaymentTokenEndpointTest.php @@ -15,6 +15,7 @@ use WooCommerce\PayPalCommerce\ApiClient\TestCase; use Mockery; use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration; use function Brain\Monkey\Functions\expect; +use function Brain\Monkey\Functions\when; class PaymentTokenEndpointTest extends TestCase { @@ -73,6 +74,8 @@ class PaymentTokenEndpointTest extends TestCase $this->logger->shouldReceive('debug'); + when('get_user_meta')->justReturn(''); + $result = $this->sut->for_user($id); $this->assertInstanceOf(PaymentToken::class, $result[0]); @@ -96,6 +99,8 @@ class PaymentTokenEndpointTest extends TestCase $this->logger->shouldReceive('log'); $this->logger->shouldReceive('debug'); + when('get_user_meta')->justReturn(''); + $this->expectException(RuntimeException::class); $this->sut->for_user($id); } @@ -123,6 +128,8 @@ class PaymentTokenEndpointTest extends TestCase $this->logger->shouldReceive('log'); $this->logger->shouldReceive('debug'); + when('get_user_meta')->justReturn(''); + $this->expectException(PayPalApiException::class); $this->sut->for_user($id); } @@ -185,6 +192,10 @@ class PaymentTokenEndpointTest extends TestCase $prefix = $this->prefix; expect('wp_remote_get')->andReturnUsing( function ($url, $args) use ($rawResponse, $host, $prefix, $id) { + // echo $url; // https://example.com/v2/vault/payment-tokens/?customer_id=prefixabc123 + // https://example.com/v2/vault/payment-tokens/?customer_id=prefix1 + // https://example.com/v2/vault/payment-tokens/?customer_id=prefixabc123 + echo $host . 'v2/vault/payment-tokens/?customer_id=' . $prefix . $id; if ($url !== $host . 'v2/vault/payment-tokens/?customer_id=' . $prefix . $id) { return false; } From f1ae75a07e6d77bcc2531c8aab0459441f7715f3 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Mon, 3 Jan 2022 15:26:07 +0100 Subject: [PATCH 05/19] Fix CI errors --- modules/ppcp-api-client/src/Endpoint/IdentityToken.php | 2 +- modules/ppcp-vaulting/src/VaultingModule.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/ppcp-api-client/src/Endpoint/IdentityToken.php b/modules/ppcp-api-client/src/Endpoint/IdentityToken.php index ae12fdfca..ee43106ac 100644 --- a/modules/ppcp-api-client/src/Endpoint/IdentityToken.php +++ b/modules/ppcp-api-client/src/Endpoint/IdentityToken.php @@ -103,7 +103,7 @@ class IdentityToken { WC()->session->set( 'ppcp_guest_customer_id', $customer_id ); } - $args['body'] = wp_json_encode( array( 'customer_id' => $this->prefix . $customer_id ) ); + $args['body'] = wp_json_encode( array( 'customer_id' => $this->prefix . (string) $customer_id ) ); } $response = $this->request( $url, $args ); diff --git a/modules/ppcp-vaulting/src/VaultingModule.php b/modules/ppcp-vaulting/src/VaultingModule.php index bedaec1bc..d74dfc86e 100644 --- a/modules/ppcp-vaulting/src/VaultingModule.php +++ b/modules/ppcp-vaulting/src/VaultingModule.php @@ -101,10 +101,10 @@ class VaultingModule implements ModuleInterface { $subscription_helper = $container->get( 'subscription.helper' ); add_action( 'woocommerce_created_customer', - function( $customer_id ) use ( $subscription_helper ) { + function( int $customer_id ) use ( $subscription_helper ) { $guest_customer_id = WC()->session->get( 'ppcp_guest_customer_id' ); if ( $guest_customer_id && $subscription_helper->cart_contains_subscription() ) { - update_user_meta( $customer_id, 'ppcp_guest_customer_id', WC()->session->get( 'ppcp_guest_customer_id' ) ); + update_user_meta( $customer_id, 'ppcp_guest_customer_id', $guest_customer_id ); } } ); From 2108b35a21ed4ea0d26ef57b82159b59065dce95 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Tue, 4 Jan 2022 12:11:19 +0100 Subject: [PATCH 06/19] Do not check saved payments via webhooks for now, see #405 --- .../src/Gateway/ProcessPaymentTrait.php | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php b/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php index 944594f36..7e974664c 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php +++ b/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php @@ -174,6 +174,97 @@ trait ProcessPaymentTrait { try { if ( $this->order_processor->process( $wc_order ) ) { + if ( $this->subscription_helper->has_subscription( $order_id ) ) { + $this->logger->info( "Trying to save payment for subscription parent order #{$order_id}." ); + + $tokens = $this->payment_token_repository->all_for_user_id( $wc_order->get_customer_id() ); + if ( $tokens ) { + $this->logger->info( "Payment for subscription parent order #{$order_id} was saved correctly." ); + + if ( $this->config->has( 'intent' ) && strtoupper( (string) $this->config->get( 'intent' ) ) === 'CAPTURE' ) { + $this->authorized_payments_processor->capture_authorized_payment( $wc_order ); + } + + $this->session_handler->destroy_session_data(); + + return array( + 'result' => 'success', + 'redirect' => $this->get_return_url( $wc_order ), + ); + } + + $this->logger->error( "Payment for subscription parent order #{$order_id} was not saved." ); + + $paypal_order_id = $wc_order->get_meta( PayPalGateway::ORDER_ID_META_KEY ); + if ( ! $paypal_order_id ) { + throw new RuntimeException( 'PayPal order ID not found in meta.' ); + } + $order = $this->order_endpoint->order( $paypal_order_id ); + + $purchase_units = $order->purchase_units(); + if ( ! $purchase_units ) { + throw new RuntimeException( 'No purchase units.' ); + } + + $payments = $purchase_units[0]->payments(); + if ( ! $payments ) { + throw new RuntimeException( 'No payments.' ); + } + + $this->logger->debug( + sprintf( + 'Trying to void order %1$s, payments: %2$s.', + $order->id(), + wp_json_encode( $payments->to_array() ) + ) + ); + + $voidable_authorizations = array_filter( + $payments->authorizations(), + function ( Authorization $authorization ): bool { + return $authorization->is_voidable(); + } + ); + if ( ! $voidable_authorizations ) { + throw new RuntimeException( 'No voidable authorizations.' ); + } + + foreach ( $voidable_authorizations as $authorization ) { + $this->payments_endpoint->void( $authorization ); + } + + $this->logger->debug( + sprintf( + 'Order %1$s voided successfully.', + $order->id() + ) + ); + + $error_message = __( 'Could not process order because it was not possible to save the payment.', 'woocommerce-paypal-payments' ); + $wc_order->update_status( 'failed', $error_message ); + + $subscriptions = wcs_get_subscriptions_for_order( $order_id ); + foreach ( $subscriptions as $key => $subscription ) { + if ( $subscription->get_parent_id() === $order_id ) { + try { + $subscription->update_status( 'cancelled' ); + break; + } catch ( Exception $exception ) { + $this->logger->error( "Could not update cancelled status on subscription #{$subscription->get_id()} " . $exception->getMessage() ); + } + } + } + + // Adds retry counter meta to avoid duplicate invoice id error on consequent tries. + $wc_order->update_meta_data( 'ppcp-retry', (int) $wc_order->get_meta( 'ppcp-retry' ) + 1 ); + $wc_order->save_meta_data(); + + $this->session_handler->destroy_session_data(); + wc_add_notice( $error_message, 'error' ); + + return $failure_data; + } + WC()->cart->empty_cart(); $this->session_handler->destroy_session_data(); From 5786ae6bbd3e70a77caa7832dd9fdbe9f8497d8d Mon Sep 17 00:00:00 2001 From: dinamiko Date: Tue, 4 Jan 2022 14:53:52 +0100 Subject: [PATCH 07/19] Use guest customer id when loading script if it exist --- modules/ppcp-api-client/src/Endpoint/IdentityToken.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/ppcp-api-client/src/Endpoint/IdentityToken.php b/modules/ppcp-api-client/src/Endpoint/IdentityToken.php index ee43106ac..83b01d9ae 100644 --- a/modules/ppcp-api-client/src/Endpoint/IdentityToken.php +++ b/modules/ppcp-api-client/src/Endpoint/IdentityToken.php @@ -103,6 +103,11 @@ class IdentityToken { WC()->session->set( 'ppcp_guest_customer_id', $customer_id ); } + $guest_customer_id = get_user_meta( $customer_id, 'ppcp_guest_customer_id', true ); + if ( $guest_customer_id ) { + $customer_id = $guest_customer_id; + } + $args['body'] = wp_json_encode( array( 'customer_id' => $this->prefix . (string) $customer_id ) ); } From ab59654a9b405599f2c9d9db04df67b13c376b87 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Tue, 4 Jan 2022 14:59:35 +0100 Subject: [PATCH 08/19] Fix phpunit errors --- tests/PHPUnit/ApiClient/Endpoint/IdentityTokenTest.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/PHPUnit/ApiClient/Endpoint/IdentityTokenTest.php b/tests/PHPUnit/ApiClient/Endpoint/IdentityTokenTest.php index 6cfc69c83..570dd164f 100644 --- a/tests/PHPUnit/ApiClient/Endpoint/IdentityTokenTest.php +++ b/tests/PHPUnit/ApiClient/Endpoint/IdentityTokenTest.php @@ -82,6 +82,7 @@ class IdentityTokenTest extends TestCase expect('is_wp_error')->with($rawResponse)->andReturn(false); expect('wp_remote_retrieve_response_code')->with($rawResponse)->andReturn(200); when('wc_print_r')->returnArg(); + when('get_user_meta')->justReturn(''); $result = $this->sut->generate_for_customer(1); $this->assertInstanceOf(Token::class, $result); @@ -104,6 +105,7 @@ class IdentityTokenTest extends TestCase $this->logger->shouldReceive('debug'); $this->settings->shouldReceive('has')->andReturn(true); $this->settings->shouldReceive('get')->andReturn(true); + when('get_user_meta')->justReturn(''); $this->expectException(RuntimeException::class); $this->sut->generate_for_customer(1); @@ -126,6 +128,7 @@ class IdentityTokenTest extends TestCase expect('is_wp_error')->andReturn(false); expect('wp_remote_retrieve_response_code')->andReturn(500); when('wc_print_r')->returnArg(); + when('get_user_meta')->justReturn(''); $this->logger->shouldReceive('log'); $this->logger->shouldReceive('debug'); $this->settings->shouldReceive('has')->andReturn(true); From d6e36e559b6679f8df1d3822b722192fdadd31e4 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Wed, 5 Jan 2022 10:28:40 +0100 Subject: [PATCH 09/19] Fix psalm error --- modules/ppcp-api-client/src/Endpoint/IdentityToken.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/ppcp-api-client/src/Endpoint/IdentityToken.php b/modules/ppcp-api-client/src/Endpoint/IdentityToken.php index 83b01d9ae..8583cff01 100644 --- a/modules/ppcp-api-client/src/Endpoint/IdentityToken.php +++ b/modules/ppcp-api-client/src/Endpoint/IdentityToken.php @@ -98,16 +98,16 @@ class IdentityToken { ( $this->settings->has( 'vault_enabled' ) && $this->settings->get( 'vault_enabled' ) ) && defined( 'PPCP_FLAG_SUBSCRIPTION' ) && PPCP_FLAG_SUBSCRIPTION ) { - if ( 0 === $customer_id ) { - $customer_id = uniqid(); - WC()->session->set( 'ppcp_guest_customer_id', $customer_id ); - } - $guest_customer_id = get_user_meta( $customer_id, 'ppcp_guest_customer_id', true ); if ( $guest_customer_id ) { $customer_id = $guest_customer_id; } + if ( 0 === $customer_id ) { + $customer_id = uniqid(); + WC()->session->set( 'ppcp_guest_customer_id', $customer_id ); + } + $args['body'] = wp_json_encode( array( 'customer_id' => $this->prefix . (string) $customer_id ) ); } From 0ae7e6e9bc97f8cbbc99953db30454a41b2e65bf Mon Sep 17 00:00:00 2001 From: dinamiko Date: Wed, 5 Jan 2022 12:49:49 +0100 Subject: [PATCH 10/19] Extract customer id retrieval --- modules/ppcp-api-client/services.php | 13 ++-- .../src/Endpoint/IdentityToken.php | 65 ++++++++++--------- .../src/Endpoint/PaymentTokenEndpoint.php | 33 ++++------ .../src/Repository/CustomerRepository.php | 52 +++++++++++++++ .../ApiClient/Endpoint/IdentityTokenTest.php | 18 +++-- .../Endpoint/PaymentTokenEndpointTest.php | 31 ++++----- 6 files changed, 134 insertions(+), 78 deletions(-) create mode 100644 modules/ppcp-api-client/src/Repository/CustomerRepository.php diff --git a/modules/ppcp-api-client/services.php b/modules/ppcp-api-client/services.php index 49b1408bf..f8054905b 100644 --- a/modules/ppcp-api-client/services.php +++ b/modules/ppcp-api-client/services.php @@ -43,6 +43,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache; use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies; use WooCommerce\PayPalCommerce\ApiClient\Repository\ApplicationContextRepository; use WooCommerce\PayPalCommerce\ApiClient\Repository\CartRepository; +use WooCommerce\PayPalCommerce\ApiClient\Repository\CustomerRepository; use WooCommerce\PayPalCommerce\ApiClient\Repository\PartnerReferralsData; use WooCommerce\PayPalCommerce\ApiClient\Repository\PayeeRepository; use WooCommerce\PayPalCommerce\ApiClient\Repository\PayPalRequestIdRepository; @@ -108,7 +109,7 @@ return array( $container->get( 'api.bearer' ), $container->get( 'api.factory.payment-token' ), $container->get( 'woocommerce.logger.woocommerce' ), - $container->get( 'api.prefix' ) + $container->get( 'api.repository.customer' ) ); }, 'api.endpoint.webhook' => static function ( ContainerInterface $container ) : WebhookEndpoint { @@ -132,14 +133,14 @@ return array( }, 'api.endpoint.identity-token' => static function ( ContainerInterface $container ) : IdentityToken { $logger = $container->get( 'woocommerce.logger.woocommerce' ); - $prefix = $container->get( 'api.prefix' ); $settings = $container->get( 'wcgateway.settings' ); + $customer_repository = $container->get( 'api.repository.customer' ); return new IdentityToken( $container->get( 'api.host' ), $container->get( 'api.bearer' ), $logger, - $prefix, - $settings + $settings, + $customer_repository ); }, 'api.endpoint.payments' => static function ( ContainerInterface $container ): PaymentsEndpoint { @@ -221,6 +222,10 @@ return array( $merchant_id = $container->get( 'api.merchant_id' ); return new PayeeRepository( $merchant_email, $merchant_id ); }, + 'api.repository.customer' => static function( ContainerInterface $container ): CustomerRepository { + $prefix = $container->get( 'api.prefix' ); + return new CustomerRepository( $prefix ); + }, 'api.factory.application-context' => static function ( ContainerInterface $container ) : ApplicationContextFactory { return new ApplicationContextFactory(); }, diff --git a/modules/ppcp-api-client/src/Endpoint/IdentityToken.php b/modules/ppcp-api-client/src/Endpoint/IdentityToken.php index 8583cff01..e56e74b7d 100644 --- a/modules/ppcp-api-client/src/Endpoint/IdentityToken.php +++ b/modules/ppcp-api-client/src/Endpoint/IdentityToken.php @@ -14,6 +14,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Entity\Token; use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; use Psr\Log\LoggerInterface; +use WooCommerce\PayPalCommerce\ApiClient\Repository\CustomerRepository; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; /** @@ -44,13 +45,6 @@ class IdentityToken { */ private $logger; - /** - * The prefix. - * - * @var string - */ - private $prefix; - /** * The settings * @@ -58,32 +52,45 @@ class IdentityToken { */ private $settings; + /** + * The customer repository. + * + * @var CustomerRepository + */ + protected $customer_repository; + /** * IdentityToken constructor. * - * @param string $host The host. - * @param Bearer $bearer The bearer. - * @param LoggerInterface $logger The logger. - * @param string $prefix The prefix. - * @param Settings $settings The settings. + * @param string $host The host. + * @param Bearer $bearer The bearer. + * @param LoggerInterface $logger The logger. + * @param Settings $settings The settings. + * @param CustomerRepository $customer_repository The customer repository. */ - public function __construct( string $host, Bearer $bearer, LoggerInterface $logger, string $prefix, Settings $settings ) { - $this->host = $host; - $this->bearer = $bearer; - $this->logger = $logger; - $this->prefix = $prefix; - $this->settings = $settings; + public function __construct( + string $host, + Bearer $bearer, + LoggerInterface $logger, + Settings $settings, + CustomerRepository $customer_repository + ) { + $this->host = $host; + $this->bearer = $bearer; + $this->logger = $logger; + $this->settings = $settings; + $this->customer_repository = $customer_repository; } /** - * Generates a token for a specific customer. + * Generates a token for a specific user. * - * @param int $customer_id The id of the customer. + * @param int $user_id The id of the user. * * @return Token * @throws RuntimeException If the request fails. */ - public function generate_for_customer( int $customer_id ): Token { + public function generate_for_customer( int $user_id ): Token { $bearer = $this->bearer->bearer(); $url = trailingslashit( $this->host ) . 'v1/identity/generate-token'; @@ -98,17 +105,11 @@ class IdentityToken { ( $this->settings->has( 'vault_enabled' ) && $this->settings->get( 'vault_enabled' ) ) && defined( 'PPCP_FLAG_SUBSCRIPTION' ) && PPCP_FLAG_SUBSCRIPTION ) { - $guest_customer_id = get_user_meta( $customer_id, 'ppcp_guest_customer_id', true ); - if ( $guest_customer_id ) { - $customer_id = $guest_customer_id; - } - - if ( 0 === $customer_id ) { - $customer_id = uniqid(); - WC()->session->set( 'ppcp_guest_customer_id', $customer_id ); - } - - $args['body'] = wp_json_encode( array( 'customer_id' => $this->prefix . (string) $customer_id ) ); + $args['body'] = wp_json_encode( + array( + 'customer_id' => $this->customer_repository->customer_id_for_user( ( $user_id ) ), + ) + ); } $response = $this->request( $url, $args ); diff --git a/modules/ppcp-api-client/src/Endpoint/PaymentTokenEndpoint.php b/modules/ppcp-api-client/src/Endpoint/PaymentTokenEndpoint.php index 8b27ec969..1e8dcf871 100644 --- a/modules/ppcp-api-client/src/Endpoint/PaymentTokenEndpoint.php +++ b/modules/ppcp-api-client/src/Endpoint/PaymentTokenEndpoint.php @@ -15,6 +15,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; use WooCommerce\PayPalCommerce\ApiClient\Factory\PaymentTokenFactory; use Psr\Log\LoggerInterface; +use WooCommerce\PayPalCommerce\ApiClient\Repository\CustomerRepository; /** * Class PaymentTokenEndpoint @@ -50,12 +51,13 @@ class PaymentTokenEndpoint { * @var LoggerInterface */ private $logger; + /** - * The prefix. + * The customer repository. * - * @var string + * @var CustomerRepository */ - private $prefix; + protected $customer_repository; /** * PaymentTokenEndpoint constructor. @@ -64,21 +66,21 @@ class PaymentTokenEndpoint { * @param Bearer $bearer The bearer. * @param PaymentTokenFactory $factory The payment token factory. * @param LoggerInterface $logger The logger. - * @param string $prefix The prefix. + * @param CustomerRepository $customer_repository The customer repository. */ public function __construct( string $host, Bearer $bearer, PaymentTokenFactory $factory, LoggerInterface $logger, - string $prefix + CustomerRepository $customer_repository ) { - $this->host = $host; - $this->bearer = $bearer; - $this->factory = $factory; - $this->logger = $logger; - $this->prefix = $prefix; + $this->host = $host; + $this->bearer = $bearer; + $this->factory = $factory; + $this->logger = $logger; + $this->customer_repository = $customer_repository; } /** @@ -91,15 +93,8 @@ class PaymentTokenEndpoint { */ public function for_user( int $id ): array { $bearer = $this->bearer->bearer(); - - $customer_id = $this->prefix . $id; - $guest_customer_id = get_user_meta( $id, 'ppcp_guest_customer_id', true ); - if ( $guest_customer_id ) { - $customer_id = $this->prefix . $guest_customer_id; - } - - $url = trailingslashit( $this->host ) . 'v2/vault/payment-tokens/?customer_id=' . $customer_id; - $args = array( + $url = trailingslashit( $this->host ) . 'v2/vault/payment-tokens/?customer_id=' . $this->customer_repository->customer_id_for_user( $id ); + $args = array( 'method' => 'GET', 'headers' => array( 'Authorization' => 'Bearer ' . $bearer->token(), diff --git a/modules/ppcp-api-client/src/Repository/CustomerRepository.php b/modules/ppcp-api-client/src/Repository/CustomerRepository.php new file mode 100644 index 000000000..d30501c4f --- /dev/null +++ b/modules/ppcp-api-client/src/Repository/CustomerRepository.php @@ -0,0 +1,52 @@ +prefix = $prefix; + } + + /** + * Returns a unique ID for the given user ID. + * + * @param int $user_id The user ID. + * @return string + */ + public function customer_id_for_user( int $user_id ): string { + $guest_customer_id = get_user_meta( $user_id, 'ppcp_guest_customer_id', true ); + if ( $guest_customer_id ) { + $user_id = $guest_customer_id; + } + + if ( 0 === $user_id ) { + $user_id = uniqid(); + WC()->session->set( 'ppcp_guest_customer_id', $user_id ); + } + + return $this->prefix . $user_id; + } +} diff --git a/tests/PHPUnit/ApiClient/Endpoint/IdentityTokenTest.php b/tests/PHPUnit/ApiClient/Endpoint/IdentityTokenTest.php index 570dd164f..134ecc885 100644 --- a/tests/PHPUnit/ApiClient/Endpoint/IdentityTokenTest.php +++ b/tests/PHPUnit/ApiClient/Endpoint/IdentityTokenTest.php @@ -9,6 +9,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer; use WooCommerce\PayPalCommerce\ApiClient\Entity\Token; use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; +use WooCommerce\PayPalCommerce\ApiClient\Repository\CustomerRepository; use WooCommerce\PayPalCommerce\ApiClient\TestCase; use Mockery; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; @@ -20,8 +21,8 @@ class IdentityTokenTest extends TestCase private $host; private $bearer; private $logger; - private $prefix; private $settings; + private $customer_repository; private $sut; public function setUp(): void @@ -31,10 +32,16 @@ class IdentityTokenTest extends TestCase $this->host = 'https://example.com/'; $this->bearer = Mockery::mock(Bearer::class); $this->logger = Mockery::mock(LoggerInterface::class); - $this->prefix = 'prefix'; $this->settings = Mockery::mock(Settings::class); + $this->customer_repository = Mockery::mock(CustomerRepository::class); - $this->sut = new IdentityToken($this->host, $this->bearer, $this->logger, $this->prefix, $this->settings); + $this->sut = new IdentityToken( + $this->host, + $this->bearer, + $this->logger, + $this->settings, + $this->customer_repository + ); } public function testGenerateForCustomerReturnsToken() @@ -52,6 +59,7 @@ class IdentityTokenTest extends TestCase $this->logger->shouldReceive('debug'); $this->settings->shouldReceive('has')->andReturn(true); $this->settings->shouldReceive('get')->andReturn(true); + $this->customer_repository->shouldReceive('customer_id_for_user')->andReturn('prefix1'); $rawResponse = [ 'body' => '{"client_token":"abc123", "expires_in":3600}', @@ -105,7 +113,7 @@ class IdentityTokenTest extends TestCase $this->logger->shouldReceive('debug'); $this->settings->shouldReceive('has')->andReturn(true); $this->settings->shouldReceive('get')->andReturn(true); - when('get_user_meta')->justReturn(''); + $this->customer_repository->shouldReceive('customer_id_for_user'); $this->expectException(RuntimeException::class); $this->sut->generate_for_customer(1); @@ -128,11 +136,11 @@ class IdentityTokenTest extends TestCase expect('is_wp_error')->andReturn(false); expect('wp_remote_retrieve_response_code')->andReturn(500); when('wc_print_r')->returnArg(); - when('get_user_meta')->justReturn(''); $this->logger->shouldReceive('log'); $this->logger->shouldReceive('debug'); $this->settings->shouldReceive('has')->andReturn(true); $this->settings->shouldReceive('get')->andReturn(true); + $this->customer_repository->shouldReceive('customer_id_for_user'); $this->expectException(PayPalApiException::class); $this->sut->generate_for_customer(1); diff --git a/tests/PHPUnit/ApiClient/Endpoint/PaymentTokenEndpointTest.php b/tests/PHPUnit/ApiClient/Endpoint/PaymentTokenEndpointTest.php index 032da9793..50a49d912 100644 --- a/tests/PHPUnit/ApiClient/Endpoint/PaymentTokenEndpointTest.php +++ b/tests/PHPUnit/ApiClient/Endpoint/PaymentTokenEndpointTest.php @@ -11,11 +11,11 @@ use WooCommerce\PayPalCommerce\ApiClient\Entity\Token; use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; use WooCommerce\PayPalCommerce\ApiClient\Factory\PaymentTokenFactory; +use WooCommerce\PayPalCommerce\ApiClient\Repository\CustomerRepository; use WooCommerce\PayPalCommerce\ApiClient\TestCase; use Mockery; use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration; use function Brain\Monkey\Functions\expect; -use function Brain\Monkey\Functions\when; class PaymentTokenEndpointTest extends TestCase { @@ -25,7 +25,7 @@ class PaymentTokenEndpointTest extends TestCase private $bearer; private $factory; private $logger; - private $prefix; + private $customer_repository; private $sut; public function setUp(): void @@ -36,13 +36,13 @@ class PaymentTokenEndpointTest extends TestCase $this->bearer = Mockery::mock(Bearer::class); $this->factory = Mockery::mock(PaymentTokenFactory::class); $this->logger = Mockery::mock(LoggerInterface::class); - $this->prefix = 'prefix'; + $this->customer_repository = Mockery::mock(CustomerRepository::class); $this->sut = new PaymentTokenEndpoint( $this->host, $this->bearer, $this->factory, $this->logger, - $this->prefix + $this->customer_repository ); } @@ -65,7 +65,7 @@ class PaymentTokenEndpointTest extends TestCase $token->shouldReceive('token') ->andReturn('bearer'); - $this->ensureRequestForUser($rawResponse, $id); + $this->ensureRequestForUser($rawResponse, '123abc'); expect('is_wp_error')->with($rawResponse)->andReturn(false); expect('wp_remote_retrieve_response_code')->with($rawResponse)->andReturn(200); @@ -74,7 +74,7 @@ class PaymentTokenEndpointTest extends TestCase $this->logger->shouldReceive('debug'); - when('get_user_meta')->justReturn(''); + $this->customer_repository->shouldReceive('customer_id_for_user')->andReturn('123abc'); $result = $this->sut->for_user($id); $this->assertInstanceOf(PaymentToken::class, $result[0]); @@ -92,14 +92,14 @@ class PaymentTokenEndpointTest extends TestCase ->andReturn($token); $token->shouldReceive('token') ->andReturn('bearer'); - $this->ensureRequestForUser($rawResponse, $id); + $this->ensureRequestForUser($rawResponse, '123abc'); expect('wp_remote_get')->andReturn($rawResponse); expect('is_wp_error')->with($rawResponse)->andReturn(true); $this->logger->shouldReceive('log'); $this->logger->shouldReceive('debug'); - when('get_user_meta')->justReturn(''); + $this->customer_repository->shouldReceive('customer_id_for_user')->andReturn('123abc'); $this->expectException(RuntimeException::class); $this->sut->for_user($id); @@ -119,7 +119,7 @@ class PaymentTokenEndpointTest extends TestCase ->andReturn($token); $token->shouldReceive('token') ->andReturn('bearer'); - $this->ensureRequestForUser($rawResponse, $id); + $this->ensureRequestForUser($rawResponse, '123abc'); expect('wp_remote_get')->andReturn($rawResponse); @@ -128,7 +128,7 @@ class PaymentTokenEndpointTest extends TestCase $this->logger->shouldReceive('log'); $this->logger->shouldReceive('debug'); - when('get_user_meta')->justReturn(''); + $this->customer_repository->shouldReceive('customer_id_for_user')->andReturn('123abc'); $this->expectException(PayPalApiException::class); $this->sut->for_user($id); @@ -186,17 +186,12 @@ class PaymentTokenEndpointTest extends TestCase * @param int $id * @throws \Brain\Monkey\Expectation\Exception\ExpectationArgsRequired */ - private function ensureRequestForUser(array $rawResponse, int $id): void + private function ensureRequestForUser(array $rawResponse, string $id): void { $host = $this->host; - $prefix = $this->prefix; expect('wp_remote_get')->andReturnUsing( - function ($url, $args) use ($rawResponse, $host, $prefix, $id) { - // echo $url; // https://example.com/v2/vault/payment-tokens/?customer_id=prefixabc123 - // https://example.com/v2/vault/payment-tokens/?customer_id=prefix1 - // https://example.com/v2/vault/payment-tokens/?customer_id=prefixabc123 - echo $host . 'v2/vault/payment-tokens/?customer_id=' . $prefix . $id; - if ($url !== $host . 'v2/vault/payment-tokens/?customer_id=' . $prefix . $id) { + function ($url, $args) use ($rawResponse, $host, $id) { + if ($url !== $host . 'v2/vault/payment-tokens/?customer_id=' . $id) { return false; } if ($args['headers']['Authorization'] !== 'Bearer bearer') { From 65b97f63c5ad993c770b4470eaf449df9507d66b Mon Sep 17 00:00:00 2001 From: dinamiko Date: Wed, 5 Jan 2022 13:09:53 +0100 Subject: [PATCH 11/19] Refactoring --- modules/ppcp-api-client/src/Endpoint/IdentityToken.php | 9 ++++++++- .../src/Repository/CustomerRepository.php | 7 +------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/modules/ppcp-api-client/src/Endpoint/IdentityToken.php b/modules/ppcp-api-client/src/Endpoint/IdentityToken.php index e56e74b7d..b03d7aa13 100644 --- a/modules/ppcp-api-client/src/Endpoint/IdentityToken.php +++ b/modules/ppcp-api-client/src/Endpoint/IdentityToken.php @@ -105,9 +105,16 @@ class IdentityToken { ( $this->settings->has( 'vault_enabled' ) && $this->settings->get( 'vault_enabled' ) ) && defined( 'PPCP_FLAG_SUBSCRIPTION' ) && PPCP_FLAG_SUBSCRIPTION ) { + $customer_id = $this->customer_repository->customer_id_for_user(($user_id)); + + if ( 0 === $user_id ) { + $customer_id = uniqid(); + WC()->session->set( 'ppcp_guest_customer_id', $customer_id ); + } + $args['body'] = wp_json_encode( array( - 'customer_id' => $this->customer_repository->customer_id_for_user( ( $user_id ) ), + 'customer_id' => $customer_id, ) ); } diff --git a/modules/ppcp-api-client/src/Repository/CustomerRepository.php b/modules/ppcp-api-client/src/Repository/CustomerRepository.php index d30501c4f..9f21a2959 100644 --- a/modules/ppcp-api-client/src/Repository/CustomerRepository.php +++ b/modules/ppcp-api-client/src/Repository/CustomerRepository.php @@ -39,12 +39,7 @@ class CustomerRepository { public function customer_id_for_user( int $user_id ): string { $guest_customer_id = get_user_meta( $user_id, 'ppcp_guest_customer_id', true ); if ( $guest_customer_id ) { - $user_id = $guest_customer_id; - } - - if ( 0 === $user_id ) { - $user_id = uniqid(); - WC()->session->set( 'ppcp_guest_customer_id', $user_id ); + return $this->prefix . $guest_customer_id; } return $this->prefix . $user_id; From 96d154a85642241b0f7e917d64faf1ed65cf826d Mon Sep 17 00:00:00 2001 From: dinamiko Date: Wed, 5 Jan 2022 14:12:37 +0100 Subject: [PATCH 12/19] Refactoring --- modules/ppcp-api-client/src/Endpoint/IdentityToken.php | 4 ++-- modules/ppcp-button/src/Endpoint/DataClientIdEndpoint.php | 2 +- tests/PHPUnit/ApiClient/Endpoint/IdentityTokenTest.php | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/ppcp-api-client/src/Endpoint/IdentityToken.php b/modules/ppcp-api-client/src/Endpoint/IdentityToken.php index b03d7aa13..c11a8d271 100644 --- a/modules/ppcp-api-client/src/Endpoint/IdentityToken.php +++ b/modules/ppcp-api-client/src/Endpoint/IdentityToken.php @@ -90,7 +90,7 @@ class IdentityToken { * @return Token * @throws RuntimeException If the request fails. */ - public function generate_for_customer( int $user_id ): Token { + public function generate_for_user( int $user_id ): Token { $bearer = $this->bearer->bearer(); $url = trailingslashit( $this->host ) . 'v1/identity/generate-token'; @@ -105,7 +105,7 @@ class IdentityToken { ( $this->settings->has( 'vault_enabled' ) && $this->settings->get( 'vault_enabled' ) ) && defined( 'PPCP_FLAG_SUBSCRIPTION' ) && PPCP_FLAG_SUBSCRIPTION ) { - $customer_id = $this->customer_repository->customer_id_for_user(($user_id)); + $customer_id = $this->customer_repository->customer_id_for_user( ( $user_id ) ); if ( 0 === $user_id ) { $customer_id = uniqid(); diff --git a/modules/ppcp-button/src/Endpoint/DataClientIdEndpoint.php b/modules/ppcp-button/src/Endpoint/DataClientIdEndpoint.php index ad5b1e750..c516d07bd 100644 --- a/modules/ppcp-button/src/Endpoint/DataClientIdEndpoint.php +++ b/modules/ppcp-button/src/Endpoint/DataClientIdEndpoint.php @@ -80,7 +80,7 @@ class DataClientIdEndpoint implements EndpointInterface { try { $this->request_data->read_request( $this->nonce() ); $user_id = get_current_user_id(); - $token = $this->identity_token->generate_for_customer( $user_id ); + $token = $this->identity_token->generate_for_user( $user_id ); wp_send_json( array( 'token' => $token->token(), diff --git a/tests/PHPUnit/ApiClient/Endpoint/IdentityTokenTest.php b/tests/PHPUnit/ApiClient/Endpoint/IdentityTokenTest.php index 134ecc885..255a59032 100644 --- a/tests/PHPUnit/ApiClient/Endpoint/IdentityTokenTest.php +++ b/tests/PHPUnit/ApiClient/Endpoint/IdentityTokenTest.php @@ -92,7 +92,7 @@ class IdentityTokenTest extends TestCase when('wc_print_r')->returnArg(); when('get_user_meta')->justReturn(''); - $result = $this->sut->generate_for_customer(1); + $result = $this->sut->generate_for_user(1); $this->assertInstanceOf(Token::class, $result); } @@ -116,7 +116,7 @@ class IdentityTokenTest extends TestCase $this->customer_repository->shouldReceive('customer_id_for_user'); $this->expectException(RuntimeException::class); - $this->sut->generate_for_customer(1); + $this->sut->generate_for_user(1); } public function testGenerateForCustomerFailsBecauseResponseCodeIsNot200() @@ -143,6 +143,6 @@ class IdentityTokenTest extends TestCase $this->customer_repository->shouldReceive('customer_id_for_user'); $this->expectException(PayPalApiException::class); - $this->sut->generate_for_customer(1); + $this->sut->generate_for_user(1); } } From a8ccd02e7790ff7fd0fa217de677c31ea7001f2c Mon Sep 17 00:00:00 2001 From: dinamiko Date: Wed, 5 Jan 2022 14:31:12 +0100 Subject: [PATCH 13/19] Revert vault enabled check in render wrapper --- modules/ppcp-button/src/Assets/SmartButton.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index e7b9bd348..1d96c3ab2 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -177,6 +177,10 @@ class SmartButton implements SmartButtonInterface { */ public function render_wrapper(): bool { + if ( ! $this->can_save_vault_token() && $this->has_subscriptions() ) { + return false; + } + if ( $this->settings->has( 'enabled' ) && $this->settings->get( 'enabled' ) ) { $this->render_button_wrapper_registrar(); $this->render_message_wrapper_registrar(); From e9cafc762883beb62aa010a377590334b21c05a9 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Wed, 5 Jan 2022 14:38:04 +0100 Subject: [PATCH 14/19] Fix phpunit --- tests/PHPUnit/Button/Endpoint/DataClientIdEndpointTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPUnit/Button/Endpoint/DataClientIdEndpointTest.php b/tests/PHPUnit/Button/Endpoint/DataClientIdEndpointTest.php index eea5df229..c5ec7fd8d 100644 --- a/tests/PHPUnit/Button/Endpoint/DataClientIdEndpointTest.php +++ b/tests/PHPUnit/Button/Endpoint/DataClientIdEndpointTest.php @@ -34,7 +34,7 @@ class DataClientIdEndpointTest extends TestCase $this->requestData->shouldReceive('read_request') ->with($this->sut::nonce()); when('get_current_user_id')->justReturn($userId); - $this->identityToken->shouldReceive('generate_for_customer') + $this->identityToken->shouldReceive('generate_for_user') ->with($userId) ->andReturn($token); From e68ff575729b6b52de2b47f53719eb435bd30f08 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Wed, 5 Jan 2022 14:56:30 +0100 Subject: [PATCH 15/19] Fix psalm --- .../ppcp-api-client/src/Repository/CustomerRepository.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/ppcp-api-client/src/Repository/CustomerRepository.php b/modules/ppcp-api-client/src/Repository/CustomerRepository.php index 9f21a2959..d703851ef 100644 --- a/modules/ppcp-api-client/src/Repository/CustomerRepository.php +++ b/modules/ppcp-api-client/src/Repository/CustomerRepository.php @@ -31,7 +31,7 @@ class CustomerRepository { } /** - * Returns a unique ID for the given user ID. + * Returns the customer ID for the given user ID. * * @param int $user_id The user ID. * @return string @@ -39,9 +39,9 @@ class CustomerRepository { public function customer_id_for_user( int $user_id ): string { $guest_customer_id = get_user_meta( $user_id, 'ppcp_guest_customer_id', true ); if ( $guest_customer_id ) { - return $this->prefix . $guest_customer_id; + return $this->prefix . $guest_customer_id; } - return $this->prefix . $user_id; + return $this->prefix . (string) $user_id; } } From c2834df32f6731c0af81c1a39717c74c0b22356d Mon Sep 17 00:00:00 2001 From: dinamiko Date: Mon, 10 Jan 2022 12:51:56 +0100 Subject: [PATCH 16/19] Add prefix to guest user id generation --- modules/ppcp-api-client/src/Endpoint/IdentityToken.php | 1 - .../ppcp-api-client/src/Repository/CustomerRepository.php | 6 +++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-api-client/src/Endpoint/IdentityToken.php b/modules/ppcp-api-client/src/Endpoint/IdentityToken.php index c11a8d271..ecbc11913 100644 --- a/modules/ppcp-api-client/src/Endpoint/IdentityToken.php +++ b/modules/ppcp-api-client/src/Endpoint/IdentityToken.php @@ -108,7 +108,6 @@ class IdentityToken { $customer_id = $this->customer_repository->customer_id_for_user( ( $user_id ) ); if ( 0 === $user_id ) { - $customer_id = uniqid(); WC()->session->set( 'ppcp_guest_customer_id', $customer_id ); } diff --git a/modules/ppcp-api-client/src/Repository/CustomerRepository.php b/modules/ppcp-api-client/src/Repository/CustomerRepository.php index d703851ef..61a3ca7b6 100644 --- a/modules/ppcp-api-client/src/Repository/CustomerRepository.php +++ b/modules/ppcp-api-client/src/Repository/CustomerRepository.php @@ -37,9 +37,13 @@ class CustomerRepository { * @return string */ public function customer_id_for_user( int $user_id ): string { + if ( 0 === $user_id ) { + return $this->prefix . uniqid(); + } + $guest_customer_id = get_user_meta( $user_id, 'ppcp_guest_customer_id', true ); if ( $guest_customer_id ) { - return $this->prefix . $guest_customer_id; + return $guest_customer_id; } return $this->prefix . (string) $user_id; From dce8a06953bae914f090340d5e723988429e207d Mon Sep 17 00:00:00 2001 From: dinamiko Date: Wed, 12 Jan 2022 15:55:09 +0100 Subject: [PATCH 17/19] Do not regenerate guest customer id if already exist in the session --- modules/ppcp-api-client/src/Endpoint/IdentityToken.php | 4 ---- .../src/Repository/CustomerRepository.php | 9 ++++++++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/modules/ppcp-api-client/src/Endpoint/IdentityToken.php b/modules/ppcp-api-client/src/Endpoint/IdentityToken.php index ecbc11913..a01e746ac 100644 --- a/modules/ppcp-api-client/src/Endpoint/IdentityToken.php +++ b/modules/ppcp-api-client/src/Endpoint/IdentityToken.php @@ -107,10 +107,6 @@ class IdentityToken { ) { $customer_id = $this->customer_repository->customer_id_for_user( ( $user_id ) ); - if ( 0 === $user_id ) { - WC()->session->set( 'ppcp_guest_customer_id', $customer_id ); - } - $args['body'] = wp_json_encode( array( 'customer_id' => $customer_id, diff --git a/modules/ppcp-api-client/src/Repository/CustomerRepository.php b/modules/ppcp-api-client/src/Repository/CustomerRepository.php index 61a3ca7b6..65deb9511 100644 --- a/modules/ppcp-api-client/src/Repository/CustomerRepository.php +++ b/modules/ppcp-api-client/src/Repository/CustomerRepository.php @@ -38,7 +38,14 @@ class CustomerRepository { */ public function customer_id_for_user( int $user_id ): string { if ( 0 === $user_id ) { - return $this->prefix . uniqid(); + if ( WC()->session->get( 'ppcp_guest_customer_id' ) ) { + return WC()->session->get( 'ppcp_guest_customer_id' ); + } + + $unique_id = $this->prefix . uniqid(); + WC()->session->set( 'ppcp_guest_customer_id', $unique_id ); + + return $unique_id; } $guest_customer_id = get_user_meta( $user_id, 'ppcp_guest_customer_id', true ); From 2f78bab9cabe758e8c31e73419b93b2152e86b96 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Mon, 17 Jan 2022 14:35:25 +0100 Subject: [PATCH 18/19] Only display buttons in checkout if guest with subscription --- modules/ppcp-button/resources/js/button.js | 10 +++++++++- modules/ppcp-button/src/Assets/SmartButton.php | 9 +++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/modules/ppcp-button/resources/js/button.js b/modules/ppcp-button/resources/js/button.js index 49fd98b52..402781bbf 100644 --- a/modules/ppcp-button/resources/js/button.js +++ b/modules/ppcp-button/resources/js/button.js @@ -82,8 +82,16 @@ document.addEventListener( console.error('PayPal button could not be configured.'); return; } - const script = document.createElement('script'); + if ( + PayPalCommerceGateway.context !== 'checkout' + && PayPalCommerceGateway.data_client_id.user === 0 + && PayPalCommerceGateway.data_client_id.has_subscriptions + ) { + return; + } + + const script = document.createElement('script'); script.addEventListener('load', (event) => { bootstrap(); }); diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index 1d96c3ab2..09c513f29 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -641,10 +641,11 @@ class SmartButton implements SmartButtonInterface { $localize = array( 'script_attributes' => $this->attributes(), 'data_client_id' => array( - 'set_attribute' => $this->can_save_vault_token(), - 'endpoint' => home_url( \WC_AJAX::get_endpoint( DataClientIdEndpoint::ENDPOINT ) ), - 'nonce' => wp_create_nonce( DataClientIdEndpoint::nonce() ), - 'user' => get_current_user_id(), + 'set_attribute' => $this->can_save_vault_token(), + 'endpoint' => home_url( \WC_AJAX::get_endpoint( DataClientIdEndpoint::ENDPOINT ) ), + 'nonce' => wp_create_nonce( DataClientIdEndpoint::nonce() ), + 'user' => get_current_user_id(), + 'has_subscriptions' => $this->has_subscriptions(), ), 'redirect' => wc_get_checkout_url(), 'context' => $this->context(), From e9e650da7972808d2d0573243affa3c209ededd9 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Mon, 17 Jan 2022 14:52:30 +0100 Subject: [PATCH 19/19] Fix psalm errors --- .../ppcp-api-client/src/Repository/CustomerRepository.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-api-client/src/Repository/CustomerRepository.php b/modules/ppcp-api-client/src/Repository/CustomerRepository.php index 65deb9511..eb6e0cf6a 100644 --- a/modules/ppcp-api-client/src/Repository/CustomerRepository.php +++ b/modules/ppcp-api-client/src/Repository/CustomerRepository.php @@ -38,8 +38,9 @@ class CustomerRepository { */ public function customer_id_for_user( int $user_id ): string { if ( 0 === $user_id ) { - if ( WC()->session->get( 'ppcp_guest_customer_id' ) ) { - return WC()->session->get( 'ppcp_guest_customer_id' ); + $guest_customer_id = WC()->session->get( 'ppcp_guest_customer_id' ); + if ( is_string( $guest_customer_id ) && $guest_customer_id ) { + return $guest_customer_id; } $unique_id = $this->prefix . uniqid();