Passed
Pull Request — master (#5)
by
unknown
09:48
created

AWS::cancelCode()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 6
nc 1
nop 2
dl 0
loc 8
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Part of the AmazonGiftCode package.
5
 * Author: Kashyap Merai <[email protected]>
6
 *
7
 */
8
9
10
namespace kamerk22\AmazonGiftCode\AWS;
11
12
13
use kamerk22\AmazonGiftCode\Client\Client;
14
use kamerk22\AmazonGiftCode\Config\Config;
15
use kamerk22\AmazonGiftCode\Exceptions\AmazonErrors;
16
use kamerk22\AmazonGiftCode\Response\CancelResponse;
17
use kamerk22\AmazonGiftCode\Response\CreateBalanceResponse;
18
use kamerk22\AmazonGiftCode\Response\CreateResponse;
19
20
class AWS
21
{
22
    public const SERVICE_NAME = 'AGCODService';
23
    public const ACCEPT_HEADER = 'accept';
24
    public const CONTENT_HEADER = 'content-type';
25
    public const HOST_HEADER = 'host';
26
    public const X_AMZ_DATE_HEADER = 'x-amz-date';
27
    public const X_AMZ_TARGET_HEADER = 'x-amz-target';
28
    public const AUTHORIZATION_HEADER = 'Authorization';
29
    public const AWS_SHA256_ALGORITHM = 'AWS4-HMAC-SHA256';
30
    public const KEY_QUALIFIER = 'AWS4';
31
    public const TERMINATION_STRING = 'aws4_request';
32
    public const CREATE_GIFT_CARD_SERVICE = 'CreateGiftCard';
33
    public const CANCEL_GIFT_CARD_SERVICE = 'CancelGiftCard';
34
    public const GET_AVAILABLE_FUNDS_SERVICE = 'GetAvailableFunds';
35
36
    private $_config;
37
38
39
    /**
40
     * AWS constructor.
41
     * @param Config $config
42
     */
43
    public function __construct(Config $config)
44
    {
45
        $this->_config = $config;
46
    }
47
48
49
    /**
50
     * @param $amount
51
     * @return CreateResponse
52
     *
53
     * @throws AmazonErrors
54
     */
55
    public function getCode($amount): CreateResponse
56
    {
57
        $serviceOperation = self::CREATE_GIFT_CARD_SERVICE;
58
        $payload = $this->getGiftCardPayload($amount);
59
        $canonicalRequest = $this->getCanonicalRequest($serviceOperation, $payload);
60
        $dateTimeString = $this->getTimestamp();
61
        $result = json_decode($this->makeRequest($payload, $canonicalRequest, $serviceOperation, $dateTimeString), true);
62
        return new CreateResponse($result);
63
64
    }
65
66
    /**
67
     * @param $creationRequestId
68
     * @param $gcId
69
     * @return CancelResponse
70
     */
71
    public function cancelCode($creationRequestId, $gcId): CancelResponse
72
    {
73
        $serviceOperation = self::CANCEL_GIFT_CARD_SERVICE;
74
        $payload = $this->getCancelGiftCardPayload($creationRequestId, $gcId);
75
        $canonicalRequest = $this->getCanonicalRequest($serviceOperation, $payload);
76
        $dateTimeString = $this->getTimestamp();
77
        $result = json_decode($this->makeRequest($payload, $canonicalRequest, $serviceOperation, $dateTimeString), true);
78
        return new CancelResponse($result);
79
    }
80
81
    /**
82
     * @return CreateBalanceResponse
83
     */
84
    public function getBalance(): CreateBalanceResponse
85
    {
86
        $serviceOperation = self::GET_AVAILABLE_FUNDS_SERVICE;
87
        $payload = $this->getAvailableFundsPayload();
88
        $canonicalRequest = $this->getCanonicalRequest($serviceOperation, $payload);
89
        $dateTimeString = $this->getTimestamp();
90
        $result = json_decode($this->makeRequest($payload, $canonicalRequest, $serviceOperation, $dateTimeString), true);
91
        return new CreateBalanceResponse($result);
92
    }
93
94
    /**
95
     * @param $payload
96
     * @param $canonicalRequest
97
     * @param $serviceOperation
98
     * @param $dateTimeString
99
     * @return String
100
     */
101
    public function makeRequest($payload, $canonicalRequest, $serviceOperation, $dateTimeString): string
102
    {
103
        $KEY_QUALIFIER = self::KEY_QUALIFIER;
104
        $canonicalRequestHash = $this->buildHash($canonicalRequest);
105
        $stringToSign = $this->buildStringToSign($canonicalRequestHash);
106
        $authorizationValue = $this->buildAuthSignature($stringToSign);
107
108
        $secretKey = $this->_config->getSecret();
109
        $endpoint = $this->_config->getEndpoint();
110
        $regionName = $this->getRegion();
111
112
        $SERVICE_NAME = 'AGCODService';
113
        $serviceTarget = 'com.amazonaws.agcod.' . $SERVICE_NAME . '.' . $serviceOperation;
114
        $dateString = $this->getDateString();
115
116
        $signatureAWSKey = $KEY_QUALIFIER . $secretKey;
117
118
        $kDate = $this->hmac($dateString, $signatureAWSKey);
119
        $kDate_hexis = $this->hmac($dateString, $signatureAWSKey, false);
0 ignored issues
show
Unused Code introduced by
The assignment to $kDate_hexis is dead and can be removed.
Loading history...
120
        $kRegion = $this->hmac($regionName, $kDate);
121
        $kRegion_hexis = $this->hmac($regionName, $kDate, false);
0 ignored issues
show
Unused Code introduced by
The assignment to $kRegion_hexis is dead and can be removed.
Loading history...
122
        $kService_hexis = $this->hmac($SERVICE_NAME, $kRegion, false);
0 ignored issues
show
Unused Code introduced by
The assignment to $kService_hexis is dead and can be removed.
Loading history...
123
124
        $url = 'https://' . $endpoint . '/' . $serviceOperation;
125
        $headers = $this->buildHeaders($payload, $authorizationValue, $dateTimeString, $serviceTarget);
126
        return (new Client())->request($url, $headers, $payload);
127
    }
128
129
    /**
130
     * @param $payload
131
     * @param $authorizationValue
132
     * @param $dateTimeString
133
     * @param $serviceTarget
134
     * @return array
135
     */
136
    public function buildHeaders($payload, $authorizationValue, $dateTimeString, $serviceTarget): array
137
    {
138
        $ACCEPT_HEADER = self::ACCEPT_HEADER;
139
        $X_AMZ_DATE_HEADER = self::X_AMZ_DATE_HEADER;
140
        $X_AMZ_TARGET_HEADER = self::X_AMZ_TARGET_HEADER;
141
        $AUTHORIZATION_HEADER = self::AUTHORIZATION_HEADER;
142
        return [
143
            'Content-Type:' . $this->getContentType(),
144
            'Content-Length: ' . strlen($payload),
145
            $AUTHORIZATION_HEADER . ':' . $authorizationValue,
146
            $X_AMZ_DATE_HEADER . ':' . $dateTimeString,
147
            $X_AMZ_TARGET_HEADER . ':' . $serviceTarget,
148
            $ACCEPT_HEADER . ':' . $this->getContentType()
149
        ];
150
    }
151
152
    /**
153
     * @param $stringToSign
154
     * @return string
155
     */
156
    public function buildAuthSignature($stringToSign): string
157
    {
158
        $AWS_SHA256_ALGORITHM = self::AWS_SHA256_ALGORITHM;
159
        $SERVICE_NAME = self::SERVICE_NAME;
160
        $TERMINATION_STRING = self::TERMINATION_STRING;
161
        $ACCEPT_HEADER = self::ACCEPT_HEADER;
162
        $HOST_HEADER = self::HOST_HEADER;
163
        $X_AMZ_DATE_HEADER = self::X_AMZ_DATE_HEADER;
164
        $X_AMZ_TARGET_HEADER = self::X_AMZ_TARGET_HEADER;
165
166
        $awsKeyId = $this->_config->getAccessKey();
167
        $regionName = $this->getRegion();
168
169
        $dateString = $this->getDateString();
170
        $derivedKey = $this->buildDerivedKey();
171
        // Calculate signature per http://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html
172
        $finalSignature = $this->hmac($stringToSign, $derivedKey, false);
173
174
        // Assemble Authorization Header with signing information
175
        // per http://docs.aws.amazon.com/general/latest/gr/sigv4-add-signature-to-request.html
176
        $authorizationValue =
177
            $AWS_SHA256_ALGORITHM
178
            . ' Credential=' . $awsKeyId
179
            . '/' . $dateString . '/' . $regionName . '/' . $SERVICE_NAME . '/' . $TERMINATION_STRING . ','
180
            . ' SignedHeaders='
181
            . $ACCEPT_HEADER . ';' . $HOST_HEADER . ';' . $X_AMZ_DATE_HEADER . ';' . $X_AMZ_TARGET_HEADER . ','
182
            . ' Signature='
183
            . $finalSignature;
184
185
        return $authorizationValue;
186
    }
187
188
    /**
189
     * @param $canonicalRequestHash
190
     * @return string
191
     */
192
    public function buildStringToSign($canonicalRequestHash): string
193
    {
194
        $AWS_SHA256_ALGORITHM = self::AWS_SHA256_ALGORITHM;
195
        $TERMINATION_STRING = self::TERMINATION_STRING;
196
        $SERVICE_NAME = self::SERVICE_NAME;
197
        $regionName = $this->getRegion();
198
        $dateTimeString = $this->getTimestamp();
199
        $dateString = $this->getDateString();
200
        $stringToSign = "$AWS_SHA256_ALGORITHM\n$dateTimeString\n$dateString/$regionName/$SERVICE_NAME/$TERMINATION_STRING\n$canonicalRequestHash";
201
202
        return $stringToSign;
203
    }
204
205
    /**
206
     * @param bool $rawOutput
207
     * @return string
208
     */
209
    public function buildDerivedKey($rawOutput = true): string
210
    {
211
        $KEY_QUALIFIER = self::KEY_QUALIFIER;
212
        $TERMINATION_STRING = self::TERMINATION_STRING;
213
        $SERVICE_NAME = self::SERVICE_NAME;
214
215
        $awsSecretKey = $this->_config->getSecret();
216
        // Append Key Qualifier, "AWS4", to secret key per http://docs.aws.amazon.com/general/latest/gr/signature-v4-examples.html
217
        $signatureAWSKey = $KEY_QUALIFIER . $awsSecretKey;
218
        $regionName = $this->getRegion();
219
        $dateString = $this->getDateString();
220
221
        $kDate = $this->hmac($dateString, $signatureAWSKey);
222
        $kRegion = $this->hmac($regionName, $kDate);
223
        $kService = $this->hmac($SERVICE_NAME, $kRegion);
224
225
        // Derived the Signing key (derivedKey aka kSigning)
226
        return $this->hmac($TERMINATION_STRING, $kService, $rawOutput);
227
    }
228
229
    /**
230
     * @return string
231
     */
232
    public function getRegion(): string
233
    {
234
        $endpoint = $this->_config->getEndpoint();
235
        $regionName = 'us-east-1';
236
237
        if ($endpoint === 'agcod-v2-eu.amazon.com' || $endpoint === 'agcod-v2-eu-gamma.amazon.com') {
238
            $regionName = 'eu-west-1';
239
        } else if ($endpoint === 'agcod-v2-fe.amazon.com' || $endpoint === 'agcod-v2-fe-gamma.amazon.com') {
240
            $regionName = 'us-west-2';
241
        }
242
        return $regionName;
243
    }
244
245
246
    /**
247
     * @param $amount
248
     * @return string
249
     */
250
    public function getGiftCardPayload($amount, $creationId = null): string
251
    {
252
        $amount = trim($amount);
253
        $payload = [
254
            'creationRequestId' => $creationId ?: uniqid($this->_config->getPartner().'_'),
255
            'partnerId' => $this->_config->getPartner(),
256
            'value' =>
257
                [
258
                    'currencyCode' => $this->_config->getCurrency(),
259
                    'amount' => (float)$amount
260
                ]
261
        ];
262
        return json_encode($payload);
263
    }
264
265
    /**
266
     * @param $creationRequestId
267
     * @param $gcId
268
     * @return string
269
     */
270
    public function getCancelGiftCardPayload($creationRequestId, $gcId): string
271
    {
272
        $gcResponseId = trim($gcId);
273
        $payload = [
274
            'creationRequestId' => $creationRequestId,
275
            'partnerId' => $this->_config->getPartner(),
276
            'gcId' => $gcResponseId
277
        ];
278
        return json_encode($payload);
279
    }
280
281
    /**
282
     * @return string
283
     */
284
    public function getAvailableFundsPayload(): string
285
    {
286
        $payload = [
287
            'partnerId' => $this->_config->getPartner(),
288
        ];
289
        return json_encode($payload);
290
    }
291
292
    /**
293
     * @param $serviceOperation
294
     * @param $payload
295
     * @return string
296
     */
297
    public function getCanonicalRequest($serviceOperation, $payload): string
298
    {
299
        $HOST_HEADER = self::HOST_HEADER;
300
        $X_AMZ_DATE_HEADER = self::X_AMZ_DATE_HEADER;
301
        $X_AMZ_TARGET_HEADER = self::X_AMZ_TARGET_HEADER;
302
        $ACCEPT_HEADER = self::ACCEPT_HEADER;
303
        $payloadHash = $this->buildHash($payload);
304
        $canonicalHeaders = $this->buildCanonicalHeaders($serviceOperation);
305
        $canonicalRequest = "POST\n/$serviceOperation\n\n$canonicalHeaders\n\n$ACCEPT_HEADER;$HOST_HEADER;$X_AMZ_DATE_HEADER;$X_AMZ_TARGET_HEADER\n$payloadHash";
306
        return $canonicalRequest;
307
    }
308
309
    /**
310
     * @param $data
311
     * @return string
312
     */
313
    public function buildHash($data): string
314
    {
315
        return hash('sha256', $data);
316
    }
317
318
    /**
319
     * @return false|string
320
     */
321
    public function getTimestamp()
322
    {
323
        return gmdate('Ymd\THis\Z');
324
    }
325
326
    /**
327
     * @param $data
328
     * @param $key
329
     * @param bool $raw
330
     * @return string
331
     */
332
    public function hmac($data, $key, $raw = true): string
333
    {
334
        return hash_hmac('sha256', $data, $key, $raw);
335
    }
336
337
    /**
338
     * @return bool|string
339
     */
340
    public function getDateString()
341
    {
342
        return substr($this->getTimestamp(), 0, 8);
343
    }
344
345
    /**
346
     * @return string
347
     */
348
    public function getContentType(): string
349
    {
350
        return 'application/json';
351
    }
352
353
    /**
354
     * @param $serviceOperation
355
     * @return string
356
     */
357
    public function buildCanonicalHeaders($serviceOperation): string
358
    {
359
        $ACCEPT_HEADER = self::ACCEPT_HEADER;
360
        $HOST_HEADER = self::HOST_HEADER;
361
        $X_AMZ_DATE_HEADER = self::X_AMZ_DATE_HEADER;
362
        $X_AMZ_TARGET_HEADER = self::X_AMZ_TARGET_HEADER;
363
        $dateTimeString = $this->getTimestamp();
364
        $endpoint = $this->_config->getEndpoint();
365
        $contentType = $this->getContentType();
366
        return
367
            "$ACCEPT_HEADER:$contentType\n$HOST_HEADER:$endpoint\n$X_AMZ_DATE_HEADER:$dateTimeString\n$X_AMZ_TARGET_HEADER:com.amazonaws.agcod.AGCODService.$serviceOperation";
368
    }
369
}
370