Stripe integration: charity connects their own Stripe account
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)
This commit is contained in:
@@ -27,9 +27,6 @@ use Filament\Tables\Actions\ActionGroup;
|
||||
use Filament\Tables\Actions\BulkAction;
|
||||
use Filament\Tables\Actions\EditAction;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Enums\FiltersLayout;
|
||||
use Filament\Tables\Filters\Filter;
|
||||
use Filament\Tables\Filters\SelectFilter;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
@@ -310,47 +307,7 @@ class AppealResource extends Resource
|
||||
->sortable()
|
||||
->description(fn (Appeal $a) => $a->created_at?->format('d M Y')),
|
||||
])
|
||||
->filters([
|
||||
SelectFilter::make('nurture_segment')
|
||||
->label('Nurture Segment')
|
||||
->options([
|
||||
'needs_outreach' => '🔴 Needs Outreach (£0 raised, 7+ days)',
|
||||
'almost_there' => '🟡 Almost There (80%+ of target)',
|
||||
'target_reached' => '🟢 Target Reached',
|
||||
'slowing' => '🟠 Slowing Down (raised something, 30+ days)',
|
||||
'new_this_week' => '🆕 New This Week',
|
||||
])
|
||||
->query(function (Builder $query, array $data) {
|
||||
if (!$data['value']) return;
|
||||
$query->where('status', 'confirmed')->where('is_accepting_donations', true);
|
||||
|
||||
match ($data['value']) {
|
||||
'needs_outreach' => $query->where('amount_raised', 0)->where('created_at', '<', now()->subDays(7)),
|
||||
'almost_there' => $query->where('amount_raised', '>', 0)->whereRaw('amount_raised >= amount_to_raise * 0.8')->whereRaw('amount_raised < amount_to_raise'),
|
||||
'target_reached' => $query->whereRaw('amount_raised >= amount_to_raise')->where('amount_raised', '>', 0),
|
||||
'slowing' => $query->where('amount_raised', '>', 0)->whereRaw('amount_raised < amount_to_raise * 0.8')->where('created_at', '<', now()->subDays(30)),
|
||||
'new_this_week' => $query->where('created_at', '>=', now()->subDays(7)),
|
||||
default => null,
|
||||
};
|
||||
}),
|
||||
|
||||
SelectFilter::make('status')
|
||||
->options([
|
||||
'confirmed' => 'Live',
|
||||
'pending' => 'Pending Review',
|
||||
]),
|
||||
|
||||
Filter::make('accepting_donations')
|
||||
->label('Currently accepting donations')
|
||||
->toggle()
|
||||
->query(fn (Builder $q) => $q->where('is_accepting_donations', true))
|
||||
->default(),
|
||||
|
||||
Filter::make('has_raised')
|
||||
->label('Has raised money')
|
||||
->toggle()
|
||||
->query(fn (Builder $q) => $q->where('amount_raised', '>', 0)),
|
||||
], layout: FiltersLayout::AboveContentCollapsible)
|
||||
->filters([])
|
||||
->actions([
|
||||
Action::make('view_page')
|
||||
->label('View Page')
|
||||
|
||||
Reference in New Issue
Block a user