'/payment/purchase', 'paymentStatus' => '/payment/status', 'verify' => '/payment/verify', ]; /** * Azki Client. * * @var object */ protected $client; /** * Invoice * * @var Invoice */ protected $invoice; /** * Driver settings * * @var object */ protected $settings; protected $paymentUrl; /** * @return string */ public function getPaymentUrl(): string { return $this->paymentUrl; } /** * @param mixed $paymentUrl */ public function setPaymentUrl($paymentUrl): void { $this->paymentUrl = $paymentUrl; } public function __construct(Invoice $invoice, $settings) { $this->invoice($invoice); $this->settings = (object)$settings; $this->client = new Client(); $this->convertAmountItems(); } public function purchase() { $details = $this->invoice->getDetails(); $order_id = $this->invoice->getUuid(); if (empty($details['phone']) && empty($details['mobile'])) { throw new PurchaseFailedException('Phone number is required'); } if (!isset($details['items']) || count($details['items']) == 0) { throw new PurchaseFailedException('Items is required for this driver'); } $merchant_id = $this->settings->merchantId; $callback = $this->settings->callbackUrl; $fallback = $this->settings->fallbackUrl != 'http://yoursite.com/path/to' && $this->settings->fallbackUrl == '' ? $this->settings->fallbackUrl : $callback; $sub_url = self::subUrls['purchase']; $url = $this->settings->apiPaymentUrl . $sub_url; $signature = $this->makeSignature( $sub_url, 'POST' ); $data = [ "amount" => $this->invoice->getAmount() * ($this->settings->currency == 'T' ? 10 : 1), // convert to rial "redirect_uri" => $callback, "fallback_uri" => $fallback, "provider_id" => $order_id, "mobile_number" => $details['mobile'] ?? $details['phone'] ?? null, "merchant_id" => $merchant_id, "description" => $details['description'] ?? $this->settings->description, "items" => $details['items'], ]; $response = $this->ApiCall($data, $signature, $url); // set transaction's id $this->invoice->transactionId($response['ticket_id']); $this->setPaymentUrl($response['payment_uri']); // return the transaction's id return $this->invoice->getTransactionId(); } public function pay(): RedirectionForm { $url = $this->getPaymentUrl(); return $this->redirectWithForm( $url, [ 'ticketId' => $this->invoice->getTransactionId(), ], 'GET' ); } public function verify(): ReceiptInterface { $paymentStatus = $this->getPaymentStatus(); if ($paymentStatus != self::STATUS_DONE) { $this->verifyFailed($paymentStatus); } $this->VerifyTransaction(); return $this->createReceipt($this->invoice->getTransactionId()); } private function makeSignature($sub_url, $request_method = 'POST') { $time = time(); $key = $this->settings->key; $plain_signature = "{$sub_url}#{$time}#{$request_method}#{$key}"; $encrypt_method = "AES-256-CBC"; $secret_key = hex2bin($key); $secret_iv = str_repeat(0, 16); $digest = @openssl_encrypt($plain_signature, $encrypt_method, $secret_key, OPENSSL_RAW_DATA); return bin2hex($digest); } private function convertAmountItems() { /** * example data * * $items = [ * [ * "name" => "Item 1", * "count" => "string", * "amount" => 0, * "url" => "http://shop.com/items/1", * ], * [ * "name" => "Item 2", * "count" => 5, * "amount" => 20000, * "url" => "http://google.com/items/2", * ], * ]; * */ $new_items = array_map( function ($item) { $item['amount'] *= ($this->settings->currency == 'T' ? 10 : 1); // convert to rial return $item; }, $this->invoice->getDetails()['items'] ?? [] ); $this->invoice->detail('items', $new_items); return $new_items; } /** * @param array $data * @param $signature * @param $url * @return mixed */ public function ApiCall(array $data, $signature, $url, $request_method = 'POST') { $response = $this ->client ->request( $request_method, $url, [ "json" => $data, "headers" => [ 'Content-Type' => 'application/json', 'Signature' => $signature, 'MerchantId' => $this->settings->merchantId, ], "http_errors" => false, ] ); $response_array = json_decode($response->getBody()->getContents(), true); if (($response->getStatusCode() === null or $response->getStatusCode() != 200) || $response_array['rsCode'] != self::SUCCESSFUL) { $this->purchaseFailed($response_array['rsCode']); } else { return $response_array['result']; } } /** * Trigger an exception * * @param $status * * @throws PurchaseFailedException */ protected function purchaseFailed($status) { $translations = [ "1" => "Internal Server Error", "2" => "Resource Not Found", "4" => "Malformed Data", "5" => "Data Not Found", "15" => "Access Denied", "16" => "Transaction already reversed", "17" => "Ticket Expired", "18" => "Signature Invalid", "19" => "Ticket unpayable", "20" => "Ticket customer mismatch", "21" => "Insufficient Credit", "28" => "Unverifiable ticket due to status", "32" => "Invalid Invoice Data", "33" => "Contract is not started", "34" => "Contract is expired", "44" => "Validation exception", "51" => "Request data is not valid", "59" => "Transaction not reversible", "60" => "Transaction must be in verified state", ]; if (array_key_exists($status, $translations)) { throw new PurchaseFailedException($translations[$status]); } else { throw new PurchaseFailedException('خطای ناشناخته ای رخ داده است.'); } } private function getPaymentStatus() { $sub_url = self::subUrls['paymentStatus']; $url = $this->settings->apiPaymentUrl . $sub_url; $signature = $this->makeSignature( $sub_url, 'POST' ); $data = [ "ticket_id" => $this->invoice->getTransactionId(), ]; return $this->ApiCall($data, $signature, $url)['status']; } /** * Trigger an exception * * @param $status * * @throws PurchaseFailedException */ protected function verifyFailed($status) { $translations = [ "1" => "Created", "2" => "Verified", "3" => "Reversed", "4" => "Failed", "5" => "Canceled", "6" => "Settled", "7" => "Expired", "8" => "Done", "9" => "Settle Queue", ]; if (array_key_exists($status, $translations)) { throw new PurchaseFailedException("تراکنش در وضعیت " . $translations[$status] . " است."); } else { throw new PurchaseFailedException('خطای ناشناخته ای رخ داده است.'); } } /** * Generate the payment's receipt * * @param $referenceId * * @return Receipt */ private function createReceipt($referenceId): Receipt { $receipt = new Receipt('azki', $referenceId); return $receipt; } private function VerifyTransaction() { $sub_url = self::subUrls['verify']; $url = $this->settings->apiPaymentUrl . $sub_url; $signature = $this->makeSignature( $sub_url, 'POST' ); $data = [ "ticket_id" => $this->invoice->getTransactionId(), ]; return $this->ApiCall($data, $signature, $url); } }