diff --git a/modules/ppcp-api-client/src/ApiModule.php b/modules/ppcp-api-client/src/ApiModule.php index 7a07be123..62e18373f 100644 --- a/modules/ppcp-api-client/src/ApiModule.php +++ b/modules/ppcp-api-client/src/ApiModule.php @@ -43,6 +43,20 @@ class ApiModule implements ModuleInterface { WC()->session->set( 'ppcp_fees', $fees ); } ); + add_filter( + 'ppcp_create_order_request_body_data', + function( array $data ) use ( $c ) { + + foreach ( $data['purchase_units'] as $purchase_unit_index => $purchase_unit ) { + foreach ( $purchase_unit['items'] as $item_index => $item ) { + $data['purchase_units'][ $purchase_unit_index ]['items'][ $item_index ]['name'] = + apply_filters( 'woocommerce_paypal_payments_cart_line_item_name', $item['name'], $item['cart_item_key'] ); + } + } + + return $data; + } + ); add_action( 'woocommerce_paypal_payments_paypal_order_created', function ( Order $order ) use ( $c ) { diff --git a/modules/ppcp-api-client/src/Endpoint/BillingAgreementsEndpoint.php b/modules/ppcp-api-client/src/Endpoint/BillingAgreementsEndpoint.php index 233c41fe2..954be01b5 100644 --- a/modules/ppcp-api-client/src/Endpoint/BillingAgreementsEndpoint.php +++ b/modules/ppcp-api-client/src/Endpoint/BillingAgreementsEndpoint.php @@ -121,7 +121,7 @@ class BillingAgreementsEndpoint { */ public function reference_transaction_enabled(): bool { try { - if ( get_transient( 'ppcp_reference_transaction_enabled' ) === true ) { + if ( wc_string_to_bool( get_transient( 'ppcp_reference_transaction_enabled' ) ) === true ) { return true; } @@ -135,7 +135,7 @@ class BillingAgreementsEndpoint { ); } finally { $this->is_request_logging_enabled = true; - set_transient( 'ppcp_reference_transaction_enabled', true, 3 * MONTH_IN_SECONDS ); + set_transient( 'ppcp_reference_transaction_enabled', true, MONTH_IN_SECONDS ); } return true; diff --git a/modules/ppcp-api-client/src/Endpoint/WebhookEndpoint.php b/modules/ppcp-api-client/src/Endpoint/WebhookEndpoint.php index 6ae411cfa..3df4c3def 100644 --- a/modules/ppcp-api-client/src/Endpoint/WebhookEndpoint.php +++ b/modules/ppcp-api-client/src/Endpoint/WebhookEndpoint.php @@ -202,7 +202,15 @@ class WebhookEndpoint { $status_code = (int) wp_remote_retrieve_response_code( $response ); if ( 204 !== $status_code ) { - $json = json_decode( $response['body'] ) ?? null; + $json = null; + /** + * Use in array as consistency check. + * + * @psalm-suppress RedundantConditionGivenDocblockType + */ + if ( is_array( $response ) ) { + $json = json_decode( $response['body'] ); + } throw new PayPalApiException( $json, $status_code diff --git a/modules/ppcp-api-client/src/Entity/Item.php b/modules/ppcp-api-client/src/Entity/Item.php index 7d38e5f78..cc739635f 100644 --- a/modules/ppcp-api-client/src/Entity/Item.php +++ b/modules/ppcp-api-client/src/Entity/Item.php @@ -249,9 +249,12 @@ class Item { 'sku' => $this->sku(), 'category' => $this->category(), 'url' => $this->url(), - 'image_url' => $this->image_url(), ); + if ( $this->image_url() ) { + $item['image_url'] = $this->image_url(); + } + if ( $this->tax() ) { $item['tax'] = $this->tax()->to_array(); } diff --git a/modules/ppcp-compat/src/PPEC/PPECHelper.php b/modules/ppcp-compat/src/PPEC/PPECHelper.php index 0c175979d..791cb7a22 100644 --- a/modules/ppcp-compat/src/PPEC/PPECHelper.php +++ b/modules/ppcp-compat/src/PPEC/PPECHelper.php @@ -98,7 +98,7 @@ class PPECHelper { set_transient( 'ppcp_has_ppec_subscriptions', ! empty( $result ) ? 'true' : 'false', - 3 * MONTH_IN_SECONDS + MONTH_IN_SECONDS ); return ! empty( $result ); diff --git a/modules/ppcp-onboarding/src/Helper/OnboardingUrl.php b/modules/ppcp-onboarding/src/Helper/OnboardingUrl.php index 881d4295f..b00c9b250 100644 --- a/modules/ppcp-onboarding/src/Helper/OnboardingUrl.php +++ b/modules/ppcp-onboarding/src/Helper/OnboardingUrl.php @@ -64,7 +64,7 @@ class OnboardingUrl { * * @var int */ - private $cache_ttl = 3 * MONTH_IN_SECONDS; + private $cache_ttl = MONTH_IN_SECONDS; /** * The TTL for the previous token cache. diff --git a/modules/ppcp-subscription/src/RenewalHandler.php b/modules/ppcp-subscription/src/RenewalHandler.php index d4b3943f9..f4a9a6cbd 100644 --- a/modules/ppcp-subscription/src/RenewalHandler.php +++ b/modules/ppcp-subscription/src/RenewalHandler.php @@ -241,7 +241,7 @@ class RenewalHandler { * @param \WC_Customer $customer The customer. * @param \WC_Order $wc_order The current WooCommerce order we want to process. * - * @return PaymentToken|null + * @return PaymentToken|null|false */ private function get_token_for_customer( \WC_Customer $customer, \WC_Order $wc_order ) { /** diff --git a/modules/ppcp-uninstall/services.php b/modules/ppcp-uninstall/services.php index 6f7cda722..ca67dc71b 100644 --- a/modules/ppcp-uninstall/services.php +++ b/modules/ppcp-uninstall/services.php @@ -45,6 +45,12 @@ return array( ); }, + 'uninstall.ppcp-all-action-names' => function( ContainerInterface $container ) : array { + return array( + 'woocommerce_paypal_payments_uninstall', + ); + }, + 'uninstall.clear-db-endpoint' => function( ContainerInterface $container ) : string { return 'ppcp-clear-db'; }, diff --git a/modules/ppcp-uninstall/src/ClearDatabase.php b/modules/ppcp-uninstall/src/ClearDatabase.php index fc09519a5..ed5d73bb6 100644 --- a/modules/ppcp-uninstall/src/ClearDatabase.php +++ b/modules/ppcp-uninstall/src/ClearDatabase.php @@ -31,4 +31,13 @@ class ClearDatabase implements ClearDatabaseInterface { as_unschedule_action( $action_name ); } } + + /** + * {@inheritDoc} + */ + public function clear_actions( array $action_names ): void { + foreach ( $action_names as $action_name ) { + do_action( $action_name ); + } + } } diff --git a/modules/ppcp-uninstall/src/ClearDatabaseInterface.php b/modules/ppcp-uninstall/src/ClearDatabaseInterface.php index 6d6aedb77..34d9b1469 100644 --- a/modules/ppcp-uninstall/src/ClearDatabaseInterface.php +++ b/modules/ppcp-uninstall/src/ClearDatabaseInterface.php @@ -29,4 +29,12 @@ interface ClearDatabaseInterface { */ public function clear_scheduled_actions( array $action_names ): void; + /** + * Clears the given actions. + * + * @param string[] $action_names The list of action names. + * @throws RuntimeException If problem clearing. + */ + public function clear_actions( array $action_names ): void; + } diff --git a/modules/ppcp-uninstall/src/UninstallModule.php b/modules/ppcp-uninstall/src/UninstallModule.php index 649be5c49..25d65ae0f 100644 --- a/modules/ppcp-uninstall/src/UninstallModule.php +++ b/modules/ppcp-uninstall/src/UninstallModule.php @@ -47,8 +47,9 @@ class UninstallModule implements ModuleInterface { $clear_db_endpoint = $container->get( 'uninstall.clear-db-endpoint' ); $option_names = $container->get( 'uninstall.ppcp-all-option-names' ); $scheduled_action_names = $container->get( 'uninstall.ppcp-all-scheduled-action-names' ); + $action_names = $container->get( 'uninstall.ppcp-all-action-names' ); - $this->handleClearDbAjaxRequest( $request_data, $clear_db, $clear_db_endpoint, $option_names, $scheduled_action_names ); + $this->handleClearDbAjaxRequest( $request_data, $clear_db, $clear_db_endpoint, $option_names, $scheduled_action_names, $action_names ); } /** @@ -69,17 +70,19 @@ class UninstallModule implements ModuleInterface { * @param string $nonce The nonce. * @param string[] $option_names The list of option names. * @param string[] $scheduled_action_names The list of scheduled action names. + * @param string[] $action_names The list of action names. */ protected function handleClearDbAjaxRequest( RequestData $request_data, ClearDatabaseInterface $clear_db, string $nonce, array $option_names, - array $scheduled_action_names + array $scheduled_action_names, + array $action_names ): void { add_action( "wc_ajax_{$nonce}", - static function () use ( $request_data, $clear_db, $nonce, $option_names, $scheduled_action_names ) { + static function () use ( $request_data, $clear_db, $nonce, $option_names, $scheduled_action_names, $action_names ) { try { if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_send_json_error( 'Not admin.', 403 ); @@ -91,6 +94,7 @@ class UninstallModule implements ModuleInterface { $clear_db->delete_options( $option_names ); $clear_db->clear_scheduled_actions( $scheduled_action_names ); + $clear_db->clear_actions( $action_names ); wp_send_json_success(); return true; diff --git a/modules/ppcp-vaulting/services.php b/modules/ppcp-vaulting/services.php index c274c4549..d18768ea2 100644 --- a/modules/ppcp-vaulting/services.php +++ b/modules/ppcp-vaulting/services.php @@ -56,10 +56,14 @@ return array( 'vaulting.payment-token-factory' => function( ContainerInterface $container ): PaymentTokenFactory { return new PaymentTokenFactory(); }, + 'vaulting.payment-token-helper' => function( ContainerInterface $container ): PaymentTokenHelper { + return new PaymentTokenHelper(); + }, 'vaulting.payment-tokens-migration' => function( ContainerInterface $container ): PaymentTokensMigration { return new PaymentTokensMigration( $container->get( 'vaulting.payment-token-factory' ), $container->get( 'vaulting.repository.payment-token' ), + $container->get( 'vaulting.payment-token-helper' ), $container->get( 'woocommerce.logger.woocommerce' ) ); }, diff --git a/modules/ppcp-vaulting/src/PaymentTokenHelper.php b/modules/ppcp-vaulting/src/PaymentTokenHelper.php new file mode 100644 index 000000000..de940ccf3 --- /dev/null +++ b/modules/ppcp-vaulting/src/PaymentTokenHelper.php @@ -0,0 +1,35 @@ +get_token() === $token_id ) { + return true; + } + } + + return false; + } +} diff --git a/modules/ppcp-vaulting/src/PaymentTokensMigration.php b/modules/ppcp-vaulting/src/PaymentTokensMigration.php index 69b6c6fda..a7d3511cc 100644 --- a/modules/ppcp-vaulting/src/PaymentTokensMigration.php +++ b/modules/ppcp-vaulting/src/PaymentTokensMigration.php @@ -35,6 +35,13 @@ class PaymentTokensMigration { */ private $payment_token_repository; + /** + * The payment token helper. + * + * @var PaymentTokenHelper + */ + private $payment_token_helper; + /** * The logger. * @@ -47,16 +54,19 @@ class PaymentTokensMigration { * * @param PaymentTokenFactory $payment_token_factory The payment token factory. * @param PaymentTokenRepository $payment_token_repository The payment token repository. + * @param PaymentTokenHelper $payment_token_helper The payment token helper. * @param LoggerInterface $logger The logger. */ public function __construct( PaymentTokenFactory $payment_token_factory, PaymentTokenRepository $payment_token_repository, + PaymentTokenHelper $payment_token_helper, LoggerInterface $logger ) { $this->payment_token_factory = $payment_token_factory; $this->payment_token_repository = $payment_token_repository; $this->logger = $logger; + $this->payment_token_helper = $payment_token_helper; } /** @@ -72,7 +82,7 @@ class PaymentTokensMigration { foreach ( $tokens as $token ) { if ( isset( $token->source()->card ) ) { $wc_tokens = WC_Payment_Tokens::get_customer_tokens( $id, CreditCardGateway::ID ); - if ( $this->token_exist( $wc_tokens, $token ) ) { + if ( $this->payment_token_helper->token_exist( $wc_tokens, $token->id() ) ) { $this->logger->info( 'Token already exist for user ' . (string) $id ); continue; } @@ -97,7 +107,7 @@ class PaymentTokensMigration { } } elseif ( $token->source()->paypal ) { $wc_tokens = WC_Payment_Tokens::get_customer_tokens( $id, PayPalGateway::ID ); - if ( $this->token_exist( $wc_tokens, $token ) ) { + if ( $this->payment_token_helper->token_exist( $wc_tokens, $token->id() ) ) { $this->logger->info( 'Token already exist for user ' . (string) $id ); continue; } @@ -126,21 +136,4 @@ class PaymentTokensMigration { } } } - - /** - * Checks if given PayPal token exist as WC Payment Token. - * - * @param array $wc_tokens WC Payment Tokens. - * @param PaymentToken $token PayPal Token ID. - * @return bool - */ - private function token_exist( array $wc_tokens, PaymentToken $token ): bool { - foreach ( $wc_tokens as $wc_token ) { - if ( $wc_token->get_token() === $token->id() ) { - return true; - } - } - - return false; - } } diff --git a/modules/ppcp-wc-gateway/src/Gateway/GatewayRepository.php b/modules/ppcp-wc-gateway/src/Gateway/GatewayRepository.php index 250b1143f..6362fa260 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/GatewayRepository.php +++ b/modules/ppcp-wc-gateway/src/Gateway/GatewayRepository.php @@ -44,4 +44,14 @@ class GatewayRepository { } ); } + + /** + * Indicates if a given gateway ID is registered. + * + * @param string $gateway_id The gateway ID. + * @return bool + */ + public function exists( string $gateway_id ): bool { + return in_array( $gateway_id, $this->ppcp_gateway_ids, true ); + } } diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php b/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php index c703de904..933e6ce51 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php @@ -227,7 +227,6 @@ class PayPalGateway extends \WC_Payment_Gateway { if ( ( $this->config->has( 'vault_enabled' ) && $this->config->get( 'vault_enabled' ) ) - || ( $this->config->has( 'vault_enabled_dcc' ) && $this->config->get( 'vault_enabled_dcc' ) ) || ( $this->config->has( 'subscriptions_mode' ) && $this->config->get( 'subscriptions_mode' ) === 'subscriptions_api' ) ) { array_push( @@ -244,6 +243,8 @@ class PayPalGateway extends \WC_Payment_Gateway { 'subscription_payment_method_change_admin', 'multiple_subscriptions' ); + } elseif ( $this->config->has( 'vault_enabled_dcc' ) && $this->config->get( 'vault_enabled_dcc' ) ) { + $this->supports[] = 'tokenization'; } } diff --git a/modules/ppcp-wc-gateway/src/Helper/DCCProductStatus.php b/modules/ppcp-wc-gateway/src/Helper/DCCProductStatus.php index 065f486be..f8d717f08 100644 --- a/modules/ppcp-wc-gateway/src/Helper/DCCProductStatus.php +++ b/modules/ppcp-wc-gateway/src/Helper/DCCProductStatus.php @@ -135,12 +135,12 @@ class DCCProductStatus { $this->settings->set( 'products_dcc_enabled', true ); $this->settings->persist(); $this->current_status_cache = true; - $this->cache->set( self::DCC_STATUS_CACHE_KEY, 'true', 3 * MONTH_IN_SECONDS ); + $this->cache->set( self::DCC_STATUS_CACHE_KEY, 'true', MONTH_IN_SECONDS ); return true; } } - $expiration = 3 * MONTH_IN_SECONDS; + $expiration = MONTH_IN_SECONDS; if ( $this->dcc_applies->for_country_currency() ) { $expiration = 3 * HOUR_IN_SECONDS; } diff --git a/modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceProductStatus.php b/modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceProductStatus.php index 4f0e4ba6c..df70c0779 100644 --- a/modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceProductStatus.php +++ b/modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceProductStatus.php @@ -127,11 +127,11 @@ class PayUponInvoiceProductStatus { $this->settings->set( 'products_pui_enabled', true ); $this->settings->persist(); $this->current_status_cache = true; - $this->cache->set( self::PUI_STATUS_CACHE_KEY, 'true', 3 * MONTH_IN_SECONDS ); + $this->cache->set( self::PUI_STATUS_CACHE_KEY, 'true', MONTH_IN_SECONDS ); return true; } } - $this->cache->set( self::PUI_STATUS_CACHE_KEY, 'false', 3 * MONTH_IN_SECONDS ); + $this->cache->set( self::PUI_STATUS_CACHE_KEY, 'false', MONTH_IN_SECONDS ); $this->current_status_cache = false; return false; diff --git a/modules/ppcp-wc-gateway/src/Processor/OrderProcessor.php b/modules/ppcp-wc-gateway/src/Processor/OrderProcessor.php index 443fd1cbd..1fb10ed8c 100644 --- a/modules/ppcp-wc-gateway/src/Processor/OrderProcessor.php +++ b/modules/ppcp-wc-gateway/src/Processor/OrderProcessor.php @@ -116,6 +116,13 @@ class OrderProcessor { */ private $order_helper; + /** + * Array to store temporary order data changes to restore after processing. + * + * @var array + */ + private $restore_order_data = array(); + /** * OrderProcessor constructor. * @@ -292,8 +299,12 @@ class OrderProcessor { * @return Order */ public function patch_order( \WC_Order $wc_order, Order $order ): Order { + $this->apply_outbound_order_filters( $wc_order ); $updated_order = $this->order_factory->from_wc_order( $wc_order, $order ); - $order = $this->order_endpoint->patch_order_with( $order, $updated_order ); + $this->restore_order_from_filters( $wc_order ); + + $order = $this->order_endpoint->patch_order_with( $order, $updated_order ); + return $order; } @@ -323,4 +334,48 @@ class OrderProcessor { true ); } + + /** + * Applies filters to the WC_Order, so they are reflected only on PayPal Order. + * + * @param WC_Order $wc_order The WoocOmmerce Order. + * @return void + */ + private function apply_outbound_order_filters( WC_Order $wc_order ): void { + $items = $wc_order->get_items(); + + $this->restore_order_data['names'] = array(); + + foreach ( $items as $item ) { + if ( ! $item instanceof \WC_Order_Item ) { + continue; + } + + $original_name = $item->get_name(); + $new_name = apply_filters( 'woocommerce_paypal_payments_order_line_item_name', $original_name, $item->get_id(), $wc_order->get_id() ); + + if ( $new_name !== $original_name ) { + $this->restore_order_data['names'][ $item->get_id() ] = $original_name; + $item->set_name( $new_name ); + } + } + } + + /** + * Restores the WC_Order to it's state before filters. + * + * @param WC_Order $wc_order The WooCommerce Order. + * @return void + */ + private function restore_order_from_filters( WC_Order $wc_order ): void { + if ( is_array( $this->restore_order_data['names'] ?? null ) ) { + foreach ( $this->restore_order_data['names'] as $wc_item_id => $original_name ) { + $wc_item = $wc_order->get_item( $wc_item_id, false ); + + if ( $wc_item ) { + $wc_item->set_name( $original_name ); + } + } + } + } } diff --git a/modules/ppcp-wc-gateway/src/Settings/SettingsListener.php b/modules/ppcp-wc-gateway/src/Settings/SettingsListener.php index e20b7cee8..df6f013a5 100644 --- a/modules/ppcp-wc-gateway/src/Settings/SettingsListener.php +++ b/modules/ppcp-wc-gateway/src/Settings/SettingsListener.php @@ -211,7 +211,7 @@ class SettingsListener { } $merchant_id = sanitize_text_field( wp_unslash( $_GET['merchantIdInPayPal'] ) ); - $merchant_email = sanitize_text_field( wp_unslash( $_GET['merchantId'] ) ); + $merchant_email = $this->sanitize_onboarding_email( sanitize_text_field( wp_unslash( $_GET['merchantId'] ) ) ); $onboarding_token = sanitize_text_field( wp_unslash( $_GET['ppcpToken'] ) ); $retry_count = isset( $_GET['ppcpRetry'] ) ? ( (int) sanitize_text_field( wp_unslash( $_GET['ppcpRetry'] ) ) ) : 0; // phpcs:enable WordPress.Security.NonceVerification.Missing @@ -278,6 +278,16 @@ class SettingsListener { $this->onboarding_redirect(); } + /** + * Sanitizes the onboarding email. + * + * @param string $email The onboarding email. + * @return string + */ + private function sanitize_onboarding_email( string $email ): string { + return str_replace( ' ', '+', $email ); + } + /** * Redirect to the onboarding URL. * @@ -401,9 +411,7 @@ class SettingsListener { $this->webhook_registrar->unregister(); foreach ( $this->signup_link_ids as $key ) { - if ( $this->signup_link_cache->has( $key ) ) { - $this->signup_link_cache->delete( $key ); - } + ( new OnboardingUrl( $this->signup_link_cache, $key, get_current_user_id() ) )->delete(); } } } @@ -613,4 +621,40 @@ class SettingsListener { } return true; } + + /** + * Prevent enabling tracking if it is not enabled for merchant account. + * + * @throws RuntimeException When API request fails. + */ + public function listen_for_tracking_enabled(): void { + if ( State::STATE_ONBOARDED !== $this->state->current_state() ) { + return; + } + + try { + $token = $this->bearer->bearer(); + if ( ! $token->is_tracking_available() ) { + $this->settings->set( 'tracking_enabled', false ); + $this->settings->persist(); + return; + } + } catch ( RuntimeException $exception ) { + $this->settings->set( 'tracking_enabled', false ); + $this->settings->persist(); + + throw $exception; + } + } + + /** + * Handles onboarding URLs deletion + */ + public function listen_for_uninstall(): void { + // Clear onboarding links from cache. + foreach ( $this->signup_link_ids as $key ) { + ( new OnboardingUrl( $this->signup_link_cache, $key, get_current_user_id() ) )->delete(); + } + } + } diff --git a/modules/ppcp-wc-gateway/src/WCGatewayModule.php b/modules/ppcp-wc-gateway/src/WCGatewayModule.php index 49fefccaa..c4fea76b3 100644 --- a/modules/ppcp-wc-gateway/src/WCGatewayModule.php +++ b/modules/ppcp-wc-gateway/src/WCGatewayModule.php @@ -33,6 +33,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Checkout\DisableGateways; use WooCommerce\PayPalCommerce\WcGateway\Endpoint\ReturnUrlEndpoint; use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; +use WooCommerce\PayPalCommerce\WcGateway\Gateway\GatewayRepository; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCProductStatus; use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceProductStatus; @@ -356,6 +357,14 @@ class WCGatewayModule implements ModuleInterface { return; } + $gateway_repository = $c->get( 'wcgateway.gateway-repository' ); + assert( $gateway_repository instanceof GatewayRepository ); + + // Only allow to proceed if the payment method is one of our Gateways. + if ( ! $gateway_repository->exists( $wc_order->get_payment_method() ) ) { + return; + } + $intent = strtoupper( (string) $wc_order->get_meta( PayPalGateway::INTENT_META_KEY ) ); $captured = wc_string_to_bool( $wc_order->get_meta( AuthorizedPaymentsProcessor::CAPTURED_META_KEY ) ); if ( $intent !== 'AUTHORIZE' || $captured ) { @@ -392,6 +401,16 @@ class WCGatewayModule implements ModuleInterface { 3 ); + add_action( + 'woocommerce_paypal_payments_uninstall', + static function () use ( $c ) { + $listener = $c->get( 'wcgateway.settings.listener' ); + assert( $listener instanceof SettingsListener ); + + $listener->listen_for_uninstall(); + } + ); + if ( defined( 'WP_CLI' ) && WP_CLI ) { \WP_CLI::add_command( 'pcp settings', diff --git a/modules/ppcp-webhooks/services.php b/modules/ppcp-webhooks/services.php index 9223a7348..4d585206a 100644 --- a/modules/ppcp-webhooks/services.php +++ b/modules/ppcp-webhooks/services.php @@ -80,6 +80,7 @@ return array( $order_endpoint = $container->get( 'api.endpoint.order' ); $authorized_payments_processor = $container->get( 'wcgateway.processor.authorized-payments' ); $payment_token_factory = $container->get( 'vaulting.payment-token-factory' ); + $payment_token_helper = $container->get( 'vaulting.payment-token-helper' ); $refund_fees_updater = $container->get( 'wcgateway.helper.refund-fees-updater' ); return array( @@ -95,7 +96,7 @@ return array( new PaymentCaptureRefunded( $logger, $refund_fees_updater ), new PaymentCaptureReversed( $logger ), new PaymentCaptureCompleted( $logger, $order_endpoint ), - new VaultPaymentTokenCreated( $logger, $prefix, $authorized_payments_processor, $payment_token_factory ), + new VaultPaymentTokenCreated( $logger, $prefix, $authorized_payments_processor, $payment_token_factory, $payment_token_helper ), new VaultPaymentTokenDeleted( $logger ), new PaymentCapturePending( $logger ), new PaymentSaleCompleted( $logger ), diff --git a/modules/ppcp-webhooks/src/Handler/VaultPaymentTokenCreated.php b/modules/ppcp-webhooks/src/Handler/VaultPaymentTokenCreated.php index 152932556..893305c75 100644 --- a/modules/ppcp-webhooks/src/Handler/VaultPaymentTokenCreated.php +++ b/modules/ppcp-webhooks/src/Handler/VaultPaymentTokenCreated.php @@ -13,6 +13,7 @@ use Psr\Log\LoggerInterface; use WC_Payment_Token_CC; use WC_Payment_Tokens; use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenFactory; +use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenHelper; use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenPayPal; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; @@ -54,6 +55,13 @@ class VaultPaymentTokenCreated implements RequestHandler { */ protected $payment_token_factory; + /** + * The payment token helper. + * + * @var PaymentTokenHelper + */ + private $payment_token_helper; + /** * VaultPaymentTokenCreated constructor. * @@ -61,17 +69,20 @@ class VaultPaymentTokenCreated implements RequestHandler { * @param string $prefix The prefix. * @param AuthorizedPaymentsProcessor $authorized_payments_processor The authorized payment processor. * @param PaymentTokenFactory $payment_token_factory The payment token factory. + * @param PaymentTokenHelper $payment_token_helper The payment token helper. */ public function __construct( LoggerInterface $logger, string $prefix, AuthorizedPaymentsProcessor $authorized_payments_processor, - PaymentTokenFactory $payment_token_factory + PaymentTokenFactory $payment_token_factory, + PaymentTokenHelper $payment_token_helper ) { $this->logger = $logger; $this->prefix = $prefix; $this->authorized_payments_processor = $authorized_payments_processor; $this->payment_token_factory = $payment_token_factory; + $this->payment_token_helper = $payment_token_helper; } /** @@ -123,33 +134,39 @@ class VaultPaymentTokenCreated implements RequestHandler { if ( ! is_null( $request['resource'] ) && isset( $request['resource']['id'] ) ) { if ( ! is_null( $request['resource']['source'] ) && isset( $request['resource']['source']['card'] ) ) { - $token = new WC_Payment_Token_CC(); - $token->set_token( $request['resource']['id'] ); - $token->set_user_id( $wc_customer_id ); - $token->set_gateway_id( CreditCardGateway::ID ); + $wc_tokens = WC_Payment_Tokens::get_customer_tokens( $wc_customer_id, CreditCardGateway::ID ); + if ( ! $this->payment_token_helper->token_exist( $wc_tokens, $request['resource']['id'] ) ) { + $token = new WC_Payment_Token_CC(); + $token->set_token( $request['resource']['id'] ); + $token->set_user_id( $wc_customer_id ); + $token->set_gateway_id( CreditCardGateway::ID ); - $token->set_last4( $request['resource']['source']['card']['last_digits'] ?? '' ); - $expiry = explode( '-', $request['resource']['source']['card']['expiry'] ?? '' ); - $token->set_expiry_year( $expiry[0] ?? '' ); - $token->set_expiry_month( $expiry[1] ?? '' ); - $token->set_card_type( $request['resource']['source']['card']['brand'] ?? '' ); - $token->save(); - WC_Payment_Tokens::set_users_default( $wc_customer_id, $token->get_id() ); - } elseif ( isset( $request['resource']['source']['paypal'] ) ) { - $payment_token_paypal = $this->payment_token_factory->create( 'paypal' ); - assert( $payment_token_paypal instanceof PaymentTokenPayPal ); - - $payment_token_paypal->set_token( $request['resource']['id'] ); - $payment_token_paypal->set_user_id( $wc_customer_id ); - $payment_token_paypal->set_gateway_id( PayPalGateway::ID ); - - $email = $request['resource']['source']['paypal']['payer']['email_address'] ?? ''; - if ( $email && is_email( $email ) ) { - $payment_token_paypal->set_email( $email ); + $token->set_last4( $request['resource']['source']['card']['last_digits'] ?? '' ); + $expiry = explode( '-', $request['resource']['source']['card']['expiry'] ?? '' ); + $token->set_expiry_year( $expiry[0] ?? '' ); + $token->set_expiry_month( $expiry[1] ?? '' ); + $token->set_card_type( $request['resource']['source']['card']['brand'] ?? '' ); + $token->save(); + WC_Payment_Tokens::set_users_default( $wc_customer_id, $token->get_id() ); } + } elseif ( isset( $request['resource']['source']['paypal'] ) ) { + $wc_tokens = WC_Payment_Tokens::get_customer_tokens( $wc_customer_id, PayPalGateway::ID ); + if ( ! $this->payment_token_helper->token_exist( $wc_tokens, $request['resource']['id'] ) ) { + $payment_token_paypal = $this->payment_token_factory->create( 'paypal' ); + assert( $payment_token_paypal instanceof PaymentTokenPayPal ); - $payment_token_paypal->save(); - WC_Payment_Tokens::set_users_default( $wc_customer_id, $payment_token_paypal->get_id() ); + $payment_token_paypal->set_token( $request['resource']['id'] ); + $payment_token_paypal->set_user_id( $wc_customer_id ); + $payment_token_paypal->set_gateway_id( PayPalGateway::ID ); + + $email = $request['resource']['source']['paypal']['payer']['email_address'] ?? ''; + if ( $email && is_email( $email ) ) { + $payment_token_paypal->set_email( $email ); + } + + $payment_token_paypal->save(); + WC_Payment_Tokens::set_users_default( $wc_customer_id, $payment_token_paypal->get_id() ); + } } } diff --git a/tests/PHPUnit/Onboarding/Helper/OnboardingUrlTest.php b/tests/PHPUnit/Onboarding/Helper/OnboardingUrlTest.php index db50265d5..ea8b0ab0e 100644 --- a/tests/PHPUnit/Onboarding/Helper/OnboardingUrlTest.php +++ b/tests/PHPUnit/Onboarding/Helper/OnboardingUrlTest.php @@ -3,7 +3,7 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Onboarding\Helper; -use PHPUnit\Framework\TestCase; +use WooCommerce\PayPalCommerce\TestCase; use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache; use RuntimeException; use function Brain\Monkey\Functions\when; @@ -15,7 +15,7 @@ class OnboardingUrlTest extends TestCase private $user_id = 123; private $onboardingUrl; - protected function setUp(): void + public function setUp(): void { parent::setUp(); diff --git a/tests/PHPUnit/WcGateway/Processor/OrderProcessorTest.php b/tests/PHPUnit/WcGateway/Processor/OrderProcessorTest.php index 32a2366ca..97ceca9b5 100644 --- a/tests/PHPUnit/WcGateway/Processor/OrderProcessorTest.php +++ b/tests/PHPUnit/WcGateway/Processor/OrderProcessorTest.php @@ -57,6 +57,7 @@ class OrderProcessorTest extends TestCase ->andReturn($payments); $wcOrder = Mockery::mock(\WC_Order::class); + $wcOrder->expects('get_items')->andReturn([]); $wcOrder->expects('update_meta_data') ->with(PayPalGateway::ORDER_PAYMENT_MODE_META_KEY, 'live'); $wcOrder->shouldReceive('get_id')->andReturn(1); @@ -193,7 +194,8 @@ class OrderProcessorTest extends TestCase ->andReturn($payments); $wcOrder = Mockery::mock(\WC_Order::class); - $orderStatus = Mockery::mock(OrderStatus::class); + $wcOrder->expects('get_items')->andReturn([]); + $orderStatus = Mockery::mock(OrderStatus::class); $orderStatus ->shouldReceive('is') ->with(OrderStatus::APPROVED)