From 24428a5b7399bc46a68f08187fb62fe42a45d455 Mon Sep 17 00:00:00 2001 From: cave Date: Fri, 17 Sep 2021 09:40:21 +0200 Subject: [PATCH] init composer.json fix readme composer.json be crazy, skip the min stability be crazy, skip the min stability be crazy, skip the min stability signature fix concat string missing exception class --- .gitignore | 8 + CONTRIBUTING.md | 10 + LICENSE | 19 + README.md | 79 ++++ composer.json | 50 +++ src/Gateway.php | 112 +++++ src/Message/Request/APSAbstractRequest.php | 84 ++++ .../Request/AbstractCheckoutRequest.php | 13 + src/Message/Request/AuthorizationRequest.php | 31 ++ src/Message/Request/CaptureRequest.php | 37 ++ src/Message/Request/PurchaseRequest.php | 31 ++ src/Message/Request/RefundRequest.php | 38 ++ .../Request/RequestPhraseException.php | 5 + src/Message/Response/AbstractResponse.php | 424 ++++++++++++++++++ src/Message/Response/AutorizationResponse.php | 17 + src/Message/Response/CaptureResponse.php | 17 + src/Message/Response/PurchaseResponse.php | 17 + src/Message/Response/RefundResponse.php | 17 + tests/GatewayTest.php | 113 +++++ tests/Mock/AuthorizationFailure.txt | 1 + tests/Mock/AuthorizationSuccess.txt | 1 + tests/Mock/PurchaseFailure.txt | 1 + tests/Mock/PurchaseSuccess.txt | 1 + 23 files changed, 1126 insertions(+) create mode 100644 .gitignore create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 composer.json create mode 100644 src/Gateway.php create mode 100644 src/Message/Request/APSAbstractRequest.php create mode 100644 src/Message/Request/AbstractCheckoutRequest.php create mode 100644 src/Message/Request/AuthorizationRequest.php create mode 100644 src/Message/Request/CaptureRequest.php create mode 100644 src/Message/Request/PurchaseRequest.php create mode 100644 src/Message/Request/RefundRequest.php create mode 100644 src/Message/Request/RequestPhraseException.php create mode 100644 src/Message/Response/AbstractResponse.php create mode 100644 src/Message/Response/AutorizationResponse.php create mode 100644 src/Message/Response/CaptureResponse.php create mode 100644 src/Message/Response/PurchaseResponse.php create mode 100644 src/Message/Response/RefundResponse.php create mode 100644 tests/GatewayTest.php create mode 100644 tests/Mock/AuthorizationFailure.txt create mode 100644 tests/Mock/AuthorizationSuccess.txt create mode 100644 tests/Mock/PurchaseFailure.txt create mode 100644 tests/Mock/PurchaseSuccess.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9c09f36 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +/vendor +composer.lock +composer.phar +phpunit.xml +.directory +/dirlist.* +/documents/ +/.idea/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..e166494 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,10 @@ +# Contributing Guidelines + +* Fork the project. +* Make your feature addition or bug fix. +* Add tests for it. This is important so I don't break it in a future version unintentionally. +* Commit just the modifications, do not mess with the composer.json or CHANGELOG.md files. +* Ensure your code is nicely formatted in the [PSR-2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md) + style and that all tests pass. +* Send the pull request. +* Check that the Travis CI build passed. If not, rinse and repeat. \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9dafda7 --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015 Marco Beinbrech + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..fdefbb1 --- /dev/null +++ b/README.md @@ -0,0 +1,79 @@ +# Omnipay: APS (Amazon Payment Service) + +**APS driver for the Omnipay PHP payment processing library** + +[Omnipay](https://github.com/thephpleague/omnipay) is a framework agnostic, multi-gateway payment +processing library for PHP. This package implements Amazon Payment Services (APS) support for Omnipay. + +## Installation + +Omnipay is installed via [Composer](http://getcomposer.org/). To install, simply require +`league/omnipay` and `cave/omnipay-aps` with Composer: + +``` +composer require league/omnipay cave/omnipay-aps +``` + +## Basic Usage + +The following gateways are provided by this package: + +* APS + +For general usage instructions, please see the main [Omnipay](https://github.com/omnipay/omnipay) +repository. + +## Example + +### Purchase + +The result will be a redirect to the gateway or bank. + +```php +use Omnipay\Omnipay; + +$gateway = Omnipay::create('Amazon Payment Service'); + +// Send purchase request (don't get so excited... params below are just fake :)) +$response = $gateway->purchase([ + 'access_code' => 'zx0IPmPy5jp1vAz8Kpg7', + 'merchant_identifier' => 'CycHZxVj', + 'merchant_reference' => 'XYZ9239-yu898', + 'amount' => '10000', + 'currency' => 'AED', + 'language' => 'en', + 'customer_email' => 'test@payfort.com', + 'order_description' => 'iPhone 6-S', +])->send(); + +// Process response +if ($response->isSuccessful()) { + // Let's party!!! +} else { + // Payment failed: display message to customer + echo $response->getMessage(); +} +``` + +The Purchase request combines Authorization and Capture in the same request +(per APS' documentation). If you need to first authorize the amount you can call the Authorization +request and then the Capture request separately. + +### Testing + +```sh +composer test +``` + +## Support + +If you are having general issues with Omnipay, we suggest posting on +[Stack Overflow](http://stackoverflow.com/). Be sure to add the +[omnipay tag](http://stackoverflow.com/questions/tagged/omnipay) so it can be easily found. + +If you want to keep up to date with release anouncements, discuss ideas for the project, +or ask more detailed questions, there is also a [mailing list](https://groups.google.com/forum/#!forum/omnipay) which +you can subscribe to. + +If you believe you have found a bug, please report it using the [GitHub issue tracker](https://github.com/cave/omnipay-aps/issues), +or better yet, fork the library and submit a pull request. \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..156a17c --- /dev/null +++ b/composer.json @@ -0,0 +1,50 @@ +{ + "name": "cave/omnipay-aps", + "type": "library", + "description": "Omnipay driver for Amazon Payment Services", + "require": { + "php": "^7.3 || ^8.0", + "ext-json": "*", + "omnipay/common": "^3.0" + }, + "require-dev": { + "omnipay/tests": "^3", + "squizlabs/php_codesniffer": "^3.4", + "phpunit/phpunit": "^6.0" + }, + "keywords": [ + "omnipay", + "amazon", + "payment", + "service", + "payment", + "gateway", + "laravel", + "purchase" + ], + "homepage": "https://github.com/cave/omnipay-aps", + "license": "MIT", + "autoload": { + "psr-4": { + "Omnipay\\APS\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Omnipay\\APS\\Tests\\": "tests/" + } + }, + "authors": [ + { + "name": "cave", + "email": "vedran.kapetanovic@gmail.com" + } + ], + "scripts": { + "test": "phpunit", + "check-style": "phpcs -p --standard=PSR2 src/", + "fix-style": "phpcbf -p --standard=PSR2 src/" + }, + "minimum-stability": "dev", + "prefer-stable": true +} diff --git a/src/Gateway.php b/src/Gateway.php new file mode 100644 index 0000000..b660b70 --- /dev/null +++ b/src/Gateway.php @@ -0,0 +1,112 @@ + FALSE, + ]; + } + + /** + * @param array $options + * + * @return AbstractRequest|RequestInterface + */ + public function authorize(array $options = []) + { + return $this->createRequest(AuthorizationRequest::class, $options); + } + + /** + * @param array $options + * + * @return AbstractRequest|RequestInterface + */ + public function purchase(array $options = []) + { + return $this->createRequest(PurchaseRequest::class, $options); + } + + /** + * @param array $options + * + * @return AbstractRequest|RequestInterface + */ + public function capture(array $options = []) + { + return $this->createRequest(CaptureRequest::class, $options); + } + + /** + * @param array $options + * + * @return AbstractRequest|RequestInterface + */ + public function refund(array $options = []) + { + return $this->createRequest(RefundRequest::class, $options); + } + + /** + * @param string $requestPhrase + * + * @return mixed + */ + public function setRequestPhrase(string $requestPhrase) + { + return $this->setParameter('request_phrase', $requestPhrase); + } + + /** + * @param string $shaType + * + * @return mixed + */ + public function setShaType(string $shaType) + { + return $this->setParameter('sha_type', $shaType); + } +} + diff --git a/src/Message/Request/APSAbstractRequest.php b/src/Message/Request/APSAbstractRequest.php new file mode 100644 index 0000000..8d0e8c4 --- /dev/null +++ b/src/Message/Request/APSAbstractRequest.php @@ -0,0 +1,84 @@ +_createSignature($data); + + $httpResponse = $this->httpClient->request( + 'POST', + $this->getEndpoint(), + [ + 'Accept' => 'application/json', + 'Content-type' => 'application/json', + ], + json_encode($data) + ); + + $json = $httpResponse->getBody()->getContents(); + + $data = !empty($json) ? json_decode($json, true) : []; + + return $this->response = $this->createResponse($data); + + } + catch (\Exception $e) + { + throw new InvalidResponseException( + "Communication failed with message: " . $e->getMessage(), + $e->getCode() + ); + } + } + + /** + * @return string + */ + protected function getEndpoint(): string + { + return $this->getTestMode() ? $this->test_endpoint : $this->live_endpoint; + } + + /** + * Creates signature used by Amazon for authorisation + * @param array $data + * + * @return string + * @throws RequestPhraseException + */ + private function _createSignature(array $data): string + { + $shaType = $this->hasParameter('sha_type') ? $this->hasParameter('sha_type') : self::DEFAULT_SHA_TYPE; + + if ( ! $this->hasParameter('request_phrase')) + throw new RequestPhraseException('Request phrase is missing.'); + + foreach ($data as $key => $value) + { + $shaString .= "$key=$value"; + } + + // "Glue" phrase to the both sides of the payload + $shaString = $this->getParameter('request_phrase') . $shaString . $this->getParameter('request_phrase'); + + return hash($shaType, $shaString); + } +} \ No newline at end of file diff --git a/src/Message/Request/AbstractCheckoutRequest.php b/src/Message/Request/AbstractCheckoutRequest.php new file mode 100644 index 0000000..01bf19d --- /dev/null +++ b/src/Message/Request/AbstractCheckoutRequest.php @@ -0,0 +1,13 @@ +validate('access_code', 'merchant_identifier', 'merchant_reference', 'amount', 'currency', 'language', 'customer_email', 'signature'); + } +} \ No newline at end of file diff --git a/src/Message/Request/AuthorizationRequest.php b/src/Message/Request/AuthorizationRequest.php new file mode 100644 index 0000000..f634c10 --- /dev/null +++ b/src/Message/Request/AuthorizationRequest.php @@ -0,0 +1,31 @@ +validateData(); + + return [ + 'command' => self::COMMAND, + ]; + } + + /** + * @param array $data + * + * @return AuthorizationResponse + * @throws \Omnipay\Common\Exception\InvalidResponseException + */ + protected function createResponse(array $data): AuthorizationResponse + { + return new AuthorizationResponse($this, $data); + } +} diff --git a/src/Message/Request/CaptureRequest.php b/src/Message/Request/CaptureRequest.php new file mode 100644 index 0000000..2d53d1a --- /dev/null +++ b/src/Message/Request/CaptureRequest.php @@ -0,0 +1,37 @@ +validate('access_code', 'merchant_identifier', 'merchant_reference', 'amount', 'currency', 'language', 'signature'); + + return [ + 'command' => self::COMMAND, + ]; + } + + /** + * @param array $data + * + * @return CaptureResponse + * @throws \Omnipay\Common\Exception\InvalidResponseException + */ + protected function createResponse(array $data): CaptureResponse + { + return new CaptureResponse($this, $data); + } +} \ No newline at end of file diff --git a/src/Message/Request/PurchaseRequest.php b/src/Message/Request/PurchaseRequest.php new file mode 100644 index 0000000..b563dae --- /dev/null +++ b/src/Message/Request/PurchaseRequest.php @@ -0,0 +1,31 @@ +validateData(); + + return [ + 'command' => self::COMMAND, + ]; + } + + /** + * @param array $data + * + * @return PurchaseResponse + * @throws \Omnipay\Common\Exception\InvalidResponseException + */ + protected function createResponse(array $data): PurchaseResponse + { + return new PurchaseResponse($this, $data); + } +} diff --git a/src/Message/Request/RefundRequest.php b/src/Message/Request/RefundRequest.php new file mode 100644 index 0000000..b11767b --- /dev/null +++ b/src/Message/Request/RefundRequest.php @@ -0,0 +1,38 @@ +validate('access_code', 'merchant_identifier', 'merchant_reference', 'amount', 'currency', 'language', 'signature'); + + return [ + 'command' => self::COMMAND, + ]; + } + + /** + * @param array $data + * + * @return RefundResponse + * @throws \Omnipay\Common\Exception\InvalidResponseException + */ + protected function createResponse(array $data): RefundResponse + { + return new RefundResponse($this, $data); + } +} \ No newline at end of file diff --git a/src/Message/Request/RequestPhraseException.php b/src/Message/Request/RequestPhraseException.php new file mode 100644 index 0000000..672a696 --- /dev/null +++ b/src/Message/Request/RequestPhraseException.php @@ -0,0 +1,5 @@ + 'Invalid Request.', + '01' => 'Order Stored.', + '02' => 'Authorization Success.', + '03' => 'Authorization Failed.', + '04' => 'Capture Success.', + '05' => 'Capture failed.', + '06' => 'Refund Success.', + '07' => 'Refund Failed.', + '08' => 'Authorization Voided Successfully.', + '09' => 'Authorization Void Failed.', + '10' => 'Incomplete.', + '11' => 'Check status Failed.', + '12' => 'Check status success.', + '13' => 'Purchase Failure.', + '14' => 'Purchase Success.', + '15' => 'Uncertain Transaction.', + '17' => 'Tokenization failed.', + '18' => 'Tokenization success.', + '19' => 'Transaction pending.', + '20' => 'On hold.', + '21' => 'SDK Token creation failure.', + '22' => 'SDK Token creation success.', + '23' => 'Failed to process Digital Wallet service.', + '24' => 'Digital wallet order processed successfully.', + '27' => 'Check card balance failed.', + '28' => 'Check card balance success.', + '29' => 'Redemption failed.', + '30' => 'Redemption success.', + '31' => 'Reverse Redemption transaction failed.', + '32' => 'Reverse Redemption transaction success.', + '40' => 'Transaction In review.', + '42' => 'Currency conversion success.', + '43' => 'Currency conversion failed.', + '44' => '3ds success.', + '45' => '3ds failed.', + '46' => 'Bill creation success.', + '47' => 'Bill creation failed.', + '48' => 'Generating invoice payment link success.', + '49' => 'Generating invoice payment link failed.', + '50' => 'Batch file upload successfully.', + '51' => 'Upload batch file failed.', + '52' => 'Token created successfully.', + '53' => 'Token creation failed.', + '54' => 'Get Tokens Success.', + '55' => 'Get Tokens Failed.', + '56' => 'Reporting Request Success.', + '57' => 'Reporting Request Failed.', + '58' => 'Token updated successfully.', + '59' => 'Token updated failed.', + '62' => 'Get Installment Plans Successfully.', + '63' => 'Get Installment plans Failed.', + '66' => 'Delete Token Success.', + '70' => 'Get batch results successfully.', + '71' => 'Get batch results failed.', + '72' => 'Batch processing success.', + '73' => 'Batch processing failed.', + '74' => 'Bank transfer successfully.', + '75' => 'Bank transfer failed.', + '76' => 'Batch validation successfully.', + '77' => 'Batch validation failed.', + '80' => 'Credit card verified successfully.', + '81' => 'Failed to verify credit card.', + ]; + + protected $messages = [ + '000' => 'Success.', + '001' => 'Missing parameter.', + '002' => 'Invalid parameter format.', + '003' => 'Payment option is not available for this merchant’s account.', + '004' => 'Invalid command.', + '005' => 'Invalid amount.', + '006' => 'Technical problem.', + '007' => 'Duplicate order number.', + '008' => 'Signature mismatch.', + '009' => 'Invalid merchant identifier.', + '010' => 'Invalid access code.', + '011' => 'Order not saved.', + '012' => 'Card expired.', + '013' => 'Invalid currency.', + '014' => 'Inactive payment option.', + '015' => 'Inactive merchant account.', + '016' => 'Invalid card number.', + '017' => 'Operation not allowed by the acquirer.', + '018' => 'Operation not allowed by processor.', + '019' => 'Inactive acquirer.', + '020' => 'Processor is inactive.', + '021' => 'Payment option deactivated by acquirer.', + '023' => 'Currency not accepted by acquirer.', + '024' => 'Currency not accepted by processor.', + '025' => 'Processor integration settings are missing.', + '026' => 'Acquirer integration settings are missing.', + '027' => 'Invalid extra parameters.', + '029' => 'Insufficient funds.', + '030' => 'Authentication failed.', + '031' => 'Invalid issuer.', + '032' => 'Invalid parameter length.', + '033' => 'Parameter value not allowed.', + '034' => 'Operation not allowed.', + '035' => 'Order created successfully.', + '036' => 'Order not found.', + '037' => 'Missing return URL.', + '038' => 'Token service inactive.', + '039' => 'No active payment option found.', + '040' => 'Invalid transaction source.', + '042' => 'Operation amount exceeds the authorized amount.', + '043' => 'Inactive Operation.', + '044' => 'Token name does not exist.', + '046' => 'Channel is not configured for the selected payment option.', + '047' => 'Order already processed.', + '048' => 'Operation amount exceeds captured amount.', + '049' => 'Operation not valid for this payment option.', + '050' => 'Merchant per transaction limit exceeded.', + '051' => 'Technical error.', + '052' => 'Consumer is not in OLP database.', + '053' => 'Merchant is not found in OLP Engine DB.', + '054' => 'Transaction cannot be processed at this moment.', + '055' => 'OLP ID Alias is not valid. Please contact your bank.', + '056' => 'OLP ID Alias does not exist. Please enter a valid OLP ID Alias.', + '057' => 'Transaction amount exceeds the daily transaction limit.', + '058' => 'Transaction amount exceeds the per transaction limit.', + '059' => 'Merchant Name and SADAD Merchant ID do not match.', + '060' => 'The entered OLP password is incorrect. Please provide a valid password.', + '062' => 'Token has been created.', + '063' => 'Token has been updated.', + '064' => '3D Secure check requested.', + '065' => 'Transaction waiting for customer’s action.', + '066' => 'Merchant reference already exists.', + '067' => 'Dynamic Descriptor not configured for selected payment option.', + '068' => 'SDK service is inactive.', + '069' => 'Mapping not found for the given error code.', + '070' => 'device_id mismatch.', + '071' => 'Failed to initiate connection.', + '072' => 'Transaction has been cancelled by the consumer.', + '073' => 'Invalid request format.', + '074' => 'Transaction failed.', + '075' => 'Transaction failed.', + '076' => 'Transaction not found in OLP.', + '077' => 'Error transaction code not found.', + '078' => 'Failed to check fraud screen.', + '079' => 'Transaction challenged by fraud rules.', + '080' => 'Invalid payment option.', + '082' => 'Inactive fraud service.', + '083' => 'Unexpected user behavior.', + '084' => 'Transaction amount is either bigger than maximum or less than minimum amount accepted for the selected plan.', + '086' => 'Installment plan is not configured for Merchant account.', + '087' => 'Card BIN does not match accepted issuer bank.', + '088' => 'Token name was not created for this transaction.', + '089' => 'Failed to retrieve digital wallet details.', + '090' => 'Transaction in review.', + '092' => 'Invalid issuer code.', + '093' => 'service inactive.', + '094' => 'Invalid Plan Code.', + '095' => 'Inactive Issuer.', + '096' => 'Inactive Plan.', + '097' => 'Operation not allowed for service.', + '098' => 'Invalid or expired call_id.', + '099' => 'Failed to execute service.', + '100' => 'Invalid expiry date.', + '101' => 'Bill number not found.', + '102' => 'Apple Pay order has been expired.', + '103' => 'Duplicate subscription ID.', + '104' => 'No plans valid for request.', + '105' => 'Invalid bank code.', + '106' => 'Inactive bank.', + '107' => 'Invalid transfer_date.', + '110' => 'Contradicting parameters, please refer to the integration guide.', + '111' => 'Service not applicable for payment option.', + '112' => 'Service not applicable for payment operation.', + '113' => 'Service not applicable for e-commerce indicator.', + '114' => 'Token already exist.', + '115' => 'Expired invoice payment link.', + '116' => 'Inactive notification type.', + '117' => 'Invoice payment link already processed.', + '118' => 'Order bounced.', + '119' => 'Request dropped.', + '120' => 'Payment link terms and conditions not found.', + '121' => 'Card number is not verified.', + '122' => 'Invalid date interval.', + '123' => 'You have exceeded the maximum number of attempts.', + '124' => 'Account successfully created.', + '125' => 'Invoice already paid.', + '126' => 'Duplicate invoice ID.', + '127' => 'Merchant reference is not generated yet.', + '128' => 'The generated report is still pending, you can’t download it now.', + '129' => '“Downloaded report” queue is full. Wait till its empty again.', + '134' => 'Your search results have exceeded the maximum number of records.', + '136' => 'The Batch file validation is failed.', + '137' => 'Invalid Batch file execution date.', + '138' => 'The Batch file still under validation.', + '140' => 'The Batch file still under processing.', + '141' => 'The Batch reference does not exist.', + '142' => 'The Batch file header is invalid.', + '144' => 'Invalid Batch file.', + '146' => 'The Batch reference is already exist.', + '147' => 'The Batch process request has been received.', + '148' => 'Batch file will be processed.', + '149' => 'Payment link request id not found.', + '150' => 'Payment link is already open.', + '151' => '3ds_id does not exist.', + '152' => '3Ds verification doesn’t match the request details.', + '154' => 'You have reached the maximum number of upload retries.', + '155' => 'The upload retries is not configured.', + '662' => 'Operation not allowed. The specified order is not confirmed yet.', + '666' => 'Transaction declined.', + '773' => 'Transaction closed.', + '777' => 'The transaction has been processed, but failed to receive confirmation.', + '778' => 'Session timed-out.', + '779' => 'Transformation error.', + '780' => 'Transaction number transformation error.', + '781' => 'Message or response code transformation error.', + '783' => 'Installments service inactive.', + '784' => 'Transaction still processing you can’t make another transaction.', + '785' => 'Transaction blocked by fraud check.', + '787' => 'Failed to authenticate the user.', + '788' => 'Invalid bill number.', + '789' => 'Expired bill number.', + '790' => 'Invalid bill type code.', + ]; + + protected $response; + + /** + * Constructor + * @param RequestInterface $request initiating request. + * @param mixed $data + * @throws InvalidResponseException + */ + public function __construct(RequestInterface $request, $data) + { + $this->request = $request; + $this->processResponse($data); + } + + /** + * @param $response + * @throws InvalidResponseException + */ + public function processResponse($response): void + { + if ( ! is_array($response)) + throw new InvalidResponseException('Wrong response format.'); + + if (empty($response)) + throw new InvalidResponseException('Response is empty.'); + } + + /** + * @return string|null + */ + public function getMessage(): ?string + { + return $this->response['response_message']; + } + + /** + * @return bool + */ + public function isPending(): bool + { + return $this->response['status'] == self::STATUS_CODE_PENDING; + } + + /** + * The ID of the Merchant. + * Example: CycHZxVj + * + * @return string + */ + public function getMerchantId(): string + { + return $this->response['merchant_identifier']; + } + + /** + * The Merchant’s unique order number. + * Example: XYZ9239-yu898 + * + * @return mixed + */ + public function getMerchantReference(): string + { + return $this->response['merchant_reference']; + } + + /** + * Access code. + * Example: zx0IPmPy5jp1vAz8Kpg7 + * + * @return string + */ + public function getAccessCode(): string + { + return $this->response['access_code']; + } + + /** + * The transaction’s amount. + * Example: 10000 + * + * @return int + */ + public function getAmount(): int + { + return $this->response['amount']; + } + + /** + * The currency of the transaction’s amount in ISO code 3. + * Example: AED + * + * @return string + */ + public function getCurrency(): string + { + return $this->response['currency']; + } + + /** + * The checkout page and messages language. + * Possible/ expected values: en/ ar + * + * @return string + */ + public function getLanguage(): string + { + return $this->response['language']; + } + + /** + * A string hashed using the Secure Hash Algorithm. + * Example: 7cad05f0212ed933c9a5d5dffa31661acf2c827a + * + * @return string + */ + public function getSignature(): string + { + return $this->response['language']; + } + + /** + * The Refund’s unique order number. + * You will be able to retry on the refund request using the same maintenance reference if the refund transaction was declined. + * + * @return string + */ + public function getMaintenanceReference(): string + { + return $this->response['maintenance_reference']; + } + + /** + * The order’s unique reference returned by our system. + * Example: 149295435400084008 + * + * @return string + */ + public function getFortId(): string + { + return $this->response['fort_id']; + } + + /** + * It holds the description of the order. + * Example: iPhone 6-S + * + * @return string + */ + public function getOrderDescription(): string + { + return $this->response['order_description']; + } + + /** + * The message description of the response code; it returns according to the request language. + * + * @return string + */ + public function getResponseMessage(): string + { + return $this->response['order_description']; + } + + /** + * Response Code carries the value of our system’s response. + * The code consists of five digits, the first 2 digits represent the response status, and the last 3 digits represent the response messages. + * + * @return int + */ + public function getResponseCode(): int + { + return $this->response['response_code']; + } + + /** + * A two-digit numeric value that indicates the status of the transaction. + * + * @return string + */ + public function getStatus(): string + { + return $this->status_codes[$this->response['status']]; + } + + /** + * Human readable status message. + * + * @return string + */ + public function getStatusMessage(): string + { + return $this->status_codes[$this->getStatus()]; + } +} \ No newline at end of file diff --git a/src/Message/Response/AutorizationResponse.php b/src/Message/Response/AutorizationResponse.php new file mode 100644 index 0000000..e0214b6 --- /dev/null +++ b/src/Message/Response/AutorizationResponse.php @@ -0,0 +1,17 @@ +response['status'] == self::STATUS_SUCCESS; + } +} \ No newline at end of file diff --git a/src/Message/Response/CaptureResponse.php b/src/Message/Response/CaptureResponse.php new file mode 100644 index 0000000..d1206ee --- /dev/null +++ b/src/Message/Response/CaptureResponse.php @@ -0,0 +1,17 @@ +response['status'] == self::STATUS_SUCCESS; + } +} \ No newline at end of file diff --git a/src/Message/Response/PurchaseResponse.php b/src/Message/Response/PurchaseResponse.php new file mode 100644 index 0000000..c010c3e --- /dev/null +++ b/src/Message/Response/PurchaseResponse.php @@ -0,0 +1,17 @@ +response['status'] == self::STATUS_SUCCESS; + } +} \ No newline at end of file diff --git a/src/Message/Response/RefundResponse.php b/src/Message/Response/RefundResponse.php new file mode 100644 index 0000000..4b0decb --- /dev/null +++ b/src/Message/Response/RefundResponse.php @@ -0,0 +1,17 @@ +response['status'] == self::STATUS_SUCCESS; + } +} \ No newline at end of file diff --git a/tests/GatewayTest.php b/tests/GatewayTest.php new file mode 100644 index 0000000..9447555 --- /dev/null +++ b/tests/GatewayTest.php @@ -0,0 +1,113 @@ +gateway = new Gateway($this->getHttpClient(), $this->getHttpRequest()); + $this->gateway->setTestMode(TRUE); + + $this->options = [ + 'access_code' => 'zx0IPmPy5jp1vAz8Kpg7', + 'merchant_identifier' => 'CycHZxVj', + 'merchant_reference' => 'XYZ9239-yu898', + 'currency' => 'AED', + 'language' => 'en', + 'customer_email' => 'test@payfort.com', + 'signature' => '7cad05f0212ed933c9a5d5dffa31661acf2c827a', + 'order_description' => 'iPhone 6-S', + ]; + } + + /** + * Authorization success + */ + public function testAuthorizationSuccess() + { + $this->setMockHttpResponse('AuthorizationSuccess.txt'); + + $this->options['amount'] = '10000'; + + /** @var AuthorizationResponse $response */ + $response = $this->gateway + ->authorize($this->options) + ->send(); + + $this->assertTrue($response->isSuccessful()); + $this->assertEquals('Success.', $response->getMessage()); + } + + /** + * Authorization failure + */ + public function testAuthorizationFailure() + { + $this->setMockHttpResponse('AuthorizationFailure.txt'); + + $this->options['amount'] = NULL; + + /** @var AuthorizationResponse $response */ + $response = $this->gateway + ->authorize($this->options) + ->send(); + + $this->assertFalse($response->isSuccessful()); + $this->assertSame('Invalid amount.', $response->getMessage()); + } + + /** + * Purchase success. Purchase combines authorization and capture requests + */ + public function testPurchaseSuccess() + { + $this->setMockHttpResponse('PurchaseFailure.txt'); + + $this->options['amount'] = '10000'; + + /** @var PurchaseResponse $response */ + $response = $this->gateway + ->authorize($this->options) + ->send(); + + $this->assertTrue($response->isSuccessful()); + $this->assertEquals('Success.', $response->getMessage()); + } + + + /** + * Purchase failure + */ + public function testPurchaseFailure() + { + $this->setMockHttpResponse('PurchaseSuccess.txt'); + + $this->options['amount'] = '10001'; + + /** @var PurchaseResponse $response */ + $response = $this->gateway + ->authorize($this->options) + ->send(); + + $this->assertFalse($response->isSuccessful()); + $this->assertEquals('Operation amount exceeds the authorized amount.', $response->getMessage()); + } +} diff --git a/tests/Mock/AuthorizationFailure.txt b/tests/Mock/AuthorizationFailure.txt new file mode 100644 index 0000000..deaac2b --- /dev/null +++ b/tests/Mock/AuthorizationFailure.txt @@ -0,0 +1 @@ +{"command":"AUTHORIZATION","access_code":"zx0IPmPy5jp1vAz8Kpg7","merchant_identifier":"CycHZxVj","merchant_reference":"XYZ9239-yu898","amount":"10000","currency":"AED","language":"en","customer_email":"test@payfort.com","signature":"7cad05f0212ed933c9a5d5dffa31661acf2c827a","fort_id":"149295435400084008","payment_option":"VISA","eci":"ECOMMERCE","order_description":"iPhone6-S","customer_ip":"192.178.1.10","customer_name":"John","response_message":"Invalid amount.","response_code":"20064","status":"03","card_holder_name":"John Smith","expiry_date":"2105","card_number":"400555******0001"} \ No newline at end of file diff --git a/tests/Mock/AuthorizationSuccess.txt b/tests/Mock/AuthorizationSuccess.txt new file mode 100644 index 0000000..cb85c05 --- /dev/null +++ b/tests/Mock/AuthorizationSuccess.txt @@ -0,0 +1 @@ +{"command":"AUTHORIZATION","access_code":"zx0IPmPy5jp1vAz8Kpg7","merchant_identifier":"CycHZxVj","merchant_reference":"XYZ9239-yu898","amount":"10000","currency":"AED","language":"en","customer_email":"test@payfort.com","signature":"7cad05f0212ed933c9a5d5dffa31661acf2c827a","fort_id":"149295435400084008","payment_option":"VISA","eci":"ECOMMERCE","order_description":"iPhone6-S","customer_ip":"192.178.1.10","customer_name":"John","response_message":"Success","response_code":"20064","status":"02","card_holder_name":"John Smith","expiry_date":"2105","card_number":"400555******0001"} \ No newline at end of file diff --git a/tests/Mock/PurchaseFailure.txt b/tests/Mock/PurchaseFailure.txt new file mode 100644 index 0000000..3c10cb2 --- /dev/null +++ b/tests/Mock/PurchaseFailure.txt @@ -0,0 +1 @@ +{"command":"AUTHORIZATION","access_code":"zx0IPmPy5jp1vAz8Kpg7","merchant_identifier":"CycHZxVj","merchant_reference":"XYZ9239-yu898","amount":"10000","currency":"AED","language":"en","customer_email":"test@payfort.com","signature":"7cad05f0212ed933c9a5d5dffa31661acf2c827a","fort_id":"149295435400084008","payment_option":"VISA","eci":"ECOMMERCE","order_description":"iPhone6-S","customer_ip":"192.178.1.10","customer_name":"John","response_message":"Operation amount exceeds the authorized amount.","response_code":"14042","status":"14","card_holder_name":"John Smith","expiry_date":"2105","card_number":"400555******0001"} \ No newline at end of file diff --git a/tests/Mock/PurchaseSuccess.txt b/tests/Mock/PurchaseSuccess.txt new file mode 100644 index 0000000..70f43d2 --- /dev/null +++ b/tests/Mock/PurchaseSuccess.txt @@ -0,0 +1 @@ +{"command":"AUTHORIZATION","access_code":"zx0IPmPy5jp1vAz8Kpg7","merchant_identifier":"CycHZxVj","merchant_reference":"XYZ9239-yu898","amount":"10000","currency":"AED","language":"en","customer_email":"test@payfort.com","signature":"7cad05f0212ed933c9a5d5dffa31661acf2c827a","fort_id":"149295435400084008","payment_option":"VISA","eci":"ECOMMERCE","order_description":"iPhone6-S","customer_ip":"192.178.1.10","customer_name":"John","response_message":"Success","response_code":"13064","status":"13","card_holder_name":"John Smith","expiry_date":"2105","card_number":"400555******0001"} \ No newline at end of file