Skip to content

Commit

Permalink
Merge branch 'PCP-3398-critical-error-when-changing-subscription-paym…
Browse files Browse the repository at this point in the history
…ent-method-to-advanced-card-processing' into PCP-4110-incorrect-subscription-cancellation-handling-with-pay-pal-subscriptions
  • Loading branch information
danielhuesken committed Feb 7, 2025
2 parents b7ad5f7 + a43f12d commit 13f1741
Show file tree
Hide file tree
Showing 229 changed files with 6,078 additions and 3,429 deletions.
39 changes: 36 additions & 3 deletions modules/ppcp-api-client/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,19 @@
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\ConnectBearer;
use WooCommerce\PayPalCommerce\WcGateway\Helper\EnvironmentConfig;
use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\WcGateway\Helper\Environment;

return array(
'api.host' => function( ContainerInterface $container ) : string {
return PAYPAL_API_URL;
'api.host' => static function( ContainerInterface $container ) : string {
$environment = $container->get( 'onboarding.environment' );
assert( $environment instanceof Environment );

if ( $environment->is_sandbox() ) {
return (string) $container->get( 'api.sandbox-host' );
}

return (string) $container->get( 'api.production-host' );
},
'api.paypal-host' => function( ContainerInterface $container ) : string {
return PAYPAL_API_URL;
Expand Down Expand Up @@ -116,6 +125,12 @@
return 'WC-';
},
'api.bearer' => static function ( ContainerInterface $container ): Bearer {
$is_connected = $container->get( 'settings.flag.is-connected' );

if ( ! $is_connected ) {
return new ConnectBearer();
}

return new PayPalBearer(
$container->get( 'api.paypal-bearer-cache' ),
$container->get( 'api.host' ),
Expand Down Expand Up @@ -811,7 +826,7 @@
return new OrderHelper();
},
'api.helper.order-transient' => static function( ContainerInterface $container ): OrderTransient {
$cache = new Cache( 'ppcp-paypal-bearer' );
$cache = $container->get( 'api.paypal-bearer-cache' );
$purchase_unit_sanitizer = $container->get( 'api.helper.purchase-unit-sanitizer' );
return new OrderTransient( $cache, $purchase_unit_sanitizer );
},
Expand Down Expand Up @@ -927,4 +942,22 @@ static function( ContainerInterface $container ): PurchaseUnitSanitizer {
$container->get( 'api.endpoint.partner-referrals-sandbox' )
);
},
'api.sandbox-host' => static function ( ContainerInterface $container ): string {
$is_connected = $container->get( 'settings.flag.is-connected' );

if ( $is_connected ) {
return PAYPAL_SANDBOX_API_URL;
}

return CONNECT_WOO_SANDBOX_URL;
},
'api.production-host' => static function ( ContainerInterface $container ): string {
$is_connected = $container->get( 'settings.flag.is-connected' );

if ( $is_connected ) {
return PAYPAL_API_URL;
}

return CONNECT_WOO_URL;
},
);
208 changes: 208 additions & 0 deletions modules/ppcp-api-client/src/Helper/ProductStatus.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
<?php
/**
* Eligibility status.
*
* @package WooCommerce\PayPalCommerce\ApiClient\Helper
*/

declare( strict_types = 1 );

namespace WooCommerce\PayPalCommerce\ApiClient\Helper;

use RuntimeException;
use Exception;
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PartnersEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\SellerStatus;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;

/**
* Class ProductStatus
*
* Base class to check the eligibility of a product for the current merchant.
*/
abstract class ProductStatus {
/**
* Caches the SellerStatus API response to avoid duplicate API calls
* during the same request.
*
* @var ?SellerStatus
*/
private static ?SellerStatus $seller_status = null;

/**
* The current status stored in memory.
*
* @var bool|null
*/
private ?bool $is_eligible = null;

/**
* If there was a request failure.
*
* @var bool
*/
private bool $has_request_failure = false;

/**
* Whether the merchant onboarding process was completed and the
* merchant API is available.
*
* @var bool
*/
private bool $is_connected;

/**
* The partners endpoint.
*
* @var PartnersEndpoint
*/
private PartnersEndpoint $partners_endpoint;

/**
* The API failure registry
*
* @var FailureRegistry
*/
private FailureRegistry $api_failure_registry;

/**
* AppleProductStatus constructor.
*
* @param bool $is_connected Whether the merchant is connected.
* @param PartnersEndpoint $partners_endpoint The Partner Endpoint.
* @param FailureRegistry $api_failure_registry The API failure registry.
*/
public function __construct(
bool $is_connected,
PartnersEndpoint $partners_endpoint,
FailureRegistry $api_failure_registry
) {
$this->is_connected = $is_connected;
$this->partners_endpoint = $partners_endpoint;
$this->api_failure_registry = $api_failure_registry;
}

/**
* Uses local data (DB values, hooks) to determine if the feature is eligible.
*
* Returns true when the feature is available, and false if ineligible.
* On failure, an RuntimeException is thrown.
*
* @return null|bool Boolean to indicate the status; null if the status not locally defined.
* @throws RuntimeException When the check failed.
* @throws NotFoundException When a relevant service or setting was not found.
*/
abstract protected function check_local_state() : ?bool;

/**
* Inspects the API response of the SellerStatus to determine feature eligibility.
*
* Returns true when the feature is available, and false if ineligible.
* On failure, an RuntimeException is thrown.
*
* @param SellerStatus $seller_status The seller status, returned from the API.
* @return bool
* @throws RuntimeException When the check failed.
*/
abstract protected function check_active_state( SellerStatus $seller_status ) : bool;

/**
* Clears the eligibility status from the local cache/DB to enforce a new
* API call on the next eligibility check.
*
* @param Settings|null $settings See description in {@see self::clear()}.
* @return void
*/
abstract protected function clear_state( Settings $settings = null ) : void;

/**
* Whether the merchant has access to the feature.
*
* @return bool
*/
public function is_active() : bool {
if ( null !== $this->is_eligible ) {
return $this->is_eligible;
}

$this->is_eligible = false;
$this->has_request_failure = false;

if ( ! $this->is_onboarded() ) {
return $this->is_eligible;
}

try {
// Try to use filters and DB values to determine the state.
$local_state = $this->check_local_state();
if ( null !== $local_state ) {
$this->is_eligible = $local_state;

return $this->is_eligible;
}

// Check using the merchant-API.
$seller_status = $this->get_seller_status_object();
$this->is_eligible = $this->check_active_state( $seller_status );
} catch ( Exception $exception ) {
$this->has_request_failure = true;
}

return $this->is_eligible;
}

/**
* Fetches the seller-status object from the PayPal merchant API.
*
* @return SellerStatus
* @throws RuntimeException When the check failed.
*/
protected function get_seller_status_object() : SellerStatus {
if ( null === self::$seller_status ) {
// Check API failure registry to prevent multiple failed API requests.
if ( $this->api_failure_registry->has_failure_in_timeframe( FailureRegistry::SELLER_STATUS_KEY, MINUTE_IN_SECONDS ) ) {
throw new RuntimeException( 'Timeout for re-check not reached yet' );
}

// Request seller status via PayPal API, might throw an Exception.
self::$seller_status = $this->partners_endpoint->seller_status();
}

return self::$seller_status;
}

/**
* Whether the merchant was fully onboarded, and we have valid API credentials.
*
* @return bool True, if we can use the merchant API endpoints.
*/
public function is_onboarded() : bool {
return $this->is_connected;
}

/**
* Returns if there was a request failure.
*
* @return bool
*/
public function has_request_failure() : bool {
return $this->has_request_failure;
}

/**
* Clears the persisted result to force a recheck.
*
* Accepts a Settings object to don't override other sequential settings that are being updated
* elsewhere.
*
* @param Settings|null $settings The settings object.
* @return void
*/
public function clear( Settings $settings = null ) : void {
$this->is_eligible = null;
$this->has_request_failure = false;

$this->clear_state( $settings );
}
}
2 changes: 1 addition & 1 deletion modules/ppcp-applepay/extensions.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
namespace WooCommerce\PayPalCommerce\Applepay;

use WooCommerce\PayPalCommerce\Applepay\Assets\PropertiesDictionary;
use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\WcGateway\Helper\Environment;
use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\WcGateway\Helper\DisplayManager;
Expand Down
5 changes: 2 additions & 3 deletions modules/ppcp-applepay/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
use WooCommerce\PayPalCommerce\Applepay\Helper\ApmApplies;
use WooCommerce\PayPalCommerce\Applepay\Helper\AvailabilityNotice;
use WooCommerce\PayPalCommerce\Common\Pattern\SingletonDecorator;
use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\WcGateway\Helper\Environment;
use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;

Expand Down Expand Up @@ -79,7 +79,7 @@ static function( ContainerInterface $container ): AppleProductStatus {
return new AppleProductStatus(
$container->get( 'wcgateway.settings' ),
$container->get( 'api.endpoint.partners' ),
$container->get( 'onboarding.state' ),
$container->get( 'settings.flag.is-connected' ),
$container->get( 'api.helper.failure-registry' )
);
}
Expand Down Expand Up @@ -308,5 +308,4 @@ static function( ContainerInterface $container ): AppleProductStatus {
$container->get( 'woocommerce.logger.woocommerce' )
);
},

);
4 changes: 2 additions & 2 deletions modules/ppcp-applepay/src/ApplepayModule.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
use WooCommerce\PayPalCommerce\Button\Assets\ButtonInterface;
use WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface;
use WooCommerce\PayPalCommerce\Applepay\Helper\AvailabilityNotice;
use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\WcGateway\Helper\Environment;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait;
Expand Down Expand Up @@ -183,7 +183,7 @@ function( array $locations, string $setting_name ): array {
);

add_filter(
'woocommerce_paypal_payments_rest_common_merchant_data',
'woocommerce_paypal_payments_rest_common_merchant_features',
function( array $features ) use ( $c ): array {
$product_status = $c->get( 'applepay.apple-product-status' );
assert( $product_status instanceof AppleProductStatus );
Expand Down
Loading

0 comments on commit 13f1741

Please sign in to comment.