Passed
Pull Request — main (#1)
by Leith
01:50
created

PurchaseRequest::getApiKey()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
1
<?php
2
3
namespace Omnipay\Worldline\Message;
4
5
use DateTime;
6
use DateTimeZone;
7
use Money\Currency;
8
use Money\Number;
9
use Money\Parser\DecimalMoneyParser;
10
use Omnipay\Common\Message\AbstractRequest;
11
use Omnipay\Common\Exception\InvalidRequestException;
12
13
/**
14
 * Worldline Purchase Request
15
 *
16
 * @see https://docs.direct.worldline-solutions.com/en/api-reference#tag/HostedCheckout/operation/CreateHostedCheckoutApi
17
 */
18
class PurchaseRequest extends AbstractRequest
19
{
20
    /** @var string */
21
    protected $liveEndpoint = 'https://payment.direct.worldline-solutions.com';
22
    /** @var string */
23
    protected $testEndpoint = 'https://payment.preprod.direct.worldline-solutions.com';
24
25
    /** @var string  Can be "FINAL_AUTHORIZATION" "PRE_AUTHORIZATION" or "SALE" */
26
    protected $authorizationMode = 'SALE';
27
    protected $requestMethod = 'POST';
28
29
    public function getApiKey()
30
    {
31
        return $this->getParameter('apiKey');
32
    }
33
34
    public function setApiKey($value)
35
    {
36
        return $this->setParameter('apiKey', $value);
37
    }
38
39
    public function getApiSecret()
40
    {
41
        return $this->getParameter('apiSecret');
42
    }
43
44
    public function setApiSecret($value)
45
    {
46
        return $this->setParameter('apiSecret', $value);
47
    }
48
49
    public function getAvailablePaymentProducts()
50
    {
51
        return $this->getParameter('availablePaymentProducts');
52
    }
53
54
    /**
55
     * @param int[] $value  @see https://docs.direct.worldline-solutions.com/en/payment-methods-and-features/
56
     */
57
    public function setAvailablePaymentProducts($value)
58
    {
59
        return $this->setParameter('availablePaymentProducts', $value);
60
    }
61
62
    public function getExcludedPaymentProducts()
63
    {
64
        return $this->getParameter('excludedPaymentProducts');
65
    }
66
67
    /**
68
     * @param int[] $value  @see https://docs.direct.worldline-solutions.com/en/payment-methods-and-features/
69
     */
70
    public function setExcludedPaymentProducts($value)
71
    {
72
        return $this->setParameter('excludedPaymentProducts', $value);
73
    }
74
75
    public function getMerchantId()
76
    {
77
        return $this->getParameter('merchantId');
78
    }
79
80
    public function setMerchantId($value)
81
    {
82
        return $this->setParameter('merchantId', $value);
83
    }
84
85
    public function getMerchantName()
86
    {
87
        return $this->getParameter('merchantName');
88
    }
89
90
    public function setMerchantName($value)
91
    {
92
        return $this->setParameter('merchantName', $value);
93
    }
94
95
    public function getShowResultPage()
96
    {
97
        return $this->getParameter('showResultPage');
98
    }
99
100
    public function setShowResultPage($value)
101
    {
102
        return $this->setParameter('showResultPage', $value);
103
    }
104
105
    public function getSessionTimeout()
106
    {
107
        return $this->getParameter('sessionTimeout');
108
    }
109
110
    /**
111
     * Timeout is in minutes, default 180
112
     */
113
    public function setSessionTimeout($value)
114
    {
115
        return $this->setParameter('sessionTimeout', $value);
116
    }
117
118
    public function getTransactionChannel()
119
    {
120
        return $this->getParameter('transactionChannel');
121
    }
122
123
    /**
124
     * Transaction channel can only be either 'ECOMMERCE' or 'MOTO'
125
     */
126
    public function setTransactionChannel($value)
127
    {
128
        if (!in_array($value, ['ECOMMERCE', 'MOTO'])) {
129
            $value = null;
130
        }
131
        return $this->setParameter('transactionChannel', $value);
132
    }
133
134
    public function getData()
135
    {
136
        $this->validate('merchantId', 'amount', 'currency');
137
138
        $formattedItems = [];
139
        $items = $this->getItems();
140
        if ($items) {
141
            foreach ($items as $item) {
142
                $itemPrice = $this->getItemPriceInteger($item);
143
                $formattedItems[] = [
144
                    'amountOfMoney' => [
145
                        'amount' => $item->getQuantity() * $itemPrice,
146
                        'currencyCode' => $this->getCurrency(),
147
                    ],
148
                    'orderLineDetails' => [
149
                        'productName' => $item->getName(),
150
                        'productPrice' => $itemPrice,
151
                        'quantity' => (int) $item->getQuantity(),
152
                    ],
153
                ];
154
            }
155
        }
156
157
        $data = [
158
            'cardPaymentMethodSpecificInput' => [
159
                'authorizationMode' => 'SALE',
160
                'transactionChannel' => $this->getTransactionChannel() ?? 'ECOMMERCE',
161
            ],
162
            'hostedCheckoutSpecificInput' => [
163
                // if adding locale, validate locale against known formats
164
                // @see https://docs.direct.worldline-solutions.com/en/integration/basic-integration-methods/hosted-checkout-page#chooselanguageversion
165
                // 'locale' => 'en_UK',
166
                'returnUrl' => $this->getReturnUrl(),
167
            ],
168
            'order' => [
169
                'amountOfMoney' => [
170
                    'amount' => $this->getAmountInteger(),
171
                    'currencyCode' => $this->getCurrency(),
172
                ],
173
                'references' => [
174
                    'descriptor' => $this->getMerchantName(),
175
                    'merchantReference' => $this->getTransactionId(),
176
                ],
177
                'shoppingCart' => [
178
                    'items' => $formattedItems,
179
                ],
180
            ],
181
        ];
182
183
        if ($this->getAvailablePaymentProducts() !== null) {
184
            if (!isset($data['hostedCheckoutSpecificInput']['paymentProductFilters'])) {
185
                $data['hostedCheckoutSpecificInput']['paymentProductFilters'] = [];
186
            }
187
            $data['hostedCheckoutSpecificInput']['paymentProductFilters']['restrictTo'] = [
188
                'products' => $this->getAvailablePaymentProducts(),
189
            ];
190
        }
191
192
        if ($this->getExcludedPaymentProducts() !== null) {
193
            if (!isset($data['hostedCheckoutSpecificInput']['paymentProductFilters'])) {
194
                $data['hostedCheckoutSpecificInput']['paymentProductFilters'] = [];
195
            }
196
            $data['hostedCheckoutSpecificInput']['paymentProductFilters']['exclude'] = [
197
                'products' => $this->getExcludedPaymentProducts(),
198
            ];
199
        }
200
201
        if ($this->getShowResultPage() !== null) {
202
            $data['hostedCheckoutSpecificInput']['showResultPage'] = (bool) $this->getShowResultPage();
203
        }
204
205
        if ($this->getSessionTimeout() !== null) {
206
            $data['hostedCheckoutSpecificInput']['sessionTimeout'] = (int) $this->getSessionTimeout();
207
        }
208
209
        if ($this->getNotifyUrl() !== null) {
0 ignored issues
show
introduced by
The condition $this->getNotifyUrl() !== null is always true.
Loading history...
210
            $data['feedbacks'] = [
211
                'webhookUrl' => $this->getNotifyUrl(),
212
            ];
213
        }
214
215
        return $data;
216
    }
217
218
    public function getEndpoint()
219
    {
220
        return ($this->getTestMode() ? $this->testEndpoint : $this->liveEndpoint).$this->getAction();
221
    }
222
223
    public function sendData($data)
224
    {
225
        $contentType = $this->requestMethod == 'POST' ? 'application/json; charset=utf-8' : '';
226
        $now = new DateTime('now', new DateTimeZone('GMT'));
227
        $dateTime = $now->format("D, d M Y H:i:s T");
228
        $endpointAction = $this->getAction();
229
230
        $message = $this->requestMethod."\n".$contentType."\n".$dateTime."\n".$endpointAction."\n";
231
        $signature = $this->createSignature($message, $this->getApiSecret());
232
233
        $headers = [
234
            'Content-Type' => $contentType,
235
            'Authorization' => 'GCS v1HMAC:'.$this->getApiKey().':'.$signature,
236
            'Date' => $dateTime,
237
        ];
238
239
        $body = json_encode($data);
240
241
        $httpResponse = $this->httpClient->request(
242
            $this->requestMethod,
243
            $this->getEndpoint(),
244
            $headers,
245
            $body
246
        );
247
248
        return $this->createResponse($httpResponse->getBody()->getContents());
249
    }
250
251
    protected function createResponse($data)
252
    {
253
        return $this->response = new PurchaseResponse($this, json_decode($data));
254
    }
255
256
    /**
257
     * Create signature hash used to verify messages
258
     *
259
     * @param string $message  The message to encrypt
260
     * @param string $key      The base64-encoded key used to encrypt the message
261
     *
262
     * @return string Generated signature
263
     */
264
    protected function createSignature($message, $key)
265
    {
266
        return base64_encode(hash_hmac('sha256', $message, $key, true));
267
    }
268
269
    protected function getAction()
270
    {
271
        return '/v2/'.$this->getMerchantId().'/hostedcheckouts';
272
    }
273
274
    /**
275
     * Get integer version (sallest unit) of item price
276
     *
277
     * Copied from {@see AbstractRequest::getAmountInteger()} & {@see AbstractRequest::getMoney()}
278
     */
279
    protected function getItemPriceInteger($item)
280
    {
281
        $currencyCode = $this->getCurrency() ?: 'USD';
282
        $currency = new Currency($currencyCode);
283
        $amount = $item->getPrice();
284
285
        $moneyParser = new DecimalMoneyParser($this->getCurrencies());
286
        $number = Number::fromString($amount);
287
        // Check for rounding that may occur if too many significant decimal digits are supplied.
288
        $decimal_count = strlen($number->getFractionalPart());
289
        $subunit = $this->getCurrencies()->subunitFor($currency);
290
        if ($decimal_count > $subunit) {
291
            throw new InvalidRequestException('Amount precision is too high for currency.');
292
        }
293
        $money = $moneyParser->parse((string) $number, $currency);
294
295
        return (int) $money->getAmount();
296
    }
297
}
298