From a894561fb84d16e4f5ebcf41373e8b63c0d2454d Mon Sep 17 00:00:00 2001 From: Rosen Zahariev Date: Mon, 15 Jul 2024 09:34:02 +0300 Subject: [PATCH 1/4] Added M_INFO field --- README.md | 5 +++++ src/Request.php | 23 +++++++++++++++++++++++ src/SaleRequest.php | 7 +++++++ 3 files changed, 35 insertions(+) diff --git a/README.md b/README.md index c9fce4a..c3fe453 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,11 @@ $saleRequest = (new SaleRequest()) ->setTerminalID('') ->setMerchantId('') ->setPrivateKey('\', '') + ->setMInfo(array( + 'email'=>'test@test.com', + 'cardholderName'=>'Ivan Ivanov', + 'mobilePhone'=>'359888123456' + )) //->setSigningSchemaMacGeneral(); // use MAC_GENERAL //->setSigningSchemaMacExtended(); // use MAC_EXTENDED //->setSigningSchemaMacAdvanced(); // use MAC_ADVANCED diff --git a/src/Request.php b/src/Request.php index b62075e..bb042d1 100644 --- a/src/Request.php +++ b/src/Request.php @@ -55,6 +55,10 @@ abstract class Request extends Base */ private $nonce; + /** + * @array + */ + private $mInfo; /** * Get description * @@ -265,4 +269,23 @@ public function setNonce($nonce) $this->nonce = $nonce; return $this; } + + /** + * @return array + */ + public function getMInfo() + { + return $this->mInfo; + } + + /** + * @param string $nonce Nonce. + * + * @return Request + */ + public function setMInfo($mInfo) + { + $this->mInfo = $mInfo; + return $this; + } } diff --git a/src/SaleRequest.php b/src/SaleRequest.php index b018d94..77bf87c 100644 --- a/src/SaleRequest.php +++ b/src/SaleRequest.php @@ -47,6 +47,11 @@ class SaleRequest extends Request implements RequestInterface */ protected $adCustBorOrderId; + /** + * @array + */ + protected $mInfo; + /** * Sale constructor. */ @@ -126,6 +131,8 @@ public function getData() 'TERMINAL' => $this->getTerminalID(), 'BACKREF' => $this->getBackRefUrl(), + + 'M_INFO' => base64_encode(json_encode($this->getMInfo())), ] + $this->generateAdCustBorOrderId(); } From deffffaf99b7030aef8561b0d6ba63a0e448c227 Mon Sep 17 00:00:00 2001 From: Rosen Zahariev Date: Mon, 15 Jul 2024 11:14:06 +0300 Subject: [PATCH 2/4] Changed readme and added fix for backward compability when M_INFO is not filled --- README.md | 12 ++++++++---- src/Request.php | 5 ++++- src/SaleRequest.php | 12 ++++-------- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index c3fe453..1ca504f 100644 --- a/README.md +++ b/README.md @@ -76,10 +76,14 @@ $saleRequest = (new SaleRequest()) ->setTerminalID('') ->setMerchantId('') ->setPrivateKey('\', '') - ->setMInfo(array( - 'email'=>'test@test.com', - 'cardholderName'=>'Ivan Ivanov', - 'mobilePhone'=>'359888123456' + ->setMInfo(array( // Mandatory cardholderName and ( email or MobilePhone ) + 'email'=>'user@sample.com', + 'cardholderName'=>'CARDHOLDER NAME', // Max 45 chars + 'mobilePhone'=> array( + 'cc'=>'359', // Country code + 'subscriber'=>'8939999888', // Subscriber number + ), + 'threeDSRequestorChallengeInd'=>'04', // Optional for Additional Authentication )) //->setSigningSchemaMacGeneral(); // use MAC_GENERAL //->setSigningSchemaMacExtended(); // use MAC_EXTENDED diff --git a/src/Request.php b/src/Request.php index bb042d1..63550d9 100644 --- a/src/Request.php +++ b/src/Request.php @@ -275,7 +275,10 @@ public function setNonce($nonce) */ public function getMInfo() { - return $this->mInfo; + if (!empty($this->mInfo)) { + return base64_encode(json_encode($this->mInfo)); + } + return ''; } /** diff --git a/src/SaleRequest.php b/src/SaleRequest.php index 77bf87c..f65d2e7 100644 --- a/src/SaleRequest.php +++ b/src/SaleRequest.php @@ -47,11 +47,6 @@ class SaleRequest extends Request implements RequestInterface */ protected $adCustBorOrderId; - /** - * @array - */ - protected $mInfo; - /** * Sale constructor. */ @@ -110,7 +105,7 @@ public function generateForm() */ public function getData() { - return [ + return array_filter([ 'NONCE' => $this->getNonce(), 'P_SIGN' => $this->generateSignature(), @@ -132,8 +127,9 @@ public function getData() 'TERMINAL' => $this->getTerminalID(), 'BACKREF' => $this->getBackRefUrl(), - 'M_INFO' => base64_encode(json_encode($this->getMInfo())), - ] + $this->generateAdCustBorOrderId(); + 'M_INFO' => $this->getMInfo(), + + ]) + $this->generateAdCustBorOrderId(); } /** From 5e80143d3400f209a47fa90704da80c5e5f2ab62 Mon Sep 17 00:00:00 2001 From: Venelin Iliev Date: Tue, 16 Jul 2024 16:48:57 +0300 Subject: [PATCH 3/4] Add check and validation for mInfo In this commit, mInfo inputs are inspected for required fields, specific length, valid email, and correct structure for mobilePhone. Additionally, adjustments in code formatting and annotations were made in several places of the Request class. A new unit test case `SaleRequestMInfoTest` was also added to support these changes. --- src/Request.php | 47 +++++++++++++++----- tests/Unit/SaleRequestMInfoTest.php | 68 +++++++++++++++++++++++++++++ tests/Unit/SaleRequestTest.php | 66 ++++++++++++++++++++++++++-- 3 files changed, 166 insertions(+), 15 deletions(-) create mode 100644 tests/Unit/SaleRequestMInfoTest.php diff --git a/src/Request.php b/src/Request.php index 63550d9..21f05b7 100644 --- a/src/Request.php +++ b/src/Request.php @@ -6,6 +6,7 @@ namespace VenelinIliev\Borica3ds; +use InvalidArgumentException; use VenelinIliev\Borica3ds\Enums\TransactionType; use VenelinIliev\Borica3ds\Exceptions\ParameterValidationException; @@ -59,10 +60,11 @@ abstract class Request extends Base * @array */ private $mInfo; + /** * Get description * - * @return mixed + * @return string */ public function getDescription() { @@ -72,7 +74,7 @@ public function getDescription() /** * Set description * - * @param string $description Описание на поръчката. + * @param string $description Описание на поръчката. * * @return Request * @throws ParameterValidationException @@ -99,7 +101,7 @@ public function getBackRefUrl() /** * Set back ref url * - * @param string $backRefUrl URL на търговеца за изпращане на резултата от авторизацията. + * @param string $backRefUrl URL на търговеца за изпращане на резултата от авторизацията. * * @return Request * @throws ParameterValidationException @@ -127,7 +129,7 @@ public function getOrder() /** * Set order * - * @param string|integer $order Номер на поръчката за търговеца, 6 цифри, който трябва да бъде уникален за деня. + * @param string|integer $order Номер на поръчката за търговеца, 6 цифри, който трябва да бъде уникален за деня. * * @return Request * @throws ParameterValidationException @@ -155,7 +157,7 @@ public function getTransactionType() /** * Set transaction type * - * @param TransactionType $transactionType Тип на транзакцията. + * @param TransactionType $transactionType Тип на транзакцията. * * @return Request */ @@ -178,7 +180,7 @@ public function getAmount() /** * Set amount * - * @param string|float|integer $amount Обща стойност на поръчката по стандарт ISO_4217 с десетичен разделител точка. + * @param string|float|integer $amount Обща стойност на поръчката по стандарт ISO_4217 с десетичен разделител точка. * * @return Request */ @@ -201,7 +203,7 @@ public function getCurrency() /** * Set currency * - * @param string $currency Валута на поръчката: три буквен код на валута по стандарт ISO 4217. + * @param string $currency Валута на поръчката: три буквен код на валута по стандарт ISO 4217. * * @return Request * @throws ParameterValidationException @@ -232,7 +234,7 @@ public function getSignatureTimestamp() /** * Set signature timestamp * - * @param string|null $signatureTimestamp Дата на подпис/изпращане на данните. + * @param string|null $signatureTimestamp Дата на подпис/изпращане на данните. * * @return Request */ @@ -260,7 +262,7 @@ public function getNonce() } /** - * @param string $nonce Nonce. + * @param string $nonce Nonce. * * @return Request */ @@ -271,7 +273,7 @@ public function setNonce($nonce) } /** - * @return array + * @return string */ public function getMInfo() { @@ -282,12 +284,35 @@ public function getMInfo() } /** - * @param string $nonce Nonce. + * @param array $mInfo * * @return Request */ public function setMInfo($mInfo) { + // Check for required fields (cardholderName and email or mobilePhone) + if (!isset($mInfo['cardholderName']) || + (!isset($mInfo['email']) && !isset($mInfo['mobilePhone']))) { + throw new InvalidArgumentException('CardholderName and email or MobilePhone must be provided'); + } + + // Check the maximum length of cardholderName + if (strlen($mInfo['cardholderName']) > 45) { + throw new InvalidArgumentException('CardHolderName must be at most 45 characters'); + } + + // Check for a valid email address format + if (isset($mInfo['email']) && !filter_var($mInfo['email'], FILTER_VALIDATE_EMAIL)) { + throw new InvalidArgumentException('Email must be a valid email address'); + } + + // Check the structure for the mobile phone + if (isset($mInfo['mobilePhone'])) { + if (!isset($mInfo['mobilePhone']['cc']) || !isset($mInfo['mobilePhone']['subscriber'])) { + throw new InvalidArgumentException('MobilePhone must contain both cc and subscriber'); + } + } + $this->mInfo = $mInfo; return $this; } diff --git a/tests/Unit/SaleRequestMInfoTest.php b/tests/Unit/SaleRequestMInfoTest.php new file mode 100644 index 0000000..f20705f --- /dev/null +++ b/tests/Unit/SaleRequestMInfoTest.php @@ -0,0 +1,68 @@ +setMInfo([ + 'cardholderName' => 'John Doe', + 'email' => 'johndoe@example.com' + ]); + $expected = base64_encode(json_encode([ + 'cardholderName' => 'John Doe', + 'email' => 'johndoe@example.com' + ])); + $this->assertEquals($expected, $request->getMInfo()); + } + + public function testMInfoWithInvalidEmail() + { + $this->expectException(\InvalidArgumentException::class); + + $request = new SaleRequest(); + $request->setMInfo([ + 'cardholderName' => 'John Doe', + 'email' => 'invalid', + ]); + } + + public function testMInfoWithInvalidCardholderNameLength() + { + $this->expectException(\InvalidArgumentException::class); + + $request = new SaleRequest(); + $request->setMInfo([ + 'cardholderName' => str_repeat('a', 46), + 'email' => 'johndoe@example.com', + ]); + } + + public function testMInfoWithMissingRequiredFields() + { + $this->expectException(\InvalidArgumentException::class); + + $request = new SaleRequest(); + $request->setMInfo(['email' => 'johndoe@example.com']); + } + + public function testMInfoWithInvalidMobilePhoneStructure() + { + $this->expectException(\InvalidArgumentException::class); + + $request = new SaleRequest(); + $request->setMInfo([ + 'cardholderName' => 'John Doe', + 'mobilePhone' => ['invalid' => 'invalid'], + ]); + } +} diff --git a/tests/Unit/SaleRequestTest.php b/tests/Unit/SaleRequestTest.php index c4e8050..ef37131 100644 --- a/tests/Unit/SaleRequestTest.php +++ b/tests/Unit/SaleRequestTest.php @@ -99,7 +99,7 @@ public function testDataMacGeneral() $this->assertEquals([ 'TRTYPE' => 1, - 'COUNTRY' => null, + //'COUNTRY' => null, 'CURRENCY' => 'BGN', 'MERCH_GMT' => '+03', 'ORDER' => '145659', @@ -108,7 +108,7 @@ public function testDataMacGeneral() 'TIMESTAMP' => '20201013115715', 'TERMINAL' => self::TERMINAL_ID, 'MERCH_URL' => 'https://test.com', - 'MERCH_NAME' => null, + //'MERCH_NAME' => null, 'EMAIL' => 'test@test.com', 'BACKREF' => 'https://test.com/back-ref-url', 'AD.CUST_BOR_ORDER_ID' => 'test', @@ -145,7 +145,7 @@ public function testData() $this->assertEquals([ 'TRTYPE' => 1, - 'COUNTRY' => null, + //'COUNTRY' => null, 'CURRENCY' => 'BGN', 'MERCH_GMT' => '+03', 'ORDER' => '145659', @@ -154,7 +154,7 @@ public function testData() 'TIMESTAMP' => '20201013115715', 'TERMINAL' => self::TERMINAL_ID, 'MERCH_URL' => 'https://test.com', - 'MERCH_NAME' => null, + //'MERCH_NAME' => null, 'EMAIL' => 'test@test.com', 'BACKREF' => 'https://test.com/back-ref-url', 'AD.CUST_BOR_ORDER_ID' => 'test', @@ -165,6 +165,64 @@ public function testData() ], $saleData); } + /** + * @throws ParameterValidationException|SignatureException + */ + public function testDataWithMInfo() + { + $mInfo = [ + 'email' => 'user@sample.com', + 'cardholderName' => 'CARDHOLDER NAME', + 'mobilePhone' => [ + 'cc' => '359', + 'subscriber' => '8939999888' + ], + 'threeDSRequestorChallengeInd' => '04', + ]; + + $saleData = (new SaleRequest()) + ->setAmount(1) + ->setCurrency('BGN') + ->setOrder(145659) + ->setDescription('Детайли плащане.') + ->setMerchantGMT('+03') + ->setMerchantUrl('https://test.com') + ->setBackRefUrl('https://test.com/back-ref-url') + ->setTerminalID(self::TERMINAL_ID) + ->setMerchantId(self::MERCHANT_ID) + ->setPrivateKey(__DIR__ . '/../certificates/test.key') + ->setPrivateKeyPassword('test') + ->setSignatureTimestamp('20201013115715') + ->setNonce('FC8AC36A9FDADCB6127D273CD15DAEC3') + ->setEmailAddress('test@test.com') + ->setAdCustBorOrderId('test') + ->setSigningSchemaMacExtended() + ->setMInfo($mInfo) + ->getData(); + + $this->assertEquals([ + 'TRTYPE' => 1, + //'COUNTRY' => null, + 'CURRENCY' => 'BGN', + 'MERCH_GMT' => '+03', + 'ORDER' => '145659', + 'AMOUNT' => '1.00', + 'DESC' => 'Детайли плащане.', + 'TIMESTAMP' => '20201013115715', + 'TERMINAL' => self::TERMINAL_ID, + 'MERCH_URL' => 'https://test.com', + //'MERCH_NAME' => null, + 'EMAIL' => 'test@test.com', + 'BACKREF' => 'https://test.com/back-ref-url', + 'AD.CUST_BOR_ORDER_ID' => 'test', + 'ADDENDUM' => 'AD,TD', + 'NONCE' => 'FC8AC36A9FDADCB6127D273CD15DAEC3', + 'MERCHANT' => self::MERCHANT_ID, + 'M_INFO' => 'eyJlbWFpbCI6InVzZXJAc2FtcGxlLmNvbSIsImNhcmRob2xkZXJOYW1lIjoiQ0FSREhPTERFUiBOQU1FIiwibW9iaWxlUGhvbmUiOnsiY2MiOiIzNTkiLCJzdWJzY3JpYmVyIjoiODkzOTk5OTg4OCJ9LCJ0aHJlZURTUmVxdWVzdG9yQ2hhbGxlbmdlSW5kIjoiMDQifQ==', + 'P_SIGN' => '8125E0E604B8BC6430B03B1365B63D91ACB7210F2777776D7587A633D222368CB36936855090C81020318503998499503595EBB32092014A2843C7E6DB75C1AD7FCB018BB4CDA98B379B411E74C62881529A7787B73D8D0E00D1406E1D2A64ADD1A298CCDF3B5A13C14825990010541444122F4A8FBB23BB3747B962BEFB5C57C5737FCF8DC9E61F377777B661B04FFE604EE5E49EB87CA49737FD39AA27639DE0CEF11B527B630070BE97ECC81F0D14D355F37C5C684A040C615563C962CE137A0B7C7F0B3567DEB0A05C4D79F373D7938D4CBFCE86CA6AA5DBAC99081F3AB4C52E0A3B35748A7600ECE4278060B14F5D3ACE5D964A73F49CF8844B6C86E10E' + ], $saleData); + } + /** * @return void * @throws ParameterValidationException|SignatureException From 1feb461f79f224c877462a05851340eaf32819c3 Mon Sep 17 00:00:00 2001 From: Venelin Iliev Date: Tue, 16 Jul 2024 17:17:07 +0300 Subject: [PATCH 4/4] Update phpunit.xml schema and structure The PHPUnit configuration file has been updated with a new schema URL. The coverage tag has been replaced with a source tag to include the testing suite. Other structural changes were applied to enhance readability and maintainability. --- phpunit.xml | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/phpunit.xml b/phpunit.xml index 8fcf679..7749bdf 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -3,15 +3,17 @@ ~ Copyright (c) 2023. Venelin Iliev. ~ https://veneliniliev.com --> - - - - ./tests/Unit - - - - - ./tests/Unit - - + + + + ./tests/Unit + + + + + ./tests/Unit + +