diff --git a/README.md b/README.md index c9fce4a..1ca504f 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,15 @@ $saleRequest = (new SaleRequest()) ->setTerminalID('') ->setMerchantId('') ->setPrivateKey('\', '') + ->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 //->setSigningSchemaMacAdvanced(); // use MAC_ADVANCED 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 + + diff --git a/src/Request.php b/src/Request.php index b62075e..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; @@ -55,10 +56,15 @@ abstract class Request extends Base */ private $nonce; + /** + * @array + */ + private $mInfo; + /** * Get description * - * @return mixed + * @return string */ public function getDescription() { @@ -68,7 +74,7 @@ public function getDescription() /** * Set description * - * @param string $description Описание на поръчката. + * @param string $description Описание на поръчката. * * @return Request * @throws ParameterValidationException @@ -95,7 +101,7 @@ public function getBackRefUrl() /** * Set back ref url * - * @param string $backRefUrl URL на търговеца за изпращане на резултата от авторизацията. + * @param string $backRefUrl URL на търговеца за изпращане на резултата от авторизацията. * * @return Request * @throws ParameterValidationException @@ -123,7 +129,7 @@ public function getOrder() /** * Set order * - * @param string|integer $order Номер на поръчката за търговеца, 6 цифри, който трябва да бъде уникален за деня. + * @param string|integer $order Номер на поръчката за търговеца, 6 цифри, който трябва да бъде уникален за деня. * * @return Request * @throws ParameterValidationException @@ -151,7 +157,7 @@ public function getTransactionType() /** * Set transaction type * - * @param TransactionType $transactionType Тип на транзакцията. + * @param TransactionType $transactionType Тип на транзакцията. * * @return Request */ @@ -174,7 +180,7 @@ public function getAmount() /** * Set amount * - * @param string|float|integer $amount Обща стойност на поръчката по стандарт ISO_4217 с десетичен разделител точка. + * @param string|float|integer $amount Обща стойност на поръчката по стандарт ISO_4217 с десетичен разделител точка. * * @return Request */ @@ -197,7 +203,7 @@ public function getCurrency() /** * Set currency * - * @param string $currency Валута на поръчката: три буквен код на валута по стандарт ISO 4217. + * @param string $currency Валута на поръчката: три буквен код на валута по стандарт ISO 4217. * * @return Request * @throws ParameterValidationException @@ -228,7 +234,7 @@ public function getSignatureTimestamp() /** * Set signature timestamp * - * @param string|null $signatureTimestamp Дата на подпис/изпращане на данните. + * @param string|null $signatureTimestamp Дата на подпис/изпращане на данните. * * @return Request */ @@ -256,7 +262,7 @@ public function getNonce() } /** - * @param string $nonce Nonce. + * @param string $nonce Nonce. * * @return Request */ @@ -265,4 +271,49 @@ public function setNonce($nonce) $this->nonce = $nonce; return $this; } + + /** + * @return string + */ + public function getMInfo() + { + if (!empty($this->mInfo)) { + return base64_encode(json_encode($this->mInfo)); + } + return ''; + } + + /** + * @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/src/SaleRequest.php b/src/SaleRequest.php index b018d94..f65d2e7 100644 --- a/src/SaleRequest.php +++ b/src/SaleRequest.php @@ -105,7 +105,7 @@ public function generateForm() */ public function getData() { - return [ + return array_filter([ 'NONCE' => $this->getNonce(), 'P_SIGN' => $this->generateSignature(), @@ -126,7 +126,10 @@ public function getData() 'TERMINAL' => $this->getTerminalID(), 'BACKREF' => $this->getBackRefUrl(), - ] + $this->generateAdCustBorOrderId(); + + 'M_INFO' => $this->getMInfo(), + + ]) + $this->generateAdCustBorOrderId(); } /** 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