From f265bd8a54ba3fab675d3fb05dd98b59c13a809d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20Reyrol?= Date: Wed, 24 Jul 2019 14:25:11 +0200 Subject: [PATCH] Update to 3DSv2 and implement recovery, cancel and refund (#13) --- .coveralls.yml | 3 + .gitignore | 1 + .scrutinizer.yml | 23 ++ .travis.yml | 17 ++ ReadMe.md | 113 ++++++++- composer.json | 7 +- composer.lock | 5 +- phpunit.xml | 9 + src/BaseMethod.php | 28 +++ src/Cancel/Cancel.php | 39 +++ src/Cancel/Response.php | 9 + src/Exceptions/AuthenticationException.php | 107 ++++++++ src/Exceptions/Exception.php | 93 +++++++ src/Exceptions/PaymentException.php | 55 +---- src/Exceptions/RecoveryException.php | 36 +++ src/Method.php | 12 + src/Monetico.php | 145 ++++++++++- src/Payment/Payment.php | 273 +++++++++++++-------- src/Payment/Response.php | 264 +++++++++++++------- src/Recovery/Recovery.php | 193 +++++++++++++++ src/Recovery/Response.php | 167 +++++++++++++ src/Refund/Refund.php | 157 ++++++++++++ src/Refund/Response.php | 77 ++++++ src/Resources/AddressBilling.php | 76 ++++++ src/Resources/AddressShipping.php | 76 ++++++ src/Resources/Authentication.php | 185 ++++++++++++++ src/Resources/Client.php | 130 ++++++++++ tests/AuthenticationResourceTest.php | 231 +++++++++++++++++ tests/CancelResponseTest.php | 24 ++ tests/CancelTest.php | 84 +++++++ tests/Credentials.fake.php | 8 + tests/MoneticoTest.php | 236 ++++++++++++++++-- tests/PaymentResponseTest.php | 130 +++++++--- tests/PaymentTest.php | 143 +++++++++-- tests/RecoveryResponseTest.php | 109 ++++++++ tests/RecoveryTest.php | 162 ++++++++++++ tests/RefundResponseTest.php | 64 +++++ tests/RefundTest.php | 159 ++++++++++++ 38 files changed, 3312 insertions(+), 338 deletions(-) create mode 100644 .coveralls.yml create mode 100644 .scrutinizer.yml create mode 100644 .travis.yml create mode 100644 src/BaseMethod.php create mode 100644 src/Cancel/Cancel.php create mode 100644 src/Cancel/Response.php create mode 100644 src/Exceptions/AuthenticationException.php create mode 100644 src/Exceptions/RecoveryException.php create mode 100644 src/Method.php create mode 100644 src/Recovery/Recovery.php create mode 100644 src/Recovery/Response.php create mode 100644 src/Refund/Refund.php create mode 100644 src/Refund/Response.php create mode 100644 src/Resources/AddressBilling.php create mode 100644 src/Resources/AddressShipping.php create mode 100644 src/Resources/Authentication.php create mode 100644 src/Resources/Client.php create mode 100644 tests/AuthenticationResourceTest.php create mode 100644 tests/CancelResponseTest.php create mode 100644 tests/CancelTest.php create mode 100644 tests/Credentials.fake.php create mode 100644 tests/RecoveryResponseTest.php create mode 100644 tests/RecoveryTest.php create mode 100644 tests/RefundResponseTest.php create mode 100644 tests/RefundTest.php diff --git a/.coveralls.yml b/.coveralls.yml new file mode 100644 index 0000000..834117d --- /dev/null +++ b/.coveralls.yml @@ -0,0 +1,3 @@ +service_name: travis-ci +coverage_clover: build/clover.xml +json_path: build/coveralls.json \ No newline at end of file diff --git a/.gitignore b/.gitignore index 77206d9..f366606 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ composer.phar # composer.lock tests/Credentials.php coverage +build .php_cs.cache \ No newline at end of file diff --git a/.scrutinizer.yml b/.scrutinizer.yml new file mode 100644 index 0000000..06c1a45 --- /dev/null +++ b/.scrutinizer.yml @@ -0,0 +1,23 @@ +filter: + excluded_paths: [tests/*] + +checks: + php: + remove_extra_empty_lines: true + remove_php_closing_tag: true + remove_trailing_whitespace: true + fix_use_statements: + remove_unused: true + preserve_multiple: false + preserve_blanklines: true + order_alphabetically: true + fix_php_opening_tag: true + fix_linefeed: true + fix_line_ending: true + fix_identation_4spaces: true + fix_doc_comments: true + +tools: + external_code_coverage: + timeout: 600 + runs: 4 \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..c35aca2 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,17 @@ +language: php + +php: + - 7.2 + +before_script: + - travis_retry composer self-update + - travis_retry composer update --no-interaction --prefer-source + - composer require --dev scrutinizer/ocular --no-interaction + - composer require --dev php-coveralls/php-coveralls --no-interaction + +script: + - vendor/bin/phpunit + +after_script: + - php vendor/bin/ocular code-coverage:upload --format=php-clover build/clover.xml + - php vendor/bin/php-coveralls \ No newline at end of file diff --git a/ReadMe.md b/ReadMe.md index 4f9ac31..a94538b 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1,21 +1,29 @@ # Monetico PHP SDK -This library aims to facilitate the usage of Monetico Payment Service +[![Latest Version](https://img.shields.io/packagist/v/DansMaCulotte/monetico-php.svg?style=flat-square)](https://packagist.org/packages/dansmaculotte/monetico-php) +[![Total Downloads](https://img.shields.io/packagist/dt/DansMaCulotte/monetico-php.svg?style=flat-square)](https://packagist.org/packages/dansmaculotte/monetico-php) +[![Build Status](https://img.shields.io/travis/DansMaCulotte/monetico-php/master.svg?style=flat-square)](https://travis-ci.org/dansmaculotte/monetico-php) +[![Quality Score](https://img.shields.io/scrutinizer/g/DansMaCulotte/monetico-php.svg?style=flat-square)](https://scrutinizer-ci.com/g/dansmaculotte/monetico-php) +[![Code Coverage](https://img.shields.io/coveralls/github/DansMaCulotte/monetico-php.svg?style=flat-square)](https://coveralls.io/github/dansmaculotte/monetico-php) + +This library aims to facilitate the usage of Monetico Service Methods ## Installation ### Requirements -- PHP 7.0 +- PHP 7.2 You can install the package via composer: -``` bash +```bash composer require dansmaculotte/monetico-php ``` ## Usage +### Monetico + ```php use DansMaCulotte\Monetico\Monetico; @@ -29,8 +37,13 @@ $monetico = new Monetico( ); ``` +### Payment + ```php use DansMaCulotte\Monetico\Payment\Payment; +use DansMaCulotte\Monetico\Resources\AddressBilling; +use DansMaCulotte\Monetico\Resources\AddressShipping; +use DansMaCulotte\Monetico\Resources\Client; $payment = new Payment(array( 'reference' => 'ABCDEF123', @@ -42,6 +55,15 @@ $payment = new Payment(array( 'datetime' => Carbon::create(2019, 1, 1), )); +$addressBilling = new AddressBilling('7 rue melingue', 'Caen', '14000', 'France'); +$payment->setAddressBilling($addressBilling); + +$addressShipping = new AddressShipping('7 rue melingue', 'Caen', '14000', 'France'); +$payment->setAddressShipping($addressShipping); + +$client = new Client('MR', 'John', 'Stark', 'Snow'); +$payment->setClient($client); + $url = $monetico->getPaymentUrl(); $fields = $monetico->getPaymentFields($payment); ``` @@ -59,6 +81,91 @@ $result = $monetico->validateSeal($response); $receipt = new Receipt($result); ``` +### Recovery + +```php +Use DansMaCulotte\Monetico\Recovery\Recovery; +use DansMaCulotte\Monetico\Recovery\Response; + +$recovery = new Recovery([ + 'reference' => 'AXCDEF123', + 'language' => 'FR', + 'amount' => 42.42, + 'amountToRecover' => 0, + 'amountRecovered' => 0, + 'amountLeft' => 42.42, + 'currency' => 'EUR', + 'orderDate' => Carbon::create(2019, 07, 17), + 'dateTime' => Carbon::create(2019, 07, 17), +]); + +$url = $monetico->getRecoveryUrl(); +$fields = $monetico->getRecoveryFields($recovery); + +$client = new GuzzleHttp\Client(); +$data = $client->request('POST', $url, $fields); + +// $data = json_decode($data, true); + +$response = new Response($data); +``` + +### Cancel + +```php +use DansMaCulotte\Monetico\Cancel\Cancel; +use DansMaCulotte\Monetico\Cancel\Response; + +$cancel = new Cancel([ + 'dateTime' => Carbon::create(2019, 2, 1), + 'orderDate' => Carbon::create(2019, 1, 1), + 'reference' => 'ABC123', + 'language' => 'FR', + 'currency' => 'EUR', + 'amount' => 100, + 'amountRecovered' => 0, +]); + +$url = $monetico->getCancelUrl(); +$fields = $monetico->getCancelFields($recovery); + +$client = new GuzzleHttp\Client(); +$data = $client->request('POST', $url, $fields); + +// $data = json_decode($data, true); + +$response = new Response($data); +``` + +### Refund + +```php +use DansMaCulotte\Monetico\Refund\Refund; +use DansMaCulotte\Monetico\Refund\Response; + +$refund = new Refund([ + 'datetime' => Carbon::create(2019, 2, 1), + 'orderDatetime' => Carbon::create(2019, 1, 1), + 'recoveryDatetime' => Carbon::create(2019, 1, 1), + 'authorizationNumber' => '1222', + 'reference' => 'ABC123', + 'language' => 'FR', + 'currency' => 'EUR', + 'amount' => 100, + 'refundAmount' => 50, + 'maxRefundAmount' => 80, +]); + +$url = $monetico->getRefundUrl(); +$fields = $monetico->getRefundFields($recovery); + +$client = new GuzzleHttp\Client(); +$data = $client->request('POST', $url, $fields); + +// $data = json_decode($data, true); + +$response = new Response($data); +``` ## License The MIT License (MIT). Please see [License File](LICENSE.md) for more information. \ No newline at end of file diff --git a/composer.json b/composer.json index 04f73f1..028d0c0 100644 --- a/composer.json +++ b/composer.json @@ -11,6 +11,10 @@ { "name": "Gaƫl Reyrol", "email": "gael@dansmaculotte.fr" + }, + { + "name": "Martin Potel", + "email": "martin@dansmaculotte.fr" } ], "autoload": { @@ -19,7 +23,8 @@ } }, "require": { - "php": "^7.0" + "php": "^7.2", + "ext-json": "*" }, "require-dev": { "phpunit/phpunit": "^8.2", diff --git a/composer.lock b/composer.lock index 79cf3af..6bd5d31 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "c236107aa8570b373bbabd1bf91ce2ab", + "content-hash": "b61103ea8dca975dae4f973d7ed7aa43", "packages": [], "packages-dev": [ { @@ -3059,7 +3059,8 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": "^7.0" + "php": "^7.2", + "ext-json": "*" }, "platform-dev": [] } diff --git a/phpunit.xml b/phpunit.xml index 9cc0032..277bf9c 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,10 +1,14 @@ @@ -16,4 +20,9 @@ src/ + + + + + \ No newline at end of file diff --git a/src/BaseMethod.php b/src/BaseMethod.php new file mode 100644 index 0000000..5d5c775 --- /dev/null +++ b/src/BaseMethod.php @@ -0,0 +1,28 @@ + $seal] + ); + } +} diff --git a/src/Cancel/Cancel.php b/src/Cancel/Cancel.php new file mode 100644 index 0000000..8f5868d --- /dev/null +++ b/src/Cancel/Cancel.php @@ -0,0 +1,39 @@ +dateTime instanceof DateTime) { + throw Exception::invalidDatetime(); + } + + if (!$this->orderDate instanceof DateTime) { + throw Exception::invalidOrderDate(); + } + + if (strlen($this->reference) > 12) { + throw Exception::invalidReference($this->reference); + } + + if (strlen($this->language) != 2) { + throw Exception::invalidLanguage($this->language); + } + } +} diff --git a/src/Cancel/Response.php b/src/Cancel/Response.php new file mode 100644 index 0000000..18ae7bb --- /dev/null +++ b/src/Cancel/Response.php @@ -0,0 +1,9 @@ +_debug || $debug) { + $mainServiceUrl .= '/test'; + } + + return $mainServiceUrl . '/' . self::RECOVERY_URI; + } + + /** + * Return recovery url required to redirect on bank interface + * + * @param bool $debug + * + * @return string + */ + public function getRefundUrl($debug = false) + { + $mainServiceUrl = self::MAIN_SERVICE_URL; + if ($this->_debug || $debug) { + $mainServiceUrl .= '/test'; + } + + return $mainServiceUrl . '/' . self::REFUND_URI; + } + + /** + * Return recovery url required to redirect on bank interface + * + * @param bool $debug + * + * @return string + */ + public function getCancelUrl($debug = false) + { + return $this->getRecoveryUrl($debug); + } + /** * Return array fields required on bank interface * @@ -118,21 +167,105 @@ public function getPaymentUrl($debug = false) */ public function getPaymentFields(Payment $input) { - $seal = $input->generateSeal( + $fields = $input->fieldsToArray( $this->_eptCode, + self::SERVICE_VERSION, + $this->_companyCode, + $this->_returnUrl, + $this->_successUrl, + $this->_errorUrl + ); + + $seal = $input->generateSeal( $this->_securityKey, + $fields + ); + + $fields = $input->generateFields( + $seal, + $fields + ); + + return $fields; + } + + /** + * Return array fields required on bank interface + * + * @param Recovery $input + * + * @return array + */ + public function getRecoveryFields(Recovery $input) + { + $fields = $input->fieldsToArray( + $this->_eptCode, self::SERVICE_VERSION, $this->_companyCode ); + $seal = $input->generateSeal( + $this->_securityKey, + $fields + ); + $fields = $input->generateFields( + $seal, + $fields + ); + + return $fields; + } + + /** + * Return array fields required on cancel bank interface + * + * @param Cancel $input + * @return array + */ + public function getCancelFields(Cancel $input) + { + $fields = $input->fieldsToArray( $this->_eptCode, + self::SERVICE_VERSION, + $this->_companyCode + ); + + $seal = $input->generateSeal( + $this->_securityKey, + $fields + ); + + $fields = $input->generateFields( $seal, + $fields + ); + + return $fields; + } + + /** + * Return array fields required on refund bank interface + * + * @param Refund $input + * @return array + */ + public function getRefundFields(Refund $input) + { + $fields = $input->fieldsToArray( + $this->_eptCode, self::SERVICE_VERSION, - $this->_companyCode, - $this->_returnUrl, - $this->_successUrl, - $this->_errorUrl + $this->_companyCode + ); + + $seal = $input->generateSeal( + $this->_securityKey, + $fields + ); + + $fields = $input->generateFields( + $seal, + $fields ); return $fields; diff --git a/src/Payment/Payment.php b/src/Payment/Payment.php index 3812e8c..a80eaee 100644 --- a/src/Payment/Payment.php +++ b/src/Payment/Payment.php @@ -2,10 +2,19 @@ namespace DansMaCulotte\Monetico\Payment; +use DansMaCulotte\Monetico\BaseMethod; +use DansMaCulotte\Monetico\Exceptions\Exception; use DansMaCulotte\Monetico\Exceptions\PaymentException; +use DansMaCulotte\Monetico\Method; +use DansMaCulotte\Monetico\Resources\AddressBilling; +use DansMaCulotte\Monetico\Resources\AddressShipping; +use DansMaCulotte\Monetico\Resources\Client; +use DateTime; -class Payment +class Payment implements Method { + use BaseMethod; + /** @var string */ public $reference; @@ -25,16 +34,25 @@ class Payment public $currency; /** @var \DateTime */ - public $datetime; + public $dateTime; /** @var array */ public $options; + /** @var AddressBilling */ + public $addressBilling; + + /** @var AddressShipping */ + public $addressShipping; + + /** @var Client */ + public $client; + /** @var array */ public $commitments; - /** @var string */ - const FORMAT_OUTPUT = '%s*%s*%s*%s*%s*%s*%s*%s*%s*%s*%s*%s*%s*%s*%s*%s*%s*%s*%s'; + /** @var int */ + const MAC_COMMITMENTS = 4; /** @var array */ const PAYMENT_WAYS = [ @@ -45,39 +63,59 @@ class Payment 'paypal' ]; + /** @var array */ + const THREE_D_SECURE_CHALLENGES = [ + 'no_preference', + 'challenge_preferred', + 'challenge_mandated', + 'no_challenge_requested', + 'no_challenge_requested_strong_authentication', + 'no_challenge_requested_trusted_third_party', + 'no_challenge_requested_risk_analysis' + ]; + + /** @var string */ + const DATETIME_FORMAT = 'd/m/Y:H:i:s'; + /** * InputPayload constructor. * * @param array $data * @param array $commitments * @param array $options - * - * @throws PaymentException + * @throws Exception */ public function __construct($data = [], $commitments = [], $options = []) { $this->reference = $data['reference']; - if (strlen($this->reference) > 12) { - throw PaymentException::invalidReference($this->reference); - } - $this->language = $data['language']; - if (strlen($this->language) != 2) { - throw PaymentException::invalidLanguage($this->language); - } - - $this->datetime = $data['datetime']; - if (!is_a($this->datetime, 'DateTime')) { - throw PaymentException::invalidDatetime(); - } - + $this->dateTime = $data['dateTime']; $this->description = $data['description']; $this->email = $data['email']; $this->amount = $data['amount']; $this->currency = $data['currency']; - $this->options = $options; $this->commitments = $commitments; + + $this->validate(); + } + + /** + * @throws Exception + */ + public function validate() + { + if (strlen($this->reference) > 12) { + throw Exception::invalidReference($this->reference); + } + + if (strlen($this->language) != 2) { + throw Exception::invalidLanguage($this->language); + } + + if (!$this->dateTime instanceof DateTime) { + throw Exception::invalidDatetime(); + } } /** @@ -110,6 +148,22 @@ public function setDisable3DS($value = true) $this->options['3dsdebrayable'] = ($value) ? '1' : '0'; } + /** + * 3DSecure V2 Choice + * + * @param bool $choice + * @throws PaymentException + */ + public function setThreeDSecureChallenge($choice) + { + if (!in_array($choice, self::THREE_D_SECURE_CHALLENGES)) { + throw PaymentException::invalidThreeDSecureChallenge($choice); + } + + $this->options['threeDsecureChallenge'] = $choice; + } + + /** * Change company sign label on payment interface * @@ -120,6 +174,23 @@ public function setSignLabel($label) $this->options['libelleMonetique'] = $label; } + + public function setAddressBilling(AddressBilling $addressBilling) + { + $this->addressBilling = $addressBilling; + } + + + public function setAddressShipping(AddressShipping $addressShipping) + { + $this->addressShipping = $addressShipping; + } + + public function setClient(Client $client) + { + $this->client = $client; + } + /** * Disable ways of payment on payment interface * @@ -139,109 +210,99 @@ public function setDisabledPaymentWays($ways = []) } /** - * Generate seal to prepare payment - * - * @param string $eptCode - * @param string $securityKey - * @param string $version - * @param string $companyCode + * Get order context * * @return string */ - public function generateSeal($eptCode, $securityKey, $version, $companyCode) - { - $commitments = $this->commitments; - $commitmentsCount = count($commitments); - - $output = sprintf( - self::FORMAT_OUTPUT, - $eptCode, - $this->datetime->format('d/m/Y:H:i:s'), - $this->amount . $this->currency, - $this->reference, - $this->description, - $version, - $this->language, - $companyCode, - $this->email, - ($commitmentsCount > 0) ? $commitmentsCount : '', - ($commitmentsCount >= 1) ? $commitments[0]['date'] : '', - ($commitmentsCount >= 1) ? $commitments[0]['amount'] : '', - ($commitmentsCount >= 2) ? $commitments[1]['date'] : '', - ($commitmentsCount >= 2) ? $commitments[1]['amount'] : '', - ($commitmentsCount >= 3) ? $commitments[2]['date'] : '', - ($commitmentsCount >= 3) ? $commitments[2]['amount'] : '', - ($commitmentsCount >= 4) ? $commitments[3]['date'] : '', - ($commitmentsCount >= 4) ? $commitments[3]['amount'] : '', - http_build_query($this->options) - ); + public function orderContextBase64() + { + $contextCommand = [ + 'billing' => (isset($this->addressBilling)) ? $this->addressBilling->data : [], + 'shipping' => (isset($this->addressShipping)) ? $this->addressShipping->data : [], + 'client' => (isset($this->client)) ? $this->client->data : [], + ]; - return strtolower( - hash_hmac( - 'sha1', - $output, - $securityKey - ) - ); + return base64_encode(json_encode($contextCommand)); } /** - * @param string $eptCode - * @param string $seal - * @param string $version - * @param string $companyCode - * @param string $returnUrl - * @param string $successUrl - * @param string $errorUrl - * + * @param $eptCode + * @param $companyCode + * @param $version * @return array */ - public function generateFields($eptCode, $seal, $version, $companyCode, $returnUrl, $successUrl, $errorUrl) + private function baseFields($eptCode, $companyCode, $version) { - $commitmentsCount = count($this->commitments); - $_submitCommitments = []; + return [ + 'TPE' => $eptCode, + 'date' => $this->dateTime->format(self::DATETIME_FORMAT), + 'contexte_commande' => $this->orderContextBase64(), + 'lgue' => $this->language, + 'mail' => $this->email, + 'montant' => $this->amount . $this->currency, + 'reference' => $this->reference, + 'societe' => $companyCode, + 'texte-libre' => $this->description, + 'version' => $version + ]; + } - if ($commitmentsCount > 0) { - $_submitCommitments['nbrech'] = $commitmentsCount; - if ($commitmentsCount >= 1) { - $_submitCommitments['dateech1'] = $this->commitments[0]['date']; - $_submitCommitments['montantech1'] = $this->commitments[0]['amount']; - } - - if ($commitmentsCount >= 2) { - $_submitCommitments['dateech2'] = $this->commitments[1]['date']; - $_submitCommitments['montantech2'] = $this->commitments[1]['amount']; - } + /** + * @param $returnUrl + * @param $successUrl + * @param $errorUrl + * @return array + */ + private function urlFields($returnUrl, $successUrl, $errorUrl) + { + return [ + 'url_retour' => $returnUrl, + 'url_retour_ok' => $successUrl . '?reference=' . $this->reference, + 'url_retour_err' => $errorUrl . '?reference=' . $this->reference, + ]; + } - if ($commitmentsCount >= 3) { - $_submitCommitments['dateech3'] = $this->commitments[2]['date']; - $_submitCommitments['montantech3'] = $this->commitments[2]['amount']; - } + /** + * @return array + */ + private function commitmentsFields() + { + $commitmentsCount = count($this->commitments); + $commitments = [ + 'nbrech' => ($commitmentsCount > 0) ? $commitmentsCount : '' + ]; - if ($commitmentsCount >= 4) { - $_submitCommitments['dateech4'] = $this->commitments[3]['date']; - $_submitCommitments['montantech4'] = $this->commitments[3]['amount']; - } + for ($i = 1; $i <= self::MAC_COMMITMENTS; $i++) { + $commitments["dateech${i}"] = ($commitmentsCount >= $i) ? $this->commitments[$i - 1]['date'] : ''; + $commitments["montantech${i}"] = ($commitmentsCount >= $i) ? $this->commitments[$i - 1]['amount'] . $this->currency : ''; } + return $commitments; + } + + /** + * @return array + */ + private function optionsFields() + { + return [ + 'ThreeDSecureChallenge' => (isset($this->options['threeDsecureChallenge'])) ? $this->options['threeDsecureChallenge'] : '', + '3dsdebrayable' => (isset($this->options['3dsdebrayable'])) ? $this->options['3dsdebrayable'] : '', + 'aliascb' => (isset($this->options['aliascb'])) ? $this->options['aliascb'] : '', + 'desactivemoyenpaiement' => (isset($this->options['desactivemoyenpaiement'])) ? $this->options['desactivemoyenpaiement'] : '', + 'forcesaisiecb' => (isset($this->options['forcesaisiecb'])) ? $this->options['forcesaisiecb'] : '', + 'libelleMonetique' => (isset($this->options['libelleMonetique'])) ? $this->options['libelleMonetique'] : '', + ]; + } + + public function fieldsToArray($eptCode, $version, $companyCode, $returnUrl, $successUrl, $errorUrl) + { return array_merge( - [ - 'version' => $version, - 'TPE' => $eptCode, - 'date' => $this->datetime->format('d/m/Y:H:i:s'), - 'montant' => $this->amount . $this->currency, - 'reference' => $this->reference, - 'MAC' => $seal, - 'url_retour' => $returnUrl, - 'url_retour_ok' => $successUrl . '?reference=' . $this->reference, - 'url_retour_err' => $errorUrl . '?reference=' . $this->reference, - 'lgue' => $this->language, - 'societe' => $companyCode, - 'texte-libre' => $this->description, - 'mail' => $this->email, - ], - $_submitCommitments + $this->baseFields($eptCode, $companyCode, $version), + $this->optionsFields(), + $this->commitmentsFields(), + $this->urlFields($returnUrl, $successUrl, $errorUrl) ); } } diff --git a/src/Payment/Response.php b/src/Payment/Response.php index 4e6df52..6281cbc 100644 --- a/src/Payment/Response.php +++ b/src/Payment/Response.php @@ -2,13 +2,18 @@ namespace DansMaCulotte\Monetico\Payment; +use DansMaCulotte\Monetico\Exceptions\Exception; use DansMaCulotte\Monetico\Exceptions\PaymentException; +use DansMaCulotte\Monetico\Resources\Authentication; use DateTime; class Response { + /** @var string */ + private $eptCode; + /** @var \DateTime */ - public $datetime; + public $dateTime; /** @var string */ public $amount; @@ -85,8 +90,11 @@ class Response /** @var string */ public $filteredStatus = null; + /** @var Authentication */ + public $authentication = null; + /** @var string */ - const FORMAT_OUTPUT = '%s*%s*%s*%s*%s*%s*%s*%s*%s*%s*%s*%s*%s*%s*%s*%s*%s*%s*%s*%s*'; + public $authenticationHash = null; /** @var string */ const DATETIME_FORMAT = 'd/m/Y_\a_H:i:s'; @@ -119,10 +127,7 @@ class Response 'na' => 'Non disponible', ]; - /** @var array */ - const DDDS_STATUSES = [ - -1, 1, 4, - ]; + /** @var array */ const REJECT_REASONS = [ @@ -158,95 +163,115 @@ class Response */ public function __construct($data = []) { - $requiredKeys = [ - 'date', - 'amount', - 'reference', - 'MAC', - 'texte-libre', - 'code-retour', - 'cvx', - 'vld', - 'brand', - 'status3ds', - 'numauto', - 'originecb', - 'bincb', - 'hpancb', - 'ipclient', - 'originetr', - 'veres', - 'pares', - ]; + $this->validateRequiredKeys($data); - foreach ($requiredKeys as $key) { - if (!in_array($key, array_keys($data))) { - throw PaymentException::missingResponseKey($key); - } + $this->dateTime = DateTime::createFromFormat(self::DATETIME_FORMAT, $data['date']); + if (!$this->dateTime instanceof DateTime) { + throw Exception::invalidResponseDateTime(); } - $this->datetime = DateTime::createFromFormat(self::DATETIME_FORMAT, $data['date']); - if (!is_a($this->datetime, 'DateTime')) { - throw PaymentException::invalidDatetime(); - } + $this->eptCode = $data['TPE']; // ToDo: Split amount and currency with ISO4217 - $this->amount = $data['amount']; - - + $this->amount = $data['montant']; $this->reference = $data['reference']; $this->seal = $data['MAC']; $this->description = $data['texte-libre']; + $this->authenticationHash = $data['authentification']; $this->returnCode = $data['code-retour']; if (!in_array($this->returnCode, self::RETURN_CODES)) { - throw PaymentException::invalidReturnCode($this->returnCode); + throw PaymentException::invalidResponseReturnCode($this->returnCode); } $this->cardVerificationStatus = $data['cvx']; if (!in_array($this->cardVerificationStatus, self::CARD_VERIFICATION_STATUSES)) { - throw PaymentException::invalidCardVerificationStatus($this->cardVerificationStatus); + throw PaymentException::invalidResponseCardVerificationStatus($this->cardVerificationStatus); } $this->cardExpirationDate = $data['vld']; $this->cardBrand = $data['brand']; if (!in_array($this->cardBrand, array_keys(self::CARD_BRANDS))) { - throw PaymentException::invalidCardBrand($this->cardBrand); - } - - $this->DDDSStatus = (int) $data['status3ds']; - if (!in_array($this->DDDSStatus, self::DDDS_STATUSES)) { - throw PaymentException::invalidDDDSStatus($this->DDDSStatus); + throw PaymentException::invalidResponseCardBrand($this->cardBrand); } - if (isset($data['motifrefus'])) { - $this->rejectReason = $data['motifrefus']; - if (!in_array($this->rejectReason, self::REJECT_REASONS)) { - throw PaymentException::invalidRejectReason($this->rejectReason); - } - } - - $this->authNumber = $data['numauto']; - // ToDo: Check Country $this->cardCountry = $data['originecb']; - + $this->authNumber = $data['numauto']; $this->cardBIN = $data['bincb']; $this->cardHash = $data['hpancb']; - $this->clientIp = $data['ipclient']; // ToDo: Check Country $this->transactionCountry = $data['originetr']; - $this->veresStatus = $data['veres']; - $this->paresStatus = $data['pares']; + $this->setAuthentication($data['authentification']); + $this->setOptions($data); + $this->setErrorsOptions($data); + } + + + /** + * @param $data + * @throws Exception + */ + private function validateRequiredKeys($data) + { + $requiredKeys = [ + 'TPE', + 'date', + 'montant', + 'reference', + 'MAC', + 'authentification', + 'texte-libre', + 'code-retour', + 'cvx', + 'vld', + 'brand', + 'numauto', + 'originecb', + 'bincb', + 'hpancb', + 'ipclient', + 'originetr', + ]; + + foreach ($requiredKeys as $key) { + if (!in_array($key, array_keys($data))) { + throw Exception::missingResponseKey($key); + } + } + } + + /** + * @param $authentication + * @throws \DansMaCulotte\Monetico\Exceptions\AuthenticationException + */ + private function setAuthentication($authentication) + { + $authentication = base64_decode($authentication); + $authentication = json_decode($authentication); + + $this->authentication = new Authentication( + $authentication->protocol, + $authentication->status, + $authentication->version, + (array) $authentication->details + ); + } + /** + * @param $data + * @throws PaymentException + */ + private function setOptions($data) + { if (isset($data['modepaiement'])) { $this->paymentMethod = $data['modepaiement']; if (!in_array($this->paymentMethod, self::PAYMENT_METHODS)) { - throw PaymentException::invalidPaymentMethod($this->paymentMethod); + throw PaymentException::invalidResponsePaymentMethod($this->paymentMethod); } } @@ -255,10 +280,32 @@ public function __construct($data = []) $this->commitmentAmount = $data['montantech']; } + if (isset($data['cbenregistree'])) { + $this->cardBookmarked = (bool) $data['cbenregistree']; + } + + if (isset($data['cbmasquee'])) { + $this->cardMask = $data['cbmasquee']; + } + } + + /** + * @param $data + * @throws PaymentException + */ + private function setErrorsOptions($data) + { if (isset($data['filtragecause'])) { $this->filteredReason = (int) $data['filtragecause']; if (!in_array($this->filteredReason, self::FILTERED_REASONS)) { - throw PaymentException::invalidFilteredReason($this->filteredReason); + throw PaymentException::invalidResponseFilteredReason($this->filteredReason); + } + } + + if (isset($data['motifrefus'])) { + $this->rejectReason = $data['motifrefus']; + if (!in_array($this->rejectReason, self::REJECT_REASONS)) { + throw PaymentException::invalidResponseRejectReason($this->rejectReason); } } @@ -269,14 +316,60 @@ public function __construct($data = []) if (isset($data['filtrage_etat'])) { $this->filteredStatus = $data['filtrage_etat']; } + } - if (isset($data['cbenregistree'])) { - $this->cardBookmarked = (bool) $data['cbenregistree']; + private function fieldsToArray($eptCode) + { + $fields = [ + 'TPE' => $eptCode, + 'authentification' => $this->authenticationHash, + 'bincb' => $this->cardBIN, + 'brand' => $this->cardBrand, + 'code-retour' => $this->returnCode, + 'cvx' => $this->cardVerificationStatus, + 'date' => $this->dateTime->format(self::DATETIME_FORMAT), + 'hpancb' => $this->cardHash, + 'ipclient' => $this->clientIp, + 'modepaiement' => $this->paymentMethod, + 'montant' => $this->amount, + 'numauto' => $this->authNumber, + 'originecb' => $this->cardCountry, + 'originetr' => $this->transactionCountry, + 'reference' => $this->reference, + 'texte-libre' => $this->description, + 'vld' => $this->cardExpirationDate, + ]; + + if (isset($this->rejectReason)) { + $fields['motifrefus'] = $this->rejectReason; } - if (isset($data['cbmasquee'])) { - $this->cardMask = $data['cbmasquee']; + + if (isset($this->commitmentAmount)) { + $fields['montantech'] = $this->commitmentAmount; } + + if (isset($this->filteredReason)) { + $fields['filtragecause'] = $this->filteredReason; + } + + if (isset($this->filteredValue)) { + $fields['filtragevaleur'] = $this->filteredValue; + } + + if (isset($this->filteredStatus)) { + $fields['filtrage_etat'] = $this->filteredStatus; + } + + if (isset($this->cardBookmarked)) { + $fields['cbenregistree'] = $this->cardBookmarked; + } + + if (isset($this->cardMask)) { + $fields['cbmasquee'] = $this->cardMask; + } + + return $fields; } /** @@ -290,38 +383,19 @@ public function __construct($data = []) */ public function validateSeal($eptCode, $securityKey, $version) { - $output = sprintf( - self::FORMAT_OUTPUT, - $eptCode, - $this->datetime->format(self::DATETIME_FORMAT), - $this->amount, - $this->reference, - $this->description, - $version, - $this->returnCode, - $this->cardVerificationStatus, - $this->cardExpirationDate, - $this->cardBrand, - $this->DDDSStatus, - $this->authNumber, - $this->rejectReason, - $this->cardCountry, - $this->cardBIN, - $this->cardHash, - $this->clientIp, - $this->transactionCountry, - $this->veresStatus, - $this->paresStatus - ); + $fields = $this->fieldsToArray($eptCode); - $hash = strtolower( - hash_hmac( - 'sha1', - $output, - $securityKey - ) - ); + ksort($fields); + + $query = http_build_query($fields, null, '*'); + $query = urldecode($query); + + $hash = strtoupper(hash_hmac( + 'sha1', + $query, + $securityKey + )); - return $hash == strtolower($this->seal); + return $hash == $this->seal; } } diff --git a/src/Recovery/Recovery.php b/src/Recovery/Recovery.php new file mode 100644 index 0000000..d581d3d --- /dev/null +++ b/src/Recovery/Recovery.php @@ -0,0 +1,193 @@ +dateTime = $data['dateTime']; + + $this->orderDate = $data['orderDate']; + + $this->reference = $data['reference']; + + $this->language = $data['language']; + + $this->currency = $data['currency']; + + $this->amount = $data['amount']; + $this->amountToRecover = $data['amountToRecover']; + $this->amountRecovered = $data['amountRecovered']; + $this->amountLeft = $data['amountLeft']; + + $this->validate(); + } + + /** + * @throws Exception + * @throws RecoveryException + */ + public function validate() + { + if (!$this->dateTime instanceof DateTime) { + throw Exception::invalidDatetime(); + } + + if (!$this->orderDate instanceof DateTime) { + throw Exception::invalidOrderDate(); + } + + if (strlen($this->reference) > 12) { + throw Exception::invalidReference($this->reference); + } + + if (strlen($this->language) != 2) { + throw Exception::invalidLanguage($this->language); + } + + if ($this->amountLeft + $this->amountRecovered + $this->amountToRecover !== $this->amount) { + throw RecoveryException::invalidAmounts($this->amount, $this->amountToRecover, $this->amountRecovered, $this->amountLeft); + } + } + + /** + * @param bool $value + */ + public function setStopRecurrence($value = true) + { + $this->stopRecurrence = ($value) ? 'oui' : '0'; + } + + /** + * @param $value + */ + public function setFileNumber($value) + { + $this->fileNumber = $value; + } + + /** + * @param string $invoiceType + * + * @throws Exception + */ + public function setInvoiceType(string $invoiceType) + { + if (!in_array($invoiceType, self::INVOICE_TYPES)) { + throw Exception::invalidInvoiceType($invoiceType); + } + $this->invoiceType = $invoiceType; + } + + /** + * @param bool $value + */ + public function setPhone(bool $value = true) + { + $this->phone = ($value) ? 'oui' : '0'; + } + + + public function fieldsToArray($eptCode, $version, $companyCode) + { + $fields = array_merge([ + 'TPE' => $eptCode, + 'date' => $this->dateTime->format(self::DATETIME_FORMAT), + 'date_commande' => $this->orderDate->format(self::DATE_FORMAT), + 'lgue' => $this->language, + 'montant' => $this->amount . $this->currency, + 'montant_a_capturer' => $this->amountToRecover . $this->currency, + 'montant_deja_capture' => $this->amountRecovered . $this->currency, + 'montant_restant' => $this->amountLeft . $this->currency, + 'reference' => $this->reference, + 'societe' => $companyCode, + 'version' => $version + ]); + + if (isset($this->stopRecurrence)) { + $fields['stoprecurrence'] = $this->stopRecurrence; + } + + if (isset($this->fileNumber)) { + $fields['numero_dossier'] = $this->fileNumber; + } + + if (isset($this->invoiceType)) { + $fields['facture'] = $this->invoiceType; + } + + if (isset($this->phone)) { + $fields['phonie'] = $this->phone; + } + + return $fields; + } +} diff --git a/src/Recovery/Response.php b/src/Recovery/Response.php new file mode 100644 index 0000000..4a15a81 --- /dev/null +++ b/src/Recovery/Response.php @@ -0,0 +1,167 @@ +validateRequiredKeys($data); + + $this->version = self::SERVICE_VERSION; + + $this->returnCode = $data['cdr']; + $this->description = $data['lib']; + $this->reference = $data['reference']; + + $this->setOptions($data); + $this->setDates($data); + $this->setAmounts($data); + } + + + /** + * @param $data + * @throws Exception + */ + private function validateRequiredKeys($data) + { + $requiredKeys = [ + 'cdr', + 'lib', + 'reference', + ]; + + foreach ($requiredKeys as $key) { + if (!in_array($key, array_keys($data))) { + throw Exception::missingResponseKey($key); + } + } + } + + /** + * @param $data + * @throws RecoveryException + */ + private function setDates($data) + { + if (isset($data['date_autorisation'])) { + $this->authorisationDate = DateTime::createFromFormat(self::DATE_FORMAT, $data['date_autorisation']); + if (!$this->authorisationDate) { + throw RecoveryException::invalidResponseAuthorizationDate(); + } + } + + if (isset($data['date_debit'])) { + $this->debitDate = DateTime::createFromFormat(self::DATE_FORMAT, $data['date_debit']); + if (!$this->authorisationDate) { + throw RecoveryException::invalidResponseDebitDate(); + } + } + } + + /** + * @param $data + * @throws Exception + */ + public function setOptions($data) + { + if (isset($data['aut'])) { + $this->authorisationNumber = $data['aut']; + } + + if (isset($data['numero_dossier'])) { + $this->fileNumber = $data['numero_dossier']; + if (strlen($this->fileNumber) > 12) { + throw Exception::invalidResponseFileNumber($this->fileNumber); + } + } + + if (isset($data['type_facture'])) { + $this->invoiceType = $data['type_facture']; + if (!in_array($this->invoiceType, self::INVOICE_TYPES)) { + throw Exception::invalidResponseInvoiceType($this->invoiceType); + } + } + + if (isset($data['phonie'])) { + $this->phone = $data['phonie']; + } + } + + /** + * @param $data + */ + private function setAmounts($data) + { + if (isset($data['montant_estime'])) { + $this->estimatedAmount = $data['montant_estime']; + } + if (isset($data['montant_debite'])) { + $this->amountDebited = $data['montant_debite']; + } + } +} diff --git a/src/Refund/Refund.php b/src/Refund/Refund.php new file mode 100644 index 0000000..1de5533 --- /dev/null +++ b/src/Refund/Refund.php @@ -0,0 +1,157 @@ +datetime = $data['datetime']; + $this->orderDate = $data['orderDatetime']; + $this->recoveryDate = $data['recoveryDatetime']; + $this->authorizationNumber = $data['authorizationNumber']; + $this->currency = $data['currency']; + $this->amount = $data['amount']; + $this->refundAmount = $data['refundAmount']; + $this->maxRefundAmount = $data['maxRefundAmount']; + $this->reference = $data['reference']; + $this->language = $data['language']; + + $this->validate(); + } + + /** + * @param $value + */ + public function setFileNumber($value) + { + $this->fileNumber = $value; + } + + + /** + * @param string $invoiceType + * @throws Exception + */ + public function setInvoiceType(string $invoiceType) + { + if (!in_array($invoiceType, self::INVOICE_TYPES)) { + throw Exception::invalidInvoiceType($invoiceType); + } + $this->invoiceType = $invoiceType; + } + + public function fieldsToArray($eptCode, $version, $companyCode) + { + $fields = array_merge([ + 'TPE' => $eptCode, + 'date' => $this->datetime->format(self::DATETIME_FORMAT), + 'date_commande' => $this->orderDate->format(self::DATE_FORMAT), + 'date_remise' => $this->recoveryDate->format(self::DATE_FORMAT), + 'num_autorisation' => $this->authorizationNumber, + 'montant' => $this->amount . $this->currency, + 'montant_recredit' => $this->refundAmount . $this->currency, + 'montant_possible' => $this->maxRefundAmount . $this->currency, + 'reference' => $this->reference, + 'lgue' => $this->language, + 'societe' => $companyCode, + 'version' => $version + ]); + + if (isset($this->fileNumber)) { + $fields['numero_dossier'] = $this->fileNumber; + } + + if (isset($this->invoiceType)) { + $fields['facture'] = $this->invoiceType; + } + + return $fields; + } + + /** + * @throws Exception + */ + public function validate() + { + if (!$this->datetime instanceof DateTime) { + throw Exception::invalidDatetime(); + } + + if (!$this->orderDate instanceof DateTime) { + throw Exception::invalidOrderDate(); + } + + if (!$this->recoveryDate instanceof DateTime) { + throw Exception::invalidRecoveryDate(); + } + + if (strlen($this->reference) > 12) { + throw Exception::invalidReference($this->reference); + } + + if (strlen($this->language) != 2) { + throw Exception::invalidLanguage($this->language); + } + } +} diff --git a/src/Refund/Response.php b/src/Refund/Response.php new file mode 100644 index 0000000..70c9fde --- /dev/null +++ b/src/Refund/Response.php @@ -0,0 +1,77 @@ +version = self::SERVICE_VERSION; + + $requiredKeys = [ + 'cdr', + 'lib', + 'reference', + ]; + + foreach ($requiredKeys as $key) { + if (!in_array($key, array_keys($data))) { + throw Exception::missingResponseKey($key); + } + } + + $this->returnCode = $data['cdr']; + $this->description = $data['lib']; + $this->reference = $data['reference']; + + if (isset($data['numero_dossier'])) { + $this->fileNumber = $data['numero_dossier']; + if (strlen($this->fileNumber) > 12) { + throw Exception::invalidResponseFileNumber($this->fileNumber); + } + } + + if (isset($data['type_facture'])) { + $this->invoiceType = $data['type_facture']; + if (!in_array($this->invoiceType, self::INVOICE_TYPES)) { + throw Exception::invalidResponseInvoiceType($this->invoiceType); + } + } + } +} diff --git a/src/Resources/AddressBilling.php b/src/Resources/AddressBilling.php new file mode 100644 index 0000000..d02bf2f --- /dev/null +++ b/src/Resources/AddressBilling.php @@ -0,0 +1,76 @@ +data = [ + 'addressLine1' => $addressLine1, + 'city' => $city, + 'postalCode' => $postalCode, + 'country' => $country, + 'civility' => $civility, + 'name' => $name, + 'firstName' => $firstName, + 'lastName' => $lastName, + 'middleName' => $middleName, + 'address' => $address, + 'addressLine2' => $addressLine2, + 'addressLine3' => $addressLine3, + 'stateOrProvince' => $stateOrProvince, + 'countrySubdivision' => $countrySubdivision, + 'email' => $email, + 'phone' => $phone, + 'mobilePhone' => $mobilePhone, + 'homePhone' => $homePhone, + 'workPhone' => $workPhone, + ]; + } +} diff --git a/src/Resources/AddressShipping.php b/src/Resources/AddressShipping.php new file mode 100644 index 0000000..94118b8 --- /dev/null +++ b/src/Resources/AddressShipping.php @@ -0,0 +1,76 @@ +data = [ + 'addressLine1' => $addressLine1, + 'city' => $city, + 'postalCode' => $postalCode, + 'country' => $country, + 'civility' => $civility, + 'name' => $name, + 'firstName' => $firstName, + 'lastName' => $lastName, + 'address' => $address, + 'addressLine2' => $addressLine2, + 'addressLine3' => $addressLine3, + 'stateOrProvince' => $stateOrProvince, + 'countrySubdivision' => $countrySubdivision, + 'email' => $email, + 'phone' => $phone, + 'mobilePhone' => $shipIndicator, + 'homePhone' => $deliveryTimeframe, + 'workPhone' => $firstUseDate, + 'matchBillingAddress' => $matchBillingAddress, + ]; + } +} diff --git a/src/Resources/Authentication.php b/src/Resources/Authentication.php new file mode 100644 index 0000000..f5ad0f7 --- /dev/null +++ b/src/Resources/Authentication.php @@ -0,0 +1,185 @@ +protocol = $protocol; + if ($this->protocol !== self::PROTOCOL) { + throw AuthenticationException::invalidProtocol($this->protocol); + } + + $this->status = $status; + if (!in_array($this->status, self::STATUSES)) { + throw AuthenticationException::invalidStatus($this->status); + } + + $this->version = $version; + if (!in_array($version, self::VERSIONS)) { + throw AuthenticationException::invalidVersion($this->version); + } + + $this->setDetails($details); + $this->setDetailsMessages($details); + } + + /** + * @param array $details + * @throws AuthenticationException + */ + public function setDetails(array $details) + { + if (isset($details['liabilityShift'])) { + if (!in_array($details['liabilityShift'], self::LIABILITY_SHIFTS)) { + throw AuthenticationException::invalidLiabilityShift($details['liabilityShift']); + } + $this->details['liabilityShift'] = $details['liabilityShift']; + } + + if (isset($details['merchantPreference'])) { + if (!in_array($details['merchantPreference'], self::MERCHANT_PREFERENCES)) { + throw AuthenticationException::invalidMerchantPreference($details['merchantPreference']); + } + $this->details['merchantPreference'] = $details['merchantPreference']; + } + + if (isset($details['transactionID'])) { + $this->details['transactionID'] = $details['transactionID']; + } + if (isset($details['authenticationValue'])) { + $this->details['authenticationValue'] = $details['authenticationValue']; + } + + if (isset($details['status3DS'])) { + if (!in_array($details['status3DS'], self::DDDS_STATUSES)) { + throw AuthenticationException::invalidDDDSStatus($details['status3DS']); + } + $this->details['status3DS'] = $details['status3DS']; + } + + if (isset($details['disablingReason'])) { + if (!in_array($details['disablingReason'], self::DISABLING_REASONS)) { + throw AuthenticationException::invalidDisablingReason($details['disablingReason']); + } + $this->details['disablingReason'] = $details['disablingReason']; + } + } + + /** + * @param $details + * @throws AuthenticationException + */ + private function setDetailsMessages($details) + { + if (isset($details['VERes'])) { + if (!in_array($details['VERes'], self::VERES_OPTIONS)) { + throw AuthenticationException::invalidVERes($details['VERes']); + } + $this->details['VERes'] = $details['VERes']; + } + + if (isset($details['PARes'])) { + if (!in_array($details['PARes'], self::PARES_OPTIONS)) { + throw AuthenticationException::invalidPARes($details['PARes']); + } + $this->details['PARes'] = $details['PARes']; + } + + if (isset($details['ARes'])) { + if (!in_array($details['ARes'], self::ARES_OPTIONS)) { + throw AuthenticationException::invalidARes($details['ARes']); + } + $this->details['ARes'] = $details['ARes']; + } + + if (isset($details['CRes'])) { + if (!in_array($details['CRes'], self::CRES_OPTIONS)) { + throw AuthenticationException::invalidCRes($details['CRes']); + } + $this->details['CRes'] = $details['CRes']; + } + } +} diff --git a/src/Resources/Client.php b/src/Resources/Client.php new file mode 100644 index 0000000..c16c1c9 --- /dev/null +++ b/src/Resources/Client.php @@ -0,0 +1,130 @@ +data = [ + 'civility' => $civility, + 'name' => $name, + 'firstName' => $firstName, + 'lastName' => $lastName, + 'middleName' => $middleName, + 'address' => $address, + 'addressLine1' => $addressLine1, + 'addressLine2' => $addressLine2, + 'addressLine3' => $addressLine3, + 'city' => $city, + 'postalCode' => $postalCode, + 'country' => $country, + 'stateOrProvince' => $stateOrProvince, + 'countrySubdivision' => $countrySubdivision, + 'email' => $email, + 'birthLastName' => $birthLastName, + 'birthCity' => $birthCity, + 'birthPostalCode' => $birthPostalCode, + 'birthCountry' => $birthCountry, + 'birthStateOrProvince' => $birthStateOrProvince, + 'birthCountrySubdivision' => $birthCountrySubdivision, + 'birthdate' => $birthdate, + 'phone' => $phone, + 'nationalIDNumber' => $nationalIDNumber, + 'suspiciousAccountActivity' => $suspiciousAccountActivity, + 'authenticationMethod' => $authenticationMethod, + 'authenticationTimestamp' => $authenticationTimestamp, + 'priorAuthenticationMethod' => $priorAuthenticationMethod, + 'priorAuthenticationTimestamp' => $priorAuthenticationTimestamp, + 'paymentMeanAge' => $paymentMeanAge, + 'lastYearTransactions' => $lastYearTransactions, + 'last24HoursTransactions' => $last24HoursTransactions, + 'addCardNbLast24Hours' => $addCardNbLast24Hours, + 'last6MonthsPurchase' => $last6MonthsPurchase, + 'lastPasswordChange' => $lastPasswordChange, + 'accountAge' => $accountAge, + 'lastAccountModification' => $lastAccountModification, + ]; + } +} diff --git a/tests/AuthenticationResourceTest.php b/tests/AuthenticationResourceTest.php new file mode 100644 index 0000000..7a43133 --- /dev/null +++ b/tests/AuthenticationResourceTest.php @@ -0,0 +1,231 @@ + "Y", + "ARes" => "C", + "CRes" => "Y", + "merchantPreference" => "no_preference", + "transactionID" => "555bd9d9-1cf1-4ba8-b37c-1a96bc8b603a", + "authenticationValue" => "cmJvd0I4SHk3UTRkYkFSQ3FYY3U=", + "disablingReason" => "seuilnonatteint" + ] + ); + $this->assertTrue($authentication instanceof Authentication); + } + + public function testAuthenticationConstructExceptionInvalidProtocol() + { + $this->expectExceptionObject(AuthenticationException::invalidProtocol('invalid')); + + new Authentication( + 'invalid', + 'authenticated', + '2.1.0', + [ + "liabilityShift" => "Y", + "ARes" => "C", + "CRes" => "Y", + "merchantPreference" => "no_preference", + "transactionID" => "555bd9d9-1cf1-4ba8-b37c-1a96bc8b603a", + "authenticationValue" => "cmJvd0I4SHk3UTRkYkFSQ3FYY3U=" + ] + ); + } + + public function testAuthenticationConstructExceptionInvalidStatus() + { + $this->expectExceptionObject(AuthenticationException::invalidStatus('invalid')); + + new Authentication( + '3DSecure', + 'invalid', + '2.1.0', + [ + "liabilityShift" => "Y", + "ARes" => "C", + "CRes" => "Y", + "merchantPreference" => "no_preference", + "transactionID" => "555bd9d9-1cf1-4ba8-b37c-1a96bc8b603a", + "authenticationValue" => "cmJvd0I4SHk3UTRkYkFSQ3FYY3U=" + ] + ); + } + + public function testAuthenticationConstructExceptionInvalidVersion() + { + $this->expectExceptionObject(AuthenticationException::invalidVersion('0.5')); + + new Authentication( + '3DSecure', + 'authenticated', + '0.5', + [ + "liabilityShift" => "Y", + "ARes" => "C", + "CRes" => "Y", + "merchantPreference" => "no_preference", + "transactionID" => "555bd9d9-1cf1-4ba8-b37c-1a96bc8b603a", + "authenticationValue" => "cmJvd0I4SHk3UTRkYkFSQ3FYY3U=" + ] + ); + } + + public function testAuthenticationConstructExceptionInvalidLiabilityShift() + { + $this->expectExceptionObject(AuthenticationException::invalidLiabilityShift('X')); + + new Authentication( + '3DSecure', + 'authenticated', + '2.1.0', + [ + "liabilityShift" => "X", + "ARes" => "C", + "CRes" => "Y", + "merchantPreference" => "no_preference", + "transactionID" => "555bd9d9-1cf1-4ba8-b37c-1a96bc8b603a", + "authenticationValue" => "cmJvd0I4SHk3UTRkYkFSQ3FYY3U=" + ] + ); + } + + public function testAuthenticationConstructExceptionInvalidVERes() + { + $this->expectExceptionObject(AuthenticationException::invalidVERes('X')); + + new Authentication( + '3DSecure', + 'authenticated', + '2.1.0', + [ + "liabilityShift" => "Y", + "ARes" => "C", + "VERes" => "X", + "CRes" => "Y", + "merchantPreference" => "no_preference", + "transactionID" => "555bd9d9-1cf1-4ba8-b37c-1a96bc8b603a", + "authenticationValue" => "cmJvd0I4SHk3UTRkYkFSQ3FYY3U=" + ] + ); + } + + public function testAuthenticationConstructExceptionInvalidPARes() + { + $this->expectExceptionObject(AuthenticationException::invalidPARes('X')); + + new Authentication( + '3DSecure', + 'authenticated', + '2.1.0', + [ + "liabilityShift" => "Y", + "ARes" => "C", + "PARes" => "X", + "CRes" => "Y", + "merchantPreference" => "no_preference", + "transactionID" => "555bd9d9-1cf1-4ba8-b37c-1a96bc8b603a", + "authenticationValue" => "cmJvd0I4SHk3UTRkYkFSQ3FYY3U=" + ] + ); + } + + public function testAuthenticationConstructExceptionInvalidARes() + { + $this->expectExceptionObject(AuthenticationException::invalidARes('X')); + + new Authentication( + '3DSecure', + 'authenticated', + '2.1.0', + [ + "liabilityShift" => "Y", + "ARes" => "X", + "CRes" => "N", + "merchantPreference" => "no_preference", + "transactionID" => "555bd9d9-1cf1-4ba8-b37c-1a96bc8b603a", + "authenticationValue" => "cmJvd0I4SHk3UTRkYkFSQ3FYY3U=" + ] + ); + } + + public function testAuthenticationConstructExceptionInvalidCRes() + { + $this->expectExceptionObject(AuthenticationException::invalidCRes('D')); + + new Authentication( + '3DSecure', + 'authenticated', + '2.1.0', + [ + "liabilityShift" => "Y", + "CRes" => "D", + "merchantPreference" => "no_preference", + "transactionID" => "555bd9d9-1cf1-4ba8-b37c-1a96bc8b603a", + "authenticationValue" => "cmJvd0I4SHk3UTRkYkFSQ3FYY3U=" + ] + ); + } + + public function testAuthenticationConstructExceptionInvalidMerchantPreference() + { + $this->expectExceptionObject(AuthenticationException::invalidMerchantPreference('invalid')); + + new Authentication( + '3DSecure', + 'authenticated', + '2.1.0', + [ + "liabilityShift" => "Y", + "merchantPreference" => "invalid", + "transactionID" => "555bd9d9-1cf1-4ba8-b37c-1a96bc8b603a", + "authenticationValue" => "cmJvd0I4SHk3UTRkYkFSQ3FYY3U=" + ] + ); + } + + public function testAuthenticationConstructExceptionInvalidDDDSStatus() + { + $this->expectExceptionObject(AuthenticationException::invalidDDDSStatus(10)); + + new Authentication( + '3DSecure', + 'authenticated', + '2.1.0', + [ + "liabilityShift" => "Y", + "transactionID" => "555bd9d9-1cf1-4ba8-b37c-1a96bc8b603a", + "authenticationValue" => "cmJvd0I4SHk3UTRkYkFSQ3FYY3U=", + "status3DS" => 10 + ] + ); + } + + public function testAuthenticationConstructExceptionInvalidDisablingReason() + { + $this->expectExceptionObject(AuthenticationException::invalidDisablingReason('invalid')); + + new Authentication( + '3DSecure', + 'authenticated', + '2.1.0', + [ + "liabilityShift" => "Y", + "transactionID" => "555bd9d9-1cf1-4ba8-b37c-1a96bc8b603a", + "authenticationValue" => "cmJvd0I4SHk3UTRkYkFSQ3FYY3U=", + "disablingReason" => 'invalid' + ] + ); + } +} diff --git a/tests/CancelResponseTest.php b/tests/CancelResponseTest.php new file mode 100644 index 0000000..e0e72d5 --- /dev/null +++ b/tests/CancelResponseTest.php @@ -0,0 +1,24 @@ + '1.0', + 'reference' => '000000000145', + 'cdr' => '1', + 'lib' => 'commande annulee', + 'aut' => '123456', + 'montant_estime' => '1.01EUR', + 'date_autorisation' => '2019-05-21', + 'numero_dossier' => '1011', + 'type_facture' => 'preauto', + ]); + + $this->assertTrue($response instanceof Response); + } +} diff --git a/tests/CancelTest.php b/tests/CancelTest.php new file mode 100644 index 0000000..2679a4b --- /dev/null +++ b/tests/CancelTest.php @@ -0,0 +1,84 @@ + Carbon::create(2019, 2, 1), + 'orderDate' => Carbon::create(2019, 1, 1), + 'reference' => 'ABC123', + 'language' => 'FR', + 'currency' => 'EUR', + 'amount' => 100, + 'amountRecovered' => 0, + ]); + + $this->assertTrue($cancel instanceof Cancel); + } + + public function testRecoveryConstructExceptionInvalidDatetime() + { + $this->expectExceptionObject(Exception::invalidDatetime()); + + new Cancel([ + 'dateTime' => 'invalid', + 'orderDate' => Carbon::create(2019, 1, 1), + 'reference' => 'ABC123', + 'language' => 'FR', + 'currency' => 'EUR', + 'amount' => 100, + 'amountRecovered' => 50, + ]); + } + + public function testRecoveryConstructExceptionInvalidOrderDatetime() + { + $this->expectExceptionObject(Exception::invalidOrderDate()); + + new Cancel([ + 'dateTime' => Carbon::create(2019, 1, 1), + 'orderDate' => 'invalid', + 'reference' => 'ABC123', + 'language' => 'FR', + 'currency' => 'EUR', + 'amount' => 100, + 'amountRecovered' => 50, + ]); + } + + public function testRecoveryConstructExceptionInvalidReference() + { + $this->expectExceptionObject(Exception::invalidReference('thisisatoolongreference')); + + new Cancel([ + 'dateTime' => Carbon::create(2019, 2, 1), + 'orderDate' => Carbon::create(2019, 1, 1), + 'reference' => 'thisisatoolongreference', + 'language' => 'FR', + 'currency' => 'EUR', + 'amount' => 100, + 'amountRecovered' => 50, + ]); + } + + public function testRecoveryConstructExceptionInvalidLanguage() + { + $this->expectExceptionObject(Exception::invalidLanguage('English')); + + new Cancel([ + 'dateTime' => Carbon::create(2019, 2, 1), + 'orderDate' => Carbon::create(2019, 1, 1), + 'reference' => 'ABC123', + 'language' => 'English', + 'currency' => 'EUR', + 'amount' => 100, + 'amountRecovered' => 50, + ]); + } +} diff --git a/tests/Credentials.fake.php b/tests/Credentials.fake.php new file mode 100644 index 0000000..5d1aa8a --- /dev/null +++ b/tests/Credentials.fake.php @@ -0,0 +1,8 @@ +assertTrue($url === 'https://p.monetico-services.com/test/paiement.cgi'); } + public function testMoneticoRecoveryUrl() + { + $monetico = new Monetico( + EPT_CODE, + SECURITY_KEY, + COMPANY_CODE, + RETURN_URL, + RETURN_SUCCESS_URL, + RETURN_ERROR_URL + ); + + $url = $monetico->getRecoveryUrl(); + + $this->assertTrue($url === 'https://p.monetico-services.com/capture_paiement.cgi'); + + $url = $monetico->getRecoveryUrl(true); + + $this->assertTrue($url === 'https://p.monetico-services.com/test/capture_paiement.cgi'); + } + + public function testMoneticoRefundUrl() + { + $monetico = new Monetico( + EPT_CODE, + SECURITY_KEY, + COMPANY_CODE, + RETURN_URL, + RETURN_SUCCESS_URL, + RETURN_ERROR_URL + ); + + $url = $monetico->getRefundUrl(); + + $this->assertTrue($url === 'https://p.monetico-services.com/recredit_paiement.cgi'); + + $url = $monetico->getRefundUrl(true); + + $this->assertTrue($url === 'https://p.monetico-services.com/test/recredit_paiement.cgi'); + } + + public function testMoneticoCancelUrl() + { + $monetico = new Monetico( + EPT_CODE, + SECURITY_KEY, + COMPANY_CODE, + RETURN_URL, + RETURN_SUCCESS_URL, + RETURN_ERROR_URL + ); + + $url = $monetico->getCancelUrl(); + + $this->assertTrue($url === 'https://p.monetico-services.com/capture_paiement.cgi'); + + $url = $monetico->getCancelUrl(true); + + $this->assertTrue($url === 'https://p.monetico-services.com/test/capture_paiement.cgi'); + } + public function testMoneticoDebugMode() { $monetico = new Monetico( @@ -103,13 +166,13 @@ public function testMoneticoPaymentFields() ); $payment = new Payment([ - 'reference' => 'ABCDEF123', + 'reference' => 'AYCDEF123', 'description' => 'PHPUnit', 'language' => 'FR', 'email' => 'john@english.fr', 'amount' => 42.42, 'currency' => 'EUR', - 'datetime' => Carbon::create(2019, 1, 1), + 'dateTime' => Carbon::create(2019, 7, 17), ]); $fields = $monetico->getPaymentFields($payment); @@ -143,33 +206,28 @@ public function testMoneticoValidateSeal() $data = [ 'TPE' => EPT_CODE, - 'date' => '01/01/2019_a_08:42:42', - 'amount' => '42.42EUR', - 'reference' => 'ABCDEF123', - 'texte-libre' => 'PHPUnit', - 'version' => '3.0', + 'authentification' => 'ewogICAiZGV0YWlscyIgOiB7CiAgICAgICJQQVJlcyIgOiAiWSIsCiAgICAgICJWRVJlcyIgOiAiWSIsCiAgICAgICJzdGF0dXMzRFMiIDogMQogICB9LAogICAicHJvdG9jb2wiIDogIjNEU2VjdXJlIiwKICAgInN0YXR1cyIgOiAiYXV0aGVudGljYXRlZCIsCiAgICJ2ZXJzaW9uIiA6ICIxLjAuMiIKfQo=', + 'bincb' => '000003', + 'brand' => 'MC', 'code-retour' => 'payetest', 'cvx' => 'oui', - 'vld' => '1219', - 'brand' => 'VI', - 'status3ds' => '4', + 'date' => '11/07/2019_a_10:51:19', + 'hpancb' => '07CDB0331260C06818027855F795C9F726585286', + 'ipclient' => '80.15.24.220', + 'modepaiement' => 'CB', + 'montant' => '42.42EUR', 'numauto' => '000000', - 'motifrefus' => null, 'originecb' => 'FRA', - 'bincb' => '000000', - 'hpancb' => 'NOPE', - 'ipclient' => '127.0.0.1', 'originetr' => 'FRA', - 'veres' => null, - 'pares' => null, + 'reference' => 'D2345677', + 'texte-libre' => 'PHPUnit', + 'vld' => '1219', ]; - $output = vsprintf( - '%s*%s*%s*%s*%s*%s*%s*%s*%s*%s*%s*%s*%s*%s*%s*%s*%s*%s*%s*%s*', - $data - ); + ksort($data); + $output = urldecode(http_build_query($data, null, '*')); - $seal = strtolower( + $seal = strtoupper( hash_hmac( 'sha1', $output, @@ -184,4 +242,138 @@ public function testMoneticoValidateSeal() $isValid = $monetico->validateSeal($response); $this->assertTrue($isValid); } + + public function testMoneticoRecoveryFields() + { + $monetico = new Monetico( + EPT_CODE, + SECURITY_KEY, + COMPANY_CODE, + RETURN_URL, + RETURN_SUCCESS_URL, + RETURN_ERROR_URL + ); + + $recovery = new Recovery([ + 'reference' => 'AXCDEF123', + 'language' => 'FR', + 'amount' => 42.42, + 'amountToRecover' => 0, + 'amountRecovered' => 0, + 'amountLeft' => 42.42, + 'currency' => 'EUR', + 'orderDate' => Carbon::create(2019, 07, 17), + 'dateTime' => Carbon::create(2019, 07, 17), + ]); + + $recovery->setFileNumber('ABC'); + $recovery->setInvoiceType('preauto'); + $recovery->setPhone(); + $recovery->setStopRecurrence(); + + $fields = $monetico->getRecoveryFields($recovery); + + $this->assertIsArray($fields); + $this->assertArrayHasKey('version', $fields); + $this->assertArrayHasKey('TPE', $fields); + $this->assertArrayHasKey('date', $fields); + $this->assertArrayHasKey('date_commande', $fields); + $this->assertArrayHasKey('reference', $fields); + $this->assertArrayHasKey('MAC', $fields); + $this->assertArrayHasKey('lgue', $fields); + $this->assertArrayHasKey('societe', $fields); + $this->assertArrayHasKey('montant', $fields); + $this->assertArrayHasKey('montant_a_capturer', $fields); + $this->assertArrayHasKey('montant_deja_capture', $fields); + $this->assertArrayHasKey('montant_restant', $fields); + $this->assertArrayHasKey('stoprecurrence', $fields); + $this->assertArrayHasKey('numero_dossier', $fields); + $this->assertArrayHasKey('facture', $fields); + $this->assertArrayHasKey('phonie', $fields); + } + + public function testMoneticoCancelFields() + { + $monetico = new Monetico( + EPT_CODE, + SECURITY_KEY, + COMPANY_CODE, + RETURN_URL, + RETURN_SUCCESS_URL, + RETURN_ERROR_URL + ); + + $cancel = new Cancel([ + 'reference' => 'AXCDEF123', + 'language' => 'FR', + 'amount' => 42.42, + 'amountRecovered' => 0, + 'currency' => 'EUR', + 'orderDate' => Carbon::create(2019, 07, 17), + 'dateTime' => Carbon::create(2019, 07, 17), + ]); + + $fields = $monetico->getCancelFields($cancel); + + $this->assertIsArray($fields); + $this->assertArrayHasKey('version', $fields); + $this->assertArrayHasKey('TPE', $fields); + $this->assertArrayHasKey('date', $fields); + $this->assertArrayHasKey('date_commande', $fields); + $this->assertArrayHasKey('reference', $fields); + $this->assertArrayHasKey('MAC', $fields); + $this->assertArrayHasKey('lgue', $fields); + $this->assertArrayHasKey('societe', $fields); + $this->assertArrayHasKey('montant', $fields); + $this->assertArrayHasKey('montant_a_capturer', $fields); + $this->assertArrayHasKey('montant_deja_capture', $fields); + $this->assertArrayHasKey('montant_restant', $fields); + } + + public function testMoneticoRefundFields() + { + $monetico = new Monetico( + EPT_CODE, + SECURITY_KEY, + COMPANY_CODE, + RETURN_URL, + RETURN_SUCCESS_URL, + RETURN_ERROR_URL + ); + + $refund = new Refund([ + 'datetime' => Carbon::create(2019, 2, 1), + 'orderDatetime' => Carbon::create(2019, 1, 1), + 'recoveryDatetime' => Carbon::create(2019, 1, 1), + 'authorizationNumber' => '1222', + 'reference' => 'ABC123', + 'language' => 'FR', + 'currency' => 'EUR', + 'amount' => 100, + 'refundAmount' => 50, + 'maxRefundAmount' => 80, + ]); + + $refund->setInvoiceType('preauto'); + $refund->setFileNumber('ABC'); + + $fields = $monetico->getRefundFields($refund); + + $this->assertIsArray($fields); + $this->assertArrayHasKey('version', $fields); + $this->assertArrayHasKey('TPE', $fields); + $this->assertArrayHasKey('date', $fields); + $this->assertArrayHasKey('date_commande', $fields); + $this->assertArrayHasKey('date_remise', $fields); + $this->assertArrayHasKey('num_autorisation', $fields); + $this->assertArrayHasKey('reference', $fields); + $this->assertArrayHasKey('MAC', $fields); + $this->assertArrayHasKey('lgue', $fields); + $this->assertArrayHasKey('societe', $fields); + $this->assertArrayHasKey('montant', $fields); + $this->assertArrayHasKey('montant_recredit', $fields); + $this->assertArrayHasKey('montant_possible', $fields); + $this->assertArrayHasKey('facture', $fields); + $this->assertArrayHasKey('numero_dossier', $fields); + } } diff --git a/tests/PaymentResponseTest.php b/tests/PaymentResponseTest.php index d7198b6..dd0fdc4 100644 --- a/tests/PaymentResponseTest.php +++ b/tests/PaymentResponseTest.php @@ -1,52 +1,67 @@ EPT_CODE, - 'date' => '01/01/2019_a_08:42:42', - 'amount' => '42.42EUR', - 'reference' => 'ABCDEF123', - 'MAC' => 'YOLO', - 'texte-libre' => 'PHPUnit', - 'version' => '3.0', + 'authentification' => 'ewogICAiZGV0YWlscyIgOiB7CiAgICAgICJQQVJlcyIgOiAiWSIsCiAgICAgICJWRVJlcyIgOiAiWSIsCiAgICAgICJzdGF0dXMzRFMiIDogMQogICB9LAogICAicHJvdG9jb2wiIDogIjNEU2VjdXJlIiwKICAgInN0YXR1cyIgOiAiYXV0aGVudGljYXRlZCIsCiAgICJ2ZXJzaW9uIiA6ICIxLjAuMiIKfQo=', + 'bincb' => '000003', + 'brand' => 'MC', 'code-retour' => 'payetest', 'cvx' => 'oui', - 'vld' => '1219', - 'brand' => 'VI', - 'status3ds' => '4', + 'date' => '11/07/2019_a_10:51:19', + 'hpancb' => '07CDB0331260C06818027855F795C9F726585286', + 'ipclient' => '80.15.24.220', + 'MAC' => '', // needs to be generated + 'modepaiement' => 'CB', + 'montant' => '42.42EUR', 'numauto' => '000000', - 'motifrefus' => null, 'originecb' => 'FRA', - 'bincb' => '000000', - 'hpancb' => 'NOPE', - 'ipclient' => '127.0.0.1', 'originetr' => 'FRA', - 'veres' => null, - 'pares' => null, + 'reference' => 'D2345677', + 'texte-libre' => 'PHPUnit', + 'TPE' => '9344512', + 'vld' => '1219', ]; public function testPaymentResponseConstruct() { $response = new Response($this->data); - $this->assertTrue($response instanceof Response); } public function testPaymentResponseMissingResponseKey() { - $this->expectExceptionObject(PaymentException::missingResponseKey('date')); + $this->expectExceptionObject(Exception::missingResponseKey('TPE')); new Response([]); } public function testPaymentResponseExceptionDateTime() { - $this->expectExceptionObject(PaymentException::invalidDatetime()); + $this->expectExceptionObject(Exception::invalidResponseDateTime()); $data = $this->data; $data['date'] = 'oups'; @@ -54,9 +69,10 @@ public function testPaymentResponseExceptionDateTime() new Response($data); } + public function testPaymentResponseExceptionReturnCode() { - $this->expectExceptionObject(PaymentException::invalidReturnCode('foo')); + $this->expectExceptionObject(PaymentException::invalidResponseReturnCode('foo')); $data = $this->data; $data['code-retour'] = 'foo'; @@ -66,7 +82,7 @@ public function testPaymentResponseExceptionReturnCode() public function testPaymentResponseExceptionCardVerificationStatus() { - $this->expectExceptionObject(PaymentException::invalidCardVerificationStatus('nope')); + $this->expectExceptionObject(PaymentException::invalidResponseCardVerificationStatus('nope')); $data = $this->data; $data['cvx'] = 'nope'; @@ -76,7 +92,7 @@ public function testPaymentResponseExceptionCardVerificationStatus() public function testPaymentResponseExceptionCardBrand() { - $this->expectExceptionObject(PaymentException::invalidCardBrand('foo')); + $this->expectExceptionObject(PaymentException::invalidResponseCardBrand('foo')); $data = $this->data; $data['brand'] = 'foo'; @@ -84,19 +100,9 @@ public function testPaymentResponseExceptionCardBrand() new Response($data); } - public function testPaymentResponseExceptionDDDSStatus() - { - $this->expectExceptionObject(PaymentException::invalidDDDSStatus('42')); - - $data = $this->data; - $data['status3ds'] = '42'; - - new Response($data); - } - public function testPaymentResponseExceptionRejectReason() { - $this->expectExceptionObject(PaymentException::invalidRejectReason('foobar')); + $this->expectExceptionObject(PaymentException::invalidResponseRejectReason('foobar')); $data = $this->data; $data['motifrefus'] = 'foobar'; @@ -106,7 +112,7 @@ public function testPaymentResponseExceptionRejectReason() public function testPaymentResponseExceptionPaymentMethod() { - $this->expectExceptionObject(PaymentException::invalidPaymentMethod('bar')); + $this->expectExceptionObject(PaymentException::invalidResponsePaymentMethod('bar')); $data = $this->data; $data['modepaiement'] = 'bar'; @@ -116,7 +122,7 @@ public function testPaymentResponseExceptionPaymentMethod() public function testPaymentResponseExceptionFilteredReason() { - $this->expectExceptionObject(PaymentException::invalidFilteredReason('10')); + $this->expectExceptionObject(PaymentException::invalidResponseFilteredReason('10')); $data = $this->data; $data['filtragecause'] = '10'; @@ -133,6 +139,10 @@ public function testPaymentWithOptionals() $data['filtrage_etat'] = 'test'; $data['cbenregistree'] = '1'; $data['cbmasquee'] = '1234XXXXXXXXXXX1234'; + $data['motifrefus'] = 'Interdit'; + $data['filtragecause'] = '1'; + $data['cbenregistree'] = '1'; + $response = new Response($data); @@ -142,4 +152,54 @@ public function testPaymentWithOptionals() $this->assertTrue($response->cardBookmarked === true); $this->assertTrue($response->cardMask === '1234XXXXXXXXXXX1234'); } + + public function testAuthenticationDecode() + { + $data = $this->data; + + $response = new Response($data); + + $this->assertEquals('3DSecure', $response->authentication->protocol); + $this->assertEquals('authenticated', $response->authentication->status); + $this->assertEquals('1.0.2', $response->authentication->version); + $this->assertEquals('Y', $response->authentication->details['PARes']); + $this->assertEquals('Y', $response->authentication->details['VERes']); + $this->assertEquals('1', $response->authentication->details['status3DS']); + } + + public function testSealIsValid() + { + $data = [ + 'authentification' => 'ewogICAiZGV0YWlscyIgOiB7CiAgICAgICJBUmVzIiA6ICJZIiwKICAgICAgImF1dGhlbnRpY2F0aW9uVmFsdWUiIDogIlFVRkNRa05EUkVSRlJVWkdRVUZDUWtORFJFUT0iLAogICAgICAibGlhYmlsaXR5U2hpZnQiIDogIlkiLAogICAgICAibWVyY2hhbnRQcmVmZXJlbmNlIiA6ICJub19wcmVmZXJlbmNlIiwKICAgICAgInRyYW5zYWN0aW9uSUQiIDogIjdjOTgyNTVhLWE5YzctNDYxYy1hZDEyLWM3NjM5MzczZDljYiIKICAgfSwKICAgInByb3RvY29sIiA6ICIzRFNlY3VyZSIsCiAgICJzdGF0dXMiIDogImF1dGhlbnRpY2F0ZWQiLAogICAidmVyc2lvbiIgOiAiMi4xLjAiCn0K', + 'bincb' => '000003', + 'brand' => 'MC', + 'code-retour' => 'payetest', + 'cvx' => 'oui', + 'hpancb' => '6FF1313F3B6FE9B053B21CBDEE516603CB8CF01E', + 'ipclient' => '80.15.24.220', + 'modepaiement' => 'CB', + 'numauto' => '000000', + 'originecb' => 'FRA', + 'originetr' => 'FRA', + 'vld' => '1221', + 'date' => '23/07/2019_a_11:55:47', + 'montant' => '42.42EUR', + 'reference' => '12345678', + 'texte-libre' => 'PHPUnit', + 'TPE' => '9344512', + 'montantech' => '50EUR', + 'filtragevaleur' => 'foobar', + 'filtrage_etat' => 'test', + 'cbenregistree' => '1', + 'cbmasquee' => '1234XXXXXXXXXXX1234', + 'motifrefus' => 'Interdit', + 'filtragecause' => '1', + ]; + + $data['MAC'] = $this->generateSeal($data); + + $response = new Response($data); + $sealValid = $response->validateSeal(EPT_CODE, Monetico::getUsableKey(SECURITY_KEY), '3.0'); + $this->assertTrue($sealValid); + } } diff --git a/tests/PaymentTest.php b/tests/PaymentTest.php index 46df1eb..725b993 100644 --- a/tests/PaymentTest.php +++ b/tests/PaymentTest.php @@ -1,10 +1,17 @@ 'john@english.fr', 'amount' => 42.42, 'currency' => 'EUR', - 'datetime' => Carbon::create(2019, 1, 1), + 'dateTime' => Carbon::create(2019, 1, 1), ]); $this->assertTrue($payment instanceof Payment); @@ -24,7 +31,7 @@ public function testPaymentConstruct() public function testPaymentExceptionReference() { - $this->expectExceptionObject(PaymentException::invalidReference('thisisabigerroryouknow')); + $this->expectExceptionObject(Exception::invalidReference('thisisabigerroryouknow')); new Payment([ 'reference' => 'thisisabigerroryouknow', @@ -33,13 +40,13 @@ public function testPaymentExceptionReference() 'email' => 'john@english.fr', 'amount' => 42.42, 'currency' => 'EUR', - 'datetime' => Carbon::create(2019, 1, 1), + 'dateTime' => Carbon::create(2019, 1, 1), ]); } public function testPaymentExceptionLanguage() { - $this->expectExceptionObject(PaymentException::invalidLanguage('WTF')); + $this->expectExceptionObject(Exception::invalidLanguage('WTF')); new Payment([ 'reference' => 'ABCDEF123', @@ -48,13 +55,13 @@ public function testPaymentExceptionLanguage() 'email' => 'john@english.fr', 'amount' => 42.42, 'currency' => 'EUR', - 'datetime' => Carbon::create(2019, 1, 1), + 'dateTime' => Carbon::create(2019, 1, 1), ]); } public function testPaymentExceptionDatetime() { - $this->expectExceptionObject(PaymentException::invalidDatetime()); + $this->expectExceptionObject(Exception::invalidDatetime()); new Payment([ 'reference' => 'ABCDEF123', @@ -63,7 +70,7 @@ public function testPaymentExceptionDatetime() 'email' => 'john@english.fr', 'amount' => 42.42, 'currency' => 'EUR', - 'datetime' => '42', + 'dateTime' => '42', ]); } @@ -76,7 +83,7 @@ public function testPaymentOptions() 'email' => 'john@english.fr', 'amount' => 42.42, 'currency' => 'EUR', - 'datetime' => Carbon::create(2019, 1, 1), + 'dateTime' => Carbon::create(2019, 1, 1), ]); $payment->setCardAlias('foobar'); @@ -132,43 +139,43 @@ public function testPaymentCommitments() 'email' => 'john@english.fr', 'amount' => 200, 'currency' => 'EUR', - 'datetime' => Carbon::create(2019, 1, 1), + 'dateTime' => Carbon::create(2019, 1, 1), ], [ [ 'date' => '06/01/2019', - 'amount' => '50EUR', + 'amount' => 50, ], [ 'date' => '12/01/2019', - 'amount' => '100EUR', + 'amount' => 100, ], [ 'date' => '24/01/2019', - 'amount' => '20EUR', + 'amount' => 20, ], [ 'date' => '02/02/2019', - 'amount' => '30EUR', + 'amount' => 30, ], ] ); $seal = $payment->generateSeal( 'FOO', - 'BAR', - '3.0', - 'FOOBAR' + [] ); $fields = $payment->generateFields( 'FOO', - 'BAR', - '3.0', - 'FOOBAR', - 'https://127.0.0.1', - 'https://127.0.0.1/success', - 'https://127.0.0.1/error' + $payment->fieldsToArray( + 'FOOBAR', + 3.0, + 'FOO', + 'https://127.0.0.1', + 'https://127.0.0.1/success', + 'https://127.0.0.1/error' + ) ); $this->assertIsArray($fields); @@ -199,4 +206,96 @@ public function testPaymentCommitments() $this->assertArrayHasKey('montantech4', $fields); $this->assertTrue($fields['montantech4'] === '30EUR'); } + + public function testSetOrderContext() + { + $payment = new Payment([ + 'reference' => 'ABCDEF123', + 'description' => 'PHPUnit', + 'language' => 'FR', + 'email' => 'john@english.fr', + 'amount' => 42.42, + 'currency' => 'EUR', + 'dateTime' => Carbon::create(2019, 1, 1), + ]); + + $addressBilling = new AddressBilling('7 rue melingue', 'Caen', '14000', 'France'); + $payment->setAddressBilling($addressBilling); + + $addressShipping = new AddressShipping('7 rue melingue', 'Caen', '14000', 'France'); + $payment->setAddressShipping($addressShipping); + + $client = new Client('MR', 'FooBoo', 'Foo', 'Boo'); + $payment->setClient($client); + + $this->assertEquals('7 rue melingue', $payment->addressShipping->data['addressLine1']); + $this->assertEquals('Caen', $payment->addressShipping->data['city']); + $this->assertEquals('14000', $payment->addressShipping->data['postalCode']); + $this->assertEquals('France', $payment->addressShipping->data['country']); + + $this->assertEquals('7 rue melingue', $payment->addressBilling->data['addressLine1']); + $this->assertEquals('Caen', $payment->addressBilling->data['city']); + $this->assertEquals('14000', $payment->addressBilling->data['postalCode']); + $this->assertEquals('France', $payment->addressBilling->data['country']); + + $this->assertEquals('MR', $payment->client->data['civility']); + $this->assertEquals('FooBoo', $payment->client->data['name']); + $this->assertEquals('Foo', $payment->client->data['firstName']); + $this->assertEquals('Boo', $payment->client->data['lastName']); + } + + public function testSet3DSecure() + { + $payment = new Payment([ + 'reference' => '12345679', + 'description' => 'PHPUnit', + 'language' => 'FR', + 'email' => 'john@english.fr', + 'amount' => 42.42, + 'currency' => 'EUR', + 'dateTime' => Carbon::create(2019, 07, 23), + ]); + + $payment->setThreeDSecureChallenge('challenge_mandated'); + $payment->setCardAlias('martin'); + $payment->setSignLabel('toto'); + + $fields = $payment->fieldsToArray( + EPT_CODE, + '3.0', + COMPANY_CODE, + 'https://dev.dansmaculotte.com', + 'https://dev.dansmaculotte.com/success', + 'https://dev.dansmaculotte.com/error' + ); + + $seal = $payment->generateSeal( + Monetico::getUsableKey(SECURITY_KEY), + $fields + ); + + $fields = $payment->generateFields( + $seal, + $fields + ); + + $this->assertEquals($fields['ThreeDSecureChallenge'], 'challenge_mandated'); + } + + public function testPaymentException3DSecure() + { + $this->expectExceptionObject(PaymentException::invalidThreeDSecureChallenge('invalid_choice')); + + $payment = new Payment([ + 'reference' => 'ABCDEF123', + 'description' => 'PHPUnit', + 'language' => 'FR', + 'email' => 'john@english.fr', + 'amount' => 42.42, + 'currency' => 'EUR', + 'dateTime' => Carbon::create(2019, 1, 1), + ]); + + $payment->setThreeDSecureChallenge('invalid_choice'); + } } diff --git a/tests/RecoveryResponseTest.php b/tests/RecoveryResponseTest.php new file mode 100644 index 0000000..8da42f7 --- /dev/null +++ b/tests/RecoveryResponseTest.php @@ -0,0 +1,109 @@ + '1.0', + 'reference' => '000000000145', + 'cdr' => '1', + 'lib' => 'paiement accepte', + 'aut' => '123456', + ]); + + $this->assertTrue($response instanceof Response); + } + + public function testRecoveryResponseWithAuthorization() + { + $response = new Response([ + 'version' => '1.0', + 'reference' => '000000000145', + 'cdr' => '1', + 'lib' => 'paiement accepte', + 'aut' => '123456', + 'phonie' => 'oui', + 'montant_estime' => '10EUR ', + 'date_autorisation' => '2019-05-20', + 'montant_debite' => '5EUR', + 'date_debit' => '2019-05-30', + 'numero_dossier' => 'doss123456', + 'type_facture' => 'preauto', + ]); + + $this->assertTrue($response instanceof Response); + } + + public function testRecoveryResponseConstructExceptionMissingResponseKey() + { + $this->expectExceptionObject(Exception::missingResponseKey('cdr')); + new Response([ + 'version' => '1.0', + 'reference' => '000000000145', + 'lib' => 'paiement accepte', + 'aut' => '123456', + ]); + } + + public function testRecoveryResponseExceptionInvalidFileNumber() + { + $this->expectExceptionObject(Exception::invalidResponseFileNumber('thisisawrongreference')); + + new Response([ + 'version' => '1.0', + 'reference' => 'ABCD123', + 'cdr' => '1', + 'lib' => 'paiement accepte', + 'aut' => '123456', + 'numero_dossier' => 'thisisawrongreference' + ]); + } + + public function testRecoveryResponseExceptionInvalidAuthDatetime() + { + $this->expectExceptionObject(RecoveryException::invalidResponseAuthorizationDate()); + + new Response([ + 'version' => '1.0', + 'reference' => 'ABCD123', + 'cdr' => '1', + 'lib' => 'paiement accepte', + 'aut' => '123456', + 'date_autorisation' => 'juin 2019' + ]); + } + + public function testRecoveryResponseExceptionInvalidDebitDatetime() + { + $this->expectExceptionObject(RecoveryException::invalidResponseDebitDate()); + + new Response([ + 'version' => '1.0', + 'reference' => 'ABCD123', + 'cdr' => '1', + 'lib' => 'paiement accepte', + 'aut' => '123456', + 'date_debit' => 'juin 2019' + ]); + } + + public function testRecoveryResponseExceptionInvalidInvoiceType() + { + $this->expectExceptionObject(Exception::invalidResponseInvoiceType('invalid')); + + new Response([ + 'version' => '1.0', + 'reference' => 'ABCD123', + 'cdr' => '1', + 'type_facture' => 'invalid', + 'lib' => 'paiement accepte', + 'aut' => '123456', + ]); + } +} diff --git a/tests/RecoveryTest.php b/tests/RecoveryTest.php new file mode 100644 index 0000000..93f183f --- /dev/null +++ b/tests/RecoveryTest.php @@ -0,0 +1,162 @@ + Carbon::create(2019, 2, 1), + 'orderDate' => Carbon::create(2019, 1, 1), + 'reference' => 'ABC123', + 'language' => 'FR', + 'currency' => 'EUR', + 'amount' => 100, + 'amountToRecover' => 50, + 'amountRecovered' => 0, + 'amountLeft' => 50 + ]); + + $this->assertTrue($recovery instanceof Recovery); + } + + public function testRecoveryConstructExceptionInvalidAmounts() + { + $this->expectExceptionObject(RecoveryException::invalidAmounts(100, 30, 0, 50)); + + new Recovery([ + 'dateTime' => Carbon::create(2019, 2, 1), + 'orderDate' => Carbon::create(2019, 1, 1), + 'reference' => 'ABC123', + 'language' => 'FR', + 'currency' => 'EUR', + 'amount' => 100, + 'amountToRecover' => 30, + 'amountRecovered' => 0, + 'amountLeft' => 50 + ]); + } + + public function testRecoveryConstructExceptionInvalidDatetime() + { + $this->expectExceptionObject(Exception::invalidDatetime()); + + new Recovery([ + 'dateTime' => 'invalid', + 'orderDate' => Carbon::create(2019, 1, 1), + 'reference' => 'ABC123', + 'language' => 'FR', + 'currency' => 'EUR', + 'amount' => 100, + 'amountToRecover' => 50, + 'amountRecovered' => 0, + 'amountLeft' => 50 + ]); + } + + public function testRecoveryConstructExceptionInvalidOrderDatetime() + { + $this->expectExceptionObject(Exception::invalidOrderDate()); + + new Recovery([ + 'dateTime' => Carbon::create(2019, 1, 1), + 'orderDate' => 'invalid', + 'reference' => 'ABC123', + 'language' => 'FR', + 'currency' => 'EUR', + 'amount' => 100, + 'amountToRecover' => 50, + 'amountRecovered' => 0, + 'amountLeft' => 50 + ]); + } + + public function testRecoveryConstructExceptionInvalidReference() + { + $this->expectExceptionObject(Exception::invalidReference('thisisatoolongreference')); + + new Recovery([ + 'dateTime' => Carbon::create(2019, 2, 1), + 'orderDate' => Carbon::create(2019, 1, 1), + 'reference' => 'thisisatoolongreference', + 'language' => 'FR', + 'currency' => 'EUR', + 'amount' => 100, + 'amountToRecover' => 50, + 'amountRecovered' => 0, + 'amountLeft' => 50 + ]); + } + + public function testRecoveryConstructExceptionInvalidLanguage() + { + $this->expectExceptionObject(Exception::invalidLanguage('English')); + + new Recovery([ + 'dateTime' => Carbon::create(2019, 2, 1), + 'orderDate' => Carbon::create(2019, 1, 1), + 'reference' => 'ABC123', + 'language' => 'English', + 'currency' => 'EUR', + 'amount' => 100, + 'amountToRecover' => 50, + 'amountRecovered' => 0, + 'amountLeft' => 50 + ]); + } + + public function testRecoveryOptions() + { + $recovery = new Recovery([ + 'dateTime' => Carbon::create(2019, 2, 1), + 'orderDate' => Carbon::create(2019, 1, 1), + 'reference' => 'ABC123', + 'language' => 'FR', + 'currency' => 'EUR', + 'amount' => 100, + 'amountToRecover' => 50, + 'amountRecovered' => 0, + 'amountLeft' => 50 + ]); + + $recovery->setStopRecurrence(); + $this->assertEquals('oui', $recovery->stopRecurrence); + + $recovery->setFileNumber('12'); + $this->assertEquals(12, $recovery->fileNumber); + + $recovery->setInvoiceType('preauto'); + $this->assertEquals('preauto', $recovery->invoiceType); + + $recovery->setInvoiceType('noshow'); + $this->assertEquals('noshow', $recovery->invoiceType); + + $recovery->setPhone(); + $this->assertEquals('oui', $recovery->phone); + } + + public function testSetInvoiceTypeExceptionInvalidInvoiceType() + { + $this->expectExceptionObject(Exception::invalidInvoiceType('invalid')); + + $recovery = new Recovery([ + 'dateTime' => Carbon::create(2019, 2, 1), + 'orderDate' => Carbon::create(2019, 1, 1), + 'reference' => 'ABC123', + 'language' => 'FR', + 'currency' => 'EUR', + 'amount' => 100, + 'amountToRecover' => 50, + 'amountRecovered' => 0, + 'amountLeft' => 50 + ]); + + $recovery->setInvoiceType('invalid'); + $this->assertEquals('preauto', $recovery->invoiceType); + } +} diff --git a/tests/RefundResponseTest.php b/tests/RefundResponseTest.php new file mode 100644 index 0000000..99309e6 --- /dev/null +++ b/tests/RefundResponseTest.php @@ -0,0 +1,64 @@ + '1.0', + 'reference' => '000000000145', + 'cdr' => '1', + 'lib' => 'paiement accepte', + 'numero_dossier' => '123', + 'type_facture' => 'complementaire' + ]); + + $this->assertTrue($response instanceof Response); + } + + public function testRefundResponseConstructExceptionMissingResponseKey() + { + $this->expectExceptionObject(Exception::missingResponseKey('cdr')); + + new Response([ + 'version' => '1.0', + 'reference' => '000000000145', + 'lib' => 'paiement accepte', + 'numero_dossier' => '123', + 'type_facture' => 'complementaire' + ]); + } + + + public function testRefundResponseConstructExceptionInvalidFileNumber() + { + $this->expectExceptionObject(Exception::invalidResponseFileNumber('thisisatoolongreference')); + + new Response([ + 'version' => '1.0', + 'reference' => 'ABC', + 'cdr' => '1', + 'lib' => 'paiement accepte', + 'numero_dossier' => 'thisisatoolongreference', + 'type_facture' => 'complementaire' + ]); + } + + public function testRefundResponseConstructExceptionInvalidInvoiceType() + { + $this->expectExceptionObject(Exception::invalidResponseInvoiceType('invalid')); + + new Response([ + 'version' => '1.0', + 'reference' => 'ABC', + 'cdr' => '1', + 'lib' => 'paiement accepte', + 'numero_dossier' => 'ABC', + 'type_facture' => 'invalid' + ]); + } +} diff --git a/tests/RefundTest.php b/tests/RefundTest.php new file mode 100644 index 0000000..17e1a59 --- /dev/null +++ b/tests/RefundTest.php @@ -0,0 +1,159 @@ + Carbon::create(2019, 2, 1), + 'orderDatetime' => Carbon::create(2019, 1, 1), + 'recoveryDatetime' => Carbon::create(2019, 1, 1), + 'authorizationNumber' => '1222', + 'reference' => 'ABC123', + 'language' => 'FR', + 'currency' => 'EUR', + 'amount' => 100, + 'refundAmount' => 50, + 'maxRefundAmount' => 80, + ]); + + $this->assertTrue($refund instanceof Refund); + } + + public function testRefundWithOptions() + { + $refund = new Refund([ + 'datetime' => Carbon::create(2019, 2, 1), + 'orderDatetime' => Carbon::create(2019, 1, 1), + 'recoveryDatetime' => Carbon::create(2019, 1, 1), + 'authorizationNumber' => '1222', + 'reference' => 'ABC123', + 'language' => 'FR', + 'currency' => 'EUR', + 'amount' => 100, + 'refundAmount' => 50, + 'maxRefundAmount' => 80, + ]); + + $refund->setFileNumber('123'); + $this->assertEquals('123', $refund->fileNumber); + + $refund->setInvoiceType('preauto'); + $this->assertEquals('preauto', $refund->invoiceType); + + $this->assertTrue($refund instanceof Refund); + } + + public function testRefundConstructExceptionInvalidDatetime() + { + $this->expectExceptionObject(Exception::invalidDatetime()); + new Refund([ + 'datetime' => 'invalid', + 'orderDatetime' => Carbon::create(2019, 1, 1), + 'recoveryDatetime' => Carbon::create(2019, 1, 1), + 'authorizationNumber' => '1222', + 'reference' => 'ABC123', + 'language' => 'FR', + 'currency' => 'EUR', + 'amount' => 100, + 'refundAmount' => 50, + 'maxRefundAmount' => 80, + ]); + } + + + public function testRefundConstructExceptionInvalidOrderDatetime() + { + $this->expectExceptionObject(Exception::invalidOrderDate()); + new Refund([ + 'datetime' => Carbon::create(2019, 1, 1), + 'orderDatetime' => 'invalid', + 'recoveryDatetime' => Carbon::create(2019, 1, 1), + 'authorizationNumber' => '1222', + 'reference' => 'ABC123', + 'language' => 'FR', + 'currency' => 'EUR', + 'amount' => 100, + 'refundAmount' => 50, + 'maxRefundAmount' => 80, + ]); + } + + public function testRefundConstructExceptionInvalidRecoveryDatetime() + { + $this->expectExceptionObject(Exception::invalidRecoveryDate()); + new Refund([ + 'datetime' => Carbon::create(2019, 1, 1), + 'orderDatetime' => Carbon::create(2019, 1, 1), + 'recoveryDatetime' =>'invalid', + 'authorizationNumber' => '1222', + 'reference' => 'ABC123', + 'language' => 'FR', + 'currency' => 'EUR', + 'amount' => 100, + 'refundAmount' => 50, + 'maxRefundAmount' => 80, + ]); + } + + public function testRefundConstructExceptionInvalidReference() + { + $this->expectExceptionObject(Exception::invalidReference('thisisatoolongreference')); + + new Refund([ + 'datetime' => Carbon::create(2019, 2, 1), + 'orderDatetime' => Carbon::create(2019, 1, 1), + 'recoveryDatetime' => Carbon::create(2019, 1, 1), + 'authorizationNumber' => '1222', + 'reference' => 'thisisatoolongreference', + 'language' => 'FR', + 'currency' => 'EUR', + 'amount' => 100, + 'refundAmount' => 50, + 'maxRefundAmount' => 80, + ]); + } + + public function testRefundConstructExceptionInvalidLanguage() + { + $this->expectExceptionObject(Exception::invalidLanguage('invalid')); + + new Refund([ + 'datetime' => Carbon::create(2019, 2, 1), + 'orderDatetime' => Carbon::create(2019, 1, 1), + 'recoveryDatetime' => Carbon::create(2019, 1, 1), + 'authorizationNumber' => '1222', + 'reference' => 'ABC123', + 'language' => 'invalid', + 'currency' => 'EUR', + 'amount' => 100, + 'refundAmount' => 50, + 'maxRefundAmount' => 80, + ]); + } + + public function testRefundConstructExceptionInvalidInvoiceType() + { + $this->expectExceptionObject(Exception::invalidInvoiceType('invalid')); + + $refund = new Refund([ + 'datetime' => Carbon::create(2019, 2, 1), + 'orderDatetime' => Carbon::create(2019, 1, 1), + 'recoveryDatetime' => Carbon::create(2019, 1, 1), + 'authorizationNumber' => '1222', + 'reference' => 'ABC123', + 'language' => 'FR', + 'currency' => 'EUR', + 'amount' => 100, + 'refundAmount' => 50, + 'maxRefundAmount' => 80, + ]); + + $refund->setInvoiceType('invalid'); + } +}