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

Azki::verifyFailed()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 19
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 2
eloc 14
c 1
b 0
f 1
nc 2
nop 1
dl 0
loc 19
rs 9.7998
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
16
    const STATUS_DONE = 8;
17
18
    const SUCCESSFUL = 0;
19
20
    const subUrls = [
21
        'purchase'      => '/payment/purchase',
22
        'paymentStatus' => '/payment/status',
23
        'verify'        => '/payment/verify',
24
    ];
25
    /**
26
     * Azki Client.
27
     *
28
     * @var object
29
     */
30
    protected $client;
31
32
    /**
33
     * Invoice
34
     *
35
     * @var Invoice
36
     */
37
    protected $invoice;
38
39
    /**
40
     * Driver settings
41
     *
42
     * @var object
43
     */
44
    protected $settings;
45
46
    protected $paymentUrl;
47
48
    /**
49
     * @return string
50
     */
51
    public function getPaymentUrl(): string
52
    {
53
        return $this->paymentUrl;
54
    }
55
56
    /**
57
     * @param mixed $paymentUrl
58
     */
59
    public function setPaymentUrl($paymentUrl): void
60
    {
61
        $this->paymentUrl = $paymentUrl;
62
    }
63
64
65
    public function __construct(Invoice $invoice, $settings)
66
    {
67
        $this->invoice($invoice);
68
        $this->settings = (object)$settings;
69
        $this->client   = new Client();
70
    }
71
72
    public function purchase()
