├── .gitignore ├── tests ├── config.php ├── phpunit.xml ├── unit_tests │ ├── Exception │ │ ├── PayplugExceptionTest.php │ │ └── HttpExceptionTest.php │ ├── Resource │ │ ├── PaymentFailureTest.php │ │ ├── PaymentNotificationTest.php │ │ ├── PaymentAuthorizationTest.php │ │ ├── PaymentCardTest.php │ │ ├── PaymentCustomerTest.php │ │ ├── PaymentHostedPaymentTest.php │ │ ├── APIResourceTest.php │ │ ├── CardTest.php │ │ ├── AccountingReportTest.php │ │ ├── OneySimulationResourceTest.php │ │ └── RefundTest.php │ ├── PluginTelemetryTest.php │ ├── Core │ │ ├── APIRoutesTest.php │ │ └── HttpClientTest.php │ ├── NotificationTest.php │ ├── PayplugTest.php │ └── OneySimulationTest.php ├── integration_test │ └── HttpClientTest.php ├── utils.php └── README.rst ├── lib ├── Payplug │ ├── Exception │ │ ├── ForbiddenException.php │ │ ├── NotFoundException.php │ │ ├── BadRequestException.php │ │ ├── NotAllowedException.php │ │ ├── UnauthorizedException.php │ │ ├── PayplugServerException.php │ │ ├── InvalidPaymentException.php │ │ ├── PHPVersionException.php │ │ ├── DependencyException.php │ │ ├── UnprocessableEntityException.php │ │ ├── ConfigurationException.php │ │ ├── UndefinedAttributeException.php │ │ ├── ConfigurationNotSetException.php │ │ ├── ConnectionException.php │ │ ├── UnknownAPIResourceException.php │ │ ├── UnexpectedAPIResponseException.php │ │ ├── PayplugException.php │ │ └── HttpException.php │ ├── Resource │ │ ├── IAPIResourceFactory.php │ │ ├── PaymentCard.php │ │ ├── PaymentCustomer.php │ │ ├── PaymentBilling.php │ │ ├── PaymentShipping.php │ │ ├── PaymentNotification.php │ │ ├── PaymentAuthorization.php │ │ ├── PaymentHostedPayment.php │ │ ├── PaymentPaymentFailure.php │ │ ├── InstallmentPlanSchedule.php │ │ ├── IVerifiableAPIResource.php │ │ ├── Card.php │ │ ├── OneySimulationResource.php │ │ ├── AccountingReport.php │ │ ├── APIResource.php │ │ ├── Refund.php │ │ ├── InstallmentPlan.php │ │ └── Payment.php │ ├── Card.php │ ├── OneySimulation.php │ ├── Notification.php │ ├── Core │ │ ├── IHttpRequest.php │ │ ├── CurlRequest.php │ │ ├── Config.php │ │ └── APIRoutes.php │ ├── AccountingReport.php │ ├── Refund.php │ ├── InstallmentPlan.php │ ├── Payment.php │ ├── PluginTelemetry.php │ ├── Payplug.php │ └── Authentication.php └── init.php ├── rector.php ├── docs ├── index.rst ├── Makefile └── conf.py ├── composer.json ├── LICENCE.md ├── CHANGELOG.md ├── .github └── workflows │ └── payplug-ci.yml └── README.rst /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.swp 3 | _build/ 4 | .idea/ 5 | vendor/ 6 | *.iml 7 | *.phar 8 | coverage/ 9 | -------------------------------------------------------------------------------- /tests/config.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | .. 5 | 6 | ../lib 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /rector.php: -------------------------------------------------------------------------------- 1 | withPaths([ 10 | __DIR__ . '/lib', 11 | ]) 12 | ->withPhpVersion(Rector\ValueObject\PhpVersion::PHP_84) 13 | ->withRules([ 14 | ExplicitNullableParamTypeRector::class, 15 | CompleteDynamicPropertiesRector::class, 16 | ]); -------------------------------------------------------------------------------- /lib/Payplug/Resource/IAPIResourceFactory.php: -------------------------------------------------------------------------------- 1 | initialize($attributes); 20 | return $object; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/Payplug/Resource/PaymentCustomer.php: -------------------------------------------------------------------------------- 1 | initialize($attributes); 20 | return $object; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/Payplug/Resource/PaymentBilling.php: -------------------------------------------------------------------------------- 1 | initialize($attributes); 20 | return $object; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/Payplug/Resource/PaymentShipping.php: -------------------------------------------------------------------------------- 1 | initialize($attributes); 20 | return $object; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/Payplug/Resource/PaymentNotification.php: -------------------------------------------------------------------------------- 1 | initialize($attributes); 20 | return $object; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/Payplug/Resource/PaymentAuthorization.php: -------------------------------------------------------------------------------- 1 | initialize($attributes); 20 | return $object; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/Payplug/Resource/PaymentHostedPayment.php: -------------------------------------------------------------------------------- 1 | initialize($attributes); 20 | return $object; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/Payplug/Resource/PaymentPaymentFailure.php: -------------------------------------------------------------------------------- 1 | initialize($attributes); 20 | return $object; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/unit_tests/Exception/PayplugExceptionTest.php: -------------------------------------------------------------------------------- 1 | getMockForAbstractClass('\PayPlug\Exception\HttpException', array('this_is_a_message', 808)); 14 | 15 | $this->assertContains('Mock_HttpException', (string)$exception); 16 | $this->assertContains('this_is_a_message', (string)$exception); 17 | $this->assertContains('808', (string)$exception); 18 | } 19 | } -------------------------------------------------------------------------------- /lib/Payplug/Card.php: -------------------------------------------------------------------------------- 1 | initialize($attributes); 20 | return $object; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/Payplug/Exception/PayplugException.php: -------------------------------------------------------------------------------- 1 | code}]: {$this->message}"; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/unit_tests/Resource/PaymentFailureTest.php: -------------------------------------------------------------------------------- 1 | 'card_stolen', 15 | 'message' => 'The card is stolen.' 16 | )); 17 | 18 | $this->assertEquals('card_stolen', $paymentFailure->code); 19 | $this->assertEquals('The card is stolen.', $paymentFailure->message); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/integration_test/HttpClientTest.php: -------------------------------------------------------------------------------- 1 | _configuration = new Payplug('abc','1970-01-01'); 19 | } 20 | 21 | public function testAPIRequest() 22 | { 23 | $httpClient = new Core\HttpClient($this->_configuration); 24 | $response = $httpClient->testRemote(); 25 | $this->assertEquals(200, $response['httpStatus']); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/Payplug/OneySimulation.php: -------------------------------------------------------------------------------- 1 | 'https://www.example.net/payplug_notification.html', 15 | 'response_code' => 200 16 | )); 17 | 18 | $this->assertEquals('https://www.example.net/payplug_notification.html', $notification->url); 19 | $this->assertEquals(200, $notification->response_code); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/unit_tests/Resource/PaymentAuthorizationTest.php: -------------------------------------------------------------------------------- 1 | 1554896133, 15 | 'expires_at' => 1555459200, 16 | 'authorized_amount' => 4200, 17 | )); 18 | 19 | $this->assertEquals(1554896133, $hostedPayment->authorized_at); 20 | $this->assertEquals(1555459200, $hostedPayment->expires_at); 21 | $this->assertEquals(4200, $hostedPayment->authorized_amount); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/Payplug/Resource/IVerifiableAPIResource.php: -------------------------------------------------------------------------------- 1 | '1234', 15 | 'country' => 'FR', 16 | 'exp_year' => 2022, 17 | 'exp_month' => 12, 18 | 'brand' => 'Visa' 19 | )); 20 | 21 | $this->assertEquals('1234', $card->last4); 22 | $this->assertEquals('FR', $card->country); 23 | $this->assertEquals(2022, $card->exp_year); 24 | $this->assertEquals(12, $card->exp_month); 25 | $this->assertEquals('Visa', $card->brand); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | PayPlug PHP library documentation 2 | ================================= 3 | 4 | Please see https://www.payplug.com/docs/api/?php for an up-to-date documentation of the PHP Library. 5 | 6 | The latest version of the library is now available on `GitHub`_. 7 | 8 | .. _github: https://github.com/payplug/payplug-php 9 | 10 | Version Support: 11 | ---------------- 12 | 13 | If you are using PHP 5.2, you can still download the 1.1.12 version (`zip`_, `gz`_, `bz2`_). 14 | 15 | .. _zip: https://bitbucket.org/payplug/payplug_php/get/V1.1.2.zip 16 | .. _gz: https://bitbucket.org/payplug/payplug_php/get/V1.1.2.tar.gz 17 | .. _bz2: https://bitbucket.org/payplug/payplug_php/get/V1.1.2.tar.bz2 18 | 19 | The documentation is still available on the `1.1.2 documentation page`_. 20 | 21 | .. _1.1.2 documentation page: http://payplug-php.readthedocs.org/en/v1.1.2/ 22 | 23 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "payplug/payplug-php", 3 | "description": "A simple PHP library for PayPlug public API.", 4 | "type": "library", 5 | "homepage": "https://www.payplug.com", 6 | "time": "2015-05-06", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "PayPlug", 11 | "email": "support@payplug.com" 12 | }, 13 | { 14 | "name": "Daniel FLOREZ MURILLO", 15 | "email": "dmurillo@payplug.com" 16 | } 17 | ], 18 | "require": { 19 | "php": ">=5.3.0", 20 | "ext-curl": "*", 21 | "ext-openssl": "*" 22 | }, 23 | "require-dev": { 24 | "phpunit/phpunit": "5.7.*", 25 | "phpdocumentor/phpdocumentor": "2.*", 26 | "rector/rector": "^1.2" 27 | }, 28 | "minimum-stability": "stable", 29 | "autoload": { 30 | "psr-0": {"Payplug\\": "lib/"}, 31 | "psr-4": {"Payplug\\": "lib/Payplug"} 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/utils.php: -------------------------------------------------------------------------------- 1 | getMethod($methodName); 22 | $method->setAccessible(true); 23 | 24 | return $method->invokeArgs($object, $parameters); 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /lib/Payplug/Notification.php: -------------------------------------------------------------------------------- 1 | getConsistentResource($authentication); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /LICENCE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Payplug 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /tests/unit_tests/Resource/PaymentCustomerTest.php: -------------------------------------------------------------------------------- 1 | 'john.doe@example.com', 15 | 'first_name' => 'John', 16 | 'last_name' => 'Doe', 17 | 'address1' => 'rue abc', 18 | 'address2' => 'cba', 19 | 'postcode' => '12345', 20 | 'country' => 'FR' 21 | )); 22 | 23 | $this->assertEquals('john.doe@example.com', $customer->email); 24 | $this->assertEquals('John', $customer->first_name); 25 | $this->assertEquals('Doe', $customer->last_name); 26 | $this->assertEquals('rue abc', $customer->address1); 27 | $this->assertEquals('cba', $customer->address2); 28 | $this->assertEquals('12345', $customer->postcode); 29 | $this->assertEquals('FR', $customer->country); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/Payplug/Core/IHttpRequest.php: -------------------------------------------------------------------------------- 1 | 'https://www.payplug.com/pay/test/7ZcMGi6KNnVT5H7o9hms9g', 15 | 'notification_url' => 'https://www.payplug.com/?notification', 16 | 'return_url' => 'https://www.payplug.com/?return', 17 | 'cancel_url' => 'https://www.payplug.com/?cancel', 18 | 'paid_at' => 1410437806, 19 | 'notification_answer_code' => 200, 20 | )); 21 | 22 | $this->assertEquals('https://www.payplug.com/pay/test/7ZcMGi6KNnVT5H7o9hms9g', $hostedPayment->payment_url); 23 | $this->assertEquals('https://www.payplug.com/?notification', $hostedPayment->notification_url); 24 | $this->assertEquals('https://www.payplug.com/?return', $hostedPayment->return_url); 25 | $this->assertEquals('https://www.payplug.com/?cancel', $hostedPayment->cancel_url); 26 | $this->assertEquals(1410437806, $hostedPayment->paid_at); 27 | $this->assertEquals(200, $hostedPayment->notification_answer_code); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/unit_tests/Exception/HttpExceptionTest.php: -------------------------------------------------------------------------------- 1 | assertContains('Payplug\Exception\HttpException', (string)$exception); 15 | $this->assertContains('message_content', (string)$exception); 16 | $this->assertContains('http_response_content', (string)$exception); 17 | $this->assertContains('808', (string)$exception); 18 | } 19 | 20 | public function testGetHttpResponse() 21 | { 22 | $exception = new HttpException('message_content', 'http_response_content', 808); 23 | $this->assertEquals('http_response_content', $exception->getHttpResponse()); 24 | } 25 | 26 | public function testGetErrorObjectWhenErrorIsJson() 27 | { 28 | $exception = new HttpException('message_content', '{"error": "an_error"}', 808); 29 | $this->assertEquals(array('error' => 'an_error'), $exception->getErrorObject()); 30 | } 31 | 32 | public function testGetErrorObjectWhenErrorIsNotJson() 33 | { 34 | $exception = new HttpException('message_content', '{}not_json{}', 808); 35 | $this->assertNull($exception->getErrorObject()); 36 | } 37 | } -------------------------------------------------------------------------------- /lib/Payplug/Core/CurlRequest.php: -------------------------------------------------------------------------------- 1 | _curl = curl_init(); 21 | } 22 | 23 | /** 24 | * @inheritDoc 25 | */ 26 | public function setopt($option, $value) 27 | { 28 | return curl_setopt($this->_curl, $option, $value); 29 | } 30 | 31 | /** 32 | * @inheritDoc 33 | */ 34 | public function getinfo($option) 35 | { 36 | return curl_getinfo($this->_curl, $option); 37 | } 38 | 39 | /** 40 | * @inheritDoc 41 | */ 42 | public function exec() 43 | { 44 | return curl_exec($this->_curl); 45 | } 46 | 47 | /** 48 | * @inheritDoc 49 | */ 50 | public function close() 51 | { 52 | curl_close($this->_curl); 53 | } 54 | 55 | /** 56 | * @inheritDoc 57 | */ 58 | public function error() 59 | { 60 | return curl_error($this->_curl); 61 | } 62 | 63 | /** 64 | * @inheritDoc 65 | */ 66 | public function errno() 67 | { 68 | return curl_errno($this->_curl); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lib/Payplug/Exception/HttpException.php: -------------------------------------------------------------------------------- 1 | _httpResponse = $httpResponse; 24 | parent::__construct($message, $code); 25 | } 26 | 27 | /** 28 | * {@inheritdoc} It also appends the HTTP response to the returned string. 29 | */ 30 | public function __toString() 31 | { 32 | return parent::__toString() . "; HTTP Response: {$this->_httpResponse}"; 33 | } 34 | 35 | /** 36 | * Get the plain HTTP response which caused the exception. 37 | * 38 | * @return string the HTTP response 39 | */ 40 | public function getHttpResponse() 41 | { 42 | return $this->_httpResponse; 43 | } 44 | 45 | /** 46 | * Try to parse the HTTP response as a JSON array and return it. 47 | * 48 | * @return array|null the error array if the HTTP response was a properly formed JSON string, null otherwise. 49 | */ 50 | public function getErrorObject() 51 | { 52 | return json_decode($this->_httpResponse, true); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/Payplug/Resource/Card.php: -------------------------------------------------------------------------------- 1 | initialize($attributes); 21 | return $object; 22 | } 23 | 24 | /** 25 | * Delete the card. 26 | * 27 | * @param Payplug\Card $card the card or card id 28 | * @param Payplug\Payplug $payplug the client configuration 29 | * 30 | * @return Card the deleted card or null on error 31 | * 32 | * @throws Payplug\Exception\ConfigurationNotSetException 33 | */ 34 | public static function deleteCard($card, $payplug = null) 35 | { 36 | if ($payplug === null) { 37 | $payplug = Payplug\Payplug::getDefaultConfiguration(); 38 | } 39 | 40 | if ($card instanceof Card) { 41 | $card = $card->id; 42 | } 43 | 44 | $httpClient = new Payplug\Core\HttpClient($payplug); 45 | $response = $httpClient->delete(Payplug\Core\APIRoutes::getRoute(Payplug\Core\APIRoutes::CARD_RESOURCE, $card)); 46 | 47 | return $response; 48 | } 49 | 50 | /** 51 | * Delete the card. 52 | * 53 | * @param Payplug\Payplug $payplug the client configuration 54 | * 55 | * @throws Payplug\Exception\ConfigurationNotSetException 56 | */ 57 | public function delete($payplug = null) 58 | { 59 | self::deleteCard($this->id, $payplug); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lib/Payplug/Core/Config.php: -------------------------------------------------------------------------------- 1 | 25 | * 'curl_version' => 'php5-curl' 26 | * 27 | * means that this program requires curl_version() function to work properly and that it corresponds to php5-curl 28 | * package. 29 | */ 30 | public static $REQUIRED_FUNCTIONS = array( 31 | 'json_decode' => 'php5-json', 32 | 'json_encode' => 'php5-json', 33 | 'curl_version' => 'php5-curl' 34 | ); 35 | } 36 | 37 | // Check PHP version 38 | if (version_compare(phpversion(), Config::PHP_MIN_VERSION, "<")) { 39 | throw new \RuntimeException('This library requires PHP ' . Config::PHP_MIN_VERSION . ' or newer.'); 40 | } 41 | 42 | // Check PHP configuration 43 | foreach(Config::$REQUIRED_FUNCTIONS as $key => $value) { 44 | if (!function_exists($key)) { 45 | throw new Exception\DependencyException('This library requires ' . $value . '.'); 46 | } 47 | } 48 | 49 | // Prior to PHP 5.5, CURL_SSLVERSION_TLSv1 and CURL_SSLVERSION_DEFAULT didn't exist. Hence, we have to use a numeric value. 50 | if (!defined('CURL_SSLVERSION_DEFAULT')) { 51 | define('CURL_SSLVERSION_DEFAULT', 0); 52 | } 53 | if (!defined('CURL_SSLVERSION_TLSv1')) { 54 | define('CURL_SSLVERSION_TLSv1', 1); 55 | } 56 | -------------------------------------------------------------------------------- /tests/README.rst: -------------------------------------------------------------------------------- 1 | Contributing 2 | ==================== 3 | 4 | Troubleshooting 5 | ---------------- 6 | 7 | PHP 8 CI test is failing: 8 | :: 9 | $ ./tools/phpunit MyTest 10 | PHP Fatal error: Declaration of MyTest::setUp() must be compatible 11 | with PHPUnit\Framework\TestCase::setUp(): void in ... 12 | 13 | To keep compatibility through PHP 5.6 to PHP 8 the setUp method cannot be typed. 14 | 15 | Use a homemade methode instead: 16 | :: 17 | /** @before **/ 18 | protected setUpTest() { 19 | ... 20 | } 21 | 22 | How to run the tests 23 | ==================== 24 | 25 | Prerequisites: 26 | -------------- 27 | 28 | Download composer and update dev dependencies. 29 | :: 30 | 31 | php composer.phar update 32 | 33 | Run the recommended tests: 34 | -------------------------- 35 | 36 | It is recommended to launch these tests at least once to ensure this library will work properly on your configuration. 37 | :: 38 | 39 | vendor/phpunit/phpunit/phpunit --group recommended --exclude-group ignore --bootstrap tests/config.php tests 40 | 41 | Run a specific test: 42 | -------------------- 43 | 44 | You can run a specific test adding a filter to the previous command. 45 | :: 46 | 47 | vendor/phpunit/phpunit/phpunit --filter CardTest --group unit --exclude-group ignore --bootstrap tests/config.php tests 48 | 49 | Run specific groups of test 50 | --------------------------- 51 | 52 | You can filter tests by groups: 53 | :: 54 | 55 | # Run unit tests 56 | vendor/phpunit/phpunit/phpunit --group unit --exclude-group ignore --bootstrap tests/config.php tests 57 | 58 | # Run functional tests 59 | vendor/phpunit/phpunit/phpunit --group functional --exclude-group ignore --bootstrap tests/config.php tests 60 | 61 | # Run unit tests and functional tests 62 | vendor/phpunit/phpunit/phpunit --group unit,functional --exclude-group ignore --bootstrap tests/config.php tests 63 | -------------------------------------------------------------------------------- /lib/Payplug/Refund.php: -------------------------------------------------------------------------------- 1 | $installmentPlanId)); 37 | return $installmentPlan->abort($payplug); 38 | } 39 | 40 | /** 41 | * Creates an InstallmentPlan. 42 | * 43 | * @param array $data API data for payment creation 44 | * @param Payplug $payplug the client configuration 45 | * 46 | * @return null|Resource\InstallmentPlan the created payment instance 47 | * 48 | * @throws Exception\ConfigurationNotSetException 49 | */ 50 | public static function create(array $data, $payplug = null) 51 | { 52 | return Resource\InstallmentPlan::create($data, $payplug); 53 | } 54 | }; 55 | -------------------------------------------------------------------------------- /tests/unit_tests/PluginTelemetryTest.php: -------------------------------------------------------------------------------- 1 | _configuration = new Payplug\Payplug('abc'); 20 | $this->_httpClient = new Payplug\Core\HttpClient(new \Payplug\Payplug('abc')); 21 | Payplug\Payplug::setDefaultConfiguration($this->_configuration); 22 | 23 | $this->_requestMock = $this->createMock('Payplug\Core\IHttpRequest'); 24 | Payplug\Core\HttpClient::$REQUEST_HANDLER = $this->_requestMock; 25 | } 26 | 27 | /** 28 | * Verify the behavior of send() method in the PluginTelemetry class 29 | * with a mocked API URL that raises an exception 30 | * 31 | * @throws Exception\ConnectionException 32 | * @throws Exception\HttpException 33 | * @throws Exception\UnexpectedAPIResponseException 34 | */ 35 | public function testSendWithBadMockedURL() 36 | { 37 | $this->expectException('Payplug\Exception\HttpException'); 38 | 39 | // Data to send to the MPDC microservice 40 | $data = array( 41 | 'version' => '4.0.0', 42 | 'php_version' => '8.2.1', 43 | 'name' => 'value', 44 | 'from' => 'save', 45 | 'domains' => array( 46 | array( 47 | 'url' => 'www.mywebsite.com', 48 | 'default' => true 49 | ) 50 | ), 51 | 'configurations' => array( 52 | 'name' => 'value' 53 | ), 54 | 'modules' => array( 55 | array( 56 | 'name' => 'value', 57 | 'version' => 'value' 58 | ) 59 | ) 60 | ); 61 | $data = json_encode($data); 62 | 63 | // call send and assert 64 | PluginTelemetry::send($data); 65 | } 66 | } -------------------------------------------------------------------------------- /lib/Payplug/Resource/OneySimulationResource.php: -------------------------------------------------------------------------------- 1 | initialize($attributes); 26 | return $object; 27 | } 28 | 29 | /** 30 | * Initializes the resource. 31 | * This method must be overridden when the resource has objects as attributes. 32 | * 33 | * @param array $attributes the attributes to initialize. 34 | */ 35 | protected function initialize(array $attributes) 36 | { 37 | parent::initialize($attributes); 38 | 39 | // initialize Oney Payment Simulation with x3_with_fees operation 40 | if (isset($attributes['x3_with_fees'])) { 41 | $this->x3_with_fees = $attributes['x3_with_fees']; 42 | } 43 | 44 | // initialize Oney Payment Simulation with x4_with_fees operation 45 | if (isset($attributes['x4_with_fees'])) { 46 | $this->x4_with_fees = $attributes['x4_with_fees']; 47 | } 48 | } 49 | 50 | /** 51 | * Get Oney Payment Simulation 52 | * 53 | * @param $sim_data 54 | * @param Payplug\Payplug|null $payplug 55 | * @return array 56 | * @throws Payplug\Exception\ConfigurationNotSetException 57 | * @throws Payplug\Exception\ConnectionException 58 | * @throws Payplug\Exception\HttpException 59 | * @throws Payplug\Exception\UnexpectedAPIResponseException 60 | */ 61 | public static function getSimulations($sim_data, $payplug = null) 62 | { 63 | if ($payplug === null) { 64 | $payplug = Payplug\Payplug::getDefaultConfiguration(); 65 | } 66 | 67 | $sim_route = Payplug\Core\APIRoutes::getRoute(Payplug\Core\APIRoutes::ONEY_PAYMENT_SIM_RESOURCE); 68 | 69 | $httpClient = new Payplug\Core\HttpClient($payplug); 70 | 71 | $response = $httpClient->post( 72 | $sim_route, 73 | $sim_data 74 | ); 75 | 76 | return $response['httpResponse']; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 3.4.0 2 | ----- 3 | - **NEW**: Add method to get publishable keys 4 | 5 | 3.3.2 6 | ----- 7 | - Fix retrieve payment function when id_payment is not set 8 | 9 | 3.3.1 10 | ----- 11 | - Update certificate 12 | 13 | 14 | 3.3.0 15 | ----- 16 | - NEW: Add AccountingReport class to handle new API endpoint 17 | 18 | 3.2.0 19 | ----- 20 | 21 | - **NEW**: Add Oney Payment Simulations 22 | 23 | 3.1.0 24 | ----- 25 | 26 | - **NEW**: Deprecated Payplug::setSecretKey function, use Payplug::init instead 27 | - **NEW**: API version should now be specified explicitly from module 28 | - **NEW**: Improve Payplug setSecretKey with init method 29 | - **NEW**: Excluded configuration API_VERSION versionning, it will be send from the apps nowadays 30 | 31 | 3.0.0 32 | ----- 33 | 34 | - **Major**: Replace customer by shipping and billing to be compliant with PSD2. 35 | - **NEW**: Add versionning with new API_VERSION parameter. 36 | 37 | 38 | 2.7.0 39 | ----- 40 | 41 | - **NEW**: Add deferred payments. 42 | 43 | 44 | 2.6.0 45 | ----- 46 | 47 | - **NEW**: Add installment plans. 48 | 49 | 50 | 2.5.1 51 | ----- 52 | 53 | - **Bugfix**: Fix packagist bundle. 54 | 55 | 56 | 2.5.0 57 | ----- 58 | 59 | - **NEW**: Remove classes and methods specific to customer 60 | - **NEW**: Add Authentication class for method specific to login and account permissions 61 | - **NEW**: Add a method to patch payment 62 | 63 | 2.4.0 64 | ----- 65 | 66 | - **NEW**: Support for __isset magic method in resources. This should bring Twig support. 67 | (Thanks [oservieres](https://github.com/oservieres)) 68 | - **NEW**: Add a method to specify custom User-Agent products. 69 | 70 | 2.3.0 71 | ----- 72 | 73 | - **NEW**: Support for Customer/Cards. (see official documentation) 74 | - **NEW**: Payment objects can now be aborted. 75 | 76 | ``` 77 | $payment->abort(); 78 | ``` 79 | 80 | - **Breaking change**: Drop *data* key in objects collections (e.g. list of payments/list of refunds): 81 | 82 | ``` 83 | $payments = \Payplug\Payment::listPayments(); 84 | // $payment = $payments['data'][0]; // BEFORE 85 | $payment = $payments[0]; // NEW BEHAVIOR 86 | 87 | $refunds = \Payplug\Refund::listRefunds($payment); 88 | // $refund = $refunds['data'][0]; // BEFORE 89 | $refund = $refunds[0]; // NEW BEHAVIOR 90 | ``` 91 | 92 | - **Breaking change**: Drop deprecated classes *PayPlugException* and *PayPlugServerException* (with two uppercase 93 | letters in **P**ay**P**lug). If you use **P**ay**p**lugException* and **P**ay**p**lugServerException* classes (with one 94 | uppercase letter in Payplug), you have nothing to do. 95 | - **NEW**: This library is now under MIT Licence (Issue #4). 96 | -------------------------------------------------------------------------------- /.github/workflows/payplug-ci.yml: -------------------------------------------------------------------------------- 1 | name: PHP Composer 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - develop 8 | tags: 9 | - "*" 10 | pull_request: 11 | branches: 12 | - master 13 | - develop 14 | 15 | jobs: 16 | tests_unit: 17 | 18 | runs-on: ubuntu-latest 19 | strategy: 20 | matrix: 21 | php-versions: ['7.3', '7.4', '8.0'] 22 | phpunit-versions: ['8.5.15'] 23 | include: 24 | - php-versions: '5.6' 25 | phpunit-versions: '5.7.27' 26 | - php-versions: '7.0' 27 | phpunit-versions: '6.5.14' 28 | 29 | name: Php Version ${{matrix.php-versions }} / php Unit ${{ matrix.phpunit-versions }} 30 | steps: 31 | - uses: actions/checkout@v2 32 | - name: Setup PHP with PECL extension 33 | uses: shivammathur/setup-php@v2 34 | with: 35 | php-version: ${{ matrix.php-versions }} 36 | tools: phpunit:${{ matrix.phpunit-versions }} 37 | 38 | - name: php version 39 | run: php -v 40 | 41 | - name: Run test suite 42 | run: phpunit --bootstrap tests/config.php tests --configuration tests/phpunit.xml 43 | 44 | sonarcloud: 45 | runs-on: ubuntu-latest 46 | continue-on-error: true 47 | steps: 48 | - uses: actions/checkout@v3 49 | with: 50 | # Disabling shallow clone is recommended for improving relevancy of reporting 51 | fetch-depth: 0 52 | - name: SonarCloud Scan 53 | uses: sonarsource/sonarcloud-github-action@master 54 | env: 55 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 56 | with: 57 | projectBaseDir: . 58 | args: > 59 | -Dsonar.organization=${{ vars.SONAR_ORGANIZATION }} 60 | -Dsonar.projectKey=github-payplug-payplug-php 61 | -Dsonar.sources=lib/ 62 | -Dsonar.test.exclusions=tests/** 63 | -Dsonar.tests=tests/ 64 | -Dsonar.verbose=true 65 | 66 | release: 67 | 68 | runs-on: ubuntu-latest 69 | needs: tests_unit 70 | if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') 71 | steps: 72 | - uses: actions/checkout@master 73 | with: 74 | token: ${{ secrets.GITHUB_TOKEN}} 75 | - name: Fetch library version 76 | run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV 77 | - name: Apply new version from tag 78 | run: | 79 | echo $RELEASE_VERSION 80 | sed -i "s/LIBRARY_VERSION = '*.*'/LIBRARY_VERSION = '${{ env.RELEASE_VERSION }}'/" lib/Payplug/Core/Config.php 81 | git config user.email actions@github.com 82 | git config user.name "GitHub Actions" 83 | git add . 84 | git commit -m "[Automated Release Action]" 85 | git push origin HEAD:master 86 | 87 | -------------------------------------------------------------------------------- /tests/unit_tests/Core/APIRoutesTest.php: -------------------------------------------------------------------------------- 1 | assertEquals($expected, $beginRoute); 17 | } 18 | 19 | public function testCreatePaymentRoute() 20 | { 21 | $route = APIRoutes::getRoute(APIRoutes::PAYMENT_RESOURCE); 22 | $expected = '/payments'; 23 | $endRoute = substr($route, -strlen($expected)); 24 | $this->assertEquals($expected, $endRoute); 25 | } 26 | 27 | public function testRetrievePaymentRoute() 28 | { 29 | $route = APIRoutes::getRoute(APIRoutes::PAYMENT_RESOURCE, 'foo'); 30 | $expected = '/payments/foo'; 31 | $endRoute = substr($route, -strlen($expected)); 32 | $this->assertEquals($expected, $endRoute); 33 | } 34 | 35 | public function testListpaymentspaginationRoute() 36 | { 37 | $pagination = array('perPage' => 5, 'page' => 1); 38 | $route = APIRoutes::getRoute(APIRoutes::PAYMENT_RESOURCE, null, array(), $pagination); 39 | $expected = '/payments?perPage=5&page=1'; 40 | $endRoute = substr($route, -strlen($expected)); 41 | $this->assertEquals($expected, $endRoute); 42 | } 43 | 44 | public function testCreateRefundRoute() 45 | { 46 | $route = APIRoutes::getRoute(APIRoutes::REFUND_RESOURCE, null, array('PAYMENT_ID' => 'foo')); 47 | $expected = '/payments/foo/refunds'; 48 | $endRoute = substr($route, -strlen($expected)); 49 | $this->assertEquals($expected, $endRoute); 50 | } 51 | 52 | public function testRetrieveRefundRoute() 53 | { 54 | $route = APIRoutes::getRoute(APIRoutes::REFUND_RESOURCE, 'bar', array('PAYMENT_ID' => 'foo')); 55 | $expected = '/payments/foo/refunds/bar'; 56 | $endRoute = substr($route, -strlen($expected)); 57 | $this->assertEquals($expected, $endRoute); 58 | } 59 | 60 | public function testListRefundsRoute() 61 | { 62 | $route = APIRoutes::getRoute(APIRoutes::REFUND_RESOURCE, null, array('PAYMENT_ID' => 'foo')); 63 | $expected = '/payments/foo/refunds'; 64 | $endRoute = substr($route, -strlen($expected)); 65 | $this->assertEquals($expected, $endRoute); 66 | } 67 | 68 | public function testSetApiBaseUrl() 69 | { 70 | APIRoutes::setApiBaseUrl('https://api.payplug.com'); 71 | $this->assertEquals('https://api.payplug.com', APIRoutes::$API_BASE_URL); 72 | 73 | 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /tests/unit_tests/Resource/APIResourceTest.php: -------------------------------------------------------------------------------- 1 | initialize($attributes); 10 | return $object; 11 | } 12 | } 13 | 14 | /** 15 | * @group unit 16 | * @group ci 17 | * @group recommended 18 | */ 19 | class APIResourceTest extends \PHPUnit\Framework\TestCase 20 | { 21 | private $_myApiResource = null; 22 | 23 | /** 24 | * @before 25 | */ 26 | protected function setUpTest() 27 | { 28 | $this->_myApiResource = APIResourceMock::fromAttributes(array( 29 | 'attr1' => 'val_attr1', 30 | 'attr2' => 'val_attr2' 31 | )); 32 | } 33 | 34 | public function testThrowsExceptionWhenKeyDoesNotExist() 35 | { 36 | $this->expectException('\PayPlug\Exception\UndefinedAttributeException'); 37 | $this->_myApiResource->an_undefined_attribute; 38 | } 39 | 40 | public function testIssetReturnsTrueWhenPropertyExists() 41 | { 42 | $this->assertTrue(isset($this->_myApiResource->attr1)); 43 | } 44 | 45 | public function testIssetReturnsFalseWhenPropertyDoesNotExist() 46 | { 47 | $this->assertFalse(isset($this->_myApiResource->an_undefined_attribute)); 48 | } 49 | 50 | public function testPaymentFromAPIResourceFactory() 51 | { 52 | $attributes = array( 53 | 'id' => 'pay_123', 54 | 'object' => 'payment' 55 | ); 56 | $payment = Resource\APIResource::factory($attributes); 57 | $this->assertTrue($payment instanceof Resource\Payment); 58 | $this->assertEquals('pay_123', $payment->id); 59 | } 60 | 61 | public function testRefundFromAPIResourceFactory() 62 | { 63 | $attributes = array( 64 | 'id' => 're_123', 65 | 'object' => 'refund' 66 | ); 67 | $refund = Resource\APIResource::factory($attributes); 68 | $this->assertTrue($refund instanceof Resource\Refund); 69 | $this->assertEquals('re_123', $refund->id); 70 | } 71 | 72 | public function testAPIResourceFactoryWhenObjectIsNotDefined() 73 | { 74 | $this->expectException('\PayPlug\Exception\UnknownAPIResourceException'); 75 | $attributes = array( 76 | 'id' => 'a_random_object' 77 | ); 78 | Resource\APIResource::factory($attributes); 79 | } 80 | 81 | public function testAPIResourceFactoryWhenObjectIsUnknown() 82 | { 83 | $this->expectException('\PayPlug\Exception\UnknownAPIResourceException'); 84 | $attributes = array( 85 | 'id' => 'a_random_object', 86 | 'object' => 'an_unknown_object' 87 | ); 88 | Resource\APIResource::factory($attributes); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /lib/Payplug/Payment.php: -------------------------------------------------------------------------------- 1 | $paymentId)); 37 | return $payment->abort($payplug); 38 | } 39 | 40 | /** 41 | * Captures a Payment. 42 | * 43 | * @param string $paymentId the payment ID 44 | * @param Payplug $payplug the client configuration 45 | * 46 | * @return null|Resource\Payment the captured payment or null on error 47 | * 48 | * @throws Exception\ConfigurationNotSetException 49 | */ 50 | public static function capture($paymentId, $payplug = null) 51 | { 52 | $payment = Resource\Payment::fromAttributes(array('id' => $paymentId)); 53 | return $payment->capture($payplug); 54 | } 55 | 56 | /** 57 | * Creates a Payment. 58 | * 59 | * @param array $data API data for payment creation 60 | * @param Payplug $payplug the client configuration 61 | * 62 | * @return null|Resource\Payment the created payment instance 63 | * 64 | * @throws Exception\ConfigurationNotSetException 65 | */ 66 | public static function create(array $data, $payplug = null) 67 | { 68 | return Resource\Payment::create($data, $payplug); 69 | } 70 | 71 | /** 72 | * List payments. 73 | * 74 | * @param int $perPage number of results per page 75 | * @param int $page the page number 76 | * @param Payplug $payplug the client configuration 77 | * 78 | * @return null|Resource\Payment[] the array of payments 79 | * 80 | * @throws Exception\InvalidPaymentException 81 | * @throws Exception\UnexpectedAPIResponseException 82 | */ 83 | public static function listPayments($perPage = null, $page = null, $payplug = null) 84 | { 85 | return Resource\Payment::listPayments($perPage, $page, $payplug); 86 | } 87 | }; -------------------------------------------------------------------------------- /lib/Payplug/Resource/AccountingReport.php: -------------------------------------------------------------------------------- 1 | initialize($attributes); 21 | return $object; 22 | } 23 | 24 | /** 25 | * Retrieves an AccountingReport. 26 | * 27 | * @param string $reportId the accounting report ID 28 | * @param Payplug\Payplug $payplug the client configuration 29 | * 30 | * @return null|AccountingReport the retrieved report or null on error 31 | * 32 | * @throws Payplug\Exception\ConfigurationNotSetException 33 | */ 34 | public static function retrieve($reportId, $payplug = null) 35 | { 36 | if ($payplug === null) { 37 | $payplug = Payplug\Payplug::getDefaultConfiguration(); 38 | } 39 | 40 | $httpClient = new Payplug\Core\HttpClient($payplug); 41 | $response = $httpClient->get( 42 | Payplug\Core\APIRoutes::getRoute(Payplug\Core\APIRoutes::ACCOUNTING_REPORT_RESOURCE, $reportId) 43 | ); 44 | 45 | return AccountingReport::fromAttributes($response['httpResponse']); 46 | } 47 | 48 | /** 49 | * Creates an AccountingReport. 50 | * 51 | * @param array $data API data for accounting report creation 52 | * @param Payplug\Payplug $payplug the client configuration 53 | * 54 | * @return null|AccountingReport the created accounting report instance 55 | * 56 | * @throws Payplug\Exception\ConfigurationNotSetException 57 | */ 58 | public static function create(array $data, $payplug = null) 59 | { 60 | if ($payplug === null) { 61 | $payplug = Payplug\Payplug::getDefaultConfiguration(); 62 | } 63 | 64 | $httpClient = new Payplug\Core\HttpClient($payplug); 65 | $response = $httpClient->post( 66 | Payplug\Core\APIRoutes::getRoute(Payplug\Core\APIRoutes::ACCOUNTING_REPORT_RESOURCE), 67 | $data 68 | ); 69 | 70 | return AccountingReport::fromAttributes($response['httpResponse']); 71 | } 72 | 73 | /** 74 | * Returns an API resource that you can trust. 75 | * 76 | * @param Payplug\Payplug $payplug the client configuration. 77 | * 78 | * @return Payplug\Resource\APIResource The consistent API resource. 79 | * 80 | * @throws Payplug\Exception\UndefinedAttributeException when the local resource is invalid. 81 | */ 82 | function getConsistentResource($payplug = null) 83 | { 84 | if (!array_key_exists('id', $this->_attributes)) { 85 | throw new Payplug\Exception\UndefinedAttributeException('The id of the accounting report is not set.'); 86 | } 87 | 88 | return AccountingReport::retrieve($this->_attributes['id'], $payplug); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /tests/unit_tests/NotificationTest.php: -------------------------------------------------------------------------------- 1 | _configuration = new \Payplug\Payplug('abc'); 24 | Payplug::setDefaultConfiguration($this->_configuration); 25 | 26 | $this->_requestMock = $this->createMock('\Payplug\Core\IHttpRequest'); 27 | Core\HttpClient::$REQUEST_HANDLER = $this->_requestMock; 28 | } 29 | 30 | protected function setUpTwice() 31 | { 32 | $this->_configuration = new Payplug('abc','1970-01-01'); 33 | Payplug::setDefaultConfiguration($this->_configuration); 34 | 35 | $this->_requestMock = $this->createMock('\Payplug\Core\IHttpRequest'); 36 | Core\HttpClient::$REQUEST_HANDLER = $this->_requestMock; 37 | } 38 | 39 | public function testTreatPayment() 40 | { 41 | 42 | $this->_requestMock 43 | ->expects($this->once()) 44 | ->method('exec') 45 | ->will($this->returnValue('{ "id": "real_payment", "object": "payment" }')); 46 | $this->_requestMock 47 | ->expects($this->any()) 48 | ->method('getinfo') 49 | ->will($this->returnCallback(function($option) { 50 | switch($option) { 51 | case CURLINFO_HTTP_CODE: 52 | return 200; 53 | } 54 | return null; 55 | })); 56 | 57 | $body = '{ "id": "pay_123", "object": "payment" }'; 58 | $payment = Notification::treat($body, $this->_configuration); 59 | $this->assertTrue($payment instanceof Resource\Payment); 60 | $this->assertEquals('real_payment', $payment->id); 61 | } 62 | 63 | public function testTreatInstallmentPlan() 64 | { 65 | 66 | $this->_requestMock 67 | ->expects($this->once()) 68 | ->method('exec') 69 | ->will($this->returnValue('{ "id": "real_installment_plan", "object": "installment_plan" }')); 70 | $this->_requestMock 71 | ->expects($this->any()) 72 | ->method('getinfo') 73 | ->will($this->returnCallback(function($option) { 74 | switch($option) { 75 | case CURLINFO_HTTP_CODE: 76 | return 200; 77 | } 78 | return null; 79 | })); 80 | 81 | $body = '{ "id": "inst_123456", "object": "installment_plan" }'; 82 | $installmentPlan = Notification::treat($body, $this->_configuration); 83 | $this->assertTrue($installmentPlan instanceof Resource\InstallmentPlan); 84 | $this->assertEquals('real_installment_plan', $installmentPlan->id); 85 | } 86 | 87 | public function testTreatWhenBodyIsNotValidJSON() 88 | { 89 | $this->expectException('\PayPlug\Exception\UnknownAPIResourceException'); 90 | 91 | $this->_requestMock 92 | ->expects($this->never()) 93 | ->method('exec'); 94 | 95 | $body = 'invalidJSON'; 96 | Notification::treat($body, $this->_configuration); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /lib/Payplug/PluginTelemetry.php: -------------------------------------------------------------------------------- 1 | post( 34 | Core\APIRoutes::getServiceRoute(Core\APIRoutes::TELEMETRY_SERVICE), 35 | $data 36 | ); 37 | } 38 | 39 | /** 40 | * Validate the $data and return UnprocessableEntityException 41 | * 42 | * @param array $data 43 | * @throws Exception\UnprocessableEntityException 44 | */ 45 | public static function validateData(array $data) 46 | { 47 | $requiredFields = array('version', 'php_version', 'name', 'source', 'domains', 'configurations', 'modules'); 48 | 49 | foreach ($requiredFields as $field) { 50 | if (!isset($data[$field])) { 51 | throw new Exception\UnprocessableEntityException( 52 | 'The server encountered an error while processing the request. The submitted data could not be processed.', 53 | '{"detail":[{"loc":["body","' . $field . '"],"msg":"field required","type":"value_error.missing"}]}', 54 | 422 55 | ); 56 | } 57 | } 58 | if (!is_array($data['domains']) || empty($data['domains']) || !isset($data['domains'][0]['url']) || !isset($data['domains'][0]['default'])) { 59 | throw new Exception\UnprocessableEntityException( 60 | 'The server encountered an error while processing the request. The submitted data could not be processed.', 61 | '{"detail":[{"loc":["body","domains"],"msg":"invalid structure","type":"value_error.invalid_structure"}]}', 62 | 422 63 | ); 64 | } 65 | 66 | if (!is_array($data['configurations']) || empty($data['configurations']) || !isset($data['configurations']['name'])) { 67 | throw new Exception\UnprocessableEntityException( 68 | 'The server encountered an error while processing the request. The submitted data could not be processed.', 69 | '{"detail":[{"loc":["body","configurations"],"msg":"invalid structure","type":"value_error.invalid_structure"}]}', 70 | 422 71 | ); 72 | } 73 | 74 | if (!is_array($data['modules']) || empty($data['modules']) || !isset($data['modules'][0]['name']) || !isset($data['modules'][0]['version'])) { 75 | throw new Exception\UnprocessableEntityException( 76 | 'The server encountered an error while processing the request. The submitted data could not be processed.', 77 | '{"detail":[{"loc":["body","modules"],"msg":"invalid structure","type":"value_error.invalid_structure"}]}', 78 | 422 79 | ); 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /tests/unit_tests/PayplugTest.php: -------------------------------------------------------------------------------- 1 | 'cba', 16 | 'apiVersion' => null 17 | )); 18 | 19 | $configuration = Payplug::getDefaultConfiguration(); 20 | 21 | $this->assertEquals('cba', $configuration->getToken()); 22 | $this->assertEquals('2019-06-14', $configuration->getApiVersion()); 23 | } 24 | public function testCanInitializeConfiguration() 25 | { 26 | Payplug::init(array( 27 | 'secretKey' => 'cba', 28 | 'apiVersion' => '1970-01-01' 29 | )); 30 | 31 | $configuration = Payplug::getDefaultConfiguration(); 32 | 33 | $this->assertEquals('cba', $configuration->getToken()); 34 | $this->assertEquals('1970-01-01', $configuration->getApiVersion()); 35 | } 36 | 37 | public function testDeprecatedCanInitializeConfiguration() 38 | { 39 | Payplug::setSecretKey('cba'); 40 | 41 | $configuration = Payplug::getDefaultConfiguration(); 42 | 43 | $this->assertEquals('cba', $configuration->getToken()); 44 | } 45 | 46 | public function testCannotInitializeConfigurationWhenLiveTokenIsNotAString() 47 | { 48 | $this->expectException('\PayPlug\Exception\ConfigurationException'); 49 | Payplug::init(array( 50 | 'secretKey' => true, 51 | 'apiVersion' => '1970-01-01', 52 | )); 53 | } 54 | 55 | public function testDeprecatedCannotInitializeConfigurationWhenLiveTokenIsNotAString() 56 | { 57 | $this->expectException('\PayPlug\Exception\ConfigurationException'); 58 | Payplug::setSecretKey(true); 59 | } 60 | 61 | public function testCannotInitializeConfigurationWhenTestTokenIsArray() 62 | { 63 | $this->expectException('\PayPlug\Exception\ConfigurationException'); 64 | 65 | Payplug::init(array( 66 | 'secretKey' => array( 67 | 'LIVE_TOKEN' => 'cba' 68 | ), 69 | 'apiVersion' => null, 70 | )); 71 | } 72 | 73 | public function testDeprecatedCannotInitializeConfigurationWhenTestTokenIsArray() 74 | { 75 | $this->expectException('\PayPlug\Exception\ConfigurationException'); 76 | 77 | Payplug::setSecretKey(array( 78 | 'LIVE_TOKEN' => 'cba' 79 | )); 80 | } 81 | 82 | public function testCanGetAToken() 83 | { 84 | $configuration = Payplug::init(array('secretKey' => 'cba', 'apiVersion' => null)); 85 | $this->assertEquals('cba', $configuration->getToken()); 86 | } 87 | 88 | public function testDeprecatedCanGetAToken() 89 | { 90 | $configuration = Payplug::setSecretKey('cba'); 91 | $this->assertEquals('cba', $configuration->getToken()); 92 | } 93 | 94 | /** 95 | * @runInSeparateProcess so that static default configuration is cleared before the test 96 | */ 97 | public function testThrowsExceptionWhenDefaultConfigurationIsNotSet() 98 | { 99 | $this->expectException('\Payplug\Exception\ConfigurationNotSetException'); 100 | Payplug::getDefaultConfiguration(); 101 | } 102 | 103 | public function testCanSetDefaultConfiguration() 104 | { 105 | $configuration = Payplug::init(array('secretKey' => 'abc', 'apiVersion' => null)); 106 | Payplug::setDefaultConfiguration($configuration); 107 | $this->assertEquals($configuration, Payplug::getDefaultConfiguration()); 108 | } 109 | 110 | public function testDeprecatedCanSetDefaultConfiguration() 111 | { 112 | $configuration = Payplug::setSecretKey('abc'); 113 | Payplug::setDefaultConfiguration($configuration); 114 | $this->assertEquals($configuration, Payplug::getDefaultConfiguration()); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /lib/Payplug/Resource/APIResource.php: -------------------------------------------------------------------------------- 1 | _attributes; 58 | } 59 | 60 | /** 61 | * Sets the attributes of this resource. 62 | * 63 | * @param array $attributes The attributes to set. 64 | */ 65 | protected function setAttributes(array $attributes) 66 | { 67 | $this->_attributes = $attributes; 68 | } 69 | 70 | /** 71 | * Reads an API resource property. 72 | * 73 | * @param string $attribute the key of the attribute to get 74 | * 75 | * @return mixed The value of the attribute 76 | * 77 | * @throws Payplug\Exception\UndefinedAttributeException 78 | */ 79 | public function __get($attribute) 80 | { 81 | if ($this->__isset($attribute)) { 82 | return $this->_attributes[$attribute]; 83 | } 84 | 85 | throw new Payplug\Exception\UndefinedAttributeException('Requested attribute ' . $attribute . ' is undefined.'); 86 | } 87 | 88 | /** 89 | * Checks if an API resource property is set 90 | * 91 | * @param string $attribute the key of the attribute to check 92 | * 93 | * @return bool True if the property is set. False otherwise. 94 | */ 95 | public function __isset($attribute) 96 | { 97 | return array_key_exists($attribute, $this->_attributes); 98 | } 99 | 100 | 101 | /** 102 | * Sets an API resource property. 103 | * 104 | * @param string $attribute the attribute key 105 | * @param mixed $value the new value of the attribute 106 | */ 107 | public function __set($attribute, $value) 108 | { 109 | $this->_attributes[$attribute] = $value; 110 | } 111 | 112 | /** 113 | * Initializes the resource. 114 | * This method must be overridden when the resource has objects as attributes. 115 | * 116 | * @param array $attributes the attributes to initialize. 117 | */ 118 | protected function initialize(array $attributes) 119 | { 120 | $this->setAttributes($attributes); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /lib/Payplug/Core/APIRoutes.php: -------------------------------------------------------------------------------- 1 | $value) { 53 | $route = str_replace('{' . $parameter . '}', $value, $route); 54 | } 55 | 56 | $resourceIdUrl = $resourceId ? '/' . $resourceId : ''; 57 | 58 | $query_parameters = ''; 59 | if (!empty($query_datas)) 60 | $query_parameters = '?' . http_build_query($query_datas); 61 | 62 | if (in_array($route, [self::OAUTH2_TOKEN_RESOURCE, self::OAUTH2_AUTH_RESOURCE]) && false !== strpos(self::$API_BASE_URL, 'https://service.')) { 63 | self::$API_BASE_URL = 'https://hydra--4444.external.gamma.notpayplug.com'; 64 | } 65 | 66 | return self::$API_BASE_URL . ($with_version ? '/v' . self::API_VERSION : '') . $route . $resourceIdUrl . $query_parameters; 67 | } 68 | 69 | /** 70 | * Get the route to a specified resource. 71 | * 72 | * @param string $route One of the routes defined above 73 | * @param array $parameters The parameters required by the route. 74 | * 75 | * @return string the full URL to the resource 76 | * 77 | */ 78 | public static function getServiceRoute($route, array $parameters = array()) 79 | { 80 | return self::$SERVICE_BASE_URL . $route . ($parameters ? '?' . http_build_query($parameters) : ''); 81 | } 82 | 83 | /** 84 | * @description set $API_BASE_URL from plugin 85 | * @param $apiBaseUrl 86 | */ 87 | public static function setApiBaseUrl($apiBaseUrl) 88 | { 89 | self::$API_BASE_URL = $apiBaseUrl; 90 | } 91 | 92 | /** 93 | * @description set $SERVICE_BASE_URL from plugin 94 | * @param $serviceBaseUrl 95 | */ 96 | public static function setServiceBaseUrl($serviceBaseUrl) 97 | { 98 | self::$SERVICE_BASE_URL = $serviceBaseUrl; 99 | } 100 | 101 | /** 102 | * Gets a route that allows to check whether the remote API is up. 103 | * 104 | * @return string the full URL to the test resource 105 | */ 106 | public static function getTestRoute() 107 | { 108 | return APIRoutes::$API_BASE_URL . '/test'; 109 | } 110 | } 111 | 112 | APIRoutes::$API_BASE_URL = 'https://api.payplug.com'; 113 | APIRoutes::$SERVICE_BASE_URL = 'https://retail.service.payplug.com'; 114 | -------------------------------------------------------------------------------- /tests/unit_tests/Resource/CardTest.php: -------------------------------------------------------------------------------- 1 | _configuration = new Payplug\Payplug('abc'); 21 | Payplug\Payplug::setDefaultConfiguration($this->_configuration); 22 | 23 | $this->_requestMock = $this->createMock('\Payplug\Core\IHttpRequest'); 24 | Payplug\Core\HttpClient::$REQUEST_HANDLER = $this->_requestMock; 25 | } 26 | 27 | protected function setUpTwice() 28 | { 29 | $this->_configuration = new Payplug\Payplug('abc','1970-01-01'); 30 | Payplug\Payplug::setDefaultConfiguration($this->_configuration); 31 | 32 | $this->_requestMock = $this->createMock('\Payplug\Core\IHttpRequest'); 33 | Payplug\Core\HttpClient::$REQUEST_HANDLER = $this->_requestMock; 34 | } 35 | 36 | public function testCreateCardFromAttributes() 37 | { 38 | $card = Card::fromAttributes(array( 39 | 'id' => 'card_167oJVCpvtR9j8N85LraL2GA', 40 | 'object' => 'card', 41 | 'created_at' => 1431523049, 42 | 'is_live' => false, 43 | 'last4' => '1111', 44 | 'brand' => 'Visa', 45 | 'exp_month' => 5, 46 | 'exp_year' => 2029, 47 | 'customer_id' => 'cus_6ESfofiMiLBjC6', 48 | 'country' => 'France', 49 | 'metadata' => array( 50 | 'a_custom_field' => 'a custom value', 51 | 'another_key' => 'another value' 52 | ) 53 | )); 54 | 55 | $this->assertEquals('card_167oJVCpvtR9j8N85LraL2GA', $card->id); 56 | $this->assertEquals('card', $card->object); 57 | $this->assertEquals(1431523049, $card->created_at); 58 | $this->assertEquals(false, $card->is_live); 59 | $this->assertEquals('1111', $card->last4); 60 | $this->assertEquals('Visa', $card->brand); 61 | $this->assertEquals(5, $card->exp_month); 62 | $this->assertEquals(2029, $card->exp_year); 63 | $this->assertEquals('cus_6ESfofiMiLBjC6', $card->customer_id); 64 | $this->assertEquals('France', $card->country); 65 | 66 | $this->assertEquals('a custom value', $card->metadata['a_custom_field']); 67 | $this->assertEquals('another value', $card->metadata['another_key']); 68 | } 69 | 70 | public function testCardDeleteCardObject() 71 | { 72 | function testCardDeleteCardObject_getinfo($option) { 73 | switch($option) { 74 | case CURLINFO_HTTP_CODE: 75 | return 200; 76 | } 77 | return null; 78 | } 79 | $GLOBALS['CURLOPT_URL_DATA'] = null; 80 | function testCardDeleteCardObject_setopt($option, $value = null) { 81 | switch($option) { 82 | case CURLOPT_URL: 83 | $GLOBALS['CURLOPT_URL_DATA'] = $value; 84 | return true; 85 | } 86 | return true; 87 | } 88 | 89 | $this->_requestMock 90 | ->expects($this->once()) 91 | ->method('exec') 92 | ->will($this->returnValue('{"status":"ok"}')); 93 | 94 | $this->_requestMock 95 | ->expects($this->any()) 96 | ->method('getinfo') 97 | ->will($this->returnCallback(function($option) { 98 | switch($option) { 99 | case CURLINFO_HTTP_CODE: 100 | return 200; 101 | } 102 | return null; 103 | })); 104 | $this->_requestMock 105 | ->expects($this->any()) 106 | ->method('setopt') 107 | ->will($this->returnCallback(function($option, $value = null) { 108 | switch($option) { 109 | case CURLOPT_URL: 110 | $GLOBALS['CURLOPT_URL_DATA'] = $value; 111 | return true; 112 | } 113 | return true; 114 | })); 115 | 116 | $card = Card::fromAttributes(array('id' => 'a_card_id')); 117 | $card->delete(); 118 | 119 | $this->assertStringEndsWith('a_card_id', $GLOBALS['CURLOPT_URL_DATA']); 120 | 121 | unset($GLOBALS['CURLOPT_URL_DATA']); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /lib/Payplug/Payplug.php: -------------------------------------------------------------------------------- 1 | _token = $token; 42 | 43 | // if no given version then set a default 44 | $this->_apiVersion = $apiVersion ? $apiVersion : '2019-06-14'; 45 | } 46 | 47 | 48 | /** 49 | * Initializes a Authentication and sets it as the new default global authentication. 50 | * It also performs some checks before saving the authentication. 51 | * 52 | *
 53 |      * Expected array format for argument $authentication :
 54 |      * $authentication['TOKEN'] = 'YOUR TOKEN'
 55 |      * 
