AdyenBridge::verifyNotification()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
nc 4
nop 1
dl 0
loc 18
rs 9.6666
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * This file has been created by developers from BitBag.
5
 * Feel free to contact us once you face any issues or want to start
6
 * another great project.
7
 * You can find more information about us on https://bitbag.shop and write us
8
 * an email on [email protected].
9
 */
10
11
declare(strict_types=1);
12
13
namespace BitBag\SyliusAdyenPlugin\Bridge;
14
15
use Payum\Core\Bridge\Spl\ArrayObject;
16
use Payum\Core\HttpClientInterface;
17
18
final class AdyenBridge implements AdyenBridgeInterface
19
{
20
    /**
21
     * @var array
22
     */
23
    protected $requiredFields = [
24
        'merchantReference' => null,
25
        'paymentAmount' => null,
26
        'currencyCode' => null,
27
        'shipBeforeDate' => null,
28
        'skinCode' => null,
29
        'merchantAccount' => null,
30
        'sessionValidity' => null,
31
        'shopperEmail' => null,
32
    ];
33
34
    /**
35
     * @var array
36
     */
37
    protected $optionalFields = [
38
        'merchantReturnData' => null,
39
        'shopperReference' => null,
40
        'allowedMethods' => null,
41
        'blockedMethods' => null,
42
        'offset' => null,
43
        'shopperStatement' => null,
44
        'recurringContract' => null,
45
        'billingAddressType' => null,
46
        'deliveryAddressType' => null,
47
        'resURL' => null,
48
    ];
49
    /**
50
     * @var array
51
     */
52
    protected $othersFields = [
53
        'brandCode' => null,
54
        'countryCode' => null,
55
        'shopperLocale' => null,
56
        'orderData' => null,
57
        'offerEmail' => null,
58
        'issuerId' => null,
59
    ];
60
61
    /**
62
     * @var array
63
     */
64
    protected $notificationFields = [
65
        'pspReference' => null,
66
        'originalReference' => null,
67
        'merchantAccountCode' => null,
68
        'merchantReference' => null,
69
        'value' => null,
70
        'currency' => null,
71
        'eventCode' => null,
72
        'success' => null,
73
    ];
74
75
    /**
76
     * @var ArrayObject
77
     */
78
    protected $options = [
79
        'skinCode' => null,
80
        'merchantAccount' => null,
81
        'hmacKey' => null,
82
        'environment' => null,
83
        'notification_method' => null,
84
        'notification_hmac' => null,
85
        'default_payment_fields' => [],
86
        'ws_user' => null,
87
        'ws_user_password' => null,
88
    ];
89
90
    /**
91
     * @var \SoapClient|object
92
     */
93
    protected $soapClient;
94
95
    /**
96
     * @param array               $options
97
     * @param HttpClientInterface $client
98
     *
99
     * @throws \Payum\Core\Exception\InvalidArgumentException if an option is invalid
100
     * @throws \Payum\Core\Exception\LogicException if a sandbox is not boolean
101
     */
102
    public function __construct(array $options, HttpClientInterface $client = null)
0 ignored issues
show
Unused Code introduced by
The parameter $client is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
103
    {
104
        $options = ArrayObject::ensureArrayObject($options);
105
        $options->defaults($this->options);
106
        $options->validateNotEmpty([
107
            'skinCode',
108
            'merchantAccount',
109
            'hmacKey',
110
            'notification_hmac',
111
            'ws_user',
112
            'ws_user_password',
113
        ]);
114
115
        $this->options = $options;
116
    }
117
118
    /**
119
     * @return string
120
     */
121
    public function getApiEndpoint(): string
122
    {
123
        return sprintf('https://%s.adyen.com/hpp/select.shtml', $this->options['environment']);
124
    }
125
126
    /**
127
     * @param array $params
128
     * @param bool $isNotify
129
     * @param string $hmacKey
130
     *
131
     * @return string
132
     */
133
    public function merchantSig(array $params, bool $isNotify = false, string $hmacKey = 'hmacKey'): string
134
    {
135
        if (false === $isNotify) {
136
            ksort($params, SORT_STRING);
137
        }
138
139
        $escapedPairs = [];
140
141
        foreach ($params as $key => $value) {
142
            $escapedPairs[$key] = str_replace(':','\\:', str_replace('\\', '\\\\', $value));
143
        }
144
145
        if (false === $isNotify) {
146
            $signingString = implode(":", array_merge(array_keys($escapedPairs), array_values($escapedPairs)));
147
        } else {
148
            $signingString = implode(":", array_merge(array_values($escapedPairs)));
149
        }
150
151
        $binaryHmacKey = pack("H*" , $this->options[$hmacKey]);
152
153
        $binaryHmac = hash_hmac('sha256', $signingString, $binaryHmacKey, true);
154
155
        $signature = base64_encode($binaryHmac);
156
157
        return $signature;
158
    }
159
160
    /**
161
     * @param array $params
162
     *
163
     * @return bool
164
     */
165
    public function verifySign(array $params): bool
166
    {
167
        if (empty($params['merchantSig'])) {
168
            return false;
169
        }
170
171
        $merchantSig = $params['merchantSig'];
172
173
        unset($params['merchantSig']);
174
175
        return $merchantSig === $this->merchantSig($params);
176
    }
177
178
    /**
179
     * @param array $params
180
     *
181
     * @return bool
182
     */
183
    public function verifyNotification(array $params): bool
184
    {
185
        if (empty($params['additionalData_hmacSignature'])) {
186
            return false;
187
        }
188
189
        $merchantSig = $params['additionalData_hmacSignature'];
190
191
        $data = [];
192
193
        foreach (array_keys($this->notificationFields) as $fieldKey) {
194
            if (isset($params[$fieldKey])) {
195
                $data[$fieldKey] = $params[$fieldKey];
196
            }
197
        }
198
199
        return $merchantSig === $this->merchantSig($data, true,'notification_hmac');
200
    }
201
202
    /**
203
     * @param array $params
204
     *
205
     * @return string
206
     */
207
    public function createSignatureForNotification(array $params): string
208
    {
209
        $data = [];
210
211
        foreach (array_keys($this->notificationFields) as $fieldKey) {
212
            $data[$fieldKey] = $params[$fieldKey];
213
        }
214
215
        return $this->merchantSig($data, true,'notification_hmac');
216
    }
217
218
    /**
219
     * @param array $params
220
     * @param array $details
221
     *
222
     * @return bool
223
     */
224
    public function verifyRequest(array $params, array $details): bool
225
    {
226
        if (!isset($params['merchantReference']) || empty($params['merchantReference']) ||
227
            !isset($params['authResult']) || empty($params['authResult'])) {
228
            return false;
229
        }
230
231
        if (!isset($details['merchantReference']) || ($details['merchantReference'] !== $params['merchantReference'])) {
232
            return false;
233
        }
234
235
        return $this->verifySign($params);
236
    }
237
238
    /**
239
     * @param array $params
240
     *
241
     * @return array
242
     */
243
    public function prepareFields(array $params): array
244
    {
245
        if (false !== empty($this->options['default_payment_fields'])) {
246
            $params = array_merge($params, (array) $this->options['default_payment_fields']);
247
        }
248
249
        $params['shipBeforeDate'] = date('Y-m-d', strtotime('+1 hour'));
250
        $params['sessionValidity'] = date(DATE_ATOM, strtotime('+1 hour'));
251
252
        $params['skinCode'] = $this->options['skinCode'];
253
        $params['merchantAccount'] = $this->options['merchantAccount'];
254
255
        $supportedParams = array_merge($this->requiredFields, $this->optionalFields, $this->othersFields);
256
257
        $params = array_filter(array_replace(
258
            $supportedParams,
259
            array_intersect_key($params, $supportedParams)
260
        ));
261
262
        $params['merchantSig'] = $this->merchantSig($params);
263
264
        return $params;
265
    }
266
267
    public function createSoapClient(): void
268
    {
269
        $this->soapClient = new \SoapClient(
270
            $this->getWsdl(), [
271
                "login" => $this->options['ws_user'],
272
                "password" => $this->options['ws_user_password'],
273
                "style" => SOAP_DOCUMENT,
274
                "encoding" => SOAP_LITERAL,
275
                "cache_wsdl" => WSDL_CACHE_BOTH,
276
                "trace" => 1
277
            ]
278
        );
279
    }
280
281
    /**
282
     * @return string
283
     */
284
    public function getWsdl(): string
285
    {
286
        return sprintf('https://pal-%s.adyen.com/pal/Payment.wsdl', $this->options['environment']);
287
    }
288
289
    /**
290
     * @return string
291
     */
292
    public function getMerchantAccount(): string
293
    {
294
        return $this->options['merchantAccount'];
295
    }
296
297
    /**
298
     * @param array $modificationAmount
299
     * @param string $originalReference
300
     * @param string $reference
301
     *
302
     * @return \stdClass
303
     */
304
    public function refundAction(
305
        array $modificationAmount,
306
        string $originalReference,
307
        string $reference
308
    ): \stdClass
309
    {
310
        $this->createSoapClient();
311
312
        $data = [
313
            'modificationRequest' => [
314
                'merchantAccount' => $this->getMerchantAccount(),
315
                'modificationAmount' => $modificationAmount,
316
                'originalReference' => $originalReference,
317
                'reference' => $reference,
318
            ]
319
        ];
320
321
        return $this->soapClient->refund($data);
322
    }
323
}
324