1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-08 12:12:48 +01:00

Revert "Add Blockonomics payment capabilities"

This commit is contained in:
David Bomba 2024-09-19 08:11:20 +10:00 committed by GitHub
parent 8247873fd3
commit 5cdc036c74
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 10 additions and 756 deletions

View File

@ -1,58 +0,0 @@
<?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
use BaconQrCode\Renderer\Image\SvgImageBackEnd;
use BaconQrCode\Renderer\ImageRenderer;
use BaconQrCode\Renderer\RendererStyle\RendererStyle;
use BaconQrCode\Writer;
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);
}
public function getQRCode(Request $request)
{
$qr_string = $request->query('qr_string');
$svg = $this->getPaymentQrCodeRaw($qr_string);
return response($svg)->header('Content-Type', 'image/svg+xml');
}
private function getPaymentQrCodeRaw($qr_string)
{
$renderer = new ImageRenderer(
new RendererStyle(150, margin: 0),
new SvgImageBackEnd()
);
$writer = new Writer($renderer);
$qr = $writer->writeString($qr_string, 'utf-8');
return $qr;
}
}

View File

@ -154,9 +154,8 @@ class CompanyGateway extends BaseModel
'b9886f9257f0c6ee7c302f1c74475f6c' => 321, //GoCardless
'hxd6gwg3ekb9tb3v9lptgx1mqyg69zu9' => 322,
'80af24a6a691230bbec33e930ab40666' => 323,
'vpyfbmdrkqcicpkjqdusgjfluebftuva' => 324, //BTCPay
'vpyfbmdrkqcicpkjqdusgjfluebftuva' => 324, //BTPay
'91be24c7b792230bced33e930ac61676' => 325,
'wbhf02us6owgo7p4nfjd0ymssdshks4d' => 326, //Blockonomics
];
protected $touches = [];

View File

@ -25,7 +25,7 @@ namespace App\Models;
* @property bool $is_offsite
* @property bool $is_secure
* @property object|null|string $fields
* @property string|int $default_gateway_type_id
* @property string $default_gateway_type_id
* @property int|null $created_at
* @property int|null $updated_at
* @property-read mixed $options
@ -107,9 +107,7 @@ class Gateway extends StaticModel
$link = 'https://docs.btcpayserver.org/InvoiceNinja/';
} elseif ($this->id == 63) {
$link = 'https://rotessa.com';
} elseif ($this->id == 64) {
$link = 'https://blockonomics.co';
}
}
return $link;
}
@ -228,8 +226,8 @@ class Gateway extends StaticModel
return [
GatewayType::CRYPTO => ['refund' => true, 'token_billing' => false, 'webhooks' => ['confirmed', 'paid_out', 'failed', 'fulfilled']],
]; //BTCPay
case 63:
return [
case 63:
return [
GatewayType::BANK_TRANSFER => [
'refund' => false,
'token_billing' => true,
@ -237,10 +235,6 @@ class Gateway extends StaticModel
],
GatewayType::ACSS => ['refund' => false, 'token_billing' => true, 'webhooks' => []]
]; // Rotessa
case 65:
return [
GatewayType::CRYPTO => ['refund' => true, 'token_billing' => false, 'webhooks' => ['confirmed', 'paid_out', 'failed', 'fulfilled']],
]; //Blockonomics
default:
return [];
}

View File

@ -81,7 +81,6 @@ class PaymentType extends StaticModel
public const STRIPE_BANK_TRANSFER = 50;
public const CASH_APP = 51;
public const PAY_LATER = 52;
public const BLOCKONOMICS = 64;
public array $type_names = [
self::BANK_TRANSFER => 'payment_type_Bank Transfer',
@ -130,7 +129,6 @@ class PaymentType extends StaticModel
self::CASH_APP => 'payment_type_Cash App',
self::VENMO => 'payment_type_Venmo',
self::PAY_LATER => 'payment_type_Pay Later',
self::BLOCKONOMICS => 'payment_type_Blockonomics',
];
public static function parseCardType($cardName)

View File

@ -153,9 +153,7 @@ class SystemLog extends Model
public const TYPE_BTC_PAY = 324;
public const TYPE_ROTESSA = 325;
public const TYPE_BLOCKONOMICS = 326;
public const TYPE_QUOTA_EXCEEDED = 400;
public const TYPE_UPSTREAM_FAILURE = 401;

