mirror of
https://github.com/HolgerHatGarKeineNode/einundzwanzig-nostr.git
synced 2025-12-13 05:26:47 +00:00
🚀 **feat(payment): integrate BTC Pay for invoice handling and remove QR code dependencies**
This commit is contained in:
@@ -40,4 +40,8 @@ return [
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
'btc_pay' => [
|
||||||
|
'api_key' => env('BTC_PAY_API_KEY'),
|
||||||
|
]
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration {
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('payment_events', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('zap_endpoint');
|
||||||
|
$table->text('btc_pay_invoice')->nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('payment_events', function (Blueprint $table) {
|
||||||
|
//
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
use Livewire\Volt\Component;
|
use Livewire\Volt\Component;
|
||||||
|
|
||||||
use SimpleSoftwareIO\QrCode\Facades\QrCode;
|
|
||||||
use swentel\nostr\Filter\Filter;
|
use swentel\nostr\Filter\Filter;
|
||||||
use swentel\nostr\Key\Key;
|
use swentel\nostr\Key\Key;
|
||||||
use swentel\nostr\Message\EventMessage;
|
use swentel\nostr\Message\EventMessage;
|
||||||
@@ -13,6 +12,7 @@ use swentel\nostr\Request\Request;
|
|||||||
use swentel\nostr\Subscription\Subscription;
|
use swentel\nostr\Subscription\Subscription;
|
||||||
use swentel\nostr\Event\Event as NostrEvent;
|
use swentel\nostr\Event\Event as NostrEvent;
|
||||||
use swentel\nostr\Sign\Sign;
|
use swentel\nostr\Sign\Sign;
|
||||||
|
use WireUi\Actions\Notification;
|
||||||
|
|
||||||
use function Livewire\Volt\computed;
|
use function Livewire\Volt\computed;
|
||||||
use function Livewire\Volt\mount;
|
use function Livewire\Volt\mount;
|
||||||
@@ -27,8 +27,6 @@ name('association.profile');
|
|||||||
state(['yearsPaid' => []]);
|
state(['yearsPaid' => []]);
|
||||||
state(['events' => []]);
|
state(['events' => []]);
|
||||||
state(['payments' => []]);
|
state(['payments' => []]);
|
||||||
state(['invoice' => null]);
|
|
||||||
state(['qrCode' => null]);
|
|
||||||
state(['amountToPay' => config('app.env') === 'production' ? 21000 : 1]);
|
state(['amountToPay' => config('app.env') === 'production' ? 21000 : 1]);
|
||||||
state(['currentYearIsPaid' => false]);
|
state(['currentYearIsPaid' => false]);
|
||||||
state(['currentPubkey' => null]);
|
state(['currentPubkey' => null]);
|
||||||
@@ -36,32 +34,6 @@ state(['currentPleb' => null]);
|
|||||||
|
|
||||||
form(\App\Livewire\Forms\ApplicationForm::class);
|
form(\App\Livewire\Forms\ApplicationForm::class);
|
||||||
|
|
||||||
updated([
|
|
||||||
'invoice' => function () {
|
|
||||||
$this->qrCode = base64_encode(
|
|
||||||
QrCode::format('png')
|
|
||||||
->size(300)
|
|
||||||
->merge('/public/android-chrome-192x192.png', .3)
|
|
||||||
->errorCorrection('H')
|
|
||||||
->generate($this->invoice),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
on([
|
|
||||||
'nostrLoggedOut' => function () {
|
|
||||||
$this->currentPubkey = null;
|
|
||||||
$this->currentPleb = null;
|
|
||||||
$this->yearsPaid = [];
|
|
||||||
$this->events = [];
|
|
||||||
$this->payments = [];
|
|
||||||
$this->invoice = null;
|
|
||||||
$this->qrCode = null;
|
|
||||||
$this->amountToPay = config('app.env') === 'production' ? 21000 : 1;
|
|
||||||
$this->currentYearIsPaid = false;
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
on([
|
on([
|
||||||
'nostrLoggedIn' => function ($pubkey) {
|
'nostrLoggedIn' => function ($pubkey) {
|
||||||
$this->currentPubkey = $pubkey;
|
$this->currentPubkey = $pubkey;
|
||||||
@@ -81,36 +53,84 @@ on([
|
|||||||
$this->loadEvents();
|
$this->loadEvents();
|
||||||
$this->listenForPayment();
|
$this->listenForPayment();
|
||||||
},
|
},
|
||||||
|
'nostrLoggedOut' => function () {
|
||||||
|
$this->currentPubkey = null;
|
||||||
|
$this->currentPleb = null;
|
||||||
|
$this->yearsPaid = [];
|
||||||
|
$this->events = [];
|
||||||
|
$this->payments = [];
|
||||||
|
$this->qrCode = null;
|
||||||
|
$this->amountToPay = config('app.env') === 'production' ? 21000 : 1;
|
||||||
|
$this->currentYearIsPaid = false;
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$pay = function ($comment) {
|
||||||
|
$paymentEvent = $this->currentPleb
|
||||||
|
->paymentEvents()
|
||||||
|
->where('year', date('Y'))
|
||||||
|
->first();
|
||||||
|
if ($paymentEvent->btc_pay_invoice) {
|
||||||
|
return redirect('https://pay.einundzwanzig.space/i/' . $paymentEvent->btc_pay_invoice);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$response = Http::withHeaders([
|
||||||
|
'Authorization' => 'token ' . config('services.btc_pay.api_key'),
|
||||||
|
])->post(
|
||||||
|
'https://pay.einundzwanzig.space/api/v1/stores/98PF86BoMd3C8P1nHHyFdoeznCwtcm5yehcAgoCYDQ2a/invoices',
|
||||||
|
[
|
||||||
|
'amount' => $this->amountToPay,
|
||||||
|
'metadata' => [
|
||||||
|
'orderId' => $comment,
|
||||||
|
'orderUrl' => url()->route('association.profile'),
|
||||||
|
'itemDesc' => 'Mitgliedsbeitrag ' . date('Y') . ' von nostr:' . $this->currentPleb->npub,
|
||||||
|
'posData' => [
|
||||||
|
'event' => $paymentEvent->event_id,
|
||||||
|
'pubkey' => $this->currentPleb->pubkey,
|
||||||
|
'npub' => $this->currentPleb->npub,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'checkout' => [
|
||||||
|
'expirationMinutes' => 60 * 24 * 356,
|
||||||
|
'redirectURL' => url()->route('association.profile'),
|
||||||
|
'redirectAutomatically' => true,
|
||||||
|
'defaultLanguage' => 'de',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
)->throw();
|
||||||
|
$paymentEvent->btc_pay_invoice = $response->json()['id'];
|
||||||
|
$paymentEvent->save();
|
||||||
|
|
||||||
|
return redirect($response->json()['checkoutLink']);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$notification = new Notification($this);
|
||||||
|
$notification->error(
|
||||||
|
'Fehler beim Erstellen der Rechnung. Bitte versuche es später erneut: ' . $e->getMessage(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
$listenForPayment = function () {
|
$listenForPayment = function () {
|
||||||
$paymentEvent = $this->currentPleb
|
$paymentEvent = $this->currentPleb
|
||||||
->paymentEvents()
|
->paymentEvents()
|
||||||
->where('year', date('Y'))
|
->where('year', date('Y'))
|
||||||
->first();
|
->first();
|
||||||
if ($paymentEvent && !$paymentEvent->paid && $paymentEvent->zap_endpoint && !$this->currentYearIsPaid) {
|
if ($paymentEvent->btc_pay_invoice) {
|
||||||
$response = Http::get($paymentEvent->zap_endpoint);
|
$response = Http::withHeaders([
|
||||||
|
'Authorization' => 'token ' . config('services.btc_pay.api_key'),
|
||||||
if (!isset($response->json()['tag'])) {
|
])
|
||||||
$paymentEvent->update(['paid' => true]);
|
->get(
|
||||||
|
'https://pay.einundzwanzig.space/api/v1/stores/98PF86BoMd3C8P1nHHyFdoeznCwtcm5yehcAgoCYDQ2a/invoices/' . $paymentEvent->btc_pay_invoice,
|
||||||
|
);
|
||||||
|
if ($response->json()['status'] === 'Settled') {
|
||||||
|
$paymentEvent->paid = true;
|
||||||
|
$paymentEvent->save();
|
||||||
|
$this->payments = $this->currentPleb
|
||||||
|
->paymentEvents()
|
||||||
|
->get();
|
||||||
$this->currentYearIsPaid = true;
|
$this->currentYearIsPaid = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($paymentEvent && $paymentEvent->paid && !$this->currentYearIsPaid) {
|
|
||||||
$this->payments = $paymentEvent = $this->currentPleb
|
|
||||||
->paymentEvents()
|
|
||||||
->get();
|
|
||||||
$this->currentYearIsPaid = true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
$updateZapEndpoint = function ($zapEndpoint) {
|
|
||||||
$this->currentPleb
|
|
||||||
->paymentEvents()
|
|
||||||
->where('year', date('Y'))
|
|
||||||
->first()
|
|
||||||
->update(['zap_endpoint' => $zapEndpoint]);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
$save = function ($type) {
|
$save = function ($type) {
|
||||||
@@ -127,7 +147,7 @@ $createPaymentEvent = function () {
|
|||||||
$note->setContent(
|
$note->setContent(
|
||||||
'Dieses Event dient der Zahlung des Mitgliedsbeitrags für das Jahr ' . date(
|
'Dieses Event dient der Zahlung des Mitgliedsbeitrags für das Jahr ' . date(
|
||||||
'Y',
|
'Y',
|
||||||
) . '. Bitte zappe den Betrag von ' . number_format($this->amountToPay, 0, ',', '.') . ' Satoshi.',
|
) . '. Bitte bezahle den Betrag von ' . number_format($this->amountToPay, 0, ',', '.') . ' Satoshis.',
|
||||||
);
|
);
|
||||||
$note->setTags([
|
$note->setTags([
|
||||||
['d', $this->currentPleb->pubkey . ',' . date('Y')],
|
['d', $this->currentPleb->pubkey . ',' . date('Y')],
|
||||||
@@ -188,7 +208,7 @@ $loadEvents = function () {
|
|||||||
|
|
||||||
<x-layouts.app title="{{ __('Wahl') }}">
|
<x-layouts.app title="{{ __('Wahl') }}">
|
||||||
@volt
|
@volt
|
||||||
<div class="px-4 sm:px-6 lg:px-8 py-8 w-full max-w-9xl mx-auto" x-data="nostrZap(@this)">
|
<div class="px-4 sm:px-6 lg:px-8 py-8 w-full max-w-9xl mx-auto">
|
||||||
|
|
||||||
<!-- Page header -->
|
<!-- Page header -->
|
||||||
<div class="mb-8">
|
<div class="mb-8">
|
||||||
@@ -425,42 +445,29 @@ $loadEvents = function () {
|
|||||||
class="break-all">{{ $currentPleb->paymentEvents->first()->event_id }}</span>
|
class="break-all">{{ $currentPleb->paymentEvents->first()->event_id }}</span>
|
||||||
</p>
|
</p>
|
||||||
<div>
|
<div>
|
||||||
@if(false && isset($events[0]))
|
@if(isset($events[0]))
|
||||||
<p>{{ $events[0]['content'] }}</p>
|
<p>{{ $events[0]['content'] }}</p>
|
||||||
<div class="mt-8">
|
<div class="mt-8">
|
||||||
@if(!$invoice && !$currentYearIsPaid)
|
@if(!$currentYearIsPaid)
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<button
|
<button
|
||||||
@click="zap('Mitgliedsbeitrag {{ date('Y') }} von {{ $currentPubkey }}', '{{ $currentPubkey }}', {{ $amountToPay }}, '{{ config('app.env') }}')"
|
wire:click="pay('{{ date('Y') }}:{{ $currentPubkey }}')"
|
||||||
class="btn text-2xl dark:bg-gray-800 border-gray-200 dark:border-gray-700/60 hover:border-gray-300 dark:hover:border-gray-600 text-green-500"
|
class="btn text-2xl dark:bg-gray-800 border-gray-200 dark:border-gray-700/60 hover:border-gray-300 dark:hover:border-gray-600 text-green-500"
|
||||||
>
|
>
|
||||||
<i class="fa-sharp-duotone fa-solid fa-bolt-lightning mr-2"></i>
|
<i class="fa-sharp-duotone fa-solid fa-bolt-lightning mr-2"></i>
|
||||||
Zap {{ $amountToPay }} Sats
|
Pay {{ $amountToPay }} Sats
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@else
|
@else
|
||||||
@if(!$currentYearIsPaid && $qrCode)
|
@if($currentYearIsPaid)
|
||||||
<div class="flex justify-center"
|
<div class="flex sm:justify-center">
|
||||||
wire:key="qrcode"
|
<button
|
||||||
wire:poll.2s.keep-alive="listenForPayment">
|
class="btn sm:text-2xl dark:bg-gray-800 border-gray-200 dark:border-gray-700/60 hover:border-gray-300 dark:hover:border-gray-600 text-green-500"
|
||||||
<a href="lightning:{{ $invoice }}">
|
>
|
||||||
<img
|
<i class="fa-sharp-duotone fa-solid fa-check-circle mr-2"></i>
|
||||||
class="p-4 sm:p-12 bg-white"
|
aktuelles Jahr bezahlt
|
||||||
src="{{ 'data:image/png;base64, '. $qrCode }}"
|
</button>
|
||||||
alt="qrcode">
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
@else
|
|
||||||
@if($currentYearIsPaid)
|
|
||||||
<div class="flex sm:justify-center">
|
|
||||||
<button
|
|
||||||
class="btn sm:text-2xl dark:bg-gray-800 border-gray-200 dark:border-gray-700/60 hover:border-gray-300 dark:hover:border-gray-600 text-green-500"
|
|
||||||
>
|
|
||||||
<i class="fa-sharp-duotone fa-solid fa-check-circle mr-2"></i>
|
|
||||||
aktuelles Jahr bezahlt
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
@endif
|
@endif
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
@@ -494,6 +501,9 @@ $loadEvents = function () {
|
|||||||
<th class="w-full hidden md:w-auto md:table-cell py-2">
|
<th class="w-full hidden md:w-auto md:table-cell py-2">
|
||||||
<div class="font-semibold text-left">Event-ID</div>
|
<div class="font-semibold text-left">Event-ID</div>
|
||||||
</th>
|
</th>
|
||||||
|
<th class="w-full hidden md:w-auto md:table-cell py-2">
|
||||||
|
<div class="font-semibold text-left">Quittung</div>
|
||||||
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<!-- Table body -->
|
<!-- Table body -->
|
||||||
@@ -517,6 +527,10 @@ $loadEvents = function () {
|
|||||||
<div
|
<div
|
||||||
class="text-left font-medium break-all">{{ $payment->event_id }}</div>
|
class="text-left font-medium break-all">{{ $payment->event_id }}</div>
|
||||||
</td>
|
</td>
|
||||||
|
<td class="w-full block md:w-auto md:table-cell py-0.5 md:py-2">
|
||||||
|
<x-button target="_blank" xs label="Quittung"
|
||||||
|
href="https://pay.einundzwanzig.space/i/{{ $payment->btc_pay_invoice }}/receipt"/>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@endforeach
|
@endforeach
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
Reference in New Issue
Block a user