Skip to content

Commit b9e3362

Browse files
committed
fixed itunes sandbox receipts sent to production (#199)
1 parent 70f2f2d commit b9e3362

File tree

4 files changed

+112
-65
lines changed

4 files changed

+112
-65
lines changed

src/AbstractValidator.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ abstract class AbstractValidator
1212
* HTTP client instance.
1313
*/
1414
public ?HttpClient $client = null;
15+
16+
/**
17+
* The base URI of the currently configured client.
18+
*/
19+
protected ?string $baseUri = null;
20+
1521
/**
1622
* Environment (sandbox or production).
1723
*/
@@ -54,13 +60,17 @@ abstract public function validate(): mixed;
5460

5561
/**
5662
* Get the Guzzle HTTP client.
63+
*
64+
* This method ensures a new client is created if the base URI changes,
65+
* which is critical for services that switch between production and sandbox endpoints.
5766
*/
5867
protected function getClient(string $base_uri): HttpClient
5968
{
60-
if ($this->client === null) {
69+
if ($this->client === null || $this->baseUri !== $base_uri) {
6170
$options = $this->client_options;
6271
$options['base_uri'] = $base_uri;
6372
$this->client = new HttpClient($options);
73+
$this->baseUri = $base_uri;
6474
}
6575

6676
return $this->client;

tests/Amazon/ValidatorTest.php

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,11 @@ public function testValidateWithFixtureReturnsValidResponse(): void
3838
->with('GET', '/version/1.0/verifyReceiptId/developer/secret123/user/user123/receiptId/receipt123')
3939
->andReturn(new GuzzleResponse(200, [], $json));
4040

41-
$validator = new AmazonValidator('secret123', Environment::SANDBOX);
41+
$validator = Mockery::mock(AmazonValidator::class, ['secret123', Environment::SANDBOX])->makePartial();
42+
$validator->shouldAllowMockingProtectedMethods();
43+
$validator->shouldReceive('getClient')->andReturn($mockClient);
44+
4245
$validator->setUserId('user123')->setReceiptId('receipt123');
43-
$validator->client = $mockClient;
4446

4547
$response = $validator->validate();
4648

@@ -64,9 +66,11 @@ public function testValidateEntitledPurchaseFixture(): void
6466
->with('GET', '/version/1.0/verifyReceiptId/developer/secret123/user/user123/receiptId/receipt123')
6567
->andReturn(new GuzzleResponse(200, [], $json));
6668

67-
$validator = new AmazonValidator('secret123', Environment::SANDBOX);
69+
$validator = Mockery::mock(AmazonValidator::class, ['secret123', Environment::SANDBOX])->makePartial();
70+
$validator->shouldAllowMockingProtectedMethods();
71+
$validator->shouldReceive('getClient')->andReturn($mockClient);
72+
6873
$validator->setUserId('user123')->setReceiptId('receipt123');
69-
$validator->client = $mockClient;
7074

7175
$response = $validator->validate();
7276

@@ -94,11 +98,13 @@ public function testValidateReturnsValidResponse(): void
9498
->with('GET', '/version/1.0/verifyReceiptId/developer/secret123/user/user123/receiptId/receipt123')
9599
->andReturn(new GuzzleResponse(200, [], $responseBody));
96100

97-
$validator = new AmazonValidator('secret123', Environment::SANDBOX);
101+
$validator = Mockery::mock(AmazonValidator::class, ['secret123', Environment::SANDBOX])->makePartial();
102+
$validator->shouldAllowMockingProtectedMethods();
103+
$validator->shouldReceive('getClient')->andReturn($mockClient);
104+
98105
$validator->setUserId('user123')->setReceiptId('receipt123');
99-
$validator->client = $mockClient;
100106

101-
$response = $validator->setDeveloperSecret('secret123')->validate();
107+
$response = $validator->validate();
102108

103109
$this->assertEquals('pack_100', $response->getTransactions()[0]->getProductId());
104110
$this->assertEquals('txn_abc', $response->getTransactions()[0]->getTransactionId());
@@ -124,9 +130,11 @@ public function testThrowsValidationExceptionOnNon200Response(): void
124130
'message' => 'Invalid developerSecret'
125131
])));
126132

127-
$validator = new AmazonValidator('secret123', Environment::SANDBOX);
133+
$validator = Mockery::mock(AmazonValidator::class, ['secret123', Environment::SANDBOX])->makePartial();
134+
$validator->shouldAllowMockingProtectedMethods();
135+
$validator->shouldReceive('getClient')->andReturn($mockClient);
136+
128137
$validator->setUserId('user123')->setReceiptId('receipt123');
129-
$validator->client = $mockClient;
130138

131139
$this->expectException(ValidationException::class);
132140
$this->expectExceptionMessage('Amazon API error [496]: Invalid developerSecret');

tests/AppleAppStore/ValidatorTest.php

Lines changed: 57 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,18 @@ public function testValidateReturnsResponse(): void
3030

3131
$signingKey = file_get_contents(__DIR__ . '/certs/testSigningKey.p8');
3232

