From 819fa78396a1bbd85dab34a1dc924bbf122c7473 Mon Sep 17 00:00:00 2001 From: Hendrik Obermayer Date: Tue, 9 May 2017 10:56:15 +0200 Subject: [PATCH] Added payouts and pdf download functionality, Updated docs and tests --- composer.json | 2 +- readme.md | 55 +++++++++++++++++++-- src/Client.php | 76 +++++++++++++---------------- src/Middleware.php | 12 +++++ src/Request/RetrievePdfRequest.php | 25 ++++++++++ tests/ClientTest.php | 30 +++++++----- tests/MiddlewareTest.php | 6 +++ tests/Request/CreateRequestTest.php | 18 +++++++ 8 files changed, 166 insertions(+), 58 deletions(-) create mode 100644 src/Request/RetrievePdfRequest.php diff --git a/composer.json b/composer.json index b4b84cc..fba9538 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,7 @@ "description": "Barzahlen PHP Library", "license": "MIT", "type": "library", - "keywords": ["Barzahlen", "Cash Payment Solutions"], + "keywords": ["payment", "Barzahlen", "Cash Payment Solutions"], "homepage": "https://www.barzahlen.de", "support": { "email": "support@barzahlen.de", diff --git a/readme.md b/readme.md index 28652ca..137a1dd 100644 --- a/readme.md +++ b/readme.md @@ -1,4 +1,4 @@ -# Barzahlen Payment Module PHP SDK (v2.0.3) +# Barzahlen Payment Module PHP SDK (v2.1.0) [![Build Status](https://travis-ci.org/Barzahlen/Barzahlen-PHP.svg?branch=master)](https://travis-ci.org/Barzahlen/Barzahlen-PHP) [![Total Downloads](https://poser.pugx.org/barzahlen/barzahlen-php/downloads)](https://packagist.org/packages/barzahlen/barzahlen-php) @@ -23,8 +23,8 @@ composer require barzahlen/barzahlen-php The client will connect your application to the Barzahlen API v2. Initiate it with the division ID and the payment key. Set the third, optional parameter to true if you want to send your requests to the sandbox for development purpose. Optional: Set a custom user agent. ```php -use \Barzahlen\Client; -use \Barzahlen\Exception\ApiException; +use Barzahlen\Client; +use Barzahlen\Exception\ApiException; $client = new Client('12345', 'f2a173a210c7c8e7e439da7dc2b8330b6c06fc04', true); $client->setUserAgent('Awesome Project v1.0.1'); @@ -48,7 +48,10 @@ There are five different requests which the client can handle for you. The requi ### CreateRequest To request a new payment or refund slip simply initiate a new CreateRequest and add the parameters. Here are three examples for a minimal payment request using setters, an array and plain json. +#### Payment Slips ```php +use Barzahlen\Request\CreateRequest; + $request = new CreateRequest(); $request->setSlipType('payment'); $request->setCustomerKey('LDFKHSLFDHFL'); @@ -56,6 +59,8 @@ $request->setTransaction('14.95', 'EUR'); ``` ```php +use Barzahlen\Request\CreateRequest; + $parameters = array( 'slip_type' => 'payment', 'customer' => array( @@ -74,6 +79,8 @@ $request->setBody($parameters); ``` ```php +use Barzahlen\Request\CreateRequest; + $json = '{ "slip_type": "payment", "customer": { @@ -88,15 +95,32 @@ $request = new CreateRequest(); $request->setBody($json); ``` +#### Refund Slips This is an example for a minimal refund request. Please note that the amount is negative and must not exceed the initial payment amount. Multiple refunds for one payment up to the initial amount are possible. ```php +use Barzahlen\Request\CreateRequest; + $request = new CreateRequest(); $request->setSlipType('refund'); $request->setForSlipId('slp-1b41145c-2dd3-4e3f-bbe1-72c09fbf3f94'); $request->setTransaction('-14.95', 'EUR'); ``` +#### Payout Slips +This is an example for a minimal payout request. +> Payout slips allow a customer to receive money and result in money being transferred from your division. They are used when paying out money that is not associated with a previous payment. When returning a portion or all of the money a customer has previously paid via Barzahlen, use refund slips. + +```php +use Barzahlen\Request\CreateRequest; + +$request = new CreateRequest(); +$request->setSlipType('payout'); +$request->setCustomerKey('LDFKHSLFDHFL'); +$request->setTransaction('-14.95', 'EUR'); +``` + +#### More parameters You may set more parameters according to the [Barzahlen API v2 Documentation](https://docs.barzahlen.de/api/v2/). ```php @@ -242,6 +266,8 @@ Representation of current slip status. (Content depends on sent parameters.) To change slip parameters afterwards initiate a new UpdateRequest using the slip id. Use setters, an array or a json string to set your new or updated parameter(s). Only pending slips can be updated. For more information please read the [Barzahlen API v2 Documentation](https://docs.barzahlen.de/api/v2/). ```php +use Barzahlen\Request\UpdateRequest; + $request = new UpdateRequest('slp-f26bcd0b-556b-4285-b0b3-ba54052df97f'); $request->setCustomer(array( 'email' => 'customer@provider.tld', @@ -258,6 +284,10 @@ The expiresAt() method can be used with a DateTime object and chaining the sette The last three requests don't require any additional parameters via setters, array or json. They can be initiate with the slip id (and message type) before they're sent with the client. ```php +use Barzahlen\Request\RetrieveRequest; +use Barzahlen\Request\ResendRequest; +use Barzahlen\Request\InvalidateRequest; + // get current information on the slip $request = new RetrieveRequest('slp-f26bcd0b-556b-4285-b0b3-ba54052df97f'); @@ -269,11 +299,28 @@ $request = new ResendRequest('slp-f26bcd0b-556b-4285-b0b3-ba54052df97f', 'text_m $request = new InvalidateRequest('slp-f26bcd0b-556b-4285-b0b3-ba54052df97f'); ``` +### RetrievePdfRequest +Retrieve the slip’s PDF representation for printing. +Downloading the PDF is only possible for slips in the pending state. + +> Note: Due to security reasons this endpoint is disabled by default and can only be enabled by Barzahlen. Please feel free to contact us if you are interested in using this feature. + +```php +use Barzahlen\Request\RetrievePdfRequest; + +$request = new RetrievePdfRequest('slp-f26bcd0b-556b-4285-b0b3-ba54052df97f'); + +// contains pdf data +$response = $client->handle($request); + +``` + + ## Webhook When the state of a slip changes (e.g. the customer payed at a retail partner) and a hook url is set, Barzahlen will send a POST request to this hook url to let you know about the change. Initiate the Webhook class with the payment key and use it to verify the incoming request's header and body. ```php -use \Barzahlen\Webhook; +use Barzahlen\Webhook; $header = $_SERVER; $body = file_get_contents('php://input'); diff --git a/src/Client.php b/src/Client.php index 10fdc9d..43be8cf 100644 --- a/src/Client.php +++ b/src/Client.php @@ -2,6 +2,9 @@ namespace Barzahlen; +use Barzahlen\Request\Request; +use Barzahlen\Exception as Exception; + class Client { const API_URL = 'https://api.barzahlen.de:443/v2'; @@ -26,7 +29,7 @@ class Client /** * @var string */ - private $userAgent = 'PHP SDK v2.0.3'; + private $userAgent = 'PHP SDK v2.1.0'; /** @@ -53,7 +56,7 @@ public function setUserAgent($userAgent) } /** - * @param Request\Request $request + * @param Request $request * @return string * @throws Exception\ApiException * @throws Exception\AuthException @@ -85,14 +88,16 @@ public function handle($request) throw new Exception\CurlException('Error during cURL: ' . $error . ' [' . curl_errno($curl) . ']'); } - $this->checkResponse($response); + $contentType = curl_getinfo($curl, CURLINFO_CONTENT_TYPE); + $this->checkResponse($response, $contentType); + curl_close($curl); return $response; } /** - * @param Request\Request $request + * @param Request $request * @return array */ public function buildHeader($request) @@ -127,6 +132,7 @@ public function buildHeader($request) /** * @param string $response + * @param string $contentType * @throws Exception\ApiException * @throws Exception\AuthException * @throws Exception\IdempotencyException @@ -138,44 +144,32 @@ public function buildHeader($request) * @throws Exception\ServerException * @throws Exception\TransportException */ - public function checkResponse($response) + public function checkResponse($response, $contentType) { - if (strpos($response, 'error_class') === false) { - return; - } - - $response = json_decode($response); - switch ($response->error_class) { - case 'auth': - throw new Exception\AuthException($response->message, $response->request_id); - break; - case 'transport': - throw new Exception\TransportException($response->message, $response->request_id); - break; - case 'idempotency': - throw new Exception\IdempotencyException($response->message, $response->request_id); - break; - case 'rate_limit': - throw new Exception\RateLimitException($response->message, $response->request_id); - break; - case 'invalid_format': - throw new Exception\InvalidFormatException($response->message, $response->request_id); - break; - case 'invalid_state': - throw new Exception\InvalidStateException($response->message, $response->request_id); - break; - case 'invalid_parameter': - throw new Exception\InvalidParameterException($response->message, $response->request_id); - break; - case 'not_allowed': - throw new Exception\NotAllowedException($response->message, $response->request_id); - break; - case 'server_error': - throw new Exception\ServerException($response->message, $response->request_id); - break; - default: - throw new Exception\ApiException($response->message, $response->request_id); - break; + if (Middleware::stringIsPrefix('application/json', $contentType)) { + + if (strpos($response, 'error_class') === false) { + return; + } + + $response = json_decode($response); + $errorMapping = array( + 'auth' => '\Barzahlen\Exception\AuthException', + 'transport' => '\Barzahlen\Exception\TransportException', + 'idempotency' => '\Barzahlen\Exception\IdempotencyException', + 'rate_limit' => '\Barzahlen\Exception\RateLimitException', + 'invalid_format' => '\Barzahlen\Exception\InvalidFormatException', + 'invalid_state' => '\Barzahlen\Exception\InvalidStateException', + 'invalid_parameter' => '\Barzahlen\Exception\InvalidParameterException', + 'not_allowed' => '\Barzahlen\Exception\NotAllowedException', + 'server_error' => '\Barzahlen\Exception\ServerException' + ); + + if (isset($errorMapping[$response->error_class])) { + throw new $errorMapping[$response->error_class]($response->message, $response->request_id); + } + + throw new Exception\ApiException($response->message, $response->request_id); } } } diff --git a/src/Middleware.php b/src/Middleware.php index ba981bc..ddb9b04 100644 --- a/src/Middleware.php +++ b/src/Middleware.php @@ -56,4 +56,16 @@ public static function stringsEqual($first, $second) } return !$ret; } + + /** + * Check if first string is a prefix of second string. + * + * @param $prefix + * @param $string + * @return bool + */ + public static function stringIsPrefix($prefix, $string) + { + return substr($string, 0, strlen($prefix)) === $prefix; + } } diff --git a/src/Request/RetrievePdfRequest.php b/src/Request/RetrievePdfRequest.php new file mode 100644 index 0000000..4cb7b43 --- /dev/null +++ b/src/Request/RetrievePdfRequest.php @@ -0,0 +1,25 @@ +parameters[] = $slipId; + } +} diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 25a9b98..a9f4c3e 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -16,7 +16,7 @@ class ClientTest extends \PHPUnit_Framework_TestCase /** * @var string */ - private $userAgent = 'PHP SDK v2.0.3'; + private $userAgent = 'PHP SDK v2.1.0'; public function setUp() { @@ -66,7 +66,7 @@ public function testBuildHeaderWithoutIdempotencyForSandbox() public function testNoneError() { $response = '{}'; - $this->assertNull($this->client->checkResponse($response)); + $this->assertNull($this->client->checkResponse($response, 'application/json;charset=utf-8')); } /** @@ -75,7 +75,7 @@ public function testNoneError() public function testAuthError() { $response = '{"error_class":"auth","error_code":"invalid_signature","message":"error message","request_id":"r3qu3s71d"}'; - $this->client->checkResponse($response); + $this->client->checkResponse($response, 'application/json;charset=utf-8'); } /** @@ -84,7 +84,7 @@ public function testAuthError() public function testIdempotencyError() { $response = '{"error_class":"idempotency","error_code":"use_idempotency_key_twice","message":"error message","request_id":"r3qu3s71d"}'; - $this->client->checkResponse($response); + $this->client->checkResponse($response, 'application/json;charset=utf-8'); } /** @@ -93,7 +93,7 @@ public function testIdempotencyError() public function testInvalidFormatError() { $response = '{"error_class":"invalid_format","error_code":"bad_json_format","message":"error message","request_id":"r3qu3s71d"}'; - $this->client->checkResponse($response); + $this->client->checkResponse($response, 'application/json;charset=utf-8'); } /** @@ -102,7 +102,7 @@ public function testInvalidFormatError() public function testInvalidParameterError() { $response = '{"error_class":"invalid_parameter","error_code":"invalid_slip_type","message":"error message","request_id":"r3qu3s71d"}'; - $this->client->checkResponse($response); + $this->client->checkResponse($response, 'application/json;charset=utf-8'); } /** @@ -111,7 +111,7 @@ public function testInvalidParameterError() public function testInvalidStateError() { $response = '{"error_class":"invalid_state","error_code":"invalid_slip_state","message":"error message","request_id":"r3qu3s71d"}'; - $this->client->checkResponse($response); + $this->client->checkResponse($response, 'application/json;charset=utf-8'); } /** @@ -120,7 +120,7 @@ public function testInvalidStateError() public function testNotAllowedError() { $response = '{"error_class":"not_allowed","error_code":"method_not_allowed","message":"error message","request_id":"r3qu3s71d"}'; - $this->client->checkResponse($response); + $this->client->checkResponse($response, 'application/json;charset=utf-8'); } /** @@ -129,7 +129,7 @@ public function testNotAllowedError() public function testRateLimitError() { $response = '{"error_class":"rate_limit","error_code":"rate_limit_exceeded","message":"error message","request_id":"r3qu3s71d"}'; - $this->client->checkResponse($response); + $this->client->checkResponse($response, 'application/json;charset=utf-8'); } /** @@ -138,7 +138,7 @@ public function testRateLimitError() public function testServerError() { $response = '{"error_class":"server_error","error_code":"internal_server_error","message":"error message","request_id":"r3qu3s71d"}'; - $this->client->checkResponse($response); + $this->client->checkResponse($response, 'application/json;charset=utf-8'); } /** @@ -147,7 +147,7 @@ public function testServerError() public function testTransportError() { $response = '{"error_class":"transport","error_code":"invalid_host_header","message":"error message","request_id":"r3qu3s71d"}'; - $this->client->checkResponse($response); + $this->client->checkResponse($response, 'application/json;charset=utf-8'); } /** @@ -156,6 +156,12 @@ public function testTransportError() public function testUnknownError() { $response = '{"error_class":"unknown","error_code":"unknown_error","message":"error message","request_id":"r3qu3s71d"}'; - $this->client->checkResponse($response); + $this->client->checkResponse($response, 'application/json;charset=utf-8'); + } + + public function testMediaResponse() + { + $response = ''; // some pdf data - non in this test case + $this->assertNull($this->client->checkResponse($response, 'application/pdf')); } } diff --git a/tests/MiddlewareTest.php b/tests/MiddlewareTest.php index bc4d68b..2eb19d9 100644 --- a/tests/MiddlewareTest.php +++ b/tests/MiddlewareTest.php @@ -45,4 +45,10 @@ public function testStringsEqualValid() $this->assertTrue(Middleware::stringsEqual($first, $second)); } + + public function testStringIsPrefix() + { + $this->assertTrue(Middleware::stringIsPrefix('thisIs?\A$9Prefix', 'thisIs?\A$9PrefixAndMore')); + $this->assertTrue(!Middleware::stringIsPrefix('a_prefix', 'has_no_prefix')); + } } diff --git a/tests/Request/CreateRequestTest.php b/tests/Request/CreateRequestTest.php index 12c17be..752548e 100644 --- a/tests/Request/CreateRequestTest.php +++ b/tests/Request/CreateRequestTest.php @@ -111,6 +111,24 @@ public function testRefundBody() $this->assertEquals($expectedBody, $this->request->getBody()); } + public function testPayoutBody() + { + $date = new \DateTime(); + $date->modify('+2 weeks'); + + $this->request->setSlipType('payout'); + $this->request->setCustomer(array( + 'key' => 'LDFKHSLFDHFL', + 'cell_phone' => '012345678910' + )); + $this->request->setTransaction('-20.95', 'EUR'); + $this->request->setExpiresAt($date); + + $expectedBody = '{"slip_type":"payout","transactions":[{"amount":"-20.95","currency":"EUR"}],"customer":{"key":"LDFKHSLFDHFL","cell_phone":"012345678910"},"expires_at":"' . $date->format('c') . '"}'; + + $this->assertEquals($expectedBody, $this->request->getBody()); + } + public function testSetMaximalParametersBody() { $this->request->setSlipType('payment');