diff --git a/Block/Checkout/Onepage/Success/PayButton.php b/Block/Checkout/Onepage/Success/PayButton.php
new file mode 100644
index 0000000..a043338
--- /dev/null
+++ b/Block/Checkout/Onepage/Success/PayButton.php
@@ -0,0 +1,140 @@
+transactionRepository = $transactionRepository;
+ $this->client = $client;
+ $this->config = $config;
+ $this->url = $url;
+ }
+
+ /**
+ * Returns true when Pay button be displayed
+ *
+ * @return bool
+ */
+ public function canViewPayButton(): bool
+ {
+ if ($this->config->getBitpayCheckoutSuccess() === 'standard'
+ && $this->config->getBitpayInvoiceCloseHandling() === 'keep_order') {
+ $invoice = $this->getBitpayInvoice();
+
+ return $invoice !== null;
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns button url
+ *
+ * @return string
+ */
+ public function getButtonUrl(): string
+ {
+ return $this->url->getUrl('bpcheckout/invoice/pay', [
+ '_query' => [
+ 'order_id' => $this->getOrder()->getId(), 'invoice_id' => $this->getBitpayInvoice()->getId()
+ ]
+ ]);
+ }
+
+ /**
+ * Get BitPay invoice by last order
+ *
+ * @return Invoice|null
+ */
+ protected function getBitpayInvoice(): ?Invoice
+ {
+ if (!$this->invoice) {
+ $order = $this->getOrder();
+ if ($order->canInvoice()) {
+ $transactions = $this->transactionRepository
+ ->findByOrderIdAndTransactionStatus($order->getIncrementId(), 'new');
+ if (!empty($transactions)) {
+ $lastTransaction = array_pop($transactions);
+ $client = $this->client->initialize();
+ $invoice = $client->getInvoice($lastTransaction['transaction_id']);
+
+ $this->invoice = $invoice;
+ }
+ }
+ }
+
+ return $this->invoice;
+ }
+
+ /**
+ * Get order instance based on last order ID
+ *
+ * @return Order
+ */
+ protected function getOrder(): Order
+ {
+ return $this->_checkoutSession->getLastRealOrder();
+ }
+}
diff --git a/Controller/Invoice/Pay.php b/Controller/Invoice/Pay.php
new file mode 100644
index 0000000..eb2f544
--- /dev/null
+++ b/Controller/Invoice/Pay.php
@@ -0,0 +1,139 @@
+request = $request;
+ $this->messageManager = $messageManager;
+ $this->resultRedirectFactory = $resultRedirectFactory;
+ $this->orderRepository = $orderRepository;
+ $this->transactionRepository = $transactionRepository;
+ $this->checkoutSession = $checkoutSession;
+ $this->client = $client;
+ $this->config = $config;
+ }
+
+ /**
+ * Get checkout customer info
+ *
+ * @return ResultInterface
+ */
+ public function execute()
+ {
+ $orderId = $this->request->getParam('order_id', null);
+ $invoiceId = $this->request->getParam('invoice_id', null);
+
+ try {
+ if (!$orderId || !$invoiceId || $this->config->getBitpayCheckoutSuccess() !== 'standard'
+ || $this->config->getBitpayInvoiceCloseHandling() !== 'keep_order') {
+ throw new LocalizedException(new Phrase('Invalid request!'));
+ }
+
+ /** @var \Magento\Sales\Model\Order $order */
+ $order = $this->orderRepository->get($orderId);
+ if (!$order->canInvoice()) {
+ throw new LocalizedException(new Phrase('Order already paid!'));
+ }
+
+ $client = $this->client->initialize();
+ $invoice = $client->getInvoice($invoiceId);
+ $invoiceStatus = $invoice->getStatus();
+ if ($invoiceStatus === 'paid' || $invoiceStatus === 'confirmed' || $invoiceStatus === 'complete') {
+ throw new LocalizedException(new Phrase('The invoice has already been paid!'));
+ } elseif ($invoiceStatus === 'expired') {
+ throw new LocalizedException(new Phrase('The invoice has expired!'));
+ } elseif ($invoiceStatus !== 'new') {
+ throw new LocalizedException(new Phrase('The invoice is invalid or expired!'));
+ }
+
+ $this->checkoutSession->setLastSuccessQuoteId($order->getQuoteId())
+ ->setLastQuoteId($order->getQuoteId())
+ ->setLastOrderId($order->getEntityId());
+
+ return $this->resultRedirectFactory->create()->setUrl($invoice->getUrl());
+ } catch (\Exception $exception) {
+ $this->messageManager->addErrorMessage($exception->getMessage());
+
+ return $this->resultRedirectFactory->create()->setPath('checkout/cart');
+ } catch (\Error $error) {
+ $this->messageManager->addErrorMessage('Invalid request!');
+
+ return $this->resultRedirectFactory->create()->setPath('checkout/cart');
+ }
+ }
+}
diff --git a/Helper/ReturnHash.php b/Helper/ReturnHash.php
new file mode 100644
index 0000000..70c8de8
--- /dev/null
+++ b/Helper/ReturnHash.php
@@ -0,0 +1,55 @@
+encryptor = $encryptor;
+
+ parent::__construct($context);
+ }
+
+ /**
+ * Generates return hash
+ *
+ * @param OrderInterface $order
+ * @return string
+ */
+ public function generate(OrderInterface $order): string
+ {
+ return $this->encryptor->hash(
+ "{$order->getIncrementId()}:{$order->getCustomerEmail()}:{$order->getProtectCode()}"
+ );
+ }
+
+ /**
+ * Checks if returnHash is valid
+ *
+ * @param string $returnHashToCheck
+ * @param OrderInterface $order
+ * @return bool
+ */
+ public function isValid(string $returnHashToCheck, OrderInterface $order): bool
+ {
+ return $returnHashToCheck === $this->generate($order);
+ }
+}
diff --git a/Model/BPRedirect.php b/Model/BPRedirect.php
index aaf6ebc..9a8546a 100755
--- a/Model/BPRedirect.php
+++ b/Model/BPRedirect.php
@@ -1,6 +1,7 @@
checkoutSession = $checkoutSession;
@@ -83,6 +87,7 @@ public function __construct(
$this->client = $client;
$this->orderRepository = $orderRepository;
$this->bitpayInvoiceRepository = $bitpayInvoiceRepository;
+ $this->returnHashHelper = $returnHashHelper;
$this->encryptor = $encryptor;
}
@@ -114,9 +119,10 @@ public function execute(ResultInterface $defaultResult, string $returnId = null)
}
$isStandardCheckoutSuccess = $this->config->getBitpayCheckoutSuccess() === 'standard';
- $returnHash = $this->encryptor->hash("$incrementId:{$order->getCustomerEmail()}:{$order->getProtectCode()}");
+
if ($isStandardCheckoutSuccess && $returnId) {
- if ($returnId !== $returnHash) {
+ $returnHash = $this->returnHashHelper->generate($order);
+ if (!$this->returnHashHelper->isValid($returnId, $order)) {
$this->checkoutSession->clearHelperData();
return $this->resultFactory->create(\Magento\Framework\Controller\ResultFactory::TYPE_REDIRECT)
@@ -127,6 +133,7 @@ public function execute(ResultInterface $defaultResult, string $returnId = null)
}
try {
+ $returnHash = $this->returnHashHelper->generate($order);
$baseUrl = $this->config->getBaseUrl();
$order = $this->setToPendingAndOverrideMagentoStatus($order);
$redirectUrl = $this->url->getUrl('bitpay-invoice', ['_query' => ['order_id' => $incrementId]]);
diff --git a/Model/Config.php b/Model/Config.php
index 1253e26..c2bf7d2 100755
--- a/Model/Config.php
+++ b/Model/Config.php
@@ -20,6 +20,7 @@ class Config
public const BPCHECKOUT_ORDER_STATUS = 'payment/bpcheckout/order_status';
public const BITPAY_UX = 'payment/bpcheckout/bitpay_ux';
public const BITPAY_CHECKOUT_SUCCESS = 'payment/bpcheckout/bitpay_checkout_success';
+ public const BITPAY_INVOICE_CLOSE_HANDLING = 'payment/bpcheckout/bitpay_invoice_close_handling';
public const BITPAY_MERCHANT_TOKEN_DATA = 'bitpay_merchant_facade/authenticate/token_data';
public const BITPAY_MERCHANT_PRIVATE_KEY_PATH = 'bitpay_merchant_facade/authenticate/private_key_path';
public const BITPAY_MERCHANT_PASSWORD = 'bitpay_merchant_facade/authenticate/password';
@@ -110,6 +111,16 @@ public function getBitpayCheckoutSuccess():? string
return $this->scopeConfig->getValue(self::BITPAY_CHECKOUT_SUCCESS, ScopeInterface::SCOPE_STORE);
}
+ /**
+ * Get BitPay InvoiceCloseHandling
+ *
+ * @return string|null
+ */
+ public function getBitpayInvoiceCloseHandling():? string
+ {
+ return $this->scopeConfig->getValue(self::BITPAY_INVOICE_CLOSE_HANDLING, ScopeInterface::SCOPE_STORE);
+ }
+
/**
* Get token
*
diff --git a/Model/Config/Source/InvoiceCloseHandling.php b/Model/Config/Source/InvoiceCloseHandling.php
new file mode 100644
index 0000000..e65f127
--- /dev/null
+++ b/Model/Config/Source/InvoiceCloseHandling.php
@@ -0,0 +1,25 @@
+ 'delete_order', 'label' => __('Delete Order')],
+ ['value' => 'keep_order', 'label' => __('Keep Order')],
+ ];
+ }
+}
diff --git a/Model/Ipn/Validator.php b/Model/Ipn/Validator.php
index 0e960fb..1895d22 100644
--- a/Model/Ipn/Validator.php
+++ b/Model/Ipn/Validator.php
@@ -15,7 +15,7 @@ class Validator
public function __construct(\BitPaySDK\Model\Invoice\Invoice $invoice, array $ipnData)
{
$name = $ipnData['buyerFields']['buyerName'];
- $email = $ipnData['buyerFields']['buyerEmail'];
+ $email = strtolower($ipnData['buyerFields']['buyerEmail']);
$address1 = $ipnData['buyerFields']['buyerAddress1'] ?? null;
$address2 = $ipnData['buyerFields']['buyerAddress2'] ?? null;
$amountPaid = $ipnData['amountPaid'];
@@ -25,8 +25,8 @@ public function __construct(\BitPaySDK\Model\Invoice\Invoice $invoice, array $ip
$this->errors[] = "Name from IPN data ('{$name}') does not match with " .
"name from invoice ('{$invoiceBuyerName}')";
}
-
- if ($email !== $invoiceBuyerEmail = $invoiceBuyer->getEmail()) {
+ $invoiceBuyerEmail = strtolower($invoiceBuyer->getEmail());
+ if ($email !== $invoiceBuyerEmail) {
$this->errors[] = "Email from IPN data ('{$email}') does not match with " .
"email from invoice ('{$invoiceBuyerEmail}')";
}
diff --git a/Model/IpnManagement.php b/Model/IpnManagement.php
index 6ae3db5..2f35fa3 100755
--- a/Model/IpnManagement.php
+++ b/Model/IpnManagement.php
@@ -6,6 +6,7 @@
use Bitpay\BPCheckout\Api\IpnManagementInterface;
use Bitpay\BPCheckout\Exception\IPNValidationException;
use Bitpay\BPCheckout\Exception\HMACVerificationException;
+use Bitpay\BPCheckout\Helper\ReturnHash;
use Bitpay\BPCheckout\Logger\Logger;
use Bitpay\BPCheckout\Model\Ipn\BPCItem;
use Bitpay\BPCheckout\Model\Ipn\Validator;
@@ -60,6 +61,11 @@ class IpnManagement implements IpnManagementInterface
*/
protected WebhookVerifier $webhookVerifier;
+ /**
+ * @var ReturnHash
+ */
+ protected ReturnHash $returnHashHelper;
+
/**
* @param ResponseFactory $responseFactory
* @param UrlInterface $url
@@ -78,6 +84,7 @@ class IpnManagement implements IpnManagementInterface
* @param BitpayInvoiceRepository $bitpayInvoiceRepository
* @param EncryptorInterface $encryptor
* @param WebhookVerifier $webhookVerifier
+ * @param ReturnHash $returnHashHelper
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
@@ -97,7 +104,8 @@ public function __construct(
Response $response,
BitpayInvoiceRepository $bitpayInvoiceRepository,
EncryptorInterface $encryptor,
- WebhookVerifier $webhookVerifier
+ WebhookVerifier $webhookVerifier,
+ ReturnHash $returnHashHelper
) {
$this->coreRegistry = $registry;
$this->responseFactory = $responseFactory;
@@ -116,6 +124,7 @@ public function __construct(
$this->bitpayInvoiceRepository = $bitpayInvoiceRepository;
$this->encryptor = $encryptor;
$this->webhookVerifier = $webhookVerifier;
+ $this->returnHashHelper = $returnHashHelper;
}
/**
@@ -131,18 +140,30 @@ public function postClose()
try {
$orderID = $this->request->getParam('orderID', null);
$order = $this->orderFactory->create()->loadByIncrementId($orderID);
- $orderData = $order->getData();
- $quoteID = $orderData['quote_id'];
- $quote = $this->quoteFactory->create()->loadByIdWithoutStore($quoteID);
- if ($quote->getId()) {
- $quote->setIsActive(1)->setReservedOrderId(null)->save();
- $this->checkoutSession->replaceQuote($quote);
- $this->coreRegistry->register('isSecureArea', 'true');
- $order->delete();
- $this->coreRegistry->unregister('isSecureArea');
- $response->setRedirect($redirectUrl)->sendResponse();
+ $invoiceCloseHandling = $this->config->getBitpayInvoiceCloseHandling();
+ if ($this->config->getBitpayCheckoutSuccess() === 'standard' && $invoiceCloseHandling === 'keep_order') {
+ $this->checkoutSession->setLastSuccessQuoteId($order->getQuoteId())
+ ->setLastQuoteId($order->getQuoteId())
+ ->setLastOrderId($order->getEntityId());
- return;
+ $returnHash = $this->returnHashHelper->generate($order);
+ $redirectUrl = $this->url->getUrl(
+ 'checkout/onepage/success',
+ ['_query' => ['return_id' => $returnHash]]
+ );
+ } else {
+ $orderData = $order->getData();
+ $quoteID = $orderData['quote_id'];
+ $quote = $this->quoteFactory->create()->loadByIdWithoutStore($quoteID);
+ if ($quote->getId()) {
+ $quote->setIsActive(1)->setReservedOrderId(null)->save();
+ $this->checkoutSession->replaceQuote($quote);
+ if ($invoiceCloseHandling !== 'keep_order') {
+ $this->coreRegistry->register('isSecureArea', 'true');
+ $order->delete();
+ $this->coreRegistry->unregister('isSecureArea');
+ }
+ }
}
$response->setRedirect($redirectUrl)->sendResponse();
diff --git a/Model/ResourceModel/Transaction.php b/Model/ResourceModel/Transaction.php
index 15c318b..6d9486b 100755
--- a/Model/ResourceModel/Transaction.php
+++ b/Model/ResourceModel/Transaction.php
@@ -63,6 +63,32 @@ public function findBy(string $orderId, string $orderInvoiceId): ?array
return $row;
}
+ /**
+ * Find transaction by order_id and transaction_status
+ *
+ * @param string $orderId
+ * @param string $transactionStatus
+ * @return array|null
+ */
+ public function findByOrderIdAndTransactionStatus(string $orderId, string $transactionStatus): ?array
+ {
+ $connection = $this->getConnection();
+ $tableName = $connection->getTableName(self::TABLE_NAME);
+
+ $sql = $connection->select()
+ ->from($tableName)
+ ->where('order_id = ?', $orderId)
+ ->where('transaction_status = ?', $transactionStatus);
+
+ $row = $connection->fetchAll($sql);
+
+ if (!$row) {
+ return null;
+ }
+
+ return $row;
+ }
+
/**
* Update transaction
*
diff --git a/Model/TransactionRepository.php b/Model/TransactionRepository.php
index 760c59b..83d3e27 100755
--- a/Model/TransactionRepository.php
+++ b/Model/TransactionRepository.php
@@ -40,6 +40,18 @@ public function findBy(string $orderId, string $orderInvoiceId): ?array
return $this->resourceTransaction->findBy($orderId, $orderInvoiceId);
}
+ /**
+ * Find Transactions by order_id and transaction_status
+ *
+ * @param string $orderId
+ * @param string $transactionStatus
+ * @return array|null
+ */
+ public function findByOrderIdAndTransactionStatus(string $orderId, string $transactionStatus): ?array
+ {
+ return $this->resourceTransaction->findByOrderIdAndTransactionStatus($orderId, $transactionStatus);
+ }
+
/**
* Update Transaction
*
diff --git a/Test/Integration/Model/BPRedirectTest.php b/Test/Integration/Model/BPRedirectTest.php
index 8ae5fa1..7cae7f9 100755
--- a/Test/Integration/Model/BPRedirectTest.php
+++ b/Test/Integration/Model/BPRedirectTest.php
@@ -3,6 +3,7 @@
namespace Bitpay\BPCheckout\Test\Integration\Model;
+use Bitpay\BPCheckout\Helper\ReturnHash;
use Bitpay\BPCheckout\Model\BitpayInvoiceRepository;
use Bitpay\BPCheckout\Model\BPRedirect;
use Bitpay\BPCheckout\Model\Client;
@@ -91,6 +92,7 @@ class BPRedirectTest extends TestCase
* @var ResultFactory $resultFactory
*/
private $resultFactory;
+
/**
* @var Client|MockObject $client
*/
@@ -111,6 +113,12 @@ class BPRedirectTest extends TestCase
*/
private $encryptor;
+ /**
+ * @var ReturnHash $returnHash
+ */
+ private $returnHash;
+
+
public function setUp(): void
{
$this->objectManager = Bootstrap::getObjectManager();
@@ -141,6 +149,8 @@ public function setUp(): void
->disableOriginalConstructor()
->getMock();
+ $this->returnHash = $this->objectManager->get(ReturnHash::class);
+
$this->bpRedirect = new BPRedirect(
$this->checkoutSession,
$this->orderInterface,
@@ -155,6 +165,7 @@ public function setUp(): void
$this->client,
$this->orderRepository,
$this->bitpayInvoiceRepository,
+ $this->returnHash,
$this->encryptor
);
}
@@ -231,8 +242,9 @@ public function testExecuteException(): void
$this->invoice->expects($this->once())->method('BPCCreateInvoice')
->willThrowException(new LocalizedException(new Phrase('Invalid token')));
-
+
$defaultResult = $this->resultFactory->create(\Magento\Framework\Controller\ResultFactory::TYPE_PAGE);
+
$this->bpRedirect->execute($defaultResult);
$this->assertEquals(
'We are unable to place your Order at this time',
diff --git a/Test/Integration/Model/IpnManagementTest.php b/Test/Integration/Model/IpnManagementTest.php
index 2ecc448..466b740 100644
--- a/Test/Integration/Model/IpnManagementTest.php
+++ b/Test/Integration/Model/IpnManagementTest.php
@@ -12,6 +12,7 @@
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;
@@ -23,7 +24,6 @@
use Magento\Framework\Webapi\Rest\Response;
use Magento\Quote\Model\QuoteFactory;
use Magento\Sales\Model\OrderFactory;
-use Magento\Sales\Api\Data\OrderInterface;
use Magento\TestFramework\Helper\Bootstrap;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\MockObject\MockObject;
@@ -127,7 +127,12 @@ class IpnManagementTest extends TestCase
/**
* @var WebhookVerifier|MockObject $webhookVerifier
*/
- protected $webhookVerifier;
+ private $webhookVerifier;
+
+ /**
+ * @var ReturnHash $returnHash
+ */
+ private $returnHash;
public function setUp(): void
{
@@ -155,6 +160,8 @@ public function setUp(): void
$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,
@@ -173,11 +180,13 @@ public function setUp(): void
$this->bitpayInvoiceRepository,
$this->encryptor,
$this->webhookVerifier,
+ $this->returnHash
);
}
/**
* @magentoDataFixture Bitpay_BPCheckout::Test/Integration/_files/order.php
+ * @magentoConfigFixture current_store payment/bpcheckout/bitpay_invoice_close_handling delete_order
*/
public function testPostClose()
{
@@ -188,7 +197,25 @@ public function testPostClose()
$this->quoteFactory->create()->loadByIdWithoutStore($quoteId);
$this->ipnManagement->postClose();
- $this->orderFactory->create()->loadByIncrementId('100000001');
+
+ $this->assertNull($this->orderFactory->create()->loadByIncrementId('100000001')->getId());
+ $this->assertEquals($quoteId, $this->checkoutSession->getQuoteId());
+ }
+
+ /**
+ * @magentoDataFixture Bitpay_BPCheckout::Test/Integration/_files/order.php
+ * @magentoConfigFixture current_store payment/bpcheckout/bitpay_invoice_close_handling keep_order
+ */
+ public function testPostCloseKeepOrder()
+ {
+ $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->assertEquals($order->getId(), $this->orderFactory->create()->loadByIncrementId('100000001')->getId());
$this->assertEquals($quoteId, $this->checkoutSession->getQuoteId());
}
diff --git a/Test/Unit/Model/BPRedirectTest.php b/Test/Unit/Model/BPRedirectTest.php
index 2030f3d..11f08bf 100755
--- a/Test/Unit/Model/BPRedirectTest.php
+++ b/Test/Unit/Model/BPRedirectTest.php
@@ -3,6 +3,7 @@
namespace Bitpay\BPCheckout\Test\Unit\Model;
+use Bitpay\BPCheckout\Helper\ReturnHash;
use Bitpay\BPCheckout\Model\BitpayInvoiceRepository;
use Bitpay\BPCheckout\Model\BPRedirect;
use Bitpay\BPCheckout\Logger\Logger;
@@ -113,6 +114,11 @@ class BPRedirectTest extends TestCase
*/
private $resultFactory;
+ /**
+ * @var ReturnHash|MockObject $returnHash
+ */
+ private $returnHash;
+
/**
* @var EncryptorInterface|MockObject $encryptor
*/
@@ -135,6 +141,7 @@ public function setUp(): void
$this->resultFactory = $this->getMock(ResultFactory::class);
$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();
}
@@ -205,7 +212,6 @@ public function testExecute(): void
public function testExecuteNoOrderId(): void
{
- $response = $this->getMock(\Magento\Framework\HTTP\PhpEnvironment\Response::class);
$this->checkoutSession->expects($this->once())
->method('getData')
->with('last_order_id')
@@ -381,6 +387,7 @@ private function getClass(): BPRedirect
$this->client,
$this->orderRepository,
$this->bitpayInvoiceRepository,
+ $this->returnHash,
$this->encryptor
);
}
diff --git a/Test/Unit/Model/IpnManagementTest.php b/Test/Unit/Model/IpnManagementTest.php
index aa38a69..81d2da3 100644
--- a/Test/Unit/Model/IpnManagementTest.php
+++ b/Test/Unit/Model/IpnManagementTest.php
@@ -3,7 +3,6 @@
namespace Bitpay\BPCheckout\Test\Unit\Model;
-use Bitpay\BPCheckout\Exception\IPNValidationException;
use Bitpay\BPCheckout\Model\Client;
use Bitpay\BPCheckout\Model\Config;
use Bitpay\BPCheckout\Model\Invoice;
@@ -11,6 +10,7 @@
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;
@@ -98,12 +98,12 @@ class IpnManagementTest extends TestCase
private $ipnManagement;
/**
- * @var Client|mockObject $client
+ * @var Client|MockObject
*/
private $client;
/**
- * @var \Magento\Framework\Webapi\Rest\Response|MockObject $response
+ * @var \Magento\Framework\Webapi\Rest\Response|MockObject
*/
private $response;
@@ -121,6 +121,11 @@ class IpnManagementTest extends TestCase
* @var WebhookVerifier|MockObject $webhookVerifier
*/
protected $webhookVerifier;
+
+ /**
+ * @var ReturnHash|MockObject
+ */
+ private $returnHashHelper;
public function setUp(): void
{
@@ -141,6 +146,7 @@ public function setUp(): void
$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();
}
@@ -173,6 +179,42 @@ public function testPostClose(): void
$this->quoteFactory->expects($this->once())->method('create')->willReturn($quote);
$response->expects($this->once())->method('setRedirect')->willReturnSelf();
+ $order->expects($this->once())->method('delete')->willReturnSelf();
+
+ $this->ipnManagement->postClose();
+ }
+
+ public function testPostCloseKeepOrder(): void
+ {
+ $this->config->expects($this->once())->method('getBitpayInvoiceCloseHandling')->willReturn('keep_order');
+
+ $cartUrl = 'http://localhost/checkout/cart?reload=1';
+ $response = $this->getMock(\Magento\Framework\HTTP\PhpEnvironment\Response::class);
+ $order = $this->getMock(Order::class);
+ $orderId = '000000012';
+ $this->url->expects($this->once())->method('getUrl')->willReturn($cartUrl);
+
+ $this->request->expects($this->once())->method('getParam')->willReturn($orderId);
+ $this->responseFactory->expects($this->once())->method('create')->willReturn($response);
+
+ $order->expects($this->once())
+ ->method('loadByIncrementId')
+ ->with($orderId)
+ ->willReturnSelf();
+ $this->orderFactory->expects($this->once())
+ ->method('create')
+ ->willReturn($order);
+
+ $this->checkoutSession
+ ->method('__call')
+ ->willReturnCallback(fn($operation) => match ([$operation]) {
+ ['setLastSuccessQuoteId'] => $this->checkoutSession,
+ ['setLastQuoteId'] => $this->checkoutSession,
+ ['setLastOrderId'] => $this->checkoutSession
+ });
+
+ $response->expects($this->once())->method('setRedirect')->willReturnSelf();
+ $order->expects($this->never())->method('delete')->willReturnSelf();
$this->ipnManagement->postClose();
}
@@ -332,8 +374,45 @@ public function testPostIpnValidatorError(): void
$client->expects($this->once())->method('getInvoice')->willReturn($invoice);
$this->client->expects($this->once())->method('initialize')->willReturn($client);
$this->transactionRepository->expects($this->once())->method('findBy')->willReturn([]);
- $this->throwException(new IPNValidationException('Email from IPN data (\'test@example.com\') does not' .
- 'match with email from invoice (\'test1@example.com\')'));
+
+ $this->response->expects($this->once())->method('addMessage')->with(
+ "Email from IPN data ('{$data['data']['buyerFields']['buyerEmail']}') does not match with " .
+ "email from invoice ('{$invoice->getBuyer()->getEmail()}')",
+ 500
+ );
+
+ $this->ipnManagement->postIpn();
+ }
+
+ public function testPostIpnNoValidatorErrorWhenEmailCasingMismatch(): void
+ {
+ $eventName = 'ivoice_confirmed';
+ $orderInvoiceId = '12';
+ $data = [
+ 'data' => [
+ 'orderId' => '00000012',
+ 'id' => $orderInvoiceId,
+ 'buyerFields' => [
+ 'buyerName' => 'test',
+ 'buyerEmail' => 'Test@exaMple.COM',
+ 'buyerAddress1' => '12 test road'
+ ],
+ 'amountPaid' => 1232132
+ ],
+ 'event' => ['name' => $eventName]
+ ];
+ $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);
+
+ $invoice = $this->prepareInvoice();
+ $client = $this->getMockBuilder(\BitPaySDK\Client::class)->disableOriginalConstructor()->getMock();
+ $client->expects($this->once())->method('getInvoice')->willReturn($invoice);
+ $this->client->expects($this->once())->method('initialize')->willReturn($client);
+ $this->transactionRepository->expects($this->once())->method('findBy')->willReturn([]);
+
+ $this->response->expects($this->never())->method('addMessage');
$this->ipnManagement->postIpn();
}
@@ -449,7 +528,8 @@ private function getClass(): IpnManagement
$this->response,
$this->bitpayInvoiceRepository,
$this->encryptor,
- $this->webhookVerifier
+ $this->webhookVerifier,
+ $this->returnHashHelper
);
}
diff --git a/Test/Unit/Model/ResourceModel/TransactionTest.php b/Test/Unit/Model/ResourceModel/TransactionTest.php
index cb5e493..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;
diff --git a/composer.json b/composer.json
index a073d5a..274dc09 100644
--- a/composer.json
+++ b/composer.json
@@ -3,7 +3,7 @@
"description": "",
"type": "magento2-module",
"license": "mit",
- "version":"10.0.0",
+ "version": "10.0.0",
"authors": [
{
"email": "integrations@bitpay.com",
diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml
index ca61dbd..6f08b89 100755
--- a/etc/adminhtml/system.xml
+++ b/etc/adminhtml/system.xml
@@ -127,6 +127,12 @@
Bitpay\BPCheckout\Model\Config\Source\CheckoutSuccess
+
+
+ Delete Order, then the order will be deleted when the user closes the BitPay Invoice before paying the invoice.
If this is set to Keep Order, then the order will not be deleted.]]>
+ Bitpay\BPCheckout\Model\Config\Source\InvoiceCloseHandling
+
+
diff --git a/etc/config.xml b/etc/config.xml
index 611c9ad..a25d43b 100755
--- a/etc/config.xml
+++ b/etc/config.xml
@@ -20,6 +20,7 @@
offline
0
module
+ delete_order
diff --git a/view/frontend/layout/checkout_onepage_success.xml b/view/frontend/layout/checkout_onepage_success.xml
new file mode 100644
index 0000000..1fd881b
--- /dev/null
+++ b/view/frontend/layout/checkout_onepage_success.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
diff --git a/view/frontend/templates/checkout/order/success/pay-button.phtml b/view/frontend/templates/checkout/order/success/pay-button.phtml
new file mode 100644
index 0000000..f71cb72
--- /dev/null
+++ b/view/frontend/templates/checkout/order/success/pay-button.phtml
@@ -0,0 +1,9 @@
+
+getOrderId() && $block->canViewPayButton()) :?>
+
+
diff --git a/view/frontend/web/images/Pay-with-BitPay-CardGroup.svg b/view/frontend/web/images/Pay-with-BitPay-CardGroup.svg
index 4313224..590c6e3 100644
--- a/view/frontend/web/images/Pay-with-BitPay-CardGroup.svg
+++ b/view/frontend/web/images/Pay-with-BitPay-CardGroup.svg
@@ -1,7 +1,7 @@