View File

@ -1,138 +0,0 @@
<?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\PaymentDrivers\Blockonomics;
use App\Models\Payment;
use App\Models\PaymentType;
use App\Models\GatewayType;
use App\Models\SystemLog;
use App\PaymentDrivers\BlockonomicsPaymentDriver;
use App\Utils\Traits\MakesHash;
use App\PaymentDrivers\Common\MethodInterface;
use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
use App\Exceptions\PaymentFailed;
use App\Jobs\Util\SystemLogger;
use App\Jobs\Mail\PaymentFailureMailer;
use Illuminate\Support\Facades\Http;
class Blockonomics implements MethodInterface
{
use MakesHash;
public function __construct(public BlockonomicsPaymentDriver $blockonomics)
{
}
public function authorizeView($data)
{
}
public function authorizeRequest($request)
{
}
public function authorizeResponse($request)
{
}
public function getBTCAddress(): string
{
$api_key = $this->blockonomics->api_key;
// $params = config('ninja.environment') == 'development' ? '?reset=1' : '';
$url = 'https://www.blockonomics.co/api/new_address';
$r = Http::withToken($api_key)
->post($url, []);
if($r->successful())
return $r->object()->address ?? 'Something went wrong';
return $r->object()->message ?? 'Something went wrong';
}
public function getBTCPrice()
{
$r = Http::get('https://www.blockonomics.co/api/price', ['currency' => $this->blockonomics->client->getCurrencyCode()]);
return $r->successful() ? $r->object()->price : 'Something went wrong';
}
public function paymentView($data)
{
$btc_price = $this->getBTCPrice();
$btc_address = $this->getBTCAddress();
$fiat_amount = $data['total']['amount_with_fee'];
$btc_amount = $fiat_amount / $btc_price;
$_invoice = collect($this->blockonomics->payment_hash->data->invoices)->first();
$data['gateway'] = $this->blockonomics;
$data['company_gateway_id'] = $this->blockonomics->getCompanyGatewayId();
$data['amount'] = $fiat_amount;
$data['currency'] = $this->blockonomics->client->getCurrencyCode();
$data['btc_amount'] = number_format($btc_amount, 10, '.', '');
$data['btc_address'] = $btc_address;
$data['btc_price'] = $btc_price;
$data['invoice_number'] = $_invoice->invoice_number;
return render('gateways.blockonomics.pay', $data);
}
public function paymentResponse(PaymentResponseRequest $request)
{
$request->validate([
'payment_hash' => ['required'],
'amount' => ['required'],
'currency' => ['required'],
'txid' => ['required'],
'payment_method_id' => ['required'],
]);
try {
$data = [];
$fiat_amount = round(($request->btc_price * $request->btc_amount), 2);
$data['amount'] = $fiat_amount;
$data['currency'] = $request->currency;
$data['payment_method_id'] = $request->payment_method_id;
$data['payment_type'] = PaymentType::CRYPTO;
$data['gateway_type_id'] = GatewayType::CRYPTO;
$data['transaction_reference'] = $request->txid;
$statusId = Payment::STATUS_PENDING;
$payment = $this->blockonomics->createPayment($data, $statusId);
SystemLogger::dispatch(
['response' => $payment, 'data' => $data],
SystemLog::CATEGORY_GATEWAY_RESPONSE,
SystemLog::EVENT_GATEWAY_SUCCESS,
SystemLog::TYPE_BLOCKONOMICS,
$this->blockonomics->client,
$this->blockonomics->client->company,
);
return redirect()->route('client.payments.show', ['payment' => $payment->hashed_id]);
} catch (\Throwable $e) {
$blockonomics = $this->blockonomics;
PaymentFailureMailer::dispatch($blockonomics->client, $blockonomics->payment_hash->data, $blockonomics->client->company, $request->amount);
throw new PaymentFailed('Error during Blockonomics payment : ' . $e->getMessage());
}
}
// Not supported yet
public function refund(Payment $payment, $amount)
{
return;
}
}

View File

