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:
88
temp_files/v4/AdminPanelProvider.php
Normal file
88
temp_files/v4/AdminPanelProvider.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers\Filament;
|
||||
|
||||
use App\Helpers;
|
||||
use Filament\Http\Middleware\Authenticate;
|
||||
use Filament\Http\Middleware\DisableBladeIconComponents;
|
||||
use Filament\Http\Middleware\DispatchServingFilamentEvent;
|
||||
use Filament\Navigation\MenuItem;
|
||||
use Filament\Navigation\NavigationGroup;
|
||||
use Filament\Navigation\NavigationItem;
|
||||
use Filament\Panel;
|
||||
use Filament\PanelProvider;
|
||||
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
|
||||
use Illuminate\Cookie\Middleware\EncryptCookies;
|
||||
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
|
||||
use Illuminate\Routing\Middleware\SubstituteBindings;
|
||||
use Illuminate\Session\Middleware\AuthenticateSession;
|
||||
use Illuminate\Session\Middleware\StartSession;
|
||||
use Illuminate\View\Middleware\ShareErrorsFromSession;
|
||||
|
||||
class AdminPanelProvider extends PanelProvider
|
||||
{
|
||||
public function panel(Panel $panel): Panel
|
||||
{
|
||||
return $panel
|
||||
->default()
|
||||
->id('admin')
|
||||
->path('admin')
|
||||
->login()
|
||||
->colors(['primary' => config('branding.colours')])
|
||||
->viteTheme('resources/css/filament/admin/theme.css')
|
||||
->sidebarCollapsibleOnDesktop()
|
||||
->sidebarWidth('16rem')
|
||||
->globalSearch(true)
|
||||
->globalSearchKeyBindings(['command+k', 'ctrl+k'])
|
||||
->globalSearchDebounce('300ms')
|
||||
->navigationGroups([
|
||||
// ── Daily Work (always visible, top of sidebar) ──
|
||||
NavigationGroup::make('Daily')
|
||||
->collapsible(false),
|
||||
|
||||
// ── Fundraising (campaigns, review queue) ──
|
||||
NavigationGroup::make('Fundraising')
|
||||
->icon('heroicon-o-megaphone')
|
||||
->collapsible(),
|
||||
|
||||
// ── Setup (rarely touched config) ──
|
||||
NavigationGroup::make('Setup')
|
||||
->icon('heroicon-o-cog-6-tooth')
|
||||
->collapsible()
|
||||
->collapsed(),
|
||||
])
|
||||
->brandLogo(Helpers::getCurrentLogo(true))
|
||||
->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources')
|
||||
->discoverPages(in: app_path('Filament/Pages'), for: 'App\\Filament\\Pages')
|
||||
->pages([\Filament\Pages\Dashboard::class])
|
||||
->userMenuItems([
|
||||
'profile' => MenuItem::make()
|
||||
->label('Edit profile')
|
||||
->url(url('user/profile')),
|
||||
|
||||
'back2site' => MenuItem::make()
|
||||
->label('Return to site')
|
||||
->icon('heroicon-o-home')
|
||||
->url(url('/')),
|
||||
])
|
||||
->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\\Filament\\Widgets')
|
||||
->middleware([
|
||||
EncryptCookies::class,
|
||||
AddQueuedCookiesToResponse::class,
|
||||
StartSession::class,
|
||||
AuthenticateSession::class,
|
||||
ShareErrorsFromSession::class,
|
||||
VerifyCsrfToken::class,
|
||||
SubstituteBindings::class,
|
||||
DisableBladeIconComponents::class,
|
||||
DispatchServingFilamentEvent::class,
|
||||
])
|
||||
->authMiddleware([
|
||||
Authenticate::class,
|
||||
])
|
||||
->login(null)
|
||||
->registration(null)
|
||||
->darkMode(false)
|
||||
->databaseNotifications();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user