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