shetabit /
multipay
| 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 | $this->convertAmountItems(); |
||||
| 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 (!isset($details['items']) || count($details['items']) == 0) { |
||||
| 81 | throw new PurchaseFailedException('Items is required for this driver'); |
||||
| 82 | } |
||||
| 83 | |||||
| 84 | $merchant_id = $this->settings->merchantId; |
||||
| 85 | $callback = $this->settings->callbackUrl; |
||||
| 86 | $fallback = |
||||
| 87 | $this->settings->fallbackUrl != 'http://yoursite.com/path/to' && $this->settings->fallbackUrl == '' |
||||
| 88 | ? $this->settings->fallbackUrl |
||||
| 89 | : $callback; |
||||
| 90 | $sub_url = self::subUrls['purchase']; |
||||
| 91 | $url = $this->settings->apiPaymentUrl . $sub_url; |
||||
| 92 | |||||
| 93 | $signature = $this->makeSignature( |
||||
| 94 | $sub_url, |
||||
| 95 | 'POST' |
||||
| 96 | ); |
||||
| 97 | |||||
| 98 | $data = [ |
||||
| 99 | "amount" => $this->invoice->getAmount() * ($this->settings->currency == 'T' ? 10 : 1), // convert to rial |
||||
| 100 | "redirect_uri" => $callback, |
||||
| 101 | "fallback_uri" => $fallback, |
||||
| 102 | "provider_id" => $order_id, |
||||
| 103 | "mobile_number" => $details['mobile'] ?? $details['phone'] ?? null, |
||||
| 104 | "merchant_id" => $merchant_id, |
||||
| 105 | "description" => $details['description'] ?? $this->settings->description, |
||||
| 106 | "items" => $details['items'], |
||||
| 107 | ]; |
||||
| 108 | |||||
| 109 | $response = $this->ApiCall($data, $signature, $url); |
||||
| 110 | |||||
| 111 | // set transaction's id |
||||
| 112 | $this->invoice->transactionId($response['ticket_id']); |
||||
| 113 | $this->setPaymentUrl($response['payment_uri']); |
||||
| 114 | |||||
| 115 | // return the transaction's id |
||||
| 116 | return $this->invoice->getTransactionId(); |
||||
| 117 | } |
||||
| 118 | |||||
| 119 | public function pay(): RedirectionForm |
||||
| 120 | { |
||||
| 121 | $url = $this->getPaymentUrl(); |
||||
| 122 | return $this->redirectWithForm( |
||||
| 123 | $url, |
||||
| 124 | [ |
||||
| 125 | 'ticketId' => $this->invoice->getTransactionId(), |
||||
| 126 | ], |
||||
| 127 | 'GET' |
||||
| 128 | ); |
||||
| 129 | } |
||||
| 130 | |||||
| 131 | public function verify(): ReceiptInterface |
||||
| 132 | { |
||||
| 133 | $paymentStatus = $this->getPaymentStatus(); |
||||
| 134 | |||||
| 135 | if ($paymentStatus != self::STATUS_DONE) { |
||||
| 136 | $this->verifyFailed($paymentStatus); |
||||
| 137 | } |
||||
| 138 | |||||
| 139 | $this->VerifyTransaction(); |
||||
| 140 | |||||
| 141 | return $this->createReceipt($this->invoice->getTransactionId()); |
||||
| 142 | } |
||||
| 143 | |||||
| 144 | |||||
| 145 | private function makeSignature($sub_url, $request_method = 'POST') |
||||
| 146 | { |
||||
| 147 | $time = time(); |
||||
| 148 | $key = $this->settings->key; |
||||
| 149 | |||||
| 150 | $plain_signature = "{$sub_url}#{$time}#{$request_method}#{$key}"; |
||||
| 151 | $encrypt_method = "AES-256-CBC"; |
||||
| 152 | $secret_key = hex2bin($key); |
||||
| 153 | $secret_iv = str_repeat(0, 16); |
||||
|
0 ignored issues
–
show
Unused Code
introduced
by
Loading history...
|
|||||
| 154 | |||||
| 155 | $digest = @openssl_encrypt($plain_signature, $encrypt_method, $secret_key, OPENSSL_RAW_DATA); |
||||
| 156 | |||||
| 157 | return bin2hex($digest); |
||||
|
0 ignored issues
–
show
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
Loading history...
|
|||||
| 158 | } |
||||
| 159 | |||||
| 160 | private function convertAmountItems() |
||||
| 161 | { |
||||
| 162 | /** |
||||
| 163 | * example data |
||||
| 164 | * |
||||
| 165 | * $items = [ |
||||
| 166 | * [ |
||||
| 167 | * "name" => "Item 1", |
||||
| 168 | * "count" => "string", |
||||
| 169 | * "amount" => 0, |
||||
| 170 | * "url" => "http://shop.com/items/1", |
||||
| 171 | * ], |
||||
| 172 | * [ |
||||
| 173 | * "name" => "Item 2", |
||||
| 174 | * "count" => 5, |
||||
| 175 | * "amount" => 20000, |
||||
| 176 | * "url" => "http://google.com/items/2", |
||||
| 177 | * ], |
||||
| 178 | * ]; |
||||
| 179 | * |
||||
| 180 | */ |
||||
| 181 | |||||
| 182 | $new_items = array_map( |
||||
| 183 | function ($item) { |
||||
| 184 | $item['amount'] *= ($this->settings->currency == 'T' ? 10 : 1); // convert to rial |
||||
| 185 | return $item; |
||||
| 186 | }, |
||||
| 187 | $this->invoice->getDetails()['items'] ?? [] |
||||
| 188 | ); |
||||
| 189 | |||||
| 190 | $this->invoice->detail('items', $new_items); |
||||
| 191 | return $new_items; |
||||
| 192 | } |
||||
| 193 | |||||
| 194 | /** |
||||
| 195 | * @param array $data |
||||
| 196 | * @param $signature |
||||
| 197 | * @param $url |
||||
| 198 | * @return mixed |
||||
| 199 | */ |
||||
| 200 | public function ApiCall(array $data, $signature, $url, $request_method = 'POST') |
||||
| 201 | { |
||||
| 202 | $response = $this |
||||
| 203 | ->client |
||||
| 204 | ->request( |
||||
| 205 | $request_method, |
||||
| 206 | $url, |
||||
| 207 | [ |
||||
| 208 | "json" => $data, |
||||
| 209 | "headers" => [ |
||||
| 210 | 'Content-Type' => 'application/json', |
||||
| 211 | 'Signature' => $signature, |
||||
| 212 | 'MerchantId' => $this->settings->merchantId, |
||||
| 213 | ], |
||||
| 214 | "http_errors" => false, |
||||
| 215 | ] |
||||
| 216 | ); |
||||
| 217 | |||||
| 218 | $response_array = json_decode($response->getBody()->getContents(), true); |
||||
| 219 | |||||
| 220 | |||||
| 221 | if (($response->getStatusCode() === null or $response->getStatusCode() != 200) || $response_array['rsCode'] != self::SUCCESSFUL) { |
||||
| 222 | $this->purchaseFailed($response_array['rsCode']); |
||||
| 223 | } else { |
||||
| 224 | return $response_array['result']; |
||||
| 225 | } |
||||
| 226 | } |
||||
| 227 | |||||
| 228 | /** |
||||
| 229 | * Trigger an exception |
||||
| 230 | * |
||||
| 231 | * @param $status |
||||
| 232 | * |
||||
| 233 | * @throws PurchaseFailedException |
||||
| 234 | */ |
||||
| 235 | protected function purchaseFailed($status) |
||||
| 236 | { |
||||
| 237 | $translations = [ |
||||
| 238 | "1" => "Internal Server Error", |
||||
| 239 | "2" => "Resource Not Found", |
||||
| 240 | "4" => "Malformed Data", |
||||
| 241 | "5" => "Data Not Found", |
||||
| 242 | "15" => "Access Denied", |
||||
| 243 | "16" => "Transaction already reversed", |
||||
| 244 | "17" => "Ticket Expired", |
||||
| 245 | "18" => "Signature Invalid", |
||||
| 246 | "19" => "Ticket unpayable", |
||||
| 247 | "20" => "Ticket customer mismatch", |
||||
| 248 | "21" => "Insufficient Credit", |
||||
| 249 | "28" => "Unverifiable ticket due to status", |
||||
| 250 | "32" => "Invalid Invoice Data", |
||||
| 251 | "33" => "Contract is not started", |
||||
| 252 | "34" => "Contract is expired", |
||||
| 253 | "44" => "Validation exception", |
||||
| 254 | "51" => "Request data is not valid", |
||||
| 255 | "59" => "Transaction not reversible", |
||||
| 256 | "60" => "Transaction must be in verified state", |
||||
| 257 | ]; |
||||
| 258 | |||||
| 259 | if (array_key_exists($status, $translations)) { |
||||
| 260 | throw new PurchaseFailedException($translations[$status]); |
||||
| 261 | } else { |
||||
| 262 | throw new PurchaseFailedException('خطای ناشناخته ای رخ داده است.'); |
||||
| 263 | } |
||||
| 264 | } |
||||
| 265 | |||||
| 266 | private function getPaymentStatus() |
||||
| 267 | { |
||||
| 268 | $sub_url = self::subUrls['paymentStatus']; |
||||
| 269 | $url = $this->settings->apiPaymentUrl . $sub_url; |
||||
| 270 | |||||
| 271 | $signature = $this->makeSignature( |
||||
| 272 | $sub_url, |
||||
| 273 | 'POST' |
||||
| 274 | ); |
||||
| 275 | |||||
| 276 | $data = [ |
||||
| 277 | "ticket_id" => $this->invoice->getTransactionId(), |
||||
| 278 | ]; |
||||
| 279 | |||||
| 280 | return $this->ApiCall($data, $signature, $url)['status']; |
||||
| 281 | } |
||||
| 282 | |||||
| 283 | |||||
| 284 | /** |
||||
| 285 | * Trigger an exception |
||||
| 286 | * |
||||
| 287 | * @param $status |
||||
| 288 | * |
||||
| 289 | * @throws PurchaseFailedException |
||||
| 290 | */ |
||||
| 291 | protected function verifyFailed($status) |
||||
| 292 | { |
||||
| 293 | $translations = [ |
||||
| 294 | "1" => "Created", |
||||
| 295 | "2" => "Verified", |
||||
| 296 | "3" => "Reversed", |
||||
| 297 | "4" => "Failed", |
||||
| 298 | "5" => "Canceled", |
||||
| 299 | "6" => "Settled", |
||||
| 300 | "7" => "Expired", |
||||
| 301 | "8" => "Done", |
||||
| 302 | "9" => "Settle Queue", |
||||
| 303 | ]; |
||||
| 304 | |||||
| 305 | if (array_key_exists($status, $translations)) { |
||||
| 306 | throw new PurchaseFailedException("تراکنش در وضعیت " . $translations[$status] . " است."); |
||||
| 307 | } else { |
||||
| 308 | throw new PurchaseFailedException('خطای ناشناخته ای رخ داده است.'); |
||||
| 309 | } |
||||
| 310 | } |
||||
| 311 | |||||
| 312 | /** |
||||
| 313 | * Generate the payment's receipt |
||||
| 314 | * |
||||
| 315 | * @param $referenceId |
||||
| 316 | * |
||||
| 317 | * @return Receipt |
||||
| 318 | */ |
||||
| 319 | private function createReceipt($referenceId): Receipt |
||||
| 320 | { |
||||
| 321 | $receipt = new Receipt('azki', $referenceId); |
||||
| 322 | |||||
| 323 | return $receipt; |
||||
| 324 | } |
||||
| 325 | |||||
| 326 | private function VerifyTransaction() |
||||
| 327 | { |
||||
| 328 | $sub_url = self::subUrls['verify']; |
||||
| 329 | $url = $this->settings->apiPaymentUrl . $sub_url; |
||||
| 330 | |||||
| 331 | $signature = $this->makeSignature( |
||||
| 332 | $sub_url, |
||||
| 333 | 'POST' |
||||
| 334 | ); |
||||
| 335 | |||||
| 336 | $data = [ |
||||
| 337 | "ticket_id" => $this->invoice->getTransactionId(), |
||||
| 338 | ]; |
||||
| 339 | |||||
| 340 | return $this->ApiCall($data, $signature, $url); |
||||
| 341 | } |
||||
| 342 | } |
||||
| 343 |