diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7c4b617..84564bb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,6 @@
+### 1.8.0
+- Monri Components GooglePay integration
+
### 1.7.0
- Improved Monri WebPay tokenization by using API for transaction creation.
- Added "Instant Purchase" on the product page for customers with saved Monri WebPay tokens.
diff --git a/Controller/GooglePay/Cancel.php b/Controller/GooglePay/Cancel.php
new file mode 100644
index 0000000..4812560
--- /dev/null
+++ b/Controller/GooglePay/Cancel.php
@@ -0,0 +1,130 @@
+
+ */
+
+namespace Monri\Payments\Controller\GooglePay;
+
+use Exception;
+use Magento\Checkout\Model\Session;
+use Magento\Framework\App\Action\Context;
+use Magento\Framework\Controller\Result\Redirect;
+use Magento\Framework\Controller\ResultFactory;
+use Magento\Framework\Exception\InputException;
+use Magento\Framework\Exception\NoSuchEntityException;
+use Magento\Framework\Exception\NotFoundException;
+use Magento\Payment\Gateway\Command\CommandManagerInterface;
+use Magento\Payment\Model\InfoInterface;
+use Magento\Payment\Model\Method\Logger;
+use Magento\Sales\Model\OrderRepository;
+use Monri\Payments\Controller\AbstractGatewayResponse;
+use Monri\Payments\Model\GetOrderIdByIncrement;
+
+/**
+ * @SuppressWarnings(PHPMD.AllPurposeAction)
+ */
+class Cancel extends AbstractGatewayResponse
+{
+ /**
+ * @var Session
+ */
+ private $checkoutSession;
+ /**
+ * @var Logger
+ */
+ private $logger;
+
+ /**
+ * Cancel constructor.
+ *
+ * @param Context $context
+ * @param OrderRepository $orderRepository
+ * @param CommandManagerInterface $commandManager
+ * @param GetOrderIdByIncrement $getOrderIdByIncrement
+ * @param Session $checkoutSession
+ * @param Logger $logger
+ */
+ public function __construct(
+ Context $context,
+ OrderRepository $orderRepository,
+ CommandManagerInterface $commandManager,
+ GetOrderIdByIncrement $getOrderIdByIncrement,
+ Session $checkoutSession,
+ Logger $logger
+ ) {
+ parent::__construct($context, $orderRepository, $commandManager, $getOrderIdByIncrement);
+
+ $this->checkoutSession = $checkoutSession;
+ $this->logger = $logger;
+ }
+
+ /**
+ * Cancels an order.
+ *
+ * @return Redirect|void
+ */
+ public function execute()
+ {
+ $log = [
+ 'location' => __METHOD__,
+ 'errors' => [],
+ 'success' => true,
+ ];
+
+ /** @var Redirect $resultRedirect */
+ $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT);
+
+ try {
+ $order = $this->getOrderById(
+ $this->checkoutSession->getData('last_order_id')
+ );
+
+ /** @var InfoInterface $payment */
+ $payment = $order->getPayment();
+
+ $gatewayResponse = $this->buildCancelGatewayResponse($payment);
+ $log['payload'] = $gatewayResponse;
+
+ $result = $this->processGatewayResponse($gatewayResponse, $payment, ['disabled' => true]);
+
+ if (isset($result['message'])) {
+ $log['errors'][] = 'Error processing payment: ' . $result['message'];
+ $this->messageManager->addNoticeMessage(__('The payment has been denied: %1', $result['message']));
+ } else {
+ $log['errors'][] = 'Error processing payment.';
+ $this->messageManager->addNoticeMessage(__('The payment has been denied.'));
+ }
+ } catch (InputException | NoSuchEntityException | NotFoundException $e) {
+ $log['errors'][] = 'Caught exception: ' . $e->getMessage();
+ $log['success'] = false;
+ $this->messageManager->addNoticeMessage(__('Order not found.'));
+ } catch (Exception $e) {
+ $log['errors'][] = 'Caught unexpected exception: ' . $e->getMessage();
+ $log['success'] = false;
+ $this->messageManager->addNoticeMessage(__('Error processing payment, please try again later.'));
+ } finally {
+ $this->checkoutSession->restoreQuote();
+ $this->logger->debug($log);
+ }
+
+ return $resultRedirect->setPath('checkout/cart');
+ }
+
+ /**
+ * Build minimal cancel response for Google Pay gateway_response command.
+ *
+ * @param InfoInterface $payment
+ * @return array
+ */
+ private function buildCancelGatewayResponse(InfoInterface $payment): array
+ {
+ return [
+ 'status' => 'declined',
+ 'order_number' => $payment->getAdditionalInformation('monri_order_number'),
+ ];
+ }
+}
diff --git a/Controller/GooglePay/OrderStatus.php b/Controller/GooglePay/OrderStatus.php
new file mode 100644
index 0000000..f929f62
--- /dev/null
+++ b/Controller/GooglePay/OrderStatus.php
@@ -0,0 +1,139 @@
+
+ */
+
+namespace Monri\Payments\Controller\GooglePay;
+
+use Exception;
+use Magento\Checkout\Model\Session;
+use Magento\Framework\App\Action\Action;
+use Magento\Framework\App\Action\Context;
+use Magento\Framework\Controller\Result\Json;
+use Magento\Framework\Controller\ResultFactory;
+use Magento\Framework\Exception\InputException;
+use Magento\Framework\Exception\NoSuchEntityException;
+use Magento\Payment\Gateway\Command\CommandManagerInterface;
+use Magento\Payment\Model\Method\Logger;
+use Magento\Sales\Model\OrderRepository;
+use Monri\Payments\Model\GetOrderIdByIncrement;
+
+/**
+ * AJAX endpoint for polling Google Pay order payment status.
+ *
+ * @SuppressWarnings(PHPMD.AllPurposeAction)
+ */
+class OrderStatus extends Action
+{
+ /**
+ * @var Session
+ */
+ private $checkoutSession;
+
+ /**
+ * @var OrderRepository
+ */
+ private $orderRepository;
+
+ /**
+ * @var CommandManagerInterface
+ */
+ private $commandManager;
+
+ /**
+ * @var Logger
+ */
+ private $logger;
+
+ /**
+ * @var GetOrderIdByIncrement
+ */
+ private $getOrderIdByIncrement;
+
+ /**
+ * OrderStatus constructor.
+ *
+ * @param Context $context
+ * @param Session $checkoutSession
+ * @param OrderRepository $orderRepository
+ * @param CommandManagerInterface $commandManager
+ * @param GetOrderIdByIncrement $getOrderIdByIncrement
+ * @param Logger $logger
+ */
+ public function __construct(
+ Context $context,
+ Session $checkoutSession,
+ OrderRepository $orderRepository,
+ CommandManagerInterface $commandManager,
+ GetOrderIdByIncrement $getOrderIdByIncrement,
+ Logger $logger
+ ) {
+ parent::__construct($context);
+
+ $this->checkoutSession = $checkoutSession;
+ $this->orderRepository = $orderRepository;
+ $this->commandManager = $commandManager;
+ $this->getOrderIdByIncrement = $getOrderIdByIncrement;
+ $this->logger = $logger;
+ }
+
+ /**
+ * Poll order payment status and return JSON.
+ *
+ * Returns:
+ * {"status": "approved"} – payment was approved
+ * {"status": "pending"} – payment not yet finalised
+ * {"status": "error", "message": "..."} – unrecoverable error
+ *
+ * @return Json
+ */
+ public function execute()
+ {
+ $log = [
+ 'location' => __METHOD__,
+ 'errors' => [],
+ ];
+
+ /** @var Json $result */
+ $result = $this->resultFactory->create(ResultFactory::TYPE_JSON);
+
+ try {
+ $orderId = $this->checkoutSession->getData('last_order_id');
+
+ if (!$orderId) {
+ $result->setData(['status' => 'error', 'message' => 'Order not found.']);
+ return $result;
+ }
+
+ $order = $this->orderRepository->get($orderId);
+ $payment = $order->getPayment();
+
+ // Run check_status which fetches the latest status from Monri API
+ // and stores it in payment additional information.
+ $this->commandManager->executeByCode('check_status', $payment);
+
+ $gatewayStatus = $payment->getAdditionalInformation('gateway_status');
+ $log['gateway_status'] = $gatewayStatus;
+
+ if ($gatewayStatus === 'approved') {
+ $result->setData(['status' => 'approved']);
+ } else {
+ $result->setData(['status' => 'pending']);
+ }
+ } catch (InputException | NoSuchEntityException $e) {
+ $log['errors'][] = 'Order not found: ' . $e->getMessage();
+ $result->setData(['status' => 'error', 'message' => 'Order not found.']);
+ } catch (Exception $e) {
+ $log['errors'][] = 'Exception: ' . $e->getMessage();
+ $result->setData(['status' => 'error', 'message' => 'Error checking order status.']);
+ } finally {
+ $this->logger->debug($log);
+ }
+
+ return $result;
+ }
+}
diff --git a/Controller/GooglePay/Payment.php b/Controller/GooglePay/Payment.php
new file mode 100644
index 0000000..ee5813d
--- /dev/null
+++ b/Controller/GooglePay/Payment.php
@@ -0,0 +1,26 @@
+resultPageFactory->create();
+ }
+}
diff --git a/Controller/GooglePay/Success.php b/Controller/GooglePay/Success.php
new file mode 100644
index 0000000..eaf4d2d
--- /dev/null
+++ b/Controller/GooglePay/Success.php
@@ -0,0 +1,151 @@
+
+ */
+
+namespace Monri\Payments\Controller\GooglePay;
+
+use Exception;
+use Magento\Checkout\Model\Session;
+use Magento\Framework\App\Action\Context;
+use Magento\Framework\Controller\Result\Redirect;
+use Magento\Framework\Controller\ResultFactory;
+use Magento\Framework\Exception\AlreadyExistsException;
+use Magento\Framework\Exception\InputException;
+use Magento\Framework\Exception\NoSuchEntityException;
+use Magento\Framework\Exception\NotFoundException;
+use Magento\Payment\Gateway\Command\CommandException;
+use Magento\Payment\Gateway\Command\CommandManagerInterface;
+use Magento\Payment\Model\InfoInterface;
+use Magento\Payment\Model\Method\Logger;
+use Magento\Sales\Model\OrderRepository;
+use Monri\Payments\Controller\AbstractGatewayResponse;
+use Monri\Payments\Gateway\Exception\TransactionAlreadyProcessedException;
+use Monri\Payments\Model\GetOrderIdByIncrement;
+
+/**
+ * @SuppressWarnings(PHPMD.AllPurposeAction)
+ */
+class Success extends AbstractGatewayResponse
+{
+
+ /**
+ * @var Session
+ */
+ private $checkoutSession;
+ /**
+ * @var Logger
+ */
+ private $logger;
+
+ /**
+ * Success constructor.
+ *
+ * @param Context $context
+ * @param OrderRepository $orderRepository
+ * @param CommandManagerInterface $commandManager
+ * @param GetOrderIdByIncrement $getOrderIdByIncrement
+ * @param Session $checkoutSession
+ * @param Logger $logger
+ */
+ public function __construct(
+ Context $context,
+ OrderRepository $orderRepository,
+ CommandManagerInterface $commandManager,
+ GetOrderIdByIncrement $getOrderIdByIncrement,
+ Session $checkoutSession,
+ Logger $logger
+ ) {
+ parent::__construct($context, $orderRepository, $commandManager, $getOrderIdByIncrement);
+
+ $this->checkoutSession = $checkoutSession;
+ $this->logger = $logger;
+ }
+
+ /**
+ * Updates the status of an order.
+ *
+ * @return Redirect
+ */
+ public function execute()
+ {
+ $log = [
+ 'location' => __METHOD__,
+ 'errors' => [],
+ 'success' => true,
+ ];
+
+ /** @var Redirect $resultRedirect */
+ $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT);
+
+ try {
+ $order = $this->getOrderById(
+ $this->checkoutSession->getData('last_order_id')
+ );
+
+ /** @var InfoInterface $payment */
+ $payment = $order->getPayment();
+
+ //Google pay has no digest in success url.
+ // Instead, we get order status using API and save it in payment additonal info
+ $this->commandManager->executeByCode('check_status', $payment);
+
+ $gatewayResponse = $this->buildGatewayResponse($payment);
+ $log['payload'] = $gatewayResponse;
+
+ $result = $this->commandManager->executeByCode('gateway_response', $payment, [
+ 'response' => $gatewayResponse,
+ ])->get();
+ $responseCodeMessage = $result['response_code_message'] ?? null;
+
+ if ($responseCodeMessage !== null) {
+ $responseCodeMessage = (string)$responseCodeMessage;
+ $this->messageManager->addNoticeMessage(
+ __('The payment has been accepted: %1', $responseCodeMessage)
+ );
+ } else {
+ $this->messageManager->addNoticeMessage(__('The payment has been accepted.'));
+ }
+ } catch (TransactionAlreadyProcessedException | AlreadyExistsException $e) {
+ $log['errors'][] = 'Already processed: ' . $e->getMessage();
+ $log['success'] = true;
+ } catch (InputException | NoSuchEntityException $e) {
+ $log['errors'][] = 'Exception caught: ' . $e->getMessage();
+ $log['success'] = false;
+ $this->messageManager->addNoticeMessage(__('Order not found.'));
+
+ return $resultRedirect->setPath('checkout/cart');
+ } catch (Exception $e) {
+ $log['errors'][] = 'Unexpected exception caught: ' . $e->getMessage();
+ $log['success'] = false;
+ $this->messageManager->addNoticeMessage(__('Error processing payment, please try again later.'));
+
+ return $resultRedirect->setPath('checkout/cart');
+ } finally {
+ $this->logger->debug($log);
+ }
+
+ return $resultRedirect->setPath('checkout/onepage/success');
+ }
+
+ /**
+ * Build response payload consumed by GooglePay gateway_response command.
+ *
+ * @param InfoInterface $payment
+ * @return array
+ */
+ private function buildGatewayResponse(InfoInterface $payment): array
+ {
+ return [
+ 'status' => $payment->getAdditionalInformation('gateway_status'),
+ 'response_code' => $payment->getAdditionalInformation('gateway_response_code'),
+ 'transaction_type' => $payment->getAdditionalInformation('gateway_transaction_type'),
+ 'approval_code' => $payment->getAdditionalInformation('gateway_approval_code'),
+ 'order_number' => $payment->getAdditionalInformation('monri_order_number'),
+ ];
+ }
+}
diff --git a/Gateway/Command/GooglePay/CreateRequestCommand.php b/Gateway/Command/GooglePay/CreateRequestCommand.php
new file mode 100644
index 0000000..12febc8
--- /dev/null
+++ b/Gateway/Command/GooglePay/CreateRequestCommand.php
@@ -0,0 +1,61 @@
+
+ */
+
+namespace Monri\Payments\Gateway\Command\GooglePay;
+
+use Magento\Payment\Gateway\Command;
+use Magento\Payment\Gateway\Command\Result\ArrayResultFactory;
+use Magento\Payment\Gateway\CommandInterface;
+use Magento\Payment\Gateway\Request\BuilderInterface;
+
+class CreateRequestCommand implements CommandInterface
+{
+ /**
+ * @var BuilderInterface
+ */
+ private $requestBuilder;
+
+ /**
+ * @method Command\Result\ArrayResult create(array $params)
+ * @var ArrayResultFactory
+ */
+ private $resultFactory;
+
+ /**
+ * CreateRequestCommand constructor.
+ *
+ * @param BuilderInterface $builder
+ * @param ArrayResultFactory $resultFactory
+ */
+ public function __construct(
+ BuilderInterface $builder,
+ ArrayResultFactory $resultFactory
+ ) {
+ $this->requestBuilder = $builder;
+ $this->resultFactory = $resultFactory;
+ }
+
+ /**
+ * Builds the data object for redirect.
+ *
+ * @param array $commandSubject
+ * @return null|Command\ResultInterface
+ */
+ public function execute(array $commandSubject)
+ {
+ $requestData = $this->requestBuilder->build($commandSubject);
+
+ /** @var Command\Result\ArrayResult $result */
+ $result = $this->resultFactory->create([
+ 'array' => $requestData
+ ]);
+
+ return $result;
+ }
+}
diff --git a/Gateway/Command/GooglePay/InitializeCommand.php b/Gateway/Command/GooglePay/InitializeCommand.php
new file mode 100644
index 0000000..109df98
--- /dev/null
+++ b/Gateway/Command/GooglePay/InitializeCommand.php
@@ -0,0 +1,95 @@
+
+ */
+
+namespace Monri\Payments\Gateway\Command\GooglePay;
+
+use Magento\Framework\Exception\LocalizedException;
+use Magento\Payment\Gateway\CommandInterface;
+use Magento\Payment\Gateway\Helper\ContextHelper;
+use Magento\Payment\Gateway\Helper\SubjectReader;
+use Magento\Payment\Model\Method\Logger;
+use Magento\Sales\Api\Data\OrderInterface;
+use Magento\Sales\Model\Order;
+use Magento\Sales\Model\Order\Payment;
+use Monri\Payments\Gateway\Config\GooglePay as Config;
+use Monri\Payments\Helper\Formatter;
+use Monri\Payments\Gateway\Helper\TestModeHelper;
+
+class InitializeCommand implements CommandInterface
+{
+ /**
+ * @var Config
+ */
+ private $config;
+
+ /**
+ * @var Logger
+ */
+ private $logger;
+
+ /**
+ * @var Formatter
+ */
+ private $formatter;
+
+ /**
+ * InitializeCommand constructor.
+ *
+ * @param Config $config
+ * @param Logger $logger
+ * @param Formatter $formatter
+ */
+ public function __construct(
+ Config $config,
+ Logger $logger,
+ Formatter $formatter
+ ) {
+ $this->config = $config;
+ $this->logger = $logger;
+ $this->formatter = $formatter;
+ }
+
+ /**
+ * Initializes the payment.
+ *
+ * @param array $commandSubject
+ */
+ public function execute(array $commandSubject)
+ {
+ $stateObject = SubjectReader::readStateObject($commandSubject);
+ $paymentDataObject = SubjectReader::readPayment($commandSubject);
+
+ /** @var Payment $payment */
+ $payment = $paymentDataObject->getPayment();
+ ContextHelper::assertOrderPayment($payment);
+
+ $payment->setAmountOrdered($payment->getOrder()->getTotalDue());
+ $payment->setBaseAmountOrdered($payment->getOrder()->getBaseTotalDue());
+ $payment->getOrder()->setCanSendNewEmailFlag(false);
+
+ try {
+ $orderId = $payment->getOrder()->getIncrementId();
+ if ($this->config->getValue('sandbox')) {
+ $orderId = TestModeHelper::generateTestOrderId($orderId);
+ }
+ $payment->setAdditionalInformation(
+ 'monri_order_number',
+ $this->formatter->formatText(
+ $orderId,
+ 40
+ )
+ );
+ } catch (LocalizedException $e) {
+ $this->logger->debug(['Failed to set transaction type for payment: ' . $e->getMessage()]);
+ }
+
+ $stateObject->setData(OrderInterface::STATE, Order::STATE_PENDING_PAYMENT);
+ $stateObject->setData(OrderInterface::STATUS, Order::STATE_PENDING_PAYMENT);
+ }
+}
diff --git a/Gateway/Config/GooglePay.php b/Gateway/Config/GooglePay.php
new file mode 100644
index 0000000..56ac41b
--- /dev/null
+++ b/Gateway/Config/GooglePay.php
@@ -0,0 +1,35 @@
+getGatewayResourceURL('v2/payment/new', $storeId);
+ }
+
+ /**
+ * Get components javascript url
+ *
+ * @param null|int $storeId
+ * @return string
+ */
+ public function getComponentsJsURL($storeId = null)
+ {
+ return $this->getGatewayResourceURL('dist/components.js', $storeId);
+ }
+}
diff --git a/Gateway/Http/Client.php b/Gateway/Http/Client.php
index 71effb3..ffda018 100644
--- a/Gateway/Http/Client.php
+++ b/Gateway/Http/Client.php
@@ -117,7 +117,9 @@ public function placeRequest(TransferInterface $transferObject)
$log['errors'][] = 'Exception caught: ' . $e->getMessage();
$log['success'] = false;
$this->logger->debug($log);
- throw $e;
+ throw new \Magento\Framework\Exception\LocalizedException(
+ __('Payment initialization failed. Please try again.')
+ );
}
$this->logger->debug($log);
diff --git a/Gateway/Http/Components/PaymentCreateTransferFactory.php b/Gateway/Http/Components/PaymentCreateTransferFactory.php
index b603768..413de8f 100644
--- a/Gateway/Http/Components/PaymentCreateTransferFactory.php
+++ b/Gateway/Http/Components/PaymentCreateTransferFactory.php
@@ -14,7 +14,7 @@
use Magento\Payment\Gateway\Http\TransferFactoryInterface;
use Magento\Payment\Gateway\Http\TransferInterface;
use Monri\Payments\Model\Crypto\Components\Digest;
-use Monri\Payments\Gateway\Config\Components as Config;
+use Monri\Payments\Gateway\Config;
class PaymentCreateTransferFactory implements TransferFactoryInterface
{
diff --git a/Gateway/Http/StatusClient.php b/Gateway/Http/StatusClient.php
new file mode 100644
index 0000000..4823c9f
--- /dev/null
+++ b/Gateway/Http/StatusClient.php
@@ -0,0 +1,157 @@
+ __METHOD__,
+ 'request_data' => [],
+ 'response_data' => [],
+ 'errors' => [],
+ 'success' => true,
+ ];
+
+ $requestUri = $transferObject->getUri();
+ $requestMethod = strtoupper($transferObject->getMethod());
+ $requestPayload = $this->prepareRequestPayload($transferObject->getBody());
+
+ $log['request_data'] = $requestPayload;
+
+ /** @var \Magento\Framework\HTTP\ClientInterface $client */
+ $client = $this->httpClientFactory->create();
+
+ $client->setTimeout($this->timeout);
+
+ $client->setHeaders(array_merge(
+ $transferObject->getHeaders(),
+ [
+ 'Content-Type' => $this->requestType
+ ]
+ ));
+
+ if ($requestMethod === 'POST') {
+ $client->post($requestUri, $requestPayload);
+ } else {
+ $client->get($requestUri);
+ }
+
+ $responseStatus = $client->getStatus();
+
+ $response = $this->parseResponseBody($client->getBody());
+
+ $log['response_data'] = $response;
+
+ try {
+ $this->assertServerResponse($response, $responseStatus);
+ } catch (ClientException $e) {
+ $log['errors'][] = 'Exception caught: ' . $e->getMessage();
+ $log['success'] = false;
+ $this->logger->debug($log);
+ throw new \Magento\Framework\Exception\LocalizedException(
+ __('Order status fetch failed.')
+ );
+ }
+
+ $this->logger->debug($log);
+ return $response;
+ }
+
+ /**
+ * Prepares request payload
+ *
+ * @param array $payload
+ * @return bool|string
+ */
+ protected function prepareRequestPayload(array $payload)
+ {
+ try {
+ $serialized = $this->serializer->serialize($payload);
+ if ($serialized === false) {
+ return '';
+ }
+
+ return $serialized;
+ } catch (Exception $e) {
+ return '';
+ }
+ }
+
+ /**
+ * Parses response and returns array
+ *
+ * @param string $response
+ * @return array
+ */
+ protected function parseResponseBody($response)
+ {
+ try {
+ //the response is xml, so we need to convert it to json and then to array
+ $xml = simplexml_load_string($response);
+ $json = json_encode($xml);
+ $data = json_decode($json, true);
+ if ($data === null) {
+ return [];
+ }
+
+ return $data;
+ } catch (Exception $e) {
+ return [];
+ }
+ }
+
+ /**
+ * Validate server response
+ *
+ * @param array $responseBody
+ * @param int $statusCode
+ * @throws ClientException
+ */
+ protected function assertServerResponse(array $responseBody, $statusCode)
+ {
+ if ($statusCode >= 400 && $statusCode <= 499) {
+ if (isset($responseBody['error'])) {
+ $errors = $responseBody['error'];
+ if (!is_array($errors)) {
+ $errors = [$errors];
+ }
+
+ throw new ClientException(__('Client error (%1): %2', $statusCode, implode(', ', $errors)));
+ } else {
+ throw new ClientException(__('Client error (%1)', $statusCode));
+ }
+ } elseif ($statusCode >= 500 && $statusCode <= 599) {
+ throw new ClientException(__('Server error (%1)', $statusCode));
+ }
+ }
+}
diff --git a/Gateway/Request/GooglePay/OrderDetailsBuilder.php b/Gateway/Request/GooglePay/OrderDetailsBuilder.php
new file mode 100644
index 0000000..58e99c2
--- /dev/null
+++ b/Gateway/Request/GooglePay/OrderDetailsBuilder.php
@@ -0,0 +1,112 @@
+
+ */
+
+namespace Monri\Payments\Gateway\Request\GooglePay;
+
+use Magento\Framework\DataObjectFactory;
+use Magento\Framework\Event\ManagerInterface;
+use Magento\Payment\Gateway\Helper\SubjectReader;
+use Magento\Payment\Gateway\Request\BuilderInterface;
+use Monri\Payments\Helper\Formatter;
+use Magento\Framework\UrlInterface;
+
+class OrderDetailsBuilder implements BuilderInterface
+{
+ public const ORDER_INFO_FIELD = 'order_info';
+
+ public const ORDER_NUMBER_FIELD = 'order_number';
+
+ public const AMOUNT_FIELD = 'amount';
+
+ public const CURRENCY_FIELD = 'currency';
+
+ public const TRANSACTION_TYPE_FIELD = 'transaction_type';
+
+ public const IP_ADDRESS_FIELD = 'ip';
+
+ /**
+ * OrderDetailsBuilder constructor.
+ *
+ * @param Formatter $formatter
+ * @param ManagerInterface $eventManager
+ * @param DataObjectFactory $dataObjectFactory
+ */
+ public function __construct(
+ private Formatter $formatter,
+ private ManagerInterface $eventManager,
+ private DataObjectFactory $dataObjectFactory,
+ ) {
+ }
+
+ /**
+ * Builds the order details object
+ *
+ * @param array $buildSubject
+ * @return array
+ */
+ public function build(array $buildSubject)
+ {
+ $paymentDataObject = SubjectReader::readPayment($buildSubject);
+ $payment = $paymentDataObject->getPayment();
+
+ /** @var \Magento\Payment\Gateway\Data\Quote\QuoteAdapter $order */
+ $order = $paymentDataObject->getOrder();
+
+ $orderNumber = $payment->getAdditionalInformation('monri_order_number');
+
+ $orderIpAddress = $paymentDataObject->getPayment()->getOrder()->getRemoteIp();
+
+ $orderInfo = __('Order: %1', $order->getOrderIncrementId())->render();
+
+ //Google Pay only supports purchase transactions
+ $transactionType = 'purchase';
+
+ $transportObject = $this->dataObjectFactory->create([
+ 'data' => [
+ 'description' => $orderInfo
+ ]
+ ]);
+
+ // For custom order descriptions
+ $this->eventManager->dispatch('monri_payments_order_description_after', [
+ 'order' => $order,
+ 'payment' => $paymentDataObject->getPayment(),
+ 'transportObject' => $transportObject
+ ]);
+
+ $orderInfo = $this->formatter->formatText($transportObject->getData('description'), 100);
+
+ /*
+ Added in 2.4.8, because \PayPal\Braintree\Gateway\Data\Order\OrderAdapter puts themselves as preference for
+ \Magento\Payment\Gateway\Data\Order\OrderAdapter. It declares strict types, but getGrandTotalAmount returns
+ string instead of float, causing it to break execution.
+ */
+ try {
+ $orderAmount = $this->formatter->formatPrice(
+ $order->getGrandTotalAmount()
+ );
+ } catch (\TypeError $e) {
+ $orderObject = $payment->getOrder();
+ $orderAmount = $this->formatter->formatPrice(
+ $orderObject->getBaseGrandTotal()
+ );
+ }
+
+ $currencyCode = $order->getCurrencyCode();
+
+ return [
+ self::ORDER_INFO_FIELD => $orderInfo,
+ self::ORDER_NUMBER_FIELD => $orderNumber,
+ self::AMOUNT_FIELD => $orderAmount,
+ self::CURRENCY_FIELD => $currencyCode,
+ self::IP_ADDRESS_FIELD => $orderIpAddress,
+ self::TRANSACTION_TYPE_FIELD => $transactionType,
+ ];
+ }
+}
diff --git a/Gateway/Request/StatusRequestBuilder.php b/Gateway/Request/StatusRequestBuilder.php
new file mode 100644
index 0000000..250c42b
--- /dev/null
+++ b/Gateway/Request/StatusRequestBuilder.php
@@ -0,0 +1,75 @@
+getOrder();
+
+ $payment = $paymentDataObject->getPayment();
+ $orderNumber = $payment->getAdditionalInformation('monri_order_number');
+ $currencyCode = $order->getCurrencyCode();
+
+ /*
+ Added in 2.4.8, because \PayPal\Braintree\Gateway\Data\Order\OrderAdapter puts themselves as preference for
+ \Magento\Payment\Gateway\Data\Order\OrderAdapter. It declares strict types, but getGrandTotalAmount returns
+ string instead of float, causing it to break execution.
+ */
+ try {
+ $amount = $this->formatter->formatPrice(
+ $order->getGrandTotalAmount()
+ );
+ } catch (\TypeError $e) {
+ $orderObject = $payment->getOrder();
+ $amount = $this->formatter->formatPrice(
+ $orderObject->getBaseGrandTotal()
+ );
+ }
+ $storeId = $order->getStoreId();
+ $authToken = $this->config->getClientAuthenticityToken($storeId);
+
+ $digest = $this->digest->build(
+ $orderNumber,
+ $order->getStoreId()
+ );
+
+ return [
+ 'order' => [
+ 'order_number' => $orderNumber,
+ 'authenticity_token' => $authToken,
+ 'digest' => $digest,
+ ],
+ '__store' => $order->getStoreId(),
+ ];
+ }
+}
diff --git a/Gateway/Response/GooglePay/PaymentCreateHandler.php b/Gateway/Response/GooglePay/PaymentCreateHandler.php
new file mode 100644
index 0000000..f659003
--- /dev/null
+++ b/Gateway/Response/GooglePay/PaymentCreateHandler.php
@@ -0,0 +1,42 @@
+
+ */
+
+namespace Monri\Payments\Gateway\Response\GooglePay;
+
+use Magento\Payment\Gateway\Helper\SubjectReader;
+use Magento\Payment\Gateway\Response\HandlerInterface;
+use Magento\Quote\Model\Quote\Payment;
+
+class PaymentCreateHandler implements HandlerInterface
+{
+ public const INITIAL_DATA = 'initial_payment_data';
+
+ /**
+ * @inheritDoc
+ */
+ public function handle(array $handlingSubject, array $response)
+ {
+ $paymentDO = SubjectReader::readPayment($handlingSubject);
+
+ /** @var Payment $payment */
+ $payment = $paymentDO->getPayment();
+ $order = $payment->getOrder();
+ $billing = $order->getBillingAddress();
+
+ $response['ch_full_name'] = trim($billing->getFirstname() . ' ' . $billing->getLastname());
+ $response['ch_address'] = $billing->getStreetLine(1) ?? '';
+ $response['ch_city'] = $billing->getCity() ?? '';
+ $response['ch_zip'] = $billing->getPostcode() ?? '';
+ $response['ch_country'] = $billing->getCountryId() ?? '';
+ $response['ch_phone'] = $billing->getTelephone() ?? '';
+ $response['ch_email'] = $order->getCustomerEmail() ?? '';
+ $response['orderInfo'] = 'Magento Order';
+ $payment->setAdditionalInformation(self::INITIAL_DATA, $response);
+ }
+}
diff --git a/Gateway/Response/StatusHandler.php b/Gateway/Response/StatusHandler.php
new file mode 100644
index 0000000..178fdfd
--- /dev/null
+++ b/Gateway/Response/StatusHandler.php
@@ -0,0 +1,39 @@
+
+ */
+
+namespace Monri\Payments\Gateway\Response;
+
+use Magento\Framework\Exception\LocalizedException;
+use Magento\Payment\Gateway\Response\HandlerInterface;
+use Magento\Payment\Gateway\Helper\SubjectReader;
+use Magento\Sales\Model\Order\Payment;
+
+class StatusHandler implements HandlerInterface
+{
+ /**
+ * Updates payment additional information with gateway status response details.
+ *
+ * @param array $handlingSubject
+ * @param array $response
+ * @return void
+ * @throws LocalizedException
+ */
+ public function handle(array $handlingSubject, array $response)
+ {
+ $paymentDO = SubjectReader::readPayment($handlingSubject);
+ /** @var Payment $payment */
+ $payment = $paymentDO->getPayment();
+
+ // Save gateway status info for use in payment handler
+ $payment->setAdditionalInformation('gateway_status', $response['status'] ?? null);
+ $payment->setAdditionalInformation('gateway_response_code', $response['response-code'] ?? null);
+ $payment->setAdditionalInformation('gateway_transaction_type', $response['transaction-type'] ?? null);
+ $payment->setAdditionalInformation('gateway_approval_code', $response['approval-code'] ?? null);
+ }
+}
diff --git a/Model/Crypto/Components/Digest.php b/Model/Crypto/Components/Digest.php
index 75e7ed9..8c47edb 100644
--- a/Model/Crypto/Components/Digest.php
+++ b/Model/Crypto/Components/Digest.php
@@ -9,7 +9,7 @@
namespace Monri\Payments\Model\Crypto\Components;
-use Monri\Payments\Gateway\Config\Components as Config;
+use Monri\Payments\Gateway\Config;
class Digest
{
diff --git a/Model/Crypto/Components/OrderStatusDigest.php b/Model/Crypto/Components/OrderStatusDigest.php
new file mode 100644
index 0000000..2829c9b
--- /dev/null
+++ b/Model/Crypto/Components/OrderStatusDigest.php
@@ -0,0 +1,32 @@
+config->getClientKey($storeId);
+ $data = $key . $order_number;
+
+ return hash('SHA1', $data);
+ }
+}
diff --git a/ViewModel/GooglePayConfig.php b/ViewModel/GooglePayConfig.php
new file mode 100644
index 0000000..286fde6
--- /dev/null
+++ b/ViewModel/GooglePayConfig.php
@@ -0,0 +1,79 @@
+checkoutSession->getData('last_order_id');
+ if (!$orderId) {
+ throw new InputException(__('Missing fields.'));
+ }
+
+ $order = $this->orderRepository->get($orderId);
+ /** @var InfoInterface $payment */
+ $payment = $order->getPayment();
+
+ // direct execution, no pool needed
+ $this->googlePayCommand->execute(['payment' => $this->paymentDataObjectFactory->create($payment)]);
+
+ $payload = $payment->getAdditionalInformation(
+ PaymentCreateHandler::INITIAL_DATA
+ );
+
+ $storeId = $order->getStoreId();
+
+ return [
+ 'payload' => $payload,
+ 'gatewayUrl' => $this->config->getGatewayPaymentCreateURL($storeId),
+ 'componentsJsUrl' => $this->config->getComponentsJsURL($storeId),
+ 'authenticityToken' => $this->config->getClientAuthenticityToken($storeId),
+ 'isTest' => $this->config->getIsSandboxMode($storeId),
+ ];
+ } catch (Exception $e) {
+ $this->logger->debug(['GooglePay initialization failed: ' . $e->getMessage()]);
+
+ return [
+ 'error' => true,
+ 'message' => __('Google Pay is currently unavailable.')
+ ];
+ }
+ }
+}
diff --git a/composer.json b/composer.json
index 39014f4..3907ddd 100644
--- a/composer.json
+++ b/composer.json
@@ -9,7 +9,7 @@
}
],
"description": "The official Monri Payments Magento 2 module",
- "version": "1.7.0",
+ "version": "1.8.0",
"license": [
"OSL-3.0"
],
@@ -22,7 +22,8 @@
"magento/module-checkout": "^100.2",
"magento/module-store": "^100.2||^101.0",
"magento/module-quote": "^101.0",
- "php": "^8.1"
+ "php": "^8.1",
+ "ext-simplexml": "*"
},
"type": "magento2-module",
"autoload": {
diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml
index 5bec32f..aa9a355 100644
--- a/etc/adminhtml/system.xml
+++ b/etc/adminhtml/system.xml
@@ -19,7 +19,7 @@
showInDefault="1" showInWebsite="1" showInStore="0">
Magento\Config\Model\Config\Source\Yesno
-
+
@@ -105,7 +105,7 @@
showInDefault="1" showInWebsite="1" showInStore="0">
Magento\Config\Model\Config\Source\Yesno
-
+
@@ -173,12 +173,77 @@
validate-number
+
+
+
+
+ Magento\Config\Model\Config\Source\Yesno
+
+
+
+
+
+
+
+
+ Magento\Config\Model\Config\Backend\Encrypted
+
+
+
+
+ Magento\Config\Model\Config\Backend\Encrypted
+
+
+
+
+ \Monri\Payments\Block\Adminhtml\Config\Source\Components\Languages
+
+
+
+ Magento\Config\Model\Config\Source\Yesno
+
+
+
+ Magento\Config\Model\Config\Source\Yesno
+
+
+ Monri\Payments\Block\Adminhtml\Config\DownloadLog
+
+ 1
+
+
+
+
+ Magento\Payment\Model\Config\Source\Allspecificcountries
+
+
+
+ Magento\Directory\Model\Config\Source\Country
+ 1
+
+
+
+ validate-number
+
+
Magento\Config\Model\Config\Source\Yesno
-
+
diff --git a/etc/config.xml b/etc/config.xml
index d292527..1098b9b 100644
--- a/etc/config.xml
+++ b/etc/config.xml
@@ -72,6 +72,30 @@
+
+
+ 0
+ Monri Google Pay
+ authorize
+
+
+
+
+
+
+ MonriGooglePayFacade
+ 0
+ 1
+ 1
+ 0
+ 1
+ 1
+ 1
+ 0
+ 1
+
+
+
0
diff --git a/etc/di.xml b/etc/di.xml
index ba9ff5d..619c3b1 100644
--- a/etc/di.xml
+++ b/etc/di.xml
@@ -564,13 +564,19 @@
Monri\Payments\Gateway\Request\Components\OrderDetailsBuilder
- Monri\Payments\Gateway\Http\Components\PaymentCreateTransferFactory
+ MonriComponentsPaymentCreateTransferFactory
MonriComponentsJsonClient
Monri\Payments\Gateway\Response\Components\PaymentCreateHandler
MonriPaymentsVirtualLogger
+
+
+ Monri\Payments\Gateway\Config\Components
+
+
+
Monri\Payments\Gateway\Validator\Components\OrderValidator
@@ -678,6 +684,166 @@
+
+
+ Monri\Payments\Gateway\Config\GooglePay::CODE
+ Magento\Payment\Block\Form
+ Magento\Payment\Block\Info
+ MonriGooglePayValueHandlerPool
+ MonriGooglePayValidatorPool
+ MonriGooglePayCommandPool
+
+
+
+
+
+
+ - MonriGooglePayConfigHandler
+
+
+
+
+
+
+ Monri\Payments\Gateway\Config\GooglePay
+
+
+
+
+
+ Monri\Payments\Gateway\Config\GooglePay::CODE
+
+
+
+
+
+
+ - MonriGooglePayCountryValidator
+ - Monri\Payments\Gateway\Validator\CurrencyValidator
+
+
+
+
+
+
+ Monri\Payments\Gateway\Config\GooglePay
+
+
+
+
+
+ MonriGooglePayOrderUpdateHandler
+ MonriPaymentsErrorsMapper
+
+
+
+
+
+
+ - Monri\Payments\Gateway\Response\Transactions\PurchaseHandler
+
+
+
+
+
+
+
+ - MonriGooglePayCreateRequestCommand
+ - Monri\Payments\Gateway\Command\GooglePay\InitializeCommand
+ - MonriGooglePayGatewayResponseCommand
+ - MonriGooglePayCheckStatusCommand
+
+
+
+
+
+
+ MonriGooglePayCommandPool
+
+
+
+
+
+ MonriGooglePayCommandManager
+ MonriPaymentsLogger
+
+
+
+
+
+ MonriGooglePayCommandManager
+ MonriPaymentsLogger
+
+
+
+
+
+ MonriGooglePayCommandManager
+ MonriPaymentsLogger
+
+
+
+
+
+
+ Monri\Payments\Gateway\Config\GooglePay
+
+
+
+
+
+ Monri\Payments\Gateway\Config\GooglePay
+
+
+
+
+
+
+ Monri\Payments\Gateway\Config\GooglePay
+ MonriGooglePayDigest
+
+
+
+
+
+ Monri\Payments\Gateway\Config\GooglePay
+ MonriGooglePayOrderStatusDigest
+
+
+
+
+
+ Monri\Payments\Gateway\Request\GooglePay\OrderDetailsBuilder
+ MonriGooglePayPaymentCreateTransferFactory
+ MonriComponentsJsonClient
+ Monri\Payments\Gateway\Response\GooglePay\PaymentCreateHandler
+ MonriPaymentsVirtualLogger
+
+
+
+
+
+ MonriGooglePayCreateRequestCommand
+ MonriPaymentsLogger
+
+
+
+
+
+ Monri\Payments\Gateway\Config\GooglePay
+ MonriGooglePayOrderStatusDigest
+
+
+
+
+
+ MonriGooglePayStatusRequestBuilder
+ MonriGooglePayOrderStatusTransferFactory
+ Monri\Payments\Gateway\Http\StatusClient
+ Monri\Payments\Gateway\Response\StatusHandler
+ MonriPaymentsVirtualLogger
+
+
diff --git a/view/frontend/layout/checkout_index_index.xml b/view/frontend/layout/checkout_index_index.xml
index 0b335fd..60ec776 100644
--- a/view/frontend/layout/checkout_index_index.xml
+++ b/view/frontend/layout/checkout_index_index.xml
@@ -44,6 +44,14 @@
+ -
+
- Monri_Payments/js/view/monri_google_pay
+ -
+
-
+
- true
+
+
+
-
- Monri_Payments/js/view/monri_wspay
-
diff --git a/view/frontend/layout/monripayments_googlepay_payment.xml b/view/frontend/layout/monripayments_googlepay_payment.xml
new file mode 100644
index 0000000..c01729c
--- /dev/null
+++ b/view/frontend/layout/monripayments_googlepay_payment.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+ Monri\Payments\ViewModel\GooglePayConfig
+
+
+
+
+
+
+
diff --git a/view/frontend/templates/googlepay/payment.phtml b/view/frontend/templates/googlepay/payment.phtml
new file mode 100644
index 0000000..2d35d3b
--- /dev/null
+++ b/view/frontend/templates/googlepay/payment.phtml
@@ -0,0 +1,16 @@
+getData('view_model')->getConfig();
+?>
+
+
+
+
+
+
diff --git a/view/frontend/web/js/view/init/googlepay-init.js b/view/frontend/web/js/view/init/googlepay-init.js
new file mode 100644
index 0000000..0627d10
--- /dev/null
+++ b/view/frontend/web/js/view/init/googlepay-init.js
@@ -0,0 +1,82 @@
+define([
+ 'jquery',
+ 'uiComponent',
+ 'mage/translate',
+ 'mage/url',
+ 'Magento_Customer/js/customer-data'
+], function ($, Component, $t, urlBuilder, customerData) {
+ 'use strict';
+
+ return Component.extend({
+ initialize: function (config) {
+ this._super();
+
+ var self = this;
+
+ if (config.error) {
+ $('#monri-error').text(config.message || $t('Google Pay is unavailable.'));
+ return;
+ }
+
+ function monriAddScriptTag(url) {
+ var deferred = $.Deferred();
+ var script = document.createElement('script');
+ script.src = url;
+ script.onload = deferred.resolve;
+ script.onerror = deferred.reject;
+ document.head.appendChild(script);
+ return deferred.promise();
+ }
+
+ function monriCreatePayment() {
+ var transaction = {
+ ch_full_name: config.payload.ch_full_name,
+ ch_address: config.payload.ch_address,
+ ch_city: config.payload.ch_city,
+ ch_zip: config.payload.ch_zip,
+ ch_phone: config.payload.ch_phone,
+ ch_country: config.payload.ch_country,
+ ch_email: config.payload.ch_email,
+ ch_language: config.payload.locale
+ };
+
+ var monri = Monri(config.authenticityToken, {locale: config.payload.locale});
+ var components = monri.components({clientSecret: config.payload.client_secret});
+
+ var googlePay = components.create('google-pay', {
+ style: { invalid: { color: 'red' } },
+ trx_token: config.payload.client_secret,
+ environment: config.isTest ? 'test' : 'production',
+ transaction: transaction
+ });
+
+ googlePay.mount('google-pay-element');
+
+ window.addEventListener('message', (event) => {
+ if (event.data?.sentinel === '__ACTIVITIES__' && event.data?.cmd === 'check') {
+ customerData.invalidate(['cart', 'checkout-data']);
+ window.location.href = urlBuilder.build('monripayments/googlepay/cancel');
+ }
+
+ if (event.data?.type === 'PAYMENT_RESULT') {
+ const {transaction} = event.data;
+ if (transaction.status === 'approved') {
+ window.location.href = urlBuilder.build('monripayments/googlepay/success');
+ } else {
+ customerData.invalidate(['cart', 'checkout-data']);
+ window.location.href = urlBuilder.build('monripayments/googlepay/cancel');
+ }
+ }
+ });
+ }
+
+ function monriFailed() {
+ $('#monri-error').text($t('Monri failed to initialize.'));
+ }
+
+ $.when(monriAddScriptTag(config.componentsJsUrl))
+ .then(monriCreatePayment)
+ .fail(monriFailed);
+ }
+ });
+});
diff --git a/view/frontend/web/js/view/method-renderer/monri_google_pay.js b/view/frontend/web/js/view/method-renderer/monri_google_pay.js
new file mode 100644
index 0000000..9182fbb
--- /dev/null
+++ b/view/frontend/web/js/view/method-renderer/monri_google_pay.js
@@ -0,0 +1,54 @@
+/**
+ * This file is part of the Monri Payments module
+ *
+ * (c) Monri Payments d.o.o.
+ *
+ * @author Favicode
+ */
+
+define(
+ [
+ 'Magento_Checkout/js/view/payment/default',
+ 'Magento_Vault/js/view/payment/vault-enabler',
+ 'jquery',
+ 'underscore',
+ 'mage/template',
+ 'Magento_Checkout/js/model/error-processor',
+ 'Magento_Checkout/js/model/full-screen-loader',
+ 'Magento_Customer/js/customer-data',
+ 'mage/url'
+ ],
+ function (Component, VaultEnabler, $, _, mageTemplate, errorProcessor, fullScreenLoader, customerData, urlBuilder) {
+ 'use strict';
+
+ return Component.extend({
+ defaults: {
+ template: 'Monri_Payments/google_pay'
+ },
+ redirectAfterPlaceOrder: false,
+
+ getCode: function () {
+ return 'monri_google_pay';
+ },
+
+ initialize: function () {
+ this._super();
+ return this;
+ },
+
+ getData: function () {
+ var data = {
+ 'method': this.getCode(),
+ 'additional_data': {}
+ };
+ return data;
+ },
+
+ afterPlaceOrder: function() {
+ fullScreenLoader.startLoader();
+ customerData.invalidate(['cart', 'checkout-data']);
+ window.location.href = urlBuilder.build('monripayments/googlepay/payment');
+ }
+ });
+ }
+);
diff --git a/view/frontend/web/js/view/monri_components.js b/view/frontend/web/js/view/monri_components.js
index 59ff1d9..07c4391 100644
--- a/view/frontend/web/js/view/monri_components.js
+++ b/view/frontend/web/js/view/monri_components.js
@@ -4,7 +4,7 @@
* (c) Monri Payments d.o.o.
*
* @author Favicode
- * @version 1.7.0
+ * @version 1.8.0
*/
define(
diff --git a/view/frontend/web/js/view/monri_google_pay.js b/view/frontend/web/js/view/monri_google_pay.js
new file mode 100644
index 0000000..a95d397
--- /dev/null
+++ b/view/frontend/web/js/view/monri_google_pay.js
@@ -0,0 +1,28 @@
+/**
+ * This file is part of the Monri Payments module
+ *
+ * (c) Monri Payments d.o.o.
+ *
+ * @author Favicode
+ * @version 1.8.0
+ */
+
+define(
+ [
+ 'uiComponent',
+ 'Magento_Checkout/js/model/payment/renderer-list'
+ ],
+ function (
+ Component,
+ rendererList
+ ) {
+ 'use strict';
+ rendererList.push(
+ {
+ type: 'monri_google_pay',
+ component: 'Monri_Payments/js/view/method-renderer/monri_google_pay'
+ }
+ );
+ return Component.extend({});
+ }
+);
diff --git a/view/frontend/web/js/view/monri_payments.js b/view/frontend/web/js/view/monri_payments.js
index d9cbb0c..0c7a0f7 100644
--- a/view/frontend/web/js/view/monri_payments.js
+++ b/view/frontend/web/js/view/monri_payments.js
@@ -4,7 +4,7 @@
* (c) Monri Payments d.o.o.
*
* @author Favicode
- * @version 1.7.0
+ * @version 1.8.0
*/
define(
diff --git a/view/frontend/web/js/view/monri_wspay.js b/view/frontend/web/js/view/monri_wspay.js
index 40db9fb..d1cc7e2 100644
--- a/view/frontend/web/js/view/monri_wspay.js
+++ b/view/frontend/web/js/view/monri_wspay.js
@@ -4,7 +4,7 @@
* (c) Monri Payments d.o.o.
*
* @author Favicode
- * @version 1.7.0
+ * @version 1.8.0
*/
define([
diff --git a/view/frontend/web/template/google_pay.html b/view/frontend/web/template/google_pay.html
new file mode 100644
index 0000000..89e7f08
--- /dev/null
+++ b/view/frontend/web/template/google_pay.html
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+