56 | * 57 | * @param string $token the authentication token 58 | * 59 | * @return Payplug the new client authentication 60 | * 61 | * @throws Exception\ConfigurationException 62 | * @deprecated Use Payplug::init(array('secretKey' => 'token', 'apiVersion' => 'version')) 63 | */ 64 | public static function setSecretKey($token) 65 | { 66 | if (!is_string($token)) { 67 | throw new Exception\ConfigurationException('Expected string values for the token.'); 68 | } 69 | 70 | $clientConfiguration = new Payplug($token); 71 | 72 | self::setDefaultConfiguration($clientConfiguration); 73 | 74 | return $clientConfiguration; 75 | } 76 | 77 | /** 78 | * Initializes a Authentication and sets it as the new default global authentication. 79 | * It also performs some checks before saving the authentication and set the API version 80 | * 81 | * @param array $props 82 | * @return Payplug 83 | * @throws ConfigurationException 84 | */ 85 | public static function init($props) 86 | { 87 | $secretKey = isset($props['secretKey']) && $props['secretKey'] ? $props['secretKey'] : null; 88 | $apiVersion = isset($props['apiVersion']) && $props['apiVersion'] ? $props['apiVersion'] : null; 89 | 90 | if (!$secretKey) { 91 | throw new Exception\ConfigurationException('Expected string values for the token.'); 92 | } 93 | 94 | $clientConfiguration = new Payplug($secretKey, $apiVersion); 95 | 96 | self::setDefaultConfiguration($clientConfiguration); 97 | 98 | return $clientConfiguration; 99 | } 100 | 101 | /** 102 | * Gets the token corresponding to the mode currently in use (Live token or Test token). 103 | * 104 | * @return string The current token 105 | */ 106 | public function getToken() 107 | { 108 | return $this->_token; 109 | } 110 | 111 | /** 112 | * Sets the API Version corresponding to the module currently in use. 113 | * 114 | * @return string 115 | */ 116 | public function getApiVersion() 117 | { 118 | return $this->_apiVersion; 119 | } 120 | 121 | /** 122 | * Gets the default global authentication. 123 | * 124 | * @return Payplug The last client authentication 125 | * 126 | * @throws Exception\ConfigurationNotSetException when the global authentication was not set. 127 | */ 128 | public static function getDefaultConfiguration() 129 | { 130 | if (self::$_defaultConfiguration === null) { 131 | throw new Exception\ConfigurationNotSetException('Unable to find an authentication.'); 132 | } 133 | 134 | return self::$_defaultConfiguration; 135 | } 136 | 137 | /** 138 | * Sets the new default client authentication. This authentication will be used when no authentication is explicitly 139 | * passed to methods. 140 | * 141 | * @param Payplug $defaultConfiguration the new default authentication 142 | */ 143 | public static function setDefaultConfiguration($defaultConfiguration) 144 | { 145 | self::$_defaultConfiguration = $defaultConfiguration; 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /lib/Payplug/Resource/Refund.php: -------------------------------------------------------------------------------- 1 | initialize($attributes); 21 | return $object; 22 | } 23 | 24 | /** 25 | * Creates a refund on a payment. 26 | * 27 | * @param string|Payment $payment the payment id or the payment object 28 | * @param array $data API data for refund 29 | * @param Payplug\Payplug $payplug the client configuration 30 | * 31 | * @return null|Refund the refund object 32 | * @throws Payplug\Exception\ConfigurationNotSetException 33 | */ 34 | public static function create($payment, $data = null, $payplug = null) 35 | { 36 | if ($payplug === null) { 37 | $payplug = Payplug\Payplug::getDefaultConfiguration(); 38 | } 39 | if ($payment instanceof Payment) { 40 | $payment = $payment->id; 41 | } 42 | 43 | $httpClient = new Payplug\Core\HttpClient($payplug); 44 | $response = $httpClient->post( 45 | Payplug\Core\APIRoutes::getRoute(Payplug\Core\APIRoutes::REFUND_RESOURCE, null, array('PAYMENT_ID' => $payment)), 46 | $data 47 | ); 48 | 49 | return Refund::fromAttributes($response['httpResponse']); 50 | } 51 | 52 | /** 53 | * Retrieves a refund object on a payment. 54 | * 55 | * @param string|Payment $payment the payment id or the payment object 56 | * @param string $refundId the refund id 57 | * @param Payplug\Payplug $payplug the client configuration 58 | * 59 | * @return null|Payplug\Resource\APIResource|Refund the refund object 60 | * 61 | * @throws Payplug\Exception\ConfigurationNotSetException 62 | */ 63 | public static function retrieve($payment, $refundId, $payplug = null) 64 | { 65 | if ($payplug === null) { 66 | $payplug = Payplug\Payplug::getDefaultConfiguration(); 67 | } 68 | if ($payment instanceof Payment) { 69 | $payment = $payment->id; 70 | } 71 | 72 | $httpClient = new Payplug\Core\HttpClient($payplug); 73 | $response = $httpClient->get( 74 | Payplug\Core\APIRoutes::getRoute( 75 | Payplug\Core\APIRoutes::REFUND_RESOURCE, $refundId, array('PAYMENT_ID' => $payment) 76 | ) 77 | ); 78 | 79 | return Refund::fromAttributes($response['httpResponse']); 80 | } 81 | 82 | /** 83 | * Lists the last refunds of a payment. 84 | * 85 | * @param string|Payment $payment the payment id or the payment object 86 | * @param Payplug\Payplug $payplug the client configuration 87 | * 88 | * @return null|Refund[] an array containing the refunds on success. 89 | * 90 | * @throws Payplug\Exception\ConfigurationNotSetException 91 | * @throws Payplug\Exception\UnexpectedAPIResponseException 92 | */ 93 | public static function listRefunds($payment, $payplug = null) 94 | { 95 | if ($payplug === null) { 96 | $payplug = Payplug\Payplug::getDefaultConfiguration(); 97 | } 98 | if ($payment instanceof Payment) { 99 | $payment = $payment->id; 100 | } 101 | 102 | $httpClient = new Payplug\Core\HttpClient($payplug); 103 | 104 | $response = $httpClient->get( 105 | Payplug\Core\APIRoutes::getRoute(Payplug\Core\APIRoutes::REFUND_RESOURCE, null, array('PAYMENT_ID' => $payment)) 106 | ); 107 | 108 | if (!array_key_exists('data', $response['httpResponse']) || !is_array($response['httpResponse']['data'])) { 109 | throw new Payplug\Exception\UnexpectedAPIResponseException( 110 | "Expected API response to contain 'data' key referencing an array.", 111 | $response['httpResponse'] 112 | ); 113 | } 114 | 115 | $refunds = array(); 116 | foreach ($response['httpResponse']['data'] as &$refund) { 117 | $refunds[] = Refund::fromAttributes($refund); 118 | } 119 | 120 | return $refunds; 121 | } 122 | 123 | /** 124 | * Returns an API resource that you can trust. 125 | * 126 | * @param Payplug\Payplug $payplug the client configuration. 127 | * 128 | * @return Payplug\Resource\APIResource The consistent API resource. 129 | * 130 | * @throws Payplug\Exception\UndefinedAttributeException when the local resource is invalid. 131 | */ 132 | function getConsistentResource($payplug = null) 133 | { 134 | if (!array_key_exists('id', $this->_attributes)) { 135 | throw new Payplug\Exception\UndefinedAttributeException('The id of the refund is not set.'); 136 | } 137 | else if (!array_key_exists('payment_id', $this->_attributes)) { 138 | throw new Payplug\Exception\UndefinedAttributeException('The payment_id of the refund is not set.'); 139 | } 140 | 141 | return Payplug\Resource\Refund::retrieve($this->_attributes['payment_id'], $this->_attributes['id'], $payplug); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /tests/unit_tests/OneySimulationTest.php: -------------------------------------------------------------------------------- 1 | _configuration = new Payplug\Payplug('abc'); 20 | Payplug\Payplug::setDefaultConfiguration($this->_configuration); 21 | 22 | $this->_requestMock = $this->createMock('\Payplug\Core\IHttpRequest'); 23 | Core\HttpClient::$REQUEST_HANDLER = $this->_requestMock; 24 | } 25 | 26 | public function testGetOneyPaymentSimulation() 27 | { 28 | $data = array( 29 | 'amount' => 50500, 30 | 'country' => 'FR', 31 | 'operations' => array( 32 | 'x3_with_fees', 33 | 'x4_with_fees', 34 | ), 35 | ); 36 | 37 | $response = array( 38 | 'x3_with_fees' => array( 39 | 'effective_annual_percentage_rate' => 19.25, 40 | 'nominal_annual_percentage_rate' => 17.74, 41 | 'total_cost' => 732, 42 | 'installments' => array( 43 | array ( 44 | 'date' => '2020-03-06T01:00:00.000Z', 45 | 'amount' => 16834, 46 | ), 47 | array ( 48 | 'date' => '2020-04-06T00:00:00.000Z', 49 | 'amount' => 16833, 50 | ), 51 | ), 52 | 'down_payment_amount' => 17565, 53 | ), 54 | 'x4_with_fees' => array( 55 | 'effective_annual_percentage_rate' => 19.25, 56 | 'nominal_annual_percentage_rate' => 17.74, 57 | 'total_cost' => 732, 58 | 'installments' => array( 59 | array ( 60 | 'date' => '2020-03-06T01:00:00.000Z', 61 | 'amount' => 16834, 62 | ), 63 | array ( 64 | 'date' => '2020-04-06T00:00:00.000Z', 65 | 'amount' => 16833, 66 | ), 67 | array ( 68 | 'date' => '2020-05-06T00:00:00.000Z', 69 | 'amount' => 16833, 70 | ), 71 | ), 72 | 'down_payment_amount' => 17565, 73 | ) 74 | ); 75 | 76 | $this->_requestMock 77 | ->expects($this->once()) 78 | ->method('exec') 79 | ->will($this->returnValue(json_encode($response))); 80 | 81 | $this->_requestMock 82 | ->expects($this->any()) 83 | ->method('getinfo') 84 | ->will($this->returnCallback(function($option) { 85 | switch($option) { 86 | case CURLINFO_HTTP_CODE: 87 | return 201; 88 | } 89 | return null; 90 | })); 91 | 92 | $simulations = OneySimulation::getSimulations($data); 93 | 94 | // check 3x with fees 95 | $this->assertArrayHasKey('x3_with_fees', $simulations); 96 | if (isset($simulations['x3_with_fees']) && $simulations['x3_with_fees']) { 97 | $x3_with_fees = $simulations['x3_with_fees']; 98 | 99 | $this->assertEquals(true, is_array($x3_with_fees)); 100 | $this->assertEquals(19.25, $x3_with_fees['effective_annual_percentage_rate']); 101 | $this->assertEquals(17.74, $x3_with_fees['nominal_annual_percentage_rate']); 102 | $this->assertEquals(732, $x3_with_fees['total_cost']); 103 | 104 | $installments_x3 = $x3_with_fees['installments']; 105 | 106 | $this->assertEquals(2, count($installments_x3)); 107 | $this->assertEquals('2020-03-06T01:00:00.000Z', $installments_x3[0]['date']); 108 | $this->assertEquals(16834, $installments_x3[0]['amount']); 109 | $this->assertEquals('2020-04-06T00:00:00.000Z', $installments_x3[1]['date']); 110 | $this->assertEquals(16833, $installments_x3[1]['amount']); 111 | 112 | $this->assertEquals(17565, $x3_with_fees['down_payment_amount']); 113 | } 114 | 115 | // check 4x with fees 116 | $this->assertArrayHasKey('x4_with_fees', $simulations); 117 | if (isset($simulations['x4_with_fees']) && $simulations['x4_with_fees']) { 118 | $x4_with_fees = $simulations['x4_with_fees']; 119 | 120 | $this->assertEquals(true, is_array($x4_with_fees)); 121 | $this->assertEquals(19.25, $x4_with_fees['effective_annual_percentage_rate']); 122 | $this->assertEquals(17.74, $x4_with_fees['nominal_annual_percentage_rate']); 123 | $this->assertEquals(732, $x4_with_fees['total_cost']); 124 | 125 | $installments_x4 = $x4_with_fees['installments']; 126 | 127 | $this->assertEquals(3, count($installments_x4)); 128 | 129 | $this->assertEquals('2020-03-06T01:00:00.000Z', $installments_x4[0]['date']); 130 | $this->assertEquals(16834, $installments_x4[0]['amount']); 131 | $this->assertEquals('2020-04-06T00:00:00.000Z', $installments_x4[1]['date']); 132 | $this->assertEquals(16833, $installments_x4[1]['amount']); 133 | $this->assertEquals('2020-05-06T00:00:00.000Z', $installments_x4[2]['date']); 134 | $this->assertEquals(16833, $installments_x4[2]['amount']); 135 | 136 | $this->assertEquals(17565, $x4_with_fees['down_payment_amount']); 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /tests/unit_tests/Resource/AccountingReportTest.php: -------------------------------------------------------------------------------- 1 | _configuration = new Payplug\Payplug('abc'); 22 | Payplug\Payplug::setDefaultConfiguration($this->_configuration); 23 | 24 | $this->_requestMock = $this->createMock('\Payplug\Core\IHttpRequest'); 25 | Payplug\Core\HttpClient::$REQUEST_HANDLER = $this->_requestMock; 26 | } 27 | 28 | protected function setUpTwice() 29 | { 30 | $this->_configuration = new Payplug\Payplug('abc','1970-01-01'); 31 | Payplug\Payplug::setDefaultConfiguration($this->_configuration); 32 | 33 | $this->_requestMock = $this->createMock('\Payplug\Core\IHttpRequest'); 34 | Payplug\Core\HttpClient::$REQUEST_HANDLER = $this->_requestMock; 35 | } 36 | 37 | public function testCreateAccountingReportFromAttributes() 38 | { 39 | $report= AccountingReport::fromAttributes(array( 40 | 'start_date' => '2020-01-01', 41 | 'object' => 'accounting_report', 42 | 'notification_url' => 'notification_url', 43 | 'end_date' => '2020-04-30', 44 | 'id' => 'ar_1GKEACvltTVXT5muBd3AQv', 45 | 'file_available_until' => 1588083743, 46 | 'temporary_url' => 'temporary_url' 47 | )); 48 | 49 | $this->assertEquals('ar_1GKEACvltTVXT5muBd3AQv', $report->id); 50 | $this->assertEquals('2020-01-01', $report->start_date); 51 | $this->assertEquals('2020-04-30', $report->end_date); 52 | $this->assertEquals('notification_url', $report->notification_url); 53 | $this->assertEquals('temporary_url', $report->temporary_url); 54 | $this->assertEquals(1588083743, $report->file_available_until); 55 | } 56 | 57 | public function testAccountingReportCreate() 58 | { 59 | $GLOBALS['CURLOPT_POSTFIELDS_DATA'] = null; 60 | 61 | $this->_requestMock 62 | ->expects($this->once()) 63 | ->method('exec') 64 | ->will($this->returnValue('{"status":"ok"}')); 65 | 66 | $this->_requestMock 67 | ->expects($this->atLeastOnce()) 68 | ->method('setopt') 69 | ->will($this->returnCallback(function($option, $value = null) { 70 | switch($option) { 71 | case CURLOPT_POSTFIELDS: 72 | $GLOBALS['CURLOPT_POSTFIELDS_DATA'] = json_decode($value, true); 73 | return true; 74 | } 75 | return true; 76 | })); 77 | $this->_requestMock 78 | ->expects($this->any()) 79 | ->method('getinfo') 80 | ->will($this->returnCallback(function($option) { 81 | switch($option) { 82 | case CURLINFO_HTTP_CODE: 83 | return 200; 84 | } 85 | return null; 86 | })); 87 | 88 | $report = AccountingReport::create(array( 89 | 'start_date' => '2020-01-01', 90 | 'object' => 'accounting_report', 91 | 'notification_url' => 'notification_url', 92 | 'end_date' => '2020-04-30', 93 | 'id' => 'ar_1GKEACvltTVXT5muBd3AQv', 94 | 'file_available_until' => 1588083743, 95 | 'temporary_url' => 'temporary_url' 96 | )); 97 | 98 | $this->assertTrue(is_array($GLOBALS['CURLOPT_POSTFIELDS_DATA'])); 99 | $this->assertEquals('ok', $report->status); 100 | 101 | unset($GLOBALS['CURLOPT_POSTFIELDS_DATA']); 102 | } 103 | 104 | public function testAccountingReportRetrieve() 105 | { 106 | $GLOBALS['CURLOPT_URL_DATA'] = null; 107 | 108 | $this->_requestMock 109 | ->expects($this->once()) 110 | ->method('exec') 111 | ->will($this->returnValue('{"status":"ok"}')); 112 | 113 | $this->_requestMock 114 | ->expects($this->any()) 115 | ->method('getinfo') 116 | ->will($this->returnCallback(function($option) { 117 | switch($option) { 118 | case CURLINFO_HTTP_CODE: 119 | return 200; 120 | } 121 | return null; 122 | })); 123 | $this->_requestMock 124 | ->expects($this->any()) 125 | ->method('setopt') 126 | ->will($this->returnCallback(function($option, $value = null) { 127 | switch($option) { 128 | case CURLOPT_URL: 129 | $GLOBALS['CURLOPT_URL_DATA'] = $value; 130 | return true; 131 | } 132 | return true; 133 | })); 134 | 135 | $report = AccountingReport::retrieve('a_report_id'); 136 | 137 | $this->assertStringEndsWith('a_report_id', $GLOBALS['CURLOPT_URL_DATA']); 138 | $this->assertEquals('ok', $report->status); 139 | 140 | unset($GLOBALS['CURLOPT_URL_DATA']); 141 | } 142 | 143 | public function testRetrieveConsistentAccountingReportWhenIdIsUndefined() 144 | { 145 | $this->expectException('\PayPlug\Exception\UndefinedAttributeException'); 146 | 147 | $report= AccountingReport::fromAttributes(array('this_report' => 'has_no_id')); 148 | $report->getConsistentResource(); 149 | } 150 | 151 | public function testRetrieveConsistentAccountingReport() 152 | { 153 | function testRetrieveConsistentAccountingReport_getinfo($option) { 154 | switch($option) { 155 | case CURLINFO_HTTP_CODE: 156 | return 200; 157 | } 158 | return null; 159 | } 160 | 161 | $this->_requestMock 162 | ->expects($this->once()) 163 | ->method('exec') 164 | ->will($this->returnValue('{"id": "ar_345"}')); 165 | 166 | $this->_requestMock 167 | ->expects($this->any()) 168 | ->method('setopt') 169 | ->will($this->returnValue(true)); 170 | $this->_requestMock 171 | ->expects($this->any()) 172 | ->method('getinfo') 173 | ->will($this->returnCallback(function($option) { 174 | switch($option) { 175 | case CURLINFO_HTTP_CODE: 176 | return 200; 177 | } 178 | return null; 179 | })); 180 | 181 | $report1 = AccountingReport::fromAttributes(array('id' => 'ar_123')); 182 | $report2 = $report1->getConsistentResource($this->_configuration); 183 | 184 | $this->assertEquals('ar_123', $report1->id); 185 | $this->assertEquals('ar_345', $report2->id); 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /lib/Payplug/Resource/InstallmentPlan.php: -------------------------------------------------------------------------------- 1 | initialize($attributes); 21 | return $object; 22 | } 23 | 24 | /** 25 | * Initializes the resource. 26 | * This method must be overridden when the resource has objects as attributes. 27 | * 28 | * @param array $attributes the attributes to initialize. 29 | */ 30 | protected function initialize(array $attributes) 31 | { 32 | parent::initialize($attributes); 33 | 34 | /* 35 | * @deprecated No longer use by API, use billing and shipping instead 36 | */ 37 | if (isset($attributes['customer'])) { 38 | $this->customer = PaymentCustomer::fromAttributes($attributes['customer']); 39 | } 40 | if (isset($attributes['billing'])) { 41 | $this->billing = PaymentBilling::fromAttributes($attributes['billing']); 42 | } 43 | if (isset($attributes['shipping'])) { 44 | $this->shipping = PaymentShipping::fromAttributes($attributes['shipping']); 45 | } 46 | if (isset($attributes['failure'])) { 47 | $this->failure = PaymentPaymentFailure::fromAttributes($attributes['failure']); 48 | } 49 | if (isset($attributes['hosted_payment'])) { 50 | $this->hosted_payment = PaymentHostedPayment::fromAttributes($attributes['hosted_payment']); 51 | } 52 | if (isset($attributes['notification'])) { 53 | $this->notification = PaymentNotification::fromAttributes($attributes['notification']); 54 | } 55 | $schedules = array(); 56 | if (isset($attributes['schedule'])) { 57 | foreach ($attributes['schedule'] as &$schedule) { 58 | $schedules[] = InstallmentPlanSchedule::fromAttributes($schedule); 59 | } 60 | } 61 | $this->schedule = $schedules; 62 | } 63 | 64 | /** 65 | * List the payments of this installment plan. 66 | * 67 | * @param Payplug\Payplug $payplug the client configuration 68 | * 69 | * @return null|Payment[] the array of payments of this installment plan 70 | * 71 | * @throws Payplug\Exception\UndefinedAttributeException 72 | * @throws Payplug\Exception\UnexpectedAPIResponseException 73 | */ 74 | public function listPayments($payplug = null) 75 | { 76 | if ($payplug === null) { 77 | $payplug = Payplug\Payplug::getDefaultConfiguration(); 78 | } 79 | 80 | if (!array_key_exists('id', $this->getAttributes())) { 81 | throw new Payplug\Exception\UndefinedAttributeException( 82 | "This installment plan object has no id. You can't list payments on it."); 83 | } 84 | $httpClient = new Payplug\Core\HttpClient($payplug); 85 | $response = $httpClient->get( 86 | Payplug\Core\APIRoutes::getRoute(Payplug\Core\APIRoutes::INSTALLMENT_PLAN_RESOURCE, $this->id) 87 | ); 88 | 89 | $payments = array(); 90 | foreach ($response['httpResponse']['schedule'] as $schedule) { 91 | foreach ($schedule['payment_ids'] as $payment_id) { 92 | $payments[$payment_id] = Payment::retrieve($payment_id, $payplug); 93 | } 94 | } 95 | 96 | return $payments; 97 | 98 | } 99 | 100 | /** 101 | * Aborts an InstallmentPlan. 102 | * 103 | * @param Payplug\Payplug $payplug the client configuration 104 | * 105 | * @return null|InstallmentPlan the aborted installment plan or null on error 106 | * 107 | * @throws Payplug\Exception\ConfigurationNotSetException 108 | */ 109 | public function abort($payplug = null) 110 | { 111 | if ($payplug === null) { 112 | $payplug = Payplug\Payplug::getDefaultConfiguration(); 113 | } 114 | 115 | $httpClient = new Payplug\Core\HttpClient($payplug); 116 | $response = $httpClient->patch( 117 | Payplug\Core\APIRoutes::getRoute(Payplug\Core\APIRoutes::INSTALLMENT_PLAN_RESOURCE, $this->id), 118 | array('aborted' => true) 119 | ); 120 | 121 | return InstallmentPlan::fromAttributes($response['httpResponse']); 122 | } 123 | 124 | /** 125 | * Retrieves an InstallmentPlan. 126 | * 127 | * @param string $installmentPlanId the installment plan ID 128 | * @param Payplug\Payplug $payplug the client configuration 129 | * 130 | * @return null|InstallmentPlan the retrieved installment plan or null on error 131 | * 132 | * @throws Payplug\Exception\ConfigurationNotSetException 133 | */ 134 | public static function retrieve($installmentPlanId, $payplug = null) 135 | { 136 | if ($payplug === null) { 137 | $payplug = Payplug\Payplug::getDefaultConfiguration(); 138 | } 139 | 140 | $httpClient = new Payplug\Core\HttpClient($payplug); 141 | $response = $httpClient->get( 142 | Payplug\Core\APIRoutes::getRoute(Payplug\Core\APIRoutes::INSTALLMENT_PLAN_RESOURCE, 143 | $installmentPlanId) 144 | ); 145 | 146 | return InstallmentPlan::fromAttributes($response['httpResponse']); 147 | } 148 | 149 | /** 150 | * Creates an InstallmentPlan. 151 | * 152 | * @param array $data API data for payment creation 153 | * @param Payplug\Payplug $payplug the client configuration 154 | * 155 | * @return null|InstallmentPlan the created installment plan instance 156 | * 157 | * @throws Payplug\Exception\ConfigurationNotSetException 158 | */ 159 | public static function create(array $data, $payplug = null) 160 | { 161 | if ($payplug === null) { 162 | $payplug = Payplug\Payplug::getDefaultConfiguration(); 163 | } 164 | 165 | $httpClient = new Payplug\Core\HttpClient($payplug); 166 | $response = $httpClient->post( 167 | Payplug\Core\APIRoutes::getRoute(Payplug\Core\APIRoutes::INSTALLMENT_PLAN_RESOURCE), 168 | $data 169 | ); 170 | 171 | return InstallmentPlan::fromAttributes($response['httpResponse']); 172 | } 173 | 174 | /** 175 | * Returns an API resource that you can trust. 176 | * 177 | * @param Payplug\Payplug $payplug the client configuration. 178 | * 179 | * @return Payplug\Resource\APIResource The consistent API resource. 180 | * 181 | * @throws Payplug\Exception\UndefinedAttributeException when the local resource is invalid. 182 | */ 183 | function getConsistentResource($payplug = null) 184 | { 185 | if (!array_key_exists('id', $this->_attributes)) { 186 | throw new Payplug\Exception\UndefinedAttributeException( 187 | 'The id of the installment plan is not set.'); 188 | } 189 | 190 | return InstallmentPlan::retrieve($this->_attributes['id'], $payplug); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/PayPluge-commercelibrary.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PayPluge-commercelibrary.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/PayPluge-commercelibrary" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/PayPluge-commercelibrary" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | PHP library for the Payplug API 2 | =============================== 3 | 4 | .. image:: https://travis-ci.org/payplug/payplug-php.svg?branch=master 5 | :target: https://travis-ci.org/payplug/payplug-php 6 | :alt: CI Status 7 | 8 | .. image:: https://img.shields.io/packagist/v/payplug/payplug-php.svg 9 | :target: https://packagist.org/packages/payplug/payplug-php 10 | :alt: Packagist 11 | 12 | This is the documentation of Payplug's PHP library. It is designed to 13 | help developers use Payplug as a payment solution in a simple, yet robust way. 14 | 15 | You can create a Payplug account at https://www.payplug.com/. 16 | 17 | Maintenance 18 | ----------- 19 | 20 | CA certificate (cacert.pem) should be updated every year during the first week of December. 21 | Go to https://curl.se/docs/caextract.html to get a recent one. 22 | 23 | Prerequisites 24 | ------------- 25 | 26 | Payplug's library relies on **cURL** to perform HTTP requests and requires **OpenSSL (1.0.1 or newer)** to secure transactions. You also need **PHP 5.3** or newer for the Payplug PHP V2. 27 | 28 | For version **PHP 5.2** or older, you must refer to Payplug PHP V1. 29 | 30 | Documentation 31 | ------------- 32 | 33 | Please see https://docs.payplug.com/api for the latest documentation. 34 | 35 | Installation 36 | ------------- 37 | 38 | **Option 1 - Strongly preferred)** via composer: 39 | 40 | - Get composer from the `composer website`_. 41 | - Make sure you have initialized your *composer.json*. 42 | - Run *composer require payplug/payplug-php* from your project directory. 43 | 44 | .. _composer website: https://getcomposer.org/download/ 45 | 46 | **Option 2-** clone the repository: 47 | 48 | :: 49 | 50 | git clone https://github.com/payplug/payplug-php.git 51 | 52 | **Option 3)** download as a tarball: 53 | 54 | - Download the most recent tarball from the `download page V2`_ (V2 for **PHP 5.3** or newer) 55 | - Download the most recent tarball from the `download page V1`_ (V1 for **PHP 5.2** or older) 56 | - Unpack the tarball 57 | - Put the files somewhere in your project 58 | 59 | .. _download page V1: https://github.com/payplug/payplug-php/releases/tag/V1.1.2 60 | .. _download page V2: https://github.com/payplug/payplug-php/releases 61 | 62 | __ https://bitbucket.org/payplug/payplug_php/downloads#tag-downloads 63 | 64 | To get started, add the following to your PHP script (if you are not running a framework): 65 | 66 | .. code-block:: php 67 | 68 | 88 | 89 | For a bug fix, use: 90 | 91 | :: 92 | 93 | git checkout develop 94 | git pull origin develop 95 | git checkout -b 96 | 97 | 2. **Work on your feature or fix:** 98 | 99 | Make your code changes and commit them to your feature or fix branch. 100 | 101 | 3. **Create a merge request:** 102 | 103 | Once your feature or fix is ready, create a merge request from your branch to the `develop` branch. Get your changes reviewed by your peers. 104 | 105 | 4. **Release preparation:** 106 | 107 | When it's time for a release, create an intermediary branch called `release-` from the `develop` branch: 108 | 109 | :: 110 | 111 | git checkout develop 112 | git pull origin develop 113 | git checkout -b release- 114 | 115 | 5. **Finalize the release:** 116 | 117 | Test the code on the `release-` branch thoroughly. Fix any bugs or issues that arise. 118 | 119 | 6. **Merge to master:** 120 | 121 | Once the release is tested and stable, create a merge request from the `release-` branch to the `master` branch. This signifies a successful release. 122 | 123 | 7. **Tag the release:** 124 | 125 | After the merge to `master`, create a new tag to mark the release version: 126 | 127 | :: 128 | 129 | git checkout master 130 | git pull origin master 131 | git tag -a -m "Release " 132 | git push origin master --tags 133 | 134 | Usage 135 | ----- 136 | 137 | Here's how simple it is to create a payment request: 138 | 139 | .. code-block:: php 140 | 141 | 'sk_live_YOUR_PRIVATE_KEY', 147 | 'apiVersion' => 'THE_API_VERSION_YOU_WANT', 148 | )); 149 | 150 | // Create a payment request of €9.99. The payment confirmation (IPN) will be sent to "'https://example.net/notifications?id='.$customer_id". 151 | // Note that all amounts must be expressed in centimes as positive whole numbers (€9.99 = 999 centimes). 152 | // Metadata allow you to include additional information when processing payments or refunds. 153 | $customer_id = '42710'; 154 | 155 | $payment = Payplug\Payment::create(array( 156 | 'amount' => 999, 157 | 'currency' => 'EUR', 158 | 'billing' => array( 159 | 'title' => 'mr', 160 | 'first_name' => 'John', 161 | 'last_name' => 'Watson', 162 | 'email' => 'john.watson@example.net', 163 | 'address1' => '221B Baker Street', 164 | 'postcode' => 'NW16XE', 165 | 'city' => 'London', 166 | 'country' => 'GB', 167 | 'language' => 'en' 168 | ), 169 | 'shipping' => array( 170 | 'title' => 'mr', 171 | 'first_name' => 'John', 172 | 'last_name' => 'Watson', 173 | 'email' => 'john.watson@example.net', 174 | 'address1' => '221B Baker Street', 175 | 'postcode' => 'NW16XE', 176 | 'city' => 'London', 177 | 'country' => 'GB', 178 | 'language' => 'en', 179 | 'delivery_type' => 'BILLING' 180 | ), 181 | 'hosted_payment' => array( 182 | 'return_url' => 'https://example.net/return?id='.$customer_id, 183 | 'cancel_url' => 'https://example.net/cancel?id='.$customer_id 184 | ), 185 | 'notification_url' => 'https://example.net/notifications?id='.$customer_id, 186 | 'metadata' => array( 187 | 'customer_id' => $customer_id 188 | ) 189 | )); 190 | 191 | // You will be able to find how the payment object is built in the documentation. 192 | // For instance, if you want to get a URL to the payment page, you can do: 193 | $paymentUrl = $payment->hosted_payment->payment_url; 194 | 195 | // Then, you can redirect the user to the payment page 196 | header("Location: $paymentUrl"); 197 | 198 | Go further: 199 | ----------- 200 | Tests: 201 | ++++++ 202 | See tests/README.rst. 203 | 204 | Project Owners 205 | -------------- 206 | 207 | This project is maintained by: 208 | 209 | 210 | - [Imène Lajili](https://github.com/ilajili) 211 | - [Manuel Mesquita](https://github.com/PPmmesquita) 212 | 213 | For any questions or concerns about the workflow, feel free to reach out to the project owners. 214 | 215 | 216 | -------------------------------------------------------------------------------- /tests/unit_tests/Resource/OneySimulationResourceTest.php: -------------------------------------------------------------------------------- 1 | array( 16 | 'effective_annual_percentage_rate' => 19.25, 17 | 'nominal_annual_percentage_rate' => 17.74, 18 | 'total_cost' => 732, 19 | 'installments' => array( 20 | array ( 21 | 'date' => '2020-03-06T01:00:00.000Z', 22 | 'amount' => 16834, 23 | ), 24 | array ( 25 | 'date' => '2020-04-06T00:00:00.000Z', 26 | 'amount' => 16833, 27 | ), 28 | ), 29 | 'down_payment_amount' => 17565, 30 | ) 31 | )); 32 | 33 | $this->assertObjectHasAttribute('x3_with_fees', $simulations); 34 | $this->assertEquals(true, is_array($simulations->x3_with_fees)); 35 | $this->assertEquals(19.25, $simulations->x3_with_fees['effective_annual_percentage_rate']); 36 | $this->assertEquals(17.74, $simulations->x3_with_fees['nominal_annual_percentage_rate']); 37 | $this->assertEquals(732, $simulations->x3_with_fees['total_cost']); 38 | 39 | $installments = $simulations->x3_with_fees['installments']; 40 | 41 | $this->assertEquals(2, count($installments)); 42 | 43 | $this->assertEquals('2020-03-06T01:00:00.000Z', $installments[0]['date']); 44 | $this->assertEquals(16834, $installments[0]['amount']); 45 | $this->assertEquals('2020-04-06T00:00:00.000Z', $installments[1]['date']); 46 | $this->assertEquals(16833, $installments[1]['amount']); 47 | 48 | $this->assertEquals(17565, $simulations->x3_with_fees['down_payment_amount']); 49 | } 50 | 51 | public function testGetOneySimulation4xWithFees() 52 | { 53 | $simulations = OneySimulationResource::fromAttributes(array( 54 | 'x4_with_fees' => array( 55 | 'effective_annual_percentage_rate' => 19.25, 56 | 'nominal_annual_percentage_rate' => 17.74, 57 | 'total_cost' => 732, 58 | 'installments' => array( 59 | array ( 60 | 'date' => '2020-03-06T01:00:00.000Z', 61 | 'amount' => 16834, 62 | ), 63 | array ( 64 | 'date' => '2020-04-06T00:00:00.000Z', 65 | 'amount' => 16833, 66 | ), 67 | array ( 68 | 'date' => '2020-05-06T00:00:00.000Z', 69 | 'amount' => 16833, 70 | ), 71 | ), 72 | 'down_payment_amount' => 17565, 73 | ) 74 | )); 75 | 76 | $this->assertObjectHasAttribute('x4_with_fees', $simulations); 77 | $this->assertEquals(true, is_array($simulations->x4_with_fees)); 78 | $this->assertEquals(19.25, $simulations->x4_with_fees['effective_annual_percentage_rate']); 79 | $this->assertEquals(17.74, $simulations->x4_with_fees['nominal_annual_percentage_rate']); 80 | $this->assertEquals(732, $simulations->x4_with_fees['total_cost']); 81 | 82 | $installments = $simulations->x4_with_fees['installments']; 83 | 84 | $this->assertEquals(3, count($installments)); 85 | 86 | $this->assertEquals('2020-03-06T01:00:00.000Z', $installments[0]['date']); 87 | $this->assertEquals(16834, $installments[0]['amount']); 88 | $this->assertEquals('2020-04-06T00:00:00.000Z', $installments[1]['date']); 89 | $this->assertEquals(16833, $installments[1]['amount']); 90 | $this->assertEquals('2020-05-06T00:00:00.000Z', $installments[2]['date']); 91 | $this->assertEquals(16833, $installments[2]['amount']); 92 | 93 | $this->assertEquals(17565, $simulations->x4_with_fees['down_payment_amount']); 94 | } 95 | 96 | public function testGetOneySimulationWithFees() 97 | { 98 | $simulations = OneySimulationResource::fromAttributes(array( 99 | 'x3_with_fees' => array( 100 | 'effective_annual_percentage_rate' => 19.25, 101 | 'nominal_annual_percentage_rate' => 17.74, 102 | 'total_cost' => 732, 103 | 'installments' => array( 104 | array ( 105 | 'date' => '2020-03-06T01:00:00.000Z', 106 | 'amount' => 16834, 107 | ), 108 | array ( 109 | 'date' => '2020-04-06T00:00:00.000Z', 110 | 'amount' => 16833, 111 | ), 112 | ), 113 | 'down_payment_amount' => 17565, 114 | ), 115 | 'x4_with_fees' => array( 116 | 'effective_annual_percentage_rate' => 19.25, 117 | 'nominal_annual_percentage_rate' => 17.74, 118 | 'total_cost' => 732, 119 | 'installments' => array( 120 | array ( 121 | 'date' => '2020-03-06T01:00:00.000Z', 122 | 'amount' => 16834, 123 | ), 124 | array ( 125 | 'date' => '2020-04-06T00:00:00.000Z', 126 | 'amount' => 16833, 127 | ), 128 | array ( 129 | 'date' => '2020-05-06T00:00:00.000Z', 130 | 'amount' => 16833, 131 | ), 132 | ), 133 | 'down_payment_amount' => 17565, 134 | ) 135 | )); 136 | 137 | // check 3x with fees 138 | $this->assertObjectHasAttribute('x3_with_fees', $simulations); 139 | $this->assertEquals(true, is_array($simulations->x3_with_fees)); 140 | $this->assertEquals(19.25, $simulations->x3_with_fees['effective_annual_percentage_rate']); 141 | $this->assertEquals(17.74, $simulations->x3_with_fees['nominal_annual_percentage_rate']); 142 | $this->assertEquals(732, $simulations->x3_with_fees['total_cost']); 143 | 144 | $installments_x3 = $simulations->x3_with_fees['installments']; 145 | 146 | $this->assertEquals(2, count($installments_x3)); 147 | $this->assertEquals('2020-03-06T01:00:00.000Z', $installments_x3[0]['date']); 148 | $this->assertEquals(16834, $installments_x3[0]['amount']); 149 | $this->assertEquals('2020-04-06T00:00:00.000Z', $installments_x3[1]['date']); 150 | $this->assertEquals(16833, $installments_x3[1]['amount']); 151 | 152 | $this->assertEquals(17565, $simulations->x3_with_fees['down_payment_amount']); 153 | 154 | // check 4x with fees 155 | $this->assertObjectHasAttribute('x4_with_fees', $simulations); 156 | $this->assertEquals(true, is_array($simulations->x4_with_fees)); 157 | $this->assertEquals(19.25, $simulations->x4_with_fees['effective_annual_percentage_rate']); 158 | $this->assertEquals(17.74, $simulations->x4_with_fees['nominal_annual_percentage_rate']); 159 | $this->assertEquals(732, $simulations->x4_with_fees['total_cost']); 160 | 161 | $installments_x4 = $simulations->x4_with_fees['installments']; 162 | 163 | $this->assertEquals(3, count($installments_x4)); 164 | 165 | $this->assertEquals('2020-03-06T01:00:00.000Z', $installments_x4[0]['date']); 166 | $this->assertEquals(16834, $installments_x4[0]['amount']); 167 | $this->assertEquals('2020-04-06T00:00:00.000Z', $installments_x4[1]['date']); 168 | $this->assertEquals(16833, $installments_x4[1]['amount']); 169 | $this->assertEquals('2020-05-06T00:00:00.000Z', $installments_x4[2]['date']); 170 | $this->assertEquals(16833, $installments_x4[2]['amount']); 171 | 172 | $this->assertEquals(17565, $simulations->x4_with_fees['down_payment_amount']); 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # PayPlug e-commerce library documentation build configuration file, created by 4 | # sphinx-quickstart on Thu Apr 10 10:05:26 2014. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import sys 16 | import os 17 | 18 | # If extensions (or modules to document with autodoc) are in another directory, 19 | # add these directories to sys.path here. If the directory is relative to the 20 | # documentation root, use os.path.abspath to make it absolute, like shown here. 21 | #sys.path.insert(0, os.path.abspath('.')) 22 | 23 | # -- General configuration ------------------------------------------------ 24 | 25 | # If your documentation needs a minimal Sphinx version, state it here. 26 | #needs_sphinx = '1.0' 27 | 28 | # Add any Sphinx extension module names here, as strings. They can be 29 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 30 | # ones. 31 | extensions = [] 32 | 33 | # Add any paths that contain templates here, relative to this directory. 34 | templates_path = ['_templates'] 35 | 36 | # The suffix of source filenames. 37 | source_suffix = '.rst' 38 | 39 | # The encoding of source files. 40 | #source_encoding = 'utf-8-sig' 41 | 42 | # The master toctree document. 43 | master_doc = 'index' 44 | 45 | # General information about the project. 46 | project = u'PayPlug PHP library' 47 | copyright = u'2014, PayPlug' 48 | 49 | # The version info for the project you're documenting, acts as replacement for 50 | # |version| and |release|, also used in various other places throughout the 51 | # built documents. 52 | # 53 | # The short X.Y version. 54 | version = '1.0' 55 | # The full version, including alpha/beta/rc tags. 56 | release = '1.0' 57 | 58 | # The language for content autogenerated by Sphinx. Refer to documentation 59 | # for a list of supported languages. 60 | #language = None 61 | 62 | # There are two options for replacing |today|: either, you set today to some 63 | # non-false value, then it is used: 64 | #today = '' 65 | # Else, today_fmt is used as the format for a strftime call. 66 | #today_fmt = '%B %d, %Y' 67 | 68 | # List of patterns, relative to source directory, that match files and 69 | # directories to ignore when looking for source files. 70 | exclude_patterns = ['_build'] 71 | 72 | # The reST default role (used for this markup: `text`) to use for all 73 | # documents. 74 | #default_role = None 75 | 76 | # If true, '()' will be appended to :func: etc. cross-reference text. 77 | #add_function_parentheses = True 78 | 79 | # If true, the current module name will be prepended to all description 80 | # unit titles (such as .. function::). 81 | #add_module_names = True 82 | 83 | # If true, sectionauthor and moduleauthor directives will be shown in the 84 | # output. They are ignored by default. 85 | #show_authors = False 86 | 87 | # The name of the Pygments (syntax highlighting) style to use. 88 | pygments_style = 'sphinx' 89 | 90 | # A list of ignored prefixes for module index sorting. 91 | #modindex_common_prefix = [] 92 | 93 | # If true, keep warnings as "system message" paragraphs in the built documents. 94 | #keep_warnings = False 95 | 96 | 97 | # -- Options for HTML output ---------------------------------------------- 98 | 99 | # The theme to use for HTML and HTML Help pages. See the documentation for 100 | # a list of builtin themes. 101 | html_theme = 'default' 102 | 103 | # Theme options are theme-specific and customize the look and feel of a theme 104 | # further. For a list of options available for each theme, see the 105 | # documentation. 106 | #html_theme_options = {} 107 | 108 | # Add any paths that contain custom themes here, relative to this directory. 109 | #html_theme_path = [] 110 | 111 | # The name for this set of Sphinx documents. If None, it defaults to 112 | # " v documentation". 113 | #html_title = None 114 | 115 | # A shorter title for the navigation bar. Default is the same as html_title. 116 | #html_short_title = None 117 | 118 | # The name of an image file (relative to this directory) to place at the top 119 | # of the sidebar. 120 | #html_logo = None 121 | 122 | # The name of an image file (within the static path) to use as favicon of the 123 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 124 | # pixels large. 125 | #html_favicon = None 126 | 127 | # Add any paths that contain custom static files (such as style sheets) here, 128 | # relative to this directory. They are copied after the builtin static files, 129 | # so a file named "default.css" will overwrite the builtin "default.css". 130 | html_static_path = ['_static'] 131 | 132 | # Add any extra paths that contain custom files (such as robots.txt or 133 | # .htaccess) here, relative to this directory. These files are copied 134 | # directly to the root of the documentation. 135 | #html_extra_path = [] 136 | 137 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 138 | # using the given strftime format. 139 | #html_last_updated_fmt = '%b %d, %Y' 140 | 141 | # If true, SmartyPants will be used to convert quotes and dashes to 142 | # typographically correct entities. 143 | #html_use_smartypants = True 144 | 145 | # Custom sidebar templates, maps document names to template names. 146 | #html_sidebars = {} 147 | 148 | # Additional templates that should be rendered to pages, maps page names to 149 | # template names. 150 | #html_additional_pages = {} 151 | 152 | # If false, no module index is generated. 153 | #html_domain_indices = True 154 | 155 | # If false, no index is generated. 156 | #html_use_index = True 157 | 158 | # If true, the index is split into individual pages for each letter. 159 | #html_split_index = False 160 | 161 | # If true, links to the reST sources are added to the pages. 162 | #html_show_sourcelink = True 163 | 164 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 165 | #html_show_sphinx = True 166 | 167 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 168 | #html_show_copyright = True 169 | 170 | # If true, an OpenSearch description file will be output, and all pages will 171 | # contain a tag referring to it. The value of this option must be the 172 | # base URL from which the finished HTML is served. 173 | #html_use_opensearch = '' 174 | 175 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 176 | #html_file_suffix = None 177 | 178 | # Output file base name for HTML help builder. 179 | htmlhelp_basename = 'PayPluge-commercelibrarydoc' 180 | 181 | 182 | # -- Options for LaTeX output --------------------------------------------- 183 | 184 | latex_elements = { 185 | # The paper size ('letterpaper' or 'a4paper'). 186 | #'papersize': 'letterpaper', 187 | 188 | # The font size ('10pt', '11pt' or '12pt'). 189 | #'pointsize': '10pt', 190 | 191 | # Additional stuff for the LaTeX preamble. 192 | #'preamble': '', 193 | } 194 | 195 | # Grouping the document tree into LaTeX files. List of tuples 196 | # (source start file, target name, title, 197 | # author, documentclass [howto, manual, or own class]). 198 | latex_documents = [ 199 | ('index', 'PayPluge-commercelibrary.tex', u'PayPlug e-commerce library Documentation', 200 | u'PayPlug', 'manual'), 201 | ] 202 | 203 | # The name of an image file (relative to this directory) to place at the top of 204 | # the title page. 205 | #latex_logo = None 206 | 207 | # For "manual" documents, if this is true, then toplevel headings are parts, 208 | # not chapters. 209 | #latex_use_parts = False 210 | 211 | # If true, show page references after internal links. 212 | #latex_show_pagerefs = False 213 | 214 | # If true, show URL addresses after external links. 215 | #latex_show_urls = False 216 | 217 | # Documents to append as an appendix to all manuals. 218 | #latex_appendices = [] 219 | 220 | # If false, no module index is generated. 221 | #latex_domain_indices = True 222 | 223 | 224 | # -- Options for manual page output --------------------------------------- 225 | 226 | # One entry per manual page. List of tuples 227 | # (source start file, name, description, authors, manual section). 228 | man_pages = [ 229 | ('index', 'paypluge-commercelibrary', u'PayPlug e-commerce library Documentation', 230 | [u'PayPlug'], 1) 231 | ] 232 | 233 | # If true, show URL addresses after external links. 234 | #man_show_urls = False 235 | 236 | 237 | # -- Options for Texinfo output ------------------------------------------- 238 | 239 | # Grouping the document tree into Texinfo files. List of tuples 240 | # (source start file, target name, title, author, 241 | # dir menu entry, description, category) 242 | texinfo_documents = [ 243 | ('index', 'PayPluge-commercelibrary', u'PayPlug e-commerce library Documentation', 244 | u'PayPlug', 'PayPluge-commercelibrary', 'One line description of project.', 245 | 'Miscellaneous'), 246 | ] 247 | 248 | # Documents to append as an appendix to all manuals. 249 | #texinfo_appendices = [] 250 | 251 | # If false, no module index is generated. 252 | #texinfo_domain_indices = True 253 | 254 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 255 | #texinfo_show_urls = 'footnote' 256 | 257 | # If true, do not generate a @detailmenu in the "Top" node's menu. 258 | #texinfo_no_detailmenu = False 259 | -------------------------------------------------------------------------------- /lib/Payplug/Resource/Payment.php: -------------------------------------------------------------------------------- 1 | initialize($attributes); 21 | return $object; 22 | } 23 | 24 | /** 25 | * Initializes the resource. 26 | * This method must be overridden when the resource has objects as attributes. 27 | * 28 | * @param array $attributes the attributes to initialize. 29 | */ 30 | protected function initialize(array $attributes) 31 | { 32 | parent::initialize($attributes); 33 | 34 | if (isset($attributes['card'])) { 35 | $this->card = PaymentCard::fromAttributes($attributes['card']); 36 | } 37 | 38 | /* 39 | * @deprecated No longer used by API, use billing and shipping instead 40 | */ 41 | if (isset($attributes['customer'])) { 42 | $this->customer = PaymentCustomer::fromAttributes($attributes['customer']); 43 | } 44 | if (isset($attributes['billing'])) { 45 | $this->billing = PaymentBilling::fromAttributes($attributes['billing']); 46 | } 47 | if (isset($attributes['shipping'])) { 48 | $this->shipping = PaymentShipping::fromAttributes($attributes['shipping']); 49 | } 50 | if (isset($attributes['hosted_payment'])) { 51 | $this->hosted_payment = PaymentHostedPayment::fromAttributes($attributes['hosted_payment']); 52 | } 53 | if (isset($attributes['failure'])) { 54 | $this->failure = PaymentPaymentFailure::fromAttributes($attributes['failure']); 55 | } 56 | if (isset($attributes['notification'])) { 57 | $this->notification = PaymentNotification::fromAttributes($attributes['notification']); 58 | } 59 | if (isset($attributes['authorization'])) { 60 | $this->authorization = PaymentAuthorization::fromAttributes($attributes['authorization']); 61 | } 62 | } 63 | 64 | /** 65 | * Open a refund on the payment. 66 | * 67 | * @param array $data the refund data 68 | * @param Payplug\Payplug $payplug the client configuration 69 | * 70 | * @return Refund|null the opened refund instance 71 | * 72 | * @throws Payplug\Exception\InvalidPaymentException when the id of the payment is invalid 73 | */ 74 | public function refund($data = null, $payplug = null) 75 | { 76 | if (!array_key_exists('id', $this->getAttributes())) { 77 | throw new Payplug\Exception\InvalidPaymentException("This payment object has no id. It can't be refunded."); 78 | } 79 | 80 | return Refund::create($this->id, $data, $payplug); 81 | } 82 | 83 | /** 84 | * List the refunds of this payment. 85 | * 86 | * @param Payplug\Payplug $payplug the client configuration 87 | * 88 | * @return null|Refund[] the array of refunds of this payment 89 | * 90 | * @throws Payplug\Exception\InvalidPaymentException 91 | * @throws Payplug\Exception\UnexpectedAPIResponseException 92 | */ 93 | public function listRefunds($payplug = null) 94 | { 95 | if (!array_key_exists('id', $this->getAttributes())) { 96 | throw new Payplug\Exception\InvalidPaymentException("This payment object has no id. You can't list refunds on it."); 97 | } 98 | 99 | return Refund::listRefunds($this->id, $payplug); 100 | } 101 | 102 | /** 103 | * Aborts a Payment. 104 | * 105 | * @param Payplug\Payplug $payplug the client configuration 106 | * 107 | * @return null|Payment the aborted payment or null on error 108 | * 109 | * @throws Payplug\Exception\ConfigurationNotSetException 110 | */ 111 | public function abort($payplug = null) 112 | { 113 | if ($payplug === null) { 114 | $payplug = Payplug\Payplug::getDefaultConfiguration(); 115 | } 116 | 117 | $httpClient = new Payplug\Core\HttpClient($payplug); 118 | $response = $httpClient->patch( 119 | Payplug\Core\APIRoutes::getRoute(Payplug\Core\APIRoutes::PAYMENT_RESOURCE, $this->id), 120 | array('aborted' => true) 121 | ); 122 | 123 | return Payment::fromAttributes($response['httpResponse']); 124 | } 125 | 126 | /** 127 | * Captures a Payment. 128 | * 129 | * @param Payplug\Payplug $payplug the client configuration 130 | * 131 | * @return null|Payment the captured payment or null on error 132 | * 133 | * @throws Payplug\Exception\ConfigurationNotSetException 134 | */ 135 | public function capture($payplug = null) 136 | { 137 | if ($payplug === null) { 138 | $payplug = Payplug\Payplug::getDefaultConfiguration(); 139 | } 140 | 141 | $httpClient = new Payplug\Core\HttpClient($payplug); 142 | $response = $httpClient->patch( 143 | Payplug\Core\APIRoutes::getRoute(Payplug\Core\APIRoutes::PAYMENT_RESOURCE, $this->id), 144 | array('captured' => true) 145 | ); 146 | 147 | return Payment::fromAttributes($response['httpResponse']); 148 | } 149 | 150 | /** 151 | * Retrieves a Payment. 152 | * 153 | * @param string $paymentId the payment ID 154 | * @param Payplug\Payplug $payplug the client configuration 155 | * 156 | * @return Payment the retrieved payment 157 | * 158 | * @throws Payplug\Exception\ConfigurationNotSetException 159 | * @throws Payplug\Exception\UndefinedAttributeException 160 | * @throws Payplug\Exception\NotFoundException 161 | */ 162 | public static function retrieve($paymentId, $payplug = null) 163 | { 164 | if ($payplug === null) { 165 | $payplug = Payplug\Payplug::getDefaultConfiguration(); 166 | } 167 | 168 | if (!$paymentId) { 169 | throw new Payplug\Exception\UndefinedAttributeException('The parameter paymentId is not set.'); 170 | } 171 | 172 | $httpClient = new Payplug\Core\HttpClient($payplug); 173 | $response = $httpClient->get( 174 | Payplug\Core\APIRoutes::getRoute(Payplug\Core\APIRoutes::PAYMENT_RESOURCE, $paymentId) 175 | ); 176 | 177 | return Payment::fromAttributes($response['httpResponse']); 178 | } 179 | 180 | /** 181 | * List payments. 182 | * 183 | * @param Payplug\Payplug $payplug the client configuration 184 | * 185 | * @param int $perPage the number of results per page 186 | * @param int $page the page number 187 | * @return null|Payment[] the array of payments 188 | * 189 | * @throws Payplug\Exception\InvalidPaymentException 190 | * @throws Payplug\Exception\UnexpectedAPIResponseException 191 | */ 192 | public static function listPayments($perPage = null, $page = null, $payplug = null) 193 | { 194 | if ($payplug === null) { 195 | $payplug = Payplug\Payplug::getDefaultConfiguration(); 196 | } 197 | 198 | $httpClient = new Payplug\Core\HttpClient($payplug); 199 | $pagination = array('per_page' => $perPage, 'page' => $page); 200 | $response = $httpClient->get( 201 | Payplug\Core\APIRoutes::getRoute(Payplug\Core\APIRoutes::PAYMENT_RESOURCE, null, array(), $pagination) 202 | ); 203 | 204 | if (!array_key_exists('data', $response['httpResponse']) 205 | || !is_array($response['httpResponse']['data'])) { 206 | throw new Payplug\Exception\UnexpectedAPIResponseException( 207 | "Expected 'data' key in API response.", 208 | $response['httpResponse'] 209 | ); 210 | } 211 | 212 | $payments = array(); 213 | foreach ($response['httpResponse']['data'] as &$payment) { 214 | $payments[] = Payment::fromAttributes($payment); 215 | } 216 | 217 | return $payments; 218 | } 219 | 220 | /** 221 | * Creates a Payment. 222 | * 223 | * @param array $data API data for payment creation 224 | * @param Payplug\Payplug $payplug the client configuration 225 | * 226 | * @return null|Payment the created payment instance 227 | * 228 | * @throws Payplug\Exception\ConfigurationNotSetException 229 | */ 230 | public static function create(array $data, $payplug = null) 231 | { 232 | if ($payplug === null) { 233 | $payplug = Payplug\Payplug::getDefaultConfiguration(); 234 | } 235 | 236 | $httpClient = new Payplug\Core\HttpClient($payplug); 237 | $response = $httpClient->post( 238 | Payplug\Core\APIRoutes::getRoute(Payplug\Core\APIRoutes::PAYMENT_RESOURCE), 239 | $data 240 | ); 241 | 242 | return Payment::fromAttributes($response['httpResponse']); 243 | } 244 | 245 | /** 246 | * Update a Payment. 247 | * 248 | * @param array $data API data for payment creation 249 | * @param Payplug\Payplug $payplug the client configuration 250 | * 251 | * @return null|Payment the updated payment instance 252 | * 253 | * @throws Payplug\Exception\ConfigurationNotSetException 254 | */ 255 | public function update(array $data, $payplug = null) 256 | { 257 | if ($payplug === null) { 258 | $payplug = Payplug\Payplug::getDefaultConfiguration(); 259 | } 260 | 261 | $httpClient = new Payplug\Core\HttpClient($payplug); 262 | $response = $httpClient->patch( 263 | Payplug\Core\APIRoutes::getRoute(Payplug\Core\APIRoutes::PAYMENT_RESOURCE, $this->id), 264 | $data 265 | ); 266 | 267 | return Payment::fromAttributes($response['httpResponse']); 268 | } 269 | 270 | /** 271 | * Returns an API resource that you can trust. 272 | * 273 | * @param Payplug\Payplug $payplug the client configuration. 274 | * 275 | * @return Payplug\Resource\APIResource The consistent API resource. 276 | * 277 | * @throws Payplug\Exception\UndefinedAttributeException when the local resource is invalid. 278 | */ 279 | function getConsistentResource($payplug = null) 280 | { 281 | if (!array_key_exists('id', $this->_attributes)) { 282 | throw new Payplug\Exception\UndefinedAttributeException('The id of the payment is not set.'); 283 | } 284 | 285 | return Payment::retrieve($this->_attributes['id'], $payplug); 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /lib/Payplug/Authentication.php: -------------------------------------------------------------------------------- 1 | post( 29 | Core\APIRoutes::getRoute(Core\APIRoutes::KEY_RESOURCE), 30 | array('email' => $email, 'password' => $password), 31 | false 32 | ); 33 | return $response; 34 | } 35 | 36 | /** 37 | * Retrieve account info. 38 | * 39 | * @param Payplug $payplug the client configuration 40 | * 41 | * @return null|array the account settings 42 | * 43 | * @throws Exception\ConfigurationNotSetException 44 | * @throws ConfigurationException 45 | */ 46 | public static function getAccount($payplug = null) 47 | { 48 | if ($payplug === null) { 49 | $payplug = Payplug::getDefaultConfiguration(); 50 | } 51 | self::validateToken($payplug); 52 | 53 | $httpClient = new Core\HttpClient($payplug); 54 | $response = $httpClient->get(Core\APIRoutes::getRoute(Core\APIRoutes::ACCOUNT_RESOURCE)); 55 | 56 | return $response; 57 | } 58 | 59 | /** 60 | * Retrieve the account permissions 61 | * 62 | * @param Payplug $payplug the client configuration 63 | * 64 | * @return null|array the account permissions 65 | * 66 | * @throws Exception\ConfigurationNotSetException 67 | * @throws ConfigurationException 68 | */ 69 | public static function getPermissions($payplug = null) 70 | { 71 | if ($payplug === null) { 72 | $payplug = Payplug::getDefaultConfiguration(); 73 | } 74 | 75 | self::validateToken($payplug); 76 | 77 | $httpClient = new Core\HttpClient($payplug); 78 | $response = $httpClient->get(Core\APIRoutes::getRoute(Core\APIRoutes::ACCOUNT_RESOURCE)); 79 | 80 | return $response['httpResponse']['permissions']; 81 | } 82 | 83 | /** 84 | * Retrieve the account permissions, using email and password. 85 | * This function is for user-friendly interface purpose only. 86 | * You should probably not use this more than once, login/password MUST NOT be stored and API Keys are enough to interact with API. 87 | * 88 | * @param string $email the user email 89 | * @param string $password the user password 90 | * 91 | * @return null|array the account permissions 92 | * 93 | * @throws Exception\ConfigurationNotSetException 94 | * @throws ConfigurationException 95 | */ 96 | public static function getPermissionsByLogin($email, $password) 97 | { 98 | $keys = self::getKeysByLogin($email, $password); 99 | $payplug = Payplug::init(array( 100 | 'secretKey' => $keys['httpResponse']['secret_keys']['live'], 101 | 'apiVersion' => null, 102 | )); 103 | self::validateToken($payplug); 104 | 105 | $httpClient = new Core\HttpClient($payplug); 106 | $response = $httpClient->get(Core\APIRoutes::getRoute(Core\APIRoutes::ACCOUNT_RESOURCE)); 107 | 108 | return $response['httpResponse']['permissions']; 109 | } 110 | 111 | /** 112 | * Retrieve publisable keys 113 | * 114 | * @param Payplug $payplug the client configuration 115 | * 116 | * @return array|false 117 | *createClientIdAndSecret 118 | * @throws Exception 119 | */ 120 | public static function getPublishableKeys($payplug = null) 121 | { 122 | if ($payplug === null) { 123 | $payplug = Payplug::getDefaultConfiguration(); 124 | } 125 | $httpClient = new Core\HttpClient($payplug); 126 | try { 127 | $response = $httpClient->post(Core\APIRoutes::getRoute(Core\APIRoutes::PUBLISHABLE_KEYS)); 128 | return $response; 129 | } catch (Exception $e) { 130 | return false; 131 | } 132 | } 133 | 134 | /** 135 | * Generate a token JWT from a given client id and secret 136 | * 137 | * @param string $client_id 138 | * @param string $client_secret 139 | * 140 | * @return array 141 | */ 142 | public static function generateJWT($client_id = '', $client_secret = '') 143 | { 144 | if ($client_id == '') { 145 | return array(); 146 | } 147 | if ($client_secret == '') { 148 | return array(); 149 | } 150 | 151 | $httpClient = new Core\HttpClient(null); 152 | try { 153 | $route = Core\APIRoutes::getRoute(Core\APIRoutes::OAUTH2_TOKEN_RESOURCE, null, array(), array(), false); 154 | $response = $httpClient->post( 155 | $route, 156 | array( 157 | 'grant_type' => 'client_credentials', 158 | 'audience' => 'https://www.payplug.com', 159 | ), false, null, array( 160 | 'Content-Type: application/x-www-form-urlencoded', 161 | 'Authorization: Basic ' . base64_encode($client_id . ':' . $client_secret) 162 | ), 163 | 'x-www-form-urlencoded'); 164 | 165 | if (!isset($response['httpResponse']) || empty($response['httpResponse'])) { 166 | return array(); 167 | } 168 | 169 | $response['httpResponse']['expires_date'] = time() + $response['httpResponse']['expires_in']; 170 | 171 | return $response; 172 | } catch (Exception $e) { 173 | return array(); 174 | } 175 | } 176 | 177 | /** 178 | * Generate a token JWT OneShot. 179 | * 180 | * @param string $authorization_code 181 | * @param string $callback_uri 182 | * @param string $client_id 183 | * @param string $code_verifier 184 | * 185 | * @return array the token JWT OneShot 186 | * 187 | * @throws Exception 188 | */ 189 | public static function generateJWTOneShot($authorization_code='', $callback_uri='', $client_id = '', $code_verifier = '') 190 | { 191 | if ($authorization_code == '') { 192 | return array(); 193 | } 194 | 195 | if ($callback_uri == '') { 196 | return array(); 197 | } 198 | 199 | if ($client_id == '') { 200 | return array(); 201 | } 202 | 203 | if ($code_verifier == '') { 204 | return array(); 205 | } 206 | 207 | $httpClient = new Core\HttpClient(null); 208 | try { 209 | $route = Core\APIRoutes::getRoute(Core\APIRoutes::OAUTH2_TOKEN_RESOURCE, null, array(), array(), false); 210 | $response = $httpClient->post( 211 | $route, 212 | array( 213 | 'grant_type' => 'authorization_code', 214 | 'code' => $authorization_code, 215 | 'redirect_uri' => $callback_uri, 216 | 'client_id' => $client_id, 217 | 'code_verifier' => $code_verifier 218 | ), 219 | false, 220 | null, 221 | array( 222 | 'Accept: application/json', 223 | 'Content-Type: application/x-www-form-urlencoded' 224 | ), 225 | 'application/x-www-form-urlencoded' 226 | ); 227 | } catch (Exception $e) { 228 | $response = array(); 229 | } 230 | 231 | return $response; 232 | } 233 | 234 | /** 235 | * Validates the Payplug token 236 | * 237 | * @param Payplug $payplug 238 | * @return void 239 | * @throws ConfigurationException 240 | */ 241 | private static function validateToken($payplug) 242 | { 243 | $token = $payplug->getToken(); 244 | if (empty($token)) { 245 | throw new ConfigurationException('The Payplug configuration requires a valid token.'); 246 | } 247 | } 248 | 249 | /** 250 | * Create a client ID and secret for a given mode 251 | * 252 | * @param $company_id 253 | * @param $client_name 254 | * @param $mode 255 | * @param $session 256 | * @param Payplug|null $payplug 257 | * 258 | * @return array 259 | * @throws ConfigurationException 260 | * @throws Exception\ConfigurationNotSetException 261 | * @throws Exception\ConnectionException 262 | * @throws Exception\HttpException 263 | * @throws Exception\UnexpectedAPIResponseException 264 | */ 265 | public static function createClientIdAndSecret($company_id = '', $client_name = '', $mode = '', $session = null, $payplug = null) 266 | { 267 | if ($payplug === null) { 268 | $payplug = Payplug::getDefaultConfiguration(); 269 | } 270 | 271 | $httpClient = new Core\HttpClient($payplug); 272 | $response = array(); 273 | $route = Core\APIRoutes::getServiceRoute(Core\APIRoutes::CLIENT_RESOURCE); 274 | try { 275 | $response = $httpClient->post( 276 | $route, 277 | array( 278 | 'company_id' => $company_id, 279 | 'client_name' => $client_name, 280 | 'client_type' => 'client_credentials_flow', 281 | 'mode' => $mode, 282 | )); 283 | } catch (Exception $e) { 284 | return $response; 285 | } 286 | 287 | return $response; 288 | } 289 | 290 | /** 291 | * Get the return url to register user through the portal 292 | * 293 | * @param string $setup_redirection_uri 294 | * @param string $oauth_callback_uri 295 | * 296 | * @return array 297 | * @throws Exception\ConnectionException 298 | * @throws Exception\HttpException 299 | * @throws Exception\UnexpectedAPIResponseException 300 | */ 301 | public static function getRegisterUrl($setup_redirection_uri = '', $oauth_callback_uri = '') 302 | { 303 | if (empty($setup_redirection_uri)) { 304 | throw new Exception\ConfigurationException('Expected string values for setup redirection uri.'); 305 | } 306 | if (empty($oauth_callback_uri)) { 307 | throw new Exception\ConfigurationException('Expected string values for oauth callback uri.'); 308 | } 309 | 310 | $url_datas = array( 311 | 'setup_redirection_uri' => $setup_redirection_uri, 312 | 'oauth_callback_uri' => $oauth_callback_uri, 313 | ); 314 | 315 | $route = Core\APIRoutes::getServiceRoute(Core\APIRoutes::PLUGIN_SETUP_SERVICE, $url_datas); 316 | 317 | return $route; 318 | } 319 | 320 | /** 321 | * Redirect to callback page and provide an authorization_code 322 | * 323 | * @param $client_id 324 | * @param $redirect_uri 325 | * @param $code_verifier 326 | * @param Payplug|null $payplug 327 | * @throws ConfigurationException 328 | * @throws Exception\ConfigurationNotSetException 329 | * @throws Exception\ConnectionException 330 | * @throws Exception\HttpException 331 | * @throws Exception\UnexpectedAPIResponseException 332 | */ 333 | public static function initiateOAuth($client_id='', $redirect_uri='', $code_verifier='') 334 | { 335 | $hash = hash("sha256", $code_verifier); 336 | $code_challenge = base64_encode(pack("H*", $hash)); 337 | $code_challenge = strtr($code_challenge, "+/", "-_"); 338 | $code_challenge = rtrim($code_challenge, "="); 339 | 340 | $portal_url_datas = array( 341 | 'client_id' => $client_id, 342 | 'redirect_uri' => $redirect_uri, 343 | 'response_type' => 'code', 344 | 'state' => bin2hex(openssl_random_pseudo_bytes(10)), 345 | 'scope' => 'openid offline profile email', 346 | 'audience' => 'https://www.payplug.com', 347 | 'code_challenge' => $code_challenge, 348 | 'code_challenge_method' => 'S256', 349 | ); 350 | 351 | $portal_url = Core\APIRoutes::getRoute(Core\APIRoutes::OAUTH2_AUTH_RESOURCE, null, array(), $portal_url_datas, false); 352 | 353 | header("Location: $portal_url"); 354 | } 355 | 356 | /** 357 | * Check if given token is expired and if so, regenerate a new one 358 | * 359 | * @param array $client_data 360 | * @param array $token 361 | * 362 | * @return array 363 | */ 364 | public static function validateJWT($client_data = array(), $token = array()) 365 | { 366 | if (!is_array($client_data) || empty($client_data)) { 367 | return array( 368 | 'result' => false, 369 | 'token' => null, 370 | 'need_update' => false, 371 | ); 372 | } 373 | if (!is_array($token) || empty($token)) { 374 | return array( 375 | 'result' => false, 376 | 'token' => null, 377 | 'need_update' => false, 378 | ); 379 | } 380 | 381 | $current_date = time(); 382 | if ($token['expires_date'] > $current_date) { 383 | return array( 384 | 'result' => true, 385 | 'token' => $token, 386 | 'need_update' => false, 387 | ); 388 | } 389 | 390 | $token = self::generateJWT($client_data['client_id'], $client_data['client_secret']); 391 | if (empty($token) || !isset($token['httpResponse'])) { 392 | return array( 393 | 'result' => false, 394 | 'token' => null, 395 | 'need_update' => false, 396 | ); 397 | } 398 | 399 | return array( 400 | 'result' => true, 401 | 'token' => $token['httpResponse'], 402 | 'need_update' => true, 403 | ); 404 | } 405 | } 406 | -------------------------------------------------------------------------------- /tests/unit_tests/Resource/RefundTest.php: -------------------------------------------------------------------------------- 1 | _configuration = new Payplug\Payplug('abc'); 22 | Payplug\Payplug::setDefaultConfiguration($this->_configuration); 23 | 24 | $this->_requestMock = $this->createMock('\Payplug\Core\IHttpRequest'); 25 | Payplug\Core\HttpClient::$REQUEST_HANDLER = $this->_requestMock; 26 | } 27 | 28 | protected function setUpTwice() 29 | { 30 | $this->_configuration = new Payplug\Payplug('abc','1970-01-01'); 31 | Payplug\Payplug::setDefaultConfiguration($this->_configuration); 32 | 33 | $this->_requestMock = $this->createMock('\Payplug\Core\IHttpRequest'); 34 | Payplug\Core\HttpClient::$REQUEST_HANDLER = $this->_requestMock; 35 | } 36 | 37 | public function testCreateRefundFromAttributes() 38 | { 39 | $refund = Refund::fromAttributes(array( 40 | 'id' => 're_390312', 41 | 'payment_id' => 'pay_490329', 42 | 'object' => 'refund', 43 | 'amount' => 3300, 44 | 'currency' => 'EUR', 45 | 'created_at' => 1410437760 46 | )); 47 | 48 | $this->assertEquals('re_390312', $refund->id); 49 | $this->assertEquals('pay_490329', $refund->payment_id); 50 | $this->assertEquals('refund', $refund->object); 51 | $this->assertEquals(3300, $refund->amount); 52 | $this->assertEquals('EUR', $refund->currency); 53 | $this->assertEquals(1410437760, $refund->created_at); 54 | } 55 | 56 | public function testRefundCreateFromPaymentId() 57 | { 58 | $GLOBALS['CURLOPT_URL_DATA'] = null; 59 | 60 | $this->_requestMock 61 | ->expects($this->once()) 62 | ->method('exec') 63 | ->will($this->returnValue('{"status":"ok"}')); 64 | 65 | $this->_requestMock 66 | ->expects($this->any()) 67 | ->method('getinfo') 68 | ->will($this->returnCallback(function($option) { 69 | switch($option) { 70 | case CURLINFO_HTTP_CODE: 71 | return 200; 72 | } 73 | return null; 74 | })); 75 | $this->_requestMock 76 | ->expects($this->any()) 77 | ->method('setopt') 78 | ->will($this->returnCallback(function($option, $value = null) { 79 | switch($option) { 80 | case CURLOPT_URL: 81 | $GLOBALS['CURLOPT_URL_DATA'] = $value; 82 | return true; 83 | } 84 | return true; 85 | })); 86 | 87 | $refund = Refund::create('a_payment_id', array('amount' => 3300)); 88 | 89 | $this->assertEquals('ok', $refund->status); 90 | $this->assertContains('a_payment_id', $GLOBALS['CURLOPT_URL_DATA']); 91 | 92 | unset($GLOBALS['CURLOPT_URL_DATA']); 93 | } 94 | 95 | public function testRefundCreateFromPaymentObject() 96 | { 97 | $GLOBALS['CURLOPT_URL_DATA'] = null; 98 | 99 | $this->_requestMock 100 | ->expects($this->once()) 101 | ->method('exec') 102 | ->will($this->returnValue('{"status":"ok"}')); 103 | 104 | $this->_requestMock 105 | ->expects($this->any()) 106 | ->method('getinfo') 107 | ->will($this->returnCallback(function($option) { 108 | switch($option) { 109 | case CURLINFO_HTTP_CODE: 110 | return 200; 111 | } 112 | return null; 113 | })); 114 | $this->_requestMock 115 | ->expects($this->any()) 116 | ->method('setopt') 117 | ->will($this->returnCallback(function($option, $value = null) { 118 | switch($option) { 119 | case CURLOPT_URL: 120 | $GLOBALS['CURLOPT_URL_DATA'] = $value; 121 | return true; 122 | } 123 | return true; 124 | })); 125 | 126 | $refund = Refund::create( 127 | Payment::fromAttributes(array('id' => 'a_payment_id')), 128 | array('amount' => 3300) 129 | ); 130 | 131 | $this->assertEquals('ok', $refund->status); 132 | $this->assertContains('a_payment_id', $GLOBALS['CURLOPT_URL_DATA']); 133 | 134 | unset($GLOBALS['CURLOPT_URL_DATA']); 135 | } 136 | 137 | public function testRefundRetrieveFromPaymentId() 138 | { 139 | function testRefundRetrieveFromPaymentId_getinfo($option) { 140 | switch($option) { 141 | case CURLINFO_HTTP_CODE: 142 | return 200; 143 | } 144 | return null; 145 | } 146 | $GLOBALS['CURLOPT_URL_DATA'] = null; 147 | function testRefundRetrieveFromPaymentId_setopt($option, $value = null) { 148 | switch($option) { 149 | case CURLOPT_URL: 150 | $GLOBALS['CURLOPT_URL_DATA'] = $value; 151 | return true; 152 | } 153 | return true; 154 | } 155 | 156 | $this->_requestMock 157 | ->expects($this->once()) 158 | ->method('exec') 159 | ->will($this->returnValue('{"status":"ok"}')); 160 | 161 | $this->_requestMock 162 | ->expects($this->any()) 163 | ->method('getinfo') 164 | ->will($this->returnCallback(function($option) { 165 | switch($option) { 166 | case CURLINFO_HTTP_CODE: 167 | return 200; 168 | } 169 | return null; 170 | })); 171 | $this->_requestMock 172 | ->expects($this->any()) 173 | ->method('setopt') 174 | ->will($this->returnCallback(function($option, $value = null) { 175 | switch($option) { 176 | case CURLOPT_URL: 177 | $GLOBALS['CURLOPT_URL_DATA'] = $value; 178 | return true; 179 | } 180 | return true; 181 | })); 182 | 183 | $refund = Refund::retrieve('a_payment_id', 'a_refund_id'); 184 | 185 | $this->assertEquals('ok', $refund->status); 186 | $this->assertContains('a_payment_id', $GLOBALS['CURLOPT_URL_DATA']); 187 | $this->assertStringEndsWith('a_refund_id', $GLOBALS['CURLOPT_URL_DATA']); 188 | 189 | unset($GLOBALS['CURLOPT_URL_DATA']); 190 | } 191 | 192 | public function testRefundRetrieveFromPaymentObject() 193 | { 194 | $GLOBALS['CURLOPT_URL_DATA'] = null; 195 | 196 | 197 | $this->_requestMock 198 | ->expects($this->once()) 199 | ->method('exec') 200 | ->will($this->returnValue('{"status":"ok"}')); 201 | 202 | $this->_requestMock 203 | ->expects($this->any()) 204 | ->method('getinfo') 205 | ->will($this->returnCallback(function($option) { 206 | switch($option) { 207 | case CURLINFO_HTTP_CODE: 208 | return 200; 209 | } 210 | return null; 211 | })); 212 | $this->_requestMock 213 | ->expects($this->any()) 214 | ->method('setopt') 215 | ->will($this->returnCallback(function($option, $value = null) { 216 | switch($option) { 217 | case CURLOPT_URL: 218 | $GLOBALS['CURLOPT_URL_DATA'] = $value; 219 | return true; 220 | } 221 | return true; 222 | })); 223 | 224 | $refund = Refund::retrieve( 225 | Payment::fromAttributes(array('id' => 'a_payment_id')), 226 | 'a_refund_id' 227 | ); 228 | 229 | $this->assertEquals('ok', $refund->status); 230 | $this->assertContains('a_payment_id', $GLOBALS['CURLOPT_URL_DATA']); 231 | $this->assertStringEndsWith('a_refund_id', $GLOBALS['CURLOPT_URL_DATA']); 232 | 233 | unset($GLOBALS['CURLOPT_URL_DATA']); 234 | } 235 | 236 | public function testRefundsListThrowsExceptionOnWongAPIResponse() 237 | { 238 | $this->expectException('\PayPlug\Exception\UnexpectedAPIResponseException'); 239 | 240 | $this->_requestMock 241 | ->expects($this->once()) 242 | ->method('exec') 243 | ->will($this->returnValue('{"status":"this_is_an_invalid_response"}')); 244 | 245 | $this->_requestMock 246 | ->expects($this->any()) 247 | ->method('getinfo') 248 | ->will($this->returnCallback(function($option) { 249 | switch($option) { 250 | case CURLINFO_HTTP_CODE: 251 | return 200; 252 | } 253 | return null; 254 | })); 255 | 256 | Refund::listRefunds('a_payment_id'); 257 | } 258 | 259 | public function testRefundsListFromPaymentId() 260 | { 261 | $GLOBALS['CURLOPT_URL_DATA'] = null; 262 | 263 | $this->_requestMock 264 | ->expects($this->once()) 265 | ->method('exec') 266 | ->will($this->returnValue('{"data":[{"id": "refund1"}, {"id": "refund2"}]}')); 267 | 268 | $this->_requestMock 269 | ->expects($this->any()) 270 | ->method('getinfo') 271 | ->will($this->returnCallback(function($option) { 272 | switch($option) { 273 | case CURLINFO_HTTP_CODE: 274 | return 200; 275 | } 276 | return null; 277 | })); 278 | $this->_requestMock 279 | ->expects($this->any()) 280 | ->method('setopt') 281 | ->will($this->returnCallback(function($option, $value = null) { 282 | switch($option) { 283 | case CURLOPT_URL: 284 | $GLOBALS['CURLOPT_URL_DATA'] = $value; 285 | return true; 286 | } 287 | return true; 288 | })); 289 | 290 | $refunds = Refund::listRefunds('a_payment_id'); 291 | 292 | $this->assertContains('a_payment_id', $GLOBALS['CURLOPT_URL_DATA']); 293 | $this->assertEquals(2, count($refunds)); 294 | $this->assertTrue('refund1' === $refunds[0]->id || 'refund2' === $refunds[1]->id); 295 | $this->assertTrue( 296 | (('refund1' === $refunds[1]->id) || ('refund2' === $refunds[1]->id)) 297 | && ($refunds[0]->id !== $refunds[1]->id) 298 | ); 299 | 300 | unset($GLOBALS['CURLOPT_URL_DATA']); 301 | } 302 | 303 | public function testRefundsListFromPaymentObject() 304 | { 305 | $GLOBALS['CURLOPT_URL_DATA'] = null; 306 | 307 | $this->_requestMock 308 | ->expects($this->once()) 309 | ->method('exec') 310 | ->will($this->returnValue('{"data":[{"id": "refund1"}, {"id": "refund2"}]}')); 311 | 312 | $this->_requestMock 313 | ->expects($this->any()) 314 | ->method('getinfo') 315 | ->will($this->returnCallback(function($option) { 316 | switch($option) { 317 | case CURLINFO_HTTP_CODE: 318 | return 200; 319 | } 320 | return null; 321 | })); 322 | $this->_requestMock 323 | ->expects($this->any()) 324 | ->method('setopt') 325 | ->will($this->returnCallback(function($option, $value = null) { 326 | switch($option) { 327 | case CURLOPT_URL: 328 | $GLOBALS['CURLOPT_URL_DATA'] = $value; 329 | return true; 330 | } 331 | return true; 332 | })); 333 | 334 | $refunds = Refund::listRefunds( 335 | Payment::fromAttributes(array('id' => 'a_payment_id')) 336 | ); 337 | 338 | $this->assertContains('a_payment_id', $GLOBALS['CURLOPT_URL_DATA']); 339 | $this->assertEquals(2, count($refunds)); 340 | $this->assertTrue('refund1' === $refunds[0]->id || 'refund2' === $refunds[1]->id); 341 | $this->assertTrue( 342 | (('refund1' === $refunds[1]->id) || ('refund2' === $refunds[1]->id)) 343 | && ($refunds[0]->id !== $refunds[1]->id) 344 | ); 345 | 346 | unset($GLOBALS['CURLOPT_URL_DATA']); 347 | } 348 | 349 | public function testRetrieveConsistentRefundWhenIdIsUndefined() 350 | { 351 | $this->expectException('\PayPlug\Exception\UndefinedAttributeException'); 352 | 353 | $payment = Refund::fromAttributes(array('this_refund' => 'has_no_id', 'payment_id' => 'pay_id')); 354 | $payment->getConsistentResource(); 355 | } 356 | 357 | public function testRetrieveConsistentRefundWhenPaymentIdIsUndefined() 358 | { 359 | $this->expectException('\PayPlug\Exception\UndefinedAttributeException'); 360 | 361 | $payment = Refund::fromAttributes(array('id' => 'an_id', 'no_payment_id' => '')); 362 | $payment->getConsistentResource(); 363 | } 364 | 365 | public function testRetrieveConsistentRefund() 366 | { 367 | function testRetrieveConsistentRefund_getinfo($option) { 368 | switch($option) { 369 | case CURLINFO_HTTP_CODE: 370 | return 200; 371 | } 372 | return null; 373 | } 374 | 375 | $this->_requestMock 376 | ->expects($this->once()) 377 | ->method('exec') 378 | ->will($this->returnValue('{"id": "re_345", "payment_id": "pay_789"}')); 379 | 380 | $this->_requestMock 381 | ->expects($this->any()) 382 | ->method('setopt') 383 | ->will($this->returnValue(true)); 384 | $this->_requestMock 385 | ->expects($this->any()) 386 | ->method('getinfo') 387 | ->will($this->returnCallback(function($option) { 388 | switch($option) { 389 | case CURLINFO_HTTP_CODE: 390 | return 200; 391 | } 392 | return null; 393 | })); 394 | 395 | $refund1 = Refund::fromAttributes(array('id' => 're_123', 'payment_id' => 'pay_321')); 396 | $refund2 = $refund1->getConsistentResource($this->_configuration); 397 | 398 | $this->assertEquals('re_123', $refund1->id); 399 | $this->assertEquals('pay_321', $refund1->payment_id); 400 | $this->assertEquals('re_345', $refund2->id); 401 | $this->assertEquals('pay_789', $refund2->payment_id); 402 | } 403 | } 404 | -------------------------------------------------------------------------------- /tests/unit_tests/Core/HttpClientTest.php: -------------------------------------------------------------------------------- 1 | _httpClient = new HttpClient(new Payplug('abc')); 21 | 22 | $this->_requestMock = $this->createMock('\Payplug\Core\IHttpRequest'); 23 | HttpClient::$REQUEST_HANDLER = $this->_requestMock; 24 | } 25 | 26 | protected function setUpTwice() 27 | { 28 | $this->_httpClient = new HttpClient(new Payplug('abc','1970-01-01')); 29 | 30 | $this->_requestMock = $this->createMock('\Payplug\Core\IHttpRequest'); 31 | HttpClient::$REQUEST_HANDLER = $this->_requestMock; 32 | } 33 | 34 | public function testPost() 35 | { 36 | 37 | $this->_requestMock 38 | ->expects($this->once()) 39 | ->method('exec') 40 | ->will($this->returnValue('{"status":"ok"}')); 41 | $this->_requestMock 42 | ->expects($this->any()) 43 | ->method('getinfo') 44 | ->will($this->returnCallback(function($option) { 45 | switch($option) { 46 | case CURLINFO_HTTP_CODE: 47 | return 200; 48 | } 49 | return null; 50 | })); 51 | 52 | $result = $this->_httpClient->post('somewhere'); 53 | 54 | $this->assertEquals(array('status' => 'ok'), $result['httpResponse']); 55 | $this->assertEquals(200, $result['httpStatus']); 56 | } 57 | 58 | public function testPatch() 59 | { 60 | $this->_requestMock 61 | ->expects($this->once()) 62 | ->method('exec') 63 | ->will($this->returnValue('{"status":"ok"}')); 64 | $this->_requestMock 65 | ->expects($this->any()) 66 | ->method('getinfo') 67 | ->will($this->returnCallback(function($option) { 68 | switch($option) { 69 | case CURLINFO_HTTP_CODE: 70 | return 200; 71 | } 72 | return null; 73 | })); 74 | 75 | $result = $this->_httpClient->patch('somewhere'); 76 | 77 | $this->assertEquals(array('status' => 'ok'), $result['httpResponse']); 78 | $this->assertEquals(200, $result['httpStatus']); 79 | } 80 | 81 | public function testDelete() 82 | { 83 | $this->_requestMock 84 | ->expects($this->once()) 85 | ->method('exec') 86 | ->will($this->returnValue('{"status":"ok"}')); 87 | $this->_requestMock 88 | ->expects($this->any()) 89 | ->method('getinfo') 90 | ->will($this->returnCallback(function($option) { 91 | switch($option) { 92 | case CURLINFO_HTTP_CODE: 93 | return 204; 94 | } 95 | return null; 96 | })); 97 | 98 | $result = $this->_httpClient->delete('somewhere'); 99 | 100 | $this->assertEquals(array('status' => 'ok'), $result['httpResponse']); 101 | $this->assertEquals(204, $result['httpStatus']); 102 | } 103 | 104 | public function testGet() 105 | { 106 | function testGet_getinfo($option) { 107 | switch($option) { 108 | case CURLINFO_HTTP_CODE: 109 | return 200; 110 | } 111 | return null; 112 | } 113 | 114 | $this->_requestMock 115 | ->expects($this->once()) 116 | ->method('exec') 117 | ->will($this->returnValue('{"status":"ok"}')); 118 | $this->_requestMock 119 | ->expects($this->any()) 120 | ->method('getinfo') 121 | ->will($this->returnCallback(function($option) { 122 | switch($option) { 123 | case CURLINFO_HTTP_CODE: 124 | return 200; 125 | } 126 | return null; 127 | })); 128 | 129 | $result = $this->_httpClient->get('somewhere_else'); 130 | 131 | $this->assertEquals(array('status' => 'ok'), $result['httpResponse']); 132 | $this->assertEquals(200, $result['httpStatus']); 133 | } 134 | 135 | public function testError500() 136 | { 137 | 138 | $this->expectException('\PayPlug\Exception\PayPlugServerException'); 139 | 140 | $this->_requestMock 141 | ->expects($this->once()) 142 | ->method('exec') 143 | ->will($this->returnValue('{"status":"not ok"}')); 144 | $this->_requestMock 145 | ->expects($this->any()) 146 | ->method('getinfo') 147 | ->will($this->returnCallback(function($option) { 148 | switch($option) { 149 | case CURLINFO_HTTP_CODE: 150 | return 500; 151 | } 152 | return null; 153 | })); 154 | 155 | $this->_httpClient->get('somewhere'); 156 | } 157 | 158 | public function testError400() 159 | { 160 | 161 | $this->expectException('\PayPlug\Exception\BadRequestException'); 162 | 163 | $this->_requestMock 164 | ->expects($this->once()) 165 | ->method('exec') 166 | ->will($this->returnValue('{"status":"not ok"}')); 167 | $this->_requestMock 168 | ->expects($this->any()) 169 | ->method('getinfo') 170 | ->will($this->returnCallback(function($option) { 171 | switch($option) { 172 | case CURLINFO_HTTP_CODE: 173 | return 400; 174 | } 175 | return null; 176 | })); 177 | 178 | $this->_httpClient->get('somewhere'); 179 | } 180 | 181 | public function testError401() 182 | { 183 | 184 | $this->expectException('\PayPlug\Exception\UnauthorizedException'); 185 | 186 | $this->_requestMock 187 | ->expects($this->once()) 188 | ->method('exec') 189 | ->will($this->returnValue('{"status":"not ok"}')); 190 | $this->_requestMock 191 | ->expects($this->any()) 192 | ->method('getinfo') 193 | ->will($this->returnCallback(function($option) { 194 | switch($option) { 195 | case CURLINFO_HTTP_CODE: 196 | return 401; 197 | } 198 | return null; 199 | })); 200 | 201 | $this->_httpClient->get('somewhere'); 202 | } 203 | 204 | public function testError403() 205 | { 206 | 207 | $this->expectException('\PayPlug\Exception\ForbiddenException'); 208 | 209 | $this->_requestMock 210 | ->expects($this->once()) 211 | ->method('exec') 212 | ->will($this->returnValue('{"status":"not ok"}')); 213 | $this->_requestMock 214 | ->expects($this->any()) 215 | ->method('getinfo') 216 | ->will($this->returnCallback(function($option) { 217 | switch($option) { 218 | case CURLINFO_HTTP_CODE: 219 | return 403; 220 | } 221 | return null; 222 | })); 223 | 224 | $this->_httpClient->get('somewhere'); 225 | } 226 | 227 | public function testError404() 228 | { 229 | 230 | $this->expectException('\PayPlug\Exception\NotFoundException'); 231 | 232 | $this->_requestMock 233 | ->expects($this->once()) 234 | ->method('exec') 235 | ->will($this->returnValue('{"status":"not ok"}')); 236 | $this->_requestMock 237 | ->expects($this->any()) 238 | ->method('getinfo') 239 | ->will($this->returnCallback(function($option) { 240 | switch($option) { 241 | case CURLINFO_HTTP_CODE: 242 | return 404; 243 | } 244 | return null; 245 | })); 246 | 247 | $this->_httpClient->get('somewhere'); 248 | } 249 | 250 | public function testError405() 251 | { 252 | 253 | $this->expectException('\PayPlug\Exception\NotAllowedException'); 254 | 255 | $this->_requestMock 256 | ->expects($this->once()) 257 | ->method('exec') 258 | ->will($this->returnValue('{"status":"not ok"}')); 259 | $this->_requestMock 260 | ->expects($this->any()) 261 | ->method('getinfo') 262 | ->will($this->returnCallback(function($option) { 263 | switch($option) { 264 | case CURLINFO_HTTP_CODE: 265 | return 405; 266 | } 267 | return null; 268 | })); 269 | 270 | $this->_httpClient->get('somewhere'); 271 | } 272 | 273 | public function testErrorUnknown() 274 | { 275 | 276 | $this->expectException('\PayPlug\Exception\HttpException'); 277 | 278 | $this->_requestMock 279 | ->expects($this->once()) 280 | ->method('exec') 281 | ->will($this->returnValue('{"status":"not ok"}')); 282 | $this->_requestMock 283 | ->expects($this->any()) 284 | ->method('getinfo') 285 | ->will($this->returnCallback(function($option) { 286 | switch($option) { 287 | case CURLINFO_HTTP_CODE: 288 | return 418; 289 | } 290 | return null; 291 | })); 292 | 293 | $this->_httpClient->get('somewhere'); 294 | } 295 | 296 | public function testNotEmptyData() 297 | { 298 | $GLOBALS['CURLOPT_POSTFIELDS_DATA'] = null; 299 | 300 | $this->_requestMock 301 | ->expects($this->once()) 302 | ->method('exec') 303 | ->will($this->returnValue('{"status":"ok"}')); 304 | $this->_requestMock 305 | ->expects($this->any()) 306 | ->method('getinfo') 307 | ->will($this->returnCallback(function($option) { 308 | switch($option) { 309 | case CURLINFO_HTTP_CODE: 310 | return 200; 311 | } 312 | return null; 313 | })); 314 | $this->_requestMock 315 | ->expects($this->any()) 316 | ->method('setopt') 317 | ->will($this->returnCallback(function($option, $value = null) { 318 | switch($option) { 319 | case CURLOPT_POSTFIELDS: 320 | $GLOBALS['CURLOPT_POSTFIELDS_DATA'] = json_decode($value, true); 321 | return true; 322 | } 323 | return true; 324 | })); 325 | 326 | $result = $this->_httpClient->get('somewhere_else', array('foo' => 'bar')); 327 | 328 | $this->assertEquals(array('foo' => 'bar'), $GLOBALS['CURLOPT_POSTFIELDS_DATA']); 329 | $this->assertEquals(array('status' => 'ok'), $result['httpResponse']); 330 | $this->assertEquals(200, $result['httpStatus']); 331 | 332 | unset($GLOBALS['CURLOPT_POSTFIELDS_DATA']); 333 | } 334 | 335 | function testInvalidAPIResponse() 336 | { 337 | $this->expectException('\PayPlug\Exception\UnexpectedAPIResponseException'); 338 | 339 | 340 | 341 | $this->_requestMock 342 | ->expects($this->once()) 343 | ->method('exec') 344 | ->will($this->returnValue('This is not JSON')); 345 | $this->_requestMock 346 | ->expects($this->any()) 347 | ->method('getinfo') 348 | ->will($this->returnCallback(function($option) { 349 | switch($option) { 350 | case CURLINFO_HTTP_CODE: 351 | return 200; 352 | } 353 | return null; 354 | })); 355 | 356 | $this->_httpClient->get('somewhere_else'); 357 | } 358 | 359 | function testConnectionError() 360 | { 361 | $this->expectException('\PayPlug\Exception\ConnectionException'); 362 | 363 | 364 | 365 | function testConnectionError_errno($option) { 366 | switch($option) { 367 | case CURLINFO_HTTP_CODE: 368 | return 0; 369 | } 370 | return null; 371 | } 372 | 373 | $this->_requestMock 374 | ->expects($this->once()) 375 | ->method('exec') 376 | ->will($this->returnValue(false)); 377 | $this->_requestMock 378 | ->expects($this->any()) 379 | ->method('errno') 380 | ->will($this->returnValue(7)); // CURLE_COULDNT_CONNECT 381 | $this->_requestMock 382 | ->expects($this->any()) 383 | ->method('error') 384 | ->will($this->returnValue('Failed to connect() to host or proxy.')); 385 | $this->_requestMock 386 | ->expects($this->any()) 387 | ->method('getinfo') 388 | ->will($this->returnCallback(function($option) { 389 | switch($option) { 390 | case CURLINFO_HTTP_CODE: 391 | return 0; 392 | } 393 | return null; 394 | })); 395 | 396 | $this->_httpClient->get('somewhere'); 397 | } 398 | 399 | function testFormatUserAgentProduct() 400 | { 401 | $result = \Payplug\Test\TestUtils::invokePrivateMethod( 402 | $this->_httpClient, 'formatUserAgentProduct', 403 | array('PayPlug-PHP', '2.2.1' , 'PHP/5.5.34; curl/7.43.0') 404 | ); 405 | 406 | $this->assertEquals($result, 'PayPlug-PHP/2.2.1 (PHP/5.5.34; curl/7.43.0)'); 407 | } 408 | 409 | function testGetUserAgent() 410 | { 411 | $userAgent = $this->_httpClient->getUserAgent(); 412 | // Expected result is something like 'PayPlug-PHP/1.0.0 (PHP/5.5.35; curl/7.44.0)' 413 | $this->assertRegExp( 414 | '/^PayPlug-PHP\/(\d+\.?){1,3} \(PHP\/(\d+\.?){1,3}(\w|\+|\~|\.|\-)*; curl\/(\d+\.?){1,3}\)$/', 415 | $userAgent 416 | ); 417 | } 418 | 419 | function testGetUserAgentWithAdditionalProduct() 420 | { 421 | \Payplug\Core\HttpClient::addDefaultUserAgentProduct('PayPlug-Test', '1.0.0', 'Comment/1.6.3'); 422 | \Payplug\Core\HttpClient::addDefaultUserAgentProduct('Another-Test', '5.8.13'); 423 | 424 | $userAgent = $this->_httpClient->getUserAgent(); 425 | 426 | $this->assertNotContains('PayPlug-Test', $userAgent); 427 | $this->assertStringEndsWith('Another-Test/5.8.13', $userAgent); 428 | } 429 | 430 | function testGetDefaultUserAgentWithAdditionalProduct() 431 | { 432 | \Payplug\Core\HttpClient::setDefaultUserAgentProduct('PayPlug-Test', '1.0.0', 'Comment/1.6.3'); 433 | \Payplug\Core\HttpClient::setDefaultUserAgentProduct('Another-Test', '5.8.13'); 434 | 435 | $userAgent = $this->_httpClient->getUserAgent(); 436 | 437 | $this->assertNotContains('PayPlug-Test', $userAgent); 438 | $this->assertStringEndsWith('Another-Test/5.8.13', $userAgent); 439 | } 440 | } 441 | --------------------------------------------------------------------------------