mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-05-24 02:14:21 -04:00
update UI and make refreshing price possible
This commit is contained in:
parent
9044f3239c
commit
34830bc9ab
32
app/Http/Controllers/Gateways/BlockonomicsController.php
Normal file
32
app/Http/Controllers/Gateways/BlockonomicsController.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\Gateways;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request; // Import the Request class
|
||||
use Illuminate\Support\Facades\Http; // Import the Http facade
|
||||
|
||||
class BlockonomicsController extends Controller
|
||||
{
|
||||
public function getBTCPrice(Request $request)
|
||||
{
|
||||
$currency = $request->query('currency');
|
||||
$response = Http::get("https://www.blockonomics.co/api/price?currency={$currency}");
|
||||
|
||||
if ($response->successful()) {
|
||||
return response()->json(['price' => $response->json('price')]);
|
||||
}
|
||||
|
||||
return response()->json(['error' => 'Unable to fetch BTC price'], 500);
|
||||
}
|
||||
}
|
@ -111,10 +111,12 @@ class Blockonomics implements MethodInterface
|
||||
$data['gateway'] = $this->blockonomics;
|
||||
$data['amount'] = $data['total']['amount_with_fee'];
|
||||
$data['currency'] = $this->blockonomics->client->getCurrencyCode();
|
||||
$btc_amount = $data['amount'] / $this->getBTCPrice();
|
||||
$btc_price = $this->getBTCPrice();
|
||||
$btc_amount = $data['amount'] / $btc_price;
|
||||
$data['btc_amount'] = number_format($btc_amount, 10, '.', '');
|
||||
$btc_address = $this->getBTCAddress();
|
||||
$data['btc_address'] = $btc_address;
|
||||
$data['btc_price'] = $btc_price;
|
||||
$data['invoice_id'] = $_invoice->invoice_id;
|
||||
$data['invoice_number'] = $_invoice->invoice_number;
|
||||
$data['end_time'] = $this->getTenMinutesCountDownEndTime();
|
||||
|
@ -10,21 +10,24 @@
|
||||
The <a id="link" href="{{ $invoice_redirect_url }}" target="_blank">invoice</a> will be marked as paid automatically once the payment is confirmed.
|
||||
</div> -->
|
||||
<div class="initial-state">
|
||||
<div class="invoice-number">Invoice #{{$invoice_number}}</div>
|
||||
<div>To pay, send bitcoin to this address:</div>
|
||||
<div class="invoice-info-wrapper">
|
||||
<div class="invoice-number">Invoice #{{$invoice_number}}</div>
|
||||
<div class="invoice-amount">{{$amount}} {{$currency}}</div>
|
||||
</div>
|
||||
<b>To pay, send bitcoin to this address:</b>
|
||||
<span class="input-wrapper">
|
||||
<input class="full-width-input" id="btcAddress" value="{{$btc_address}}" readonly>
|
||||
<img onclick='copyToClipboard("{{$btc_address}}", this)' src="{{ 'data:image/svg+xml;base64,' . base64_encode('<svg width="22" height="24" viewBox="0 0 22 24" fill="none" xmlns="http://www.w3.org/2000/svg" ><path d="M15.5 1H3.5C2.4 1 1.5 1.9 1.5 3V17H3.5V3H15.5V1ZM18.5 5H7.5C6.4 5 5.5 5.9 5.5 7V21C5.5 22.1 6.4 23 7.5 23H18.5C19.6 23 20.5 22.1 20.5 21V7C20.5 5.9 19.6 5 18.5 5ZM18.5 21H7.5V7H18.5V21Z" fill="#000"/></svg>') }}" class="icon" alt="Copy Icon">
|
||||
<input onclick='copyToClipboard("{{$btc_address}}", this, true)' class="full-width-input" id="btcAddress" value="{{$btc_address}}" readonly>
|
||||
<img onclick='copyToClipboard("{{$btc_address}}", this)' style="margin-right: 32px;" src="{{ 'data:image/svg+xml;base64,' . base64_encode('<svg width="22" height="24" viewBox="0 0 22 24" fill="none" xmlns="http://www.w3.org/2000/svg" ><path d="M15.5 1H3.5C2.4 1 1.5 1.9 1.5 3V17H3.5V3H15.5V1ZM18.5 5H7.5C6.4 5 5.5 5.9 5.5 7V21C5.5 22.1 6.4 23 7.5 23H18.5C19.6 23 20.5 22.1 20.5 21V7C20.5 5.9 19.6 5 18.5 5ZM18.5 21H7.5V7H18.5V21Z" fill="#000"/></svg>') }}" class="icon" alt="Copy Icon">
|
||||
</span>
|
||||
<div>Amount of bitcoin (BTC) to send:</div>
|
||||
<b>Amount of bitcoin (BTC) to send:</b>
|
||||
<span class="input-wrapper">
|
||||
<div class="full-width-input">
|
||||
<div class="full-width-input" id="btc-amount" onclick='copyToClipboard("{{$btc_amount}}", this, true)'>
|
||||
{{$btc_amount}}
|
||||
</div>
|
||||
<img onclick='copyToClipboard("{{$btc_amount}}", this)' src="{{ 'data:image/svg+xml;base64,' . base64_encode('<svg width="22" height="24" viewBox="0 0 22 24" fill="none" xmlns="http://www.w3.org/2000/svg" ><path d="M15.5 1H3.5C2.4 1 1.5 1.9 1.5 3V17H3.5V3H15.5V1ZM18.5 5H7.5C6.4 5 5.5 5.9 5.5 7V21C5.5 22.1 6.4 23 7.5 23H18.5C19.6 23 20.5 22.1 20.5 21V7C20.5 5.9 19.6 5 18.5 5ZM18.5 21H7.5V7H18.5V21Z" fill="#000"/></svg>') }}" class="icon" alt="Copy Icon">
|
||||
<span class="icon-refresh"></span>
|
||||
<span class="icon-refresh" onclick='refreshBTCPrice()'></span>
|
||||
</span>
|
||||
<span>1 BTC = {{$amount}} {{$currency}}, updates in <span id="countdown"></span></span>
|
||||
<div class="btc-value">1 BTC = {{$btc_price}} {{$currency}}, updates in <span id="countdown"></span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -41,19 +44,22 @@
|
||||
</form>
|
||||
|
||||
<script>
|
||||
// Get the end time as a Unix timestamp (seconds)
|
||||
const endTimeUnix = {{ $end_time }};
|
||||
console.log("End time (Unix timestamp):", endTimeUnix); // For debugging
|
||||
|
||||
// Convert Unix timestamp to milliseconds for JavaScript Date
|
||||
const countDownDate = endTimeUnix * 1000;
|
||||
// Define the startTimer function
|
||||
function startTimer(seconds) {
|
||||
const countDownDate = new Date().getTime() + seconds * 1000;
|
||||
document.getElementById("countdown").innerHTML = "10" + ":" + "00" + " min";
|
||||
|
||||
function updateCountdown() {
|
||||
const now = new Date().getTime();
|
||||
const distance = countDownDate - now;
|
||||
|
||||
const isRefreshing = document.getElementsByClassName("btc-value")[0].innerHTML.includes("Refreshing");
|
||||
if (isRefreshing) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (distance < 0) {
|
||||
document.getElementById("countdown").innerHTML = "EXPIRED";
|
||||
refreshBTCPrice();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -64,10 +70,18 @@
|
||||
|
||||
document.getElementById("countdown").innerHTML = formattedMinutes + ":" + formattedSeconds + " min";
|
||||
}
|
||||
setInterval(updateCountdown, 1000);
|
||||
|
||||
// Clear any existing intervals to avoid multiple intervals running simultaneously
|
||||
clearInterval(window.countdownInterval);
|
||||
window.countdownInterval = setInterval(updateCountdown, 1000);
|
||||
}
|
||||
// Call startTimer initially with the desired countdown time
|
||||
startTimer(600); // Start the timer for 10 minutes (600 seconds)
|
||||
</script>
|
||||
<script>
|
||||
function copyToClipboard(text, element) {
|
||||
function copyToClipboard(text, passedElement, shouldGrabNextElementSibling) {
|
||||
const element = shouldGrabNextElementSibling ? passedElement.nextElementSibling : passedElement;
|
||||
const originalIcon = element.src; // Store the original icon
|
||||
const tempInput = document.createElement("input");
|
||||
tempInput.value = text;
|
||||
document.body.appendChild(tempInput);
|
||||
@ -75,10 +89,7 @@
|
||||
document.execCommand("copy");
|
||||
document.body.removeChild(tempInput);
|
||||
|
||||
// Change the icon to the check icon
|
||||
const iconElement = element.querySelector('.icon');
|
||||
const originalIcon = iconElement.src;
|
||||
iconElement.src = 'data:image/svg+xml;base64,' + btoa(`
|
||||
element.src = 'data:image/svg+xml;base64,' + btoa(`
|
||||
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.04706 14C4.04706 8.55609 8.46025 4.1429 13.9042 4.1429C19.3482 4.1429 23.7613 8.55609 23.7613 14C23.7613 19.444 19.3482 23.8572 13.9042 23.8572C8.46025 23.8572 4.04706 19.444 4.04706 14Z" stroke="#000" stroke-width="2.19048" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9.52325 14L12.809 17.2858L18.2852 11.8096" stroke="#000" stroke-width="2.19048" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
@ -87,7 +98,7 @@
|
||||
|
||||
// Change the icon back to the original after 5 seconds
|
||||
setTimeout(() => {
|
||||
iconElement.src = originalIcon;
|
||||
element.src = originalIcon;
|
||||
}, 5000);
|
||||
|
||||
// Optionally, you can show a message to the user
|
||||
@ -95,6 +106,40 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
async function getBTCPrice() {
|
||||
try {
|
||||
const response = await fetch(`/api/v1/get-btc-price?currency={{$currency}}`); // New endpoint to call server-side function
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok');
|
||||
}
|
||||
const data = await response.json();
|
||||
return data.price;
|
||||
} catch (error) {
|
||||
console.error('There was a problem with the fetch operation:', error);
|
||||
// Handle error appropriately
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshBTCPrice() {
|
||||
const refreshIcon = document.querySelector('.icon-refresh');
|
||||
refreshIcon.classList.add('rotating');
|
||||
document.getElementsByClassName("btc-value")[0].innerHTML = "Refreshing...";
|
||||
|
||||
try {
|
||||
const newPrice = await getBTCPrice();
|
||||
if (newPrice) {
|
||||
document.getElementsByClassName("btc-value")[0].innerHTML = "1 BTC = " + (newPrice || "N/A") + " {{$currency}}, updates in <span id='countdown'></span>";
|
||||
const newBtcAmount = ({{$amount}} / newPrice).toFixed(10);
|
||||
document.getElementById('btc-amount').textContent = newBtcAmount;
|
||||
startTimer(600); // Restart timer for 10 minutes (600 seconds)
|
||||
}
|
||||
} finally {
|
||||
refreshIcon.classList.remove('rotating');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
var webSocketUrl = "{{ $websocket_url }}";
|
||||
const ws = new WebSocket(webSocketUrl);
|
||||
@ -127,23 +172,39 @@
|
||||
|
||||
|
||||
<style type="text/css">
|
||||
.invoice-number {
|
||||
.invoice-info-wrapper {
|
||||
width: 100%;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 20px;
|
||||
font-weight: bold;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.invoice-number {
|
||||
width: 50%;
|
||||
float: left;
|
||||
text-align: left;
|
||||
}
|
||||
.invoice-amount {
|
||||
width: 50%;
|
||||
float: right;
|
||||
text-align: right;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.blockonomics-payment-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
}
|
||||
.initial-state {
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
padding: 12px;
|
||||
width: 100%;
|
||||
padding: 24px;
|
||||
}
|
||||
.input-wrapper {
|
||||
display: flex;
|
||||
@ -153,22 +214,43 @@
|
||||
}
|
||||
.full-width-input {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
margin: 10px;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
text-align: left;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
font-weight: bold;
|
||||
color: #444;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
}
|
||||
.icon {
|
||||
cursor: pointer;
|
||||
width: 28px;
|
||||
}
|
||||
.icon-refresh::before {
|
||||
content: '\27F3';
|
||||
cursor: pointer;
|
||||
margin-left: 5px;
|
||||
width: 28px;
|
||||
display: flex;
|
||||
font-size: 32px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.btc-value {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
@keyframes rotating {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
.rotating {
|
||||
animation: rotating 2s linear infinite;
|
||||
}
|
||||
/* .progress-message {
|
||||
display: none;
|
||||
|
@ -12,6 +12,7 @@
|
||||
*/
|
||||
use App\Http\Controllers\SubscriptionStepsController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use App\Http\Controllers\Gateways\BlockonomicsController;
|
||||
use App\Http\Controllers\BaseController;
|
||||
use App\Http\Controllers\BrevoController;
|
||||
use App\Http\Controllers\PingController;
|
||||
@ -460,5 +461,6 @@ Route::post('api/v1/yodlee/balance', [YodleeController::class, 'balanceWebhook']
|
||||
Route::get('api/v1/protected_download/{hash}', [ProtectedDownloadController::class, 'index'])->name('protected_download')->middleware('throttle:300,1');
|
||||
Route::post('api/v1/ppcp/webhook', [PayPalPPCPPaymentDriver::class, 'processWebhookRequest'])->middleware('throttle:1000,1');
|
||||
Route::get('api/v1/blockonomics/callback', [BlockonomicsPaymentDriver::class, 'processWebhookRequest'])->middleware('throttle:1000,1');
|
||||
Route::get('api/v1/get-btc-price', [BlockonomicsController::class, 'getBTCPrice'])->middleware('throttle:1000,1');
|
||||
|
||||
Route::fallback([BaseController::class, 'notFound'])->middleware('throttle:404');
|
||||
|
@ -5631,5 +5631,14 @@
|
||||
"method": [
|
||||
"GET"
|
||||
]
|
||||
},
|
||||
"get-btc-price": {
|
||||
"name": "get-btc-price",
|
||||
"domain": null,
|
||||
"action": "App\\Http\\Controllers\\BlockonomicsController@getBTCPrice",
|
||||
"uri": "api/v1/get-btc-price",
|
||||
"method": [
|
||||
"GET"
|
||||
]
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user