Passed
Pull Request — master (#159)
by
unknown
02:26
created

Azki::getItems()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 23
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 1
Metric Value
cc 1
eloc 1
c 2
b 1
f 1
nc 1
nop 0
dl 0
loc 23
rs 10
1
<?php
2
3
namespace Shetabit\Multipay\Drivers\Azki;
4
5
use GuzzleHttp\Client;
6
use Shetabit\Multipay\Abstracts\Driver;
7
use Shetabit\Multipay\Contracts\ReceiptInterface;
8
use Shetabit\Multipay\Exceptions\PurchaseFailedException;
9
use Shetabit\Multipay\Invoice;
10
use Shetabit\Multipay\Receipt;
11
use Shetabit\Multipay\RedirectionForm;
12
13
class Azki extends Driver
14
{
15
    const STATUS_DONE = 8;
16
17
    const SUCCESSFUL = 0;
18
19
    const subUrls = [
20
        'purchase'      => '/payment/purchase',
21
        'paymentStatus' => '/payment/status',
22
        'verify'        => '/payment/verify',
23
    ];
24
    /**
25
     * Azki Client.
26
     *
27
     * @var object
28
     */
29
    protected $client;
30
31
    /**
32
     * Invoice
33
     *
34
     * @var Invoice
35
     */
36
    protected $invoice;
37
38
    /**
39
     * Driver settings
40
     *
41
     * @var object
42
     */
43
    protected $settings;
44
45
    protected $paymentUrl;
46
47
    /**
48
     * @return string
49
     */
50
    public function getPaymentUrl(): string
51
    {
52
        return $this->paymentUrl;
53
    }
54
55
    /**
56
     * @param mixed $paymentUrl
57
     */
58
    public function setPaymentUrl($paymentUrl): void
59
    {
60
        $this->paymentUrl = $paymentUrl;
61
    }
62
63
64
    public function __construct(Invoice $invoice, $settings)
65
    {
66
        $this->invoice($invoice);
67
        $this->settings = (object)$settings;
68
        $this->client   = new Client();
69
    }
70
71
    public function purchase()
72
    {
73
        $details  = $this->invoice->getDetails();
74
        $order_id = $this->invoice->getUuid();
75
76
        if (empty($details['phone']) && empty($details['mobile'])) {
77
            throw new PurchaseFailedException('Phone number is required');
78
        }
79
        if (count($this->getItems()) == 0) {
80
            throw new PurchaseFailedException('Items is required');
81
        }
82
83
        $merchant_id = $this->settings->merchantId;
84
        $callback    = $this->settings->callbackUrl;
85
        $fallback    = $this->settings->fallbackUrl;
86
        $sub_url     = self::subUrls['purchase'];
87
        $url         = $this->settings->apiPaymentUrl . $sub_url;
88
89
        $signature = $this->makeSignature(
90
            $sub_url,
91
            'POST'
92
        );
93
94
        $data = [
95
            "amount"        => $this->invoice->getAmount() * 10, // convert toman to rial
96
            "redirect_uri"  => $callback,
97
            "fallback_uri"  => $fallback,
98
            "provider_id"   => $order_id,
99
            "mobile_number" => $details['mobile'] ?? $details['phone'] ?? null,
100
            "merchant_id"   => $merchant_id,
101
            "description"   => $details['description'] ?? $this->settings->description,
102
            "items"         => $details['items'],
103
        ];
104
105
        $response = $this->ApiCall($data, $signature, $url);
106
107
        // set transaction's id
108
        $this->invoice->transactionId($response['ticket_id']);
109
        $this->setPaymentUrl($response['payment_uri']);
110
111
        // return the transaction's id
112
        return $this->invoice->getTransactionId();
113
    }
114
115
    public function pay(): RedirectionForm
116
    {
117
        $url = $this->getPaymentUrl();
118
        return $this->redirectWithForm(
119
            $url,
120
            [
121
                'ticketId' => $this->invoice->getTransactionId(),
122
            ],
123
            'GET'
124
        );
125
    }
126
127
    public function verify(): ReceiptInterface
128
    {
129
        $paymentStatus = $this->getPaymentStatus();
130
131
        if ($paymentStatus != self::STATUS_DONE) {
132
            $this->verifyFailed($paymentStatus);
133
        }
134
135
        $this->VerifyTransaction();
136
137
        return $this->createReceipt($this->invoice->getTransactionId());
138
    }
139
140
141
    private function makeSignature($sub_url, $request_method = 'POST')
142
    {
143
        $time = time();
144
        $key  = $this->settings->key;
145
146
        $plain_signature = "{$sub_url}#{$time}#{$request_method}#{$key}";
147
        $encrypt_method  = "AES-256-CBC";
148
        $secret_key      = hex2bin($key);
149
        $secret_iv       = str_repeat(0, 16);
0 ignored issues
show
Unused Code introduced by
The assignment to $secret_iv is dead and can be removed.
Loading history...
150
151
        $digest = @openssl_encrypt($plain_signature, $encrypt_method, $secret_key, OPENSSL_RAW_DATA);
152
153
        return bin2hex($digest);
0 ignored issues
show
Bug introduced by
It seems like $digest can also be of type false; however, parameter $string of bin2hex() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

153
        return bin2hex(/** @scrutinizer ignore-type */ $digest);
Loading history...
154
    }
155
156
    private function getItems()
157
    {
158
        /**
159
         * example data
160
         *
161
         *  $items = [
162
         *      [
163
         *          "name"   => "Item 1",
164
         *          "count"  => "string",
165
         *          "amount" => 0,
166
         *          "url"    => "http://shop.com/items/1",
167
         *      ],
168
         *      [
169
         *          "name"   => "Item 2",
170
         *          "count"  => 5,
171
         *          "amount" => 20000,
172
         *          "url"    => "http://google.com/items/2",
173
         *      ],
174
         *  ];
175
         *
176
         */
177
178
        return $this->invoice->getDetails()['items'];
179
    }
180
181
    /**
182
     * @param array $data
183
     * @param       $signature
184
     * @param       $url
185
     * @return mixed
186
     */
187
    public function ApiCall(array $data, $signature, $url, $request_method = 'POST')
188
    {
189
        $response = $this
190
            ->client
191
            ->request(
192
                $request_method,
193
                $url,
194
                [
195
                    "json"        => $data,
196
                    "headers"     => [
197
                        'Content-Type' => 'application/json',
198
                        'Signature'    => $signature,
199
                        'MerchantId'   => $this->settings->merchantId,
200
                    ],
201
                    "http_errors" => false,
202
                ]
203
            );
204
205
        $response_array = json_decode($response->getBody()->getContents(), true);
206
207
208
        if (($response->getStatusCode() === null or $response->getStatusCode() != 200) || $response_array['rsCode'] != self::SUCCESSFUL) {
209
            $this->purchaseFailed($response_array['rsCode']);
210
        } else {
211
            return $response_array['result'];
212
        }
213
214
215
    }
216
217
    /**
218
     * Trigger an exception
219
     *
220
     * @param $status
221
     *
222
     * @throws PurchaseFailedException
223
     */
224
    protected function purchaseFailed($status)
225
    {
226
        $translations = [
227
            "1"  => "Internal Server Error",
228
            "2"  => "Resource Not Found",
229
            "4"  => "Malformed Data",
230
            "5"  => "Data Not Found",
231
            "15" => "Access Denied",
232
            "16" => "Transaction already reversed",
233
            "17" => "Ticket Expired",
234
            "18" => "Signature Invalid",
235
            "19" => "Ticket unpayable",
236
            "20" => "Ticket customer mismatch",
237
            "21" => "Insufficient Credit",
238
            "28" => "Unverifiable ticket due to status",
239
            "32" => "Invalid Invoice Data",
240
            "33" => "Contract is not started",
241
            "34" => "Contract is expired",
242
            "44" => "Validation exception",
243
            "51" => "Request data is not valid",
244
            "59" => "Transaction not reversible",
245
            "60" => "Transaction must be in verified state",
246
        ];
247
248
        if (array_key_exists($status, $translations)) {
249
            throw new PurchaseFailedException($translations[$status]);
250
        } else {
251
            throw new PurchaseFailedException('خطای ناشناخته ای رخ داده است.');
252
        }
253
    }
254
255
    private function getPaymentStatus()
256
    {
257
        $sub_url = self::subUrls['paymentStatus'];
258
        $url     = $this->settings->apiPaymentUrl . $sub_url;
259
260
        $signature = $this->makeSignature(
261
            $sub_url,
262
            'POST'
263
        );
264
265
        $data = [
266
            "ticket_id" => $this->invoice->getTransactionId(),
267
        ];
268
269
        return $this->ApiCall($data, $signature, $url)['status'];
270
    }
271
272
273
    /**
274
     * Trigger an exception
275
     *
276
     * @param $status
277
     *
278
     * @throws PurchaseFailedException
279
     */
280
    protected function verifyFailed($status)
281
    {
282
        $translations = [
283
            "1" => "Created",
284
            "2" => "Verified",
285
            "3" => "Reversed",
286
            "4" => "Failed",
287
            "5" => "Canceled",
288
            "6" => "Settled",
289
            "7" => "Expired",
290
            "8" => "Done",
291
            "9" => "Settle Queue",
292
        ];
293
294
        if (array_key_exists($status, $translations)) {
295
            throw new PurchaseFailedException("تراکنش در وضعیت " . $translations[$status] . " است.");
296
        } else {
297
            throw new PurchaseFailedException('خطای ناشناخته ای رخ داده است.');
298
        }
299
    }
300
301
    /**
302
     * Generate the payment's receipt
303
     *
304
     * @param $referenceId
305
     *
306
     * @return Receipt
307
     */
308
    private function createReceipt($referenceId): Receipt
309
    {
310
        $receipt = new Receipt('azki', $referenceId);
311
312
        return $receipt;
313
    }
314
315
    private function VerifyTransaction()
316
    {
317
318
        $sub_url = self::subUrls['verify'];
319
        $url     = $this->settings->apiPaymentUrl . $sub_url;
320
321
        $signature = $this->makeSignature(
322
            $sub_url,
323
            'POST'
324
        );
325
326
        $data = [
327
            "ticket_id" => $this->invoice->getTransactionId(),
328
        ];
329
330
        return $this->ApiCall($data, $signature, $url);
331
    }
332
}
333