33-
$validator = new Validator(
34-
signingKey: $signingKey,
35-
keyId: 'ABC123XYZ',
36-
issuerId: 'DEF456UVW',
37-
bundleId: 'com.example',
38-
environment: Environment::SANDBOX
39-
);
40-
41-
$validator->client = $mockClient;
33+
// Pass constructor args as an indexed array
34+
$validator = Mockery::mock(Validator::class, [
35+
$signingKey,
36+
'ABC123XYZ',
37+
'DEF456UVW',
38+
'com.example',
39+
Environment::SANDBOX
40+
])->makePartial();
41+
42+
$validator->shouldReceive('__construct')->passthru();
43+
$validator->shouldAllowMockingProtectedMethods();
44+
$validator->shouldReceive('getClient')->andReturn($mockClient);
4245

4346
$response = $validator->validate('abc123');
4447
$this->assertInstanceOf(Response::class, $response);
@@ -58,15 +61,18 @@ public function testRequestTestNotificationReturnsToken(): void
5861

5962
$signingKey = file_get_contents(__DIR__ . '/certs/testSigningKey.p8');
6063

61-
$validator = new Validator(
62-
signingKey: $signingKey,
63-
keyId: 'ABC123XYZ',
64-
issuerId: 'DEF456UVW',
65-
bundleId: 'com.example',
66-
environment: Environment::SANDBOX
67-
);
64+
// Pass constructor args as an indexed array
65+
$validator = Mockery::mock(Validator::class, [
66+
$signingKey,
67+
'ABC123XYZ',
68+
'DEF456UVW',
69+
'com.example',
70+
Environment::SANDBOX
71+
])->makePartial();
6872

69-
$validator->client = $mockClient;
73+
$validator->shouldReceive('__construct')->passthru();
74+
$validator->shouldAllowMockingProtectedMethods();
75+
$validator->shouldReceive('getClient')->andReturn($mockClient);
7076

7177
$token = $validator->requestTestNotification();
7278

@@ -87,15 +93,18 @@ public function testValidateThrowsWithInvalidTransactionId(): void
8793

8894
$signingKey = file_get_contents(__DIR__ . '/certs/testSigningKey.p8');
8995

90-
$validator = new Validator(
91-
signingKey: $signingKey,
92-
keyId: 'ABC123XYZ',
93-
issuerId: 'DEF456UVW',
94-
bundleId: 'com.example',
95-
environment: Environment::SANDBOX
96-
);
96+
// Pass constructor args as an indexed array
97+
$validator = Mockery::mock(Validator::class, [
98+
$signingKey,
99+
'ABC123XYZ',
100+
'DEF456UVW',
101+
'com.example',
102+
Environment::SANDBOX
103+
])->makePartial();
97104

98-
$validator->client = $mockClient;
105+
$validator->shouldReceive('__construct')->passthru();
106+
$validator->shouldAllowMockingProtectedMethods();
107+
$validator->shouldReceive('getClient')->andReturn($mockClient);
99108

100109
$this->expectException(ValidationException::class);
101110
$this->expectExceptionCode(APIError::INVALID_TRANSACTION_ID);
@@ -118,15 +127,18 @@ public function testValidateThrowsWithUnknownErrorCode(): void
118127

119128
$signingKey = file_get_contents(__DIR__ . '/certs/testSigningKey.p8');
120129

121-
$validator = new Validator(
122-
signingKey: $signingKey,
123-
keyId: 'ABC123XYZ',
124-
issuerId: 'DEF456UVW',
125-
bundleId: 'com.example',
126-
environment: Environment::SANDBOX
127-
);
130+
// Pass constructor args as an indexed array
131+
$validator = Mockery::mock(Validator::class, [
132+
$signingKey,
133+
'ABC123XYZ',
134+
'DEF456UVW',
135+
'com.example',
136+
Environment::SANDBOX
137+
])->makePartial();
128138

129-
$validator->client = $mockClient;
139+
$validator->shouldReceive('__construct')->passthru();
140+
$validator->shouldAllowMockingProtectedMethods();
141+
$validator->shouldReceive('getClient')->andReturn($mockClient);
130142

131143
$this->expectException(ValidationException::class);
132144
$this->expectExceptionCode(4999999);
@@ -144,15 +156,18 @@ public function testValidateHandles401Unauthorized(): void
144156

145157
$signingKey = file_get_contents(__DIR__ . '/certs/testSigningKey.p8');
146158

147-
$validator = new Validator(
148-
signingKey: $signingKey,
149-
keyId: 'ABC123XYZ',
150-
issuerId: 'DEF456UVW',
151-
bundleId: 'com.example',
152-
environment: Environment::SANDBOX
153-
);
154-
155-
$validator->client = $mockClient;
159+
// Pass constructor args as an indexed array
160+
$validator = Mockery::mock(Validator::class, [
161+
$signingKey,
162+
'ABC123XYZ',
163+
'DEF456UVW',
164+
'com.example',
165+
Environment::SANDBOX
166+
])->makePartial();
167+
168+
$validator->shouldReceive('__construct')->passthru();
169+
$validator->shouldAllowMockingProtectedMethods();
170+
$validator->shouldReceive('getClient')->andReturn($mockClient);
156171

