Issues (236)

src/pay/zarinpal.php (5 issues)

1
<?php
2
3
namespace BPT\pay;
4
5
use BPT\pay\zarinpal\refundInterface;
6
use BPT\pay\zarinpal\requestInterface;
7
use BPT\pay\zarinpal\unverifiedInterface;
8
use BPT\pay\zarinpal\verifyInterface;
9
use CurlHandle;
10
use JetBrains\PhpStorm\NoReturn;
11
12
class zarinpal {
13
    public static string $merchant_id = '';
14
    public static bool $sandbox = false;
15
    public static bool $zarin_gate = false;
16
17
    const API_BASE = 'https://api.zarinpal.com/pg/v4/payment/';
18
19
    const SANDBOX_API_BASE = 'https://sandbox.zarinpal.com/pg/v4/payment/';
20
21
    const PAY_BASE = 'https://www.zarinpal.com/pg/StartPay/';
22
23
    const SANDBOX_PAY_BASE = 'https://sandbox.zarinpal.com/pg/StartPay/';
24
25
    private static CurlHandle $session;
26
27
    public static function init (string $merchant_id = '', bool $sandbox = false, bool $zarin_gate = false): void {
28
        self::$sandbox = $sandbox;
29
        self::$zarin_gate = $zarin_gate;
30
        self::$merchant_id = $merchant_id;
31
        self::$session = curl_init();
32
        curl_setopt(self::$session, CURLOPT_RETURNTRANSFER, true);
33
        curl_setopt(self::$session, CURLOPT_SSL_VERIFYPEER, 1);
34
        curl_setopt(self::$session, CURLOPT_SSL_VERIFYHOST, 2);
35
        curl_setopt(self::$session, CURLOPT_HTTPHEADER, [
36
            'Content-Type: application/json',
37
            'Accept: application/json'
38
        ]);
39
        curl_setopt(self::$session, CURLOPT_POST, true);
40
    }
41
42
    private static function getUrl (string $endpoint, bool $pay = false): string {
43
        if ($pay) {
44
            $url = self::$sandbox ? self::SANDBOX_PAY_BASE : self::PAY_BASE;
45
        }
46
        else {
47
            $url = self::$sandbox ? self::SANDBOX_API_BASE : self::API_BASE;
48
        }
49
        $url .= $endpoint;
50
        if (self::$zarin_gate) {
51
            $url .= '/ZarinGate';
52
        }
53
        return $url;
54
    }
55
56
    private static function execute (string $endpoint, array $params = []): object {
57
        foreach ($params as $key => $value) {
58
            if (empty($value)) {
59
                unset($params[$key]);
60
            }
61
        }
62
63
        $session = self::$session;
64
65
        $params['merchant_id'] = self::$merchant_id;
66
67
        if (isset($params['authorization'])) {
68
            curl_setopt(self::$session, CURLOPT_HTTPHEADER, [
69
                'Content-Type: application/json',
70
                'Accept: application/json',
71
                'authorization: Bearer '.$params['authorization']
72
            ]);
73
            unset($params['authorization']);
74
        }
75
76
        curl_setopt($session, CURLOPT_POSTFIELDS, json_encode($params));
77
        curl_setopt($session, CURLOPT_URL, self::getUrl($endpoint));
78
79
        $result = json_decode(curl_exec($session));
0 ignored issues
show
It seems like curl_exec($session) can also be of type true; however, parameter $json of json_decode() 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

79
        $result = json_decode(/** @scrutinizer ignore-type */ curl_exec($session));
Loading history...
80
        if (isset($result->data)) {
81
            return $result->data;
82
        }
83
84
        return $result;
85
    }
86
87
    /**
88
     * @return object|requestInterface
89
     */
90
    public static function request (int $amount, string $description, string $callback_url, array $metadata = [], string $mobile = '', string $email = '', array $wages = [], int $card_pan = null, string $currency = ''): object {
91
        return self::execute('/request.json', [
92
            'amount'       => $amount,
93
            'description'  => $description,
94
            'callback_url' => $callback_url,
95
            'metadata'     => $metadata,
96
            'mobile'       => $mobile,
97
            'email'        => $email,
98
            'wages'        => $wages,
99
            'card_pan'     => $card_pan,
100
            'currency'     => $currency,
101
        ]);
102
    }
103
104
    public static function payURL (string|array $authority): bool|string {
105
        if (is_array($authority)) {
0 ignored issues
show
The condition is_array($authority) is always true.
Loading history...
106
            if (!isset($authority->authority)) {
107
                return false;
108
            }
109
            $authority = $authority->authority;
110
        }
111
        return self::getUrl("/$authority", true);
112
    }
113
114
    /**
115
     * @return object|verifyInterface
116
     */
117
    public static function verify (int $amount, string $authority): object {
118
        return self::execute('/verify.json', [
119
            'amount'    => $amount,
120
            'authority' => $authority
121
        ]);
122
    }
123
124
    /**
125
     * @return object|unverifiedInterface
126
     */
127
    public static function unVerified (): object {
128
        return self::execute('/unVerified.json');
129
    }
130
131
    /**
132
     * @return object|refundInterface
133
     */
134
    public static function refund (string $authorization, string $authority): object {
135
        return self::execute('/refund.json', [
136
            'authorization' => $authorization,
137
            'authority'     => $authority
138
        ]);
139
    }
140
141
    #[NoReturn]
142
    public static function redirect (string $url): void {
143
        @header('Location: ' . $url);
0 ignored issues
show
Are you sure the usage of header('Location: ' . $url) is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
Security Best Practice introduced by
It seems like you do not handle an error condition for header(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

143
        /** @scrutinizer ignore-unhandled */ @header('Location: ' . $url);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
144
        die("<meta http-equiv='refresh' content='0; url=$url' /><script>window.location.href = '$url';</script>");
0 ignored issues
show
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
145
    }
146
147
    public static function processCallback (int $amount): object|bool|int {
148
        if (!isset($_GET['Authority']) || !isset($_GET['Status'])) {
149
            return false;
150
        }
151
152
        if ($_GET['status'] != 'OK') {
153
            return false;
154
        }
155
156
        $detail = self::verify($amount, $_GET['Authority']);
157
158
        if (isset($detail->code) && $detail->code != 100) {
159
            return $detail->code;
160
        }
161
162
        return $detail;
163
    }
164
}
165