invoice($invoice); $this->settings = (object) $settings; $this->client = new Client(); $this->oauthToken = $this->oauth(); } /** * @throws PurchaseFailedException */ public function purchase(): string { $phone = $this->invoice->getDetail('phone') ?? $this->invoice->getDetail('cellphone') ?? $this->invoice->getDetail('mobile'); // convert to format +98 901 XXX XXXX $phone = preg_replace('/^0/', '+98', $phone); $data = [ 'amount' => $this->normalizerAmount($this->invoice->getAmount()), 'mobile' => $phone, 'paymentMethodTypeDto' => 'INSTALLMENT', 'transactionId' => $this->invoice->getUuid(), 'returnURL' => $this->settings->callbackUrl, ]; if (!is_null($discountAmount = $this->invoice->getDetail('discountAmount'))) { $data['discountAmount'] = $this->normalizerAmount($discountAmount); } if (!is_null($externalSourceAmount = $this->invoice->getDetail('externalSourceAmount'))) { $data['externalSourceAmount'] = $this->normalizerAmount($externalSourceAmount) ; } if (is_null($this->invoice->getDetail('cartList'))) { throw new PurchaseFailedException('"cartList" is required for this driver'); } $data['cartList'] = $this->invoice->getDetail('cartList'); $this->normalizerCartList($data); $response = $this ->client ->post( $this->settings->apiPaymentUrl.self::TOKEN_URL, [ RequestOptions::BODY => json_encode($data), RequestOptions::HEADERS => [ 'Content-Type' => 'application/json', 'Authorization' => 'Bearer '.$this->oauthToken, ], RequestOptions::HTTP_ERRORS => false, ] ); $body = json_decode($response->getBody()->getContents(), true); if ($response->getStatusCode() != 200 || $body['successful'] === false) { // error has happened $message = $body['errorData']['message'] ?? 'خطا در هنگام درخواست برای پرداخت رخ داده است.'; throw new PurchaseFailedException($message); } $this->invoice->transactionId($body['response']['paymentToken']); $this->setPaymentUrl($body['response']['paymentPageUrl']); // return the transaction's id return $this->invoice->getTransactionId(); } public function pay(): RedirectionForm { parse_str(parse_url($this->getPaymentUrl(), PHP_URL_QUERY), $formData); return $this->redirectWithForm($this->getPaymentUrl(), $formData, 'GET'); } /** * @throws TimeoutException * @throws PurchaseFailedException */ public function verify(): ReceiptInterface { $data = [ 'paymentToken' => $this->invoice->getTransactionId(), ]; try { $response = $this ->client ->post( $this->settings->apiPaymentUrl.self::VERIFY_URL, [ RequestOptions::TIMEOUT => 60, // 1 minute RequestOptions::BODY => json_encode($data), RequestOptions::HEADERS => [ 'Content-Type' => 'application/json', 'Authorization' => 'Bearer '.$this->oauthToken, ], RequestOptions::HTTP_ERRORS => false, ] ); $body = json_decode($response->getBody()->getContents(), true); if ($response->getStatusCode() != 200 || $body['successful'] === false) { // error has happened $message = $body['errorData']['message'] ?? 'خطا در هنگام تایید تراکنش'; throw new PurchaseFailedException($message); } return (new Receipt('snapppay', $body['response']['transactionId']))->detail($body['response']); } catch (ConnectException $exception) { $status_response = $this->status(); if (isset($status_response['status']) && $status_response['status'] == 'VERIFY') { return (new Receipt('snapppay', $status_response['transactionId']))->detail($status_response); } throw new TimeoutException('پاسخی از درگاه دریافت نشد.'); } } /** * @throws PurchaseFailedException */ protected function oauth() { $response = $this ->client ->post( $this->settings->apiPaymentUrl.self::OAUTH_URL, [ RequestOptions::HEADERS => [ 'Authorization' => 'Basic '.base64_encode("{$this->settings->client_id}:{$this->settings->client_secret}"), ], RequestOptions::FORM_PARAMS => [ 'grant_type' => 'password', 'scope' => 'online-merchant', 'username' => $this->settings->username, 'password' => $this->settings->password, ], RequestOptions::HTTP_ERRORS => false, ] ); if ($response->getStatusCode() != 200) { throw new PurchaseFailedException('خطا در هنگام احراز هویت.'); } $body = json_decode($response->getBody()->getContents(), true); return $body['access_token']; } /** * @throws PurchaseFailedException */ public function eligible() { if (is_null($amount = $this->invoice->getAmount())) { throw new PurchaseFailedException('"amount" is required for this method.'); } $response = $this->client->get($this->settings->apiPaymentUrl.self::ELIGIBLE_URL, [ RequestOptions::HEADERS => [ 'Authorization' => 'Bearer '.$this->oauthToken, ], RequestOptions::QUERY => [ 'amount' => $this->normalizerAmount($amount), ], ]); $body = json_decode($response->getBody()->getContents(), true); if ($response->getStatusCode() != 200 || $body['successful'] === false) { $message = $body['errorData']['message'] ?? 'خطا در هنگام درخواست برای پرداخت رخ داده است.'; throw new InvalidPaymentException($message, (int) $response->getStatusCode()); } return $body['response']; } private function normalizerAmount(int $amount): int { return $amount * ($this->settings->currency == 'T' ? 10 : 1); } private function normalizerCartList(array &$data): void { if (isset($data['cartList']['shippingAmount'])) { $data['cartList'] = [$data['cartList']]; } foreach ($data['cartList'] as &$item) { if (isset($item['shippingAmount'])) { $item['shippingAmount'] = $this->normalizerAmount($item['shippingAmount']); } if (isset($item['taxAmount'])) { $item['taxAmount'] = $this->normalizerAmount($item['taxAmount']); } if (isset($item['totalAmount'])) { $item['totalAmount'] = $this->normalizerAmount($item['totalAmount']); } foreach ($item['cartItems'] as &$cartItem) { $cartItem['amount'] = $this->normalizerAmount($cartItem['amount']); } } } public function settle(): array { $data = [ 'paymentToken' => $this->invoice->getTransactionId(), ]; $response = $this ->client ->post( $this->settings->apiPaymentUrl.self::SETTLE_URL, [ RequestOptions::BODY => json_encode($data), RequestOptions::HEADERS => [ 'Content-Type' => 'application/json', 'Authorization' => 'Bearer '.$this->oauthToken, ], RequestOptions::HTTP_ERRORS => false, ] ); $body = json_decode($response->getBody()->getContents(), true); if ($response->getStatusCode() != 200 || $body['successful'] === false) { // error has happened $message = $body['errorData']['message'] ?? 'خطا در Settle تراکنش'; throw new PurchaseFailedException($message); } return $body['response']; } public function revert() { $data = [ 'paymentToken' => $this->invoice->getTransactionId(), ]; $response = $this ->client ->post( $this->settings->apiPaymentUrl.self::REVERT_URL, [ RequestOptions::BODY => json_encode($data), RequestOptions::HEADERS => [ 'Content-Type' => 'application/json', 'Authorization' => 'Bearer '.$this->oauthToken, ], RequestOptions::HTTP_ERRORS => false, ] ); $body = json_decode($response->getBody()->getContents(), true); if ($response->getStatusCode() != 200 || $body['successful'] === false) { // error has happened $message = $body['errorData']['message'] ?? 'خطا در Revert تراکنش'; throw new PurchaseFailedException($message); } return $body['response']; } public function status() { $data = [ 'paymentToken' => $this->invoice->getTransactionId(), ]; $response = $this ->client ->get( $this->settings->apiPaymentUrl.self::STATUS_URL, [ RequestOptions::QUERY => $data, RequestOptions::HEADERS => [ 'Content-Type' => 'application/json', 'Authorization' => 'Bearer '.$this->oauthToken, ], RequestOptions::HTTP_ERRORS => false, ] ); $body = json_decode($response->getBody()->getContents(), true); if ($response->getStatusCode() != 200 || $body['successful'] === false) { // error has happened $message = $body['errorData']['message'] ?? 'خطا در status تراکنش'; throw new PurchaseFailedException($message); } return $body['response']; } public function cancel() { $data = [ 'paymentToken' => $this->invoice->getTransactionId(), ]; $response = $this ->client ->post( $this->settings->apiPaymentUrl.self::CANCEL_URL, [ RequestOptions::BODY => json_encode($data), RequestOptions::HEADERS => [ 'Content-Type' => 'application/json', 'Authorization' => 'Bearer '.$this->oauthToken, ], RequestOptions::HTTP_ERRORS => false, ] ); $body = json_decode($response->getBody()->getContents(), true); if ($response->getStatusCode() != 200 || $body['successful'] === false) { // error has happened $message = $body['errorData']['message'] ?? 'خطا در Cancel تراکنش'; throw new PurchaseFailedException($message); } return $body['response']; } public function update() { $data = [ 'amount' => $this->normalizerAmount($this->invoice->getAmount()), 'paymentMethodTypeDto' => 'INSTALLMENT', 'paymentToken' => $this->invoice->getTransactionId(), ]; if (!is_null($discountAmount = $this->invoice->getDetail('discountAmount'))) { $data['discountAmount'] = $this->normalizerAmount($discountAmount); } if (!is_null($externalSourceAmount = $this->invoice->getDetail('externalSourceAmount'))) { $data['externalSourceAmount'] = $this->normalizerAmount($externalSourceAmount) ; } if (is_null($this->invoice->getDetail('cartList'))) { throw new PurchaseFailedException('"cartList" is required for this driver'); } $data['cartList'] = $this->invoice->getDetail('cartList'); $this->normalizerCartList($data); $response = $this ->client ->post( $this->settings->apiPaymentUrl.self::UPDATE_URL, [ RequestOptions::BODY => json_encode($data), RequestOptions::HEADERS => [ 'Content-Type' => 'application/json', 'Authorization' => 'Bearer '.$this->oauthToken, ], RequestOptions::HTTP_ERRORS => false, ] ); $body = json_decode($response->getBody()->getContents(), true); if ($response->getStatusCode() != 200 || $body['successful'] === false) { // error has happened $message = $body['errorData']['message'] ?? 'خطا در بروزرسانی تراکنش رخ داده است.'; throw new PurchaseFailedException($message); } return $body['response']; } private function getPaymentUrl(): string { return $this->paymentUrl; } private function setPaymentUrl(string $paymentUrl): void { $this->paymentUrl = $paymentUrl; } }