diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..601afbc
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,3 @@
+github: tuyennn
+ko_fi: thinghost76
+custom: ["https://www.paypal.me/thinghost"]
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000..9f4f3ba
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,29 @@
+---
+name: Bug report
+about: Technical issue with the Module
+
+---
+
+### Preconditions (*)
+
+1.
+2.
+
+### Steps to reproduce (*)
+
+1.
+2.
+
+### Expected result (*)
+
+1. [Screenshots, logs or description]
+2.
+
+### Actual result (*)
+
+1. [Screenshots, logs or description]
+2.
diff --git a/.github/Screenshot/kbank_embedded_full_payment.png b/.github/Screenshot/kbank_embedded_full_payment.png
new file mode 100644
index 0000000..5131aaf
Binary files /dev/null and b/.github/Screenshot/kbank_embedded_full_payment.png differ
diff --git a/.github/Screenshot/kbank_embedded_installment.png b/.github/Screenshot/kbank_embedded_installment.png
new file mode 100644
index 0000000..67ba7d9
Binary files /dev/null and b/.github/Screenshot/kbank_embedded_installment.png differ
diff --git a/.github/Screenshot/kbank_payment_configuration.png b/.github/Screenshot/kbank_payment_configuration.png
new file mode 100644
index 0000000..cd76a10
Binary files /dev/null and b/.github/Screenshot/kbank_payment_configuration.png differ
diff --git a/.github/workflows/coding-standard.yml b/.github/workflows/coding-standard.yml
new file mode 100644
index 0000000..885022d
--- /dev/null
+++ b/.github/workflows/coding-standard.yml
@@ -0,0 +1,14 @@
+name: ExtDN M2 Coding Standard
+on:
+ push:
+ branches:
+ - master
+ pull_request:
+
+jobs:
+ static:
+ name: M2 Coding Standard
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - uses: extdn/github-actions-m2/magento-coding-standard@master
diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml
new file mode 100644
index 0000000..338fc72
--- /dev/null
+++ b/.github/workflows/integration.yml
@@ -0,0 +1,119 @@
+name: ExtDN M2 Integration Tests
+on: [ push, pull_request ]
+
+jobs:
+ mage247:
+ name: Magento 2 Integration Tests (Magento v2.4.7)
+ runs-on: ubuntu-latest
+ services:
+ mysql:
+ image: mysql:8.0
+ env:
+ MYSQL_ROOT_PASSWORD: root
+ ports:
+ - 3306:3306
+ options: --tmpfs /tmp:rw --tmpfs /var/lib/mysql:rw --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
+ es:
+ image: docker.io/wardenenv/elasticsearch:8.1
+ ports:
+ - 9200:9200
+ env:
+ 'discovery.type': single-node
+ 'xpack.security.enabled': false
+ ES_JAVA_OPTS: "-Xms64m -Xmx512m"
+ options: --health-cmd="curl localhost:9200/_cluster/health?wait_for_status=yellow&timeout=60s" --health-interval=10s --health-timeout=5s --health-retries=3
+ steps:
+ - uses: actions/checkout@v2
+ - name: M2 Integration Tests with Magento 2
+ uses: extdn/github-actions-m2/magento-integration-tests/8.3@master
+ with:
+ module_name: GhoSter_KbankPayments
+ composer_name: ghoster/module-kbankpayments
+ ce_version: 2.4.7
+
+ mage246:
+ name: Magento 2 Integration Tests (Magento v2.4.6)
+ runs-on: ubuntu-latest
+ services:
+ mysql:
+ image: mysql:8.0
+ env:
+ MYSQL_ROOT_PASSWORD: root
+ ports:
+ - 3306:3306
+ options: --tmpfs /tmp:rw --tmpfs /var/lib/mysql:rw --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
+ es:
+ image: docker.io/wardenenv/elasticsearch:8.1
+ ports:
+ - 9200:9200
+ env:
+ 'discovery.type': single-node
+ 'xpack.security.enabled': false
+ ES_JAVA_OPTS: "-Xms64m -Xmx512m"
+ options: --health-cmd="curl localhost:9200/_cluster/health?wait_for_status=yellow&timeout=60s" --health-interval=10s --health-timeout=5s --health-retries=3
+ steps:
+ - uses: actions/checkout@v2
+ - name: M2 Integration Tests with Magento 2
+ uses: extdn/github-actions-m2/magento-integration-tests/8.2@master
+ with:
+ module_name: GhoSter_KbankPayments
+ composer_name: ghoster/module-kbankpayments
+ ce_version: 2.4.6
+
+ mage244:
+ name: Magento 2 Integration Tests (Magento v2.4.4)
+ runs-on: ubuntu-latest
+ services:
+ mysql:
+ image: mysql:8.0
+ env:
+ MYSQL_ROOT_PASSWORD: root
+ ports:
+ - 3306:3306
+ options: --tmpfs /tmp:rw --tmpfs /var/lib/mysql:rw --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
+ es:
+ image: docker.io/wardenenv/elasticsearch:7.16
+ ports:
+ - 9200:9200
+ env:
+ 'discovery.type': single-node
+ 'xpack.security.enabled': false
+ ES_JAVA_OPTS: "-Xms64m -Xmx512m"
+ options: --health-cmd="curl localhost:9200/_cluster/health?wait_for_status=yellow&timeout=60s" --health-interval=10s --health-timeout=5s --health-retries=3
+ steps:
+ - uses: actions/checkout@v2
+ - name: M2 Integration Tests with Magento 2
+ uses: extdn/github-actions-m2/magento-integration-tests/7.4@master
+ with:
+ module_name: GhoSter_KbankPayments
+ composer_name: ghoster/module-kbankpayments
+ ce_version: 2.4.4
+
+ mage24:
+ name: Magento 2 Integration Tests (Magento v2.4.3)
+ runs-on: ubuntu-latest
+ services:
+ mysql:
+ image: mysql:8.0
+ env:
+ MYSQL_ROOT_PASSWORD: root
+ ports:
+ - 3306:3306
+ options: --tmpfs /tmp:rw --tmpfs /var/lib/mysql:rw --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
+ es:
+ image: docker.io/wardenenv/elasticsearch:7.10
+ ports:
+ - 9200:9200
+ env:
+ 'discovery.type': single-node
+ 'xpack.security.enabled': false
+ ES_JAVA_OPTS: "-Xms64m -Xmx512m"
+ options: --health-cmd="curl localhost:9200/_cluster/health?wait_for_status=yellow&timeout=60s" --health-interval=10s --health-timeout=5s --health-retries=3
+ steps:
+ - uses: actions/checkout@v2
+ - name: M2 Integration Tests with Magento 2
+ uses: extdn/github-actions-m2/magento-integration-tests/7.4@master
+ with:
+ module_name: GhoSter_KbankPayments
+ composer_name: ghoster/module-kbankpayments
+ ce_version: 2.4.3
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f66850f
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+.buildpath
+.project
+.settings/
+.idea/
\ No newline at end of file
diff --git a/Api/Data/MetaInterface.php b/Api/Data/MetaInterface.php
new file mode 100644
index 0000000..110b081
--- /dev/null
+++ b/Api/Data/MetaInterface.php
@@ -0,0 +1,490 @@
+_backendConfig = $backendConfig;
+ parent::__construct($context, $authSession, $jsHelper, $data);
+ }
+
+ /**
+ * Add custom css class
+ *
+ * @param AbstractElement $element
+ * @return string
+ */
+ protected function _getFrontendClass($element)
+ {
+ return parent::_getFrontendClass($element) . ' with-button enabled';
+ }
+
+ /**
+ * Return header title part of html for payment solution
+ *
+ * @param AbstractElement $element
+ * @return string
+ * @SuppressWarnings(PHPMD.NPathComplexity)
+ */
+ protected function _getHeaderTitleHtml($element)
+ {
+ $html = '
';
+
+ $groupConfig = $element->getGroup();
+
+ $htmlId = $element->getHtmlId();
+ $html .= '
getUrl(
+ 'adminhtml/*/state'
+ ) . '\'); return false;">' . __(
+ 'Configure'
+ ) . ' ' . __(
+ 'Close'
+ ) . ' ';
+
+ $html .= '
';
+ $html .= '
' . $element->getLegend() . ' ';
+
+ if ($element->getComment()) {
+ $html .= '
' . $element->getComment() . ' ';
+ }
+ $html .= '
';
+ $html .= '
';
+
+ return $html;
+ }
+
+ /**
+ * Return header comment part of html for payment solution
+ *
+ * @param AbstractElement $element
+ * @return string
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ protected function _getHeaderCommentHtml($element)
+ {
+ return '';
+ }
+
+ /**
+ * Get collapsed state on-load
+ *
+ * @param AbstractElement $element
+ * @return false
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ protected function _isCollapseState($element)
+ {
+ return false;
+ }
+
+ /**
+ * Extra javascript
+ *
+ * @param AbstractElement $element
+ * @return string
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ protected function _getExtraJs($element)
+ {
+ $script = "require(['jquery', 'prototype'], function(jQuery){
+ window.toggleSolution = function (id, url) {
+ var doScroll = false;
+ Fieldset.toggleCollapse(id, url);
+ if ($(this).hasClassName(\"open\")) {
+ $$(\".with-button button.button\").each(function(anotherButton) {
+ if (anotherButton != this && $(anotherButton).hasClassName(\"open\")) {
+ $(anotherButton).click();
+ doScroll = true;
+ }
+ }.bind(this));
+ }
+ if (doScroll) {
+ var pos = Element.cumulativeOffset($(this));
+ window.scrollTo(pos[0], pos[1] - 45);
+ }
+ }
+ });";
+
+ return $this->_jsHelper->getScript($script);
+ }
+}
diff --git a/Block/Adminhtml/Config/Form/Field/Webhook.php b/Block/Adminhtml/Config/Form/Field/Webhook.php
new file mode 100644
index 0000000..1f6ed8d
--- /dev/null
+++ b/Block/Adminhtml/Config/Form/Field/Webhook.php
@@ -0,0 +1,66 @@
+storeManager = $storeManager;
+ $this->request = $request;
+ parent::__construct($context, $data);
+ }
+
+ /**
+ * Get Element Html
+ *
+ * @param AbstractElement $element
+ * @return string
+ * @throws NoSuchEntityException
+ */
+ protected function _getElementHtml(AbstractElement $element)
+ {
+ $storeId = $this->request->getParam('website') ?? $this->request->getParam('store');
+
+ if ($storeId) {
+ $this->storeManager->setCurrentStore($storeId);
+ }
+
+ return $this->storeManager->getStore()->getBaseUrl() . self::URI;
+ }
+}
diff --git a/Block/Adminhtml/System/Config/SmartPayId.php b/Block/Adminhtml/System/Config/SmartPayId.php
new file mode 100644
index 0000000..05ff091
--- /dev/null
+++ b/Block/Adminhtml/System/Config/SmartPayId.php
@@ -0,0 +1,91 @@
+addColumn(
+ 'smartpay_id',
+ [
+ 'label' => __('SMARTPAY ID'),
+ 'renderer' => false
+ ]
+ );
+ $this->addColumn(
+ 'payment_term',
+ [
+ 'label' => __('PAYMENT TERM'),
+ 'renderer' => false
+ ]
+ );
+ $this->addColumn(
+ 'installment_title',
+ [
+ 'label' => __('Installment Title'),
+ 'renderer' => false
+ ]
+ );
+ $this->addColumn(
+ 'status',
+ [
+ 'label' => __('Status'),
+ 'renderer' => $this->_getActivationRenderer()
+ ]
+ );
+ $this->_addAfter = false;
+ $this->_addButtonLabel = __('Add Installment');
+ }
+
+ /**
+ * Get activation options.
+ *
+ * @return Activation
+ * @throws LocalizedException
+ */
+ protected function _getActivationRenderer(): Activation
+ {
+ if (!$this->activation) {
+ $this->activation = $this->getLayout()->createBlock(
+ Activation::class,
+ '',
+ ['data' => ['is_render_to_js_template' => true]]
+ );
+ }
+
+ return $this->activation;
+ }
+
+ /**
+ * Prepare existing row data object.
+ *
+ * @param DataObject $row
+ * @return void
+ * @throws LocalizedException
+ */
+ protected function _prepareArrayRow(DataObject $row)
+ {
+ $options = [];
+ $customAttribute = $row->getData('status');
+
+ $key = 'option_' . $this->_getActivationRenderer()->calcOptionHash($customAttribute);
+ $options[$key] = 'selected = "selected"';
+ $row->setData('option_extra_attrs', $options);
+ }
+}
diff --git a/Block/Adminhtml/System/Config/SmartPayId/Activation.php b/Block/Adminhtml/System/Config/SmartPayId/Activation.php
new file mode 100644
index 0000000..289ed9b
--- /dev/null
+++ b/Block/Adminhtml/System/Config/SmartPayId/Activation.php
@@ -0,0 +1,61 @@
+enableDisable = $enableDisable;
+ }
+
+ /**
+ * Set Input Name
+ *
+ * @param string $value
+ * @return $this
+ */
+ public function setInputName($value)
+ {
+ return $this->setName($value);
+ }
+
+ /**
+ * Parse to html.
+ *
+ * @return mixed
+ */
+ public function _toHtml()
+ {
+ if (!$this->getOptions()) {
+ $attributes = $this->enableDisable->toOptionArray();
+
+ foreach ($attributes as $attribute) {
+ $this->addOption($attribute['value'], $attribute['label']);
+ }
+ }
+
+ return parent::_toHtml();
+ }
+}
diff --git a/Block/Form.php b/Block/Form.php
new file mode 100644
index 0000000..cbb827b
--- /dev/null
+++ b/Block/Form.php
@@ -0,0 +1,67 @@
+getData('method');
+
+ if (!$method instanceof MethodInterface) {
+ throw new LocalizedException(
+ __('We cannot retrieve the payment method model object.')
+ );
+ }
+ return $method;
+ }
+
+ /**
+ * Sets payment method instance to form
+ *
+ * @param MethodInterface $method
+ * @return Form
+ */
+ public function setMethod(MethodInterface $method)
+ {
+ $this->setData('method', $method);
+ return $this;
+ }
+
+ /**
+ * Retrieve payment method code
+ *
+ * @return string
+ * @throws LocalizedException
+ */
+ public function getMethodCode()
+ {
+ return $this->getMethod()->getCode();
+ }
+
+ /**
+ * Retrieve field value data from payment info object
+ *
+ * @param mixed $field
+ * @return string
+ * @throws LocalizedException
+ */
+ public function getInfoData($field)
+ {
+ return $this->escapeHtml($this->getMethod()->getInfoInstance()->getData($field));
+ }
+}
diff --git a/Block/Info.php b/Block/Info.php
new file mode 100644
index 0000000..af11cc7
--- /dev/null
+++ b/Block/Info.php
@@ -0,0 +1,23 @@
+resultPageFactory = $resultPageFactory;
+ $this->resultForwardFactory = $resultForwardFactory;
+ $this->checkoutSession = $checkoutSession;
+ $this->orderFactory = $orderFactory;
+ $this->checkoutHelper = $checkoutHelper;
+ $this->metaRepository = $metaRepository;
+ $this->logger = $logger;
+ $this->dataObjectFactory = $dataObjectFactory;
+ $this->kbankConfig = $kbankConfig ?: ObjectManager::getInstance()
+ ->get(KbankConfig::class);
+ $this->paymentFailures = $paymentFailures ?: ObjectManager::getInstance()
+ ->get(PaymentFailuresInterface::class);
+
+ $this->metaDataFactory = $metaDataFactory ?: ObjectManager::getInstance()
+ ->get(MetaInterfaceFactory::class);
+
+ $this->url = $url ?: ObjectManager::getInstance()
+ ->get(Url::class);
+ $this->httpHeader = $header ?: ObjectManager::getInstance()
+ ->get(Header::class);
+ parent::__construct($context);
+ }
+}
diff --git a/Controller/Payment/Callback.php b/Controller/Payment/Callback.php
new file mode 100644
index 0000000..39a2872
--- /dev/null
+++ b/Controller/Payment/Callback.php
@@ -0,0 +1,204 @@
+resultRedirectFactory->create();
+
+ $this->_eventManager->dispatch(
+ 'none_samesite_response_before',
+ ['request' => $this->getRequest(), 'header' => $this->httpHeader]
+ );
+
+ $validResponse = $this->_verifyResponse();
+
+ if ($this->chargeId) {
+ try {
+ $this->meta = $this->metaRepository->getByChargeId($this->chargeId);
+
+ $this->order = $this->orderFactory->create()
+ ->loadByIncrementId($this->meta->getOrderId());
+
+ $this->url->setOrder($this->order);
+ } catch (\Exception $e) {
+ $this->errors[] = __('Meta was mismatched or empty.');
+ }
+
+ if (!$this->order) {
+ $this->errors[] = __('Order was mismatched or empty.');
+ }
+
+ if ($validResponse && $this->order instanceof Order && $this->meta) {
+ try {
+ if (!$this->_checkOrderState($this->order)) {
+ $this->logger
+ ->info(
+ sprintf(
+ 'Order status already changed. Do nothing: %s',
+ $this->order->getStatus()
+ )
+ );
+ } else {
+ $errorObject = $this->dataObjectFactory
+ ->create(
+ [
+ 'data' => ['error' => $this->errors]
+ ]
+ );
+
+ $this->_eventManager->dispatch(
+ 'kbank_embedded_payment_method_callback_validation',
+ [
+ 'meta' => $this->meta,
+ 'order' => $this->order,
+ 'error' => $errorObject,
+ 'type' => 'callback'
+ ]
+ );
+
+ $this->errors = $errorObject->getData('error');
+
+ if (!in_array($this->order->getStatus(), $this->processedOrderStates)) {
+ $this->errors[] = __('Callback Order was not processed correctly or order was cancelled');
+ }
+ }
+ } catch (\Exception $e) {
+ $this->errors[] = $e->getMessage();
+ }
+ }
+ }
+
+ if (!empty($this->errors)) {
+ $comment = implode(',', $this->errors);
+ $this->messageManager->addErrorMessage($comment);
+ if ($this->order) {
+ $this->order->cancel();
+ $this->order->save();
+ }
+ }
+
+ $resultRedirect->setUrl(empty($this->errors) ? $this->url->getSuccessUrl() : $this->url->getFailureUrl());
+ return $resultRedirect;
+ }
+
+ /**
+ * Verify the response
+ *
+ * @return bool
+ */
+ protected function _verifyResponse(): bool
+ {
+ $responseData = $this->getRequest()->getPostValue();
+
+ if ($this->kbankConfig->isDebugMode()) {
+ $this->logger->info('Header Callback response data from Callback Controller');
+ $this->logger->info($this->getRequest()->getHeader('Content-Type'));
+ $this->logger->info('Callback response data from Callback Controller');
+ $this->logger->info($responseData);
+ }
+
+ $this->chargeId = $responseData['objectId'] ?? false;
+ $this->token = $responseData['token'] ?? false;
+ $status = $responseData['status'] ?? Config::CALLBACK_INVALID_STATUS;
+
+ if ($status !== Config::CALLBACK_VALID_STATUS) {
+ $this->logger->info(sprintf('Callback Controller: Status was mismatched or empty - %s', $status));
+ $this->errors[] = __('Status was mismatched or empty.');
+ return false;
+ }
+
+ if (!$this->chargeId) {
+ $this->logger
+ ->info(
+ sprintf('Callback Controller: Charge Id was mismatched or empty. - %s', $this->chargeId)
+ );
+ $this->errors[] = __('Charge Id was mismatched or empty.');
+ return false;
+ }
+
+ if (!$this->token) {
+ $this->logger->info(sprintf('Callback Controller: Token was mismatched or empty. - %s', $this->token));
+ $this->errors[] = __('Token was mismatched or empty.');
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Check order state
+ *
+ * @param Order $order
+ * @return bool
+ */
+ protected function _checkOrderState($order): bool
+ {
+ if (!$order) {
+ return false;
+ }
+
+ return in_array($order->getState(), $this->allowedOrderStates);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function createCsrfValidationException(
+ RequestInterface $request
+ ): ?InvalidRequestException {
+ return null;
+ }
+
+ /**
+ * Disable Magento's CSRF validation.
+ *
+ * @param RequestInterface $request
+ * @return bool|null
+ */
+ public function validateForCsrf(RequestInterface $request): ?bool
+ {
+ return true;
+ }
+}
diff --git a/Controller/Token/Process.php b/Controller/Token/Process.php
new file mode 100644
index 0000000..6029226
--- /dev/null
+++ b/Controller/Token/Process.php
@@ -0,0 +1,129 @@
+resultRedirectFactory->create();
+
+ $token = $this->getRequest()->getPostValue('token');
+ $lastRealOrderId = $this->getRequest()->getParam('order_id');
+
+ if (!$lastRealOrderId) {
+ $lastRealOrderId = $this->checkoutSession->getLastRealOrderId();
+ }
+
+ if (!!$lastRealOrderId) {
+ /** @var Order $order */
+ $this->order = $this->orderFactory->create()->loadByIncrementId($lastRealOrderId);
+ }
+
+ $orderIncrementId = $this->order instanceof Order ? $this->order->getIncrementId() : 0;
+
+ if (!empty($orderIncrementId)
+ && !empty($token)
+ ) {
+ $this->url->setOrder($this->order);
+
+ $storeId = $this->order->getStoreId();
+
+ $result = $this->dataObjectFactory->create()
+ ->addData(['result' => true, 'message' => '']);
+
+ $meta = $this->metaDataFactory->create();
+
+ $this->_eventManager->dispatch(
+ 'kbank_embedded_payment_method_process_token_data',
+ [
+ 'gateway_token' => $this->getRequest()->getPostValue(),
+ 'order' => $this->order,
+ 'result' => $result,
+ 'meta' => $meta
+ ]
+ );
+
+ if ($this->_checkOrderState($this->order)
+ && !$this->kbankConfig->is3DS($storeId)
+ && $result->getData('result') === true
+ ) {
+ /** Non 3DS */
+ $resultRedirect->setUrl($this->url->getSuccessUrl());
+ } elseif ($this->_checkOrderState($this->order)
+ && $this->kbankConfig->is3DS($storeId)
+ && $result->getData('result') === true
+ ) {
+ /** 3DS Support */
+ try {
+ $meta = $this->metaRepository->getByOrderIncrement($this->order->getIncrementId());
+ if ($redirectUrl = $meta->getRedirectUrl()) {
+ $resultRedirect->setUrl($redirectUrl);
+ return $resultRedirect;
+ }
+ } catch (\Exception $e) {
+ $this->messageManager->addErrorMessage(__('Your payment has been declined. Please try again.'));
+ $resultRedirect->setUrl($this->url->getFailureUrl());
+ }
+ } else {
+ $this->messageManager->addErrorMessage(__('Something went wrong while processing your order.'));
+ $resultRedirect->setUrl($this->url->getFailureUrl());
+ }
+ }
+
+ return $resultRedirect;
+ }
+
+ /**
+ * Check order state
+ *
+ * @param Order $order
+ * @return bool
+ */
+ protected function _checkOrderState(Order $order): bool
+ {
+ return in_array($order->getState(), $this->allowedOrderStates);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function createCsrfValidationException(
+ RequestInterface $request
+ ): ?InvalidRequestException {
+ return null;
+ }
+
+ /**
+ * Disable Magento's CSRF validation.
+ *
+ * @param RequestInterface $request
+ * @return bool|null
+ */
+ public function validateForCsrf(RequestInterface $request): ?bool
+ {
+ return true;
+ }
+}
diff --git a/Cron/CancelExpiredPendingOrder.php b/Cron/CancelExpiredPendingOrder.php
new file mode 100644
index 0000000..89be78e
--- /dev/null
+++ b/Cron/CancelExpiredPendingOrder.php
@@ -0,0 +1,222 @@
+transactionProcessor = $transactionProcessor;
+ $this->orderManagement = $orderManagement;
+ $this->orderCollectionFactory = $orderCollectionFactory;
+ $this->orderFactory = $orderFactory;
+ $this->metaRepository = $metaRepository;
+ $this->iterator = $iterator;
+ $this->kbankEmbeddedConfig = $kbankEmbeddedConfig;
+ $this->orderRepository = $orderRepository;
+ $this->logger = $logger;
+ }
+
+ /**
+ * Cancel the deprecated order
+ *
+ * @return $this
+ */
+ public function execute()
+ {
+ $expiredPendingMinutes = $this->kbankEmbeddedConfig->getExpiredPendingTimes();
+
+ if (!$expiredPendingMinutes) {
+ return $this;
+ }
+
+ /** @var $orders OrderCollection */
+ $orders = $this->orderCollectionFactory->create();
+ $orders->addFieldToFilter('status', ['in' => ['pending', 'pending_payment']]);
+ $orders->getSelect()->where(
+ new \Zend_Db_Expr(
+ 'TIME_TO_SEC(TIMEDIFF(CURRENT_TIMESTAMP, `updated_at`)) >= ' . $expiredPendingMinutes * 60
+ )
+ );
+
+ $orders->getSelect()
+ ->join(
+ ["sop" => "sales_order_payment"],
+ 'main_table.entity_id = sop.parent_id',
+ ['method']
+ )
+ ->where(
+ 'sop.method IN (?)',
+ [
+ KbankEmbeddedConfig::METHOD_KBANK_INLINE,
+ KbankEmbeddedConfig::METHOD_KBANK_EMBEDDED_INSTALLMENT,
+ KbankEmbeddedConfig::METHOD_KBANK_EMBEDDED_FULLPAYMENT
+ ]
+ )
+ ->limit(5);
+
+ $orders->setOrder(
+ 'created_at',
+ 'desc'
+ );
+
+ if ($orders->getSize() > 0) {
+ $this->iterator->walk(
+ $orders->getSelect(),
+ [[$this, 'callbackUpdateKbankOrders']],
+ [
+ 'order' => $this->orderFactory->create()
+ ]
+ );
+ }
+
+ return $this;
+ }
+
+ /**
+ * Callback update
+ *
+ * @param array $args
+ * @throws \Exception
+ */
+ public function callbackUpdateKbankOrders($args)
+ {
+ /** @var Order $order */
+ $order = $args['order'];
+ $order->setData($args['row']);
+
+ try {
+ $meta = $this->metaRepository->getByOrderIncrement($order->getIncrementId());
+ $resultResponse = $this->transactionProcessor
+ ->transactionDetail($order, $meta->getChargeId());
+
+ $this->processInquiryResponse($order, $resultResponse);
+ } catch (\Exception $e) {
+ $this->logger->error('Error while processing deprecated orders: ' . $e->getMessage());
+ $order->cancel();
+ $this->orderRepository->save($order);
+ } finally {
+ return;
+ }
+ }
+
+ /**
+ * Try to Inquiry order before cancel
+ *
+ * @param Order $order
+ * @param array $resultResponse
+ */
+ protected function processInquiryResponse(Order $order, array $resultResponse)
+ {
+ if (!isset($resultResponse['object'])
+ || !isset($resultResponse['transaction_state'])
+ ) {
+ $order->addCommentToStatusHistory(
+ __("Invalid returned while inquiring or invalid transaction state"),
+ $order->getStatus(),
+ )->setIsCustomerNotified(false);
+
+ $order->cancel();
+ }
+
+ if ($resultResponse['object'] == KbankEmbeddedConfig::VALIDATION_3DS_VALIDATION_OBJECT
+ && $resultResponse['transaction_state'] == KbankEmbeddedConfig::TRANSACTION_STATE_AUTH
+ ) {
+ $order->setState(Order::STATE_PROCESSING)
+ ->setStatus($order->getConfig()->getStateDefaultStatus(Order::STATE_PROCESSING));
+ $order->addStatusToHistory($order->getStatus(), __('Payment re-Authorized automatically.'));
+
+ if ($this->kbankEmbeddedConfig->isAutoInvoice()
+ && $order->canInvoice()
+ ) {
+ $this->transactionProcessor->createInvoice($order);
+ }
+ } else {
+ $order->addCommentToStatusHistory(
+ __("Invalid returned while inquiring or invalid transaction state"),
+ $order->getStatus(),
+ )->setIsCustomerNotified(false);
+ $order->cancel();
+ }
+
+ $this->orderRepository->save($order);
+ }
+}
diff --git a/Gateway/Command/AuthorizeCommand.php b/Gateway/Command/AuthorizeCommand.php
new file mode 100644
index 0000000..8bbf248
--- /dev/null
+++ b/Gateway/Command/AuthorizeCommand.php
@@ -0,0 +1,91 @@
+commandPool = $commandPool;
+ $this->transactionRepository = $repository;
+ $this->filterBuilder = $filterBuilder;
+ $this->searchCriteriaBuilder = $searchCriteriaBuilder;
+ $this->subjectReader = $subjectReader;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function execute(array $commandSubject)
+ {
+ /** @var PaymentDataObjectInterface $paymentDO */
+ $paymentDO = $this->subjectReader->readPayment($commandSubject);
+
+ $command = $this->getCommand($paymentDO);
+ $this->commandPool->get($command)
+ ->execute($commandSubject);
+ }
+
+ /**
+ * Get execution command name.
+ *
+ * @param PaymentDataObjectInterface $paymentDO
+ * @return string
+ */
+ private function getCommand(PaymentDataObjectInterface $paymentDO)
+ {
+ $payment = $paymentDO->getPayment();
+ ContextHelper::assertOrderPayment($payment);
+ return self::AUTHORIZE;
+ }
+}
diff --git a/Gateway/Command/CancelCommand.php b/Gateway/Command/CancelCommand.php
new file mode 100644
index 0000000..2e60cf7
--- /dev/null
+++ b/Gateway/Command/CancelCommand.php
@@ -0,0 +1,92 @@
+request = $request;
+ $this->validator = $validator;
+ $this->logger = $logger;
+ $this->handler = $handler;
+ }
+
+ /**
+ * Executes command basing on business object
+ *
+ * @param array $commandSubject
+ * @return null|Command\ResultInterface
+ * @throws CommandException
+ */
+ public function execute(array $commandSubject)
+ {
+ $response = $this->request->getParams();
+ /** @var Payment $payment */
+ $result = $this->validator->validate(
+ array_merge($commandSubject, ['response' => $response])
+ );
+
+ if (!$result->isValid()) {
+ $this->logExceptions($result->getFailsDescription());
+ throw new CommandException(
+ __('Transaction has been declined. Please try again later.')
+ );
+ }
+
+ $this->handler->handle(
+ array_merge($commandSubject),
+ $response
+ );
+ }
+
+ /**
+ * Log exceptions
+ *
+ * @param Phrase[] $fails
+ * @return void
+ */
+ private function logExceptions(array $fails)
+ {
+ foreach ($fails as $failPhrase) {
+ $this->logger->info((string) $failPhrase);
+ }
+ }
+}
diff --git a/Gateway/Command/CaptureCommand.php b/Gateway/Command/CaptureCommand.php
new file mode 100644
index 0000000..4926e35
--- /dev/null
+++ b/Gateway/Command/CaptureCommand.php
@@ -0,0 +1,139 @@
+commandPool = $commandPool;
+ $this->transactionRepository = $repository;
+ $this->filterBuilder = $filterBuilder;
+ $this->searchCriteriaBuilder = $searchCriteriaBuilder;
+ $this->subjectReader = $subjectReader;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function execute(array $commandSubject)
+ {
+ /** @var PaymentDataObjectInterface $paymentDO */
+ $paymentDO = $this->subjectReader->readPayment($commandSubject);
+
+ $command = $this->getCommand($paymentDO);
+ $this->commandPool->get($command)
+ ->execute($commandSubject);
+ }
+
+ /**
+ * Get execution command name.
+ *
+ * @TODO: Need to implement CAPTURE as Settle;
+ * Settling an order only can be done after 1 day order authorized;
+ *
+ * @param PaymentDataObjectInterface $paymentDO
+ * @return string
+ */
+ private function getCommand(PaymentDataObjectInterface $paymentDO)
+ {
+ $payment = $paymentDO->getPayment();
+ ContextHelper::assertOrderPayment($payment);
+
+ // If auth transaction does not exist then execute authorize&capture command
+ $captureExists = $this->captureTransactionExists($payment);
+ if (!$payment->getAuthorizationTransaction() && !$captureExists) {
+ return self::AUTHORIZE;
+ }
+
+ return self::DO_NOTHING;
+ //return self::CAPTURE;
+ }
+
+ /**
+ * Check if capture transaction already exists
+ *
+ * @param OrderPaymentInterface $payment
+ * @return bool
+ */
+ private function captureTransactionExists(OrderPaymentInterface $payment)
+ {
+ $this->searchCriteriaBuilder->addFilters(
+ [
+ $this->filterBuilder
+ ->setField('payment_id')
+ ->setValue($payment->getId())
+ ->create(),
+ ]
+ );
+
+ $this->searchCriteriaBuilder->addFilters(
+ [
+ $this->filterBuilder
+ ->setField('txn_type')
+ ->setValue(TransactionInterface::TYPE_CAPTURE)
+ ->create(),
+ ]
+ );
+
+ $searchCriteria = $this->searchCriteriaBuilder->create();
+ $count = $this->transactionRepository->getList($searchCriteria)
+ ->getTotalCount();
+
+ return $count > 0;
+ }
+}
diff --git a/Gateway/Command/DoNothingCommand.php b/Gateway/Command/DoNothingCommand.php
new file mode 100644
index 0000000..b8a71c2
--- /dev/null
+++ b/Gateway/Command/DoNothingCommand.php
@@ -0,0 +1,16 @@
+commandPool = $commandPool;
+ $this->subjectReader = $subjectReader;
+ $this->config = $config;
+ $this->handler = $handler;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function execute(array $commandSubject)
+ {
+ $command = $this->commandPool->get('get_transaction_details');
+ $result = $command->execute($commandSubject);
+ $response = $result->get();
+
+ if ($this->handler) {
+ $this->handler->handle($commandSubject, $response);
+ }
+
+ $rawDetails = $source = [];
+ foreach ($response as $key => &$value) {
+ if (!$value || empty($value)) {
+ $value = '';
+ }
+
+ if ($key == 'source') {
+ $source = $value;
+ }
+
+ if (!is_array($value)) {
+ $rawDetails[$key] = $response[$key];
+ }
+ }
+
+ return array_merge($source, $rawDetails);
+ }
+}
diff --git a/Gateway/Command/GatewayQueryCommand.php b/Gateway/Command/GatewayQueryCommand.php
new file mode 100644
index 0000000..e97c9f9
--- /dev/null
+++ b/Gateway/Command/GatewayQueryCommand.php
@@ -0,0 +1,94 @@
+requestBuilder = $requestBuilder;
+ $this->transferFactory = $transferFactory;
+ $this->client = $client;
+ $this->validator = $validator;
+ $this->logger = $logger;
+ }
+
+ /**
+ * @inheritdoc
+ *
+ * @throws Exception
+ */
+ public function execute(array $commandSubject)
+ {
+ $transferO = $this->transferFactory->create(
+ $this->requestBuilder->build($commandSubject)
+ );
+
+ try {
+ $response = $this->client->placeRequest($transferO);
+ } catch (Exception $e) {
+ $this->logger->critical($e);
+
+ throw new CommandException(__('There was an error while trying to process the request.'));
+ }
+
+ $result = $this->validator->validate(
+ array_merge($commandSubject, ['response' => $response])
+ );
+ if (!$result->isValid()) {
+ throw new CommandException(__('There was an error while trying to process the request.'));
+ }
+
+ return new ArrayResult($response);
+ }
+}
diff --git a/Gateway/Command/InitializeCommand.php b/Gateway/Command/InitializeCommand.php
new file mode 100644
index 0000000..6788d39
--- /dev/null
+++ b/Gateway/Command/InitializeCommand.php
@@ -0,0 +1,41 @@
+getPayment();
+ $order = $payment->getOrder();
+ ContextHelper::assertOrderPayment($payment);
+ if (!$payment instanceof Payment) {
+ throw new \LogicException('Order Payment should be provided');
+ }
+
+ $payment->setAmountAuthorized($order->getTotalDue());
+ $payment->setBaseAmountAuthorized($order->getBaseTotalDue());
+
+ $payment->getOrder()->setCanSendNewEmailFlag(false);
+ $stateObject->setData(OrderInterface::STATE, Order::STATE_PENDING_PAYMENT);
+ $stateObject->setData(OrderInterface::STATUS, Order::STATE_PENDING_PAYMENT);
+ $stateObject->setData('is_notified', false);
+ }
+}
diff --git a/Gateway/Command/RefundTransactionStrategyCommand.php b/Gateway/Command/RefundTransactionStrategyCommand.php
new file mode 100644
index 0000000..46d8ba1
--- /dev/null
+++ b/Gateway/Command/RefundTransactionStrategyCommand.php
@@ -0,0 +1,72 @@
+commandPool = $commandPool;
+ $this->subjectReader = $subjectReader;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function execute(array $commandSubject)
+ {
+ $command = $this->getCommand($commandSubject);
+
+ $this->commandPool->get($command)
+ ->execute($commandSubject);
+ }
+
+ /**
+ * Determines the command that should be used based on the status of the transaction
+ *
+ * @param array $commandSubject
+ * @return string
+ * @throws CommandException|NotFoundException
+ */
+ private function getCommand(array $commandSubject)
+ {
+ $details = $this->commandPool->get('get_transaction_details')
+ ->execute($commandSubject)
+ ->get();
+
+ if ($details['transaction_state'] === \GhoSter\KbankPayments\Gateway\Config::TRANSACTION_STATE_AUTH) {
+ return self::VOID;
+ } elseif ($details['transaction_state'] !== \GhoSter\KbankPayments\Gateway\Config::TRANSACTION_STATE_SETTLED) {
+ throw new CommandException(__('This transaction cannot be refunded with its current status.'));
+ }
+
+ return self::REFUND;
+ }
+}
diff --git a/Gateway/Config.php b/Gateway/Config.php
new file mode 100644
index 0000000..5f985de
--- /dev/null
+++ b/Gateway/Config.php
@@ -0,0 +1,315 @@
+getValue(Config::KEY_ACTIVATE, $storeId);
+ }
+
+ /**
+ * Gets the login id
+ *
+ * @param int|null $storeId
+ * @return string
+ */
+ public function getPublicKey(?int $storeId = null): ?string
+ {
+ $publicKey = $this->getGeneralConfig(Config::KEY_PUBLIC_KEY, $storeId);
+
+ if (mb_detect_encoding($publicKey) !== 'ASCII') {
+ return '';
+ }
+
+ return $publicKey;
+ }
+
+ /**
+ * Gets the secret key id
+ *
+ * @param ?int $storeId
+ * @return string
+ */
+ public function getSecretKey(?int $storeId = null): ?string
+ {
+ $secretKey = $this->getGeneralConfig(Config::KEY_SECRET_KEY, $storeId);
+
+ if (mb_detect_encoding($secretKey) !== 'ASCII') {
+ return '';
+ }
+
+ return $secretKey;
+ }
+
+ /**
+ * Get Shop name
+ *
+ * @param ?int $storeId
+ * @return string
+ */
+ public function getShopName(?int $storeId = null): ?string
+ {
+ return $this->getGeneralConfig(Config::KEY_SHOP_NAME, $storeId);
+ }
+
+ /**
+ * Gets the current environment
+ *
+ * @param ?int $storeId
+ * @return string
+ */
+ public function getEnvironment(?int $storeId = null): ?string
+ {
+ $config = $this->getGeneralConfig(Config::KEY_ENVIRONMENT, $storeId);
+ return $config ?? Environment::ENVIRONMENT_SANDBOX;
+ }
+
+ /**
+ * Is Debug mode
+ *
+ * @param ?int $storeId
+ * @return bool
+ */
+ public function isDebugMode(?int $storeId = null): bool
+ {
+ return (bool)$this->getGeneralConfig(Config::KEY_DEBUG, $storeId);
+ }
+
+ /**
+ * Gets the API endpoint URL
+ *
+ * @param ?int $storeId
+ * @return string
+ */
+ public function getApiUrl(?int $storeId = null): ?string
+ {
+ $environment = $this->getEnvironment($storeId);
+
+ return $environment === Environment::ENVIRONMENT_SANDBOX
+ ? self::ENDPOINT_URL_SANDBOX
+ : self::ENDPOINT_URL_PRODUCTION;
+ }
+
+ /**
+ * Gets the API js embedded endpoint URL
+ *
+ * @param ?int $storeId
+ * @return string
+ */
+ public function getJsSrc(?int $storeId = null): ?string
+ {
+ $environment = $this->getEnvironment($storeId);
+
+ return $environment === Environment::ENVIRONMENT_SANDBOX
+ ? self::ENDPOINT_JS_URL_SANDBOX
+ : self::ENDPOINT_JS_URL_PRODUCTION;
+ }
+
+ /**
+ * Expired time
+ *
+ * @param ?int $storeId
+ * @return int
+ */
+ public function getExpiredPendingTimes(?int $storeId = null): ?int
+ {
+ return $this->getGeneralConfig(Config::KEY_EXPIRED_PENDING_TIME, $storeId);
+ }
+
+ /**
+ * Pre-defined Installment Selection
+ *
+ * @param ?int $storeId
+ * @return bool
+ */
+ public function isPredefinedInstallmentSelection(?int $storeId = null): bool
+ {
+ return (bool)$this->getValue(Config::KEY_PRE_DEFINED_INSTALLMENT_SELECTION, $storeId);
+ }
+
+ /**
+ * Merchant ID
+ *
+ * @param ?int $storeId
+ * @return string|null
+ */
+ public function getMerchantId(?int $storeId = null): ?string
+ {
+ return (string)$this->getValue(Config::KEY_MERCHANT_ID, $storeId);
+ }
+
+ /**
+ * Is 3ds
+ *
+ * @param ?int $storeId
+ * @return bool
+ */
+ public function is3DS(?int $storeId = null): bool
+ {
+ return (bool)$this->getGeneralConfig(Config::KEY_3DS_SUPPORT, $storeId);
+ }
+
+ /**
+ * Is auto invoice
+ *
+ * @param ?int $storeId
+ * @return bool
+ */
+ public function isAutoInvoice(?int $storeId = null): bool
+ {
+ return (bool)$this->getGeneralConfig(Config::KEY_AUTO_INVOICE, $storeId);
+ }
+
+ /**
+ * Get custom frontend response
+ *
+ * @param int|null $storeId
+ * @return string
+ */
+ public function getFrontendResponseUrl(?int $storeId = null): string
+ {
+ if ($this->is3DS($storeId)) {
+ return '';
+ }
+ return (string)$this->getGeneralConfig(Config::KEY_FRONTEND_RESPONSE, $storeId);
+ }
+
+ /**
+ * Installment Support enabling
+ *
+ * @param ?int $storeId
+ * @return bool
+ */
+ public function isInstallmentSupport(?int $storeId = null): bool
+ {
+ return (bool)$this->getValue(Config::KEY_INSTALLMENT_SUPPORT, $storeId);
+ }
+
+ /**
+ * Get Terminal Info
+ *
+ * @param int|null $storeId
+ * @return string|null
+ */
+ public function getTerminalId(?int $storeId = null): ?string
+ {
+ return (string)$this->getValue(Config::KEY_TERMINAL_ID, $storeId);
+ }
+
+ /**
+ * Installment Information
+ *
+ * @param ?int $storeId
+ * @return string|null
+ */
+ public function getInstallmentInfo(?int $storeId = null): ?string
+ {
+ return $this->getValue(Config::KEY_INSTALLMENT_INFO, $storeId);
+ }
+
+ /**
+ * Get scope config
+ *
+ * @return ScopeConfigInterface
+ * @deprecated 100.0.10
+ * @see \Magento\Framework\App\ObjectManager\ConfigWriterInterface
+ */
+ protected function getScopeConfig(): ScopeConfigInterface
+ {
+ if ($this->scopeConfig === null) {
+ $this->scopeConfig = ObjectManager::getInstance()->get(
+ ScopeConfigInterface::class
+ );
+ }
+
+ return $this->scopeConfig;
+ }
+
+ /**
+ * General configuration
+ *
+ * @param mixed $field
+ * @param ?int $storeId
+ * @return mixed
+ */
+ public function getGeneralConfig($field, ?int $storeId = null)
+ {
+ return $this->getScopeConfig()->getValue(
+ sprintf(self::GENERAL_PATH_PATTERN, $field),
+ ScopeInterface::SCOPE_STORE,
+ $storeId
+ );
+ }
+
+ /**
+ * Specific payment configuration value
+ *
+ * @param string $paymentCode
+ * @param string $field
+ * @param int|null $storeId
+ * @return mixed
+ */
+ public function getSpecificPaymentConfig(string $paymentCode, string $field, ?int $storeId = null)
+ {
+ return $this->getScopeConfig()->getValue(
+ sprintf(self::PAYMENT_PATH_PATTERN, $paymentCode, $field),
+ ScopeInterface::SCOPE_STORE,
+ $storeId
+ );
+ }
+}
diff --git a/Gateway/Http/Client.php b/Gateway/Http/Client.php
new file mode 100644
index 0000000..f211e4f
--- /dev/null
+++ b/Gateway/Http/Client.php
@@ -0,0 +1,201 @@
+httpClientFactory = $httpClientFactory;
+ $this->config = $config;
+ $this->logger = $logger;
+ $this->json = $json;
+ }
+
+ /**
+ * Places request to gateway. Returns result as ENV array
+ *
+ * @param TransferInterface $transferObject
+ * @return array
+ * @throws ClientException
+ */
+ public function placeRequest(TransferInterface $transferObject)
+ {
+ try {
+ $request = $transferObject->getBody();
+ $this->logData = [
+ 'request' => $request,
+ ];
+
+ return $this->postRequest($request);
+ // phpcs:ignore Magento2.Exceptions.ThrowCatch
+ } catch (\Exception $e) {
+ $this->logger->critical($e);
+
+ throw new ClientException(
+ __('Something went wrong in the payment gateway.')
+ );
+ } finally {
+ $this->logger->debug($this->logData);
+ }
+ }
+
+ /**
+ * Post request
+ *
+ * @param array $request
+ * @return array
+ * @throws \Zend_Http_Client_Exception
+ */
+ public function postRequest(&$request)
+ {
+ $this->logData['request'] = $request;
+ $payLoadType = $request['payload_type'];
+ $requestType = $request['method_type'];
+
+ if (isset($request['payload_type'])) {
+ unset($request['payload_type']);
+ }
+
+ if (isset($request['method_type'])) {
+ unset($request['method_type']);
+ }
+
+ if (!in_array($requestType, ['GET', 'POST', 'PUT', 'DELETE'])) {
+ //phpcs:ignore Magento2.Exceptions.DirectThrow
+ throw new \Exception('Send first parameter must be "GET", "POST", "PUT" or "DELETE"');
+ }
+
+ $url = $this->getEndpointUrl($payLoadType, $request);
+
+ try {
+
+ /** @var ZendClient $client */
+ $client = $this->httpClientFactory->create();
+
+ $client->setUri($url);
+ $client->setConfig(['maxredirects' => 0, 'timeout' => 30]);
+ $client->setHeaders([
+ 'Content-Type: application/json',
+ 'x-api-key: ' . $this->config->getSecretKey(),
+ ]);
+
+ $client->setMethod($requestType);
+
+ if (!empty($request)) {
+ $client->setRawData($this->json->serialize($request), 'application/json');
+ }
+
+ $responseBody = $client->request()
+ ->getBody();
+
+ $this->logData['response'] = $responseBody;
+
+ $data = $this->json->unserialize($responseBody);
+ } catch (InvalidArgumentException $e) {
+ // phpcs:ignore Magento2.Exceptions.DirectThrow
+ throw new \Exception('Invalid JSON was returned by the gateway');
+ } finally {
+ $this->logger->debug($this->logData);
+ }
+
+ return $data;
+ }
+
+ /**
+ * Get Endpoint Url
+ *
+ * @param string $payloadType
+ * @param array $request
+ * @return string
+ */
+ private function getEndpointUrl($payloadType, &$request)
+ {
+ $url = $this->config->getApiUrl();
+
+ switch ($payloadType) {
+ case 'createAuthorizeRequest':
+ $url .= '/card/v2/charge';
+ break;
+
+ case 'createSettleRequest':
+ $url .= '/card/v2/charge/{charge_id}/settle';
+ break;
+
+ case 'transactionDetailsRequest':
+ $url .= '/card/v2/charge/{charge_id}';
+ break;
+
+ case 'voidRequest':
+ $url .= '/card/v2/charge/{charge_id}/void';
+ break;
+
+ case 'refundRequest':
+ $url .= '/card/v2/charge/{charge_id}/refund';
+ break;
+ default:
+ break;
+ }
+
+ if (preg_match_all('/{+(.*?)}/', $url, $matches)) {
+ if (!empty($matches[1])) {
+ foreach ($matches[1] as $match) {
+ if (array_key_exists($match, $request)) {
+ $url = str_replace('{' . $match . '}', $request[$match], $url);
+ unset($request[$match]);
+ }
+ }
+ }
+ }
+
+ return $url;
+ }
+}
diff --git a/Gateway/Http/Payload/Filter/RemoveFieldsFilter.php b/Gateway/Http/Payload/Filter/RemoveFieldsFilter.php
new file mode 100644
index 0000000..60084be
--- /dev/null
+++ b/Gateway/Http/Payload/Filter/RemoveFieldsFilter.php
@@ -0,0 +1,35 @@
+fields = $fields;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function filter(array $data)
+ {
+ foreach ($this->fields as $field) {
+ unset($data[$field]);
+ }
+
+ return $data;
+ }
+}
diff --git a/Gateway/Http/Payload/FilterInterface.php b/Gateway/Http/Payload/FilterInterface.php
new file mode 100644
index 0000000..1809a35
--- /dev/null
+++ b/Gateway/Http/Payload/FilterInterface.php
@@ -0,0 +1,17 @@
+transferBuilder = $transferBuilder;
+ $this->payloadFilters = $payloadFilters;
+ }
+
+ /**
+ * Builds gateway transfer object
+ *
+ * @param array $request
+ * @return TransferInterface
+ */
+ public function create(array $request)
+ {
+ foreach ($this->payloadFilters as $filter) {
+ $request = $filter->filter($request);
+ }
+
+ return $this->transferBuilder
+ ->setBody($request)
+ ->build();
+ }
+}
diff --git a/Gateway/Request/AuthorizeDataBuilder.php b/Gateway/Request/AuthorizeDataBuilder.php
new file mode 100644
index 0000000..de4dc2a
--- /dev/null
+++ b/Gateway/Request/AuthorizeDataBuilder.php
@@ -0,0 +1,54 @@
+subjectReader = $subjectReader;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function build(array $buildSubject)
+ {
+ $paymentDO = $this->subjectReader->readPayment($buildSubject);
+ $payment = $paymentDO->getPayment();
+ $order = $paymentDO->getOrder();
+ $data = [];
+
+ $tokenValue = $payment->getAdditionalInformation('tokenValue');
+
+ if ($payment instanceof Payment) {
+
+ $data = [
+ 'amount' => $order->getGrandTotalAmount(),
+ 'currency' => $order->getCurrencyCode(),
+ 'description' => $order->getOrderIncrementId(),
+ 'source_type' => 'card',
+ 'mode' => 'token',
+ 'reference_order' => $order->getOrderIncrementId(),
+ 'token' => $tokenValue
+ ];
+ }
+
+ return $data;
+ }
+}
diff --git a/Gateway/Request/MethodTypeBuilder.php b/Gateway/Request/MethodTypeBuilder.php
new file mode 100644
index 0000000..ebad74d
--- /dev/null
+++ b/Gateway/Request/MethodTypeBuilder.php
@@ -0,0 +1,38 @@
+type = $type;
+ }
+
+ /**
+ * Adds the type of the request to the build subject
+ *
+ * @param array $buildSubject
+ * @return array
+ *
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ public function build(array $buildSubject)
+ {
+ return [
+ 'method_type' => $this->type
+ ];
+ }
+}
diff --git a/Gateway/Request/RefundDataBuilder.php b/Gateway/Request/RefundDataBuilder.php
new file mode 100644
index 0000000..bfd79b7
--- /dev/null
+++ b/Gateway/Request/RefundDataBuilder.php
@@ -0,0 +1,52 @@
+subjectReader = $subjectReader;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function build(array $buildSubject)
+ {
+ $paymentDO = $this->subjectReader->readPayment($buildSubject);
+ $payment = $paymentDO->getPayment();
+ $order = $paymentDO->getOrder();
+
+ $data = [];
+
+ if ($payment instanceof Payment) {
+ $authTransaction = $payment->getAuthorizationTransaction();
+ $chargeId = $authTransaction->getAdditionalInformation('charge_id');
+
+ $data = [
+ 'charge_id' => $chargeId,
+ 'amount' => $order->getGrandTotalAmount(),
+ 'reason' => 'Refund transaction for reference_order: ' . $order->getOrderIncrementId()
+ ];
+ }
+
+ return $data;
+ }
+}
diff --git a/Gateway/Request/RequestTypeBuilder.php b/Gateway/Request/RequestTypeBuilder.php
new file mode 100644
index 0000000..ca7b1af
--- /dev/null
+++ b/Gateway/Request/RequestTypeBuilder.php
@@ -0,0 +1,38 @@
+type = $type;
+ }
+
+ /**
+ * Adds the type of the request to the build subject
+ *
+ * @param array $buildSubject
+ * @return array
+ *
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ public function build(array $buildSubject)
+ {
+ return [
+ 'payload_type' => $this->type
+ ];
+ }
+}
diff --git a/Gateway/Request/SettleDataBuilder.php b/Gateway/Request/SettleDataBuilder.php
new file mode 100644
index 0000000..960cb63
--- /dev/null
+++ b/Gateway/Request/SettleDataBuilder.php
@@ -0,0 +1,50 @@
+subjectReader = $subjectReader;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function build(array $buildSubject)
+ {
+ $paymentDO = $this->subjectReader->readPayment($buildSubject);
+ $payment = $paymentDO->getPayment();
+ $order = $paymentDO->getOrder();
+ $data = [];
+
+ if ($payment instanceof Payment) {
+ $authTransaction = $payment->getAuthorizationTransaction();
+ $chargeId = $authTransaction->getAdditionalInformation('charge_id');
+
+ $data = [
+ 'charge_id' => $chargeId,
+ 'amount' => $order->getGrandTotalAmount(),
+ 'type' => 'full'
+ ];
+ }
+
+ return $data;
+ }
+}
diff --git a/Gateway/Request/SourceTypeBuilder.php b/Gateway/Request/SourceTypeBuilder.php
new file mode 100644
index 0000000..3902ec4
--- /dev/null
+++ b/Gateway/Request/SourceTypeBuilder.php
@@ -0,0 +1,35 @@
+type = $type;
+ }
+
+ /**
+ * Adds the type of the request to the build subject
+ *
+ * @param array $buildSubject
+ * @return array
+ *
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ public function build(array $buildSubject)
+ {
+ return [
+ 'source_type' => $this->type
+ ];
+ }
+}
diff --git a/Gateway/Request/TransactionDetailsDataBuilder.php b/Gateway/Request/TransactionDetailsDataBuilder.php
new file mode 100644
index 0000000..9d8a632
--- /dev/null
+++ b/Gateway/Request/TransactionDetailsDataBuilder.php
@@ -0,0 +1,68 @@
+subjectReader = $subjectReader;
+ $this->metaRepository = $metaRepository;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function build(array $buildSubject)
+ {
+ $chargeId = null;
+ $paymentDO = $this->subjectReader->readPayment($buildSubject);
+ $payment = $paymentDO->getPayment();
+
+ if ($payment instanceof Payment) {
+ $authTransaction = $payment->getAuthorizationTransaction();
+ $chargeId = $authTransaction->getAdditionalInformation('charge_id');
+ }
+
+ if (!$chargeId) {
+ try {
+ $order = $payment->getOrder();
+ $meta = $this->metaRepository->getByOrderIncrement($order->getIncrementId());
+ $chargeId = $meta->getChargeId();
+ } catch (\Exception $e) {
+ return [
+ 'charge_id' => $chargeId
+ ];
+ }
+
+ }
+
+ return [
+ 'charge_id' => $chargeId
+ ];
+ }
+}
diff --git a/Gateway/Request/VoidDataBuilder.php b/Gateway/Request/VoidDataBuilder.php
new file mode 100644
index 0000000..c3d49a6
--- /dev/null
+++ b/Gateway/Request/VoidDataBuilder.php
@@ -0,0 +1,51 @@
+subjectReader = $subjectReader;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function build(array $buildSubject)
+ {
+ $paymentDO = $this->subjectReader->readPayment($buildSubject);
+ $payment = $paymentDO->getPayment();
+ $order = $paymentDO->getOrder();
+
+ $data = [];
+
+ if ($payment instanceof Payment) {
+ $authTransaction = $payment->getAuthorizationTransaction();
+ $chargeId = $authTransaction->getAdditionalInformation('charge_id');
+
+ $data = [
+ 'charge_id' => $chargeId,
+ 'reason' => 'Cancel transaction for reference_order: ' . $order->getOrderIncrementId()
+ ];
+ }
+
+ return $data;
+ }
+}
diff --git a/Gateway/Response/Handler/Authorize/AuthTransaction.php b/Gateway/Response/Handler/Authorize/AuthTransaction.php
new file mode 100644
index 0000000..268d2ab
--- /dev/null
+++ b/Gateway/Response/Handler/Authorize/AuthTransaction.php
@@ -0,0 +1,96 @@
+subjectReader = $subjectReader;
+ $this->paymentResource = $paymentResource;
+ $this->transactionBuilder = $transactionBuilder;
+ $this->logger = $logger;
+ }
+
+ /**
+ * Handle the Auth
+ *
+ * @param array $handlingSubject
+ * @param array $response
+ */
+ public function handle(array $handlingSubject, array $response)
+ {
+ try {
+ $paymentDO = $this->subjectReader->readPayment($handlingSubject);
+ $payment = $paymentDO->getPayment();
+
+ if ($payment instanceof Payment) {
+ $chargeId = $response['id'];
+ $source = $response['source'] ?? [];
+
+ $order = $payment->getOrder();
+ $formattedPrice = $order->getBaseCurrency()->formatTxt(
+ $order->getGrandTotal()
+ );
+
+ $transaction = $this->transactionBuilder->setPayment($payment)
+ ->setOrder($order)
+ ->setTransactionId($chargeId)
+ ->setAdditionalInformation(
+ $payment->getTransactionAdditionalInfo()
+ )
+ ->setFailSafe(true)
+ ->build(TransactionInterface::TYPE_AUTH);
+
+ $payment->setCcLast4($source['card_masking'] ? substr($source['card_masking'], -4) : '');
+ $payment->setCcAvsStatus('N/A');
+ $payment->setIsTransactionClosed(false);
+ $payment->addTransactionCommentsToOrder(
+ $transaction,
+ __('The Payment Authorization success. The authorized amount is %1.', $formattedPrice)
+ );
+ $payment->setParentTransactionId(null);
+ }
+ } catch (\Exception $e) {
+ $this->logger->info($e->getMessage());
+ }
+ }
+}
diff --git a/Gateway/Response/Handler/Authorize/OrderStatus.php b/Gateway/Response/Handler/Authorize/OrderStatus.php
new file mode 100644
index 0000000..6e3799b
--- /dev/null
+++ b/Gateway/Response/Handler/Authorize/OrderStatus.php
@@ -0,0 +1,62 @@
+subjectReader = $subjectReader;
+ $this->orderRepository = $orderRepository;
+ $this->paymentStatus = $paymentStatus;
+ }
+
+ /**
+ * Handle
+ *
+ * @param array $handlingSubject
+ * @param array $response
+ */
+ public function handle(array $handlingSubject, array $response)
+ {
+ $paymentDO = $this->subjectReader->readPayment($handlingSubject);
+ $payment = $paymentDO->getPayment();
+
+ if ($payment instanceof Payment) {
+ $order = $payment->getOrder();
+
+ $order->setState($this->paymentStatus->getOrderStateByPaymentStatus(PaymentStatus::PAYMENT_AUTHORIZED))
+ ->setStatus(PaymentStatus::PAYMENT_AUTHORIZED);
+ $order->addStatusToHistory($order->getStatus(), 'Payment Authorized.');
+ $this->orderRepository->save($order);
+ }
+ }
+}
diff --git a/Gateway/Response/Handler/Cancel/CancelOrder.php b/Gateway/Response/Handler/Cancel/CancelOrder.php
new file mode 100644
index 0000000..0658b0f
--- /dev/null
+++ b/Gateway/Response/Handler/Cancel/CancelOrder.php
@@ -0,0 +1,52 @@
+orderRepository = $orderRepository;
+ $this->evenManager = $evenManager;
+ }
+
+ /**
+ * Handle the request
+ *
+ * @param array $handlingSubject
+ * @param array $response
+ * @throws LocalizedException
+ */
+ public function handle(array $handlingSubject, array $response)
+ {
+ /** @var $order Order */
+ $order = $handlingSubject['order'];
+ $order->registerCancellation('Payment Failed. Cancel Order.');
+
+ $this->evenManager->dispatch('order_cancel_after', ['order' => $order]);
+ $this->orderRepository->save($order);
+ }
+}
diff --git a/Gateway/Response/Handler/Capture/CaptureTransaction.php b/Gateway/Response/Handler/Capture/CaptureTransaction.php
new file mode 100644
index 0000000..6064666
--- /dev/null
+++ b/Gateway/Response/Handler/Capture/CaptureTransaction.php
@@ -0,0 +1,97 @@
+logger = $logger;
+ $this->orderFactory = $orderFactory;
+ $this->transactionBuilder = $transactionBuilder;
+ $this->paymentResource = $paymentResource;
+ }
+
+ /**
+ * Handle
+ *
+ * @param array $handlingSubject
+ * @param array $response
+ */
+ public function handle(array $handlingSubject, array $response)
+ {
+ try {
+ $payment = $handlingSubject['payment']->getPayment();
+
+ if ($payment instanceof Payment) {
+ $chargeId = $response['id'];
+ $source = $response['source'] ?? [];
+ $order = $payment->getOrder();
+
+ $formattedPrice = $order->getBaseCurrency()->formatTxt(
+ $order->getGrandTotal()
+ );
+
+ $transaction = $this->transactionBuilder->setPayment($payment)
+ ->setOrder($order)
+ ->setTransactionId($chargeId)
+ ->setAdditionalInformation(
+ $payment->getTransactionAdditionalInfo()
+ )
+ ->setFailSafe(true)
+ ->build(TransactionInterface::TYPE_CAPTURE);
+
+ $payment->setCcLast4($source['card_masking'] ? substr($source['card_masking'], -4) : '');
+ $payment->setCcAvsStatus('N/A');
+ $payment->setAmountAuthorized($order->getGrandTotal());
+ $payment->setCcNumberEnc($source['card_masking'] ?? '');
+ $payment->addTransactionCommentsToOrder(
+ $transaction,
+ __('The Payment Capture success. Captured amount is %1.', $formattedPrice)
+ );
+ $payment->setParentTransactionId(null);
+ }
+ } catch (\Exception $e) {
+ $this->logger->warning($e->getMessage());
+ }
+ }
+}
diff --git a/Gateway/Response/Handler/Capture/OrderStatus.php b/Gateway/Response/Handler/Capture/OrderStatus.php
new file mode 100644
index 0000000..19a7188
--- /dev/null
+++ b/Gateway/Response/Handler/Capture/OrderStatus.php
@@ -0,0 +1,54 @@
+logger = $logger;
+ $this->orderFactory = $orderFactory;
+ $this->orderRepository = $orderRepository;
+ }
+
+ /**
+ * Handle
+ *
+ * @param array $handlingSubject
+ * @param array $response
+ */
+ public function handle(array $handlingSubject, array $response)
+ {
+ $order = $handlingSubject['order'];
+ $order->addStatusToHistory($order->getStatus(), 'Payment Captured.');
+ $this->orderRepository->save($order);
+ }
+}
diff --git a/Gateway/Response/Handler/ChargeIdHandler.php b/Gateway/Response/Handler/ChargeIdHandler.php
new file mode 100644
index 0000000..890986c
--- /dev/null
+++ b/Gateway/Response/Handler/ChargeIdHandler.php
@@ -0,0 +1,51 @@
+subjectReader = $subjectReader;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function handle(array $handlingSubject, array $response)
+ {
+ $paymentDO = $this->subjectReader->readPayment($handlingSubject);
+ $payment = $paymentDO->getPayment();
+
+ if ($payment instanceof Payment && isset($response['id']) && isset($response['object'])) {
+ if ($response['object'] === self::CHARGE_OBJECT) {
+ $order = $payment->getOrder();
+
+ $payment->setTransactionAdditionalInfo(
+ 'charge_id',
+ $response['id']
+ );
+
+ $order->addStatusToHistory($order->getStatus(), 'Processed Charge Id: ' . $response['id']);
+ }
+ }
+ }
+}
diff --git a/Gateway/Response/Handler/CloseParentTransactionHandler.php b/Gateway/Response/Handler/CloseParentTransactionHandler.php
new file mode 100644
index 0000000..d0ac810
--- /dev/null
+++ b/Gateway/Response/Handler/CloseParentTransactionHandler.php
@@ -0,0 +1,38 @@
+subjectReader = $subjectReader;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function handle(array $handlingSubject, array $response)
+ {
+ $paymentDO = $this->subjectReader->readPayment($handlingSubject);
+ $payment = $paymentDO->getPayment();
+
+ if ($payment instanceof Payment) {
+ $payment->setShouldCloseParentTransaction(true);
+ }
+ }
+}
diff --git a/Gateway/Response/Handler/CloseTransactionHandler.php b/Gateway/Response/Handler/CloseTransactionHandler.php
new file mode 100644
index 0000000..56d9475
--- /dev/null
+++ b/Gateway/Response/Handler/CloseTransactionHandler.php
@@ -0,0 +1,46 @@
+subjectReader = $subjectReader;
+ $this->closeTransaction = $closeTransaction;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function handle(array $handlingSubject, array $response)
+ {
+ $paymentDO = $this->subjectReader->readPayment($handlingSubject);
+ $payment = $paymentDO->getPayment();
+
+ if ($payment instanceof Payment) {
+ $payment->setIsTransactionClosed($this->closeTransaction);
+ $payment->setShouldCloseParentTransaction(true);
+ }
+ }
+}
diff --git a/Gateway/Response/Handler/VoidResponseHandler.php b/Gateway/Response/Handler/VoidResponseHandler.php
new file mode 100644
index 0000000..37c0f04
--- /dev/null
+++ b/Gateway/Response/Handler/VoidResponseHandler.php
@@ -0,0 +1,43 @@
+subjectReader = $subjectReader;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function handle(array $handlingSubject, array $response)
+ {
+ $paymentDO = $this->subjectReader->readPayment($handlingSubject);
+ $payment = $paymentDO->getPayment();
+
+ if ($payment instanceof Payment) {
+ $chargeId = $response['id'];
+
+ $payment->setIsTransactionClosed(true);
+ $payment->setShouldCloseParentTransaction(true);
+ $payment->setTransactionAdditionalInfo('real_transaction_id', $chargeId);
+ }
+ }
+}
diff --git a/Gateway/SubjectReader.php b/Gateway/SubjectReader.php
new file mode 100644
index 0000000..c5e3f26
--- /dev/null
+++ b/Gateway/SubjectReader.php
@@ -0,0 +1,90 @@
+readPayment($subject)
+ ->getOrder()
+ ->getStoreId();
+ // phpcs:ignore Magento2.CodeAnalysis.EmptyBlock
+ } catch (\InvalidArgumentException $e) {
+ // No store id is current set
+ }
+ }
+
+ return $storeId ? (int)$storeId : null;
+ }
+
+ /**
+ * Reads amount from subject
+ *
+ * @param array $subject
+ * @return string
+ */
+ public function readAmount(array $subject)
+ {
+ return (string)Helper\SubjectReader::readAmount($subject);
+ }
+
+ /**
+ * Reads response from subject
+ *
+ * @param array $subject
+ * @return array
+ */
+ public function readResponse(array $subject)
+ {
+ return Helper\SubjectReader::readResponse($subject);
+ }
+
+ /**
+ * Reads login id from subject
+ *
+ * @param array $subject
+ * @return string|null
+ */
+ public function readLoginId(array $subject)
+ {
+ return $subject['merchantAuthentication']['name'] ?? null;
+ }
+
+ /**
+ * Reads transaction key from subject
+ *
+ * @param array $subject
+ * @return string|null
+ */
+ public function readTransactionKey(array $subject)
+ {
+ return $subject['merchantAuthentication']['transactionKey'] ?? null;
+ }
+}
diff --git a/Gateway/Validator/GeneralResponseValidator.php b/Gateway/Validator/GeneralResponseValidator.php
new file mode 100644
index 0000000..ee1e551
--- /dev/null
+++ b/Gateway/Validator/GeneralResponseValidator.php
@@ -0,0 +1,56 @@
+resultFactory = $resultFactory;
+ $this->subjectReader = $subjectReader;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function validate(array $validationSubject)
+ {
+ $response = $this->subjectReader->readResponse($validationSubject);
+ $object = $response['object'];
+ $isValid = ($object != 'error');
+ $errorMessages = [];
+
+ if (!$isValid) {
+ if (isset($response['code'])) {
+ $errorMessages[] = $response['message'];
+ }
+ }
+
+ return $this->createResult($isValid, $errorMessages);
+ }
+}
diff --git a/Gateway/Validator/TransactionResponseValidator.php b/Gateway/Validator/TransactionResponseValidator.php
new file mode 100644
index 0000000..51d9e4b
--- /dev/null
+++ b/Gateway/Validator/TransactionResponseValidator.php
@@ -0,0 +1,79 @@
+subjectReader = $subjectReader;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function validate(array $validationSubject)
+ {
+ $response = $this->subjectReader->readResponse($validationSubject);
+
+ if ($this->isResponseCodeAnError($response)) {
+ $errorMessages = [];
+
+ if (isset($response['failure_code'])) {
+ $errorMessages[] = $response['failure_message'];
+ }
+
+ if ($response['object'] === 'error') {
+ $errorMessages[] = $response['message'];
+ }
+
+ return $this->createResult(false, $errorMessages);
+ }
+
+ return $this->createResult(true);
+ }
+
+ /**
+ * Determines if the response code is actually an error
+ *
+ * @param array $transactionResponse
+ * @return bool
+ */
+ private function isResponseCodeAnError(array $transactionResponse)
+ {
+ $object = $transactionResponse['object'] ?? false;
+
+ if (!$object) {
+ return true;
+ }
+
+ if ($object == 'error') {
+ return true;
+ }
+
+ if (isset($transactionResponse['failure_code']) || isset($transactionResponse['failure_message'])) {
+ return true;
+
+ }
+
+ return false;
+ }
+}
diff --git a/Helper/Checkout.php b/Helper/Checkout.php
new file mode 100644
index 0000000..391e79c
--- /dev/null
+++ b/Helper/Checkout.php
@@ -0,0 +1,52 @@
+session = $session;
+ $this->urlBuilder = $urlBuilder;
+ }
+
+ /**
+ * Cancel last placed order with specified comment message
+ *
+ * @param string $comment Comment appended to order history
+ * @return bool True if order cancelled, false otherwise
+ * @throws LocalizedException
+ */
+ public function cancelCurrentOrder($comment): bool
+ {
+ $order = $this->session->getLastRealOrder();
+ if ($order->getId() && $order->getState() != Order::STATE_CANCELED) {
+ $order->registerCancellation($comment)->save();
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/Logger/Handler.php b/Logger/Handler.php
new file mode 100644
index 0000000..d466f84
--- /dev/null
+++ b/Logger/Handler.php
@@ -0,0 +1,18 @@
+getConfig()->isDebugMode() && parent::isHandling($level);
+ }
+
+ /**
+ * Get configuration
+ *
+ * @return Config
+ */
+ public function getConfig()
+ {
+ if ($this->config === null) {
+ $this->config = ObjectManager::getInstance()->get(Config::class);
+ }
+
+ return $this->config;
+ }
+
+ /**
+ * This method allows for compatibility with common interfaces.
+ *
+ * @param string|array $message
+ * @param array $context
+ * @return void
+ */
+ public function debug($message, array $context = []): void
+ {
+ if (is_array($message)) {
+ $message = var_export($message, true);
+ }
+
+ parent::debug($message, $context);
+ }
+
+ /**
+ * This method allows for compatibility with common interfaces.
+ *
+ * @param string|array $message
+ * @param array $context
+ * @return void
+ */
+ public function info($message, array $context = []): void
+ {
+ if (is_array($message)) {
+ $message = var_export($message, true);
+ }
+
+ parent::info($message, $context);
+ }
+}
diff --git a/Model/Adminhtml/Source/Cctype.php b/Model/Adminhtml/Source/Cctype.php
new file mode 100644
index 0000000..4aa98d4
--- /dev/null
+++ b/Model/Adminhtml/Source/Cctype.php
@@ -0,0 +1,15 @@
+ self::ENVIRONMENT_SANDBOX,
+ 'label' => 'Sandbox',
+ ],
+ [
+ 'value' => self::ENVIRONMENT_PRODUCTION,
+ 'label' => 'Production'
+ ]
+ ];
+ }
+}
diff --git a/Model/Adminhtml/Source/PaymentAction.php b/Model/Adminhtml/Source/PaymentAction.php
new file mode 100644
index 0000000..aa4a399
--- /dev/null
+++ b/Model/Adminhtml/Source/PaymentAction.php
@@ -0,0 +1,24 @@
+ 'authorize',
+ 'label' => __('Authorize Only'),
+ ],
+ [
+ 'value' => 'authorize_capture',
+ 'label' => __('Authorize and Capture')
+ ]
+ ];
+ }
+}
diff --git a/Model/Config/Backend/SmartPayId.php b/Model/Config/Backend/SmartPayId.php
new file mode 100644
index 0000000..5856ebe
--- /dev/null
+++ b/Model/Config/Backend/SmartPayId.php
@@ -0,0 +1,21 @@
+getValue();
+ $this->setValue($data);
+
+ return parent::beforeSave();
+ }
+}
diff --git a/Model/Data/Mpi.php b/Model/Data/Mpi.php
new file mode 100644
index 0000000..64b5d46
--- /dev/null
+++ b/Model/Data/Mpi.php
@@ -0,0 +1,73 @@
+_get(self::ECI);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setEci($eci)
+ {
+ return $this->setData(self::ECI, $eci);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getXid()
+ {
+ return $this->_get(self::XID);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setXid($xid)
+ {
+ return $this->setData(self::XID, $xid);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getCavv()
+ {
+ return $this->_get(self::CAVV);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setCavv($cavv)
+ {
+ return $this->setData(self::CAVV, $cavv);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getKbankMpi()
+ {
+ return $this->_get(self::KBANK_MPI);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setKbankMpi($kbankMpi)
+ {
+ return $this->setData(self::KBANK_MPI, $kbankMpi);
+ }
+}
diff --git a/Model/Data/Notify.php b/Model/Data/Notify.php
new file mode 100644
index 0000000..1f48cab
--- /dev/null
+++ b/Model/Data/Notify.php
@@ -0,0 +1,394 @@
+_get(self::ID);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setId($id)
+ {
+ return $this->setData(self::ID, $id);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getObject()
+ {
+ return $this->_get(self::OBJECT);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setObject($object)
+ {
+ return $this->setData(self::OBJECT, $object);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getAmount()
+ {
+ return $this->_get(self::AMOUNT);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setAmount($amount)
+ {
+ return $this->setData(self::AMOUNT, $amount);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getCurrency()
+ {
+ return $this->_get(self::CURRENCY);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setCurrency($currency)
+ {
+ return $this->setData(self::CURRENCY, $currency);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getTransactionState()
+ {
+ return $this->_get(self::TRANSACTION_STATE);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setTransactionState($transactionState)
+ {
+ return $this->setData(self::TRANSACTION_STATE, $transactionState);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getSource()
+ {
+ return $this->_get(self::SOURCE);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setSource($source)
+ {
+ return $this->setData(self::SOURCE, $source);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getCreated()
+ {
+ return $this->_get(self::CREATED);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setCreated($created)
+ {
+ return $this->setData(self::CREATED, $created);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getStatus()
+ {
+ return $this->_get(self::STATUS);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setStatus($status)
+ {
+ return $this->setData(self::STATUS, $status);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getLivemode()
+ {
+ return $this->_get(self::LIVEMODE);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setLivemode($livemode)
+ {
+ return $this->setData(self::LIVEMODE, $livemode);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getMetadata()
+ {
+ return $this->_get(self::METADATA);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setMetadata($metadata)
+ {
+ return $this->setData(self::METADATA, $metadata);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getFailureCode()
+ {
+ return $this->_get(self::FAILURE_CODE);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setFailureCode($failureCode)
+ {
+ return $this->setData(self::FAILURE_CODE, $failureCode);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getFailureMessage()
+ {
+ return $this->_get(self::FAILURE_MESSAGE);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setFailureMessage($failureMessage)
+ {
+ return $this->setData(self::FAILURE_MESSAGE, $failureMessage);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getDescription()
+ {
+ return $this->_get(self::DESCRIPTION);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setDescription($description)
+ {
+ return $this->setData(self::DESCRIPTION, $description);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getMpi()
+ {
+ return $this->_get(self::MPI);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setMpi($mpi)
+ {
+ return $this->setData(self::MPI, $mpi);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getChecksum()
+ {
+ return $this->_get(self::CHECKSUM);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setChecksum($checksum)
+ {
+ return $this->setData(self::CHECKSUM, $checksum);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getRedirectUrl()
+ {
+ return $this->_get(self::REDIRECT_URL);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setRedirectUrl($redirectUrl)
+ {
+ return $this->setData(self::REDIRECT_URL, $redirectUrl);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getSettlementInfo()
+ {
+ return $this->_get(self::SETTLEMENT_INFO);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setSettlementInfo($settlementInfo)
+ {
+ return $this->setData(self::SETTLEMENT_INFO, $settlementInfo);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getRefundInfo()
+ {
+ return $this->_get(self::REFUND_INFO);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setRefundInfo($refundInfo)
+ {
+ return $this->setData(self::REFUND_INFO, $refundInfo);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getApprovalCode()
+ {
+ return $this->_get(self::APPROVAL_CODE);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setApprovalCode($approvalCode)
+ {
+ return $this->setData(self::APPROVAL_CODE, $approvalCode);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getCampaignId()
+ {
+ return $this->_get(self::CAMPAIGN_ID);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setCampaignId($campaignId)
+ {
+ return $this->setData(self::CAMPAIGN_ID, $campaignId);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getRef1()
+ {
+ return $this->_get(self::REF_1);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setRef1($ref1)
+ {
+ return $this->setData(self::REF_1, $ref1);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getRef2()
+ {
+ return $this->_get(self::REF_2);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setRef2($ref2)
+ {
+ return $this->setData(self::REF_2, $ref2);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getRef3()
+ {
+ return $this->_get(self::REF_3);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setRef3($ref3)
+ {
+ return $this->setData(self::REF_3, $ref3);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getReferenceOrder()
+ {
+ return $this->_get(self::REFERENCE_ORDER);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setReferenceOrder($referenceOrder)
+ {
+ return $this->setData(self::REFERENCE_ORDER, $referenceOrder);
+ }
+}
diff --git a/Model/Data/Source.php b/Model/Data/Source.php
new file mode 100644
index 0000000..3d89d5c
--- /dev/null
+++ b/Model/Data/Source.php
@@ -0,0 +1,90 @@
+_get(self::ID);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setId($id)
+ {
+ return $this->setData(self::ID, $id);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getObject()
+ {
+ return $this->_get(self::OBJECT);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setObject($object)
+ {
+ return $this->setData(self::OBJECT, $object);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getBrand()
+ {
+ return $this->_get(self::BRAND);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setBrand($brand)
+ {
+ return $this->setData(self::BRAND, $brand);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getCardMasking()
+ {
+ return $this->_get(self::CARD_MASKING);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setCardMasking($cardMasking)
+ {
+ return $this->setData(self::CARD_MASKING, $cardMasking);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getIssuerBank()
+ {
+ return $this->_get(self::ISSUER_BANK);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setIssuerBank($issuerBank)
+ {
+ return $this->setData(self::ISSUER_BANK, $issuerBank);
+ }
+}
diff --git a/Model/InstallmentManagement.php b/Model/InstallmentManagement.php
new file mode 100644
index 0000000..a7acdd5
--- /dev/null
+++ b/Model/InstallmentManagement.php
@@ -0,0 +1,69 @@
+config = $config;
+ $this->serializer = $serializer;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getAvailableInstallment(string $methodCode, ?int $storeId = null): array
+ {
+ $availableInstallmentsStripped = [];
+ $availableInstallments = $this->config->getSpecificPaymentConfig($methodCode, Config::KEY_INSTALLMENT_INFO);
+
+ if (!$availableInstallments) {
+ return $availableInstallmentsStripped;
+ }
+
+ try {
+ $configurations = $this->serializer->unserialize($availableInstallments);
+ $i = 0;
+ if (is_array($configurations)) {
+ foreach ($configurations as $configuration) {
+ if (!$configuration['status']) {
+ continue;
+ }
+ $availableInstallmentsStripped[$i]['smartpay_id'] = preg_replace(
+ '/\s+/',
+ '',
+ $configuration['smartpay_id']
+ );
+ $availableInstallmentsStripped[$i]['payment_term'] = preg_replace(
+ '/\s+/',
+ '',
+ $configuration['payment_term']
+ );
+ $availableInstallmentsStripped[$i]['installment_title'] = trim($configuration['installment_title']);
+ $i++;
+ }
+ }
+ } catch (\Exception $e) { //phpcs:ignore Magento2.CodeAnalysis.EmptyBlock
+ } finally {
+ return $availableInstallmentsStripped;
+ }
+ }
+}
diff --git a/Model/Meta.php b/Model/Meta.php
new file mode 100644
index 0000000..fa87077
--- /dev/null
+++ b/Model/Meta.php
@@ -0,0 +1,498 @@
+_init(ResourceMeta::class);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getMetaId()
+ {
+ return $this->getData(self::META_ID);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function setMetaId(int $metaId)
+ {
+ return $this->setData(self::META_ID, $metaId);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getOrderId()
+ {
+ return $this->getData(self::ORDER_ID);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function setOrderId(string $orderId)
+ {
+ return $this->setData(self::ORDER_ID, $orderId);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getCustomerId()
+ {
+ return $this->getData(self::CUSTOMER_ID);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function setCustomerId($customerId)
+ {
+ return $this->setData(self::CUSTOMER_ID, $customerId);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getChargeId()
+ {
+ return $this->getData(self::CHARGE_ID);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function setChargeId($chargeId)
+ {
+ return $this->setData(self::CHARGE_ID, $chargeId);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getObject()
+ {
+ return $this->getData(self::OBJECT);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function setObject($object)
+ {
+ return $this->setData(self::OBJECT, $object);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getAmount()
+ {
+ return $this->getData(self::AMOUNT);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function setAmount($amount)
+ {
+ return $this->setData(self::AMOUNT, $amount);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getCurrency()
+ {
+ return $this->getData(self::CURRENCY);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function setCurrency($currency)
+ {
+ return $this->setData(self::CURRENCY, $currency);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getTransactionState()
+ {
+ return $this->getData(self::TRANSACTION_STATE);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setTransactionState($transactionState)
+ {
+ return $this->setData(self::TRANSACTION_STATE, $transactionState);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getCreated()
+ {
+ return $this->getData(self::CREATED);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setCreated($created)
+ {
+ return $this->setData(self::CREATED, $created);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getStatus()
+ {
+ return $this->getData(self::STATUS);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setStatus($status)
+ {
+ return $this->setData(self::STATUS, $status);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getApprovalCode()
+ {
+ return $this->getData(self::APPROVAL_CODE);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setApprovalCode($approvalCode)
+ {
+ return $this->setData(self::APPROVAL_CODE, $approvalCode);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getLivemode()
+ {
+ return $this->getData(self::LIVEMODE);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setLivemode($livemode)
+ {
+ return $this->setData(self::LIVEMODE, $livemode);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getMetadata()
+ {
+ return $this->getData(self::METADATA);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setMetadata($metadata)
+ {
+ return $this->setData(self::METADATA, $metadata);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getFailureCode()
+ {
+ return $this->getData(self::FAILURE_CODE);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setFailureCode($failureCode)
+ {
+ return $this->setData(self::FAILURE_CODE, $failureCode);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getFailureMessage()
+ {
+ return $this->getData(self::FAILURE_MESSAGE);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setFailureMessage($failureMessage)
+ {
+ return $this->setData(self::FAILURE_MESSAGE, $failureMessage);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getRedirectUrl()
+ {
+ return $this->getData(self::REDIRECT_URL);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setRedirectUrl($redirectUrl)
+ {
+ return $this->setData(self::REDIRECT_URL, $redirectUrl);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getCardId()
+ {
+ return $this->getData(self::CARD_ID);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setCardId($cardId)
+ {
+ return $this->setData(self::CARD_ID, $cardId);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getCardBrand()
+ {
+ return $this->getData(self::CARD_BRAND);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setCardBrand($cardBrand)
+ {
+ return $this->setData(self::CARD_BRAND, $cardBrand);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getIssuerBank()
+ {
+ return $this->getData(self::ISSUER_BANK);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setIssuerBank($issuerBank)
+ {
+ return $this->setData(self::ISSUER_BANK, $issuerBank);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getMaskedPan()
+ {
+ return $this->getData(self::MASKED_PAN);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setMaskedPan($maskedPan)
+ {
+ return $this->setData(self::MASKED_PAN, $maskedPan);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getRef1()
+ {
+ return $this->getData(self::REF1);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setRef1($ref1)
+ {
+ return $this->setData(self::REF1, $ref1);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getRef2()
+ {
+ return $this->getData(self::REF2);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setRef2($ref2)
+ {
+ return $this->setData(self::REF2, $ref2);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getRef3()
+ {
+ return $this->getData(self::REF3);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setRef3($ref3)
+ {
+ return $this->setData(self::REF3, $ref3);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getBahtAmount()
+ {
+ return $this->getData(self::BAHT_AMOUNT);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setBahtAmount($bahtAmount)
+ {
+ return $this->setData(self::BAHT_AMOUNT, $bahtAmount);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getRate()
+ {
+ return $this->getData(self::RATE);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setRate($rate)
+ {
+ return $this->setData(self::RATE, $rate);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getConvertedAmount()
+ {
+ return $this->getData(self::CONVERTED_AMOUNT);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setConvertedAmount($convertedAmount)
+ {
+ return $this->setData(self::CONVERTED_AMOUNT, $convertedAmount);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getConvertedCurrencyCode()
+ {
+ return $this->getData(self::CONVERTED_CURRENCY_CODE);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setConvertedCurrencyCode($convertedCurrencyCode)
+ {
+ return $this->setData(self::CONVERTED_CURRENCY_CODE, $convertedCurrencyCode);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getConvertedCurrencyName()
+ {
+ return $this->getData(self::CONVERTED_CURRENCY_NAME);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setConvertedCurrencyName($convertedCurrencyName)
+ {
+ return $this->setData(self::CONVERTED_CURRENCY_NAME, $convertedCurrencyName);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getF63()
+ {
+ return $this->getData(self::F63);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setF63($f63)
+ {
+ return $this->setData(self::F63, $f63);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getMid()
+ {
+ return $this->getData(self::MID);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setMid($mid)
+ {
+ return $this->setData(self::MID, $mid);
+ }
+}
diff --git a/Model/MetaRepository.php b/Model/MetaRepository.php
new file mode 100644
index 0000000..ab4b982
--- /dev/null
+++ b/Model/MetaRepository.php
@@ -0,0 +1,210 @@
+resource = $resource;
+ $this->metaFactory = $metaFactory;
+ $this->metaCollectionFactory = $metaCollectionFactory;
+ $this->searchResultsFactory = $searchResultsFactory;
+ $this->dataObjectHelper = $dataObjectHelper;
+ $this->dataMetaFactory = $dataMetaFactory;
+ $this->dataObjectProcessor = $dataObjectProcessor;
+ $this->storeManager = $storeManager;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function save(
+ \GhoSter\KbankPayments\Api\Data\MetaInterface $meta
+ ) {
+ try {
+ $this->resource->save($meta);
+ } catch (\Exception $exception) {
+ throw new CouldNotSaveException(__(
+ 'Could not save the meta: %1',
+ $exception->getMessage()
+ ));
+ }
+ return $meta;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getById($metaId)
+ {
+ $meta = $this->metaFactory->create();
+ $this->resource->load($meta, $metaId);
+ if (!$meta->getId()) {
+ throw new NoSuchEntityException(__('Meta with id "%1" does not exist.', $metaId));
+ }
+ return $meta;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getByOrderIncrement($orderIncrementId)
+ {
+ $meta = $this->metaFactory->create();
+ $this->resource->loadByOrderIncrement($meta, $orderIncrementId);
+ return $meta;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getByChargeId($chargeId)
+ {
+ $meta = $this->metaFactory->create();
+ $this->resource->loadByChargeId($meta, $chargeId);
+ if (!$meta->getId()) {
+ throw new NoSuchEntityException(__('Meta with charge id %1 does not exist.', $chargeId));
+ }
+ return $meta;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getList(
+ SearchCriteriaInterface $searchCriteria
+ ) {
+ $collection = $this->metaCollectionFactory->create();
+ foreach ($searchCriteria->getFilterGroups() as $filterGroup) {
+ $fields = [];
+ $conditions = [];
+ foreach ($filterGroup->getFilters() as $filter) {
+ if ($filter->getField() === 'store_id') {
+ $collection->addStoreFilter($filter->getValue(), false);
+ continue;
+ }
+ $fields[] = $filter->getField();
+ $condition = $filter->getConditionType() ?: 'eq';
+ $conditions[] = [$condition => $filter->getValue()];
+ }
+ $collection->addFieldToFilter($fields, $conditions);
+ }
+
+ $sortOrders = $searchCriteria->getSortOrders();
+ if ($sortOrders) {
+ /** @var SortOrder $sortOrder */
+ foreach ($sortOrders as $sortOrder) {
+ $collection->addOrder(
+ $sortOrder->getField(),
+ ($sortOrder->getDirection() == SortOrder::SORT_ASC) ? 'ASC' : 'DESC'
+ );
+ }
+ }
+ $collection->setCurPage($searchCriteria->getCurrentPage());
+ $collection->setPageSize($searchCriteria->getPageSize());
+
+ $searchResults = $this->searchResultsFactory->create();
+ $searchResults->setSearchCriteria($searchCriteria);
+ $searchResults->setTotalCount($collection->getSize());
+ $searchResults->setItems($collection->getItems());
+ return $searchResults;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function delete(
+ \GhoSter\KbankPayments\Api\Data\MetaInterface $meta
+ ) {
+ try {
+ $this->resource->delete($meta);
+ } catch (\Exception $exception) {
+ throw new CouldNotDeleteException(__(
+ 'Could not delete the Meta: %1',
+ $exception->getMessage()
+ ));
+ }
+ return true;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function deleteById($metaId)
+ {
+ return $this->delete($this->getById($metaId));
+ }
+}
diff --git a/Model/NotifyManagement.php b/Model/NotifyManagement.php
new file mode 100644
index 0000000..8e95d69
--- /dev/null
+++ b/Model/NotifyManagement.php
@@ -0,0 +1,261 @@
+request = $request;
+ $this->metaRepository = $metaRepository;
+ $this->kbankEmbeddedConfig = $config;
+ $this->orderFactory = $orderFactory;
+ $this->dataObjectFactory = $dataObjectFactory;
+ $this->dataObjectHelper = $dataObjectHelper;
+ $this->eventManager = $eventManager;
+ $this->notifyDataFactory = $notifyDataFactory;
+ $this->sourceDataFactory = $sourceDataFactory;
+ $this->mpiDataFactory = $mpiDataFactory;
+ $this->logger = $logger;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function notify(): bool
+ {
+ $notifyData = $this->request->getBodyParams();
+
+ /** @var SourceInterface $mpi */
+ $source = $this->sourceDataFactory->create();
+ $this->dataObjectHelper->populateWithArray(
+ $source,
+ $notifyData['source'] ?? [],
+ SourceInterface::class
+ );
+
+ /** @var MpiInterface $mpi */
+ $mpi = $this->mpiDataFactory->create();
+ $this->dataObjectHelper->populateWithArray(
+ $mpi,
+ $notifyData['mpi'] ?? [],
+ MpiInterface::class
+ );
+
+ /** @var NotifyInterface $notify */
+ $notify = $this->notifyDataFactory->create();
+
+ $this->dataObjectHelper->populateWithArray(
+ $notify,
+ $notifyData,
+ NotifyInterface::class
+ );
+
+ $notify->setSource($source)
+ ->setMpi($mpi);
+
+ if ($notify->getStatus() !== Config::VALID_STATUS) {
+ $this->logger->info(sprintf('Status was mismatched or empty. %s', $notify->getStatus()));
+ return false;
+ }
+
+ if (!$notify->getId()) {
+ $this->logger->info(sprintf('Charge Id was mismatched or empty. %s', $notify->getId()));
+ return false;
+ }
+
+ if (!$notify->getChecksum()) {
+ $this->logger->info(sprintf('Checksum was mismatched or empty. %s', $notify->getChecksum()));
+ return false;
+ }
+
+ $concatenate = $notify->getId()
+ . number_format($notify->getAmount(), 4, '.', '')
+ . $notify->getCurrency()
+ . $notify->getStatus()
+ . $notify->getTransactionState();
+
+ $salt = $this->kbankEmbeddedConfig->getSecretKey();
+ $expectedCheckSum = hash('sha256', $concatenate . $salt);
+
+ if ($notify->getChecksum() !== $expectedCheckSum) {
+ $this->logger->info(sprintf('Checksum was mismatched or empty. %s', $notify->getChecksum()));
+ return false;
+ }
+
+ try {
+ $meta = $this->metaRepository->getByChargeId($notify->getId());
+
+ if (!$meta->getMetaId()) {
+ $this->logger->info('Meta was mismatched or empty.');
+ return false;
+ }
+
+ /** @var Order $order */
+ $order = $this->orderFactory->create()
+ ->loadByIncrementId($meta->getOrderId());
+
+ if (!$order->getId() || $order->getIncrementId() !== $notify->getReferenceOrder()) {
+ $this->logger->info(sprintf('Order was mismatched or empty. %s', $notify->getReferenceOrder()));
+ return false;
+ }
+
+ if ($this->_checkOrderState($order)) {
+ $this->logger->info(sprintf('Order status already changed. %s', $order->getStatus()));
+ } else {
+ $errorObject = $this->dataObjectFactory
+ ->create(['data' => ['error' => $this->errors]]);
+
+ $this->eventManager->dispatch(
+ 'kbank_embedded_payment_method_callback_validation',
+ [
+ 'meta' => $meta,
+ 'order' => $order,
+ 'error' => $errorObject,
+ 'type' => 'notify'
+ ]
+ );
+
+ if (!empty($errorObject->getData('error')) && is_array($errorObject->getData('error'))) {
+ $comment = implode(PHP_EOL, $errorObject->getData('error'));
+ $this->logger->critical("Something went wrong while validation\n");
+ $this->logger->critical($comment);
+
+ $order->cancel();
+ $order->save();
+ return false;
+ }
+ }
+ } catch (\Exception $e) {
+ $this->logger->critical("Something went wrong while notify\n" . $e->getMessage());
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Check order state
+ *
+ * @param Order $order
+ * @return bool
+ */
+ protected function _checkOrderState(Order $order): bool
+ {
+ if (!$order->getEntityId()) {
+ return false;
+ }
+
+ return in_array($order->getState(), $this->allowedOrderStates);
+ }
+}
diff --git a/Model/OrderInformationManagement.php b/Model/OrderInformationManagement.php
new file mode 100644
index 0000000..b5c61a5
--- /dev/null
+++ b/Model/OrderInformationManagement.php
@@ -0,0 +1,45 @@
+orderRepository = $orderRepository;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function validateOrderStatus(int $orderId): bool
+ {
+ try {
+ $order = $this->orderRepository->get($orderId);
+ return in_array($order->getState(), $this->allowedOrderStates);
+ } catch (\Exception $e) {
+ return false;
+ }
+ }
+}
diff --git a/Model/PaymentStatus.php b/Model/PaymentStatus.php
new file mode 100644
index 0000000..26b1292
--- /dev/null
+++ b/Model/PaymentStatus.php
@@ -0,0 +1,38 @@
+_init('ghoster_kbank_meta', 'meta_id');
+ $this->_isPkAutoIncrement = false;
+ }
+
+ /**
+ * Load by Order Increment Id
+ *
+ * @param \GhoSter\KbankPayments\Model\Meta $meta
+ * @param string $orderIncrementId
+ * @return $this
+ * @throws LocalizedException
+ */
+ public function loadByOrderIncrement(\GhoSter\KbankPayments\Model\Meta $meta, $orderIncrementId)
+ {
+ $connection = $this->getConnection();
+ $bind = ['order_id' => $orderIncrementId];
+ $select = $connection->select()
+ ->from($this->getMainTable(), $this->getIdFieldName())
+ ->where(
+ 'order_id = :order_id'
+ )->order('created DESC');
+
+ $metaId = $connection->fetchOne($select, $bind);
+
+ if ($metaId) {
+ $this->load($meta, $metaId);
+ } else {
+ $meta->setData([]);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Load by Charge Id
+ *
+ * @param \GhoSter\KbankPayments\Model\Meta $meta
+ * @param mixed $chargeId
+ * @return $this
+ * @throws LocalizedException
+ */
+ public function loadByChargeId(\GhoSter\KbankPayments\Model\Meta $meta, $chargeId): Meta
+ {
+ $connection = $this->getConnection();
+ $bind = ['charge_id' => $chargeId];
+ $select = $connection->select()
+ ->from($this->getMainTable(), $this->getIdFieldName())
+ ->where(
+ 'charge_id = :charge_id'
+ );
+
+ $metaId = $connection->fetchOne($select, $bind);
+
+ if ($metaId) {
+ $this->load($meta, $metaId);
+ } else {
+ $meta->setData([]);
+ }
+
+ return $this;
+ }
+}
diff --git a/Model/ResourceModel/Meta/Collection.php b/Model/ResourceModel/Meta/Collection.php
new file mode 100644
index 0000000..d0c8720
--- /dev/null
+++ b/Model/ResourceModel/Meta/Collection.php
@@ -0,0 +1,23 @@
+_init(
+ Meta::class,
+ ResourceMeta::class
+ );
+ }
+}
diff --git a/Model/ResourceModel/Token.php b/Model/ResourceModel/Token.php
new file mode 100644
index 0000000..0b296c6
--- /dev/null
+++ b/Model/ResourceModel/Token.php
@@ -0,0 +1,75 @@
+_init('ghoster_kbank_token', 'token_id');
+ }
+
+ /**
+ * Load by Order Increment Id
+ *
+ * @param \GhoSter\KbankPayments\Model\Token $token
+ * @param string $orderIncrementId
+ * @return $this
+ * @throws LocalizedException
+ */
+ public function loadByOrderIncrement(\GhoSter\KbankPayments\Model\Token $token, string $orderIncrementId): Token
+ {
+ $connection = $this->getConnection();
+ $bind = ['order_id' => $orderIncrementId];
+ $select = $connection->select()
+ ->from($this->getMainTable(), $this->getIdFieldName())
+ ->where(
+ 'order_id = :order_id'
+ )->order('created DESC');
+
+ $tokenId = $connection->fetchOne($select, $bind);
+
+ if ($tokenId) {
+ $this->load($token, $tokenId);
+ } else {
+ $token->setData([]);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Load by Token
+ *
+ * @param \GhoSter\KbankPayments\Model\Token $token
+ * @param string $tokenValue
+ * @return $this
+ * @throws LocalizedException
+ */
+ public function loadByToken(\GhoSter\KbankPayments\Model\Token $token, string $tokenValue): Token
+ {
+ $connection = $this->getConnection();
+ $bind = ['token' => $tokenValue];
+ $select = $connection->select()
+ ->from($this->getMainTable(), $this->getIdFieldName())
+ ->where(
+ 'token = :token'
+ );
+
+ $tokenId = $connection->fetchOne($select, $bind);
+
+ if ($tokenId) {
+ $this->load($token, $tokenId);
+ } else {
+ $token->setData([]);
+ }
+
+ return $this;
+ }
+}
diff --git a/Model/ResourceModel/Token/Collection.php b/Model/ResourceModel/Token/Collection.php
new file mode 100644
index 0000000..2cf7697
--- /dev/null
+++ b/Model/ResourceModel/Token/Collection.php
@@ -0,0 +1,21 @@
+_init(
+ \GhoSter\KbankPayments\Model\Token::class,
+ \GhoSter\KbankPayments\Model\ResourceModel\Token::class
+ );
+ }
+}
diff --git a/Model/Token.php b/Model/Token.php
new file mode 100644
index 0000000..dd5f00e
--- /dev/null
+++ b/Model/Token.php
@@ -0,0 +1,242 @@
+_init(\GhoSter\KbankPayments\Model\ResourceModel\Token::class);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getTokenId()
+ {
+ return $this->getData(self::TOKEN_ID);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setTokenId($tokenId)
+ {
+ return $this->setData(self::TOKEN_ID, $tokenId);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getToken()
+ {
+ return $this->getData(self::TOKEN);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setToken($token)
+ {
+ return $this->setData(self::TOKEN, $token);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getOrderId()
+ {
+ return $this->getData(self::ORDER_ID);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setOrderId($orderId)
+ {
+ return $this->setData(self::ORDER_ID, $orderId);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getCustomerId()
+ {
+ return $this->getData(self::CUSTOMER_ID);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setCustomerId($customerId)
+ {
+ return $this->setData(self::CUSTOMER_ID, $customerId);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getPaymentMethods()
+ {
+ return $this->getData(self::PAYMENT_METHODS);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setPaymentMethods($paymentMethods)
+ {
+ return $this->setData(self::PAYMENT_METHODS, $paymentMethods);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getCreatedTime()
+ {
+ return $this->getData(self::CREATED_TIME);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setCreatedTime($createdTime)
+ {
+ return $this->setData(self::CREATED_TIME, $createdTime);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getDccCurrency()
+ {
+ return $this->getData(self::DCC_CURRENCY);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setDccCurrency($dccCurrency)
+ {
+ return $this->setData(self::DCC_CURRENCY, $dccCurrency);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getSmartpayId()
+ {
+ return $this->getData(self::SMARTPAY_ID);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setSmartpayId($smartpayId)
+ {
+ return $this->setData(self::SMARTPAY_ID, $smartpayId);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getTerm()
+ {
+ return $this->getData(self::TERM);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setTerm($term)
+ {
+ return $this->setData(self::TERM, $term);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getSaveCard()
+ {
+ return $this->getData(self::SAVECARD);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setSaveCard($saveCard)
+ {
+ return $this->setData(self::SAVECARD, $saveCard);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getCardId()
+ {
+ return $this->getData(self::CARDID);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setCardId($cardId)
+ {
+ return $this->setData(self::CARDID, $cardId);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getMid()
+ {
+ return $this->getData(self::MID);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setMid($mid)
+ {
+ return $this->setData(self::MID, $mid);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getTerminalId()
+ {
+ return $this->getData(self::TERMINAL_ID);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setTerminalId($terminalId)
+ {
+ return $this->setData(self::TERMINAL_ID, $terminalId);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getExtensionAttributes()
+ {
+ return $this->_getExtensionAttributes();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setExtensionAttributes(
+ \GhoSter\KbankPayments\Api\Data\TokenExtensionInterface $extensionAttributes = null
+ ) {
+ return $this->_setExtensionAttributes($extensionAttributes);
+ }
+}
diff --git a/Model/TokenManagement.php b/Model/TokenManagement.php
new file mode 100644
index 0000000..fad3c97
--- /dev/null
+++ b/Model/TokenManagement.php
@@ -0,0 +1,85 @@
+tokenRepository = $tokenRepository;
+ $this->transactionProcessor = $transactionProcessor;
+ $this->orderFactory = $orderFactory;
+ $this->metaFactory = $metaFactory;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function saveTokenForOrder(TokenInterface $token, string $orderIncrement): TokenInterface
+ {
+ $token->setOrderId($orderIncrement);
+
+ if (!$token->getTerminalId()) {
+ $token->setTerminalId($this->config->getTerminalId());
+ }
+
+ return $this->tokenRepository->save($token);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function saveTokenThenAuthorize(TokenInterface $token, string $orderIncrement): MetaInterface
+ {
+ /** @var Meta $meta */
+ $meta = $this->metaFactory->create();
+
+ $token = $this->saveTokenForOrder($token, $orderIncrement);
+ $order = $this->orderFactory->create()->loadByIncrementId($orderIncrement);
+
+ if ($token->getTokenId() && $order->getEntityId()) {
+ return $this->transactionProcessor->authorizeOrderByToken($order, $token);
+ }
+
+ return $meta;
+ }
+}
diff --git a/Model/TokenRepository.php b/Model/TokenRepository.php
new file mode 100644
index 0000000..d8594c6
--- /dev/null
+++ b/Model/TokenRepository.php
@@ -0,0 +1,217 @@
+resource = $resource;
+ $this->tokenFactory = $tokenFactory;
+ $this->tokenCollectionFactory = $tokenCollectionFactory;
+ $this->searchResultsFactory = $searchResultsFactory;
+ $this->dataObjectHelper = $dataObjectHelper;
+ $this->dataTokenFactory = $dataTokenFactory;
+ $this->dataObjectProcessor = $dataObjectProcessor;
+ $this->storeManager = $storeManager;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function save(
+ \GhoSter\KbankPayments\Api\Data\TokenInterface $token
+ ) {
+ try {
+ $this->resource->save($token);
+ } catch (\Exception $exception) {
+ throw new CouldNotSaveException(__(
+ 'Could not save the token: %1',
+ $exception->getMessage()
+ ));
+ }
+ return $token;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getById($tokenId)
+ {
+ $token = $this->tokenFactory->create();
+ $this->resource->load($token, $tokenId);
+ if (!$token->getId()) {
+ throw new NoSuchEntityException(__('Token with id "%1" does not exist.', $tokenId));
+ }
+ return $token;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getByOrderIncrement($orderIncrementId)
+ {
+ $token = $this->tokenFactory->create();
+ $this->resource->loadByOrderIncrement($token, $orderIncrementId);
+ if (!$token->getId()) {
+ throw new NoSuchEntityException(
+ __('Token with order increment id "%1" does not exist.', $orderIncrementId)
+ );
+ }
+ return $token;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getByToken($token)
+ {
+ $tokenModel = $this->tokenFactory->create();
+ $this->resource->loadByToken($tokenModel, $token);
+ if (!$tokenModel->getId()) {
+ throw new NoSuchEntityException(
+ __('Token with token value id "%1" does not exist.', $token)
+ );
+ }
+ return $tokenModel;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getList(
+ SearchCriteriaInterface $searchCriteria
+ ) {
+ $collection = $this->tokenCollectionFactory->create();
+ foreach ($searchCriteria->getFilterGroups() as $filterGroup) {
+ $fields = [];
+ $conditions = [];
+ foreach ($filterGroup->getFilters() as $filter) {
+ if ($filter->getField() === 'store_id') {
+ $collection->addStoreFilter($filter->getValue(), false);
+ continue;
+ }
+ $fields[] = $filter->getField();
+ $condition = $filter->getConditionType() ?: 'eq';
+ $conditions[] = [$condition => $filter->getValue()];
+ }
+ $collection->addFieldToFilter($fields, $conditions);
+ }
+
+ $sortOrders = $searchCriteria->getSortOrders();
+ if ($sortOrders) {
+ /** @var SortOrder $sortOrder */
+ foreach ($sortOrders as $sortOrder) {
+ $collection->addOrder(
+ $sortOrder->getField(),
+ ($sortOrder->getDirection() == SortOrder::SORT_ASC) ? 'ASC' : 'DESC'
+ );
+ }
+ }
+ $collection->setCurPage($searchCriteria->getCurrentPage());
+ $collection->setPageSize($searchCriteria->getPageSize());
+
+ $searchResults = $this->searchResultsFactory->create();
+ $searchResults->setSearchCriteria($searchCriteria);
+ $searchResults->setTotalCount($collection->getSize());
+ $searchResults->setItems($collection->getItems());
+ return $searchResults;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function delete(
+ \GhoSter\KbankPayments\Api\Data\TokenInterface $token
+ ) {
+ try {
+ $this->resource->delete($token);
+ } catch (\Exception $exception) {
+ throw new CouldNotDeleteException(__(
+ 'Could not delete the Token: %1',
+ $exception->getMessage()
+ ));
+ }
+ return true;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function deleteById($tokenId)
+ {
+ return $this->delete($this->getById($tokenId));
+ }
+}
diff --git a/Model/TransactionProcessor.php b/Model/TransactionProcessor.php
new file mode 100644
index 0000000..3c79483
--- /dev/null
+++ b/Model/TransactionProcessor.php
@@ -0,0 +1,472 @@
+objectFactory = $objectFactory;
+ $this->dataObjectHelper = $dataObjectHelper;
+ $this->kbankClient = $kbankClient;
+ $this->kbankConfig = $kbankConfig;
+ $this->orderRepository = $orderRepository;
+ $this->transactionResponseValidator = $transactionResponseValidator;
+ $this->metaFactory = $metaFactory;
+ $this->invoiceSender = $invoiceSender;
+ $this->invoiceService = $invoiceService;
+ $this->transaction = $transaction;
+ $this->logger = $logger;
+ $this->tokenRepository = $tokenRepository;
+ $this->orderFactory = $orderFactory;
+ }
+
+ /**
+ * @inheritDoc
+ * @SuppressWarnings(PHPMD.NPathComplexity)
+ */
+ public function authorizeOrderByToken(
+ Order $order,
+ TokenInterface $token
+ ): MetaInterface {
+ /** @var Meta $meta */
+ $meta = $this->metaFactory->create();
+
+ $request = [
+ 'method_type' => 'POST',
+ 'payload_type' => 'createAuthorizeRequest'
+ ];
+ $data = [
+ 'amount' => $this->_formatAmount($order->getGrandTotal()),
+ 'currency' => $order->getOrderCurrencyCode(),
+ 'description' => $order->getIncrementId(),
+ 'source_type' => 'card',
+ 'mode' => 'token',
+ 'reference_order' => $order->getIncrementId(),
+ 'token' => $token->getToken()
+ ];
+
+ $terminalId = $token->getTerminalId();
+ if (!$terminalId) {
+ $terminalId = $order->getPayment()->getAdditionalInformation('terminal_id');
+ }
+
+ $smartpayId = $token->getSmartpayId();
+ if (!$smartpayId) {
+ $smartpayId = $order->getPayment()->getAdditionalInformation('smartpay_id');
+ }
+
+ $term = $token->getTerm();
+ if (!$term) {
+ $term = $order->getPayment()->getAdditionalInformation('term');
+ }
+
+ if (!empty($smartpayId)
+ && !empty($terminalId)
+ ) {
+ $data = array_merge($data, [
+ 'additional_data' => [
+ 'mid' => $token->getMid(),
+ 'tid' => $terminalId,
+ 'smartpay_id' => $smartpayId,
+ 'term' => $term
+ ]
+ ]);
+ }
+
+ $dataPost = array_merge($request, $data);
+ $resultResponse = $this->kbankClient->postRequest($dataPost);
+
+ $result = $this->transactionResponseValidator->validate(
+ array_merge($dataPost, ['response' => $resultResponse])
+ );
+
+ if ($result->isValid()) {
+ $meta->setOrderId($order->getIncrementId())
+ ->setCustomerId($order->getCustomerId())
+ ->setChargeId($resultResponse['id'])
+ ->setObject($resultResponse['object'])
+ ->setAmount($resultResponse['amount'])
+ ->setCurrency($resultResponse['currency'])
+ ->setTransactionState($resultResponse['transaction_state'])
+ ->setCreated($resultResponse['created'])
+ ->setStatus($resultResponse['status'])
+ ->setApprovalCode($resultResponse['approval_code'])
+ ->setLivemode($resultResponse['livemode'])
+ ->setFailureCode((string)$resultResponse['failure_code'])
+ ->setFailureMessage((string)$resultResponse['failure_message'])
+ ->setRedirectUrl($resultResponse['redirect_url'])
+ ->setCardId($resultResponse['source']['id'])
+ ->setCardBrand($resultResponse['source']['brand'])
+ ->setIssuerBank($resultResponse['source']['issuer_bank'])
+ ->setMaskedPan($resultResponse['source']['card_masking'])
+ ->setRef1($resultResponse['ref_1'])
+ ->setRef2($resultResponse['ref_2'])
+ ->setRef3($resultResponse['ref_3']);
+
+ $meta->save();
+
+ if ($meta->getTransactionState() == KbankPaymentsConfig::TRANSACTION_STATE_AUTH) {
+ $order->setState(Order::STATE_PROCESSING)
+ ->setStatus($order->getConfig()->getStateDefaultStatus(Order::STATE_PROCESSING));
+ $order->addStatusToHistory($order->getStatus(), __('Non 3DS Payment Authorized.'));
+
+ if ($this->kbankConfig->isAutoInvoice()
+ && $order->canInvoice()
+ ) {
+ $this->createInvoice($order);
+ }
+ } elseif ($meta->getTransactionState() == KbankPaymentsConfig::TRANSACTION_STATE_PRE_AUTH) {
+ $order->addStatusToHistory($order->getStatus(), __('3DS Payment Pre-Authorized.'));
+ }
+ } else {
+ $message = 'Order is suspended as its authorizing amount of %1 is suspected to be fraudulent.';
+ $order->addStatusToHistory(
+ $order->getStatus(),
+ __($message, $order->getIncrementId())
+ );
+ $order->registerCancellation(implode(', ', $result->getErrorCodes()));
+ }
+
+ $this->orderRepository->save($order);
+
+ return $meta;
+ }
+
+ /**
+ * @inheritDoc
+ * @SuppressWarnings(PHPMD.NPathComplexity)
+ */
+ public function authorizeOrder(Order $order): MetaInterface
+ {
+ /** @var Meta $meta */
+ $meta = $this->metaFactory->create();
+
+ try {
+ $token = $this->tokenRepository->getByOrderIncrement($order->getIncrementId());
+ return $this->authorizeOrderByToken($order, $token);
+ } catch (\Exception $e) {
+ return $meta;
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function authorizeOrderByTokenValue(string $tokenValue): MetaInterface
+ {
+ /** @var Meta $meta */
+ $meta = $this->metaFactory->create();
+
+ try {
+ $token = $this->tokenRepository->getByToken($tokenValue);
+ $orderIncrement = $token->getOrderId();
+ $order = $this->orderFactory->create()->loadByIncrementId($orderIncrement);
+
+ if ($order->getId()) {
+ return $this->authorizeOrderByToken($order, $token);
+ } else {
+ return $meta;
+ }
+
+ } catch (\Exception $e) {
+ return $meta;
+ }
+ }
+
+ /**
+ * @inheritDoc
+ * @SuppressWarnings(PHPMD.NPathComplexity)
+ */
+ public function settleOrder(Order $order, $chargeId): array
+ {
+ $request = [
+ 'method_type' => 'POST',
+ 'payload_type' => 'createSettleRequest'
+ ];
+ $data = [
+ 'charge_id' => $chargeId,
+ 'amount' => $this->_formatAmount($order->getGrandTotal()),
+ 'type' => 'full'
+ ];
+
+ $dataPost = array_merge($request, $data);
+ $resultResponse = $this->kbankClient->postRequest($dataPost);
+
+ $result = $this->transactionResponseValidator->validate(
+ array_merge($dataPost, ['response' => $resultResponse])
+ );
+
+ if ($result->isValid()) {
+ $order->addStatusToHistory($order->getStatus(), __('Payment Captured.'));
+ $this->orderRepository->save($order);
+ }
+
+ return $resultResponse;
+ }
+
+ /**
+ * @inheritDoc
+ * @SuppressWarnings(PHPMD.NPathComplexity)
+ */
+ public function transactionDetail(Order $order, $chargeId): array
+ {
+ $request = [
+ 'method_type' => 'GET',
+ 'payload_type' => 'transactionDetailsRequest'
+ ];
+ $data = [
+ 'charge_id' => $chargeId,
+ ];
+
+ $dataPost = array_merge($request, $data);
+
+ return $this->kbankClient->postRequest($dataPost);
+ }
+
+ /**
+ * @inheritDoc
+ * @SuppressWarnings(PHPMD.NPathComplexity)
+ */
+ public function voidOrder(Order $order, $chargeId): array
+ {
+ $request = [
+ 'method_type' => 'POST',
+ 'payload_type' => 'voidRequest'
+ ];
+ $data = [
+ 'charge_id' => $chargeId,
+ 'reason' => 'Cancel transaction for reference_order: ' . $order->getIncrementId()
+ ];
+
+ $dataPost = array_merge($request, $data);
+ $resultResponse = $this->kbankClient->postRequest($dataPost);
+
+ $result = $this->transactionResponseValidator->validate(
+ array_merge($dataPost, ['response' => $resultResponse])
+ );
+
+ if ($result->isValid()) {
+ $message = 'Order is voided as its authorizing amount of %1 on gateway.';
+ $order->addStatusToHistory(
+ $order->getStatus(),
+ __($message, $order->getIncrementId())
+ );
+ $order->registerCancellation($message);
+ } else {
+ $message = 'Cannot void order %1 on gateway.';
+ $order->addStatusToHistory(
+ $order->getStatus(),
+ __($message, $order->getOrderIncrementId())
+ );
+ }
+
+ $this->orderRepository->save($order);
+
+ return $resultResponse;
+ }
+
+ /**
+ * @inheritDoc
+ * @SuppressWarnings(PHPMD.NPathComplexity)
+ */
+ public function refundOrder(Order $order, $chargeId): array
+ {
+ $request = [
+ 'method_type' => 'POST',
+ 'payload_type' => 'refundRequest'
+ ];
+
+ $data = [
+ 'charge_id' => $chargeId,
+ 'amount' => $this->_formatAmount($order->getGrandTotal()),
+ 'reason' => 'Refund transaction for reference_order: ' . $order->getIncrementId()
+ ];
+
+ $dataPost = array_merge($request, $data);
+ $resultResponse = $this->kbankClient->postRequest($dataPost);
+
+ $result = $this->transactionResponseValidator->validate(
+ array_merge($dataPost, ['response' => $resultResponse])
+ );
+
+ if ($result->isValid()) {
+ $message = 'Order is refunded as its authorizing amount of %1 on gateway.';
+ $order->addStatusToHistory(
+ $order->getStatus(),
+ __($message, $order->getIncrementId())
+ );
+ $order->setState(Order::STATE_COMPLETE)
+ ->setStatus($order->getConfig()->getStateDefaultStatus(Order::STATE_COMPLETE));
+ } else {
+ $message = 'Cannot refund order %1 on gateway.';
+ $order->addStatusToHistory(
+ $order->getStatus(),
+ __($message, $order->getOrderIncrementId())
+ );
+ }
+
+ $this->orderRepository->save($order);
+
+ return $resultResponse;
+ }
+
+ /**
+ * @inheritDoc
+ * @SuppressWarnings(PHPMD.NPathComplexity)
+ */
+ public function createInvoice(Order $order): int
+ {
+ try {
+ $invoice = $this->invoiceService
+ ->prepareInvoice($order);
+ $invoice->register();
+ $invoice->getOrder()->setIsInProcess(true);
+ $invoice->save();
+
+ $transactionSave = $this->transaction->addObject(
+ $invoice
+ )->addObject(
+ $invoice->getOrder()
+ );
+
+ $transactionSave->save();
+
+ $this->invoiceSender->send($invoice);
+
+ $order->addStatusToHistory(
+ $order->getStatus(),
+ __('Notified customer about invoice creation #%1.', $invoice->getId()),
+ true
+ );
+
+ $result = $order->getId();
+ } catch (\Exception $e) {
+ $this->logger->critical($e->getMessage());
+ $result = $order->getId();
+ }
+
+ return $result;
+ }
+
+ /**
+ * Format Amount
+ *
+ * @param float $orderAmount
+ * @return string
+ */
+ protected function _formatAmount($orderAmount): string
+ {
+ return number_format((float)$orderAmount, 2, '.', '');
+ }
+}
diff --git a/Model/Ui/ConfigProvider.php b/Model/Ui/ConfigProvider.php
new file mode 100644
index 0000000..01a5d09
--- /dev/null
+++ b/Model/Ui/ConfigProvider.php
@@ -0,0 +1,134 @@
+config = $config;
+ $this->cart = $cart;
+ $this->urlBuilder = $urlBuilder;
+ $this->serializer = $serializer;
+ $this->installmentManagement = $installmentManagement;
+
+ foreach ($methodCodes as $code) {
+ $this->methods[$code] = $paymentHelper->getMethodInstance($code);
+ }
+ }
+
+ /**
+ * Retrieve assoc array of checkout configuration
+ *
+ * @return array
+ */
+ public function getConfig(): array
+ {
+ $configValues = [];
+ $storeId = $this->cart->getStoreId();
+
+ foreach ($this->methods as $methodCode => $method) {
+ if ($method->isAvailable()) {
+ $configValues = array_merge_recursive($configValues, [
+ 'payment' => [
+ $methodCode => [
+ 'processTokenActionUrl' => $this->getProcessTokenActionUrl(),
+ 'publicKey' => $this->config->getPublicKey($storeId),
+ 'jsSrc' => $this->config->getJsSrc($storeId),
+ 'environment' => $this->config->getEnvironment($storeId),
+ 'merchantId' => $method->getConfigData(Config::KEY_MERCHANT_ID),
+ 'terminalId' => $method->getConfigData(Config::KEY_TERMINAL_ID),
+ 'shopName' => $this->config->getShopName($storeId),
+ 'failureUrl' => $this->getFailureUrl(),
+ 'isUseInstallment' => (bool)$method->getConfigData(Config::KEY_INSTALLMENT_SUPPORT),
+ 'isPreDefinedInstallment' => (bool)$method->getConfigData(
+ Config::KEY_PRE_DEFINED_INSTALLMENT_SELECTION
+ ),
+ 'allowedInstallment' =>
+ $this->installmentManagement->getAvailableInstallment($methodCode)
+ ],
+ ]
+ ]);
+ }
+ }
+
+ return $configValues;
+ }
+
+ /**
+ * Get popup action URL
+ *
+ * @return string
+ */
+ protected function getProcessTokenActionUrl(): string
+ {
+ return $this->urlBuilder->getUrl('kbank/token/process', ['_secure' => true]);
+ }
+
+ /**
+ * Get failure url
+ *
+ * @return string
+ */
+ public function getFailureUrl(): string
+ {
+ return $this->urlBuilder->getUrl('checkout/onepage/failure', ['_secure' => true]);
+ }
+}
diff --git a/Model/Url.php b/Model/Url.php
new file mode 100644
index 0000000..ab79657
--- /dev/null
+++ b/Model/Url.php
@@ -0,0 +1,82 @@
+urlBuilder = $urlBuilder;
+ }
+
+ /**
+ * Get Success Url
+ *
+ * @return string
+ */
+ public function getSuccessUrl(): string
+ {
+ return $this->getUrl('checkout/onepage/success');
+ }
+
+ /**
+ * Get Failure Url
+ *
+ * @return string
+ */
+ public function getFailureUrl(): string
+ {
+ return $this->getUrl('checkout/onepage/failure');
+ }
+
+ /**
+ * Get Url
+ *
+ * @param string $route
+ * @param array $params
+ * @return string
+ */
+ public function getUrl(string $route = '', array $params = []): string
+ {
+ return $this->urlBuilder->getUrl($route, $params);
+ }
+
+ /**
+ * Set current order
+ *
+ * @param Order $order
+ * @return $this
+ */
+ public function setOrder(Order $order): Url
+ {
+ $this->order = $order;
+ return $this;
+ }
+
+ /**
+ * Get current order
+ *
+ * @return ?Order
+ */
+ public function getOrder(): ?Order
+ {
+ return $this->order;
+ }
+}
diff --git a/Observer/InstallmentDataAssignObserver.php b/Observer/InstallmentDataAssignObserver.php
new file mode 100644
index 0000000..f1f81b4
--- /dev/null
+++ b/Observer/InstallmentDataAssignObserver.php
@@ -0,0 +1,27 @@
+readDataArgument($observer);
+
+ $additionalData = $data->getData(PaymentInterface::KEY_ADDITIONAL_DATA);
+ if (!is_array($additionalData)) {
+ return;
+ }
+
+ $paymentInfo = $this->readPaymentModelArgument($observer);
+
+ foreach ($this->additionalInformationList as $additionalInformationKey) {
+ if (isset($additionalData[$additionalInformationKey])) {
+ $paymentInfo->setAdditionalInformation(
+ $additionalInformationKey,
+ $additionalData[$additionalInformationKey]
+ );
+ }
+ }
+ }
+}
diff --git a/Observer/ProcessTokenDataObserver.php b/Observer/ProcessTokenDataObserver.php
new file mode 100644
index 0000000..e57ba50
--- /dev/null
+++ b/Observer/ProcessTokenDataObserver.php
@@ -0,0 +1,137 @@
+ 'smartpay_id',
+ 'smartpayid' => 'smartpay_id',
+ 'smartpay_id' => 'smartpay_id',
+ 'mid' => 'mid',
+ 'mId' => 'mid',
+ 'term' => 'term',
+ 'terminal_id' => 'terminal_id',
+ 'terminalId' => 'terminal_id',
+ 'token' => 'token',
+ 'paymentMethods' => 'payment_methods',
+ 'payment_methods' => 'payment_methods',
+ 'dcc_currency' => 'dcc_currency',
+ 'saveCard' => 'save_card',
+ 'save_card' => 'save_card',
+ 'cardid' => 'card_id',
+ 'card_id' => 'card_id'
+ ];
+
+ /**
+ * @var TransactionProcessor
+ */
+ protected $transactionProcessor;
+
+ /**
+ * @var TokenFactory
+ */
+ protected $tokenFactory;
+
+ /**
+ * @var TokenRepository
+ */
+ protected $tokenRepository;
+
+ /**
+ * @var PaymentHelper
+ */
+ private $paymentHelper;
+
+ /**
+ * DataAssignObserver constructor.
+ *
+ * @param TransactionProcessor $transactionProcessor
+ * @param TokenFactory $tokenFactory
+ * @param TokenRepository $tokenRepository
+ * @param PaymentHelper $paymentHelper
+ */
+ public function __construct(
+ TransactionProcessor $transactionProcessor,
+ TokenFactory $tokenFactory,
+ TokenRepository $tokenRepository,
+ PaymentHelper $paymentHelper
+ ) {
+ $this->transactionProcessor = $transactionProcessor;
+ $this->tokenFactory = $tokenFactory;
+ $this->tokenRepository = $tokenRepository;
+ $this->paymentHelper = $paymentHelper;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function execute(Observer $observer)
+ {
+ /** @var array $gatewayTokenData */
+ $gatewayTokenData = $observer->getData('gateway_token');
+
+ /** @var Order $order */
+ $order = $observer->getData('order');
+
+ /** @var DataObject $result */
+ $result = $observer->getData('result');
+
+ if (empty($gatewayTokenData)) {
+ return $this;
+ }
+
+ /** @var Token $token */
+ $token = $this->tokenFactory->create();
+ $token->setOrderId($order->getIncrementId())
+ ->setCustomerId($order->getCustomerId());
+ foreach ($this->additionalInformationMappingList as $dataKey => $dataMap) {
+ if (isset($gatewayTokenData[$dataKey])) {
+ $token->setData($dataMap, $gatewayTokenData[$dataKey]);
+ }
+ }
+
+ $paymentCode = $order->getPayment()->getMethod();
+ $paymentInstance = $this->paymentHelper->getMethodInstance($paymentCode);
+
+ if (!$token->getTerminalId()) {
+ $token->setTerminalId(
+ $paymentInstance->getConfigData(
+ Config::KEY_TERMINAL_ID,
+ $order->getStoreId()
+ )
+ );
+ }
+
+ try {
+ $token = $this->tokenRepository->save($token);
+
+ $meta = $this->transactionProcessor
+ ->authorizeOrderByToken($order, $token);
+
+ $observer->setData('meta', $meta);
+ } catch (\Exception $e) {
+ $result->setData('result', false)
+ ->setData('message', __('Sorry, but we can\'t authorize your order. Please try again later'));
+ }
+ return $this;
+ }
+}
diff --git a/Observer/ThreeDSValidationObserver.php b/Observer/ThreeDSValidationObserver.php
new file mode 100644
index 0000000..4d6718c
--- /dev/null
+++ b/Observer/ThreeDSValidationObserver.php
@@ -0,0 +1,120 @@
+transactionProcessor = $transactionProcessor;
+ $this->tokenFactory = $tokenFactory;
+ $this->kbankEmbeddedConfig = $kbankEmbeddedConfig;
+ $this->orderRepository = $orderRepository;
+ $this->logger = $logger;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function execute(Observer $observer)
+ {
+ /** @var MetaInterface $meta */
+ $meta = $observer->getData('meta');
+
+ /** @var Order $order */
+ $order = $observer->getData('order');
+
+ /** @var DataObject */
+ $errorObj = $observer->getData('error');
+ $error = $errorObj->getError() ?? [];
+ $type = (string)$observer->getData('type');
+
+ try {
+ $resultResponse = $this->transactionProcessor
+ ->transactionDetail($order, $meta->getChargeId());
+
+ if (!isset($resultResponse['object'])
+ || !isset($resultResponse['transaction_state'])
+ ) {
+ $error[] = __('Invalid returned while inquiring or invalid transaction state');
+ return $this;
+ }
+
+ if ($resultResponse['object'] == KbankEmbeddedConfig::VALIDATION_3DS_VALIDATION_OBJECT
+ && $resultResponse['transaction_state'] == KbankEmbeddedConfig::TRANSACTION_STATE_AUTH
+ ) {
+ $order->setState(Order::STATE_PROCESSING)
+ ->setStatus($order->getConfig()->getStateDefaultStatus(Order::STATE_PROCESSING));
+ $order->addStatusToHistory($order->getStatus(), __('3DS Payment Authorized.'));
+
+ $this->logger->info(sprintf('3DS Payment Authorized by %s', $type));
+
+ if ($this->kbankEmbeddedConfig->isAutoInvoice()
+ && $order->canInvoice()
+ ) {
+ $this->transactionProcessor->createInvoice($order);
+ }
+
+ $this->orderRepository->save($order);
+ }
+ } catch (\Exception $e) {
+ $error[] = $e->getMessage();
+ } finally {
+ $errorObj->setData('error', $error);
+ }
+
+ return $this;
+ }
+}
diff --git a/Plugin/Api/OrderRepositoryInterfacePlugin.php b/Plugin/Api/OrderRepositoryInterfacePlugin.php
new file mode 100644
index 0000000..998dccb
--- /dev/null
+++ b/Plugin/Api/OrderRepositoryInterfacePlugin.php
@@ -0,0 +1,104 @@
+metaRepository = $metaRepository;
+ $this->orderExtensionFactory = $orderExtensionFactory;
+ }
+
+ /**
+ * Add more fields
+ *
+ * @param OrderRepositoryInterface $subject
+ * @param OrderInterface $order
+ * @return OrderInterface
+ */
+ public function afterGet(OrderRepositoryInterface $subject, OrderInterface $order): OrderInterface
+ {
+ $this->addSalesExtensionData($order);
+ return $order;
+ }
+
+ /**
+ * Add more fields
+ *
+ * @param OrderRepositoryInterface $subject
+ * @param OrderSearchResultInterface $orderSearchResult
+ * @return OrderSearchResultInterface
+ */
+ public function afterGetList(
+ OrderRepositoryInterface $subject,
+ OrderSearchResultInterface $orderSearchResult
+ ): OrderSearchResultInterface {
+ foreach ($orderSearchResult->getItems() as $item) {
+ $this->addSalesExtensionData($item);
+ }
+
+ return $orderSearchResult;
+ }
+
+ /**
+ * Append sales order extension data
+ *
+ * @param OrderInterface $order
+ * @return void
+ */
+ protected function addSalesExtensionData(OrderInterface $order)
+ {
+ $extensionAttributes = $order->getExtensionAttributes();
+ $orderExtension = $extensionAttributes ?: $this->orderExtensionFactory->create();
+
+ $payment = $order->getPayment();
+
+ if (!in_array(
+ $payment->getMethod(),
+ [
+ Config::METHOD_KBANK_EMBEDDED_INSTALLMENT,
+ Config::METHOD_KBANK_EMBEDDED_FULLPAYMENT,
+ Config::METHOD_KBANK_INLINE
+ ]
+ )) {
+ return;
+ }
+
+ try {
+ $meta = $this->metaRepository->getByOrderIncrement($order->getIncrementId());
+ if ($meta->getMetaId()) {
+
+ $orderExtension->setCreditCardNo($meta->getMaskedPan());
+ $orderExtension->setCreditType($meta->getCardBrand());
+
+ $order->setExtensionAttributes($orderExtension);
+ }
+ } catch (\Exception $e) {
+ return;
+ }
+ }
+}
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..3e63e69
--- /dev/null
+++ b/README.md
@@ -0,0 +1,46 @@
+# Kasikornbank(Kbank) Magento 2 Payments Gateway Module #
+
+ composer require ghoster/module-kbankpayments
+
+[![Latest Stable Version](https://poser.pugx.org/ghoster/module-kbankpayments/v)](https://packagist.org/packages/ghoster/module-kbankpayments)
+[![Latest Unstable Version](https://poser.pugx.org/ghoster/module-kbankpayments/v/unstable)](https://packagist.org/packages/ghoster/module-kbankpayments)
+[![License](https://poser.pugx.org/ghoster/module-outofstockatlast/license)](https://packagist.org/packages/ghoster/module-outofstockatlast)
+[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.me/thinghost)
+[![Build Status](https://github.com/tuyennn/magento2-kbank-payment/actions/workflows/coding-standard.yml/badge.svg)](https://github.com/tuyennn/magento2-kbank-payment/actions/workflows/coding-standard.yml)
+---
+
+## Features
+
+* Module to add payment method Kbank (Kasikornbank) for Embedded UI Method
+* Support 3DS
+* Support Installment
+
+![kbank_embedded_installment](./.github/Screenshot/kbank_embedded_installment.png)
+![kbank_embedded_full_payment](./.github/Screenshot/kbank_embedded_full_payment.png)
+
+## Compatibility
+
+| Magento Version (Open Source/Commerce) | Supported |
+| -------------------------------------- | --------- |
+| **2.0.x** | No ❌ |
+| **2.1.x** | No ❌ |
+| **2.2.x** | No ❌ |
+| **<2.3.2** | No ❌ |
+| **<2.3.5** | No ❌ |
+| **>=2.3.5** | No ❌ |
+| **2.4.0** | Yes ✔️ |
+| **>=2.4.1 && < 2.4.6** | Yes ✔️ |
+| **>=2.4.6** | Yes ✔️ |
+
+## Configurations
+* Require the public and secret key from the Gateway:
+* `/kbank/payment/callback` for Card Payment Callback URL: (Embedded Method only)
+* `/rest/V1/kbank/payment/notify` for Card Payment Notify URL: (Embedded Method only)
+
+![Configuration](./.github/Screenshot/kbank_payment_configuration.png)
+
+## Donation
+
+If this project help you reduce time to develop, you can give me a cup of beer :)
+
+[![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.me/thinghost)
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..f4b7023
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,54 @@
+{
+ "name": "ghoster/module-kbankpayments",
+ "description": "Kasikornbank Magento 2 Payments Gateway Module",
+ "keywords": [
+ "magento 2",
+ "magento",
+ "m2",
+ "kasikornbank payment",
+ "kbank payment",
+ "magento 2 extension",
+ "magento 2 extension free"
+ ],
+ "require": {
+ "magento/framework": "^103.0"
+ },
+ "minimum-stability": "stable",
+ "archive": {
+ "exclude": [
+ "/.gitignore",
+ "/grumphp.yml",
+ "/pdepend.xml",
+ "/phpstan.neon",
+ "/phpunit.xml",
+ "/phpcs.xml",
+ "/phpmd.xml",
+ "/package.json",
+ "/.eslintrc.json",
+ "/.eslintignore",
+ "/tests",
+ "/.github"
+ ]
+ },
+ "type": "magento2-module",
+ "version": "1.0.0",
+ "authors": [
+ {
+ "name": "Tuyen Nguyen",
+ "email": "thinghost76@gmail.com",
+ "homepage": "https://thinghost.info/"
+ }
+ ],
+ "license": [
+ "OSL-3.0",
+ "AFL-3.0"
+ ],
+ "autoload": {
+ "files": [
+ "registration.php"
+ ],
+ "psr-4": {
+ "GhoSter\\KbankPayments\\": ""
+ }
+ }
+}
diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml
new file mode 100644
index 0000000..4109a6c
--- /dev/null
+++ b/etc/adminhtml/system.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+ Accept online payments using Kbank Embedded Method. K-Payment Gateway provided by KASIKORNBANK Public Company Limited (hereinafter
+ referred to as “KBank”) is the online payment service to facilitate merchants and offers
+ customers more online payment options.
+ K-Payment Gateway merchant is able to integrate with APIs and also test on 24x7
+ available sandbox environment provided by KBank before go live.
]]>
+
+ complex kbank-admin-config kbank-section
+ GhoSter\KbankPayments\Block\Adminhtml\Config\Fieldset
+
+
+
+
+
+
+
diff --git a/etc/adminhtml/system/basic.xml b/etc/adminhtml/system/basic.xml
new file mode 100644
index 0000000..6a926fa
--- /dev/null
+++ b/etc/adminhtml/system/basic.xml
@@ -0,0 +1,56 @@
+
+
+
+ Credentials Settings
+ <p>You will need to set your API keys before you can accept payments. If you do not yet have a Kbank account, please <a href="https://kasikornbank.com" target="_blank" rel="noopener noreferrer">click here</a> to register.</p>
+
+ <p>For additional technical support contact <a href="mailto:info@kasikornbank.com" target="_blank" rel="noopener noreferrer">info@kasikornbank.com</a>. Or access <a href="https://apiportal.kasikornbank.com/product/content/All/K%20PAYMENT%20GATEWAY/Getting%20Started/K%20PAYMENT%20GATEWAY" target="_blank" rel="noopener noreferrer">API Portal</a>. </p>
+
+
+
+ Public Key
+ Magento\Config\Model\Config\Backend\Encrypted
+ ghoster_kbank/general/public_key
+
+
+ Secret Key
+ Magento\Config\Model\Config\Backend\Encrypted
+ ghoster_kbank/general/secret_key
+
+
+ Environment
+ GhoSter\KbankPayments\Model\Adminhtml\Source\Environment
+ ghoster_kbank/general/environment
+
+
+ Shop Name
+ ghoster_kbank/general/shop_name
+
+
+ 3DS
+ Magento\Config\Model\Config\Source\Yesno
+ ghoster_kbank/general/is_3ds_support
+
+
+ Debug
+ Magento\Config\Model\Config\Source\Yesno
+ ghoster_kbank/general/debug
+
+
+ Auto Create Invoice
+ Magento\Config\Model\Config\Source\Yesno
+ ghoster_kbank/general/auto_invoice
+
+
+ Expired Pending Time(minutes)
+ validate-digits validate-digits-range digits-range-1-
+ ghoster_kbank/general/expired_pending_time
+
+
+ Notify Response Endpoint
+ Kbank Portal .]]>
+ GhoSter\KbankPayments\Block\Adminhtml\Config\Form\Field\Webhook
+
+
+
diff --git a/etc/adminhtml/system/embedded_fullpayment.xml b/etc/adminhtml/system/embedded_fullpayment.xml
new file mode 100644
index 0000000..0a913a7
--- /dev/null
+++ b/etc/adminhtml/system/embedded_fullpayment.xml
@@ -0,0 +1,55 @@
+
+
+
+ Kbank Embedded Full Payment Settings
+ <p>The settings on these fields should be related only with Embedded Settings</p>
+
+
+ Enabled
+ Magento\Config\Model\Config\Source\Yesno
+ payment/kbank_embedded_fullpayment/active
+
+
+ Title
+ payment/kbank_embedded_fullpayment/title
+
+
+ Payment Action
+ GhoSter\KbankPayments\Model\Adminhtml\Source\PaymentAction
+ payment/kbank_embedded_fullpayment/payment_action
+
+
+ Merchant Id(MID)
+ payment/kbank_embedded_fullpayment/merchant_id
+
+
+ Terminal Id(TID)
+ payment/kbank_embedded_fullpayment/terminal_id
+
+
+ New Order Status
+ Magento\Sales\Model\Config\Source\Order\Status\Newprocessing
+ payment/kbank_embedded_fullpayment/order_status
+
+
+ Payment from Applicable Countries
+ Magento\Payment\Model\Config\Source\Allspecificcountries
+ payment/kbank_embedded_fullpayment/allowspecific
+
+
+ Payment from Specific Countries
+ Magento\Directory\Model\Config\Source\Country
+ payment/kbank_embedded_fullpayment/specificcountry
+
+
+ Sort Order
+ validate-number
+ payment/kbank_embedded_fullpayment/sort_order
+
+
+ Instructions
+ payment/kbank_embedded_fullpayment/instructions
+
+
+
diff --git a/etc/adminhtml/system/embedded_installment.xml b/etc/adminhtml/system/embedded_installment.xml
new file mode 100644
index 0000000..a6211c7
--- /dev/null
+++ b/etc/adminhtml/system/embedded_installment.xml
@@ -0,0 +1,82 @@
+
+
+
+ Kbank Embedded Installment Settings
+ <p>The settings on these fields should be related only with Embedded Settings</p>
+
+
+ Enabled
+ Magento\Config\Model\Config\Source\Yesno
+ payment/kbank_embedded_installment/active
+
+
+ Title
+ payment/kbank_embedded_installment/title
+
+
+ Payment Action
+ GhoSter\KbankPayments\Model\Adminhtml\Source\PaymentAction
+ payment/kbank_embedded_installment/payment_action
+
+
+ Merchant Id(MID)
+ payment/kbank_embedded_installment/merchant_id
+
+
+ Terminal Id(TID)
+ payment/kbank_embedded_installment/terminal_id
+
+
+ Pre-defined Installment Terms for selection on checkout
+ Magento\Config\Model\Config\Source\Yesno
+ payment/kbank_embedded_installment/predefine_installment
+
+ 1
+
+
+
+ Smart Pay ID
+ payment/kbank_embedded_installment/smartpay_id
+
+ 1
+ 0
+
+
+
+ Installment Information
+ GhoSter\KbankPayments\Block\Adminhtml\System\Config\SmartPayId
+ GhoSter\KbankPayments\Model\Config\Backend\SmartPayId
+
+
+ 1
+ 1
+
+ payment/kbank_embedded_installment/installment_info
+
+
+ New Order Status
+ Magento\Sales\Model\Config\Source\Order\Status\Newprocessing
+ payment/kbank_embedded_installment/order_status
+
+
+ Payment from Applicable Countries
+ Magento\Payment\Model\Config\Source\Allspecificcountries
+ payment/kbank_embedded_installment/allowspecific
+
+
+ Payment from Specific Countries
+ Magento\Directory\Model\Config\Source\Country
+ payment/kbank_embedded_installment/specificcountry
+
+
+ Sort Order
+ validate-number
+ payment/kbank_embedded_installment/sort_order
+
+
+ Instructions
+ payment/kbank_embedded_installment/instructions
+
+
+
diff --git a/etc/config.xml b/etc/config.xml
new file mode 100644
index 0000000..8869413
--- /dev/null
+++ b/etc/config.xml
@@ -0,0 +1,122 @@
+
+
+
+
+
+ sandbox
+ Your Shop Name
+ 1
+ 1
+ 1
+ 10
+
+
+
+
+
+
+ 0
+ GhoSterKbankEmbeddedInstallmentFacade
+ Kbank Embedded Installment
+ pending
+ 1
+ 0
+ 0
+ 0
+ 1
+ 0
+ 0
+ 1
+ 0
+ 0
+ 1
+ 1
+ 0
+ 1
+ 1
+ 0001
+ 0
+ 1
+ 0
+
+
+ 0001
+ 3
+ 3 - 0.00%
+ 1
+
+
+ 0001
+ 4
+ 4 - 0.00%
+ 1
+
+
+ 0001
+ 5
+ 5 - 0.00%
+ 1
+
+
+ 0001
+ 6
+ 6 - 0.00%
+ 1
+
+
+ 0002
+ 3
+ 3 - 0.65%
+ 1
+
+
+ 0002
+ 4
+ 4 - 0.65%
+ 1
+
+
+ 0002
+ 5
+ 5 - 0.65%
+ 1
+
+
+ 0002
+ 6
+ 6 - 0.65%
+ 1
+
+
+ 10
+
+
+ 0
+ GhoSterKbankEmbeddedFullPaymentFacade
+ Kbank Embedded Full Payment
+ pending
+ 1
+ 0
+ 0
+ 0
+ 1
+ 0
+ 0
+ 1
+ 0
+ 0
+ 1
+ 1
+ 0
+ 1
+ 1
+ 0
+ 0
+ 10
+
+
+
+
+
diff --git a/etc/crontab.xml b/etc/crontab.xml
new file mode 100644
index 0000000..3ee8eb3
--- /dev/null
+++ b/etc/crontab.xml
@@ -0,0 +1,9 @@
+
+
+
+
+ */5 * * * *
+
+
+
+
diff --git a/etc/csp_whitelist.xml b/etc/csp_whitelist.xml
new file mode 100644
index 0000000..9c7e925
--- /dev/null
+++ b/etc/csp_whitelist.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+ *.kasikornbank.com
+
+
+
+
+ *.kasikornbank.com
+
+
+
+
+ *.kasikornbank.com
+
+
+
+
+ *.kasikornbank.com
+
+
+
+
+ *.kasikornbank.com
+
+
+
+
diff --git a/etc/db_schema.xml b/etc/db_schema.xml
new file mode 100644
index 0000000..6341763
--- /dev/null
+++ b/etc/db_schema.xml
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/etc/db_schema_whitelist.json b/etc/db_schema_whitelist.json
new file mode 100644
index 0000000..ba0bbf7
--- /dev/null
+++ b/etc/db_schema_whitelist.json
@@ -0,0 +1,67 @@
+{
+ "ghoster_kbank_token": {
+ "column": {
+ "token_id": true,
+ "customer_id": true,
+ "token": true,
+ "order_id": true,
+ "paymentMethods": true,
+ "created_time": true,
+ "mid": true,
+ "dcc_currency": true,
+ "smartpayid": true,
+ "term": true,
+ "saveCard": true,
+ "cardid": true,
+ "campaign_id": true,
+ "terminal_id": true
+ },
+ "index": {
+ "GHOSTER_KBANK_TOKEN_TOKEN_ID": true
+ },
+ "constraint": {
+ "PRIMARY": true,
+ "GHOSTER_KBANK_TOKEN_CUSTOMER_ID_CUSTOMER_ENTITY_ENTITY_ID": true
+ }
+ },
+ "ghoster_kbank_meta": {
+ "column": {
+ "meta_id": true,
+ "order_id": true,
+ "customer_id": true,
+ "charge_id": true,
+ "object": true,
+ "amount": true,
+ "currency": true,
+ "transaction_state": true,
+ "created": true,
+ "status": true,
+ "approval_code": true,
+ "livemode": true,
+ "metadata": true,
+ "failure_code": true,
+ "failure_message": true,
+ "redirect_url": true,
+ "card_id": true,
+ "card_brand": true,
+ "issuer_bank": true,
+ "masked_pan": true,
+ "ref_1": true,
+ "ref_2": true,
+ "ref_3": true,
+ "baht_amount": true,
+ "rate": true,
+ "converted_amount": true,
+ "converted_currency_code": true,
+ "converted_currency_name": true,
+ "f63": true,
+ "mid": true
+ },
+ "index": {
+ "GHOSTER_KBANK_META_META_ID": true
+ },
+ "constraint": {
+ "PRIMARY": true
+ }
+ }
+}
diff --git a/etc/di.xml b/etc/di.xml
new file mode 100644
index 0000000..2bdd5c6
--- /dev/null
+++ b/etc/di.xml
@@ -0,0 +1,361 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ GhoSter\KbankPayments\Gateway\Config::METHOD_KBANK_EMBEDDED_INSTALLMENT
+
+
+
+
+ GhoSter\KbankPayments\Gateway\Config::METHOD_KBANK_EMBEDDED_FULLPAYMENT
+
+
+
+
+
+
+ - /kbank/token/
+ - /kbank/payment/
+
+
+
+
+
+
+ GhoSterKbankEmbeddedInstallmentConfig
+
+
+
+
+ GhoSterKbankEmbeddedFullPaymentConfig
+
+
+
+
+
+
+ - GhoSterKbankEmbeddedInstallmentValueHandler
+
+
+
+
+
+
+ - GhoSterKbankEmbeddedFullPaymentValueHandler
+
+
+
+
+
+ GhoSterKbankEmbeddedInstallmentConfig
+
+
+
+
+ GhoSterKbankEmbeddedFullPaymentConfig
+
+
+
+
+
+ - GhoSterKbankEmbeddedInstallmentCountryValidator
+
+
+
+
+
+
+ - GhoSterKbankEmbeddedFullPaymentCountryValidator
+
+
+
+
+
+ GhoSterKbankEmbeddedInstallmentConfig
+
+
+
+
+ GhoSterKbankEmbeddedFullPaymentConfig
+
+
+
+
+
+
+
+ - GhoSterKbankVoidCommand
+ - GhoSterKbankEmbeddedRefundCommand
+ - GhoSterKbankEmbeddedRefundSettledCommand
+ - GhoSterKbankCancelCommand
+ - GhoSterKbankTransactionDetailsCommand
+ - GhoSterKbankEmbeddedFetchTransactionInfoCommand
+ - GhoSterKbankEmbeddedDoNothingCommand
+
+
+
+
+
+
+
+
+ GhoSterKbankVoidRequest
+ GhoSterKbankDefaultTransferFactory
+ GhoSter\KbankPayments\Gateway\Http\Client
+ GhoSter\KbankPayments\Logger\Logger
+ GhoSterKbankVoidHandler
+ GhoSterKbankTransactionValidator
+
+
+
+
+ GhoSterKbankEmbeddedCommandPool
+
+
+
+
+ GhoSterKbankEmbeddedRefundRequest
+ GhoSterKbankDefaultTransferFactory
+ GhoSter\KbankPayments\Gateway\Http\Client
+ GhoSter\KbankPayments\Logger\Logger
+ GhoSterKbankTransactionValidator
+ GhoSterKbankEmbeddedPaymentRefundHandler
+
+
+
+
+ GhoSterKbankEmbeddedCommandPool
+
+
+
+
+ GhoSterKbankTransactionDetailsRequest
+ GhoSterKbankDefaultTransferFactory
+ GhoSter\KbankPayments\Gateway\Http\Client
+ GhoSterKbankTransactionDetailsValidator
+
+
+
+
+ GhoSterKbankCancelHandler
+
+
+
+
+ GhoSterKbankEmbeddedCommandPool
+
+
+
+
+
+
+
+
+ - GhoSterKbankTransactionDetailsRequestTypeBuilder
+ - GhoSterKbankRequestMethodGetBuilder
+ - GhoSter\KbankPayments\Gateway\Request\TransactionDetailsDataBuilder
+
+
+
+
+
+
+ - GhoSterKbankVoidRequestTypeBuilder
+ - GhoSterKbankRequestMethodPostBuilder
+ - GhoSter\KbankPayments\Gateway\Request\VoidDataBuilder
+
+
+
+
+
+
+ - GhoSterKbankEmbeddedRefundRequestTypeBuilder
+ - GhoSterKbankRequestMethodPostBuilder
+ - GhoSter\KbankPayments\Gateway\Request\RefundDataBuilder
+
+
+
+
+
+
+
+
+ - GhoSter\KbankPayments\Gateway\Response\Handler\VoidResponseHandler
+
+
+
+
+
+ false
+
+
+
+
+
+ - GhoSter\KbankPayments\Gateway\Response\Handler\ChargeIdHandler
+ - GhoSter\KbankPayments\Gateway\Response\Handler\CloseParentTransactionHandler
+ - GhoSter\KbankPayments\Gateway\Response\Handler\CloseTransactionHandler
+
+
+
+
+
+
+ - GhoSter\KbankPayments\Gateway\Response\Handler\CloseTransactionHandler
+ - GhoSter\KbankPayments\Gateway\Response\Handler\CloseParentTransactionHandler
+ - GhoSter\KbankPayments\Gateway\Response\Handler\Cancel\CancelOrder
+
+
+
+
+
+
+
+
+
+ - store_id
+
+
+
+
+
+
+ - GhoSterKbankRemoveStoreConfigFilter
+
+
+
+
+
+
+
+
+ transactionDetailsRequest
+
+
+
+
+ voidRequest
+
+
+
+
+ refundRequest
+
+
+
+
+ POST
+
+
+
+
+ GET
+
+
+
+
+
+
+ GhoSter\KbankPayments\Gateway\Config::METHOD_KBANK_EMBEDDED_INSTALLMENT
+ GhoSter\KbankPayments\Block\Form
+ GhoSterKbankEmbeddedInstallmentInfoBlock
+ GhoSterKbankEmbeddedInstallmentValueHandlerPool
+ GhoSterEmbeddedInstallmentValidatorPool
+ GhoSterKbankEmbeddedCommandPool
+
+
+
+
+ GhoSter\KbankPayments\Gateway\Config::METHOD_KBANK_EMBEDDED_FULLPAYMENT
+ GhoSter\KbankPayments\Block\Form
+ GhoSterKbankEmbeddedFullPaymentInfoBlock
+ GhoSterKbankEmbeddedFullPaymentValueHandlerPool
+ GhoSterEmbeddedFullPaymentValidatorPool
+ GhoSterKbankEmbeddedCommandPool
+
+
+
+
+
+
+
+ - true
+
+
+ - GhoSter\KbankPayments\Gateway\Validator\GeneralResponseValidator
+ - GhoSter\KbankPayments\Gateway\Validator\TransactionResponseValidator
+
+
+
+
+
+
+ - GhoSter\KbankPayments\Gateway\Validator\GeneralResponseValidator
+
+
+
+
+
+
+
+
+ ghoster_kbank_logger
+
+ - GhoSter\KbankPayments\Logger\PaymentHandler
+
+
+
+
+
+ Magento\Framework\Filesystem\Driver\File
+ /var/log/kbank_payments.log
+
+
+
+
+
+
+ GhoSter\KbankPayments\Logger\Logger
+
+
+
+
+ GhoSter\KbankPayments\Logger\Logger
+
+
+
+
+ GhoSter\KbankPayments\Logger\Logger
+
+
+
+
+ GhoSter\KbankPayments\Logger\Logger
+
+
+
+
+ GhoSter\KbankPayments\Logger\Logger
+
+
+
+
+ GhoSter\KbankPayments\Logger\Logger
+
+
+
diff --git a/etc/events.xml b/etc/events.xml
new file mode 100644
index 0000000..fa15d81
--- /dev/null
+++ b/etc/events.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/etc/extension_attributes.xml b/etc/extension_attributes.xml
new file mode 100644
index 0000000..a4ac022
--- /dev/null
+++ b/etc/extension_attributes.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/etc/frontend/di.xml b/etc/frontend/di.xml
new file mode 100644
index 0000000..cac9e44
--- /dev/null
+++ b/etc/frontend/di.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+ - 1
+
+
+
+
+
+
+ GhoSterKbankEmbeddedInstallmentConfig
+
+ - GhoSter\KbankPayments\Gateway\Config::METHOD_KBANK_EMBEDDED_INSTALLMENT
+ - GhoSter\KbankPayments\Gateway\Config::METHOD_KBANK_EMBEDDED_FULLPAYMENT
+
+
+
+
+
+
+
+ - GhoSterKbankEmbeddedConfigProvider
+
+
+
+
+
+
+
+ - /kbank/
+
+
+
+
diff --git a/etc/frontend/routes.xml b/etc/frontend/routes.xml
new file mode 100644
index 0000000..3cbb50b
--- /dev/null
+++ b/etc/frontend/routes.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/etc/module.xml b/etc/module.xml
new file mode 100644
index 0000000..ae44c3b
--- /dev/null
+++ b/etc/module.xml
@@ -0,0 +1,5 @@
+
+
+
+
diff --git a/etc/payment.xml b/etc/payment.xml
new file mode 100644
index 0000000..bbe80d8
--- /dev/null
+++ b/etc/payment.xml
@@ -0,0 +1,11 @@
+
+
+
+
+ 0
+
+
+ 0
+
+
+
diff --git a/etc/webapi.xml b/etc/webapi.xml
new file mode 100644
index 0000000..aff2878
--- /dev/null
+++ b/etc/webapi.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/etc/webapi_rest/di.xml b/etc/webapi_rest/di.xml
new file mode 100644
index 0000000..7368ad5
--- /dev/null
+++ b/etc/webapi_rest/di.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
diff --git a/registration.php b/registration.php
new file mode 100644
index 0000000..28b0e4e
--- /dev/null
+++ b/registration.php
@@ -0,0 +1,7 @@
+
+
+
+
+
+
diff --git a/view/adminhtml/web/images/logo-kbank.png b/view/adminhtml/web/images/logo-kbank.png
new file mode 100644
index 0000000..8a6ea31
Binary files /dev/null and b/view/adminhtml/web/images/logo-kbank.png differ
diff --git a/view/adminhtml/web/styles.css b/view/adminhtml/web/styles.css
new file mode 100644
index 0000000..fc98a5b
--- /dev/null
+++ b/view/adminhtml/web/styles.css
@@ -0,0 +1,4 @@
+.kbank-section .kbank-payment-logo {background: url("./images/logo-kbank.png") no-repeat 0 40% / 18rem auto;
+ height: 100px; width: auto; padding-left: 20rem; display:inline-block; vertical-align: middle;}
+.kbank-section .button-container {float: right;}
+.kbank-section .kbank-payment-text { display: inline-block; vertical-align: middle; width: 50%}
diff --git a/view/base/web/js/view/payment/kbankjs-client.js b/view/base/web/js/view/payment/kbankjs-client.js
new file mode 100644
index 0000000..cc36c3b
--- /dev/null
+++ b/view/base/web/js/view/payment/kbankjs-client.js
@@ -0,0 +1,72 @@
+define([
+ 'jquery',
+ 'uiClass',
+ 'GhoSter_KbankPayments/js/view/payment/validator-handler'
+ //phpcs:ignore Squiz.Functions.MultiLineFunctionDeclaration.SpaceBeforeOpenParen
+], function (
+ $,
+ Class,
+ validatorHandler
+) {
+ 'use strict';
+
+ return Class.extend({
+ defaults: {
+ environment: 'production'
+ },
+
+ /**
+ * @{inheritdoc}
+ */
+ initialize: function () {
+ validatorHandler.initialize();
+
+ this._super();
+
+ return this;
+ },
+
+ /**
+ * Creates the token pair with the provided data
+ *
+ * @param {Object} data
+ * @return {jQuery.Deferred}
+ */
+ createTokens: function (data) {
+ var deferred = $.Deferred();
+
+ this._createTokens(deferred, data);
+
+ return deferred.promise();
+ },
+
+ /**
+ * Creates a token from the payment information in the form
+ *
+ * @param {jQuery.Deferred} deferred
+ * @param {Object} data
+ */
+ _createTokens: async function (deferred, data) {
+ if (window.hasOwnProperty('KInlineCheckout') && typeof window.KInlineCheckout !== 'undefined') {
+
+ let token = await window.KInlineCheckout.getToken()
+ .then((result) => {
+ return result;
+ });
+
+ validatorHandler.validate(token, function (valid, messages) {
+ if (valid) {
+ deferred.resolve({
+ tokenValue: token['token'],
+ cardData: token.hasOwnProperty('card') ? token['card'] : null
+ });
+ } else {
+ deferred.reject(messages);
+ }
+ });
+ } else {
+ deferred.reject(['The payment gateway was not ready.']);
+ }
+ }
+ });
+});
diff --git a/view/base/web/js/view/payment/response-validator.js b/view/base/web/js/view/payment/response-validator.js
new file mode 100644
index 0000000..fabb97f
--- /dev/null
+++ b/view/base/web/js/view/payment/response-validator.js
@@ -0,0 +1,29 @@
+define([
+ 'jquery',
+ 'mage/translate'
+], function ($, $t) {
+ 'use strict';
+
+ return {
+ /**
+ * Validate Kbank response
+ *
+ * @param {Object} context
+ * @returns {jQuery.Deferred}
+ */
+ validate: function (context) {
+ var state = $.Deferred(),
+ messages = [];
+
+ if (context.hasOwnProperty('token')) {
+ state.resolve();
+ } else {
+ messages.push($t('There is an error during getting token.'));
+ state.reject(messages);
+ }
+
+ return state.promise();
+ }
+ };
+});
+
diff --git a/view/base/web/js/view/payment/validator-handler.js b/view/base/web/js/view/payment/validator-handler.js
new file mode 100644
index 0000000..a4e1f47
--- /dev/null
+++ b/view/base/web/js/view/payment/validator-handler.js
@@ -0,0 +1,54 @@
+define([
+ 'jquery',
+ 'GhoSter_KbankPayments/js/view/payment/response-validator'
+], function ($, responseValidator) {
+ 'use strict';
+
+ return {
+ validators: [],
+
+ /**
+ * Init list of validators
+ */
+ initialize: function () {
+ this.add(responseValidator);
+ },
+
+ /**
+ * Add new validator
+ * @param {Object} validator
+ */
+ add: function (validator) {
+ this.validators.push(validator);
+ },
+
+ /**
+ * Run pull of validators
+ * @param {Object} context
+ * @param {Function} callback
+ */
+ validate: function (context, callback) {
+ var self = this,
+ deferred;
+
+ // no available validators
+ if (!self.validators.length) {
+ callback(true);
+
+ return;
+ }
+
+ // get list of deferred validators
+ deferred = $.map(self.validators, function (current) {
+ return current.validate(context);
+ });
+
+ $.when.apply($, deferred)
+ .done(function () {
+ callback(true);
+ }).fail(function (error) {
+ callback(false, error);
+ });
+ }
+ };
+});
diff --git a/view/frontend/layout/checkout_index_index.xml b/view/frontend/layout/checkout_index_index.xml
new file mode 100644
index 0000000..ec7cc7c
--- /dev/null
+++ b/view/frontend/layout/checkout_index_index.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+ -
+
-
+
-
+
-
+
-
+
-
+
- uiComponent
+ -
+
-
+
-
+
-
+
+
-
+
-
+
- GhoSter_KbankPayments/js/view/payment/kbank-embedded-payments
+ -
+
-
+
- true
+
+ -
+
- true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/view/frontend/templates/hss/js.phtml b/view/frontend/templates/hss/js.phtml
new file mode 100644
index 0000000..24a976a
--- /dev/null
+++ b/view/frontend/templates/hss/js.phtml
@@ -0,0 +1,6 @@
+
+
diff --git a/view/frontend/web/css/source/_module.less b/view/frontend/web/css/source/_module.less
new file mode 100644
index 0000000..32d1eef
--- /dev/null
+++ b/view/frontend/web/css/source/_module.less
@@ -0,0 +1,97 @@
+
+//
+// Common
+// _____________________________________________
+@payment-active__color: #00ab86;
+
+& when (@media-common = true) {
+ .checkout-payment-method {
+ .payment-method-content {
+ kbank-payment-method {
+ padding-top: 15px;
+
+ label {
+ font-size: 1em;
+ margin-bottom: 10px;
+
+ &::after {
+ content: '*';
+ color: #e02b27;
+ font-size: 1.2rem;
+ margin: 0 0 0 5px;
+ }
+ }
+
+ .field {
+ margin-bottom: 10px;
+ }
+
+ .input-mask {
+ border: 1px solid #ccc !important;
+ border-radius: 5px;
+ padding: 3px 10px 2px;
+ width: 225px;
+ }
+
+ iframe {
+ width: 250px;
+ }
+ }
+ }
+ }
+
+ .kbank-installment {
+ .payment-method-type {
+ display: flex;
+ }
+
+ .bank-installment-item {
+ background-color: #F2F2F2;
+ border: 1px solid transparent;
+ border-radius: 5px;
+ margin: 5px;
+ position: relative;
+ padding: 10px;
+ text-align: center;
+ min-width: 64px;
+ min-height: 32px;
+
+ label {
+ padding-left: 0 !important;
+
+ &:before {
+ display: none !important;
+ }
+ }
+
+ &.selected-item {
+ border-color: @payment-active__color;
+ }
+
+ img {
+ max-width: 80px;
+ }
+
+ label {
+ display: block;
+ margin-top: 5px;
+ text-align: center;
+ }
+ }
+ }
+
+ .checkout-index-index {
+ .modal-popup.kbank-embedded-form-box {
+ .modal-content {
+ padding-bottom: 20px;
+ }
+ }
+ }
+
+ .kbank-embedded-form-box {
+ .kbank.iframe {
+ height: 100%;
+ min-height: 600px;
+ }
+ }
+}
diff --git a/view/frontend/web/images/icons/icon-checked.svg b/view/frontend/web/images/icons/icon-checked.svg
new file mode 100644
index 0000000..b2cdadc
--- /dev/null
+++ b/view/frontend/web/images/icons/icon-checked.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/view/frontend/web/js/action/validate-order-status.js b/view/frontend/web/js/action/validate-order-status.js
new file mode 100644
index 0000000..f44167a
--- /dev/null
+++ b/view/frontend/web/js/action/validate-order-status.js
@@ -0,0 +1,35 @@
+define(
+ [
+ 'ko',
+ 'jquery',
+ 'Magento_Checkout/js/model/url-builder',
+ 'mage/storage'
+ ],
+ function (
+ ko,
+ $,
+ urlBuilder,
+ storage
+ ) {
+ 'use strict';
+
+ return function (orderId, deferred) {
+ var serviceUrl;
+
+ deferred = deferred || $.Deferred();
+
+ serviceUrl = urlBuilder.createUrl('/kbank/:orderId/validate-order-status', {
+ orderId: orderId
+ });
+
+ return storage.get(
+ serviceUrl,
+ false
+ ).done(function (response) {
+ deferred.resolve();
+ }).fail(function (response) {
+ deferred.reject();
+ });
+ };
+ }
+);
diff --git a/view/frontend/web/js/model/alert.js b/view/frontend/web/js/model/alert.js
new file mode 100644
index 0000000..59d7e74
--- /dev/null
+++ b/view/frontend/web/js/model/alert.js
@@ -0,0 +1,54 @@
+define([
+ 'jquery',
+ 'underscore',
+ 'mage/translate',
+ 'jquery/ui',
+ 'Magento_Ui/js/modal/confirm'
+], function ($, _, $t) {
+ 'use strict';
+
+ $.widget('mage.alert', $.mage.confirm, {
+ options: {
+ modalClass: 'confirm',
+ title: $.mage.__('Attention'),
+ actions: {
+
+ /**
+ * Callback always - called on all actions.
+ */
+ always: function () {}
+ },
+ buttons: [{
+ text: $.mage.__('OK'),
+ class: 'action-primary action-accept',
+
+ /**
+ * Click handler.
+ */
+ click: function () {
+ this.closeModal(true);
+ }
+ }]
+ },
+
+ /**
+ * Close modal window.
+ */
+ closeModal: function () {
+ this.options.actions.always();
+ this.element.bind('alertclosed', _.bind(this._remove, this));
+ var textOrder = $.mage.__('Place Order'),
+ placeOrderText = $('button.ghoster').find('span');
+
+ placeOrderText.attr('data-bind', 'i18n: \'' + textOrder + '\'');
+ placeOrderText.text($t(textOrder));
+
+
+ return this._super();
+ }
+ });
+
+ return function (config) {
+ return $('
').html(config.content).alert(config);
+ };
+});
diff --git a/view/frontend/web/js/model/installment.js b/view/frontend/web/js/model/installment.js
new file mode 100644
index 0000000..79e884a
--- /dev/null
+++ b/view/frontend/web/js/model/installment.js
@@ -0,0 +1,50 @@
+/**
+ * @api
+ */
+define([
+ 'ko',
+ 'underscore',
+ 'domReady!'
+], function (ko, _) {
+ 'use strict';
+
+ var proceedInstallmentData = function (data) {
+ let installmentData = {};
+ if (_.isObject(data)) {
+ installmentData['payment_term'] = data.hasOwnProperty('payment_term') ? data.payment_term : null;
+ installmentData['smartpay_id'] = data.hasOwnProperty('smartpay_id') ? data.smartpay_id : null;
+ installmentData['installment_title'] = data.hasOwnProperty('installment_title') ? data.installment_title : null;
+ }
+ return installmentData;
+ },
+ installmentData = proceedInstallmentData(window.checkoutConfig.quoteData),
+ installmentInformation = ko.observable(installmentData),
+ quoteData = window.checkoutConfig.quoteData;
+
+ return {
+ installmentInformation: installmentInformation,
+
+ /**
+ * @return {*}
+ */
+ getQuoteId: function () {
+ return quoteData['entity_id'];
+ },
+
+ /**
+ *
+ * @return {*}
+ */
+ getInstallmentInformation: function () {
+ return installmentInformation;
+ },
+
+ /**
+ * @param {Object} data
+ */
+ setInstallmentInformation: function (data) {
+ data = proceedInstallmentData(data);
+ installmentInformation(data);
+ }
+ };
+});
diff --git a/view/frontend/web/js/view/payment/kbank-embedded-payments.js b/view/frontend/web/js/view/payment/kbank-embedded-payments.js
new file mode 100644
index 0000000..aaa631d
--- /dev/null
+++ b/view/frontend/web/js/view/payment/kbank-embedded-payments.js
@@ -0,0 +1,23 @@
+define(
+ [
+ 'uiComponent',
+ 'Magento_Checkout/js/model/payment/renderer-list'
+ ],
+ function (
+ Component,
+ rendererList
+ ) {
+ 'use strict';
+ rendererList.push(
+ {
+ type: 'kbank_embedded_fullpayment',
+ component: 'GhoSter_KbankPayments/js/view/payment/method-renderer/kbank-embedded-fullpayment-method'
+ },
+ {
+ type: 'kbank_embedded_installment',
+ component: 'GhoSter_KbankPayments/js/view/payment/method-renderer/kbank-embedded-installment-method'
+ }
+ );
+ return Component.extend({});
+ }
+);
diff --git a/view/frontend/web/js/view/payment/kbank-embedded/installment-renderer/default.js b/view/frontend/web/js/view/payment/kbank-embedded/installment-renderer/default.js
new file mode 100644
index 0000000..940bb8c
--- /dev/null
+++ b/view/frontend/web/js/view/payment/kbank-embedded/installment-renderer/default.js
@@ -0,0 +1,63 @@
+define([
+ 'jquery',
+ 'ko',
+ 'uiComponent',
+ 'underscore',
+ 'GhoSter_KbankPayments/js/model/installment'
+], function ($, ko, Component, _, installment) {
+ 'use strict';
+
+ return Component.extend({
+ defaults: {
+ template: 'GhoSter_KbankPayments/payment/installment-renderer/default'
+ },
+
+ /**
+ * @returns {*}
+ */
+ initObservable: function () {
+ this._super();
+ this.isSelected = ko.computed(function () {
+ var isSelected = false,
+ installmentInformation = installment.installmentInformation();
+
+ if (installmentInformation) {
+ isSelected = installmentInformation.smartpay_id == this.installment().smartpay_id &&
+ installmentInformation.payment_term == this.installment().payment_term; //eslint-disable-line eqeqeq
+ }
+
+ return isSelected;
+ }, this);
+
+ return this;
+ },
+
+ /**
+ * @inheritdoc
+ */
+ isChecked: ko.computed(function () {
+ return installment.installmentInformation() ? installment.installmentInformation().smartpay_id : null;
+ }),
+
+ /**
+ * @inheritdoc
+ */
+ selectInstallment: function () {
+ installment.installmentInformation(this.installment());
+ },
+
+ /**
+ * @returns {string}
+ */
+ getInstallmentElmId: function () {
+ return this.paymentMethodCode() + '-smartpayid-' + this.installment().smartpay_id;
+ },
+
+ /**
+ * @returns {string}
+ */
+ getInstallmentElmName: function () {
+ return 'payment[' + this.paymentMethodCode() + '][smartpayid-' + this.installment().smartpay_id + ']';
+ }
+ });
+});
diff --git a/view/frontend/web/js/view/payment/method-renderer/kbank-embedded-fullpayment-method.js b/view/frontend/web/js/view/payment/method-renderer/kbank-embedded-fullpayment-method.js
new file mode 100644
index 0000000..a1dd403
--- /dev/null
+++ b/view/frontend/web/js/view/payment/method-renderer/kbank-embedded-fullpayment-method.js
@@ -0,0 +1,271 @@
+define(
+ [
+ 'Magento_Checkout/js/view/payment/default',
+ 'ko',
+ 'jquery',
+ 'underscore',
+ 'Magento_Checkout/js/action/place-order',
+ 'Magento_Checkout/js/model/full-screen-loader',
+ 'Magento_Checkout/js/model/quote',
+ 'GhoSter_KbankPayments/js/action/validate-order-status',
+ 'Magento_Ui/js/modal/modal',
+ 'mage/translate'
+ ],
+ function (
+ Component,
+ ko,
+ $,
+ _,
+ placeOrderAction,
+ fullScreenLoader,
+ quote,
+ validateOrderStatusAction
+ ) {
+ 'use strict';
+
+ return Component.extend({
+ defaults: {
+ template: 'GhoSter_KbankPayments/payment/kbank-embedded-fullpayment',
+ paymentReady: false,
+ inActionBodyClass: 'kbank-embedded-in-action',
+ grandTotalAmount: null,
+ embeddedFormPopup: null,
+ orderId: null,
+ isUseInstallment: false,
+ rendererTemplates: [],
+ embeddedFormElmPrefix: 'kbank-embedded-',
+ embeddedFormElmPostfix: '-hidden'
+ },
+ redirectAfterPlaceOrder: false,
+ isInitializedTrigger: ko.observable(false),
+ isPopupClicked: ko.observable(false),
+ isInAction: ko.observable(false),
+
+ initialize: function () {
+ this._super();
+ var context = this;
+
+ $(document)
+ .on('ghoster:closeKbankPopup', function () {
+ context.isInAction(false);
+ });
+
+ this.isUseInstallment = false;
+ },
+
+ /** @inheritdoc */
+ initConfig: function () {
+ this._super();
+ // the list of child components that are responsible for address rendering
+ return this;
+ },
+
+ /**
+ * Initialize form elements
+ */
+ initKbankFormElement: function () {
+ var script,
+ hiddenEmbeddedFormElmId = this.getHiddenButtonEmbeddedFormElmId();
+
+ script = document.querySelector('script[src^="' + this.getKbankSrc() + '"]');
+
+ if (!script) {
+ script = document.createElement('script');
+ script.src = this.getKbankSrc();
+ this.setDataForScript(script);
+ document.getElementById(hiddenEmbeddedFormElmId).appendChild(script);
+ } else {
+ this.setDataForScript(script);
+ }
+ },
+
+ /**
+ * Set data for script
+ *
+ * @param script
+ */
+ setDataForScript: function (script) {
+ script.setAttribute('data-apikey', window.checkoutConfig.payment[this.getCode()].publicKey);
+ script.setAttribute('data-amount', quote.totals()['base_grand_total']);
+ script.setAttribute('data-currency', window.checkoutConfig.quoteData.quote_currency_code);
+ script.setAttribute('data-payment-methods', 'card');
+ script.setAttribute('data-name', window.checkoutConfig.payment[this.getCode()]['shopName']);
+ script.setAttribute('data-mid', window.checkoutConfig.payment[this.getCode()]['merchantId']);
+ },
+
+ /**
+ * Get action url for payment method popup.
+ * @returns {String}
+ */
+ getTokenProcessActionUrl: function () {
+ return window.checkoutConfig.payment[this.item.method]['processTokenActionUrl'];
+ },
+
+ /**
+ * @returns {String}
+ */
+ getEmbeddedFormElmId: function () {
+ return this.embeddedFormElmPrefix + this.getCode();
+ },
+
+ /**
+ * @returns {String}
+ */
+ getHiddenButtonEmbeddedFormElmId: function () {
+ return this.embeddedFormElmPrefix + this.getCode() + this.embeddedFormElmPostfix;
+ },
+
+ /**
+ * @returns {String}
+ */
+ getKbankSrc: function () {
+ return window.checkoutConfig.payment[this.getCode()].jsSrc;
+ },
+
+ /**
+ * @returns {String}
+ */
+ getPublicKey: function () {
+ return window.checkoutConfig.payment[this.getCode()].publicKey;
+ },
+
+ /**
+ * @return {exports}
+ */
+ initObservable: function () {
+ this._super()
+ .observe(['paymentReady']);
+
+ this.grandTotalAmount = quote.totals()['base_grand_total'];
+
+ quote.totals.subscribe(function () {
+ if (this.grandTotalAmount !== quote.totals()['base_grand_total']) {
+ this.grandTotalAmount = quote.totals()['base_grand_total'];
+ }
+ }, this);
+
+ this.isInAction.subscribe(function (isInAction) {
+ this.processKbankPopup(isInAction);
+ }, this);
+
+ return this;
+ },
+
+ /**
+ * Open popup for the payment
+ */
+ processKbankPopup: function (isInAction) {
+ var self = this;
+
+ if (!window.hasOwnProperty('KPayment')) {
+ return this;
+ }
+
+ if (!this.isInitializedTrigger()) {
+ window.KPayment.onClose(function () {
+ $(document).trigger('ghoster:closeKbankPopup');
+ });
+
+ this.isInitializedTrigger(true);
+ }
+
+ if (isInAction) {
+ self.initKbankFormElement();
+ window.KPayment.create();
+ window.KPayment.setMid(window.checkoutConfig.payment[this.getCode()]['merchantId']);
+ window.KPayment.setPublickey(window.checkoutConfig.payment[this.getCode()]['publicKey']);
+ window.KPayment.setName(window.checkoutConfig.payment[this.getCode()]['shopName']);
+ window.KPayment.setAmount(quote.totals()['base_grand_total']);
+ window.KPayment.setCurrency(window.checkoutConfig.quoteData.quote_currency_code);
+ window.KPayment.setPaymentMethods('card');
+ window.KPayment.show();
+
+ this.isPopupClicked(true);
+ }
+
+ if (!isInAction
+ && this.isPopupClicked()
+ && !!this.orderId
+ ) {
+ return $.when(
+ validateOrderStatusAction(self.orderId)
+ ).done(function (response) {
+ if (!response) {
+ $.mage.redirect(
+ window.checkoutConfig.payment[self.getCode()]['failureUrl']
+ );
+ }
+ }).fail(function () {
+ $.mage.redirect(
+ window.checkoutConfig.payment[self.getCode()]['failureUrl']
+ );
+ });
+ }
+ },
+
+ /**
+ * @return {*}
+ */
+ isPaymentReady: function () {
+ return this.paymentReady();
+ },
+
+ /**
+ * Places order in pending payment status.
+ */
+ placePendingPaymentOrder: function () {
+ if (this.placeOrder()) {
+ fullScreenLoader.startLoader();
+ this.isInAction(true);
+ }
+ },
+
+ /**
+ * Show popup after order placed
+ * @returns {*|boolean}
+ */
+ getPlaceOrderDeferredObject: function () {
+ var self = this;
+
+ return $.when(
+ placeOrderAction(this.getData(), this.messageContainer)
+ ).done(
+ function (orderId) {
+ if (!isNaN(orderId)) {
+ self.orderId = orderId;
+ self.isInAction(true);
+ }
+ fullScreenLoader.stopLoader();
+ }
+ ).fail(
+ function () {
+ fullScreenLoader.stopLoader();
+ self.isInAction(false);
+ });
+ },
+
+ /**
+ * Hide loader when popup is fully loaded.
+ */
+ popupLoaded: function () {
+ fullScreenLoader.stopLoader();
+ },
+
+
+ /**
+ * @return {Boolean}
+ */
+ selectPaymentMethod: function () {
+ this.initKbankFormElement();
+ return this._super();
+ },
+
+ /**
+ * @return {Boolean}
+ */
+ validate: function () {
+ return true;
+ }
+ });
+ }
+);
diff --git a/view/frontend/web/js/view/payment/method-renderer/kbank-embedded-installment-method.js b/view/frontend/web/js/view/payment/method-renderer/kbank-embedded-installment-method.js
new file mode 100644
index 0000000..d3c6dd9
--- /dev/null
+++ b/view/frontend/web/js/view/payment/method-renderer/kbank-embedded-installment-method.js
@@ -0,0 +1,390 @@
+define(
+ [
+ 'Magento_Checkout/js/view/payment/default',
+ 'ko',
+ 'jquery',
+ 'underscore',
+ 'Magento_Checkout/js/action/place-order',
+ 'Magento_Checkout/js/model/full-screen-loader',
+ 'mage/template',
+ 'text!GhoSter_KbankPayments/template/payment/form/embedded.html',
+ 'Magento_Checkout/js/model/quote',
+ 'GhoSter_KbankPayments/js/model/alert',
+ 'GhoSter_KbankPayments/js/model/installment',
+ 'GhoSter_KbankPayments/js/action/validate-order-status',
+ 'Magento_Checkout/js/model/payment/additional-validators',
+ 'mageUtils',
+ 'uiLayout',
+ 'Magento_Ui/js/modal/modal',
+ 'mage/translate'
+ ],
+ function (
+ Component,
+ ko,
+ $,
+ _,
+ placeOrderAction,
+ fullScreenLoader,
+ mageTemplate,
+ embeddedFormTemplate,
+ quote,
+ alert,
+ installment,
+ validateOrderStatusAction,
+ additionalValidators,
+ utils,
+ layout
+ ) {
+ 'use strict';
+
+ var defaultRendererTemplate = {
+ parent: '${ $.$data.parentName }',
+ name: '${ $.$data.name }',
+ component: 'GhoSter_KbankPayments/js/view/payment/kbank-embedded/installment-renderer/default',
+ provider: 'checkoutProvider'
+ };
+
+ return Component.extend({
+ defaults: {
+ template: 'GhoSter_KbankPayments/payment/kbank-embedded-installment',
+ paymentReady: false,
+ inActionBodyClass: 'kbank-embedded-in-action',
+ grandTotalAmount: null,
+ embeddedFormPopup: null,
+ orderId: null,
+ isUseInstallment: false,
+ isPreDefinedInstallment: false,
+ selectedBankInstallment: installment.getInstallmentInformation()(),
+ rendererTemplates: [],
+ embeddedFormElmPrefix: 'kbank-embedded-',
+ embeddedFormElmPostfix: '-hidden'
+ },
+ redirectAfterPlaceOrder: false,
+ isInitializedTrigger: ko.observable(false),
+ isPopupClicked: ko.observable(false),
+ isInAction: ko.observable(false),
+
+ initialize: function () {
+ this._super();
+ var context = this;
+
+ $(document)
+ .on('ghoster:closeKbankPopup', function () {
+ context.isInAction(false);
+ });
+
+ this.isUseInstallment = window.checkoutConfig.payment[this.item.method]['isUseInstallment'] || false;
+ this.isPreDefinedInstallment = window.checkoutConfig.payment[this.item.method]['isPreDefinedInstallment'] || false;
+ this.initChildren();
+
+ },
+
+ /** @inheritdoc */
+ initConfig: function () {
+ this._super();
+ // the list of child components that are responsible for address rendering
+ this.rendererComponents = {};
+
+ return this;
+ },
+
+ /** @inheritdoc */
+ initChildren: function () {
+ if (this.isUseInstallment && this.isPreDefinedInstallment) {
+ _.each(window.checkoutConfig.payment[this.item.method]['allowedInstallment'], this.createRendererComponent, this);
+ }
+ return this;
+ },
+
+ /**
+ * Initialize form elements
+ */
+ initKbankFormElement: function () {
+ var script,
+ hiddenEmbeddedFormElmId = this.getHiddenButtonEmbeddedFormElmId();
+
+ script = document.querySelector('script[src^="' + this.getKbankSrc() + '"]');
+
+ if (!script) {
+ script = document.createElement('script');
+ script.src = this.getKbankSrc();
+ this.setDataForScript(script);
+ document.getElementById(hiddenEmbeddedFormElmId).appendChild(script);
+ } else {
+ this.setDataForScript(script);
+ }
+ },
+
+ /**
+ * Set data for script
+ *
+ * @param script
+ */
+ setDataForScript: function (script) {
+ script.setAttribute('data-apikey', window.checkoutConfig.payment[this.getCode()].publicKey);
+ script.setAttribute('data-amount', quote.totals()['base_grand_total']);
+ script.setAttribute('data-currency', window.checkoutConfig.quoteData.quote_currency_code);
+ script.setAttribute('data-payment-methods', 'card');
+ script.setAttribute('data-name', window.checkoutConfig.payment[this.getCode()]['shopName']);
+ script.setAttribute('data-mid', window.checkoutConfig.payment[this.getCode()]['merchantId']);
+ },
+
+ /**
+ * Get action url for payment method popup.
+ * @returns {String}
+ */
+ getTokenProcessActionUrl: function () {
+ return window.checkoutConfig.payment[this.item.method]['processTokenActionUrl'];
+ },
+
+ /**
+ * @returns {String}
+ */
+ getEmbeddedFormElmId: function () {
+ return this.embeddedFormElmPrefix + this.getCode();
+ },
+
+ /**
+ * @returns {String}
+ */
+ getHiddenButtonEmbeddedFormElmId: function () {
+ return this.embeddedFormElmPrefix + this.getCode() + this.embeddedFormElmPostfix;
+ },
+
+ /**
+ * @returns {String}
+ */
+ getKbankSrc: function () {
+ return window.checkoutConfig.payment[this.getCode()].jsSrc;
+ },
+
+ /**
+ * @returns {String}
+ */
+ getPublicKey: function () {
+ return window.checkoutConfig.payment[this.getCode()].publicKey;
+ },
+
+ /**
+ * Create new component that will render given installment in the address list
+ *
+ * @param {Object} installment
+ * @param {*} index
+ */
+ createRendererComponent: function (installment, index) {
+ var rendererTemplate, templateData, rendererComponent, paymentMethodCode;
+
+ paymentMethodCode = this.item.method;
+
+ if (index in this.rendererComponents) {
+ this.rendererComponents[index].installment(installment);
+ } else {
+ // rendererTemplates are provided via layout
+ rendererTemplate = defaultRendererTemplate;
+
+ templateData = {
+ parentName: this.name,
+ name: index
+ };
+ rendererComponent = utils.template(rendererTemplate, templateData);
+ utils.extend(rendererComponent, {
+ installment: ko.observable(installment),
+ paymentMethodCode: ko.observable(paymentMethodCode)
+
+ });
+ layout([rendererComponent]);
+ this.rendererComponents[index] = rendererComponent;
+ }
+ },
+
+ /**
+ * @return {exports}
+ */
+ initObservable: function () {
+ this._super()
+ .observe(['paymentReady', 'selectedBankInstallment']);
+
+ this.grandTotalAmount = quote.totals()['base_grand_total'];
+
+ quote.totals.subscribe(function () {
+ if (this.grandTotalAmount !== quote.totals()['base_grand_total']) {
+ this.grandTotalAmount = quote.totals()['base_grand_total'];
+ }
+ }, this);
+
+ installment.installmentInformation.subscribe(function (installmentData) {
+ this.selectedBankInstallment(installmentData);
+ }, this);
+
+ this.isInAction.subscribe(function (isInAction) {
+ this.processKbankPopup(isInAction);
+ }, this);
+
+ return this;
+ },
+
+ /**
+ * Open popup for the payment
+ */
+ processKbankPopup: function (isInAction) {
+ var self = this;
+
+ if (!window.hasOwnProperty('KPayment')) {
+ return this;
+ }
+
+ if (!this.isInitializedTrigger()) {
+ window.KPayment.onClose(function () {
+ $(document).trigger('ghoster:closeKbankPopup');
+ });
+
+ this.isInitializedTrigger(true);
+ }
+
+ if (isInAction) {
+ self.initKbankFormElement();
+ window.KPayment.create();
+ window.KPayment.setMid(window.checkoutConfig.payment[this.getCode()]['merchantId']);
+ window.KPayment.setPublickey(window.checkoutConfig.payment[this.getCode()]['publicKey']);
+ window.KPayment.setName(window.checkoutConfig.payment[this.getCode()]['shopName']);
+ window.KPayment.setAmount(quote.totals()['base_grand_total']);
+ window.KPayment.setCurrency(window.checkoutConfig.quoteData.quote_currency_code);
+ window.KPayment.setPaymentMethods('card');
+
+ var selectedBankInstallment = this.selectedBankInstallment();
+
+ if (this.isUseInstallment && this.isPreDefinedInstallment && !_.isEmpty(selectedBankInstallment)) {
+ window.KPayment.setSmartpayId(selectedBankInstallment['smartpay_id']);
+ window.KPayment.setTerm(selectedBankInstallment['payment_term']);
+ }
+
+ window.KPayment.show();
+
+ this.isPopupClicked(true);
+ }
+
+ if (!isInAction
+ && this.isPopupClicked()
+ && !!this.orderId
+ ) {
+ return $.when(
+ validateOrderStatusAction(self.orderId)
+ ).done(function (response) {
+ if (!response) {
+ $.mage.redirect(
+ window.checkoutConfig.payment[self.getCode()]['failureUrl']
+ );
+ }
+ }).fail(function () {
+ $.mage.redirect(
+ window.checkoutConfig.payment[self.getCode()]['failureUrl']
+ );
+ });
+ }
+ },
+
+ /**
+ * @returns {Object}
+ */
+ getData: function () {
+ var self = this,
+ selectedBankInstallment = this.selectedBankInstallment();
+
+ return {
+ method: this.getCode(),
+ 'additional_data': {
+ smartpay_id: self.isUseInstallment && self.isPreDefinedInstallment
+ ? selectedBankInstallment['smartpay_id']
+ : '',
+ term: self.isUseInstallment && self.isPreDefinedInstallment
+ ? selectedBankInstallment['payment_term']
+ : '',
+ terminal_id: self.isUseInstallment && self.isPreDefinedInstallment
+ ? window.checkoutConfig.payment[self.getCode()]['terminalId']
+ : ''
+ }
+ };
+ },
+
+ /**
+ * @return {*}
+ */
+ isPaymentReady: function () {
+ return this.paymentReady();
+ },
+
+ /**
+ * Places order in pending payment status.
+ */
+ placePendingPaymentOrder: function () {
+ if (!this.validate()) {
+ alert({content: $.mage.__('Please specify installment option')});
+ return false;
+ }
+
+ if (this.placeOrder()) {
+ fullScreenLoader.startLoader();
+ this.isInAction(true);
+ }
+ },
+
+ /**
+ * Show popup after order placed
+ * @returns {*|boolean}
+ */
+ getPlaceOrderDeferredObject: function () {
+ var self = this;
+
+ return $.when(
+ placeOrderAction(this.getData(), this.messageContainer)
+ ).done(
+ function (orderId) {
+ if (!isNaN(orderId)) {
+ self.orderId = orderId;
+ self.isInAction(true);
+ }
+ fullScreenLoader.stopLoader();
+ }
+ ).fail(
+ function () {
+ fullScreenLoader.stopLoader();
+ self.isInAction(false);
+ });
+ },
+
+ /**
+ * Hide loader when popup is fully loaded.
+ */
+ popupLoaded: function () {
+ fullScreenLoader.stopLoader();
+ },
+
+ /**
+ *
+ * @returns {*}
+ */
+ getAllowedInstallments: function () {
+ return this.rendererComponents;
+ },
+
+ /**
+ * @return {Boolean}
+ */
+ selectPaymentMethod: function () {
+ this.initKbankFormElement();
+ return this._super();
+ },
+
+ /**
+ * @return {Boolean}
+ */
+ validate: function () {
+ if (!this.isUseInstallment || !this.isPreDefinedInstallment) {
+ return true;
+ }
+
+ return !_.isEmpty(this.selectedBankInstallment()['smartpay_id']);
+ }
+ });
+ }
+);
diff --git a/view/frontend/web/template/payment/form/embedded.html b/view/frontend/web/template/payment/form/embedded.html
new file mode 100644
index 0000000..cca972a
--- /dev/null
+++ b/view/frontend/web/template/payment/form/embedded.html
@@ -0,0 +1,13 @@
+
+
\ No newline at end of file
diff --git a/view/frontend/web/template/payment/installment-renderer/default.html b/view/frontend/web/template/payment/installment-renderer/default.html
new file mode 100644
index 0000000..12f6fc1
--- /dev/null
+++ b/view/frontend/web/template/payment/installment-renderer/default.html
@@ -0,0 +1,15 @@
+
diff --git a/view/frontend/web/template/payment/kbank-embedded-fullpayment.html b/view/frontend/web/template/payment/kbank-embedded-fullpayment.html
new file mode 100644
index 0000000..5aa40be
--- /dev/null
+++ b/view/frontend/web/template/payment/kbank-embedded-fullpayment.html
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/view/frontend/web/template/payment/kbank-embedded-installment.html b/view/frontend/web/template/payment/kbank-embedded-installment.html
new file mode 100644
index 0000000..d93e8d0
--- /dev/null
+++ b/view/frontend/web/template/payment/kbank-embedded-installment.html
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+