diff --git a/Exception/HMACVerificationException.php b/Exception/HMACVerificationException.php new file mode 100644 index 0000000..87ccf0a --- /dev/null +++ b/Exception/HMACVerificationException.php @@ -0,0 +1,9 @@ +checkoutSession = $checkoutSession; $this->orderInterface = $orderInterface; @@ -85,6 +88,7 @@ public function __construct( $this->orderRepository = $orderRepository; $this->bitpayInvoiceRepository = $bitpayInvoiceRepository; $this->returnHashHelper = $returnHashHelper; + $this->encryptor = $encryptor; } /** @@ -150,7 +154,8 @@ public function execute(ResultInterface $defaultResult, string $returnId = null) $order->getId(), $invoiceID, $invoice->getExpirationTime(), - $invoice->getAcceptanceWindow() + $invoice->getAcceptanceWindow(), + $this->encryptor->encrypt($this->config->getToken()) ); $this->transactionRepository->add($incrementId, $invoiceID, 'new'); diff --git a/Model/BitpayInvoiceRepository.php b/Model/BitpayInvoiceRepository.php index bae178e..8afe623 100755 --- a/Model/BitpayInvoiceRepository.php +++ b/Model/BitpayInvoiceRepository.php @@ -21,11 +21,17 @@ public function __construct(BitpayInvoice $bitpayInvoice) * @param string $invoiceID * @param int $expirationTime * @param int|null $acceptanceWindow + * @param string|null $bitpayToken * @return void */ - public function add(string $orderId, string $invoiceID, int $expirationTime, ?int $acceptanceWindow): void - { - $this->bitpayInvoice->add($orderId, $invoiceID, $expirationTime, $acceptanceWindow); + public function add( + string $orderId, + string $invoiceID, + int $expirationTime, + ?int $acceptanceWindow, + ?string $bitpayToken + ): void { + $this->bitpayInvoice->add($orderId, $invoiceID, $expirationTime, $acceptanceWindow, $bitpayToken); } /** diff --git a/Model/Ipn/WebhookVerifier.php b/Model/Ipn/WebhookVerifier.php new file mode 100644 index 0000000..676a985 --- /dev/null +++ b/Model/Ipn/WebhookVerifier.php @@ -0,0 +1,32 @@ +coreRegistry = $registry; $this->responseFactory = $responseFactory; $this->url = $url; $this->quoteFactory = $quoteFactory; - $this->orderInterface = $orderInterface; + $this->orderFactory = $orderFactory; $this->checkoutSession = $checkoutSession; $this->logger = $logger; $this->config = $config; @@ -93,6 +121,9 @@ public function __construct( $this->request = $request; $this->client = $client; $this->response = $response; + $this->bitpayInvoiceRepository = $bitpayInvoiceRepository; + $this->encryptor = $encryptor; + $this->webhookVerifier = $webhookVerifier; $this->returnHashHelper = $returnHashHelper; } @@ -108,7 +139,7 @@ public function postClose() $response = $this->responseFactory->create(); try { $orderID = $this->request->getParam('orderID', null); - $order = $this->orderInterface->loadByIncrementId($orderID); + $order = $this->orderFactory->create()->loadByIncrementId($orderID); $invoiceCloseHandling = $this->config->getBitpayInvoiceCloseHandling(); if ($this->config->getBitpayCheckoutSuccess() === 'standard' && $invoiceCloseHandling === 'keep_order') { $this->checkoutSession->setLastSuccessQuoteId($order->getQuoteId()) @@ -151,10 +182,22 @@ public function postClose() public function postIpn() { try { - $allData = $this->serializer->unserialize($this->request->getContent()); + $requestBody = $this->request->getContent(); + $allData = $this->serializer->unserialize($requestBody); $data = $allData['data']; $event = $allData['event']; $orderId = $data['orderId']; + + $bitPayInvoiceData = $this->bitpayInvoiceRepository->getByOrderId($orderId); + if (!empty($bitPayInvoiceData['bitpay_token'])) { + $signingKey = $this->encryptor->decrypt($bitPayInvoiceData['bitpay_token']); + $xSignature = $this->request->getHeader('x-signature'); + + if (!$this->webhookVerifier->isValidHmac($signingKey, $xSignature, $requestBody)) { + throw new HMACVerificationException('HMAC Verification Failed!'); + } + } + $orderInvoiceId = $data['id']; $row = $this->transactionRepository->findBy($orderId, $orderInvoiceId); $client = $this->client->initialize(); @@ -179,7 +222,7 @@ public function postIpn() $invoiceStatus = $this->invoice->getBPCCheckInvoiceStatus($client, $orderInvoiceId); $updateWhere = ['order_id = ?' => $orderId, 'transaction_id = ?' => $orderInvoiceId]; $this->transactionRepository->update('transaction_status', $invoiceStatus, $updateWhere); - $order = $this->orderInterface->loadByIncrementId($orderId); + $order = $this->orderFactory->create()->loadByIncrementId($orderId); switch ($event['name']) { case Invoice::COMPLETED: if ($invoiceStatus == 'complete') { diff --git a/Model/ResourceModel/BitpayInvoice.php b/Model/ResourceModel/BitpayInvoice.php index 536401a..40e8f7c 100755 --- a/Model/ResourceModel/BitpayInvoice.php +++ b/Model/ResourceModel/BitpayInvoice.php @@ -26,10 +26,16 @@ public function _construct() * @param string $invoiceID * @param int $expirationTime * @param int|null $acceptanceWindow + * @param string|null $bitpayToken * @return void */ - public function add(string $orderId, string $invoiceID, int $expirationTime, ?int $acceptanceWindow) - { + public function add( + string $orderId, + string $invoiceID, + int $expirationTime, + ?int $acceptanceWindow, + ?string $bitpayToken + ) { $connection = $this->getConnection(); $table_name = $connection->getTableName(self::TABLE_NAME); $connection->insert( @@ -38,7 +44,8 @@ public function add(string $orderId, string $invoiceID, int $expirationTime, ?in 'order_id' => $orderId, 'invoice_id' => $invoiceID, 'expiration_time' => $expirationTime, - 'acceptance_window'=> $acceptanceWindow + 'acceptance_window'=> $acceptanceWindow, + 'bitpay_token' => $bitpayToken ] ); } diff --git a/Test/Integration/Model/BPRedirectTest.php b/Test/Integration/Model/BPRedirectTest.php index bf9266f..7cae7f9 100755 --- a/Test/Integration/Model/BPRedirectTest.php +++ b/Test/Integration/Model/BPRedirectTest.php @@ -24,6 +24,7 @@ use Magento\Sales\Model\OrderRepository; use Magento\TestFramework\Helper\Bootstrap; use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\MockObject\MockObject; use Magento\Framework\Encryption\EncryptorInterface; /** @@ -111,6 +112,13 @@ class BPRedirectTest extends TestCase * @var EncryptorInterface|MockObject $encryptor */ private $encryptor; + + /** + * @var ReturnHash $returnHash + */ + private $returnHash; + + public function setUp(): void { $this->objectManager = Bootstrap::getObjectManager(); @@ -118,20 +126,31 @@ public function setUp(): void $this->orderInterface = $this->objectManager->get(OrderInterface::class); $this->config = $this->objectManager->get(Config::class); $this->transactionRepository = $this->objectManager->get(TransactionRepository::class); + /** + * @var Invoice|MockObject + */ $this->invoice = $this->getMockBuilder(Invoice::class)->disableOriginalConstructor()->getMock(); $this->messageManager = $this->objectManager->get(Manager::class); $this->registry = $this->objectManager->get(Registry::class); $this->url = $this->objectManager->get(UrlInterface::class); $this->logger = $this->objectManager->get(Logger::class); $this->resultFactory = $this->objectManager->get(ResultFactory::class); + /** + * @var Client|MockObject + */ $this->client = $this->getMockBuilder(Client::class)->disableOriginalConstructor()->getMock(); $this->orderRepository = $this->objectManager->get(OrderRepository::class); $this->bitpayInvoiceRepository = $this->objectManager->get(BitpayInvoiceRepository::class); $this->bitpayInvoiceRepository = $this->objectManager->get(BitpayInvoiceRepository::class); + /** + * @var EncryptorInterface|MockObject + */ $this->encryptor = $this->getMockBuilder(EncryptorInterface::class) ->disableOriginalConstructor() ->getMock(); + $this->returnHash = $this->objectManager->get(ReturnHash::class); + $this->bpRedirect = new BPRedirect( $this->checkoutSession, $this->orderInterface, @@ -146,6 +165,7 @@ public function setUp(): void $this->client, $this->orderRepository, $this->bitpayInvoiceRepository, + $this->returnHash, $this->encryptor ); } @@ -197,7 +217,6 @@ public function testExecute(): void $this->assertEquals('100000001', $result[0]['order_id']); $this->assertEquals('new', $result[0]['transaction_status']); $this->assertEquals('test', $this->config->getBitpayEnv()); - $this->assertEquals('redirect', $this->config->getBitpayUx()); $this->assertEquals($bitpayMethodCode, $methodCode); } diff --git a/Test/Integration/Model/IpnManagementTest.php b/Test/Integration/Model/IpnManagementTest.php index 174cafa..466b740 100644 --- a/Test/Integration/Model/IpnManagementTest.php +++ b/Test/Integration/Model/IpnManagementTest.php @@ -9,20 +9,24 @@ use Bitpay\BPCheckout\Model\TransactionRepository; use BitPaySDK\Model\Invoice\Buyer; use Magento\Framework\ObjectManagerInterface; -use Bitpay\BPCheckout\Helper\ReturnHash; use Bitpay\BPCheckout\Logger\Logger; +use Bitpay\BPCheckout\Model\BitpayInvoiceRepository; +use Bitpay\BPCheckout\Model\Ipn\WebhookVerifier; +use Bitpay\BPCheckout\Helper\ReturnHash; use Magento\Checkout\Model\Session; use Magento\Framework\App\ResponseFactory; use Magento\Framework\DataObject; +use Magento\Framework\Encryption\EncryptorInterface; use Magento\Framework\Registry; use Magento\Framework\Serialize\Serializer\Json; use Magento\Framework\UrlInterface; use Magento\Framework\Webapi\Rest\Request; use Magento\Framework\Webapi\Rest\Response; use Magento\Quote\Model\QuoteFactory; -use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Model\OrderFactory; use Magento\TestFramework\Helper\Bootstrap; use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\MockObject\MockObject; /** * @SuppressWarnings(PHPMD.TooManyFields) @@ -56,9 +60,9 @@ class IpnManagementTest extends TestCase private $quoteFactory; /** - * @var OrderInterface $orderInterface + * @var OrderFactory|MockObject $orderFactory */ - private $orderInterface; + private $orderFactory; /** * @var Registry $coreRegistry @@ -101,7 +105,7 @@ class IpnManagementTest extends TestCase private $objectManager; /** - * @var Client $client + * @var Client|MockObject $client */ private $client; @@ -110,10 +114,25 @@ class IpnManagementTest extends TestCase */ private $response; + /** + * @var BitpayInvoiceRepository|MockObject $bitpayInvoiceRepository + */ + private $bitpayInvoiceRepository; + + /** + * @var EncryptorInterface|MockObject $encryptor + */ + private $encryptor; + + /** + * @var WebhookVerifier|MockObject $webhookVerifier + */ + private $webhookVerifier; + /** * @var ReturnHash $returnHash */ - private ReturnHash $returnHash; + private $returnHash; public function setUp(): void { @@ -122,24 +141,33 @@ public function setUp(): void $this->responseFactory = $this->objectManager->get(ResponseFactory::class); $this->url = $this->objectManager->get(UrlInterface::class); $this->quoteFactory = $this->objectManager->get(QuoteFactory::class); - $this->orderInterface = $this->objectManager->get(OrderInterface::class); + $this->orderFactory = $this->objectManager->get(OrderFactory::class); $this->checkoutSession = $this->objectManager->get(Session::class); $this->logger = $this->objectManager->get(Logger::class); $this->config = $this->objectManager->get(Config::class); $this->serializer = $this->objectManager->get(Json::class); $this->transactionRepository = $this->objectManager->get(TransactionRepository::class); + /** + * @var Invoice|MockObject + */ $this->invoice = $this->getMockBuilder(Invoice::class)->disableOriginalConstructor()->getMock(); $this->request = $this->objectManager->get(Request::class); + /** + * @var Client|MockObject + */ $this->client = $this->getMockBuilder(Client::class)->disableOriginalConstructor()->getMock(); $this->response = $this->objectManager->get(Response::class); + $this->bitpayInvoiceRepository = $this->objectManager->get(BitpayInvoiceRepository::class); + $this->encryptor =$this->objectManager->get(EncryptorInterface::class); + $this->webhookVerifier = $this->objectManager->get(WebhookVerifier::class); $this->returnHash = $this->objectManager->get(ReturnHash::class); - + $this->ipnManagement = new IpnManagement( $this->responseFactory, $this->url, $this->coreRegistry, $this->checkoutSession, - $this->orderInterface, + $this->orderFactory, $this->quoteFactory, $this->logger, $this->config, @@ -149,6 +177,9 @@ public function setUp(): void $this->request, $this->client, $this->response, + $this->bitpayInvoiceRepository, + $this->encryptor, + $this->webhookVerifier, $this->returnHash ); } @@ -159,15 +190,15 @@ public function setUp(): void */ public function testPostClose() { - $order = $this->orderInterface->loadByIncrementId('100000001'); - $this->request->setParam('orderID', $order->getEntityId()); + $order = $this->orderFactory->create()->loadByIncrementId('100000001'); + $this->request->setParam('orderID', $order->getIncrementId()); $quoteId = $order->getQuoteId(); /** @var \Magento\Quote\Model\Quote $quote */ $this->quoteFactory->create()->loadByIdWithoutStore($quoteId); $this->ipnManagement->postClose(); - - $this->assertTrue($this->orderInterface->loadByIncrementId('100000001')->isDeleted()); + + $this->assertNull($this->orderFactory->create()->loadByIncrementId('100000001')->getId()); $this->assertEquals($quoteId, $this->checkoutSession->getQuoteId()); } @@ -177,14 +208,14 @@ public function testPostClose() */ public function testPostCloseKeepOrder() { - $order = $this->orderInterface->loadByIncrementId('100000001'); - $this->request->setParam('orderID', $order->getEntityId()); + $order = $this->orderFactory->create()->loadByIncrementId('100000001'); + $this->request->setParam('orderID', $order->getIncrementId()); $quoteId = $order->getQuoteId(); /** @var \Magento\Quote\Model\Quote $quote */ $this->quoteFactory->create()->loadByIdWithoutStore($quoteId); $this->ipnManagement->postClose(); - $this->assertFalse($this->orderInterface->loadByIncrementId('100000001')->isDeleted()); + $this->assertEquals($order->getId(), $this->orderFactory->create()->loadByIncrementId('100000001')->getId()); $this->assertEquals($quoteId, $this->checkoutSession->getQuoteId()); } @@ -224,7 +255,7 @@ public function testPostIpn() $this->ipnManagement->postIpn(); - $order = $this->orderInterface->loadByIncrementId($orderId); + $order = $this->orderFactory->create()->loadByIncrementId('100000001'); $result = $this->transactionRepository->findBy($orderId, $orderInvoiceId); $this->assertEquals('complete', $result[0]['transaction_status']); diff --git a/Test/Unit/Model/BPRedirectTest.php b/Test/Unit/Model/BPRedirectTest.php index c855dd5..11f08bf 100755 --- a/Test/Unit/Model/BPRedirectTest.php +++ b/Test/Unit/Model/BPRedirectTest.php @@ -21,6 +21,7 @@ use Magento\Framework\UrlInterface; use \Magento\Framework\Controller\Result\Redirect; use Magento\Framework\Controller\ResultFactory; +use Magento\Framework\Encryption\EncryptorInterface; use Magento\Sales\Model\Order; use Magento\Payment\Model\MethodInterface; use Magento\Sales\Model\OrderRepository; @@ -118,6 +119,11 @@ class BPRedirectTest extends TestCase */ private $returnHash; + /** + * @var EncryptorInterface|MockObject $encryptor + */ + private $encryptor; + public function setUp(): void { $this->checkoutSession = $this->getMock(Session::class); @@ -136,6 +142,7 @@ public function setUp(): void $this->orderRepository = $this->getMock(OrderRepository::class); $this->bitpayInvoiceRepository = $this->getMock(BitpayInvoiceRepository::class); $this->returnHash = $this->getMock(ReturnHash::class); + $this->encryptor = $this->getMock(EncryptorInterface::class); $this->bpRedirect = $this->getClass(); } @@ -175,7 +182,7 @@ public function testExecute(): void $billingAddress->expects($this->once())->method('getFirstName')->willReturn('test'); $billingAddress->expects($this->once())->method('getLastName')->willReturn('test1'); $order = $this->getOrder($incrementId, $payment, $billingAddress, $lastOrderId); - $this->prepareConfig($baseUrl, 'redirect'); + $this->prepareConfig($baseUrl); $method->expects($this->once())->method('getCode')->willReturn(Config::BITPAY_PAYMENT_METHOD_NAME); $payment->expects($this->once())->method('getMethodInstance')->willReturn($method); $this->order->expects($this->once())->method('load')->with($lastOrderId)->willReturn($order); @@ -196,7 +203,7 @@ public function testExecute(): void $this->resultFactory->expects($this->once())->method('create')->willReturn($result); /** - * @var \Magento\Framework\View\Result\Page + * @var \Magento\Framework\Controller\ResultInterface|MockObject */ $page = $this->getMock(\Magento\Framework\View\Result\Page::class); @@ -214,7 +221,7 @@ public function testExecuteNoOrderId(): void $this->resultFactory->expects($this->once())->method('create')->willReturn($result); /** - * @var \Magento\Framework\View\Result\Page + * @var \Magento\Framework\Controller\ResultInterface|MockObject */ $page = $this->getMock(\Magento\Framework\View\Result\Page::class); @@ -242,7 +249,7 @@ public function testExecuteNoBitpayPaymentMethod(): void $this->order->expects($this->once())->method('load')->with($lastOrderId)->willReturn($order); /** - * @var \Magento\Framework\View\Result\Page + * @var \Magento\Framework\Controller\ResultInterface|MockObject */ $page = $this->getMock(\Magento\Framework\View\Result\Page::class); @@ -285,7 +292,7 @@ public function testExecuteException($exceptionType): void $billingAddress->expects($this->once())->method('getFirstName')->willReturn('test'); $billingAddress->expects($this->once())->method('getLastName')->willReturn('test1'); $order = $this->getOrder($incrementId, $payment, $billingAddress, null); - $this->prepareConfig($baseUrl, 'redirect'); + $this->prepareConfig($baseUrl); $method->expects($this->once())->method('getCode')->willReturn(Config::BITPAY_PAYMENT_METHOD_NAME); $payment->expects($this->once())->method('getMethodInstance')->willReturn($method); $this->order->expects($this->once())->method('load')->with($lastOrderId)->willReturn($order); @@ -299,7 +306,7 @@ public function testExecuteException($exceptionType): void ->willThrowException(new $exceptionType('something went wrong')); /** - * @var \Magento\Framework\View\Result\Page + * @var \Magento\Framework\Controller\ResultInterface|MockObject */ $page = $this->getMock(\Magento\Framework\View\Result\Page::class); @@ -337,7 +344,7 @@ private function getOrder(string $incrementId, MockObject $payment, MockObject $ return $order; } - private function prepareConfig(string $baseUrl, string $ux): void + private function prepareConfig(string $baseUrl): void { $this->config->expects($this->once())->method('getBPCheckoutOrderStatus')->willReturn('pending'); $this->config->expects($this->once())->method('getBaseUrl')->willReturn($baseUrl); @@ -380,7 +387,8 @@ private function getClass(): BPRedirect $this->client, $this->orderRepository, $this->bitpayInvoiceRepository, - $this->returnHash + $this->returnHash, + $this->encryptor ); } diff --git a/Test/Unit/Model/Ipn/WebhookVerifierTest.php b/Test/Unit/Model/Ipn/WebhookVerifierTest.php new file mode 100644 index 0000000..6b3a201 --- /dev/null +++ b/Test/Unit/Model/Ipn/WebhookVerifierTest.php @@ -0,0 +1,43 @@ +webhookVerifier = new WebhookVerifier(); + } + + public function testIsValidHmac(): void + { + $this->assertTrue( + $this->webhookVerifier->isValidHmac( + 'testkey', + 'SKEpFPexQ4ko9QAEre51+n+ypvQQidUheDl3+4irEOQ=', + '{"data":{"test":true}' + ) + ); + } + + public function testIsValidHmacFalse(): void + { + $this->assertFalse( + $this->webhookVerifier->isValidHmac( + 'differentkey', + 'SKEpFPexQ4ko9QAEre51+n+ypvQQidUheDl3+4irEOQ=', + '{"data":{"test":true}' + ) + ); + } +} diff --git a/Test/Unit/Model/IpnManagementTest.php b/Test/Unit/Model/IpnManagementTest.php index f1dcf68..81d2da3 100644 --- a/Test/Unit/Model/IpnManagementTest.php +++ b/Test/Unit/Model/IpnManagementTest.php @@ -7,19 +7,22 @@ use Bitpay\BPCheckout\Model\Config; use Bitpay\BPCheckout\Model\Invoice; use Bitpay\BPCheckout\Model\IpnManagement; -use Bitpay\BPCheckout\Helper\ReturnHash; +use Bitpay\BPCheckout\Model\Ipn\WebhookVerifier; use Bitpay\BPCheckout\Logger\Logger; +use Bitpay\BPCheckout\Model\BitpayInvoiceRepository; +use Bitpay\BPCheckout\Helper\ReturnHash; use Bitpay\BPCheckout\Model\TransactionRepository; use BitPaySDK\Model\Invoice\Buyer; use Magento\Checkout\Model\Session; use Magento\Framework\App\ResponseFactory; +use Magento\Framework\Encryption\EncryptorInterface; use Magento\Framework\Registry; use Magento\Framework\Serialize\Serializer\Json; use Magento\Framework\UrlInterface; use Magento\Framework\Webapi\Rest\Request; use Magento\Quote\Model\Quote; use Magento\Quote\Model\QuoteFactory; -use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Model\OrderFactory; use Magento\Sales\Model\Order; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -50,9 +53,9 @@ class IpnManagementTest extends TestCase private $quoteFactory; /** - * @var OrderInterface|MockObject + * @var OrderFactory|MockObject */ - private $orderInterface; + private $orderFactory; /** * @var Registry|MockObject @@ -104,6 +107,21 @@ class IpnManagementTest extends TestCase */ private $response; + /** + * @var BitpayInvoiceRepository|MockObject $bitpayInvoiceRepository + */ + private $bitpayInvoiceRepository; + + /** + * @var EncryptorInterface|MockObject $encryptor + */ + private $encryptor; + + /** + * @var WebhookVerifier|MockObject $webhookVerifier + */ + protected $webhookVerifier; + /** * @var ReturnHash|MockObject */ @@ -115,7 +133,7 @@ public function setUp(): void $this->responseFactory = $this->getMock(ResponseFactory::class); $this->url = $this->getMock(UrlInterface::class); $this->quoteFactory = $this->getMock(QuoteFactory::class); - $this->orderInterface = $this->getMock(\Magento\Sales\Model\Order::class); + $this->orderFactory = $this->getMock(\Magento\Sales\Model\OrderFactory::class); $this->checkoutSession = $this->getMock(Session::class); $this->logger = $this->getMock(Logger::class); $this->config = $this->getMock(Config::class); @@ -125,6 +143,9 @@ public function setUp(): void $this->request = $this->getMock(Request::class); $this->client = $this->getMock(Client::class); $this->response = $this->getMock(\Magento\Framework\Webapi\Rest\Response::class); + $this->bitpayInvoiceRepository = $this->getMock(BitpayInvoiceRepository::class); + $this->encryptor = $this->getMock(EncryptorInterface::class); + $this->webhookVerifier = $this->getMock(WebhookVerifier::class); $this->returnHashHelper = $this->getMock(ReturnHash::class); $this->ipnManagement = $this->getClass(); } @@ -138,11 +159,17 @@ public function testPostClose(): void $order = $this->getMock(Order::class); $orderId = '000000012'; $this->url->expects($this->once())->method('getUrl')->willReturn($cartUrl); - + $order->expects($this->once()) + ->method('loadByIncrementId') + ->with($orderId) + ->willReturnSelf(); $this->request->expects($this->once())->method('getParam')->willReturn($orderId); $this->responseFactory->expects($this->once())->method('create')->willReturn($response); $order->expects($this->once())->method('getData')->willReturn(['quote_id' => $quoteId]); - $this->orderInterface->expects($this->once())->method('loadByIncrementId')->willReturn($order); + + $this->orderFactory->expects($this->once()) + ->method('create') + ->willReturn($order); $quote->expects($this->once())->method('loadByIdWithoutStore')->willReturnSelf(); $quote->expects($this->once())->method('getId')->willReturn($quoteId); @@ -169,7 +196,14 @@ public function testPostCloseKeepOrder(): void $this->request->expects($this->once())->method('getParam')->willReturn($orderId); $this->responseFactory->expects($this->once())->method('create')->willReturn($response); - $this->orderInterface->expects($this->once())->method('loadByIncrementId')->willReturn($order); + + $order->expects($this->once()) + ->method('loadByIncrementId') + ->with($orderId) + ->willReturnSelf(); + $this->orderFactory->expects($this->once()) + ->method('create') + ->willReturn($order); $this->checkoutSession ->method('__call') @@ -195,11 +229,16 @@ public function testPostCloseQuoteNotFound(): void $this->url->expects($this->once()) ->method('getUrl') ->willReturn('http://localhost/checkout/cart?reload=1'); - + $order->expects($this->once()) + ->method('loadByIncrementId') + ->with($orderId) + ->willReturnSelf(); $this->responseFactory->expects($this->once())->method('create')->willReturn($response); $this->request->expects($this->once())->method('getParam')->willReturn($orderId); $order->expects($this->once())->method('getData')->willReturn(['quote_id' => $quoteId]); - $this->orderInterface->expects($this->once())->method('loadByIncrementId')->willReturn($order); + $this->orderFactory->expects($this->once()) + ->method('create') + ->willReturn($order); $quote->expects($this->once())->method('loadByIdWithoutStore')->willReturnSelf(); $quote->expects($this->once())->method('getId')->willReturn(null); $this->quoteFactory->expects($this->once())->method('create')->willReturn($quote); @@ -214,13 +253,19 @@ public function testPostCloseExeception(): void $orderId = '000000012'; $response = $this->getMock(\Magento\Framework\HTTP\PhpEnvironment\Response::class); $order = $this->getMock(Order::class); + $order->expects($this->once()) + ->method('loadByIncrementId') + ->with($orderId) + ->willReturnSelf(); $this->url->expects($this->once()) ->method('getUrl') ->willReturn('http://localhost/checkout/cart?reload=1'); $this->responseFactory->expects($this->once())->method('create')->willReturn($response); $this->request->expects($this->once())->method('getParam')->willReturn($orderId); $order->expects($this->once())->method('getData')->willReturn([]); - $this->orderInterface->expects($this->once())->method('loadByIncrementId')->willReturn($order); + $this->orderFactory->expects($this->once()) + ->method('create') + ->willReturn($order); $response->expects($this->once())->method('setRedirect')->willReturnSelf(); @@ -379,6 +424,53 @@ public function testPostIpnCompleteInvalid(): void $this->ipnManagement->postIpn(); } + public function testPostIpnHmacVerificationSuccess(): void + { + $this->bitpayInvoiceRepository->expects($this->once())->method('getByOrderId')->willReturn([ + 'order_id' => 12, + 'invoice_id' => '12', + 'expiration_time' => 1726740384932, + 'acceptance_window'=> '', + 'bitpay_token' => '0:3:testtokenencoded' + ]); + $this->encryptor->expects($this->once())->method('decrypt')->willReturn('testtoken'); + $this->request->expects($this->once())->method('getHeader')->with('x-signature')->willReturn('test'); + $this->webhookVerifier->expects($this->once())->method('isValidHmac')->willReturn(true); + $this->response->expects($this->never())->method('addMessage'); + + $this->preparePostIpn('invoice_completed', 'test'); + + $this->ipnManagement->postIpn(); + } + + public function testPostIpnHmacVerificationFailure(): void + { + $orderInvoiceId = '12'; + $data = $this->prepareData($orderInvoiceId, 'invoice_completed'); + $serializer = new Json(); + $serializerData = $serializer->serialize($data); + $this->serializer->expects($this->once())->method('unserialize')->willReturn($data); + $this->request->expects($this->once())->method('getContent')->willReturn($serializerData); + + $this->bitpayInvoiceRepository->expects($this->once())->method('getByOrderId')->willReturn([ + 'order_id' => 12, + 'invoice_id' => '12', + 'expiration_time' => 1726740384932, + 'acceptance_window'=> '', + 'bitpay_token' => '0:3:testtokenencoded' + ]); + $this->encryptor->expects($this->once())->method('decrypt')->willReturn('testtoken'); + $this->request->expects($this->once())->method('getHeader')->with('x-signature')->willReturn('test'); + $this->webhookVerifier->expects($this->once())->method('isValidHmac')->willReturn(false); + + $this->response->expects($this->once()) + ->method('addMessage') + ->with('HMAC Verification Failed!', 500) + ->willReturnSelf(); + + $this->ipnManagement->postIpn(); + } + private function preparePostIpn(string $eventName, string $invoiceStatus): void { $orderInvoiceId = '12'; @@ -403,7 +495,13 @@ private function preparePostIpn(string $eventName, string $invoiceStatus): void $this->config->expects($this->once())->method('getToken')->willReturn('test'); $this->invoice->expects($this->once())->method('getBPCCheckInvoiceStatus')->willReturn($invoiceStatus); $order = $this->getMock(Order::class); - $this->orderInterface->expects($this->once())->method('loadByIncrementId')->willReturn($order); + $order->expects($this->once()) + ->method('loadByIncrementId') + ->with($data['data']['orderId']) + ->willReturnSelf(); + $this->orderFactory->expects($this->once()) + ->method('create') + ->willReturn($order); } private function getMock(string $className): MockObject @@ -418,7 +516,7 @@ private function getClass(): IpnManagement $this->url, $this->coreRegistry, $this->checkoutSession, - $this->orderInterface, + $this->orderFactory, $this->quoteFactory, $this->logger, $this->config, @@ -428,6 +526,9 @@ private function getClass(): IpnManagement $this->request, $this->client, $this->response, + $this->bitpayInvoiceRepository, + $this->encryptor, + $this->webhookVerifier, $this->returnHashHelper ); } diff --git a/Test/Unit/Model/ResourceModel/TransactionTest.php b/Test/Unit/Model/ResourceModel/TransactionTest.php index 184b7ed..427168b 100755 --- a/Test/Unit/Model/ResourceModel/TransactionTest.php +++ b/Test/Unit/Model/ResourceModel/TransactionTest.php @@ -14,7 +14,7 @@ class TransactionTest extends TestCase { /** - * @var Context $context + * @var Context|MockObject $context */ private $context; @@ -28,11 +28,6 @@ class TransactionTest extends TestCase */ private $adapter; - /** - * @var Context|MockObject $context - */ - private $context; - public function setUp(): void { $this->prepareContext(); diff --git a/etc/db_schema.xml b/etc/db_schema.xml index 9ae2172..d2f635f 100644 --- a/etc/db_schema.xml +++ b/etc/db_schema.xml @@ -25,6 +25,7 @@ comment="Expiration time to pay invoice"/> +