diff --git a/.gitignore b/.gitignore index a0787251a..3dd761b9e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ # ignore config file -/tests/config/* +tests/config # Ignore the /vendor/ directory /vendor/ @@ -11,4 +11,4 @@ composer.lock .DS_Store # ignore PhpStorm .idea folder -.idea \ No newline at end of file +.idea diff --git a/.travis.yml b/.travis.yml index 5ad90ffc5..898c8664b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ matrix: allow_failures: - php: nightly include: - - php: 7.1 + - php: 7.3 - php: 7.2 - php: 7.1 - php: 7.0 @@ -16,4 +16,4 @@ matrix: - dist: precise php: 5.3 before_script: - - composer install \ No newline at end of file + - composer install diff --git a/composer.json b/composer.json index 6d74531be..52a3ed556 100644 --- a/composer.json +++ b/composer.json @@ -29,4 +29,4 @@ "Adyen\\Tests\\": "tests/" } } -} \ No newline at end of file +} diff --git a/src/Adyen/AdyenException.php b/src/Adyen/AdyenException.php index 35ddaa58c..6726ccc72 100644 --- a/src/Adyen/AdyenException.php +++ b/src/Adyen/AdyenException.php @@ -16,19 +16,32 @@ class AdyenException extends Exception */ protected $errorType; - /** - * AdyenException constructor. - * - * @param string $message - * @param int $code - * @param Exception|null $previous - * @param null $status - * @param null $errorType - */ - public function __construct($message = "", $code = 0, Exception $previous = null, $status = null, $errorType = null) - { + /** + * @var string + */ + protected $pspReference; + + /** + * AdyenException constructor. + * + * @param string $message + * @param int $code + * @param Exception|null $previous + * @param null $status + * @param null $errorType + * @param null $pspReference + */ + public function __construct( + $message = "", + $code = 0, + Exception $previous = null, + $status = null, + $errorType = null, + $pspReference = null + ) { $this->status = $status; $this->errorType = $errorType; + $this->pspReference = $pspReference; parent::__construct($message, (int)$code, $previous); } @@ -49,4 +62,12 @@ public function getErrorType() { return $this->errorType; } + + /** + * @return string + */ + public function getPspReference() + { + return $this->pspReference; + } } diff --git a/src/Adyen/Client.php b/src/Adyen/Client.php index f0dc18505..803d7b6ad 100644 --- a/src/Adyen/Client.php +++ b/src/Adyen/Client.php @@ -8,7 +8,7 @@ class Client { - const LIB_VERSION = "2.0.0"; + const LIB_VERSION = "2.1.0"; const LIB_NAME = "adyen-php-api-library"; const USER_AGENT_SUFFIX = "adyen-php-api-library/"; const ENDPOINT_TEST = "https://pal-test.adyen.com"; @@ -17,6 +17,7 @@ class Client const ENDPOINT_TEST_DIRECTORY_LOOKUP = "https://test.adyen.com/hpp/directory/v2.shtml"; const ENDPOINT_LIVE_DIRECTORY_LOOKUP = "https://live.adyen.com/hpp/directory/v2.shtml"; const API_PAYMENT_VERSION = "v40"; + const API_BIN_LOOKUP_VERSION = "v40"; const API_PAYOUT_VERSION = "v30"; const API_RECURRING_VERSION = "v25"; const API_CHECKOUT_VERSION = "v41"; @@ -108,6 +109,16 @@ public function setXApiKey($xApiKey) $this->config->set('x-api-key', $xApiKey); } + /** + * Set HTTP proxy information + * + * @param string $proxy + */ + public function setHttpProxy($proxy) + { + $this->config->set('http-proxy', $proxy); + } + /** * Set environment to connect to test or live platform of Adyen * For live please specify the unique identifier. @@ -269,6 +280,16 @@ public function getApiPaymentVersion() return self::API_PAYMENT_VERSION; } + /** + * Get the version of the API BinLookUp endpoint + * + * @return string + */ + public function getApiBinLookupVersion() + { + return self::API_BIN_LOOKUP_VERSION; + } + /** * Get the version of the API Payout endpoint * diff --git a/src/Adyen/Config.php b/src/Adyen/Config.php index 50e30c1a1..2a1a6aad0 100644 --- a/src/Adyen/Config.php +++ b/src/Adyen/Config.php @@ -94,6 +94,16 @@ public function getXApiKey() return !empty($this->data['x-api-key']) ? $this->data['x-api-key'] : null; } + /** + * Get the http proxy configuration + * + * @return mixed|null + */ + public function getHttpProxy() + { + return !empty($this->data['http-proxy']) ? $this->data['http-proxy'] : null; + } + /** * @return mixed|string */ diff --git a/src/Adyen/HttpClient/CurlClient.php b/src/Adyen/HttpClient/CurlClient.php index 2cb226f2b..09d12f6d5 100644 --- a/src/Adyen/HttpClient/CurlClient.php +++ b/src/Adyen/HttpClient/CurlClient.php @@ -1,6 +1,9 @@ getUsername(); $password = $config->getPassword(); $xApiKey = $config->getXApiKey(); + $httpProxy = $config->getHttpProxy(); $jsonRequest = json_encode($params); @@ -36,6 +40,8 @@ public function requestJson(\Adyen\Service $service, $requestUrl, $params) //Attach our encoded JSON string to the POST fields. curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonRequest); + $this->curlSetHttpProxy($ch, $httpProxy); + //create a custom User-Agent $userAgent = $config->get('applicationName') . " " . \Adyen\Client::USER_AGENT_SUFFIX . $client->getLibraryVersion(); @@ -51,7 +57,7 @@ public function requestJson(\Adyen\Service $service, $requestUrl, $params) $headers[] = 'x-api-key: ' . $xApiKey; } elseif ($service->requiresApiKey()) { $msg = "Please provide a valid Checkout API Key"; - throw new \Adyen\AdyenException($msg); + throw new AdyenException($msg); } else { //Set the basic auth credentials @@ -101,6 +107,35 @@ public function requestJson(\Adyen\Service $service, $requestUrl, $params) return $result; } + /** + * Set httpProxy in the current curl configuration + * + * @param resource $ch + * @param string $httpProxy + * @throws AdyenException + */ + public function curlSetHttpProxy($ch, $httpProxy) + { + if (empty($httpProxy)) { + return; + } + + $urlParts = parse_url($httpProxy); + if ($urlParts == false || !array_key_exists("host", $urlParts)) { + throw new AdyenException("Invalid proxy configuration " . $httpProxy); + } + + $proxy = $urlParts["host"]; + if (isset($urlParts["port"])) { + $proxy .= ":" . $urlParts["port"]; + } + curl_setopt($ch, CURLOPT_PROXY, $proxy); + + if (isset($urlParts["user"])) { + curl_setopt($ch, CURLOPT_PROXYUSERPWD, $urlParts["user"] . ":" . $urlParts["pass"]); + } + } + /** * Request to Adyen with query string used for Directory Lookup * @@ -108,7 +143,7 @@ public function requestJson(\Adyen\Service $service, $requestUrl, $params) * @param $requestUrl * @param $params * @return mixed - * @throws \Adyen\AdyenException + * @throws AdyenException */ public function requestPost(\Adyen\Service $service, $requestUrl, $params) { @@ -117,6 +152,7 @@ public function requestPost(\Adyen\Service $service, $requestUrl, $params) $logger = $client->getLogger(); $username = $config->getUsername(); $password = $config->getPassword(); + $httpProxy = $config->getHttpProxy(); // log the requestUr, params and json request $logger->info("Request url to Adyen: " . $requestUrl); @@ -128,6 +164,8 @@ public function requestPost(\Adyen\Service $service, $requestUrl, $params) //Tell cURL that we want to send a POST request. curl_setopt($ch, CURLOPT_POST, 1); + $this->curlSetHttpProxy($ch, $httpProxy); + // set authorisation curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); curl_setopt($ch, CURLOPT_USERPWD, $username . ":" . $password); @@ -173,7 +211,7 @@ public function requestPost(\Adyen\Service $service, $requestUrl, $params) if (!$result) { $msg = "The result is empty, looks like your request is invalid"; $logger->error($msg); - throw new \Adyen\AdyenException($msg); + throw new AdyenException($msg); } // log the array result @@ -224,18 +262,24 @@ protected function handleCurlError($url, $errno, $message, $logger) * * @param $result * @param $logger - * @throws \Adyen\AdyenException + * @throws AdyenException */ protected function handleResultError($result, $logger) { $decodeResult = json_decode($result, true); if (isset($decodeResult['message']) && isset($decodeResult['errorCode'])) { $logger->error($decodeResult['errorCode'] . ': ' . $decodeResult['message']); - throw new \Adyen\AdyenException($decodeResult['message'], $decodeResult['errorCode'], null, - $decodeResult['status'], $decodeResult['errorType']); + throw new AdyenException( + $decodeResult['message'], + $decodeResult['errorCode'], + null, + $decodeResult['status'], + $decodeResult['errorType'], + isset($decodeResult['pspReference']) ? $decodeResult['pspReference'] : null + ); } $logger->error($result); - throw new \Adyen\AdyenException($result); + throw new AdyenException($result); } /** diff --git a/src/Adyen/Service/BinLookup.php b/src/Adyen/Service/BinLookup.php new file mode 100644 index 000000000..8c6a19ba0 --- /dev/null +++ b/src/Adyen/Service/BinLookup.php @@ -0,0 +1,35 @@ +get3dsAvailability = new \Adyen\Service\ResourceModel\BinLookup\Get3dsAvailability($this); + } + + + /** + * @param $params + * @return mixed + * @throws \Adyen\AdyenException + */ + public function get3dsAvailability($params) + { + $result = $this->get3dsAvailability->request($params); + return $result; + } +} diff --git a/src/Adyen/Service/Modification.php b/src/Adyen/Service/Modification.php index 3d024669a..dcee270b6 100644 --- a/src/Adyen/Service/Modification.php +++ b/src/Adyen/Service/Modification.php @@ -24,6 +24,11 @@ class Modification extends \Adyen\Service */ protected $refund; + /** + * @var ResourceModel\Modification\RefundWithData + */ + protected $refundWithData; + /** * @var ResourceModel\Modification\AdjustAuthorisation */ @@ -42,6 +47,7 @@ public function __construct(\Adyen\Client $client) $this->cancelOrRefund = new \Adyen\Service\ResourceModel\Modification\CancelOrRefund($this); $this->capture = new \Adyen\Service\ResourceModel\Modification\Capture($this); $this->refund = new \Adyen\Service\ResourceModel\Modification\Refund($this); + $this->refundWithData = new \Adyen\Service\ResourceModel\Modification\RefundWithData($this); $this->adjustAuthorisation = new \Adyen\Service\ResourceModel\Modification\AdjustAuthorisation($this); } @@ -89,6 +95,17 @@ public function refund($params) return $result; } + /** + * @param $params + * @return mixed + * @throws \Adyen\AdyenException + */ + public function refundWithData($params) + { + $result = $this->refundWithData->request($params); + return $result; + } + /** * @param $params * @return mixed diff --git a/src/Adyen/Service/PosPayment.php b/src/Adyen/Service/PosPayment.php index 3b75e0037..f223c569d 100644 --- a/src/Adyen/Service/PosPayment.php +++ b/src/Adyen/Service/PosPayment.php @@ -14,11 +14,16 @@ class PosPayment extends \Adyen\ApiKeyAuthenticatedService */ protected $runTenderAsync; - /** - * @var - */ + /** + * @var + */ protected $txType; + /** + * @var ResourceModel\Payment\ConnectedTerminals + */ + protected $connectedTerminals; + /** * PosPayment constructor. * @@ -30,6 +35,7 @@ public function __construct(\Adyen\Client $client) parent::__construct($client); $this->runTenderSync = new \Adyen\Service\ResourceModel\Payment\TerminalCloudAPI($this, false); $this->runTenderAsync = new \Adyen\Service\ResourceModel\Payment\TerminalCloudAPI($this, true); + $this->connectedTerminals = new \Adyen\Service\ResourceModel\Payment\ConnectedTerminals($this); } /** @@ -65,4 +71,15 @@ public function getServiceId($request) } return null; } + + /** + * @param $params + * @return mixed + * @throws \Adyen\AdyenException + */ + public function getConnectedTerminals($params) + { + $result = $this->connectedTerminals->request($params); + return $result; + } } diff --git a/src/Adyen/Service/ResourceModel/BinLookup/Get3dsAvailability.php b/src/Adyen/Service/ResourceModel/BinLookup/Get3dsAvailability.php new file mode 100644 index 000000000..91a9ca174 --- /dev/null +++ b/src/Adyen/Service/ResourceModel/BinLookup/Get3dsAvailability.php @@ -0,0 +1,29 @@ +endpoint = $service->getClient()->getConfig()->get('endpoint') . '/pal/servlet/BinLookup/' . $service->getClient()->getApiBinLookupVersion() . '/get3dsAvailability'; + parent::__construct($service, $this->endpoint, $this->allowApplicationInfo); + } +} diff --git a/src/Adyen/Service/ResourceModel/Modification/RefundWithData.php b/src/Adyen/Service/ResourceModel/Modification/RefundWithData.php new file mode 100644 index 000000000..a2bef815d --- /dev/null +++ b/src/Adyen/Service/ResourceModel/Modification/RefundWithData.php @@ -0,0 +1,32 @@ +endpoint = $service->getClient()->getConfig()->get('endpoint') . + '/pal/servlet/Payment/' . $service->getClient()->getApiPaymentVersion() . '/refundWithData'; + parent::__construct($service, $this->endpoint, $this->allowApplicationInfo); + } +} diff --git a/src/Adyen/Service/ResourceModel/Payment/ConnectedTerminals.php b/src/Adyen/Service/ResourceModel/Payment/ConnectedTerminals.php new file mode 100644 index 000000000..865150b4e --- /dev/null +++ b/src/Adyen/Service/ResourceModel/Payment/ConnectedTerminals.php @@ -0,0 +1,22 @@ +endpoint = $service->getClient()->getConfig()->get('endpointTerminalCloud') . '/connectedTerminals'; + parent::__construct($service, $this->endpoint); + } +} diff --git a/tests/BinLookupTest.php b/tests/BinLookupTest.php new file mode 100644 index 000000000..4e7760280 --- /dev/null +++ b/tests/BinLookupTest.php @@ -0,0 +1,56 @@ +createClient(); + + // initialize service + $service = new Service\BinLookup($client); + + $json = '{ + "cardNumber": "4111111111111111", + "merchantAccount": "' . $this->merchantAccount .'" + }'; + + + $params = json_decode($json, true); + + $result = $service->get3dsAvailability($params); + + + // 4111111111111111 is a valid 3ds card so the result should be true + $this->assertEquals(true, $result['threeDS2supported']); + } + + public function testInvalid3dsCard() + { + // initialize client + $client = $this->createClient(); + + // initialize service + $service = new Service\BinLookup($client); + + $json = '{ + "cardNumber": "1111111111111111", + "merchantAccount": "' . $this->merchantAccount .'" + }'; + + + $params = json_decode($json, true); + + $result = $service->get3dsAvailability($params); + + + // 1111111111111111 is a invalid 3ds card so the result should be false + $this->assertEquals(false, $result['threeDS2supported']); + } + +} diff --git a/tests/MockTest/ModificationTest.php b/tests/MockTest/ModificationTest.php new file mode 100644 index 000000000..5c503ac21 --- /dev/null +++ b/tests/MockTest/ModificationTest.php @@ -0,0 +1,50 @@ +createMockClient($jsonFile, $httpStatus); + + // initialize service + $service = new \Adyen\Service\Modification($client); + + $params = json_decode(' + { + "amount":{ + "value":1500, + "currency":"GBP" + }, + "selectedRecurringDetailReference":"8315535507322518", + "shopperReference":"myshopperreference", + "reference":"myreference", + "merchantAccount":"mymerchantaccount", + "recurring":{ + "contract":"RECURRING" + }, + "shopperInteraction":"ContAuth" + }', + true + ); + + $result = $service->refundWithData($params); + + $this->assertContains($result['resultCode'], array('Received')); + } + + public static function successRefundWithDataProvider() + { + return array( + array('tests/Resources/Modification/refundWithData-success.json', 200), + ); + } +} diff --git a/tests/MockTest/PosPaymentTest.php b/tests/MockTest/PosPaymentTest.php new file mode 100644 index 000000000..c8479abf0 --- /dev/null +++ b/tests/MockTest/PosPaymentTest.php @@ -0,0 +1,40 @@ +createMockClient($jsonFile, $httpStatus); + + // initialize service + $service = new \Adyen\Service\PosPayment($client); + + $json = '{ + "merchantAccount": "PME_POS" + }'; + + $params = json_decode($json, true); + + $result = $service->getConnectedTerminals($params); + var_dump($result); + + $this->assertArrayHasKey('uniqueTerminalIds', $result); + } + + public static function resultSuccessGetConnectedTerminals() + { + return array( + array('tests/Resources/Payment/connected-terminals.json', 200) + ); + } +} diff --git a/tests/PosPaymentTest.php b/tests/PosPaymentTest.php index e79bc0fe0..9a4a361ac 100644 --- a/tests/PosPaymentTest.php +++ b/tests/PosPaymentTest.php @@ -9,6 +9,10 @@ class PosPaymentTest extends TestCase public function testCreatePosPaymentSuccess() { + if (empty($this->settings['POIID']) || $this->settings['POIID'] = 'UNIQUETERMINALID') { + $this->skipTest("Skipped the test. Configure your POIID in the config"); + } + // initialize client $client = $this->createTerminalCloudAPIClient(); @@ -69,6 +73,11 @@ public function testCreatePosPaymentSuccess() public function testCreatePosPaymentDeclined() { + + if (empty($this->settings['POIID']) || $this->settings['POIID'] = 'UNIQUETERMINALID') { + $this->skipTest("Skipped the test. Configure your POIID in the config"); + } + // initialize client $client = $this->createTerminalCloudAPIClient(); @@ -129,6 +138,10 @@ public function testCreatePosPaymentDeclined() public function testCreatePosEMVRefundSuccess() { + if (empty($this->settings['POIID']) || $this->settings['POIID'] = 'UNIQUETERMINALID') { + $this->skipTest("Skipped the test. Configure your POIID in the config"); + } + // initialize client $client = $this->createTerminalCloudAPIClient(); @@ -193,6 +206,10 @@ public function testCreatePosEMVRefundSuccess() */ public function testPosPaymentFailTimeout() { + if (empty($this->settings['POIID']) || $this->settings['POIID'] = 'UNIQUETERMINALID') { + $this->skipTest("Skipped the test. Configure your POIID in the config"); + } + // initialize client $client = $this->createTerminalCloudAPIClient(); $client->setTimeout(1); @@ -251,4 +268,28 @@ public function testPosPaymentFailTimeout() } + public function testGetConnectedTerminals() + { + // initialize client + $client = $this->createTerminalCloudAPIClient(); + + // initialize service + $service = new Service\PosPayment($client); + + //Construct request + $json = '{ + "merchantAccount": "' . $this->settings['merchantAccount'] . '" + }'; + + $params = json_decode($json, true); //Create associative array for passing along + + try { + $result = $service->getConnectedTerminals($params); + } catch (\Exception $e) { + $this->validateApiPermission($e); + } + + $this->assertTrue(isset($result['uniqueTerminalIds'])); + } + } diff --git a/tests/Resources/Modification/refundWithData-success.json b/tests/Resources/Modification/refundWithData-success.json new file mode 100644 index 000000000..6d99ba5fe --- /dev/null +++ b/tests/Resources/Modification/refundWithData-success.json @@ -0,0 +1,4 @@ +{ + "pspReference": "8835538461311270", + "resultCode": "Received" +} \ No newline at end of file diff --git a/tests/Resources/Payment/connected-terminals.json b/tests/Resources/Payment/connected-terminals.json new file mode 100644 index 000000000..9953895a9 --- /dev/null +++ b/tests/Resources/Payment/connected-terminals.json @@ -0,0 +1,9 @@ +{ + "uniqueTerminalIds" : [ + "MX915-284251016", + "MX925-260390740", + "V400m-324688136", + "V400m-324688178", + "V400m-324688181" + ] +} \ No newline at end of file diff --git a/tests/TestCase.php b/tests/TestCase.php index 618cee1bb..a05d7c623 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -172,8 +172,8 @@ protected function createTerminalCloudAPIClient() // load settings from .ini file $settings = $this->settings; - if(!isset($settings['x-api-key']) || !isset($settings['POIID']) || $settings['x-api-key'] == 'YOUR X-API KEY' || $settings['POIID'] == 'UNIQUETERMINALID'){ - $this->skipTest("Skipped the test. Configure your x-api-key and POIID in the config"); + if(!empty($settings['x-api-key']) || $settings['x-api-key'] == 'YOUR X-API KEY'){ + $this->skipTest("Skipped the test. Configure your x-api-key in the config"); }else{ $client = new \Adyen\Client(); @@ -192,7 +192,7 @@ protected function createClientWithMerchantAccount() // load settings from .ini file $settings = $this->settings; - if(!isset($settings['merchantAccount']) || $settings['merchantAccount'] == 'YOUR MERCHANTACCOUNT') { + if(!empty($settings['merchantAccount']) || $settings['merchantAccount'] == 'YOUR MERCHANTACCOUNT') { $this->skipTest("Skipped the test. Configure your MerchantAccount in the config"); return null; } @@ -208,7 +208,7 @@ protected function createCheckoutAPIClient() // load settings from .ini file $settings = $this->settings; - if(!isset($settings['x-api-key']) || $settings['x-api-key'] == 'YOUR X-API KEY'){ + if(!empty($settings['x-api-key']) || $settings['x-api-key'] == 'YOUR X-API KEY'){ $this->skipTest("Skipped the test. Configure your x-api-key"); }else{ $client->setXApiKey($settings['x-api-key']); @@ -220,7 +220,7 @@ protected function getMerchantAccount() { $settings = $this->settings; - if(!isset($settings['merchantAccount']) || $settings['merchantAccount'] == 'YOUR MERCHANTACCOUNT') { + if(!empty($settings['merchantAccount']) || $settings['merchantAccount'] == 'YOUR MERCHANTACCOUNT') { return null; } @@ -231,7 +231,7 @@ protected function getSkinCode() { $settings = $this->settings; - if(!isset($settings['skinCode']) || $settings['skinCode'] == 'YOUR SKIN CODE') { + if(!empty($settings['skinCode']) || $settings['skinCode'] == 'YOUR SKIN CODE') { return null; } @@ -242,7 +242,7 @@ protected function getHmacSignature() { $settings = $this->settings; - if(!isset($settings['hmacSignature'])|| $settings['hmacSignature'] == 'YOUR HMAC SIGNATURE') { + if(!empty($settings['hmacSignature'])|| $settings['hmacSignature'] == 'YOUR HMAC SIGNATURE') { return null; } @@ -253,7 +253,7 @@ protected function getPOIID() { $settings = $this->settings; - if(!isset($settings['POIID']) || $settings['POIID'] == 'MODEL-SERIALNUMBER') { + if(!empty($settings['POIID']) || $settings['POIID'] == 'MODEL-SERIALNUMBER') { return null; }