Passed
Push — master ( 4d4957...de4778 )
by mahdi
04:23 queued 01:58
created

Azki::ApiCall()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 25
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 16
c 1
b 0
f 0
nc 2
nop 4
dl 0
loc 25
rs 9.7333
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
     * Trigger an exception
217
     *
218
     * @param $status
219
     *
220
     * @throws PurchaseFailedException
221
     */
222
    protected function purchaseFailed($status)
223
    {
224
        $translations = [
225
            "1"  => "Internal Server Error",
226
            "2"  => "Resource Not Found",
227
            "4"  => "Malformed Data",
228
            "5"  => "Data Not Found",
229
            "15" => "Access Denied",
230
            "16" => "Transaction already reversed",
231
            "17" => "Ticket Expired",
232
            "18" => "Signature Invalid",
233
            "19" => "Ticket unpayable",
234
            "20" => "Ticket customer mismatch",
235
            "21" => "Insufficient Credit",
236
            "28" => "Unverifiable ticket due to status",
237
            "32" => "Invalid Invoice Data",
238
            "33" => "Contract is not started",
239
            "34" => "Contract is expired",
240
            "44" => "Validation exception",
241
            "51" => "Request data is not valid",
242
            "59" => "Transaction not reversible",
243
            "60" => "Transaction must be in verified state",
244
        ];
245
246
        if (array_key_exists($status, $translations)) {
247
            throw new PurchaseFailedException($translations[$status]);
248
        } else {
249
            throw new PurchaseFailedException('خطای ناشناخته ای رخ داده است.');
250
        }
251
    }
252
253
    private function getPaymentStatus()
254
    {
255
        $sub_url = self::subUrls['paymentStatus'];
256
        $url     = $this->settings->apiPaymentUrl . $sub_url;
257
258
        $signature = $this->makeSignature(
259
            $sub_url,
260
            'POST'
261
        );
262
263
        $data = [
264
            "ticket_id" => $this->invoice->getTransactionId(),
265
        ];
266
267
        return $this->ApiCall($data, $signature, $url)['status'];
268
    }
269
270
271
    /**
272
     * Trigger an exception
273
     *
274
     * @param $status
275
     *
276
     * @throws PurchaseFailedException
277
     */
278
    protected function verifyFailed($status)
279
    {
280
        $translations = [
281
            "1" => "Created",
282
            "2" => "Verified",
283
            "3" => "Reversed",
284
            "4" => "Failed",
285
            "5" => "Canceled",
286
            "6" => "Settled",
287
            "7" => "Expired",
288
            "8" => "Done",
289
            "9" => "Settle Queue",
290
        ];
291
292
        if (array_key_exists($status, $translations)) {
293
            throw new PurchaseFailedException("تراکنش در وضعیت " . $translations[$status] . " است.");
294
        } else {
295
            throw new PurchaseFailedException('خطای ناشناخته ای رخ داده است.');
296
        }
297
    }
298
299
    /**
300
     * Generate the payment's receipt
301
     *
302
     * @param $referenceId
303
     *
304
     * @return Receipt
305
     */
306
    private function createReceipt($referenceId): Receipt
307
    {
308
        $receipt = new Receipt('azki', $referenceId);
309
310
        return $receipt;
311
    }
312
313
    private function VerifyTransaction()
314
    {
315
        $sub_url = self::subUrls['verify'];
316
        $url     = $this->settings->apiPaymentUrl . $sub_url;
317
318
        $signature = $this->makeSignature(
319
            $sub_url,
320
            'POST'
321
        );
322
323
        $data = [
324
            "ticket_id" => $this->invoice->getTransactionId(),
325
        ];
326
327
        return $this->ApiCall($data, $signature, $url);
328
    }
329
}
330