73
    {
74
        $details  = $this->invoice->getDetails();
75
        $order_id = $this->invoice->getUuid();
76
77
        if (empty($details['phone']) && empty($details['mobile'])) {
78
            throw new PurchaseFailedException('Phone number is required');
79
        }
80
        if (count($this->getItems()) == 0) {
81
            throw new PurchaseFailedException('Items is required');
82
        }
83
84
        $merchant_id = $this->settings->merchantId;
85
        $callback    = $this->settings->callbackUrl;
86
        $fallback    = $this->settings->fallbackUrl;
87
        $sub_url     = self::subUrls['purchase'];
88
        $url         = $this->settings->apiPaymentUrl . $sub_url;
89
90
        $signature = $this->makeSignature(
91
            $sub_url,
92
            'POST');
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
    public function verify(): ReceiptInterface
127
    {
128
        $paymentStatus = $this->getPaymentStatus();
129
130
        if ($paymentStatus != self::STATUS_DONE) {
131
            $this->verifyFailed($paymentStatus);
132
        }
133
134
        $this->VerifyTransaction();
135
136
        return $this->createReceipt($this->invoice->getTransactionId());
137
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
157
    private function getItems()
158
    {
159
        /**
160
         * example data
161
         *
162
         *  $items = [
163
         *      [
164
         *          "name"   => "Item 1",
165
         *          "count"  => "string",
166
         *          "amount" => 0,
167
         *          "url"    => "http://shop.com/items/1",
168
         *      ],
169
         *      [
170
         *          "name"   => "Item 2",
171
         *          "count"  => 5,
172
         *          "amount" => 20000,
173
         *          "url"    => "http://google.com/items/2",
174
         *      ],
175
         *  ];
176
         *
177
         */
178
179
        return $this->invoice->getDetails()['items'];
180
181
    }
182
183
    /**
184
     * @param array $data
185
     * @param       $signature
186
     * @param       $url
187
     * @return mixed
188
     */
189
    public function ApiCall(array $data, $signature, $url, $request_method = 'POST')
190
    {
191
        $response = $this
192
            ->client
193
            ->request(
194
                $request_method,
195
                $url,
196
                [
197
                    "json"        => $data,
198
                    "headers"     => [
199
                        'Content-Type' => 'application/json',
200
                        'Signature'    => $signature,
201
                        'MerchantId'   => $this->settings->merchantId,
202
                    ],
203
                    "http_errors" => FALSE,
204
                ]
205
            );
206
207
        $response_array = json_decode($response->getBody()->getContents(), TRUE);
208
209
210
        if (($response->getStatusCode() === NULL or $response->getStatusCode() != 200) || $response_array['rsCode'] != self::SUCCESSFUL) {
211
            $this->purchaseFailed($response_array['rsCode']);
212
        }
213
        else {
214
            return $response_array['result'];
215
        }
216
217
218
    }
219
220
    /**
221
     * Trigger an exception
222
     *
223
     * @param $status
224
     *
225
     * @throws PurchaseFailedException
226
     */
227
    protected function purchaseFailed($status)
228
    {
229
        $translations = [
230
            "1"  => "Internal Server Error",
231
            "2"  => "Resource Not Found",
232
            "4"  => "Malformed Data",
233
            "5"  => "Data Not Found",
234
            "15" => "Access Denied",
235
            "16" => "Transaction already reversed",
236
            "17" => "Ticket Expired",
237
            "18" => "Signature Invalid",
238
            "19" => "Ticket unpayable",
239
            "20" => "Ticket customer mismatch",
240
            "21" => "Insufficient Credit",
241
            "28" => "Unverifiable ticket due to status",
242
            "32" => "Invalid Invoice Data",
243
            "33" => "Contract is not started",
244
            "34" => "Contract is expired",
245
            "44" => "Validation exception",
246
            "51" => "Request data is not valid",
247
            "59" => "Transaction not reversible",
248
            "60" => "Transaction must be in verified state",
249
        ];
250
251
        if (array_key_exists($status, $translations)) {
252
            throw new PurchaseFailedException($translations[$status]);
253
        }
254
        else {
255
            throw new PurchaseFailedException('خطای ناشناخته ای رخ داده است.');
256
        }
257
    }
258
259
    private function getPaymentStatus()
260
    {
261
        $sub_url = self::subUrls['paymentStatus'];
262
        $url     = $this->settings->apiPaymentUrl . $sub_url;
263
264
        $signature = $this->makeSignature(
265
            $sub_url,
266
            'POST');
267
268
        $data = [
269
            "ticket_id" => $this->invoice->getTransactionId(),
270
        ];
271
272
        return $this->ApiCall($data, $signature, $url)['status'];
273
    }
274
275
276
    /**
277
     * Trigger an exception
278
     *
279
     * @param $status
280
     *
281
     * @throws PurchaseFailedException
282
     */
283
    protected function verifyFailed($status)
284
    {
285
        $translations = [
286
            "1" => "Created",
287
            "2" => "Verified",
288
            "3" => "Reversed",
289
            "4" => "Failed",
290
            "5" => "Canceled",
291
            "6" => "Settled",
292
            "7" => "Expired",
293
            "8" => "Done",
294
            "9" => "Settle Queue",
295
        ];
296
297
        if (array_key_exists($status, $translations)) {
298
            throw new PurchaseFailedException("تراکنش در وضعیت " . $translations[$status] . " است.");
299
        }
300
        else {
301
            throw new PurchaseFailedException('خطای ناشناخته ای رخ داده است.');
302
        }
303
    }
304
305
    /**
306
     * Generate the payment's receipt
307
     *
308
     * @param $referenceId
309
     *
310
     * @return Receipt
311
     */
312
    private function createReceipt($referenceId): Receipt
313
    {
314
        $receipt = new Receipt('azki', $referenceId);
315
316
        return $receipt;
317
    }
318
319
    private function VerifyTransaction()
320
    {
321
322
        $sub_url = self::subUrls['verify'];
323
        $url     = $this->settings->apiPaymentUrl . $sub_url;
324
325
        $signature = $this->makeSignature(
326
            $sub_url,
327
            'POST');
328
329
        $data = [
330
            "ticket_id" => $this->invoice->getTransactionId(),
331
        ];
332
333
        return $this->ApiCall($data, $signature, $url);
334
    }
335
}
336