diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 62c8935..0000000 --- a/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.idea/ \ No newline at end of file diff --git a/Block/Adminhtml/Sales/Order/Fee.php b/Block/Adminhtml/Sales/Order/Fee.php new file mode 100644 index 0000000..85e415b --- /dev/null +++ b/Block/Adminhtml/Sales/Order/Fee.php @@ -0,0 +1,71 @@ +_config = $taxConfig; + parent::__construct($context, $data); + } + + public function displayFullSummary() + { + return true; + } + + public function getSource() + { + return $this->source; + } + + public function getStore() + { + return $this->order->getStore(); + } + + public function getOrder() + { + return $this->order; + } + + public function getLabelProperties() + { + return $this->getParentBlock()->getLabelProperties(); + } + + public function getValueProperties() + { + return $this->getParentBlock()->getValueProperties(); + } + + public function initTotals() + { + $parent = $this->getParentBlock(); + $this->order = $parent->getOrder(); + $this->source = $parent->getSource(); + $fee = new \Magento\Framework\DataObject( + [ + 'code' => 'cryptapi_fee', + 'strong' => false, + 'value' => $this->order->getData('cryptapi_fee'), + 'label' => __('Service Fee'), + ] + ); + $parent->addTotal($fee, 'cryptapi_fee'); + return $this; + } +} diff --git a/Block/Adminhtml/Sales/Order/View.php b/Block/Adminhtml/Sales/Order/View.php index ba3f3ef..9cd845e 100644 --- a/Block/Adminhtml/Sales/Order/View.php +++ b/Block/Adminhtml/Sales/Order/View.php @@ -1,7 +1,9 @@ helper = $helper; $this->payment = $payment; $this->scopeConfig = $scopeConfig; - $this->logger = $logger; + $this->request = $request; + $this->orderRepository = $orderRepository; + $this->productMetadata = $productMetadata; } public function getTemplateValues() { - $order = $this->payment->getOrder(); + if ($this->productMetadata->getVersion() >= 2.3 && $this->productMetadata->getVersion() < 2.4) { + $order = $this->payment->getOrder(); + } else { + $order_id = (int)$this->request->getParam('order_id'); + $nonce = (string)$this->request->getParam('nonce'); + $order = $this->orderRepository->get($order_id); + } $total = $order->getGrandTotal(); $currencySymbol = $order->getOrderCurrencyCode(); - $metaData = $this->helper->getPaymentResponse($order->getQuoteId()); if (empty($metaData)) { return false; } + $qrCodeSize = $this->scopeConfig->getValue('payment/cryptapi/qrcode_size', \Magento\Store\Model\ScopeInterface::SCOPE_STORE); + + $branding = $this->scopeConfig->getValue('payment/cryptapi/show_branding', \Magento\Store\Model\ScopeInterface::SCOPE_STORE); + $metaData = json_decode($metaData, true); + if (!$this->productMetadata->getVersion() >= 2.3 && $this->productMetadata->getVersion() < 2.4) { + if ($nonce != $metaData['cryptapi_nonce']) { + return false; + } + } + $cryptoValue = $metaData['cryptapi_total']; $cryptoCoin = $metaData['cryptapi_currency']; if (isset($metaData['cryptapi_address']) && !empty($metaData['cryptapi_address'])) { $addressIn = $metaData['cryptapi_address']; } else { - $selected = $cryptoCoin; $address = ''; @@ -69,37 +88,41 @@ public function getTemplateValues() $api = new CryptAPIHelper($selected, $address, $callbackUrl, $params, true); $addressIn = $api->get_address(); + $qrCode = $api->get_qrcode('', $qrCodeSize); + $qrCodeValue = $api->get_qrcode($cryptoValue, $qrCodeSize); + $this->helper->updatePaymentData($order->getQuoteId(), 'cryptapi_address', $addressIn); + $this->helper->updatePaymentData($order->getQuoteId(), 'cryptapi_qr_code_value', $qrCodeValue['qr_code']); + $this->helper->updatePaymentData($order->getQuoteId(), 'cryptapi_qr_code', $qrCode['qr_code']); } $ajaxParams = [ - 'order_id' => $order->getId() + 'order_id' => $order->getId(), ]; $ajaxUrl = $this->payment->getAjaxStatusUrl($ajaxParams); - $qrCodeSize = $this->scopeConfig->getValue('payment/cryptapi/qrcode_size', \Magento\Store\Model\ScopeInterface::SCOPE_STORE); - - $branding = $this->scopeConfig->getValue('payment/cryptapi/show_branding', \Magento\Store\Model\ScopeInterface::SCOPE_STORE); - - $qrCode = $api->get_qrcode('', $qrCodeSize); - $qrCodeValue = $api->get_qrcode($cryptoValue, $qrCodeSize); + $metaData = $this->helper->getPaymentResponse($order->getQuoteId()); + $metaData = json_decode($metaData, true); - $values = [ - 'crypto_value' => $cryptoValue, + return [ + 'crypto_value' => floatval($cryptoValue), 'currency_symbol' => $currencySymbol, 'total' => $total, 'address_in' => $addressIn, 'crypto_coin' => $cryptoCoin, 'ajax_url' => $ajaxUrl, 'qrcode_size' => $qrCodeSize, - 'qrcode' => $qrCode['qr_code'], - 'qrcode_value' => $qrCodeValue['qr_code'], + 'qrcode' => $metaData['cryptapi_qr_code'], + 'qrcode_value' => $metaData['cryptapi_qr_code_value'], 'qrcode_default' => $this->scopeConfig->getValue('payment/cryptapi/qrcode_default', \Magento\Store\Model\ScopeInterface::SCOPE_STORE), - 'payment_uri' => $qrCode['uri'], - 'show_branding' => $branding + 'show_branding' => $branding, + 'qr_code_setting' => $this->scopeConfig->getValue('payment/cryptapi/qrcode_setting', \Magento\Store\Model\ScopeInterface::SCOPE_STORE), + 'order_timestamp' => strtotime($order->getCreatedAt()), + 'order_cancelation_timeout' => $this->scopeConfig->getValue('payment/cryptapi/order_cancelation_timeout', \Magento\Store\Model\ScopeInterface::SCOPE_STORE), + 'refresh_value_interval' => $this->scopeConfig->getValue('payment/cryptapi/refresh_value_interval', \Magento\Store\Model\ScopeInterface::SCOPE_STORE), + 'last_price_update' => $metaData['cryptapi_last_price_update'], + 'min_tx' => $metaData['cryptapi_min'], ]; - - return $values; } } diff --git a/Controller/Index/Callback.php b/Controller/Index/Callback.php index 16d727d..7c75d81 100644 --- a/Controller/Index/Callback.php +++ b/Controller/Index/Callback.php @@ -3,9 +3,9 @@ namespace Cryptapi\Cryptapi\Controller\Index; use Cryptapi\Cryptapi\lib\CryptAPIHelper; -use Magento\Framework\Controller\ResultFactory; +use Magento\Framework\App\Action\HttpGetActionInterface; -class Callback extends \Magento\Framework\App\Action\Action +class Callback implements HttpGetActionInterface { protected $helper; protected $payment; @@ -13,65 +13,94 @@ class Callback extends \Magento\Framework\App\Action\Action public function __construct( \Cryptapi\Cryptapi\Helper\Data $helper, - \Cryptapi\Cryptapi\Model\Pay $payment, - \Magento\Sales\Model\OrderFactory $orderFactory, + \Cryptapi\Cryptapi\Model\Method\CryptapiPayment $payment, + \Magento\Sales\Api\OrderRepositoryInterface $orderRepository, \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, - \Magento\Framework\App\Action\Context $context + \Magento\Framework\App\Request\Http $request, + \Magento\Framework\App\Response\Http $response ) { $this->helper = $helper; $this->payment = $payment; - $this->orderFactory = $orderFactory; + $this->orderRepository = $orderRepository; $this->scopeConfig = $scopeConfig; - parent::__construct($context); + $this->request = $request; + $this->response = $response; } public function execute() { - $params = $this->getRequest()->getParams(); + $params = $this->request->getParams(); $data = CryptAPIHelper::process_callback($params); - $order = $this->orderFactory->create()->load($data['order_id']); + $order = $this->orderRepository->get($data['order_id']); + $orderId = $order->getQuoteId(); - $metaData = $this->helper->getPaymentResponse($order->getQuoteId()); + $currencySymbol = $order->getOrderCurrencyCode(); - if (!empty($metaData)) { - $metaData = json_decode($metaData, true); - } + $metaData = json_decode($this->helper->getPaymentResponse($orderId), true); if ($this->payment->hasBeenPaid($order) || $data['nonce'] != $metaData['cryptapi_nonce']) { - return $this->getResponse()->setBody("*ok*"); + return $this->response->setBody("*ok*"); } - $alreadyPaid = 0; + $paid = floatval($data['value_coin']); - if (isset($metaData['cryptapi_paid'])) { - $alreadyPaid = $metaData['cryptapi_paid']; - } + $min_tx = floatval($metaData['cryptapi_min']); - $paid = floatval($alreadyPaid) + floatval($data['value_coin']); + $history = json_decode($metaData['cryptapi_history'], true); - if (!$data['pending']) { - $this->helper->updatePaymentData($order->getQuoteId(), 'cryptapi_paid', $paid); + $update_history = true; + + foreach ($history as $uuid => $item) { + if ($uuid === $data['uuid']) { + $update_history = false; + } } - if ($paid >= $metaData['cryptapi_total']) { - if ($data['pending']) { - $this->helper->updatePaymentData($order->getQuoteId(), 'cryptapi_pending', "1"); - } else { - $this->helper->deletePaymentData($order->getQuoteId(), 'cryptapi_pending'); + if ($update_history) { + $fiat_conversion = CryptAPIHelper::get_conversion($metaData['cryptapi_currency'], $currencySymbol, $paid, $this->scopeConfig->getValue('payment/cryptapi/disable_conversion', \Magento\Store\Model\ScopeInterface::SCOPE_STORE)); + + $history[$data['uuid']] = [ + 'timestamp' => time(), + 'value_paid' => $paid, + 'value_paid_fiat' => $fiat_conversion, + 'pending' => $data['pending'] + ]; + } else { + $history[$data['uuid']]['pending'] = $data['pending']; + } + + $this->helper->updatePaymentData($orderId, 'cryptapi_history', json_encode($history)); + + $metaData = json_decode($this->helper->getPaymentResponse($orderId), true); + $history = json_decode($metaData['cryptapi_history'], true); + + $calc = $this->payment::calcOrder($history, $metaData); + + $remaining = $calc['remaining']; + $remaining_pending = $calc['remaining_pending']; + + if ($remaining_pending <= 0) { + if ($remaining <= 0) { $state = \Magento\Sales\Model\Order::STATE_PROCESSING; $status = \Magento\Sales\Model\Order::STATE_PROCESSING; $order->setState($state); $order->setStatus($status); $order->setTotalPaid($order->getGrandTotal()); $order->save(); - - $this->helper->updatePaymentData($order->getQuoteId(), 'cryptapi_txid', $data['txid_in']); } + return $this->response->setBody("*ok*"); } - return $this->getResponse()->setBody("*ok*"); + + if ($remaining_pending <= $min_tx) { + $this->helper->updatePaymentData($orderId, 'cryptapi_qr_code_value', CryptAPIHelper::get_static_qrcode($metaData['cryptapi_address'], $metaData['cryptapi_currency'], $min_tx, $this->scopeConfig->getValue('payment/cryptapi/qrcode_size', \Magento\Store\Model\ScopeInterface::SCOPE_STORE))['qr_code']); + } else { + $this->helper->updatePaymentData($orderId, 'cryptapi_qr_code_value', CryptAPIHelper::get_static_qrcode($metaData['cryptapi_address'], $metaData['cryptapi_currency'], $remaining_pending, $this->scopeConfig->getValue('payment/cryptapi/qrcode_size', \Magento\Store\Model\ScopeInterface::SCOPE_STORE))['qr_code']); + } + + return $this->response->setBody("*ok*"); } } diff --git a/Controller/Index/CartQuote.php b/Controller/Index/CartQuote.php new file mode 100644 index 0000000..5fbdede --- /dev/null +++ b/Controller/Index/CartQuote.php @@ -0,0 +1,38 @@ +checkoutSession = $checkoutSession; + $this->request = $request; + $this->response = $response; + } + + public function execute() + { + $selected = (string)$this->request->getParam('selected'); + + $this->checkoutSession->setCurrency($selected); + + $data = [ + 'status' => 'done' + ]; + + $response = json_encode($data); + return $this->response->setBody($response); + } +} diff --git a/Controller/Index/Payment.php b/Controller/Index/Payment.php new file mode 100644 index 0000000..8e5c910 --- /dev/null +++ b/Controller/Index/Payment.php @@ -0,0 +1,24 @@ +pageFactory = $pageFactory; + } + + public function execute() + { + return $this->pageFactory->create(); + } +} + diff --git a/Controller/Index/Status.php b/Controller/Index/Status.php index df7e074..93c5e7d 100644 --- a/Controller/Index/Status.php +++ b/Controller/Index/Status.php @@ -1,52 +1,98 @@ helper = $helper; $this->payment = $payment; - $this->orderFactory = $orderFactory; - parent::__construct($context); + $this->orderRepository = $orderRepository; + $this->scopeConfig = $scopeConfig; + $this->cronjob = $cronjob; + $this->request = $request; + $this->response = $response; + $this->priceHelper = $priceHelper; + $this->logger = $logger; } public function execute() { - $order_id = (int)$this->getRequest()->getParam('order_id'); - - try { - $order = $this->orderFactory->create()->load($order_id); - $metaData = $this->helper->getPaymentResponse($order->getQuoteId()); - if (!empty($metaData)) { - $metaData = json_decode($metaData, true); - } - - $cryptapi_pending = 0; - if (isset($metaData['cryptapi_pending'])) { - $cryptapi_pending = $metaData['cryptapi_pending']; - } - - $data = [ - 'is_paid' => $this->payment->hasBeenPaid($order), - 'is_pending' => (int)($cryptapi_pending), - ]; - - $response = json_encode($data); - return $this->getResponse()->setBody($response); - } catch (\Exception $e) { - ; + $orderId = (int)$this->request->getParam('order_id'); + + $order = $this->orderRepository->get($orderId); + $metaData = $this->helper->getPaymentResponse($order->getQuoteId()); + + if (!empty($metaData)) { + $metaData = json_decode($metaData, true); } - $response = json_encode(['status' => 'error', 'error' => 'Not a valid order id']); + + $showMinFee = '0'; + + $history = json_decode($metaData['cryptapi_history'], true); + + $calc = $this->payment::calcOrder($history, $metaData); + + $already_paid = $calc['already_paid']; + $already_paid_fiat = $calc['already_paid_fiat'] <= 0 ? 0 : $calc['already_paid_fiat']; + + $min_tx = floatval($metaData['cryptapi_min']); + + // $remaining = $calc['remaining']; + $remaining_pending = $calc['remaining_pending']; + $remaining_fiat = $calc['remaining_fiat']; + + $cryptapi_pending = '0'; + if ($remaining_pending <= 0 && !$this->payment->hasBeenPaid($order)) { + $cryptapi_pending = '1'; + } + + $counter_calc = (int)$metaData['cryptapi_last_price_update'] + (int)$this->scopeConfig->getValue('payment/cryptapi/refresh_value_interval', \Magento\Store\Model\ScopeInterface::SCOPE_STORE) - time(); + if (!$this->payment->hasBeenPaid($order) && $counter_calc <= 0) { + $this->cronjob->execute(); + } + + if ($remaining_pending <= $min_tx && $remaining_pending > 0) { + $remaining_pending = $min_tx; + $showMinFee = '1'; + } + + $data = [ + 'is_paid' => $this->payment->hasBeenPaid($order), + 'is_pending' => (int)($cryptapi_pending), + 'crypto_total' => floatval($metaData['cryptapi_total']), + 'qr_code_value' => $metaData['cryptapi_qr_code_value'], + 'cancelled' => $metaData['cryptapi_cancelled'], + 'remaining' => $remaining_pending <= 0 ? 0 : $remaining_pending, + 'fiat_remaining' => $this->priceHelper->currency($remaining_fiat, true, false), + 'coin' => strtoupper($metaData['cryptapi_currency']), + 'show_min_fee' => $showMinFee, + 'order_history' => $history, + 'already_paid' => $already_paid, + 'already_paid_fiat' => $this->priceHelper->currency($remaining_pending <= 0 ? 0 : floatval($already_paid_fiat), true, false), + 'counter' => (string)$counter_calc, + 'fiat_symbol' => $order->getOrderCurrencyCode() + ]; + $response = json_encode($data); - return $this->getResponse()->setBody($response); + return $this->response->setBody($response); } } diff --git a/Cron/CryptapiCronjob.php b/Cron/CryptapiCronjob.php new file mode 100644 index 0000000..b6d7324 --- /dev/null +++ b/Cron/CryptapiCronjob.php @@ -0,0 +1,121 @@ +scopeConfig = $scopeConfig; + $this->orderCollectionFactory = $orderCollectionFactory; + $this->helper = $helper; + $this->orderRepository = $orderRepository; + $this->payment = $payment; + $this->logger = $logger; + } + + public function execute() + { + $order_timeout = (int)$this->scopeConfig->getValue('payment/cryptapi/order_cancelation_timeout'); + $value_refresh = (int)$this->scopeConfig->getValue('payment/cryptapi/refresh_value_interval'); + + if ($order_timeout === 0 && $value_refresh === 0) { + return; + } + + $orders = $this->getOrderCollectionPaymentMethod(); + + if (empty($orders)) { + return; + } + + $disable_conversion = $this->scopeConfig->getValue('payment/cryptapi/disable_conversion'); + + foreach ($orders as $order) { + $orderQuoteId = $order->getQuoteId(); + + $metaData = json_decode($this->helper->getPaymentResponse($order->getQuoteId()), true); + + $history = json_decode($metaData['cryptapi_history'], true); + + $min_tx = floatval($metaData['cryptapi_min']); + + $calc = $this->payment::calcOrder($history, $metaData); + + $remaining = $calc['remaining']; + $remaining_pending = $calc['remaining_pending']; + $remaining_fiat = $calc['remaining_fiat']; + + if (!empty($metaData['cryptapi_address']) && $value_refresh !== 0 && $metaData['cryptapi_cancelled'] !== '1' && (int)$metaData['cryptapi_last_price_update'] + $value_refresh <= time() && $remaining_pending > 0) { + + if ($remaining === $remaining_pending) { + $cryptapi_coin = $metaData['cryptapi_currency']; + + $crypto_total = CryptAPIHelper::get_conversion($order->getOrderCurrencyCode(), $cryptapi_coin, $metaData['cryptapi_total_fiat'], $disable_conversion); + $this->helper->updatePaymentData($orderQuoteId, 'cryptapi_total', $crypto_total); + + $calc_cron = $this->payment::calcOrder($history, $metaData); + $crypto_remaining_total = $calc_cron['remaining_pending']; + + if ($remaining_pending <= $min_tx && !$remaining_pending <= 0) { + $qr_code_data_value = CryptAPIHelper::get_static_qrcode($metaData['cryptapi_address'], $cryptapi_coin, $min_tx, $this->scopeConfig->getValue('payment/cryptapi/qrcode_size')); + } else { + $qr_code_data_value = CryptAPIHelper::get_static_qrcode($metaData['cryptapi_address'], $cryptapi_coin, $crypto_remaining_total, $this->scopeConfig->getValue('payment/cryptapi/qrcode_size')); + } + + $this->helper->updatePaymentData($orderQuoteId, 'cryptapi_qr_code_value', $qr_code_data_value['qr_code']); + + } + + $this->helper->updatePaymentData($orderQuoteId, 'cryptapi_last_price_update', time()); + } + + if ($order_timeout !== 0 && ((int)strtotime($order->getCreatedAt()) + $order_timeout) <= time() && empty($metaData['cryptapi_pending']) && $remaining_fiat <= $order->getGrandTotal() && (string)$metaData['cryptapi_cancelled'] === '0') { + $state = \Magento\Sales\Model\Order::STATE_CANCELED; + $status = \Magento\Sales\Model\Order::STATE_CANCELED; + $order->setState($state); + $order->setStatus($status); + $this->helper->updatePaymentData($orderQuoteId, 'cryptapi_cancelled', '1'); + $order->save(); + } + } + } + + private function getOrderCollectionPaymentMethod() + { + $orders = $this->orderCollectionFactory->create() + ->addFieldToSelect('*') + ->addFieldToFilter('status', + ['in' => ['pending']] + ); + + $orders->getSelect() + ->join( + ["sop" => "sales_order_payment"], + 'main_table.entity_id = sop.parent_id', + array('method') + ) + ->where('sop.method = ?', 'cryptapi'); + + $orders->setOrder( + 'created_at', + 'desc' + ); + + return $orders; + } +} diff --git a/Helper/Decimal.php b/Helper/Decimal.php new file mode 100644 index 0000000..f54e113 --- /dev/null +++ b/Helper/Decimal.php @@ -0,0 +1,53 @@ +precision; + } + + private function maybe_reduce_precision($new_val) { + while ($new_val >= PHP_INT_MAX) { + $new_val = ($new_val / 10 ** $this->precision) * 10 ** ($this->precision - 1); + $this->precision -= 1; + } + + $this->val = intval($new_val); + } + + function __construct($float_val) { + $this->sum($float_val); + } + + function mult($float_val) { + $new_val = $this->val * $this->to_int($float_val); + $this->maybe_reduce_precision($new_val); + return $this; + } + + function div($float_val) { + $new_val = $this->val / $this->to_int($float_val); + $this->maybe_reduce_precision($new_val); + return $this; + } + + function sum($float_val) { + $new_val = $this->val + $this->to_int($float_val); + $this->maybe_reduce_precision($new_val); + return $this; + } + + function sub($float_val) { + $new_val = $this->val - $this->to_int($float_val); + $this->maybe_reduce_precision($new_val); + return $this; + } + + function result() { + return $this->val / 10 ** $this->precision; + } +} diff --git a/Model/ConfigPlugin.php b/Model/Config/ConfigPlugin.php similarity index 94% rename from Model/ConfigPlugin.php rename to Model/Config/ConfigPlugin.php index ca549bc..f14f3ee 100644 --- a/Model/ConfigPlugin.php +++ b/Model/Config/ConfigPlugin.php @@ -1,8 +1,6 @@ 'Never', + '3600' => '1 Hour', + '21600' => '6 Hours', + '43200' => '12 Hours', + '64800' => '18 Hours', + '86400' => '24 Hours', + ]; + } +} diff --git a/Model/Config/Source/FeesList.php b/Model/Config/Source/FeesList.php new file mode 100644 index 0000000..9edc0a6 --- /dev/null +++ b/Model/Config/Source/FeesList.php @@ -0,0 +1,42 @@ + '5%', + '0.048' => '4.8%', + '0.045' => '4.5%', + '0.042' => '4.2%', + '0.04' => '4%', + '0.038' => '3.8%', + '0.035' => '3.5%', + '0.032' => '3.2%', + '0.03' => '3%', + '0.028' => '2.8%', + '0.025' => '2.5%', + '0.022' => '2.2%', + '0.02' => '2%', + '0.018' => '1.8%', + '0.015' => '1.5%', + '0.012' => '1.2%', + '0.01' => '1%', + '0.0090' => '0.90%', + '0.0085' => '0.85%', + '0.0080' => '0.80%', + '0.0075' => '0.75%', + '0.0070' => '0.70%', + '0.0065' => '0.65%', + '0.0060' => '0.60%', + '0.0055' => '0.55%', + '0.0050' => '0.50%', + '0.0040' => '0.40%', + '0.0030' => '0.30%', + '0.0025' => '0.25%', + 'none' => '0%', + ]; + } +} diff --git a/Model/Config/Source/QrcodeOptions.php b/Model/Config/Source/QrcodeOptions.php new file mode 100644 index 0000000..487a6a4 --- /dev/null +++ b/Model/Config/Source/QrcodeOptions.php @@ -0,0 +1,16 @@ + 'Default Without Ammount', + 'ammount' => 'Default Ammount', + 'hide_ammount' => 'Hide Ammount', + 'hide_without_ammount' => 'Hide Without Ammount', + ]; + } +} diff --git a/Model/Config/Source/RefreshList.php b/Model/Config/Source/RefreshList.php new file mode 100644 index 0000000..fb86c0d --- /dev/null +++ b/Model/Config/Source/RefreshList.php @@ -0,0 +1,19 @@ + 'Never', + '300' => 'Every 5 Minutes', + '600' => 'Every 10 Minutes', + '900' => 'Every 15 Minutes', + '1800' => 'Every 30 Minutes', + '2700' => 'Every 45 Minutes', + '3600' => 'Every 60 Minutes', + ]; + } +} diff --git a/Model/Config/Source/SchemeList.php b/Model/Config/Source/SchemeList.php new file mode 100644 index 0000000..6e991ac --- /dev/null +++ b/Model/Config/Source/SchemeList.php @@ -0,0 +1,15 @@ + 'Light', + 'dark' => 'Dark', + 'auto' => 'Auto', + ]; + } +} diff --git a/Model/Pay.php b/Model/Method/CryptapiPayment.php similarity index 76% rename from Model/Pay.php rename to Model/Method/CryptapiPayment.php index d86bfdb..0c23f75 100644 --- a/Model/Pay.php +++ b/Model/Method/CryptapiPayment.php @@ -1,12 +1,14 @@ getAdditionalInformation('cryptapi_coin'); if (empty($selected)) { - throw new \Magento\Framework\Exception\LocalizedException( - __('Please select a cryptocurrency.') - ); + return $this; } $nonce = $this->generateNonce(); @@ -135,9 +135,9 @@ public function validate() $total = $quote->getGrandTotal(); $cryptoTotal = CryptAPIHelper::get_conversion( + $currencyCode, $selected, $total, - $currencyCode, $this->scopeConfig->getValue('payment/cryptapi/disable_conversion', \Magento\Store\Model\ScopeInterface::SCOPE_STORE) ); @@ -153,7 +153,14 @@ public function validate() 'cryptapi_nonce' => $nonce, 'cryptapi_address' => '', 'cryptapi_total' => $cryptoTotal, + 'cryptapi_total_fiat' => $total, 'cryptapi_currency' => $selected, + 'cryptapi_history' => json_encode([]), + 'cryptapi_cancelled' => '0', + 'cryptapi_last_price_update' => time(), + 'cryptapi_min' => $minTx, + 'cryptapi_qr_code_value' => '', + 'cryptapi_qr_code' => '', ]; $paymentData = json_encode($paymentData); @@ -197,6 +204,37 @@ public function hasBeenPaid($order) } } + public static function calcOrder($history, $meta) + { + $already_paid = 0; + $already_paid_fiat = 0; + $remaining = $meta['cryptapi_total']; + $remaining_pending = $meta['cryptapi_total']; + $remaining_fiat = $meta['cryptapi_total_fiat']; + + if (!empty($history)) { + foreach ($history as $uuid => $item) { + if ((int)$item['pending'] === 0) { + $remaining = bcsub(CryptAPIHelper::sig_fig($remaining, 6), $item['value_paid'], 8); + } + + $remaining_pending = bcsub(CryptAPIHelper::sig_fig($remaining_pending, 6), $item['value_paid'], 8); + $remaining_fiat = bcsub(CryptAPIHelper::sig_fig($remaining_fiat, 6), $item['value_paid_fiat'], 8); + + $already_paid = bcadd(CryptAPIHelper::sig_fig($already_paid, 6), $item['value_paid'], 8); + $already_paid_fiat = bcadd(CryptAPIHelper::sig_fig($already_paid_fiat, 6), $item['value_paid_fiat'], 8); + } + } + + return [ + 'already_paid' => floatval($already_paid), + 'already_paid_fiat' => floatval($already_paid_fiat), + 'remaining' => floatval($remaining), + 'remaining_pending' => floatval($remaining_pending), + 'remaining_fiat' => floatval($remaining_fiat) + ]; + } + public function generateNonce($len = 32) { $data = str_split('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'); diff --git a/Model/Total/Fee.php b/Model/Total/Fee.php new file mode 100644 index 0000000..c137cfd --- /dev/null +++ b/Model/Total/Fee.php @@ -0,0 +1,116 @@ +checkoutSession = $checkoutSession; + $this->scopeConfig = $scopeConfig; + $this->totals = $orderTotals; + $this->logger = $logger; + } + + public function collect( + Quote $quote, + ShippingAssignmentInterface $shippingAssignment, + Total $total + ) + { + parent::collect($quote, $shippingAssignment, $total); + + if (!count($shippingAssignment->getItems())) { + return $this; + } + + $fee = $this->calculateFee($quote); + + $total->setTotalAmount('fee', $fee); + $total->setBaseTotalAmount('fee', $fee); + $total->setFee($fee); + $total->setBaseFee($fee); + $total->setGrandTotal($total->getGrandTotal()); + $total->setBaseGrandTotal($total->getBaseGrandTotal()); + + $quote->setFee($fee); + + return $this; + } + + protected function clearValues(Total $total) + { + $total->setTotalAmount('subtotal', 0); + $total->setBaseTotalAmount('subtotal', 0); + $total->setTotalAmount('tax', 0); + $total->setBaseTotalAmount('tax', 0); + $total->setTotalAmount('discount_tax_compensation', 0); + $total->setBaseTotalAmount('discount_tax_compensation', 0); + $total->setTotalAmount('shipping_discount_tax_compensation', 0); + $total->setBaseTotalAmount('shipping_discount_tax_compensation', 0); + $total->setSubtotalInclTax(0); + $total->setBaseSubtotalInclTax(0); + } + + public function fetch(Quote $quote, Total $total) + { + return [ + 'code' => 'fee', + 'title' => __('Service Fee'), + 'value' => $this->calculateFee($quote), + ]; + } + + private function calculateFee(Quote $quote) + { + + try { + $paymentMethod = $quote->getPayment()->getMethodInstance()->getCode(); + + if ($paymentMethod === 'cryptapi') { + $conv = 0; + $totalPrice = 0; + + $feePercentage = $this->scopeConfig->getValue('payment/cryptapi/fee_order_percentage', \Magento\Store\Model\ScopeInterface::SCOPE_STORE); + $estimateBlockchain = $this->scopeConfig->getValue('payment/cryptapi/add_blockchain_fee', \Magento\Store\Model\ScopeInterface::SCOPE_STORE); + $coin = $this->checkoutSession->getCurrency(); + + if ($feePercentage !== 'none') { + $totalPrice = $quote->getGrandTotal() * $feePercentage; + } + + if (!empty($coin) && $estimateBlockchain) { + $conv = CryptAPIHelper::get_estimate($coin)->{$quote->getQuoteCurrencyCode()}; + } + + return $totalPrice + $conv; + } + } catch (\Exception $ex) { + return 0; + } + + return 0; + } +} diff --git a/Model/ConfigProvider.php b/Model/Ui/CryptapiConfigProvider.php similarity index 80% rename from Model/ConfigProvider.php rename to Model/Ui/CryptapiConfigProvider.php index 2cf8c4a..d801f9f 100644 --- a/Model/ConfigProvider.php +++ b/Model/Ui/CryptapiConfigProvider.php @@ -1,20 +1,16 @@ $coin) { - foreach ($selected as $uid => $data) { - if ($ticker == $data['cryptocurrency']) - $output[] = [ - 'value' => $data['cryptocurrency'], - 'type' => $coin, - ]; + if (!empty($selected)) { + foreach (json_decode($available_cryptos) as $ticker => $coin) { + foreach ($selected as $uuid => $data) { + if ($ticker == $data['cryptocurrency']) + $output[] = [ + 'value' => $data['cryptocurrency'], + 'type' => $coin, + ]; + } } } diff --git a/Observer/AfterSuccess.php b/Observer/AfterSuccess.php new file mode 100644 index 0000000..064a1f8 --- /dev/null +++ b/Observer/AfterSuccess.php @@ -0,0 +1,59 @@ +helper = $helper; + $this->payment = $payment; + $this->url = $url; + $this->responseFactory = $responseFactory; + $this->logger = $logger; + $this->productMetadata = $productMetadata; + $this->redirect = $redirect; + } + + public function execute(Observer $observer) + { + $version_check = 1; + + if ($this->productMetadata->getVersion() >= 2.3 && $this->productMetadata->getVersion() < 2.4) { + $version_check = 0; + } + + if (empty($version_check)) { + return false; + } + + $order = $this->payment->getOrder(); + $paymentMethod = $order->getPayment()->getMethodInstance()->getCode(); + + if ($paymentMethod === 'cryptapi') { + $metaData = json_decode($this->helper->getPaymentResponse($order->getQuoteId()), true); + + $params = [ + 'order_id' => $order->getId(), + 'nonce' => $metaData['cryptapi_nonce'] + ]; + + $redirectOrder = $this->url->getUrl('cryptapi/index/payment', $params); + $this->responseFactory->create()->setRedirect($redirectOrder)->sendResponse(); + return $this->redirect->setRedirect($redirectOrder); + } + } +} diff --git a/Observer/QuoteSubmitBefore.php b/Observer/QuoteSubmitBefore.php new file mode 100644 index 0000000..6df7214 --- /dev/null +++ b/Observer/QuoteSubmitBefore.php @@ -0,0 +1,41 @@ +orderResourceModel = $orderResourceModel; + $this->orderRepository = $orderRepository; + $this->payment = $payment; + $this->objectCopyService = $objectCopyService; + $this->logger = $logger; + } + + public function execute(Observer $observer) + { + + $quote = $observer->getQuote(); + $order = $observer->getOrder(); + $paymentMethod = $order->getPayment()->getMethodInstance()->getCode(); + + if ($paymentMethod === 'cryptapi') { + $order =$observer->getOrder(); + $order->setData('cryptapi_fee', (float)$quote->getData('fee')); + } + + } +} diff --git a/README.md b/README.md index 5fd3244..b24937f 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,8 @@ Accept cryptocurrency payments on your Magento store ### Requirements: ``` -Magento >= 2.3 +Magento >= 2.4 +Magento >= 2.3.5 ``` ### Description @@ -18,19 +19,21 @@ All you need is to provide your crypto address. The CryptAPI extension enables your Magento store to get receive payments in cryptocurrency, with a simple setup and no sign-ups required. -Currently accepted cryptocurrencies are: +#### Accepted cryptocurrencies & tokens include: * (BTC) Bitcoin +* (ETH) Ethereum * (BCH) Bitcoin Cash * (LTC) Litecoin -* (ETH) Ethereum * (XMR) Monero -* (IOTA) IOTA +* (TRX) Tron +* (BNB) Binance Coin +* (USDT) USDT -CryptAPI will attempt to automatically convert the value you set on your store to the cryptocurrency your customer chose. -Exchange rates are fetched hourly from CoinMarketCap. +CryptAPI plugin will attempt to automatically convert the value you set on your store to the cryptocurrency your customer chose. +Exchange rates are fetched every 5 minutes. -Supported currencies for automatic exchange rates are: +### Supported currencies for automatic exchange rates are: * (USD) United States Dollar * (EUR) Euro @@ -58,12 +61,15 @@ For more info on our fees [click here](https://cryptapi.io/get_started/#fees) ### Installation -1. Upload code to folder app/code/Cryptapi/Cryptapi +1. Upload code to the folder app/code/Cryptapi/Cryptapi 2. Enter following commands to install module: ```bash -php bin/magento setup:upgrade -php bin/magento setup:static-content:deploy +php bin/magento module:enable Cryptapi_Cryptapi +php bin/magento setup:upgrade +php bin/magento setup:di:compile +php bin/magento setup:static-content:deploy -f +php bin/magento cache:flush php bin/magento cache:enable cryptapi_cryptocurrencies ``` @@ -118,5 +124,25 @@ The easiest and fastest way is via our live chat on our [website](https://crypta * UI Improvements * Minor Bug Fixes +#### 3.0 +* New settings and color schemes to fit dark mode +* New settings to add CryptAPI's services fees to the checkout +* New settings to add blockchain fees to the checkout +* Upgrade the settings +* Added a history of transactions to the order payment page +* Better handling of partial payments +* Disable QR Code with value in certain currencies due to some wallets not supporting it +* Minor fixes +* UI Improvements + +#### 3.0.1 +* Minor fixes + +#### 3.0.2 +* Minor fixes + +#### 3.0.3 +* Minor fixes + ### Upgrade Notice * No breaking changes. diff --git a/Setup/InstallSchema.php b/Setup/InstallSchema.php deleted file mode 100644 index e317e8b..0000000 --- a/Setup/InstallSchema.php +++ /dev/null @@ -1,36 +0,0 @@ -startSetup(); - - $table = $installer->getConnection()->newTable($installer->getTable('cryptapi')) - ->addColumn( - 'order_id', - \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, - null, - ['unsigned' => true], - 'Order Id' - )->addColumn( - 'response', - \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, - null, - [], - 'Response' - )->setComment( - 'Cryptapi Table' - ); - - $installer->getConnection()->createTable($table); - - $installer->endSetup(); - } -} diff --git a/composer.json b/composer.json index 786a492..70177f0 100644 --- a/composer.json +++ b/composer.json @@ -2,11 +2,12 @@ "name": "cryptapi/cryptapi", "description": "CryptAPI's Magento extension", "type": "magento2-module", - "version": "2.0.0", + "version": "3.0.3", "require": { "magento/module-payment": "100.1.*", "magento/module-checkout": "100.1.*", "magento/module-sales": "100.1.*", + "ext-bcmath": "*", "ext-curl": "*", "ext-json": "*" }, diff --git a/etc/adminhtml/di.xml b/etc/adminhtml/di.xml index 0fe12a6..3ea2313 100644 --- a/etc/adminhtml/di.xml +++ b/etc/adminhtml/di.xml @@ -2,6 +2,6 @@ - + diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml index 0aaab7c..2fcedc4 100644 --- a/etc/adminhtml/system.xml +++ b/etc/adminhtml/system.xml @@ -5,57 +5,83 @@
- + Magento\Config\Model\Config\Source\Yesno - + - - - - - Cryptapi\Cryptapi\Block\Adminhtml\Cryptocurrencies - Magento\Config\Model\Config\Backend\Serialized\ArraySerialized - Important: Add only 1 address per cryptocurrency! - - - + Show CryptAPI Logo Magento\Config\Model\Config\Source\Yesno - - - - Attention: - This option will disable the price conversion for ALL cryptocurrencies! If you check this, pricing will not be converted from the currency of your - shop to the cryptocurrency selected by the user, and users will be requested to pay the same value as shown on your shop, regardless of the cryptocurrency selected - + + + This will add an estimation of the blockchain fee to the order value Magento\Config\Model\Config\Source\Yesno - - + + + Set the CryptAPI service fee you want to charge the costumer. Note: Fee you want to charge your costumers (to cover CryptAPI\'s fees fully or partially) + Cryptapi\Cryptapi\Model\Config\Source\FeesList - + Show the QR Code by default Magento\Config\Model\Config\Source\Yesno - + + + + + + Selects the color scheme of the plugin to match your website (Light, Dark and Auto to automatically detect it). + Cryptapi\Cryptapi\Model\Config\Source\QrcodeOptions + + + + Select how you want to show the QR Code to the user. Either select a default to show first, or hide one of them. + Cryptapi\Cryptapi\Model\Config\Source\SchemeList + + + + The system will automatically update the conversion value of the invoices (with real-time data), every X minutes. This feature is helpful whenever a customer takes long time to pay a generated invoice and the selected crypto a volatile coin/token (not stable coin). Warning: Setting this setting to none might create conversion issues, as we advise you to keep it at 5 minutes. + Cryptapi\Cryptapi\Model\Config\Source\RefreshList + + + + Selects the ammount of time the user has to pay for the order. When this time is over, order will be marked as 'Cancelled' and every paid value will be ignored. Notice: If the user still sends money to the generated address, value will still be redirected to you. Warning: We do not advice more than 1 Hour. + Cryptapi\Cryptapi\Model\Config\Source\CancellationList + + + + + + Cryptapi\Cryptapi\Block\Adminhtml\Cryptocurrencies + Magento\Config\Model\Config\Backend\Serialized\ArraySerialized + Important: Add only 1 address per cryptocurrency! + + + Magento\Payment\Model\Config\Source\Allspecificcountries - + Magento\Directory\Model\Config\Source\Country - + + + + Attention: This option will disable the price conversion for ALL cryptocurrencies! If you check this, pricing will not be converted from the currency of your shop to the cryptocurrency selected by the user, and users will be requested to pay the same value as shown on your shop, regardless of the cryptocurrency selected + Magento\Config\Model\Config\Source\Yesno +
diff --git a/etc/config.xml b/etc/config.xml index 461b7d0..e5b884c 100644 --- a/etc/config.xml +++ b/etc/config.xml @@ -4,7 +4,7 @@ - Cryptapi\Cryptapi\Model\Pay + Cryptapi\Cryptapi\Model\Method\CryptapiPayment Cryptocurrency 0 1 @@ -12,6 +12,12 @@ 300 1 0 + without_ammount + auto + 300 + 3600 + none + 1 diff --git a/etc/crontab.xml b/etc/crontab.xml new file mode 100644 index 0000000..7c7881d --- /dev/null +++ b/etc/crontab.xml @@ -0,0 +1,8 @@ + + + + + * * * * * + + + diff --git a/etc/db_schema.xml b/etc/db_schema.xml new file mode 100644 index 0000000..bd83683 --- /dev/null +++ b/etc/db_schema.xml @@ -0,0 +1,14 @@ + + + + + +
+ + +
+ + +
+
diff --git a/etc/events.xml b/etc/events.xml new file mode 100644 index 0000000..c9c5fb7 --- /dev/null +++ b/etc/events.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/etc/extension_attributes.xml b/etc/extension_attributes.xml new file mode 100644 index 0000000..8016c2d --- /dev/null +++ b/etc/extension_attributes.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/etc/fieldset.xml b/etc/fieldset.xml new file mode 100644 index 0000000..067c62a --- /dev/null +++ b/etc/fieldset.xml @@ -0,0 +1,11 @@ + + + +
+ + + +
+
+
diff --git a/etc/frontend/di.xml b/etc/frontend/di.xml index 4159601..f570430 100644 --- a/etc/frontend/di.xml +++ b/etc/frontend/di.xml @@ -5,7 +5,7 @@ - Cryptapi\Cryptapi\Model\ConfigProvider + Cryptapi\Cryptapi\Model\Ui\CryptapiConfigProvider diff --git a/etc/frontend/events.xml b/etc/frontend/events.xml new file mode 100644 index 0000000..6fca4ee --- /dev/null +++ b/etc/frontend/events.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/etc/frontend/routes.xml b/etc/frontend/routes.xml index 42309ff..6fe6ccc 100644 --- a/etc/frontend/routes.xml +++ b/etc/frontend/routes.xml @@ -1,7 +1,8 @@ - + + - + - \ No newline at end of file + diff --git a/etc/module.xml b/etc/module.xml index b6b12b4..21c293a 100644 --- a/etc/module.xml +++ b/etc/module.xml @@ -5,7 +5,7 @@ */ --> - + diff --git a/etc/sales.xml b/etc/sales.xml new file mode 100644 index 0000000..2bb1f30 --- /dev/null +++ b/etc/sales.xml @@ -0,0 +1,9 @@ + + + +
+ + + +
+
diff --git a/lib/CryptAPIHelper.php b/lib/CryptAPIHelper.php index b4fda96..257f2d5 100644 --- a/lib/CryptAPIHelper.php +++ b/lib/CryptAPIHelper.php @@ -173,19 +173,48 @@ public static function process_callback($_get) return $params; } - public static function get_conversion($coin, $total, $currency, $disable_conversion) + public static function get_static_qrcode($address, $coin, $value, $size = 300) + { + if (empty($address)) { + return null; + } + + if (!empty($value)) { + $params = [ + 'address' => $address, + 'value' => $value, + 'size' => $size, + ]; + } else { + $params = [ + 'address' => $address, + 'size' => $size, + ]; + } + + $response = CryptAPIHelper::_request($coin, 'qrcode', $params); + + if ($response->status == 'success') { + return ['qr_code' => $response->qr_code, 'uri' => $response->payment_uri]; + } + + return null; + } + + public static function get_conversion($from, $to, $value, $disable_conversion) { if ($disable_conversion) { - return $total; + return $value; } $params = [ - 'value' => $total, - 'from' => $currency, + 'from' => $from, + 'to' => $to, + 'value' => $value, ]; - $response = CryptAPIHelper::_request($coin, 'convert', $params); + $response = CryptAPIHelper::_request('', 'convert', $params); if ($response->status == 'success') { return $response->value_coin; @@ -194,6 +223,39 @@ public static function get_conversion($coin, $total, $currency, $disable_convers return null; } + public static function get_estimate($coin) + { + + $params = [ + 'addresses' => 1, + 'priority' => 'default', + ]; + + $response = CryptAPIHelper::_request($coin, 'estimate', $params); + + if ($response->status == 'success') { + + return $response->estimated_cost_currency; + } + + return null; + } + + public static function sig_fig($value, $digits) + { + if ($value == 0) { + $decimalPlaces = $digits - 1; + } elseif ($value < 0) { + $decimalPlaces = $digits - floor(log10($value * -1)) - 1; + } else { + $decimalPlaces = $digits - floor(log10($value)) - 1; + } + + $answer = ($decimalPlaces > 0) ? + number_format($value, $decimalPlaces, '.', '') : round($value, $decimalPlaces); + return $answer; + } + private static function _request($coin, $endpoint, $params = [], $assoc = false) { diff --git a/view/adminhtml/layout/sales_order_view.xml b/view/adminhtml/layout/sales_order_view.xml index bd8711e..b70a8be 100644 --- a/view/adminhtml/layout/sales_order_view.xml +++ b/view/adminhtml/layout/sales_order_view.xml @@ -2,11 +2,14 @@ - + + + - \ No newline at end of file + diff --git a/view/frontend/layout/checkout_cart_index.xml b/view/frontend/layout/checkout_cart_index.xml new file mode 100644 index 0000000..e760fba --- /dev/null +++ b/view/frontend/layout/checkout_cart_index.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + Cryptapi_Cryptapi/js/view/checkout/cart/totals/fee + 20 + + Cryptapi_Cryptapi/checkout/cart/totals/fee + Service Fee + + + + + + + + + + + diff --git a/view/frontend/layout/checkout_index_index.xml b/view/frontend/layout/checkout_index_index.xml index cf2c7d2..e9aa58f 100644 --- a/view/frontend/layout/checkout_index_index.xml +++ b/view/frontend/layout/checkout_index_index.xml @@ -17,7 +17,7 @@ - Cryptapi_Cryptapi/js/cryptapi-payments + Cryptapi_Cryptapi/js/view/checkout/payment/cryptapi-payments true @@ -26,6 +26,44 @@ + + + + Cryptapi_Cryptapi/js/view/cryptapi-validation + + + + + + + + + + + + + + + + + Cryptapi_Cryptapi/js/view/checkout/cart/totals/fee + 20 + + Cryptapi_Cryptapi/checkout/cart/totals/fee + Service Fee + + + + + + + + + + Magento_Tax/js/view/checkout/summary/item/details/subtotal + + + @@ -38,5 +76,8 @@ + + + diff --git a/view/frontend/layout/checkout_onepage_success.xml b/view/frontend/layout/checkout_onepage_success.xml index d4c6074..538639f 100644 --- a/view/frontend/layout/checkout_onepage_success.xml +++ b/view/frontend/layout/checkout_onepage_success.xml @@ -5,9 +5,9 @@ - diff --git a/view/frontend/layout/cryptapi_index_payment.xml b/view/frontend/layout/cryptapi_index_payment.xml new file mode 100644 index 0000000..63fe1e5 --- /dev/null +++ b/view/frontend/layout/cryptapi_index_payment.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/view/frontend/layout/sales_order_view.xml b/view/frontend/layout/sales_order_view.xml new file mode 100644 index 0000000..13aaea3 --- /dev/null +++ b/view/frontend/layout/sales_order_view.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/view/frontend/requirejs-config.js b/view/frontend/requirejs-config.js new file mode 100644 index 0000000..fe5842d --- /dev/null +++ b/view/frontend/requirejs-config.js @@ -0,0 +1,7 @@ +var config = { + map: { + '*': { + checkoutjs:'Cryptapi_Cryptapi/js/view/checkout/cart/cart-script', + } + } +}; diff --git a/view/frontend/templates/payment.phtml b/view/frontend/templates/payment.phtml new file mode 100644 index 0000000..dd0269d --- /dev/null +++ b/view/frontend/templates/payment.phtml @@ -0,0 +1,507 @@ +
+ getTemplateValues(); + + if ($values) { + $allowed_to_value = array( + 'btc', + 'eth', + 'bch', + 'ltc', + 'miota', + 'xmr', + ); + + $crypto_allowed_value = false; + + if (in_array($values['crypto_coin'], $allowed_to_value, true)) { + $crypto_allowed_value = true; + } + + $conversion_timer = ((int)$values['last_price_update'] + (int)$values['refresh_value_interval']) - time(); + $cancel_timer = (int)$values['order_timestamp'] + (int)$values['order_cancelation_timeout'] - time(); + ?> + + +
+
+
+
+ +
+
+ + src="data:image/png;base64," alt=""/> + + src="data:image/png;base64," + alt=""/> + +
+ +
+ + + + +
+ +
+ +
+
+ <?= __('QR Code without value') ?> +
+
+ +
+
+ +
+
+
+ + + (" . $values['total'] . ""; ?>) +
+ + + +
+ ' . date('i:s', $conversion_timer) . '' + ); + ?> +
+ +
+ + +
')">
+
+
+ + + ' . date('H:i', $cancel_timer) . '', + ); ?> + + + + + + +
+ + + + +
+
+ + + +

+
+
+ + + +

+
+
+ + + +

+
+
+
+
+ +

+ +
diff --git a/view/frontend/templates/script.phtml b/view/frontend/templates/script.phtml new file mode 100644 index 0000000..4f55774 --- /dev/null +++ b/view/frontend/templates/script.phtml @@ -0,0 +1,7 @@ + diff --git a/view/frontend/templates/success.phtml b/view/frontend/templates/success.phtml deleted file mode 100644 index ddb10e9..0000000 --- a/view/frontend/templates/success.phtml +++ /dev/null @@ -1,230 +0,0 @@ -getTemplateValues(); -if ($values) { - ?> - - -
-
-
-
-
-
- QR Code without value - -
-
- - -
-
-
-
-
- PLEASE SEND - - () -
-
- - -
')">
-
-
- - - - -
- -
-
- - - -

Waiting for payment

-
-
- - - -

Waiting for network confirmation

-
-
- - - -

Waiting for network confirmation

-
-
-
-
- diff --git a/view/frontend/web/css/cryptapi.css b/view/frontend/web/css/cryptapi.css index 4a94dd8..72d1563 100644 --- a/view/frontend/web/css/cryptapi.css +++ b/view/frontend/web/css/cryptapi.css @@ -14,9 +14,10 @@ .ca_details_copy { padding: 7px 10px; margin: 0 5px; - background: #FAFAFA !important; + background: #e0e0e0 !important; font-weight: bold; color: #000 !important; + border: 0 !important; } .ca_details_box { @@ -57,20 +58,20 @@ .ca_qrcode_buttons button { display: inline-block; - width: 50%; + width: 100%; height: 42px; - font-size: 14px; + font-size: 12px; line-height: 1; letter-spacing: .045em; - background-color: #FAFAF9 !important; + background: #E0E0E0 !important; color: #000 !important; border: 0 !important; } .ca_qrcode_buttons button.active { - background: #E0E0E0 !important; + background-color: #FAFAF9 !important; color: #000; - box-shadow: 1px 0 4px rgba(0, 0, 0, 0.25) !important; + box-shadow: none !important; } .ca_details_input { @@ -83,19 +84,25 @@ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.25); } +.ca_details_input .ca_copy .ca_tooltip { + left: 25%; +} + .ca_details_input span { - font-size: 22px; + font-size: 18px; display: flex; align-items: center; justify-content: center; font-weight: 600; height: 100%; - padding: 0 8% + padding: 0 8%; + line-height: 20px; + white-space: normal; + line-break: anywhere; } .ca_copy { position: relative; - border: 0 !important; } .ca_copy .ca_copy_icon_tooltip { @@ -138,9 +145,9 @@ width: 24px; height: 28px; right: 25px; - background-size: contain !important; - background-repeat: no-repeat !important; - background-color: transparent !important; + background-size: contain; + background: transparent url(../files/ca_copy_icon.svg) no-repeat !important; + border: 0 !important; } .ca_copy_icon_tooltip { @@ -155,20 +162,30 @@ left: 10px; width: 50px; height: 50px; - background-image: url(./files/ca_loader.svg); + background-image: url(../files/ca_loader.svg); background-size: contain; top: calc(50% - 25px); bottom: 0; background-repeat: no-repeat; } +.ca_loader_payment_processing { + background-image: url(../files/ca_loader.svg); + background-size: 150%; + background-repeat: no-repeat; + width: 120px; + height: 120px; + margin: 0 auto; + background-position: center; +} + .ca_buttons_container { display: flex; justify-content: center; padding: 13px 0; background-color: #FAFAFA; box-shadow: 0 1px 4px rgba(0, 0, 0, 0.25); - max-width: 250px; + max-width: 180px; width: 100%; margin: 0 auto; border-radius: 50px; @@ -178,16 +195,16 @@ display: flex; justify-content: center; align-items: center; - text-decoration: none; color: #2695D3; font-weight: 600; letter-spacing: .045em; - width: 100px; + width: 140px; height: 38px; border-radius: 50px; border: 3px solid #2695D3; font-size: 13px; margin: 0 6px; + text-decoration: none !important; } .ca_buttons_container a:hover { @@ -195,11 +212,34 @@ background-color: #2695D3; } +.ca_buttons_container a span { + display: none; + white-space: nowrap; +} + +.ca_buttons_container a span.active { + display: block; +} + .ca_branding { margin-top: 26px; text-align: center; } +.ca_branding a { + text-decoration: none; +} + +.ca_branding span { + display: block; + font-size: 10px; + font-weight: 500; + font-style: italic; + margin-bottom: 4px; + color: #757575; + letter-spacing: 1px; +} + .ca_branding img { display: inline-block !important; } @@ -207,14 +247,30 @@ .ca_progress { display: flex; margin-top: 52px; + flex-wrap: wrap; } .ca_progress .ca_progress_icon { position: relative; width: 33.33%; + flex: 0 0 33.33%; text-align: center; } +.ca_notification_text { + display: block; + width: 100%; + flex: 0 0 100%; + margin-top: 35px; + text-align: center; +} + +.ca_notification_text span { + display: block; + margin-bottom: 3px; + font-size: 14px; +} + .ca_progress .ca_progress_icon.waiting_network { position: relative; } @@ -254,25 +310,99 @@ width: 100%; } -.ca_payment_confirmed h2 { +.ca_payment_confirmed h2, +.ca_payment_processing h2, +.ca_payment_cancelled h2 { + display: block; text-align: center; font-size: 28px; text-transform: uppercase; font-weight: 700; margin-top: 26px; + margin-bottom: 15px; } -.ca_payment_confirmed .ca_payment_confirmed_icon { +.ca_payment_confirmed .ca_payment_confirmed_icon, +.ca_payment_processing .ca_payment_confirmed_icon, +.ca_payment_cancelled .ca_payment_cancelled_icon { text-align: center !important; + margin-bottom: 15px; } -.ca_payment_confirmed .ca_payment_confirmed_icon svg { +.ca_payment_confirmed .ca_payment_confirmed_icon svg, +.ca_payment_cancelled .ca_payment_cancelled_icon svg { max-width: 100% !important; width: 100px !important; margin: 0 auto !important; display: inline-block !important; } +.ca_payment_processing h5 { + text-align: center; + font-size: 13px; + text-transform: uppercase; + font-weight: 600; + margin-top: -10px; +} + +.ca_progress .ca_progress_icon.done { + padding: unset !important; + margin: unset !important; +} + +.ca_payment_notification { + text-align: center; + margin-top: 15px; + font-size: 14px; +} + +.ca_history .ca_history_fill { + border-radius: 15px !important; + font-size: 14px !important; + text-align: center !important; + width: 70% !important; + margin: 20px auto !important; + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.25) !important; + overflow: hidden !important; + border-collapse: collapse !important; +} + +.ca_history .ca_history_fill th { + border: 0 !important; + padding: 15px !important; + text-align: center !important; + border-bottom: 1px solid #ccc !important; + vertical-align: middle !important; +} + +.ca_history .ca_history_fill td { + border: 0 !important; + overflow: auto !important; + text-align: center !important; + padding: 15px !important; + vertical-align: middle !important; +} + +.ca_history_date { + font-size: 10px !important; + display: block !important; +} + +.ca_notification_cancel { + display: block; + text-align: center; + font-size: 14px; + margin-bottom: 25px; +} + +.ca_time_refresh { + display: block; + text-align: center; + margin: 10px 0 20px; + font-size: 14px; + font-weight: 500; +} + @media screen and (max-width: 768.98px) { .ca_details_input span { font-size: 16px; @@ -287,16 +417,16 @@ } .ca_progress .ca_progress_icon.waiting_network:before, - .ca_progress .ca_progress_icon.waiting_network:after { - width: 60px; + .ca_progress .ca_progress_icon.waiting_payment:before { + width: 60px !important; } .ca_progress .ca_progress_icon.waiting_network:before { - left: -22%; + right: -17%; } - .ca_progress .ca_progress_icon.waiting_network:after { - right: -22%; + .ca_progress .ca_progress_icon.waiting_payment:before { + right: -17%; } } @@ -307,7 +437,8 @@ } .ca_details_input span { - padding: 0 50px 0 8%; + padding: 0 60px 0 30px; + font-size: 14px; } .ca_progress { @@ -316,11 +447,12 @@ .ca_progress .ca_progress_icon { width: 100%; + flex: 0 0 100%; } .ca_progress .ca_progress_icon.waiting_payment:before, .ca_progress .ca_progress_icon.waiting_network:before { - width: 2px; + width: 2px !important; height: 40px; left: 0; right: 0; @@ -339,9 +471,134 @@ .ca_loader { top: unset; - bottom: -243px; + bottom: -56px; right: 0; left: 0; margin: 0 auto; } + + .ca_copy_icon { + right: 0; + } +} + +.ca_payment-panel.dark { + color: #fafafa; +} + +.ca_payment-panel.dark .ca_details_input { + background-color: #424242 !important; + color: #FAFAFA !important; +} + +.ca_payment-panel.dark .ca_loader { + filter: invert(1); +} + +.ca_payment-panel.dark .ca_details_copy { + background-color: #424242 !important; + color: #FAFAFA !important; +} + +.ca_payment-panel.dark .ca_copy .ca_copy_icon_tooltip { + background-color: rgba(245, 245, 245, 1) !important; + color: #121212 !important; +} + +.ca_payment-panel.dark .ca_copy .ca_copy_icon_tooltip::after { + border-color: rgba(245, 245, 245, 1) transparent transparent transparent !important; +} + +.ca_payment-panel.dark .ca_buttons_container { + background-color: #424242 !important; + color: #FAFAFA !important; +} + +.ca_payment-panel.dark .ca_buttons_container a { + color: #6ac5ff !important; + border: 3px solid #6ac5ff !important; +} + +.ca_payment-panel.dark .ca_buttons_container a:hover { + background-color: #6ac5ff !important; + color: #212121 !important; +} + +.ca_payment-panel.dark .ca_copy_icon { + background: transparent url(../files/ca_copy_icon_dark.svg) no-repeat !important +} + +.ca_payment-panel.dark .ca_progress_icon svg path { + fill: #6ac5ff !important; +} + +.ca_payment-panel.dark .ca_progress .ca_progress_icon.waiting_payment:before, +.ca_payment-panel.dark .ca_progress .ca_progress_icon.waiting_network:before { + background: #6ac5ff !important; +} + +.ca_payment-panel.dark .ca_payment_confirmed h2, +.ca_payment-panel.dark .ca_payment_cancelled h2 { + color: #FFF; +} + +@media (prefers-color-scheme: dark) { + .ca_payment-panel.auto { + color: #fafafa; + } + + .ca_payment-panel.auto .ca_details_input { + background-color: #424242 !important; + color: #FAFAFA !important; + } + + .ca_payment-panel.auto .ca_loader { + filter: invert(1); + } + + .ca_payment-panel.auto .ca_details_copy { + background-color: #424242 !important; + color: #FAFAFA !important; + } + + .ca_payment-panel.auto .ca_copy .ca_copy_icon_tooltip { + background-color: rgba(245, 245, 245, 1) !important; + color: #121212 !important; + } + + .ca_payment-panel.auto .ca_copy .ca_copy_icon_tooltip::after { + border-color: rgba(245, 245, 245, 1) transparent transparent transparent !important; + } + + .ca_payment-panel.auto .ca_buttons_container { + background-color: #424242 !important; + color: #FAFAFA !important; + } + + .ca_payment-panel.auto .ca_buttons_container a { + color: #6ac5ff !important; + border: 3px solid #6ac5ff !important; + } + + .ca_payment-panel.auto .ca_buttons_container a:hover { + background-color: #6ac5ff !important; + color: #212121 !important; + } + + .ca_payment-panel.auto .ca_copy_icon { + background: transparent url(../files/ca_copy_icon_dark.svg) no-repeat !important + } + + .ca_payment-panel.auto .ca_progress_icon svg path { + fill: #6ac5ff !important; + } + + .ca_payment-panel.auto .ca_progress .ca_progress_icon.waiting_payment:before, + .ca_payment-panel.auto .ca_progress .ca_progress_icon.waiting_network:before { + background: #6ac5ff !important; + } + + .ca_payment-panel.auto .ca_payment_confirmed h2, .ca_payment-panel.auto .ca_payment_cancelled h2 { + color: #FFF; + } } diff --git a/view/frontend/web/images/200_logo_ca.png b/view/frontend/web/files/200_logo_ca.png similarity index 100% rename from view/frontend/web/images/200_logo_ca.png rename to view/frontend/web/files/200_logo_ca.png diff --git a/view/frontend/web/images/ca_copy_icon.svg b/view/frontend/web/files/ca_copy_icon.svg similarity index 100% rename from view/frontend/web/images/ca_copy_icon.svg rename to view/frontend/web/files/ca_copy_icon.svg diff --git a/view/frontend/web/files/ca_copy_icon_dark.svg b/view/frontend/web/files/ca_copy_icon_dark.svg new file mode 100644 index 0000000..fa5eb65 --- /dev/null +++ b/view/frontend/web/files/ca_copy_icon_dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/view/frontend/web/images/ca_loader.svg b/view/frontend/web/files/ca_loader.svg similarity index 100% rename from view/frontend/web/images/ca_loader.svg rename to view/frontend/web/files/ca_loader.svg diff --git a/view/frontend/web/images/logo_ca.png b/view/frontend/web/files/logo_ca.png similarity index 100% rename from view/frontend/web/images/logo_ca.png rename to view/frontend/web/files/logo_ca.png diff --git a/view/frontend/web/js/model/validate-cryptocurrency.js b/view/frontend/web/js/model/validate-cryptocurrency.js new file mode 100644 index 0000000..165400b --- /dev/null +++ b/view/frontend/web/js/model/validate-cryptocurrency.js @@ -0,0 +1,28 @@ +define( + [ + 'mage/translate', + 'Magento_Ui/js/model/messageList' + ], + function ($t, messageList) { + 'use strict'; + return { + validate: function () { + var isValid = false; + + if (document.getElementById("cryptapi_payment_cryptocurrency_id").value) { + isValid = true; + } + + if(!document.getElementById("cryptapi").checked) { + isValid = true; + } + + if (!isValid) { + messageList.addErrorMessage({message: $t('Please select a cryptocurrency.')}); + } + + return isValid; + } + } + } +); diff --git a/view/frontend/web/js/view/checkout/cart/cart-script.js b/view/frontend/web/js/view/checkout/cart/cart-script.js new file mode 100644 index 0000000..914ca69 --- /dev/null +++ b/view/frontend/web/js/view/checkout/cart/cart-script.js @@ -0,0 +1,37 @@ +require([ + 'jquery', + 'mage/url', + 'Magento_Checkout/js/model/quote', + 'Magento_Checkout/js/model/cart/totals-processor/default' +], function ($, url, quote, totalsDefaultProvider) { + 'use strict'; + $.getJSON(url.build('cryptapi/index/cartquote') + '?selected=', function (data) { + totalsDefaultProvider.estimateTotals(quote.shippingAddress()); + }); + + $('body').on('change', function () { + + var cryptoSelector = $('#cryptapi_payment_cryptocurrency_id'); + + var linkUrl = url.build('cryptapi/index/cartquote'); + + var feeContainer = $('.totals.fee.excl'); + + setInterval(function () { + if ($('body').attr('aria-busy') === 'false') { + if (quote.paymentMethod._latestValue.method === 'cryptapi' && parseFloat($('.totals.fee.excl .price').html().replace(/\D/g, '')) > 0) { + feeContainer.show(); + } else { + feeContainer.hide(); + } + } + }, 1000); + + cryptoSelector.unbind('change'); + cryptoSelector.on('change', function () { + $.getJSON(linkUrl + '?selected=' + cryptoSelector.val(), function (data) { + totalsDefaultProvider.estimateTotals(quote.shippingAddress()); + }); + }); + }); +}); diff --git a/view/frontend/web/js/view/checkout/cart/totals/fee.js b/view/frontend/web/js/view/checkout/cart/totals/fee.js new file mode 100644 index 0000000..bd6c415 --- /dev/null +++ b/view/frontend/web/js/view/checkout/cart/totals/fee.js @@ -0,0 +1,22 @@ +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +define( + [ + 'Cryptapi_Cryptapi/js/view/checkout/summary/fee' + ], + function (Component) { + 'use strict'; + + return Component.extend({ + + /** + * @override + */ + isDisplayed: function () { + return true; + } + }); + } +); diff --git a/view/frontend/web/js/cryptapi-payments.js b/view/frontend/web/js/view/checkout/payment/cryptapi-payments.js similarity index 65% rename from view/frontend/web/js/cryptapi-payments.js rename to view/frontend/web/js/view/checkout/payment/cryptapi-payments.js index 4e8a491..340ea29 100644 --- a/view/frontend/web/js/cryptapi-payments.js +++ b/view/frontend/web/js/view/checkout/payment/cryptapi-payments.js @@ -1,20 +1,20 @@ - define( [ 'uiComponent', - 'Magento_Checkout/js/model/payment/renderer-list' + 'Magento_Checkout/js/model/payment/renderer-list', ], function ( Component, rendererList ) { 'use strict'; + rendererList.push( { type: 'cryptapi', - component: 'Cryptapi_Cryptapi/js/cryptapi' + component: 'Cryptapi_Cryptapi/js/view/checkout/payment/cryptapi' } ); return Component.extend({}); } -); \ No newline at end of file +); diff --git a/view/frontend/web/js/cryptapi.js b/view/frontend/web/js/view/checkout/payment/cryptapi.js similarity index 80% rename from view/frontend/web/js/cryptapi.js rename to view/frontend/web/js/view/checkout/payment/cryptapi.js index e403178..e05a4f8 100644 --- a/view/frontend/web/js/cryptapi.js +++ b/view/frontend/web/js/view/checkout/payment/cryptapi.js @@ -6,7 +6,7 @@ define([ return Component.extend({ defaults: { - template: 'Cryptapi_Cryptapi/cryptapi' + template: 'Cryptapi_Cryptapi/checkout/payment/cryptapi' }, getCryptocurrencies: function () { @@ -27,9 +27,7 @@ define([ }, getSelectedCoin() { - var selected = document.getElementById("cryptapi_payment_cryptocurrency_id").value; - - return selected; + return document.getElementById("cryptapi_payment_cryptocurrency_id").value; } }); }); diff --git a/view/frontend/web/js/view/checkout/summary/fee.js b/view/frontend/web/js/view/checkout/summary/fee.js new file mode 100644 index 0000000..432bafa --- /dev/null +++ b/view/frontend/web/js/view/checkout/summary/fee.js @@ -0,0 +1,37 @@ +/*jshint browser:true jquery:true*/ +/*global alert*/ +define( + [ + 'Magento_Checkout/js/view/summary/abstract-total', + 'Magento_Checkout/js/model/quote', + 'Magento_Catalog/js/price-utils', + 'Magento_Checkout/js/model/totals', + 'Magento_Checkout/js/model/cart/totals-processor/default' + ], + function (Component, quote, priceUtils, totals) { + "use strict"; + return Component.extend({ + defaults: { + isFullTaxSummaryDisplayed: window.checkoutConfig.isFullTaxSummaryDisplayed || false, + template: 'Cryptapi_Cryptapi/checkout/summary/fee' + }, + totals: quote.getTotals(), + isTaxDisplayedInGrandTotal: window.checkoutConfig.includeTaxInGrandTotal || false, + + getValue: function () { + var price = 0; + if (this.totals()) { + price = totals.getSegment('fee').value; + } + return this.getFormattedPrice(price); + }, + getBaseValue: function () { + var price = 0; + if (this.totals()) { + price = this.totals().base_fee; + } + return priceUtils.formatPrice(price, quote.getBasePriceFormat()); + } + }); + } +); diff --git a/view/frontend/web/js/view/cryptapi-validation.js b/view/frontend/web/js/view/cryptapi-validation.js new file mode 100644 index 0000000..e42decd --- /dev/null +++ b/view/frontend/web/js/view/cryptapi-validation.js @@ -0,0 +1,12 @@ +define( + [ + 'uiComponent', + 'Magento_Checkout/js/model/payment/additional-validators', + 'Cryptapi_Cryptapi/js/model/validate-cryptocurrency' + ], + function (Component, additionalValidators, validateCryptocurrency) { + 'use strict'; + additionalValidators.registerValidator(validateCryptocurrency); + return Component.extend({}); + } +); diff --git a/view/frontend/web/template/checkout/cart/totals/fee.html b/view/frontend/web/template/checkout/cart/totals/fee.html new file mode 100644 index 0000000..2aa1c42 --- /dev/null +++ b/view/frontend/web/template/checkout/cart/totals/fee.html @@ -0,0 +1,15 @@ + + + + + + + + + + diff --git a/view/frontend/web/template/cryptapi.html b/view/frontend/web/template/checkout/payment/cryptapi.html similarity index 71% rename from view/frontend/web/template/cryptapi.html rename to view/frontend/web/template/checkout/payment/cryptapi.html index 53ad420..d0f132a 100644 --- a/view/frontend/web/template/cryptapi.html +++ b/view/frontend/web/template/checkout/payment/cryptapi.html @@ -1,31 +1,29 @@
- - - +
+ + +

-
-
- - -
+
-
diff --git a/view/frontend/web/template/checkout/summary/fee.html b/view/frontend/web/template/checkout/summary/fee.html new file mode 100644 index 0000000..cc34e63 --- /dev/null +++ b/view/frontend/web/template/checkout/summary/fee.html @@ -0,0 +1,20 @@ + + + + + + + + + + + + + +