diff --git a/examples/basicExampleV4.php b/examplesV4/createTokenForRecurrentPaymentV4.php similarity index 82% rename from examples/basicExampleV4.php rename to examplesV4/createTokenForRecurrentPaymentV4.php index 388a3c52..e0dc8c5c 100644 --- a/examples/basicExampleV4.php +++ b/examplesV4/createTokenForRecurrentPaymentV4.php @@ -1,10 +1,10 @@ withBackRef('http://path/to/your/returnUrlScript') - ->withOrderRef($merchantOrderRef) - //->withOrderRef('MerchantOrderRef') + ->withOrderRef('MerchantOrderRef') ->withCurrency('RON') ->withOrderDate(gmdate('Y-m-d\TH:i:sP')) ->withOrderTimeout(1000) @@ -124,7 +115,6 @@ ->withPhoneNumber('40123456789') ->withIdentityCardNumber('111222'); - /** * Create new delivery address * @@ -156,7 +146,18 @@ * Credit Card CVV (Security Code) * Credit Card Owner */ -$card = new Card('4111111111111111', '01', '2026', '123', 'Card Owner Name'); +$card = new Card( + '4111111111111111', + '01', + date("Y", strtotime("+1 year")), + 123, + 'Card Owner Name' +); + +/** + * tokenize card for further token payments + */ +$card->enableTokenCreation(); /** * Create new Request with params: @@ -168,7 +169,6 @@ * User object */ $request = new Request($cfg, $order, $billing, $delivery, $user, PaymentsV4::API_VERSION_V4); -//$request = new Request($cfg, $order, $billing, $delivery, $user); /** * Add the Credit Card to the Request @@ -202,12 +202,17 @@ die(); } - echo $response->getCode() - . ' ' . $response->getStatus() - . ' ' . $response->getReturnCode() - . ' ' . $response->getReturnMessage(); -} catch (ConnectionException $exception) { - echo $exception->getMessage(); + echo $response->getStatus() . ' ' . $response->getReturnCode() . ' ' . $response->getReturnMessage() . "\n"; + + if ($response->getTokenResponseData() === null) { + echo 'Could not make token request because authorization failed.' . "\n"; + die(); + } + + echo('Token response data: '); + echo $response->getTokenResponseData()->getTokenCode() . ' ' . + $response->getTokenResponseData()->getTokenMessage() . + ' Token:' . $response->getTokenHash(); } catch (ClientException $exception) { echo $exception->getErrorMessage(); } diff --git a/examplesV4/tokenCreationAndPaymentExample.php b/examplesV4/tokenCreationAndPaymentExample.php new file mode 100644 index 00000000..2e4310a2 --- /dev/null +++ b/examplesV4/tokenCreationAndPaymentExample.php @@ -0,0 +1,249 @@ +withBackRef('http://path/to/your/returnUrlScript') + ->withOrderRef('MerchantOrderRef') + ->withCurrency('RON') + ->withOrderDate(gmdate('Y-m-d\TH:i:sP')) + ->withOrderTimeout(1000) + ->withPayMethod('CCVISAMC'); + +/** + * Create new product + */ +$product = new Product(); + +/** + * Setup the product params + * + * Full params available in the documentation + */ +$product->withCode('PCODE01') + ->withName('PNAME01') + ->withPrice(100.0) + ->withVAT(24.0) + ->withQuantity(1); + +/** + * Add the product to the order + */ +$order->addProduct($product); + +/** + * Create another product + */ +$product = new Product(); + +/** + * Setup the product params + * + * Full params available in the documentation + */ +$product->withCode('PCODE02') + ->withName('PNAME02') + ->withPrice(200.0) + ->withVAT(24.0) + ->withQuantity(1); + +/** + * Add the second product to the same order + */ +$order->addProduct($product); + +/** + * Create new billing address + */ +$billing = new Billing(); + +/** + * Setup the billing address params + * + * Full params available in the documentation + */ +$billing->withAddressLine1('Address1') + ->withAddressLine2('Address2') + ->withCity('City') + ->withCountryCode('RO') + ->withEmail('john.doe@mail.com') + ->withFirstName('FirstName') + ->withLastName('LastName') + ->withPhoneNumber('40123456789') + ->withIdentityCardNumber('111222'); + +/** + * Create new delivery address + * + * If you want to have the same delivery as billing, skip these two steps + * and pass the Billing $billing object to the request twice + */ +$delivery = new Delivery(); + +/** + * Setup the delivery address params + * + * Full params available in the documentation + */ +$delivery->withAddressLine1('Address1') + ->withAddressLine2('Address2') + ->withCity('City') + ->withCountryCode('RO') + ->withEmail('john.doe@mail.com') + ->withFirstName('FirstName') + ->withLastName('LastName') + ->withPhoneNumber('40123456789'); + +/** + * Create new Card with params: + * + * Credit Card Number + * Credit Card Expiration Month + * Credit Card Expiration Year + * Credit Card CVV (Security Code) + * Credit Card Owner + */ +$card = new Card( + '4111111111111111', + '01', + date("Y", strtotime("+1 year")), + 123, + 'Card Owner Name' +); + +/** + * tokenize card for further token payments + */ +$card->enableTokenCreation(); + +/** + * Create new Request with params: + * + * Config object + * Order object + * Billing object + * Delivery (or Billing object again, if you want to have the delivery address the same as the billing address) + * User object + */ +$request = new Request($cfg, $order, $billing, $delivery, $user, PaymentsV4::API_VERSION_V4); +/** + * Add the Credit Card to the Request + */ +$request->setCard($card); + +/** + * Create new API Client, passing the Config object as parameter + */ +$client = new Client($cfg); + +/** + * Will throw different Exceptions on errors + */ +try { + /** + * Sends the Request to ALU and returns a Response + * + * See documentation for Response params + */ + $initialPaymentResponse = $client->pay($request); + + /** + * In case of 3DS enrolled cards, PayU will return the URL_3DS that contains a unique url for each + * transaction. The merchant must redirect the browser to this url to allow user to authenticate. + * After the authentication process ends the user will be redirected to BACK_REF url + * with payment result in a HTTP POST request + */ + if ($initialPaymentResponse->isThreeDs()) { + header("Location:" . $initialPaymentResponse->getThreeDsUrl()); + die(); + } + + echo $initialPaymentResponse->getStatus() . ' ' . + $initialPaymentResponse->getReturnCode() . ' ' . + $initialPaymentResponse->getReturnMessage() . + "\n"; + + if ($initialPaymentResponse->getTokenResponseData() === null) { + echo 'Could not make token request because authorization failed.' . "\n"; + die(); + } + + echo 'Token response data: '; + echo $initialPaymentResponse->getTokenResponseData()->getTokenCode() . ' ' . + $initialPaymentResponse->getTokenResponseData()->getTokenMessage() . + ' Token:' . $initialPaymentResponse->getTokenHash() . + "\n"; + + if ($initialPaymentResponse->getTokenResponseData()->getTokenCode() == 0 && + $initialPaymentResponse->getTokenHash() !== '' + ) { + $cardToken = new CardToken($initialPaymentResponse->getTokenHash()); + + // Create a new request using the token received + $requestWithToken = new Request( + $cfg, + $order, + $billing, + $delivery, + $user, + PaymentsV4::API_VERSION_V4 + ); + $requestWithToken->getOrder()->withOrderRef('TokenOrderRef'); + $requestWithToken->setCardToken($cardToken); + + $tokenPaymentResponse = $client->pay($requestWithToken); + + echo $tokenPaymentResponse->getStatus() . ' ' . + $tokenPaymentResponse->getReturnCode() . ' ' . + $tokenPaymentResponse->getReturnMessage() . + "\n"; + } else { + echo "No token received"; + } +} catch (ClientException $exception) { + echo $exception->getErrorMessage(); +} diff --git a/examplesV4/tokenPayment.php b/examplesV4/tokenPayment.php new file mode 100644 index 00000000..13ffb803 --- /dev/null +++ b/examplesV4/tokenPayment.php @@ -0,0 +1,193 @@ +withBackRef('http://path/to/your/returnUrlScript') + ->withOrderRef('MerchantOrderRef') + ->withCurrency('RON') + ->withOrderDate(gmdate('Y-m-d\TH:i:sP')) + ->withOrderTimeout(1000) + ->withPayMethod('CCVISAMC'); + +/** + * Create new product + */ +$product = new Product(); + +/** + * Setup the product params + * + * Full params available in the documentation + */ +$product->withCode('PCODE01') + ->withName('PNAME01') + ->withPrice(100.0) + ->withVAT(24.0) + ->withQuantity(1); + +/** + * Add the product to the order + */ +$order->addProduct($product); + +/** + * Create another product + */ +$product = new Product(); + +/** + * Setup the product params + * + * Full params available in the documentation + */ +$product->withCode('PCODE02') + ->withName('PNAME02') + ->withPrice(200.0) + ->withVAT(24.0) + ->withQuantity(1); + +/** + * Add the second product to the same order + */ +$order->addProduct($product); + +/** + * Create new billing address + */ +$billing = new Billing(); + +/** + * Setup the billing address params + * + * Full params available in the documentation + */ +$billing->withAddressLine1('Address1') + ->withAddressLine2('Address2') + ->withCity('City') + ->withCountryCode('RO') + ->withEmail('john.doe@mail.com') + ->withFirstName('FirstName') + ->withLastName('LastName') + ->withPhoneNumber('40123456789') + ->withIdentityCardNumber('111222'); + +/** + * Create new delivery address + * + * If you want to have the same delivery as billing, skip these two steps + * and pass the Billing $billing object to the request twice + */ +$delivery = new Delivery(); + +/** + * Setup the delivery address params + * + * Full params available in the documentation + */ +$delivery->withAddressLine1('Address1') + ->withAddressLine2('Address2') + ->withCity('City') + ->withCountryCode('RO') + ->withEmail('john.doe@mail.com') + ->withFirstName('FirstName') + ->withLastName('LastName') + ->withPhoneNumber('40123456789'); + +/** + * Create new CardToken with params: + * + * Token + */ +$cardToken = new CardToken("9964cc6183134b2b796bcead87aa14ab"); + +/** + * Create new Request with params: + * + * Config object + * Order object + * Billing object + * Delivery (or Billing object again, if you want to have the delivery address the same as the billing address) + * User object + */ +$request = new Request($cfg, $order, $billing, $delivery, $user, PaymentsV4::API_VERSION_V4); + +/** + * Add the Card Token to the Request + */ +$request->setCardToken($cardToken); + +/** + * Create new API Client, passing the Config object as parameter + */ +$client = new Client($cfg); + +/** + * Will throw different Exceptions on errors + */ +try { + /** + * Sends the Request to ALU and returns a Response + * + * See documentation for Response params + */ + + $response = $client->pay($request); + + /** + * In case of 3DS enrolled cards, PayU will return the URL_3DS that contains a unique url for each + * transaction. The merchant must redirect the browser to this url to allow user to authenticate. + * After the authentication process ends the user will be redirected to BACK_REF url + * with payment result in a HTTP POST request + */ + if ($response->isThreeDs()) { + header("Location:" . $response->getThreeDsUrl()); + die(); + } + + echo $response->getReturnMessage() . ' ' . $response->getRefno(); +} catch (ClientException $exception) { + echo $exception->getErrorMessage(); +} diff --git a/src/PayU/Alu/Response.php b/src/PayU/Alu/Response.php index 5413cdf8..d5662c4b 100644 --- a/src/PayU/Alu/Response.php +++ b/src/PayU/Alu/Response.php @@ -119,6 +119,25 @@ class Response */ private $type; + /** @var TokenResponseData */ + private $tokenResponseData; + + /** + * @return TokenResponseData + */ + public function getTokenResponseData() + { + return $this->tokenResponseData; + } + + /** + * @param TokenResponseData $tokenResponseData + */ + public function setTokenResponseData($tokenResponseData) + { + $this->tokenResponseData = $tokenResponseData; + } + /** * @param string $type */ diff --git a/src/PayU/Alu/TokenResponseData.php b/src/PayU/Alu/TokenResponseData.php new file mode 100644 index 00000000..615768fc --- /dev/null +++ b/src/PayU/Alu/TokenResponseData.php @@ -0,0 +1,43 @@ +tokenCode = $tokenCode; + $this->tokenMessage = $tokenMessage; + } + + /** + * @return int + */ + public function getTokenCode() + { + return $this->tokenCode; + } + + /** + * @return string + */ + public function getTokenMessage() + { + return $this->tokenMessage; + } +} diff --git a/src/PayU/PaymentsApi/PaymentsV4/PaymentsV4.php b/src/PayU/PaymentsApi/PaymentsV4/PaymentsV4.php index 03522ae8..e4817d56 100644 --- a/src/PayU/PaymentsApi/PaymentsV4/PaymentsV4.php +++ b/src/PayU/PaymentsApi/PaymentsV4/PaymentsV4.php @@ -3,6 +3,7 @@ namespace PayU\PaymentsApi\PaymentsV4; use PayU\Alu\Request; +use PayU\Alu\Response; use PayU\PaymentsApi\Exceptions\AuthorizationException; use PayU\PaymentsApi\Interfaces\AuthorizationPaymentsApiClient; use PayU\PaymentsApi\PaymentsV4\Exceptions\ConnectionException; @@ -16,6 +17,8 @@ class PaymentsV4 implements AuthorizationPaymentsApiClient { const PAYMENTS_API_AUTHORIZE_PATH = '/api/v4/payments/authorize'; + const BASE_CREATE_TOKEN_PATH = '/order/token/v2/merchantToken'; + const API_VERSION_V4 = 'v4'; /** @@ -38,6 +41,13 @@ class PaymentsV4 implements AuthorizationPaymentsApiClient */ private $responseBuilder; + /** @var array */ + private $platformHostname = [ + 'ro' => 'https://secure.payu.ro', + 'ru' => 'https://secure.payu.ru', + 'tr' => 'https://secure.payu.com.tr', + ]; + /** * PaymentsV4 constructor. * @throws AuthorizationException @@ -57,19 +67,25 @@ public function __construct() */ private function getPaymentsUrl($country) { - $platformHostname = [ - 'ro' => 'https://secure.payu.ro', - 'ru' => 'https://secure.payu.ru', - 'ua' => 'https://secure.payu.ua', - 'hu' => 'https://secure.payu.hu', - 'tr' => 'https://secure.payu.com.tr', - ]; - - if (!isset($platformHostname[$country])) { + if (!isset($this->platformHostname[$country])) { throw new AuthorizationException('Invalid platform'); } - return $platformHostname[$country] . self::PAYMENTS_API_AUTHORIZE_PATH; + return $this->platformHostname[$country] . self::PAYMENTS_API_AUTHORIZE_PATH; + } + + /** + * @param string $country + * @return string + * @throws AuthorizationException + */ + private function getTokenUrl($country) + { + if (!isset($this->platformHostname[$country])) { + throw new AuthorizationException('Invalid platform'); + } + + return $this->platformHostname[$country] . self::BASE_CREATE_TOKEN_PATH; } /** @@ -100,6 +116,51 @@ public function authorize(Request $request) throw new AuthorizationException($e->getMessage(), $e->getCode(), $e); } - return $this->responseBuilder->buildResponse($authorizationResponse); + $response = $this->responseBuilder->buildResponse($authorizationResponse); + + if (($response->getCode() === 200 || $response->getCode() === 202) + && $request->getCard() !== null + && $request->getCard()->isEnableTokenCreation() + ) { + return $this->makeTokenCreationRequest($request, $response); + } + + return $response; + } + + /** + * @param Request $request + * @param Response $response + * @return Response + * @throws AuthorizationException + */ + private function makeTokenCreationRequest(Request $request, Response $response) + { + try { + $tokenRequest = $this->requestBuilder->buildTokenRequestBody( + $request->getMerchantConfig()->getMerchantCode(), + $response->getRefno() + ); + } catch (\Exception $e) { + throw new AuthorizationException($e->getMessage(), $e->getCode(), $e); + } + + try { + $responseJson = $this->httpClient->postTokenCreationRequest( + $this->getTokenUrl($request->getMerchantConfig()->getPlatform()), + $tokenRequest, + $request->getMerchantConfig()->getSecretKey() + ); + } catch (ConnectionException $e) { + throw new AuthorizationException($e->getMessage(), $e->getCode(), $e); + } + + try { + $authorizationResponse = $this->responseParser->parseTokenJsonResponse($responseJson); + } catch (AuthorizationResponseException $e) { + throw new AuthorizationException($e->getMessage(), $e->getCode(), $e); + } + + return $this->responseBuilder->buildTokenResponse($authorizationResponse, $response); } } diff --git a/src/PayU/PaymentsApi/PaymentsV4/Services/HTTPClient.php b/src/PayU/PaymentsApi/PaymentsV4/Services/HTTPClient.php index be8fde10..44c010f8 100644 --- a/src/PayU/PaymentsApi/PaymentsV4/Services/HTTPClient.php +++ b/src/PayU/PaymentsApi/PaymentsV4/Services/HTTPClient.php @@ -100,4 +100,42 @@ public function buildRequestHeaders($merchantCode, $orderDate, $signature) 'Content-Type: application/json;charset=utf-8' ]; } + + /** + * @param string $url + * @param array $requestBody + * @param string $secretKey + * @return string + * @throws ConnectionException + */ + public function postTokenCreationRequest( + $url, + $requestBody, + $secretKey + ) { + $signature = $this->hashService->generateTokenSignature($requestBody, $secretKey); + $requestBody['signature'] = $signature; + + curl_setopt_array( + $this->handler, + array( + CURLOPT_URL => $url, + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => http_build_query($requestBody), + CURLOPT_HTTPHEADER => array('Content-Type: application/x-www-form-urlencoded') + ) + ); + + $result = curl_exec($this->handler); + if (curl_errno($this->handler) > 0) { + throw new ConnectionException( + sprintf( + 'Curl error "%s" when accessing url: "%s"', + curl_error($this->handler), + $url + ) + ); + } + return $result; + } } diff --git a/src/PayU/PaymentsApi/PaymentsV4/Services/HashService.php b/src/PayU/PaymentsApi/PaymentsV4/Services/HashService.php index 20c66104..8b791c5b 100644 --- a/src/PayU/PaymentsApi/PaymentsV4/Services/HashService.php +++ b/src/PayU/PaymentsApi/PaymentsV4/Services/HashService.php @@ -26,4 +26,23 @@ public function generateSignature($merchantConfig, $orderDate, $jsonRequest) return hash_hmac("sha256", $stringToBeHashed, $merchantConfig->getSecretKey()); } + + /** + * @param array $requestArray + * @param string $secretKey + * @return string + */ + public function generateTokenSignature($requestArray, $secretKey) + { + ksort($requestArray); + $stringToBeHashed = ''; + foreach ($requestArray as $key => $val) { + if ($key !== 'timestamp') { + $stringToBeHashed = $stringToBeHashed . $val; + } + } + $stringToBeHashed = $stringToBeHashed . $requestArray['timestamp']; + + return hash_hmac("sha256", $stringToBeHashed, $secretKey); + } } diff --git a/src/PayU/PaymentsApi/PaymentsV4/Services/RequestBuilder.php b/src/PayU/PaymentsApi/PaymentsV4/Services/RequestBuilder.php index 222cb39d..2824a011 100644 --- a/src/PayU/PaymentsApi/PaymentsV4/Services/RequestBuilder.php +++ b/src/PayU/PaymentsApi/PaymentsV4/Services/RequestBuilder.php @@ -3,6 +3,7 @@ namespace PayU\PaymentsApi\PaymentsV4\Services; +use DateTime; use PayU\Alu\Product; use PayU\Alu\Request; use PayU\PaymentsApi\PaymentsV4\Entities\ApplePayToken; @@ -288,4 +289,21 @@ private function getFlightSegmentsArray($request) return $flightSegmentsArray; } + + /** + * @param string $merchantCode + * @param int $payuPaymentReference + * @return array + * @throws \Exception + */ + public function buildTokenRequestBody($merchantCode, $payuPaymentReference) + { + $date = new DateTime(); + + return [ + 'merchant' => $merchantCode, + 'refNo' => $payuPaymentReference, + 'timestamp' => strval($date->getTimestamp()) + ]; + } } diff --git a/src/PayU/PaymentsApi/PaymentsV4/Services/ResponseBuilder.php b/src/PayU/PaymentsApi/PaymentsV4/Services/ResponseBuilder.php index b9e51d02..4a709883 100644 --- a/src/PayU/PaymentsApi/PaymentsV4/Services/ResponseBuilder.php +++ b/src/PayU/PaymentsApi/PaymentsV4/Services/ResponseBuilder.php @@ -5,6 +5,7 @@ use PayU\Alu\Response; use PayU\Alu\ResponseWireAccount; +use PayU\Alu\TokenResponseData; use PayU\PaymentsApi\PaymentsV4\Entities\AuthorizationResponse; class ResponseBuilder @@ -98,4 +99,28 @@ private function getResponseWireAccount($account) return $responseWireAccount; } + + /** + * @param AuthorizationResponse $authorizationResponse + * @param Response $response + * @return Response + */ + public function buildTokenResponse( + AuthorizationResponse $authorizationResponse, + Response $response + ) { + $responseArray = $authorizationResponse->getResponse(); + + if ($responseArray["meta"]["status"]["code"] === 0) { + $response->setTokenHash($responseArray["response"]["token"]); + } + + $responseTokenData = new TokenResponseData( + $responseArray["meta"]["status"]["code"], + $responseArray["meta"]["status"]["message"] + ); + $response->setTokenResponseData($responseTokenData); + + return $response; + } } diff --git a/src/PayU/PaymentsApi/PaymentsV4/Services/ResponseParser.php b/src/PayU/PaymentsApi/PaymentsV4/Services/ResponseParser.php index a4291dc7..86be6be3 100644 --- a/src/PayU/PaymentsApi/PaymentsV4/Services/ResponseParser.php +++ b/src/PayU/PaymentsApi/PaymentsV4/Services/ResponseParser.php @@ -42,4 +42,20 @@ private function createArray($jsonResponse) { return $this->aluResponseMapper->processResponse($jsonResponse); } + + /** + * @param string $jsonResponse + * @return AuthorizationResponse + * @throws AuthorizationResponseException + */ + public function parseTokenJsonResponse($jsonResponse) + { + $responseArray = json_decode($jsonResponse, true); + + if (is_array($responseArray)) { + return new AuthorizationResponse($responseArray); + } + + throw new AuthorizationResponseException('Could not decode Json response'); + } } diff --git a/tests/PayU/PaymentsApi/PaymentsV4/PaymentsV4Test.php b/tests/PayU/PaymentsApi/PaymentsV4/PaymentsV4Test.php index d2be2590..1853f889 100644 --- a/tests/PayU/PaymentsApi/PaymentsV4/PaymentsV4Test.php +++ b/tests/PayU/PaymentsApi/PaymentsV4/PaymentsV4Test.php @@ -2,6 +2,7 @@ namespace PayU\PaymentsApi\PaymentsV4; +use Exception; use PayU\Alu\Billing; use PayU\Alu\Card; use PayU\Alu\Delivery; @@ -10,6 +11,7 @@ use PayU\Alu\Product; use PayU\Alu\Request; use PayU\Alu\Response; +use PayU\Alu\TokenResponseData; use PayU\Alu\User; use PayU\PaymentsApi\PaymentsV4\Entities\AuthorizationResponse; use PayU\PaymentsApi\PaymentsV4\Exceptions\AuthorizationResponseException; @@ -56,22 +58,22 @@ public function setUp() { $this->mockRequestBuilder = $this->getMockBuilder(RequestBuilder::class) ->disableOriginalConstructor() - ->setMethods(array('buildAuthorizationRequest')) + ->setMethods(array('buildAuthorizationRequest', 'buildTokenRequestBody')) ->getMock(); $this->mockResponseParser = $this->getMockBuilder(ResponseParser::class) ->disableOriginalConstructor() - ->setMethods(array('parseJsonResponse')) + ->setMethods(array('parseJsonResponse', 'parseTokenJsonResponse')) ->getMock(); $this->mockResponseBuilder = $this->getMockBuilder(ResponseBuilder::class) ->disableOriginalConstructor() - ->setMethods(array('buildResponse')) + ->setMethods(array('buildResponse', 'buildTokenResponse')) ->getMock(); $this->mockHttpClient = $this->getMockBuilder(HTTPClient::class) ->disableOriginalConstructor() - ->setMethods(array('post')) + ->setMethods(array('post', 'postTokenCreationRequest')) ->getMock(); $this->paymentsV4 = new PaymentsV4(); @@ -103,7 +105,7 @@ private function addMockObjectsToPaymentsV4() */ private function createAluRequest() { - $cfg = new MerchantConfig('PAYU_2', 'SECRET_KEY', 'RO'); + $cfg = new MerchantConfig('MERCHANT_CODE', 'SECRET_KEY', 'RO'); $user = new User('127.0.0.1'); @@ -167,6 +169,74 @@ private function createAluRequest() return $request; } + + private function createAluRequestWithTokenCreation() + { + $cfg = new MerchantConfig('MERCHANT_CODE', 'SECRET_KEY', 'RO'); + + $user = new User('127.0.0.1'); + + $order = new Order(); + + $order->withBackRef('http://path/to/your/returnUrlScript') + ->withOrderRef(self::MERCHANT_PAYMENT_REFERENCE) + ->withCurrency('RON') + ->withOrderDate(self::ORDER_DATE) + ->withOrderTimeout(1000) + ->withPayMethod('CCVISAMC'); + + $product = new Product(); + $product->withCode('PCODE01') + ->withName('PNAME01') + ->withPrice(100.0) + ->withVAT(24.0) + ->withQuantity(1); + + $order->addProduct($product); + + $product = new Product(); + $product->withCode('PCODE02') + ->withName('PNAME02') + ->withPrice(200.0) + ->withVAT(24.0) + ->withQuantity(1); + + $order->addProduct($product); + + $billing = new Billing(); + + $billing->withAddressLine1('Address1') + ->withAddressLine2('Address2') + ->withCity('City') + ->withCountryCode('RO') + ->withEmail('john.doe@mail.com') + ->withFirstName('FirstName') + ->withLastName('LastName') + ->withPhoneNumber('40123456789') + ->withIdentityCardNumber('111222') + ->withIdentityCardType(null); + + $delivery = new Delivery(); + $delivery->withAddressLine1('Address1') + ->withAddressLine2('Address2') + ->withCity('City') + ->withCountryCode('RO') + ->withEmail('john.doe@mail.com') + ->withFirstName('FirstName') + ->withLastName('LastName') + ->withPhoneNumber('40123456789'); + + + $card = new Card('4111111111111111', '01', '2026', 123, 'Card Owner Name'); + $card->enableTokenCreation(); + + $request = new Request($cfg, $order, $billing, $delivery, $user, PaymentsV4::API_VERSION_V4); + + $request->setCard($card); + + return $request; + } + private function createJsonRequest() { $requestArray = [ @@ -302,6 +372,57 @@ private function createAluResponse() return $response; } + private function createTokenRequestArray() + { + return [ + 'merchant' => 'MERCHANT_CODE', + 'refNo' => self::PAYU_PAYMENT_REFERENCE, + 'timestamp' => '1428045257' + ]; + } + + private function createTokenJsonResponse() + { + return '{ + "meta":{ + "status":{ + "code":0, + "message":"success" + }, + "response":{ + "httpCode":200, + "httpMessage":"200 OK" + }, + "version":"v2" + }, + "response":{ + "token":"b7e5d8649c9e2e75726b59c56c29e91d", + "cardUniqueIdentifier":"e9fc5107db302fa8373efbedf55a1614b5a3125ee59fe274e7dc802930d68f6d" + } + '; + } + + private function createTokenArrayResponse() + { + return [ + 'meta' => [ + 'status' => [ + "code" => 0, + "message" => "success" + ], + 'response' => [ + "httpCode" => 200, + "httpMessage" => "200 OK" + ], + 'version' => 'v2' + ], + 'response' => [ + "token" => "b7e5d8649c9e2e75726b59c56c29e91d", + "cardUniqueIdentifier" => "e9fc5107db302fa8373efbedf55a1614b5a3125ee59fe274e7dc802930d68f6d" + ] + ]; + } + public function testAuthorize() { // Given @@ -414,4 +535,140 @@ public function testResponseParserThrowsAuthorizationResponseException() $this->paymentsV4->authorize($aluRequest); } + + public function testAuthorizeWithCreateToken() + { + // Given + $aluRequest = $this->createAluRequestWithTokenCreation(); + $jsonRequest = $this->createJsonRequest(); + $jsonResponse = $this->createJsonResponse(); + $arrayResponse = $this->createArrayResponse(); + $aluResponse = $this->createAluResponse(); + + $tokenRequest = $this->createTokenRequestArray(); + $tokenJsonResponse = $this->createTokenJsonResponse(); + $tokenArrayResponse = $this->createTokenArrayResponse(); + $aluResponseToken = $this->createAluResponse(); + + $tokenResponseData = new TokenResponseData(0, 'success'); + $aluResponseToken->setTokenResponseData($tokenResponseData); + $aluResponseToken->setTokenHash('b7e5d8649c9e2e75726b59c56c29e91d'); + + // When + $this->addMockObjectsToPaymentsV4(); + + $this->mockRequestBuilder->expects($this->once()) + ->method('buildAuthorizationRequest') + ->with($aluRequest) + ->willReturn($jsonRequest); + + $this->mockHttpClient->expects($this->once()) + ->method('post') + ->with( + self::PAYMENTS_URL . PaymentsV4::PAYMENTS_API_AUTHORIZE_PATH, + $aluRequest->getMerchantConfig(), + self::ORDER_DATE, + $jsonRequest + ) + ->willReturn($jsonResponse); + + $authorizationResponse = new AuthorizationResponse($arrayResponse); + + $this->mockResponseParser->expects($this->once()) + ->method('parseJsonResponse') + ->with($jsonResponse) + ->willReturn($authorizationResponse); + + $this->mockResponseBuilder->expects($this->once()) + ->method('buildResponse') + ->with($authorizationResponse) + ->willReturn($aluResponse); + + // Begin token flow + + $this->mockRequestBuilder->expects($this->once()) + ->method('buildTokenRequestBody') + ->with($aluRequest->getMerchantConfig()->getMerchantCode(), $aluResponse->getRefno()) + ->willReturn($tokenRequest); + + $this->mockHttpClient->expects($this->once()) + ->method('postTokenCreationRequest') + ->with( + self::PAYMENTS_URL . PaymentsV4::BASE_CREATE_TOKEN_PATH, + $tokenRequest, + $aluRequest->getMerchantConfig()->getSecretKey() + ) + ->willReturn($tokenJsonResponse); + + $authorizationTokenResponse = new AuthorizationResponse($tokenArrayResponse); + + $this->mockResponseParser->expects($this->once()) + ->method('parseTokenJsonResponse') + ->with($tokenJsonResponse) + ->willReturn($authorizationTokenResponse); + + $this->mockResponseBuilder->expects($this->once()) + ->method('buildTokenResponse') + ->with($authorizationTokenResponse, $aluResponse) + ->willReturn($aluResponseToken); + + $actualResponse = $this->paymentsV4->authorize($aluRequest); + + // Then + $this->assertInstanceOf(Response::class, $actualResponse); + $this->assertEquals($aluResponseToken, $actualResponse); + } + + /** + * @expectedException \PayU\PaymentsApi\Exceptions\AuthorizationException + */ + public function testRequestBuilderThrowsException() + { + // Given + $aluRequest = $this->createAluRequestWithTokenCreation(); + $jsonRequest = $this->createJsonRequest(); + $jsonResponse = $this->createJsonResponse(); + $arrayResponse = $this->createArrayResponse(); + $aluResponse = $this->createAluResponse(); + + // When + $this->addMockObjectsToPaymentsV4(); + + $this->mockRequestBuilder->expects($this->once()) + ->method('buildAuthorizationRequest') + ->with($aluRequest) + ->willReturn($jsonRequest); + + $this->mockHttpClient->expects($this->once()) + ->method('post') + ->with( + self::PAYMENTS_URL . PaymentsV4::PAYMENTS_API_AUTHORIZE_PATH, + $aluRequest->getMerchantConfig(), + self::ORDER_DATE, + $jsonRequest + ) + ->willReturn($jsonResponse); + + $authorizationResponse = new AuthorizationResponse($arrayResponse); + + $this->mockResponseParser->expects($this->once()) + ->method('parseJsonResponse') + ->with($jsonResponse) + ->willReturn($authorizationResponse); + + $this->mockResponseBuilder->expects($this->once()) + ->method('buildResponse') + ->with($authorizationResponse) + ->willReturn($aluResponse); + + // Begin token flow + + $this->mockRequestBuilder->expects($this->once()) + ->method('buildTokenRequestBody') + ->with($aluRequest->getMerchantConfig()->getMerchantCode(), $aluResponse->getRefno()) + ->willThrowException(new Exception()); + + // Then + $this->paymentsV4->authorize($aluRequest); + } } diff --git a/tests/PayU/PaymentsApi/PaymentsV4/Services/HashServiceTest.php b/tests/PayU/PaymentsApi/PaymentsV4/Services/HashServiceTest.php index 2c2ea8db..0f9122af 100644 --- a/tests/PayU/PaymentsApi/PaymentsV4/Services/HashServiceTest.php +++ b/tests/PayU/PaymentsApi/PaymentsV4/Services/HashServiceTest.php @@ -80,6 +80,22 @@ public function generateSignatureProvider() ); } + public function generateTokenSignatureProvider() + { + return [ + [ + [ + 'merchant' => 'CC921', + 'refNo' => 12039391, + 'timestamp' => 1428045257 + ], + 'secretKey' => 'SECRET_KEY', + 'expectedHash' => 'c211b9fc82bc10fb9d104ba3c756bbb0e61eddb3bd26303b9e4109f271c7059e' + ] + + ]; + } + /** * @dataProvider generateSignatureProvider */ @@ -101,4 +117,16 @@ public function testGenerateSignature($orderDate, $jsonRequest, $expectedHash) $this->hashService->generateSignature($this->merchantConfigMock, $orderDate, $jsonRequest) ); } + + /** + * @dataProvider generateTokenSignatureProvider + */ + public function testGenerateTokenSignature($requestArray, $secretKey, $expectedHash) + { + // Then + $this->assertEquals( + $expectedHash, + $this->hashService->generateTokenSignature($requestArray, $secretKey) + ); + } } diff --git a/tests/PayU/PaymentsApi/PaymentsV4/Services/ResponseBuilderTest.php b/tests/PayU/PaymentsApi/PaymentsV4/Services/ResponseBuilderTest.php new file mode 100644 index 00000000..55c41786 --- /dev/null +++ b/tests/PayU/PaymentsApi/PaymentsV4/Services/ResponseBuilderTest.php @@ -0,0 +1,98 @@ +responseBuilder = new ResponseBuilder(); + } + + private function createSuccessResponseArray() + { + return [ + 'meta' => [ + 'status' => [ + "code" => 0, + "message" => "success" + ], + 'response' => [ + "httpCode" => 200, + "httpMessage" => "200 OK" + ], + 'version' => 'v2' + ], + 'response' => [ + "token" => "b7e5d8649c9e2e75726b59c56c29e91d", + "cardUniqueIdentifier" => "e9fc5107db302fa8373efbedf55a1614b5a3125ee59fe274e7dc802930d68f6d" + ] + ]; + } + + private function createErrorResponseArray() + { + return [ + 'meta' => [ + 'status' => [ + "code" => 400, + "message" => "No order with reference number: 120393911" + ], + 'response' => [ + "httpCode" => 400, + "httpMessage" => "400 Bad Request" + ], + 'version' => 'v2' + ], + 'error' => [ + "code" => 400, + "message" => "No order with reference number: 120393911" + ] + ]; + } + + public function testBuildTokenResponseWithSuccessResponse() + { + // Given + $authorizationResponse = new AuthorizationResponse($this->createSuccessResponseArray()); + $expectedResponse = new Response(); + $tokenResponseData = new TokenResponseData(0, 'success'); + $expectedResponse->setTokenResponseData($tokenResponseData); + $expectedResponse->setTokenHash('b7e5d8649c9e2e75726b59c56c29e91d'); + + // Then + $this->assertEquals( + $expectedResponse, + $this->responseBuilder->buildTokenResponse($authorizationResponse, new Response()) + ); + } + + public function testBuildTokenResponseWithErrorResponse() + { + // Given + $authorizationResponse = new AuthorizationResponse($this->createErrorResponseArray()); + $expectedResponse = new Response(); + $tokenResponseData = new TokenResponseData( + 400, + 'No order with reference number: 120393911' + ); + $expectedResponse->setTokenResponseData($tokenResponseData); + + // Then + $this->assertEquals( + $expectedResponse, + $this->responseBuilder->buildTokenResponse($authorizationResponse, new Response()) + ); + } +}