Skip to content

Commit bf4b4eb

Browse files
committed
refactor: 🔥 Simplify the PayPalBearerFactory
We do not need to know or override the current ConnectionState, since the presence/value of the client_id and client_secret define the state
1 parent 13a8145 commit bf4b4eb

6 files changed

Lines changed: 42 additions & 95 deletions

File tree

‎modules/ppcp-api-client/services.php‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,6 @@
179179
*/
180180
'api.factory.paypal-bearer' => static function ( ContainerInterface $container ): PayPalBearerFactory {
181181
return new PayPalBearerFactory(
182-
$container->get( 'settings.connection-state' ),
183182
$container->get( 'api.token-rate-limiter' ),
184183
$container->get( 'woocommerce.logger.woocommerce' )
185184
);

‎modules/ppcp-api-client/src/Factory/PartnersEndpointFactory.php‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,7 @@ public function create(
9696
): PartnersEndpoint {
9797
$host = (string) $this->paypal_host->get_value( $is_sandbox );
9898

99-
// The partners endpoint always needs a full API bearer for the given
100-
// credentials, independent of the ambient connection state.
101-
$bearer = $this->bearer_factory->create( $host, $client_id, $client_secret, true );
99+
$bearer = $this->bearer_factory->create( $host, $client_id, $client_secret );
102100

103101
return new PartnersEndpoint(
104102
$host,

‎modules/ppcp-api-client/src/Factory/PayPalBearerFactory.php‎

Lines changed: 19 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?php
22
/**
3-
* Builds the API bearer for a set of credentials, honoring the connection state.
3+
* Builds the API bearer for a set of credentials.
44
*
55
* @package WooCommerce\PayPalCommerce\ApiClient\Factory
66
*/
@@ -17,66 +17,54 @@
1717
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
1818
use WooCommerce\PayPalCommerce\ApiClient\Helper\InMemoryCache;
1919
use WooCommerce\PayPalCommerce\Settings\Data\SettingsProvider;
20-
use WooCommerce\PayPalCommerce\WcGateway\Helper\ConnectionState;
2120

2221
/**
23-
* Produces the bearer appropriate for the connection state, authenticated with the
24-
* passed client credentials.
22+
* Produces the bearer that matches the supplied credentials.
2523
*
26-
* While no merchant is connected the store can only use a login-scoped
27-
* ConnectBearer; once connected it uses a full API PayPalBearer. This factory owns
28-
* that decision by consulting the shared ConnectionState, so callers do not have to.
24+
* Credential presence is the connection signal: with both a client ID and secret
25+
* the factory builds an authenticated PayPalBearer, otherwise it returns a
26+
* login-only ConnectBearer (nothing is connected, so there is nothing to
27+
* authenticate with). This mirrors PayPalBearer itself, which cannot fetch a token
28+
* without credentials.
2929
*
3030
* By default the PayPalBearer gets an isolated in-memory token cache and static
3131
* credentials, scoping its token to the passed credentials so it never mixes with
3232
* another account's cached token. Pass a persistent cache and a settings provider
33-
* to reproduce the shared, connection-bound bearer instead.
33+
* to reproduce the shared, connection-bound bearer instead; when the settings
34+
* provider resolves credentials dynamically, still pass the current client ID and
35+
* secret so the bearer-type decision stays correct.
3436
*/
3537
class PayPalBearerFactory {
3638

37-
private ConnectionState $connection_state;
38-
3939
private TokenRateLimiter $rate_limiter;
4040

4141
private LoggerInterface $logger;
4242

43-
public function __construct(
44-
ConnectionState $connection_state,
45-
TokenRateLimiter $rate_limiter,
46-
LoggerInterface $logger
47-
) {
48-
$this->connection_state = $connection_state;
49-
$this->rate_limiter = $rate_limiter;
50-
$this->logger = $logger;
43+
public function __construct( TokenRateLimiter $rate_limiter, LoggerInterface $logger ) {
44+
$this->rate_limiter = $rate_limiter;
45+
$this->logger = $logger;
5146
}
5247

5348
/**
5449
* Builds the bearer for the given credentials.
5550
*
56-
* Returns a login-only ConnectBearer while the store is not connected, otherwise
57-
* a full API PayPalBearer. The connection state is read from the shared
58-
* ConnectionState unless $is_connected overrides it. Callers that operate outside
59-
* the ambient connection (such as verifying credentials mid-connect) pass the
60-
* override to force the API bearer.
51+
* Returns an authenticated PayPalBearer when both credentials are present,
52+
* otherwise a login-only ConnectBearer.
6153
*
6254
* @param string $host The PayPal API host.
6355
* @param string $client_id The client ID.
6456
* @param string $client_secret The client secret.
65-
* @param ?bool $is_connected Overrides the ambient connection state when not null.
6657
* @param ?Cache $cache Token cache; defaults to an isolated in-memory cache.
6758
* @param ?SettingsProvider $settings Resolves credentials dynamically when provided.
6859
*/
6960
public function create(
70-
string $host,
71-
string $client_id,
72-
string $client_secret,
73-
?bool $is_connected = null,
61+
string $host = '',
62+
string $client_id = '',
63+
string $client_secret = '',
7464
?Cache $cache = null,
7565
?SettingsProvider $settings = null
7666
): Bearer {
77-
$is_connected = $is_connected ?? $this->connection_state->is_connected();
78-
79-
if ( ! $is_connected ) {
67+
if ( '' === $client_id || '' === $client_secret ) {
8068
return new ConnectBearer();
8169
}
8270

‎modules/ppcp-settings/src/Service/AuthenticationManager.php‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -426,9 +426,9 @@ private function request_payee(
426426
): array {
427427
$host = $this->connection_host->get_value( $use_sandbox );
428428

429-
// Verification runs before the merchant is connected, so force the API
430-
// bearer for the credentials being verified instead of reading the state.
431-
$bearer = $this->bearer_factory->create( $host, $client_id, $client_secret, true );
429+
// The credentials being verified are non-empty, so the factory yields an
430+
// authenticated bearer even though the merchant is not connected yet.
431+
$bearer = $this->bearer_factory->create( $host, $client_id, $client_secret );
432432

433433
$orders = new Orders(
434434
$host,

‎tests/PHPUnit/ApiClient/Factory/PartnersEndpointFactoryTest.php‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,7 @@ private function sut(
4949
* GIVEN merchant credentials and a per-environment host resolved from the environment config
5050
* WHEN create() is called for either the sandbox or the live environment
5151
* THEN it returns a PartnersEndpoint instance
52-
* AND it requests a full API bearer from the bearer factory, using the resolved host,
53-
* the given credentials, and forcing the "true" override so the ambient connection
54-
* state is never consulted
52+
* AND it delegates to the bearer factory with the resolved host and given credentials
5553
*
5654
* @dataProvider environment_provider
5755
*/
@@ -67,7 +65,7 @@ public function testCreateReturnsPartnersEndpointAndForcesFullApiBearer(bool $is
6765
$bearer_factory = Mockery::mock(PayPalBearerFactory::class);
6866
$bearer_factory->shouldReceive('create')
6967
->once()
70-
->with($expected_host, 'client-id', 'client-secret', true)
68+
->with($expected_host, 'client-id', 'client-secret')
7169
->andReturn($bearer);
7270

7371
$result = $this->sut($paypal_host, $partner_id, $bearer_factory)->create(

‎tests/PHPUnit/ApiClient/Factory/PayPalBearerFactoryTest.php‎

Lines changed: 17 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
1313
use WooCommerce\PayPalCommerce\Settings\Data\SettingsProvider;
1414
use WooCommerce\PayPalCommerce\TestCase;
15-
use WooCommerce\PayPalCommerce\WcGateway\Helper\ConnectionState;
1615

1716
class PayPalBearerFactoryTest extends TestCase
1817
{
@@ -28,87 +27,52 @@ public function setUp(): void
2827
$this->logger = Mockery::mock(LoggerInterface::class)->shouldIgnoreMissing();
2928
}
3029

31-
private function sut(ConnectionState $connection_state): PayPalBearerFactory
30+
private function sut(): PayPalBearerFactory
3231
{
3332
$rate_limiter = Mockery::mock(TokenRateLimiter::class);
3433

35-
return new PayPalBearerFactory($connection_state, $rate_limiter, $this->logger);
34+
return new PayPalBearerFactory($rate_limiter, $this->logger);
3635
}
3736

3837
/**
39-
* GIVEN a store with no explicit connection override
40-
* WHEN create() is called and the ambient connection state resolves to a given value
41-
* THEN it returns a PayPalBearer while connected, otherwise a login-only ConnectBearer
42-
*
43-
* @dataProvider ambientConnectionData
44-
*/
45-
public function testCreateWithoutOverrideUsesAmbientConnectionState(bool $ambientConnected, string $expectedClass): void
46-
{
47-
$connection_state = Mockery::mock(ConnectionState::class);
48-
$connection_state->shouldReceive('is_connected')->andReturn($ambientConnected);
49-
50-
$result = $this->sut($connection_state)->create('https://example.com', 'client-id', 'client-secret');
51-
52-
$this->assertInstanceOf($expectedClass, $result);
53-
}
54-
55-
public function ambientConnectionData(): array
56-
{
57-
return [
58-
'ambient connected returns PayPalBearer' => [true, PayPalBearer::class],
59-
'ambient disconnected returns ConnectBearer' => [false, ConnectBearer::class],
60-
];
61-
}
62-
63-
/**
64-
* GIVEN a store whose ambient connection state is irrelevant
65-
* WHEN create() is called with an explicit connection override
66-
* THEN it returns the bearer matching the override
67-
* AND it never consults the ambient connection state
38+
* GIVEN a host and a set of client credentials
39+
* WHEN create() is called
40+
* THEN it returns a PayPalBearer only when both client ID and secret are present,
41+
* otherwise a login-only ConnectBearer
6842
*
69-
* @dataProvider overrideConnectionData
43+
* @dataProvider credentialPresenceData
7044
*/
71-
public function testCreateWithOverrideShortCircuitsAmbientConnectionState(bool $override, string $expectedClass): void
45+
public function testCreateDecidesBearerTypeByCredentialPresence(string $clientId, string $clientSecret, string $expectedClass): void
7246
{
73-
$connection_state = Mockery::mock(ConnectionState::class);
74-
$connection_state->shouldReceive('is_connected')->never();
75-
76-
$result = $this->sut($connection_state)->create(
77-
'https://example.com',
78-
'client-id',
79-
'client-secret',
80-
$override
81-
);
47+
$result = $this->sut()->create('https://example.com', $clientId, $clientSecret);
8248

8349
$this->assertInstanceOf($expectedClass, $result);
8450
}
8551

86-
public function overrideConnectionData(): array
52+
public function credentialPresenceData(): array
8753
{
8854
return [
89-
'override true returns PayPalBearer' => [true, PayPalBearer::class],
90-
'override false returns ConnectBearer' => [false, ConnectBearer::class],
55+
'client id and secret present returns PayPalBearer' => ['client-id', 'client-secret', PayPalBearer::class],
56+
'empty client id returns ConnectBearer' => ['', 'client-secret', ConnectBearer::class],
57+
'empty client secret returns ConnectBearer' => ['client-id', '', ConnectBearer::class],
58+
'no credentials returns ConnectBearer' => ['', '', ConnectBearer::class],
9159
];
9260
}
9361

9462
/**
95-
* GIVEN a store that is connected, with an explicit cache and settings provider
63+
* GIVEN non-empty client credentials, an explicit cache and settings provider
9664
* WHEN create() is called
9765
* THEN it still returns a PayPalBearer, passing both through to the constructor
9866
*/
99-
public function testCreateWhenConnectedWithExplicitCacheAndSettingsReturnsPayPalBearer(): void
67+
public function testCreateWithExplicitCacheAndSettingsReturnsPayPalBearer(): void
10068
{
101-
$connection_state = Mockery::mock(ConnectionState::class);
102-
$connection_state->shouldReceive('is_connected')->never();
103-
10469
$cache = Mockery::mock(Cache::class);
10570
$settings = Mockery::mock(SettingsProvider::class);
10671

107-
$result = $this->sut($connection_state)->create(
72+
$result = $this->sut()->create(
10873
'https://example.com',
10974
'client-id',
11075
'client-secret',
111-
true,
11276
$cache,
11377
$settings
11478
);

0 commit comments

Comments
 (0)