Skip to main content

General

Payment Method code

Copy the implementation or inspect every file that belongs to this component unit.

resources/views/components/ui/payment-method.blade.php

@props([
    'brand' => null,
    'last4' => null,
    'expMonth' => null,
    'expYear' => null,
    'expiry' => null,
    'name' => null,
    'default' => false,
    'expired' => false,
    'type' => 'card',
    'email' => null,
    'updateLabel' => 'Update',
    'updateHref' => null,
])

@php
    /*
    | Payment method card — a stored card (or alternative method) with a brand
    | mark, masked number, expiry, an optional "Default" badge and an actions
    | menu. The companion to <x-ui.invoice> / <x-ui.billing-history>. Use the
    | `actions` slot to override the menu.
    |
    | type:  card, paypal, bank
    | brand: visa, mastercard, amex, discover, … (shown as a recognisable label)
    |
    | The brand chip and the default-method accent use the themeable palette
    | (neutral surfaces + the `brand-*` ramp) so the card re-themes with the app.
    |
    | Usage:
    |   <x-ui.payment-method brand="visa" last4="4242" exp-month="8" exp-year="2027" name="Ada Lovelace" default />
    |   <x-ui.payment-method type="paypal" email="ada@example.com" />
    */
    // Brand chip uses neutral, theme-aware surfaces (mirrors the provider picker)
    // rather than fixed brand hexes, so it adapts to light/dark and re-theming.
    $chipClass = $default
        ? 'bg-brand-600 text-white dark:bg-brand-500'
        : 'bg-gray-900 text-white dark:bg-gray-700';

    $rootClass = implode(' ', [
        'flex items-center gap-4 rounded-2xl border bg-white p-4 dark:bg-gray-900 sm:p-5',
        $default
            ? 'border-brand-300 ring-1 ring-brand-500/20 dark:border-brand-800'
            : 'border-gray-200 dark:border-gray-800',
    ]);

    $brandIcon = match ($type) {
        'paypal' => 'paypal-logo',
        'bank' => 'bank',
        default => 'credit-card',
    };

    $brandLabel = $brand
        ? \Illuminate\Support\Str::of($brand)->title()->toString()
        : match ($type) {
            'paypal' => 'PayPal',
            'bank' => 'Bank account',
            default => 'Card',
        };

    $resolvedExpiry = $expiry
        ?? ($expMonth && $expYear
            ? str_pad((string) $expMonth, 2, '0', STR_PAD_LEFT).' / '.mb_substr((string) $expYear, -2)
            : null);

    $primaryLine = match ($type) {
        'paypal' => $email,
        'bank' => $last4 ? 'Account ending in '.$last4 : null,
        default => $last4 ? '•••• •••• •••• '.$last4 : null,
    };
@endphp

<div {{ $attributes->merge(['class' => $rootClass]) }}>
    {{-- Brand mark --}}
    <div class="flex h-11 w-16 shrink-0 items-center justify-center rounded-lg text-xs font-bold uppercase tracking-wide {{ $chipClass }}">
        @if ($type === 'card' && $brand)
            <span class="truncate px-1">{{ $brandLabel }}</span>
        @else
            <x-ui.icon :name="$brandIcon" weight="fill" size="lg" />
        @endif
    </div>

    {{-- Details --}}
    <div class="min-w-0 flex-1">
        <div class="flex flex-wrap items-center gap-2">
            <p class="truncate text-sm font-semibold text-gray-900 dark:text-white">
                @if ($type === 'card')
                    {{ $brandLabel }}@if ($last4) ending in {{ $last4 }}@endif
                @else
                    {{ $brandLabel }}
                @endif
            </p>
            @if ($default)
                <x-ui.badge variant="brand" size="sm" icon="check-circle">Default</x-ui.badge>
            @endif
            @if ($expired)
                <x-ui.badge variant="red" size="sm" icon="warning">Expired</x-ui.badge>
            @endif
        </div>

        <div class="mt-0.5 flex flex-wrap items-center gap-x-3 gap-y-0.5 text-sm text-gray-500 dark:text-gray-400">
            @if ($primaryLine && $type !== 'card')
                <span class="truncate">{{ $primaryLine }}</span>
            @endif
            @if ($resolvedExpiry)
                <span @class(['tabular-nums', 'text-red-500 dark:text-red-400' => $expired])>Expires {{ $resolvedExpiry }}</span>
            @endif
            @if ($name)
                <span class="truncate">{{ $name }}</span>
            @endif
        </div>
    </div>

    {{-- Actions --}}
    <div class="shrink-0">
        @isset($actions)
            {{ $actions }}
        @else
            <x-ui.dropdown align="right" width="48">
                <x-slot:trigger>
                    <x-ui.icon-button icon="dots-three-vertical" size="sm" label="Manage payment method" />
                </x-slot:trigger>
                @if ($updateHref)
                    <x-ui.dropdown.item :href="$updateHref" icon="pencil-simple">{{ $updateLabel }}</x-ui.dropdown.item>
                @else
                    <x-ui.dropdown.item icon="pencil-simple">{{ $updateLabel }}</x-ui.dropdown.item>
                @endif
                @unless ($default)
                    <x-ui.dropdown.item icon="check-circle">Set as default</x-ui.dropdown.item>
                @endunless
                <x-ui.dropdown.item icon="trash" variant="danger">Remove</x-ui.dropdown.item>
            </x-ui.dropdown>
        @endisset
    </div>
</div>