Completed
Push — master ( 66bd98...7177e9 )
by Adar
24s
created

Validator::getClientConfig()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 0
cts 2
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace ReceiptValidator\iTunes;
4
5
use ReceiptValidator\RunTimeException;
6
use GuzzleHttp\Client as HttpClient;
7
8
class Validator
9
{
10
    const ENDPOINT_SANDBOX = 'https://sandbox.itunes.apple.com/verifyReceipt';
11
    const ENDPOINT_PRODUCTION = 'https://buy.itunes.apple.com/verifyReceipt';
12
13
    /**
14
     * endpoint url
15
     *
16
     * @var string
17
     */
18
    protected $endpoint;
19
20
    /**
21
     * Whether to exclude old transactions
22
     *
23
     * @var bool
24
     */
25
    protected $exclude_old_transactions = false;
26
27
    /**
28
     * itunes receipt data, in base64 format
29
     *
30
     * @var string|null
31
     */
32
    protected $receipt_data;
33
34
    /**
35
     * The shared secret is a unique code to receive your In-App Purchase receipts.
36
     * Without a shared secret, you will not be able to test or offer your automatically
37
     * renewable In-App Purchase subscriptions.
38
     *
39
     * @var string|null
40
     */
41
    protected $shared_secret;
42
43
    /**
44
     * Guzzle http client
45
     *
46
     * @var HttpClient
47
     */
48
    protected $client;
49
50
    /**
51
     * Validator constructor
52
     *
53
     * @param string $endpoint
54
     * @throws \InvalidArgumentException
55
     */
56 6
    public function __construct(string $endpoint = self::ENDPOINT_PRODUCTION)
57
    {
58 6
        if ($endpoint !== self::ENDPOINT_PRODUCTION && $endpoint !== self::ENDPOINT_SANDBOX) {
59 1
            throw new \InvalidArgumentException("Invalid endpoint '{$endpoint}'");
60
        }
61
62 6
        $this->endpoint = $endpoint;
63 6
    }
64
65
    /**
66
     * Get receipt data
67
     *
68
     * @return string|null
69
     */
70 2
    public function getReceiptData(): ?string
71
    {
72 2
        return $this->receipt_data;
73
    }
74
75
    /**
76
     * Set receipt data, either in base64, or in json
77
     *
78
     * @param string|null $receipt_data
79
     *
80
     * @return $this
81
     */
82 2
    public function setReceiptData($receipt_data): self
83
    {
84 2
        if (strpos($receipt_data, '{') !== false) {
85 1
            $this->receipt_data = base64_encode($receipt_data);
86
        } else {
87 1
            $this->receipt_data = $receipt_data;
88
        }
89
90 2
        return $this;
91
    }
92
93
    /**
94
     * @return string|null
95
     */
96 1
    public function getSharedSecret(): ?string
97
    {
98 1
        return $this->shared_secret;
99
    }
100
101
    /**
102
     * @param string|null $shared_secret
103
     * @return $this
104
     */
105 1
    public function setSharedSecret($shared_secret = null): self
106
    {
107 1
        $this->shared_secret = $shared_secret;
108
109 1
        return $this;
110
    }
111
112
    /**
113
     * get endpoint
114
     *
115
     * @return string
116
     */
117 1
    public function getEndpoint(): string
118
    {
119 1
        return $this->endpoint;
120
    }
121
122
    /**
123
     * set endpoint
124
     *
125
     * @param string $endpoint
126
     * @return $this
127
     */
128 1
    public function setEndpoint(string $endpoint): self
129
    {
130 1
        $this->endpoint = $endpoint;
131
132 1
        return $this;
133
    }
134
135
    /**
136
     * Get exclude old transactions
137
     *
138
     * @return bool
139
     */
140 1
    public function getExcludeOldTransactions(): bool
141
    {
142 1
        return $this->exclude_old_transactions;
143
    }
144
145
    /**
146
     * Set exclude old transactions
147
     *
148
     * @param bool $exclude
149
     * @return Validator
150
     */
151 1
    public function setExcludeOldTransactions(bool $exclude): self
152
    {
153 1
        $this->exclude_old_transactions = $exclude;
154
155 1
        return $this;
156
    }
157
158
    /**
159
     * Get Guzzle client config
160
     * @return array
161
     */
162
    protected function getClientConfig(): array
163
    {
164
        return ['base_uri' => $this->endpoint];
165
    }
166
167
    /**
168
     * Returns the Guzzle client
169
     *
170
     * @return HttpClient
171
     */
172
    protected function getClient(): HttpClient
173
    {
174
        if ($this->client === null) {
175
            $this->client = new HttpClient($this->getClientConfig());
176
        }
177
178
        return $this->client;
179
    }
180
181
    /**
182
     * Prepare request data (json)
183
     *
184
     * @return string
185
     */
186
    protected function prepareRequestData(): string
187
    {
188
        $request = [
189
            'receipt-data' => $this->getReceiptData(),
190
            'exclude-old-transactions' => $this->getExcludeOldTransactions(),
191
        ];
192
193
        if ($this->shared_secret !== null) {
194
            $request['password'] = $this->shared_secret;
195
        }
196
197
        return json_encode($request);
198
    }
199
200
201
    /**
202
     * @param null|string $receipt_data
203
     * @param null|string $shared_secret
204
     * @return ResponseInterface
205
     * @throws RunTimeException
206
     * @throws \GuzzleHttp\Exception\GuzzleException
207
     */
208
    public function validate(?string $receipt_data = null, ?string $shared_secret = null): ResponseInterface
209
    {
210
211
        if ($receipt_data !== null) {
212
            $this->setReceiptData($receipt_data);
213
        }
214
215
        if ($shared_secret !== null) {
216
            $this->setSharedSecret($shared_secret);
217
        }
218
219
        $client = $this->getClient();
220
221
        return $this->sendRequestUsingClient($client);
222
    }
223
224
    /**
225
     * @param HttpClient $client
226
     * @return ProductionResponse|SandboxResponse
227
     * @throws RunTimeException
228
     * @throws \GuzzleHttp\Exception\GuzzleException
229
     */
230
    private function sendRequestUsingClient(HttpClient $client)
231
    {
232
        $baseUri = (string)$client->getConfig('base_uri');
233
234
        $httpResponse = $client->request('POST', null, ['body' => $this->prepareRequestData()]);
235
236
        if ($httpResponse->getStatusCode() !== 200) {
237
            throw new RunTimeException('Unable to get response from itunes server');
238
        }
239
240
        $decodedBody = json_decode($httpResponse->getBody(), true);
241
242
        if ($baseUri === self::ENDPOINT_PRODUCTION) {
243
            $response = new ProductionResponse($decodedBody);
244
245
            // on a 21007 error, retry the request in the sandbox environment
246
            // these are receipts from the Apple review team
247
            if ($response->getResultCode() === ResponseInterface::RESULT_SANDBOX_RECEIPT_SENT_TO_PRODUCTION) {
248
                $config = array_merge($this->getClientConfig(), ['base_uri' => self::ENDPOINT_SANDBOX]);
249
                $client = new HttpClient($config);
250
251
                return $this->sendRequestUsingClient($client);
252
            }
253
254
        } else {
255
            $response = new SandboxResponse($decodedBody);
256
        }
257
258
        return $response;
259
    }
260
}
261