From abe765b7357b1f4b4c454eb1fd0cd862ceca9be6 Mon Sep 17 00:00:00 2001 From: eafshary Date: Wed, 16 Jun 2021 14:34:39 +0430 Subject: [PATCH 01/21] fix verify api change in sadad (#72) (#73) --- src/Drivers/Sadad/Sadad.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Drivers/Sadad/Sadad.php b/src/Drivers/Sadad/Sadad.php index 40e907e..0c07a20 100644 --- a/src/Drivers/Sadad/Sadad.php +++ b/src/Drivers/Sadad/Sadad.php @@ -173,7 +173,7 @@ public function verify() : ReceiptInterface $receipt->detail([ 'orderId' => $body->OrderId, 'traceNo' => $body->SystemTraceNo, - 'referenceNo' => $body->RetrievalRefNo, + 'referenceNo' => $body->RetrivalRefNo, 'description' => $body->Description, ]); From 8fb633ffba2561ebbbc1396f1291926f617eff68 Mon Sep 17 00:00:00 2001 From: mohammad hossein Honarkar <22825860+programmerboy78@users.noreply.github.com> Date: Sat, 19 Jun 2021 12:36:38 +0430 Subject: [PATCH 02/21] fix number_format parameter count (#76) fix #75 Co-authored-by: Honarkar --- src/Drivers/Local/Local.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Drivers/Local/Local.php b/src/Drivers/Local/Local.php index 6a9bea5..bf99e75 100644 --- a/src/Drivers/Local/Local.php +++ b/src/Drivers/Local/Local.php @@ -126,7 +126,7 @@ protected function getFormData(): array { return [ 'orderId' => $this->invoice->getDetail('orderId'), - 'price' => number_format($this->invoice->getAmount(), 0, ','), + 'price' => number_format($this->invoice->getAmount()), 'successUrl' => $this->addUrlQuery($this->settings->callbackUrl, [ 'transactionId' => $this->invoice->getTransactionId(), ]), From 6d379fad1b2d3dcca9420997c0610b9e95874706 Mon Sep 17 00:00:00 2001 From: Mahdi Khanzadi Date: Fri, 25 Jun 2021 23:54:28 +0430 Subject: [PATCH 03/21] implement zarinpal API v4 Apply fixes from StyleCI (#79) Co-authored-by: mahdikhanzadi fix bugs and clean code --- config/payment.php | 4 +- src/Drivers/Zarinpal/Strategies/Normal.php | 215 +++++++++++++++++ src/Drivers/Zarinpal/Strategies/Sandbox.php | 213 +++++++++++++++++ src/Drivers/Zarinpal/Strategies/Zaringate.php | 213 +++++++++++++++++ src/Drivers/Zarinpal/Zarinpal.php | 218 ++++-------------- 5 files changed, 686 insertions(+), 177 deletions(-) create mode 100644 src/Drivers/Zarinpal/Strategies/Normal.php create mode 100644 src/Drivers/Zarinpal/Strategies/Sandbox.php create mode 100644 src/Drivers/Zarinpal/Strategies/Zaringate.php diff --git a/config/payment.php b/config/payment.php index f57ff99..48261ac 100755 --- a/config/payment.php +++ b/config/payment.php @@ -188,9 +188,9 @@ ], 'zarinpal' => [ /* normal api */ - 'apiPurchaseUrl' => 'https://ir.zarinpal.com/pg/services/WebGate/wsdl', + 'apiPurchaseUrl' => 'https://api.zarinpal.com/pg/v4/payment/request.json', 'apiPaymentUrl' => 'https://www.zarinpal.com/pg/StartPay/', - 'apiVerificationUrl' => 'https://ir.zarinpal.com/pg/services/WebGate/wsdl', + 'apiVerificationUrl' => 'https://api.zarinpal.com/pg/v4/payment/verify.json', /* sandbox api */ 'sandboxApiPurchaseUrl' => 'https://sandbox.zarinpal.com/pg/services/WebGate/wsdl', diff --git a/src/Drivers/Zarinpal/Strategies/Normal.php b/src/Drivers/Zarinpal/Strategies/Normal.php new file mode 100644 index 0000000..b0ec76e --- /dev/null +++ b/src/Drivers/Zarinpal/Strategies/Normal.php @@ -0,0 +1,215 @@ +invoice($invoice); + $this->settings = (object) $settings; + $this->client = new Client(); + } + + /** + * Purchase Invoice. + * + * @return string + * + * @throws PurchaseFailedException + * @throws \SoapFault + */ + public function purchase() + { + $metadata = []; + + if (!empty($this->invoice->getDetails()['description'])) { + $description = $this->invoice->getDetails()['description']; + } else { + $description = $this->settings->description; + } + + if (!empty($this->invoice->getDetails()['mobile'])) { + $metadata['mobile'] = $this->invoice->getDetails()['mobile']; + } + + if (!empty($this->invoice->getDetails()['email'])) { + $metadata['email'] = $this->invoice->getDetails()['email']; + } + + $data = [ + "merchant_id" => $this->settings->merchantId, + "amount" => $this->invoice->getAmount(), + "callback_url" => $this->settings->callbackUrl, + "description" => $description, + "metadata" => array_merge($this->invoice->getDetails() ?? [], $metadata), + ]; + + + $response = $this + ->client + ->request( + 'POST', + $this->settings->apiPurchaseUrl, + [ + "json" => $data, + "headers" => [ + 'Content-Type' => 'application/json', + ], + "http_errors" => false, + ] + ); + + $result = json_decode($response->getBody()->getContents(), true); + + // some error has happened + if (! empty($result['errors']) || empty($result['data']) || $result['data']['code'] != 100) { + throw new PurchaseFailedException($result['errors']['message'], $result['errors']['code']); + } + + $this->invoice->transactionId($result['data']["authority"]); + + // return the transaction's id + return $this->invoice->getTransactionId(); + } + + /** + * Pay the Invoice + * + * @return RedirectionForm + */ + public function pay() : RedirectionForm + { + $transactionId = $this->invoice->getTransactionId(); + $paymentUrl = $this->getPaymentUrl(); + + $payUrl = $paymentUrl.$transactionId; + + return $this->redirectWithForm($payUrl, [], 'GET'); + } + + /** + * Verify payment + * + * @return ReceiptInterface + * + * @throws InvalidPaymentException + */ + public function verify() : ReceiptInterface + { + $status = Request::input('Status'); + if ($status != 'OK') { + throw new InvalidPaymentException('عملیات پرداخت توسط کاربر لغو شد.', -54); + } + + $authority = $this->invoice->getTransactionId() ?? Request::input('Authority'); + $data = [ + "merchant_id" => $this->settings->merchantId, + "authority" => $authority, + "amount" => $this->invoice->getAmount(), + ]; + + $response = $this->client->request( + 'POST', + $this->getVerificationUrl(), + [ + 'json' => $data, + "headers" => [ + 'Content-Type' => 'application/json', + ], + "http_errors" => false, + ] + ); + + $result = json_decode($response->getBody()->getContents(), true); + + if (empty($result['data']) || ! isset($result['data']['ref_id']) || $result['data']['code'] != 100) { + $message = $result['errors']['message']; + $code = $result['errors']['code']; + + throw new InvalidPaymentException($message, $code); + } + + return $this->createReceipt($result['data']['ref_id']); + } + + /** + * Generate the payment's receipt + * + * @param $referenceId + * + * @return Receipt + */ + public function createReceipt($referenceId) + { + return new Receipt('zarinpal', $referenceId); + } + + /** + * Retrieve purchase url + * + * @return string + */ + protected function getPurchaseUrl() : string + { + return $this->settings->apiPurchaseUrl; + } + + /** + * Retrieve Payment url + * + * @return string + */ + protected function getPaymentUrl() : string + { + return $this->settings->apiPaymentUrl; + } + + /** + * Retrieve verification url + * + * @return string + */ + protected function getVerificationUrl() : string + { + return $this->settings->apiVerificationUrl; + } +} diff --git a/src/Drivers/Zarinpal/Strategies/Sandbox.php b/src/Drivers/Zarinpal/Strategies/Sandbox.php new file mode 100644 index 0000000..669bd48 --- /dev/null +++ b/src/Drivers/Zarinpal/Strategies/Sandbox.php @@ -0,0 +1,213 @@ +invoice($invoice); + $this->settings = (object) $settings; + } + + /** + * Purchase Invoice. + * + * @return string + * + * @throws PurchaseFailedException + * @throws \SoapFault + */ + public function purchase() + { + if (!empty($this->invoice->getDetails()['description'])) { + $description = $this->invoice->getDetails()['description']; + } else { + $description = $this->settings->description; + } + + if (!empty($this->invoice->getDetails()['mobile'])) { + $mobile = $this->invoice->getDetails()['mobile']; + } + + if (!empty($this->invoice->getDetails()['email'])) { + $email = $this->invoice->getDetails()['email']; + } + + $data = array( + 'MerchantID' => $this->settings->merchantId, + 'Amount' => $this->invoice->getAmount(), + 'CallbackURL' => $this->settings->callbackUrl, + 'Description' => $description, + 'Mobile' => $mobile ?? '', + 'Email' => $email ?? '', + 'AdditionalData' => $this->invoice->getDetails() + ); + + $client = new \SoapClient($this->getPurchaseUrl(), ['encoding' => 'UTF-8']); + $result = $client->PaymentRequest($data); + + if ($result->Status != 100 || empty($result->Authority)) { + // some error has happened + $message = $this->translateStatus($result->Status); + throw new PurchaseFailedException($message, $result->Status); + } + + $this->invoice->transactionId($result->Authority); + + // return the transaction's id + return $this->invoice->getTransactionId(); + } + + /** + * Pay the Invoice + * + * @return RedirectionForm + */ + public function pay() : RedirectionForm + { + $transactionId = $this->invoice->getTransactionId(); + $paymentUrl = $this->getPaymentUrl(); + + $payUrl = $paymentUrl.$transactionId; + + return $this->redirectWithForm($payUrl, [], 'GET'); + } + + /** + * Verify payment + * + * @return ReceiptInterface + * + * @throws InvalidPaymentException + * @throws \SoapFault + */ + public function verify() : ReceiptInterface + { + $status = Request::input('Status'); + if ($status != 'OK') { + throw new InvalidPaymentException('عملیات پرداخت توسط کاربر لغو شد.', -22); + } + + $authority = $this->invoice->getTransactionId() ?? Request::input('Authority'); + $data = [ + 'MerchantID' => $this->settings->merchantId, + 'Authority' => $authority, + 'Amount' => $this->invoice->getAmount(), + ]; + + $client = new \SoapClient($this->getVerificationUrl(), ['encoding' => 'UTF-8']); + $result = $client->PaymentVerification($data); + + if ($result->Status != 100) { + $message = $this->translateStatus($result->Status); + throw new InvalidPaymentException($message, $result->Status); + } + + return $this->createReceipt($result->RefID); + } + + /** + * Generate the payment's receipt + * + * @param $referenceId + * + * @return Receipt + */ + public function createReceipt($referenceId) + { + return new Receipt('zarinpal', $referenceId); + } + + /** + * Convert status to a readable message. + * + * @param $status + * + * @return mixed|string + */ + private function translateStatus($status) + { + $translations = array( + "-1" => "اطلاعات ارسال شده ناقص است.", + "-2" => "IP و يا مرچنت كد پذيرنده صحيح نيست", + "-3" => "با توجه به محدوديت هاي شاپرك امكان پرداخت با رقم درخواست شده ميسر نمي باشد", + "-4" => "سطح تاييد پذيرنده پايين تر از سطح نقره اي است.", + "-11" => "درخواست مورد نظر يافت نشد.", + "-12" => "امكان ويرايش درخواست ميسر نمي باشد.", + "-21" => "هيچ نوع عمليات مالي براي اين تراكنش يافت نشد", + "-22" => "تراكنش نا موفق ميباشد", + "-33" => "رقم تراكنش با رقم پرداخت شده مطابقت ندارد", + "-34" => "سقف تقسيم تراكنش از لحاظ تعداد يا رقم عبور نموده است", + "-40" => "اجازه دسترسي به متد مربوطه وجود ندارد.", + "-41" => "اطلاعات ارسال شده مربوط به AdditionalData غيرمعتبر ميباشد.", + "-42" => "مدت زمان معتبر طول عمر شناسه پرداخت بايد بين 30 دقيه تا 45 روز مي باشد.", + "-54" => "درخواست مورد نظر آرشيو شده است", + "101" => "عمليات پرداخت موفق بوده و قبلا PaymentVerification تراكنش انجام شده است.", + ); + + $unknownError = 'خطای ناشناخته رخ داده است.'; + + return array_key_exists($status, $translations) ? $translations[$status] : $unknownError; + } + + /** + * Retrieve purchase url + * + * @return string + */ + protected function getPurchaseUrl() : string + { + return $this->settings->sandboxApiPurchaseUrl; + } + + /** + * Retrieve Payment url + * + * @return string + */ + protected function getPaymentUrl() : string + { + return $this->settings->sandboxApiPaymentUrl; + } + + /** + * Retrieve verification url + * + * @return string + */ + protected function getVerificationUrl() : string + { + return $this->settings->sandboxApiVerificationUrl; + } +} diff --git a/src/Drivers/Zarinpal/Strategies/Zaringate.php b/src/Drivers/Zarinpal/Strategies/Zaringate.php new file mode 100644 index 0000000..8ad2a46 --- /dev/null +++ b/src/Drivers/Zarinpal/Strategies/Zaringate.php @@ -0,0 +1,213 @@ +invoice($invoice); + $this->settings = (object) $settings; + } + + /** + * Purchase Invoice. + * + * @return string + * + * @throws PurchaseFailedException + * @throws \SoapFault + */ + public function purchase() + { + if (!empty($this->invoice->getDetails()['description'])) { + $description = $this->invoice->getDetails()['description']; + } else { + $description = $this->settings->description; + } + + if (!empty($this->invoice->getDetails()['mobile'])) { + $mobile = $this->invoice->getDetails()['mobile']; + } + + if (!empty($this->invoice->getDetails()['email'])) { + $email = $this->invoice->getDetails()['email']; + } + + $data = array( + 'MerchantID' => $this->settings->merchantId, + 'Amount' => $this->invoice->getAmount(), + 'CallbackURL' => $this->settings->callbackUrl, + 'Description' => $description, + 'Mobile' => $mobile ?? '', + 'Email' => $email ?? '', + 'AdditionalData' => $this->invoice->getDetails() + ); + + $client = new \SoapClient($this->getPurchaseUrl(), ['encoding' => 'UTF-8']); + $result = $client->PaymentRequest($data); + + if ($result->Status != 100 || empty($result->Authority)) { + // some error has happened + $message = $this->translateStatus($result->Status); + throw new PurchaseFailedException($message, $result->Status); + } + + $this->invoice->transactionId($result->Authority); + + // return the transaction's id + return $this->invoice->getTransactionId(); + } + + /** + * Pay the Invoice + * + * @return RedirectionForm + */ + public function pay() : RedirectionForm + { + $transactionId = $this->invoice->getTransactionId(); + $paymentUrl = $this->getPaymentUrl(); + + $payUrl = str_replace(':authority', $transactionId, $paymentUrl); + + return $this->redirectWithForm($payUrl, [], 'GET'); + } + + /** + * Verify payment + * + * @return ReceiptInterface + * + * @throws InvalidPaymentException + * @throws \SoapFault + */ + public function verify() : ReceiptInterface + { + $status = Request::input('Status'); + if ($status != 'OK') { + throw new InvalidPaymentException('عملیات پرداخت توسط کاربر لغو شد.', -22); + } + + $authority = $this->invoice->getTransactionId() ?? Request::input('Authority'); + $data = [ + 'MerchantID' => $this->settings->merchantId, + 'Authority' => $authority, + 'Amount' => $this->invoice->getAmount(), + ]; + + $client = new \SoapClient($this->getVerificationUrl(), ['encoding' => 'UTF-8']); + $result = $client->PaymentVerification($data); + + if ($result->Status != 100) { + $message = $this->translateStatus($result->Status); + throw new InvalidPaymentException($message, $result->Status); + } + + return $this->createReceipt($result->RefID); + } + + /** + * Generate the payment's receipt + * + * @param $referenceId + * + * @return Receipt + */ + public function createReceipt($referenceId) + { + return new Receipt('zarinpal', $referenceId); + } + + /** + * Convert status to a readable message. + * + * @param $status + * + * @return mixed|string + */ + private function translateStatus($status) + { + $translations = array( + "-1" => "اطلاعات ارسال شده ناقص است.", + "-2" => "IP و يا مرچنت كد پذيرنده صحيح نيست", + "-3" => "با توجه به محدوديت هاي شاپرك امكان پرداخت با رقم درخواست شده ميسر نمي باشد", + "-4" => "سطح تاييد پذيرنده پايين تر از سطح نقره اي است.", + "-11" => "درخواست مورد نظر يافت نشد.", + "-12" => "امكان ويرايش درخواست ميسر نمي باشد.", + "-21" => "هيچ نوع عمليات مالي براي اين تراكنش يافت نشد", + "-22" => "تراكنش نا موفق ميباشد", + "-33" => "رقم تراكنش با رقم پرداخت شده مطابقت ندارد", + "-34" => "سقف تقسيم تراكنش از لحاظ تعداد يا رقم عبور نموده است", + "-40" => "اجازه دسترسي به متد مربوطه وجود ندارد.", + "-41" => "اطلاعات ارسال شده مربوط به AdditionalData غيرمعتبر ميباشد.", + "-42" => "مدت زمان معتبر طول عمر شناسه پرداخت بايد بين 30 دقيه تا 45 روز مي باشد.", + "-54" => "درخواست مورد نظر آرشيو شده است", + "101" => "عمليات پرداخت موفق بوده و قبلا PaymentVerification تراكنش انجام شده است.", + ); + + $unknownError = 'خطای ناشناخته رخ داده است.'; + + return array_key_exists($status, $translations) ? $translations[$status] : $unknownError; + } + + /** + * Retrieve purchase url + * + * @return string + */ + protected function getPurchaseUrl() : string + { + return $this->settings->zaringateApiPurchaseUrl; + } + + /** + * Retrieve Payment url + * + * @return string + */ + protected function getPaymentUrl() : string + { + return $this->settings->zaringateApiPaymentUrl; + } + + /** + * Retrieve verification url + * + * @return string + */ + protected function getVerificationUrl() : string + { + return $this->settings->zaringateApiVerificationUrl; + } +} diff --git a/src/Drivers/Zarinpal/Zarinpal.php b/src/Drivers/Zarinpal/Zarinpal.php index 9746395..9c9c1ba 100644 --- a/src/Drivers/Zarinpal/Zarinpal.php +++ b/src/Drivers/Zarinpal/Zarinpal.php @@ -3,16 +3,37 @@ namespace Shetabit\Multipay\Drivers\Zarinpal; use Shetabit\Multipay\Abstracts\Driver; +use Shetabit\Multipay\Contracts\DriverInterface; use Shetabit\Multipay\Exceptions\InvalidPaymentException; use Shetabit\Multipay\Exceptions\PurchaseFailedException; use Shetabit\Multipay\Contracts\ReceiptInterface; +use Shetabit\Multipay\Drivers\Zarinpal\Strategies\Normal; +use Shetabit\Multipay\Drivers\Zarinpal\Strategies\Sandbox; +use Shetabit\Multipay\Drivers\Zarinpal\Strategies\Zaringate; +use Shetabit\Multipay\Exceptions\DriverNotFoundException; use Shetabit\Multipay\Invoice; -use Shetabit\Multipay\Receipt; use Shetabit\Multipay\RedirectionForm; -use Shetabit\Multipay\Request; class Zarinpal extends Driver { + /** + * Strategies map. + * + * @var array + */ + public static $strategies = [ + 'normal' => Normal::class, + 'sandbox' => Sandbox::class, + 'zaringate' => Zaringate::class, + ]; + + /** + * Current strategy instance. + * + * @var DriverInterface $strategy + */ + protected $strategy; + /** * Invoice * @@ -36,8 +57,9 @@ class Zarinpal extends Driver */ public function __construct(Invoice $invoice, $settings) { - $this->invoice($invoice); + $this->invoice = $invoice; $this->settings = (object) $settings; + $this->strategy = $this->getFreshStrategyInstance($this->invoice, $this->settings); } /** @@ -50,43 +72,7 @@ public function __construct(Invoice $invoice, $settings) */ public function purchase() { - if (!empty($this->invoice->getDetails()['description'])) { - $description = $this->invoice->getDetails()['description']; - } else { - $description = $this->settings->description; - } - - if (!empty($this->invoice->getDetails()['mobile'])) { - $mobile = $this->invoice->getDetails()['mobile']; - } - - if (!empty($this->invoice->getDetails()['email'])) { - $email = $this->invoice->getDetails()['email']; - } - - $data = array( - 'MerchantID' => $this->settings->merchantId, - 'Amount' => $this->invoice->getAmount(), - 'CallbackURL' => $this->settings->callbackUrl, - 'Description' => $description, - 'Mobile' => $mobile ?? '', - 'Email' => $email ?? '', - 'AdditionalData' => $this->invoice->getDetails() - ); - - $client = new \SoapClient($this->getPurchaseUrl(), ['encoding' => 'UTF-8']); - $result = $client->PaymentRequest($data); - - if ($result->Status != 100 || empty($result->Authority)) { - // some error has happened - $message = $this->translateStatus($result->Status); - throw new PurchaseFailedException($message, $result->Status); - } - - $this->invoice->transactionId($result->Authority); - - // return the transaction's id - return $this->invoice->getTransactionId(); + return $this->strategy->purchase(); } /** @@ -96,16 +82,7 @@ public function purchase() */ public function pay() : RedirectionForm { - $transactionId = $this->invoice->getTransactionId(); - $paymentUrl = $this->getPaymentUrl(); - - if (strtolower($this->getMode()) == 'zaringate') { - $payUrl = str_replace(':authority', $transactionId, $paymentUrl); - } else { - $payUrl = $paymentUrl.$transactionId; - } - - return $this->redirectWithForm($payUrl, [], 'GET'); + return $this->strategy->pay(); } /** @@ -118,144 +95,35 @@ public function pay() : RedirectionForm */ public function verify() : ReceiptInterface { - $authority = $this->invoice->getTransactionId() ?? Request::input('Authority'); - $status = Request::input('Status'); - - $data = [ - 'MerchantID' => $this->settings->merchantId, - 'Authority' => $authority, - 'Amount' => $this->invoice->getAmount(), - ]; - - if ($status != 'OK') { - throw new InvalidPaymentException('عملیات پرداخت توسط کاربر لغو شد.', -22); - } - - $client = new \SoapClient($this->getVerificationUrl(), ['encoding' => 'UTF-8']); - $result = $client->PaymentVerification($data); - - if ($result->Status != 100) { - $message = $this->translateStatus($result->Status); - throw new InvalidPaymentException($message, $result->Status); - } - - return $this->createReceipt($result->RefID); + return $this->strategy->verify(); } /** - * Generate the payment's receipt + * Get zarinpal payment's strategy according to config's mode. * - * @param $referenceId - * - * @return Receipt - */ - public function createReceipt($referenceId) - { - return new Receipt('zarinpal', $referenceId); - } - - /** - * Convert status to a readable message. - * - * @param $status - * - * @return mixed|string - */ - private function translateStatus($status) - { - $translations = array( - "-1" => "اطلاعات ارسال شده ناقص است.", - "-2" => "IP و يا مرچنت كد پذيرنده صحيح نيست", - "-3" => "با توجه به محدوديت هاي شاپرك امكان پرداخت با رقم درخواست شده ميسر نمي باشد", - "-4" => "سطح تاييد پذيرنده پايين تر از سطح نقره اي است.", - "-11" => "درخواست مورد نظر يافت نشد.", - "-12" => "امكان ويرايش درخواست ميسر نمي باشد.", - "-21" => "هيچ نوع عمليات مالي براي اين تراكنش يافت نشد", - "-22" => "تراكنش نا موفق ميباشد", - "-33" => "رقم تراكنش با رقم پرداخت شده مطابقت ندارد", - "-34" => "سقف تقسيم تراكنش از لحاظ تعداد يا رقم عبور نموده است", - "-40" => "اجازه دسترسي به متد مربوطه وجود ندارد.", - "-41" => "اطلاعات ارسال شده مربوط به AdditionalData غيرمعتبر ميباشد.", - "-42" => "مدت زمان معتبر طول عمر شناسه پرداخت بايد بين 30 دقيه تا 45 روز مي باشد.", - "-54" => "درخواست مورد نظر آرشيو شده است", - "101" => "عمليات پرداخت موفق بوده و قبلا PaymentVerification تراكنش انجام شده است.", - ); - - $unknownError = 'خطای ناشناخته رخ داده است.'; - - return array_key_exists($status, $translations) ? $translations[$status] : $unknownError; - } - - /** - * Retrieve purchase url - * - * @return string - */ - protected function getPurchaseUrl() : string - { - $mode = $this->getMode(); - - switch ($mode) { - case 'sandbox': - $url = $this->settings->sandboxApiPurchaseUrl; - break; - case 'zaringate': - $url = $this->settings->zaringateApiPurchaseUrl; - break; - default: // default: normal - $url = $this->settings->apiPurchaseUrl; - break; - } - - return $url; - } - - /** - * Retrieve Payment url - * - * @return string + * @param Invoice $invoice + * @param $settings + * @return DriverInterface */ - protected function getPaymentUrl() : string + protected function getFreshStrategyInstance($invoice, $settings) : DriverInterface { - $mode = $this->getMode(); + $strategy = static::$strategies[$this->getMode()] ?? null; - switch ($mode) { - case 'sandbox': - $url = $this->settings->sandboxApiPaymentUrl; - break; - case 'zaringate': - $url = $this->settings->zaringateApiPaymentUrl; - break; - default: // default: normal - $url = $this->settings->apiPaymentUrl; - break; + if (! $strategy) { + $this->strategyNotFound(); } - return $url; + return new $strategy($invoice, $settings); } - /** - * Retrieve verification url - * - * @return string - */ - protected function getVerificationUrl() : string + protected function strategyNotFound() { - $mode = $this->getMode(); - - switch ($mode) { - case 'sandbox': - $url = $this->settings->sandboxApiVerificationUrl; - break; - case 'zaringate': - $url = $this->settings->zaringateApiVerificationUrl; - break; - default: // default: normal - $url = $this->settings->apiVerificationUrl; - break; - } + $message = sprintf( + 'Zarinpal payment mode not found (check your settings), valid modes are: %s', + implode(',', array_keys(static::$strategies)) + ); - return $url; + throw new DriverNotFoundException($message); } /** From fb0e56932242638d316535ba854f2b7f77b01024 Mon Sep 17 00:00:00 2001 From: Erfan <37825504+erfantkerfan@users.noreply.github.com> Date: Thu, 1 Jul 2021 22:57:11 +0430 Subject: [PATCH 04/21] add details to "Behpardakht" receipt (#82) --- src/Drivers/Behpardakht/Behpardakht.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Drivers/Behpardakht/Behpardakht.php b/src/Drivers/Behpardakht/Behpardakht.php index ae21da0..66e5c39 100644 --- a/src/Drivers/Behpardakht/Behpardakht.php +++ b/src/Drivers/Behpardakht/Behpardakht.php @@ -133,8 +133,18 @@ public function verify(): ReceiptInterface } throw new InvalidPaymentException($this->translateStatus($settleResponse), $settleResponse); } + + $receipt = $this->createReceipt($data['saleReferenceId']); + + $receipt->detail([ + "RefId" => Request::input('RefId'), + "SaleOrderId" => Request::input('SaleOrderId'), + "CardHolderPan" => Request::input('CardHolderPan'), + "CardHolderInfo" => Request::input('CardHolderInfo'), + "SaleReferenceId" => Request::input('SaleReferenceId'), + ]); - return $this->createReceipt($data['saleReferenceId']); + return $receipt; } /** From bd1c009f955566b3ec9e2e23f0cc8e91bc4c55ba Mon Sep 17 00:00:00 2001 From: Meysam Fallah Date: Sat, 3 Jul 2021 09:49:31 +0430 Subject: [PATCH 05/21] Update Behpardakht.php (#83) Solution for: SOAP-ERROR: Parsing WSDL: Couldn't load from 'https://bpm.shaparak.ir/pgwchannel/services/pgw?wsdl' : failed to load external entity "https://bpm.shaparak.ir/pgwchannel/services/pgw?wsdl" --- src/Drivers/Behpardakht/Behpardakht.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Drivers/Behpardakht/Behpardakht.php b/src/Drivers/Behpardakht/Behpardakht.php index 66e5c39..19cf1c0 100644 --- a/src/Drivers/Behpardakht/Behpardakht.php +++ b/src/Drivers/Behpardakht/Behpardakht.php @@ -52,8 +52,18 @@ public function __construct(Invoice $invoice, $settings) public function purchase() { + $context = stream_context_create( + [ + 'ssl' => array( + 'verify_peer' => false, + 'verify_peer_name' => false + ) + ] + ); $soap = new \SoapClient($this->settings->apiPurchaseUrl); - $response = $soap->bpPayRequest($this->preparePurchaseData()); + $response = $soap->bpPayRequest($this->preparePurchaseData(), [ + 'stream_context' => $context + ]); // fault has happened in bank gateway if ($response->return == 21) { From 3903675081037b85ad22441f03772a3139863e0a Mon Sep 17 00:00:00 2001 From: Meysam Fallah Date: Sat, 3 Jul 2021 18:54:22 +0430 Subject: [PATCH 06/21] pass context array to SoapClient (#84) * Update Behpardakht.php Solution for: SOAP-ERROR: Parsing WSDL: Couldn't load from 'https://bpm.shaparak.ir/pgwchannel/services/pgw?wsdl' : failed to load external entity "https://bpm.shaparak.ir/pgwchannel/services/pgw?wsdl" add context array to verify method to solve this error: SoapFault(code: 0 faultcode: HTTP): Could not connect to host --- src/Drivers/Behpardakht/Behpardakht.php | 35 ++++++++++++++++--------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/src/Drivers/Behpardakht/Behpardakht.php b/src/Drivers/Behpardakht/Behpardakht.php index 19cf1c0..00d7960 100644 --- a/src/Drivers/Behpardakht/Behpardakht.php +++ b/src/Drivers/Behpardakht/Behpardakht.php @@ -53,17 +53,17 @@ public function __construct(Invoice $invoice, $settings) public function purchase() { $context = stream_context_create( - [ - 'ssl' => array( - 'verify_peer' => false, - 'verify_peer_name' => false - ) - ] - ); - $soap = new \SoapClient($this->settings->apiPurchaseUrl); - $response = $soap->bpPayRequest($this->preparePurchaseData(), [ - 'stream_context' => $context - ]); + [ + 'ssl' => array( + 'verify_peer' => false, + 'verify_peer_name' => false + )] + ); + + $soap = new \SoapClient($this->settings->apiPurchaseUrl, [ + 'stream_context' => $context + ]); + $response = $soap->bpPayRequest($this->preparePurchaseData()); // fault has happened in bank gateway if ($response->return == 21) { @@ -120,7 +120,18 @@ public function verify(): ReceiptInterface } $data = $this->prepareVerificationData(); - $soap = new \SoapClient($this->settings->apiVerificationUrl); + + $context = stream_context_create( + [ + 'ssl' => array( + 'verify_peer' => false, + 'verify_peer_name' => false + )] + ); + + $soap = new \SoapClient($this->settings->apiVerificationUrl, [ + 'stream_context' => $context + ]); // step1: verify request $verifyResponse = (int)$soap->bpVerifyRequest($data)->return; From 09ce30e78f30ccb58272ab33676346ea69cc4d3a Mon Sep 17 00:00:00 2001 From: Meysam Fallah Date: Wed, 7 Jul 2021 18:23:20 +0430 Subject: [PATCH 07/21] Solved new error on http1.1 (#87) * Update Behpardakht.php Solution for: SOAP-ERROR: Parsing WSDL: Couldn't load from 'https://bpm.shaparak.ir/pgwchannel/services/pgw?wsdl' : failed to load external entity "https://bpm.shaparak.ir/pgwchannel/services/pgw?wsdl" * change to work with http2 and http1.1 --- src/Drivers/Behpardakht/Behpardakht.php | 53 +++++++++++++++---------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/src/Drivers/Behpardakht/Behpardakht.php b/src/Drivers/Behpardakht/Behpardakht.php index 00d7960..d89eb03 100644 --- a/src/Drivers/Behpardakht/Behpardakht.php +++ b/src/Drivers/Behpardakht/Behpardakht.php @@ -52,17 +52,22 @@ public function __construct(Invoice $invoice, $settings) public function purchase() { - $context = stream_context_create( - [ - 'ssl' => array( - 'verify_peer' => false, - 'verify_peer_name' => false - )] - ); - - $soap = new \SoapClient($this->settings->apiPurchaseUrl, [ - 'stream_context' => $context - ]); + if($_SERVER['SERVER_PROTOCOL'] == "HTTP/2.0") { + $context = stream_context_create( + [ + 'ssl' => array( + 'verify_peer' => false, + 'verify_peer_name' => false + )] + ); + + $soap = new \SoapClient($this->settings->apiPurchaseUrl, [ + 'stream_context' => $context + ]); + } else { + $soap = new \SoapClient($this->settings->apiPurchaseUrl); + } + $response = $soap->bpPayRequest($this->preparePurchaseData()); // fault has happened in bank gateway @@ -121,17 +126,21 @@ public function verify(): ReceiptInterface $data = $this->prepareVerificationData(); - $context = stream_context_create( - [ - 'ssl' => array( - 'verify_peer' => false, - 'verify_peer_name' => false - )] - ); - - $soap = new \SoapClient($this->settings->apiVerificationUrl, [ - 'stream_context' => $context - ]); + if($_SERVER['SERVER_PROTOCOL'] == "HTTP/2.0") { + $context = stream_context_create( + [ + 'ssl' => array( + 'verify_peer' => false, + 'verify_peer_name' => false + )] + ); + + $soap = new \SoapClient($this->settings->apiPurchaseUrl, [ + 'stream_context' => $context + ]); + } else { + $soap = new \SoapClient($this->settings->apiPurchaseUrl); + } // step1: verify request $verifyResponse = (int)$soap->bpVerifyRequest($data)->return; From 60defd1007003d9fb1b1b59d023dc192c6aae141 Mon Sep 17 00:00:00 2001 From: mahdikhanzadi Date: Wed, 7 Jul 2021 18:26:58 +0430 Subject: [PATCH 08/21] Apply fixes from StyleCI (#88) Co-authored-by: mahdikhanzadi --- src/Drivers/Behpardakht/Behpardakht.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Drivers/Behpardakht/Behpardakht.php b/src/Drivers/Behpardakht/Behpardakht.php index d89eb03..adcb2af 100644 --- a/src/Drivers/Behpardakht/Behpardakht.php +++ b/src/Drivers/Behpardakht/Behpardakht.php @@ -52,7 +52,7 @@ public function __construct(Invoice $invoice, $settings) public function purchase() { - if($_SERVER['SERVER_PROTOCOL'] == "HTTP/2.0") { + if ($_SERVER['SERVER_PROTOCOL'] == "HTTP/2.0") { $context = stream_context_create( [ 'ssl' => array( @@ -126,7 +126,7 @@ public function verify(): ReceiptInterface $data = $this->prepareVerificationData(); - if($_SERVER['SERVER_PROTOCOL'] == "HTTP/2.0") { + if ($_SERVER['SERVER_PROTOCOL'] == "HTTP/2.0") { $context = stream_context_create( [ 'ssl' => array( From 17c6386fdc58dcb1607280c180fdd6eead83741e Mon Sep 17 00:00:00 2001 From: mahdi khanzadi Date: Fri, 9 Jul 2021 19:55:52 +0430 Subject: [PATCH 09/21] use toman for zarinpal normal gateway --- src/Drivers/Zarinpal/Strategies/Normal.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Drivers/Zarinpal/Strategies/Normal.php b/src/Drivers/Zarinpal/Strategies/Normal.php index b0ec76e..6552dd2 100644 --- a/src/Drivers/Zarinpal/Strategies/Normal.php +++ b/src/Drivers/Zarinpal/Strategies/Normal.php @@ -77,7 +77,7 @@ public function purchase() $data = [ "merchant_id" => $this->settings->merchantId, - "amount" => $this->invoice->getAmount(), + "amount" => $this->invoice->getAmount() * 10, // convert toman to rial "callback_url" => $this->settings->callbackUrl, "description" => $description, "metadata" => array_merge($this->invoice->getDetails() ?? [], $metadata), @@ -144,7 +144,7 @@ public function verify() : ReceiptInterface $data = [ "merchant_id" => $this->settings->merchantId, "authority" => $authority, - "amount" => $this->invoice->getAmount(), + "amount" => $this->invoice->getAmount() * 10, // convert toman to rial ]; $response = $this->client->request( From a12a3da78c0fb38e0c3603615ef7e906c71028b9 Mon Sep 17 00:00:00 2001 From: mohammad abbasi <77800167+mohammadv184@users.noreply.github.com> Date: Tue, 13 Jul 2021 20:33:02 +0430 Subject: [PATCH 10/21] Add Digipay gateway (#90) * Add Digipay driver * Add Digipay to en doc * Add Digipay to fa doc * Add Digipay to zh doc --- README-FA.md | 1 + README-ZH.md | 1 + README.md | 1 + config/payment.php | 12 +++ src/Drivers/Digipay/Digipay.php | 168 ++++++++++++++++++++++++++++++++ 5 files changed, 183 insertions(+) create mode 100644 src/Drivers/Digipay/Digipay.php diff --git a/README-FA.md b/README-FA.md index 805d423..4e9d64e 100644 --- a/README-FA.md +++ b/README-FA.md @@ -52,6 +52,7 @@ - [اسان پرداخت](https://asanpardakht.ir/) :heavy_check_mark: - [به‌پرداخت (mellat)](http://www.behpardakht.com/) :heavy_check_mark: +- [دیجی پی](https://www.mydigipay.com/) :heavy_check_mark: - [ایدی پی](https://idpay.ir/) :heavy_check_mark: - [ایرانکیش](http://irankish.com/) :heavy_check_mark: - [نکست پی](https://nextpay.ir/) :heavy_check_mark: diff --git a/README-ZH.md b/README-ZH.md index f8b31bc..6d9ba55 100644 --- a/README-ZH.md +++ b/README-ZH.md @@ -49,6 +49,7 @@ For **Laravel** integration you can use [shetabit/payment](https://github.com/sh # 可用驱动列表 - [asanpardakht](https://asanpardakht.ir/) :heavy_check_mark: - [behpardakht (mellat)](http://www.behpardakht.com/) :heavy_check_mark: +- [digipay](https://www.mydigipay.com/) :heavy_check_mark: - [idpay](https://idpay.ir/) :heavy_check_mark: - [irankish](http://irankish.com/) :heavy_check_mark: - [nextpay](https://nextpay.ir/) :heavy_check_mark: diff --git a/README.md b/README.md index 86ee542..bbc2cf8 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ For **Laravel** integration you can use [shetabit/payment](https://github.com/sh # List of available drivers - [asanpardakht](https://asanpardakht.ir/) :heavy_check_mark: - [behpardakht (mellat)](http://www.behpardakht.com/) :heavy_check_mark: +- [digipay](https://www.mydigipay.com/) :heavy_check_mark: - [idpay](https://idpay.ir/) :heavy_check_mark: - [irankish](http://irankish.com/) :heavy_check_mark: - [nextpay](https://nextpay.ir/) :heavy_check_mark: diff --git a/config/payment.php b/config/payment.php index 48261ac..72ceb9c 100755 --- a/config/payment.php +++ b/config/payment.php @@ -55,6 +55,17 @@ 'callbackUrl' => 'http://yoursite.com/path/to', 'description' => 'payment using behpardakht', ], + 'digipay' =>[ + 'apiOauthUrl' =>'https://api.mydigipay.com/digipay/api/oauth/token', + 'apiPurchaseUrl' => 'https://api.mydigipay.com/digipay/api/businesses/ticket?type=0', + 'apiPaymentUrl' => 'https://api.mydigipay.com/digipay/api/purchases/ipg/pay/', + 'apiVerificationUrl' => 'https://api.mydigipay.com/digipay/api/purchases/verify/', + 'username' => 'username', + 'password' => 'password', + 'client_id' => '', + 'client_secret' => '', + 'callbackUrl' => 'http://yoursite.com/path/to', + ], 'idpay' => [ 'apiPurchaseUrl' => 'https://api.idpay.ir/v1.1/payment', 'apiPaymentUrl' => 'https://idpay.ir/p/ws/', @@ -237,6 +248,7 @@ 'local' => \Shetabit\Multipay\Drivers\Local\Local::class, 'asanpardakht' => \Shetabit\Multipay\Drivers\Asanpardakht\Asanpardakht::class, 'behpardakht' => \Shetabit\Multipay\Drivers\Behpardakht\Behpardakht::class, + 'digipay' => \Shetabit\Multipay\Drivers\Digipay\Digipay::class, 'idpay' => \Shetabit\Multipay\Drivers\Idpay\Idpay::class, 'irankish' => \Shetabit\Multipay\Drivers\Irankish\Irankish::class, 'nextpay' => \Shetabit\Multipay\Drivers\Nextpay\Nextpay::class, diff --git a/src/Drivers/Digipay/Digipay.php b/src/Drivers/Digipay/Digipay.php new file mode 100644 index 0000000..f252622 --- /dev/null +++ b/src/Drivers/Digipay/Digipay.php @@ -0,0 +1,168 @@ +invoice($invoice); + $this->settings=$settings; + $this->client = new Client(); + $this->oauthToken = $this->oauth(); + } + + public function purchase() + { + $details = $this->invoice->getDetails(); + + $phone = null; + if (!empty($details['phone'])) { + $phone = $details['phone']; + } elseif (!empty($details['mobile'])) { + $phone = $details['mobile']; + } + $data = array( + 'amount' => $this->invoice->getAmount(), + 'phone' => $phone, + 'providerId' => $this->invoice->getUuid(), + 'redirectUrl' => $this->settings->callbackUrl, + 'type' => 0, + 'userType' => is_null($phone) ? 2 : 0 + ); + + $response = $this + ->client + ->request( + 'POST', + $this->settings->apiPurchaseUrl, + [ + "json" => $data, + "headers" => [ + 'Content-Type' => 'application/json', + 'Authorization' => 'Bearer '.$this->oauthToken + ], + "http_errors" => false, + ] + ); + + $body = json_decode($response->getBody()->getContents(), true); + if ($response->getStatusCode() != 200) { + // error has happened + $message = $body['result']['message'] ?? 'خطا در هنگام درخواست برای پرداخت رخ داده است.'; + throw new PurchaseFailedException($message); + } + + $this->invoice->transactionId($body['ticket']); + + // return the transaction's id + return $this->invoice->getTransactionId(); + } + + public function pay(): RedirectionForm + { + $payUrl = $this->settings->apiPaymentUrl.$this->invoice->getTransactionId(); + + return $this->redirectWithForm($payUrl, [], 'GET'); + } + + public function verify(): ReceiptInterface + { + $tracingId=Request::input("trackingCode"); + + $response = $this->client->request( + 'POST', + $this->settings->apiVerificationUrl.$tracingId, + [ + 'json' => [], + "headers" => [ + "Accept" => "application/json", + "Authorization" => "Bearer ".$this->oauthToken, + ], + "http_errors" => false, + ] + ); + $body = json_decode($response->getBody()->getContents(), true); + + if ($response->getStatusCode() != 200) { + $message = 'تراکنش تایید نشد'; + + throw new InvalidPaymentException($message); + } + + return new Receipt('digipay', $body["trackingCode"]); + } + + protected function oauth() + { + $response = $this + ->client + ->request( + 'POST', + $this->settings->apiOauthUrl, + [ + "headers" => [ + 'Content-Type' => 'multipart/form-data', + 'Authorization' => 'Basic '.base64_encode("{$this->settings->client_id}:{$this->settings->client_secret}") + ], + "username" => $this->settings->username, + "password" => $this->settings->password, + "grant_type" => 'password', + ] + ); + if ($response->getStatusCode()!=200) { + if ($response->getStatusCode()==401) { + throw new PurchaseFailedException("خطا نام کاربری یا رمز عبور شما اشتباه می باشد."); + } else { + throw new PurchaseFailedException("خطا در هنگام احراز هویت."); + } + } + $body = json_decode($response->getBody()->getContents(), true); + return $body['access_token']; + } +} From d9779d31f9ef4137ca9b22f21696711595e60ee9 Mon Sep 17 00:00:00 2001 From: mohammad abbasi <77800167+mohammadv184@users.noreply.github.com> Date: Wed, 14 Jul 2021 23:24:25 +0430 Subject: [PATCH 11/21] Add phpunit test (#91) * Add unitTest --- .gitignore | 3 ++ .travis.yml | 14 ++++++++-- phpunit.xml | 5 ---- tests/PaymentTest.php | 47 ++++++++++++++++++++++++++++++++ tests/TestCase.php | 36 ++++++++++++------------ tests/helpers/TestDriverMock.php | 38 ++++++++++++++++++++++++++ tests/helpers/config.php | 46 +++++++++++++++++++++++++++++++ 7 files changed, 164 insertions(+), 25 deletions(-) create mode 100644 tests/PaymentTest.php create mode 100644 tests/helpers/TestDriverMock.php create mode 100644 tests/helpers/config.php diff --git a/.gitignore b/.gitignore index 8e830ab..54dd5d7 100644 --- a/.gitignore +++ b/.gitignore @@ -52,5 +52,8 @@ Temporary Items # VSCode project's cache .vscode +# phpunit cache +.phpunit.result.cache + # Vendor directory vendor/ diff --git a/.travis.yml b/.travis.yml index bad31a9..9305f90 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,13 @@ language: php + php: - - '7.2' - - '7.3' -before_script: composer install + - 7.2 + - 7.3 + - 7.4 + +before_script: + - travis_retry composer self-update + - travis_retry composer update + - travis_retry composer install --prefer-source --no-interaction --dev + +script: phpunit diff --git a/phpunit.xml b/phpunit.xml index db3e54a..8ee732b 100755 --- a/phpunit.xml +++ b/phpunit.xml @@ -8,11 +8,6 @@ convertWarningsToExceptions="true" processIsolation="false" stopOnFailure="false"> - - - ./src/ - - ./tests/ diff --git a/tests/PaymentTest.php b/tests/PaymentTest.php new file mode 100644 index 0000000..eb9b6ef --- /dev/null +++ b/tests/PaymentTest.php @@ -0,0 +1,47 @@ +payment->amount(10000)->detail('foo', 'bar'); + $payment->purchase(null, function ($driver, $transactionId) { + $this->assertEquals(10000, $driver->getInvoice()->getAmount()); + $this->assertEquals(['foo'=>'bar'], $driver->getInvoice()->getDetails()); + $this->assertEquals('biSBUv86G', $transactionId); + }); + } + + /** + * test pay method + * @throws \Exception + */ + public function testPay() + { + $payment=$this->payment->amount(10000)->purchase()->pay(); + $this->assertTrue($payment instanceof RedirectionForm); + } + + /** + * test Verify method + * @throws \Shetabit\Multipay\Exceptions\InvoiceNotFoundException + */ + public function testVerify() + { + $payment=$this->payment->amount(10000)->transactionId("biSBUv86G")->verify(); + $this->assertEquals(Carbon::now()->format("Y-m-d h:t:s"), $payment->getDate()->format("Y-m-d h:t:s")); + $this->assertEquals("test", $payment->getDriver()); + $this->assertEquals("122156415036", $payment->getReferenceId()); + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php index 4cdcd45..4122cd2 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -2,29 +2,31 @@ namespace Shetabit\Multipay\Tests; -use Orchestra\Testbench\TestCase as BaseTestCase; -use Shetabit\Multipay\Tests\Mocks\Drivers\BarDriver; +use PHPUnit\Framework\TestCase as BaseTestCase; +use Shetabit\Multipay\Invoice; +use Shetabit\Multipay\Payment; +use Shetabit\Multipay\Tests\helpers\TestDriverMock; class TestCase extends BaseTestCase { - protected function getPackageProviders($app) + /** + * @var Payment + */ + protected $payment; + /** + * @throws \Exception + */ + protected function setUp(): void { - return ['Shetabit\Multipay\Provider\PaymentServiceProvider']; + $this->payment=new Payment($this->config()); } - protected function getPackageAliases($app) + /** + * return config + * @return mixed + */ + protected function config() { - return [ - 'Payment' => 'Shetabit\Multipay\Payment', - ]; - } - - protected function getEnvironmentSetUp($app) - { - $settings = require __DIR__.'/../src/Config/payment.php'; - $settings['drivers']['bar'] = ['key' => 'foo']; - $settings['map']['bar'] = BarDriver::class; - - $app['config']->set('payment', $settings); + return require(__DIR__.'/helpers/config.php'); } } diff --git a/tests/helpers/TestDriverMock.php b/tests/helpers/TestDriverMock.php new file mode 100644 index 0000000..be51b00 --- /dev/null +++ b/tests/helpers/TestDriverMock.php @@ -0,0 +1,38 @@ +invoice($invoice); + $this->settings=$settings; + } + + public function purchase() + { + return "biSBUv86G"; + } + + public function pay(): RedirectionForm + { + return $this->redirectWithForm('/', [], 'GET'); + } + + public function verify(): ReceiptInterface + { + return new Receipt("test", "122156415036"); + } +} diff --git a/tests/helpers/config.php b/tests/helpers/config.php new file mode 100644 index 0000000..495d1b9 --- /dev/null +++ b/tests/helpers/config.php @@ -0,0 +1,46 @@ + 'test', + + /* + |-------------------------------------------------------------------------- + | List of Drivers + |-------------------------------------------------------------------------- + | + | These are the list of drivers to use for this package. + | You can change the name. Then you'll have to change + | it in the map array too. + | + */ + 'drivers' => [ + 'test' => [ + 'callbackUrl' => '/callback', + ], + ], + + /* + |-------------------------------------------------------------------------- + | Class Maps + |-------------------------------------------------------------------------- + | + | This is the array of Classes that maps to Drivers above. + | You can create your own driver if you like and add the + | config in the drivers array and the class to use for + | here with the same name. You will have to extend + | Shetabit\Multipay\Abstracts\Driver in your driver. + | + */ + 'map' => [ + 'test' => \Shetabit\Multipay\Tests\helpers\TestDriverMock::class + ] +]; From 21ee81c8f3b5cb109b6bbdd2c7c88bafafc2db93 Mon Sep 17 00:00:00 2001 From: mahdi khanzadi Date: Thu, 15 Jul 2021 11:05:01 +0430 Subject: [PATCH 12/21] add tests skeleton --- phpunit.xml | 9 +- .../BarDriver.php} | 16 +- tests/Mocks/MockPaymentManager.php | 33 ++++ tests/PaymentTest.php | 144 ++++++++++++++---- tests/TestCase.php | 40 ++--- tests/Traits/DriverCommon.php | 77 ++++++++++ tests/helpers/config.php | 46 ------ 7 files changed, 267 insertions(+), 98 deletions(-) rename tests/{helpers/TestDriverMock.php => Drivers/BarDriver.php} (55%) create mode 100644 tests/Mocks/MockPaymentManager.php create mode 100644 tests/Traits/DriverCommon.php delete mode 100644 tests/helpers/config.php diff --git a/phpunit.xml b/phpunit.xml index 8ee732b..d0d4f3b 100755 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,13 +1,16 @@ - + stopOnFailure="false" + > ./tests/ diff --git a/tests/helpers/TestDriverMock.php b/tests/Drivers/BarDriver.php similarity index 55% rename from tests/helpers/TestDriverMock.php rename to tests/Drivers/BarDriver.php index be51b00..6270e2a 100644 --- a/tests/helpers/TestDriverMock.php +++ b/tests/Drivers/BarDriver.php @@ -1,7 +1,7 @@ redirectWithForm('/', [], 'GET'); + return $this->redirectWithForm('/', [ + 'amount' => $this->invoice->getAmount() + ], 'GET'); } public function verify(): ReceiptInterface { - return new Receipt("test", "122156415036"); + return new Receipt(static::DRIVER_NAME, static::REFERENCE_ID); } } diff --git a/tests/Mocks/MockPaymentManager.php b/tests/Mocks/MockPaymentManager.php new file mode 100644 index 0000000..8cae352 --- /dev/null +++ b/tests/Mocks/MockPaymentManager.php @@ -0,0 +1,33 @@ +driver; + } + + public function getConfig() : array + { + return $this->config; + } + + public function getCallbackUrl() : string + { + return $this->settings['callbackUrl']; + } + + public function getInvoice() + { + return $this->invoice; + } + + public function getCurrentDriverSetting() + { + return $this->settings; + } +} diff --git a/tests/PaymentTest.php b/tests/PaymentTest.php index eb9b6ef..5aadf49 100644 --- a/tests/PaymentTest.php +++ b/tests/PaymentTest.php @@ -1,47 +1,139 @@ config(); + $manager = $this->getManagerFreshInstance(); + + $this->assertEquals($config['default'], $manager->getDriver()); + } + + public function testItWontAcceptInvalidDriver() + { + $this->expectException(\Exception::class); + + $manager = $this->getManagerFreshInstance(); + $manager->via('none_existance_driver_name'); + } + + public function testConfigCanBeModified() + { + $manager = $this->getManagerFreshInstance(); + + $manager->config('foo', 'bar'); + + $config = $manager->getCurrentDriverSetting(); + + $this->assertArrayHasKey('foo', $config); + $this->assertSame('bar', $config['foo']); + } + + public function testCallbackUrlCanBeModified() + { + $manager = $this->getManagerFreshInstance(); + $manager->callbackUrl('/random_url'); + + $this->assertEquals('/random_url', $manager->getCallbackUrl()); + } + + public function testAmountCanBeSetted() + { + $amount = 10000; + $manager = $this->getManagerFreshInstance(); + $manager->amount($amount); + + $this->assertSame($amount, $manager->getInvoice()->getAmount()); + } + + public function testDeteilCanBeSetted() + { + $manager = $this->getManagerFreshInstance(); + + // array style + $manager->detail(['foo' => 'bar']); + + // normal style + $manager->detail('john', 'doe'); + + $invoice = $manager->getInvoice(); + + $this->assertEquals('bar', $invoice->getDetail('foo')); + $this->assertEquals('doe', $invoice->getDetail('john')); + } + + public function testDriverCanBeChanged() + { + $driverName = 'bar'; + $manager = $this->getManagerFreshInstance(); + $manager->via($driverName); + + $this->assertEquals($driverName, $manager->getDriver()); + } + public function testPurchase() { - $payment=$this->payment->amount(10000)->detail('foo', 'bar'); - $payment->purchase(null, function ($driver, $transactionId) { - $this->assertEquals(10000, $driver->getInvoice()->getAmount()); - $this->assertEquals(['foo'=>'bar'], $driver->getInvoice()->getDetails()); - $this->assertEquals('biSBUv86G', $transactionId); - }); + $amount = 10000; + $manager = $this->getManagerFreshInstance(); + + $manager + ->via('bar') + ->amount($amount) + ->purchase(null, function($driver, $transactionId) use ($amount) { + $this->assertEquals(BarDriver::TRANSACTION_ID, $transactionId); + $this->assertSame($amount, $driver->getInvoice()->getAmount()); + }); + } + + public function testCustomInvoiceCanBeUsedInPurchase() + { + $manager = $this->getManagerFreshInstance(); + + $invoice = new Invoice; + $invoice->amount(10000); + + $manager + ->via('bar') + ->purchase($invoice, function($driver, $transactionId) use ($invoice) { + $this->assertEquals(BarDriver::TRANSACTION_ID, $transactionId); + $this->assertSame($invoice->getAmount(), $driver->getInvoice()->getAmount()); + }); } - /** - * test pay method - * @throws \Exception - */ public function testPay() { - $payment=$this->payment->amount(10000)->purchase()->pay(); - $this->assertTrue($payment instanceof RedirectionForm); + $amount = 10000; + $manager = $this->getManagerFreshInstance(); + + $redirectionForm = $manager->amount($amount)->via('bar')->purchase()->pay(); + $inputs = $redirectionForm->getInputs(); + + $this->assertInstanceOf(RedirectionForm::class, $redirectionForm); + $this->assertEquals($inputs['amount'], $amount); } - /** - * test Verify method - * @throws \Shetabit\Multipay\Exceptions\InvoiceNotFoundException - */ public function testVerify() { - $payment=$this->payment->amount(10000)->transactionId("biSBUv86G")->verify(); - $this->assertEquals(Carbon::now()->format("Y-m-d h:t:s"), $payment->getDate()->format("Y-m-d h:t:s")); - $this->assertEquals("test", $payment->getDriver()); - $this->assertEquals("122156415036", $payment->getReferenceId()); + $amount = 10000; + $manager = $this->getManagerFreshInstance(); + + $receipt = $manager->amount($amount)->via('bar')->transactionId(BarDriver::TRANSACTION_ID)->verify(); + + $this->assertSame(BarDriver::REFERENCE_ID, $receipt->getReferenceId()); + $this->assertInstanceOf(Carbon::class, $receipt->getDate()); + } + + protected function getManagerFreshInstance() + { + return new MockPaymentManager($this->config()); } } diff --git a/tests/TestCase.php b/tests/TestCase.php index 4122cd2..b0d23e3 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -3,30 +3,34 @@ namespace Shetabit\Multipay\Tests; use PHPUnit\Framework\TestCase as BaseTestCase; -use Shetabit\Multipay\Invoice; -use Shetabit\Multipay\Payment; -use Shetabit\Multipay\Tests\helpers\TestDriverMock; +use Shetabit\Multipay\Tests\Drivers\BarDriver; class TestCase extends BaseTestCase { - /** - * @var Payment - */ - protected $payment; - /** - * @throws \Exception - */ - protected function setUp(): void + private $config = []; + + protected function setUp() : void + { + $this->environmentSetUp(); + } + + protected function config() : array { - $this->payment=new Payment($this->config()); + return $this->config; + } + + private function environmentSetUp() + { + $this->config = $this->loadConfig(); + + $this->config['map']['bar'] = BarDriver::class; + $this->config['drivers']['bar'] = [ + 'callback' => '/callback' + ]; } - /** - * return config - * @return mixed - */ - protected function config() + private function loadConfig() : array { - return require(__DIR__.'/helpers/config.php'); + return require(__DIR__.'/../config/payment.php'); } } diff --git a/tests/Traits/DriverCommon.php b/tests/Traits/DriverCommon.php new file mode 100644 index 0000000..88b63ac --- /dev/null +++ b/tests/Traits/DriverCommon.php @@ -0,0 +1,77 @@ +driverInstance = $this->getDriverInstance(); + $this->transactionId = 'biSBUv86G'; + } + + /** + * Test Purchase method. + * + * @throws \Exception + */ + public function testPurchase() + { + $amount = 10000; + $detailKey = 'foo'; + $detailValue = 'bar'; + + $this + ->driverInstance + ->detail($detailKey, $detailValue) + ->purchase($amount, function ($driver, $transactionId) { + $this->assertEquals($this->amount, $driver->getInvoice()->getAmount()); + $this->assertEquals([$this->detailKey => $this->detailValue], $driver->getInvoice()->getDetails()); + $this->assertEquals($this->transactionId, $transactionId); + }); + } + + /** + * Test pay method. + * + * @throws \Exception + */ + public function testPay() + { + $amount = 1000; + + $this + ->driverInstance + ->amount($amount) + ->purchase(); + + $this->assertInstanceOf(RedirectionForm::class, $this->driverInstance); + } + + /** + * Test Verify method + * + * @throws \Shetabit\Multipay\Exceptions\InvoiceNotFoundException + */ + public function testVerify() + { + $amount = 1000; + + $receipt = $this + ->driverInstance + ->amount($amount) + ->transactionId($this->transactionId) + ->verify(); + + $this->assertInstanceOf(Carbon::class, $receipt->getDate()); + $this->assertEquals("test", $receipt->getDriver()); + $this->assertEquals("122156415036", $receipt->getReferenceId()); + } +} diff --git a/tests/helpers/config.php b/tests/helpers/config.php deleted file mode 100644 index 495d1b9..0000000 --- a/tests/helpers/config.php +++ /dev/null @@ -1,46 +0,0 @@ - 'test', - - /* - |-------------------------------------------------------------------------- - | List of Drivers - |-------------------------------------------------------------------------- - | - | These are the list of drivers to use for this package. - | You can change the name. Then you'll have to change - | it in the map array too. - | - */ - 'drivers' => [ - 'test' => [ - 'callbackUrl' => '/callback', - ], - ], - - /* - |-------------------------------------------------------------------------- - | Class Maps - |-------------------------------------------------------------------------- - | - | This is the array of Classes that maps to Drivers above. - | You can create your own driver if you like and add the - | config in the drivers array and the class to use for - | here with the same name. You will have to extend - | Shetabit\Multipay\Abstracts\Driver in your driver. - | - */ - 'map' => [ - 'test' => \Shetabit\Multipay\Tests\helpers\TestDriverMock::class - ] -]; From 10264cf4fbe32627b5a9bde26434a9660da559bf Mon Sep 17 00:00:00 2001 From: mahdikhanzadi Date: Thu, 15 Jul 2021 11:07:31 +0430 Subject: [PATCH 13/21] Apply fixes from StyleCI (#92) Co-authored-by: mahdikhanzadi --- tests/PaymentTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/PaymentTest.php b/tests/PaymentTest.php index 5aadf49..c411c3b 100644 --- a/tests/PaymentTest.php +++ b/tests/PaymentTest.php @@ -88,7 +88,7 @@ public function testPurchase() $manager ->via('bar') ->amount($amount) - ->purchase(null, function($driver, $transactionId) use ($amount) { + ->purchase(null, function ($driver, $transactionId) use ($amount) { $this->assertEquals(BarDriver::TRANSACTION_ID, $transactionId); $this->assertSame($amount, $driver->getInvoice()->getAmount()); }); @@ -103,7 +103,7 @@ public function testCustomInvoiceCanBeUsedInPurchase() $manager ->via('bar') - ->purchase($invoice, function($driver, $transactionId) use ($invoice) { + ->purchase($invoice, function ($driver, $transactionId) use ($invoice) { $this->assertEquals(BarDriver::TRANSACTION_ID, $transactionId); $this->assertSame($invoice->getAmount(), $driver->getInvoice()->getAmount()); }); From d085e272916c2e4c1dc7dc50376a12c722799760 Mon Sep 17 00:00:00 2001 From: "Amin.M Mazreali" Date: Thu, 15 Jul 2021 11:19:50 +0430 Subject: [PATCH 14/21] Improvement AsanPardakht rest api (#89) * fix sadad detail in response * add rest api class for AssanPardakht Gateway --- config/payment.php | 8 +- src/Drivers/Asanpardakht/Asanpardakht.php | 378 +++++++++++----------- 2 files changed, 197 insertions(+), 189 deletions(-) diff --git a/config/payment.php b/config/payment.php index 72ceb9c..16059f0 100755 --- a/config/payment.php +++ b/config/payment.php @@ -33,15 +33,11 @@ 'cancelButton' => 'پرداخت ناموفق', ], 'asanpardakht' => [ - 'apiPurchaseUrl' => 'https://ipgsoap.asanpardakht.ir/paygate/merchantservices.asmx?wsdl', 'apiPaymentUrl' => 'https://asan.shaparak.ir', - 'apiVerificationUrl' => 'https://ipgsoap.asanpardakht.ir/paygate/merchantservices.asmx?wsdl', - 'apiUtilsUrl' => 'https://ipgsoap.asanpardakht.ir/paygate/internalutils.asmx?wsdl', - 'key' => '', - 'iv' => '', + 'apiRestPaymentUrl' => 'https://ipgrest.asanpardakht.ir/v1/', 'username' => '', 'password' => '', - 'merchantId' => '', + 'merchantConfigID' => '', 'callbackUrl' => 'http://yoursite.com/path/to', 'description' => 'payment using asanpardakht', ], diff --git a/src/Drivers/Asanpardakht/Asanpardakht.php b/src/Drivers/Asanpardakht/Asanpardakht.php index 116a7e4..f73680d 100644 --- a/src/Drivers/Asanpardakht/Asanpardakht.php +++ b/src/Drivers/Asanpardakht/Asanpardakht.php @@ -2,17 +2,25 @@ namespace Shetabit\Multipay\Drivers\Asanpardakht; +use GuzzleHttp\Client; use Shetabit\Multipay\Abstracts\Driver; -use Shetabit\Multipay\Exceptions\InvalidPaymentException; use Shetabit\Multipay\Exceptions\PurchaseFailedException; use Shetabit\Multipay\Contracts\ReceiptInterface; use Shetabit\Multipay\Invoice; use Shetabit\Multipay\Receipt; use Shetabit\Multipay\RedirectionForm; -use Shetabit\Multipay\Request; class Asanpardakht extends Driver { + const TokenURL = 'Token'; + const TimeURL = 'Time'; + const TranResultURL = 'TranResult'; + const CardHashURL = 'CardHash'; + const SettlementURL = 'Settlement'; + const VerifyURL = 'Verify'; + const CancelURL = 'Cancel'; + const ReverseURL = 'Reverse'; + /** * Invoice * @@ -20,6 +28,19 @@ class Asanpardakht extends Driver */ protected $invoice; + /** + * Response + * + * @var object + */ + protected $response; + + /** + * PayGateTransactionId + * + */ + protected $payGateTransactionId; + /** * Driver settings * @@ -38,6 +59,9 @@ public function __construct(Invoice $invoice, $settings) { $this->invoice($invoice); $this->settings = (object)$settings; + + //convert to rial + $this->invoice->amount($this->invoice->getAmount() * 10); } /** @@ -46,25 +70,18 @@ public function __construct(Invoice $invoice, $settings) * @return string * * @throws PurchaseFailedException - * @throws \SoapFault */ public function purchase() { - $client = $this->createSoapClient($this->settings->apiPurchaseUrl); + $this->invoice->uuid(crc32($this->invoice->getUuid())); - $params = $this->preparePurchaseData(); - $result = $client->RequestOperation($params); - if (!$result) { - throw new PurchaseFailedException('خطای فراخوانی متد درخواست تراکنش.'); - } + $result = $this->token(); - $result = $result->RequestOperationResult; - if ($result[0] != '0') { - $message = "خطای شماره " . $result . " رخ داده است."; - throw new PurchaseFailedException($message); + if (!isset($result['status_code']) or $result['status_code'] != 200) { + $this->purchaseFailed($result['status_code']); } - $this->invoice->transactionId(substr($result, 2)); + $this->invoice->transactionId($result['content']); // return the transaction's id return $this->invoice->getTransactionId(); @@ -77,10 +94,8 @@ public function purchase() */ public function pay(): RedirectionForm { - $payUrl = $this->settings->apiPaymentUrl; - $data = [ - 'RefId' => $this->invoice->getTransactionId() + 'RefID' => $this->invoice->getTransactionId() ]; //set mobileap for get user cards @@ -88,7 +103,7 @@ public function pay(): RedirectionForm $data['mobileap'] = $this->invoice->getDetails()['mobile']; } - return $this->redirectWithForm($payUrl, $data, 'POST'); + return $this->redirectWithForm($this->settings->apiPaymentUrl, $data, 'POST'); } /** @@ -96,95 +111,65 @@ public function pay(): RedirectionForm * * @return mixed|Receipt * - * @throws InvalidPaymentException - * @throws \SoapFault + * @throws PurchaseFailedException */ public function verify(): ReceiptInterface { - $encryptedReturningParamsString = Request::input('ReturningParams'); - $returningParamsString = $this->decrypt($encryptedReturningParamsString); - $returningParams = explode(",", $returningParamsString); - - /** - * other data: - * $amount = $returningParams[0]; - * $saleOrderId = $returningParams[1]; - * $refId = $this->invoice->getTransactionId() ?? $returningParams[2]; - * $resMessage = $returningParams[4]; - * $rrn = $returningParams[6]; - * $lastFourDigitOfPAN = $returningParams[7]; - **/ - - $resCode = $returningParams[3]; - $payGateTranID = $returningParams[5]; - - if ($resCode != '0' && $resCode != '00') { - $message = "خطای شماره " . $resCode . " رخ داده و تراکنش ناموفق بوده است."; - throw new InvalidPaymentException($message); + $result = $this->transactionResult(); + + if (!isset($result['status_code']) or $result['status_code'] != 200) { + $this->purchaseFailed($result['status_code']); } - $client = $this->createSoapClient($this->settings->apiVerificationUrl); - $params = $this->prepareVerificationData($payGateTranID); + $this->payGateTransactionId = $result['content']['payGateTranID']; + + //step1: verify + $verify_result = $this->verifyTransaction(); - // step1: verify - $this->verifyStep($client, $params); + if (!isset($verify_result['status_code']) or $verify_result['status_code'] != 200) { + $this->purchaseFailed($verify_result['status_code']); + } - // step2: settle - $this->settleStep($client, $params); + //step2: settlement + $this->settlement(); - $receipt = $this->createReceipt($payGateTranID); + $receipt = $this->createReceipt($this->payGateTransactionId); $receipt->detail([ - 'traceNo' => $payGateTranID, - 'referenceNo' => $returningParams[6], - 'transactionId' => $returningParams[2], - 'cardNo' => $returningParams[7], + 'traceNo' => $this->payGateTransactionId, + 'referenceNo' => $result['content']['rrn'], + 'transactionId' => $result['content']['refID'], + 'cardNo' => $result['content']['cardNumber'], ]); return $receipt; } /** - * payment verification step - * - * @param $client - * @param $params - * - * @throws InvalidPaymentException - */ - protected function verifyStep($client, $params) - { - $result = $client->RequestVerification($params); - if (!$result) { - throw new InvalidPaymentException("خطای فراخوانی متد وريفای رخ داده است."); - } - - $result = $result->RequestVerificationResult; - if ($result != '500') { - $message = "خطای شماره: " . $result . " در هنگام Verify"; - throw new InvalidPaymentException($message); - } - } - - /** - * payment settlement step. + * send request to Asanpardakht * - * @param $client - * @param $params - * - * @throws InvalidPaymentException + * @param $method + * @param $url + * @param array $data + * @return array */ - protected function settleStep($client, $params) + protected function callApi($method, $url, $data = []): array { - $result = $client->RequestReconciliation($params); - if (!$result) { - throw new InvalidPaymentException('خطای فراخوانی متد تسويه رخ داده است.'); - } + $client = new Client(['base_uri' => $this->settings->apiRestPaymentUrl]); + + $response = $client->request($method, $url, [ + "json" => $data, + "headers" => [ + 'Content-Type' => 'application/json', + 'usr' => $this->settings->username, + 'pwd' => $this->settings->password + ], + "http_errors" => false, + ]); - $result = $result->RequestReconciliationResult; - if ($result != '600') { - $message = "خطای شماره: " . $result . " در هنگام Settlement"; - throw new InvalidPaymentException($message); - } + return [ + 'status_code' => $response->getStatusCode(), + 'content' => json_decode($response->getBody()->getContents(), true) + ]; } /** @@ -194,7 +179,7 @@ protected function settleStep($client, $params) * * @return Receipt */ - protected function createReceipt($referenceId) + protected function createReceipt($referenceId): Receipt { $receipt = new Receipt('asanpardakht', $referenceId); @@ -202,134 +187,161 @@ protected function createReceipt($referenceId) } /** - * Prepare data for payment verification - * - * @param $payGateTranID + * call create token request * * @return array - * - * @throws \SoapFault */ - protected function prepareVerificationData($payGateTranID) + public function token(): array { - $credentials = array( - $this->settings->username, - $this->settings->password - ); - - $encryptedCredentials = $this->encrypt(implode(',', $credentials)); - - return array( - 'merchantConfigurationID' => $this->settings->merchantId, - 'encryptedCredentials' => $encryptedCredentials, - 'payGateTranID' => $payGateTranID - ); + return $this->callApi('POST', self::TokenURL, [ + 'serviceTypeId' => 1, + 'merchantConfigurationId' => $this->settings->merchantConfigID, + 'localInvoiceId' => $this->invoice->getUuid(), + 'amountInRials' => $this->invoice->getAmount(), + 'localDate' => $this->getTime()['content'], + 'callbackURL' => $this->settings->callbackUrl . "?" . http_build_query(['invoice' => $this->invoice->getUuid()]), + 'paymentId' => "0", + 'additionalData' => '', + ]); } /** - * Prepare data for purchasing invoice + * call reserve request * * @return array - * - * @throws \SoapFault */ - protected function preparePurchaseData() + public function reverse(): array { - if (!empty($this->invoice->getDetails()['description'])) { - $description = $this->invoice->getDetails()['description']; - } else { - $description = $this->settings->description; - } - - // configs - $username = $this->settings->username; - $password = $this->settings->password; - $callBackUrl = $this->settings->callbackUrl; - - // invoice details - $price = $this->invoice->getAmount() * 10; // convert to rial - $additionalData = $description ?? ''; - $orderId = crc32($this->invoice->getUuid()); - $localDate = date("Ymd His"); - - // box and encrypt everything - $requestString = "1,{$username},{$password},{$orderId},{$price},{$localDate},{$additionalData},{$callBackUrl},0"; - $encryptedRequestString = $this->encrypt($requestString); - - return array( - 'merchantConfigurationID' => $this->settings->merchantId, - 'encryptedRequest' => $encryptedRequestString - ); + return $this->callApi('POST', self::ReverseURL, [ + 'merchantConfigurationId' => (int)$this->settings->merchantConfigID, + 'payGateTranId' => (int)$this->invoice->getUuid() + ]); } /** - * Encrypt given string. + * send cancel request * - * @param $string - * - * @return mixed - * - * @throws \SoapFault + * @return array */ - protected function encrypt($string) + public function cancel(): array { - $client = $this->createSoapClient($this->settings->apiUtilsUrl); - - $params = array( - 'aesKey' => $this->settings->key, - 'aesVector' => $this->settings->iv, - 'toBeEncrypted' => $string - ); - - $result = $client->EncryptInAES($params); - - return $result->EncryptInAESResult; + return $this->callApi('POST', self::CancelURL, [ + 'merchantConfigurationId' => (int)$this->settings->merchantConfigID, + 'payGateTranId' => (int)$this->payGateTransactionId + ]); } /** - * Decrypt given string. - * - * @param $string - * - * @return mixed + * send verify request * - * @throws \SoapFault + * @return array */ - protected function decrypt($string) + public function verifyTransaction(): array { - $client = $this->createSoapClient($this->settings->apiUtilsUrl); + return $this->callApi('POST', self::VerifyURL, [ + 'merchantConfigurationId' => (int)$this->settings->merchantConfigID, + 'payGateTranId' => (int)$this->payGateTransactionId + ]); + } - $params = array( - 'aesKey' => $this->settings->key, - 'aesVector' => $this->settings->iv, - 'toBeDecrypted' => $string - ); + /** + * send settlement request + * + * @return array + */ + public function settlement(): array + { + return $this->callApi('POST', self::SettlementURL, [ + 'merchantConfigurationId' => (int)$this->settings->merchantConfigID, + 'payGateTranId' => (int)$this->payGateTransactionId + ]); + } - $result = $client->DecryptInAES($params); + /** + * get card hash request + * + * @return array + */ + public function cardHash(): array + { + return $this->callApi('GET', self::CardHashURL . '?merchantConfigurationId=' . $this->settings->merchantConfigID . '&localInvoiceId=' . $this->invoice->getTransactionId(), []); + } - return $result->DecryptInAESResult; + /** + * get transaction result + * + * @return array + */ + public function transactionResult(): array + { + return $this->callApi('GET', self::TranResultURL . '?merchantConfigurationId=' . $this->settings->merchantConfigID . '&localInvoiceId=' . $this->invoice->getTransactionId(), []); } /** - * create a new SoapClient + * get Asanpardakht server time * - * @param $url + * @return array + */ + public function getTime(): array + { + return $this->callApi('GET', self::TimeURL); + } + + /** + * Trigger an exception * - * @return \SoapClient + * @param $status * - * @throws \SoapFault + * @throws PurchaseFailedException */ - protected function createSoapClient($url) + protected function purchaseFailed($status) { - $opts = array( - 'ssl' => array( - 'verify_peer' => false, - 'verify_peer_name' => false - ) - ); - - $configs = array('stream_context' => stream_context_create($opts)); + $translations = [ + 400 => "bad request", + 401 => "unauthorized. probably wrong or unsent header(s)", + 471 => "identity not trusted to proceed", + 472 => "no records found", + 473 => "invalid merchant username or password", + 474 => "invalid incoming request machine ip. check response body to see your actual public IP address", + 475 => "invoice identifier is not a number", + 476 => "request amount is not a number", + 477 => "request local date length is invalid", + 478 => "request local date is not in valid format", + 479 => "invalid service type id", + 480 => "invalid payer id", + 481 => "incorrect settlement description format", + 482 => "settlement slices does not match total amount", + 483 => "unregistered iban", + 484 => "internal error for other reasons", + 485 => "invalid local date", + 486 => "amount not in range", + 487 => "service not found or not available for merchant", + 488 => "invalid default callback", + 489 => "duplicate local invoice id", + 490 => "merchant disabled or misconfigured", + 491 => "too many settlement destinations", + 492 => "unprocessable request", + 493 => "error processing special request for other reasons like business restrictions", + 494 => "invalid payment_id for governmental payment", + 495 => "invalid referenceId in additionalData", + 496 => "invalid json in additionalData", + 497 => "invalid payment_id location", + 571 => "misconfiguration OR not yet processed", + 572 => "misconfiguration OR transaction status undetermined", + 573 => "misconfiguraed valid ips for configuration OR unable to request for verification due to an internal error", + 574 => "internal error in uthorization", + 575 => "no valid ibans found for merchant", + 576 => "internal error", + 577 => "internal error", + 578 => "no default sharing is defined for merchant", + 579 => "cant submit ibans with default sharing endpoint", + 580 => "error processing special request" + ]; - return new \SoapClient($url, $configs); + if (array_key_exists($status, $translations)) { + throw new PurchaseFailedException($translations[$status]); + } else { + throw new PurchaseFailedException('خطای ناشناخته ای رخ داده است.'); + } } } From 315b9f9bf10c4ccb26cdaa5dd3ab8601b572f868 Mon Sep 17 00:00:00 2001 From: Ali Nazari Date: Sat, 17 Jul 2021 14:18:56 +0430 Subject: [PATCH 15/21] Update Normal.php --- src/Drivers/Zarinpal/Strategies/Normal.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Drivers/Zarinpal/Strategies/Normal.php b/src/Drivers/Zarinpal/Strategies/Normal.php index 6552dd2..7c07597 100644 --- a/src/Drivers/Zarinpal/Strategies/Normal.php +++ b/src/Drivers/Zarinpal/Strategies/Normal.php @@ -162,7 +162,7 @@ public function verify() : ReceiptInterface $result = json_decode($response->getBody()->getContents(), true); if (empty($result['data']) || ! isset($result['data']['ref_id']) || $result['data']['code'] != 100) { - $message = $result['errors']['message']; + $message = $result['errors']['message'] ?? ""; $code = $result['errors']['code']; throw new InvalidPaymentException($message, $code); From 23fd5d2bcdfb0719284c9c63bcab650a79dcf4eb Mon Sep 17 00:00:00 2001 From: "Amin.M Mazreali" Date: Sun, 25 Jul 2021 10:55:02 +0430 Subject: [PATCH 16/21] add Walleta gateway (#95) * fix sadad detail in response * add rest api class for AssanPardakht Gateway * remove soap url and add api rest url in config file * fix merchant config id * fix transaction result and DOC * fix uri for guzzle request * fix and test all function on AsanPardakht gateway * add walleta config and class * fix style * bug in walleta get error --- README-FA.md | 1 + README.md | 1 + config/payment.php | 13 +- src/Drivers/Walleta/Walleta.php | 226 ++++++++++++++++++++++++++++++++ 4 files changed, 239 insertions(+), 2 deletions(-) create mode 100644 src/Drivers/Walleta/Walleta.php diff --git a/README-FA.md b/README-FA.md index 4e9d64e..9a4092f 100644 --- a/README-FA.md +++ b/README-FA.md @@ -66,6 +66,7 @@ - [سداد (بانک ملی)](https://sadadpsp.ir/) :heavy_check_mark: - [سامان](https://www.sep.ir) :heavy_check_mark: - [سپهر (بانک صادرات)](https://www.sepehrpay.com/) :heavy_check_mark: +- [والتا (پرداخت اقساطی)](https://walleta.ir/) :heavy_check_mark: - [یک پی](https://yekpay.com/) :heavy_check_mark: - [زرین پال](https://www.zarinpal.com/) :heavy_check_mark: - [زیبال](https://www.zibal.ir/) :heavy_check_mark: diff --git a/README.md b/README.md index bbc2cf8..fd48a8d 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,7 @@ For **Laravel** integration you can use [shetabit/payment](https://github.com/sh - [sadad (melli)](https://sadadpsp.ir/) :heavy_check_mark: - [saman](https://www.sep.ir) :heavy_check_mark: - [sepehr (saderat)](https://www.sepehrpay.com/) :heavy_check_mark: +- [walleta (Installment payment)](https://walleta.ir/) :heavy_check_mark: - [yekpay](https://yekpay.com/) :heavy_check_mark: - [zarinpal](https://www.zarinpal.com/) :heavy_check_mark: - [zibal](https://www.zibal.ir/) :heavy_check_mark: diff --git a/config/payment.php b/config/payment.php index 16059f0..a771e62 100755 --- a/config/payment.php +++ b/config/payment.php @@ -51,8 +51,8 @@ 'callbackUrl' => 'http://yoursite.com/path/to', 'description' => 'payment using behpardakht', ], - 'digipay' =>[ - 'apiOauthUrl' =>'https://api.mydigipay.com/digipay/api/oauth/token', + 'digipay' => [ + 'apiOauthUrl' => 'https://api.mydigipay.com/digipay/api/oauth/token', 'apiPurchaseUrl' => 'https://api.mydigipay.com/digipay/api/businesses/ticket?type=0', 'apiPaymentUrl' => 'https://api.mydigipay.com/digipay/api/purchases/ipg/pay/', 'apiVerificationUrl' => 'https://api.mydigipay.com/digipay/api/purchases/verify/', @@ -183,6 +183,14 @@ 'callbackUrl' => '', 'description' => 'payment using sepehr(saderat)', ], + 'walleta' => [ + 'apiPurchaseUrl' => 'https://cpg.walleta.ir/payment/request.json', + 'apiPaymentUrl' => 'https://cpg.walleta.ir/ticket/', + 'apiVerificationUrl' => 'https://cpg.walleta.ir/payment/verify.json', + 'merchantId' => '', + 'callbackUrl' => 'http://yoursite.com/path/to', + 'description' => 'payment using walleta', + ], 'yekpay' => [ 'apiPurchaseUrl' => 'https://gate.yekpay.com/api/payment/server?wsdl', 'apiPaymentUrl' => 'https://gate.yekpay.com/api/payment/start/', @@ -258,6 +266,7 @@ 'sadad' => \Shetabit\Multipay\Drivers\Sadad\Sadad::class, 'saman' => \Shetabit\Multipay\Drivers\Saman\Saman::class, 'sepehr' => \Shetabit\Multipay\Drivers\Sepehr\Sepehr::class, + 'walleta' => \Shetabit\Multipay\Drivers\Walleta\Walleta::class, 'yekpay' => \Shetabit\Multipay\Drivers\Yekpay\Yekpay::class, 'zarinpal' => \Shetabit\Multipay\Drivers\Zarinpal\Zarinpal::class, 'zibal' => \Shetabit\Multipay\Drivers\Zibal\Zibal::class, diff --git a/src/Drivers/Walleta/Walleta.php b/src/Drivers/Walleta/Walleta.php new file mode 100644 index 0000000..4fae63d --- /dev/null +++ b/src/Drivers/Walleta/Walleta.php @@ -0,0 +1,226 @@ +invoice($invoice); + $this->settings = (object)$settings; + } + + /** + * Purchase Invoice.09214125578 + * + * @return string + * + * @throws PurchaseFailedException + */ + public function purchase() + { + $result = $this->token(); + + if (!isset($result['status_code']) or $result['status_code'] != 200) { + $this->purchaseFailed($result['content']['type']); + } + + $this->invoice->transactionId($result['content']['token']); + + // return the transaction's id + return $this->invoice->getTransactionId(); + } + + /** + * Pay the Invoice + * + * @return RedirectionForm + */ + public function pay(): RedirectionForm + { + return $this->redirectWithForm($this->settings->apiPaymentUrl . $this->invoice->getTransactionId(), [], 'GET'); + } + + /** + * Verify payment + * + * @return mixed|Receipt + * + * @throws PurchaseFailedException + */ + public function verify(): ReceiptInterface + { + $result = $this->verifyTransaction(); + + if (!isset($result['status_code']) or $result['status_code'] != 200) { + $this->purchaseFailed($result['content']['type']); + } + + $receipt = $this->createReceipt($this->invoice->getUuid()); + + return $receipt; + } + + /** + * send request to Walleta + * + * @param $method + * @param $url + * @param array $data + * @return array + */ + protected function callApi($method, $url, $data = []): array + { + $client = new Client(); + + $response = $client->request($method, $url, [ + "json" => $data, + "headers" => [ + 'Content-Type' => 'application/json', + ], + "http_errors" => false, + ]); + + return [ + 'status_code' => $response->getStatusCode(), + 'content' => json_decode($response->getBody()->getContents(), true) + ]; + } + + /** + * Generate the payment's receipt + * + * @param $referenceId + * + * @return Receipt + */ + protected function createReceipt($referenceId): Receipt + { + $receipt = new Receipt('walleta', $referenceId); + + return $receipt; + } + + /** + * call create token request + * + * @return array + */ + public function token(): array + { + return $this->callApi('POST', $this->settings->apiPurchaseUrl, [ + 'merchant_code' => $this->settings->merchantId, + 'invoice_reference' => $this->invoice->getUuid(), + 'invoice_date' => date('Y-m-d H:i:s'), + 'invoice_amount' => $this->invoice->getAmount(), + 'payer_first_name' => $this->invoice->getDetails()['first_name'], + 'payer_last_name' => $this->invoice->getDetails()['last_name'], + 'payer_national_code' => $this->invoice->getDetails()['national_code'], + 'payer_mobile' => $this->invoice->getDetails()['mobile'], + 'callback_url' => $this->settings->callbackUrl, + 'items' => $this->getItems(), + ]); + } + + /** + * call verift transaction request + * + * @return array + */ + public function verifyTransaction(): array + { + return $this->callApi('POST', $this->settings->apiVerificationUrl, [ + 'merchant_code' => $this->settings->merchantId, + 'token' => $this->invoice->getTransactionId(), + 'invoice_reference' => $this->invoice->getUuid(), + 'invoice_amount' => $this->invoice->getAmount(), + ]); + } + + /** + * get Items for + * + * + */ + private function getItems() + { + /** + * example data + * + * $items = [ + * [ + * "reference" => "string", + * "name" => "string", + * "quantity" => 0, + * "unit_price" => 0, + * "unit_discount" => 0, + * "unit_tax_amount" => 0, + * "total_amount" => 0 + * ] + * ]; + */ + return $this->invoice->getDetails()['items']; + } + + + /** + * Trigger an exception + * + * @param $status + * + * @throws PurchaseFailedException + */ + protected function purchaseFailed($status) + { + $translations = [ + "server_error" => "یک خطای داخلی رخ داده است.", + "ip_address_error" => "آدرس IP پذیرنده صحیح نیست.", + "validation_error" => "اطلاعات ارسال شده صحیح نیست.", + "merchant_error" => "کد پذیرنده معتبر نیست.", + "payment_token_error" => "شناسه پرداخت معتبر نیست.", + "invoice_amount_error" => "مبلغ تراکنش با مبلغ پرداخت شده مطابقت ندارد.", + ]; + + if (array_key_exists($status, $translations)) { + throw new PurchaseFailedException($translations[$status]); + } else { + throw new PurchaseFailedException('خطای ناشناخته ای رخ داده است.'); + } + } +} From 0bbd612908d5138ca2b2c73061b4a0f81283d1e7 Mon Sep 17 00:00:00 2001 From: kamal gharejeloo <36801162+kamalgharejeloo@users.noreply.github.com> Date: Mon, 26 Jul 2021 17:27:09 +0430 Subject: [PATCH 17/21] verif zarinpal exception with 101 status (#97) Co-authored-by: kamal gharejeloo --- src/Drivers/Zarinpal/Strategies/Normal.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Drivers/Zarinpal/Strategies/Normal.php b/src/Drivers/Zarinpal/Strategies/Normal.php index 7c07597..1c3ee7d 100644 --- a/src/Drivers/Zarinpal/Strategies/Normal.php +++ b/src/Drivers/Zarinpal/Strategies/Normal.php @@ -161,13 +161,16 @@ public function verify() : ReceiptInterface $result = json_decode($response->getBody()->getContents(), true); - if (empty($result['data']) || ! isset($result['data']['ref_id']) || $result['data']['code'] != 100) { - $message = $result['errors']['message'] ?? ""; + if (empty($result['data']) || ! isset($result['data']['ref_id']) || ($result['data']['code'] != 100 && $result['data']['code'] != 101)) { + $message = $result['errors']['message']; $code = $result['errors']['code']; - throw new InvalidPaymentException($message, $code); } - + if ($result['data']['code'] == 101) { + $message = $result['data']['message']; + $code = $result['data']['code']; + throw new InvalidPaymentException($message, $code); + } return $this->createReceipt($result['data']['ref_id']); } From fb3991b4ea0157888b6efb8459dbe7d6ad822f01 Mon Sep 17 00:00:00 2001 From: "Amin.M Mazreali" Date: Mon, 26 Jul 2021 17:27:58 +0430 Subject: [PATCH 18/21] fix tiny bug in Walleta gateway (#96) * fix tiny bug and check/test all section is worked true --- src/Drivers/Walleta/Walleta.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Drivers/Walleta/Walleta.php b/src/Drivers/Walleta/Walleta.php index 4fae63d..fd3dedd 100644 --- a/src/Drivers/Walleta/Walleta.php +++ b/src/Drivers/Walleta/Walleta.php @@ -92,7 +92,7 @@ public function verify(): ReceiptInterface $this->purchaseFailed($result['content']['type']); } - $receipt = $this->createReceipt($this->invoice->getUuid()); + $receipt = $this->createReceipt($this->invoice->getTransactionId()); return $receipt; } @@ -168,7 +168,7 @@ public function verifyTransaction(): array return $this->callApi('POST', $this->settings->apiVerificationUrl, [ 'merchant_code' => $this->settings->merchantId, 'token' => $this->invoice->getTransactionId(), - 'invoice_reference' => $this->invoice->getUuid(), + 'invoice_reference' => $this->invoice->getDetail('uuid'), 'invoice_amount' => $this->invoice->getAmount(), ]); } From 2c79dec39f23be54bdd3947a3b17d0870613f37d Mon Sep 17 00:00:00 2001 From: Mohammad Abbasi Date: Tue, 27 Jul 2021 10:58:54 +0430 Subject: [PATCH 19/21] Update Receipt.php (#98) --- src/Receipt.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Receipt.php b/src/Receipt.php index ba78ed5..1ca1a5b 100644 --- a/src/Receipt.php +++ b/src/Receipt.php @@ -27,6 +27,6 @@ public function __set($name, $value) */ public function __get($name) { - $this->getDetail($name); + return $this->getDetail($name); } } From f15473be67a92c3a60ba7c3773a51c99d37b7edb Mon Sep 17 00:00:00 2001 From: Erfan <37825504+erfantkerfan@users.noreply.github.com> Date: Sat, 31 Jul 2021 15:41:47 +0430 Subject: [PATCH 20/21] fix behpardakht http version detection (#99) this solves the problem where http_version is not set --- src/Drivers/Behpardakht/Behpardakht.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Drivers/Behpardakht/Behpardakht.php b/src/Drivers/Behpardakht/Behpardakht.php index adcb2af..fa73fe4 100644 --- a/src/Drivers/Behpardakht/Behpardakht.php +++ b/src/Drivers/Behpardakht/Behpardakht.php @@ -52,7 +52,7 @@ public function __construct(Invoice $invoice, $settings) public function purchase() { - if ($_SERVER['SERVER_PROTOCOL'] == "HTTP/2.0") { + if (isset($_SERVER['SERVER_PROTOCOL']) && $_SERVER['SERVER_PROTOCOL'] == "HTTP/2.0") { $context = stream_context_create( [ 'ssl' => array( @@ -126,7 +126,7 @@ public function verify(): ReceiptInterface $data = $this->prepareVerificationData(); - if ($_SERVER['SERVER_PROTOCOL'] == "HTTP/2.0") { + if (isset($_SERVER['SERVER_PROTOCOL']) && $_SERVER['SERVER_PROTOCOL'] == "HTTP/2.0") { $context = stream_context_create( [ 'ssl' => array( From e55111b2429976a445c394eebb5d843dca130b87 Mon Sep 17 00:00:00 2001 From: Hadi Sharghi Date: Sun, 1 Aug 2021 20:04:53 +0430 Subject: [PATCH 21/21] Update Behpardakht.php change Behpardakht receipt data --- src/Drivers/Behpardakht/Behpardakht.php | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Drivers/Behpardakht/Behpardakht.php b/src/Drivers/Behpardakht/Behpardakht.php index fa73fe4..d7652b5 100644 --- a/src/Drivers/Behpardakht/Behpardakht.php +++ b/src/Drivers/Behpardakht/Behpardakht.php @@ -165,13 +165,11 @@ public function verify(): ReceiptInterface } $receipt = $this->createReceipt($data['saleReferenceId']); - $receipt->detail([ - "RefId" => Request::input('RefId'), - "SaleOrderId" => Request::input('SaleOrderId'), - "CardHolderPan" => Request::input('CardHolderPan'), - "CardHolderInfo" => Request::input('CardHolderInfo'), - "SaleReferenceId" => Request::input('SaleReferenceId'), + 'traceNo' => Request::input('SaleOrderId'), + 'referenceNo' => Request::input('SaleReferenceId'), + 'transactionId' => Request::input('RefId'), + 'cardNo' => Request::input('CardHolderPan'), ]); return $receipt;