<?php /** * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Authorizenet\Test\Unit\Model; use Magento\Sales\Api\PaymentFailuresInterface; use Magento\Framework\Simplexml\Element; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; use Magento\Authorizenet\Model\Directpost; use Magento\Authorizenet\Model\TransactionService; use Magento\Authorizenet\Model\Request; use Magento\Authorizenet\Model\Directpost\Request\Factory; use Magento\Sales\Model\Order; use Magento\Sales\Model\Order\Payment\Transaction\Repository as TransactionRepository; /** * Class DirectpostTest * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class DirectpostTest extends \PHPUnit\Framework\TestCase { const TOTAL_AMOUNT = 100.02; const INVOICE_NUM = '00000001'; const TRANSACTION_ID = '41a23x34fd124'; /** * @var \Magento\Authorizenet\Model\Directpost */ protected $directpost; /** * @var \Magento\Framework\App\Config\ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject */ protected $scopeConfigMock; /** * @var \Magento\Payment\Model\InfoInterface|\PHPUnit_Framework_MockObject_MockObject */ protected $paymentMock; /** * @var \Magento\Authorizenet\Helper\Data|\PHPUnit_Framework_MockObject_MockObject */ protected $dataHelperMock; /** * @var \Magento\Authorizenet\Model\Directpost\Response\Factory|\PHPUnit_Framework_MockObject_MockObject */ protected $responseFactoryMock; /** * @var TransactionRepository|\PHPUnit_Framework_MockObject_MockObject */ protected $transactionRepositoryMock; /** * @var \Magento\Authorizenet\Model\Directpost\Response|\PHPUnit_Framework_MockObject_MockObject */ protected $responseMock; /** * @var TransactionService|\PHPUnit_Framework_MockObject_MockObject */ protected $transactionServiceMock; /** * @var \Magento\Framework\HTTP\ZendClient|\PHPUnit_Framework_MockObject_MockObject */ protected $httpClientMock; /** * @var \Magento\Authorizenet\Model\Directpost\Request\Factory|\PHPUnit_Framework_MockObject_MockObject */ protected $requestFactory; /** * @var PaymentFailuresInterface|\PHPUnit_Framework_MockObject_MockObject */ private $paymentFailures; /** * @inheritdoc */ protected function setUp() { $this->scopeConfigMock = $this->getMockBuilder(\Magento\Framework\App\Config\ScopeConfigInterface::class) ->getMock(); $this->paymentMock = $this->getMockBuilder(\Magento\Sales\Model\Order\Payment::class) ->disableOriginalConstructor() ->setMethods([ 'getOrder', 'getId', 'setAdditionalInformation', 'getAdditionalInformation', 'setIsTransactionDenied', 'setIsTransactionClosed', 'decrypt', 'getCcLast4', 'getParentTransactionId', 'getPoNumber' ]) ->getMock(); $this->dataHelperMock = $this->getMockBuilder(\Magento\Authorizenet\Helper\Data::class) ->disableOriginalConstructor() ->getMock(); $this->initResponseFactoryMock(); $this->transactionRepositoryMock = $this->getMockBuilder( \Magento\Sales\Model\Order\Payment\Transaction\Repository::class ) ->disableOriginalConstructor() ->setMethods(['getByTransactionId']) ->getMock(); $this->transactionServiceMock = $this->getMockBuilder(\Magento\Authorizenet\Model\TransactionService::class) ->disableOriginalConstructor() ->setMethods(['getTransactionDetails']) ->getMock(); $this->paymentFailures = $this->getMockBuilder( PaymentFailuresInterface::class ) ->disableOriginalConstructor() ->getMock(); $this->requestFactory = $this->getRequestFactoryMock(); $httpClientFactoryMock = $this->getHttpClientFactoryMock(); $helper = new ObjectManagerHelper($this); $this->directpost = $helper->getObject( \Magento\Authorizenet\Model\Directpost::class, [ 'scopeConfig' => $this->scopeConfigMock, 'dataHelper' => $this->dataHelperMock, 'requestFactory' => $this->requestFactory, 'responseFactory' => $this->responseFactoryMock, 'transactionRepository' => $this->transactionRepositoryMock, 'transactionService' => $this->transactionServiceMock, 'httpClientFactory' => $httpClientFactoryMock, 'paymentFailures' => $this->paymentFailures, ] ); } public function testGetConfigInterface() { $this->assertInstanceOf( \Magento\Payment\Model\Method\ConfigInterface::class, $this->directpost->getConfigInterface() ); } public function testGetConfigValue() { $field = 'some_field'; $returnValue = 'expected'; $this->scopeConfigMock->expects($this->once()) ->method('getValue') ->with('payment/authorizenet_directpost/' . $field) ->willReturn($returnValue); $this->assertEquals($returnValue, $this->directpost->getValue($field)); } public function testSetDataHelper() { $storeId = 'store-id'; $expectedResult = 'relay-url'; $helperDataMock = $this->getMockBuilder(\Magento\Authorizenet\Helper\Backend\Data::class) ->disableOriginalConstructor() ->getMock(); $helperDataMock->expects($this->once()) ->method('getRelayUrl') ->with($storeId) ->willReturn($expectedResult); $this->directpost->setDataHelper($helperDataMock); $this->assertEquals($expectedResult, $this->directpost->getRelayUrl($storeId)); } public function testAuthorize() { $paymentAction = 'some_action'; $this->scopeConfigMock->expects($this->any()) ->method('getValue') ->with('payment/authorizenet_directpost/payment_action', 'store', null) ->willReturn($paymentAction); $this->paymentMock->expects($this->once()) ->method('setAdditionalInformation') ->with('payment_type', $paymentAction); $this->directpost->authorize($this->paymentMock, 10); } public function testGetCgiUrl() { $url = 'cgi/url'; $this->scopeConfigMock->expects($this->any()) ->method('getValue') ->with('payment/authorizenet_directpost/cgi_url', 'store', null) ->willReturn($url); $this->assertEquals($url, $this->directpost->getCgiUrl()); } public function testGetCgiUrlWithEmptyConfigValue() { $this->scopeConfigMock->expects($this->any()) ->method('getValue') ->with('payment/authorizenet_directpost/cgi_url', 'store', null) ->willReturn(null); $this->assertEquals(Directpost::CGI_URL, $this->directpost->getCgiUrl()); } public function testGetRelayUrl() { $storeId = 100; $url = 'relay/url'; $this->directpost->setData('store', $storeId); $this->dataHelperMock->expects($this->any()) ->method('getRelayUrl') ->with($storeId) ->willReturn($url); $this->assertEquals($url, $this->directpost->getRelayUrl()); $this->assertEquals($url, $this->directpost->getRelayUrl($storeId)); } public function testGetResponse() { $this->assertSame($this->responseMock, $this->directpost->getResponse()); } public function testSetResponseData() { $data = [ 'key' => 'value' ]; $this->responseMock->expects($this->once()) ->method('setData') ->with($data) ->willReturnSelf(); $this->assertSame($this->directpost, $this->directpost->setResponseData($data)); } public function testValidateResponseSuccess() { $this->prepareTestValidateResponse('some_md5', 'login', true); $this->assertEquals(true, $this->directpost->validateResponse()); } /** * @expectedException \Magento\Framework\Exception\LocalizedException */ public function testValidateResponseFailure() { $this->prepareTestValidateResponse('some_md5', 'login', false); $this->directpost->validateResponse(); } /** * @param string $transMd5 * @param string $login * @param bool $isValidHash */ protected function prepareTestValidateResponse($transMd5, $login, $isValidHash) { $this->scopeConfigMock->expects($this->any()) ->method('getValue') ->willReturnMap( [ ['payment/authorizenet_directpost/trans_md5', 'store', null, $transMd5], ['payment/authorizenet_directpost/login', 'store', null, $login] ] ); $this->responseMock->expects($this->any()) ->method('isValidHash') ->with($transMd5, $login) ->willReturn($isValidHash); } public function testCheckTransIdSuccess() { $this->responseMock->expects($this->once()) ->method('getXTransId') ->willReturn('111'); $this->assertEquals(true, $this->directpost->checkTransId()); } /** * @expectedException \Magento\Framework\Exception\LocalizedException */ public function testCheckTransIdFailure() { $this->responseMock->expects($this->once()) ->method('getXTransId') ->willReturn(null); $this->directpost->checkTransId(); } /** * @param bool $responseCode * * @dataProvider checkResponseCodeSuccessDataProvider */ public function testCheckResponseCodeSuccess($responseCode) { $this->responseMock->expects($this->once()) ->method('getXResponseCode') ->willReturn($responseCode); $this->assertEquals(true, $this->directpost->checkResponseCode()); } /** * @return array */ public function checkResponseCodeSuccessDataProvider() { return [ ['responseCode' => Directpost::RESPONSE_CODE_APPROVED], ['responseCode' => Directpost::RESPONSE_CODE_HELD] ]; } /** * Checks response failures behaviour. * * @param int $responseCode * @param int $failuresHandlerCalls * @return void * * @expectedException \Magento\Framework\Exception\LocalizedException * @dataProvider checkResponseCodeFailureDataProvider */ public function testCheckResponseCodeFailure(int $responseCode, int $failuresHandlerCalls): void { $reasonText = 'reason text'; $this->responseMock->expects($this->once()) ->method('getXResponseCode') ->willReturn($responseCode); $this->responseMock->expects($this->any()) ->method('getXResponseReasonText') ->willReturn($reasonText); $this->dataHelperMock->expects($this->any()) ->method('wrapGatewayError') ->with($reasonText) ->willReturn(__('Gateway error: %1', $reasonText)); $orderMock = $this->getMockBuilder(Order::class) ->disableOriginalConstructor() ->getMock(); $orderMock->expects($this->exactly($failuresHandlerCalls)) ->method('getQuoteId') ->willReturn(1); $this->paymentFailures->expects($this->exactly($failuresHandlerCalls)) ->method('handle') ->with(1); $reflection = new \ReflectionClass($this->directpost); $order = $reflection->getProperty('order'); $order->setAccessible(true); $order->setValue($this->directpost, $orderMock); $this->directpost->checkResponseCode(); } /** * @return array */ public function checkResponseCodeFailureDataProvider(): array { return [ ['responseCode' => Directpost::RESPONSE_CODE_DECLINED, 1], ['responseCode' => Directpost::RESPONSE_CODE_ERROR, 1], ['responseCode' => 999999, 0], ]; } /** * @param bool $isInitializeNeeded * * @dataProvider setIsInitializeNeededDataProvider */ public function testSetIsInitializeNeeded($isInitializeNeeded) { $this->directpost->setIsInitializeNeeded($isInitializeNeeded); $this->assertEquals($isInitializeNeeded, $this->directpost->isInitializeNeeded()); } /** * @return array */ public function setIsInitializeNeededDataProvider() { return [ ['isInitializationNeeded' => true], ['isInitializationNeeded' => false] ]; } /** * @param bool $isGatewayActionsLocked * @param bool $canCapture * * @dataProvider canCaptureDataProvider */ public function testCanCapture($isGatewayActionsLocked, $canCapture) { $this->directpost->setData('info_instance', $this->paymentMock); $this->paymentMock->expects($this->any()) ->method('getAdditionalInformation') ->with(Directpost::GATEWAY_ACTIONS_LOCKED_STATE_KEY) ->willReturn($isGatewayActionsLocked); $this->assertEquals($canCapture, $this->directpost->canCapture()); } /** * @return array */ public function canCaptureDataProvider() { return [ ['isGatewayActionsLocked' => false, 'canCapture' => true], ['isGatewayActionsLocked' => true, 'canCapture' => false] ]; } /** * @covers \Magento\Authorizenet\Model\Directpost::fetchTransactionInfo * * @param $transactionId * @param $resultStatus * @param $responseStatus * @param $responseCode * @return void * * @dataProvider dataProviderTransaction */ public function testFetchVoidedTransactionInfo($transactionId, $resultStatus, $responseStatus, $responseCode) { $paymentId = 36; $orderId = 36; $this->paymentMock->expects(static::once()) ->method('getId') ->willReturn($paymentId); $orderMock = $this->getMockBuilder(\Magento\Sales\Model\Order::class) ->disableOriginalConstructor() ->setMethods(['getId', '__wakeup']) ->getMock(); $orderMock->expects(static::once()) ->method('getId') ->willReturn($orderId); $this->paymentMock->expects(static::once()) ->method('getOrder') ->willReturn($orderMock); $transactionMock = $this->getMockBuilder(\Magento\Sales\Model\Order\Payment\Transaction::class) ->disableOriginalConstructor() ->getMock(); $this->transactionRepositoryMock->expects(static::once()) ->method('getByTransactionId') ->with($transactionId, $paymentId, $orderId) ->willReturn($transactionMock); $document = $this->getTransactionXmlDocument( $transactionId, TransactionService::PAYMENT_UPDATE_STATUS_CODE_SUCCESS, $resultStatus, $responseStatus, $responseCode ); $this->transactionServiceMock->expects(static::once()) ->method('getTransactionDetails') ->with($this->directpost, $transactionId) ->willReturn($document); // transaction should be closed $this->paymentMock->expects(static::once()) ->method('setIsTransactionDenied') ->with(true); $this->paymentMock->expects(static::once()) ->method('setIsTransactionClosed') ->with(true); $transactionMock->expects(static::once()) ->method('close'); $this->directpost->fetchTransactionInfo($this->paymentMock, $transactionId); } /** * @covers \Magento\Authorizenet\Model\Directpost::refund() * @return void */ public function testSuccessRefund() { $card = 1111; $this->paymentMock->expects(static::exactly(2)) ->method('getCcLast4') ->willReturn($card); $this->paymentMock->expects(static::once()) ->method('decrypt') ->willReturn($card); $this->paymentMock->expects(static::exactly(3)) ->method('getParentTransactionId') ->willReturn(self::TRANSACTION_ID . '-capture'); $this->paymentMock->expects(static::once()) ->method('getPoNumber') ->willReturn(self::INVOICE_NUM); $this->paymentMock->expects(static::once()) ->method('setIsTransactionClosed') ->with(true) ->willReturnSelf(); $orderMock = $this->getOrderMock(); $this->paymentMock->expects(static::exactly(2)) ->method('getOrder') ->willReturn($orderMock); $transactionMock = $this->getMockBuilder(Order\Payment\Transaction::class) ->disableOriginalConstructor() ->setMethods(['getAdditionalInformation']) ->getMock(); $transactionMock->expects(static::once()) ->method('getAdditionalInformation') ->with(Directpost::REAL_TRANSACTION_ID_KEY) ->willReturn(self::TRANSACTION_ID); $this->transactionRepositoryMock->expects(static::once()) ->method('getByTransactionId') ->willReturn($transactionMock); $response = $this->getRefundResponseBody( Directpost::RESPONSE_CODE_APPROVED, Directpost::RESPONSE_REASON_CODE_APPROVED, 'Successful' ); $this->httpClientMock->expects(static::once()) ->method('getBody') ->willReturn($response); $this->responseMock->expects(static::once()) ->method('getXResponseCode') ->willReturn(Directpost::RESPONSE_CODE_APPROVED); $this->responseMock->expects(static::once()) ->method('getXResponseReasonCode') ->willReturn(Directpost::RESPONSE_REASON_CODE_APPROVED); $this->dataHelperMock->expects(static::never()) ->method('wrapGatewayError'); $this->directpost->refund($this->paymentMock, self::TOTAL_AMOUNT); } /** * Get data for tests * @return array */ public function dataProviderTransaction() { return [ [ 'transactionId' => '9941997799', 'resultStatus' => 'Successful.', 'responseStatus' => 'voided', 'responseCode' => 1 ] ]; } /** * Create mock for response factory * @return void */ private function initResponseFactoryMock() { $this->responseFactoryMock = $this->getMockBuilder( \Magento\Authorizenet\Model\Directpost\Response\Factory::class )->disableOriginalConstructor()->getMock(); $this->responseMock = $this->getMockBuilder(\Magento\Authorizenet\Model\Directpost\Response::class) ->setMethods( [ 'isValidHash', 'getXTransId', 'getXResponseCode', 'getXResponseReasonCode', 'getXResponseReasonText', 'getXAmount', 'setXResponseCode', 'setXResponseReasonCode', 'setXAvsCode', 'setXResponseReasonText', 'setXTransId', 'setXInvoiceNum', 'setXAmount', 'setXMethod', 'setXType', 'setData', 'setXAccountNumber', '__wakeup' ] ) ->disableOriginalConstructor() ->getMock(); $this->responseMock->expects(static::any()) ->method('setXResponseCode') ->willReturnSelf(); $this->responseMock->expects(static::any()) ->method('setXResponseReasonCode') ->willReturnSelf(); $this->responseMock->expects(static::any()) ->method('setXResponseReasonText') ->willReturnSelf(); $this->responseMock->expects(static::any()) ->method('setXAvsCode') ->willReturnSelf(); $this->responseMock->expects(static::any()) ->method('setXTransId') ->willReturnSelf(); $this->responseMock->expects(static::any()) ->method('setXInvoiceNum') ->willReturnSelf(); $this->responseMock->expects(static::any()) ->method('setXAmount') ->willReturnSelf(); $this->responseMock->expects(static::any()) ->method('setXMethod') ->willReturnSelf(); $this->responseMock->expects(static::any()) ->method('setXType') ->willReturnSelf(); $this->responseMock->expects(static::any()) ->method('setData') ->willReturnSelf(); $this->responseFactoryMock->expects($this->any()) ->method('create') ->willReturn($this->responseMock); } /** * Get transaction data * @param $transactionId * @param $resultCode * @param $resultStatus * @param $responseStatus * @param $responseCode * @return Element */ private function getTransactionXmlDocument( $transactionId, $resultCode, $resultStatus, $responseStatus, $responseCode ) { $body = sprintf( '<?xml version="1.0" encoding="utf-8"?> <getTransactionDetailsResponse xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd"> <messages> <resultCode>%s</resultCode> <message> <code>I00001</code> <text>%s</text> </message> </messages> <transaction> <transId>%s</transId> <transactionType>authOnlyTransaction</transactionType> <transactionStatus>%s</transactionStatus> <responseCode>%s</responseCode> <responseReasonCode>%s</responseReasonCode> </transaction> </getTransactionDetailsResponse>', $resultCode, $resultStatus, $transactionId, $responseStatus, $responseCode, $responseCode ); libxml_use_internal_errors(true); $document = new Element($body); libxml_use_internal_errors(false); return $document; } /** * Get mock for authorize.net request factory * @return \PHPUnit\Framework\MockObject_MockBuilder */ private function getRequestFactoryMock() { $requestFactory = $this->getMockBuilder(Factory::class) ->disableOriginalConstructor() ->setMethods(['create']) ->getMock(); $request = $this->getMockBuilder(Request::class) ->disableOriginalConstructor() ->setMethods(['__wakeup']) ->getMock(); $requestFactory->expects(static::any()) ->method('create') ->willReturn($request); return $requestFactory; } /** * Get mock for order * @return \PHPUnit_Framework_MockObject_MockObject */ private function getOrderMock() { $orderMock = $this->getMockBuilder(Order::class) ->disableOriginalConstructor() ->setMethods([ 'getId', 'getIncrementId', 'getStoreId', 'getBillingAddress', 'getShippingAddress', 'getBaseCurrencyCode', 'getBaseTaxAmount', '__wakeup' ]) ->getMock(); $orderMock->expects(static::once()) ->method('getId') ->willReturn(1); $orderMock->expects(static::exactly(2)) ->method('getIncrementId') ->willReturn(self::INVOICE_NUM); $orderMock->expects(static::once()) ->method('getStoreId') ->willReturn(1); $orderMock->expects(static::once()) ->method('getBaseCurrencyCode') ->willReturn('USD'); return $orderMock; } /** * Create and return mock for http client factory * @return \PHPUnit_Framework_MockObject_MockObject */ private function getHttpClientFactoryMock() { $this->httpClientMock = $this->getMockBuilder(\Magento\Framework\HTTP\ZendClient::class) ->disableOriginalConstructor() ->setMethods(['request', 'getBody', '__wakeup']) ->getMock(); $this->httpClientMock->expects(static::any()) ->method('request') ->willReturnSelf(); $httpClientFactoryMock = $this->getMockBuilder(\Magento\Framework\HTTP\ZendClientFactory::class) ->disableOriginalConstructor() ->setMethods(['create']) ->getMock(); $httpClientFactoryMock->expects(static::any()) ->method('create') ->willReturn($this->httpClientMock); return $httpClientFactoryMock; } /** * Get mocked response for refund transaction * @param $code * @param $reasonCode * @param $reasonText * @return string */ private function getRefundResponseBody($code, $reasonCode, $reasonText) { $result = array_fill(0, 50, ''); $result[0] = $code; // XResponseCode $result[2] = $reasonCode; // XResponseReasonCode $result[3] = $reasonText; // XResponseReasonText $result[6] = self::TRANSACTION_ID; // XTransId $result[7] = self::INVOICE_NUM; // XInvoiceNum $result[9] = self::TOTAL_AMOUNT; // XAmount $result[10] = Directpost::REQUEST_METHOD_CC; // XMethod $result[11] = Directpost::REQUEST_TYPE_CREDIT; // XType $result[37] = md5(self::TRANSACTION_ID); // x_MD5_Hash $result[50] = '48329483921'; // setXAccountNumber return implode(Directpost::RESPONSE_DELIM_CHAR, $result); } }