CluPilotCloud/resources/views/livewire/payment-method.blade.php

84 lines
3.6 KiB
PHP

<div class="space-y-4 rounded-lg border border-line bg-surface p-6 shadow-xs">
<div>
<h2 class="font-semibold text-ink">{{ __('billing.card_title') }}</h2>
<p class="mt-1 max-w-[70ch] text-sm text-muted">{{ __('billing.card_body') }}</p>
</div>
@if ($card !== null)
<p class="rounded-lg border border-line bg-surface-2 px-4 py-3 text-sm text-body">
{{ __('billing.card_on_file', [
'brand' => \Illuminate\Support\Str::title($card['brand']),
'last4' => $card['last4'],
'month' => str_pad((string) $card['exp_month'], 2, '0', STR_PAD_LEFT),
'year' => $card['exp_year'],
]) }}
</p>
@endif
@if ($clientSecret === null)
<x-ui.alert variant="warning">{{ __('billing.card_unavailable') }}</x-ui.alert>
@else
{{-- Stripe.js kommt von js.stripe.com und wird bewusst NICHT selbst
gehostet. Das ist die eine Ausnahme von „keine fremden Quellen im
Browser": Stripe untersagt das Spiegeln, und eine eigene Kopie
nähme dieser Installation die vereinfachte PCI-Einstufung (SAQ-A),
weil dann wieder Kartendaten durch eigenen Code laufen könnten.
Nur auf dieser Seite geladen, nicht im Layout — jede andere Seite
des Portals kommt ohne aus und soll auch keine Verbindung dorthin
aufbauen.
`wire:ignore` um das Feld: Elements hängt sein eigenes iframe
hinein, und ein Livewire-Neuaufbau würde es abreißen. --}}
<div wire:ignore>
<label for="card-element" class="block text-sm font-medium text-body">{{ __('billing.card_new') }}</label>
<div id="card-element" class="mt-1.5 rounded border border-line-strong bg-surface px-3.5 py-3"></div>
<p id="card-error" class="mt-1.5 text-xs text-danger" role="alert"></p>
</div>
<x-ui.button variant="primary" id="card-submit" type="button">
{{ __('billing.card_save') }}
</x-ui.button>
@script
<script>
const laden = () => new Promise((fertig) => {
if (window.Stripe) { fertig(); return; }
const skript = document.createElement('script');
skript.src = 'https://js.stripe.com/v3/';
skript.onload = fertig;
document.head.appendChild(skript);
});
laden().then(() => {
const stripe = Stripe(@js($publishableKey));
const karte = stripe.elements().create('card');
karte.mount('#card-element');
const knopf = document.getElementById('card-submit');
const fehler = document.getElementById('card-error');
knopf.addEventListener('click', async () => {
knopf.disabled = true;
fehler.textContent = '';
const { setupIntent, error } = await stripe.confirmCardSetup(
@js($clientSecret), { payment_method: { card: karte } }
);
knopf.disabled = false;
// Stripes eigene Meldung, nicht eine eigene Umschreibung:
// sie nennt den Grund (abgelaufen, abgelehnt, falsche
// Prüfziffer), und der Kunde kann danach handeln.
if (error) { fehler.textContent = error.message; return; }
$wire.confirmed(setupIntent.payment_method);
});
});
</script>
@endscript
@endif
</div>