@ -1,151 +0,0 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\PaymentDrivers;
use App\Utils\Traits\MakesHash;
use App\Models\PaymentHash;
use App\Models\GatewayType;
use App\PaymentDrivers\Blockonomics\Blockonomics;
use App\Models\SystemLog;
use App\Models\Payment;
use App\Models\Gateway;
use App\Models\Client;
use App\Exceptions\PaymentFailed;
use App\Models\PaymentType;
use App\Http\Requests\Payments\PaymentWebhookRequest;
use App\Models\Invoice;
class BlockonomicsPaymentDriver extends BaseDriver
{
use MakesHash;
public $refundable = false; //does this gateway support refunds?
public $token_billing = false; //does this gateway support token billing?
public $can_authorise_credit_card = false; //does this gateway support authorizations?
public $gateway; //initialized gateway
public $payment_method; //initialized payment method
public static $methods = [
GatewayType::CRYPTO => Blockonomics::class, //maps GatewayType => Implementation class
];
public const SYSTEM_LOG_TYPE = SystemLog::TYPE_CHECKOUT; //define a constant for your gateway ie TYPE_YOUR_CUSTOM_GATEWAY - set the const in the SystemLog model
public $BASE_URL = 'https://www.blockonomics.co';
public $NEW_ADDRESS_URL = 'https://www.blockonomics.co/api/new_address';
public $PRICE_URL = 'https://www.blockonomics.co/api/price';
public $api_key;
public $callback_secret;
public function init()
{
$this->api_key = $this->company_gateway->getConfigField('apiKey');
$this->callback_secret = $this->company_gateway->getConfigField('callbackSecret');
return $this; /* This is where you boot the gateway with your auth credentials*/
}
/* Returns an array of gateway types for the payment gateway */
public function gatewayTypes(): array
{
$types = [];
$types[] = GatewayType::CRYPTO;
return $types;
}
public function setPaymentMethod($payment_method_id)
{
$class = self::$methods[$payment_method_id];
$this->payment_method = new $class($this);
return $this;
}
public function processPaymentView(array $data)
{
$this->init();
return $this->payment_method->paymentView($data); //this is your custom implementation from here
}
public function processPaymentResponse($request)
{
$this->init();
return $this->payment_method->paymentResponse($request);
}
public function processWebhookRequest(PaymentWebhookRequest $request)
{
$company = $request->getCompany();
$url_callback_secret = $request->secret;
$db_callback_secret = $this->company_gateway->getConfigField('callbackSecret');
if ($url_callback_secret != $db_callback_secret) {
throw new PaymentFailed('Secret does not match');
}
$txid = $request->txid;
$value = $request->value;
$status = $request->status;
$addr = $request->addr;
$payment = Payment::query()
->where('company_id', $company->id)
->where('transaction_reference', $txid)
->firstOrFail();
if (!$payment) {
return response()->json([], 200);
// TODO: Implement logic to create new payment in case user sends payment to the address after closing the payment page
}
$statusId = Payment::STATUS_PENDING;
switch ($status) {
case 0:
$statusId = Payment::STATUS_PENDING;
break;
case 1:
$statusId = Payment::STATUS_PENDING;
break;
case 2:
$statusId = Payment::STATUS_COMPLETED;
break;
}
if($payment->status_id == $statusId) {
return response()->json([], 200);
} else {
$payment->status_id = $statusId;
$payment->save();
return response()->json([], 200);
}
}
public function refund(Payment $payment, $amount, $return_client_response = false)
{
$this->setPaymentMethod(GatewayType::CRYPTO);
return $this->payment_method->refund($payment, $amount); //this is your custom implementation from here
}
}

View File

@ -1,45 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use App\Models\Gateway;
use App\Models\GatewayType;
use Illuminate\Support\Str;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
if(!Gateway::find(65))
{
$fields = new \stdClass;
$fields->apiKey = "";
$fields->callbackSecret = "";
$gateway = new Gateway;
$gateway->id = 65;
$gateway->name = 'Blockonomics';
$gateway->key = 'wbhf02us6owgo7p4nfjd0ymssdshks4d';
$gateway->provider = 'Blockonomics';
$gateway->is_offsite = false;
$gateway->fields = \json_encode($fields);
$gateway->visible = true;
$gateway->site_url = 'https://blockonomics.co';
$gateway->default_gateway_type_id = GatewayType::CRYPTO;
$gateway->save();
}
}
/**
* Reverse the migrations.
*/
public function down(): void
{
//
}
};

View File