157172
$this->expectException(ValidationException::class);
158173
$this->expectExceptionCode(401);

tests/iTunes/ValidatorTest.php

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,10 @@ public function testValidateReturnsResponse(): void
5454
'receipt' => ['app_item_id' => 123, 'in_app' => []]
5555
])));
5656

57-
$validator = new Validator('secret');
58-
$validator->client = $mockClient;
57+
$validator = Mockery::mock(Validator::class, ['secret'])->makePartial();
58+
$validator->shouldAllowMockingProtectedMethods();
59+
$validator->shouldReceive('getClient')->andReturn($mockClient);
60+
5961
$validator->setReceiptData('abc');
6062

6163
$response = $validator->validate();
@@ -65,19 +67,23 @@ public function testValidateReturnsResponse(): void
6567
public function testRetryOnSandboxError(): void
6668
{
6769
$mockClient = Mockery::mock(Client::class);
70+
// Expect the first request (to production)
6871
$mockClient->shouldReceive('request')
6972
->once()
7073
->andReturn(new GuzzleResponse(200, [], json_encode(['status' => 21007])));
7174

75+
// Expect the second request (to sandbox)
7276
$mockClient->shouldReceive('request')
7377
->once()
7478
->andReturn(new GuzzleResponse(200, [], json_encode([
7579
'status' => 0,
7680
'receipt' => ['app_item_id' => 123, 'in_app' => []]
7781
])));
7882

79-
$validator = new Validator('secret', Environment::PRODUCTION);
80-
$validator->client = $mockClient;
83+
$validator = Mockery::mock(Validator::class, ['secret', Environment::PRODUCTION])->makePartial();
84+
$validator->shouldAllowMockingProtectedMethods();
85+
$validator->shouldReceive('getClient')->andReturn($mockClient);
86+
8187
$validator->setReceiptData('xyz');
8288

8389
$response = $validator->validate();
@@ -91,8 +97,10 @@ public function testThrowsOnInvalidHttpStatus(): void
9197
->once()
9298
->andReturn(new GuzzleResponse(500, [], 'Server error'));
9399

94-
$validator = new Validator('secret', Environment::PRODUCTION);
95-
$validator->client = $mockClient;
100+
$validator = Mockery::mock(Validator::class, ['secret', Environment::PRODUCTION])->makePartial();
101+
$validator->shouldAllowMockingProtectedMethods();
102+
$validator->shouldReceive('getClient')->andReturn($mockClient);
103+
96104
$validator->setReceiptData('test');
97105

98106
$this->expectException(ValidationException::class);
@@ -109,8 +117,10 @@ public function testInAppPurchaseResponseFromFixture(): void
109117
->once()
110118
->andReturn(new GuzzleResponse(200, [], $json));
111119

112-
$validator = new Validator('secret', Environment::SANDBOX);
113-
$validator->client = $mockClient;
120+
$validator = Mockery::mock(Validator::class, ['secret', Environment::SANDBOX])->makePartial();
121+
$validator->shouldAllowMockingProtectedMethods();
122+
$validator->shouldReceive('getClient')->andReturn($mockClient);
123+
114124
$validator->setReceiptData('dummy-data');
115125

116126
$response = $validator->validate();
@@ -128,14 +138,16 @@ public function testInAppPurchaseInvalidReceiptResponseFromFixture(): void
128138
->once()
129139
->andReturn(new GuzzleResponse(200, [], $json));
130140

131-
$validator = new Validator('secret', Environment::SANDBOX);
132-
$validator->client = $mockClient;
141+
$validator = Mockery::mock(Validator::class, ['secret', Environment::SANDBOX])->makePartial();
142+
$validator->shouldAllowMockingProtectedMethods();
143+
$validator->shouldReceive('getClient')->andReturn($mockClient);
144+
133145
$validator->setReceiptData('dummy-data');
134146

135147
$this->expectException(ValidationException::class);
136148
$this->expectExceptionMessage('The data in the receipt-data property was malformed.');
137149

138-
$response = $validator->validate();
150+
$validator->validate();
139151
}
140152

141153
public function testThrowsValidationExceptionWithFormattedMessage(): void
@@ -147,8 +159,10 @@ public function testThrowsValidationExceptionWithFormattedMessage(): void
147159
'status' => 21004,
148160
])));
149161

150-
$validator = new Validator('invalid-shared-secret', Environment::PRODUCTION);
151-
$validator->client = $mockClient;
162+
$validator = Mockery::mock(Validator::class, ['invalid-shared-secret', Environment::PRODUCTION])->makePartial();
163+
$validator->shouldAllowMockingProtectedMethods();
164+
$validator->shouldReceive('getClient')->andReturn($mockClient);
165+
152166
$validator->setReceiptData('dummy');
153167

154168
$this->expectException(ValidationException::class);

0 commit comments

Comments
 (0)