Model: PNPL never touches the money. Each charity connects their own Stripe account by pasting their API key in Settings. When a donor chooses card payment, they're redirected to Stripe Checkout. The money lands in the charity's Stripe balance. ## Schema - Organization.stripeSecretKey (new column) - Organization.stripeWebhookSecret (new column) ## New/rewritten files - src/lib/stripe.ts — getStripeForOrg(secretKey), per-org client - src/app/api/stripe/checkout/route.ts — uses org's key, not env var - src/app/api/stripe/webhook/route.ts — tries all org webhook secrets - src/app/p/[token]/steps/card-payment-step.tsx — redirect to Stripe Checkout (no fake card form — Stripe handles PCI) ## Settings page - New 'Card payments' section between Bank and Charity - Instructions: how to get your Stripe API key - Webhook setup in collapsed <details> (optional, for auto-confirm) - 'Card payments live' green banner when connected - Readiness bar shows Stripe status (5 columns now) ## Pledge flow - PaymentStep shows card option ONLY if org has Stripe configured - hasStripe flag passed from /api/qr/[token] → PaymentStep - Secret key never exposed to frontend (only boolean hasStripe) ## How it works 1. Charity pastes sk_live_... in Settings → Save 2. Donor opens pledge link → sees 'Bank Transfer', 'Direct Debit', 'Card' 3. Donor picks card → enters name + email → redirects to Stripe Checkout 4. Stripe processes payment → money in charity's Stripe balance 5. (Optional) Webhook auto-confirms pledge as paid Payment options: - Bank Transfer: zero fees (default, always available) - Direct Debit via GoCardless: 1% + 20p (if org configured) - Card via Stripe: standard Stripe fees (if org configured)
80 lines
3.1 KiB
PHP
80 lines
3.1 KiB
PHP
<?php
|
|
|
|
namespace App\Filament\Resources\DonationResource\Pages;
|
|
|
|
use App\Filament\Resources\DonationResource;
|
|
use App\Models\Donation;
|
|
use Filament\Resources\Components\Tab;
|
|
use Filament\Resources\Pages\ListRecords;
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
|
|
class ListDonations extends ListRecords
|
|
{
|
|
protected static string $resource = DonationResource::class;
|
|
|
|
public function getHeading(): string
|
|
{
|
|
return 'Donations';
|
|
}
|
|
|
|
public function getSubheading(): string
|
|
{
|
|
$todayCount = Donation::whereHas('donationConfirmation', fn ($q) => $q->whereNotNull('confirmed_at'))
|
|
->whereDate('created_at', today())
|
|
->count();
|
|
$todayAmount = Donation::whereHas('donationConfirmation', fn ($q) => $q->whereNotNull('confirmed_at'))
|
|
->whereDate('created_at', today())
|
|
->sum('amount') / 100;
|
|
|
|
return "Today: {$todayCount} confirmed (£" . number_format($todayAmount, 0) . ")";
|
|
}
|
|
|
|
public function getTabs(): array
|
|
{
|
|
$incompleteCount = Donation::whereDoesntHave('donationConfirmation', fn ($q) => $q->whereNotNull('confirmed_at'))
|
|
->where('created_at', '>=', now()->subDays(7))
|
|
->count();
|
|
|
|
return [
|
|
'today' => Tab::make('Today')
|
|
->icon('heroicon-o-clock')
|
|
->modifyQueryUsing(fn (Builder $query) => $query
|
|
->whereDate('created_at', today())
|
|
->whereHas('donationConfirmation', fn ($q) => $q->whereNotNull('confirmed_at'))
|
|
),
|
|
|
|
'all_confirmed' => Tab::make('All Confirmed')
|
|
->icon('heroicon-o-check-circle')
|
|
->modifyQueryUsing(fn (Builder $query) => $query
|
|
->whereHas('donationConfirmation', fn ($q) => $q->whereNotNull('confirmed_at'))
|
|
),
|
|
|
|
'incomplete' => Tab::make('Incomplete')
|
|
->icon('heroicon-o-exclamation-triangle')
|
|
->badge($incompleteCount > 0 ? $incompleteCount : null)
|
|
->badgeColor('danger')
|
|
->modifyQueryUsing(fn (Builder $query) => $query
|
|
->whereDoesntHave('donationConfirmation', fn ($q) => $q->whereNotNull('confirmed_at'))
|
|
->where('created_at', '>=', now()->subDays(7))
|
|
),
|
|
|
|
'zakat' => Tab::make('Zakat')
|
|
->icon('heroicon-o-star')
|
|
->modifyQueryUsing(fn (Builder $query) => $query
|
|
->whereHas('donationConfirmation', fn ($q) => $q->whereNotNull('confirmed_at'))
|
|
->whereHas('donationPreferences', fn ($q) => $q->where('is_zakat', true))
|
|
),
|
|
|
|
'gift_aid' => Tab::make('Gift Aid')
|
|
->icon('heroicon-o-gift')
|
|
->modifyQueryUsing(fn (Builder $query) => $query
|
|
->whereHas('donationConfirmation', fn ($q) => $q->whereNotNull('confirmed_at'))
|
|
->whereHas('donationPreferences', fn ($q) => $q->where('is_gift_aid', true))
|
|
),
|
|
|
|
'everything' => Tab::make('Everything')
|
|
->icon('heroicon-o-squares-2x2'),
|
|
];
|
|
}
|
|
}
|