From 5b99938f22480d96091a9a423d8b76492e486cd7 Mon Sep 17 00:00:00 2001
From: "(AJ) Zin Kyaw Kyaw" <108650842+ajzkk@users.noreply.github.com>
Date: Tue, 10 Oct 2023 17:15:09 +0630
Subject: [PATCH] Add feature flag to enable dynamic webhooks (#450)
* add dynamic webhook when webhook is enabled
* update webhook description
* remove duplicate config injection
* update webhooks description and label
* update webhooks description
* add feature flag for dynamic webhook
* bug fixed on test
* update test
* add more unit test cases for config
* remove empty line
---
Gateway/Request/PaymentDataBuilder.php | 24 ++--
Model/Config/Config.php | 10 ++
Test/Unit/ConfigTest.php | 91 +++++++++++++++
Test/Unit/PaymentDataBuilderTest.php | 148 +++++++++++++++++++++++++
etc/adminhtml/system.xml | 23 +++-
etc/config.xml | 1 +
6 files changed, 282 insertions(+), 15 deletions(-)
create mode 100644 Test/Unit/ConfigTest.php
create mode 100644 Test/Unit/PaymentDataBuilderTest.php
diff --git a/Gateway/Request/PaymentDataBuilder.php b/Gateway/Request/PaymentDataBuilder.php
index be1bf8e1..9def1703 100644
--- a/Gateway/Request/PaymentDataBuilder.php
+++ b/Gateway/Request/PaymentDataBuilder.php
@@ -1,13 +1,15 @@
omiseHelper = $omiseHelper;
$this->money = $money;
$this->ccConfig = $ccConfig;
}
@@ -76,7 +77,7 @@ public function build(array $buildSubject)
$store_id = $order->getStoreId();
$om = \Magento\Framework\App\ObjectManager::getInstance();
$manager = $om->get(\Magento\Store\Model\StoreManagerInterface::class);
- $store_name = $manager->getStore($store_id)->getName();
+ $store = $manager->getStore($store_id);
$currency = $order->getCurrencyCode();
$requestBody = [
@@ -89,10 +90,15 @@ public function build(array $buildSubject)
self::METADATA => [
'order_id' => $order->getOrderIncrementId(),
'store_id' => $order->getStoreId(),
- 'store_name' => $store_name
+ 'store_name' => $store->getName()
]
];
+ if ($this->ccConfig->isDynamicWebhooksEnabled()) {
+ $webhookUrl = $store->getBaseUrl() . Webhook::URI;
+ $requestBody[self::WEBHOOKS_ENDPOINT] = [$webhookUrl];
+ }
+
if (Installment::CODE === $method->getMethod()) {
$requestBody[self::ZERO_INTEREST_INSTALLMENTS] = $this->isZeroInterestInstallment($method);
}
diff --git a/Model/Config/Config.php b/Model/Config/Config.php
index 51e7d89c..68045b92 100644
--- a/Model/Config/Config.php
+++ b/Model/Config/Config.php
@@ -198,6 +198,16 @@ public function isWebhookEnabled()
return $this->getValue('webhook_status');
}
+ /**
+ * Check if using dynamic webhooks or not
+ *
+ * @return bool
+ */
+ public function isDynamicWebhooksEnabled()
+ {
+ return $this->isWebhookEnabled() && $this->getValue('dynamic_webhooks');
+ }
+
/**
* Retrieve the order status in which to generate invoice at
*
diff --git a/Test/Unit/ConfigTest.php b/Test/Unit/ConfigTest.php
new file mode 100644
index 00000000..e7f39a1c
--- /dev/null
+++ b/Test/Unit/ConfigTest.php
@@ -0,0 +1,91 @@
+scopeConfigMock = m::mock(ScopeConfigInterface::class);
+ $this->storeManagerMock = m::mock(StoreManagerInterface::class);
+ $this->storeMock = m::mock(StoreInterface::class);
+ }
+
+ /**
+ * @dataProvider isDynamicWebhooksEnabledProvider
+ * @covers Omise\Payment\Model\Config\Config
+ */
+ public function testIsDynamicWebhooksEnabled($webhookEnabled, $dynamicWebhooksEnabled, $expected)
+ {
+ $this->scopeConfigMock->shouldReceive('getValue')
+ ->with('general/locale/code', m::any(), m::any())
+ ->andReturn('en');
+
+ $this->scopeConfigMock->shouldReceive('getValue')
+ ->with('payment/omise/sandbox_status', m::any(), m::any())
+ ->andReturn(1);
+
+ $this->scopeConfigMock->shouldReceive('getValue')
+ ->with('payment/omise/test_public_key', m::any(), m::any())
+ ->andReturn('pkey_test_xx');
+
+ $this->scopeConfigMock->shouldReceive('getValue')
+ ->with('payment/omise/test_secret_key', m::any(), m::any())
+ ->andReturn('pkey_test_xx');
+
+ $this->scopeConfigMock->shouldReceive('getValue')
+ ->with('payment/omise/dynamic_webhooks', m::any(), m::any())
+ ->andReturn($dynamicWebhooksEnabled);
+
+ $this->scopeConfigMock->shouldReceive('getValue')
+ ->with('payment/omise/webhook_status', m::any(), m::any())
+ ->andReturn($webhookEnabled);
+
+ $this->storeMock->shouldReceive('getId')->andReturn(1);
+ $this->storeManagerMock->shouldReceive('getStore')->andReturn($this->storeMock);
+
+ $config = new Config($this->scopeConfigMock, $this->storeManagerMock);
+ $result = $config->isDynamicWebhooksEnabled();
+ $this->assertEquals($result, $expected);
+ }
+
+ /**
+ * Data provider for testIsDynamicWebhooksEnabled method
+ */
+ public function isDynamicWebhooksEnabledProvider()
+ {
+ return [
+ [
+ 'webhookEnabled' => 1,
+ 'dynamicWebhooksEnabled' => 1,
+ 'expected' => true,
+ ],
+ [
+ 'webhookEnabled' => 1,
+ 'dynamicWebhooksEnabled' => 1,
+ 'expected' => true,
+ ],
+ [
+ 'webhookEnabled' => 0,
+ 'dynamicWebhooksEnabled' => 1,
+ 'expected' => false,
+ ],
+ [
+ 'webhookEnabled' => 0,
+ 'dynamicWebhooksEnabled' => 0,
+ 'expected' => false,
+ ],
+ ];
+ }
+}
diff --git a/Test/Unit/PaymentDataBuilderTest.php b/Test/Unit/PaymentDataBuilderTest.php
new file mode 100644
index 00000000..6ad111ee
--- /dev/null
+++ b/Test/Unit/PaymentDataBuilderTest.php
@@ -0,0 +1,148 @@
+factoryMock = m::mock(FactoryInterface::class);
+ $this->configMock = m::mock(ConfigInterface::class);
+ $this->omiseMoneyMock = m::mock(OmiseMoney::class);
+ $this->ccConfigMock = m::mock(Cc::class);
+ $this->paymentMock = m::mock(OrderPaymentInterface::class);
+ $this->paymentDataMock = m::mock(PaymentDataObjectInterface::class);
+ $this->orderMock = m::mock(OrderInterface::class);
+ $this->storeManagerMock = m::mock(StoreManagerInterface::class);
+ $this->storeMock = m::mock(StoreInterface::class);
+ }
+
+ /**
+ * @covers Omise\Payment\Gateway\Request\PaymentDataBuilder
+ */
+ public function testConstants()
+ {
+ $this->assertEquals('webhook_endpoints', PaymentDataBuilder::WEBHOOKS_ENDPOINT);
+ $this->assertEquals('amount', PaymentDataBuilder::AMOUNT);
+ $this->assertEquals('currency', PaymentDataBuilder::CURRENCY);
+ $this->assertEquals('description', PaymentDataBuilder::DESCRIPTION);
+ $this->assertEquals('metadata', PaymentDataBuilder::METADATA);
+ $this->assertEquals('zero_interest_installments', PaymentDataBuilder::ZERO_INTEREST_INSTALLMENTS);
+ }
+
+ /**
+ * @dataProvider buildDataProvider
+ * @covers Omise\Payment\Gateway\Request\PaymentDataBuilder
+ */
+ public function testBuild($paymentMethod, $expectedMetadata)
+ {
+ $amount = 1000;
+ $currency = 'THB';
+ $orderId = 123;
+ $storeId = 1;
+ $storeName = 'opn-store';
+ $storeBaseUrl = 'https://omise.co/';
+ $secureFormEnabled = true;
+
+ new ObjectManager($this->factoryMock, $this->configMock);
+
+ $this->paymentMock->shouldReceive('getMethod')->andReturn($paymentMethod);
+ $this->paymentMock->shouldReceive('getAdditionalInformation')->andReturn('installment_mbb');
+
+ $this->ccConfigMock->shouldReceive('getSecureForm')->andReturn($secureFormEnabled);
+ $this->ccConfigMock->shouldReceive('isDynamicWebhooksEnabled')->andReturn(true);
+
+ $this->omiseMoneyMock->shouldReceive('setAmountAndCurrency')->andReturn($this->omiseMoneyMock);
+ $this->omiseMoneyMock->shouldReceive('toSubunit')->andReturn($amount * 100);
+
+ $this->storeMock->shouldReceive('getName')->andReturn($storeName);
+ $this->storeMock->shouldReceive('getBaseUrl')->andReturn($storeBaseUrl);
+
+ $this->storeManagerMock->shouldReceive('getStore')->andReturn($this->storeMock);
+ $this->configMock->shouldReceive('getPreference');
+ $this->factoryMock->shouldReceive('create')->andReturn($this->storeManagerMock);
+
+ $this->orderMock->shouldReceive('getCurrencyCode')->andReturn($currency);
+ $this->orderMock->shouldReceive('getGrandTotalAmount')->andReturn($amount);
+ $this->orderMock->shouldReceive('getOrderIncrementId')->andReturn($orderId);
+ $this->orderMock->shouldReceive('getStoreId')->andReturn($storeId);
+
+ $this->paymentDataMock->shouldReceive('getOrder')->andReturn($this->orderMock);
+ $this->paymentDataMock->shouldReceive('getPayment')->andReturn($this->paymentMock);
+
+ $model = new PaymentDataBuilder($this->ccConfigMock, $this->omiseMoneyMock);
+ $result = $model->build(['payment' => $this->paymentDataMock]);
+
+ $this->assertEquals(100000, $result['amount']);
+ $this->assertEquals('THB', $result['currency']);
+ $this->assertEquals([
+ 'https://omise.co/omise/callback/webhook'
+ ], $result['webhook_endpoints']);
+ $this->assertEquals($expectedMetadata, $result['metadata']);
+
+ if ($paymentMethod === Installment::CODE) {
+ $this->assertEquals(true, $result['zero_interest_installments']);
+ }
+ }
+
+ /**
+ * Data provider for testBuild method
+ */
+ public function buildDataProvider()
+ {
+ return [
+ [
+ 'paymentMethod' => Cc::CODE,
+ 'expectedMetadata' => [
+ 'order_id' => 123,
+ 'store_id' => 1,
+ 'store_name' => 'opn-store',
+ 'secure_form_enabled' => true
+ ],
+ ],
+ [
+ 'paymentMethod' => Installment::CODE,
+ 'expectedMetadata' => [
+ 'order_id' => 123,
+ 'store_id' => 1,
+ 'store_name' => 'opn-store',
+ ],
+ ],
+ [
+ 'paymentMethod' => Promptpay::CODE,
+ 'expectedMetadata' => [
+ 'order_id' => 123,
+ 'store_id' => 1,
+ 'store_name' => 'opn-store',
+ ],
+ ],
+ ];
+ }
+}
diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml
index 30ae13f5..ebe8fc25 100644
--- a/etc/adminhtml/system.xml
+++ b/etc/adminhtml/system.xml
@@ -33,19 +33,30 @@
The "Live" mode secret key can be found in Opn Payments Dashboard.
-
+
Magento\Config\Model\Config\Source\Yesno
- Use webhook.
+
+ our webhooks documentation.
+ Unless dynamic webhooks are enabled, you must add the URL below as a new endpoint on your Opn Payments dashboard (HTTPS only).
+ ]]>
+
-
-
+
+
+ Magento\Config\Model\Config\Source\Yesno
+
+ 1
+
- WebHooks feature, you must copy the above url to setup an endpoint at Opn Payments dashboard (HTTPS only).]]>
+
+
+
+
Omise\Payment\Block\Adminhtml\System\Config\Form\Field\Webhook
-
+
Magento\Config\Model\Config\Source\Yesno
Enabling cron allows Magento to check orders with expired charge status and mark them as canceled.
diff --git a/etc/config.xml b/etc/config.xml
index 3d5a1a6d..6ce3d96e 100644
--- a/etc/config.xml
+++ b/etc/config.xml
@@ -13,6 +13,7 @@
OmiseAdapter
pending_payment
1
+ 0