84 lines
3.6 KiB
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>
|