@ -89,7 +89,6 @@ class PaymentLibrariesSeeder extends Seeder
['id' => 61, 'name' => 'PayPal Platform', 'provider' => 'PayPal_PPCP', 'key' => '80af24a6a691230bbec33e930ab40666', 'fields' => '{"testMode":false}'],
['id' => 62, 'name' => 'BTCPay', 'provider' => 'BTCPay', 'key' => 'vpyfbmdrkqcicpkjqdusgjfluebftuva', 'fields' => '{"btcpayUrl":"", "apiKey":"", "storeId":"", "webhookSecret":""}'],
['id' => 63, 'name' => 'Rotessa', 'is_offsite' => false, 'sort_order' => 22, 'provider' => 'Rotessa', 'key' => '91be24c7b792230bced33e930ac61676', 'fields' => '{"apiKey":"", "testMode":""}'],
['id' => 65, 'name' => 'Blockonomics', 'provider' => 'Blockonomics', 'key' => 'wbhf02us6owgo7p4nfjd0ymssdshks4d', 'fields' => '{"apiKey":"", "callbackSecret":""}'],
];
foreach ($gateways as $gateway) {
@ -106,7 +105,7 @@ class PaymentLibrariesSeeder extends Seeder
Gateway::query()->update(['visible' => 0]);
Gateway::whereIn('id', [1, 3, 7, 11, 15, 20, 39, 46, 55, 50, 57, 52, 58, 59, 60, 62, 63, 64, 65])->update(['visible' => 1]);
Gateway::whereIn('id', [1, 3, 7, 11, 15, 20, 39, 46, 55, 50, 57, 52, 58, 59, 60, 62, 63])->update(['visible' => 1]);
if (Ninja::isHosted()) {
Gateway::whereIn('id', [20, 49])->update(['visible' => 0]);

1
public/build/assets/app-02bc3b96.css vendored Normal file

File diff suppressed because one or more lines are too long

1
public/build/assets/app-039bd735.css vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -240,7 +240,7 @@
"src": "resources/js/setup/setup.js"
},
"resources/sass/app.scss": {
"file": "assets/app-61bfe239.css",
"file": "assets/app-039bd735.css",
"isEntry": true,
"src": "resources/sass/app.scss"
}

View File

@ -1,322 +0,0 @@
@extends('portal.ninja2020.layout.payments', ['gateway_title' => ctrans('texts.payment_type_Crypto'), 'card_title' => ctrans('texts.payment_type_Crypto')])
@section('gateway_content')
<div class="alert alert-failure mb-4" hidden id="errors"></div>
<div class="blockonomics-payment-wrapper">
<div class="initial-state">
<div class="invoice-info-wrapper">
<div class="invoice-number">Invoice #{{$invoice_number}}</div>
<div class="invoice-amount">{{$amount}} {{$currency}}</div>
</div>
<div class="sections-wrapper">
<div class="scan-section">
<div class="title">Scan</div>
<span class="input-wrapper">
<a href="bitcoin:{{$btc_address}}?amount={{$btc_amount}}" id="qr-code-link" target="_blank">
<div id="qrcode-container"></div>
</a>
</span>
<a href="bitcoin:{{$btc_address}}?amount={{$btc_amount}}" target="_blank" id="open-in-wallet-link">Open in Wallet</a>
</div>
<div class="copy-section">
<div class="title">Copy</div>
<span>To pay, send bitcoin to this address:</span>
<span class="input-wrapper">
<input onclick='copyToClipboard("btc-address", this, true)' class="full-width-input" id="btc-address" 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">
</span>
<span>Amount of bitcoin (BTC) to send:</span>
<span class="input-wrapper">
<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>
<div class="btc-value-wrapper">
<div class="btc-value">1 BTC = {{$btc_price}} {{$currency}}, updates in <span id="countdown"></span></div>
<span class="icon-refresh" onclick='refreshBTCPrice()'></span>
</div>
</div>
</div>
</div>
<form action="{{ route('client.payments.response') }}" method="post" id="server-response">
@csrf
<input type="hidden" name="gateway_response">
<input type="hidden" name="company_gateway_id" value="{{ $company_gateway_id }}">
<input type="hidden" name="payment_method_id" value="{{ $payment_method_id }}">
<input type="hidden" name="token">
<input type="hidden" name="amount" value="{{ $amount }}">
<input type="hidden" name="btc_price" value="{{ $btc_price }}">
<input type="hidden" name="btc_amount" value="{{ $btc_amount }}">
<input type="hidden" name="currency" value="{{ $currency }}">
<input type="hidden" name="payment_hash" value="{{ $payment_hash }}">
<input type="hidden" name="txid" value="">
</form>
<script>
const startTimer = (seconds) => {
const countDownDate = new Date().getTime() + seconds * 1000;
document.getElementById("countdown").innerHTML = "10" + ":" + "00" + " min";
const 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) {
refreshBTCPrice();
return;
}
const minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((distance % (1000 * 60)) / 1000);
const formattedMinutes = String(minutes).padStart(2, '0');
const formattedSeconds = String(seconds).padStart(2, '0');
document.getElementById("countdown").innerHTML = formattedMinutes + ":" + formattedSeconds + " min";
}
clearInterval(window.countdownInterval);
window.countdownInterval = setInterval(updateCountdown, 1000);
}
const copyToClipboard = (elementId, passedElement, shouldGrabNextElementSibling) => {
const element = shouldGrabNextElementSibling ? passedElement.nextElementSibling : passedElement;
const originalIcon = element.src; // Store the original icon
const tempInput = document.createElement("input");
const elementWithId = document.getElementById(elementId);
const { value, innerText } = elementWithId || {};
const text = value || innerText;
tempInput.value = text;
document.body.appendChild(tempInput);
tempInput.select();
document.execCommand("copy");
document.body.removeChild(tempInput);
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"/>
</svg>
`);
// Change the icon back to the original after 5 seconds
setTimeout(() => {
element.src = originalIcon;
}, 5000);
}
const getBTCPrice = async () => {
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 BTC price fetch operation:', error);
// Handle error appropriately
}
}
const refreshBTCPrice = async () => {
const refreshIcon = document.querySelector('.icon-refresh');
refreshIcon.classList.add('rotating');
document.getElementsByClassName("btc-value")[0].innerHTML = "Refreshing...";
try {
const newPrice = await getBTCPrice();
if (newPrice) {
// Update the text content of the countdown span to the new bitcoin price
document.getElementsByClassName("btc-value")[0].innerHTML = "1 BTC = " + (newPrice || "N/A") + " {{$currency}}, updates in <span id='countdown'></span>";
const newBtcAmount = ({{$amount}} / newPrice).toFixed(10);
// set the value of the input field and the text content of the span to the new bitcoin amount
document.querySelector('input[name="btc_price"]').value = newPrice;
document.querySelector('input[name="btc_amount"]').value = newBtcAmount;
document.getElementById('btc-amount').textContent = newBtcAmount;
// set the href attribute of the link to the new bitcoin amount
const qrCodeLink = document.getElementById('qr-code-link');
const openInWalletLink = document.getElementById('open-in-wallet-link');
qrCodeLink.href = `bitcoin:{{$btc_address}}?amount=${newBtcAmount}`;
openInWalletLink.href = `bitcoin:{{$btc_address}}?amount=${newBtcAmount}`;
// fetch and display the new QR code
fetchAndDisplayQRCode(newBtcAmount);
startTimer(600); // Restart timer for 10 minutes (600 seconds)
}
} finally {
refreshIcon.classList.remove('rotating');
}
}
const connectToWebsocket = () => {
const webSocketUrl = "wss://www.blockonomics.co/payment/{{ $btc_address }}";
const ws = new WebSocket(webSocketUrl);
ws.onmessage = function(event) {
const data = JSON.parse(event.data);
console.log('Payment status:', data.status);
const isPaymentUnconfirmed = data.status === 0;
const isPaymentPartiallyConfirmed = data.status === 1;
// TODO: Do we need to handle Payment confirmed status?
// This usually takes too long to happen, so we can just wait for the unconfirmed status?
if (isPaymentUnconfirmed || isPaymentPartiallyConfirmed) {
document.querySelector('input[name="txid"]').value = data.txid || '';
document.getElementById('server-response').submit();
}
}
};
const fetchAndDisplayQRCode = async (newBtcAmount = null) => {
try {
const btcAmount = newBtcAmount || '{{$btc_amount}}';
const qrString = encodeURIComponent(`bitcoin:{{$btc_address}}?amount=${btcAmount}`);
const response = await fetch(`/api/v1/get-blockonomics-qr-code?qr_string=${qrString}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const svgText = await response.text();
document.getElementById('qrcode-container').innerHTML = svgText;
} catch (error) {
console.error('Error fetching QR code:', error);
document.getElementById('qrcode-container').textContent = 'Error loading QR code';
}
};
startTimer(600); // Start timer for 10 minutes (600 seconds)
connectToWebsocket();
fetchAndDisplayQRCode();
</script>
<style type="text/css">
.sections-wrapper {
display: flex;
flex-direction: row;
justify-content: space-around;
/* Mobile devices */
@media (max-width: 768px) {
flex-direction: column; /* Change to column on smaller screens */
}
}
.copy-section {
width: 60%;
@media (max-width: 768px) {
width: 100%; /* Full width on smaller screens */
}
}
.title {
font-size: 17px;
font-weight: bold;
margin-bottom: 6px;
}
#open-in-wallet-link {
text-align: center;
text-decoration: underline;
width: 100%;
justify-content: center;
display: flex;
margin-top: 10px;
margin-bottom: 20px;
&:hover {
text-decoration: none;
}
}
.invoice-info-wrapper {
width: 100%;
text-transform: uppercase;
margin-bottom: 20px;
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;
}
.blockonomics-payment-wrapper {
display: flex;
justify-content: center;
width: 100%;
}
.initial-state {
justify-content: center;
display: flex;
flex-direction: column;
width: 100%;
padding: 24px;
}
.input-wrapper {
display: flex;
justify-content: center;
align-items: center;
flex-direction: row;
width: 100%;
margin-bottom: 10px;
}
.full-width-input {
width: 100%;
max-width: 400px;
padding: 10px;
text-align: left;
border: 1px solid #ccc;
border-radius: 5px;
color: #444;
cursor: pointer;
position: relative;
}
.icon {
cursor: pointer;
width: 28px;
margin-left: 5px;
}
.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;
text-align: center;
}
.btc-value-wrapper {
display: flex;
justify-content: center;
align-items: center;
flex-direction: row;
}
@keyframes rotating {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.rotating {
animation: rotating 2s linear infinite;
}
</style>
@endsection

View File

@ -63,7 +63,6 @@ use App\Http\Controllers\Bank\YodleeController;
use App\Http\Controllers\CompanyUserController;
use App\Http\Controllers\PaymentTermController;
use App\PaymentDrivers\PayPalPPCPPaymentDriver;
use App\PaymentDrivers\BlockonomicsPaymentDriver;
use App\Http\Controllers\EmailHistoryController;
use App\Http\Controllers\GroupSettingController;
use App\Http\Controllers\OneTimeTokenController;
@ -98,7 +97,6 @@ use App\Http\Controllers\Reports\ClientReportController;
use App\Http\Controllers\Reports\CreditReportController;
use App\Http\Controllers\Reports\ReportExportController;
use App\Http\Controllers\Reports\VendorReportController;
use App\Http\Controllers\Gateways\BlockonomicsController;
use App\Http\Controllers\Reports\ExpenseReportController;
use App\Http\Controllers\Reports\InvoiceReportController;
use App\Http\Controllers\Reports\PaymentReportController;
@ -460,7 +458,5 @@ 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/get-btc-price', [BlockonomicsController::class, 'getBTCPrice'])->middleware('throttle:1000,1');
Route::get('api/v1/get-blockonomics-qr-code', [BlockonomicsController::class, 'getQRCode'])->middleware('throttle:1000,1');
Route::fallback([BaseController::class, 'notFound'])->middleware('throttle:404');

View File

@ -5622,23 +5622,5 @@
"GET",
"HEAD"
]
},
"generated::Xy7ZkLm8NpQ4Rt5V": {
"name": "generated::Xy7ZkLm8NpQ4Rt5V",
"domain": null,
"action": "App\\Http\\Controllers\\BlockonomicsController@getBTCPrice",
"uri": "api/v1/get-btc-price",
"method": [
"GET"
]
},
"generated::Jk2MnOp3QrS6Tu9W": {
"name": "generated::Jk2MnOp3QrS6Tu9W",
"domain": null,
"action": "App\\Http\\Controllers\\BlockonomicsController@getQRCode",
"uri": "api/v1/get-blockonomics-qr-code",
"method": [
"GET"
]
}
}