diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..abc8bed --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +payabbhi.tar.gz +.DS_Store +dist/* + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..dbcd938 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Paypermint + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2b41fbc --- /dev/null +++ b/Makefile @@ -0,0 +1,29 @@ +VERSION_FILE=VERSION +VER=`cat $(VERSION_FILE)` + +release: init prepare archive cleanup + +init: + mkdir dist + +prepare: + cp VERSION payabbhi + markdown-pdf README.md + zip -r payabbhi.zip payabbhi + + +archive: + zip -r payabbhi-prestashop-$(VER).zip payabbhi.zip README.pdf + tar -cvzf payabbhi-prestashop-$(VER).tar.gz payabbhi.zip README.pdf + + +cleanup: + mv payabbhi-prestashop-$(VER).zip dist + mv payabbhi-prestashop-$(VER).tar.gz dist + rm payabbhi/VERSION + rm README.pdf + rm payabbhi.zip + + +clean: + rm -rf dist diff --git a/README.md b/README.md new file mode 100644 index 0000000..d0ed6fc --- /dev/null +++ b/README.md @@ -0,0 +1,40 @@ +## Payabbhi Payments - Prestashop Integration + +This extension is built on Payabbhi PHP Library to provide seamless integration of [Payabbhi Checkout ](https://payabbhi.com/docs/checkout) with PrestaShop 1.6 + + +### Installation + +Make sure you have signed up for your [Payabbhi Account](https://payabbhi.com/docs/account) and downloaded the [API keys](https://payabbhi.com/docs/account/#api-keys) from the [Portal](https://payabbhi.com/portal). + +1. Unzip [payabbhi-prestashop-VERSION.zip](https://github.com/payabbhi/payabbhi-prestashop/releases). + +2. Navigate to `PrestaShop Back Office` -> `Modules and Services` and click on `Add a new module`. + +3. Browse for `payabbhi.zip` to add Payabbhi Payment Extension to PrestaShop. + +4. On successful upload, `payabbhi` folder should get added to PrestaShop installation directory as follows: + +``` +PrestaShop/ + modules/ + payabbhi/ + config.xml + controllers/ + index.php + logo.png + payabbhi-php/ + payabbhi.php + VERSION + views/ +``` + +4. After successful upload, navigate to `Modules List` ->`Payments and Gateways` and install `Payabbhi Checkout` as per on-screen instructions. If you do not find Payabbhi on the list, please use the search option to find it. + +5. Configure `Payabbhi` and Save the settings: + - [Access ID](https://payabbhi.com/docs/account/#api-keys) + - [Secret Key](https://payabbhi.com/docs/account/#api-keys) + - [payment_auto_capture](https://payabbhi.com/docs/api/#create-an-order) + + +[Payabbhi Checkout](https://payabbhi.com/docs/checkout) is now enabled in PrestaShop. diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..3eefcb9 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +1.0.0 diff --git a/payabbhi/config.xml b/payabbhi/config.xml new file mode 100644 index 0000000..1045060 --- /dev/null +++ b/payabbhi/config.xml @@ -0,0 +1,13 @@ + + + payabbhi + + + + + + + 1 + 1 + + diff --git a/payabbhi/controllers/front/index.php b/payabbhi/controllers/front/index.php new file mode 100644 index 0000000..01f7806 --- /dev/null +++ b/payabbhi/controllers/front/index.php @@ -0,0 +1,10 @@ +context->cart; + $payabbhi = new payabbhi(); + $payabbhi->execPayment($cart); + + $this->context->smarty->assign(array( + 'nbProducts' => $cart->nbProducts(), + 'total' => $cart->getOrderTotal(true, Cart::BOTH), + 'this_path' => $this->module->getPathUri(), + 'this_path_bw' => $this->module->getPathUri(), + 'this_path_ssl' => Tools::getShopDomainSsl(true, true).__PS_BASE_URI__.'modules/'.$this->module->name.'/' + ) + ); + + $this->setTemplate('payment_execution.tpl'); + } +} diff --git a/payabbhi/controllers/front/validation.php b/payabbhi/controllers/front/validation.php new file mode 100644 index 0000000..60c43d3 --- /dev/null +++ b/payabbhi/controllers/front/validation.php @@ -0,0 +1,70 @@ + $payment_id, + 'order_id' => $_REQUEST['order_id'], + 'payment_signature' => $_REQUEST['payment_signature'], + ); + + $cart = $this->context->cart; + $cart_id = $cart->id; + + $payabbhi = new Payabbhi(); + $success = true; + + $accessID = Configuration::get('PAYABBHI_ACCESS_ID'); //TODO fetch from $this if possible + $secretKey = Configuration::get('PAYABBHI_SECRET_KEY'); + $client = new \Payabbhi\Client($accessID, $secretKey); + + try { + $client->utility->verifyPaymentSignature($attributes); + $payment = $client->payment->retrieve($payment_id); + } catch (\Payabbhi\Error $e) { + $success = false; + $error = 'Prestashop Error: Payment failed because signature verification error'; + } + + if ($success == true) + { + $customer = new Customer($cart->id_customer); + $total = (float) $cart->getOrderTotal(true, Cart::BOTH); + $payabbhi->validateOrder($cart_id, _PS_OS_PAYMENT_, $total, $payabbhi->displayName . ' (' . $payment->method . ')', '', array(), NULL, false, $customer->secure_key); + + Logger::addLog("Payment Successful for Order#" . $cart_id . ". Payabbhi payment ID: " . $payment_id . ". Payabbhi order ID: " . $_REQUEST['order_id'], 1); + + $order = new Order((int)$payabbhi->currentOrder); + $payments = $order->getOrderPayments(); + + if (!empty($payments)) { + $payments[0]->transaction_id = $payment_id; + $payments[0]->update(); + } + + $query = http_build_query(array( + 'controller' => 'order-confirmation', + 'id_cart' => (int) $cart->id, + 'id_module' => (int) $this->module->id, + 'id_order' => $payabbhi->currentOrder + ), '', '&'); + + $url = 'index.php?'. $query; + Tools::redirect($url); + } + else + { + Logger::addLog("Payment Failed for Order# ". $cart_id . "Error: ". $error, 4); + echo 'Error! Please contact the seller directly for assistance.
'; + echo 'Order Id: '.$cart_id.'
'; + echo 'Error: '.str_replace(' ', ' ', ucwords(str_replace('_', ' ', $response_code))).'
'; + } + + } +} diff --git a/payabbhi/index.php b/payabbhi/index.php new file mode 100644 index 0000000..01f7806 --- /dev/null +++ b/payabbhi/index.php @@ -0,0 +1,10 @@ +setAppInfo('app_name','app_version','app_url'); +``` + +### Orders + +```php + +// Create order +$order = $client->order->create(array('merchant_order_id' => $merchantOrderID, + 'amount' => $amount, + 'currency' => $currency)); + +// Retrieve a particular order object +$order = $client->order->retrieve($orderId); + +// Retrieve a set of order objects based on given filter params +$orders = $client->order->all(array('count' => 2)); + +// Retrieve a set of payments for a given order +$payments = $client->order->retrieve($orderId)->payments(); +``` + +### Payments + +```php +// Retrieve all payments +$payments = $client->payment->all(); + +// Retrieve a particular payment +$payment = $client->payment->retrieve($id); + +// Capture a payment +$payment->capture(); + +// Fully Refund a payment +$refund = $payment->refund(); + +// Retrieve a set of refund objects for a given payment with optional filter params +$refunds = $payment->refunds(); +``` + + +### Refunds + +```php +// Create a refund +$fullRefund = $client->refund->create($paymentID); + +// Create a partial refund +$partialRefund = $client->refund->create($paymentID, array('amount'=>$refundAmount)); + +// Retrieve a set of orders with the given filter params +$refunds = $client->refund->all(array('count' => 2)); + +// Retrieve a particular refund object +$refund = $client->refund->retrieve($refundId); +``` + +### Verifying payment signature + +```php +$attributes = array( + 'payment_id' => $payment_id, + 'order_id' => $order_id, + 'payment_signature' => $payment_signature + ); +$client->utility->verifyPaymentSignature($attributes); + +``` + +## Development + +Install dependencies: + +``` bash +$ composer install +``` + +## Tests + +Install dependencies as mentioned above (which will resolve [PHPUnit](http://packagist.org/packages/phpunit/phpunit)), set ACCESS_ID and SECRET_KEY as environment variable then you can run the test suite: + +```bash +$ export ACCESS_ID="" +$ export SECRET_KEY="" +$ ./vendor/bin/phpunit +``` + +Or to run an individual test file: + +```bash +$ export ACCESS_ID="" +$ export SECRET_KEY="" +$ ./vendor/bin/phpunit tests/PaymentTest.php +``` diff --git a/payabbhi/payabbhi-php/VERSION b/payabbhi/payabbhi-php/VERSION new file mode 100644 index 0000000..3eefcb9 --- /dev/null +++ b/payabbhi/payabbhi-php/VERSION @@ -0,0 +1 @@ +1.0.0 diff --git a/payabbhi/payabbhi-php/composer.json b/payabbhi/payabbhi-php/composer.json new file mode 100644 index 0000000..f1c8680 --- /dev/null +++ b/payabbhi/payabbhi-php/composer.json @@ -0,0 +1,35 @@ +{ + "name": "payabbhi/payabbhi-php", + "description": "Payabbhi PHP package provides client library for Payabbhi API's", + "keywords": [ + "payabbhi", + "payment processing", + "api" + ], + "homepage": "https://payabbhi.com/", + "type": "library", + "license": "MIT", + "authors": [ + { + "name": "Payabbhi Team", + "homepage": "http://github.com/payabbhi" + } + ], + "require": { + "php": ">=5.3.0", + "ext-curl": "*", + "ext-json": "*" + }, + "require-dev": { + "phpunit/phpunit": "~4.0", + "satooshi/php-coveralls": "~0.6.1" + }, + "config": { + "bin-dir": "bin" + }, + "autoload": { + "psr-0": { + "Payabbhi": "src/" + } + } +} diff --git a/payabbhi/payabbhi-php/init.php b/payabbhi/payabbhi-php/init.php new file mode 100644 index 0000000..951e43d --- /dev/null +++ b/payabbhi/payabbhi-php/init.php @@ -0,0 +1,33 @@ + + + + tests + + + + + src + + + + + + diff --git a/payabbhi/payabbhi-php/src/Payabbhi/ApiResource.php b/payabbhi/payabbhi-php/src/Payabbhi/ApiResource.php new file mode 100644 index 0000000..1f51977 --- /dev/null +++ b/payabbhi/payabbhi-php/src/Payabbhi/ApiResource.php @@ -0,0 +1,262 @@ +id)) { + throw new Error\InvalidRequest("Object Identifier not set"); + } + return $this->id; + } + + protected function _retrieve($id) + { + return $this->_request(static::instanceUrl($id), "GET", null); + } + + protected function _all($params = null) + { + $this->_validateParams($params); + return $this->_request(static::classUrl(), "GET", $params); + } + + /** + * @param array $params + * @throws Error\InvalidRequest + */ + public static function _validateParams($params = null) + { + if ($params && !is_array($params)) { + $message = "You must pass an array as the first argument to Payabbhi API method calls."; + throw new Error\InvalidRequest($message); + } + } + + + protected function _request($url, $method, $params) + { + $response = CurlClient::instance()->request($url, $method, $params); + if ((isset($response->object)) and + ($response->object == $this->getObject())) + { + $this->update($response); + + return $this; + } + else + { + return static::buildObject($response); + } + } + + public static function convertObjectToArray($values) + { + $results = array(); + foreach ($values as $k => $v) { + if ($v instanceof Collection || $v instanceof Order || $v instanceof Payment || $v instanceof Refund) { + $results[$k] = $v->__toArray(true); + } elseif (is_array($v)) { + $results[$k] = self::convertObjectToArray($v); + } else { + $results[$k] = $v; + } + } + return $results; + } + + + protected static function buildObject($data) + { + $objects = static::getObjectsArray(); + if (isset($data->object)) + { + + if (in_array($data->object, $objects)) + { + $class = static::getObjectClass($data->object); + if ($class == "Payabbhi\List") { + $object = new Collection; + } else { + $object = new $class; + } + + } + else + { + $object = new static; + } + } + else + { + $object = new static; + } + + $object->update($data); + + return $object; + } + + protected static function getObjectsArray() + { + return array( + 'list', + 'payment', + 'refund', + 'order'); + } + + protected static function getObjectClass($name) + { + return __NAMESPACE__.'\\'.ucfirst($name); + } + + protected function getObject() + { + $class = get_class($this); + $pos = strrpos($class, '\\'); + $object = strtolower(substr($class, $pos + 1)); + if ($object == 'list') { + $object = 'collection'; + } + + return $object; + } + public function update($data) + { + $attributes = array(); + + foreach ($data as $key => $value) + { + if (is_array($value)) + { + if (static::isAssocArray($value) === false) + { + $collection = array(); + + foreach ($value as $v) + { + if (is_array($v)) + { + $object = static::buildObject($v); + array_push($collection, $object); + } + else + { + array_push($collection, static::buildObject($v)); + } + } + + $value = $collection; + } + else + { + $value = static::buildObject($value); + } + } + else if(is_object($value)) + { + $value = static::buildObject($value); + } + + $attributes[$key] = $value; + } + + $this->attributes = $attributes; + } + + public static function isAssocArray($arr) + { + return array_keys($arr) !== range(0, count($arr) - 1); + } + + public function toArray() + { + return $this->convertToArray($this->attributes); + } + + protected function convertToArray($attributes) + { + $array = $attributes; + + foreach ($attributes as $key => $value) + { + if (is_object($value)) + { + $array[$key] = $value->toArray(); + } + else if (is_array($value) and self::isAssocArray($value) == false) + { + $array[$key] = $this->convertToArray($value); + } + } + + return $array; + } + + public function __toJSON() + { + if (defined('JSON_PRETTY_PRINT')) { + return json_encode($this->__toArray(true), JSON_PRETTY_PRINT| JSON_UNESCAPED_SLASHES); + } else { + return json_encode($this->__toArray(true)); + } + } + + + public function __toString() + { + return $this->__toJSON(); + } + + public function __toArray($recursive = false) + { + if ($recursive) { + return self::convertObjectToArray($this->attributes); + } else { + return $this->attributes; + } + } +} diff --git a/payabbhi/payabbhi-php/src/Payabbhi/ArrayableInterface.php b/payabbhi/payabbhi-php/src/Payabbhi/ArrayableInterface.php new file mode 100644 index 0000000..17ca530 --- /dev/null +++ b/payabbhi/payabbhi-php/src/Payabbhi/ArrayableInterface.php @@ -0,0 +1,13 @@ +attributes['total_count'])) + { + return $this->attributes['total_count']; + } + + return $totalCount; + } +} diff --git a/payabbhi/payabbhi-php/src/Payabbhi/Error/Api.php b/payabbhi/payabbhi-php/src/Payabbhi/Error/Api.php new file mode 100644 index 0000000..a353d0b --- /dev/null +++ b/payabbhi/payabbhi-php/src/Payabbhi/Error/Api.php @@ -0,0 +1,8 @@ +httpStatus = $httpStatus; + $this->description = $description; + $this->field = $field; + $this->message = $this->errorMessage(); + } + + function __toString() + { + return $this->message; + } + + public function getHttpStatus() + { + return $this->httpStatus; + } + + public function getDescription() + { + return $this->description; + } + + public function getField() + { + return $this->field; + } + + public function getHttpHeaders() + { + return $this->httpHeaders; + } + + private function errorMessage() + { + $msg = "message: " . $this->description; + $msg = empty($this->httpStatus) ? $msg : ($msg . ", http_code: " . $this->httpStatus); + $msg = empty($this->field) ? $msg : ($msg . ", field: " . $this->field); + + return $msg . "\n"; + } + +} diff --git a/payabbhi/payabbhi-php/src/Payabbhi/Error/Gateway.php b/payabbhi/payabbhi-php/src/Payabbhi/Error/Gateway.php new file mode 100644 index 0000000..2bcb642 --- /dev/null +++ b/payabbhi/payabbhi-php/src/Payabbhi/Error/Gateway.php @@ -0,0 +1,8 @@ +timeout = (int) max($seconds, 0); + return $this; + } + + /** + * @return int timeout + */ + public function getTimeout() + { + return $this->timeout; + } + public function setConnectTimeout($seconds) + { + $this->connectTimeout = (int) max($seconds, 0); + return $this; + } + + /** + * @return int connectTimeout + */ + public function getConnectTimeout() + { + return $this->connectTimeout; + } + + /** + * @return mixed curl handler + * @throws Error\Api + */ + private function getCurlHandler() + { + $ch = curl_init(); + if (!$ch) { + throw new Error\Api("Could not initialize curl handler"); + } + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->connectTimeout); + curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); + curl_setopt($ch, CURLOPT_HEADER, true); + return $ch; + } + + /** + * @param mixed $ch + * @param string $path + * @param null|array $params + */ + public static function buildGETRequest($ch, $path, $params) + { + $url = Client::baseUrl() . $path; + if ($params != null) { + foreach ($params as $key => $value){ + if (gettype($value) == "boolean"){ + $params[$key] = ($value) ? 'true' : 'false'; + } + } + $query = http_build_query($params); + $url = $url . '?' . $query; + } + curl_setopt($ch, CURLOPT_URL, $url); + } + + /** + * @param mixed $ch + * @param string $path + * @param null|array $params + * @return mixed json encoded body + * @throws Error\InvalidRequest + */ + public static function buildPOSTRequest($ch, $path, $params) + { + $url = Client::baseUrl() . $path; + $body = json_encode($params); + switch (json_last_error()) { + case JSON_ERROR_NONE: + break; + default: + curl_close($ch); + throw new Error\InvalidRequest("Error in request payload formation"); + } + curl_setopt($ch, CURLOPT_POSTFIELDS, $body); + curl_setopt($ch, CURLOPT_URL, $url); + } + + /** + * @param mixed $ch + * @return string $url + */ + public static function getPath($ch) + { + $url = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL); + $parsed_url = parse_url($url); + $path = $parsed_url['path']; + $url = isset($parsed_url['query']) ? $path . '?' . $parsed_url['query'] : $path; + return $url; + } + + + /** + * @param mixed $ch + * @param string $method + * @param null|array $body + * @throws Error\Authentication + */ + public static function signRequest($ch) + { + $access_id = Client::getAccessID(); + $secret_key = Client::getSecretKey(); + curl_setopt($ch, CURLOPT_USERPWD, $access_id . ":" . $secret_key); + } + + + /** + * @param mixed $ch + * @param string $path + * @param string $method + * @param null|array $params + * @throws Error\Api + */ + private static function handleHTTPMethod($ch, $path, $method, $params) + { + switch ($method) { + case "GET": + self::buildGETRequest($ch, $path, $params); + self::signRequest($ch); + break; + case "POST": + self::buildPOSTRequest($ch, $path, $params); + self::signRequest($ch); + break; + default: + curl_close($ch); + $msg = 'Unexpected Method: ' . $method; + throw new Error\Api($msg, "method"); + } + } + + /** + * @param mixed $ch + * @throws Error\ApiConnection + */ + public static function handleCurlError($ch) + { + $url = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL); + $errno = curl_errno($ch); + $message = curl_error($ch); + curl_close($ch); + switch ($errno) { + case CURLE_COULDNT_CONNECT: + case CURLE_COULDNT_RESOLVE_HOST: + case CURLE_OPERATION_TIMEOUTED: + $msg = "Could not connect to Payabbhi ($url). Please check your " . "internet connection and try again, or"; + break; + case CURLE_SSL_CACERT: + case CURLE_SSL_PEER_CERTIFICATE: + $msg = "Could not verify Payabbhi's SSL certificate. Please make sure " . "that your network is not intercepting certificates. " . "(Try going to $url in your browser.) " . "If this problem persists,"; + break; + default: + $msg = "Unexpected error communicating with Payabbhi. " . "If this problem persists,"; + } + $msg .= " let us know at support@payabbhi.com."; + $msg .= "\n\n(Network error [errno $errno]: $message)"; + throw new Error\ApiConnection($msg); + + } + + /** + * @param mixed $ch + * @param mixed $result + * @return array + */ + public static function parseRawResponse($ch, $result) + { + $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $header = substr($result, 0, $header_size); + $body = substr($result, $header_size); + return array( + $http_code, + $header, + $body + ); + } + + /** + * @param mixed $ch + * @param mixed $result + * @throws Error\Api + */ + public static function parseResult($ch, $result) + { + $decodedResult = json_decode($result); + switch (json_last_error()) { + case JSON_ERROR_NONE: + return $decodedResult; + default: + curl_close($ch); + throw new Error\Api("Something did not work as expected on our side", null, 500); + } + } + + /** + * @param mixed $ch. + * @param array $decodedResult + * + * @throws Error\InvalidRequest if the error is caused by the invalid request parameters. + * @throws Error\Authentication if the error is caused by invalid keys. + * @throws Error\Api otherwise. + */ + private static function checkHTTPStatusCode($ch, $decodedResult) + { + if (!curl_errno($ch)) { + switch ($http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE)) { + case 200: # OK + break; + case 400: + case 404: + curl_close($ch); + $decodedError = $decodedResult->error; + throw new Error\InvalidRequest($decodedError->message, $decodedError->field, $http_code); + case 401: + curl_close($ch); + $decodedError = $decodedResult->error; + throw new Error\Authentication($decodedError->message, $decodedError->field, $http_code); + case 500: + curl_close($ch); + $decodedError = $decodedResult->error; + throw new Error\Api($decodedError->message, $decodedError->field, $http_code); + case 502: + curl_close($ch); + $decodedError = $decodedResult->error; + if (empty($decodedError->message)) { + throw new Error\Api("Something did not work as expected on our side", null, $http_code); + } + throw new Error\Gateway($decodedError->message, $decodedError->field, $http_code); + default: + curl_close($ch); + $msg = 'Unexpected HTTP code: ' . $http_code; + throw new Error\Api($msg, null, $http_code); + } + } + } + + private static function _formatAppInfo($appInfo) + { + if ($appInfo !== null) { + $string = $appInfo['name']; + if ($appInfo['version'] !== null) { + $string .= '/' . $appInfo['version']; + } + if ($appInfo['url'] !== null) { + $string .= ' (' . $appInfo['url'] . ')'; + } + return $string; + } else { + return null; + } + } + + private static function setDefaultHeaders($ch) + { + $appInfo = \Payabbhi\Client::getAppInfo(); + + $uaString = 'Payabbhi/v1 PhpBindings/' . Client::VERSION; + + $langVersion = phpversion(); + $uname = php_uname(); + + $httplib = 'unknown'; + $ssllib = 'unknown'; + + if (function_exists('curl_version')) { + $curlVersion = curl_version(); + $httplib = 'curl ' . $curlVersion['version']; + $ssllib = $curlVersion['ssl_version']; + } + + $ua = array( + 'bindings_version' => Client::VERSION, + 'lang' => 'php', + 'lang_version' => $langVersion, + 'publisher' => 'payabbhi', + 'uname' => $uname, + 'httplib' => $httplib, + 'ssllib' => $ssllib, + ); + if ($appInfo !== null) { + $uaString .= ' ' . self::_formatAppInfo($appInfo); + $ua['application'] = $appInfo; + } + + $defaultHeaders = array( + 'Content-Type: application/json', + 'X-Payabbhi-Client-User-Agent: ' . json_encode($ua, JSON_UNESCAPED_SLASHES), + 'User-Agent: '. $uaString + ); + curl_setopt($ch, CURLOPT_HTTPHEADER, $defaultHeaders); + } + + /** + * @param string $path. + * @param string $method + * @param null|array $params + * + * @return $json + */ + public function request($path, $method, $params) + { + $ch = $this->getCurlHandler(); + self::setDefaultHeaders($ch); + self::handleHTTPMethod($ch, $path, $method, $params); + $result = curl_exec($ch); + if (!$result) { + self::handleCurlError($ch); + } + $raw_resp = self::parseRawResponse($ch, $result); + $json = self::parseResult($ch, $raw_resp[2]); + self::checkHTTPStatusCode($ch, $json); + curl_close($ch); + return $json; + } +} diff --git a/payabbhi/payabbhi-php/src/Payabbhi/Order.php b/payabbhi/payabbhi-php/src/Payabbhi/Order.php new file mode 100644 index 0000000..84411cf --- /dev/null +++ b/payabbhi/payabbhi-php/src/Payabbhi/Order.php @@ -0,0 +1,61 @@ +_retrieve($id); + } + + /** + * @param array|null $params + * + * @return Collection of Orders + */ + public function all($params = null) + { + return $this->_all($params); + + } + + /** + * @return Collection of Payments + */ + public function payments() + { + return $this->_request(static::instanceUrl($this->getObjectIdentifier()) . "/payments", "GET",null); + } + + /** + * @param array|null $params. + * + * @return Order + */ + public function create($params) + { + return $this->_request(static::classUrl(), "POST", $params); + } +} diff --git a/payabbhi/payabbhi-php/src/Payabbhi/Payment.php b/payabbhi/payabbhi-php/src/Payabbhi/Payment.php new file mode 100644 index 0000000..f246789 --- /dev/null +++ b/payabbhi/payabbhi-php/src/Payabbhi/Payment.php @@ -0,0 +1,92 @@ +getObjectIdentifier()) . "/refunds", "GET", $params); + } + + /** + * @param string $id The ID of the payment to capture. + * + * @return Payment + */ + public function capture($params = null) + { + return self::_request(static::instanceUrl($this->getObjectIdentifier()) . "/capture", "POST", $params); + } + + /** + * @param array|null $params + * + * @return Refund + */ + public function refund($params = null) + { + $refund = new Refund; + $refund->create($this->getObjectIdentifier(), $params); + self::retrieve($this->getObjectIdentifier()); + return $refund; + } +} diff --git a/payabbhi/payabbhi-php/src/Payabbhi/Refund.php b/payabbhi/payabbhi-php/src/Payabbhi/Refund.php new file mode 100644 index 0000000..7de33ae --- /dev/null +++ b/payabbhi/payabbhi-php/src/Payabbhi/Refund.php @@ -0,0 +1,53 @@ +attributes); + } + + public function offsetExists($offset) + { + return (isset($this->attributes[$offset])); + } + + public function offsetSet($offset, $value) + { + $this->attributes[$offset] = $value; + } + + public function offsetGet($offset) + { + return $this->attributes[$offset]; + } + + public function offsetUnset($offset) + { + unset($this->attributes[$offset]); + } + + public function __get($key) + { + return $this->attributes[$key]; + } + + public function __set($key, $value) + { + return $this->attributes[$key] = $value; + } + + public function __isset($key) + { + return (isset($this->attributes[$key])); + } + + public function __unset($key) + { + unset($this->attributes[$key]); + } +} diff --git a/payabbhi/payabbhi-php/src/Payabbhi/Util/Util.php b/payabbhi/payabbhi-php/src/Payabbhi/Util/Util.php new file mode 100644 index 0000000..915b0e0 --- /dev/null +++ b/payabbhi/payabbhi-php/src/Payabbhi/Util/Util.php @@ -0,0 +1,34 @@ +hashEquals($actualSignature, $expectedSignature)) === false) + { + throw new Error\SignatureVerification('Invalid signature passed'); + } + return true; + } + + private function hashEquals($actualSignature, $expectedSignature) + { + if (!is_string($actualSignature) || !is_string($expectedSignature)) { + return false; + } + + if (strlen($actualSignature) !== strlen($expectedSignature)) { + return false; + } + + $status = 0; + for ($i = 0; $i < strlen($actualSignature); $i++) { + $status |= ord($actualSignature[$i]) ^ ord($expectedSignature[$i]); + } + return $status === 0; + } +} diff --git a/payabbhi/payabbhi-php/tests/ApiConnectionErrorTest.php b/payabbhi/payabbhi-php/tests/ApiConnectionErrorTest.php new file mode 100644 index 0000000..58460b4 --- /dev/null +++ b/payabbhi/payabbhi-php/tests/ApiConnectionErrorTest.php @@ -0,0 +1,50 @@ +assertEmpty($e->getHttpStatus()); + $this->assertContains('errno 28',$e->getDescription()); + $this->assertContains('errno 28',$e->getMessage()); + $this->assertEmpty($e->getField()); + } + } + + public function testInsecureCertificate() + { + try { + $ch = curl_init(); + $url = "https://www.payabbhi.com/"; + curl_setopt($ch, CURLOPT_URL, $url); + $result = curl_exec($ch); + } catch (\Payabbhi\Error\ApiConnection $e) { + $this->assertEmpty($e->getHttpStatus()); + $message = "SSL certificate problem: Invalid certificate chain"; + $msg = "Could not verify Payabbhi's SSL certificate. Please make sure " . "that your network is not intercepting certificates. " . "(Try going to $url in your browser.) " . "If this problem persists,"; + $msg .= " let us know at support@payabbhi.com."; + $msg .= "\n\n(Network error [errno 60]: $message)"; + $this->assertEquals($message,$e->getDescription()); + $this->assertEquals($msg,$e->getMessage()); + $this->assertEmpty($e->getField()); + } + } +} diff --git a/payabbhi/payabbhi-php/tests/ApiResourceTest.php b/payabbhi/payabbhi-php/tests/ApiResourceTest.php new file mode 100644 index 0000000..b0fe4be --- /dev/null +++ b/payabbhi/payabbhi-php/tests/ApiResourceTest.php @@ -0,0 +1,42 @@ +assertEquals("apiresource", $result); + } + + public function testClassUrl() + { + $result = ApiResource::classUrl(); + $this->assertEquals("/api/v1/apiresources", $result); + } + + public function testBadParams() + { + try { + ApiResource::_validateParams("123"); + } catch (\Payabbhi\Error\InvalidRequest $e) { + $message = "You must pass an array as the first argument to Payabbhi API method calls."; + $this->assertNull($e->getHttpStatus()); + $this->assertEquals($message,$e->getDescription()); + $this->assertEquals("message: You must pass an array as the first argument to Payabbhi API method calls.\n",$e->getMessage()); + $this->assertEmpty($e->getField()); + } + } + + public function testArrayParams() + { + $result = ApiResource::_validateParams(Array("123")); + $this->assertNull($result); + } + + +} diff --git a/payabbhi/payabbhi-php/tests/AuthenticationErrorTest.php b/payabbhi/payabbhi-php/tests/AuthenticationErrorTest.php new file mode 100644 index 0000000..2178f10 --- /dev/null +++ b/payabbhi/payabbhi-php/tests/AuthenticationErrorTest.php @@ -0,0 +1,22 @@ +payment->all(); + $this->fail("Did not raise error"); + } catch (\Payabbhi\Error\Authentication $e) { + $this->assertEquals(401, $e->getHttpStatus()); + $this->assertEquals("Incorrect access_id or secret_key provided",$e->getDescription()); + $this->assertEquals("message: Incorrect access_id or secret_key provided, http_code: 401\n",$e->getMessage()); + $this->assertEmpty($e->getField()); + } + } +} diff --git a/payabbhi/payabbhi-php/tests/ClientTest.php b/payabbhi/payabbhi-php/tests/ClientTest.php new file mode 100644 index 0000000..2f59fab --- /dev/null +++ b/payabbhi/payabbhi-php/tests/ClientTest.php @@ -0,0 +1,52 @@ +assertEquals("https://payabbhitest.com", $result); + \Payabbhi\Client::$apiBase = $result; + } + + public function testAccessID() + { + $api = new \Payabbhi\Client("test_access_id","test_secret_key"); + $this->assertEquals("test_access_id",\Payabbhi\Client::getAccessID()); + } + + public function testSecretKey() + { + $api = new \Payabbhi\Client("test_access_id","test_secret_key"); + $this->assertEquals("test_secret_key",\Payabbhi\Client::getSecretKey()); + } + + /** + * @dataProvider AppInfoProvider + */ + public function testAppInfo($name,$version,$url) + { + $api = new \Payabbhi\Client("test_access_id","test_secret_key"); + $api->setAppInfo($name , $version, $url); + $info = \Payabbhi\Client::getAppInfo(); + + $this->assertEquals($name, $info['name']); + $this->assertEquals($version, $info['version']); + $this->assertEquals($url, $info['url']); + + } + + public function AppInfoProvider() + { + return [ + ["TestPayabbhiApplication", null, null], + ["TestPayabbhiApplication","beta",null], + ["TestPayabbhiApplication" , "beta", "https://testpayabbhiapplication.com"] + ]; + } +} diff --git a/payabbhi/payabbhi-php/tests/CurlClientTest.php b/payabbhi/payabbhi-php/tests/CurlClientTest.php new file mode 100644 index 0000000..cdd5d7a --- /dev/null +++ b/payabbhi/payabbhi-php/tests/CurlClientTest.php @@ -0,0 +1,264 @@ +assertSame(CurlClient::DEFAULT_TIMEOUT, $curl->getTimeout()); + $this->assertSame(CurlClient::DEFAULT_CONNECT_TIMEOUT, $curl->getConnectTimeout()); + $curl = $curl->setConnectTimeout(1)->setTimeout(10); + $this->assertSame(1, $curl->getConnectTimeout()); + $this->assertSame(10, $curl->getTimeout()); + $curl->setTimeout(-1); + $curl->setConnectTimeout(-999); + $this->assertSame(0, $curl->getTimeout()); + $this->assertSame(0, $curl->getConnectTimeout()); + $curl->setTimeout("a"); + $curl->setConnectTimeout("b"); + $this->assertSame(0, $curl->getTimeout()); + $this->assertSame(0, $curl->getConnectTimeout()); + + } + + public function testInvalidHTTPMethod() + { + try { + $method= "GETS"; + CurlClient::instance()->request("https://www.google.com",$method,null); + } catch (Api $e){ + $msg = 'Unexpected Method: ' . $method; + $this->assertNull($e->getHttpStatus()); + $this->assertEquals($msg, $e->getDescription()); + $this->assertEquals("message: $msg, field: method\n", $e->getMessage()); + $this->assertEquals("method", $e->getField()); + } + + } + + public function testUrlForGETRequestWithParams() + { + $ch = curl_init(); + $base = \Payabbhi\Client::baseUrl(); + \Payabbhi\Client::$apiBase = "https://payabbhi.com"; + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + $params = array('foo'=>'bar', + 'baz'=>'boom', + 'cow'=>'milk', + 'php'=>'hypertext processor'); + CurlClient::buildGETRequest($ch,"/api/v1/check",$params); + curl_exec($ch); + $url = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL); + \Payabbhi\Client::$apiBase = $base; + $this->assertEquals($url,"https://payabbhi.com/api/v1/check?foo=bar&baz=boom&cow=milk&php=hypertext+processor"); + } + + public function testUrlForGETRequestWithNoParams() + { + $ch = curl_init(); + $base = \Payabbhi\Client::baseUrl(); + \Payabbhi\Client::$apiBase = "https://payabbhi.com"; + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + CurlClient::buildGETRequest($ch,"/api/v1/check",null); + curl_exec($ch); + $url = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL); + \Payabbhi\Client::$apiBase = $base; + $this->assertEquals($url,"https://payabbhi.com/api/v1/check"); + } + + public function testUrlForPOSTRequestWithParams() + { + $ch = curl_init(); + $base = \Payabbhi\Client::baseUrl(); + \Payabbhi\Client::$apiBase = "https://payabbhi.com"; + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + $params = array('foo'=>'bar', + 'baz'=>'boom', + 'cow'=>'milk', + 'php'=>'hypertext processor'); + CurlClient::buildPOSTRequest($ch,"/api/v1/check",$params); + curl_exec($ch); + $url = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL); + \Payabbhi\Client::$apiBase = $base; + $this->assertEquals("https://payabbhi.com/api/v1/check",$url); + } + + public function testUrlForPOSTRequestWithParamsAsNull() + { + $ch = curl_init(); + $base = \Payabbhi\Client::baseUrl(); + \Payabbhi\Client::$apiBase = "https://payabbhi.com"; + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + CurlClient::buildPOSTRequest($ch,"/api/v1/check",null); + curl_exec($ch); + $url = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL); + \Payabbhi\Client::$apiBase = $base; + $this->assertEquals("https://payabbhi.com/api/v1/check",$url); + } + + public function testErrorForInvalidPayload() + { + try { + $ch = curl_init(); + $params = "\xB1\x31"; + CurlClient::buildPOSTRequest($ch,"/api/v1/check",$params); + } catch (InvalidRequest $e){ + $msg = "Error in request payload formation"; + $this->assertNull($e->getHttpStatus()); + $this->assertEquals($msg, $e->getDescription()); + $this->assertEquals("message: $msg\n", $e->getMessage()); + $this->assertNull($e->getField()); + } + } + + public function testGetPathForUrl() + { + $ch = curl_init(); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + $url = "https://payabbhi.com/search?hl=en&client=firefox-a&hs=42F"; + curl_setopt($ch, CURLOPT_URL, $url); + curl_exec($ch); + $this->assertEquals(CurlClient::getPath($ch),"/search?hl=en&client=firefox-a&hs=42F"); + } + + /** + * @dataProvider CredentialProvider + */ + public function testErrorForEmptyCredentials($accessid, $secretkey, $method) + { + try { + $ch = curl_init(); + \Payabbhi\Client::$apiBase = "https://payabbhi.com"; + $api = new \Payabbhi\Client($accessid,$secretkey); + $api->payment->all(); + $this->fail("Did not raise error"); + } catch (Authentication $e){ + $msg = "Incorrect access_id or secret_key provided"; + $this->assertEquals(401,$e->getHttpStatus()); + $this->assertEquals($msg, $e->getDescription()); + $this->assertEquals("message: $msg, http_code: 401\n", $e->getMessage()); + $this->assertEmpty($e->getField()); + } + } + + public function CredentialProvider() + { + return [ + ["","secret_key","GET"], + ["","secret_key","POST"], + ["access_id","","GET"], + ["access_id","","POST"], + ["","","GET"], + ["","","POST"] + ]; + } + + private function parseHeader($header) + { + $myarray=array(); + $data=explode("\n",$header); + $myarray['status']=$data[0]; + array_shift($data); + array_pop($data); + array_pop($data); + foreach($data as $part) + { + $middle=explode(":",$part); + $myarray[trim($middle[0])] = trim($middle[1]); + } + return $myarray; + } + + public function testSignRequestInCurlHeaderForGET() + { + $ch = curl_init(); + $methodGET = "GET"; + $url = "http://payabbhi.com/search?hl=en&client=firefox-a&hs=42F"; + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch,CURLINFO_HEADER_OUT,true); + $api = new \Payabbhi\Client("access_id","secret_key"); + CurlClient::signRequest($ch,$methodGET); + curl_exec($ch); + $headerarray = self::parseHeader(curl_getinfo($ch,CURLINFO_HEADER_OUT)); + $this->assertRegExp('/Basic/', $headerarray['Authorization']); + } + + public function testSignRequestInCurlHeaderForPOST() + { + $ch = curl_init(); + $methodPOST = "POST"; + $url = "http://payabbhi.com/search?hl=en&client=firefox-a&hs=42F"; + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch,CURLINFO_HEADER_OUT,true); + $api = new \Payabbhi\Client("access_id","secret_key"); + $params = array('foo'=>'bar', + 'baz'=>'boom', + 'cow'=>'milk', + 'php'=>'hypertext processor'); + $body =json_encode($params); + CurlClient::signRequest($ch,$methodPOST,$body); + curl_exec($ch); + $headerarray = self::parseHeader(curl_getinfo($ch,CURLINFO_HEADER_OUT)); + $this->assertRegExp('/Basic/', $headerarray['Authorization']); + } + + public function testNullResultForParseResult() + { + $ch = curl_init(); + curl_exec($ch); + $this->assertNull(CurlClient::parseResult($ch,null)); + } + + public function testJsonResultForParseResult() + { + $ch = curl_init(); + curl_exec($ch); + $this->assertEquals(CurlClient::parseResult($ch,'{"foo_bar": 12345}')->foo_bar,12345); + } + + public function testErrorForBadJsonForParseResult() + { + try { + $ch = curl_init(); + curl_exec($ch); + } catch (Api $e) { + $this->assertNull($e->getHttpStatus()); + $msg = "Something did not work as expected on our side"; + $this->assertEquals($msg, $e->getDescription()); + $this->assertEquals("description: $msg\n", $e->getMessage()); + $this->assertEmpty($e->getField()); + } + } + + public function testParsingRawHTTPResponse() + { + $ch = curl_init(); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HEADER, true); + curl_setopt($ch,CURLINFO_HEADER_OUT,true); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); + $url = "https://payabbhi.com/api/v1/payments"; + curl_setopt($ch, CURLOPT_URL, $url); + $params = array('foo'=>'bar', + 'baz'=>'boom', + 'cow'=>'milk', + 'php'=>'hypertext processor'); + $result =curl_exec($ch); + $resp = CurlClient::parseRawResponse($ch,$result); + $this->assertTrue(is_array($resp)); + $this->assertEquals($resp[0],401); + $this->assertContains("Content-Type: application/json; charset=UTF-8",$resp[1]); + $this->assertCount(3,$resp); + } + + +} diff --git a/payabbhi/payabbhi-php/tests/InvalidRequestErrorTest.php b/payabbhi/payabbhi-php/tests/InvalidRequestErrorTest.php new file mode 100644 index 0000000..3c0a419 --- /dev/null +++ b/payabbhi/payabbhi-php/tests/InvalidRequestErrorTest.php @@ -0,0 +1,36 @@ +payment->retrieve("abcd")->refund(); + } catch (\Payabbhi\Error\InvalidRequest $e) { + $this->assertEquals(400, $e->getHttpStatus()); + $this->assertEquals("The request has invalid parameters",$e->getDescription()); + $this->assertEquals("message: The request has invalid parameters, http_code: 400, field: payment_id\n",$e->getMessage()); + $this->assertEquals("payment_id", $e->getField()); + } + } + + public function testBadURLPath() + { + try { + $api = self::authorizeFromEnv(); + CurlClient::instance()->request("/api/v2/payments","GET",null); + } catch (\Payabbhi\Error\InvalidRequest $e) { + $this->assertEquals(404, $e->getHttpStatus()); + $this->assertEquals("Request URL GET /api/v2/payments does not exist. Please check documentation",$e->getDescription()); + $this->assertEquals("message: Request URL GET /api/v2/payments does not exist. Please check documentation, http_code: 404\n",$e->getMessage()); + $this->assertEmpty($e->getField()); + } + } +} diff --git a/payabbhi/payabbhi-php/tests/OrderTest.php b/payabbhi/payabbhi-php/tests/OrderTest.php new file mode 100644 index 0000000..f78a4d5 --- /dev/null +++ b/payabbhi/payabbhi-php/tests/OrderTest.php @@ -0,0 +1,18 @@ +order->all(); + $this->assertTrue(is_int($a->total_count)); + $this->assertTrue(is_string($a->object)); + $this->assertTrue(is_array($a->data)); + $this->assertEquals("list",($a->object)); + } +} diff --git a/payabbhi/payabbhi-php/tests/PayabbhiTestCase.php b/payabbhi/payabbhi-php/tests/PayabbhiTestCase.php new file mode 100644 index 0000000..330ffff --- /dev/null +++ b/payabbhi/payabbhi-php/tests/PayabbhiTestCase.php @@ -0,0 +1,20 @@ +payment->all(); + $this->assertTrue(is_int($a->total_count)); + $this->assertTrue(is_string($a->object)); + $this->assertTrue(is_array($a->data)); + $this->assertEquals("list",($a->object)); + } + +} diff --git a/payabbhi/payabbhi-php/tests/RefundTest.php b/payabbhi/payabbhi-php/tests/RefundTest.php new file mode 100644 index 0000000..9246d75 --- /dev/null +++ b/payabbhi/payabbhi-php/tests/RefundTest.php @@ -0,0 +1,18 @@ +refund->all(); + $this->assertTrue(is_int($a->total_count)); + $this->assertTrue(is_string($a->object)); + $this->assertTrue(is_array($a->data)); + $this->assertEquals("list",($a->object)); + } +} diff --git a/payabbhi/payabbhi-php/tests/UtilTest.php b/payabbhi/payabbhi-php/tests/UtilTest.php new file mode 100644 index 0000000..d50c268 --- /dev/null +++ b/payabbhi/payabbhi-php/tests/UtilTest.php @@ -0,0 +1,24 @@ +assertSame(Util::utf8($x), $x); + + // Latin-1 string + $x = "\xe9"; + $this->assertSame(Util::utf8($x), "\xc3\xa9"); + + // Not a string + $x = true; + $this->assertSame(Util::utf8($x), $x); + } +} + +?> diff --git a/payabbhi/payabbhi-php/tests/UtilityTest.php b/payabbhi/payabbhi-php/tests/UtilityTest.php new file mode 100644 index 0000000..f6b045e --- /dev/null +++ b/payabbhi/payabbhi-php/tests/UtilityTest.php @@ -0,0 +1,50 @@ +assertSame($api->utility->verifyPaymentSignature(array('payment_id'=>'pay_123','order_id'=>'order_test','payment_signature'=>'31b8de6a27e9911fe8828d80fda4c2c7cefdf332c06eeaa734a0708af6cafd87')),true); + + } + + public function testverifyPaymentSignatureError() + { + try { + $api = new \Payabbhi\Client("BKa70BUicoKuQnZ6_ZSOJIWbi6nCO8mL83-4DJTcxdU=", "exq4JxHbTtMS9IM5hnoPNkdMXKW-ws36y4RgGtzGeGg="); + $api->utility->verifyPaymentSignature(array('payment_id'=>'pay_123','order_id'=>'order_test','payment_signature'=>'wrong_signature')); + $this->fail("Did not raise error"); + } catch (\Payabbhi\Error\SignatureVerification $e) { + $this->assertEquals("Invalid signature passed",$e->getDescription()); + $this->assertEquals("message: Invalid signature passed\n",$e->getMessage()); + $this->assertEmpty($e->getField()); + } + } + + public function testverifySignatureError() + { + try { + $api = new \Payabbhi\Client("BKa70BUicoKuQnZ6_ZSOJIWbi6nCO8mL83-4DJTcxdU=", "exq4JxHbTtMS9IM5hnoPNkdMXKW-ws36y4RgGtzGeGg="); + $api->utility->verifySignature('pay_123&order_test', 'wrong_signature'); + $this->fail("Did not raise error"); + } catch (\Payabbhi\Error\SignatureVerification $e) { + $this->assertEquals("Invalid signature passed",$e->getDescription()); + $this->assertEquals("message: Invalid signature passed\n",$e->getMessage()); + $this->assertEmpty($e->getField()); + } + } + + public function testverifySignature() + { + $api = new \Payabbhi\Client("BKa70BUicoKuQnZ6_ZSOJIWbi6nCO8mL83-4DJTcxdU=", "exq4JxHbTtMS9IM5hnoPNkdMXKW-ws36y4RgGtzGeGg="); + $this->assertSame($api->utility->verifySignature('pay_123&order_test', '31b8de6a27e9911fe8828d80fda4c2c7cefdf332c06eeaa734a0708af6cafd87'),true); + + } + +} + +?> diff --git a/payabbhi/payabbhi-php/tests/bootstrap.php b/payabbhi/payabbhi-php/tests/bootstrap.php new file mode 100644 index 0000000..9f569c0 --- /dev/null +++ b/payabbhi/payabbhi-php/tests/bootstrap.php @@ -0,0 +1,3 @@ +name = 'payabbhi'; + $this->tab = 'payments_gateways'; + $this->version = '1.0.0'; + $this->author = 'Payabbhi Team'; + $this->need_instance = 0; + $this->ps_versions_compliancy = array('min' => '1.6', 'max' => _PS_VERSION_); + $this->bootstrap = true; + + $config = Configuration::getMultiple(array('PAYABBHI_ACCESS_ID', 'PAYABBHI_SECRET_KEY', 'PAYABBHI_PAYMENT_AUTO_CAPTURE')); + + $accessID = $config['PAYABBHI_ACCESS_ID']; + $secretKey = $config['PAYABBHI_SECRET_KEY']; + $paymentAutoCapture = $config['PAYABBHI_PAYMENT_AUTO_CAPTURE']; + + if (!empty($accessID)) { + $this->access_id = $config['PAYABBHI_ACCESS_ID']; + } + + if (!empty($secretKey)){ + $this->secret_key = $config['PAYABBHI_SECRET_KEY']; + } + + if (!empty($paymentAutoCapture)) { + $this->payment_auto_capture = $config['PAYABBHI_PAYMENT_AUTO_CAPTURE']; + } else { + $this->payment_auto_capture = 'true'; + } + + parent::__construct(); + + $this->displayName = $this->l('Payabbhi'); + $this->description = $this->l('Prestashop module for accepting payments with Payabbhi.'); + $this->confirmUninstall = $this->l('Are you sure you want to uninstall?'); + + + if (!isset($this->access_id)) { + $this->warning = $this->l('Payabbhi Acccess ID not set.'); + } + + if (!isset($this->secret_key)) { + $this->warning = $this->l('Payabbhi Secret Key not set.'); + } + } + + public function install() + { + if (Shop::isFeatureActive()) { + Shop::setContext(Shop::CONTEXT_ALL); + } + + if (!parent::install() || !$this->registerHook('payment') || !$this->registerHook('paymentReturn')) { + return false; + } + + if (!Configuration::updateValue('PAYABBHI_ACCESS_ID', '') + || !Configuration::updateValue('PAYABBHI_SECRET_KEY', '') + || !Configuration::updateValue('PAYABBHI_PAYMENT_AUTO_CAPTURE', '')) { + return false; + } + + return true; + } + + public function uninstall() + { + if (!Configuration::deleteByName('PAYABBHI_ACCESS_ID') + || !Configuration::deleteByName('PAYABBHI_SECRET_KEY') + || !Configuration::deleteByName('PAYABBHI_PAYMENT_AUTO_CAPTURE') + || !parent::uninstall()) { + return false; + } + return true; + } + + private function checkConfig($accessID, $secretKey) + { + if (!$accessID || empty($accessID)) { + return false; + } + + if (!$secretKey || empty($secretKey)) { + return false; + } + + return true; + } + + public function getContent() + { + $output = null; + + if (Tools::isSubmit('submit'.$this->name)) + { + $accessID = strval(Tools::getValue('PAYABBHI_ACCESS_ID')); + $secretKey = strval(Tools::getValue('PAYABBHI_SECRET_KEY')); + $paymentAutoCapture = strval(Tools::getValue('PAYABBHI_PAYMENT_AUTO_CAPTURE')); + + if (!$this->checkConfig($accessID,$secretKey)) + { + $output .= $this->displayError($this->l('Invalid Configuration value')); + } + else + { + Configuration::updateValue('PAYABBHI_ACCESS_ID', $accessID); + Configuration::updateValue('PAYABBHI_SECRET_KEY', $secretKey); + Configuration::updateValue('PAYABBHI_PAYMENT_AUTO_CAPTURE', $paymentAutoCapture); + $output .= $this->displayConfirmation($this->l('Settings updated')); + } + } + return $output.$this->displayForm(); + } + + public function displayForm() + { + // Get default language + $default_lang = (int)Configuration::get('PS_LANG_DEFAULT'); + + // Init Fields form array + $fields_form[0]['form'] = array( + 'legend' => array( + 'title' => $this->l('Settings'), + ), + 'input' => array( + array( + 'type' => 'text', + 'label' => $this->l('Access ID'), + 'desc' => $this->l('Access ID is available as part of API keys downloaded from the Portal'), + 'name' => 'PAYABBHI_ACCESS_ID', + 'size' => 20, + 'required' => true + ), + array( + 'type' => 'text', + 'label' => $this->l('Secret Key'), + 'desc' => $this->l('Secret Key is available as part of API keys downloaded from the Portal'), + 'name' => 'PAYABBHI_SECRET_KEY', + 'size' => 20, + 'required' => true + ), + array( + 'type' => 'select', + 'label' => $this->l('Payment Auto Capture'), + 'desc' => $this->l('Specify whether the payment should be captured automatically. Refer to Payabbhi API Reference.'), + 'name' => 'PAYABBHI_PAYMENT_AUTO_CAPTURE', + 'options' => array( + 'id' => 'id_option', + 'name' => 'name', + 'query' => array( + array( + 'id_option' => 'true', + 'name' => 'True' + ), + array( + 'id_option' => 'false', + 'name' => 'False' + ) + ) + ) + ) + ), + 'submit' => array( + 'title' => $this->l('Save'), + 'class' => 'btn btn-default pull-right' + ) + ); + + $helper = new HelperForm(); + + // Module, token and currentIndex + $helper->module = $this; + $helper->name_controller = $this->name; + $helper->token = Tools::getAdminTokenLite('AdminModules'); + $helper->currentIndex = AdminController::$currentIndex.'&configure='.$this->name; + + // Language + $helper->default_form_language = $default_lang; + $helper->allow_employee_form_lang = $default_lang; + + // Title and toolbar + $helper->title = $this->displayName; + $helper->show_toolbar = true; // false -> remove toolbar + $helper->toolbar_scroll = true; // yes - > Toolbar is always visible on the top of the screen. + $helper->submit_action = 'submit'.$this->name; + $helper->toolbar_btn = array( + 'save' => + array( + 'desc' => $this->l('Save'), + 'href' => AdminController::$currentIndex.'&configure='.$this->name.'&save'.$this->name. + '&token='.Tools::getAdminTokenLite('AdminModules'), + ), + 'back' => array( + 'href' => AdminController::$currentIndex.'&token='.Tools::getAdminTokenLite('AdminModules'), + 'desc' => $this->l('Back to list') + ) + ); + + // Load current value + $helper->fields_value['PAYABBHI_ACCESS_ID'] = Configuration::get('PAYABBHI_ACCESS_ID'); + $helper->fields_value['PAYABBHI_SECRET_KEY'] = Configuration::get('PAYABBHI_SECRET_KEY'); + $helper->fields_value['PAYABBHI_PAYMENT_AUTO_CAPTURE'] = Configuration::get('PAYABBHI_PAYMENT_AUTO_CAPTURE'); + return $helper->generateForm($fields_form); + } + + public function hookPayment($params) + { + global $smarty,$cart; + $smarty->assign(array( + 'this_path' => $this->_path, + 'this_path_ssl' => Configuration::get('PS_FO_PROTOCOL').$_SERVER['HTTP_HOST'].__PS_BASE_URI__."modules/{$this->name}/")); + return $this->display(__FILE__, 'payment.tpl'); + } + + protected function verify_order_amount($payabbhi_order_id, $cart) { + $accessID = Configuration::get('PAYABBHI_ACCESS_ID'); + $secretKey = Configuration::get('PAYABBHI_SECRET_KEY'); + + $client = new \Payabbhi\Client($accessID, $secretKey); + + try { + $payabbhi_order = $client->order->retrieve($payabbhi_order_id); + } catch(Exception $e) { + return false; + } + + $payabbhi_order_args = array( + 'id' => $payabbhi_order_id, + 'amount' => (int) (number_format($cart->getOrderTotal(true, 3), 2, '.', '')*100), + 'currency' => 'INR', + 'merchant_order_id' => (string) $cart->id, + ); + + $orderKeys = array_keys($payabbhi_order_args); + + foreach ($orderKeys as $key) + { + if ($payabbhi_order_args[$key] !== $payabbhi_order[$key]) + { + return false; + } + } + + return true; + } + + protected function create_payabbhi_order($cart) { + global $cookie; + + $accessID = Configuration::get('PAYABBHI_ACCESS_ID'); + $secretKey = Configuration::get('PAYABBHI_SECRET_KEY'); + $autoCapture = Configuration::get('PAYABBHI_PAYMENT_AUTO_CAPTURE'); + + if (!empty($autoCapture)) { + $paymentAutoCapture = ($autoCapture === 'true'); + } else { + $paymentAutoCapture = true; + } + $orderAmount = number_format($cart->getOrderTotal(true, 3), 2, '.', '')*100; + $orderCurrency = 'INR'; + + $orderParams = array('merchant_order_id' => $cart->id, + 'amount' => $orderAmount, + 'currency' => $orderCurrency, + 'payment_auto_capture' => $paymentAutoCapture); + + $client = new \Payabbhi\Client($accessID, $secretKey); + $payabbhi_order_id = $client->order->create($orderParams)->id; + $cookie->payabbhi_order_id = $payabbhi_order_id; + return $payabbhi_order_id; + } + + public function execPayment($cart) + { + global $smarty; + global $cookie; + + $invoice = new Address((int) $cart->id_address_invoice); + $customer = new Customer((int) $cart->id_customer); + + $contact = $invoice->phone; + + if (empty($contact)) { + $contact = $invoice->phone_mobile; + } + + if ($cart->id_currency != 1) + { + $error = 'Payabbhi Error: Currency should only be in INR'; + die(Tools::displayError($error)); + } + + try { + $payabbhi_order_id = $cookie->payabbhi_order_id; + + if (($payabbhi_order_id === false) or + (($payabbhi_order_id and ($this->verify_order_amount($payabbhi_order_id, $cart)) === false))) + { + $payabbhi_order_id = $this->create_payabbhi_order($cart); + } + + } catch (\Payabbhi\Error $e) { + die(Tools::displayError('Payabbhi Error: ' . $e->getMessage())); + } catch (Exception $e) { + die(Tools::displayError('Prestashop Error: ' . $e->getMessage())); + } + + $checkoutArgs = array( + 'access_id' => Configuration::get('PAYABBHI_ACCESS_ID'), + 'order_id' => $payabbhi_order_id, + 'amount' => number_format($cart->getOrderTotal(true, 3), 2, '.', '')*100, + 'name' => Configuration::get('PS_SHOP_NAME'), + 'description' => 'Order #' . $cart->id, + 'prefill' => array( + 'name' => $invoice->firstname . ' ' . $invoice->lastname, + 'email' => $customer->email, + 'contact' => $contact + ), + 'notes' => array( + 'merchant_order_id' => (string) $cart->id + ) + ); + + $checkout_url = 'https://checkout.payabbhi.com/v1/checkout.js'; + $return_url = __PS_BASE_URI__."?fc=module&module=payabbhi&controller=validation"; + + $smarty->assign(array( + 'checkout_url' => $checkout_url, + 'return_url' => $return_url, + 'checkout_args' => Tools::jsonEncode($checkoutArgs), + 'cart_id' => $cart->id + )); + + return $this->display(__FILE__, 'payment_execution.tpl'); + } +} diff --git a/payabbhi/views/index.php b/payabbhi/views/index.php new file mode 100644 index 0000000..01f7806 --- /dev/null +++ b/payabbhi/views/index.php @@ -0,0 +1,10 @@ + + +{capture name=path} +{l s='Checkout' mod='payabbhi'}{$navigationPipe}{l s='Pay With Payabbhi' +mod='payabbhi'} {/capture} + +

+ {l s='Order summary' mod='payabbhi'} +

+ +{assign var='current_step' value='payment'} +{include file="$tpl_dir./order-steps.tpl"} + +{if $nbProducts <= 0} +

{l s='Your shopping cart is empty.' mod='payabbhi'}

+{else} +
+

+ {l s='Payabbhi payment' mod='payabbhi'} +

+

+ + {l s='You have chosen to pay by payabbhi.Here is a short summary of your order:' mod='payabbhi'} + +

+

+ - {l s='The total amount of your order is' mod='payabbhi'} + {displayPrice price=$total} {if $use_taxes == 1} {l s='(tax incl.)' mod='payabbhi'} {/if} +

+

+ - {l s='Please confirm your order by clicking "I confirm my order".' mod='payabbhi'} +

+
+ +
+ + + + +
+ +

+ + {l s='Other payment methods' mod='payabbhi'} + + + +

+ +{/if} + + diff --git a/payabbhi/views/templates/hook/index.php b/payabbhi/views/templates/hook/index.php new file mode 100644 index 0000000..01f7806 --- /dev/null +++ b/payabbhi/views/templates/hook/index.php @@ -0,0 +1,10 @@ + + + {l s='Pay with Card, NetBanking, Wallet' mod='payabbhi'} + (via Payabbhi) + +

+ + diff --git a/payabbhi/views/templates/hook/payment_return.tpl b/payabbhi/views/templates/hook/payment_return.tpl new file mode 100644 index 0000000..5ba9807 --- /dev/null +++ b/payabbhi/views/templates/hook/payment_return.tpl @@ -0,0 +1,5 @@ +{if $status == 'ok'} +

Payment successful

+{else} +

Payment failed

+{/if} diff --git a/payabbhi/views/templates/index.php b/payabbhi/views/templates/index.php new file mode 100644 index 0000000..01f7806 --- /dev/null +++ b/payabbhi/views/templates/index.php @@ -0,0 +1,10 @@ +