Merge branch 'trunk' into PCP-1968-subscriptions-api-renewals

This commit is contained in:
Emili Castells Guasch 2023-09-12 09:36:51 +02:00
commit 250172a2ad
25 changed files with 301 additions and 68 deletions

View file

@ -43,6 +43,20 @@ class ApiModule implements ModuleInterface {
WC()->session->set( 'ppcp_fees', $fees ); 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( add_action(
'woocommerce_paypal_payments_paypal_order_created', 'woocommerce_paypal_payments_paypal_order_created',
function ( Order $order ) use ( $c ) { function ( Order $order ) use ( $c ) {

View file

@ -121,7 +121,7 @@ class BillingAgreementsEndpoint {
*/ */
public function reference_transaction_enabled(): bool { public function reference_transaction_enabled(): bool {
try { try {
if ( get_transient( 'ppcp_reference_transaction_enabled' ) === true ) { if ( wc_string_to_bool( get_transient( 'ppcp_reference_transaction_enabled' ) ) === true ) {
return true; return true;
} }
@ -135,7 +135,7 @@ class BillingAgreementsEndpoint {
); );
} finally { } finally {
$this->is_request_logging_enabled = true; $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; return true;

View file

@ -202,7 +202,15 @@ class WebhookEndpoint {
$status_code = (int) wp_remote_retrieve_response_code( $response ); $status_code = (int) wp_remote_retrieve_response_code( $response );
if ( 204 !== $status_code ) { 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( throw new PayPalApiException(
$json, $json,
$status_code $status_code

View file

@ -249,9 +249,12 @@ class Item {
'sku' => $this->sku(), 'sku' => $this->sku(),
'category' => $this->category(), 'category' => $this->category(),
'url' => $this->url(), 'url' => $this->url(),
'image_url' => $this->image_url(),
); );
if ( $this->image_url() ) {
$item['image_url'] = $this->image_url();
}
if ( $this->tax() ) { if ( $this->tax() ) {
$item['tax'] = $this->tax()->to_array(); $item['tax'] = $this->tax()->to_array();
} }

View file

@ -98,7 +98,7 @@ class PPECHelper {
set_transient( set_transient(
'ppcp_has_ppec_subscriptions', 'ppcp_has_ppec_subscriptions',
! empty( $result ) ? 'true' : 'false', ! empty( $result ) ? 'true' : 'false',
3 * MONTH_IN_SECONDS MONTH_IN_SECONDS
); );
return ! empty( $result ); return ! empty( $result );

View file

@ -64,7 +64,7 @@ class OnboardingUrl {
* *
* @var int * @var int
*/ */
private $cache_ttl = 3 * MONTH_IN_SECONDS; private $cache_ttl = MONTH_IN_SECONDS;
/** /**
* The TTL for the previous token cache. * The TTL for the previous token cache.

View file

@ -241,7 +241,7 @@ class RenewalHandler {
* @param \WC_Customer $customer The customer. * @param \WC_Customer $customer The customer.
* @param \WC_Order $wc_order The current WooCommerce order we want to process. * @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 ) { private function get_token_for_customer( \WC_Customer $customer, \WC_Order $wc_order ) {
/** /**

View file

@ -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 { 'uninstall.clear-db-endpoint' => function( ContainerInterface $container ) : string {
return 'ppcp-clear-db'; return 'ppcp-clear-db';
}, },

View file

@ -31,4 +31,13 @@ class ClearDatabase implements ClearDatabaseInterface {
as_unschedule_action( $action_name ); as_unschedule_action( $action_name );
} }
} }
/**
* {@inheritDoc}
*/
public function clear_actions( array $action_names ): void {
foreach ( $action_names as $action_name ) {
do_action( $action_name );
}
}
} }

View file

@ -29,4 +29,12 @@ interface ClearDatabaseInterface {
*/ */
public function clear_scheduled_actions( array $action_names ): void; 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;
} }

View file

@ -47,8 +47,9 @@ class UninstallModule implements ModuleInterface {
$clear_db_endpoint = $container->get( 'uninstall.clear-db-endpoint' ); $clear_db_endpoint = $container->get( 'uninstall.clear-db-endpoint' );
$option_names = $container->get( 'uninstall.ppcp-all-option-names' ); $option_names = $container->get( 'uninstall.ppcp-all-option-names' );
$scheduled_action_names = $container->get( 'uninstall.ppcp-all-scheduled-action-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 $nonce The nonce.
* @param string[] $option_names The list of option names. * @param string[] $option_names The list of option names.
* @param string[] $scheduled_action_names The list of scheduled action names. * @param string[] $scheduled_action_names The list of scheduled action names.
* @param string[] $action_names The list of action names.
*/ */
protected function handleClearDbAjaxRequest( protected function handleClearDbAjaxRequest(
RequestData $request_data, RequestData $request_data,
ClearDatabaseInterface $clear_db, ClearDatabaseInterface $clear_db,
string $nonce, string $nonce,
array $option_names, array $option_names,
array $scheduled_action_names array $scheduled_action_names,
array $action_names
): void { ): void {
add_action( add_action(
"wc_ajax_{$nonce}", "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 { try {
if ( ! current_user_can( 'manage_woocommerce' ) ) { if ( ! current_user_can( 'manage_woocommerce' ) ) {
wp_send_json_error( 'Not admin.', 403 ); wp_send_json_error( 'Not admin.', 403 );
@ -91,6 +94,7 @@ class UninstallModule implements ModuleInterface {
$clear_db->delete_options( $option_names ); $clear_db->delete_options( $option_names );
$clear_db->clear_scheduled_actions( $scheduled_action_names ); $clear_db->clear_scheduled_actions( $scheduled_action_names );
$clear_db->clear_actions( $action_names );
wp_send_json_success(); wp_send_json_success();
return true; return true;

View file

@ -56,10 +56,14 @@ return array(
'vaulting.payment-token-factory' => function( ContainerInterface $container ): PaymentTokenFactory { 'vaulting.payment-token-factory' => function( ContainerInterface $container ): PaymentTokenFactory {
return new PaymentTokenFactory(); return new PaymentTokenFactory();
}, },
'vaulting.payment-token-helper' => function( ContainerInterface $container ): PaymentTokenHelper {
return new PaymentTokenHelper();
},
'vaulting.payment-tokens-migration' => function( ContainerInterface $container ): PaymentTokensMigration { 'vaulting.payment-tokens-migration' => function( ContainerInterface $container ): PaymentTokensMigration {
return new PaymentTokensMigration( return new PaymentTokensMigration(
$container->get( 'vaulting.payment-token-factory' ), $container->get( 'vaulting.payment-token-factory' ),
$container->get( 'vaulting.repository.payment-token' ), $container->get( 'vaulting.repository.payment-token' ),
$container->get( 'vaulting.payment-token-helper' ),
$container->get( 'woocommerce.logger.woocommerce' ) $container->get( 'woocommerce.logger.woocommerce' )
); );
}, },

View file

@ -0,0 +1,35 @@
<?php
/**
* Payment Tokens helper methods.
*
* @package WooCommerce\PayPalCommerce\Vaulting
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Vaulting;
use WC_Payment_Token;
/**
* Class PaymentTokenHelper
*/
class PaymentTokenHelper {
/**
* Checks if given token exist as WC Payment Token.
*
* @param WC_Payment_Token[] $wc_tokens WC Payment Tokens.
* @param string $token_id Payment Token ID.
* @return bool
*/
public function token_exist( array $wc_tokens, string $token_id ): bool {
foreach ( $wc_tokens as $wc_token ) {
if ( $wc_token->get_token() === $token_id ) {
return true;
}
}
return false;
}
}

View file

@ -35,6 +35,13 @@ class PaymentTokensMigration {
*/ */
private $payment_token_repository; private $payment_token_repository;
/**
* The payment token helper.
*
* @var PaymentTokenHelper
*/
private $payment_token_helper;
/** /**
* The logger. * The logger.
* *
@ -47,16 +54,19 @@ class PaymentTokensMigration {
* *
* @param PaymentTokenFactory $payment_token_factory The payment token factory. * @param PaymentTokenFactory $payment_token_factory The payment token factory.
* @param PaymentTokenRepository $payment_token_repository The payment token repository. * @param PaymentTokenRepository $payment_token_repository The payment token repository.
* @param PaymentTokenHelper $payment_token_helper The payment token helper.
* @param LoggerInterface $logger The logger. * @param LoggerInterface $logger The logger.
*/ */
public function __construct( public function __construct(
PaymentTokenFactory $payment_token_factory, PaymentTokenFactory $payment_token_factory,
PaymentTokenRepository $payment_token_repository, PaymentTokenRepository $payment_token_repository,
PaymentTokenHelper $payment_token_helper,
LoggerInterface $logger LoggerInterface $logger
) { ) {
$this->payment_token_factory = $payment_token_factory; $this->payment_token_factory = $payment_token_factory;
$this->payment_token_repository = $payment_token_repository; $this->payment_token_repository = $payment_token_repository;
$this->logger = $logger; $this->logger = $logger;
$this->payment_token_helper = $payment_token_helper;
} }
/** /**
@ -72,7 +82,7 @@ class PaymentTokensMigration {
foreach ( $tokens as $token ) { foreach ( $tokens as $token ) {
if ( isset( $token->source()->card ) ) { if ( isset( $token->source()->card ) ) {
$wc_tokens = WC_Payment_Tokens::get_customer_tokens( $id, CreditCardGateway::ID ); $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 ); $this->logger->info( 'Token already exist for user ' . (string) $id );
continue; continue;
} }
@ -97,7 +107,7 @@ class PaymentTokensMigration {
} }
} elseif ( $token->source()->paypal ) { } elseif ( $token->source()->paypal ) {
$wc_tokens = WC_Payment_Tokens::get_customer_tokens( $id, PayPalGateway::ID ); $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 ); $this->logger->info( 'Token already exist for user ' . (string) $id );
continue; 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;
}
} }

View file

@ -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 );
}
} }

View file

@ -227,7 +227,6 @@ class PayPalGateway extends \WC_Payment_Gateway {
if ( if (
( $this->config->has( 'vault_enabled' ) && $this->config->get( 'vault_enabled' ) ) ( $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' ) || ( $this->config->has( 'subscriptions_mode' ) && $this->config->get( 'subscriptions_mode' ) === 'subscriptions_api' )
) { ) {
array_push( array_push(
@ -244,6 +243,8 @@ class PayPalGateway extends \WC_Payment_Gateway {
'subscription_payment_method_change_admin', 'subscription_payment_method_change_admin',
'multiple_subscriptions' 'multiple_subscriptions'
); );
} elseif ( $this->config->has( 'vault_enabled_dcc' ) && $this->config->get( 'vault_enabled_dcc' ) ) {
$this->supports[] = 'tokenization';
} }
} }

View file

@ -135,12 +135,12 @@ class DCCProductStatus {
$this->settings->set( 'products_dcc_enabled', true ); $this->settings->set( 'products_dcc_enabled', true );
$this->settings->persist(); $this->settings->persist();
$this->current_status_cache = true; $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; return true;
} }
} }
$expiration = 3 * MONTH_IN_SECONDS; $expiration = MONTH_IN_SECONDS;
if ( $this->dcc_applies->for_country_currency() ) { if ( $this->dcc_applies->for_country_currency() ) {
$expiration = 3 * HOUR_IN_SECONDS; $expiration = 3 * HOUR_IN_SECONDS;
} }

View file

@ -127,11 +127,11 @@ class PayUponInvoiceProductStatus {
$this->settings->set( 'products_pui_enabled', true ); $this->settings->set( 'products_pui_enabled', true );
$this->settings->persist(); $this->settings->persist();
$this->current_status_cache = true; $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; 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; $this->current_status_cache = false;
return false; return false;

View file

@ -116,6 +116,13 @@ class OrderProcessor {
*/ */
private $order_helper; private $order_helper;
/**
* Array to store temporary order data changes to restore after processing.
*
* @var array
*/
private $restore_order_data = array();
/** /**
* OrderProcessor constructor. * OrderProcessor constructor.
* *
@ -292,8 +299,12 @@ class OrderProcessor {
* @return Order * @return Order
*/ */
public function patch_order( \WC_Order $wc_order, Order $order ): 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 ); $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; return $order;
} }
@ -323,4 +334,48 @@ class OrderProcessor {
true 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 );
}
}
}
}
} }

View file

@ -211,7 +211,7 @@ class SettingsListener {
} }
$merchant_id = sanitize_text_field( wp_unslash( $_GET['merchantIdInPayPal'] ) ); $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'] ) ); $onboarding_token = sanitize_text_field( wp_unslash( $_GET['ppcpToken'] ) );
$retry_count = isset( $_GET['ppcpRetry'] ) ? ( (int) sanitize_text_field( wp_unslash( $_GET['ppcpRetry'] ) ) ) : 0; $retry_count = isset( $_GET['ppcpRetry'] ) ? ( (int) sanitize_text_field( wp_unslash( $_GET['ppcpRetry'] ) ) ) : 0;
// phpcs:enable WordPress.Security.NonceVerification.Missing // phpcs:enable WordPress.Security.NonceVerification.Missing
@ -278,6 +278,16 @@ class SettingsListener {
$this->onboarding_redirect(); $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. * Redirect to the onboarding URL.
* *
@ -401,9 +411,7 @@ class SettingsListener {
$this->webhook_registrar->unregister(); $this->webhook_registrar->unregister();
foreach ( $this->signup_link_ids as $key ) { foreach ( $this->signup_link_ids as $key ) {
if ( $this->signup_link_cache->has( $key ) ) { ( new OnboardingUrl( $this->signup_link_cache, $key, get_current_user_id() ) )->delete();
$this->signup_link_cache->delete( $key );
}
} }
} }
} }
@ -613,4 +621,40 @@ class SettingsListener {
} }
return true; 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();
}
}
} }

View file

@ -33,6 +33,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Checkout\DisableGateways;
use WooCommerce\PayPalCommerce\WcGateway\Endpoint\ReturnUrlEndpoint; use WooCommerce\PayPalCommerce\WcGateway\Endpoint\ReturnUrlEndpoint;
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException; use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\GatewayRepository;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCProductStatus; use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCProductStatus;
use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceProductStatus; use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceProductStatus;
@ -356,6 +357,14 @@ class WCGatewayModule implements ModuleInterface {
return; 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 ) ); $intent = strtoupper( (string) $wc_order->get_meta( PayPalGateway::INTENT_META_KEY ) );
$captured = wc_string_to_bool( $wc_order->get_meta( AuthorizedPaymentsProcessor::CAPTURED_META_KEY ) ); $captured = wc_string_to_bool( $wc_order->get_meta( AuthorizedPaymentsProcessor::CAPTURED_META_KEY ) );
if ( $intent !== 'AUTHORIZE' || $captured ) { if ( $intent !== 'AUTHORIZE' || $captured ) {
@ -392,6 +401,16 @@ class WCGatewayModule implements ModuleInterface {
3 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 ) { if ( defined( 'WP_CLI' ) && WP_CLI ) {
\WP_CLI::add_command( \WP_CLI::add_command(
'pcp settings', 'pcp settings',

View file

@ -80,6 +80,7 @@ return array(
$order_endpoint = $container->get( 'api.endpoint.order' ); $order_endpoint = $container->get( 'api.endpoint.order' );
$authorized_payments_processor = $container->get( 'wcgateway.processor.authorized-payments' ); $authorized_payments_processor = $container->get( 'wcgateway.processor.authorized-payments' );
$payment_token_factory = $container->get( 'vaulting.payment-token-factory' ); $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' ); $refund_fees_updater = $container->get( 'wcgateway.helper.refund-fees-updater' );
return array( return array(
@ -95,7 +96,7 @@ return array(
new PaymentCaptureRefunded( $logger, $refund_fees_updater ), new PaymentCaptureRefunded( $logger, $refund_fees_updater ),
new PaymentCaptureReversed( $logger ), new PaymentCaptureReversed( $logger ),
new PaymentCaptureCompleted( $logger, $order_endpoint ), 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 VaultPaymentTokenDeleted( $logger ),
new PaymentCapturePending( $logger ), new PaymentCapturePending( $logger ),
new PaymentSaleCompleted( $logger ), new PaymentSaleCompleted( $logger ),

View file

@ -13,6 +13,7 @@ use Psr\Log\LoggerInterface;
use WC_Payment_Token_CC; use WC_Payment_Token_CC;
use WC_Payment_Tokens; use WC_Payment_Tokens;
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenFactory; use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenFactory;
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenHelper;
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenPayPal; use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenPayPal;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
@ -54,6 +55,13 @@ class VaultPaymentTokenCreated implements RequestHandler {
*/ */
protected $payment_token_factory; protected $payment_token_factory;
/**
* The payment token helper.
*
* @var PaymentTokenHelper
*/
private $payment_token_helper;
/** /**
* VaultPaymentTokenCreated constructor. * VaultPaymentTokenCreated constructor.
* *
@ -61,17 +69,20 @@ class VaultPaymentTokenCreated implements RequestHandler {
* @param string $prefix The prefix. * @param string $prefix The prefix.
* @param AuthorizedPaymentsProcessor $authorized_payments_processor The authorized payment processor. * @param AuthorizedPaymentsProcessor $authorized_payments_processor The authorized payment processor.
* @param PaymentTokenFactory $payment_token_factory The payment token factory. * @param PaymentTokenFactory $payment_token_factory The payment token factory.
* @param PaymentTokenHelper $payment_token_helper The payment token helper.
*/ */
public function __construct( public function __construct(
LoggerInterface $logger, LoggerInterface $logger,
string $prefix, string $prefix,
AuthorizedPaymentsProcessor $authorized_payments_processor, AuthorizedPaymentsProcessor $authorized_payments_processor,
PaymentTokenFactory $payment_token_factory PaymentTokenFactory $payment_token_factory,
PaymentTokenHelper $payment_token_helper
) { ) {
$this->logger = $logger; $this->logger = $logger;
$this->prefix = $prefix; $this->prefix = $prefix;
$this->authorized_payments_processor = $authorized_payments_processor; $this->authorized_payments_processor = $authorized_payments_processor;
$this->payment_token_factory = $payment_token_factory; $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'] ) && isset( $request['resource']['id'] ) ) {
if ( ! is_null( $request['resource']['source'] ) && isset( $request['resource']['source']['card'] ) ) { if ( ! is_null( $request['resource']['source'] ) && isset( $request['resource']['source']['card'] ) ) {
$token = new WC_Payment_Token_CC(); $wc_tokens = WC_Payment_Tokens::get_customer_tokens( $wc_customer_id, CreditCardGateway::ID );
$token->set_token( $request['resource']['id'] ); if ( ! $this->payment_token_helper->token_exist( $wc_tokens, $request['resource']['id'] ) ) {
$token->set_user_id( $wc_customer_id ); $token = new WC_Payment_Token_CC();
$token->set_gateway_id( CreditCardGateway::ID ); $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'] ?? '' ); $token->set_last4( $request['resource']['source']['card']['last_digits'] ?? '' );
$expiry = explode( '-', $request['resource']['source']['card']['expiry'] ?? '' ); $expiry = explode( '-', $request['resource']['source']['card']['expiry'] ?? '' );
$token->set_expiry_year( $expiry[0] ?? '' ); $token->set_expiry_year( $expiry[0] ?? '' );
$token->set_expiry_month( $expiry[1] ?? '' ); $token->set_expiry_month( $expiry[1] ?? '' );
$token->set_card_type( $request['resource']['source']['card']['brand'] ?? '' ); $token->set_card_type( $request['resource']['source']['card']['brand'] ?? '' );
$token->save(); $token->save();
WC_Payment_Tokens::set_users_default( $wc_customer_id, $token->get_id() ); 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 );
} }
} 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(); $payment_token_paypal->set_token( $request['resource']['id'] );
WC_Payment_Tokens::set_users_default( $wc_customer_id, $payment_token_paypal->get_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() );
}
} }
} }

View file

@ -3,7 +3,7 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Onboarding\Helper; namespace WooCommerce\PayPalCommerce\Onboarding\Helper;
use PHPUnit\Framework\TestCase; use WooCommerce\PayPalCommerce\TestCase;
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache; use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
use RuntimeException; use RuntimeException;
use function Brain\Monkey\Functions\when; use function Brain\Monkey\Functions\when;
@ -15,7 +15,7 @@ class OnboardingUrlTest extends TestCase
private $user_id = 123; private $user_id = 123;
private $onboardingUrl; private $onboardingUrl;
protected function setUp(): void public function setUp(): void
{ {
parent::setUp(); parent::setUp();

View file

@ -57,6 +57,7 @@ class OrderProcessorTest extends TestCase
->andReturn($payments); ->andReturn($payments);
$wcOrder = Mockery::mock(\WC_Order::class); $wcOrder = Mockery::mock(\WC_Order::class);
$wcOrder->expects('get_items')->andReturn([]);
$wcOrder->expects('update_meta_data') $wcOrder->expects('update_meta_data')
->with(PayPalGateway::ORDER_PAYMENT_MODE_META_KEY, 'live'); ->with(PayPalGateway::ORDER_PAYMENT_MODE_META_KEY, 'live');
$wcOrder->shouldReceive('get_id')->andReturn(1); $wcOrder->shouldReceive('get_id')->andReturn(1);
@ -193,7 +194,8 @@ class OrderProcessorTest extends TestCase
->andReturn($payments); ->andReturn($payments);
$wcOrder = Mockery::mock(\WC_Order::class); $wcOrder = Mockery::mock(\WC_Order::class);
$orderStatus = Mockery::mock(OrderStatus::class); $wcOrder->expects('get_items')->andReturn([]);
$orderStatus = Mockery::mock(OrderStatus::class);
$orderStatus $orderStatus
->shouldReceive('is') ->shouldReceive('is')
->with(OrderStatus::APPROVED) ->with(OrderStatus::APPROVED)