Commit 9b05b7c4 by lmf

增加Payoneer 支付渠道

parent 5e3ec3c3
# These are some examples of commonly ignored file patterns.
# You should customize this list as applicable to your project.
# Learn more about .gitignore:
# https://www.atlassian.com/git/tutorials/saving-changes/gitignore
# Node artifact files
node_modules/
dist/
# Compiled Java class files
*.class
# Compiled Python bytecode
*.py[cod]
# Log files
*.log
# Package files
*.jar
# Maven
target/
dist/
# JetBrains IDE
.idea/
# Unit test reports
TEST*.xml
# Generated by MacOS
.DS_Store
# Generated by Windows
Thumbs.db
# Applications
*.app
*.exe
*.war
# Large media files
*.mp4
*.tiff
*.avi
*.flv
*.mov
*.wmv
<?php
namespace Payoneer\OpenPaymentGateway\Api\Data;
/**
* @api
* @since 100.0.2
*/
interface NotificationInterface
{
const TABLE = 'payoneer_notification';
const ID = 'id';
const TRANSACTION_ID = 'transactionId';
const LONG_ID = 'longId';
const ORDER_ID = 'order_id';
const CONTENT = 'content';
const CRON_STATUS = 'cron_status';
const CREATED_AT = 'created_at';
const UPDATED_AT = 'updated_at';
/**
* Get notification id
*
* @return int
*/
public function getId();
/**
* Set notification id
*
* @param int $id
* @return $this
*/
public function setId($id);
/**
* Get transaction id
*
* @return string
*/
public function getTransactionId();
/**
* Set transaction id
*
* @param string $txnId
* @return $this
*/
public function setTransactionId($txnId);
/**
* Get long id
*
* @return string
*/
public function getLongId();
/**
* Set long id
*
* @param string $longId
* @return $this
*/
public function setLongId($longId);
/**
* Get order id
*
* @return string
*/
public function getOrderId();
/**
* Set order id
*
* @param string $orderId
* @return $this
*/
public function setOrderId($orderId);
/**
* Get response content
*
* @return string
*/
public function getContent();
/**
* Set response content
*
* @param string $response
* @return $this
*/
public function setContent($response);
/**
* Get response processed status
*
* @return bool
*/
public function getCronStatus();
/**
* Set response processed status
*
* @param bool $status
* @return $this
*/
public function setCronStatus($status);
}
<?php
declare(strict_types=1);
namespace Payoneer\OpenPaymentGateway\Api\Data;
interface PayoneerTransactionInterface
{
/**
* Constants for keys of data array. Identical to the name of the getter in snake case.
*/
const TRANSACTION_ID = 'transaction_id';
const CUSTOMER_ID = 'customer_id';
const REGISTRATION_ID = 'registration_id';
/**
* Get TransactionId.
*
* @return int
*/
public function getTransactionId();
/**
* Set TransactionId.
* @param int $transactionId
* @return void
*/
public function setTransactionId($transactionId);
/**
* @return int
*/
public function getCustomerId();
/**
* Set CustomerId.
* @param int $customerId.
* @return void
*/
public function setCustomerId($customerId);
/**
* Get RegistrationId.
*
* @return string
*/
public function getRegistrationId();
/**
* Set RegistrationId.
* @param string $registrationId.
* @return void
*/
public function setRegistrationId($registrationId);
}
<?php
namespace Payoneer\OpenPaymentGateway\Api;
use Payoneer\OpenPaymentGateway\Api\Data\NotificationInterface;
/**
* @api
* @since 100.0.2
*/
interface PayoneerNotificationRepositoryInterface
{
/**
* Save the notification data
*
* @param NotificationInterface $notification
* @return NotificationInterface
* @throws \Magento\Framework\Exception\InputException
* @throws \Magento\Framework\Exception\LocalizedException
* @throws \Magento\Framework\Exception\CouldNotSaveException
*/
public function save(NotificationInterface $notification);
}
<?php
declare(strict_types=1);
namespace Payoneer\OpenPaymentGateway\Api;
use Payoneer\OpenPaymentGateway\Api\Data\PayoneerTransactionInterface;
/**
* Grid CRUD interface.
* @api
*/
interface PayoneerTransactionRepositoryInterface
{
/**
* @param PayoneerTransactionInterface $payoneerTransaction
* @return mixed
*/
public function save(PayoneerTransactionInterface $payoneerTransaction);
/**
* @param int $customerId
* @return mixed
*/
public function getByCustomerId($customerId);
/**
* @return mixed
*/
public function create();
}
<?php
namespace Payoneer\OpenPaymentGateway\Block\Adminhtml\Form\Field;
use Magento\Config\Block\System\Config\Form\Field;
use Magento\Framework\Data\Form\Element\AbstractElement;
/**
* Class ColorPicker
* Creates ColorPicker element
*/
class ColorPicker extends Field
{
/**
* @param AbstractElement $element
* @return string
*/
protected function _getElementHtml(AbstractElement $element)
{
$html = $element->getElementHtml();
$value = $element->getData('value');
$html .= '<script type="text/javascript">
require(["jquery","jquery/colorpicker/js/colorpicker"], function ($) {
$(document).ready(function () {
var $el = $("#' . $element->getHtmlId() . '");
$el.css("backgroundColor", "' . $value . '");
// Attach the color picker
$el.ColorPicker({
color: "' . $value . '",
onChange: function (hsb, hex, rgb) {
$el.css("backgroundColor", "#" + hex).val("#" + hex);
}
});
});
});
</script>';
return $html;
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Block\Adminhtml\Form\Field;
use Magento\Config\Block\System\Config\Form\Field;
use Magento\Framework\Data\Form\Element\AbstractElement;
/**
* Class Disable
* Disables the configuration field
*/
class Disable extends Field
{
/**
* Disables the configuration field
*
* @param AbstractElement $element
* @return string
*/
protected function _getElementHtml(AbstractElement $element)
{
$element->setDisabled('disabled');
return $element->getElementHtml();
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Block\Adminhtml\Form\Field;
use Magento\Config\Block\System\Config\Form\Field;
use Magento\Framework\Data\Form\Element\AbstractElement;
use Magento\Framework\Exception\LocalizedException;
use Magento\Store\Model\Store;
/**
* Class ValidateCredentials
* Creates button for validating the credentials
*/
class ValidateCredentials extends Field
{
/**
* @inheritDoc
*/
protected function _renderScopeLabel(AbstractElement $element): string
{
// Return empty label
return '';
}
/**
* @inheritDoc
* @throws LocalizedException
*/
protected function _getElementHtml(AbstractElement $element): string
{
// Replace field markup with validation button
$title = __('Validate credentials');
$envId = 'select-groups-payoneer-fields-environment-value';
$storeId = 0;
if ($this->getRequest()->getParam('website')) {
$website = $this->_storeManager->getWebsite($this->getRequest()->getParam('website'));
if ($website->getId()) {
/** @var Store $store */
$store = $website->getDefaultStore();/** @phpstan-ignore-line */
$storeId = $store->getStoreId();
}
}
$endpoint = $this->getUrl('payoneer/configuration/validatecredentials', ['storeId' => $storeId]);
// @codingStandardsIgnoreStart
$html = <<<TEXT
<button
type="button"
title="{$title}"
class="button"
onclick="payoneerValidator.call(this, '{$endpoint}', '{$envId}')">
<span>{$title}</span>
</button>
TEXT;
// @codingStandardsIgnoreEnd
return $html;
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Block;
use Magento\Framework\Phrase;
use Magento\Payment\Block\ConfigurableInfo;
/**
* Class Info
*
* Info block for Payoneer payment gateway
*/
class Info extends ConfigurableInfo
{
/**
* Returns label
*
* @param string $field
* @return Phrase
*/
protected function getLabel($field)
{
return __($field);
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Controller\Adminhtml\Configuration;
use Exception;
use Magento\Backend\App\Action;
use Magento\Framework\Controller\Result\JsonFactory;
use Magento\Framework\Controller\ResultFactory;
use Magento\Framework\Controller\ResultInterface;
use Payoneer\OpenPaymentGateway\Gateway\Config\Config;
use Payoneer\OpenPaymentGateway\Model\Api\Request;
/**
* Class ValidateCredentials
* Validate the API Credentials
*/
class ValidateCredentials extends Action
{
const ADMIN_RESOURCE = 'Magento_Config::config';
/**
* @var Config
*/
private $config;
/**
* @var Request
*/
private $request;
/**
* Json Factory
*
* @var JsonFactory
*/
private $jsonResultFactory;
/**
* ValidateCredentials constructor.
* @param Action\Context $context
* @param Config $config
* @param Request $request
* @param JsonFactory $jsonResultFactory
*/
public function __construct(
Action\Context $context,
Config $config,
Request $request,
JsonFactory $jsonResultFactory
) {
parent::__construct($context);
$this->config = $config;
$this->request = $request;
$this->jsonResultFactory = $jsonResultFactory;
}
/**
* Validates the field values
*
* @return ResultInterface
*/
public function execute(): ResultInterface
{
$gatewayResponse = [];
$endPoint = Config::LIST_END_POINT;
$data = $this->config->getMockData();
$storeCode = $this->getRequest()->getParam('storeCode');
$credentials['merchantCode'] = $this->getRequest()->getParam('merchantCode');
$credentials['apiKey'] = $this->getRequest()->getParam('apiKey');
$hostName = $this->getRequest()->getParam('hostName');
if ($hostName) {
$credentials['hostName'] = $hostName;
} else {
$credentials['hostName'] = $this->config->getCredentials($this->config::HOST_NAME);
}
if ($storeCode) {
$data['division'] = $storeCode;
}
$response = $this->resultFactory->create(ResultFactory::TYPE_JSON);
try {
$gatewayResponse = $this->request->send(
Config::METHOD_POST,
$endPoint,
$credentials,
$data
);
$response->setHttpResponseCode($gatewayResponse['status']);
} catch (Exception $e) {
$response->setHttpResponseCode(400);
}
$result = $this->jsonResultFactory->create();
$responseData = isset($gatewayResponse['response'])
? $gatewayResponse['response'] : [];
return $result->setData(['data' => $responseData]);
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Controller\Adminhtml\Gateway;
use Magento\Backend\App\Action;
use Magento\Framework\App\ResponseInterface;
use Magento\Framework\Controller\Result\Redirect;
use Magento\Framework\Controller\ResultInterface;
use Magento\Sales\Model\Order;
use Payoneer\OpenPaymentGateway\Gateway\Config\Config;
use Payoneer\OpenPaymentGateway\Model\Adminhtml\Helper;
use Payoneer\OpenPaymentGateway\Model\Adminhtml\TransactionService as AdminTransactionService;
/**
* Class Capture
*
* Process Payoneer capture request
*/
class Capture extends Action
{
/**
* @var AdminTransactionService
*/
protected $listCapture;
/**
* @var Helper
*/
protected $helper;
/**
* Capture constructor.
*
* @param Action\Context $context
* @param AdminTransactionService $listCapture
* @param Helper $helper
*/
public function __construct(
Action\Context $context,
AdminTransactionService $listCapture,
Helper $helper
) {
parent::__construct($context);
$this->listCapture = $listCapture;
$this->helper = $helper;
}
/**
* Process Payoneer capture
*
* @return ResponseInterface|Redirect|ResultInterface
*/
public function execute()
{
$resultRedirect = $this->resultRedirectFactory->create();
$orderId = (int)$this->getRequest()->getParam('order_id');
if ($orderId) {
try {
/** @var Order $order */
$order = $this->helper->getOrder($orderId);
/** @var string[] $result */
$result = $this->listCapture->process($order, Config::LIST_CAPTURE);
if ($result) {
$this->helper->processCaptureResponse($result, $order);
}
} catch (\Exception $e) {
$this->helper
->showErrorMessage(__('Transaction failed') . ' ' . $e->getMessage());
}
}
return $resultRedirect->setPath('sales/order/view', ['order_id' => $orderId]);
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Controller\Adminhtml\Gateway;
use Magento\Backend\App\Action;
use Magento\Framework\App\ResponseInterface;
use Magento\Framework\Controller\Result\Redirect;
use Magento\Framework\Controller\ResultInterface;
use Magento\Sales\Model\Order;
use Payoneer\OpenPaymentGateway\Model\Adminhtml\Helper;
use Payoneer\OpenPaymentGateway\Model\Adminhtml\TransactionService as AdminTransactionService;
use Payoneer\OpenPaymentGateway\Gateway\Config\Config;
use Payoneer\OpenPaymentGateway\Model\TransactionOrderUpdater;
/**
* Class Fetch
* Process Payoneer fetch request
*/
class Fetch extends Action
{
/**
* @var AdminTransactionService
*/
protected $transactionService;
/**
* @var Helper
*/
protected $helper;
/**
* @var TransactionOrderUpdater
*/
protected $transactionOrderUpdater;
/**
* Fetch constructor.
*
* @param Action\Context $context
* @param AdminTransactionService $transactionService
* @param TransactionOrderUpdater $transactionOrderUpdater
* @param Helper $helper
* @return void
*/
public function __construct(
Action\Context $context,
AdminTransactionService $transactionService,
TransactionOrderUpdater $transactionOrderUpdater,
Helper $helper
) {
parent::__construct($context);
$this->transactionService = $transactionService;
$this->helper=$helper;
$this->transactionOrderUpdater=$transactionOrderUpdater;
}
/**
* Process Payoneer fetch
*
* @return ResponseInterface|Redirect|ResultInterface
*/
public function execute()
{
$resultRedirect = $this->resultRedirectFactory->create();
$orderId = (int)$this->getRequest()->getParam('order_id');
if ($orderId) {
try {
/** @var Order $order */
$order = $this->helper->getOrder($orderId);
/** @var mixed $result */
$result = $this->transactionService->process($order, Config::LIST_FETCH);
if ($result && $result['status'] == 200) {
$this->transactionOrderUpdater->processFetchUpdateResponse(
$order,
$result
);
$this->helper->showSuccessMessage(
__('Data successfully synced')
);
} else {
$this->helper
->showErrorMessage(
__('Error response with the %1 code received from Payoneer. Check the payoneer.log file for details.', $result['status'])
);
}
} catch (\Exception $e) {
$this->helper
->showErrorMessage(__('Transaction failed') . ' ' . $e->getMessage());
}
}
return $resultRedirect->setPath('sales/order/view', ['order_id' => $orderId]);
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Controller\Embedded;
use Magento\Framework\App\Action\HttpGetActionInterface;
use Magento\Framework\App\ResponseInterface;
use Magento\Framework\Controller\Result\Raw;
use Magento\Framework\Controller\Result\RawFactory;
use Magento\Framework\Controller\ResultInterface;
use Payoneer\OpenPaymentGateway\Gateway\Config\Config;
/**
* Class Style
* Process Style data for embedded integration
*/
class Style implements HttpGetActionInterface
{
/**
* @var RawFactory
*/
protected $resultRawFactory;
/**
* @var Config
*/
protected $config;
/**
* Style constructor.
* @param RawFactory $resultRawFactory
* @param Config $config
*/
public function __construct(
RawFactory $resultRawFactory,
Config $config
) {
$this->resultRawFactory = $resultRawFactory;
$this->config = $config;
}
/**
* Creates raw css file based on configuration
* @return ResponseInterface|Raw|ResultInterface
*/
public function execute()
{
$styleConfig = $this->config->getStyleConfig();
$containerCSS = $this->getContainerStyle($styleConfig);
$containerPlaceholderCSS = $this->getContainerPlaceholderStyle();
$checkoutCssConfig = $this->config->getValue('widget_appearance/checkout_css');
$widgetCSS = $containerCSS . $containerPlaceholderCSS;
if ($checkoutCssConfig) {
$widgetCSS = $widgetCSS . $checkoutCssConfig;
}
$resultRaw = $this->resultRawFactory->create();
$resultRaw->setHeader('Content-type', 'text/css');
$resultRaw->setContents($widgetCSS);
return $resultRaw;
}
/**
* Get formatted css for the container
* @param array <mixed> $styleConfig
* @return string
*/
public function getContainerStyle($styleConfig)
{
$content = '#networkForm, .op-payment-widget-container {';
foreach ($styleConfig as $key => $value) {
$content = $content . $key . ':' . $value . ';';
}
return $content . '}';
}
/**
* Get placeholder css
* @return string
*/
public function getContainerPlaceholderStyle()
{
$inputStyle = '';
$selectStyle = '';
$placeholderValue = $this->config->getValue('widget_appearance/placeholders_color');
$phContentClass = '#networkForm ::placeholder, .op-payment-widget-container ::placeholder {';
$phContent = $phContentClass . 'opacity: 1;';
if ($placeholderValue) {
$phColor = 'color:' . $placeholderValue . ';';
$phContent = $phContent . $phColor;
$inputStyle = '#networkForm ::-ms-input-placeholder, .op-payment-widget-container ::-ms-input-placeholder{'
. $phColor . '}';
$selectStyle = '#networkForm select {' . $phColor . '}';
}
$phContent = $phContent . '}';
if ($inputStyle) {
$phContent = $phContent . $inputStyle . $selectStyle;
}
return $phContent;
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Controller\Integration;
use Exception;
use Magento\Checkout\Model\Session;
use Magento\Framework\App\ActionInterface;
use Magento\Framework\App\RequestInterface as Request;
use Magento\Framework\Controller\Result\Json;
use Magento\Framework\Controller\Result\JsonFactory;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Message\ManagerInterface;
use Magento\Payment\Gateway\Command\ResultInterface;
use Payoneer\OpenPaymentGateway\Gateway\Config\Config;
use Payoneer\OpenPaymentGateway\Model\ListUpdateTransactionService;
use Payoneer\OpenPaymentGateway\Model\TransactionService;
/**
* Class ProcessPayment
* Process List request for hosted payment
*/
class ProcessPayment implements ActionInterface
{
const LIST_EXPIRED = 'list_expired';
const HOSTED = 'hosted';
/**
* @var Session
*/
private $checkoutSession;
/**
* @var TransactionService
*/
private $transactionService;
/**
* @var ListUpdateTransactionService
*/
private $updateTransactionService;
/**
* @var ManagerInterface
*/
protected $messageManager;
/**
* @var Request
*/
protected $request;
/**
* @var JsonFactory
*/
protected $resultJsonFactory;
/**
* ProcessPayment constructor.
* @param Session $checkoutSession
* @param TransactionService $transactionService
* @param ListUpdateTransactionService $updateTransactionService
* @param ManagerInterface $messageManager
* @param Request $request
* @param JsonFactory $resultJsonFactory
*/
public function __construct(
Session $checkoutSession,
TransactionService $transactionService,
ListUpdateTransactionService $updateTransactionService,
ManagerInterface $messageManager,
Request $request,
JsonFactory $resultJsonFactory
) {
$this->checkoutSession = $checkoutSession;
$this->transactionService = $transactionService;
$this->updateTransactionService = $updateTransactionService;
$this->messageManager = $messageManager;
$this->request = $request;
$this->resultJsonFactory = $resultJsonFactory;
}
/**
* @return Json | array <mixed>
* @throws LocalizedException
* @throws NoSuchEntityException
* @throws \Exception
*/
public function execute()
{
$quote = $this->checkoutSession->getQuote();
$payment = $quote->getPayment();
$additionalInformation = $payment->getAdditionalInformation();
/** @phpstan-ignore-next-line */
$listId = isset($additionalInformation[Config::LIST_ID]) ?? $additionalInformation[Config::LIST_ID];
try {
if (!$listId) {
$response = $this->transactionService->process($quote);
} else {
/** @var array <mixed> $response */
$response = $this->updateTransactionService->process($quote->getPayment(), Config::LIST_UPDATE);
//if list session gave an update error, create a new one
if ($this->updateError($response)) {
$response = $this->transactionService->process($quote);
}
}
if ($this->isHostedIntegration()) {
$jsonData = $this->processHostedResponse($response);
} else {
$jsonData = $this->processEmbeddedResponse($response);
}
return $this->resultJsonFactory->create()->setData($jsonData);
} catch (Exception $e) {
return $this->resultJsonFactory->create()->setHttpResponseCode(400)->setData([
'error' => $e->getMessage()
]);
}
}
/**
* @param array <mixed> $result
* @return bool
*/
public function isListExpired($result)
{
if (isset($result['reason']) && str_contains($result['reason'], self::LIST_EXPIRED)) {
return true;
}
return false;
}
/**
* @param array <mixed> $result
* @return bool
*/
public function updateError($result)
{
if (isset($result['status']) && ($result['status'] == 422 || $result['status'] == 409)) {
return true;
} else {
return false;
}
}
/**
* Process response of hosted integration
* @param ResultInterface|null|bool|array <mixed> $result
* @return array <mixed>
* @throws LocalizedException
* @throws NoSuchEntityException
*/
public function processHostedResponse($result)
{
if ($result && isset($result['response']['redirect'])) {
$redirectURL = $result['response']['redirect']['url'];
} else {
$quote = $this->checkoutSession->getQuote();
$payment = $quote->getPayment();
$additionalInformation = $payment->getAdditionalInformation();
if (isset($additionalInformation[Config::REDIRECT_URL])) {
$redirectURL = $additionalInformation[Config::REDIRECT_URL];
} else {
$this->messageManager->addErrorMessage(__('We couldn\'t process the payment'));
}
}
return (isset($redirectURL)? ['redirectURL' => $redirectURL]: []);
}
/**
* Process response of embedded integration
* @param ResultInterface|null|bool|array <mixed> $result
* @return array <mixed>
*/
public function processEmbeddedResponse($result)
{
$jsonData = [];
if ($result && isset($result['response']['links'])) {
$jsonData = [
'links' => $result['response']['links']
];
} else {
$this->messageManager->addErrorMessage(__('We couldn\'t process the payment'));
}
return $jsonData;
}
/**
* @return bool
*/
public function isHostedIntegration()
{
return $this->request->getParam(Config::INTEGRATION) == Config::INTEGRATION_HOSTED;
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Controller\Redirect;
use Magento\Framework\App\Action\Context;
use Magento\Framework\App\Action\HttpGetActionInterface;
use Magento\Framework\App\ResponseInterface;
use Magento\Framework\Controller\ResultInterface;
use Magento\Framework\Exception\CouldNotSaveException;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\View\Result\Page;
use Magento\Quote\Api\CartRepositoryInterface;
use Magento\Quote\Model\Quote;
use Magento\Sales\Api\TransactionRepositoryInterface;
use Magento\Sales\Model\Order\Payment\Transaction;
use Magento\Sales\Model\ResourceModel\Order\Payment\Transaction\CollectionFactory as OrderTransactionCollectionFactory;
use Payoneer\OpenPaymentGateway\Model\Helper;
use Payoneer\OpenPaymentGateway\Logger\NotificationLogger;
use Magento\Checkout\Model\Session as CheckoutSession;
/**
* Class Cancel
* Process CANCEL request
*/
class Cancel implements HttpGetActionInterface
{
/**
* @var Context
*/
private $context;
/**
* @var CartRepositoryInterface
*/
private $cartRepository;
/**
* @var Helper
*/
private $helper;
/**
* @var TransactionRepositoryInterface
*/
private $transactionRepository;
/**
* @var OrderTransactionCollectionFactory
*/
protected $orderTransactionCollectionFactory;
/**
* @var NotificationLogger
*/
protected $notificationLogger;
/**
* @var CheckoutSession
*/
protected $checkoutSession;
/**
* Helper constructor.
* @param Context $context
* @param CartRepositoryInterface $cartRepository
* @param Helper $helper
* @param TransactionRepositoryInterface $transactionRepository
* @param OrderTransactionCollectionFactory $orderTransactionCollectionFactory
* @param NotificationLogger $notificationLogger
* @param CheckoutSession $checkoutSession
*/
public function __construct(
Context $context,
CartRepositoryInterface $cartRepository,
Helper $helper,
TransactionRepositoryInterface $transactionRepository,
OrderTransactionCollectionFactory $orderTransactionCollectionFactory,
NotificationLogger $notificationLogger,
CheckoutSession $checkoutSession
) {
$this->context = $context;
$this->cartRepository = $cartRepository;
$this->helper = $helper;
$this->transactionRepository = $transactionRepository;
$this->orderTransactionCollectionFactory = $orderTransactionCollectionFactory;
$this->notificationLogger = $notificationLogger;
$this->checkoutSession = $checkoutSession;
}
/**
* @return ResponseInterface|ResultInterface|Page
*/
public function execute()
{
$reqParams = $this->context->getRequest()->getParams();
try {
// Session to skip order confirmation email sending
$this->checkoutSession->setIsPayoneerCancelledOrder(true);
$this->checkoutSession->setPayoneerSkipInvoiceCreation(true);
$this->helper->setPayoneerInvalidTxnSession();
// Place order with invalid transaction details
$this->saveCartAndPlaceOrder($reqParams);
// Restore quote
$this->checkoutSession->restoreQuote();
// Update the invalid order transaction type to void
$this->updateTransactionType();
//Add comment to the order
$this->helper->addCommentToOrder();
} catch (\Exception $e) {
$this->notificationLogger->addError(
'CancelError - ' . $e->getMessage()
);
}
return $this->helper->redirectToCart(
__('We couldn\'t process the payment')
);
}
/**
* @param array <mixed> $reqParams
* @throws CouldNotSaveException
* @throws LocalizedException
* @throws NoSuchEntityException
* @return void
*/
public function saveCartAndPlaceOrder($reqParams)
{
$cartId = $reqParams['cart_id'];
if ($cartId) {
/** @var Quote $quote */
$quote = $this->cartRepository->getActive($reqParams['cart_id']);
$payment = $quote->getPayment();
foreach ($reqParams as $key => $value) {
$payment->setAdditionalInformation($key, $value);
}
$quote->setPayment($payment);
if (!$quote->getCustomerId()) {
$quote = $this->helper->setGuestCustomerEmail($quote);
}
$this->cartRepository->save($quote);
$this->helper->placeOrder($reqParams['cart_id']);
}
}
/**
* Updates transaction type to 'void'
*
* @return void
*/
public function updateTransactionType()
{
$orderId = $this->helper->getLastOrderId();
$collection = $this->orderTransactionCollectionFactory->create();
$collection->addOrderIdFilter($orderId);
if ($collection->getSize()) {
/** @var Transaction $transaction */
$transaction = $collection->getFirstItem();
$transaction->setTxnType('void');
$transaction->setIsClosed(1);
$this->transactionRepository->save($transaction);
}
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Controller\Redirect;
use Exception;
use Magento\Framework\App\CsrfAwareActionInterface;
use Magento\Framework\App\Request\Http;
use Magento\Framework\App\Request\InvalidRequestException;
use Magento\Framework\App\RequestInterface;
use Magento\Framework\App\ResponseInterface;
use Magento\Framework\Controller\ResultInterface;
use Payoneer\OpenPaymentGateway\Api\Data\NotificationInterfaceFactory;
use Payoneer\OpenPaymentGateway\Api\PayoneerNotificationRepositoryInterface;
use Payoneer\OpenPaymentGateway\Gateway\Config\Config;
use Payoneer\OpenPaymentGateway\Model\PayoneerNotification;
use Payoneer\OpenPaymentGateway\Model\PayoneerNotificationFactory;
use Magento\Sales\Model\ResourceModel\Order\CollectionFactory as OrderCollectionFactory;
use Payoneer\OpenPaymentGateway\Logger\NotificationLogger;
use Payoneer\OpenPaymentGateway\Model\Adminhtml\Helper;
use Payoneer\OpenPaymentGateway\Model\TransactionOrderUpdater;
/**
* Class Notification
*
* Process Notification request
*/
class Notification implements CsrfAwareActionInterface
{
/**
* @var PayoneerNotificationFactory
*/
protected $payoneerNotification;
/**
* @var Http
*/
protected $request;
/**
* @var PayoneerNotificationRepositoryInterface
*/
protected $notificationRepository;
/**
* @var NotificationInterfaceFactory
*/
protected $notificationFactory;
/**
* @var OrderCollectionFactory
*/
protected $orderCollectionFactory;
/**
* @var NotificationLogger
*/
protected $notificationLogger;
/**
* @var TransactionOrderUpdater
*/
protected $transactionOrderUpdater;
/**
* Notification constructor.
*
* @param PayoneerNotificationFactory $payoneerNotification
* @param PayoneerNotificationRepositoryInterface $notificationRepository
* @param NotificationInterfaceFactory $notificationFactory
* @param OrderCollectionFactory $orderCollectionFactory
* @param NotificationLogger $notificationLogger
* @param Http $request
* @param TransactionOrderUpdater $transactionOrderUpdater
*/
public function __construct(
PayoneerNotificationFactory $payoneerNotification,
PayoneerNotificationRepositoryInterface $notificationRepository,
NotificationInterfaceFactory $notificationFactory,
OrderCollectionFactory $orderCollectionFactory,
NotificationLogger $notificationLogger,
Http $request,
TransactionOrderUpdater $transactionOrderUpdater
) {
$this->payoneerNotification = $payoneerNotification;
$this->notificationRepository = $notificationRepository;
$this->notificationFactory = $notificationFactory;
$this->request = $request;
$this->orderCollectionFactory = $orderCollectionFactory;
$this->notificationLogger = $notificationLogger;
$this->transactionOrderUpdater = $transactionOrderUpdater;
}
/**
* Listen notification from payoneer side and save to db.
*
* @return ResponseInterface|ResultInterface|void|null
* @throws Exception
*/
public function execute()
{
try {
$urlParams = $this->request->getParams();
$post = $this->request->getContent();
$postArray = \Safe\json_decode($post, true);
if ($postArray['entity'] == Config::ENTITY_PAYMENT) {
/** @var PayoneerNotification $notification */
$notification = $this->notificationFactory->create();
$notification->addData([
'transactionId' => $postArray['transactionId'],
'longId' => $postArray['longId'],
'content' => $post,
'order_id' => $urlParams['order_id'],
'cron_status' => 0
]);
$this->notificationRepository->save($notification);
if (isset($postArray['statusCode']) &&
isset($postArray['interactionReason']) &&
$postArray['interactionReason'] != Helper::SYSTEM_FAILURE
) {
$this->transactionOrderUpdater->processNotificationResponse(
$notification->getOrderId(),
$postArray
);
$notification->setCronStatus(true);
$this->notificationRepository->save($notification);
}
}
} catch (Exception $e) {
$this->notificationLogger->addError(
__(
'ErrorMessage = %1, OrderId = %2, NotificationResponse = %3.',
$e->getMessage(),
$urlParams['order_id'],
$post
)
);
}
exit; // @codingStandardsIgnoreLine
}
/**
* Get the token from the order payment additional info.
*
* @param string $orderId
* @return string|null
*/
private function getTokenFromOrder($orderId)
{
$collection = $this->orderCollectionFactory->create();
$collection->addFieldToFilter('increment_id', ['eq' => $orderId]);
if ($collection->getSize()) {
$order = $collection->getFirstItem();
$payment = $order->getPayment();
return $payment->getAdditionalInformation(Config::TOKEN_NOTIFICATION);
}
return null;
}
/**
* @param RequestInterface $request
* @return InvalidRequestException|null
*/
public function createCsrfValidationException(RequestInterface $request): ?InvalidRequestException
{
return null;
}
/**
* @param RequestInterface $request
* @return bool|null
*/
public function validateForCsrf(RequestInterface $request): ?bool
{
$orderId = $request->getParam('order_id');
$notificationToken = $request->getParam('token');
$orderToken = $this->getTokenFromOrder($orderId);
if ($notificationToken == '' || $notificationToken == null || $notificationToken != $orderToken) {
$this->notificationLogger->addError(
__(
'Invalid token for order #%1, order token = %2, received token = %3',
$orderId,
$orderToken,
$notificationToken
)
);
return false;
}
return true;
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Controller\Redirect;
use Magento\Checkout\Model\Session;
use Magento\Framework\App\Action\Context;
use Magento\Framework\App\Action\HttpGetActionInterface;
use Magento\Framework\App\ResponseInterface;
use Magento\Framework\Controller\Result\Redirect;
use Magento\Framework\Controller\ResultInterface;
use Magento\Framework\View\Result\Page;
use Magento\Framework\View\Result\PageFactory;
use Magento\Quote\Api\CartManagementInterface;
use Magento\Quote\Api\CartRepositoryInterface;
use Magento\Quote\Model\Quote;
use Payoneer\OpenPaymentGateway\Gateway\Config\Config;
use Payoneer\OpenPaymentGateway\Model\Adminhtml\Source\Fields as AdminFields;
use Payoneer\OpenPaymentGateway\Model\Helper;
/**
* Class Success
* Process SUCCESS request
*/
class Success implements HttpGetActionInterface
{
const INTERACTION_CODE_PROCEED = 'PROCEED';
/**
* @var Context
*/
private $context;
/**
* @var CartRepositoryInterface
*/
private $cartRepository;
/**
* @var PageFactory
*/
private $resultPageFactory;
/**
* @var Helper
*/
protected $helper;
/**
* @var Session
*/
private $checkoutSession;
/**
* @var Config
*/
private $config;
/**
* Success constructor.
* @param Context $context
* @param CartRepositoryInterface $cartRepository
* @param PageFactory $resultPageFactory
* @param Helper $helper
* @param Session $checkoutSession
* @param Config $config
*/
public function __construct(
Context $context,
CartRepositoryInterface $cartRepository,
PageFactory $resultPageFactory,
Helper $helper,
Session $checkoutSession,
Config $config
) {
$this->context = $context;
$this->cartRepository = $cartRepository;
$this->resultPageFactory = $resultPageFactory;
$this->helper = $helper;
$this->checkoutSession = $checkoutSession;
$this->config = $config;
}
/**
* Dispatch request
* @return ResponseInterface|Redirect|ResultInterface|Page
*/
public function execute()
{
$reqParams = $this->context->getRequest()->getParams();
try {
if (isset($reqParams['listUrl']) &&
isset($reqParams['interactionCode']) &&
$reqParams['interactionCode'] == self::INTERACTION_CODE_PROCEED
) {
$cartId = $reqParams['cart_id'];
/** @var Quote $quote */
$quote = $this->cartRepository->getActive($cartId);
$quoteData = $quote->getData();
$payment = $quote->getPayment();
if (!isset($reqParams['token'])
|| $reqParams['token'] == ''
|| $reqParams['token'] == null
|| $payment->getAdditionalInformation('token') != $reqParams['token']
) {
return $this->redirectToCart();
} else {
foreach ($reqParams as $key => $value) {
$payment->setAdditionalInformation($key, $value);
}
if ($this->config->getValue('payment_action') == AdminFields::CAPTURE) {
$payment->setAdditionalInformation('payoneerCapture', 'Success');
}
$quote->setPayment($payment);
}
if ($quote->getCustomerId() && isset($reqParams['customerRegistrationId'])) {
$this->helper->saveRegistrationId($reqParams['customerRegistrationId'], $quote->getCustomerId());
}
if (!$quote->getCustomerId()) {
$quote = $this->helper->setGuestCustomerEmail($quote);
} else {
$this->helper->unsetPayoneerCustomerEmailSession();
}
$this->cartRepository->save($quote);
if ($quoteData['grand_total'] != $reqParams['amount']) {
$this->checkoutSession->setUpdateOrderStatus(true);
}
//Place order in magento
$this->helper->placeOrder($cartId);
//Unset custom Payoneer sessions
$this->helper->unsetPayoneerCountryIdSession();
return $this->resultPageFactory->create();
} else {
return $this->redirectToCart();
}
} catch (\Exception $e) {
return $this->redirectToCart($e->getMessage());
}
}
/**
* @param string|null $message
* @return Redirect
*/
public function redirectToCart($message = null)
{
if (!$message) {
$message = 'We couldn\'t process the payment. Invalid response from Payoneer.';
}
return $this->helper->redirectToCart(__($message));
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Cron;
use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
use Payoneer\OpenPaymentGateway\Model\ResourceModel\PayoneerNotification\CollectionFactory;
use Payoneer\OpenPaymentGateway\Model\TransactionOrderUpdater;
use Payoneer\OpenPaymentGateway\Model\ResourceModel\PayoneerNotification\Collection;
use Payoneer\OpenPaymentGateway\Logger\NotificationLogger;
use Payoneer\OpenPaymentGateway\Gateway\Config\Config;
use Payoneer\OpenPaymentGateway\Model\NotificationEmailSender;
use Payoneer\OpenPaymentGateway\Api\PayoneerNotificationRepositoryInterface;
/**
* Update order on running cron with notification data
*/
class OrderUpdate
{
/**
* @var CollectionFactory
*/
private $notificationCollectionFactory;
/**
* @var TransactionOrderUpdater
*/
protected $transactionOrderUpdater;
/**
* @var NotificationLogger
*/
protected $notificationLogger;
/**
* @var Config
*/
protected $config;
/**
* @var TimezoneInterface
*/
protected $timezone;
/**
* @var NotificationEmailSender
*/
protected $emailSender;
/**
* @var PayoneerNotificationRepositoryInterface
*/
protected $notificationRepository;
/**
* OrderUpdate construct function
*
* @param CollectionFactory $notificationCollectionFactory
* @param TransactionOrderUpdater $transactionOrderUpdater
* @param NotificationLogger $notificationLogger
* @param Config $config
* @param TimezoneInterface $timezone
* @param NotificationEmailSender $emailSender
* @param PayoneerNotificationRepositoryInterface $notificationRepository
* @return void
*/
public function __construct(
CollectionFactory $notificationCollectionFactory,
TransactionOrderUpdater $transactionOrderUpdater,
NotificationLogger $notificationLogger,
Config $config,
TimezoneInterface $timezone,
NotificationEmailSender $emailSender,
PayoneerNotificationRepositoryInterface $notificationRepository
) {
$this->notificationCollectionFactory = $notificationCollectionFactory;
$this->transactionOrderUpdater = $transactionOrderUpdater;
$this->notificationLogger = $notificationLogger;
$this->config = $config;
$this->timezone = $timezone;
$this->emailSender = $emailSender;
$this->notificationRepository = $notificationRepository;
}
/**
* Get the non-processed notification and process it.
*
* @return bool|void
*/
public function execute()
{
try {
$notificationCollection = $this->getNotificationsToCleanUp();
if ($notificationCollection != null) {
/** @phpstan-ignore-next-line */
$notificationCollection->walk('delete');
}
} catch (\Exception $e) {
$this->notificationLogger->addError(
__('NotificationCleanupError: %1', $e->getMessage())
);
}
try {
$notificationCollection = $this->getNotificationsToSendEmail();
if ($notificationCollection != null) {
foreach ($notificationCollection as $notification) {
$response = \Safe\json_decode($notification->getContent(), true);
$this->emailSender->send($response);
$notification->setSendEmail(true);
$this->notificationRepository->save($notification);
}
}
} catch (\Exception $e) {
$this->notificationLogger->addError(
__('NotificationEmailSendError: %1', $e->getMessage())
);
}
}
/**
* Get all the notifications to cleanup
*
* @return Collection|null
*/
private function getNotificationsToCleanUp()
{
$cleanupDays = $this->config->getValue(Config::NOTIFICATION_CLEANUP_DAYS_PATH);
if (empty($cleanupDays)) {
return null;
}
$collection = $this->notificationCollectionFactory->create();
$collection->addFieldToFilter('cron_status', ['eq' => 1]);
$collection->addFieldToFilter(
'created_at',
['lteq' => $this->getUtcDateXDaysBefore($cleanupDays)]
);
return $collection;
}
/**
* Get all the notifications to which the email should be send
*
* @return Collection|null
*/
private function getNotificationsToSendEmail()
{
$emailSendDays = $this->config->getValue(Config::EMAIL_NOTIFICATION_DAYS_PATH);
if (empty($emailSendDays)) {
return null;
}
$collection = $this->notificationCollectionFactory->create();
$collection->addFieldToFilter('cron_status', ['eq' => 0]);
$collection->addFieldToFilter('send_email', ['eq' => 0]);
$collection->addFieldToFilter(
'created_at',
['lteq' => $this->getUtcDateXDaysBefore($emailSendDays)]
);
return $collection;
}
/**
* Get the date in UTC timezone
*
* @param int $noOfDays
* @return string
*/
private function getUtcDateXDaysBefore($noOfDays)
{
return $this->timezone->date()->setTimezone(new \DateTimeZone('UTC'))
->modify('-' . $noOfDays . ' day')
->format('Y-m-d 23:59:59');
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Gateway\Command;
use Magento\Checkout\Model\Session;
use Magento\Payment\Gateway\Command\CommandException;
use Magento\Payment\Gateway\Command\ResultInterface as CommandResultInterface;
use Magento\Payment\Gateway\CommandInterface;
use Magento\Payment\Gateway\ErrorMapper\ErrorMessageMapperInterface;
use Magento\Payment\Gateway\Helper\SubjectReader;
use Magento\Payment\Gateway\Http\ClientException;
use Magento\Payment\Gateway\Http\ClientInterface;
use Magento\Payment\Gateway\Http\ConverterException;
use Magento\Payment\Gateway\Request\BuilderInterface;
use Magento\Payment\Gateway\Response\HandlerInterface;
use Magento\Payment\Gateway\Validator\ResultInterface;
use Magento\Payment\Gateway\Validator\ValidatorInterface;
use Payoneer\OpenPaymentGateway\Gateway\Http\TransferFactoryInterface;
use Psr\Log\LoggerInterface;
/**
* Class GatewayCommand
*
* GatewayCommand for Payoneer payment
*/
class GatewayCommand implements CommandInterface
{
/**
* @var BuilderInterface
*/
protected $requestBuilder;
/**
* @var TransferFactoryInterface
*/
protected $transferFactory;
/**
* @var ClientInterface
*/
protected $client;
/**
* @var LoggerInterface
*/
protected $logger;
/**
* @var HandlerInterface|null
*/
protected $handler;
/**
* @var ValidatorInterface|null
*/
protected $validator;
/**
* @var ErrorMessageMapperInterface|null
*/
private $errorMessageMapper;
/**
* @var Session
*/
private $session;
/**
* Gateway command constructor
*
* @param BuilderInterface $requestBuilder
* @param TransferFactoryInterface $transferFactory
* @param ClientInterface $client
* @param LoggerInterface $logger
* @param Session $checkoutSession
* @param HandlerInterface $handler
* @param ValidatorInterface $validator
* @param ErrorMessageMapperInterface|null $errorMessageMapper
*
*/
public function __construct(
BuilderInterface $requestBuilder,
TransferFactoryInterface $transferFactory,
ClientInterface $client,
LoggerInterface $logger,
Session $checkoutSession,
HandlerInterface $handler = null,
ValidatorInterface $validator = null,
ErrorMessageMapperInterface $errorMessageMapper = null
) {
$this->requestBuilder = $requestBuilder;
$this->transferFactory = $transferFactory;
$this->client = $client;
$this->logger = $logger;
$this->session = $checkoutSession;
$this->handler = $handler;
$this->validator = $validator;
$this->errorMessageMapper = $errorMessageMapper;
}
/**
* Executes command basing on business object
*
* @param array <mixed> $commandSubject
* @return CommandResultInterface | array <mixed>
* @throws CommandException
* @throws ClientException
* @throws ConverterException
*/
public function execute(array $commandSubject)
{
$response = [];
$payment = SubjectReader::readPayment($commandSubject);
$transferO = $this->transferFactory->create(
$this->requestBuilder->build($commandSubject),
$payment
);
//process payoneer request only if it is not via fetch or notification
if ($this->session->getFetchNotificationResponse()) {
$response = $this->session->getFetchNotificationResponse();
} else {
$response = $this->client->placeRequest($transferO);
if ($this->validator !== null) {
$result = $this->validator->validate(
array_merge($commandSubject, ['response' => $response])
);
if (!$result->isValid()) {
$this->processErrors($result);
}
}
}
if ($this->handler) {
$this->handler->handle(
$commandSubject,
$response
);
}
if ($this->session->getFetchNotificationResponse()) {
$this->session->unsFetchNotificationResponse();
}
return $response;
}
/**
* Tries to map error messages from validation result and logs processed message.
* Throws an exception with mapped message or default error.
*
* @param ResultInterface $result
* @return void
* @throws CommandException
*/
private function processErrors(ResultInterface $result)
{
$messages = [];
$errorCodeOrMessage = null;
$errorsSource = array_merge($result->getErrorCodes(), $result->getFailsDescription());
foreach ($errorsSource as $errorCodeOrMessage) {
$errorCodeOrMessage = (string) $errorCodeOrMessage;
// error messages mapper can be not configured if payment method doesn't have custom error messages.
if ($this->errorMessageMapper !== null) {
$mapped = (string) $this->errorMessageMapper->getMessage($errorCodeOrMessage);
if (!empty($mapped)) {
$messages[] = $mapped;
$errorCodeOrMessage = $mapped;
}
}
$this->logger->critical('Payment Error: ' . $errorCodeOrMessage);
}
$exceptionMessage = $errorCodeOrMessage ?: 'Transaction declined. Try again later.';
throw new CommandException(
!empty($messages)
? __(implode(PHP_EOL, $messages))
: __($exceptionMessage)
);
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Gateway\Config;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\UrlInterface;
use Magento\Store\Model\StoreManagerInterface;
use Payoneer\OpenPaymentGateway\Model\Adminhtml\Source\Fields as AdminFields;
use Payoneer\OpenPaymentGateway\Model\Ui\ConfigProvider;
use Magento\Store\Model\ScopeInterface;
use Magento\Framework\Locale\Resolver as LocaleResolver;
/**
* Class Config
*
* Payoneer configurations
*/
class Config extends \Magento\Payment\Gateway\Config\Config
{
const DEFAULT_PATH_PATTERN = 'payment/%s/%s';
/**
* API method types
*/
const METHOD_GET = 'GET';
const METHOD_PUT = 'PUT';
const METHOD_POST = 'POST';
const METHOD_DELETE = 'DELETE';
/**
* API endpoints
*/
const LIST_END_POINT = 'api/lists';
const LIST_UPDATE_END_POINT = 'api/lists/%s'; //api/lists/{longId}
const CAPTURE_END_POINT = 'api/charges/%s/closing'; //api/charges/{longId}/closing
const REFUND_END_POINT = 'api/charges/%s/payout'; //api/charges/{longId}/payout
const AUTHORIZATION_CANCEL_END_POINT = 'api/charges/%s'; //api/charges/{longId}
/**
* List operation constants
*/
const LIST_CAPTURE = 'list_capture';
const LIST_FETCH = 'list_fetch';
const LIST_UPDATE = 'list_update';
const LIST_DELETE = 'list_delete';
/**
* Country path
*/
const COUNTRY_CODE_PATH = 'general/country/default';
/**
* API Request constants
*/
const CALLBACK = 'callback';
const RETURN_URL = 'returnUrl';
const CANCEL_URL = 'cancelUrl';
const NOTIFICATION_URL = 'notificationUrl';
const RETURN_URL_PATH = 'payoneer/redirect/success';
const CANCEL_URL_PATH = 'payoneer/redirect/cancel';
const NOTIFICATION_URL_PATH = 'payoneer/redirect/notification';
const EMBEDDED_STYLE_PATH = 'payoneer/embedded/style';
/**
* Notification configuration constants
*/
const NOTIFICATION_CLEANUP_DAYS_PATH = 'notification_settings/cleanup_days';
const EMAIL_NOTIFICATION_DAYS_PATH = 'notification_settings/send_email_days';
const PRESELECTION = 'preselection';
const DEFERRAL = 'deferral';
const PAYMENT = 'payment';
const AMOUNT = 'amount';
const CURRENCY = 'currency';
const REFERENCE = 'reference';
const CUSTOMER = 'customer';
const NUMBER = 'number';
const EMAIL = 'email';
const COMPANY = 'company';
const NAME = 'name';
const TITLE = 'title';
const FIRST_NAME = 'firstName';
const MIDDLE_NAME = 'middleName';
const LAST_NAME = 'lastName';
const SHIPPING = 'shipping';
const BILLING = 'billing';
const ADDRESSES = 'addresses';
const STREET = 'street';
const HOUSE_NUMBER = 'houseNumber';
const ZIP = 'zip';
const POSTCODE = 'postcode';
const CITY = 'city';
const STATE = 'state';
const REGION = 'region';
const COUNTRY = 'country';
const COUNTRY_ID = 'countryId';
const TRANSACTION_ID = 'transactionId';
const INTEGRATION = 'integration';
const DIVISION = 'division';
const ALLOW_DELETE = 'allowDelete';
const PRODUCTS = 'products';
const SKU = 'code';
const QUANTITY = 'quantity';
const TYPE = 'type';
const NET_AMOUNT = 'netAmount';
const TAX_AMOUNT = 'taxAmount';
const TAX_PERCENT = 'taxRatePercentage';
const INVOICE_ID = 'invoiceId';
const STYLE = 'style';
const RESOLUTION_1X = '1x';
const HOSTED_VERSION = 'hostedVersion';
const VERSION_V4 = 'v4';
const TOKEN_ID = 'token';
const TXN_ID = 'transaction_id';
const REGISTRATION = 'registration';
const ID = 'id';
const TOKEN = 'token';
const TOKEN_NOTIFICATION = 'notification_token';
const LIST_ID = 'listId';
const LANGUAGE = 'language';
const REDIRECT_URL = 'redirect_url';
const ENTITY_PAYMENT = 'payment';
const HOST_NAME = 'host_name';
const HOSTED = 'HOSTED';
const SELECT_NATIVE = 'SELECTIVE_NATIVE';
const INTEGRATION_EMBEDDED = 'embedded';
const INTEGRATION_HOSTED = 'hosted';
const PAYMENT_FLOW = 'payment_flow';
/**
* @var StoreManagerInterface
*/
protected $storeManager;
/**
* @var ScopeConfigInterface
*/
protected $scopeConfig;
/**
* @var LocaleResolver
*/
protected $localeResolver;
/**
* @var string|null
*/
private $methodCode;/** @phpstan-ignore-line */
/**
* @var string|null
*/
private $pathPattern;/** @phpstan-ignore-line */
/**
* @var string[]
*/
protected $styleConfigs = [
'background-color' => 'payment/payoneer/widget_appearance/background_color',
'color' => 'payment/payoneer/widget_appearance/color',
'font-size' => 'payment/payoneer/widget_appearance/font_size',
'font-weight' => 'payment/payoneer/widget_appearance/font_weight',
'letter-spacing' => 'payment/payoneer/widget_appearance/letter_spacing',
'line-height' => 'payment/payoneer/widget_appearance/line_height',
'padding' => 'payment/payoneer/widget_appearance/padding',
'text-align' => 'payment/payoneer/widget_appearance/text_align',
];
/**
* Config constructor.
* @param StoreManagerInterface $storeManager
* @param ScopeConfigInterface $scopeConfig
* @param LocaleResolver $localeResolver
* @param string $methodCode
* @param string $pathPattern
*/
public function __construct(
StoreManagerInterface $storeManager,
ScopeConfigInterface $scopeConfig,
LocaleResolver $localeResolver,
$methodCode = ConfigProvider::CODE,
$pathPattern = self::DEFAULT_PATH_PATTERN
) {
parent::__construct($scopeConfig, $methodCode, $pathPattern);
$this->storeManager = $storeManager;
$this->scopeConfig = $scopeConfig;
$this->methodCode = $methodCode;
$this->pathPattern = $pathPattern;
$this->localeResolver = $localeResolver;
}
/**
* Get store identifier
*
* @return int
* @throws NoSuchEntityException
*/
public function getStoreId(): int
{
return $this->storeManager->getStore()->getId();
}
/**
* Get website identifier
*
* @return int
* @throws NoSuchEntityException
*/
public function getWebsiteId(): int
{
return $this->storeManager->getStore()->getWebsiteId();
}
/**
* @param string $type
* @return string
* @throws NoSuchEntityException
*/
public function getBaseUrl($type = UrlInterface::URL_TYPE_WEB): string
{
return $this->storeManager->getStore()->getBaseUrl($type);
}
/**
* @return string
* @throws NoSuchEntityException
*/
public function getCurrentCurrency(): string
{
return $this->storeManager->getStore()->getCurrentCurrencyCode();/** @phpstan-ignore-line */
}
/**
* @return array <mixed>
*/
public function getMockData()
{
return [
'transactionId' => '21-0005',
'country' => 'DE',
'customer' => [
'email' => 'test@test.com'
],
'payment' => [
'amount' => 0.89,
'currency' => 'EUR',
'reference' => 'Shop 101/20-03-2016'
],
'callback' => [
'returnUrl' => 'https://resources.integration.oscato.com/paymentpage/v3-examples/success.html',
'cancelUrl' => 'https://resources.integration.oscato.com/paymentpage/v3-examples/cancel.html'
]
];
}
/**
* Prepare header for API requests
*
* @param null $merchantCode
* @param null $appKey
* @return array <mixed>
*/
public function prepareHeaders(
$merchantCode = null,
$appKey = null
): array {
$headers = [];
$headers['Content-Type'] = 'application/vnd.optile.payment.enterprise-v1-extensible+json';
$headers['Accept'] = 'application/vnd.optile.payment.enterprise-v1-extensible+json';
$headers['Authorization'] = 'Basic ' . base64_encode($merchantCode . ':' . $appKey);
return $headers;
}
/**
* Get environment credentials
*
* @param string $key
* @return mixed|string|null
*/
public function getCredentials($key)
{
$environment = $this->getValue('environment');
switch ($key) {
case 'api_key':
return ($environment && $environment == AdminFields::ENVIRONMENT_SANDBOX_VALUE) ?
$this->getValue('sandbox_api_key') :
$this->getValue('live_api_key');
case 'store_code':
return ($environment && $environment == AdminFields::ENVIRONMENT_SANDBOX_VALUE) ?
$this->getValue('sandbox_store_code') :
$this->getValue('live_store_code');
case self::HOST_NAME:
return ($environment && $environment == AdminFields::ENVIRONMENT_SANDBOX_VALUE) ?
$this->getValue('sandbox_host_name') :
$this->getValue('live_host_name');
}
return null;
}
/**
* Check if hosted integration
* @return bool
*/
public function isHostedIntegration()
{
return $this->getValue('payment_flow') == AdminFields::HOSTED;
}
/**
* Check if Payoneer module is enabled
* @return mixed|null
*/
public function isPayoneerEnabled()
{
return $this->getValue('active');
}
/**
* Get style configuration values
* @return array <mixed>
*/
public function getStyleConfig()
{
$styleConfigValues = [];
foreach ($this->styleConfigs as $key => $path) {
$value = $this->scopeConfig->getValue($path);
if ($value) {
$styleConfigValues[$key] = trim($value);
}
}
return $styleConfigValues;
}
/**
* Check if module debugging is enabled or not
*
* @return bool
*/
public function isDebuggingEnabled()
{
return $this->getValue('debug');
}
/**
* Get Country code by website scope
*
* @return string
*/
public function getCountryByStore(): string
{
return $this->scopeConfig->getValue(
self::COUNTRY_CODE_PATH,
ScopeInterface::SCOPE_STORE
);
}
/**
* Return the current store locale
*
* @return string
*/
public function getStoreLocale(): string
{
$storeLocale = $this->localeResolver->getLocale();
$languageMapping = $this->getValue('language_mapping');
if ($languageMapping != '') {
$languageMappings = explode(',', $languageMapping);
foreach ($languageMappings as $language) {
$localeParts = explode(':', $language);
if (count($localeParts) > 1 && $localeParts[0] == $storeLocale) {
return $localeParts[1];
}
}
}
return $storeLocale;
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Gateway\Http\Authorization;
use Payoneer\OpenPaymentGateway\Gateway\Config\Config;
use Magento\Payment\Gateway\Data\PaymentDataObjectInterface;
use Payoneer\OpenPaymentGateway\Gateway\Http\TransferFactory;
/**
* Class CancellationTransferFactory
*
* Builds authorization cancellation transfer object
*/
class CancellationTransferFactory extends TransferFactory
{
/**
* @inheritDoc
*/
protected function getApiUri(PaymentDataObjectInterface $payment)
{
$longId = $payment->getPayment()->getAdditionalInformation('longId');
return sprintf(
Config::AUTHORIZATION_CANCEL_END_POINT,
$longId
);
}
/**
* @inheritDoc
*/
protected function getMethod()
{
return Config::METHOD_DELETE;
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Gateway\Http\Client;
use Magento\Framework\DataObject;
use Magento\Payment\Gateway\Http\ClientInterface;
use Magento\Payment\Gateway\Http\TransferInterface;
use Payoneer\OpenPaymentGateway\Gateway\Config\Config;
use Payoneer\OpenPaymentGateway\Model\Api\Request;
use Payoneer\OpenPaymentGateway\Model\Method\Logger;
/**
* Class Client
* Payoneer client for transactions
*/
class Client implements ClientInterface
{
const AUTHORIZE = 'authorize';
const LIST = 'list';
const CAPTURE = 'authorize_capture';
const AUTHORIZATION_CANCEL = 'authorize_cancel';
const REFUND = 'refund';
const VOID = 'void';
const LIST_FETCH = 'list_fetch';
const LIST_CAPTURE = 'list_capture';
const LIST_UPDATE = 'list_update';
const LIST_DELETE = 'list_delete';
const PREAUTH_CANCEL = 'preauth_cancel';
/**
* @var Logger
*/
protected $logger;
/**
* @var Request
*/
protected $request;
/**
* @var Config
*/
protected $config;
/**
* @var string
*/
protected $operation;
/**
* Mandatory request fields
* @var string[]
*/
protected $mandatoryFields = [
'transactionId',
'country',
'customer',
'payment',
'callback'
];
/**
* Mandatory request fields for 'payment' object
* @var string[]
*/
protected $mandatoryFieldsPayment = [
'amount',
'currency',
'reference',
'invoiceId'
];
/**
* Mandatory request fields for 'customer' object
* @var string[]
*/
protected $mandatoryFieldsCustomer = [
'number'
];
/**
* @var array <mixed> | string
*/
protected $requestData;
/**
* Client construct
*
* @param Logger $logger
* @param Request $request
* @param Config $config
* @param string $operation
* @return void
*/
public function __construct(
Logger $logger,
Request $request,
Config $config,
$operation
) {
$this->logger = $logger;
$this->request = $request;
$this->config = $config;
$this->operation = $operation;
}
/**
* @param TransferInterface $transferObject
* @return array|DataObject
* @return array <mixed>
*/
public function placeRequest(TransferInterface $transferObject)
{
$response = [];
$responseObj = null;
$this->requestData = $transferObject->getBody();
$this->logData(['operation' => $this->operation]);
switch ($this->operation) {
case self::LIST:
case self::LIST_UPDATE:
$isRequestValid = $this->validateRequest();
if ($isRequestValid) {
$responseObj = $this->processRequest($transferObject);
} else {
$this->logData(['request' => $this->requestData]);
}
break;
case self::AUTHORIZE:
case self::CAPTURE:
$responseObj = $this->processAuthRequest($transferObject);
break;
case self::REFUND:
case self::AUTHORIZATION_CANCEL:
case self::LIST_CAPTURE:
case self::LIST_FETCH:
case self::LIST_DELETE:
$responseObj = $this->processRequest($transferObject);
break;
default:
throw new \InvalidArgumentException(sprintf('Unknown operation [%s]', $this->operation));
}
if ($responseObj instanceof DataObject) {
$response['response'] = $responseObj->getData('response') ?: '';
$response['status'] = $responseObj->getData('status') ?: '';
$response['reason'] = $responseObj->getData('reason') ?: '';
}
$this->logData($response);
return $response;
}
/**
* @param TransferInterface $transferObject
* @return DataObject
*/
protected function processAuthRequest($transferObject)
{
$responseObject = new \Magento\Framework\DataObject();
$responseObject->setData('response', $this->getResponseData($transferObject));
return $responseObject;
}
/**
* @param TransferInterface $transfer
* @return array|mixed
*/
private function getResponseData(TransferInterface $transfer)
{
$headers = $transfer->getHeaders();
if (isset($headers[Config::TXN_ID])) {
return [Config::TXN_ID => $headers[Config::TXN_ID]];
}
return [];
}
/**
* Validate payoneer request data
*
* @return bool
*/
public function validateRequest()
{
$isValid = true;
foreach ($this->mandatoryFields as $mandatoryField) {
switch ($mandatoryField) {
case 'payment':
$isValid = $this->mandatoryFieldsExists($this->mandatoryFieldsPayment, 'payment');
break;
case 'customer':
$isValid = $this->mandatoryFieldsExists($this->mandatoryFieldsCustomer, 'customer');
break;
default:
if (is_array($this->requestData) && !isset($this->requestData[$mandatoryField]) ||
(is_array($this->requestData)
&& isset($this->requestData[$mandatoryField])
&& $this->requestData[$mandatoryField] == '')) {
$this->logData([$mandatoryField . ' must not be empty']);
return false;
}
}
if (!$isValid) {
return false;
}
}
return $isValid;
}
/**
* Check if mandatory fields exists
*
* @param array <mixed> $mandatoryFields
* @param string $objectName
* @return bool
*/
public function mandatoryFieldsExists($mandatoryFields, $objectName)
{
foreach ($mandatoryFields as $mandatoryField) {
if (is_array($this->requestData) && !isset($this->requestData[$objectName][$mandatoryField])) {
$this->logData([$objectName . '.' . $mandatoryField . ' must not be empty']);
return false;
}
}
return true;
}
/**
* Log data to payoneer.log
* @param array <mixed> $result
* @return void
*/
public function logData($result)
{
if ((bool)$this->config->getValue('debug') == true) {
$this->logger->debug([$result]);
}
}
/**
* Process the api request.
*
* @param TransferInterface $transferObject
* @return DataObject <mixed> | DataObject
*/
public function processRequest($transferObject)
{
$credentials = [];
$credentials['merchantCode'] = $transferObject->getAuthUsername();
$credentials['apiKey'] = $transferObject->getAuthPassword();
$clientConfigs = $transferObject->getClientConfig();
$credentials['hostName'] = $clientConfigs['host_name'];
$data = $transferObject->getBody();
if ((bool)$this->config->getValue('debug') == true) {
$this->logger->debug(['request' => $data]);
}
return $this->request->send(
$transferObject->getMethod(),
$transferObject->getUri(),
$credentials,
$data
);
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Gateway\Http;
use Magento\Payment\Gateway\Data\PaymentDataObjectInterface;
use Payoneer\OpenPaymentGateway\Gateway\Config\Config;
/**
* Class ListCaptureTransferFactory
*
* Builds gateway transfer object
*/
class ListCaptureTransferFactory extends TransferFactory
{
/**
* @inheritDoc
*/
protected function getApiUri(PaymentDataObjectInterface $payment)
{
$additionalInformation = $payment->getPayment()->getAdditionalInformation();
$longId = $additionalInformation['longId'];
return sprintf(
Config::CAPTURE_END_POINT,
$longId
);
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Gateway\Http;
use Magento\Payment\Gateway\Data\PaymentDataObjectInterface;
use Payoneer\OpenPaymentGateway\Gateway\Config\Config;
/**
* Class ListFetchTransferFactory
*
* Builds gateway transfer object
*/
class ListFetchTransferFactory extends TransferFactory
{
/**
* @inheritDoc
*/
protected function getApiUri(PaymentDataObjectInterface $payment)
{
$additionalInformation = $payment->getPayment()->getAdditionalInformation();
$longId = $additionalInformation['longId'];
return 'api/charges/' . $longId;
}
/**
* Return the method
*
* @return string
*/
protected function getMethod()
{
return Config::METHOD_GET;
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Gateway\Http;
use Magento\Payment\Gateway\Http\TransferBuilder;
use Magento\Payment\Gateway\Http\TransferFactoryInterface;
use Magento\Payment\Gateway\Http\TransferInterface;
use Payoneer\OpenPaymentGateway\Gateway\Config\Config;
/**
* Class MagentoTransferFactory
*
* Builds gateway transfer object
*/
class MagentoTransferFactory implements TransferFactoryInterface
{
/**
* @var TransferBuilder
*/
protected $transferBuilder;
/**
* @var Config
*/
protected $config;
/**
* @param TransferBuilder $transferBuilder
* @param Config $config
*/
public function __construct(
TransferBuilder $transferBuilder,
Config $config
) {
$this->transferBuilder = $transferBuilder;
$this->config = $config;
}
/**
* Builds gateway transfer object
*
* @param array <mixed> $request
* @return TransferInterface
*/
public function create(array $request)
{
return $this->transferBuilder
->setBody($request)
->setMethod(Config::METHOD_POST)
->setHeaders(
[
Config::TXN_ID => $request[Config::TXN_ID]
]
)
->build();
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Gateway\Http;
use Magento\Payment\Gateway\Data\PaymentDataObjectInterface;
use Payoneer\OpenPaymentGateway\Gateway\Config\Config;
/**
* Class RefundTransferFactory
*
* Builds refund transfer object
*/
class RefundTransferFactory extends TransferFactory
{
/**
* @inheritDoc
*/
protected function getApiUri(PaymentDataObjectInterface $payment)
{
$payment = $payment->getPayment();
$captureResponse = $payment->getAdditionalInformation('capture_response');
if ($captureResponse) {
$longId = isset($captureResponse['longId']) ? $captureResponse['longId'] : null;
} else {
$longId = $payment->getAdditionalInformation('longId');
}
return sprintf(
Config::REFUND_END_POINT,
$longId
);
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Gateway\Http;
use Magento\Payment\Gateway\Data\PaymentDataObjectInterface;
use Magento\Payment\Gateway\Http\TransferBuilder;
use Magento\Payment\Gateway\Http\TransferInterface;
use Payoneer\OpenPaymentGateway\Gateway\Config\Config;
/**
* Class TransferFactory
*
* Builds gateway transfer object
*/
class TransferFactory implements TransferFactoryInterface
{
/**
* @var TransferBuilder
*/
protected $transferBuilder;
/**
* @var Config
*/
protected $config;
/**
* @var string
*/
protected $method;
/**
* @param TransferBuilder $transferBuilder
* @param Config $config
* @param string $method
*/
public function __construct(
TransferBuilder $transferBuilder,
Config $config,
$method
) {
$this->transferBuilder = $transferBuilder;
$this->config = $config;
$this->method = $method;
}
/**
* Builds gateway transfer object
*
* @param array <mixed> $request
* @param PaymentDataObjectInterface $payment
* @return TransferInterface
*/
public function create(array $request, PaymentDataObjectInterface $payment)
{
$merchantCode = $this->config->getValue('merchant_gateway_key');
$apiKey = $this->config->getCredentials('api_key');
$hostName = $this->config->getCredentials('host_name');
return $this->transferBuilder
->setBody($request)
->setAuthUsername($merchantCode)
->setAuthPassword($apiKey)
->setMethod($this->getMethod())
->setUri($this->getApiUri($payment))
->setHeaders(
$this->config->prepareHeaders($merchantCode, $apiKey)
)->setClientConfig(['host_name' => $hostName])
->build();
}
/**
* Return the api uri
*
* @param PaymentDataObjectInterface $payment
* @return string
*/
protected function getApiUri(PaymentDataObjectInterface $payment)
{
if ($this->method == Config::METHOD_POST) {
return Config::LIST_END_POINT;
} else {
$additionalInformation = $payment->getPayment()->getAdditionalInformation();
$listId = isset($additionalInformation['listId']) ? $additionalInformation['listId'] : null;
return sprintf(
Config::LIST_UPDATE_END_POINT,
$listId
);
}
}
/**
* Return the method
*
* @return string
*/
protected function getMethod()
{
return $this->method;
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Gateway\Http;
use Magento\Payment\Gateway\Data\PaymentDataObjectInterface;
use Magento\Payment\Gateway\Http\TransferInterface;
interface TransferFactoryInterface
{
/**
* Build gateway transfer object
*
* @param array <mixed> $request
* @param PaymentDataObjectInterface $payment
*
* @return TransferInterface
*/
public function create(array $request, PaymentDataObjectInterface $payment);
}
<?php
namespace Payoneer\OpenPaymentGateway\Gateway;
use Magento\Payment\Gateway\Data\Quote\AddressAdapterFactory;
use Magento\Quote\Api\Data\CartInterface;
use Magento\Quote\Api\Data\CartItemInterface;
use Magento\Quote\Model\Quote;
use Magento\Quote\Model\Quote\Address;
use Magento\Payment\Gateway\Data\Quote\QuoteAdapter as MagentoQuoteAdapter;
/**
* Class QuoteAdapter
* Gets the details from quote
*/
class QuoteAdapter extends MagentoQuoteAdapter
{
/** @var Quote */
protected $quote;
/**
* QuoteAdapter constructor.
* @param CartInterface $quote
* @param AddressAdapterFactory $addressAdapterFactory
*/
public function __construct(CartInterface $quote, AddressAdapterFactory $addressAdapterFactory)
{
parent::__construct($quote, $addressAdapterFactory);
$this->quote = $quote;/** @phpstan-ignore-line */
}
/**
* Get remote ip
* @return string|null
*/
public function getRemoteIp()
{
return $this->quote->getRemoteIp();
}
/**
* Get shipping amount
* @return float
*/
public function getShippingAmount()
{
return $this->getAddressModel()->getBaseShippingAmount();
}
/**
* Get tax amount
* @return float
*/
public function getTaxAmount()
{
return $this->getAddressModel()->getTaxAmount();
}
/**
* Get shipping amount inclusive tax
* @return float
*/
public function getShippingAmountInclTax()
{
return $this->getAddressModel()->getBaseShippingInclTax();
}
/**
* Get discount amount
* @return float
*/
public function getDiscountAmount()
{
return $this->getAddressModel()->getBaseDiscountAmount();
}
/**
* @return Address
*/
protected function getAddressModel()
{
return $this->quote->isVirtual() ? $this->quote->getBillingAddress() : $this->quote->getShippingAddress();
}
/**
* Get quote items
* @return array|CartItemInterface[]|null
*/
public function getItems()
{
$items = parent::getItems();
if (!$items) {
$items = $this->quote->getAllItems();
}
return $items;
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Gateway\Request;
use Magento\Checkout\Model\Session;
use Magento\Payment\Gateway\Data\AddressAdapterInterface;
use Magento\Payment\Gateway\Helper\SubjectReader;
use Magento\Payment\Gateway\Request\BuilderInterface;
use Payoneer\OpenPaymentGateway\Gateway\Config\Config;
/**
* Class AddressDataBuilder
* Class to build address data
*/
class AddressDataBuilder implements BuilderInterface
{
/**
* Billing address data constants
*/
const FIRST_NAME = 'firstname';
const LAST_NAME = 'lastname';
const MIDDLE_NAME = 'middlename';
const EMPTY_STRING = '';
const SHIPPING = 'shipping';
const BILLING = 'billing';
/**
* @var Session
*/
private $checkoutSession;
/**
* @var Config
*/
private $config;
/**
* @param Session $checkoutSession
* @param Config $config
*/
public function __construct(
Session $checkoutSession,
Config $config
) {
$this->checkoutSession = $checkoutSession;
$this->config = $config;
}
/**
* Builds address data
*
* @param array <mixed> $buildSubject
* @return array <mixed>
*/
public function build(array $buildSubject)
{
$payment = SubjectReader::readPayment($buildSubject);
$order = $payment->getOrder();
$billingAddress = $order->getBillingAddress();
$shippingAddress = $order->getShippingAddress();
$billingAddressChanged = false;
$address = isset($buildSubject['address']) ? $buildSubject['address'] : null;
if ($address) {
$billingAddress = $address;
$billingAddressChanged = true;
}
$shippingAddressCountryId = $shippingAddress ? $shippingAddress->getCountryId() : null;
if ($shippingAddressCountryId) {
$this->checkoutSession->setShippingCountryId($shippingAddressCountryId);
}
return [
Config::CUSTOMER => [
Config::ADDRESSES => [
Config::SHIPPING => $shippingAddress ? $this->getAddressData($shippingAddress, self::SHIPPING) : [],
Config::BILLING => $billingAddressChanged ?
$this->getNewBillingAddress($billingAddress) :
$this->getAddressData($billingAddress, self::BILLING)
]
]
];
}
/**
* Gets address details
*
* @param AddressAdapterInterface $address
* @param string $type
* @return array <mixed>
*/
public function getAddressData($address, $type)
{
return [
Config::STREET => $address->getStreetLine1() ?: null,
Config::HOUSE_NUMBER => $address->getStreetLine2(),
Config::ZIP => $address->getPostcode(),
Config::CITY => $address->getCity(),
Config::STATE => $address->getRegionCode(),
Config::COUNTRY => $this->getCountryId($address, $type),
Config::NAME => [
Config::FIRST_NAME => $address->getFirstname(),
Config::MIDDLE_NAME => $address->getMiddlename(),
Config::LAST_NAME => $address->getLastname()
]
];
}
/**
* @param AddressAdapterInterface $address
* @param string $type
* @return string
*/
public function getCountryId($address, $type)
{
$countryId = $address->getCountryId();
if (!$countryId) {
switch ($type) {
case self::SHIPPING:
$countryId = $this->checkoutSession->getShippingCountryId() ?: $this->config->getCountryByStore();
break;
case self::BILLING:
$countryId = $this->checkoutSession->getBillingCountryId() ?: $this->config->getCountryByStore();
break;
}
}
return $countryId;
}
/**
* Build new billing address data
* @param array <mixed> $billingAddress
* @return array <mixed>
*/
public function getNewBillingAddress($billingAddress)
{
$billingAddressCountryId = isset($billingAddress[Config::COUNTRY_ID]) ?
$billingAddress[Config::COUNTRY_ID] : $this->checkoutSession->getBillingCountryId();
return [
Config::STREET => isset($billingAddress[Config::STREET][0]) ?
$billingAddress[Config::STREET][0] : self::EMPTY_STRING,
Config::HOUSE_NUMBER => isset($billingAddress[Config::STREET][1]) ?
$billingAddress[Config::STREET][1] : self::EMPTY_STRING,
Config::ZIP => isset($billingAddress[Config::POSTCODE]) ?
$billingAddress[Config::POSTCODE] : self::EMPTY_STRING,
Config::CITY => isset($billingAddress[Config::CITY]) ?
$billingAddress[Config::CITY] : self::EMPTY_STRING,
Config::STATE => isset($billingAddress[Config::REGION]) ?
$billingAddress[Config::REGION] : self::EMPTY_STRING,
Config::COUNTRY => $billingAddressCountryId ?: $this->config->getCountryByStore(),
Config::NAME => [
Config::FIRST_NAME => isset($billingAddress[self::FIRST_NAME]) ?
$billingAddress[self::FIRST_NAME] : self::EMPTY_STRING,
Config::MIDDLE_NAME => isset($billingAddress[self::MIDDLE_NAME]) ?
$billingAddress[self::MIDDLE_NAME] : self::EMPTY_STRING,
Config::LAST_NAME => isset($billingAddress[self::LAST_NAME]) ?
$billingAddress[self::LAST_NAME] : self::EMPTY_STRING
]
];
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Gateway\Request;
use Magento\Payment\Gateway\Helper\SubjectReader;
use Magento\Payment\Gateway\Request\BuilderInterface;
use Payoneer\OpenPaymentGateway\Gateway\Config\Config;
/**
* Class AdminTransactionIDDataBuilder
* Builds transaction request
*/
class AdminTransactionIDDataBuilder implements BuilderInterface
{
/**
* Builds transaction id
*
* @param array <mixed> $buildSubject
* @return array <mixed>
*/
public function build(array $buildSubject)
{
$payment = SubjectReader::readPayment($buildSubject);
return [
Config::TRANSACTION_ID => $payment->getOrder()->getId() . strtotime('now')
];
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Gateway\Request;
use Magento\Framework\App\RequestInterface as Request;
use Magento\Framework\Module\ModuleListInterface;
use Magento\Payment\Gateway\Data\PaymentDataObjectInterface;
use Magento\Payment\Gateway\Helper\SubjectReader;
use Magento\Payment\Gateway\Request\BuilderInterface;
use Payoneer\OpenPaymentGateway\Gateway\Config\Config;
use Payoneer\OpenPaymentGateway\Model\Adminhtml\Source\Fields;
/**
* Class BaseRequestDataBuilder
* Builds base request data
*/
class BaseRequestDataBuilder implements BuilderInterface
{
/**
* @var Config
*/
private $config;
/**
* @var Request
*/
private $request;
/**
* @var ModuleListInterface
*/
private $moduleList;
/**
* @param Config $config
* @param Request $request
*/
public function __construct(
Config $config,
Request $request,
ModuleListInterface $moduleList
) {
$this->config = $config;
$this->request = $request;
$this->moduleList = $moduleList;
}
/**
* Builds base request data
*
* @param array <mixed> $buildSubject
* @return array <mixed>
*/
public function build(array $buildSubject)
{
$payment = SubjectReader::readPayment($buildSubject);
return [
Config::TRANSACTION_ID => $payment->getPayment()->getAdditionalInformation(Config::TXN_ID),
Config::COUNTRY => $this->getCountryId($payment),
Config::INTEGRATION => $this->getPaymentFlow(),
Config::DIVISION => $this->config->getValue('environment') == Fields::ENVIRONMENT_SANDBOX_VALUE
? $this->config->getValue('sandbox_store_code')
: $this->config->getValue('live_store_code'),
Config::ALLOW_DELETE => true,
//system attributes
"system" => [
"type" => "SHOP_PLATFORM",
"code" => "MAGENTO",
"version" => $this->moduleList->getOne('Payoneer_OpenPaymentGateway')['setup_version']
]
];
}
/**
* @param PaymentDataObjectInterface $payment
* @return string
*/
private function getCountryId($payment)
{
$order = $payment->getOrder();
//use country of shipping address if it exists, else billing address or store country
$shippingAddress = $order->getShippingAddress();
if (isset($shippingAddress) && $shippingAddress->getCountryId()) {
$countryId = $shippingAddress->getCountryId();
} else {
$billingAddress = $order->getBillingAddress();
if (isset($billingAddress) && $billingAddress->getCountryId()) {
$countryId = $billingAddress->getCountryId();
} else {
$countryId = $this->config->getCountryByStore();
}
}
return $countryId;
}
/**
* Build payment flow from request
*
* @return string
*/
private function getPaymentFlow()
{
$integration = $this->request->getParam(Config::INTEGRATION);
if ($integration == Config::INTEGRATION_HOSTED) {
return Config::HOSTED;
}
if ($integration == Config::INTEGRATION_EMBEDDED) {
return Config::SELECT_NATIVE;
}
return $this->config->getValue(Config::PAYMENT_FLOW);
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Gateway\Request;
use Magento\Checkout\Model\Session;
use Magento\Framework\UrlInterface;
use Magento\Payment\Gateway\Helper\SubjectReader;
use Magento\Payment\Gateway\Request\BuilderInterface;
use Payoneer\OpenPaymentGateway\Gateway\Config\Config;
/**
* Class CallBackBuilder
* Builds Callback array
*/
class CallBackDataBuilder implements BuilderInterface
{
/**
* @var UrlInterface
*/
private $urlBuilder;
/**
* @var Session
*/
private $checkoutSession;
/**
* @var Config
*/
protected $config;
/**
* CallBackBuilder constructor.
* @param UrlInterface $urlBuilder
* @param Session $checkoutSession
* @param Config $config
*/
public function __construct(
UrlInterface $urlBuilder,
Session $checkoutSession,
Config $config
) {
$this->urlBuilder = $urlBuilder;
$this->checkoutSession = $checkoutSession;
$this->config = $config;
}
/**
* Builds callback data
*
* @param array <mixed> $buildSubject
* @return array <mixed>
*/
public function build(array $buildSubject)
{
$payment = SubjectReader::readPayment($buildSubject);
$token = $payment->getPayment()->getAdditionalInformation(Config::TOKEN);
$notificationToken = $payment->getPayment()->getAdditionalInformation(Config::TOKEN_NOTIFICATION);
$orderId = $payment->getOrder()->getOrderIncrementId();
$cartId = $this->getQuoteIdFromSession();
$successParams = ['cart_id' => $cartId, 'token' => $token];
$cancelParams = ['cart_id' => $cartId, 'error' => true];
return [
Config::CALLBACK => [
Config::RETURN_URL => $this->urlBuilder->getUrl(Config::RETURN_URL_PATH, $successParams),
Config::CANCEL_URL => $this->urlBuilder->getUrl(Config::CANCEL_URL_PATH, $cancelParams),
Config::NOTIFICATION_URL => $this->getNotificationUrl($orderId, $notificationToken)
]
];
}
/**
* @param string $orderId
* @param mixed $token
* @return string
*/
public function getNotificationUrl($orderId, $token)
{
$configUrl = $this->config->getValue('notification_url');
if ($configUrl) {
$configUrl = $configUrl . '/order_id/' . $orderId . '/token/' . $token;
} else {
$configUrl = $this->urlBuilder->getUrl(
Config::NOTIFICATION_URL_PATH,
['order_id' => $orderId, 'token' => $token]
);
}
return $configUrl;
}
/**
* Gets quote id from the checkout session
* @return int|null
*/
public function getQuoteIdFromSession()
{
$quoteId = null;
if ($this->checkoutSession->hasQuote()) {
$quoteId = $this->checkoutSession->getQuoteId();
}
return $quoteId;
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Gateway\Request;
use Magento\Checkout\Model\Session;
use Magento\Payment\Gateway\Helper\SubjectReader;
use Magento\Payment\Gateway\Request\BuilderInterface;
use Payoneer\OpenPaymentGateway\Gateway\Config\Config;
use Payoneer\OpenPaymentGateway\Model\Helper;
/**
* Class CustomerDataBuilder
* Builds customer data
*/
class CustomerDataBuilder implements BuilderInterface
{
/**
* @var Helper
*/
private $helper;
/**
* @var Session
*/
private $checkoutSession;
/**
* @param Helper $helper
* @param Session $checkoutSession
*/
public function __construct(
Helper $helper,
Session $checkoutSession
) {
$this->helper = $helper;
$this->checkoutSession = $checkoutSession;
}
/**
* Builds customer data
*
* @param array <mixed> $buildSubject
* @return array <mixed>
*/
public function build(array $buildSubject)
{
$payment = SubjectReader::readPayment($buildSubject);
$order = $payment->getOrder();
$billingAddress = $order->getBillingAddress();
$registrationId = null;
$number = null;
$customerId = $order->getCustomerId();
if ($customerId) {
$registrationId = $this->helper->getRegistrationId($customerId);
$number = $customerId;
} else {
$number = $billingAddress ? $billingAddress->getTelephone() : null;
}
$customerEmail = $billingAddress ? $billingAddress->getEmail() : null;
if (!$customerEmail) {
$customerEmail = $this->checkoutSession->getPayoneerCustomerEmail();
}
$customerData = [
Config::CUSTOMER => [
Config::NUMBER => $number,
Config::EMAIL => $customerEmail,
Config::COMPANY => [
Config::NAME => $billingAddress ? $billingAddress->getCompany() : null,
],
Config::NAME => [
Config::FIRST_NAME => $billingAddress ? $billingAddress->getFirstname() : null,
Config::MIDDLE_NAME => $billingAddress ? $billingAddress->getMiddlename() : null,
Config::LAST_NAME => $billingAddress ? $billingAddress->getLastname() : null
]
]
];
if ($registrationId) {
$customerData[Config::CUSTOMER][Config::REGISTRATION] = [
Config::ID => $registrationId
];
}
return $customerData;
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Gateway\Request;
use Magento\Payment\Gateway\Data\OrderAdapterInterface;
use Magento\Payment\Gateway\Helper\SubjectReader;
use Magento\Payment\Gateway\Request\BuilderInterface;
use Magento\Sales\Model\Order\Item;
use Payoneer\OpenPaymentGateway\Gateway\Config\Config;
use Payoneer\OpenPaymentGateway\Gateway\QuoteAdapter as PayoneerQuoteAdapter;
use Payoneer\OpenPaymentGateway\Model\Helper;
/**
* Class ItemsDataBuilder
* Build Item Data
*/
class ItemsDataBuilder implements BuilderInterface
{
const ADJUSTMENTS = 'Total Adjustments';
/**
* @var Helper
*/
private $helper;
/**
* @param Helper $helper
*/
public function __construct(
Helper $helper
) {
$this->helper = $helper;
}
/**
* Builds items data
*
* @param array <mixed> $buildSubject
* @return array <mixed>
*/
public function build(array $buildSubject)
{
$payment = SubjectReader::readPayment($buildSubject);
$order = $payment->getOrder();
$totalItemsCount = isset($buildSubject['totalItemsCount'])
? $buildSubject['totalItemsCount'] : 0;
if ($order->getItems() && $totalItemsCount > 0) {
$items = $this->buildItems($order);
return [
Config::PRODUCTS => $items
];
} else {
return [];
}
}
/**
* Build items
*
* @param OrderAdapterInterface $order
* @return array <mixed>
*/
protected function buildItems($order)
{
$result = [];
/** @var Item[] $items */
$items = $order->getItems();
foreach ($items as $item) {
$result[] = [
Config::SKU => $item->getSku(),
Config::NAME => $item->getName(),
Config::QUANTITY => $item->getData('qty'),
Config::CURRENCY => $order->getCurrencyCode(),
Config::AMOUNT => $this->helper->formatNumber($item->getBaseRowTotal()),
Config::NET_AMOUNT => $this->helper->formatNumber($item->getBaseRowTotal()),
Config::TAX_AMOUNT => $this->helper->formatNumber($item->getBaseTaxAmount()),
Config::TAX_PERCENT => $this->helper->formatNumber($item->getBaseRowTotalInclTax())
];
}
if ($order instanceof PayoneerQuoteAdapter) {
$totalAdjustments = 0.00;
if ($order->getShippingAmountInclTax() > 0) {
$totalAdjustments += $order->getShippingAmountInclTax();
}
if ($order->getDiscountAmount() < 0) {
$totalAdjustments += $order->getDiscountAmount();
}
if ($order->getTaxAmount() > 0) {
$totalAdjustments += $order->getTaxAmount();
}
$result[] = [
Config::NAME => self::ADJUSTMENTS,
Config::AMOUNT => number_format($totalAdjustments, 2)
];
}
return $result;
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Gateway\Request;
use Magento\Payment\Gateway\Request\BuilderInterface;
use Magento\Payment\Gateway\Helper\SubjectReader;
use Payoneer\OpenPaymentGateway\Gateway\Config\Config;
/**
* Class PaymentDataBuilder
* Builds payment data
*/
class PaymentDataBuilder implements BuilderInterface
{
/**
* @var Config
*/
protected $config;
/**
* @param Config $config
*/
public function __construct(
Config $config
) {
$this->config = $config;
}
/**
* Builds payment data
*
* @param array <mixed> $buildSubject
* @return array <mixed>
*/
public function build(array $buildSubject)
{
$payment = SubjectReader::readPayment($buildSubject);
$order = $payment->getOrder();
return [
Config::PAYMENT => [
Config::AMOUNT => number_format($buildSubject[Config::AMOUNT], 2),
Config::CURRENCY => $order->getCurrencyCode(),
Config::REFERENCE => $this->config->getValue('order_reference_message'),
Config::INVOICE_ID => $order->getId()
]
];
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Gateway\Request;
use Magento\Payment\Gateway\Request\BuilderInterface;
use Payoneer\OpenPaymentGateway\Gateway\Config\Config;
use Payoneer\OpenPaymentGateway\Model\Adminhtml\Source\Fields;
/**
* Class PreselectionBuilder
* Builds 'preselection' array
*/
class PreselectionBuilder implements BuilderInterface
{
/**
* Preference array constants
*/
const DEFERRED = 'DEFERRED';
const NON_DEFERRED = 'NON_DEFERRED';
/**
* @var Config
*/
protected $config;
/**
* PreselectionBuilder constructor.
* @param Config $config
*/
public function __construct(
Config $config
) {
$this->config = $config;
}
/**
* Builds preselection data
*
* @param array <mixed> $buildSubject
* @return array <mixed>
*/
public function build(array $buildSubject)
{
return [
Config::PRESELECTION => [
Config::DEFERRAL => $this->config->getValue('payment_action') == Fields::AUTHORIZE
? self::DEFERRED
: self::NON_DEFERRED
]
];
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Gateway\Request;
use Magento\Framework\UrlInterface;
use Magento\Payment\Gateway\Helper\SubjectReader;
use Magento\Payment\Gateway\Request\BuilderInterface;
use Payoneer\OpenPaymentGateway\Gateway\Config\Config;
use Magento\Framework\App\RequestInterface as Request;
/**
* Class StyleDataBuilder
* Builds Style data array
*/
class StyleDataBuilder implements BuilderInterface
{
/**
* @var Config
*/
protected $config;
/**
* @var UrlInterface
*/
private $urlBuilder;
/**
* @var Request
*/
private $request;
/**
* @param UrlInterface $urlBuilder
* @param Config $config
* @param Request $request
*/
public function __construct(
UrlInterface $urlBuilder,
Config $config,
Request $request
) {
$this->urlBuilder = $urlBuilder;
$this->config = $config;
$this->request = $request;
}
/**
* Builds style data
*
* @param array <mixed> $buildSubject
* @return array <mixed>
*/
public function build(array $buildSubject)
{
$payment = SubjectReader::readPayment($buildSubject);
$styleData = [
Config::STYLE => [
Config::HOSTED_VERSION => Config::VERSION_V4,
Config::LANGUAGE => $this->config->getStoreLocale()
]
];
if ($this->isHostedIntegration()) {
return $styleData;
} else {
$styleDataValues = $this->config->getStyleConfig();
$styleData[Config::STYLE]['resolution'] = Config::RESOLUTION_1X;
if (isset($styleDataValues['background-color'])) {
$styleData[Config::STYLE]['primaryColor'] = $styleDataValues['background-color'];
}
$styleData[Config::STYLE]['cssOverride'] =
$this->urlBuilder->getUrl(Config::EMBEDDED_STYLE_PATH);
}
return $styleData;
}
/**
* @return bool
*/
private function isHostedIntegration()
{
$integration = $this->request->getParam(Config::INTEGRATION);
if ($integration == Config::INTEGRATION_HOSTED) {
return true;
}
if ($this->config->getValue(Config::PAYMENT_FLOW) == Config::HOSTED) {
return true;
}
return false;
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Gateway\Request;
use Magento\Payment\Gateway\Helper\SubjectReader;
use Magento\Payment\Gateway\Request\BuilderInterface;
use Payoneer\OpenPaymentGateway\Gateway\Config\Config;
/**
* Class TransactionIDDataBuilder
* Builds magento transaction request
*/
class TransactionIDDataBuilder implements BuilderInterface
{
/**
* Builds transaction id
*
* @param array <mixed> $buildSubject
* @return array <mixed>
*/
public function build(array $buildSubject)
{
$payment = SubjectReader::readPayment($buildSubject);
return [
Config::TXN_ID => $payment->getPayment()->getAdditionalInformation(Config::TXN_ID)
];
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Gateway\Response;
use Magento\Checkout\Model\Session;
use Magento\Framework\Exception\LocalizedException;
use Magento\Payment\Gateway\Helper\SubjectReader;
use Magento\Payment\Gateway\Response\HandlerInterface;
use Magento\Sales\Model\Order\Payment;
use Magento\Sales\Model\Order\Payment\Transaction;
/**
* Class PayoneerResponseHandler
*
* Payoneer gateway response handler
*/
class PayoneerResponseHandler implements HandlerInterface
{
const ADDITIONAL_INFO_KEY_REFUND_RESPONSE = 'refund_response';
const ADDITIONAL_INFO_KEY_PARTIAL_REFUND_RESPONSE = 'partial_refund_response';
const ADDITIONAL_INFO_KEY_CAPTURE_RESPONSE = 'capture_response';
const ADDITIONAL_INFO_KEY_AUTH_CANCEL_RESPONSE = 'auth_cancel_response';
const AUTH_CANCEL_STATUS_NODE = 'auth_cancel_status';
const AUTH_CAPTURE_STATUS_NODE = 'capture_status';
const REFUND_TXN_TYPE = 'refund';
/**
* @var SubjectReader
*/
private $subjectReader;
/**
* @var mixed|string
*/
private $additionalInfoKey;
/**
* @var mixed|string
*/
private $actionSuccessResponseKey;
/**
* @var mixed
*/
private $transactionType;
/**
* @var Session
*/
private $session;
/**
* PayoneerResponseHandler constructor.
*
* @param SubjectReader $subjectReader
* @param Session $checkoutSession
* @param string|mixed $additionalInfoKey
* @param string|mixed $actionSuccessResponseKey
* @param string $transactionType
*/
public function __construct(
SubjectReader $subjectReader,
Session $checkoutSession,
$additionalInfoKey = '',
$actionSuccessResponseKey = '',
$transactionType = ''
) {
$this->subjectReader = $subjectReader;
$this->session = $checkoutSession;
$this->additionalInfoKey = $additionalInfoKey;
$this->actionSuccessResponseKey = $actionSuccessResponseKey;
$this->transactionType = $transactionType;
}
/**
* Handle the response.
*
* @param array <mixed> $handlingSubject
* @param array <mixed> $response
* @return void
* @throws LocalizedException
*/
public function handle(array $handlingSubject, array $response)
{
$paymentDO = $this->subjectReader->readPayment($handlingSubject);
/** @var Payment $orderPayment */
$orderPayment = $paymentDO->getPayment();
if ($this->session->getFetchNotificationResponse()) {
$additionalInfo = $this->session->getFetchNotificationResponse();
$longId = $additionalInfo['longId'];
$orderPayment->setTransactionId($longId . '- refund');
} else {
$additionalInfo = $this->buildAdditionalInfoDataFromResponse($response);
if ($this->transactionType == self::REFUND_TXN_TYPE) {
$longId = $response['response']['identification']['longId'];
$orderPayment->setTransactionId($longId . '- refund');
}
}
if ($this->additionalInfoKey == self::ADDITIONAL_INFO_KEY_REFUND_RESPONSE) {
$paymentAdditionalInfo = $orderPayment->getAdditionalInformation();
$refundResponse = isset($paymentAdditionalInfo[$this->additionalInfoKey]) ?
$paymentAdditionalInfo[$this->additionalInfoKey] : [];
$refundResponse[] = $additionalInfo;
$orderPayment->setAdditionalInformation(
$this->additionalInfoKey,
$refundResponse
);
} else {
$paymentAdditionalInfo = $orderPayment->getAdditionalInformation();
$mergedInfo = array_merge($paymentAdditionalInfo, $additionalInfo);
$orderPayment->setAdditionalInformation($mergedInfo);
}
$orderPayment->setTransactionAdditionalInfo(
Transaction::RAW_DETAILS,
$additionalInfo
);
}
/**
* Build the payment additional info data.
*
* @param array <mixed> $response
* @return array <mixed>
*/
public function buildAdditionalInfoDataFromResponse($response)
{
$additionalInfo = [];
if (isset($response['response'])) {
$additionalInfo = [
'resultinfo' => $response['response']['resultInfo'],
'returncode_name' => $response['response']['returnCode']['name'],
'returncode_source' => $response['response']['returnCode']['source'],
'status_code' => $response['response']['status']['code'],
'status_reason' => $response['response']['status']['reason'],
'interaction_code' => $response['response']['interaction']['code'],
'interaction_reason' => $response['response']['interaction']['reason'],
'longId' => $response['response']['identification']['longId'],
'shortId' => $response['response']['identification']['shortId'],
'transactionId' => $response['response']['identification']['transactionId'],
'amount' => $response['response']['payment']['amount']
];
}
if (!empty($this->actionSuccessResponseKey)) {
$additionalInfo[$this->actionSuccessResponseKey] = 'success';
}
return $additionalInfo;
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Gateway\Response;
use Magento\Payment\Gateway\Response\HandlerInterface;
use Magento\Sales\Model\Order\Payment;
use Payoneer\OpenPaymentGateway\Gateway\Config\Config;
use Magento\Payment\Gateway\Helper\SubjectReader;
use Magento\Sales\Model\Order\Payment\Transaction;
/**
* Class ResponseHandler
* Payoneer Response Handler
*/
class ResponseHandler implements HandlerInterface
{
/**
* @var SubjectReader
*/
private $subjectReader;
/**
* ResponseHandler constructor.
* @param SubjectReader $subjectReader
*/
public function __construct(
SubjectReader $subjectReader
) {
$this->subjectReader = $subjectReader;
}
/**
* Handles response
*
* @param array <mixed> $handlingSubject
* @param array <mixed> $response
* @return void
*/
public function handle(array $handlingSubject, array $response)
{
$paymentDO = $this->subjectReader->readPayment($handlingSubject);
/** @var Payment $orderPayment */
$orderPayment = $paymentDO->getPayment();
$orderPayment->setTransactionId($response['response'][Config::TXN_ID]);
$orderPayment->setIsTransactionClosed(false);
$additionalInfo = $orderPayment->getAdditionalInformation();
$orderPayment->setTransactionAdditionalInfo(
Transaction::RAW_DETAILS,
$additionalInfo
);
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Gateway\Validator;
use Magento\Payment\Gateway\Helper\SubjectReader;
use Magento\Payment\Gateway\Validator\AbstractValidator;
use Magento\Payment\Gateway\Validator\ResultInterface;
use Magento\Payment\Gateway\Validator\ResultInterfaceFactory;
/**
* Class ResponseValidator
* Payoneer Response Validator
*/
class ResponseValidator extends AbstractValidator
{
const REFUND_PAID_OUT_STATUS = 'paid_out';
const REFUND_PAID_OUT_PARTIAL = 'paid_out_partial';
const CAPTURE_STATUS = 'charged';
const REFUND_CREDITED = 'refund_credited';
const AUTH_CANCEL_PENDING_STATUS = 'pending';
const CANCELLATION_REQUESTED = 'cancelation_requested';
const AUTH_CANCELLED_STATUS = 'canceled';
const PREAUTHORIZATION_CANCELLED = 'preauthorization_canceled';
/**
* @var bool
*/
private $skipValidation;
/**
* @var mixed
*/
private $successStatusCode;
/**
* @param ResultInterfaceFactory $resultFactory
* @param bool $skipValidation
* @param mixed $successStatusCode
* @return void
*/
public function __construct(
ResultInterfaceFactory $resultFactory,
$skipValidation = false,
$successStatusCode = ''
) {
parent::__construct($resultFactory);
$this->skipValidation = $skipValidation;
$this->successStatusCode = $successStatusCode;
}
/**
* Performs domain-related validation for business object
*
* @param array <mixed> $validationSubject
* @return ResultInterface
*/
public function validate(array $validationSubject): ResultInterface
{
$response = SubjectReader::readResponse($validationSubject);
if ($this->skipValidation == true) {
return $this->createResult(true);
}
if ($response['status'] != 200) {
return $this->createResult(false, [$response['reason']]);
}
if (isset($response['response']['status']['code']) &&
$response['response']['status']['code'] != $this->successStatusCode
) {
return $this->createResult(false, [$response['response']['resultInfo']]);
}
return $this ->createResult(true);
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Http;
use GuzzleHttp\Client;
use GuzzleHttp\ClientFactory;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Psr7\Response;
use GuzzleHttp\Psr7\ResponseFactory;
use Magento\Framework\Webapi\Rest\Request;
/**
* Class PayoneerClient to manage API client communication
*/
class PayoneerClient
{
/**
* @var ResponseFactory
*/
protected $responseFactory;
/**
* @var ClientFactory
*/
protected $clientFactory;
/**
* @var array <mixed>
*/
protected $handlers = [];
/**
* PayoneerClient constructor.
* @param ClientFactory $clientFactory
* @param ResponseFactory $responseFactory
*/
public function __construct(
ClientFactory $clientFactory,
ResponseFactory $responseFactory
) {
$this->clientFactory = $clientFactory;
$this->responseFactory = $responseFactory;
}
/**
* @param int|string $method
* @param string $hostname
* @param string $endpoint
* @param array <mixed> $options
* @return mixed
*/
public function send(
$method,
$hostname,
$endpoint,
array $options = []
) {
$response = $this->doRequest($hostname, $endpoint, $options, $method);
$responseBody = $response->getBody();
$responseBody->rewind();
$responseObject = new \Magento\Framework\DataObject();
$responseObject->setData('status', $response->getStatusCode());
$responseObject->setData('reason', $response->getReasonPhrase());
$responseObject->setData('response', json_decode($responseBody->getContents(), true));
return $responseObject;
}
/**
* Do API request with provided params
*
* @param string $hostname
* @param string $endpoint
* @param array <mixed> $options
* @param string|int $requestMethod
* @return mixed
*/
private function doRequest(
$hostname,
$endpoint,
$options = [],
$requestMethod = Request::HTTP_METHOD_GET
) {
try {
$uriEndpoint = $hostname . $endpoint;
$client = $this->clientFactory->create(['config' => [
'base_uri' => $hostname
]]);
$response = $client->request(
(string)$requestMethod,
$uriEndpoint,
$options
);
} catch (GuzzleException $exception) {
$response = $this->responseFactory->create([
'status' => $exception->getCode(),
'reason' => $exception->getMessage()
]);
}
return $response;
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Logger;
use Magento\Framework\Filesystem\DriverInterface;
use Magento\Framework\Logger\Handler\Base as BaseHandler;
/**
* Logger handler class for Payoneer
*/
class Handler extends BaseHandler
{
/**
* @var string
*/
protected $fileName;
/**
* Handler constructor.
* @param DriverInterface $filesystem
* @param string $fileName
* @param null $filePath
* @throws \Exception
*/
public function __construct(
DriverInterface $filesystem,
$fileName,
$filePath = null
) {
$this->fileName = $fileName;
parent::__construct($filesystem, $filePath);
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Logger;
use Payoneer\OpenPaymentGateway\Model\Helper;
use Monolog\Handler\HandlerInterface;
use Monolog\Logger;
use Payoneer\OpenPaymentGateway\Gateway\Config\Config;
/**
* NotificationLogger class
* Log the notification related errors
*/
class NotificationLogger extends Logger
{
/**
* @var array <mixed>
*/
private $generalInfo = [];
/**
* @var Helper
*/
private $helper;
/**
* @var Config
*/
private $config;
/**
* NotificationLogger construct
*
* @param Helper $helper
* @param Config $config
* @param string $name The logging channel
* @param HandlerInterface[] $handlers Optional stack of handlers
* @param callable[] $processors Optional array of processors
* @return void
*/
public function __construct(
Helper $helper,
Config $config,
$name,
array $handlers = [],
array $processors = []
) {
parent::__construct($name, $handlers, $processors);
$this->helper = $helper;
$this->config = $config;
}
/**
* Adds a log record at the ERROR level.
*
* @param string $message The log message
* @param array <mixed> $context The log context
* @return bool Whether the record has been processed
*/
public function addError($message, array $context = [])
{
if (!$this->config->isDebuggingEnabled()) {
return true;
}
$this->prepareGeneralInfo();
$message = $this->addGeneralInfoToMessage($message);
return $this->addRecord(static::ERROR, $message, $context);
}
/**
* Prepare the general info data for logging.
*
* @return void
*/
private function prepareGeneralInfo()
{
if (empty($this->generalInfo)) {
$this->generalInfo = $this->helper->getProductMetaData();
}
}
/**
* Append the general info data before the message.
*
* @param string $message
* @return string
*/
private function addGeneralInfoToMessage($message)
{
$finalMessage = '[';
foreach ($this->generalInfo as $key => $value) {
$finalMessage .= $key . '=' . $value . '|';
}
$finalMessage .= ']' . $message;
return $finalMessage;
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Model\Adminhtml;
use Magento\Framework\DB\Transaction;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Message\ManagerInterface;
use Magento\Sales\Api\Data\OrderInterface;
use Magento\Sales\Api\Data\OrderPaymentInterface;
use Magento\Sales\Api\Data\TransactionSearchResultInterfaceFactory;
use Magento\Sales\Api\OrderRepositoryInterface;
use Magento\Sales\Model\Order;
use Magento\Sales\Model\Order\Email\Sender\InvoiceSender;
use Magento\Sales\Model\Order\Invoice;
use Magento\Sales\Model\Order\Payment;
use Magento\Sales\Model\Service\InvoiceService;
use Payoneer\OpenPaymentGateway\Gateway\Config\Config;
use Payoneer\OpenPaymentGateway\Gateway\Response\PayoneerResponseHandler;
use Payoneer\OpenPaymentGateway\Model\Ui\ConfigProvider;
/**
* Class Helper
*
* Module helper file for backend
*/
class Helper
{
const AUTHORIZATION = 'authorization';
const CAPTURE = 'capture';
const CHARGED = 'charged';
const PAID_OUT_PARTIAL = 'paid_out_partial';
const DEBITED = 'debited';
const CAPTURE_CLOSED = 'closed';
const SYSTEM_FAILURE = 'SYSTEM_FAILURE';
/**
* @var TransactionSearchResultInterfaceFactory
*/
protected $transactions;
/**
* @var OrderRepositoryInterface
*/
protected $orderRepository;
/**
* @var Config
*/
protected $config;
/**
* @var OrderInterface
*/
protected $order;
/**
* @var ManagerInterface
*/
protected $messageManager;
/**
* @var InvoiceService
*/
protected $invoiceService;
/**
* @var Transaction
*/
protected $transaction;
/**
* @var InvoiceSender
*/
protected $invoiceSender;
/**
* Helper constructor.
* @param TransactionSearchResultInterfaceFactory $transactions
* @param OrderRepositoryInterface $orderRepository
* @param ManagerInterface $messageManager
* @param InvoiceService $invoiceService
* @param InvoiceSender $invoiceSender
* @param Transaction $transaction
* @param Config $config
*/
public function __construct(
TransactionSearchResultInterfaceFactory $transactions,
OrderRepositoryInterface $orderRepository,
ManagerInterface $messageManager,
InvoiceService $invoiceService,
InvoiceSender $invoiceSender,
Transaction $transaction,
Config $config
) {
$this->transactions = $transactions;
$this->orderRepository = $orderRepository;
$this->messageManager = $messageManager;
$this->invoiceService = $invoiceService;
$this->invoiceSender = $invoiceSender;
$this->transaction = $transaction;
$this->config = $config;
}
/**
* Get the transaction type
* @param OrderPaymentInterface $payment
* @return string
*/
public function getTransactionType($payment)
{
$transactionType = null;
$transactions =
$this->transactions/** @phpstan-ignore-line */
->create()
->addPaymentIdFilter($payment->getEntityId());
$transactionItems = $transactions->getItems();
foreach ($transactionItems as $transaction) {
$transactionType = $transaction->getData('txn_type');
}
return $transactionType;
}
/**
* Check if Payoneer capture button can be shown
*
* @param OrderInterface $order
* @return bool
*/
public function canShowCaptureBtn(OrderInterface $order)
{
if (!$this->isPayoneerEnabled()) {
return false;
} else {
$payment = $order->getPayment();
if ($payment) {
if ($payment->getMethod() !== ConfigProvider::CODE) {
return false;
}
$transactionType = $this->getTransactionType($payment);
switch ($transactionType) {
case self::AUTHORIZATION:
return true;
case self::CAPTURE:
$additionalInformation = $payment->getAdditionalInformation();
if (!isset($additionalInformation['payoneerCapture'])) {
return true;
} else {
return false;
}
}
}
}
return false;
}
/**
* Check if payment id done via Payoneer gateway
*
* @param OrderInterface $order
* @return bool
*/
public function isPayoneerOrder(OrderInterface $order)
{
if (!$this->isPayoneerEnabled()) {
return false;
} else {
$payment = $order->getPayment();
if ($payment) {
if ($payment->getMethod() !== ConfigProvider::CODE) {
return false;
}
}
}
return true;
}
/**
* @param int $orderId
* @return OrderInterface
*/
public function getOrder($orderId)
{
if (!$this->order instanceof OrderInterface) {
$this->order = $this->orderRepository->get($orderId);
}
return $this->order;
}
/**
* Process capture response
* @param array <mixed> $result
* @param Order $order
* @return void
* @throws LocalizedException
*/
public function processCaptureResponse($result, $order)
{
if ($result
&& $result['response']['status']['code'] == self::CHARGED
&& $result['response']['status']['reason'] == self::DEBITED
&& $result['status'] == 200
) {
$payment = $order->getPayment();
if ($payment) {
$additionalInformation = $payment->getAdditionalInformation();
$additionalInformation = array_merge($additionalInformation, ['payoneerCapture' => 'success']);
$payment->setAdditionalInformation($additionalInformation);
}
if ($order->canInvoice()) {
$this->generateInvoice($order);
}
$this->showSuccessMessage(__('Payoneer capture completed successfully'));
} else {
$this->showErrorMessage(__('We couldn\'t capture the transaction. Check the payoneer.log file for details.'));
}
}
/**
* Process capture response
* @param array <mixed> $result
* @param Order $order
* @return void
* @throws LocalizedException
*/
public function processFetchResponse($result, $order)
{
if ($result && $result['status'] == 200) {
$this->showSuccessMessage(__('Payoneer fetch completed successfully'));
} else {
$this->showErrorMessage(__('We couldn\'t fetch the data. Check the payoneer.log file for details.'));
}
}
/**
* Generates invoice
* @param Order $order
* @return void
* @throws LocalizedException
*/
public function generateInvoice($order)
{
if (!$order->getEntityId()) {
throw new LocalizedException(__('The order no longer exists'));
}
if (!$order->canInvoice()) {
throw new LocalizedException(
__('You can\'t create an invoice with this order')
);
}
try {
$invoice = $this->invoiceService->prepareInvoice($order);
if (!$invoice->getTotalQty()) {
throw new LocalizedException(
__('You can\'t create an invoice without products')
);
}
$invoice->setRequestedCaptureCase(Invoice::CAPTURE_ONLINE);
$invoice->register();
$invoice->getOrder()->setCustomerNoteNotify(0);
$invoice->getOrder()->setIsInProcess(true);
$transactionSave =
$this->transaction
->addObject($invoice)
->addObject($invoice->getOrder());
$transactionSave->save();
try {
$this->invoiceSender->send($invoice);
$order->addCommentToStatusHistory(
__('We\'ve notified the customer that the #%1 invoice has been created', $invoice->getId())
)->setIsCustomerNotified(1);
} catch (\Exception $e) {
$this->showErrorMessage(__('We can\'t send an email with the invoice. Try again later.'));
}
} catch (\Exception $e) {
$this->showSuccessMessage(__('We can\'t generate the invoice. Try again later.'));
}
}
/**
* Check if Payoneer gateway is enabled
* @return mixed|null
*/
public function isPayoneerEnabled()
{
return $this->config->isPayoneerEnabled();
}
/**
* Show success message
* @param string $message
* @return void
*/
public function showSuccessMessage($message)
{
$this->messageManager
->addSuccessMessage(__($message));
}
/**
* Show error message
* @param string $message
* @return void
*/
public function showErrorMessage($message)
{
$this->messageManager
->addErrorMessage(__($message));
}
/**
* Check if the order is authorized only order.
*
* @param Order $order
* @return bool
*/
public function canCancelAuthorization($order)
{
if (!$this->canShowCaptureBtn($order)) {
return false;
}
$payment = $order->getPayment();
if ($payment) {
/** @var Payment $payment */
$authCancelResponse = $payment->getAdditionalInformation(
PayoneerResponseHandler::ADDITIONAL_INFO_KEY_AUTH_CANCEL_RESPONSE
);
if (isset($authCancelResponse['auth_cancel_status'])
&& $authCancelResponse['auth_cancel_status'] == 'success') {
return false;
}
}
return true;
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Model\Adminhtml\Source;
use Magento\Framework\Data\OptionSourceInterface;
/**
* Class Fields
* Admin config fields
*/
class Fields implements OptionSourceInterface
{
const ENVIRONMENT_PRODUCTION_LABEL = 'Live';
const ENVIRONMENT_PRODUCTION_VALUE = 'live';
const ENVIRONMENT_SANDBOX_LABEL = 'Test';
const ENVIRONMENT_SANDBOX_VALUE = 'test';
const HOSTED = 'HOSTED';
const SELECTIVE_NATIVE = 'SELECTIVE_NATIVE';
const AUTHORIZE = 'authorize';
const CAPTURE = 'authorize_capture';
const STANDALONE = 'Standalone';
const EMBEDDED = 'Embedded';
const DIRECT_PAYMENT = 'Direct payment';
const DEFERRED = 'Deferred';
const FONT_BOLD = 'bold';
const FONT_LIGHTER = 'lighter';
const FONT_NORMAL = 'normal';
const REGULAR = 'Regular';
const BOLD = 'Bold';
const LIGHTER = 'Lighter';
const LEFT = 'Left';
const RIGHT = 'Right';
const CENTER = 'Center';
const ALIGN_LEFT = 'left';
const ALIGN_RIGHT = 'right';
const ALIGN_CENTER = 'center';
/**
* Possible environment types
*
* @return array <mixed>
*/
public function toOptionArray(): array
{
return [
[
'value' => self::ENVIRONMENT_SANDBOX_VALUE,
'label' => self::ENVIRONMENT_SANDBOX_LABEL
],
[
'value' => self::ENVIRONMENT_PRODUCTION_VALUE,
'label' => self::ENVIRONMENT_PRODUCTION_LABEL
]
];
}
/**
* @return array <mixed>
*/
public function fontWeight(): array
{
return [
[
'value' => self::FONT_LIGHTER,
'label' => self::LIGHTER,
],
[
'value' => self::FONT_NORMAL,
'label' => self::REGULAR
],
[
'value' => self::FONT_BOLD,
'label' => self::BOLD
]
];
}
/**
* @return array <mixed>
*/
public function alignText(): array
{
return [
[
'value' => self::ALIGN_LEFT,
'label' => self::LEFT,
],
[
'value' => self::ALIGN_RIGHT,
'label' => self::RIGHT
],
[
'value' => self::ALIGN_CENTER,
'label' => self::CENTER
]
];
}
/**
* @return array <mixed>
*/
public function paymentAction(): array
{
return [
[
'value' => self::AUTHORIZE,
'label' => self::DEFERRED
],
[
'value' => self::CAPTURE,
'label' => self::DIRECT_PAYMENT
]
];
}
/**
* @return array <mixed>
*/
public function paymentFlow(): array
{
return [
[
'value' => self::HOSTED,
'label' => self::STANDALONE
],
[
'value' => self::SELECTIVE_NATIVE,
'label' => self::EMBEDDED
]
];
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Model\Adminhtml;
use Exception;
use Magento\Payment\Gateway\Command\CommandPoolInterface;
use Magento\Payment\Gateway\Command\ResultInterface;
use Magento\Payment\Gateway\Data\PaymentDataObjectFactory;
use Magento\Sales\Model\Order;
use Magento\Payment\Model\InfoInterface;
/**
* Class TransactionService
*
* Process admin transactions api requests
*/
class TransactionService
{
/**
* @var CommandPoolInterface
*/
protected $commandPool;
/**
* @var PaymentDataObjectFactory
*/
protected $paymentDataObjectFactory;
/**
* TransactionService constructor.
* @param CommandPoolInterface $commandPool
* @param PaymentDataObjectFactory $paymentDataObjectFactory
*/
public function __construct(
CommandPoolInterface $commandPool,
PaymentDataObjectFactory $paymentDataObjectFactory
) {
$this->commandPool = $commandPool;
$this->paymentDataObjectFactory = $paymentDataObjectFactory;
}
/**
* Process Api request
*
* @param Order $order
* @param string $command
* @return ResultInterface|null|bool|array <mixed>
*/
public function process(Order $order, $command)
{
$result = [];
/** @var InfoInterface $payment*/
$payment = $order->getPayment();
try {
$paymentDataObject = $this->paymentDataObjectFactory->create($payment);
/** @var array <mixed> $result */
$result = $this->commandPool->get($command)->execute([
'payment' => $paymentDataObject
]);
return $result;
} catch (Exception $e) {
return $result;
}
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Model\Api;
use Magento\Framework\DataObject;
use Magento\Framework\Webapi\Rest\Request as WebRequest;
use Payoneer\OpenPaymentGateway\Gateway\Config\Config;
use Payoneer\OpenPaymentGateway\Http\PayoneerClient;
/**
* Class Request - Manage Payoneer API requests
*/
class Request
{
/**
* @var Config
*/
protected $config;
/**
* @var PayoneerClient
*/
protected $payoneerHttpClient;
/**
* Request constructor.
* @param Config $config
* @param PayoneerClient $payoneerHttpClient
*/
public function __construct(
Config $config,
PayoneerClient $payoneerHttpClient
) {
$this->config = $config;
$this->payoneerHttpClient = $payoneerHttpClient;
}
/**
* @param int|string $method
* @param string $endPoint
* @param array <mixed> $credentials
* @param array <mixed>|string $data
* @return DataObject
*/
public function send(
$method,
$endPoint,
$credentials,
$data
): DataObject {
$options = [];
if ($method == WebRequest::HTTP_METHOD_GET) {
$options['query'] = $data;
} else {
$options['json'] = $data;
}
$options['headers'] = $this->config->prepareHeaders($credentials['merchantCode'], $credentials['apiKey']);
return $this->payoneerHttpClient->send($method, $credentials['hostName'], $endPoint, $options);
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Model\Api;
/**
* Class Response - Validate API response
*/
class Response
{
/**
* @var array<mixed>
*/
protected $responseCodes = [
'success' => ['200','201','204'],
'invalid_token' => ['401', '403']
];
/**
* @param string $key
* @return array<mixed>
*/
public function getResponseCode(string $key): array
{
return $this->responseCodes[$key];
}
/**
* @param mixed $response
* @return bool|string
*/
public function validateResponse($response)
{
return in_array($response->getStatus(), $this->getResponseCode('success'));
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Model\Config\Source;
use Magento\Framework\Data\OptionSourceInterface;
class PaymentIconType implements OptionSourceInterface
{
const PAYMENT_ICON_STATIC = 'static';
const PAYMENT_ICON_DYNAMIC = 'dynamic';
const PAYMENT_ICON_BOTH = 'both';
/**
* Payment icon types
*
* @return array <mixed>
*/
public function toOptionArray(): array
{
return [
[
'value' => self::PAYMENT_ICON_STATIC,
'label' => __('Static')
],
[
'value' => self::PAYMENT_ICON_DYNAMIC,
'label' => __('Dynamic')
],
[
'value' => self::PAYMENT_ICON_BOTH,
'label' => __('Both')
]
];
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Model\Creditmemo;
use Magento\Framework\Exception\LocalizedException;
use Magento\Sales\Api\InvoiceRepositoryInterface;
use Magento\Sales\Model\Order;
use Magento\Sales\Model\Order\CreditmemoFactory;
use Magento\Sales\Model\Service\CreditmemoService;
class CreditmemoCreator
{
/**
* @var CreditmemoFactory
*/
protected $creditmemoFactory;
/**
* @var CreditmemoService
*/
protected $creditmemoService;
/**
* @var InvoiceRepositoryInterface
*/
protected $invoiceRepository;
/**
* CreditmemoCreator constructor
*
* @param CreditmemoFactory $creditmemoFactory
* @param CreditmemoService $creditmemoService
* @param InvoiceRepositoryInterface $invoiceRepository
* @return void
*/
public function __construct(
CreditmemoFactory $creditmemoFactory,
CreditmemoService $creditmemoService,
InvoiceRepositoryInterface $invoiceRepository
) {
$this->creditmemoFactory = $creditmemoFactory;
$this->creditmemoService = $creditmemoService;
$this->invoiceRepository = $invoiceRepository;
}
/**
* Create creditmemo for full order.
*
* @param Order $order
* @return bool
* @throws LocalizedException
*/
public function create($order)
{
try {
$invoices = $order->getInvoiceCollection();
$invoiceId = 0;
foreach ($invoices as $invoice) {
$invoiceId = $invoice->getId();
}
$invoice = $this->invoiceRepository->get($invoiceId);
$creditmemo = $this->creditmemoFactory->createByOrder($order);
$creditmemo->setData('invoice', $invoice);
$this->creditmemoService->refund($creditmemo, true);
return true;
} catch (\Exception $e) {
throw new LocalizedException(
__(__('We couldn\'t create a credit memo for the %1 order. Try again later.') . $e->getMessage(), $order->getIncrementId())
);
}
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Model;
use Magento\Checkout\Model\Session as CheckoutSession;
use Magento\Checkout\Model\SessionFactory as CheckoutSessionFactory;
use Magento\Framework\App\ProductMetadataInterface;
use Magento\Framework\Controller\Result\Redirect;
use Magento\Framework\Controller\Result\RedirectFactory;
use Magento\Framework\Exception\CouldNotSaveException;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Message\ManagerInterface;
use Magento\Framework\Module\ModuleListInterface;
use Magento\Framework\View\Asset\Repository as AssetRepository;
use Magento\Quote\Api\CartManagementInterface;
use Magento\Quote\Api\CartRepositoryInterface;
use Magento\Quote\Model\Quote;
use Magento\Sales\Api\OrderRepositoryInterface;
use Magento\Sales\Model\Order;
use Magento\Sales\Api\OrderStatusHistoryRepositoryInterface;
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory as ProductCollectionFactory;
use Magento\Catalog\Model\ResourceModel\Product\Collection as ProductCollection;
use Magento\Framework\DataObject;
use Magento\Store\Model\StoreManagerInterface;
/**
* Class Helper
*
* Module helper file
*/
class Helper
{
const MODULE_NAME = 'Payoneer_OpenPaymentGateway';
/**
* @var RedirectFactory
*/
protected $resultRedirectFactory;
/**
* @var ManagerInterface
*/
protected $messageManager;
/**
* @var AssetRepository
*/
protected $assetRepository;
/**
* @var PayoneerTransactionRepository
*/
protected $payoneerTransactionRepository;
/**
* @var ProductMetadataInterface
*/
protected $productMetadata;
/**
* @var ModuleListInterface
*/
protected $moduleList;
/**
* @var CheckoutSession
*/
private $checkoutSession;
/**
* @var CartManagementInterface
*/
private $cartManagement;
/**
* @var CheckoutSessionFactory
*/
private $checkoutSessionFactory;
/**
* @var OrderRepositoryInterface
*/
private $orderRepository;
/**
* @var CartRepositoryInterface
*/
private $cartRepository;
/** @var OrderStatusHistoryRepositoryInterface */
private $orderStatusRepository;
/**
* @var ProductCollectionFactory
*/
private $productCollectionFactory;
/**
* @var StoreManagerInterface
*/
private $storeManager;
/**
* Helper constructor
*
* @param RedirectFactory $resultRedirectFactory
* @param ManagerInterface $messageManager
* @param AssetRepository $assetRepository
* @param PayoneerTransactionRepository $payoneerTransactionRepository
* @param ProductMetadataInterface $productMetadata
* @param ModuleListInterface $moduleList
* @param CheckoutSession $checkoutSession
* @param CartManagementInterface $cartManagement
* @param OrderRepositoryInterface $orderRepository
* @param CheckoutSessionFactory $checkoutSessionFactory
* @param CartRepositoryInterface $cartRepository
* @param OrderStatusHistoryRepositoryInterface $orderStatusRepository
* @param ProductCollectionFactory $productCollectionFactory
* @param StoreManagerInterface $storeManager
* @return void
*/
public function __construct(
RedirectFactory $resultRedirectFactory,
ManagerInterface $messageManager,
AssetRepository $assetRepository,
PayoneerTransactionRepository $payoneerTransactionRepository,
ProductMetadataInterface $productMetadata,
ModuleListInterface $moduleList,
CheckoutSession $checkoutSession,
CartManagementInterface $cartManagement,
OrderRepositoryInterface $orderRepository,
CheckoutSessionFactory $checkoutSessionFactory,
CartRepositoryInterface $cartRepository,
OrderStatusHistoryRepositoryInterface $orderStatusRepository,
ProductCollectionFactory $productCollectionFactory,
StoreManagerInterface $storeManager
) {
$this->resultRedirectFactory = $resultRedirectFactory;
$this->messageManager = $messageManager;
$this->assetRepository = $assetRepository;
$this->payoneerTransactionRepository = $payoneerTransactionRepository;
$this->productMetadata = $productMetadata;
$this->moduleList = $moduleList;
$this->checkoutSession = $checkoutSession;
$this->cartManagement = $cartManagement;
$this->orderRepository = $orderRepository;
$this->checkoutSessionFactory = $checkoutSessionFactory;
$this->cartRepository = $cartRepository;
$this->orderStatusRepository = $orderStatusRepository;
$this->productCollectionFactory = $productCollectionFactory;
$this->storeManager = $storeManager;
}
/**
* Redirects to cart
*
* @param string $message
* @return Redirect
*/
public function redirectToCart($message)
{
$this->messageManager->addErrorMessage($message);
return $this->resultRedirectFactory->create()->setPath('checkout/cart');
}
/**
* @return void
* @throws LocalizedException
*/
public function redirectToReorderCart()
{
$orderItemProductIds = $orderItemsByProductId = [];
$orderId = $this->getLastOrderId();
$storeId = $this->storeManager->getStore()->getId();
try {
/** @var Order $order */
$order = $this->orderRepository->get($orderId);
foreach ($order->getItemsCollection() as $item) {
if ($item->getParentItem() === null) {
$orderItemProductIds[] = $item->getProductId();
$orderItemsByProductId[$item->getProductId()][$item->getId()] = $item;
}
}
$products = $this->getOrderProducts($storeId, $orderItemProductIds);
$session = $this->checkoutSessionFactory->create();
$quote = $session->getQuote();
foreach ($orderItemsByProductId as $productId => $orderItems) {
if (!isset($products[$productId])) {
continue;
}
$product = $products[$productId];
foreach ($orderItems as $orderItem) {
$info = $orderItem->getProductOptionByCode('info_buyRequest');
$info = new DataObject($info);
$info->setQty($orderItem->getQtyOrdered());
$quote->addProduct($product, $info);
}
}
$this->cartRepository->save($quote);
$session->replaceQuote($quote)->unsLastRealOrderId();
} catch (LocalizedException $e) {
throw new LocalizedException(
__($e->getMessage())
);
} catch (\Exception $e) {
throw new LocalizedException(
__($e->getMessage())
);
}
}
/**
* Get the static file's path
* @param string $fileId
* @param array <mixed> $params
* @return string
*/
public function getStaticFilePath($fileId, $params)
{
return $this->assetRepository->getUrlWithParams($fileId, $params);
}
/**
* Save customer registration id
* @param string $registrationId
* @param int $customerId
* @return void
* @throws CouldNotSaveException
*/
public function saveRegistrationId($registrationId, $customerId)
{
$regId = $this->getRegistrationId($customerId);
if (!$regId) {
$payoneerTransactionModel = $this->payoneerTransactionRepository->create();
$payoneerTransactionModel->setCustomerId($customerId);
$payoneerTransactionModel->setRegistrationId($registrationId);
$this->payoneerTransactionRepository->save($payoneerTransactionModel);
}
}
/**
* Get customer registration id
* @param int $customerId
* @return string | null
*/
public function getRegistrationId($customerId)
{
$payoneerTransaction = $this->payoneerTransactionRepository->getByCustomerId($customerId);
return $payoneerTransaction ? $payoneerTransaction->getRegistrationId() : null;
}
/**
* Format amount to 2 decimal points
* @param float|null $amount
* @return string|null
*/
public function formatNumber($amount)
{
return $amount ? number_format($amount, 2, '.', '') : null;
}
/**
* Get Magento application product metadata
*
* @return array <mixed>
*/
public function getProductMetaData()
{
return [
'magentoVersion' => $this->productMetadata->getVersion(),
'magentoEdition' => $this->productMetadata->getEdition(),
'moduleVersion' => $this->getModuleVersion()
];
}
/**
* Find the installed module version
*
* @return mixed
*/
public function getModuleVersion()
{
$module = $this->moduleList->getOne(self::MODULE_NAME);
return $module ? $module ['setup_version'] : null;
}
/**
* Unset custom checkout session variable
* @return void
*/
public function unsetPayoneerCustomerEmailSession()
{
if ($this->checkoutSession->getPayoneerCustomerEmail()) {
$this->checkoutSession->unsPayoneerCustomerEmail();
}
}
/**
* Unset custom checkout session variable
* @return void
*/
public function unsetPayoneerCountryIdSession()
{
if ($this->checkoutSession->getShippingCountryId()) {
$this->checkoutSession->unsShippingCountryId();
}
if ($this->checkoutSession->getBillingCountryId()) {
$this->checkoutSession->unsBillingCountryId();
}
}
/**
* Unset custom checkout session variable
* @return void
*/
public function unsetPayoneerInvalidTxnSession()
{
if ($this->checkoutSession->getPayoneerInvalidTxn()) {
$this->checkoutSession->unsPayoneerInvalidTxn();
}
}
/**
* Set invalid transaction session
*
* @return void
*/
public function setPayoneerInvalidTxnSession()
{
$this->checkoutSession->setPayoneerInvalidTxn(true);
}
/**
* @param Quote $quote
* @return Quote
*/
public function setGuestCustomerEmail($quote)
{
$quote->setCheckoutMethod(CartManagementInterface::METHOD_GUEST);
$customerEmail = $quote->getCustomerEmail() ?: $quote->getBillingAddress()->getEmail();
if (!$customerEmail) {
$quote->setCustomerEmail($this->checkoutSession->getPayoneerCustomerEmail());
}
return $quote;
}
/**
* @param int $cartId
* @throws CouldNotSaveException
* @return void
*/
public function placeOrder($cartId)
{
$this->cartManagement->placeOrder($cartId);
}
/**
* @return mixed|null
*/
public function getLastOrderId()
{
return $this->checkoutSession->getData('last_order_id');
}
/**
* Add comment to the order history
*
* @return void
* @throws CouldNotSaveException
*/
public function addCommentToOrder()
{
$order = $this->orderRepository->get($this->getLastOrderId());
/** @phpstan-ignore-next-line */
$comment = $order->addStatusHistoryComment('Transaction voided.');
$this->orderStatusRepository->save($comment);
}
/**
* Get the product collection from the order item product ids.
*
* @param int $storeId
* @param array <mixed> $orderItemProductIds
* @return array <mixed>
*/
public function getOrderProducts($storeId, array $orderItemProductIds): array
{
/** @var ProductCollection $collection */
$collection = $this->productCollectionFactory->create();
$collection->setStore($storeId)
->addIdFilter($orderItemProductIds)
->addStoreFilter()
->addAttributeToSelect('*')
->joinAttribute('status', 'catalog_product/status', 'entity_id', null, 'inner')
->joinAttribute('visibility', 'catalog_product/visibility', 'entity_id', null, 'inner')
->addOptionsToResult();
return $collection->getItems();
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Model;
use Exception;
use Magento\Framework\Exception\LocalizedException;
use Magento\Payment\Gateway\Command\CommandPoolInterface;
use Magento\Payment\Gateway\Command\ResultInterface;
use Magento\Payment\Gateway\Data\PaymentDataObjectFactory;
use Magento\Payment\Model\InfoInterface;
use Magento\Quote\Model\Quote\Payment;
/**
* Class ListUpdateTransactionService
*
* Process transactions api request for list update
*/
class ListUpdateTransactionService
{
/**
* @var CommandPoolInterface
*/
private $commandPool;
/**
* @var PaymentDataObjectFactory
*/
private $paymentDataObjectFactory;
/**
* ListUpdateTransactionService constructor.
*
* @param CommandPoolInterface $commandPool
* @param PaymentDataObjectFactory $paymentDataObjectFactory
*/
public function __construct(
CommandPoolInterface $commandPool,
PaymentDataObjectFactory $paymentDataObjectFactory
) {
$this->commandPool = $commandPool;
$this->paymentDataObjectFactory = $paymentDataObjectFactory;
}
/**
* Process Api request
*
* @param Payment $payment
* @param string $command
* @return ResultInterface|null|bool|array <mixed>
* @throws LocalizedException
*/
public function process(Payment $payment, $command)
{
try {
/** @var InfoInterface $payment*/
$paymentDataObject = $this->paymentDataObjectFactory->create($payment);
return $this->commandPool->get($command)->execute([
'payment' => $paymentDataObject,
'amount' => $payment->getQuote()->getGrandTotal(), /** @phpstan-ignore-line */
'totalItemsCount' => $payment->getQuote()->getItemsCount() /** @phpstan-ignore-line */
]);
} catch (Exception $e) {
throw new LocalizedException(__($e->getMessage()));
}
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Model\Method;
use Magento\Payment\Gateway\ConfigInterface;
use Psr\Log\LoggerInterface;
use Magento\Framework\App\ProductMetadataInterface;
use Magento\Framework\Module\ModuleListInterface;
/**
* Class Logger for payment related information (request, response, etc.) which is used for debug.
*
* @api
* @since 100.0.2
*/
class Logger
{
const MODULE_NAME = 'Payoneer_OpenPaymentGateway';
const DEBUG_KEYS_MASK = '****';
/**
* @var LoggerInterface
*/
protected $logger;
/**
* @var ConfigInterface|null
*/
private $config;
/**
* @var ProductMetadataInterface
*/
protected $productMetadata;
/**
* @var ModuleListInterface
*/
protected $moduleList;
/**
* @param ProductMetadataInterface $productMetadata
* @param ModuleListInterface $moduleList
* @param LoggerInterface $logger
* @param ConfigInterface|null $config
*/
public function __construct(
ProductMetadataInterface $productMetadata,
ModuleListInterface $moduleList,
LoggerInterface $logger,
ConfigInterface $config = null
) {
$this->productMetadata = $productMetadata;
$this->moduleList = $moduleList;
$this->logger = $logger;
$this->config = $config;
}
/**
* Logs payment related information used for debug
*
* @param array <mixed> $data
* @param array <mixed>|null $maskKeys
* @param bool|null $forceDebug
* @return void
*/
public function debug(array $data, array $maskKeys = null, $forceDebug = null)
{
$data = array_merge($data, $this->getProductMetaData());
$maskKeys = $maskKeys !== null ? $maskKeys : $this->getDebugReplaceFields();
$debugOn = $forceDebug !== null ? $forceDebug : $this->isDebugOn();
if ($debugOn === true) {
$data = $this->filterDebugData(
$data,
$maskKeys
);
$this->logger->debug(var_export($data, true));
}
}
/**
* Returns configured keys to be replaced with mask
*
* @return array <mixed>
*/
private function getDebugReplaceFields()
{
if ($this->config && $this->config->getValue('debugReplaceKeys')) {
return explode(',', $this->config->getValue('debugReplaceKeys'));
}
return [];
}
/**
* Whether debug is enabled in configuration
*
* @return bool
*/
private function isDebugOn()
{
return $this->config && (bool)$this->config->getValue('debug');
}
/**
* Recursive filter data by private conventions
*
* @param array <mixed> $debugData
* @param array <mixed> $debugReplacePrivateDataKeys
* @return array <mixed>
*/
protected function filterDebugData(array $debugData, array $debugReplacePrivateDataKeys)
{
$debugReplacePrivateDataKeys = array_map('strtolower', $debugReplacePrivateDataKeys);
foreach (array_keys($debugData) as $key) {
if (in_array(strtolower($key), $debugReplacePrivateDataKeys)) {
$debugData[$key] = self::DEBUG_KEYS_MASK;
} elseif (is_array($debugData[$key])) {
$debugData[$key] = $this->filterDebugData($debugData[$key], $debugReplacePrivateDataKeys);
}
}
return $debugData;
}
/**
* Get Magento application product metadata
* @return array <mixed>
*/
public function getProductMetaData()
{
return [
'magentoVersion' => $this->productMetadata->getVersion(),
'magentoEdition' => $this->productMetadata->getEdition(),
'moduleVersion' => $this->getModuleVersion()
];
}
/**
* Find the installed module version
*
* @return mixed
*/
public function getModuleVersion()
{
$module = $this->moduleList->getOne(self::MODULE_NAME);
return $module ? $module ['setup_version'] : null;
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Model;
use Magento\Framework\Mail\Template\TransportBuilder;
use Magento\Store\Model\StoreManagerInterface;
use Magento\Framework\App\Area;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\Exception\LocalizedException;
use Magento\Store\Model\ScopeInterface;
class NotificationEmailSender
{
const NOTIFICATION_EMAIL_TEMPLATE_ID = "notification_email_template";
/**
* @var TransportBuilder
*/
private $transportBuilder;
/**
* @var StoreManagerInterface
*/
private $storeManager;
/**
* @var ScopeConfigInterface
*/
private $scopeConfig;
/**
* Initialize dependencies.
*
* @param TransportBuilder $transportBuilder
* @param StoreManagerInterface $storeManager
* @param ScopeConfigInterface $scopeConfig
*/
public function __construct(
TransportBuilder $transportBuilder,
StoreManagerInterface $storeManager,
ScopeConfigInterface $scopeConfig
) {
$this->transportBuilder = $transportBuilder;
$this->storeManager = $storeManager;
$this->scopeConfig = $scopeConfig;
}
/**
* Send notification email
*
* @param array <mixed> $notificationResponse
* @return void
*/
public function send($notificationResponse)
{
$emailTemplateVars = $this->prepareEmailTemplateVars($notificationResponse);
$storeId = $this->storeManager->getStore()->getId();
$fromToAddressData = $this->getSenderDetails($storeId);
try {
$transport = $this->transportBuilder
->setTemplateIdentifier(self::NOTIFICATION_EMAIL_TEMPLATE_ID)
->setTemplateOptions(
[
'area' => Area::AREA_FRONTEND,
'store' => $storeId
]
)
->setTemplateVars($emailTemplateVars)
->setFromByScope($fromToAddressData)
->addTo($fromToAddressData['email'], $fromToAddressData['name'])
->getTransport();
$transport->sendMessage();
} catch (\Exception $e) {
throw new LocalizedException(
__('Error sending email = %1', $e->getMessage())
);
}
}
/**
* Prepare the email template variables.
*
* @param array <mixed> $notificationResponse
* @return array <mixed>
*/
private function prepareEmailTemplateVars($notificationResponse)
{
$emailTemplateVars = [];
$emailTemplateVars['resultCode'] = isset($notificationResponse['resultCode'])
? $notificationResponse['resultCode'] : '';
$emailTemplateVars['longId'] = isset($notificationResponse['longId'])
? $notificationResponse['longId'] : '';
$emailTemplateVars['transactionId'] = isset($notificationResponse['transactionId'])
? $notificationResponse['transactionId'] : '';
$emailTemplateVars['interactionCode'] = isset($notificationResponse['interactionCode'])
? $notificationResponse['interactionCode'] : '';
$emailTemplateVars['notificationId'] = isset($notificationResponse['notificationId'])
? $notificationResponse['notificationId'] : '';
$emailTemplateVars['reasonCode'] = isset($notificationResponse['reasonCode'])
? $notificationResponse['reasonCode'] : '';
$emailTemplateVars['statusCode'] = isset($notificationResponse['statusCode'])
? $notificationResponse['statusCode'] : '';
return $emailTemplateVars;
}
/**
* Get the email sender details
*
* @param int $storeId
* @return array <mixed>
*/
private function getSenderDetails($storeId)
{
$senderData = [];
$senderData['name'] = $this->scopeConfig->getValue(
'trans_email/ident_general/name',
ScopeInterface::SCOPE_STORE,
$storeId
);
$senderData['email'] = $this->scopeConfig->getValue(
'trans_email/ident_general/email',
ScopeInterface::SCOPE_STORE,
$storeId
);
return $senderData;
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Model;
use Payoneer\OpenPaymentGateway\Model\ResourceModel\PayoneerNotification as PayoneerNotificationResource;
use Magento\Framework\Model\AbstractModel;
use Payoneer\OpenPaymentGateway\Api\Data\NotificationInterface;
/**
* PayoneerNotification class
*
* Payoneer_notification table model class
*/
class PayoneerNotification extends AbstractModel implements NotificationInterface
{
/**
* Initialize the resource model
*
* @return void
*/
public function _construct()
{
$this->_init(PayoneerNotificationResource::class);
}
/**
* @inheritDoc
*/
public function getTransactionId()
{
return $this->_getData(NotificationInterface::TRANSACTION_ID);
}
/**
* @inheritDoc
*/
public function setTransactionId($txnId)
{
return $this->setData(NotificationInterface::TRANSACTION_ID, $txnId);
}
/**
* @inheritDoc
*/
public function getLongId()
{
return $this->_getData(NotificationInterface::LONG_ID);
}
/**
* @inheritDoc
*/
public function setLongId($longId)
{
return $this->setData(NotificationInterface::LONG_ID, $longId);
}
/**
* @inheritDoc
*/
public function getOrderId()
{
return $this->_getData(NotificationInterface::ORDER_ID);
}
/**
* @inheritDoc
*/
public function setOrderId($orderId)
{
return $this->setData(NotificationInterface::ORDER_ID, $orderId);
}
/**
* @inheritDoc
*/
public function getContent()
{
return $this->_getData(NotificationInterface::CONTENT);
}
/**
* @inheritDoc
*/
public function setContent($response)
{
return $this->setData(NotificationInterface::CONTENT, $response);
}
/**
* @inheritDoc
*/
public function getCronStatus()
{
return $this->_getData(NotificationInterface::CRON_STATUS);
}
/**
* @inheritDoc
*/
public function setCronStatus($status)
{
return $this->setData(NotificationInterface::CRON_STATUS, $status);
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Model;
use Exception;
use Magento\Framework\Exception\CouldNotSaveException;
use Payoneer\OpenPaymentGateway\Api\Data\NotificationInterface;
use Payoneer\OpenPaymentGateway\Api\PayoneerNotificationRepositoryInterface;
use Payoneer\OpenPaymentGateway\Model\ResourceModel\PayoneerNotification;
use Payoneer\OpenPaymentGateway\Model\PayoneerNotification as PayoneerNotificationModel;
class PayoneerNotificationRepository implements PayoneerNotificationRepositoryInterface
{
/**
* @var PayoneerNotification
*/
private $notificationResource;
/**
* PayoneerNotificationRepository construct
*
* @param PayoneerNotification $notificationResource
* @return void
*/
public function __construct(
PayoneerNotification $notificationResource
) {
$this->notificationResource = $notificationResource;
}
/**
* @inheriDoc
*/
public function save(NotificationInterface $notification)
{
try {
/** @var PayoneerNotificationModel $notification */
$this->notificationResource->save($notification);
return $notification;
} catch (Exception $e) {
throw new CouldNotSaveException(
__('We couldn\'t save the notification. Try again later.')
);
}
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Model;
use Payoneer\OpenPaymentGateway\Model\ResourceModel\PayoneerTransaction as PayoneerTransactionResource;
use Payoneer\OpenPaymentGateway\Api\Data\PayoneerTransactionInterface;
use Magento\Framework\Model\AbstractModel;
/**
* PayoneerTransaction class
*
* payoneer_payment_transaction table model class
*/
class PayoneerTransaction extends AbstractModel implements PayoneerTransactionInterface
{
/**
* Initialize the resource model
*
* @return void
*/
public function _construct()
{
$this->_init(PayoneerTransactionResource::class);
}
/**
* @inheritDoc
*/
public function getTransactionId()
{
return $this->getData(self::TRANSACTION_ID);
}
/**
* @inheritDoc
*/
public function setTransactionId($transactionId)
{
$this->setData(self::TRANSACTION_ID, $transactionId);
}
/**
* @inheritDoc
*/
public function getCustomerId()
{
return $this->getData(self::CUSTOMER_ID);
}
/**
* @inheritDoc
*/
public function setCustomerId($customerId)
{
$this->setData(self::CUSTOMER_ID, $customerId);
}
/**
* @inheritDoc
*/
public function getRegistrationId()
{
return $this->getData(self::REGISTRATION_ID);
}
/**
* @inheritDoc
*/
public function setRegistrationId($registrationId)
{
$this->setData(self::REGISTRATION_ID, $registrationId);
}
}
<?php
declare(strict_types=1);
namespace Payoneer\OpenPaymentGateway\Model;
use Payoneer\OpenPaymentGateway\Api\PayoneerTransactionRepositoryInterface;
use Payoneer\OpenPaymentGateway\Api\Data\PayoneerTransactionInterface;
use Payoneer\OpenPaymentGateway\Model\ResourceModel\PayoneerTransaction as ResourceModel;
use Magento\Framework\Exception\CouldNotSaveException;
use Payoneer\OpenPaymentGateway\Model\ResourceModel\PayoneerTransaction\CollectionFactory;
/**
* Class PayoneerTransactionRepository
* Repository class for Payoneer transaction
*/
class PayoneerTransactionRepository implements PayoneerTransactionRepositoryInterface
{
/**
* @var ResourceModel
*/
protected $resource;
/**
* @var PayoneerTransactionFactory
*/
protected $modelFactory;
/**
* @var CollectionFactory
*/
protected $collectionFactory;
/**
* PayoneerTransactionRepository constructor.
* @param ResourceModel $resource
* @param PayoneerTransactionFactory $modelFactory
* @param CollectionFactory $collectionFactory
*/
public function __construct(
ResourceModel $resource,
PayoneerTransactionFactory $modelFactory,
CollectionFactory $collectionFactory
) {
$this->resource = $resource;
$this->modelFactory = $modelFactory;
$this->collectionFactory = $collectionFactory;
}
/**
* @param PayoneerTransactionInterface $payoneerTransaction
* @return mixed|PayoneerTransactionInterface
* @throws CouldNotSaveException
*/
public function save(PayoneerTransactionInterface $payoneerTransaction)
{
try {
/** @var PayoneerTransaction $payoneerTransaction */
$this->resource->save($payoneerTransaction);
} catch (\Exception $exception) {
throw new CouldNotSaveException(__($exception->getMessage()));
}
return $payoneerTransaction;
}
/**
* @param int $customerId
* @return PayoneerTransaction|null
*/
public function getByCustomerId($customerId)
{
$transactions = $this->collectionFactory->create();
$transactions->addFieldToFilter(PayoneerTransactionInterface::CUSTOMER_ID, (string)$customerId);
if ($transactions->count()>0) {
/** @var PayoneerTransaction $transaction */
$transaction = $transactions->getFirstItem();
return $transaction;
}
return null;
}
/**
* @return mixed|PayoneerTransaction
*/
public function create()
{
return $this->modelFactory->create();
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Model\ResourceModel;
use Magento\Framework\Model\ResourceModel\Db\AbstractDb;
use Payoneer\OpenPaymentGateway\Api\Data\NotificationInterface;
/**
* PayoneerNotification resource model class
*/
class PayoneerNotification extends AbstractDb
{
/**
* Initialize the resource model with the table name
* and the primary key column name.
*
* @return void
*/
public function _construct()
{
$this->_init(NotificationInterface::TABLE, NotificationInterface::ID);
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Model\ResourceModel\PayoneerNotification;
use Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection;
use Payoneer\OpenPaymentGateway\Model\PayoneerNotification as PayoneerNotificationModel;
use Payoneer\OpenPaymentGateway\Model\ResourceModel\PayoneerNotification as PayoneerNotificationResource;
class Collection extends AbstractCollection
{
/**
* Initialize the payoneer notification
* collection class
*
* @return void
*/
public function _construct()
{
$this->_init(PayoneerNotificationModel::class, PayoneerNotificationResource::class);
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Model\ResourceModel;
use Magento\Framework\Model\ResourceModel\Db\AbstractDb;
/**
* PayoneerTransaction resource model class
*/
class PayoneerTransaction extends AbstractDb
{
/**
* Table name
*/
const TABLE = 'payoneer_payment_transaction';
/**
* Table primary key column name
*/
const TABLE_PRIMARY_KEY = 'transaction_id';
/**
* Initialize the resource model with the table name
* and the primary key column name.
*
* @return void
*/
public function _construct()
{
$this->_init(self::TABLE, self::TABLE_PRIMARY_KEY);
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Model\ResourceModel\PayoneerTransaction;
use Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection;
use Payoneer\OpenPaymentGateway\Model\PayoneerTransaction as PayoneerTransactionModel;
use Payoneer\OpenPaymentGateway\Model\ResourceModel\PayoneerTransaction as PayoneerTransactionResource;
/**
* Payoneer transaction collection class
*/
class Collection extends AbstractCollection
{
protected $_idFieldName = 'transaction_id';
/**
* Initialize the payoneer transaction
* collection class
*
* @return void
*/
public function _construct()
{
$this->_init(PayoneerTransactionModel::class, PayoneerTransactionResource::class);
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Model;
use Magento\Checkout\Model\Session;
use Magento\Framework\Api\SearchCriteriaBuilder;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Registry;
use Magento\Sales\Api\CreditmemoManagementInterface;
use Magento\Sales\Api\Data\OrderInterface;
use Magento\Sales\Api\Data\OrderPaymentInterface;
use Magento\Sales\Api\Data\TransactionInterface;
use Magento\Sales\Api\OrderManagementInterface;
use Magento\Sales\Api\OrderPaymentRepositoryInterface;
use Magento\Sales\Api\OrderRepositoryInterface;
use Magento\Sales\Api\TransactionRepositoryInterface;
use Magento\Sales\Controller\Adminhtml\Order\CreditmemoLoader;
use Magento\Sales\Model\Order;
use Magento\Sales\Model\Order\Invoice;
use Magento\Sales\Model\Order\Payment;
use Magento\Sales\Model\Order\Payment\Transaction;
use Magento\Sales\Model\Order\Payment\Transaction\BuilderInterface;
use Magento\Sales\Model\ResourceModel\Order\CollectionFactory as OrderCollectionFactory;
use Magento\Sales\Model\ResourceModel\Order\Payment\Transaction\CollectionFactory as OrderTransactionCollectionFactory;
use Payoneer\OpenPaymentGateway\Gateway\Http\Client\Client;
use Payoneer\OpenPaymentGateway\Gateway\Response\PayoneerResponseHandler;
use Payoneer\OpenPaymentGateway\Gateway\Validator\ResponseValidator;
use Payoneer\OpenPaymentGateway\Model\Adminhtml\Helper;
use Payoneer\OpenPaymentGateway\Model\Creditmemo\CreditmemoCreator;
/**
* TransactionOrderUpdater class
*
* Class will handle the order update based on the response
* from payoneer side during the notification processing via
* cron job or after the fetch operation from admin side.
*/
class TransactionOrderUpdater
{
const PRE_AUTHORIZED_STATUS = 'preauthorized';
const TXN_TYPE_CAPTURE = 'payoneerCapture';
/**
* @var OrderCollectionFactory
*/
protected $orderCollectionFactory;
/**
* @var OrderTransactionCollectionFactory
*/
protected $orderTransactionCollectionFactory;
/**
* @var BuilderInterface
*/
protected $paymentTransactionBuilder;
/**
* @var Helper
*/
protected $adminHelper;
/**
* @var CreditmemoCreator
*/
protected $creditmemoCreator;
/**
* @var OrderManagementInterface
*/
protected $orderManager;
/**
* @var OrderRepositoryInterface
*/
protected $orderRepository;
/**
* @var SearchCriteriaBuilder
*/
protected $searchCriteria;
/**
* @var TransactionRepositoryInterface
*/
protected $transactionRepository;
/**
* @var OrderPaymentRepositoryInterface
*/
protected $orderPaymentRepository;
/**
* @var CreditmemoLoader
*/
private $creditmemoLoader;
/**
* @var CreditmemoManagementInterface
*/
private $creditmemoManagement;
/**
* @var Registry
*/
private $registry;
/**
* @var Invoice
*/
private $invoice;
/**
* @var Session
*/
private $session;
/**
* TransactionOrderUpdater construct function
*
* @param OrderCollectionFactory $orderCollectionFactory
* @param OrderTransactionCollectionFactory $orderTransactionCollectionFactory
* @param BuilderInterface $paymentTransactionBuilder
* @param Helper $adminHelper
* @param CreditmemoCreator $creditmemoCreator
* @param OrderManagementInterface $orderManager
* @param OrderRepositoryInterface $orderRepository
* @param SearchCriteriaBuilder $searchCriteria
* @param TransactionRepositoryInterface $transactionRepository
* @param OrderPaymentRepositoryInterface $orderPaymentRepository
* @param CreditmemoLoader $creditmemoLoader
* @param CreditmemoManagementInterface $creditmemoManagement
* @param Invoice $invoice
* @param Registry $registry
* @param Session $checkoutSession
*/
public function __construct(
OrderCollectionFactory $orderCollectionFactory,
OrderTransactionCollectionFactory $orderTransactionCollectionFactory,
BuilderInterface $paymentTransactionBuilder,
Helper $adminHelper,
CreditmemoCreator $creditmemoCreator,
OrderManagementInterface $orderManager,
OrderRepositoryInterface $orderRepository,
SearchCriteriaBuilder $searchCriteria,
TransactionRepositoryInterface $transactionRepository,
OrderPaymentRepositoryInterface $orderPaymentRepository,
CreditmemoLoader $creditmemoLoader,
CreditmemoManagementInterface $creditmemoManagement,
Invoice $invoice,
Registry $registry,
Session $checkoutSession
) {
$this->orderCollectionFactory = $orderCollectionFactory;
$this->orderTransactionCollectionFactory = $orderTransactionCollectionFactory;
$this->paymentTransactionBuilder = $paymentTransactionBuilder;
$this->adminHelper = $adminHelper;
$this->creditmemoCreator = $creditmemoCreator;
$this->orderManager = $orderManager;
$this->orderRepository = $orderRepository;
$this->searchCriteria = $searchCriteria;
$this->transactionRepository = $transactionRepository;
$this->orderPaymentRepository = $orderPaymentRepository;
$this->creditmemoLoader = $creditmemoLoader;
$this->creditmemoManagement = $creditmemoManagement;
$this->registry = $registry;
$this->invoice = $invoice;
$this->session = $checkoutSession;
}
/**
* Process the response got from the notification.
*
* @param string $orderId
* @param array <mixed> $response
* @return bool|void
* @throws LocalizedException
*/
public function processNotificationResponse($orderId, $response)
{
$filteredResponse = [];
$filteredResponse['transaction_id'] = $response['transactionId'];
$filteredResponse['status_code'] = $response['statusCode'];
$filteredResponse['reason_code'] = $response['reasonCode'];
$filteredResponse['longId'] = $response['longId'];
$filteredResponse['amount'] = $response['amount'];
$filteredResponse['interactionReason'] = $response['interactionReason'];
$filteredResponse['interactionCode'] = $response['interactionCode'];
$filteredResponse['previousReasonCode'] =
isset($response['previousReasonCode']) ?
$response['previousReasonCode'] : null;
return $this->processResponse($orderId, $filteredResponse, true);
}
/**
* Process the response got from the fetch operation
*
* @param string|Order $order
* @param array <mixed> $response
* @return bool|void
* @throws LocalizedException
*/
public function processFetchUpdateResponse($order, $response)
{
$filteredResponse = $this->getFilteredResponse($response);
return $this->processResponse($order, $filteredResponse);
}
/**
* @param array <mixed> $response
* @return array <mixed>
*/
public function getFilteredResponse($response)
{
$actualResponse = $response['response'];
$filteredResponse = [];
$filteredResponse['transaction_id'] = $actualResponse['identification']['transactionId'];
$filteredResponse['status_code'] = $actualResponse['status']['code'];
$filteredResponse['reason_code'] = $actualResponse['status']['reason'];
$filteredResponse['longId'] = $actualResponse['identification']['longId'];
$filteredResponse['amount'] = $actualResponse['payment']['amount'];
$filteredResponse['interactionReason'] = $actualResponse['interaction']['reason'];
$filteredResponse['interactionCode'] = $actualResponse['interaction']['code'];
return $filteredResponse;
}
/**
* Check the response status code and reason and based
* on the values do the corresponding actions.
*
* @param string|Order $order
* @param array <mixed> $response
* @param bool $notification
* @return bool|void
* @throws LocalizedException
*/
public function processResponse($order, $response, $notification = false)
{
switch ([$response['status_code'], $response['reason_code']]) {
case [self::PRE_AUTHORIZED_STATUS, self::PRE_AUTHORIZED_STATUS]:
if ($this->isValidOrder($order, $response)) {
return $this->checkAndAuthorizeOrder($order, $response);
}
break;
case [Helper::CHARGED, Helper::DEBITED]:
case [Helper::CHARGED, Helper::CAPTURE_CLOSED]:
if ($this->isValidOrder($order, $response)) {
return $this->checkAndCaptureOrder($order, $response, $notification);
}
break;
case [ResponseValidator::REFUND_PAID_OUT_STATUS, ResponseValidator::REFUND_CREDITED]:
case [ResponseValidator::REFUND_PAID_OUT_STATUS, ResponseValidator::REFUND_PAID_OUT_STATUS]:
return $this->checkAndRefundOrder($order, $response);
case [ResponseValidator::AUTH_CANCEL_PENDING_STATUS, ResponseValidator::CANCELLATION_REQUESTED]:
return $this->checkAndVoidOrder($order, $response);
case [ResponseValidator::AUTH_CANCELLED_STATUS, ResponseValidator::PREAUTHORIZATION_CANCELLED]:
return $this->checkAndVoidOrderOnPreAuthCancel($order, $response);
}
}
/**
* @param Order|string $order
* @param array <mixed> $response
* @return bool
* @throws LocalizedException
*/
public function isValidOrder($order, $response)
{
$orderObj = $this->getOrder($order);
$isValidData = $this->compareData($orderObj, $response);
if (!$isValidData) {
$this->updateOrderStatusToPaymentReview($orderObj);
return false;
} else {
return true;
}
}
/**
* @param Order $order
* @param array <mixed> $response
* @return bool
* @throws LocalizedException
*/
public function compareData($order, $response)
{
if (!($order instanceof Order)) {
$orderObj = $this->getOrder($order);
} else {
$orderObj = $order;
}
$payment = $orderObj->getPayment();
$additionalInformation = $payment ? $payment->getAdditionalInformation() : [];
if (($response['interactionReason'] !== $additionalInformation['interactionReason'])
|| ($response['interactionCode'] !== $additionalInformation['interactionCode'])
|| ($response['amount'] != $additionalInformation['amount'])) {
return false;
}
return true;
}
/**
* @param Order|string $order
* @return void
* @throws LocalizedException
*/
public function updateOrderStatusToPaymentReview($order)
{
if (!($order instanceof Order)) {
$order = $this->getOrder($order);
}
$order->setState('payment_review')->setStatus('payment_review');
$this->orderRepository->save($order);
}
/**
* @param Order|string $order
* @return void
* @throws LocalizedException
*/
public function updateOrderStatusToClosed($order)
{
if (!($order instanceof Order)) {
$order = $this->getOrder($order);
}
$order->setState('closed')->setStatus('closed');
$this->orderRepository->save($order);
}
/**
* Authorize the order if it's not already authorized.
*
* @param string|Order $order
* @param array <mixed> $response
* @return bool|void
* @throws LocalizedException
*/
public function checkAndAuthorizeOrder($order, $response)
{
try {
$orderObj = $this->getOrder($order);
$authTxn = $this->getTransaction($orderObj->getId(), Helper::AUTHORIZATION);
if ($authTxn != null && $authTxn->getTransactionId()) {
return $this->changeOrderToProcessing($orderObj);
}
$orderTotal = $orderObj->getBaseCurrency()->formatTxt(
$orderObj->getGrandTotal()
);
$txnData = [
'additional_info' => $response,
'additional_info_key' => 'auth_response',
'is_transaction_closed' => false,
'transaction_type' => Helper::AUTHORIZATION,
'order_comment' => __('Authorized amount of %1', $orderTotal),
'parent_txn_id' => null
];
$this->addNewTransactionEntry(
$orderObj,
$txnData
);
} catch (LocalizedException $le) {
throw new LocalizedException(
__($le->getMessage())
);
} catch (\Exception $e) {
throw new LocalizedException(
__('We couldn\'t authorize the order. Try again later.')
);
}
}
/**
* Refund the order if it's not already refunded.
*
* @param string|Order $order
* @param array <mixed> $response
* @return bool|void
* @throws LocalizedException
*/
public function checkAndRefundOrder($order, $response)
{
$this->session->setFetchNotificationResponse($response);
try {
$orderObj = $this->getOrder($order);
$canRefundOrder = $this->canRefund($order, $response);
if (!$canRefundOrder) {
return true;
}
if (isset($response['previousReasonCode']) &&
$response['reason_code'] == ResponseValidator::REFUND_PAID_OUT_STATUS
&& $response['previousReasonCode'] == ResponseValidator::REFUND_PAID_OUT_PARTIAL) {
return true;
}
if ($response['amount'] < $orderObj->getGrandTotal()) {
$this->checkAndRefundPartial($orderObj, $response);
return true;
}
if ($orderObj->getState() == Order::STATE_CLOSED && $response['amount'] == $orderObj->getGrandTotal()) {
return true;
}
$orderObj->setData('total_refunded', 0.00);
$this->clearRegistry();
$this->creditmemoCreator->create($orderObj);
$txnData = [
'additional_info' => $response,
'additional_info_key' => PayoneerResponseHandler::ADDITIONAL_INFO_KEY_REFUND_RESPONSE,
'is_transaction_closed' => true,
'transaction_type' => Client::REFUND,
'parent_txn_id' => $response['transaction_id'],
'txn_id_post_text' => 'refund'
];
$this->addNewTransactionEntry(
$orderObj,
$txnData
);
} catch (LocalizedException $le) {
throw new LocalizedException(
__($le->getMessage())
);
} catch (\Exception $e) {
throw new LocalizedException(
__('We couldn\'t refund the order. Try again later.')
);
}
}
/**
* Partial refund, only add a new refund transaction.
*
* @param string|Order $order
* @param array <mixed> $response
* @return bool|void
* @throws LocalizedException
*/
public function checkAndRefundPartial($order, $response)
{
$orderObj = $this->getOrder($order);
try {
$this->createCreditMemo($orderObj, $response['amount']);
//$this->createNewTransactionEntry($orderObj, $response);
} catch (LocalizedException $le) {
throw new LocalizedException(
__($le->getMessage())
);
} catch (\Exception $e) {
throw new LocalizedException(
__('We couldn\'t process the partial refund of the order. Try again later.')
);
}
}
/**
* @param Order $orderObj
* @param float $amount
* @return void
*/
public function createCreditMemo($orderObj, $amount)
{
try {
$this->clearRegistry();
$creditMemoData = [];
$creditMemoData['do_offline'] = 0;
$creditMemoData['shipping_amount'] = 0;
$creditMemoData['subtotal'] = 0;
$creditMemoData['adjustment_positive'] = $amount;
$creditMemoData['adjustment_negative'] = 0;
$creditMemoData['send_email'] = 0;
$itemsToCredit = [];
foreach ($orderObj->getAllVisibleItems() as $item) {
$itemsToCredit[$item->getId()] = ['qty'=>0];
}
$creditMemoData['items'] = $itemsToCredit;
$this->creditmemoLoader->setOrderId($orderObj->getEntityId()); //pass order id
$this->creditmemoLoader->setCreditmemo($creditMemoData);
$creditmemo = $this->creditmemoLoader->load();
if ($creditmemo) {
if (!$creditmemo->isValidGrandTotal()) {
throw new \Magento\Framework\Exception\LocalizedException(
__('The credit memo\'s total must be positive')
);
}
$creditmemo->getOrder()->setCustomerNoteNotify(0);
$invoiceObj = $this->getInvoice($orderObj);
if ($invoiceObj) {
$creditmemo->setInvoice($invoiceObj);/** @phpstan-ignore-line */
}
$this->creditmemoManagement->refund($creditmemo, (bool)$creditMemoData['do_offline']);
$this->adminHelper->showSuccessMessage(__('Credit memo created.'));
}
} catch (\Magento\Framework\Exception\LocalizedException $e) {
$this->adminHelper->showErrorMessage($e->getMessage());
} catch (\Exception $e) {
$this->adminHelper->showErrorMessage(__('We couldn\'t save the credit memo. Try again later.'));
}
}
/**
* @return void
*/
public function clearRegistry()
{
if ($this->registry->registry('current_creditmemo')) {
$this->registry->unregister('current_creditmemo');
}
}
/**
* @param Order $orderObj
* @return bool|Invoice
*/
public function getInvoice($orderObj)
{
$invoiceIncrementId = null;
$invoices = $orderObj->getInvoiceCollection();
foreach ($invoices as $invoice) {
$invoiceIncrementId = $invoice->getIncrementId();
}
if ($invoiceIncrementId) {
return $this->invoice->loadByIncrementId($invoiceIncrementId);
}
return false;
}
/**
* @param string|Order $order
* @param array <mixed> $response
* @return bool
* @throws LocalizedException
*/
public function canRefund($order, $response)
{
$orderObj = $this->getOrder($order);
$newRefundLongId = $response['longId'];
$additionalInformation = $orderObj->getPayment()->getAdditionalInformation();
$refundResponse = isset($additionalInformation['refund_response']) ?
$additionalInformation['refund_response'] : null;
if (is_array($refundResponse)) {
$longId = '';
if (isset($refundResponse['longId'])) {
$longId = $refundResponse['longId'];
} elseif (isset($refundResponse[0]['longId'])) {
$longId = $refundResponse[0]['longId'];
}
if ($newRefundLongId == $longId) {
return false;
}
}
if ($response['reason_code'] == ResponseValidator::REFUND_PAID_OUT_STATUS) {
if (isset($response['previousReasonCode'])
&& $response['previousReasonCode'] == ResponseValidator::REFUND_PAID_OUT_PARTIAL
) {
return false;
} elseif ($orderObj->getTotalRefunded() >= $orderObj->getGrandTotal()) {
return false;
}
}
return true;
}
/**
* Cancel the auth if not already cancelled.
*
* @param string|Order $order
* @param array <mixed> $response
* @return bool|void
* @throws LocalizedException
*/
public function checkAndVoidOrder($order, $response)
{
try {
$orderObj = $this->getOrder($order);
$authTxn = $this->getTransaction($orderObj->getId(), Client::VOID);
if ($authTxn != null && $authTxn->getTransactionId()) {
if ($orderObj->getState() != Order::STATE_CANCELED) {
$this->orderManager->cancel($orderObj->getId());
}
return true;
}
$this->cancelOrderAndAddNewVoidTransaction(
$orderObj,
$response
);
} catch (LocalizedException $le) {
throw new LocalizedException(
__($le->getMessage())
);
} catch (\Exception $e) {
throw new LocalizedException(
__('We couldn\'t cancel the authorization. Try again later.')
);
}
}
/**
* Cancel the auth if not already cancelled.
*
* @param string|Order $order
* @param array <mixed> $response
* @return bool|void
* @throws LocalizedException
*/
public function checkAndVoidOrderOnPreAuthCancel($order, $response)
{
try {
$orderObj = $this->getOrder($order);
$authTxn = $this->getTransaction($orderObj->getId(), Client::VOID);
if ($authTxn != null && $authTxn->getTransactionId()) {
if ($orderObj->getState() != Order::STATE_CANCELED) {
$this->orderManager->cancel($orderObj->getId());
}
$authTxn->unsAdditionalInformation();
$authTxn->setAdditionalInformation(
Transaction::RAW_DETAILS,
$response
);
$this->transactionRepository->save($authTxn);
$orderObj->addCommentToStatusHistory(
__('Payoneer status changed to preauthorization_canceled')
);
$this->orderRepository->save($orderObj);
return true;
}
$this->cancelOrderAndAddNewVoidTransaction(
$orderObj,
$response
);
} catch (LocalizedException $le) {
throw new LocalizedException(
__($le->getMessage())
);
} catch (\Exception $e) {
throw new LocalizedException(
__('We couldn\'t cancel the preauthorization. Try again later.')
);
}
}
/**
* Cancel the order and add new void transaction entry.
*
* @param Order $orderObj
* @param array <mixed> $response
* @return bool
* @throws LocalizedException
*/
private function cancelOrderAndAddNewVoidTransaction($orderObj, $response)
{
$this->setAdditionalInformation($orderObj, 'payoneerCancel');
$this->orderManager->cancel($orderObj->getId());
$orderTotal = $orderObj->getBaseCurrency()->formatTxt(
$orderObj->getGrandTotal()
);
$txnData = [
'additional_info' => $response,
'additional_info_key' => PayoneerResponseHandler::ADDITIONAL_INFO_KEY_AUTH_CANCEL_RESPONSE,
'is_transaction_closed' => true,
'transaction_type' => Client::VOID,
'order_comment' => __('The amount of %1 is void', $orderTotal),
'parent_txn_id' => $response['transaction_id'],
'txn_id_post_text' => 'void'
];
$this->addNewTransactionEntry(
$orderObj,
$txnData
);
return true;
}
/**
* @param Order $orderObj
* @param string $transactionType
* @param null|array <mixed> $response
* @return void
*/
public function setAdditionalInformation($orderObj, $transactionType, $response = null)
{
$payment = $orderObj->getPayment();
if ($payment) {
$additionalInformation = $payment->getAdditionalInformation();
$additionalInformation = array_merge($additionalInformation, [$transactionType => 'success']);
if ($transactionType == PayoneerResponseHandler::ADDITIONAL_INFO_KEY_CAPTURE_RESPONSE) {
$additionalInformation = array_merge(
$additionalInformation,
[$transactionType => $response]
);
}
$payment->setAdditionalInformation($additionalInformation);/** @phpstan-ignore-line */
$orderObj->setPayment($payment);
$this->orderRepository->save($orderObj);
}
}
/**
* Capture the order if it's not already captured.
*
* @param string|Order $order
* @param array <mixed> $response
* @param bool $notification
* @return bool|void
* @throws LocalizedException
*/
public function checkAndCaptureOrder($order, $response, $notification = false)
{
try {
$orderObj = $this->getOrder($order);
if ($notification && $response['reason_code'] == Helper::DEBITED) {
$this->setAdditionalInformation(
$orderObj,
PayoneerResponseHandler::ADDITIONAL_INFO_KEY_CAPTURE_RESPONSE,
$response
);
}
$authTxn = $this->getTransaction($orderObj->getId(), Helper::CAPTURE);
if ($authTxn != null && $authTxn->getTransactionId()) {
return $this->changeOrderToProcessing($orderObj);
}
$this->setAdditionalInformation($orderObj, self::TXN_TYPE_CAPTURE);
if ($orderObj->canInvoice()) {
$this->adminHelper->generateInvoice($orderObj);
}
} catch (LocalizedException $le) {
throw new LocalizedException(
__($le->getMessage())
);
} catch (\Exception $e) {
throw new LocalizedException(
__('We couldn\'t capture the transaction. Try again later.')
);
}
}
/**
* Get the order. If its an increment id then the corresponding
* model is loaded with the id and returned.
*
* @param Order|string $order
* @return false|mixed
* @throws LocalizedException
*/
private function getOrder($order)
{
if (is_object($order)) {
return $order;
}
$criteria = $this->searchCriteria->addFilter(OrderInterface::INCREMENT_ID, $order)->create();
$orders = $this->orderRepository->getList($criteria)->getItems();
if (count($orders)) {
return reset($orders);
}
throw new LocalizedException(
__('We couldn\'t find an order with the increment ID of %s', $order)
);
}
/**
* Get the transaction from the payment transaction collection
* matching the conditions.
*
* @param int $orderId
* @param string $txnType
* @param int $txnId
* @return Transaction|null
*/
private function getTransaction($orderId, $txnType = null, $txnId = null)
{
$transaction = null;
$collection = $this->orderTransactionCollectionFactory->create();
$collection->addOrderIdFilter($orderId);
if (!empty($txnType)) {
$collection->addTxnTypeFilter($txnType);
}
if (!empty($txnId)) {
$collection->addFieldToFilter('transaction_id', ['eq' => $txnId]);
}
if ($collection->getSize()) {
/** @var Transaction $transaction */
$transaction = $collection->getFirstItem();
}
return $transaction;
}
/**
* Add new payment transaction corresponding to the
* operation.
*
* @param Order $order
* @param array <mixed> $data
* @return void
* @throws LocalizedException
*/
private function addNewTransactionEntry($order, $data)
{
try {
/** @var Payment $payment */
$payment = $order->getPayment();
$payment->setLastTransId($data['additional_info']['transaction_id']);
$payment->setTransactionId($data['additional_info']['transaction_id']);
$payment->setAdditionalInformation(
$data['additional_info_key'],
$data['additional_info']
);
$payment->setIsTransactionClosed($data['is_transaction_closed']);
/** @var Transaction $transaction */
$transaction = $this->buildTransactionObject(
$order,
$payment,
$data
);
if (isset($data['order_comment'])) {
$payment->addTransactionCommentsToOrder(
$transaction,
$data['order_comment']
);
}
$payment->setParentTransactionId($data['parent_txn_id']);
$this->orderPaymentRepository->save($payment);
$this->orderRepository->save($order);
$this->transactionRepository->save($transaction);
return;
} catch (\Exception $e) {
throw new LocalizedException(
__('We couldn\'t create the %1 transaction entry', $data['transaction_type'])
);
}
}
/**
* Build the transaction object with the corresponding
* configurations.
*
* @param Order $order
* @param Order\Payment|OrderPaymentInterface $payment
* @param array <mixed> $data
* @return Transaction|TransactionInterface
*/
private function buildTransactionObject($order, $payment, $data)
{
$txnId = $this->getFinalTransactionId($data);
return $this->paymentTransactionBuilder->setPayment($payment)
->setOrder($order)
->setTransactionId($txnId)
->setAdditionalInformation(
[Transaction::RAW_DETAILS => $data['additional_info']]
)->setFailSafe(true)
->build($data['transaction_type']);
}
/**
* Change the order status to processing.
*
* @param Order $order
* @return bool
*/
private function changeOrderToProcessing($order)
{
if ($order->getStatus() == Order::STATE_PROCESSING) {
return true;
}
$order->setStatus(Order::STATE_PROCESSING);
$this->orderRepository->save($order);
return true;
}
/**
* Get the final txn id for the payment transaction
* table.
*
* @param array <mixed> $data
* @return string
*/
private function getFinalTransactionId($data)
{
$postText = isset($data['txn_id_post_text']) ? $data['txn_id_post_text'] : null;
$longId = isset($data['additional_info']['longId']) ? $data['additional_info']['longId'] : null;
$txnId = $data['additional_info']['transaction_id'];
if ($postText == Client::REFUND && $longId) {
return $longId . '-' . $data['txn_id_post_text'];
}
if (!empty($postText)) {
return $txnId . '-' . $data['txn_id_post_text'];
}
return $txnId;
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Model;
use Exception;
use Magento\Framework\App\RequestInterface as Request;
use Magento\Framework\Controller\Result\Json;
use Magento\Framework\Controller\Result\JsonFactory;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\NotFoundException;
use Magento\Framework\Message\ManagerInterface;
use Magento\Payment\Gateway\Command\CommandException;
use Magento\Payment\Gateway\Command\CommandPoolInterface;
use Magento\Payment\Gateway\Command\ResultInterface;
use Magento\Payment\Gateway\ConfigInterface;
use Magento\Payment\Gateway\Data\PaymentDataObjectFactory;
use Magento\Payment\Model\InfoInterface;
use Magento\Quote\Api\CartRepositoryInterface;
use Magento\Quote\Model\Quote;
use Magento\Quote\Model\Quote\Payment;
use Magento\Sales\Model\Order;
use Payoneer\OpenPaymentGateway\Gateway\Config\Config;
use Payoneer\OpenPaymentGateway\Gateway\Http\Client\Client;
/**
* Class GetPayoneerTransactionService
*
* Process List Api Request
*/
class TransactionService
{
const HOSTED = 'hosted';
/**
* @var CommandPoolInterface
*/
protected $commandPool;
/**
* @var PaymentDataObjectFactory
*/
protected $paymentDataObjectFactory;
/**
* @var JsonFactory
*/
protected $resultJsonFactory;
/**
* @var ConfigInterface
*/
protected $config;
/**
* @var ManagerInterface
*/
protected $messageManager;
/**
* @var Request
*/
protected $request;
/**
* @var CartRepositoryInterface
*/
protected $quoteRepository;
/**
* GetPayoneerTransactionService constructor.
* @param CommandPoolInterface $commandPool
* @param PaymentDataObjectFactory $paymentDataObjectFactory
* @param JsonFactory $resultJsonFactory
* @param ConfigInterface $config
* @param ManagerInterface $messageManager
* @param Request $request
* @param CartRepositoryInterface $quoteRepository
*/
public function __construct(
CommandPoolInterface $commandPool,
PaymentDataObjectFactory $paymentDataObjectFactory,
JsonFactory $resultJsonFactory,
ConfigInterface $config,
ManagerInterface $messageManager,
Request $request,
CartRepositoryInterface $quoteRepository
) {
$this->commandPool = $commandPool;
$this->paymentDataObjectFactory = $paymentDataObjectFactory;
$this->resultJsonFactory = $resultJsonFactory;
$this->config = $config;
$this->messageManager = $messageManager;
$this->request = $request;
$this->quoteRepository = $quoteRepository;
}
/**
* Process Api request
*
* @param Quote $quote
* @return ResultInterface|null|bool|array <mixed>
* @throws Exception
*/
public function process(Quote $quote)
{
if (!$this->config->getValue('active')) {
return [];
}
if (!$quote->getReservedOrderId()) {
$quote = $quote->reserveOrderId();
$this->quoteRepository->save($quote);
}
$token = strtotime('now') . uniqid();
$nToken = $this->getNotificationToken();
$payment = $quote->getPayment();
$transactionId = $payment->getId() . strtotime('now');
$payment->setAdditionalInformation(Config::TOKEN, $token);
$payment->setAdditionalInformation(Config::TOKEN_NOTIFICATION, $nToken);
$payment->setAdditionalInformation(Config::TXN_ID, $transactionId);
$quote->setPayment($payment);
$this->quoteRepository->save($quote);
$paymentDataObject = $this->paymentDataObjectFactory->create($payment);
try {
$address = $this->request->getParam('address');
$address = json_decode($address, true);
/** @var ResultInterface $result */
$result = $this->commandPool->get($this->getIntegration())->execute([
'payment' => $paymentDataObject,
'amount' => $quote->getGrandTotal(),
'address' => $address,
'totalItemsCount' => $quote->getItemsCount()
]);
/** @phpstan-ignore-next-line */
$this->setAdditionalInformation($quote, $result);
return $result;
} catch (Exception $e) {
throw new LocalizedException(__($e->getMessage()));
}
}
/**
* Initiate the authorization cancellation request.
*
* @param Order $order
* @return ResultInterface|void|null|array <mixed>
* @throws NotFoundException
* @throws CommandException
*/
public function processAuthCancel($order)
{
/** @var InfoInterface $payment */
$payment = $order->getPayment();
$paymentDataObject = $this->paymentDataObjectFactory->create($payment);
return $this->commandPool->get(Client::AUTHORIZATION_CANCEL)->execute([
'payment' => $paymentDataObject,
'order' => $order
]);
}
/**
* @return string
* @throws Exception
*/
public function getNotificationToken()
{
$bytes = random_bytes(20);
return bin2hex($bytes);
}
/**
* @param Quote $quote
* @param array <mixed> $result
* @return void
* @throws LocalizedException
*/
public function setAdditionalInformation($quote, $result)
{
$payment = $quote->getPayment();
if (isset($result['response'])
&& isset($result['response']['identification'])
&& isset($result['response']['identification']['longId'])) {
$listId = $result['response']['identification']['longId'];
$payment->setAdditionalInformation(Config::LIST_ID, $listId);
if ($this->getIntegration() == Config::INTEGRATION_HOSTED
&& isset($result['response']['redirect'])
&& isset($result['response']['redirect']['url'])) {
$payment->setAdditionalInformation(Config::REDIRECT_URL, $result['response']['redirect']['url']);
}
$this->saveQuote($quote, $payment);
}
}
/**
* @param Quote $quote
* @param Payment $payment
* @return void
*/
public function saveQuote($quote, $payment)
{
$quote->setPayment($payment);
//$this->quoteRepository->save($quote);//Gift cart amount is getting as null if quoterepository save is called
$quote->save();/** @phpstan-ignore-line */
}
/**
* Get integration type from request
*
* @return string
*/
private function getIntegration()
{
return (string)$this->request->getParam(Config::INTEGRATION);
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Model\Ui;
use Magento\Checkout\Model\ConfigProviderInterface;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Store\Model\Store;
use Magento\Store\Model\StoreManagerInterface;
use Payoneer\OpenPaymentGateway\Gateway\Config\Config;
use Payoneer\OpenPaymentGateway\Model\Helper;
/**
* Class ConfigProvider - Payoneer configuration class
*/
class ConfigProvider implements ConfigProviderInterface
{
const CODE = 'payoneer';
/**
* @var StoreManagerInterface
*/
private $storeManager;
/**
* @var Config
*/
private $config;
/**
* @var Helper
*/
private $helper;
/**
* ConfigProvider constructor.
* @param StoreManagerInterface $storeManager
* @param Config $config
* @param Helper $helper
*/
public function __construct(
StoreManagerInterface $storeManager,
Config $config,
Helper $helper
) {
$this->storeManager = $storeManager;
$this->config = $config;
$this->helper = $helper;
}
/**
* Retrieve assoc array of checkout configuration
*
* @return array <mixed>
* @throws NoSuchEntityException
*/
public function getConfig()
{
/** @var Store $store */
$store = $this->storeManager->getStore();
return [
'payment' => [
self::CODE => [
'config' => [
'active' => (bool)$this->config->getValue('active'),
'environment' => $this->config->getValue('environment'),
'payment_flow' => $this->config->getValue('payment_flow'),
'widgetCssUrl' => $this->getStaticFilePath(),
'payment_icon_type' => $this->config->getValue('widget_appearance/payment_icon_type'),
'processPaymentUrl' => $store->getUrl(
'payoneer/integration/processpayment',
['_secure' => $store->isCurrentlySecure()]
),
]
]
]
];
}
/**
* Get the widget css file path
* @return string
*/
public function getStaticFilePath()
{
$fileId = 'Payoneer_OpenPaymentGateway::css/widget.min.css';
$params = [
'area' => 'frontend'
];
return $this->helper->getStaticFilePath($fileId, $params);
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Observer;
use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;
use Magento\Framework\Exception\NotFoundException;
use Magento\Payment\Gateway\Command\CommandException;
use Magento\Payment\Gateway\Command\ResultInterface;
use Magento\Sales\Model\Order;
use Payoneer\OpenPaymentGateway\Model\Adminhtml\Helper as AdminHelper;
use Payoneer\OpenPaymentGateway\Model\TransactionService;
/**
* CancelOrderObserver class
*
* Class will handle the payoneer side cancellation
* of the auth request if already created.
*/
class CancelOrderObserver implements ObserverInterface
{
/**
* @var TransactionService
*/
protected $transactionService;
/**
* @var AdminHelper
*/
protected $payoneerAdminHelper;
/**
* CancelOrderObserver construct function
*
* @param AdminHelper $payoneerAdminHelper
* @param TransactionService $transactionService
*
* @return void
*/
public function __construct(
TransactionService $transactionService,
AdminHelper $payoneerAdminHelper
) {
$this->transactionService = $transactionService;
$this->payoneerAdminHelper = $payoneerAdminHelper;
}
/**
* Cancel authorization.
*
* @param Observer $observer
* @return ResultInterface|void|null|array <mixed>
* @throws NotFoundException
* @throws CommandException
*/
public function execute(Observer $observer)
{
if ($this->payoneerAdminHelper->isPayoneerEnabled()) {
$cancellationDone = false;
/**@var Order $order */
$order = $observer->getEvent()->getOrder();
$additionalInformation = $order->getPayment()->getAdditionalInformation();
if ($additionalInformation && isset($additionalInformation['payoneerCancel'])) {
$cancellationDone = true;
}
if (!$cancellationDone) {
if ($order->getState() == 'canceled' && $this->payoneerAdminHelper->canCancelAuthorization($order)) {
return $this->transactionService->processAuthCancel($order);
}
}
}
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Observer\Config;
use Magento\Backend\App\Action\Context;
use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;
use Magento\Framework\Exception\AlreadyExistsException;
use Magento\Framework\Exception\LocalizedException;
use Payoneer\OpenPaymentGateway\Gateway\Config\Config as PayoneerConfig;
use Payoneer\OpenPaymentGateway\Model\Api\Request;
/**
* Class ValidateCredentials - Validates the API credentials in admin
*
*/
class ValidateCredentialsObserver implements ObserverInterface
{
/**
* @var PayoneerConfig
*/
private $payoneerConfig;
/**
* @var Context
*/
private $context;
/**
* @var Request
*/
private $request;
/**
* @var array <mixed>
*/
private $configFieldValues = [];
/**
* ValidateCredentialsObserver constructor.
* @param PayoneerConfig $payoneerConfig
* @param Context $context
* @param Request $request
*/
public function __construct(
PayoneerConfig $payoneerConfig,
Context $context,
Request $request
) {
$this->payoneerConfig = $payoneerConfig;
$this->context = $context;
$this->request = $request;
}
/**
* @param Observer $observer
* @return void
* @throws AlreadyExistsException
* @throws LocalizedException
*/
public function execute(Observer $observer)
{
$this->validateApi($observer);
}
/**
* Validates Api call
* @param Observer $observer
* @throws AlreadyExistsException
* @return void
*/
public function validateApi(Observer $observer)
{
if (!$this->payoneerConfig->getValue('active')) {
return;
}
$credentials = [];
$sharedFields = ['environment', 'merchant_gateway_key'];
$sandboxFields = ['sandbox_api_key', 'sandbox_host_name', 'sandbox_store_code'];
$liveFields = ['live_api_key', 'live_host_name', 'live_store_code'];
/** @phpstan-ignore-next-line */
$groups = $this->context->getRequest()->getPost('groups');
$payoneerGroup = $groups['payoneer'];
$this->prepareConfigValues($sharedFields, $payoneerGroup);
if ($this->configFieldValues['environment'] == 'test') {
$this->prepareConfigValues($sandboxFields, $payoneerGroup);
$apiKey = $this->configFieldValues['sandbox_api_key'];
$hostName = $this->configFieldValues['sandbox_host_name'];
$storeCode = $this->configFieldValues['sandbox_store_code'];
} else {
$this->prepareConfigValues($liveFields, $payoneerGroup);
$apiKey = $this->configFieldValues['live_api_key'];
$hostName = $this->configFieldValues['live_host_name'];
$storeCode = $this->configFieldValues['live_store_code'];
}
$credentials['merchantCode'] = $this->configFieldValues['merchant_gateway_key'];
$credentials['apiKey'] = $apiKey;
$credentials['hostName'] = $hostName;
$data = $this->payoneerConfig->getMockData();
if ($storeCode) {
$data['division'] = $storeCode;
}
$response = $this->request->send(
PayoneerConfig::METHOD_POST,
PayoneerConfig::LIST_END_POINT,
$credentials,
$data
);
if ($response->getData('status') !== 200) {
throw new AlreadyExistsException(__(
'Payoneer validation failed. Make sure the credentials you have entered are correct.'
));
}
}
/**
* Prepare config values
* @param array <mixed> $fields
* @param array <mixed> $group
* @return void
*/
public function prepareConfigValues($fields, $group)
{
foreach ($fields as $field) {
if (isset($group['fields'][$field]['inherit']) || !isset($group['fields'][$field]['value'])) {
$this->configFieldValues[$field] = $this->payoneerConfig->getValue($field);
} else {
$this->configFieldValues[$field] = $group['fields'][$field]['value'];
}
}
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Observer;
use Magento\Framework\Event\Observer;
use Magento\Framework\Exception\LocalizedException;
use Magento\Payment\Observer\AbstractDataAssignObserver;
class DataAssignObserver extends AbstractDataAssignObserver
{
/**
* @param Observer $observer
* @return void
* @throws LocalizedException
*/
public function execute(Observer $observer)
{
$method = $this->readMethodArgument($observer);
$data = $this->readDataArgument($observer);
$paymentInfo = $method->getInfoInstance();
if ($data->getDataByKey('transaction_result') !== null) {
$paymentInfo->setAdditionalInformation(
'transaction_result',
$data->getDataByKey('transaction_result')
);
}
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Observer;
use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Message\ManagerInterface;
use Magento\Sales\Model\Order;
use Payoneer\OpenPaymentGateway\Gateway\Config\Config;
use Payoneer\OpenPaymentGateway\Model\Adminhtml\Helper;
use Payoneer\OpenPaymentGateway\Model\Adminhtml\TransactionService;
/**
* Class InvoiceSaveAfterObserver
* Call PayoneerCapture after invoice creation
*/
class InvoiceSaveAfterObserver implements ObserverInterface
{
/**
* @var ManagerInterface
*/
protected $messageManager;
/**
* @var TransactionService
*/
protected $transactionService;
/**
* @var Helper
*/
protected $helper;
/**
* InvoiceSaveAfterObserver constructor.
* @param TransactionService $transactionService
* @param Helper $helper
*/
public function __construct(
TransactionService $transactionService,
Helper $helper
) {
$this->transactionService = $transactionService;
$this->helper=$helper;
}
/**
* Call Payoneer capture process if not yet captured
* used for event: sales_order_invoice_save_after
*
* @param Observer $observer
* @return $this
* @throws LocalizedException
*/
public function execute(Observer $observer)
{
if ($this->helper->isPayoneerEnabled()) {
$invoice = $observer->getEvent()->getInvoice();
/**@var Order $order */
$order = $invoice->getOrder();
$additionalInformation = $order->getPayment()->getAdditionalInformation();
if (!isset($additionalInformation['payoneerCapture'])) {
$result = $this->transactionService->process($order, Config::LIST_CAPTURE);
if ($result && is_array($result)) {
$result = $this->transactionService->process($order, Config::LIST_CAPTURE);
if ($result) {
/** @phpstan-ignore-next-line */
$this->helper->processCaptureResponse($result, $order);
}
}
}
}
return $this;
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Observer;
use Magento\Checkout\Model\Session as CheckoutSession;
use Magento\Framework\Controller\Result\Redirect;
use Magento\Framework\Controller\Result\RedirectFactory;
use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;
use Magento\Sales\Api\OrderRepositoryInterface;
use Magento\Sales\Model\Order;
use Payoneer\OpenPaymentGateway\Model\Helper;
/**
* Class RedirectCartObserver
*
* Update order status to payment_review in case of invalid transactions
*
*/
class RedirectCartObserver implements ObserverInterface
{
/**
* @var CheckoutSession
*/
private $checkoutSession;
/**
* @var Helper
*/
private $helper;
/**
* @var OrderRepositoryInterface
*/
private $orderRepository;
/**
* RedirectCartObserver constructor.
* @param CheckoutSession $checkoutSession
* @param OrderRepositoryInterface $orderRepository
* @param Helper $helper
*/
public function __construct(
CheckoutSession $checkoutSession,
OrderRepositoryInterface $orderRepository,
Helper $helper
) {
$this->checkoutSession = $checkoutSession;
$this->orderRepository = $orderRepository;
$this->helper = $helper;
}
/**
* Change order status to payment_review and add transaction
*
* @param Observer $observer
* @return Redirect|void
*/
public function execute(Observer $observer)
{
if ($this->checkoutSession->getPayoneerInvalidTxn()) {
/** @var $orderInstance Order */
$order = $observer->getOrder();/** @phpstan-ignore-line */
$this->setOrderStatus($order);
$this->helper->unsetPayoneerInvalidTxnSession();
}
}
/**
* @param Order $order
* @return void
*/
public function setOrderStatus($order)
{
$order->setState('canceled')->setStatus('canceled');
$this->orderRepository->save($order);
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Observer;
use Magento\Checkout\Model\Session;
use Magento\Framework\Event\Observer as EventObserver;
use Magento\Framework\Event\ObserverInterface;
use Payoneer\OpenPaymentGateway\Gateway\Config\Config;
/**
* Updates order status
*/
class UpdateOrderStatusObserver implements ObserverInterface
{
/**
* @var Session
*/
private $checkoutSession;
/**
* @var Config
*/
private $config;
/**
* UpdateOrderStatusObserver constructor.
* @param Session $checkoutSession
* @param Config $config
*/
public function __construct(
Session $checkoutSession,
Config $config
) {
$this->checkoutSession = $checkoutSession;
$this->config = $config;
}
/**
* @param EventObserver $observer
* @return void
*/
public function execute(EventObserver $observer)
{
if (!$this->config->isPayoneerEnabled()) {
return;
}
if ($this->checkoutSession->getUpdateOrderStatus() == true) {
/** @var \Magento\Sales\Model\Order\Payment $orderPayment */
$orderPayment = $observer->getEvent()->getPayment();
$order = $orderPayment->getOrder();
$grandTotal = $order->getGrandTotal();
$orderPayment->setAdditionalInformation('amount', $grandTotal);
$order->setStatus('payment_review')->setState('payment_review');
$this->checkoutSession->unsUpdateOrderStatus();
}
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Plugin\Adminhtml;
use Magento\Sales\Block\Adminhtml\Order\View as OrderView;
use Payoneer\OpenPaymentGateway\Model\Adminhtml\Helper;
/**
* Class SalesOrderViewPlugin
* Add Payoneer action buttons
*/
class OrderViewPlugin
{
/**
* @var Helper
*/
protected $helper;
/**
* OrderViewPlugin constructor.
* @param Helper $helper
*/
public function __construct(
Helper $helper
) {
$this->helper = $helper;
}
/**
* Add Payoneer action buttons
*
* @param OrderView $subject
* @return void
*/
public function beforeSetLayout(OrderView $subject)
{
if ($this->helper->isPayoneerEnabled()) {
$order = $subject->getOrder();
if ($this->helper->canShowCaptureBtn($order)) {
$subject->addButton(
'payoneer_capture',
[
'label' => __('Payoneer capture'),
'class' => __('action-default scalable'),
'id' => 'order-view-payoneer-capture-button',
'onclick' => 'setLocation(\'' . $this->getCaptureUrl($subject) . '\')'
]
);
}
if ($this->helper->isPayoneerOrder($order)) {
$subject->addButton(
'payoneer_fetch',
[
'label' => __('Payoneer fetch'),
'class' => __('action-default scalable'),
'id' => 'order-view-payoneer-fetch-button',
'onclick' => 'setLocation(\'' . $this->getFetchUrl($subject) . '\')'
]
);
}
}
}
/**
* Get URL for Payoneer capture
*
* @param OrderView $subject
* @return mixed
*/
protected function getCaptureUrl($subject)
{
return $subject->getUrl('payoneer/gateway/capture/order_id/' . $subject->getOrderId());
}
/**
* Get URL for Payoneer fetch
*
* @param OrderView $subject
* @return mixed
*/
protected function getFetchUrl($subject)
{
return $subject->getUrl('payoneer/gateway/fetch/order_id/' . $subject->getOrderId());
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Plugin\Cart;
use Magento\Checkout\Model\Cart;
use Magento\Framework\Exception\LocalizedException;
use Magento\Quote\Api\CartRepositoryInterface;
use Magento\Quote\Model\Quote;
use Payoneer\OpenPaymentGateway\Gateway\Config\Config;
use Payoneer\OpenPaymentGateway\Model\ListUpdateTransactionService;
/**
* CartUpdatePlugin - Update list if list session already exists
*/
class CartUpdatePlugin
{
const LIST_EXPIRED = 'list_expired';
/**
* @var ListUpdateTransactionService
*/
private $transactionService;
/**
* @var Config
*/
private $config;
/**
* @var CartRepositoryInterface
*/
private $cartRepository;
/**
* CartUpdatePlugin constructor.
* @param ListUpdateTransactionService $transactionService
* @param Config $config
* @param CartRepositoryInterface $cartRepository
*/
public function __construct(
ListUpdateTransactionService $transactionService,
Config $config,
CartRepositoryInterface $cartRepository
) {
$this->transactionService = $transactionService;
$this->config = $config;
$this->cartRepository = $cartRepository;
}
/**
* @param Cart $cart
* @param mixed $result
* @return mixed
* @throws LocalizedException
*/
public function afterSave(Cart $cart, $result)
{
if (!$this->config->isPayoneerEnabled()) {
return $result;
}
$quoteId = $cart->getQuote()->getId();
/** @var Quote $quote */
$quote = $this->cartRepository->get($quoteId);
$payment = $quote->getPayment();
$additionalInformation = $payment->getAdditionalInformation();
$listId = isset($additionalInformation[Config::LIST_ID]) ? $additionalInformation[Config::LIST_ID] : null;
if (!$listId) {
return $result;
} else {
/** @var array <mixed> $response */
$response = $this->transactionService->process($quote->getPayment(), Config::LIST_UPDATE);
if ($this->isListExpired($response)) {
$payment->setAdditionalInformation(Config::LIST_ID, null);
$payment->setAdditionalInformation(Config::REDIRECT_URL, null);
$quote->setPayment($payment);
$this->cartRepository->save($quote);
} elseif ($this->updateError($response)) {
$this->transactionService->process($quote->getPayment(), Config::LIST_DELETE);
$payment->setAdditionalInformation(Config::LIST_ID, null);
$payment->setAdditionalInformation(Config::REDIRECT_URL, null);
$quote->setPayment($payment);
$this->cartRepository->save($quote);
}
}
return $result;
}
/**
* @param array <mixed> $result
* @return bool
*/
public function isListExpired($result)
{
if (isset($result['reason']) && str_contains($result['reason'], self::LIST_EXPIRED)) {
return true;
}
return false;
}
/**
* @param array <mixed> $result
* @return bool
*/
public function updateError($result)
{
if (isset($result['status']) && $result['status'] == 422) {
return true;
} else {
return false;
}
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Plugin\Customer\Model;
use Magento\Checkout\Model\Session as CheckoutSession;
use Magento\Framework\App\Request\Http;
/**
* AccountManagement - Save guest email on session
*/
class AccountManagement
{
/**
* @var CheckoutSession
*/
protected $checkoutSession;
/**
* @var Http
*/
protected $request;
/**
* AccountManagement constructor.
* @param CheckoutSession $checkoutSession
* @param Http $request
*/
public function __construct(
CheckoutSession $checkoutSession,
Http $request
) {
$this->checkoutSession = $checkoutSession;
$this->request = $request;
}
/**
* @param \Magento\Customer\Model\AccountManagement $subject
* @param bool $result
* @param string $customerEmail
* @return bool
*/
public function afterIsEmailAvailable(\Magento\Customer\Model\AccountManagement $subject, $result, $customerEmail)
{
if (stripos($this->request->getRequestString(), 'customers/isEmailAvailable') === false) {
return $result;
}
$this->checkoutSession->setPayoneerCustomerEmail($customerEmail);
return $result;
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Plugin\Order\Email\Sender;
use Magento\Sales\Model\Order\Email\Sender\OrderSender;
use Magento\Checkout\Model\Session as CheckoutSession;
use Magento\Sales\Model\Order;
/**
* Class OrderSenderPlugin
* Plugin to skip order confirmation email sending
* for cancelled orders.
*/
class OrderSenderPlugin
{
/**
* @var CheckoutSession
*/
private $checkoutSession;
/**
* Construct function
*
* @param CheckoutSession $checkoutSession
* @return void
*/
public function __construct(
CheckoutSession $checkoutSession
) {
$this->checkoutSession = $checkoutSession;
}
/**
* Around plugin will skip order sending if the order is a payoneer
* cancel order, else do the email sending.
*
* @param OrderSender $subject
* @param callable $proceed
* @param Order $order
* @param boolean $forceSyncMode
* @return boolean
*/
public function aroundSend(OrderSender $subject, callable $proceed, Order $order, $forceSyncMode = false)
{
$isCancelledOrder = $this->checkoutSession->getIsPayoneerCancelledOrder();
if ($isCancelledOrder == true) {
$this->checkoutSession->unsIsPayoneerCancelledOrder();
return false;
}
return $proceed($order, $forceSyncMode);
}
}
<?php
namespace Payoneer\OpenPaymentGateway\Plugin\Order;
use Magento\Checkout\Model\Session as CheckoutSession;
use Magento\Sales\Model\Order\Payment;
/**
* Class PaymentPlugin
* Plugin to skip the invoice creation for a payoneer
* cancelled orders.
*/
class PaymentPlugin
{
/**
* @var CheckoutSession
*/
private $checkoutSession;
/**
* Construct function
*
* @param CheckoutSession $checkoutSession
* @return void
*/
public function __construct(
CheckoutSession $checkoutSession
) {
$this->checkoutSession = $checkoutSession;
}
/**
* Around plugin will skip invoice creation for the
* payoneer cancelled orders.
*
* @param Payment $subject
* @param callable $proceed
* @param null $invoice
* @return Payment
*/
public function aroundCapture(
Payment $subject,
callable $proceed,
$invoice = null
) {
$skipInvoiceCreation = $this->checkoutSession->getPayoneerSkipInvoiceCreation();
if ($skipInvoiceCreation == true) {
$this->checkoutSession->unsPayoneerSkipInvoiceCreation();
return $subject;
}
return $proceed($invoice);
}
}
# Payoneer Magento 2 Integration Extension with [Open Payment Gateway](https://www.optile.io/opg)
This library includes the files of the Magento 2 Extension for Payoneer Checkout or Open Payment Gateway (OPG). The directories hierarchy is as positioned in a standard magento 2 project library.
## Version
Payoneer Magento Extension version: 0.7
## Release Notes
### 0.7 - 2022-11-18
- Send system information in list session call
### 0.6 - 2022-11-01
- Fixed issue with locale sent to PSP list session
- Updated documentation
### 0.5 - 2022-09-30
- Compatibility with Magento 2.4.5
- Fixed issue with mini cart in case of failed transactions.
- Update list session for already existing sessions.
- Create new payment list session if the update session fails.
- Fixed issue with transaction page for captured orders.
- New Feature to display static payment icons.
- Handle notifications from Payoneer immediately.
- Compatibility with Magento 2.4.0
- Fixed issue with invoice of cancelled orders.
- Localization in Simplified and Traditional Chinese.
- Fixed issue with partial refund.
## Requirements
Magento versions 2.4.0 - 2.4.5
PHP versions 7.3, 7.4, 8.1
## Install via [composer](https://getcomposer.org/download/)
Run the following command under your Magento 2 root dir:
```cmd
composer require payoneer/open-payment-gateway
php bin/magento maintenance:enable
php bin/magento setup:upgrade
php bin/magento setup:di:compile
php bin/magento setup:static-content:deploy
php bin/magento maintenance:disable
php bin/magento cache:clean
```
## Install manually under app/code
1. Download and place the contents of this repository under {YOUR-MAGENTO-ROOT-DIR}/app/code/Payoneer/OpenPaymentGateway.
2. Run the following commands under your Magento 2 root dir:
```cmd
php bin/magento maintenance:enable
php bin/magento module:enable Payoneer_OpenPaymentGateway
php bin/magento setup:upgrade
php bin/magento setup:di:compile
php bin/magento setup:static-content:deploy
php bin/magento maintenance:disable
php bin/magento cache:clean
```
## Usage
After the installation, Go to the Magento 2 admin panel
Go to Stores -> Settings -> Configuration -> Sales -> Payment Methods -> Other Payment Methods -> Payoneer Checkout
![Payoneer Main Configuration](docs/img/payoneer_config.png)
Enable the payment gateway and choose whether it's Test environment or Live. Provide the corresponding merchant code and API key. If your Merchant account doesn't have a default Store code against it, provide it here.
## Advanced Configuration
![Payoneer Advanced Configuration](docs/img/payoneer_advanced_config.png)
You can set advanced configurations here.
## Style Configuration
![Payoneer Style Configuration](docs/img/payoneer_style_config.png)
You can set the style of the embedded widget from here. Please note that, styles defined in the field Custom CSS will override any conflicting configurations as per CSS specificity rules.
{
"name": "payoneer/open-payment-gateway",
"description": "Integration with Payoneer Open Payment Gateway (OPG)",
"require": {
"php": "~7.3|~7.4|~8.1"
},
"type": "magento2-module",
"version": "0.7",
"license": [
"OSL-3.0",
"AFL-3.0"
],
"autoload": {
"files": [ "registration.php" ],
"psr-4": {
"Payoneer\\OpenPaymentGateway\\": ""
}
}
}
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<type name="Payoneer\OpenPaymentGateway\Block\Info">
<arguments>
<argument name="data" xsi:type="array">
<item xsi:type="string" name="is_secure_mode">0</item>
</argument>
</arguments>
</type>
<type name="Magento\Sales\Block\Adminhtml\Order\View">
<plugin name="payoneer_action_buttons" type="Payoneer\OpenPaymentGateway\Plugin\Adminhtml\OrderViewPlugin"/>
</type>
</config>
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
<event name="admin_system_config_changed_section_payment">
<observer name="payoneer_admin_system_config_changed_section" instance="Payoneer\OpenPaymentGateway\Observer\Config\ValidateCredentialsObserver" />
</event>
</config>
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
<router id="admin">
<route id="payoneer" frontName="payoneer">
<module name="Payoneer_OpenPaymentGateway" before="Magento_Backend" />
</route>
</router>
</config>
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd">
<system>
<section id="payment">
<group id="payoneer" translate="label" type="text" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="1">
<label>Payoneer Checkout</label>
<field id="active" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1">
<label>Enabled</label>
<source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
</field>
<field id="title" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1">
<label>Title</label>
</field>
<field id="merchant_gateway_key" translate="label" type="text" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="1">
<label>API username</label>
</field>
<field id="environment" translate="label" type="select" sortOrder="40" showInDefault="1" showInWebsite="1" showInStore="1">
<label>Environment</label>
<source_model>Payoneer\OpenPaymentGateway\Model\Adminhtml\Source\Fields</source_model>
</field>
<field id="live_api_key" translate="label" type="password" sortOrder="50" showInDefault="1" showInWebsite="1" showInStore="1">
<label>Live API token</label>
<depends>
<field id="environment">live</field>
</depends>
</field>
<field id="live_store_code" translate="label" type="text" sortOrder="60" showInDefault="1" showInWebsite="1" showInStore="1">
<label>Live Store code</label>
<depends>
<field id="environment">live</field>
</depends>
</field>
<field id="live_host_name" translate="label" type="text" sortOrder="70" showInDefault="0" showInWebsite="0" showInStore="0">
<label>Live OPG hostname</label>
<validate>validate-url validate-no-html-tags</validate>
<depends>
<field id="environment">live</field>
</depends>
</field>
<field id="sandbox_api_key" translate="label" type="password" sortOrder="80" showInDefault="1" showInWebsite="1" showInStore="1">
<label>Test API token</label>
<depends>
<field id="environment">test</field>
</depends>
</field>
<field id="sandbox_store_code" translate="label" type="text" sortOrder="90" showInDefault="1" showInWebsite="1" showInStore="1">
<label>Test Store code</label>
<depends>
<field id="environment">test</field>
</depends>
</field>
<field id="sandbox_host_name" translate="label" type="text" sortOrder="100" showInDefault="0" showInWebsite="0" showInStore="0">
<label>Test OPG hostname</label>
<validate>validate-url validate-no-html-tags</validate>
<depends>
<field id="environment">test</field>
</depends>
</field>
<field id="key_validation" translate="label comment" type="text" sortOrder="110" showInDefault="1" showInWebsite="1" showInStore="1">
<label></label>
<frontend_model>Payoneer\OpenPaymentGateway\Block\Adminhtml\Form\Field\ValidateCredentials</frontend_model>
<comment>Makes a List API call to check if it returns HTTP 200 and the interaction reason is OK</comment>
</field>
<field id="success_url" translate="label" type="text" sortOrder="111" showInDefault="1" showInWebsite="1" showInStore="1">
<label>Success URL</label>
<frontend_model>Payoneer\OpenPaymentGateway\Block\Adminhtml\Form\Field\Disable</frontend_model>
</field>
<field id="cancel_url" translate="label" type="text" sortOrder="112" showInDefault="1" showInWebsite="1" showInStore="1">
<label>Cancel URL</label>
<frontend_model>Payoneer\OpenPaymentGateway\Block\Adminhtml\Form\Field\Disable</frontend_model>
</field>
<field id="notification_url" translate="label" type="text" sortOrder="113" showInDefault="1" showInWebsite="1" showInStore="1">
<label>Notification URL</label>
</field>
<field id="payment_action" translate="label" type="select" sortOrder="120" showInDefault="1" showInWebsite="1" showInStore="1">
<label>Payment action</label>
<source_model>Payoneer\OpenPaymentGateway\Model\Adminhtml\Source\Fields::paymentAction</source_model>
</field>
<field id="payment_flow" translate="label" type="select" sortOrder="130" showInDefault="1" showInWebsite="1" showInStore="1">
<label>Payment flow</label>
<source_model>Payoneer\OpenPaymentGateway\Model\Adminhtml\Source\Fields::paymentFlow</source_model>
</field>
<field id="order_reference_message" translate="label" type="text" sortOrder="140" showInDefault="1" showInWebsite="1" showInStore="1">
<label>Order reference message</label>
<validate>required-entry</validate>
</field>
<field id="language_mapping" translate="label" type="text" sortOrder="145" showInDefault="0" showInWebsite="0" showInStore="0">
<label>Magento-Payoneer Language Mapping</label>
<comment>Mapping will be in the format - magento_language_code:payoneer_language_code. Multiple entries should be separated by comma. Eg: en_CA:en_US,en_NL:en_GB...</comment>
</field>
<field id="sort_order" translate="label" type="text" sortOrder="150" showInDefault="1" showInWebsite="1" showInStore="1">
<label>Payment method position</label>
<frontend_class>validate-number</frontend_class>
</field>
<field id="debug" translate="label" type="select" sortOrder="160" showInDefault="1" showInWebsite="1" showInStore="1">
<label>Debug</label>
<source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
</field>
<group id="notification_settings" translate="label" showInDefault="0" showInWebsite="0" showInStore="0" sortOrder="170">
<label>Notification Setings</label>
<frontend_model>Magento\Config\Block\System\Config\Form\Fieldset</frontend_model>
<field id="cleanup_days" translate="label" type="text" sortOrder="145" showInDefault="1" showInWebsite="1" showInStore="1">
<label>Notification Cleanup Days</label>
<comment>Cronjob will remove all the processed notifications which are created this much of days before.eg:4, means remove notification created 4 days before.</comment>
</field>
<field id="send_email_days" translate="label" type="text" sortOrder="145" showInDefault="1" showInWebsite="1" showInStore="1">
<label>Email Notification Days</label>
<comment>Send email notifications for the unprocessed notification which are created this much of days before.eg:4, means send email for notification which are created 4 days before.</comment>
</field>
</group>
<group id="widget_appearance" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="175">
<label>Payment widget appearance</label>
<frontend_model>Magento\Config\Block\System\Config\Form\Fieldset</frontend_model>
<field id="background_color" translate="label comment" type="text" sortOrder="180" showInDefault="1" showInWebsite="1" showInStore="1">
<label>Background color</label>
<comment><![CDATA[Payment widget background color]]></comment>
<frontend_model>Payoneer\OpenPaymentGateway\Block\Adminhtml\Form\Field\ColorPicker</frontend_model>
</field>
<field id="color" translate="label comment" type="text" sortOrder="190" showInDefault="1" showInWebsite="1" showInStore="1">
<label>Text color</label>
<comment>Payment widget text color</comment>
<frontend_model>Payoneer\OpenPaymentGateway\Block\Adminhtml\Form\Field\ColorPicker</frontend_model>
</field>
<field id="placeholders_color" translate="label comment" type="text" sortOrder="200" showInDefault="1" showInWebsite="1" showInStore="1">
<label>Placeholder color</label>
<comment>Payment widget placeholder color</comment>
<frontend_model>Payoneer\OpenPaymentGateway\Block\Adminhtml\Form\Field\ColorPicker</frontend_model>
</field>
<field id="font_size" translate="label comment" type="text" sortOrder="210" showInDefault="1" showInWebsite="1" showInStore="1">
<label>Font size</label>
<comment>Payment widget font size in px</comment>
</field>
<field id="font_weight" translate="label comment" type="select" sortOrder="220" showInDefault="1" showInWebsite="1" showInStore="1">
<label>Font weight</label>
<comment>Payment widget font weight in px</comment>
<source_model>Payoneer\OpenPaymentGateway\Model\Adminhtml\Source\Fields::fontWeight</source_model>
</field>
<field id="letter_spacing" translate="label comment" type="text" sortOrder="230" showInDefault="1" showInWebsite="1" showInStore="1">
<label>Letter spacing</label>
<comment>Payment widget letter spacing in px</comment>
</field>
<field id="line_height" translate="label comment" type="text" sortOrder="240" showInDefault="1" showInWebsite="1" showInStore="1">
<label>Line height</label>
<comment>Payment widget line height, e.g. 1.5 or normal</comment>
</field>
<field id="padding" translate="label comment" type="text" sortOrder="250" showInDefault="1" showInWebsite="1" showInStore="1">
<label>Padding</label>
<comment>Payment widget padding in px</comment>
</field>
<field id="text_align" translate="label comment" type="select" sortOrder="260" showInDefault="1" showInWebsite="1" showInStore="1">
<label>Align text</label>
<comment>Payment widget text alignment</comment>
<source_model>Payoneer\OpenPaymentGateway\Model\Adminhtml\Source\Fields::alignText</source_model>
</field>
<field id="checkout_css" translate="label" type="textarea" sortOrder="270" showInDefault="1" showInWebsite="1" showInStore="1">
<label>Custom CSS</label>
</field>
<field id="payment_icon_type" translate="label" type="select" sortOrder="160" showInDefault="1" showInWebsite="1" showInStore="1">
<label>Payment Icon Type</label>
<source_model>Payoneer\OpenPaymentGateway\Model\Config\Source\PaymentIconType</source_model>
</field>
</group>
</group>
</section>
</system>
</config>
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/config.xsd">
<default>
<payment>
<payoneer>
<active>0</active>
<model>PayoneerFacade</model>
<order_status>pending_payment</order_status>
<payment_action>authorize</payment_action>
<title>Payoneer Checkout</title>
<live_host_name>https://api.live.oscato.com/</live_host_name>
<sandbox_host_name>https://api.sandbox.oscato.com/</sandbox_host_name>
<success_url>{{base_url}}payoneer/redirect/success</success_url>
<cancel_url>{{base_url}}payoneer/redirect/cancel</cancel_url>
<currency>USD</currency>
<can_authorize>1</can_authorize>
<can_capture>1</can_capture>
<can_void>1</can_void>
<can_use_checkout>1</can_use_checkout>
<is_gateway>1</is_gateway>
<sort_order>1</sort_order>
<can_refund>1</can_refund>
<can_refund_partial_per_invoice>1</can_refund_partial_per_invoice>
<language_mapping>zh_Hans_CN:zh_CN,zh_Hant_TW:zh_TW</language_mapping>
<notification_settings>
<cleanup_days>100</cleanup_days>
<send_email_days>1</send_email_days>
</notification_settings>
<widget_appearance>
<payment_icon_type>static</payment_icon_type>
</widget_appearance>
</payoneer>
</payment>
<csp>
<mode>
<storefront>
<report_only>1</report_only>
</storefront>
<admin>
<report_only>1</report_only>
</admin>
</mode>
<policies>
<storefront>
<frame-ancestors>
<inline>0</inline>
</frame-ancestors>
</storefront>
</policies>
</csp>
</default>
</config>
<?xml version="1.0" ?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Cron:etc/crontab.xsd">
<group id="default">
<job instance="Payoneer\OpenPaymentGateway\Cron\OrderUpdate" method="execute" name="payoneer_openpaymentgateway_orderupdate">
<schedule>*/5 * * * *</schedule>
</job>
</group>
</config>
<?xml version="1.0"?>
<csp_whitelist xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Csp/etc/csp_whitelist.xsd">
<policies>
<policy id="script-src">
<values>
<value id="assets-optile-script" type="host">https://assets.optile.net</value>
<value id="oscato-payment-script" type="host">*.oscato.com</value>
</values>
</policy>
<policy id="connect-src">
<values>
<value id="oscato-payment-src" type="host">*.oscato.com</value>
</values>
</policy>
<policy id="frame-src">
<values>
<value id="oscato-payment-api-frame" type="host">*.oscato.com</value>
</values>
</policy>
<policy id="img-src">
<values>
<value id="sandbox-paypal-image" type="host">https://hnd.stats.paypal.com</value>
<value id="oscato-payment-api-img" type="host">*.oscato.com</value>
</values>
</policy>
</policies>
</csp_whitelist>
<?xml version="1.0"?>
<schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd">
<table name="payoneer_payment_transaction" resource="default" engine="innodb"
comment="Payoneer Payment Transaction">
<column xsi:type="int" name="transaction_id" unsigned="true" nullable="false" identity="true"
comment="Transaction Id"/>
<column xsi:type="int" name="customer_id" unsigned="true" nullable="false" comment="Customer ID"/>
<column xsi:type="varchar" name="registration_id" nullable="true" length="255"
comment="Registration Id"/>
<column xsi:type="timestamp" name="created_at" on_update="false" nullable="false" default="CURRENT_TIMESTAMP"
comment="Creation Time"/>
<column xsi:type="timestamp" name="updated_at" on_update="true" nullable="false" default="CURRENT_TIMESTAMP"
comment="Update Time"/>
<constraint xsi:type="primary" referenceId="PRIMARY">
<column name="transaction_id"/>
</constraint>
<constraint xsi:type="foreign" referenceId="PAYONEER_PAYMENT_TRANSACTION_CUSTOMER_ID_CUSTOMER_ENTITY_ENTITY_ID"
table="payoneer_payment_transaction" column="customer_id" referenceTable="customer_entity"
referenceColumn="entity_id" onDelete="CASCADE"/>
<constraint xsi:type="unique" referenceId="PAYONEER_PAYMENT_TRANSACTION_CUSTOMER_ID">
<column name="customer_id"/>
</constraint>
</table>
<table name="payoneer_notification" resource="default" engine="innodb" comment="payoneer notification Table">
<column xsi:type="int" name="id" unsigned="true" nullable="false" padding="10" identity="true" comment="Entity Id"/>
<constraint xsi:type="primary" referenceId="PRIMARY">
<column name="id"/>
</constraint>
<column xsi:type="varchar" name="transactionId" nullable="true" length="255"
comment="transaction Id"/>
<column xsi:type="varchar" name="longId" nullable="true" length="255"
comment="long Id"/>
<column xsi:type="varchar" name="order_id" nullable="true" length="255"
comment="Order increment id"/>
<column xsi:type="longtext" name="content" nullable="true"
comment="notification content"/>
<column xsi:type="varchar" name="cron_status" nullable="true" length="255"
comment="cron status"/>
<column xsi:type="smallint" name="send_email" unsigned="true" nullable="true" identity="false" default="0"
comment="Send Email"/>
<column xsi:type="timestamp" name="created_at" on_update="false" nullable="false" default="CURRENT_TIMESTAMP"
comment="Creation Time"/>
<column xsi:type="timestamp" name="updated_at" on_update="true" nullable="false" default="CURRENT_TIMESTAMP"
comment="Update Time"/>
</table>
</schema>
{
"payoneer_payment_transaction": {
"column": {
"transaction_id": true,
"customer_id": true,
"registration_id": true,
"created_at": true,
"updated_at": true
},
"constraint": {
"PRIMARY": true,
"PAYONEER_PAYMENT_TRANSACTION_CSTR_ID_CSTR_ENTT_ENTT_ID": true,
"PAYONEER_PAYMENT_TRANSACTION_CUSTOMER_ID": true
}
},
"payoneer_notification": {
"column": {
"id": true,
"transactionId": true,
"longId": true,
"order_id": true,
"content": true,
"cron_status": true,
"created_at": true,
"updated_at": true
},
"constraint": {
"PRIMARY": true
}
}
}
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<preference for="Magento\Payment\Gateway\Data\Quote\QuoteAdapter" type="Payoneer\OpenPaymentGateway\Gateway\QuoteAdapter" />
<preference for="Payoneer\OpenPaymentGateway\Api\PayoneerNotificationRepositoryInterface" type="Payoneer\OpenPaymentGateway\Model\PayoneerNotificationRepository" />
<preference for="Payoneer\OpenPaymentGateway\Api\Data\NotificationInterface" type="Payoneer\OpenPaymentGateway\Model\PayoneerNotification" />
<!-- Payment Method Facade configuration -->
<virtualType name="PayoneerFacade" type="Magento\Payment\Model\Method\Adapter">
<arguments>
<argument name="code" xsi:type="const">Payoneer\OpenPaymentGateway\Model\Ui\ConfigProvider::CODE</argument>
<argument name="formBlockType" xsi:type="string">Magento\Payment\Block\Form</argument>
<argument name="infoBlockType" xsi:type="string">Payoneer\OpenPaymentGateway\Block\Info</argument>
<argument name="valueHandlerPool" xsi:type="object">PayoneerValueHandlerPool</argument>
<argument name="commandPool" xsi:type="object">PayoneerCommandPool</argument>
</arguments>
</virtualType>
<!-- Configuration reader -->
<virtualType name="PayoneerConfig" type="Payoneer\OpenPaymentGateway\Gateway\Config\Config">
<arguments>
<argument name="methodCode" xsi:type="const">Payoneer\OpenPaymentGateway\Model\Ui\ConfigProvider::CODE</argument>
</arguments>
</virtualType>
<!-- Information block -->
<type name="Payoneer\OpenPaymentGateway\Block\Info">
<arguments>
<argument name="config" xsi:type="object">PayoneerConfig</argument>
</arguments>
</type>
<!-- Payoneer Client -->
<type name="Payoneer\OpenPaymentGateway\Gateway\Http\Client\Client">
<arguments>
<argument name="logger" xsi:type="object">PayoneerPaymentLogger</argument>
</arguments>
</type>
<!-- Payoneer Transaction Service -->
<type name="Payoneer\OpenPaymentGateway\Model\TransactionService">
<arguments>
<argument name="config" xsi:type="object">PayoneerConfig</argument>
<argument name="commandPool" xsi:type="object">PayoneerCommandPool</argument>
</arguments>
</type>
<type name="Payoneer\OpenPaymentGateway\Model\ListUpdateTransactionService">
<arguments>
<argument name="commandPool" xsi:type="object">PayoneerCommandPool</argument>
</arguments>
</type>
<!-- Payoneer Transaction Order Updater -->
<type name="Payoneer\OpenPaymentGateway\Model\TransactionOrderUpdater">
<arguments>
<argument name="commandPool" xsi:type="object">PayoneerCommandPool</argument>
</arguments>
</type>
<!-- Payoneer Adminhtml Transaction Service -->
<type name="Payoneer\OpenPaymentGateway\Model\Adminhtml\TransactionService">
<arguments>
<argument name="commandPool" xsi:type="object">PayoneerCommandPool</argument>
</arguments>
</type>
<!-- Commands infrastructure -->
<virtualType name="PayoneerCommandPool" type="Magento\Payment\Gateway\Command\CommandPool">
<arguments>
<argument name="commands" xsi:type="array">
<item name="authorize" xsi:type="string">PayoneerAuthorizeCommand</item>
<item name="capture" xsi:type="string">PayoneerCaptureCommand</item>
<item name="refund" xsi:type="string">PayoneerRefundCommand</item>
<item name="authorize_cancel" xsi:type="string">PayoneerCancelAuthorizationCommand</item>
<item name="hosted" xsi:type="string">PayoneerListCommand</item>
<item name="embedded" xsi:type="string">PayoneerListCommand</item>
<item name="list_capture" xsi:type="string">PayoneerListCaptureCommand</item>
<item name="list_fetch" xsi:type="string">PayoneerListFetchCommand</item>
<item name="list_update" xsi:type="string">PayoneerListUpdateCommand</item>
<item name="list_delete" xsi:type="string">PayoneerListDeleteCommand</item>
</argument>
</arguments>
</virtualType>
<!-- Payoneer List command -->
<virtualType name="PayoneerListCommand" type="Payoneer\OpenPaymentGateway\Gateway\Command\GatewayCommand">
<arguments>
<argument name="requestBuilder" xsi:type="object">PayoneerListRequestBuilder</argument>
<argument name="transferFactory" xsi:type="object">PayoneerListTransferFactory</argument>
<argument name="client" xsi:type="object">PayoneerClientList</argument>
</arguments>
</virtualType>
<!-- Payoneer List Request -->
<virtualType name="PayoneerListRequestBuilder" type="Magento\Payment\Gateway\Request\BuilderComposite">
<arguments>
<argument name="builders" xsi:type="array">
<item name="base_request" xsi:type="string">Payoneer\OpenPaymentGateway\Gateway\Request\BaseRequestDataBuilder</item>
<item name="customer" xsi:type="string">Payoneer\OpenPaymentGateway\Gateway\Request\CustomerDataBuilder</item>
<item name="addresses" xsi:type="string">Payoneer\OpenPaymentGateway\Gateway\Request\AddressDataBuilder</item>
<item name="items" xsi:type="string">Payoneer\OpenPaymentGateway\Gateway\Request\ItemsDataBuilder</item>
<item name="payment" xsi:type="string">Payoneer\OpenPaymentGateway\Gateway\Request\PaymentDataBuilder</item>
<item name="style" xsi:type="string">Payoneer\OpenPaymentGateway\Gateway\Request\StyleDataBuilder</item>
<item name="preselection" xsi:type="string">Payoneer\OpenPaymentGateway\Gateway\Request\PreselectionBuilder</item>
<item name="callback" xsi:type="string">Payoneer\OpenPaymentGateway\Gateway\Request\CallBackDataBuilder</item>
</argument>
</arguments>
</virtualType>
<!-- Payoneer List Transfer Factory -->
<virtualType name="PayoneerListTransferFactory" type="Payoneer\OpenPaymentGateway\Gateway\Http\TransferFactory">
<arguments>
<argument name="method" xsi:type="const">Payoneer\OpenPaymentGateway\Gateway\Config\Config::METHOD_POST</argument>
</arguments>
</virtualType>
<!-- Payoneer List Client -->
<virtualType name="PayoneerClientList" type="Payoneer\OpenPaymentGateway\Gateway\Http\Client\Client">
<arguments>
<argument name="operation" xsi:type="const">Payoneer\OpenPaymentGateway\Gateway\Http\Client\Client::LIST</argument>
</arguments>
</virtualType>
<!-- Payoneer List Update Command -->
<virtualType name="PayoneerListUpdateCommand" type="Payoneer\OpenPaymentGateway\Gateway\Command\GatewayCommand">
<arguments>
<argument name="requestBuilder" xsi:type="object">PayoneerListUpdateRequestBuilder</argument>
<argument name="transferFactory" xsi:type="object">PayoneerListUpdateTransferFactory</argument>
<argument name="client" xsi:type="object">PayoneerListUpdateClient</argument>
</arguments>
</virtualType>
<!-- Payoneer List Update Request Builder -->
<virtualType name="PayoneerListUpdateRequestBuilder" type="Magento\Payment\Gateway\Request\BuilderComposite">
<arguments>
<argument name="builders" xsi:type="array">
<item name="base_request" xsi:type="string">Payoneer\OpenPaymentGateway\Gateway\Request\BaseRequestDataBuilder</item>
<item name="customer" xsi:type="string">Payoneer\OpenPaymentGateway\Gateway\Request\CustomerDataBuilder</item>
<item name="addresses" xsi:type="string">Payoneer\OpenPaymentGateway\Gateway\Request\AddressDataBuilder</item>
<item name="items" xsi:type="string">Payoneer\OpenPaymentGateway\Gateway\Request\ItemsDataBuilder</item>
<item name="payment" xsi:type="string">Payoneer\OpenPaymentGateway\Gateway\Request\PaymentDataBuilder</item>
<item name="style" xsi:type="string">Payoneer\OpenPaymentGateway\Gateway\Request\StyleDataBuilder</item>
<item name="callback" xsi:type="string">Payoneer\OpenPaymentGateway\Gateway\Request\CallBackDataBuilder</item>
</argument>
</arguments>
</virtualType>
<!-- Payoneer List Update Transfer Factory -->
<virtualType name="PayoneerListUpdateTransferFactory" type="Payoneer\OpenPaymentGateway\Gateway\Http\TransferFactory">
<arguments>
<argument name="method" xsi:type="const">Payoneer\OpenPaymentGateway\Gateway\Config\Config::METHOD_PUT</argument>
</arguments>
</virtualType>
<!-- Payoneer List Update Client -->
<virtualType name="PayoneerListUpdateClient" type="Payoneer\OpenPaymentGateway\Gateway\Http\Client\Client">
<arguments>
<argument name="operation" xsi:type="const">Payoneer\OpenPaymentGateway\Gateway\Http\Client\Client::LIST_UPDATE</argument>
</arguments>
</virtualType>
<!-- Payoneer List Delete Command -->
<virtualType name="PayoneerListDeleteCommand" type="Payoneer\OpenPaymentGateway\Gateway\Command\GatewayCommand">
<arguments>
<argument name="requestBuilder" xsi:type="object">PayoneerListDeleteRequestBuilder</argument>
<argument name="transferFactory" xsi:type="object">PayoneerListDeleteTransferFactory</argument>
<argument name="client" xsi:type="object">PayoneerListDeleteClient</argument>
</arguments>
</virtualType>
<!-- Payoneer List Delete Request Builder -->
<virtualType name="PayoneerListDeleteRequestBuilder" type="Magento\Payment\Gateway\Request\BuilderComposite">
</virtualType>
<!-- Payoneer List Delete Transfer Factory -->
<virtualType name="PayoneerListDeleteTransferFactory" type="Payoneer\OpenPaymentGateway\Gateway\Http\TransferFactory">
<arguments>
<argument name="method" xsi:type="const">Payoneer\OpenPaymentGateway\Gateway\Config\Config::METHOD_DELETE</argument>
</arguments>
</virtualType>
<!-- Payoneer List Update Client -->
<virtualType name="PayoneerListDeleteClient" type="Payoneer\OpenPaymentGateway\Gateway\Http\Client\Client">
<arguments>
<argument name="operation" xsi:type="const">Payoneer\OpenPaymentGateway\Gateway\Http\Client\Client::LIST_DELETE</argument>
</arguments>
</virtualType>
<!-- Payoneer List Capture Command -->
<virtualType name="PayoneerListCaptureCommand" type="Payoneer\OpenPaymentGateway\Gateway\Command\GatewayCommand">
<arguments>
<argument name="requestBuilder" xsi:type="object">PayoneerListCaptureRequest</argument>
<argument name="transferFactory" xsi:type="object">PayoneerCaptureTransferFactory</argument>
<argument name="client" xsi:type="object">PayoneerClientListCapture</argument>
<argument name="handler" xsi:type="object">PayoneerCaptureResponseHandler</argument>
<argument name="validator" xsi:type="object">PayoneerListCaptureResponseValidator</argument>
</arguments>
</virtualType>
<!-- Payoneer capture Transfer Factory -->
<virtualType name="PayoneerCaptureTransferFactory" type="Payoneer\OpenPaymentGateway\Gateway\Http\ListCaptureTransferFactory">
<arguments>
<argument name="method" xsi:type="const">Payoneer\OpenPaymentGateway\Gateway\Config\Config::METHOD_POST</argument>
</arguments>
</virtualType>
<!-- Payoneer List Capture Request -->
<virtualType name="PayoneerListCaptureRequest" type="Magento\Payment\Gateway\Request\BuilderComposite">
<arguments>
<argument name="builders" xsi:type="array">
<item name="adminTransactionId" xsi:type="string">Payoneer\OpenPaymentGateway\Gateway\Request\AdminTransactionIDDataBuilder</item>
</argument>
</arguments>
</virtualType>
<!-- Payoneer List Capture Client -->
<virtualType name="PayoneerClientListCapture" type="Payoneer\OpenPaymentGateway\Gateway\Http\Client\Client">
<arguments>
<argument name="operation" xsi:type="const">Payoneer\OpenPaymentGateway\Gateway\Config\Config::LIST_CAPTURE</argument>
</arguments>
</virtualType>
<!-- Payoneer List Capture Response Handler -->
<virtualType name="PayoneerCaptureResponseHandler" type="Payoneer\OpenPaymentGateway\Gateway\Response\PayoneerResponseHandler">
<arguments>
<argument name="additionalInfoKey" xsi:type="const">Payoneer\OpenPaymentGateway\Gateway\Response\PayoneerResponseHandler::ADDITIONAL_INFO_KEY_CAPTURE_RESPONSE</argument>
<argument name="actionSuccessResponseKey" xsi:type="const">Payoneer\OpenPaymentGateway\Gateway\Response\PayoneerResponseHandler::AUTH_CAPTURE_STATUS_NODE</argument>
</arguments>
</virtualType>
<!-- Payoneer List Capture Response Validator -->
<virtualType name="PayoneerListCaptureResponseValidator" type="Payoneer\OpenPaymentGateway\Gateway\Validator\ResponseValidator">
<arguments>
<argument name="successStatusCode" xsi:type="const">Payoneer\OpenPaymentGateway\Gateway\Validator\ResponseValidator::CAPTURE_STATUS</argument>
<argument name="skipValidation" xsi:type="boolean">false</argument>
</arguments>
</virtualType>
<!-- Magento Authorize Command -->
<virtualType name="PayoneerAuthorizeCommand" type="Magento\Payment\Gateway\Command\GatewayCommand">
<arguments>
<argument name="requestBuilder" xsi:type="object">Payoneer\OpenPaymentGateway\Gateway\Request\TransactionIDDataBuilder</argument>
<argument name="transferFactory" xsi:type="object">Payoneer\OpenPaymentGateway\Gateway\Http\MagentoTransferFactory</argument>
<argument name="client" xsi:type="object">PayoneerClientAuthorize</argument>
<argument name="handler" xsi:type="object">Payoneer\OpenPaymentGateway\Gateway\Response\ResponseHandler</argument>
<argument name="validator" xsi:type="object">PayoneerAuthorizeResponseValidator</argument>
</arguments>
</virtualType>
<!-- Magento Authorize Client -->
<virtualType name="PayoneerClientAuthorize" type="Payoneer\OpenPaymentGateway\Gateway\Http\Client\Client">
<arguments>
<argument name="operation" xsi:type="const">Payoneer\OpenPaymentGateway\Gateway\Http\Client\Client::AUTHORIZE</argument>
</arguments>
</virtualType>
<!-- Magento Authorize Response Validator -->
<virtualType name="PayoneerAuthorizeResponseValidator" type="Payoneer\OpenPaymentGateway\Gateway\Validator\ResponseValidator">
<arguments>
<argument name="skipValidation" xsi:type="boolean">true</argument>
</arguments>
</virtualType>
<!-- Magento Capture Command -->
<virtualType name="PayoneerCaptureCommand" type="Magento\Payment\Gateway\Command\GatewayCommand">
<arguments>
<argument name="requestBuilder" xsi:type="object">Payoneer\OpenPaymentGateway\Gateway\Request\TransactionIDDataBuilder</argument>
<argument name="transferFactory" xsi:type="object">Payoneer\OpenPaymentGateway\Gateway\Http\MagentoTransferFactory</argument>
<argument name="client" xsi:type="object">PayoneerClientAuthorizeCapture</argument>
<argument name="handler" xsi:type="object">Payoneer\OpenPaymentGateway\Gateway\Response\ResponseHandler</argument>
<argument name="validator" xsi:type="object">PayoneerCaptureResponseValidator</argument>
</arguments>
</virtualType>
<!-- Magento Capture Client -->
<virtualType name="PayoneerClientAuthorizeCapture" type="Payoneer\OpenPaymentGateway\Gateway\Http\Client\Client">
<arguments>
<argument name="operation" xsi:type="const">Payoneer\OpenPaymentGateway\Gateway\Http\Client\Client::CAPTURE</argument>
</arguments>
</virtualType>
<!-- Magento Capture Response Validator -->
<virtualType name="PayoneerCaptureResponseValidator" type="Payoneer\OpenPaymentGateway\Gateway\Validator\ResponseValidator">
<arguments>
<argument name="skipValidation" xsi:type="boolean">true</argument>
</arguments>
</virtualType>
<!-- Payoneer List Fetch command -->
<virtualType name="PayoneerListFetchCommand" type="Payoneer\OpenPaymentGateway\Gateway\Command\GatewayCommand">
<arguments>
<argument name="requestBuilder" xsi:type="object">PayoneerListFetchRequest</argument>
<argument name="transferFactory" xsi:type="object">Payoneer\OpenPaymentGateway\Gateway\Http\ListFetchTransferFactory</argument>
<argument name="client" xsi:type="object">PayoneerListFetchClient</argument>
</arguments>
</virtualType>
<!-- Payoneer List Fetch Request -->
<virtualType name="PayoneerListFetchRequest" type="PayoneerListCaptureRequest">
</virtualType>
<!-- Payoneer List Fetch Client -->
<virtualType name="PayoneerListFetchClient" type="Payoneer\OpenPaymentGateway\Gateway\Http\Client\Client">
<arguments>
<argument name="operation" xsi:type="const">Payoneer\OpenPaymentGateway\Gateway\Config\Config::LIST_FETCH</argument>
</arguments>
</virtualType>
<!-- Payoneer Refund command -->
<virtualType name="PayoneerRefundCommand" type="Payoneer\OpenPaymentGateway\Gateway\Command\GatewayCommand">
<arguments>
<argument name="requestBuilder" xsi:type="object">PayoneerRefundRequest</argument>
<argument name="validator" xsi:type="object">PayoneerRefundResponseValidator</argument>
<argument name="handler" xsi:type="object">PayoneerRefundResponseHandler</argument>
<argument name="transferFactory" xsi:type="object">PayoneerRefundTransferFactory</argument>
<argument name="client" xsi:type="object">PayoneerRefundClient</argument>
<argument name="errorMessageMapper" xsi:type="object">Magento\Payment\Gateway\ErrorMapper\ErrorMessageMapper</argument>
</arguments>
</virtualType>
<!-- Payoneer Refund Transfer Factory -->
<virtualType name="PayoneerRefundTransferFactory" type="Payoneer\OpenPaymentGateway\Gateway\Http\RefundTransferFactory">
<arguments>
<argument name="method" xsi:type="const">Payoneer\OpenPaymentGateway\Gateway\Config\Config::METHOD_POST</argument>
</arguments>
</virtualType>
<!-- Payoneer Refund Response Validator -->
<virtualType name="PayoneerRefundResponseValidator" type="Payoneer\OpenPaymentGateway\Gateway\Validator\ResponseValidator">
<arguments>
<argument name="successStatusCode" xsi:type="const">Payoneer\OpenPaymentGateway\Gateway\Validator\ResponseValidator::REFUND_PAID_OUT_STATUS</argument>
</arguments>
</virtualType>
<!-- Payoneer Refund Request -->
<virtualType name="PayoneerRefundRequest" type="Magento\Payment\Gateway\Request\BuilderComposite">
<arguments>
<argument name="builders" xsi:type="array">
<item name="adminTransactionId" xsi:type="string">Payoneer\OpenPaymentGateway\Gateway\Request\AdminTransactionIDDataBuilder</item>
<item name="payment" xsi:type="string">Payoneer\OpenPaymentGateway\Gateway\Request\PaymentDataBuilder</item>
</argument>
</arguments>
</virtualType>
<!-- Payoneer Refund Client -->
<virtualType name="PayoneerRefundClient" type="Payoneer\OpenPaymentGateway\Gateway\Http\Client\Client">
<arguments>
<argument name="operation" xsi:type="const">Payoneer\OpenPaymentGateway\Gateway\Http\Client\Client::REFUND</argument>
</arguments>
</virtualType>
<!-- Payoneer Refund Response Handler -->
<virtualType name="PayoneerRefundResponseHandler" type="Payoneer\OpenPaymentGateway\Gateway\Response\PayoneerResponseHandler">
<arguments>
<argument name="additionalInfoKey" xsi:type="const">Payoneer\OpenPaymentGateway\Gateway\Response\PayoneerResponseHandler::ADDITIONAL_INFO_KEY_REFUND_RESPONSE</argument>
<argument name="transactionType" xsi:type="const">Payoneer\OpenPaymentGateway\Gateway\Response\PayoneerResponseHandler::REFUND_TXN_TYPE</argument>
</arguments>
</virtualType>
<!-- Cancel deferred(Authorization) Command -->
<virtualType name="PayoneerCancelAuthorizationCommand" type="Payoneer\OpenPaymentGateway\Gateway\Command\GatewayCommand">
<arguments>
<argument name="requestBuilder" xsi:type="object">Payoneer\OpenPaymentGateway\Gateway\Request\AdminTransactionIDDataBuilder</argument>
<argument name="validator" xsi:type="object">PayoneerCancelAuthorizationResponseValidator</argument>
<argument name="handler" xsi:type="object">PayoneerAuthCancelResponseHandler</argument>
<argument name="transferFactory" xsi:type="object">Payoneer\OpenPaymentGateway\Gateway\Http\Authorization\CancellationTransferFactory</argument>
<argument name="client" xsi:type="object">PayoneerClientAuthorizationCancellation</argument>
</arguments>
</virtualType>
<!-- Cancel Deferred(Authorization) Response Validator -->
<virtualType name="PayoneerCancelAuthorizationResponseValidator" type="Payoneer\OpenPaymentGateway\Gateway\Validator\ResponseValidator">
<arguments>
<argument name="successStatusCode" xsi:type="const">Payoneer\OpenPaymentGateway\Gateway\Validator\ResponseValidator::AUTH_CANCEL_PENDING_STATUS</argument>
</arguments>
</virtualType>
<!-- Cancel Deferred(Authorization) Client -->
<virtualType name="PayoneerClientAuthorizationCancellation" type="Payoneer\OpenPaymentGateway\Gateway\Http\Client\Client">
<arguments>
<argument name="operation" xsi:type="const">Payoneer\OpenPaymentGateway\Gateway\Http\Client\Client::AUTHORIZATION_CANCEL</argument>
</arguments>
</virtualType>
<!-- Cancel Deferred(Authorization) Response Handler -->
<virtualType name="PayoneerAuthCancelResponseHandler" type="Payoneer\OpenPaymentGateway\Gateway\Response\PayoneerResponseHandler">
<arguments>
<argument name="additionalInfoKey" xsi:type="const">Payoneer\OpenPaymentGateway\Gateway\Response\PayoneerResponseHandler::ADDITIONAL_INFO_KEY_AUTH_CANCEL_RESPONSE</argument>
<argument name="actionSuccessResponseKey" xsi:type="const">Payoneer\OpenPaymentGateway\Gateway\Response\PayoneerResponseHandler::AUTH_CANCEL_STATUS_NODE</argument>
</arguments>
</virtualType>
<!--
Ading the pool to the command manager to auto execute the
commands for the default actions.
eg: execute the refund action when the credit memo is
created from the admin side etc...
-->
<type name="Magento\Payment\Gateway\Command\CommandManagerPool">
<arguments>
<argument name="executors" xsi:type="array">
<item name="payoneer" xsi:type="string">PayoneerCommandManager</item>
</argument>
</arguments>
</type>
<virtualType name="PayoneerCommandManager" type="Magento\Payment\Gateway\Command\CommandManager">
<arguments>
<argument name="commandPool" xsi:type="object">PayoneerCommandPool</argument>
</arguments>
</virtualType>
<!-- Value Handlers Infrastructure -->
<virtualType name="PayoneerValueHandlerPool" type="Magento\Payment\Gateway\Config\ValueHandlerPool">
<arguments>
<argument name="handlers" xsi:type="array">
<item name="default" xsi:type="string">PayoneerConfigValueHandler</item>
</argument>
</arguments>
</virtualType>
<virtualType name="PayoneerConfigValueHandler" type="Magento\Payment\Gateway\Config\ConfigValueHandler">
<arguments>
<argument name="configInterface" xsi:type="object">PayoneerConfig</argument>
</arguments>
</virtualType>
<!-- Logger -->
<virtualType name="PayoneerLoggerHandler" type="\Payoneer\OpenPaymentGateway\Logger\Handler">
<arguments>
<argument name="fileName" xsi:type="string">/var/log/payoneer.log</argument>
</arguments>
</virtualType>
<virtualType name="PayoneerLogger" type="\Monolog\Logger">
<arguments>
<argument name="name" xsi:type="string">payoneer</argument>
<argument name="handlers" xsi:type="array">
<item name="payoneer" xsi:type="object">PayoneerLoggerHandler</item>
</argument>
</arguments>
</virtualType>
<virtualType name="PayoneerPaymentLogger" type="Payoneer\OpenPaymentGateway\Model\Method\Logger">
<arguments>
<argument name="config" xsi:type="object">PayoneerConfig</argument>
<argument name="logger" xsi:type="object">PayoneerLogger</argument>
</arguments>
</virtualType>
<virtualType name="PayoneerNotificationLoggerHandler" type="\Payoneer\OpenPaymentGateway\Logger\Handler">
<arguments>
<argument name="fileName" xsi:type="string">/var/log/payoneer_notification.log</argument>
</arguments>
</virtualType>
<type name="Payoneer\OpenPaymentGateway\Logger\NotificationLogger">
<arguments>
<argument name="name" xsi:type="string">payoneer_notification</argument>
<argument name="handlers" xsi:type="array">
<item name="system" xsi:type="object">PayoneerNotificationLoggerHandler</item>
</argument>
</arguments>
</type>
</config>
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Email:etc/email_templates.xsd">
<template id="notification_email_template" label="Notification Email" file="notification.html" type="html" module="Payoneer_OpenPaymentGateway" area="frontend"/>
</config>
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
<event name="payment_method_assign_data">
<observer name="payoneer_payment_gateway_data_assign" instance="Payoneer\OpenPaymentGateway\Observer\DataAssignObserver" />
</event>
<event name="order_cancel_after">
<observer name="payoneer_auth_cancel_after_order_cancel" instance="Payoneer\OpenPaymentGateway\Observer\CancelOrderObserver"/>
</event>
<event name="sales_order_invoice_save_after">
<observer name="payoneer_payment_gateway_capture" instance="Payoneer\OpenPaymentGateway\Observer\InvoiceSaveAfterObserver" />
</event>
<event name="sales_order_payment_place_end">
<observer name="payoneer_update_order_status" instance="Payoneer\OpenPaymentGateway\Observer\UpdateOrderStatusObserver"/>
</event>
<event name="sales_order_place_after">
<observer name="payoneer_redirect_previous_cart" instance="Payoneer\OpenPaymentGateway\Observer\RedirectCartObserver" />
</event>
</config>
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<type name="Magento\Checkout\Model\CompositeConfigProvider">
<arguments>
<argument name="configProviders" xsi:type="array">
<item name="payoneer_config_provider" xsi:type="object">Payoneer\OpenPaymentGateway\Model\Ui\ConfigProvider</item>
</argument>
</arguments>
</type>
<type name="Payoneer\OpenPaymentGateway\Block\Info">
<arguments>
<argument name="data" xsi:type="array">
<item xsi:type="string" name="is_secure_mode">1</item>
</argument>
</arguments>
</type>
<type name="Magento\Checkout\Model\Cart">
<plugin name="payoneer_cart_update_plugin"
type="Payoneer\OpenPaymentGateway\Plugin\Cart\CartUpdatePlugin" />
</type>
<type name="Magento\Sales\Model\Order\Email\Sender\OrderSender">
<plugin name="payoneer_order_email_sender_plugin"
type="Payoneer\OpenPaymentGateway\Plugin\Order\Email\Sender\OrderSenderPlugin" />
</type>
<type name="Magento\Sales\Model\Order\Payment">
<plugin name="payoneer_invoice_creation_blocker_plugin"
type="Payoneer\OpenPaymentGateway\Plugin\Order\PaymentPlugin" />
</type>
</config>
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
<router id="standard">
<route id="payoneer" frontName="payoneer">
<module name="Payoneer_OpenPaymentGateway" />
</route>
</router>
</config>
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Customer:etc/sections.xsd">
<action name="checkout/onepage/success">
<section name="cart"/>
</action>
<action name="payoneer/redirect/success">
<section name="cart"/>
</action>
</config>
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
<module name="Payoneer_OpenPaymentGateway" setup_version="0.7">
<sequence>
<module name="Magento_Sales"/>
<module name="Magento_Payment"/>
<module name="Magento_Checkout"/>
</sequence>
</module>
</config>
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"><!-- Plugin to save guest customer email -->
<type name="Magento\Customer\Model\AccountManagement">
<plugin name="payoneer_guest_email_save"
type="Payoneer\OpenPaymentGateway\Plugin\Customer\Model\AccountManagement"/>
</type>
</config>
"Validate credentials", "Validate credentials"
"Transaction failed", "Transaction failed"
"Data successfully synced", "Data successfully synced"
"Error response with the %1 code received from Payoneer. Check the payoneer.log file for details.", "Error response with the %1 code received from Payoneer. Check the payoneer.log file for details."
"We couldn\'t process the payment", "We couldn\'t process the payment"
"We couldn\'t capture the transaction. Check the payoneer.log file for details.", "We couldn\'t capture the transaction. Check the payoneer.log file for details."
"We couldn\'t fetch the data. Check the payoneer.log file for details.", "We couldn\'t fetch the data. Check the payoneer.log file for details."
"The order no longer exists", "The order no longer exists"
"You can\'t create an invoice with this order", "You can\'t create an invoice with this order"
"You can\'t create an invoice without products", "You can\'t create an invoice without products"
"We\'ve notified the customer that the #%1 invoice has been created", "We\'ve notified the customer that the #%1 invoice has been created"
"We can\'t send an email with the invoice. Try again later.", "We can\'t send an email with the invoice. Try again later."
"We can\'t generate the invoice. Try again later.", "We can\'t generate the invoice. Try again later."
"We couldn\'t create a credit memo for the %1 order. Try again later.", "We couldn\'t create a credit memo for the %1 order. Try again later."
"We couldn\'t save the notification. Try again later.", "We couldn\'t save the notification. Try again later."
"Authorized amount of %1", "Authorized amount of %1"
"We couldn\'t authorize the order. Try again later.", "We couldn\'t authorize the order. Try again later."
"We couldn\'t refund the order. Try again later.", "We couldn\'t refund the order. Try again later."
"We couldn\'t process the partial refund of the order. Try again later.", "We couldn\'t process the partial refund of the order. Try again later."
"The credit memo\'s total must be positive", "The credit memo\'s total must be positive"
"Credit memo created.", "Credit memo created"
"We couldn\'t save the credit memo. Try again later.", "We couldn\'t save the credit memo. Try again later."
"We couldn\'t cancel the authorization. Try again later.", "We couldn\'t cancel the authorization. Try again later."
"Payoneer status changed to preauthorization_canceled", "Payoneer status changed to preauthorization_canceled"
"We couldn\'t cancel the preauthorization. Try again later.", "We couldn\'t cancel the preauthorization. Try again later."
"The amount of %1 is void", "The amount of %1 is void"
"We couldn\'t capture the transaction. Try again later.", "We couldn\'t capture the transaction. Try again later."
"We couldn\'t find an order with the increment ID of %s", "We couldn\'t find an order with the increment ID of %s"
"We couldn\'t create the %1 transaction entry", "We couldn\'t create the %1 transaction entry"
"Payoneer validation failed. Make sure the credentials you have entered are correct.", "Payoneer validation failed. Make sure the credentials you have entered are correct."
"Payoneer capture", "Payoneer capture"
"action-default scalable", "action-default scalable"
"Payoneer fetch", "Payoneer fetch"
"We couldn\'t process the payment. Invalid response from Payoneer.", "We couldn\'t process the payment. Invalid response from Payoneer."
"Transaction declined. Try again later.", "Transaction declined. Try again later."
"Payoneer capture completed successfully", "Payoneer capture completed successfully"
"Payoneer fetch completed successfully", "Payoneer fetch completed successfully"
"Payoneer Checkout", "Payoneer Checkout"
"Title", "Title"
"API username", "API username"
"Environment", "Environment"
"Live API token", "Live API token"
"Live Store code", "Live Store code"
"Live OPG hostname", "Live OPG hostname"
"Test API token", "Test API token"
"Test Store code", "Test Store code"
"Test OPG hostname", "Test OPG hostname"
"Makes a List API call to check if it returns HTTP 200 and the interaction reason is OK", "Makes a List API call to check if it returns HTTP 200 and the interaction reason is OK"
"Success URL", "Success URL"
"Cancel URL", "Cancel URL"
"Notification URL", "Notification URL"
"Payment action", "Payment action"
"Payment flow", "Payment flow"
"Order reference message", "Order reference message"
"Payment method position", "Payment method position"
"Debug", "Debug"
"Payment widget appearance", "Payment widget appearance"
"Background color", "Background color"
"Payment widget background color", "Payment widget background color"
"Text color", "Text color"
"Payment widget text color", "Payment widget text color"
"Placeholder color", "Placeholder color"
"Payment widget placeholder color", "Payment widget placeholder color"
"Font size", "Font size"
"Payment widget font size in px", "Payment widget font size in px"
"Font weight", "Font weight"
"Payment widget font weight in px", "Payment widget font weight in px"
"Letter spacing", "Letter spacing"
"Payment widget letter spacing in px", "Payment widget letter spacing in px"
"Line height", "Line height"
"Payment widget line height, e.g. 1.5 or normal", "Payment widget line height, e.g. 1.5 or normal"
"Padding", "Padding"
"Payment widget padding in px", "Payment widget padding in px"
"Align text", "Align text"
"Payment widget text alignment", "Payment widget text alignment"
"Custom CSS", "Custom CSS"
"Validate credentials","验证凭据"
"Transaction failed","交易失败"
"Data successfully synced","数据已成功同步"
"Error response with the %1 code received from Payoneer. Check the payoneer.log file for details.","从 Payoneer 收到的带有 %1 代码的错误响应。请查看 payoneer.log 文件以了解详情。"
"We couldn\'t process the payment","我们无法处理付款"
"We couldn\'t capture the transaction. Check the payoneer.log file for details.","我们无法捕获交易。请查看 payoneer.log 文件以了解详情。"
"We couldn\'t fetch the data. Check the payoneer.log file for details.","我们无法获取数据。请查看 payoneer.log 文件以了解详情。"
"The order no longer exists","订单不存在"
"You can\'t create an invoice with this order","您无法为此订单创建发票"
"You can\'t create an invoice without products","您无法在没有产品的情况下创建发票"
"We\'ve notified the customer that the #%1 invoice has been created","我们已通知客户,#%1 发票已创建"
"We can\'t send an email with the invoice. Try again later.","我们无法发送带有发票的电子邮件。请稍后再试。"
"We can\'t generate the invoice. Try again later.","我们无法生成发票。请稍后再试。"
"We couldn\'t create a credit memo for the %1 order. Try again later.","我们无法为 %1 订单创建贷项通知单。请稍后再试。"
"We couldn\'t save the notification. Try again later.","我们无法保存通知。请稍后再试。"
"Authorized amount of %1","%1 的授权金额"
"We couldn\'t authorize the order. Try again later.","我们无法授权此订单。请稍后再试。"
"We couldn\'t refund the order. Try again later.","我们无法为此订单退款。请稍后再试。"
"We couldn\'t process the partial refund of the order. Try again later.","我们无法处理此订单的部分退款。请稍后再试。"
"The credit memo\'s total must be positive","贷项通知单的总额必须是正数"
"Credit memo created.","贷项通知单已创建"
"We couldn\'t save the credit memo. Try again later.","我们无法保存贷项通知单。请稍后再试。"
"We couldn\'t cancel the authorization. Try again later.","我们无法取消授权。请稍后再试。"
"Payoneer status changed to preauthorization_canceled","Payoneer 状态已更改为 preauthorization_canceled"
"We couldn\'t cancel the preauthorization. Try again later.","我们无法取消预授权。请稍后再试。"
"The amount of %1 is void","%1 的金额无效"
"We couldn\'t capture the transaction. Try again later.","我们无法捕获交易。请稍后再试。"
"We couldn\'t find an order with the increment ID of %s","我们找不到增量 ID 为 %s 的订单"
"We couldn\'t create the %1 transaction entry","我们无法创建 %1 交易条目"
"Payoneer validation failed. Make sure the credentials you have entered are correct.","Payoneer 验证失败。请确认您输入的凭据正确。"
"Payoneer capture","Payoneer 捕获"
"action-default scalable","action-default 可扩展"
"Payoneer fetch","Payoneer 获取"
"We couldn\'t process the payment. Invalid response from Payoneer.","我们无法处理付款。收到 Payoneer 的无效响应。"
"Transaction declined. Try again later.","交易被拒绝。请稍后再试。"
"Payoneer capture completed successfully","Payoneer 捕获成功完成"
"Payoneer fetch completed successfully","Payoneer 获取成功完成"
"Payoneer Checkout","Payoneer Checkout"
"Title","头衔"
"API username","API 用户名"
"Environment","环境"
"Live API token","正式 API 令牌"
"Live Store code","正式店铺代码"
"Live OPG hostname","正式 OPG 主机名称"
"Test API token","测试 API 令牌"
"Test Store code","测试店铺代码"
"Test OPG hostname","测试 OPG 主机名称"
"Makes a List API call to check if it returns HTTP 200 and the interaction reason is OK","进行 List API 调用以检查它是否返回 HTTP 200 以及交互原因是否正确"
"Success URL","成功 URL"
"Cancel URL","取消 URL"
"Notification URL","通知 URL"
"Payment action","付款操作"
"Payment flow","付款流程"
"Order reference message","订单参考消息"
"Payment method position","支付方式位置"
"Debug","调试"
"Payment widget appearance","支付小部件外观"
"Background color","背景颜色"
"Payment widget background color","支付小部件背景颜色"
"Text color","文本颜色"
"Payment widget text color","支付小部件文本颜色"
"Placeholder color","占位符颜色"
"Payment widget placeholder color","支付小部件占位符颜色"
"Font size","字体大小"
"Payment widget font size in px","支付小部件字体大小(以 px 为单位)"
"Font weight","字体粗细"
"Payment widget font weight in px","支付小部件字体粗细(以 px 为单位)"
"Letter spacing","字母间距"
"Payment widget letter spacing in px","支付小部件字母间距(以 px 为单位)"
"Line height","行高"
"Payment widget line height, e.g. 1.5 or normal","支付小部件行高,例如 1.5 或标准"
"Padding","内边距"
"Payment widget padding in px","支付小部件内边距(以 px 为单位)"
"Align text","对齐文本"
"Payment widget text alignment","支付小部件文本对齐"
"Custom CSS","自定义 CSS"
"Validate credentials","驗證憑據"
"Transaction failed","交易失敗"
"Data successfully synced","數據已成功同步"
"Error response with the %1 code received from Payoneer. Check the payoneer.log file for details.","從Payoneer收到的%1代碼錯誤回應。有關詳細資料,請查看payoneer.log文件。"
"We couldn\'t process the payment","我們無法處理付款"
"We couldn\'t capture the transaction. Check the payoneer.log file for details.","我們無法獲取交易。有關詳細資料,請查看payoneer.log文件。"
"We couldn\'t fetch the data. Check the payoneer.log file for details.","我們無法選取數據。有關詳細資料,請查看payoneer.log文件。"
"The order no longer exists","訂單已不存在"
"You can\'t create an invoice with this order","無法以此訂單創建發票"
"You can\'t create an invoice without products","如果沒有商品,便無法創建發票"
"We\'ve notified the customer that the #%1 invoice has been created","我們已通知客戶#%1發票已發出"
"We can\'t send an email with the invoice. Try again later.","我們無法發送帶有發票的電郵。請稍後再次嘗試。"
"We can\'t generate the invoice. Try again later.","我們無法生成發票。請稍後再次嘗試。"
"We couldn\'t create a credit memo for the %1 order. Try again later.","我們無法為訂單%1創建貸項通知單。請稍後再次嘗試。"
"We couldn\'t save the notification. Try again later.","我們無法保存有關通知。請稍後再次嘗試。"
"Authorized amount of %1","%1的授權金額"
"We couldn\'t authorize the order. Try again later.","我們無法授權有關訂單。請稍後再次嘗試。"
"We couldn\'t refund the order. Try again later.","我們無法為訂單退款。請稍後再次嘗試。"
"We couldn\'t process the partial refund of the order. Try again later.","我們無法處理訂單的部份退款。請稍後再次嘗試。"
"The credit memo\'s total must be positive","貸項通知單的總額必須為正數"
"Credit memo created.","已創建貸項通知單"
"We couldn\'t save the credit memo. Try again later.","我們無法保存貸方通知單。請稍後再次嘗試"
"We couldn\'t cancel the authorization. Try again later.","我們無法取消授權。請稍後再次嘗試。"
"Payoneer status changed to preauthorization_canceled","Payoneer狀態已更改為preauthorization_canceled"
"We couldn\'t cancel the preauthorization. Try again later.","我們無法取消預授權。請稍後再次嘗試。"
"The amount of %1 is void","%1的金額無效"
"We couldn\'t capture the transaction. Try again later.","我們無法獲取交易。請稍後再次嘗試。"
"We couldn\'t find an order with the increment ID of %s","找不到增量ID為%s的訂單"
"We couldn\'t create the %1 transaction entry","無法創建%1交易條目"
"Payoneer validation failed. Make sure the credentials you have entered are correct.","Payoneer驗證失敗。確保您輸入的憑據正確無誤。"
"Payoneer capture","Payoneer獲取"
"action-default scalable","action-default可擴展"
"Payoneer fetch","Payoneer選取"
"We couldn\'t process the payment. Invalid response from Payoneer.","我們無法處理付款。Payoneer的回應無效。"
"Transaction declined. Try again later.","交易被拒絕。請稍後再次嘗試。"
"Payoneer capture completed successfully","「Payoneer獲取」成功完成"
"Payoneer fetch completed successfully","「Payoneer選取」成功完成"
"Payoneer Checkout","Payoneer Checkout"
"Title","頭銜"
"API username","API用戶名稱"
"Environment","環境"
"Live API token","實時API token"
"Live Store code","實時店舖代碼"
"Live OPG hostname","實時OPG主機名稱"
"Test API token","測試API token"
"Test Store code","測試店舖代碼"
"Test OPG hostname","測試OPG主機名稱"
"Makes a List API call to check if it returns HTTP 200 and the interaction reason is OK","進行List API調用以檢查它是否返回HTTP 200,以及互動原因是否正確"
"Success URL","成功URL"
"Cancel URL","取消URL"
"Notification URL","通知URL"
"Payment action","付款操作"
"Payment flow","付款流程"
"Order reference message","訂單參考訊息"
"Payment method position","付款方式位置"
"Debug","偵錯"
"Payment widget appearance","付款小部件外觀"
"Background color","背景顏色"
"Payment widget background color","付款小部件背景顏色"
"Text color","文字顏色"
"Payment widget text color","付款小部件文字顏色"
"Placeholder color","佔位符顏色"
"Payment widget placeholder color","付款小部件佔位符顏色"
"Font size","字體大小"
"Payment widget font size in px","付款小部件字體大小(以px為單位)"
"Font weight","字型粗細"
"Payment widget font weight in px","付款小部件字體粗細(以px為單位)"
"Letter spacing","字母間距"
"Payment widget letter spacing in px","付款小部件字母間距(以px為單位)"
"Line height","行高"
"Payment widget line height, e.g. 1.5 or normal","付款小部件行高,例如1.5或正常"
"Padding","邊距"
"Payment widget padding in px","付款小部件邊距(以px為單位)"
"Align text","對齊文字"
"Payment widget text alignment","付款小部件文字對齊"
"Custom CSS","自定CSS"
<?php
\Magento\Framework\Component\ComponentRegistrar::register(
\Magento\Framework\Component\ComponentRegistrar::MODULE,
'Payoneer_OpenPaymentGateway',
__DIR__
);
<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<head>
<css src="jquery/colorpicker/css/colorpicker.css"/>
<!--<link src="jquery/colorpicker/js/colorpicker.js"/>-->
<script src="Payoneer_OpenPaymentGateway::js/validateCredentials.js" />
</head>
</page>
require([
'jquery',
'Magento_Ui/js/modal/alert',
'mage/translate',
'domReady!'
], function ($, alert, $t) {
window.payoneerValidator = function (endpoint, envId) {
envId = $('[data-ui-id="' + envId + '"]').val();
var merchCode = '', storeCode = '', apiKey = '', hostName = '';
merchCode = $('[data-ui-id="text-groups-payoneer-fields-merchant-gateway-key-value"]').val();
if (envId === 'test') {
apiKey = $('[data-ui-id="password-groups-payoneer-fields-sandbox-api-key-value"]').val();
storeCode = $('[data-ui-id="text-groups-payoneer-fields-sandbox-store-code-value"]').val();
hostName = $('[data-ui-id="text-groups-payoneer-fields-sandbox-host-name-value"]').val();
} else {
apiKey = $('[data-ui-id="password-groups-payoneer-fields-live-api-key-value"]').val();
storeCode = $('[data-ui-id="text-groups-payoneer-fields-live-store-code-value"]').val();
hostName = $('[data-ui-id="text-groups-payoneer-fields-live-host-name-value"]').val();
}
/* Remove previous success message if present */
if ($(".payoneer-credentials-success-message")) {
$(".payoneer-credentials-success-message").remove();
}
/* Basic field validation */
var errors = [];
if (!envId || envId !== 'test' && envId !== 'live') {
errors.push($t("Please select an Environment"));
}
if (!merchCode) {
errors.push($t("Please enter a API username"));
}
if (!apiKey) {
errors.push($t("Please enter an API Key"));
}
if (errors.length > 0) {
alert({
title: $t('Payoneer Credential Validation Failed'),
content: errors.join('<br />')
});
return false;
}
$(this).text($t("We're validating your credentials...")).attr('disabled', true);
var self = this;
$.post(endpoint, {
environment: envId,
merchantCode: merchCode,
apiKey: apiKey,
storeCode: storeCode,
hostName: hostName
}).done(function (response) {
if (response.data &&
response.data.interaction &&
response.data.interaction.reason &&
response.data.interaction.reason === 'OK') {
$('<div class="message message-success payoneer-credentials-success-message">' + $t("Your credentials are valid.") + '</div>').insertAfter(self);
} else {
alert({
title: $t('Payoneer Credential Validation Failed'),
content: $t('Your Payoneer Credentials could not be validated. Please ensure you have selected the correct environment and entered a valid API username, API Key, Store Code and Host Name.')
});
}
}).fail(function () {
alert({
title: $t('Payoneer Credential Validation Failed'),
content: $t('Your Payoneer Credentials could not be validated. Please ensure you have selected the correct environment and entered a valid API username, API Key, Store Code and Host Name.')
});
}).always(function () {
$(self).text($t("Validate credentials")).attr('disabled', false);
});
}
});
<!--@subject {{trans "Unprocessed Notification"}} @-->
<!--@vars {
"var longId":"Long ID",
"var transactionId":"Transaction ID",
"var notificationId":"Notification ID",
"var interactionCode":"Interaction Code",
"var reasonCode":"Reason Code",
"var statusCode":"Status Code",
"var resultCode":"Result Code",
} @-->
{{template config_path="design/email/header_template"}}
<p>{{trans "Dear Merchant"}},</p>
<p>{{trans "Please find the details of the notification which is in unprocessed state."}}</p>
<table class="message-details">
<tr>
<td><strong>{{trans "Long ID"}}</strong></td>
<td>{{var longId}}</td>
</tr>
<tr>
<td><strong>{{trans "Transaction ID"}}</strong></td>
<td>{{var transactionId}}</td>
</tr>
<tr>
<td><strong>{{trans "Notification ID"}}</strong></td>
<td>{{var notificationId}}</td>
</tr>
<tr>
<td><strong>{{trans "Interaction Code"}}</strong></td>
<td>{{var interactionCode}}</td>
</tr>
<tr>
<td><strong>{{trans "Reason Code"}}</strong></td>
<td>{{var reasonCode}}</td>
</tr>
<tr>
<td><strong>{{trans "Status Code"}}</strong></td>
<td>{{var statusCode}}</td>
</tr>
<tr>
<td><strong>{{trans "Result Code"}}</strong></td>
<td>{{var resultCode}}</td>
</tr>
</table>
{{template config_path="design/email/footer_template"}}
<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="1column" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<head>
<css src="Payoneer_OpenPaymentGateway::css/op-payment-widget-v3.css"/>
<css src="Payoneer_OpenPaymentGateway::css/custom.css"/>
</head>
<body>
<referenceBlock name="checkout.root">
<arguments>
<argument name="jsLayout" xsi:type="array">
<item name="components" xsi:type="array">
<item name="checkout" xsi:type="array">
<item name="children" xsi:type="array">
<item name="steps" xsi:type="array">
<item name="children" xsi:type="array">
<item name="billing-step" xsi:type="array">
<item name="component" xsi:type="string">uiComponent</item>
<item name="children" xsi:type="array">
<item name="payment" xsi:type="array">
<item name="children" xsi:type="array">
<item name="renders" xsi:type="array">
<!-- merge payment method renders here -->
<item name="children" xsi:type="array">
<item name="payoneer" xsi:type="array">
<item name="component" xsi:type="string">Payoneer_OpenPaymentGateway/js/view/payment/payoneer</item>
<item name="methods" xsi:type="array">
<item name="payoneer" xsi:type="array">
<item name="isBillingAddressRequired" xsi:type="boolean">true</item>
</item>
</item>
</item>
</item>
</item>
</item>
</item>
</item>
</item>
</item>
</item>
</item>
</item>
</item>
</argument>
</arguments>
</referenceBlock>
</body>
</page>
<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<referenceContainer name="content">
<block class="Magento\Framework\View\Element\Template"
name="payoneer_redirect_success" template="Payoneer_OpenPaymentGateway::invalidate-cart.phtml" cacheable="false"/>
</referenceContainer>
</page>
var config = {
map: {
'*': {
invalidateCart:'Payoneer_OpenPaymentGateway/js/invalidate-cart',
payoneerWidget:'Payoneer_OpenPaymentGateway/js/op-payment-widget-v3'
}
},
shim: {
payoneerWidget: {
deps: ['jquery']
}
}
};
<script type="text/x-magento-init">
{
"*": {
"invalidateCart": {}
}
}
</script>
.checkout-container .checkout-payment-method .payment-method .payment-method-icon {
vertical-align: middle;
}
.checkout-container .checkout-payment-method .payment-method .payment-method-content .payment-networks-container.static label.imgLabel {
display: none;
}
\ No newline at end of file
@import url('https://fonts.googleapis.com/css?family=Roboto:300,500');
/* roboto-300 - latin */
@font-face {
font-family: 'RobotoFallback';
font-style: normal;
font-weight: 300;
src: url('static/fonts/roboto-v29-latin-300.eot'); /* IE9 Compat Modes */
src: local(''), url('static/fonts/roboto-v29-latin-300.eot?#iefix') format('embedded-opentype'),
/* IE6-IE8 */ url('static/fonts/roboto-v29-latin-300.woff2') format('woff2'),
/* Super Modern Browsers */ url('static/fonts/roboto-v29-latin-300.woff') format('woff'),
/* Modern Browsers */ url('static/fonts/roboto-v29-latin-300.ttf') format('truetype'),
/* Safari, Android, iOS */ url('static/fonts/roboto-v29-latin-300.svg#Roboto') format('svg'); /* Legacy iOS */
}
/* roboto-500 - latin */
@font-face {
font-family: 'RobotoFallback';
font-style: normal;
font-weight: 500;
src: url('static/fonts/roboto-v29-latin-500.eot'); /* IE9 Compat Modes */
src: local(''), url('static/fonts/roboto-v29-latin-500.eot?#iefix') format('embedded-opentype'),
/* IE6-IE8 */ url('static/fonts/roboto-v29-latin-500.woff2') format('woff2'),
/* Super Modern Browsers */ url('static/fonts/roboto-v29-latin-500.woff') format('woff'),
/* Modern Browsers */ url('static/fonts/roboto-v29-latin-500.ttf') format('truetype'),
/* Safari, Android, iOS */ url('static/fonts/roboto-v29-latin-500.svg#Roboto') format('svg'); /* Legacy iOS */
}
@keyframes mui-progress-circular-rotate {
100% {
transform: rotate(360deg);
}
}
@keyframes mui-progress-circular-dash {
0% {
stroke-dasharray: 1px, 200px;
stroke-dashoffset: 0px;
}
50% {
stroke-dasharray: 100px, 200px;
stroke-dashoffset: -15px;
}
100% {
stroke-dasharray: 100px, 200px;
stroke-dashoffset: -125px;
}
}
.op-payment-widget-main-container {
position: relative;
min-height: 80px;
height: 100%;
width: 100%;
}
.op-payment-widget-summary {
min-height: 0;
}
.container-loading {
width: 64px;
height: 64px;
}
.MuiCircularProgress-svg {
color: #299928;
}
.MuiCircularProgress-circle {
-webkit-animation: mui-progress-circular-dash 1.4s ease-in-out infinite;
animation: mui-progress-circular-dash 1.4s ease-in-out infinite;
stroke-dasharray: 80px, 200px;
stroke-dashoffset: 0px;
stroke: currentColor;
}
.single-payment-option input[type='radio'] {
display: none;
}
.op-payment-widget-container {
position: relative;
font-size: 16px;
font-weight: 300;
color: #424242;
}
.op-payment-widget-container strong {
font-weight: 500;
}
.op-payment-widget-container .sepa-bic-row {
display: none;
}
.op-payment-widget-container .float-left {
float: left;
}
.op-payment-widget-container > div.list label.clickable_cont {
display: none;
}
.op-payment-widget-container * {
font-family: 'Roboto', 'RobotoFallback', Arial, sans-serif;
}
.op-payment-widget-container > div.list {
list-style: none;
margin-bottom: 12px;
}
.op-payment-widget-container > div.list.no-style {
margin: 12px 0 12px 0;
font-weight: 500;
}
.op-payment-widget-container > div.list > label,
.op-payment-widget-container .textLabel {
display: inline-block;
vertical-align: middle;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.op-payment-widget-container .textLabel {
margin-left: 14px;
}
.op-payment-widget-container .textLabel .the-date {
margin-top: 4px;
font-size: 14px;
}
.op-payment-widget-container .textLabel .the-date .expired-icon {
display: inline-block;
width: 14px;
height: 14px;
background: url(img/ic-alert@2x.png);
background-size: 100%;
position: relative;
}
.op-payment-widget-container .textLabel .the-date-expired {
font-weight: 500;
color: rgb(183, 28, 28);
}
.op-payment-widget-container .textLabel .badge-expired {
display: inline-block;
position: relative;
background-image: url('./img/ic-alert@2x.png');
background-size: 16px 16px;
background-repeat: no-repeat;
margin-left: 4px;
margin-bottom: -2px;
height: 16px;
width: 16px;
cursor: pointer;
}
.op-payment-widget-container .expiredLabel {
overflow: visible;
}
.expired-tooltip {
display: none;
position: absolute;
top: -3px;
left: 22px;
background: #ffffff;
box-shadow: 0 0 4px 0 rgb(0 0 0 / 12%), 0 4px 4px 0 rgb(0 0 0 / 24%);
width: -webkit-max-content;
width: -moz-max-content;
width: max-content;
height: 14px;
max-width: 670px;
max-height: 46px;
border-radius: 4px;
font-size: 12px;
font-weight: 300;
color: #424242;
text-align: center;
padding: 4px 8px;
margin-bottom: 4px;
bottom: 100%;
text-indent: initial;
}
.badge-expired:hover .expired-tooltip {
display: block;
}
.op-payment-widget-container > div.list label img {
cursor: pointer;
}
.op-payment-widget-container .imgLabel {
padding: 7px 0;
vertical-align: middle;
display: inline-block;
min-width: 52px;
}
.op-payment-widget-container .imgLabel img {
margin-right: 10px;
max-height: 32px;
max-width: 100px;
display: block;
float: left;
}
.op-payment-widget-container .imgLabel img:last-child {
margin-right: 0;
}
.op-payment-widget-container .inactive label img,
.op-payment-widget-container img.inactive {
opacity: 0.5;
filter: alpha(opacity=50);
zoom: 1;
}
.op-payment-widget-container label img {
vertical-align: middle;
}
.op-payment-widget-container select.select-in-group {
margin-left: 23px;
width: auto;
}
.submitBtnContainerGroup {
max-width: 370px;
}
/*.row and .col styling*/
.op-payment-widget-container .row {
position: relative;
}
.op-payment-widget-container fieldset .row {
position: unset;
}
.op-payment-widget-container .row div {
margin: 6px 0;
}
.op-payment-widget-container .row .col1,
.op-payment-widget-container .row .col2,
.op-payment-widget-container .row .col3 {
display: inline-block;
vertical-align: middle;
}
.op-payment-widget-container .row .col1 {
width: 200px;
}
.op-payment-widget-container .row .col2 {
width: 204px;
margin-right: 10px;
}
.op-payment-widget-container .row .col3 {
max-width: calc(100% - 425px);
vertical-align: middle;
}
.op-payment-widget-container .fieldset-has-iban .row .col3 {
max-width: calc(100% - 505px);
}
.op-payment-widget-container.card-view .row .col3 {
width: auto;
max-width: none;
}
.op-payment-widget-container.card-view .fieldset-has-iban .row .col3 {
width: auto;
max-width: none;
}
.op-payment-widget-container .fieldset-has-iban .row .col2 {
width: 286px;
}
.op-payment-widget-container input.iban {
text-transform: uppercase;
}
.op-payment-widget-container input.iban:-ms-input-placeholder {
text-transform: none;
}
.op-payment-widget-container input.iban::-moz-placeholder {
text-transform: none;
}
.op-payment-widget-container input.iban::placeholder {
text-transform: none;
}
/*.formContainer STYLING*/
.op-payment-widget-container .form {
margin: 0 0 10px;
}
.op-payment-widget-container .formContainer {
margin: 0 20px 0 27px;
position: relative;
}
.op-payment-widget-container fieldset {
border: none;
padding: 0;
margin: 0;
}
.mobile-only {
display: none;
}
.op-payment-widget-container .ERROR input,
.op-payment-widget-container .ERROR select {
background: rgba(194, 45, 45, 0.1);
border: 1px solid #c22d2d;
}
.op-payment-widget-container select,
.op-payment-widget-container input {
height: 44px;
margin: 0 10px 0 0;
font-size: 16px;
color: #424242;
background-color: #fff;
border: 1px solid #8f8f8f;
border-radius: 4px;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
padding: 0 8px;
box-sizing: border-box;
font-weight: 300;
max-width: 100%;
}
.op-payment-widget-container select,
.op-payment-widget-container input[type='text'] {
width: 100%;
}
.op-payment-widget-container select:invalid {
color: #999;
}
.op-payment-widget-container select:focus,
.op-payment-widget-container select:active,
.op-payment-widget-container input:focus,
.op-payment-widget-container input:active {
outline-width: 0;
border: 2px solid #207a20;
box-shadow: none;
}
.op-payment-widget-container input[type='radio']:focus,
.op-payment-widget-container input[type='checkbox']:focus{
outline-width: initial;
}
.op-payment-widget-container .ERROR select:focus,
.op-payment-widget-container .ERROR select:active,
.op-payment-widget-container .ERROR input:focus,
.op-payment-widget-container .ERROR input:active {
border-color: #c22d2d;
}
::-webkit-input-placeholder {
/* Chrome/Opera/Safari */
color: rgba(66, 66, 66, 0.4);
}
::-moz-placeholder {
/* Firefox 19+ */
color: rgba(66, 66, 66, 0.4);
}
:-ms-input-placeholder {
/* IE 10+ */
color: rgba(66, 66, 66, 0.4);
}
:-moz-placeholder {
/* Firefox 18- */
color: rgba(66, 66, 66, 0.4);
}
.op-payment-widget-container input[type='radio'],
.op-payment-widget-container input[type='checkbox'] {
height: auto;
padding: 4px 6px;
}
.op-payment-widget-container .hintIcon {
display: inline-block;
width: 18px;
height: 18px;
vertical-align: middle;
background: url(img/hint.png);
background-size: cover;
}
.op-payment-widget-container .hint {
display: none;
}
.op-payment-widget-container .hint img {
float: left;
padding: 0;
}
.op-payment-widget-container .hintShown {
display: block;
position: absolute;
z-index: 1;
box-sizing: border-box;
background-color: #fff;
padding: 4px;
border-radius: 4px;
box-shadow: 0 0 4px 0 rgb(0 0 0 / 12%), 0 4px 4px 0 rgb(0 0 0 / 24%);
}
.op-payment-widget-container .hintShown h3 {
margin-top: 12px;
margin-bottom: 16px;
font-size: 18px;
font-weight: 500;
}
.op-payment-widget-container .standard-fieldset .hint.hintShown {
width: 320px;
min-height: 190px;
padding: 0 12px 12px;
top: 0px;
left: 335px;
}
.op-payment-widget-container .registered-fieldset .hint.hintShown,
.op-payment-widget-container .update-fieldset .hint.hintShown {
top: 8px;
left: 335px;
}
.op-payment-widget-container.card-view .registered-fieldset .hint.hintShown,
.op-payment-widget-container.card-view .update-fieldset .hint.hintShown {
position: absolute;
bottom: 46px;
left: 0;
width: calc(50% + 25px);
top: auto;
}
.op-payment-widget-container fieldset.registered-fieldset .row,
.op-payment-widget-container fieldset.update-fieldset .row {
position: relative;
}
.op-payment-widget-container.card-view .standard-fieldset .hint.hintShown {
position: absolute;
bottom: 46px;
top: auto;
left: 0;
max-width: 280px;
}
/* installments */
.op-payment-widget-container span.installmentInlineInfo {
padding-right: 5px;
line-height: 29px;
}
.op-payment-widget-container .row .details {
position: absolute;
overflow: auto;
z-index: 1;
height: 170px;
margin-top: -45px;
font-size: 12px;
}
.op-payment-widget-container .row .details h3 {
margin: 0;
font-size: 16px;
font-weight: 500;
}
.op-payment-widget-container .installmentPlanRates {
overflow: auto;
width: 225px;
height: 135px;
font-size: 12px;
line-height: 20px;
border: 1px solid #ccc;
}
.op-payment-widget-container a.inline-link {
margin: 0;
}
.op-payment-widget-container .installmentPlanRates p {
margin-top: 0;
margin-left: 5px;
}
.op-payment-widget-container .latentMessage.INFO {
display: none;
}
/* options and agreements, other */
.op-payment-widget-container .separator {
height: 10px;
border-bottom: 2px dotted #b6b6b6;
margin-bottom: 18px;
}
.op-payment-widget-container .options {
display: flex;
margin-bottom: 20px;
}
.op-payment-widget-container .options input,
.op-payment-widget-container.card-view .options input {
margin: 5px 8px 2px 0;
vertical-align: bottom;
align-self: flex-start;
/* needed to replace flex 1 used for input text*/
flex: none;
}
.op-payment-widget-container.card-view .options input {
margin-right: 5px;
}
.op-payment-widget-container .registration input {
float: left;
}
.op-payment-widget-container .registration label {
display: block;
margin-bottom: 5px;
line-height: 24px;
}
.op-payment-widget-container .information {
margin-bottom: 10px;
}
.op-payment-widget-container .link-group {
margin: 10px 0;
}
.op-payment-widget-container .link-group a {
margin-left: 0;
}
.op-payment-widget-container .ERROR {
color: #c22d2d;
}
.op-payment-widget-container .INFO {
color: #299928;
}
.op-payment-widget-container > div.list.GLOBAL_ERROR,
.op-payment-widget-container.card-view > div.list.GLOBAL_ERROR,
.op-payment-widget-container > div.list.GLOBAL_INFO,
.op-payment-widget-container.card-view > div.list.GLOBAL_INFO {
color: #424242;
background: #fdecea;
border: 0;
border-radius: 4px;
font-size: 14px;
color: #424242;
text-align: left;
display: block;
max-width: 659px;
width: 100%;
padding: 16px 16px 16px 52px;
box-sizing: border-box;
position: relative;
box-shadow: none;
margin-top: 8px;
}
.op-payment-widget-container.card-view > div.list.GLOBAL_ERROR {
max-width: none;
}
.op-payment-widget-container > div.list.GLOBAL_ERROR .global-error-title,
.op-payment-widget-container.card-view > div.list.GLOBAL_ERROR .global-error-title,
.op-payment-widget-container > div.list.GLOBAL_INFO .global-info-title,
.op-payment-widget-container.card-view > div.list.GLOBAL_INFO .global-info-title {
font-size: 16px;
font-weight: 500;
}
.op-payment-widget-container .GLOBAL_ERROR::before,
.op-payment-widget-container.card-view .GLOBAL_ERROR::before {
content: '';
position: absolute;
left: 16px;
background-image: url(img/ic-error@2x.png);
background-size: 20px 20px;
width: 20px;
height: 20px;
}
.op-payment-widget-container .GLOBAL_MESSAGE,
.op-payment-widget-container.card-view .GLOBAL_MESSAGE {
color: #299928;
}
.op-payment-widget-container .GLOBAL_INFO::before,
.op-payment-widget-container.card-view .GLOBAL_INFO::before {
content: '';
position: absolute;
left: 16px;
background-image: url(img/ic-info@2x.png);
background-size: 20px;
width: 20px;
height: 20px;
}
.op-payment-widget-container > div.list.GLOBAL_INFO,
.op-payment-widget-container.card-view > div.list.GLOBAL_INFO {
background: #e8f4fd;
}
.op-payment-widget-container .formContainer iframe {
width: 100%;
}
.op-payment-widget-container .sepadd-toc {
display: none;
}
.op-payment-widget-container .flex-center {
display: flex;
justify-content: space-around;
}
.op-payment-widget-container .flex-end {
display: flex;
justify-content: flex-end;
}
/* popup and buttons */
.op-payment-widget-container .popup-dialog {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(66, 66, 66, 0.8);
font-size: 16px;
z-index: 104;
}
@media only screen and (max-width: 497px) {
/* 465 + 16 * 2 */
.op-payment-widget-container .popup-dialog {
font-size: 14px;
}
}
.op-payment-widget-container .dialog-inside,
.op-payment-widget-container .confirm-button {
font-family: 'Roboto', 'RobotoFallback', sans-serif;
}
.op-payment-widget-container .dialog-middle {
display: table-cell;
vertical-align: middle;
}
.op-payment-widget-container .dialog-inside {
display: table;
position: relative;
max-height: 80%;
margin: auto;
padding: 2em;
font-weight: 300;
background-color: #fff;
color: rgba(0, 0, 0, 0.74);
box-shadow: 0 4px 3px 0 rgba(0, 0, 0, 0.06), 0 5px 9px 0 rgba(0, 0, 0, 0.12);
border-radius: 4px;
max-width: 465px;
width: calc(100% - 32px);
box-sizing: border-box;
text-align: center;
}
.op-payment-widget-container .dialog-inside h2 {
font-size: 1.25em;
text-align: left;
font-weight: 700;
text-align: center;
}
.op-payment-widget-container .dialog-inside .close-modal {
position: absolute;
top: -28px;
right: 0;
text-indent: -1000000000px;
width: 14px;
height: 14px;
background-image: url(img/ic-x-default-14p@2x.png);
background-size: 14px 14px;
background-repeat: no-repeat;
border-radius: 0;
background-color: transparent;
vertical-align: middle;
padding: 0;
border: 0;
cursor: pointer;
}
.op-payment-widget-container .delete-buttons-group,
.op-payment-widget-container .buttons-group {
margin-top: 3.125em;
}
.op-payment-widget-container .submit-delete-button,
.op-payment-widget-container .submit-button {
padding: 8px 36px;
font-weight: 500;
border: 0;
cursor: pointer;
font-size: 1em;
border-radius: 4px;
height: 44px;
background: #424242;
color: #ffffff;
display: block;
margin: 0 auto 18px auto;
border-radius: 4px;
}
.op-payment-widget-container .submit-delete-button:hover,
.op-payment-widget-container .submit-button:hover {
background: #000000;
}
.op-payment-widget-container .cancel-delete {
padding: 8px 21px;
font-weight: normal;
border: 0;
cursor: pointer;
font-size: 1em;
height: 44px;
background: transparent;
color: rgba(0, 0, 0, 0.72);
text-decoration: underline;
}
.op-payment-widget-container .cancel-delete:hover {
color: #000000;
}
.op-payment-widget-container .delete-account.has-date {
margin-top: -22px;
}
.submit-button {
padding: 8px 25px;
font-weight: 500;
color: #fff;
border: 0;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
margin: 20px 0;
background-color: #54b94d;
background: #54b94d;
background-image: linear-gradient(#54b94d, #299928);
}
.submit-button.disabled,
.submit-button[disabled] {
cursor: not-allowed;
opacity: 0.65;
filter: alpha(opacity=65);
box-shadow: none;
}
.op-payment-widget-container .delete-account {
position: relative;
margin-left: 40px;
text-indent: -1000000000px;
width: 10px;
height: 13px;
background-image: url(img/ic-delete-default-10p@2x.png);
background-size: 10px 13px;
background-repeat: no-repeat;
border-radius: 0;
background-color: transparent;
vertical-align: middle;
padding: 0;
border: 0;
cursor: pointer;
display: none;
}
.op-payment-widget-container .delete-account:hover {
background-image: url(img/ic-delete-hover-10p@2x.png);
}
.delete-tooltip {
display: none;
position: absolute;
background: #ffffff;
box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.12), 0 4px 4px 0 rgba(0, 0, 0, 0.24);
border-radius: 4px;
font-size: 12px;
font-weight: 300;
color: #424242;
text-align: center;
height: 23px;
line-height: 23px;
padding: 0 12px;
margin-bottom: 4px;
bottom: 100%;
text-indent: initial;
width: -webkit-max-content;
width: -moz-max-content;
width: max-content;
top: -4px;
left: 15px;
}
.delete-account:hover .delete-tooltip {
display: block;
}
.payment-in-progress {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
margin: 0;
}
.op-payment-widget-full-loading {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 150;
background: #fff;
}
#paymentLoadingIcon {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 150;
background: rgba(255, 255, 255, 0.8);
}
.op-payment-widget-full-loading.loadingInContainer,
#paymentLoadingIcon.loadingInContainer {
position: absolute;
}
.loading-container {
width: 64px;
height: 64px;
position: absolute;
top: 50%;
left: 50%;
margin-left: -32px;
margin-top: -32px;
}
.op-payment-redirect-info-modal {
position: fixed;
background: rgba(0,0,0,0.6);
left: 0;
right: 0;
top: 0;
bottom: 0;
max-width: 100%;
margin: 0 auto;
padding: 12px;
z-index: 9;
font-family: 'Roboto', 'RobotoFallback', Arial, sans-serif;
}
.op-payment-redirect-info-modal .popup-modal {
position: absolute;
background: #eee;
left: 0;
right: 0;
max-width: 400px;
margin: 0 auto;
padding: 12px;
top: 50%;
transform: translateY(-50%);
border-radius: 5px;
}
.op-payment-redirect-info-modal .popup-modal .footer {
margin: 12px 5px 5px 5px;
}
.op-payment-redirect-info-modal .popup-modal button {
margin: 0 5px 0 0;
}
.op-payment-redirect-info-modal .popup-modal .modal-header h3 {
margin: 8px;
}
.op-payment-widget-container .expiryMonth,
.op-payment-widget-container .expiryYear,
.op-payment-widget-container input.verificationCode {
width: calc(50% - 5px);
}
.op-payment-widget-container .expiryMonth,
.op-payment-widget-container.card-view .expiryMonth {
margin-right: 10px;
}
.op-payment-widget-container .expiryYear {
margin-right: 0;
margin-left: 0;
}
.op-payment-widget-container .row .col2.birthdate {
width: auto;
}
.op-payment-widget-container .header-title {
display: block;
margin-bottom: 4px;
}
.op-payment-widget-container .header-description {
display: block;
color: #6b6b6b;
font-weight: 300;
font-size: 12px;
}
.op-payment-widget-container a.verification-code-link {
font-size: 12px;
display: inline-block;
text-decoration: none;
max-width: 85px;
vertical-align: middle;
font-weight: 500;
color: #299928;
}
.op-payment-widget-container .row .col2.birthdate select {
width: auto;
}
.op-payment-widget-container .message {
font-size: 12px;
}
.op-payment-widget-container .registered-fieldset .row .col2 {
width: 130px;
margin-right: 0;
}
.op-payment-widget-container .registered-fieldset input.verificationCode {
width: calc(100% - 32px);
}
.op-payment-widget-container .generic-card-img {
background-image: url(img/ic-cards.png);
}
/* Any device including desktop but with small width (resized) */
@media only screen and (max-width: 700px) {
.op-payment-widget-container .standard-fieldset .row.row3 {
position: relative;
}
.op-payment-widget-container .standard-fieldset .hint.hintShown {
top: 36px;
left: 3px;
}
}
@media only screen and (max-width: 674px) {
.op-payment-widget-container .row .col3 {
display: block;
margin-left: 206px;
margin-bottom: 0;
margin-top: 0;
width: auto;
max-width: unset;
}
.op-payment-widget-container .fieldset-has-iban .row .col3 {
width: auto;
max-width: unset;
}
.op-payment-widget-container .INFO,
.op-payment-widget-container .ERROR {
margin-bottom: 8px;
display: block;
}
}
/* Any device including desktop but with small width (resized) */
@media only screen and (max-width: 555px) {
.op-payment-widget-container .fieldset-has-iban .row .col1,
.op-payment-widget-container .fieldset-has-iban .row .col2,
.op-payment-widget-container .fieldset-has-iban .row .col3 {
display: block;
}
.op-payment-widget-container .fieldset-has-iban .row .col1 {
width: auto;
}
.op-payment-widget-container .fieldset-has-iban .row .col3 {
margin-left: 0;
margin-bottom: 16px;
width: auto;
}
.op-payment-widget-container .row .col1,
.op-payment-widget-container .row .col2,
.op-payment-widget-container .row .col3 {
display: block;
}
.op-payment-widget-container .row .col1 {
width: auto;
}
.op-payment-widget-container .row .col3 {
margin-left: 0;
margin-bottom: 16px;
width: auto;
}
.op-payment-widget-container .INFO,
.op-payment-widget-container .ERROR {
margin-bottom: 0;
display: block;
}
.op-payment-widget-container .header-title {
display: block;
margin-bottom: 4px;
}
.op-payment-widget-container .header-description {
display: block;
color: #6b6b6b;
font-weight: 300;
font-size: 12px;
}
.delete-tooltip {
left: auto;
right: 0;
top: 17px;
}
.op-payment-widget-container .standard-fieldset .hint.hintShown {
top: 55px;
left: 0;
}
.op-payment-widget-container .registered-fieldset .hint.hintShown,
.op-payment-widget-container .update-fieldset .hint.hintShown {
top: 27px;
left: 122px;
}
}
/* Smartphones ----------- */
.op-payment-widget-container.card-view {
max-width: 500px;
margin: auto;
overflow: hidden;
}
.op-payment-widget-container.card-view .row .col3 {
margin-bottom: 4px;
}
.op-payment-widget-container.card-view .INFO,
.op-payment-widget-container.card-view .ERROR {
margin-bottom: 0;
}
.op-payment-widget-container.card-view > div.list * {
z-index: 2;
}
.op-payment-widget-container.card-view > div.list select,
.op-payment-widget-container.card-view > div.list label {
position: relative;
}
.op-payment-widget-container.card-view > div.list label.clickable_cont {
position: absolute;
background: transparent;
top: 0;
bottom: 0;
left: 0;
right: 0;
padding: 0;
margin: 0;
z-index: 0;
display: block;
}
.op-payment-widget-container.card-view .row div {
margin: 4px 0;
}
.op-payment-widget-container.card-view .form label {
margin-top: 9px;
}
.op-payment-widget-container.card-view div.list.registered-account-update {
padding-right: 50px;
}
.op-payment-widget-container.card-view input[type='radio'] {
display: none;
}
.op-payment-widget-container.card-view .delete-account-submit.mobile-only {
width: 36%;
height: 100%;
top: 0;
right: 0;
background: #c22d2d;
position: absolute;
display: flex;
align-items: center;
justify-content: center;
color: #ffffff;
display: none;
font-size: 14px;
font-weight: 500;
text-transform: capitalize;
border: 0;
}
.op-payment-widget-container.card-view > div.list {
position: relative;
margin: 10px 0;
padding: 5px 10px;
background-color: #fff;
border: 1px solid #e0e0e0;
border-radius: 5px;
}
.op-payment-widget-container.card-view > div.list.no-style {
padding: 0;
background-color: transparent;
border: none;
box-shadow: none;
}
.op-payment-widget-container.card-view > div.list > label:nth-child(3) {
padding: 10px 0;
}
.op-payment-widget-container.card-view .imgLabel {
width: 52px;
padding: 10px 0;
}
.op-payment-widget-container.card-view .textLabel {
width: 46%;
}
.op-payment-widget-container.card-view .expired-tooltip {
max-width: 230px;
}
.op-payment-widget-container.card-view > div.list > select:nth-child(3) {
margin-bottom: 10px;
width: calc(64% - 14px);
}
.op-payment-widget-container.card-view .row {
position: relative;
}
.op-payment-widget-container.card-view .row .col {
display: flex;
align-items: center;
width: auto;
}
.op-payment-widget-container.card-view .row .col2 {
margin-right: 0;
}
.op-payment-widget-container.card-view .textLabel .the-date {
font-size: 12px;
}
/*.formContainer styling*/
.op-payment-widget-container.card-view .formContainer {
display: none;
margin: 0;
flex-grow: 2;
}
.op-payment-widget-container.card-view select,
.op-payment-widget-container.card-view input {
flex: 1;
margin: 0;
}
.op-payment-widget-container.card-view select:invalid {
color: #999;
}
.op-payment-widget-container.card-view .verificationCode {
flex: initial;
}
.op-payment-widget-container.card-view select:nth-child(n + 2),
.op-payment-widget-container.card-view .hintIcon {
margin-left: 10px;
}
.op-payment-widget-container.card-view .row .hintShown {
position: static;
z-index: 3;
overflow: hidden;
background-color: #fff;
box-sizing: border-box;
padding: 4px;
display: inline-block;
width: auto;
}
.op-payment-widget-container.card-view .hintIcon {
flex: initial;
background-size: 18px 18px;
background-repeat: no-repeat;
}
/* installments */
.op-payment-widget-container.card-view .installments input[type='text'],
.op-payment-widget-container.card-view .installments .installmentPlanId {
max-width: none;
}
.op-payment-widget-container.card-view .row .details {
position: static;
height: auto;
margin-top: initial;
}
.op-payment-widget-container.card-view .installmentPlanRates {
height: auto;
}
/* delete and errors */
.submit-button.card-view,
.op-payment-widget-container.card-view .confirm-button,
.op-payment-widget-container.card-view .delete-account {
font-size: 16px;
}
.op-payment-widget-container.card-view .delete-account {
top: 0;
bottom: 0;
height: 100%;
right: 0;
width: 39px;
background: transparent;
margin: 0;
padding: 0;
place-self: center flex-end;
}
.op-payment-widget-container.card-view .delete-account::after {
content: '';
position: absolute;
margin: -10px 0 0 0;
padding: 0;
top: 50%;
right: 10px;
background-image: url(img/ic-delete-default-14p@2x.png);
width: 14px;
height: 19px;
background-size: 14px 19px;
}
.op-payment-widget-container.card-view .delete-account.has-date {
margin-top: 0;
}
.op-payment-widget-container.card-view .delete-account:hover {
background-image: none;
}
.op-payment-widget-container.card-view .delete-account:hover::after {
background-image: url(img/ic-delete-hover-10p@2x.png);
}
.op-payment-widget-container.card-view .delete-account:hover .delete-tooltip {
display: none;
}
.op-payment-widget-container.card-view .message {
display: block;
height: 15px;
margin-top: -4px;
margin-left: 0px;
font-size: 12px;
}
.op-payment-widget-container.card-view .message.ERROR,
.op-payment-widget-container.card-view .message.INFO {
padding: 0 10px;
color: #fff;
border-radius: 0 0 4px 4px;
}
.op-payment-widget-container.card-view .message.ERROR {
background-color: #ee574d;
}
.op-payment-widget-container.card-view .col2.ERROR > input:first-child,
.op-payment-widget-container.card-view .col2.INFO > input:first-child,
.op-payment-widget-container.card-view .col2.ERROR > select:first-child,
.op-payment-widget-container.card-view .col2.INFO > select:first-child {
border-radius: 4px 4px 4px 0;
}
.op-payment-widget-container.card-view .message.INFO {
background-color: #207a20;
}
.op-payment-widget-container.card-view .message,
.op-payment-widget-container .message {
display: none;
}
.op-payment-widget-container.card-view .message.ERROR,
.op-payment-widget-container .message.ERROR {
display: block;
}
.submit-button.card-view {
display: block;
width: 100%;
max-width: 500px;
height: 50px;
margin: 20px auto;
font-size: 18px;
}
.op-payment-widget-container.card-view > div.list.ERROR {
position: relative;
margin: 0;
padding: 0;
background-color: #fff;
border: 0;
border-radius: 0;
box-shadow: none;
}
.op-payment-widget-container.card-view > div.list.selected {
box-shadow: 0 2px 4px #b6b6b6;
}
.op-payment-widget-container.card-view .textLabel {
margin-left: 23px;
flex-grow: 2;
align-self: center;
}
.op-payment-widget-container.card-view .select.expiryYear {
margin-left: 0;
}
.op-payment-widget-container.card-view a.verification-code-link {
margin-left: 10px;
max-width: 46%;
}
.op-payment-widget-container.card-view .row .col2 {
margin-right: 0;
}
#submitBtnContainer.card-view {
margin: auto;
max-width: 500px;
}
#submitBtnContainer.card-view .submitBtnContainerGroup {
max-width: 500px;
display: flex;
align-items: center;
}
#submitBtnContainer.card-view .paypal-button,
#submitBtnContainer.card-view .apple-pay-button,
#submitBtnContainer.card-view .googlepay {
margin: auto;
}
#submitBtnContainer.card-view .zoid-outlet {
margin: auto;
max-width: 500px;
display: block;
}
.op-payment-widget-lb-overlay {
height: 100%;
width: 100%;
position: fixed;
z-index: 9999; /* op-demo-shop mui has 1100 */
left: 0;
top: 0;
background-color: rgba(0, 0, 0, 0.5);
overflow-x: hidden;
transition: 0.5s;
}
.op-payment-widget-lb-overlay-content {
position: relative;
width: 100%;
height: 100%;
}
.op-payment-widget-lb-iframe {
height: 600px;
width: 500px;
background-color: #ffffff;
border: none;
border-radius: 4px;
padding: 10px;
box-sizing: border-box;
margin: 0;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.op-payment-widget-lb-mobile {
height: 400px;
width: 250px;
}
.op-payment-widget-lb-small {
height: 400px;
width: 390px;
}
.op-payment-widget-lb-landscape {
height: 400px;
width: 600px;
}
.op-payment-widget-lb-full-page {
height: 100%;
width: 100%;
border-radius: 0;
}
.op-payment-widget-lb-loaderContainer {
height: 100vh;
width: 100%;
display: flex;
position: relative;
flex-direction: column;
align-items: center;
justify-content: center;
}
.op-payment-widget-lb-logo {
position: absolute;
left: 0;
right: 0;
margin: 0 auto;
transform: translateY(-200%);
}
.op-payment-widget-lb-loader {
width: 64px;
height: 64px;
color: #6b6b6b;
margin-bottom: 16px;
display: inline-block;
line-height: 1;
-webkit-animation: mui-progress-circular-rotate 1.4s linear infinite;
animation: mui-progress-circular-rotate 1.4s linear infinite;
}
.op-payment-widget-hide {
display: none;
}
.op-payment-widget-lb-loader-circle {
-webkit-animation: mui-progress-circular-dash 1.4s ease-in-out infinite;
animation: mui-progress-circular-dash 1.4s ease-in-out infinite;
stroke-dasharray: 80px, 200px;
stroke-dashoffset: 0px;
stroke: currentColor;
}
@-webkit-keyframes mui-progress-circular-rotate {
100% {
transform: rotate(360deg);
}
}
@-webkit-keyframes mui-progress-circular-dash {
0% {
stroke-dasharray: 1px, 200px;
stroke-dashoffset: 0px;
}
50% {
stroke-dasharray: 100px, 200px;
stroke-dashoffset: -15px;
}
100% {
stroke-dasharray: 100px, 200px;
stroke-dashoffset: -125px;
}
}
/* Smartphones ----------- */
@media only screen and (max-device-width: 530px) {
.op-payment-widget-container.card-view .imgLabel {
width: auto;
display: inline-grid;
grid-auto-flow: column;
grid-template-rows: repeat(3, auto);
min-width: 52px;
}
.op-payment-widget-container.card-view .imgLabelGrid {
gap: 8px;
}
.op-payment-widget-container.card-view .submitBtnContainerGroup {
max-width: 100%;
}
}
/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL3NyYy9vcC1wYXltZW50LXdpZGdldC12My5jc3MiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEscUVBQXFFO0FBQ3JFLHVCQUF1QjtBQUN2QjtJQUNJLDZCQUE2QjtJQUM3QixrQkFBa0I7SUFDbEIsZ0JBQWdCO0lBQ2hCLGlEQUFpRCxFQUFFLHFCQUFxQjtJQUN4RTs7OztvR0FJZ0csRUFBRSxlQUFlO0FBQ3JIO0FBQ0EsdUJBQXVCO0FBQ3ZCO0lBQ0ksNkJBQTZCO0lBQzdCLGtCQUFrQjtJQUNsQixnQkFBZ0I7SUFDaEIsaURBQWlELEVBQUUscUJBQXFCO0lBQ3hFOzs7O29HQUlnRyxFQUFFLGVBQWU7QUFDckg7O0FBRUE7SUFDSTtRQUNJLHlCQUF5QjtJQUM3QjtBQUNKOztBQUVBO0lBQ0k7UUFDSSw0QkFBNEI7UUFDNUIsc0JBQXNCO0lBQzFCOztJQUVBO1FBQ0ksOEJBQThCO1FBQzlCLHdCQUF3QjtJQUM1Qjs7SUFFQTtRQUNJLDhCQUE4QjtRQUM5Qix5QkFBeUI7SUFDN0I7QUFDSjs7QUFFQTtJQUNJLGtCQUFrQjtJQUNsQixnQkFBZ0I7SUFDaEIsWUFBWTtJQUNaLFdBQVc7QUFDZjs7QUFFQTtJQUNJLGFBQWE7QUFDakI7O0FBRUE7SUFDSSxXQUFXO0lBQ1gsWUFBWTtBQUNoQjs7QUFFQTtJQUNJLGNBQWM7QUFDbEI7O0FBRUE7SUFDSSx1RUFBK0Q7WUFBL0QsK0RBQStEO0lBQy9ELDZCQUE2QjtJQUM3QixzQkFBc0I7SUFDdEIsb0JBQW9CO0FBQ3hCOztBQUVBO0lBQ0ksYUFBYTtBQUNqQjs7QUFFQTtJQUNJLGtCQUFrQjtJQUNsQixlQUFlO0lBQ2YsZ0JBQWdCO0lBQ2hCLGNBQWM7QUFDbEI7O0FBRUE7SUFDSSxnQkFBZ0I7QUFDcEI7O0FBRUE7SUFDSSxhQUFhO0FBQ2pCOztBQUVBO0lBQ0ksV0FBVztBQUNmOztBQUVBO0lBQ0ksYUFBYTtBQUNqQjs7QUFFQTtJQUNJLDBEQUEwRDtBQUM5RDs7QUFFQTtJQUNJLGdCQUFnQjtJQUNoQixtQkFBbUI7QUFDdkI7O0FBRUE7SUFDSSxxQkFBcUI7SUFDckIsZ0JBQWdCO0FBQ3BCOztBQUVBOztJQUVJLHFCQUFxQjtJQUNyQixzQkFBc0I7SUFDdEIsdUJBQXVCO0lBQ3ZCLGdCQUFnQjtJQUNoQixtQkFBbUI7QUFDdkI7O0FBRUE7SUFDSSxpQkFBaUI7QUFDckI7O0FBRUE7SUFDSSxlQUFlO0lBQ2YsZUFBZTtBQUNuQjs7QUFFQTtJQUNJLHFCQUFxQjtJQUNyQixXQUFXO0lBQ1gsWUFBWTtJQUNaLG9DQUFvQztJQUNwQyxxQkFBcUI7SUFDckIsa0JBQWtCO0FBQ3RCOztBQUVBO0lBQ0ksZ0JBQWdCO0lBQ2hCLHVCQUF1QjtBQUMzQjs7QUFFQTtJQUNJLHFCQUFxQjtJQUNyQixrQkFBa0I7SUFDbEIsOENBQThDO0lBQzlDLDBCQUEwQjtJQUMxQiw0QkFBNEI7SUFDNUIsZ0JBQWdCO0lBQ2hCLG1CQUFtQjtJQUNuQixZQUFZO0lBQ1osV0FBVztJQUNYLGVBQWU7QUFDbkI7O0FBRUE7SUFDSSxpQkFBaUI7QUFDckI7O0FBRUE7SUFDSSxhQUFhO0lBQ2Isa0JBQWtCO0lBQ2xCLFNBQVM7SUFDVCxVQUFVO0lBQ1YsbUJBQW1CO0lBQ25CLG9FQUFvRTtJQUNwRSwwQkFBa0I7SUFBbEIsdUJBQWtCO0lBQWxCLGtCQUFrQjtJQUNsQixZQUFZO0lBQ1osZ0JBQWdCO0lBQ2hCLGdCQUFnQjtJQUNoQixrQkFBa0I7SUFDbEIsZUFBZTtJQUNmLGdCQUFnQjtJQUNoQixjQUFjO0lBQ2Qsa0JBQWtCO0lBQ2xCLGdCQUFnQjtJQUNoQixrQkFBa0I7SUFDbEIsWUFBWTtJQUNaLG9CQUFvQjtBQUN4Qjs7QUFFQTtJQUNJLGNBQWM7QUFDbEI7O0FBRUE7SUFDSSxlQUFlO0FBQ25COztBQUVBO0lBQ0ksY0FBYztJQUNkLHNCQUFzQjtJQUN0QixxQkFBcUI7SUFDckIsZUFBZTtBQUNuQjs7QUFFQTtJQUNJLGtCQUFrQjtJQUNsQixnQkFBZ0I7SUFDaEIsZ0JBQWdCO0lBQ2hCLGNBQWM7SUFDZCxXQUFXO0FBQ2Y7O0FBRUE7SUFDSSxlQUFlO0FBQ25COztBQUVBOztJQUVJLFlBQVk7SUFDWix5QkFBeUI7SUFDekIsT0FBTztBQUNYOztBQUVBO0lBQ0ksc0JBQXNCO0FBQzFCOztBQUVBO0lBQ0ksaUJBQWlCO0lBQ2pCLFdBQVc7QUFDZjs7QUFFQTtJQUNJLGdCQUFnQjtBQUNwQjs7QUFFQSx3QkFBd0I7O0FBRXhCO0lBQ0ksa0JBQWtCO0FBQ3RCOztBQUVBO0lBQ0ksZUFBZTtBQUNuQjs7QUFFQTtJQUNJLGFBQWE7QUFDakI7O0FBRUE7OztJQUdJLHFCQUFxQjtJQUNyQixzQkFBc0I7QUFDMUI7O0FBRUE7SUFDSSxZQUFZO0FBQ2hCOztBQUVBO0lBQ0ksWUFBWTtJQUNaLGtCQUFrQjtBQUN0Qjs7QUFFQTtJQUNJLDZCQUE2QjtJQUM3QixzQkFBc0I7QUFDMUI7O0FBRUE7SUFDSSw2QkFBNkI7QUFDakM7O0FBRUE7SUFDSSxXQUFXO0lBQ1gsZUFBZTtBQUNuQjs7QUFFQTtJQUNJLFdBQVc7SUFDWCxlQUFlO0FBQ25COztBQUVBO0lBQ0ksWUFBWTtBQUNoQjs7QUFFQTtJQUNJLHlCQUF5QjtBQUM3Qjs7QUFNQTtJQUNJLG9CQUFvQjtBQUN4Qjs7QUFFQTtJQUNJLG9CQUFvQjtBQUN4Qjs7QUFGQTtJQUNJLG9CQUFvQjtBQUN4QjtBQUNBLHlCQUF5Qjs7QUFFekI7SUFDSSxnQkFBZ0I7QUFDcEI7O0FBRUE7SUFDSSxxQkFBcUI7SUFDckIsa0JBQWtCO0FBQ3RCOztBQUVBO0lBQ0ksWUFBWTtJQUNaLFVBQVU7SUFDVixTQUFTO0FBQ2I7O0FBRUE7SUFDSSxhQUFhO0FBQ2pCOztBQUVBOztJQUVJLGtDQUFrQztJQUNsQyx5QkFBeUI7QUFDN0I7O0FBRUE7O0lBRUksWUFBWTtJQUNaLGtCQUFrQjtJQUNsQixlQUFlO0lBQ2YsY0FBYztJQUNkLHNCQUFzQjtJQUN0Qix5QkFBeUI7SUFDekIsa0JBQWtCO0lBQ2xCLGdEQUFnRDtJQUNoRCx3RUFBd0U7SUFDeEUsY0FBYztJQUNkLHNCQUFzQjtJQUN0QixnQkFBZ0I7SUFDaEIsZUFBZTtBQUNuQjs7QUFFQTs7SUFFSSxXQUFXO0FBQ2Y7O0FBRUE7SUFDSSxXQUFXO0FBQ2Y7O0FBRUE7Ozs7SUFJSSxnQkFBZ0I7SUFDaEIseUJBQXlCO0lBQ3pCLGdCQUFnQjtBQUNwQjtBQUNBOztJQUVJLHNCQUFzQjtBQUMxQjtBQUNBOzs7O0lBSUkscUJBQXFCO0FBQ3pCOztBQUVBO0lBQ0ksd0JBQXdCO0lBQ3hCLDRCQUE0QjtBQUNoQztBQUNBO0lBQ0ksZ0JBQWdCO0lBQ2hCLDRCQUE0QjtBQUNoQztBQUNBO0lBQ0ksV0FBVztJQUNYLDRCQUE0QjtBQUNoQztBQUNBO0lBQ0ksZ0JBQWdCO0lBQ2hCLDRCQUE0QjtBQUNoQzs7QUFFQTs7SUFFSSxZQUFZO0lBQ1osZ0JBQWdCO0FBQ3BCOztBQUVBO0lBQ0kscUJBQXFCO0lBQ3JCLFdBQVc7SUFDWCxZQUFZO0lBQ1osc0JBQXNCO0lBQ3RCLDZCQUE2QjtJQUM3QixzQkFBc0I7QUFDMUI7O0FBRUE7SUFDSSxhQUFhO0FBQ2pCOztBQUVBO0lBQ0ksV0FBVztJQUNYLFVBQVU7QUFDZDs7QUFFQTtJQUNJLGNBQWM7SUFDZCxrQkFBa0I7SUFDbEIsVUFBVTtJQUNWLHNCQUFzQjtJQUN0QixzQkFBc0I7SUFDdEIsWUFBWTtJQUNaLGtCQUFrQjtJQUNsQixvRUFBb0U7QUFDeEU7O0FBRUE7SUFDSSxnQkFBZ0I7SUFDaEIsbUJBQW1CO0lBQ25CLGVBQWU7SUFDZixnQkFBZ0I7QUFDcEI7O0FBRUE7SUFDSSxZQUFZO0lBQ1osaUJBQWlCO0lBQ2pCLG9CQUFvQjtJQUNwQixRQUFRO0lBQ1IsV0FBVztBQUNmO0FBQ0E7O0lBRUksUUFBUTtJQUNSLFdBQVc7QUFDZjtBQUNBOztJQUVJLGtCQUFrQjtJQUNsQixZQUFZO0lBQ1osT0FBTztJQUNQLHVCQUF1QjtJQUN2QixTQUFTO0FBQ2I7QUFDQTs7SUFFSSxrQkFBa0I7QUFDdEI7QUFDQTtJQUNJLGtCQUFrQjtJQUNsQixZQUFZO0lBQ1osU0FBUztJQUNULE9BQU87SUFDUCxnQkFBZ0I7QUFDcEI7QUFDQSxtQkFBbUI7QUFDbkI7SUFDSSxrQkFBa0I7SUFDbEIsaUJBQWlCO0FBQ3JCOztBQUVBO0lBQ0ksa0JBQWtCO0lBQ2xCLGNBQWM7SUFDZCxVQUFVO0lBQ1YsYUFBYTtJQUNiLGlCQUFpQjtJQUNqQixlQUFlO0FBQ25COztBQUVBO0lBQ0ksU0FBUztJQUNULGVBQWU7SUFDZixnQkFBZ0I7QUFDcEI7O0FBRUE7SUFDSSxjQUFjO0lBQ2QsWUFBWTtJQUNaLGFBQWE7SUFDYixlQUFlO0lBQ2YsaUJBQWlCO0lBQ2pCLHNCQUFzQjtBQUMxQjs7QUFFQTtJQUNJLFNBQVM7QUFDYjs7QUFFQTtJQUNJLGFBQWE7SUFDYixnQkFBZ0I7QUFDcEI7O0FBRUE7SUFDSSxhQUFhO0FBQ2pCOztBQUVBLG9DQUFvQzs7QUFFcEM7SUFDSSxZQUFZO0lBQ1osaUNBQWlDO0lBQ2pDLG1CQUFtQjtBQUN2Qjs7QUFFQTtJQUNJLGFBQWE7SUFDYixtQkFBbUI7QUFDdkI7O0FBRUE7O0lBRUkscUJBQXFCO0lBQ3JCLHNCQUFzQjtJQUN0QixzQkFBc0I7SUFDdEIsZ0RBQWdEO0lBQ2hELFVBQVU7QUFDZDs7QUFFQTtJQUNJLGlCQUFpQjtBQUNyQjs7QUFFQTtJQUNJLFdBQVc7QUFDZjs7QUFFQTtJQUNJLGNBQWM7SUFDZCxrQkFBa0I7SUFDbEIsaUJBQWlCO0FBQ3JCOztBQUVBO0lBQ0ksbUJBQW1CO0FBQ3ZCOztBQUVBO0lBQ0ksY0FBYztBQUNsQjs7QUFFQTtJQUNJLGNBQWM7QUFDbEI7O0FBRUE7SUFDSSxjQUFjO0FBQ2xCOztBQUVBO0lBQ0ksY0FBYztBQUNsQjs7QUFFQTs7OztJQUlJLGNBQWM7SUFDZCxtQkFBbUI7SUFDbkIsU0FBUztJQUNULGtCQUFrQjtJQUNsQixlQUFlO0lBQ2YsY0FBYztJQUNkLGdCQUFnQjtJQUNoQixjQUFjO0lBQ2QsZ0JBQWdCO0lBQ2hCLFdBQVc7SUFDWCw0QkFBNEI7SUFDNUIsc0JBQXNCO0lBQ3RCLGtCQUFrQjtJQUNsQixnQkFBZ0I7SUFDaEIsZUFBZTtBQUNuQjs7QUFFQTtJQUNJLGVBQWU7QUFDbkI7QUFDQTs7OztJQUlJLGVBQWU7SUFDZixnQkFBZ0I7QUFDcEI7O0FBRUE7O0lBRUksV0FBVztJQUNYLGtCQUFrQjtJQUNsQixVQUFVO0lBQ1YsMENBQTBDO0lBQzFDLDBCQUEwQjtJQUMxQixXQUFXO0lBQ1gsWUFBWTtBQUNoQjs7QUFFQTs7SUFFSSxjQUFjO0FBQ2xCOztBQUVBOztJQUVJLFdBQVc7SUFDWCxrQkFBa0I7SUFDbEIsVUFBVTtJQUNWLHlDQUF5QztJQUN6QyxxQkFBcUI7SUFDckIsV0FBVztJQUNYLFlBQVk7QUFDaEI7O0FBRUE7O0lBRUksbUJBQW1CO0FBQ3ZCOztBQUVBO0lBQ0ksV0FBVztBQUNmOztBQUVBO0lBQ0ksYUFBYTtBQUNqQjs7QUFFQTtJQUNJLGFBQWE7SUFDYiw2QkFBNkI7QUFDakM7O0FBRUE7SUFDSSxhQUFhO0lBQ2IseUJBQXlCO0FBQzdCOztBQUVBLHdCQUF3Qjs7QUFFeEI7SUFDSSxhQUFhO0lBQ2IsZUFBZTtJQUNmLE1BQU07SUFDTixPQUFPO0lBQ1AsV0FBVztJQUNYLFlBQVk7SUFDWixpQ0FBaUM7SUFDakMsZUFBZTtJQUNmLFlBQVk7QUFDaEI7QUFDQTtJQUNJLGlCQUFpQjtJQUNqQjtRQUNJLGVBQWU7SUFDbkI7QUFDSjs7QUFFQTs7SUFFSSxtREFBbUQ7QUFDdkQ7O0FBRUE7SUFDSSxtQkFBbUI7SUFDbkIsc0JBQXNCO0FBQzFCOztBQUVBO0lBQ0ksY0FBYztJQUNkLGtCQUFrQjtJQUNsQixlQUFlO0lBQ2YsWUFBWTtJQUNaLFlBQVk7SUFDWixnQkFBZ0I7SUFDaEIsc0JBQXNCO0lBQ3RCLDBCQUEwQjtJQUMxQiw0RUFBNEU7SUFDNUUsa0JBQWtCO0lBQ2xCLGdCQUFnQjtJQUNoQix3QkFBd0I7SUFDeEIsc0JBQXNCO0lBQ3RCLGtCQUFrQjtBQUN0Qjs7QUFFQTtJQUNJLGlCQUFpQjtJQUNqQixnQkFBZ0I7SUFDaEIsZ0JBQWdCO0lBQ2hCLGtCQUFrQjtBQUN0QjtBQUNBO0lBQ0ksa0JBQWtCO0lBQ2xCLFVBQVU7SUFDVixRQUFRO0lBQ1IsMEJBQTBCO0lBQzFCLFdBQVc7SUFDWCxZQUFZO0lBQ1osa0RBQWtEO0lBQ2xELDBCQUEwQjtJQUMxQiw0QkFBNEI7SUFDNUIsZ0JBQWdCO0lBQ2hCLDZCQUE2QjtJQUM3QixzQkFBc0I7SUFDdEIsVUFBVTtJQUNWLFNBQVM7SUFDVCxlQUFlO0FBQ25COztBQUVBOztJQUVJLG1CQUFtQjtBQUN2Qjs7QUFFQTs7SUFFSSxpQkFBaUI7SUFDakIsZ0JBQWdCO0lBQ2hCLFNBQVM7SUFDVCxlQUFlO0lBQ2YsY0FBYztJQUNkLGtCQUFrQjtJQUNsQixZQUFZO0lBQ1osbUJBQW1CO0lBQ25CLGNBQWM7SUFDZCxjQUFjO0lBQ2Qsd0JBQXdCO0lBQ3hCLGtCQUFrQjtBQUN0Qjs7QUFFQTs7SUFFSSxtQkFBbUI7QUFDdkI7O0FBRUE7SUFDSSxpQkFBaUI7SUFDakIsbUJBQW1CO0lBQ25CLFNBQVM7SUFDVCxlQUFlO0lBQ2YsY0FBYztJQUNkLFlBQVk7SUFDWix1QkFBdUI7SUFDdkIsMEJBQTBCO0lBQzFCLDBCQUEwQjtBQUM5Qjs7QUFFQTtJQUNJLGNBQWM7QUFDbEI7O0FBRUE7SUFDSSxpQkFBaUI7QUFDckI7O0FBRUE7SUFDSSxpQkFBaUI7SUFDakIsZ0JBQWdCO0lBQ2hCLFdBQVc7SUFDWCxTQUFTO0lBQ1Qsa0JBQWtCO0lBQ2xCLGVBQWU7SUFDZixlQUFlO0lBQ2YsY0FBYztJQUNkLHlCQUF5QjtJQUN6QixtQkFBbUI7SUFDbkIsbURBQW1EO0FBQ3ZEO0FBQ0E7O0lBRUksbUJBQW1CO0lBQ25CLGFBQWE7SUFDYix5QkFBeUI7SUFFekIsZ0JBQWdCO0FBQ3BCOztBQUVBO0lBQ0ksa0JBQWtCO0lBQ2xCLGlCQUFpQjtJQUNqQiwwQkFBMEI7SUFDMUIsV0FBVztJQUNYLFlBQVk7SUFDWix1REFBdUQ7SUFDdkQsMEJBQTBCO0lBQzFCLDRCQUE0QjtJQUM1QixnQkFBZ0I7SUFDaEIsNkJBQTZCO0lBQzdCLHNCQUFzQjtJQUN0QixVQUFVO0lBQ1YsU0FBUztJQUNULGVBQWU7SUFDZixhQUFhO0FBQ2pCO0FBQ0E7SUFDSSxxREFBcUQ7QUFDekQ7QUFDQTtJQUNJLGFBQWE7SUFDYixrQkFBa0I7SUFDbEIsbUJBQW1CO0lBQ25CLDBFQUEwRTtJQUMxRSxrQkFBa0I7SUFDbEIsZUFBZTtJQUNmLGdCQUFnQjtJQUNoQixjQUFjO0lBQ2Qsa0JBQWtCO0lBQ2xCLFlBQVk7SUFDWixpQkFBaUI7SUFDakIsZUFBZTtJQUNmLGtCQUFrQjtJQUNsQixZQUFZO0lBQ1osb0JBQW9CO0lBQ3BCLDBCQUFrQjtJQUFsQix1QkFBa0I7SUFBbEIsa0JBQWtCO0lBQ2xCLFNBQVM7SUFDVCxVQUFVO0FBQ2Q7QUFDQTtJQUNJLGNBQWM7QUFDbEI7O0FBRUE7SUFDSSxlQUFlO0lBQ2YsT0FBTztJQUNQLE1BQU07SUFDTixXQUFXO0lBQ1gsWUFBWTtJQUNaLFNBQVM7QUFDYjs7QUFFQTtJQUNJLGVBQWU7SUFDZixPQUFPO0lBQ1AsTUFBTTtJQUNOLFdBQVc7SUFDWCxZQUFZO0lBQ1osWUFBWTtJQUNaLGdCQUFnQjtBQUNwQjs7QUFFQTtJQUNJLGVBQWU7SUFDZixPQUFPO0lBQ1AsTUFBTTtJQUNOLFdBQVc7SUFDWCxZQUFZO0lBQ1osWUFBWTtJQUNaLG9DQUFvQztBQUN4Qzs7QUFFQTs7SUFFSSxrQkFBa0I7QUFDdEI7O0FBRUE7SUFDSSxXQUFXO0lBQ1gsWUFBWTtJQUNaLGtCQUFrQjtJQUNsQixRQUFRO0lBQ1IsU0FBUztJQUNULGtCQUFrQjtJQUNsQixpQkFBaUI7QUFDckI7O0FBRUE7SUFDSSxlQUFlO0lBQ2YsMkJBQTJCO0lBQzNCLE9BQU87SUFDUCxRQUFRO0lBQ1IsTUFBTTtJQUNOLFNBQVM7SUFDVCxlQUFlO0lBQ2YsY0FBYztJQUNkLGFBQWE7SUFDYixVQUFVO0lBQ1YsMERBQTBEO0FBQzlEOztBQUVBO0lBQ0ksa0JBQWtCO0lBQ2xCLGdCQUFnQjtJQUNoQixPQUFPO0lBQ1AsUUFBUTtJQUNSLGdCQUFnQjtJQUNoQixjQUFjO0lBQ2QsYUFBYTtJQUNiLFFBQVE7SUFDUiwyQkFBMkI7SUFDM0Isa0JBQWtCO0FBQ3RCOztBQUVBO0lBQ0ksd0JBQXdCO0FBQzVCOztBQUVBO0lBQ0ksaUJBQWlCO0FBQ3JCOztBQUVBO0lBQ0ksV0FBVztBQUNmOztBQUVBOzs7SUFHSSxzQkFBc0I7QUFDMUI7O0FBRUE7O0lBRUksa0JBQWtCO0FBQ3RCOztBQUVBO0lBQ0ksZUFBZTtJQUNmLGNBQWM7QUFDbEI7O0FBRUE7SUFDSSxXQUFXO0FBQ2Y7O0FBRUE7SUFDSSxjQUFjO0lBQ2Qsa0JBQWtCO0FBQ3RCOztBQUVBO0lBQ0ksY0FBYztJQUNkLGNBQWM7SUFDZCxnQkFBZ0I7SUFDaEIsZUFBZTtBQUNuQjs7QUFFQTtJQUNJLGVBQWU7SUFDZixxQkFBcUI7SUFDckIscUJBQXFCO0lBQ3JCLGVBQWU7SUFDZixzQkFBc0I7SUFDdEIsZ0JBQWdCO0lBQ2hCLGNBQWM7QUFDbEI7O0FBRUE7SUFDSSxXQUFXO0FBQ2Y7O0FBRUE7SUFDSSxlQUFlO0FBQ25CO0FBQ0E7SUFDSSxZQUFZO0lBQ1osZUFBZTtBQUNuQjtBQUNBO0lBQ0ksd0JBQXdCO0FBQzVCO0FBQ0E7SUFDSSx1Q0FBdUM7QUFDM0M7QUFDQSxnRUFBZ0U7O0FBRWhFO0lBQ0k7UUFDSSxrQkFBa0I7SUFDdEI7SUFDQTtRQUNJLFNBQVM7UUFDVCxTQUFTO0lBQ2I7QUFDSjs7QUFFQTtJQUNJO1FBQ0ksY0FBYztRQUNkLGtCQUFrQjtRQUNsQixnQkFBZ0I7UUFDaEIsYUFBYTtRQUNiLFdBQVc7UUFDWCxnQkFBZ0I7SUFDcEI7O0lBRUE7UUFDSSxXQUFXO1FBQ1gsZ0JBQWdCO0lBQ3BCOztJQUVBOztRQUVJLGtCQUFrQjtRQUNsQixjQUFjO0lBQ2xCO0FBQ0o7O0FBRUEsZ0VBQWdFOztBQUVoRTtJQUNJOzs7UUFHSSxjQUFjO0lBQ2xCO0lBQ0E7UUFDSSxXQUFXO0lBQ2Y7SUFDQTtRQUNJLGNBQWM7UUFDZCxtQkFBbUI7UUFDbkIsV0FBVztJQUNmO0lBQ0E7OztRQUdJLGNBQWM7SUFDbEI7SUFDQTtRQUNJLFdBQVc7SUFDZjtJQUNBO1FBQ0ksY0FBYztRQUNkLG1CQUFtQjtRQUNuQixXQUFXO0lBQ2Y7SUFDQTs7UUFFSSxnQkFBZ0I7UUFDaEIsY0FBYztJQUNsQjtJQUNBO1FBQ0ksY0FBYztRQUNkLGtCQUFrQjtJQUN0QjtJQUNBO1FBQ0ksY0FBYztRQUNkLGNBQWM7UUFDZCxnQkFBZ0I7UUFDaEIsZUFBZTtJQUNuQjtJQUNBO1FBQ0ksVUFBVTtRQUNWLFFBQVE7UUFDUixTQUFTO0lBQ2I7SUFDQTtRQUNJLFNBQVM7UUFDVCxPQUFPO0lBQ1g7SUFDQTs7UUFFSSxTQUFTO1FBQ1QsV0FBVztJQUNmO0FBQ0o7QUFDQSw0QkFBNEI7QUFDNUI7SUFDSSxnQkFBZ0I7SUFDaEIsWUFBWTtJQUNaLGdCQUFnQjtBQUNwQjs7QUFFQTtJQUNJLGtCQUFrQjtBQUN0Qjs7QUFFQTs7SUFFSSxnQkFBZ0I7QUFDcEI7O0FBRUE7SUFDSSxVQUFVO0FBQ2Q7QUFDQTs7SUFFSSxrQkFBa0I7QUFDdEI7QUFDQTtJQUNJLGtCQUFrQjtJQUNsQix1QkFBdUI7SUFDdkIsTUFBTTtJQUNOLFNBQVM7SUFDVCxPQUFPO0lBQ1AsUUFBUTtJQUNSLFVBQVU7SUFDVixTQUFTO0lBQ1QsVUFBVTtJQUNWLGNBQWM7QUFDbEI7O0FBRUE7SUFDSSxhQUFhO0FBQ2pCOztBQUVBO0lBQ0ksZUFBZTtBQUNuQjs7QUFFQTtJQUNJLG1CQUFtQjtBQUN2Qjs7QUFFQTtJQUNJLGFBQWE7QUFDakI7O0FBRUE7SUFDSSxVQUFVO0lBQ1YsWUFBWTtJQUNaLE1BQU07SUFDTixRQUFRO0lBQ1IsbUJBQW1CO0lBQ25CLGtCQUFrQjtJQUNsQixhQUFhO0lBQ2IsbUJBQW1CO0lBQ25CLHVCQUF1QjtJQUN2QixjQUFjO0lBQ2QsYUFBYTtJQUNiLGVBQWU7SUFDZixnQkFBZ0I7SUFDaEIsMEJBQTBCO0lBQzFCLFNBQVM7QUFDYjs7QUFFQTtJQUNJLGtCQUFrQjtJQUNsQixjQUFjO0lBQ2QsaUJBQWlCO0lBQ2pCLHNCQUFzQjtJQUN0Qix5QkFBeUI7SUFDekIsa0JBQWtCO0FBQ3RCO0FBQ0E7SUFDSSxVQUFVO0lBQ1YsNkJBQTZCO0lBQzdCLFlBQVk7SUFDWixnQkFBZ0I7QUFDcEI7QUFDQTtJQUNJLGVBQWU7QUFDbkI7QUFDQTtJQUNJLFdBQVc7SUFDWCxlQUFlO0FBQ25CO0FBQ0E7SUFDSSxVQUFVO0FBQ2Q7O0FBRUE7SUFDSSxnQkFBZ0I7QUFDcEI7O0FBRUE7SUFDSSxtQkFBbUI7SUFDbkIsdUJBQXVCO0FBQzNCO0FBQ0E7SUFDSSxrQkFBa0I7QUFDdEI7QUFDQTtJQUNJLGFBQWE7SUFDYixtQkFBbUI7SUFDbkIsV0FBVztBQUNmO0FBQ0E7SUFDSSxlQUFlO0FBQ25CO0FBQ0E7SUFDSSxlQUFlO0FBQ25CO0FBQ0EseUJBQXlCO0FBQ3pCO0lBQ0ksYUFBYTtJQUNiLFNBQVM7SUFDVCxZQUFZO0FBQ2hCO0FBQ0E7O0lBRUksT0FBTztJQUNQLFNBQVM7QUFDYjs7QUFFQTtJQUNJLFdBQVc7QUFDZjs7QUFFQTtJQUNJLGFBQWE7QUFDakI7QUFDQTs7SUFFSSxpQkFBaUI7QUFDckI7QUFDQTtJQUNJLGdCQUFnQjtJQUNoQixVQUFVO0lBQ1YsZ0JBQWdCO0lBQ2hCLHNCQUFzQjtJQUN0QixzQkFBc0I7SUFDdEIsWUFBWTtJQUNaLHFCQUFxQjtJQUNyQixXQUFXO0FBQ2Y7QUFDQTtJQUNJLGFBQWE7SUFDYiwwQkFBMEI7SUFDMUIsNEJBQTRCO0FBQ2hDO0FBQ0EsbUJBQW1CO0FBQ25COztJQUVJLGVBQWU7QUFDbkI7QUFDQTtJQUNJLGdCQUFnQjtJQUNoQixZQUFZO0lBQ1osbUJBQW1CO0FBQ3ZCO0FBQ0E7SUFDSSxZQUFZO0FBQ2hCO0FBQ0Esd0JBQXdCOztBQUV4Qjs7O0lBR0ksZUFBZTtBQUNuQjtBQUNBO0lBQ0ksTUFBTTtJQUNOLFNBQVM7SUFDVCxZQUFZO0lBQ1osUUFBUTtJQUNSLFdBQVc7SUFDWCx1QkFBdUI7SUFDdkIsU0FBUztJQUNULFVBQVU7SUFDViwyQkFBMkI7QUFDL0I7QUFDQTtJQUNJLFdBQVc7SUFDWCxrQkFBa0I7SUFDbEIsbUJBQW1CO0lBQ25CLFVBQVU7SUFDVixRQUFRO0lBQ1IsV0FBVztJQUNYLHVEQUF1RDtJQUN2RCxXQUFXO0lBQ1gsWUFBWTtJQUNaLDBCQUEwQjtBQUM5Qjs7QUFFQTtJQUNJLGFBQWE7QUFDakI7O0FBRUE7SUFDSSxzQkFBc0I7QUFDMUI7O0FBRUE7SUFDSSxxREFBcUQ7QUFDekQ7O0FBRUE7SUFDSSxhQUFhO0FBQ2pCOztBQUVBO0lBQ0ksY0FBYztJQUNkLFlBQVk7SUFDWixnQkFBZ0I7SUFDaEIsZ0JBQWdCO0lBQ2hCLGVBQWU7QUFDbkI7QUFDQTs7SUFFSSxlQUFlO0lBQ2YsV0FBVztJQUNYLDBCQUEwQjtBQUM5QjtBQUNBO0lBQ0kseUJBQXlCO0FBQzdCO0FBQ0E7Ozs7SUFJSSw0QkFBNEI7QUFDaEM7QUFDQTtJQUNJLHlCQUF5QjtBQUM3QjtBQUNBOztJQUVJLGFBQWE7QUFDakI7QUFDQTs7SUFFSSxjQUFjO0FBQ2xCO0FBQ0E7SUFDSSxjQUFjO0lBQ2QsV0FBVztJQUNYLGdCQUFnQjtJQUNoQixZQUFZO0lBQ1osaUJBQWlCO0lBQ2pCLGVBQWU7QUFDbkI7O0FBRUE7SUFDSSxrQkFBa0I7SUFDbEIsU0FBUztJQUNULFVBQVU7SUFDVixzQkFBc0I7SUFDdEIsU0FBUztJQUNULGdCQUFnQjtJQUNoQixnQkFBZ0I7QUFDcEI7QUFDQTtJQUNJLDZCQUE2QjtBQUNqQzs7QUFFQTtJQUNJLGlCQUFpQjtJQUNqQixZQUFZO0lBQ1osa0JBQWtCO0FBQ3RCOztBQUVBO0lBQ0ksY0FBYztBQUNsQjs7QUFFQTtJQUNJLGlCQUFpQjtJQUNqQixjQUFjO0FBQ2xCOztBQUVBO0lBQ0ksZUFBZTtBQUNuQjs7QUFFQTtJQUNJLFlBQVk7SUFDWixnQkFBZ0I7QUFDcEI7QUFDQTtJQUNJLGdCQUFnQjtJQUNoQixhQUFhO0lBQ2IsbUJBQW1CO0FBQ3ZCO0FBQ0E7OztJQUdJLFlBQVk7QUFDaEI7QUFDQTtJQUNJLFlBQVk7SUFDWixnQkFBZ0I7SUFDaEIsY0FBYztBQUNsQjs7QUFFQTtJQUNJLFlBQVk7SUFDWixXQUFXO0lBQ1gsZUFBZTtJQUNmLGFBQWEsRUFBRSw4QkFBOEI7SUFDN0MsT0FBTztJQUNQLE1BQU07SUFDTixvQ0FBb0M7SUFDcEMsa0JBQWtCO0lBQ2xCLGdCQUFnQjtBQUNwQjs7QUFFQTtJQUNJLGtCQUFrQjtJQUNsQixXQUFXO0lBQ1gsWUFBWTtBQUNoQjs7QUFFQTtJQUNJLGFBQWE7SUFDYixZQUFZO0lBQ1oseUJBQXlCO0lBQ3pCLFlBQVk7SUFDWixrQkFBa0I7SUFDbEIsYUFBYTtJQUNiLHNCQUFzQjtJQUN0QixTQUFTO0lBQ1Qsa0JBQWtCO0lBQ2xCLFFBQVE7SUFDUixTQUFTO0lBRVQsZ0NBQWdDO0FBQ3BDOztBQUVBO0lBQ0ksYUFBYTtJQUNiLFlBQVk7QUFDaEI7O0FBRUE7SUFDSSxhQUFhO0lBQ2IsWUFBWTtBQUNoQjs7QUFFQTtJQUNJLGFBQWE7SUFDYixZQUFZO0FBQ2hCOztBQUVBO0lBQ0ksWUFBWTtJQUNaLFdBQVc7SUFDWCxnQkFBZ0I7QUFDcEI7O0FBRUE7SUFDSSxhQUFhO0lBQ2IsV0FBVztJQUNYLGFBQWE7SUFDYixrQkFBa0I7SUFDbEIsc0JBQXNCO0lBQ3RCLG1CQUFtQjtJQUNuQix1QkFBdUI7QUFDM0I7O0FBRUE7SUFDSSxrQkFBa0I7SUFDbEIsT0FBTztJQUNQLFFBQVE7SUFDUixjQUFjO0lBQ2QsNEJBQTRCO0FBQ2hDOztBQUVBO0lBQ0ksV0FBVztJQUNYLFlBQVk7SUFDWixjQUFjO0lBQ2QsbUJBQW1CO0lBQ25CLHFCQUFxQjtJQUNyQixjQUFjO0lBQ2Qsb0VBQTREO1lBQTVELDREQUE0RDtBQUNoRTs7QUFFQTtJQUNJLGFBQWE7QUFDakI7O0FBRUE7SUFDSSx1RUFBK0Q7WUFBL0QsK0RBQStEO0lBQy9ELDZCQUE2QjtJQUM3QixzQkFBc0I7SUFDdEIsb0JBQW9CO0FBQ3hCOztBQUVBO0lBQ0k7UUFDSSx5QkFBeUI7SUFDN0I7QUFDSjs7QUFFQTtJQUNJO1FBQ0ksNEJBQTRCO1FBQzVCLHNCQUFzQjtJQUMxQjtJQUNBO1FBQ0ksOEJBQThCO1FBQzlCLHdCQUF3QjtJQUM1QjtJQUNBO1FBQ0ksOEJBQThCO1FBQzlCLHlCQUF5QjtJQUM3QjtBQUNKOztBQUVBLDRCQUE0Qjs7QUFFNUI7SUFDSTtRQUNJLFdBQVc7UUFDWCxvQkFBb0I7UUFDcEIsc0JBQXNCO1FBQ3RCLG1DQUFtQztRQUNuQyxlQUFlO0lBQ25COztJQUVBO1FBQ0ksUUFBUTtJQUNaOztJQUVBO1FBQ0ksZUFBZTtJQUNuQjtBQUNKIiwiZmlsZSI6Im9wLXBheW1lbnQtd2lkZ2V0LXYzLmNzcyIsInNvdXJjZXNDb250ZW50IjpbIkBpbXBvcnQgdXJsKCdodHRwczovL2ZvbnRzLmdvb2dsZWFwaXMuY29tL2Nzcz9mYW1pbHk9Um9ib3RvOjMwMCw1MDAnKTtcbi8qIHJvYm90by0zMDAgLSBsYXRpbiAqL1xuQGZvbnQtZmFjZSB7XG4gICAgZm9udC1mYW1pbHk6ICdSb2JvdG9GYWxsYmFjayc7XG4gICAgZm9udC1zdHlsZTogbm9ybWFsO1xuICAgIGZvbnQtd2VpZ2h0OiAzMDA7XG4gICAgc3JjOiB1cmwoJ3N0YXRpYy9mb250cy9yb2JvdG8tdjI5LWxhdGluLTMwMC5lb3QnKTsgLyogSUU5IENvbXBhdCBNb2RlcyAqL1xuICAgIHNyYzogbG9jYWwoJycpLCB1cmwoJ3N0YXRpYy9mb250cy9yb2JvdG8tdjI5LWxhdGluLTMwMC5lb3Q/I2llZml4JykgZm9ybWF0KCdlbWJlZGRlZC1vcGVudHlwZScpLFxuICAgICAgICAvKiBJRTYtSUU4ICovIHVybCgnc3RhdGljL2ZvbnRzL3JvYm90by12MjktbGF0aW4tMzAwLndvZmYyJykgZm9ybWF0KCd3b2ZmMicpLFxuICAgICAgICAvKiBTdXBlciBNb2Rlcm4gQnJvd3NlcnMgKi8gdXJsKCdzdGF0aWMvZm9udHMvcm9ib3RvLXYyOS1sYXRpbi0zMDAud29mZicpIGZvcm1hdCgnd29mZicpLFxuICAgICAgICAvKiBNb2Rlcm4gQnJvd3NlcnMgKi8gdXJsKCdzdGF0aWMvZm9udHMvcm9ib3RvLXYyOS1sYXRpbi0zMDAudHRmJykgZm9ybWF0KCd0cnVldHlwZScpLFxuICAgICAgICAvKiBTYWZhcmksIEFuZHJvaWQsIGlPUyAqLyB1cmwoJ3N0YXRpYy9mb250cy9yb2JvdG8tdjI5LWxhdGluLTMwMC5zdmcjUm9ib3RvJykgZm9ybWF0KCdzdmcnKTsgLyogTGVnYWN5IGlPUyAqL1xufVxuLyogcm9ib3RvLTUwMCAtIGxhdGluICovXG5AZm9udC1mYWNlIHtcbiAgICBmb250LWZhbWlseTogJ1JvYm90b0ZhbGxiYWNrJztcbiAgICBmb250LXN0eWxlOiBub3JtYWw7XG4gICAgZm9udC13ZWlnaHQ6IDUwMDtcbiAgICBzcmM6IHVybCgnc3RhdGljL2ZvbnRzL3JvYm90by12MjktbGF0aW4tNTAwLmVvdCcpOyAvKiBJRTkgQ29tcGF0IE1vZGVzICovXG4gICAgc3JjOiBsb2NhbCgnJyksIHVybCgnc3RhdGljL2ZvbnRzL3JvYm90by12MjktbGF0aW4tNTAwLmVvdD8jaWVmaXgnKSBmb3JtYXQoJ2VtYmVkZGVkLW9wZW50eXBlJyksXG4gICAgICAgIC8qIElFNi1JRTggKi8gdXJsKCdzdGF0aWMvZm9udHMvcm9ib3RvLXYyOS1sYXRpbi01MDAud29mZjInKSBmb3JtYXQoJ3dvZmYyJyksXG4gICAgICAgIC8qIFN1cGVyIE1vZGVybiBCcm93c2VycyAqLyB1cmwoJ3N0YXRpYy9mb250cy9yb2JvdG8tdjI5LWxhdGluLTUwMC53b2ZmJykgZm9ybWF0KCd3b2ZmJyksXG4gICAgICAgIC8qIE1vZGVybiBCcm93c2VycyAqLyB1cmwoJ3N0YXRpYy9mb250cy9yb2JvdG8tdjI5LWxhdGluLTUwMC50dGYnKSBmb3JtYXQoJ3RydWV0eXBlJyksXG4gICAgICAgIC8qIFNhZmFyaSwgQW5kcm9pZCwgaU9TICovIHVybCgnc3RhdGljL2ZvbnRzL3JvYm90by12MjktbGF0aW4tNTAwLnN2ZyNSb2JvdG8nKSBmb3JtYXQoJ3N2ZycpOyAvKiBMZWdhY3kgaU9TICovXG59XG5cbkBrZXlmcmFtZXMgbXVpLXByb2dyZXNzLWNpcmN1bGFyLXJvdGF0ZSB7XG4gICAgMTAwJSB7XG4gICAgICAgIHRyYW5zZm9ybTogcm90YXRlKDM2MGRlZyk7XG4gICAgfVxufVxuXG5Aa2V5ZnJhbWVzIG11aS1wcm9ncmVzcy1jaXJjdWxhci1kYXNoIHtcbiAgICAwJSB7XG4gICAgICAgIHN0cm9rZS1kYXNoYXJyYXk6IDFweCwgMjAwcHg7XG4gICAgICAgIHN0cm9rZS1kYXNob2Zmc2V0OiAwcHg7XG4gICAgfVxuXG4gICAgNTAlIHtcbiAgICAgICAgc3Ryb2tlLWRhc2hhcnJheTogMTAwcHgsIDIwMHB4O1xuICAgICAgICBzdHJva2UtZGFzaG9mZnNldDogLTE1cHg7XG4gICAgfVxuXG4gICAgMTAwJSB7XG4gICAgICAgIHN0cm9rZS1kYXNoYXJyYXk6IDEwMHB4LCAyMDBweDtcbiAgICAgICAgc3Ryb2tlLWRhc2hvZmZzZXQ6IC0xMjVweDtcbiAgICB9XG59XG5cbi5vcC1wYXltZW50LXdpZGdldC1tYWluLWNvbnRhaW5lciB7XG4gICAgcG9zaXRpb246IHJlbGF0aXZlO1xuICAgIG1pbi1oZWlnaHQ6IDgwcHg7XG4gICAgaGVpZ2h0OiAxMDAlO1xuICAgIHdpZHRoOiAxMDAlO1xufVxuXG4ub3AtcGF5bWVudC13aWRnZXQtc3VtbWFyeSB7XG4gICAgbWluLWhlaWdodDogMDtcbn1cblxuLmNvbnRhaW5lci1sb2FkaW5nIHtcbiAgICB3aWR0aDogNjRweDtcbiAgICBoZWlnaHQ6IDY0cHg7XG59XG5cbi5NdWlDaXJjdWxhclByb2dyZXNzLXN2ZyB7XG4gICAgY29sb3I6ICMyOTk5Mjg7XG59XG5cbi5NdWlDaXJjdWxhclByb2dyZXNzLWNpcmNsZSB7XG4gICAgYW5pbWF0aW9uOiBtdWktcHJvZ3Jlc3MtY2lyY3VsYXItZGFzaCAxLjRzIGVhc2UtaW4tb3V0IGluZmluaXRlO1xuICAgIHN0cm9rZS1kYXNoYXJyYXk6IDgwcHgsIDIwMHB4O1xuICAgIHN0cm9rZS1kYXNob2Zmc2V0OiAwcHg7XG4gICAgc3Ryb2tlOiBjdXJyZW50Q29sb3I7XG59XG5cbi5zaW5nbGUtcGF5bWVudC1vcHRpb24gaW5wdXRbdHlwZT0ncmFkaW8nXSB7XG4gICAgZGlzcGxheTogbm9uZTtcbn1cblxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lciB7XG4gICAgcG9zaXRpb246IHJlbGF0aXZlO1xuICAgIGZvbnQtc2l6ZTogMTZweDtcbiAgICBmb250LXdlaWdodDogMzAwO1xuICAgIGNvbG9yOiAjNDI0MjQyO1xufVxuXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyIHN0cm9uZyB7XG4gICAgZm9udC13ZWlnaHQ6IDUwMDtcbn1cblxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lciAuc2VwYS1iaWMtcm93IHtcbiAgICBkaXNwbGF5OiBub25lO1xufVxuXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyIC5mbG9hdC1sZWZ0IHtcbiAgICBmbG9hdDogbGVmdDtcbn1cblxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lciA+IGRpdi5saXN0IGxhYmVsLmNsaWNrYWJsZV9jb250IHtcbiAgICBkaXNwbGF5OiBub25lO1xufVxuXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyICoge1xuICAgIGZvbnQtZmFtaWx5OiAnUm9ib3RvJywgJ1JvYm90b0ZhbGxiYWNrJywgQXJpYWwsIHNhbnMtc2VyaWY7XG59XG5cbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgPiBkaXYubGlzdCB7XG4gICAgbGlzdC1zdHlsZTogbm9uZTtcbiAgICBtYXJnaW4tYm90dG9tOiAxMnB4O1xufVxuXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyID4gZGl2Lmxpc3Qubm8tc3R5bGUge1xuICAgIG1hcmdpbjogMTJweCAwIDEycHggMDtcbiAgICBmb250LXdlaWdodDogNTAwO1xufVxuXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyID4gZGl2Lmxpc3QgPiBsYWJlbCxcbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgLnRleHRMYWJlbCB7XG4gICAgZGlzcGxheTogaW5saW5lLWJsb2NrO1xuICAgIHZlcnRpY2FsLWFsaWduOiBtaWRkbGU7XG4gICAgdGV4dC1vdmVyZmxvdzogZWxsaXBzaXM7XG4gICAgb3ZlcmZsb3c6IGhpZGRlbjtcbiAgICB3aGl0ZS1zcGFjZTogbm93cmFwO1xufVxuXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyIC50ZXh0TGFiZWwge1xuICAgIG1hcmdpbi1sZWZ0OiAxNHB4O1xufVxuXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyIC50ZXh0TGFiZWwgLnRoZS1kYXRlIHtcbiAgICBtYXJnaW4tdG9wOiA0cHg7XG4gICAgZm9udC1zaXplOiAxNHB4O1xufVxuXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyIC50ZXh0TGFiZWwgLnRoZS1kYXRlIC5leHBpcmVkLWljb24ge1xuICAgIGRpc3BsYXk6IGlubGluZS1ibG9jaztcbiAgICB3aWR0aDogMTRweDtcbiAgICBoZWlnaHQ6IDE0cHg7XG4gICAgYmFja2dyb3VuZDogdXJsKGltZy9pYy1hbGVydEAyeC5wbmcpO1xuICAgIGJhY2tncm91bmQtc2l6ZTogMTAwJTtcbiAgICBwb3NpdGlvbjogcmVsYXRpdmU7XG59XG5cbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgLnRleHRMYWJlbCAudGhlLWRhdGUtZXhwaXJlZCB7XG4gICAgZm9udC13ZWlnaHQ6IDUwMDtcbiAgICBjb2xvcjogcmdiKDE4MywgMjgsIDI4KTtcbn1cblxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lciAudGV4dExhYmVsIC5iYWRnZS1leHBpcmVkIHtcbiAgICBkaXNwbGF5OiBpbmxpbmUtYmxvY2s7XG4gICAgcG9zaXRpb246IHJlbGF0aXZlO1xuICAgIGJhY2tncm91bmQtaW1hZ2U6IHVybCgnLi9pbWcvaWMtYWxlcnRAMngucG5nJyk7XG4gICAgYmFja2dyb3VuZC1zaXplOiAxNnB4IDE2cHg7XG4gICAgYmFja2dyb3VuZC1yZXBlYXQ6IG5vLXJlcGVhdDtcbiAgICBtYXJnaW4tbGVmdDogNHB4O1xuICAgIG1hcmdpbi1ib3R0b206IC0ycHg7XG4gICAgaGVpZ2h0OiAxNnB4O1xuICAgIHdpZHRoOiAxNnB4O1xuICAgIGN1cnNvcjogcG9pbnRlcjtcbn1cblxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lciAuZXhwaXJlZExhYmVsIHtcbiAgICBvdmVyZmxvdzogdmlzaWJsZTtcbn1cblxuLmV4cGlyZWQtdG9vbHRpcCB7XG4gICAgZGlzcGxheTogbm9uZTtcbiAgICBwb3NpdGlvbjogYWJzb2x1dGU7XG4gICAgdG9wOiAtM3B4O1xuICAgIGxlZnQ6IDIycHg7XG4gICAgYmFja2dyb3VuZDogI2ZmZmZmZjtcbiAgICBib3gtc2hhZG93OiAwIDAgNHB4IDAgcmdiKDAgMCAwIC8gMTIlKSwgMCA0cHggNHB4IDAgcmdiKDAgMCAwIC8gMjQlKTtcbiAgICB3aWR0aDogbWF4LWNvbnRlbnQ7XG4gICAgaGVpZ2h0OiAxNHB4O1xuICAgIG1heC13aWR0aDogNjcwcHg7XG4gICAgbWF4LWhlaWdodDogNDZweDtcbiAgICBib3JkZXItcmFkaXVzOiA0cHg7XG4gICAgZm9udC1zaXplOiAxMnB4O1xuICAgIGZvbnQtd2VpZ2h0OiAzMDA7XG4gICAgY29sb3I6ICM0MjQyNDI7XG4gICAgdGV4dC1hbGlnbjogY2VudGVyO1xuICAgIHBhZGRpbmc6IDRweCA4cHg7XG4gICAgbWFyZ2luLWJvdHRvbTogNHB4O1xuICAgIGJvdHRvbTogMTAwJTtcbiAgICB0ZXh0LWluZGVudDogaW5pdGlhbDtcbn1cblxuLmJhZGdlLWV4cGlyZWQ6aG92ZXIgLmV4cGlyZWQtdG9vbHRpcCB7XG4gICAgZGlzcGxheTogYmxvY2s7XG59XG5cbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgPiBkaXYubGlzdCBsYWJlbCBpbWcge1xuICAgIGN1cnNvcjogcG9pbnRlcjtcbn1cblxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lciAuaW1nTGFiZWwge1xuICAgIHBhZGRpbmc6IDdweCAwO1xuICAgIHZlcnRpY2FsLWFsaWduOiBtaWRkbGU7XG4gICAgZGlzcGxheTogaW5saW5lLWJsb2NrO1xuICAgIG1pbi13aWR0aDogNTJweDtcbn1cblxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lciAuaW1nTGFiZWwgaW1nIHtcbiAgICBtYXJnaW4tcmlnaHQ6IDEwcHg7XG4gICAgbWF4LWhlaWdodDogMzJweDtcbiAgICBtYXgtd2lkdGg6IDEwMHB4O1xuICAgIGRpc3BsYXk6IGJsb2NrO1xuICAgIGZsb2F0OiBsZWZ0O1xufVxuXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyIC5pbWdMYWJlbCBpbWc6bGFzdC1jaGlsZCB7XG4gICAgbWFyZ2luLXJpZ2h0OiAwO1xufVxuXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyIC5pbmFjdGl2ZSBsYWJlbCBpbWcsXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyIGltZy5pbmFjdGl2ZSB7XG4gICAgb3BhY2l0eTogMC41O1xuICAgIGZpbHRlcjogYWxwaGEob3BhY2l0eT01MCk7XG4gICAgem9vbTogMTtcbn1cblxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lciBsYWJlbCBpbWcge1xuICAgIHZlcnRpY2FsLWFsaWduOiBtaWRkbGU7XG59XG5cbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgc2VsZWN0LnNlbGVjdC1pbi1ncm91cCB7XG4gICAgbWFyZ2luLWxlZnQ6IDIzcHg7XG4gICAgd2lkdGg6IGF1dG87XG59XG5cbi5zdWJtaXRCdG5Db250YWluZXJHcm91cCB7XG4gICAgbWF4LXdpZHRoOiAzNzBweDtcbn1cblxuLyoucm93IGFuZCAuY29sIHN0eWxpbmcqL1xuXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyIC5yb3cge1xuICAgIHBvc2l0aW9uOiByZWxhdGl2ZTtcbn1cblxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lciBmaWVsZHNldCAucm93IHtcbiAgICBwb3NpdGlvbjogdW5zZXQ7XG59XG5cbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgLnJvdyBkaXYge1xuICAgIG1hcmdpbjogNnB4IDA7XG59XG5cbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgLnJvdyAuY29sMSxcbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgLnJvdyAuY29sMixcbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgLnJvdyAuY29sMyB7XG4gICAgZGlzcGxheTogaW5saW5lLWJsb2NrO1xuICAgIHZlcnRpY2FsLWFsaWduOiBtaWRkbGU7XG59XG5cbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgLnJvdyAuY29sMSB7XG4gICAgd2lkdGg6IDIwMHB4O1xufVxuXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyIC5yb3cgLmNvbDIge1xuICAgIHdpZHRoOiAyMDRweDtcbiAgICBtYXJnaW4tcmlnaHQ6IDEwcHg7XG59XG5cbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgLnJvdyAuY29sMyB7XG4gICAgbWF4LXdpZHRoOiBjYWxjKDEwMCUgLSA0MjVweCk7XG4gICAgdmVydGljYWwtYWxpZ246IG1pZGRsZTtcbn1cblxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lciAuZmllbGRzZXQtaGFzLWliYW4gLnJvdyAuY29sMyB7XG4gICAgbWF4LXdpZHRoOiBjYWxjKDEwMCUgLSA1MDVweCk7XG59XG5cbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIuY2FyZC12aWV3IC5yb3cgLmNvbDMge1xuICAgIHdpZHRoOiBhdXRvO1xuICAgIG1heC13aWR0aDogbm9uZTtcbn1cblxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lci5jYXJkLXZpZXcgLmZpZWxkc2V0LWhhcy1pYmFuIC5yb3cgLmNvbDMge1xuICAgIHdpZHRoOiBhdXRvO1xuICAgIG1heC13aWR0aDogbm9uZTtcbn1cblxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lciAuZmllbGRzZXQtaGFzLWliYW4gLnJvdyAuY29sMiB7XG4gICAgd2lkdGg6IDI4NnB4O1xufVxuXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyIGlucHV0LmliYW4ge1xuICAgIHRleHQtdHJhbnNmb3JtOiB1cHBlcmNhc2U7XG59XG5cbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgaW5wdXQuaWJhbjo6LXdlYmtpdC1pbnB1dC1wbGFjZWhvbGRlciB7XG4gICAgdGV4dC10cmFuc2Zvcm06IG5vbmU7XG59XG5cbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgaW5wdXQuaWJhbjotbXMtaW5wdXQtcGxhY2Vob2xkZXIge1xuICAgIHRleHQtdHJhbnNmb3JtOiBub25lO1xufVxuXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyIGlucHV0LmliYW46OnBsYWNlaG9sZGVyIHtcbiAgICB0ZXh0LXRyYW5zZm9ybTogbm9uZTtcbn1cbi8qLmZvcm1Db250YWluZXIgU1RZTElORyovXG5cbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgLmZvcm0ge1xuICAgIG1hcmdpbjogMCAwIDEwcHg7XG59XG5cbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgLmZvcm1Db250YWluZXIge1xuICAgIG1hcmdpbjogMCAyMHB4IDAgMjdweDtcbiAgICBwb3NpdGlvbjogcmVsYXRpdmU7XG59XG5cbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgZmllbGRzZXQge1xuICAgIGJvcmRlcjogbm9uZTtcbiAgICBwYWRkaW5nOiAwO1xuICAgIG1hcmdpbjogMDtcbn1cblxuLm1vYmlsZS1vbmx5IHtcbiAgICBkaXNwbGF5OiBub25lO1xufVxuXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyIC5FUlJPUiBpbnB1dCxcbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgLkVSUk9SIHNlbGVjdCB7XG4gICAgYmFja2dyb3VuZDogcmdiYSgxOTQsIDQ1LCA0NSwgMC4xKTtcbiAgICBib3JkZXI6IDFweCBzb2xpZCAjYzIyZDJkO1xufVxuXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyIHNlbGVjdCxcbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgaW5wdXQge1xuICAgIGhlaWdodDogNDRweDtcbiAgICBtYXJnaW46IDAgMTBweCAwIDA7XG4gICAgZm9udC1zaXplOiAxNnB4O1xuICAgIGNvbG9yOiAjNDI0MjQyO1xuICAgIGJhY2tncm91bmQtY29sb3I6ICNmZmY7XG4gICAgYm9yZGVyOiAxcHggc29saWQgIzhmOGY4ZjtcbiAgICBib3JkZXItcmFkaXVzOiA0cHg7XG4gICAgYm94LXNoYWRvdzogaW5zZXQgMCAxcHggMXB4IHJnYmEoMCwgMCwgMCwgMC4wNzUpO1xuICAgIHRyYW5zaXRpb246IGJvcmRlci1jb2xvciBlYXNlLWluLW91dCAwLjE1cywgYm94LXNoYWRvdyBlYXNlLWluLW91dCAwLjE1cztcbiAgICBwYWRkaW5nOiAwIDhweDtcbiAgICBib3gtc2l6aW5nOiBib3JkZXItYm94O1xuICAgIGZvbnQtd2VpZ2h0OiAzMDA7XG4gICAgbWF4LXdpZHRoOiAxMDAlO1xufVxuXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyIHNlbGVjdCxcbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgaW5wdXRbdHlwZT0ndGV4dCddIHtcbiAgICB3aWR0aDogMTAwJTtcbn1cblxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lciBzZWxlY3Q6aW52YWxpZCB7XG4gICAgY29sb3I6ICM5OTk7XG59XG5cbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgc2VsZWN0OmZvY3VzLFxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lciBzZWxlY3Q6YWN0aXZlLFxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lciBpbnB1dDpmb2N1cyxcbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgaW5wdXQ6YWN0aXZlIHtcbiAgICBvdXRsaW5lLXdpZHRoOiAwO1xuICAgIGJvcmRlcjogMnB4IHNvbGlkICMyMDdhMjA7XG4gICAgYm94LXNoYWRvdzogbm9uZTtcbn1cbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgaW5wdXRbdHlwZT0ncmFkaW8nXTpmb2N1cyxcbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgaW5wdXRbdHlwZT0nY2hlY2tib3gnXTpmb2N1c3tcbiAgICBvdXRsaW5lLXdpZHRoOiBpbml0aWFsO1xufVxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lciAuRVJST1Igc2VsZWN0OmZvY3VzLFxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lciAuRVJST1Igc2VsZWN0OmFjdGl2ZSxcbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgLkVSUk9SIGlucHV0OmZvY3VzLFxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lciAuRVJST1IgaW5wdXQ6YWN0aXZlIHtcbiAgICBib3JkZXItY29sb3I6ICNjMjJkMmQ7XG59XG5cbjo6LXdlYmtpdC1pbnB1dC1wbGFjZWhvbGRlciB7XG4gICAgLyogQ2hyb21lL09wZXJhL1NhZmFyaSAqL1xuICAgIGNvbG9yOiByZ2JhKDY2LCA2NiwgNjYsIDAuNCk7XG59XG46Oi1tb3otcGxhY2Vob2xkZXIge1xuICAgIC8qIEZpcmVmb3ggMTkrICovXG4gICAgY29sb3I6IHJnYmEoNjYsIDY2LCA2NiwgMC40KTtcbn1cbjotbXMtaW5wdXQtcGxhY2Vob2xkZXIge1xuICAgIC8qIElFIDEwKyAqL1xuICAgIGNvbG9yOiByZ2JhKDY2LCA2NiwgNjYsIDAuNCk7XG59XG46LW1vei1wbGFjZWhvbGRlciB7XG4gICAgLyogRmlyZWZveCAxOC0gKi9cbiAgICBjb2xvcjogcmdiYSg2NiwgNjYsIDY2LCAwLjQpO1xufVxuXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyIGlucHV0W3R5cGU9J3JhZGlvJ10sXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyIGlucHV0W3R5cGU9J2NoZWNrYm94J10ge1xuICAgIGhlaWdodDogYXV0bztcbiAgICBwYWRkaW5nOiA0cHggNnB4O1xufVxuXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyIC5oaW50SWNvbiB7XG4gICAgZGlzcGxheTogaW5saW5lLWJsb2NrO1xuICAgIHdpZHRoOiAxOHB4O1xuICAgIGhlaWdodDogMThweDtcbiAgICB2ZXJ0aWNhbC1hbGlnbjogbWlkZGxlO1xuICAgIGJhY2tncm91bmQ6IHVybChpbWcvaGludC5wbmcpO1xuICAgIGJhY2tncm91bmQtc2l6ZTogY292ZXI7XG59XG5cbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgLmhpbnQge1xuICAgIGRpc3BsYXk6IG5vbmU7XG59XG5cbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgLmhpbnQgaW1nIHtcbiAgICBmbG9hdDogbGVmdDtcbiAgICBwYWRkaW5nOiAwO1xufVxuXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyIC5oaW50U2hvd24ge1xuICAgIGRpc3BsYXk6IGJsb2NrO1xuICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgICB6LWluZGV4OiAxO1xuICAgIGJveC1zaXppbmc6IGJvcmRlci1ib3g7XG4gICAgYmFja2dyb3VuZC1jb2xvcjogI2ZmZjtcbiAgICBwYWRkaW5nOiA0cHg7XG4gICAgYm9yZGVyLXJhZGl1czogNHB4O1xuICAgIGJveC1zaGFkb3c6IDAgMCA0cHggMCByZ2IoMCAwIDAgLyAxMiUpLCAwIDRweCA0cHggMCByZ2IoMCAwIDAgLyAyNCUpO1xufVxuXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyIC5oaW50U2hvd24gaDMge1xuICAgIG1hcmdpbi10b3A6IDEycHg7XG4gICAgbWFyZ2luLWJvdHRvbTogMTZweDtcbiAgICBmb250LXNpemU6IDE4cHg7XG4gICAgZm9udC13ZWlnaHQ6IDUwMDtcbn1cblxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lciAuc3RhbmRhcmQtZmllbGRzZXQgLmhpbnQuaGludFNob3duIHtcbiAgICB3aWR0aDogMzIwcHg7XG4gICAgbWluLWhlaWdodDogMTkwcHg7XG4gICAgcGFkZGluZzogMCAxMnB4IDEycHg7XG4gICAgdG9wOiAwcHg7XG4gICAgbGVmdDogMzM1cHg7XG59XG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyIC5yZWdpc3RlcmVkLWZpZWxkc2V0IC5oaW50LmhpbnRTaG93bixcbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgLnVwZGF0ZS1maWVsZHNldCAuaGludC5oaW50U2hvd24ge1xuICAgIHRvcDogOHB4O1xuICAgIGxlZnQ6IDMzNXB4O1xufVxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lci5jYXJkLXZpZXcgLnJlZ2lzdGVyZWQtZmllbGRzZXQgLmhpbnQuaGludFNob3duLFxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lci5jYXJkLXZpZXcgLnVwZGF0ZS1maWVsZHNldCAuaGludC5oaW50U2hvd24ge1xuICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgICBib3R0b206IDQ2cHg7XG4gICAgbGVmdDogMDtcbiAgICB3aWR0aDogY2FsYyg1MCUgKyAyNXB4KTtcbiAgICB0b3A6IGF1dG87XG59XG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyIGZpZWxkc2V0LnJlZ2lzdGVyZWQtZmllbGRzZXQgLnJvdyxcbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgZmllbGRzZXQudXBkYXRlLWZpZWxkc2V0IC5yb3cge1xuICAgIHBvc2l0aW9uOiByZWxhdGl2ZTtcbn1cbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIuY2FyZC12aWV3IC5zdGFuZGFyZC1maWVsZHNldCAuaGludC5oaW50U2hvd24ge1xuICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgICBib3R0b206IDQ2cHg7XG4gICAgdG9wOiBhdXRvO1xuICAgIGxlZnQ6IDA7XG4gICAgbWF4LXdpZHRoOiAyODBweDtcbn1cbi8qICBpbnN0YWxsbWVudHMgICovXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyIHNwYW4uaW5zdGFsbG1lbnRJbmxpbmVJbmZvIHtcbiAgICBwYWRkaW5nLXJpZ2h0OiA1cHg7XG4gICAgbGluZS1oZWlnaHQ6IDI5cHg7XG59XG5cbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgLnJvdyAuZGV0YWlscyB7XG4gICAgcG9zaXRpb246IGFic29sdXRlO1xuICAgIG92ZXJmbG93OiBhdXRvO1xuICAgIHotaW5kZXg6IDE7XG4gICAgaGVpZ2h0OiAxNzBweDtcbiAgICBtYXJnaW4tdG9wOiAtNDVweDtcbiAgICBmb250LXNpemU6IDEycHg7XG59XG5cbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgLnJvdyAuZGV0YWlscyBoMyB7XG4gICAgbWFyZ2luOiAwO1xuICAgIGZvbnQtc2l6ZTogMTZweDtcbiAgICBmb250LXdlaWdodDogNTAwO1xufVxuXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyIC5pbnN0YWxsbWVudFBsYW5SYXRlcyB7XG4gICAgb3ZlcmZsb3c6IGF1dG87XG4gICAgd2lkdGg6IDIyNXB4O1xuICAgIGhlaWdodDogMTM1cHg7XG4gICAgZm9udC1zaXplOiAxMnB4O1xuICAgIGxpbmUtaGVpZ2h0OiAyMHB4O1xuICAgIGJvcmRlcjogMXB4IHNvbGlkICNjY2M7XG59XG5cbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgYS5pbmxpbmUtbGluayB7XG4gICAgbWFyZ2luOiAwO1xufVxuXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyIC5pbnN0YWxsbWVudFBsYW5SYXRlcyBwIHtcbiAgICBtYXJnaW4tdG9wOiAwO1xuICAgIG1hcmdpbi1sZWZ0OiA1cHg7XG59XG5cbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgLmxhdGVudE1lc3NhZ2UuSU5GTyB7XG4gICAgZGlzcGxheTogbm9uZTtcbn1cblxuLyogIG9wdGlvbnMgYW5kIGFncmVlbWVudHMsIG90aGVyICAqL1xuXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyIC5zZXBhcmF0b3Ige1xuICAgIGhlaWdodDogMTBweDtcbiAgICBib3JkZXItYm90dG9tOiAycHggZG90dGVkICNiNmI2YjY7XG4gICAgbWFyZ2luLWJvdHRvbTogMThweDtcbn1cblxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lciAub3B0aW9ucyB7XG4gICAgZGlzcGxheTogZmxleDtcbiAgICBtYXJnaW4tYm90dG9tOiAyMHB4O1xufVxuXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyIC5vcHRpb25zIGlucHV0LFxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lci5jYXJkLXZpZXcgLm9wdGlvbnMgaW5wdXQge1xuICAgIG1hcmdpbjogNXB4IDhweCAycHggMDtcbiAgICB2ZXJ0aWNhbC1hbGlnbjogYm90dG9tO1xuICAgIGFsaWduLXNlbGY6IGZsZXgtc3RhcnQ7XG4gICAgLyogbmVlZGVkIHRvIHJlcGxhY2UgZmxleCAxIHVzZWQgZm9yIGlucHV0IHRleHQqL1xuICAgIGZsZXg6IG5vbmU7XG59XG5cbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIuY2FyZC12aWV3IC5vcHRpb25zIGlucHV0IHtcbiAgICBtYXJnaW4tcmlnaHQ6IDVweDtcbn1cblxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lciAucmVnaXN0cmF0aW9uIGlucHV0IHtcbiAgICBmbG9hdDogbGVmdDtcbn1cblxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lciAucmVnaXN0cmF0aW9uIGxhYmVsIHtcbiAgICBkaXNwbGF5OiBibG9jaztcbiAgICBtYXJnaW4tYm90dG9tOiA1cHg7XG4gICAgbGluZS1oZWlnaHQ6IDI0cHg7XG59XG5cbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgLmluZm9ybWF0aW9uIHtcbiAgICBtYXJnaW4tYm90dG9tOiAxMHB4O1xufVxuXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyIC5saW5rLWdyb3VwIHtcbiAgICBtYXJnaW46IDEwcHggMDtcbn1cblxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lciAubGluay1ncm91cCBhIHtcbiAgICBtYXJnaW4tbGVmdDogMDtcbn1cblxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lciAuRVJST1Ige1xuICAgIGNvbG9yOiAjYzIyZDJkO1xufVxuXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyIC5JTkZPIHtcbiAgICBjb2xvcjogIzI5OTkyODtcbn1cblxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lciA+IGRpdi5saXN0LkdMT0JBTF9FUlJPUixcbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIuY2FyZC12aWV3ID4gZGl2Lmxpc3QuR0xPQkFMX0VSUk9SLFxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lciA+IGRpdi5saXN0LkdMT0JBTF9JTkZPLFxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lci5jYXJkLXZpZXcgPiBkaXYubGlzdC5HTE9CQUxfSU5GTyB7XG4gICAgY29sb3I6ICM0MjQyNDI7XG4gICAgYmFja2dyb3VuZDogI2ZkZWNlYTtcbiAgICBib3JkZXI6IDA7XG4gICAgYm9yZGVyLXJhZGl1czogNHB4O1xuICAgIGZvbnQtc2l6ZTogMTRweDtcbiAgICBjb2xvcjogIzQyNDI0MjtcbiAgICB0ZXh0LWFsaWduOiBsZWZ0O1xuICAgIGRpc3BsYXk6IGJsb2NrO1xuICAgIG1heC13aWR0aDogNjU5cHg7XG4gICAgd2lkdGg6IDEwMCU7XG4gICAgcGFkZGluZzogMTZweCAxNnB4IDE2cHggNTJweDtcbiAgICBib3gtc2l6aW5nOiBib3JkZXItYm94O1xuICAgIHBvc2l0aW9uOiByZWxhdGl2ZTtcbiAgICBib3gtc2hhZG93OiBub25lO1xuICAgIG1hcmdpbi10b3A6IDhweDtcbn1cblxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lci5jYXJkLXZpZXcgPiBkaXYubGlzdC5HTE9CQUxfRVJST1Ige1xuICAgIG1heC13aWR0aDogbm9uZTtcbn1cbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgPiBkaXYubGlzdC5HTE9CQUxfRVJST1IgLmdsb2JhbC1lcnJvci10aXRsZSxcbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIuY2FyZC12aWV3ID4gZGl2Lmxpc3QuR0xPQkFMX0VSUk9SIC5nbG9iYWwtZXJyb3ItdGl0bGUsXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyID4gZGl2Lmxpc3QuR0xPQkFMX0lORk8gLmdsb2JhbC1pbmZvLXRpdGxlLFxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lci5jYXJkLXZpZXcgPiBkaXYubGlzdC5HTE9CQUxfSU5GTyAuZ2xvYmFsLWluZm8tdGl0bGUge1xuICAgIGZvbnQtc2l6ZTogMTZweDtcbiAgICBmb250LXdlaWdodDogNTAwO1xufVxuXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyIC5HTE9CQUxfRVJST1I6OmJlZm9yZSxcbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIuY2FyZC12aWV3IC5HTE9CQUxfRVJST1I6OmJlZm9yZSB7XG4gICAgY29udGVudDogJyc7XG4gICAgcG9zaXRpb246IGFic29sdXRlO1xuICAgIGxlZnQ6IDE2cHg7XG4gICAgYmFja2dyb3VuZC1pbWFnZTogdXJsKGltZy9pYy1lcnJvckAyeC5wbmcpO1xuICAgIGJhY2tncm91bmQtc2l6ZTogMjBweCAyMHB4O1xuICAgIHdpZHRoOiAyMHB4O1xuICAgIGhlaWdodDogMjBweDtcbn1cblxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lciAuR0xPQkFMX01FU1NBR0UsXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyLmNhcmQtdmlldyAuR0xPQkFMX01FU1NBR0Uge1xuICAgIGNvbG9yOiAjMjk5OTI4O1xufVxuXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyIC5HTE9CQUxfSU5GTzo6YmVmb3JlLFxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lci5jYXJkLXZpZXcgLkdMT0JBTF9JTkZPOjpiZWZvcmUge1xuICAgIGNvbnRlbnQ6ICcnO1xuICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgICBsZWZ0OiAxNnB4O1xuICAgIGJhY2tncm91bmQtaW1hZ2U6IHVybChpbWcvaWMtaW5mb0AyeC5wbmcpO1xuICAgIGJhY2tncm91bmQtc2l6ZTogMjBweDtcbiAgICB3aWR0aDogMjBweDtcbiAgICBoZWlnaHQ6IDIwcHg7XG59XG5cbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgPiBkaXYubGlzdC5HTE9CQUxfSU5GTyxcbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIuY2FyZC12aWV3ID4gZGl2Lmxpc3QuR0xPQkFMX0lORk8ge1xuICAgIGJhY2tncm91bmQ6ICNlOGY0ZmQ7XG59XG5cbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgLmZvcm1Db250YWluZXIgaWZyYW1lIHtcbiAgICB3aWR0aDogMTAwJTtcbn1cblxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lciAuc2VwYWRkLXRvYyB7XG4gICAgZGlzcGxheTogbm9uZTtcbn1cblxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lciAuZmxleC1jZW50ZXIge1xuICAgIGRpc3BsYXk6IGZsZXg7XG4gICAganVzdGlmeS1jb250ZW50OiBzcGFjZS1hcm91bmQ7XG59XG5cbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgLmZsZXgtZW5kIHtcbiAgICBkaXNwbGF5OiBmbGV4O1xuICAgIGp1c3RpZnktY29udGVudDogZmxleC1lbmQ7XG59XG5cbi8qICBwb3B1cCBhbmQgYnV0dG9ucyAgKi9cblxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lciAucG9wdXAtZGlhbG9nIHtcbiAgICBkaXNwbGF5OiBub25lO1xuICAgIHBvc2l0aW9uOiBmaXhlZDtcbiAgICB0b3A6IDA7XG4gICAgbGVmdDogMDtcbiAgICB3aWR0aDogMTAwJTtcbiAgICBoZWlnaHQ6IDEwMCU7XG4gICAgYmFja2dyb3VuZDogcmdiYSg2NiwgNjYsIDY2LCAwLjgpO1xuICAgIGZvbnQtc2l6ZTogMTZweDtcbiAgICB6LWluZGV4OiAxMDQ7XG59XG5AbWVkaWEgb25seSBzY3JlZW4gYW5kIChtYXgtd2lkdGg6IDQ5N3B4KSB7XG4gICAgLyogNDY1ICsgMTYgKiAyICovXG4gICAgLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lciAucG9wdXAtZGlhbG9nIHtcbiAgICAgICAgZm9udC1zaXplOiAxNHB4O1xuICAgIH1cbn1cblxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lciAuZGlhbG9nLWluc2lkZSxcbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgLmNvbmZpcm0tYnV0dG9uIHtcbiAgICBmb250LWZhbWlseTogJ1JvYm90bycsICdSb2JvdG9GYWxsYmFjaycsIHNhbnMtc2VyaWY7XG59XG5cbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgLmRpYWxvZy1taWRkbGUge1xuICAgIGRpc3BsYXk6IHRhYmxlLWNlbGw7XG4gICAgdmVydGljYWwtYWxpZ246IG1pZGRsZTtcbn1cblxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lciAuZGlhbG9nLWluc2lkZSB7XG4gICAgZGlzcGxheTogdGFibGU7XG4gICAgcG9zaXRpb246IHJlbGF0aXZlO1xuICAgIG1heC1oZWlnaHQ6IDgwJTtcbiAgICBtYXJnaW46IGF1dG87XG4gICAgcGFkZGluZzogMmVtO1xuICAgIGZvbnQtd2VpZ2h0OiAzMDA7XG4gICAgYmFja2dyb3VuZC1jb2xvcjogI2ZmZjtcbiAgICBjb2xvcjogcmdiYSgwLCAwLCAwLCAwLjc0KTtcbiAgICBib3gtc2hhZG93OiAwIDRweCAzcHggMCByZ2JhKDAsIDAsIDAsIDAuMDYpLCAwIDVweCA5cHggMCByZ2JhKDAsIDAsIDAsIDAuMTIpO1xuICAgIGJvcmRlci1yYWRpdXM6IDRweDtcbiAgICBtYXgtd2lkdGg6IDQ2NXB4O1xuICAgIHdpZHRoOiBjYWxjKDEwMCUgLSAzMnB4KTtcbiAgICBib3gtc2l6aW5nOiBib3JkZXItYm94O1xuICAgIHRleHQtYWxpZ246IGNlbnRlcjtcbn1cblxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lciAuZGlhbG9nLWluc2lkZSBoMiB7XG4gICAgZm9udC1zaXplOiAxLjI1ZW07XG4gICAgdGV4dC1hbGlnbjogbGVmdDtcbiAgICBmb250LXdlaWdodDogNzAwO1xuICAgIHRleHQtYWxpZ246IGNlbnRlcjtcbn1cbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgLmRpYWxvZy1pbnNpZGUgLmNsb3NlLW1vZGFsIHtcbiAgICBwb3NpdGlvbjogYWJzb2x1dGU7XG4gICAgdG9wOiAtMjhweDtcbiAgICByaWdodDogMDtcbiAgICB0ZXh0LWluZGVudDogLTEwMDAwMDAwMDBweDtcbiAgICB3aWR0aDogMTRweDtcbiAgICBoZWlnaHQ6IDE0cHg7XG4gICAgYmFja2dyb3VuZC1pbWFnZTogdXJsKGltZy9pYy14LWRlZmF1bHQtMTRwQDJ4LnBuZyk7XG4gICAgYmFja2dyb3VuZC1zaXplOiAxNHB4IDE0cHg7XG4gICAgYmFja2dyb3VuZC1yZXBlYXQ6IG5vLXJlcGVhdDtcbiAgICBib3JkZXItcmFkaXVzOiAwO1xuICAgIGJhY2tncm91bmQtY29sb3I6IHRyYW5zcGFyZW50O1xuICAgIHZlcnRpY2FsLWFsaWduOiBtaWRkbGU7XG4gICAgcGFkZGluZzogMDtcbiAgICBib3JkZXI6IDA7XG4gICAgY3Vyc29yOiBwb2ludGVyO1xufVxuXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyIC5kZWxldGUtYnV0dG9ucy1ncm91cCxcbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgLmJ1dHRvbnMtZ3JvdXAge1xuICAgIG1hcmdpbi10b3A6IDMuMTI1ZW07XG59XG5cbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgLnN1Ym1pdC1kZWxldGUtYnV0dG9uLFxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lciAuc3VibWl0LWJ1dHRvbiB7XG4gICAgcGFkZGluZzogOHB4IDM2cHg7XG4gICAgZm9udC13ZWlnaHQ6IDUwMDtcbiAgICBib3JkZXI6IDA7XG4gICAgY3Vyc29yOiBwb2ludGVyO1xuICAgIGZvbnQtc2l6ZTogMWVtO1xuICAgIGJvcmRlci1yYWRpdXM6IDRweDtcbiAgICBoZWlnaHQ6IDQ0cHg7XG4gICAgYmFja2dyb3VuZDogIzQyNDI0MjtcbiAgICBjb2xvcjogI2ZmZmZmZjtcbiAgICBkaXNwbGF5OiBibG9jaztcbiAgICBtYXJnaW46IDAgYXV0byAxOHB4IGF1dG87XG4gICAgYm9yZGVyLXJhZGl1czogNHB4O1xufVxuXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyIC5zdWJtaXQtZGVsZXRlLWJ1dHRvbjpob3Zlcixcbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgLnN1Ym1pdC1idXR0b246aG92ZXIge1xuICAgIGJhY2tncm91bmQ6ICMwMDAwMDA7XG59XG5cbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgLmNhbmNlbC1kZWxldGUge1xuICAgIHBhZGRpbmc6IDhweCAyMXB4O1xuICAgIGZvbnQtd2VpZ2h0OiBub3JtYWw7XG4gICAgYm9yZGVyOiAwO1xuICAgIGN1cnNvcjogcG9pbnRlcjtcbiAgICBmb250LXNpemU6IDFlbTtcbiAgICBoZWlnaHQ6IDQ0cHg7XG4gICAgYmFja2dyb3VuZDogdHJhbnNwYXJlbnQ7XG4gICAgY29sb3I6IHJnYmEoMCwgMCwgMCwgMC43Mik7XG4gICAgdGV4dC1kZWNvcmF0aW9uOiB1bmRlcmxpbmU7XG59XG5cbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgLmNhbmNlbC1kZWxldGU6aG92ZXIge1xuICAgIGNvbG9yOiAjMDAwMDAwO1xufVxuXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyIC5kZWxldGUtYWNjb3VudC5oYXMtZGF0ZSB7XG4gICAgbWFyZ2luLXRvcDogLTIycHg7XG59XG5cbi5zdWJtaXQtYnV0dG9uIHtcbiAgICBwYWRkaW5nOiA4cHggMjVweDtcbiAgICBmb250LXdlaWdodDogNTAwO1xuICAgIGNvbG9yOiAjZmZmO1xuICAgIGJvcmRlcjogMDtcbiAgICBib3JkZXItcmFkaXVzOiA1cHg7XG4gICAgY3Vyc29yOiBwb2ludGVyO1xuICAgIGZvbnQtc2l6ZTogMTRweDtcbiAgICBtYXJnaW46IDIwcHggMDtcbiAgICBiYWNrZ3JvdW5kLWNvbG9yOiAjNTRiOTRkO1xuICAgIGJhY2tncm91bmQ6ICM1NGI5NGQ7XG4gICAgYmFja2dyb3VuZC1pbWFnZTogbGluZWFyLWdyYWRpZW50KCM1NGI5NGQsICMyOTk5MjgpO1xufVxuLnN1Ym1pdC1idXR0b24uZGlzYWJsZWQsXG4uc3VibWl0LWJ1dHRvbltkaXNhYmxlZF0ge1xuICAgIGN1cnNvcjogbm90LWFsbG93ZWQ7XG4gICAgb3BhY2l0eTogMC42NTtcbiAgICBmaWx0ZXI6IGFscGhhKG9wYWNpdHk9NjUpO1xuICAgIC13ZWJraXQtYm94LXNoYWRvdzogbm9uZTtcbiAgICBib3gtc2hhZG93OiBub25lO1xufVxuXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyIC5kZWxldGUtYWNjb3VudCB7XG4gICAgcG9zaXRpb246IHJlbGF0aXZlO1xuICAgIG1hcmdpbi1sZWZ0OiA0MHB4O1xuICAgIHRleHQtaW5kZW50OiAtMTAwMDAwMDAwMHB4O1xuICAgIHdpZHRoOiAxMHB4O1xuICAgIGhlaWdodDogMTNweDtcbiAgICBiYWNrZ3JvdW5kLWltYWdlOiB1cmwoaW1nL2ljLWRlbGV0ZS1kZWZhdWx0LTEwcEAyeC5wbmcpO1xuICAgIGJhY2tncm91bmQtc2l6ZTogMTBweCAxM3B4O1xuICAgIGJhY2tncm91bmQtcmVwZWF0OiBuby1yZXBlYXQ7XG4gICAgYm9yZGVyLXJhZGl1czogMDtcbiAgICBiYWNrZ3JvdW5kLWNvbG9yOiB0cmFuc3BhcmVudDtcbiAgICB2ZXJ0aWNhbC1hbGlnbjogbWlkZGxlO1xuICAgIHBhZGRpbmc6IDA7XG4gICAgYm9yZGVyOiAwO1xuICAgIGN1cnNvcjogcG9pbnRlcjtcbiAgICBkaXNwbGF5OiBub25lO1xufVxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lciAuZGVsZXRlLWFjY291bnQ6aG92ZXIge1xuICAgIGJhY2tncm91bmQtaW1hZ2U6IHVybChpbWcvaWMtZGVsZXRlLWhvdmVyLTEwcEAyeC5wbmcpO1xufVxuLmRlbGV0ZS10b29sdGlwIHtcbiAgICBkaXNwbGF5OiBub25lO1xuICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgICBiYWNrZ3JvdW5kOiAjZmZmZmZmO1xuICAgIGJveC1zaGFkb3c6IDAgMCA0cHggMCByZ2JhKDAsIDAsIDAsIDAuMTIpLCAwIDRweCA0cHggMCByZ2JhKDAsIDAsIDAsIDAuMjQpO1xuICAgIGJvcmRlci1yYWRpdXM6IDRweDtcbiAgICBmb250LXNpemU6IDEycHg7XG4gICAgZm9udC13ZWlnaHQ6IDMwMDtcbiAgICBjb2xvcjogIzQyNDI0MjtcbiAgICB0ZXh0LWFsaWduOiBjZW50ZXI7XG4gICAgaGVpZ2h0OiAyM3B4O1xuICAgIGxpbmUtaGVpZ2h0OiAyM3B4O1xuICAgIHBhZGRpbmc6IDAgMTJweDtcbiAgICBtYXJnaW4tYm90dG9tOiA0cHg7XG4gICAgYm90dG9tOiAxMDAlO1xuICAgIHRleHQtaW5kZW50OiBpbml0aWFsO1xuICAgIHdpZHRoOiBtYXgtY29udGVudDtcbiAgICB0b3A6IC00cHg7XG4gICAgbGVmdDogMTVweDtcbn1cbi5kZWxldGUtYWNjb3VudDpob3ZlciAuZGVsZXRlLXRvb2x0aXAge1xuICAgIGRpc3BsYXk6IGJsb2NrO1xufVxuXG4ucGF5bWVudC1pbi1wcm9ncmVzcyB7XG4gICAgcG9zaXRpb246IGZpeGVkO1xuICAgIGxlZnQ6IDA7XG4gICAgdG9wOiAwO1xuICAgIHdpZHRoOiAxMDAlO1xuICAgIGhlaWdodDogMTAwJTtcbiAgICBtYXJnaW46IDA7XG59XG5cbi5vcC1wYXltZW50LXdpZGdldC1mdWxsLWxvYWRpbmcge1xuICAgIHBvc2l0aW9uOiBmaXhlZDtcbiAgICBsZWZ0OiAwO1xuICAgIHRvcDogMDtcbiAgICB3aWR0aDogMTAwJTtcbiAgICBoZWlnaHQ6IDEwMCU7XG4gICAgei1pbmRleDogMTUwO1xuICAgIGJhY2tncm91bmQ6ICNmZmY7XG59XG5cbiNwYXltZW50TG9hZGluZ0ljb24ge1xuICAgIHBvc2l0aW9uOiBmaXhlZDtcbiAgICBsZWZ0OiAwO1xuICAgIHRvcDogMDtcbiAgICB3aWR0aDogMTAwJTtcbiAgICBoZWlnaHQ6IDEwMCU7XG4gICAgei1pbmRleDogMTUwO1xuICAgIGJhY2tncm91bmQ6IHJnYmEoMjU1LCAyNTUsIDI1NSwgMC44KTtcbn1cblxuLm9wLXBheW1lbnQtd2lkZ2V0LWZ1bGwtbG9hZGluZy5sb2FkaW5nSW5Db250YWluZXIsXG4jcGF5bWVudExvYWRpbmdJY29uLmxvYWRpbmdJbkNvbnRhaW5lciB7XG4gICAgcG9zaXRpb246IGFic29sdXRlO1xufVxuXG4ubG9hZGluZy1jb250YWluZXIge1xuICAgIHdpZHRoOiA2NHB4O1xuICAgIGhlaWdodDogNjRweDtcbiAgICBwb3NpdGlvbjogYWJzb2x1dGU7XG4gICAgdG9wOiA1MCU7XG4gICAgbGVmdDogNTAlO1xuICAgIG1hcmdpbi1sZWZ0OiAtMzJweDtcbiAgICBtYXJnaW4tdG9wOiAtMzJweDtcbn1cblxuLm9wLXBheW1lbnQtcmVkaXJlY3QtaW5mby1tb2RhbCB7XG4gICAgcG9zaXRpb246IGZpeGVkO1xuICAgIGJhY2tncm91bmQ6IHJnYmEoMCwwLDAsMC42KTtcbiAgICBsZWZ0OiAwO1xuICAgIHJpZ2h0OiAwO1xuICAgIHRvcDogMDtcbiAgICBib3R0b206IDA7XG4gICAgbWF4LXdpZHRoOiAxMDAlO1xuICAgIG1hcmdpbjogMCBhdXRvO1xuICAgIHBhZGRpbmc6IDEycHg7XG4gICAgei1pbmRleDogOTtcbiAgICBmb250LWZhbWlseTogJ1JvYm90bycsICdSb2JvdG9GYWxsYmFjaycsIEFyaWFsLCBzYW5zLXNlcmlmO1xufVxuXG4ub3AtcGF5bWVudC1yZWRpcmVjdC1pbmZvLW1vZGFsIC5wb3B1cC1tb2RhbCB7XG4gICAgcG9zaXRpb246IGFic29sdXRlO1xuICAgIGJhY2tncm91bmQ6ICNlZWU7XG4gICAgbGVmdDogMDtcbiAgICByaWdodDogMDtcbiAgICBtYXgtd2lkdGg6IDQwMHB4O1xuICAgIG1hcmdpbjogMCBhdXRvO1xuICAgIHBhZGRpbmc6IDEycHg7XG4gICAgdG9wOiA1MCU7XG4gICAgdHJhbnNmb3JtOiB0cmFuc2xhdGVZKC01MCUpO1xuICAgIGJvcmRlci1yYWRpdXM6IDVweDtcbn1cblxuLm9wLXBheW1lbnQtcmVkaXJlY3QtaW5mby1tb2RhbCAucG9wdXAtbW9kYWwgLmZvb3RlciB7XG4gICAgbWFyZ2luOiAxMnB4IDVweCA1cHggNXB4O1xufVxuXG4ub3AtcGF5bWVudC1yZWRpcmVjdC1pbmZvLW1vZGFsIC5wb3B1cC1tb2RhbCBidXR0b24ge1xuICAgIG1hcmdpbjogMCA1cHggMCAwO1xufVxuXG4ub3AtcGF5bWVudC1yZWRpcmVjdC1pbmZvLW1vZGFsIC5wb3B1cC1tb2RhbCAubW9kYWwtaGVhZGVyIGgzIHtcbiAgICBtYXJnaW46IDhweDtcbn1cblxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lciAuZXhwaXJ5TW9udGgsXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyIC5leHBpcnlZZWFyLFxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lciBpbnB1dC52ZXJpZmljYXRpb25Db2RlIHtcbiAgICB3aWR0aDogY2FsYyg1MCUgLSA1cHgpO1xufVxuXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyIC5leHBpcnlNb250aCxcbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIuY2FyZC12aWV3IC5leHBpcnlNb250aCB7XG4gICAgbWFyZ2luLXJpZ2h0OiAxMHB4O1xufVxuXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyIC5leHBpcnlZZWFyIHtcbiAgICBtYXJnaW4tcmlnaHQ6IDA7XG4gICAgbWFyZ2luLWxlZnQ6IDA7XG59XG5cbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgLnJvdyAuY29sMi5iaXJ0aGRhdGUge1xuICAgIHdpZHRoOiBhdXRvO1xufVxuXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyIC5oZWFkZXItdGl0bGUge1xuICAgIGRpc3BsYXk6IGJsb2NrO1xuICAgIG1hcmdpbi1ib3R0b206IDRweDtcbn1cblxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lciAuaGVhZGVyLWRlc2NyaXB0aW9uIHtcbiAgICBkaXNwbGF5OiBibG9jaztcbiAgICBjb2xvcjogIzZiNmI2YjtcbiAgICBmb250LXdlaWdodDogMzAwO1xuICAgIGZvbnQtc2l6ZTogMTJweDtcbn1cblxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lciBhLnZlcmlmaWNhdGlvbi1jb2RlLWxpbmsge1xuICAgIGZvbnQtc2l6ZTogMTJweDtcbiAgICBkaXNwbGF5OiBpbmxpbmUtYmxvY2s7XG4gICAgdGV4dC1kZWNvcmF0aW9uOiBub25lO1xuICAgIG1heC13aWR0aDogODVweDtcbiAgICB2ZXJ0aWNhbC1hbGlnbjogbWlkZGxlO1xuICAgIGZvbnQtd2VpZ2h0OiA1MDA7XG4gICAgY29sb3I6ICMyOTk5Mjg7XG59XG5cbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgLnJvdyAuY29sMi5iaXJ0aGRhdGUgc2VsZWN0IHtcbiAgICB3aWR0aDogYXV0bztcbn1cblxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lciAubWVzc2FnZSB7XG4gICAgZm9udC1zaXplOiAxMnB4O1xufVxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lciAucmVnaXN0ZXJlZC1maWVsZHNldCAucm93IC5jb2wyIHtcbiAgICB3aWR0aDogMTMwcHg7XG4gICAgbWFyZ2luLXJpZ2h0OiAwO1xufVxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lciAucmVnaXN0ZXJlZC1maWVsZHNldCBpbnB1dC52ZXJpZmljYXRpb25Db2RlIHtcbiAgICB3aWR0aDogY2FsYygxMDAlIC0gMzJweCk7XG59XG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyIC5nZW5lcmljLWNhcmQtaW1nIHtcbiAgICBiYWNrZ3JvdW5kLWltYWdlOiB1cmwoaW1nL2ljLWNhcmRzLnBuZyk7XG59XG4vKiBBbnkgZGV2aWNlIGluY2x1ZGluZyBkZXNrdG9wIGJ1dCB3aXRoIHNtYWxsIHdpZHRoIChyZXNpemVkKSAqL1xuXG5AbWVkaWEgb25seSBzY3JlZW4gYW5kIChtYXgtd2lkdGg6IDcwMHB4KSB7XG4gICAgLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lciAuc3RhbmRhcmQtZmllbGRzZXQgLnJvdy5yb3czIHtcbiAgICAgICAgcG9zaXRpb246IHJlbGF0aXZlO1xuICAgIH1cbiAgICAub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyIC5zdGFuZGFyZC1maWVsZHNldCAuaGludC5oaW50U2hvd24ge1xuICAgICAgICB0b3A6IDM2cHg7XG4gICAgICAgIGxlZnQ6IDNweDtcbiAgICB9XG59XG5cbkBtZWRpYSBvbmx5IHNjcmVlbiBhbmQgKG1heC13aWR0aDogNjc0cHgpIHtcbiAgICAub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyIC5yb3cgLmNvbDMge1xuICAgICAgICBkaXNwbGF5OiBibG9jaztcbiAgICAgICAgbWFyZ2luLWxlZnQ6IDIwNnB4O1xuICAgICAgICBtYXJnaW4tYm90dG9tOiAwO1xuICAgICAgICBtYXJnaW4tdG9wOiAwO1xuICAgICAgICB3aWR0aDogYXV0bztcbiAgICAgICAgbWF4LXdpZHRoOiB1bnNldDtcbiAgICB9XG5cbiAgICAub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyIC5maWVsZHNldC1oYXMtaWJhbiAucm93IC5jb2wzIHtcbiAgICAgICAgd2lkdGg6IGF1dG87XG4gICAgICAgIG1heC13aWR0aDogdW5zZXQ7XG4gICAgfVxuXG4gICAgLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lciAuSU5GTyxcbiAgICAub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyIC5FUlJPUiB7XG4gICAgICAgIG1hcmdpbi1ib3R0b206IDhweDtcbiAgICAgICAgZGlzcGxheTogYmxvY2s7XG4gICAgfVxufVxuXG4vKiBBbnkgZGV2aWNlIGluY2x1ZGluZyBkZXNrdG9wIGJ1dCB3aXRoIHNtYWxsIHdpZHRoIChyZXNpemVkKSAqL1xuXG5AbWVkaWEgb25seSBzY3JlZW4gYW5kIChtYXgtd2lkdGg6IDU1NXB4KSB7XG4gICAgLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lciAuZmllbGRzZXQtaGFzLWliYW4gLnJvdyAuY29sMSxcbiAgICAub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyIC5maWVsZHNldC1oYXMtaWJhbiAucm93IC5jb2wyLFxuICAgIC5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgLmZpZWxkc2V0LWhhcy1pYmFuIC5yb3cgLmNvbDMge1xuICAgICAgICBkaXNwbGF5OiBibG9jaztcbiAgICB9XG4gICAgLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lciAuZmllbGRzZXQtaGFzLWliYW4gLnJvdyAuY29sMSB7XG4gICAgICAgIHdpZHRoOiBhdXRvO1xuICAgIH1cbiAgICAub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyIC5maWVsZHNldC1oYXMtaWJhbiAucm93IC5jb2wzIHtcbiAgICAgICAgbWFyZ2luLWxlZnQ6IDA7XG4gICAgICAgIG1hcmdpbi1ib3R0b206IDE2cHg7XG4gICAgICAgIHdpZHRoOiBhdXRvO1xuICAgIH1cbiAgICAub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyIC5yb3cgLmNvbDEsXG4gICAgLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lciAucm93IC5jb2wyLFxuICAgIC5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgLnJvdyAuY29sMyB7XG4gICAgICAgIGRpc3BsYXk6IGJsb2NrO1xuICAgIH1cbiAgICAub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyIC5yb3cgLmNvbDEge1xuICAgICAgICB3aWR0aDogYXV0bztcbiAgICB9XG4gICAgLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lciAucm93IC5jb2wzIHtcbiAgICAgICAgbWFyZ2luLWxlZnQ6IDA7XG4gICAgICAgIG1hcmdpbi1ib3R0b206IDE2cHg7XG4gICAgICAgIHdpZHRoOiBhdXRvO1xuICAgIH1cbiAgICAub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyIC5JTkZPLFxuICAgIC5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgLkVSUk9SIHtcbiAgICAgICAgbWFyZ2luLWJvdHRvbTogMDtcbiAgICAgICAgZGlzcGxheTogYmxvY2s7XG4gICAgfVxuICAgIC5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgLmhlYWRlci10aXRsZSB7XG4gICAgICAgIGRpc3BsYXk6IGJsb2NrO1xuICAgICAgICBtYXJnaW4tYm90dG9tOiA0cHg7XG4gICAgfVxuICAgIC5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgLmhlYWRlci1kZXNjcmlwdGlvbiB7XG4gICAgICAgIGRpc3BsYXk6IGJsb2NrO1xuICAgICAgICBjb2xvcjogIzZiNmI2YjtcbiAgICAgICAgZm9udC13ZWlnaHQ6IDMwMDtcbiAgICAgICAgZm9udC1zaXplOiAxMnB4O1xuICAgIH1cbiAgICAuZGVsZXRlLXRvb2x0aXAge1xuICAgICAgICBsZWZ0OiBhdXRvO1xuICAgICAgICByaWdodDogMDtcbiAgICAgICAgdG9wOiAxN3B4O1xuICAgIH1cbiAgICAub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyIC5zdGFuZGFyZC1maWVsZHNldCAuaGludC5oaW50U2hvd24ge1xuICAgICAgICB0b3A6IDU1cHg7XG4gICAgICAgIGxlZnQ6IDA7XG4gICAgfVxuICAgIC5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgLnJlZ2lzdGVyZWQtZmllbGRzZXQgLmhpbnQuaGludFNob3duLFxuICAgIC5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgLnVwZGF0ZS1maWVsZHNldCAuaGludC5oaW50U2hvd24ge1xuICAgICAgICB0b3A6IDI3cHg7XG4gICAgICAgIGxlZnQ6IDEyMnB4O1xuICAgIH1cbn1cbi8qIFNtYXJ0cGhvbmVzIC0tLS0tLS0tLS0tICovXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyLmNhcmQtdmlldyB7XG4gICAgbWF4LXdpZHRoOiA1MDBweDtcbiAgICBtYXJnaW46IGF1dG87XG4gICAgb3ZlcmZsb3c6IGhpZGRlbjtcbn1cblxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lci5jYXJkLXZpZXcgLnJvdyAuY29sMyB7XG4gICAgbWFyZ2luLWJvdHRvbTogNHB4O1xufVxuXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyLmNhcmQtdmlldyAuSU5GTyxcbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIuY2FyZC12aWV3IC5FUlJPUiB7XG4gICAgbWFyZ2luLWJvdHRvbTogMDtcbn1cblxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lci5jYXJkLXZpZXcgPiBkaXYubGlzdCAqIHtcbiAgICB6LWluZGV4OiAyO1xufVxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lci5jYXJkLXZpZXcgPiBkaXYubGlzdCBzZWxlY3QsXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyLmNhcmQtdmlldyA+IGRpdi5saXN0IGxhYmVsIHtcbiAgICBwb3NpdGlvbjogcmVsYXRpdmU7XG59XG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyLmNhcmQtdmlldyA+IGRpdi5saXN0IGxhYmVsLmNsaWNrYWJsZV9jb250IHtcbiAgICBwb3NpdGlvbjogYWJzb2x1dGU7XG4gICAgYmFja2dyb3VuZDogdHJhbnNwYXJlbnQ7XG4gICAgdG9wOiAwO1xuICAgIGJvdHRvbTogMDtcbiAgICBsZWZ0OiAwO1xuICAgIHJpZ2h0OiAwO1xuICAgIHBhZGRpbmc6IDA7XG4gICAgbWFyZ2luOiAwO1xuICAgIHotaW5kZXg6IDA7XG4gICAgZGlzcGxheTogYmxvY2s7XG59XG5cbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIuY2FyZC12aWV3IC5yb3cgZGl2IHtcbiAgICBtYXJnaW46IDRweCAwO1xufVxuXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyLmNhcmQtdmlldyAuZm9ybSBsYWJlbCB7XG4gICAgbWFyZ2luLXRvcDogOXB4O1xufVxuXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyLmNhcmQtdmlldyBkaXYubGlzdC5yZWdpc3RlcmVkLWFjY291bnQtdXBkYXRlIHtcbiAgICBwYWRkaW5nLXJpZ2h0OiA1MHB4O1xufVxuXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyLmNhcmQtdmlldyBpbnB1dFt0eXBlPSdyYWRpbyddIHtcbiAgICBkaXNwbGF5OiBub25lO1xufVxuXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyLmNhcmQtdmlldyAuZGVsZXRlLWFjY291bnQtc3VibWl0Lm1vYmlsZS1vbmx5IHtcbiAgICB3aWR0aDogMzYlO1xuICAgIGhlaWdodDogMTAwJTtcbiAgICB0b3A6IDA7XG4gICAgcmlnaHQ6IDA7XG4gICAgYmFja2dyb3VuZDogI2MyMmQyZDtcbiAgICBwb3NpdGlvbjogYWJzb2x1dGU7XG4gICAgZGlzcGxheTogZmxleDtcbiAgICBhbGlnbi1pdGVtczogY2VudGVyO1xuICAgIGp1c3RpZnktY29udGVudDogY2VudGVyO1xuICAgIGNvbG9yOiAjZmZmZmZmO1xuICAgIGRpc3BsYXk6IG5vbmU7XG4gICAgZm9udC1zaXplOiAxNHB4O1xuICAgIGZvbnQtd2VpZ2h0OiA1MDA7XG4gICAgdGV4dC10cmFuc2Zvcm06IGNhcGl0YWxpemU7XG4gICAgYm9yZGVyOiAwO1xufVxuXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyLmNhcmQtdmlldyA+IGRpdi5saXN0IHtcbiAgICBwb3NpdGlvbjogcmVsYXRpdmU7XG4gICAgbWFyZ2luOiAxMHB4IDA7XG4gICAgcGFkZGluZzogNXB4IDEwcHg7XG4gICAgYmFja2dyb3VuZC1jb2xvcjogI2ZmZjtcbiAgICBib3JkZXI6IDFweCBzb2xpZCAjZTBlMGUwO1xuICAgIGJvcmRlci1yYWRpdXM6IDVweDtcbn1cbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIuY2FyZC12aWV3ID4gZGl2Lmxpc3Qubm8tc3R5bGUge1xuICAgIHBhZGRpbmc6IDA7XG4gICAgYmFja2dyb3VuZC1jb2xvcjogdHJhbnNwYXJlbnQ7XG4gICAgYm9yZGVyOiBub25lO1xuICAgIGJveC1zaGFkb3c6IG5vbmU7XG59XG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyLmNhcmQtdmlldyA+IGRpdi5saXN0ID4gbGFiZWw6bnRoLWNoaWxkKDMpIHtcbiAgICBwYWRkaW5nOiAxMHB4IDA7XG59XG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyLmNhcmQtdmlldyAuaW1nTGFiZWwge1xuICAgIHdpZHRoOiA1MnB4O1xuICAgIHBhZGRpbmc6IDEwcHggMDtcbn1cbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIuY2FyZC12aWV3IC50ZXh0TGFiZWwge1xuICAgIHdpZHRoOiA0NiU7XG59XG5cbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIuY2FyZC12aWV3IC5leHBpcmVkLXRvb2x0aXAge1xuICAgIG1heC13aWR0aDogMjMwcHg7XG59XG5cbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIuY2FyZC12aWV3ID4gZGl2Lmxpc3QgPiBzZWxlY3Q6bnRoLWNoaWxkKDMpIHtcbiAgICBtYXJnaW4tYm90dG9tOiAxMHB4O1xuICAgIHdpZHRoOiBjYWxjKDY0JSAtIDE0cHgpO1xufVxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lci5jYXJkLXZpZXcgLnJvdyB7XG4gICAgcG9zaXRpb246IHJlbGF0aXZlO1xufVxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lci5jYXJkLXZpZXcgLnJvdyAuY29sIHtcbiAgICBkaXNwbGF5OiBmbGV4O1xuICAgIGFsaWduLWl0ZW1zOiBjZW50ZXI7XG4gICAgd2lkdGg6IGF1dG87XG59XG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyLmNhcmQtdmlldyAucm93IC5jb2wyIHtcbiAgICBtYXJnaW4tcmlnaHQ6IDA7XG59XG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyLmNhcmQtdmlldyAudGV4dExhYmVsIC50aGUtZGF0ZSB7XG4gICAgZm9udC1zaXplOiAxMnB4O1xufVxuLyouZm9ybUNvbnRhaW5lciBzdHlsaW5nKi9cbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIuY2FyZC12aWV3IC5mb3JtQ29udGFpbmVyIHtcbiAgICBkaXNwbGF5OiBub25lO1xuICAgIG1hcmdpbjogMDtcbiAgICBmbGV4LWdyb3c6IDI7XG59XG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyLmNhcmQtdmlldyBzZWxlY3QsXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyLmNhcmQtdmlldyBpbnB1dCB7XG4gICAgZmxleDogMTtcbiAgICBtYXJnaW46IDA7XG59XG5cbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIuY2FyZC12aWV3IHNlbGVjdDppbnZhbGlkIHtcbiAgICBjb2xvcjogIzk5OTtcbn1cblxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lci5jYXJkLXZpZXcgLnZlcmlmaWNhdGlvbkNvZGUge1xuICAgIGZsZXg6IGluaXRpYWw7XG59XG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyLmNhcmQtdmlldyBzZWxlY3Q6bnRoLWNoaWxkKG4gKyAyKSxcbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIuY2FyZC12aWV3IC5oaW50SWNvbiB7XG4gICAgbWFyZ2luLWxlZnQ6IDEwcHg7XG59XG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyLmNhcmQtdmlldyAucm93IC5oaW50U2hvd24ge1xuICAgIHBvc2l0aW9uOiBzdGF0aWM7XG4gICAgei1pbmRleDogMztcbiAgICBvdmVyZmxvdzogaGlkZGVuO1xuICAgIGJhY2tncm91bmQtY29sb3I6ICNmZmY7XG4gICAgYm94LXNpemluZzogYm9yZGVyLWJveDtcbiAgICBwYWRkaW5nOiA0cHg7XG4gICAgZGlzcGxheTogaW5saW5lLWJsb2NrO1xuICAgIHdpZHRoOiBhdXRvO1xufVxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lci5jYXJkLXZpZXcgLmhpbnRJY29uIHtcbiAgICBmbGV4OiBpbml0aWFsO1xuICAgIGJhY2tncm91bmQtc2l6ZTogMThweCAxOHB4O1xuICAgIGJhY2tncm91bmQtcmVwZWF0OiBuby1yZXBlYXQ7XG59XG4vKiAgaW5zdGFsbG1lbnRzICAqL1xuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lci5jYXJkLXZpZXcgLmluc3RhbGxtZW50cyBpbnB1dFt0eXBlPSd0ZXh0J10sXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyLmNhcmQtdmlldyAuaW5zdGFsbG1lbnRzIC5pbnN0YWxsbWVudFBsYW5JZCB7XG4gICAgbWF4LXdpZHRoOiBub25lO1xufVxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lci5jYXJkLXZpZXcgLnJvdyAuZGV0YWlscyB7XG4gICAgcG9zaXRpb246IHN0YXRpYztcbiAgICBoZWlnaHQ6IGF1dG87XG4gICAgbWFyZ2luLXRvcDogaW5pdGlhbDtcbn1cbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIuY2FyZC12aWV3IC5pbnN0YWxsbWVudFBsYW5SYXRlcyB7XG4gICAgaGVpZ2h0OiBhdXRvO1xufVxuLyogIGRlbGV0ZSBhbmQgZXJyb3JzICAqL1xuXG4uc3VibWl0LWJ1dHRvbi5jYXJkLXZpZXcsXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyLmNhcmQtdmlldyAuY29uZmlybS1idXR0b24sXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyLmNhcmQtdmlldyAuZGVsZXRlLWFjY291bnQge1xuICAgIGZvbnQtc2l6ZTogMTZweDtcbn1cbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIuY2FyZC12aWV3IC5kZWxldGUtYWNjb3VudCB7XG4gICAgdG9wOiAwO1xuICAgIGJvdHRvbTogMDtcbiAgICBoZWlnaHQ6IDEwMCU7XG4gICAgcmlnaHQ6IDA7XG4gICAgd2lkdGg6IDM5cHg7XG4gICAgYmFja2dyb3VuZDogdHJhbnNwYXJlbnQ7XG4gICAgbWFyZ2luOiAwO1xuICAgIHBhZGRpbmc6IDA7XG4gICAgcGxhY2Utc2VsZjogY2VudGVyIGZsZXgtZW5kO1xufVxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lci5jYXJkLXZpZXcgLmRlbGV0ZS1hY2NvdW50OjphZnRlciB7XG4gICAgY29udGVudDogJyc7XG4gICAgcG9zaXRpb246IGFic29sdXRlO1xuICAgIG1hcmdpbjogLTEwcHggMCAwIDA7XG4gICAgcGFkZGluZzogMDtcbiAgICB0b3A6IDUwJTtcbiAgICByaWdodDogMTBweDtcbiAgICBiYWNrZ3JvdW5kLWltYWdlOiB1cmwoaW1nL2ljLWRlbGV0ZS1kZWZhdWx0LTE0cEAyeC5wbmcpO1xuICAgIHdpZHRoOiAxNHB4O1xuICAgIGhlaWdodDogMTlweDtcbiAgICBiYWNrZ3JvdW5kLXNpemU6IDE0cHggMTlweDtcbn1cblxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lci5jYXJkLXZpZXcgLmRlbGV0ZS1hY2NvdW50Lmhhcy1kYXRlIHtcbiAgICBtYXJnaW4tdG9wOiAwO1xufVxuXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyLmNhcmQtdmlldyAuZGVsZXRlLWFjY291bnQ6aG92ZXIge1xuICAgIGJhY2tncm91bmQtaW1hZ2U6IG5vbmU7XG59XG5cbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIuY2FyZC12aWV3IC5kZWxldGUtYWNjb3VudDpob3Zlcjo6YWZ0ZXIge1xuICAgIGJhY2tncm91bmQtaW1hZ2U6IHVybChpbWcvaWMtZGVsZXRlLWhvdmVyLTEwcEAyeC5wbmcpO1xufVxuXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyLmNhcmQtdmlldyAuZGVsZXRlLWFjY291bnQ6aG92ZXIgLmRlbGV0ZS10b29sdGlwIHtcbiAgICBkaXNwbGF5OiBub25lO1xufVxuXG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyLmNhcmQtdmlldyAubWVzc2FnZSB7XG4gICAgZGlzcGxheTogYmxvY2s7XG4gICAgaGVpZ2h0OiAxNXB4O1xuICAgIG1hcmdpbi10b3A6IC00cHg7XG4gICAgbWFyZ2luLWxlZnQ6IDBweDtcbiAgICBmb250LXNpemU6IDEycHg7XG59XG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyLmNhcmQtdmlldyAubWVzc2FnZS5FUlJPUixcbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIuY2FyZC12aWV3IC5tZXNzYWdlLklORk8ge1xuICAgIHBhZGRpbmc6IDAgMTBweDtcbiAgICBjb2xvcjogI2ZmZjtcbiAgICBib3JkZXItcmFkaXVzOiAwIDAgNHB4IDRweDtcbn1cbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIuY2FyZC12aWV3IC5tZXNzYWdlLkVSUk9SIHtcbiAgICBiYWNrZ3JvdW5kLWNvbG9yOiAjZWU1NzRkO1xufVxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lci5jYXJkLXZpZXcgLmNvbDIuRVJST1IgPiBpbnB1dDpmaXJzdC1jaGlsZCxcbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIuY2FyZC12aWV3IC5jb2wyLklORk8gPiBpbnB1dDpmaXJzdC1jaGlsZCxcbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIuY2FyZC12aWV3IC5jb2wyLkVSUk9SID4gc2VsZWN0OmZpcnN0LWNoaWxkLFxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lci5jYXJkLXZpZXcgLmNvbDIuSU5GTyA+IHNlbGVjdDpmaXJzdC1jaGlsZCB7XG4gICAgYm9yZGVyLXJhZGl1czogNHB4IDRweCA0cHggMDtcbn1cbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIuY2FyZC12aWV3IC5tZXNzYWdlLklORk8ge1xuICAgIGJhY2tncm91bmQtY29sb3I6ICMyMDdhMjA7XG59XG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyLmNhcmQtdmlldyAubWVzc2FnZSxcbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgLm1lc3NhZ2Uge1xuICAgIGRpc3BsYXk6IG5vbmU7XG59XG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyLmNhcmQtdmlldyAubWVzc2FnZS5FUlJPUixcbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIgLm1lc3NhZ2UuRVJST1Ige1xuICAgIGRpc3BsYXk6IGJsb2NrO1xufVxuLnN1Ym1pdC1idXR0b24uY2FyZC12aWV3IHtcbiAgICBkaXNwbGF5OiBibG9jaztcbiAgICB3aWR0aDogMTAwJTtcbiAgICBtYXgtd2lkdGg6IDUwMHB4O1xuICAgIGhlaWdodDogNTBweDtcbiAgICBtYXJnaW46IDIwcHggYXV0bztcbiAgICBmb250LXNpemU6IDE4cHg7XG59XG5cbi5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIuY2FyZC12aWV3ID4gZGl2Lmxpc3QuRVJST1Ige1xuICAgIHBvc2l0aW9uOiByZWxhdGl2ZTtcbiAgICBtYXJnaW46IDA7XG4gICAgcGFkZGluZzogMDtcbiAgICBiYWNrZ3JvdW5kLWNvbG9yOiAjZmZmO1xuICAgIGJvcmRlcjogMDtcbiAgICBib3JkZXItcmFkaXVzOiAwO1xuICAgIGJveC1zaGFkb3c6IG5vbmU7XG59XG4ub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyLmNhcmQtdmlldyA+IGRpdi5saXN0LnNlbGVjdGVkIHtcbiAgICBib3gtc2hhZG93OiAwIDJweCA0cHggI2I2YjZiNjtcbn1cblxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lci5jYXJkLXZpZXcgLnRleHRMYWJlbCB7XG4gICAgbWFyZ2luLWxlZnQ6IDIzcHg7XG4gICAgZmxleC1ncm93OiAyO1xuICAgIGFsaWduLXNlbGY6IGNlbnRlcjtcbn1cblxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lci5jYXJkLXZpZXcgLnNlbGVjdC5leHBpcnlZZWFyIHtcbiAgICBtYXJnaW4tbGVmdDogMDtcbn1cblxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lci5jYXJkLXZpZXcgYS52ZXJpZmljYXRpb24tY29kZS1saW5rIHtcbiAgICBtYXJnaW4tbGVmdDogMTBweDtcbiAgICBtYXgtd2lkdGg6IDQ2JTtcbn1cblxuLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lci5jYXJkLXZpZXcgLnJvdyAuY29sMiB7XG4gICAgbWFyZ2luLXJpZ2h0OiAwO1xufVxuXG4jc3VibWl0QnRuQ29udGFpbmVyLmNhcmQtdmlldyB7XG4gICAgbWFyZ2luOiBhdXRvO1xuICAgIG1heC13aWR0aDogNTAwcHg7XG59XG4jc3VibWl0QnRuQ29udGFpbmVyLmNhcmQtdmlldyAuc3VibWl0QnRuQ29udGFpbmVyR3JvdXAge1xuICAgIG1heC13aWR0aDogNTAwcHg7XG4gICAgZGlzcGxheTogZmxleDtcbiAgICBhbGlnbi1pdGVtczogY2VudGVyO1xufVxuI3N1Ym1pdEJ0bkNvbnRhaW5lci5jYXJkLXZpZXcgLnBheXBhbC1idXR0b24sXG4jc3VibWl0QnRuQ29udGFpbmVyLmNhcmQtdmlldyAuYXBwbGUtcGF5LWJ1dHRvbixcbiNzdWJtaXRCdG5Db250YWluZXIuY2FyZC12aWV3IC5nb29nbGVwYXkge1xuICAgIG1hcmdpbjogYXV0bztcbn1cbiNzdWJtaXRCdG5Db250YWluZXIuY2FyZC12aWV3IC56b2lkLW91dGxldCB7XG4gICAgbWFyZ2luOiBhdXRvO1xuICAgIG1heC13aWR0aDogNTAwcHg7XG4gICAgZGlzcGxheTogYmxvY2s7XG59XG5cbi5vcC1wYXltZW50LXdpZGdldC1sYi1vdmVybGF5IHtcbiAgICBoZWlnaHQ6IDEwMCU7XG4gICAgd2lkdGg6IDEwMCU7XG4gICAgcG9zaXRpb246IGZpeGVkO1xuICAgIHotaW5kZXg6IDk5OTk7IC8qIG9wLWRlbW8tc2hvcCBtdWkgaGFzIDExMDAgKi9cbiAgICBsZWZ0OiAwO1xuICAgIHRvcDogMDtcbiAgICBiYWNrZ3JvdW5kLWNvbG9yOiByZ2JhKDAsIDAsIDAsIDAuNSk7XG4gICAgb3ZlcmZsb3cteDogaGlkZGVuO1xuICAgIHRyYW5zaXRpb246IDAuNXM7XG59XG5cbi5vcC1wYXltZW50LXdpZGdldC1sYi1vdmVybGF5LWNvbnRlbnQge1xuICAgIHBvc2l0aW9uOiByZWxhdGl2ZTtcbiAgICB3aWR0aDogMTAwJTtcbiAgICBoZWlnaHQ6IDEwMCU7XG59XG5cbi5vcC1wYXltZW50LXdpZGdldC1sYi1pZnJhbWUge1xuICAgIGhlaWdodDogNjAwcHg7XG4gICAgd2lkdGg6IDUwMHB4O1xuICAgIGJhY2tncm91bmQtY29sb3I6ICNmZmZmZmY7XG4gICAgYm9yZGVyOiBub25lO1xuICAgIGJvcmRlci1yYWRpdXM6IDRweDtcbiAgICBwYWRkaW5nOiAxMHB4O1xuICAgIGJveC1zaXppbmc6IGJvcmRlci1ib3g7XG4gICAgbWFyZ2luOiAwO1xuICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgICB0b3A6IDUwJTtcbiAgICBsZWZ0OiA1MCU7XG4gICAgLW1zLXRyYW5zZm9ybTogdHJhbnNsYXRlKC01MCUsIC01MCUpO1xuICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlKC01MCUsIC01MCUpO1xufVxuXG4ub3AtcGF5bWVudC13aWRnZXQtbGItbW9iaWxlIHtcbiAgICBoZWlnaHQ6IDQwMHB4O1xuICAgIHdpZHRoOiAyNTBweDtcbn1cblxuLm9wLXBheW1lbnQtd2lkZ2V0LWxiLXNtYWxsIHtcbiAgICBoZWlnaHQ6IDQwMHB4O1xuICAgIHdpZHRoOiAzOTBweDtcbn1cblxuLm9wLXBheW1lbnQtd2lkZ2V0LWxiLWxhbmRzY2FwZSB7XG4gICAgaGVpZ2h0OiA0MDBweDtcbiAgICB3aWR0aDogNjAwcHg7XG59XG5cbi5vcC1wYXltZW50LXdpZGdldC1sYi1mdWxsLXBhZ2Uge1xuICAgIGhlaWdodDogMTAwJTtcbiAgICB3aWR0aDogMTAwJTtcbiAgICBib3JkZXItcmFkaXVzOiAwO1xufVxuXG4ub3AtcGF5bWVudC13aWRnZXQtbGItbG9hZGVyQ29udGFpbmVyIHtcbiAgICBoZWlnaHQ6IDEwMHZoO1xuICAgIHdpZHRoOiAxMDAlO1xuICAgIGRpc3BsYXk6IGZsZXg7XG4gICAgcG9zaXRpb246IHJlbGF0aXZlO1xuICAgIGZsZXgtZGlyZWN0aW9uOiBjb2x1bW47XG4gICAgYWxpZ24taXRlbXM6IGNlbnRlcjtcbiAgICBqdXN0aWZ5LWNvbnRlbnQ6IGNlbnRlcjtcbn1cblxuLm9wLXBheW1lbnQtd2lkZ2V0LWxiLWxvZ28ge1xuICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgICBsZWZ0OiAwO1xuICAgIHJpZ2h0OiAwO1xuICAgIG1hcmdpbjogMCBhdXRvO1xuICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlWSgtMjAwJSk7XG59XG5cbi5vcC1wYXltZW50LXdpZGdldC1sYi1sb2FkZXIge1xuICAgIHdpZHRoOiA2NHB4O1xuICAgIGhlaWdodDogNjRweDtcbiAgICBjb2xvcjogIzZiNmI2YjtcbiAgICBtYXJnaW4tYm90dG9tOiAxNnB4O1xuICAgIGRpc3BsYXk6IGlubGluZS1ibG9jaztcbiAgICBsaW5lLWhlaWdodDogMTtcbiAgICBhbmltYXRpb246IG11aS1wcm9ncmVzcy1jaXJjdWxhci1yb3RhdGUgMS40cyBsaW5lYXIgaW5maW5pdGU7XG59XG5cbi5vcC1wYXltZW50LXdpZGdldC1oaWRlIHtcbiAgICBkaXNwbGF5OiBub25lO1xufVxuXG4ub3AtcGF5bWVudC13aWRnZXQtbGItbG9hZGVyLWNpcmNsZSB7XG4gICAgYW5pbWF0aW9uOiBtdWktcHJvZ3Jlc3MtY2lyY3VsYXItZGFzaCAxLjRzIGVhc2UtaW4tb3V0IGluZmluaXRlO1xuICAgIHN0cm9rZS1kYXNoYXJyYXk6IDgwcHgsIDIwMHB4O1xuICAgIHN0cm9rZS1kYXNob2Zmc2V0OiAwcHg7XG4gICAgc3Ryb2tlOiBjdXJyZW50Q29sb3I7XG59XG5cbkAtd2Via2l0LWtleWZyYW1lcyBtdWktcHJvZ3Jlc3MtY2lyY3VsYXItcm90YXRlIHtcbiAgICAxMDAlIHtcbiAgICAgICAgdHJhbnNmb3JtOiByb3RhdGUoMzYwZGVnKTtcbiAgICB9XG59XG5cbkAtd2Via2l0LWtleWZyYW1lcyBtdWktcHJvZ3Jlc3MtY2lyY3VsYXItZGFzaCB7XG4gICAgMCUge1xuICAgICAgICBzdHJva2UtZGFzaGFycmF5OiAxcHgsIDIwMHB4O1xuICAgICAgICBzdHJva2UtZGFzaG9mZnNldDogMHB4O1xuICAgIH1cbiAgICA1MCUge1xuICAgICAgICBzdHJva2UtZGFzaGFycmF5OiAxMDBweCwgMjAwcHg7XG4gICAgICAgIHN0cm9rZS1kYXNob2Zmc2V0OiAtMTVweDtcbiAgICB9XG4gICAgMTAwJSB7XG4gICAgICAgIHN0cm9rZS1kYXNoYXJyYXk6IDEwMHB4LCAyMDBweDtcbiAgICAgICAgc3Ryb2tlLWRhc2hvZmZzZXQ6IC0xMjVweDtcbiAgICB9XG59XG5cbi8qIFNtYXJ0cGhvbmVzIC0tLS0tLS0tLS0tICovXG5cbkBtZWRpYSBvbmx5IHNjcmVlbiBhbmQgKG1heC1kZXZpY2Utd2lkdGg6IDUzMHB4KSB7XG4gICAgLm9wLXBheW1lbnQtd2lkZ2V0LWNvbnRhaW5lci5jYXJkLXZpZXcgLmltZ0xhYmVsIHtcbiAgICAgICAgd2lkdGg6IGF1dG87XG4gICAgICAgIGRpc3BsYXk6IGlubGluZS1ncmlkO1xuICAgICAgICBncmlkLWF1dG8tZmxvdzogY29sdW1uO1xuICAgICAgICBncmlkLXRlbXBsYXRlLXJvd3M6IHJlcGVhdCgzLCBhdXRvKTtcbiAgICAgICAgbWluLXdpZHRoOiA1MnB4O1xuICAgIH1cblxuICAgIC5vcC1wYXltZW50LXdpZGdldC1jb250YWluZXIuY2FyZC12aWV3IC5pbWdMYWJlbEdyaWQge1xuICAgICAgICBnYXA6IDhweDtcbiAgICB9XG5cbiAgICAub3AtcGF5bWVudC13aWRnZXQtY29udGFpbmVyLmNhcmQtdmlldyAuc3VibWl0QnRuQ29udGFpbmVyR3JvdXAge1xuICAgICAgICBtYXgtd2lkdGg6IDEwMCU7XG4gICAgfVxufVxuIl19 */
\ No newline at end of file
.row div{margin:6px 0}.row .col2{width:204px}.row .col3{max-width:calc(100% - 425px);vertical-align:middle}select,input{height:44px;font-size:16px;border:1px solid #8f8f8f;border-radius:4px;padding:0 8px;box-sizing:border-box;font-weight:300;max-width:100%;width:100%}.select:invalid{color:#999}.ERROR input,.ERROR select{background:rgba(194,45,45,0.1);border:1px solid #c22d2d}.ERROR select:focus,.ERROR select:active,.ERROR input:focus,.ERROR input:active{border-color:#c22d2d}.expiryMonth,.expiryYear,input.verificationCode{width:calc(50% - 5px)}.expiryYear{margin-right:0;margin-left:0}.message{font-size:12px}select:focus,select:active,input:focus,input:active{outline-width:0;border:2px solid #207a20}input[type='radio']:focus,input[type='checkbox']:focus{outline-width:initial}input[type='radio'],input[type='checkbox']{height:auto;padding:4px 6px}.row .div{margin:4px 0}a.verification-code-link{font-size:12px;display:inline-block;text-decoration:none;max-width:85px;vertical-align:middle;font-weight:500;color:#299928}.hint.hintShown{box-shadow:0 0 4px 0 rgba(0,0,0,0.12),0 4px 4px 0 rgba(0,0,0,0.24);border-radius:4px;font-weight:300;font-size:12px}.registered-fieldset .hint.hintShown,.update-fieldset .hint.hintShown{margin:0;left:335px;top:15px;bottom:auto;box-sizing:border-box}.registered-fieldset .row .col2{width:130px;margin-right:0}.registered-fieldset input.verificationCode{width:calc(100% - 32px)}.hint.hintShown h3{margin-top:12px;margin-bottom:5px;margin-left:0;font-size:18px;font-weight:500}.update-fieldset .hint.hintShown{top:71px}.standard-fieldset .hint.hintShown{margin:0;left:335px;top:6px;width:280px;min-height:153px;padding:0 12px 12px;font-size:18px;box-sizing:border-box}.standard-fieldset .hint.hintShown img{padding:0}.standard-fieldset .hint.hintShown p{font-size:12px;margin-left:95px}.message{display:none}.message.ERROR{display:block}@media only screen and (max-width:740px){.registered-fieldset .hint.hintShown,.update-fieldset .hint.hintShown{margin:0;left:0;top:7px;box-sizing:border-box;width:300px;height:44px;display:flex;flex-direction:row;align-items:center}.update-fieldset .hint.hintShown{top:63px}}@media only screen and (max-width:618px){.row .col3{display:block;margin-left:206px;margin-bottom:0;margin-top:0;width:auto;max-width:unset}.INFO,.ERROR{margin-bottom:8px}.message{display:none}.message.ERROR{display:block}.standard-fieldset .hint.hintShown{margin:0;left:20px;top:6px;max-width:280px;width:auto}}@media only screen and (max-width:500px){.row .col2{display:block}.row .col1{width:auto;display:block}.row .col3{margin-left:0;margin-bottom:16px;width:auto;display:block}.standard-fieldset .hint.hintShown{bottom:0;top:auto;left:123px}.standard-fieldset .row.row3{position:relative}.registered-fieldset .row .col1{width:200px}}@media only screen and (max-width:406px){.registered-fieldset .hint.hintShown,.update-fieldset .hint.hintShown{font-size:12px;width:auto}.update-fieldset .hint.hintShown{top:90px}}
\ No newline at end of file
define([
'jquery',
'Magento_Customer/js/customer-data',
'mage/url'
], function ($, customerData, urlBuilder){
'use strict';
$.widget('mage.invalidateCart', {
_init: function () {
var url = urlBuilder.build('checkout/onepage/success');
customerData.invalidate(['cart']);
window.location.href = url;
}
});
return $.mage.invalidateCart;
});
This source diff could not be displayed because it is too large. You can view the blob instead.
/*browser:true*/
/*global define*/
define(
[
'jquery',
'ko',
'Magento_Checkout/js/view/payment/default',
'Magento_Checkout/js/checkout-data',
'Magento_Checkout/js/model/quote',
'Magento_Checkout/js/model/full-screen-loader',
'mage/url',
'payoneerWidget'
],
function (
$,
ko,
Component,
checkoutData,
quote,
fullScreenLoader,
urlBuilder,
payoneerWidget
) {
'use strict';
return Component.extend({
defaults: {
template: 'Payoneer_OpenPaymentGateway/payment/form',
transactionResult: '',
},
initObservable: function () {
this._super()
.observe({
shouldShowMessage: ko.observable(false),
isSuccessResponse: ko.observable(false)
});
this.shouldShowMessage.subscribe(function (newValue) {
});
this.isSuccessResponse.subscribe(function (newValue) {
});
let self=this;
let prevAddress;
quote.billingAddress.subscribe(
function(newAddress) {
if (!newAddress ^ !prevAddress || newAddress.getKey() !== prevAddress.getKey()) {
prevAddress = newAddress;
if (newAddress) {
if (self.getCurrentPaymentMethod() === self.getCode()){
self.processPayoneerPayment(newAddress);
}
}
}
}
);
quote.totals.subscribe(function (totals) {
if (self.getCurrentPaymentMethod() === self.getCode()) {
self.processPayoneerPayment('');
}
});
return this;
},
initialize: function () {
$('.payoneer.message.error').hide();
this._super();
},
getCode: function() {
return 'payoneer';
},
getTitle: function () {
let environment = window.checkoutConfig.payment.payoneer.config.environment;
if (environment === 'test'){
return 'Test Mode: ' + this.item.title;
}
return this.item.title;
},
getCurrentPaymentMethod: function() {
return checkoutData.getSelectedPaymentMethod();
},
getData: function() {
return {
'method': this.item.method
};
},
selectPaymentMethod: function () {
let isSelected = this._super();
if (isSelected) {
this.processPayoneerPayment('');
}
return isSelected;
},
isActive: function() {
return window.checkoutConfig.payment.payoneer.config.active;
},
isHostedIntegration: function() {
return window.checkoutConfig.payment.payoneer.config.payment_flow === 'HOSTED';
},
getErrorMessage: function() {
return 'Something went wrong while processing payment';
},
/**
* Get widget css url
* @returns {*}
*/
getWidgetCssUrl: function() {
return window.checkoutConfig.payment.payoneer.config.widgetCssUrl;
},
/**
* Proceed to hosted page
*/
proceedToPayoneer: function() {
if (window.checkoutConfig.payment.payoneer.config.redirectURL !== undefined) {
window.location.href = window.checkoutConfig.payment.payoneer.config.redirectURL;
} else {
self.shouldShowMessage(true);
}
},
/**
* Process Payoneer payment
*/
processPayoneerPayment: function(newAddress) {
let self = this;
let integrationType = '';
let changedAddress = newAddress;
if(this.isHostedIntegration()) {
integrationType = 'hosted';
} else {
integrationType = 'embedded';
}
$('.payoneer.message.error').hide();
let endpoint = window.checkoutConfig.payment.payoneer.config.processPaymentUrl;
$('body').trigger('processStart');
self.shouldShowMessage(false);
$.ajax({
url: urlBuilder.build(endpoint),
type: "POST",
data: {
integration : integrationType,
address: JSON.stringify(changedAddress)
},
dataType: 'json'
}).done(function (response) {
if(integrationType === 'hosted') {
if (response.redirectURL) {
window.checkoutConfig.payment.payoneer.config.redirectURL = response.redirectURL;
} else {
self.shouldShowMessage(true);
}
} else{
if (response.links) {
var configObj = {
payButton: 'submitBtn',
payButtonContainer: 'submitBtnContainer',
listUrl: response.links.self,
smartSwitch: true,
fullPageLoading: false,
widgetCssUrl: self.getWidgetCssUrl()
}
$('#paymentNetworks').empty();
checkoutList('paymentNetworks',configObj);
self.isSuccessResponse(true);
} else{
self.shouldShowMessage(true);
}
}
$('body').trigger('processStop');
fullScreenLoader.stopLoader();
}).fail(function (response) {
$('.payoneer.message.error').show();
$('body').trigger('processStop');
self.shouldShowMessage(true);
fullScreenLoader.stopLoader();
});
},
/**
* Show or hide static payment icons
*/
showStaticPaymentIcons: function() {
if (
window.checkoutConfig.payment.payoneer.config.payment_icon_type == 'static' ||
window.checkoutConfig.payment.payoneer.config.payment_icon_type == 'both'
) {
return true;
}
return false;
},
/**
* Get #paymentNetworks div class attribute value
*/
getPaymentNetworkDivClassAttribute: function() {
return 'payment-networks-container ' + window.checkoutConfig.payment.payoneer.config.payment_icon_type;
},
});
}
);
/*browser:true*/
/*global define*/
define(
[
'uiComponent',
'Magento_Checkout/js/model/payment/renderer-list'
],
function (
Component,
rendererList
) {
'use strict';
rendererList.push(
{
type: 'payoneer',
component: 'Payoneer_OpenPaymentGateway/js/view/payment/method-renderer/payoneer'
}
);
/** Add view logic here if needed */
return Component.extend({});
}
);
<div class="payment-method" data-bind="css: {'_active': (getCode() == isChecked())}">
<div class="payment-method-title field choice">
<input type="radio"
name="payment[method]"
class="radio"
data-bind="attr: {'id': getCode()}, value: getCode(), checked: isChecked, click: selectPaymentMethod, visible: isRadioButtonVisible()"/>
<label class="label" data-bind="attr: {'for': getCode()}">
<span data-bind="text: getTitle()"></span>
</label>
<!-- ko if: showStaticPaymentIcons()-->
<img src="https://resources.sandbox.oscato.com/resource/network/MRS_TEST_TRYZENS/zh_CN/VISA/logo.png" class="payment-method-icon" name="visa_logo" alt="Visa logo" />
<img src="https://resources.sandbox.oscato.com/resource/network/MRS_TEST_TRYZENS/zh_CN/MASTERCARD/logo.png" class="payment-method-icon" name="mastercard_logo" alt="Mastercard logo" />
<!-- /ko -->
</div>
<div class="payment-method-content">
<!-- ko foreach: getRegion('messages') -->
<!-- ko template: getTemplate() --><!-- /ko -->
<!--/ko-->
<div class="payment-method-billing-address">
<!-- ko foreach: $parent.getRegion(getBillingAddressFormName()) -->
<!-- ko template: getTemplate() --><!-- /ko -->
<!--/ko-->
</div>
<!-- ko if: isHostedIntegration() -->
<div class="actions-toolbar">
<div class="primary">
<button class="action primary checkout"
type="submit"
data-bind="
click: proceedToPayoneer,
attr: {title: $t('Proceed to Payoneer')}
">
<span data-bind="i18n: 'Proceed to Payoneer'"></span>
</button>
</div>
</div>
<!-- /ko -->
<!-- ko ifnot: isHostedIntegration() -->
<div data-bind="attr: {'class':getPaymentNetworkDivClassAttribute()}, visible: isSuccessResponse" id="paymentNetworks"></div>
<div data-bind="visible: isSuccessResponse" id="submitBtnContainer" class="submitBtn submit-buttons-container">
<button
id="submitBtn"
type="button"
class="submit-button"
></button>
</div>
<!-- /ko -->
<div data-bind="visible: shouldShowMessage" class="message error">
<span data-bind="text: getErrorMessage()"></span>
</div>
</div>
</div>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment