Remove dead Stripe integration

Stripe was wired up but never used:
- No STRIPE_SECRET_KEY in .env
- Card payment step had a 'simulated fallback' that pretended to charge
- Stripe fees (1.4% + 20p) contradict '100% goes to charity' brand promise
- Bank transfer is the primary rail, GoCardless (DD) is the secondary

Removed:
- src/lib/stripe.ts (Stripe client, checkout sessions, webhooks)
- src/app/api/stripe/checkout/route.ts
- src/app/api/stripe/webhook/route.ts
- src/app/p/[token]/steps/card-payment-step.tsx (263 lines)
- 'stripe' and '@stripe/stripe-js' npm packages
- Card option from PaymentStep (payment-step.tsx)
- Card references from confirmation-step.tsx, success/page.tsx
- Stripe from landing page integrations grid
- Stripe from privacy policy sub-processors
- Stripe from terms of service payment references

Type Rail changed: 'bank' | 'gocardless' | 'card' → 'bank' | 'gocardless'
Pledge flow bundle: 19.5kB → 18.2kB (-1.3kB)

Payment options donors now see:
1. Bank Transfer (recommended, zero fees)
2. Direct Debit via GoCardless (1% + 20p, hassle-free)
This commit is contained in:
2026-03-04 22:29:49 +08:00
parent f75cc29980
commit 62be460643
13 changed files with 12 additions and 716 deletions

View File

@@ -1,127 +0,0 @@
import Stripe from "stripe"
let stripeClient: Stripe | null = null
export function getStripe(): Stripe | null {
if (stripeClient) return stripeClient
const key = process.env.STRIPE_SECRET_KEY
if (!key || key === "sk_test_REPLACE_ME") return null
stripeClient = new Stripe(key, {
apiVersion: "2025-01-27.acacia" as Stripe.LatestApiVersion,
typescript: true,
})
return stripeClient
}
/**
* Create a Stripe Checkout Session for a card payment.
* Returns the checkout URL to redirect the donor to.
*/
export async function createCheckoutSession(opts: {
amountPence: number
currency: string
pledgeId: string
reference: string
eventName: string
organizationName: string
donorEmail?: string
successUrl: string
cancelUrl: string
}): Promise<{ sessionId: string; checkoutUrl: string } | null> {
const stripe = getStripe()
if (!stripe) return null
try {
const session = await stripe.checkout.sessions.create({
mode: "payment",
payment_method_types: ["card"],
line_items: [
{
price_data: {
currency: opts.currency.toLowerCase(),
unit_amount: opts.amountPence,
product_data: {
name: `Donation — ${opts.eventName}`,
description: `Pledge ref: ${opts.reference} to ${opts.organizationName}`,
},
},
quantity: 1,
},
],
customer_email: opts.donorEmail || undefined,
metadata: {
pledge_id: opts.pledgeId,
reference: opts.reference,
},
success_url: opts.successUrl,
cancel_url: opts.cancelUrl,
})
return {
sessionId: session.id,
checkoutUrl: session.url!,
}
} catch (error) {
console.error("Stripe checkout session error:", error)
return null
}
}
/**
* Create a Stripe Payment Intent for embedded payment (Stripe Elements).
* Returns client secret for frontend confirmation.
*/
export async function createPaymentIntent(opts: {
amountPence: number
currency: string
pledgeId: string
reference: string
donorEmail?: string
}): Promise<{ clientSecret: string; paymentIntentId: string } | null> {
const stripe = getStripe()
if (!stripe) return null
try {
const pi = await stripe.paymentIntents.create({
amount: opts.amountPence,
currency: opts.currency.toLowerCase(),
metadata: {
pledge_id: opts.pledgeId,
reference: opts.reference,
},
receipt_email: opts.donorEmail || undefined,
automatic_payment_methods: {
enabled: true,
},
})
return {
clientSecret: pi.client_secret!,
paymentIntentId: pi.id,
}
} catch (error) {
console.error("Stripe payment intent error:", error)
return null
}
}
/**
* Verify a Stripe webhook signature.
*/
export function constructWebhookEvent(
body: string | Buffer,
signature: string
): Stripe.Event | null {
const stripe = getStripe()
const secret = process.env.STRIPE_WEBHOOK_SECRET
if (!stripe || !secret || secret === "whsec_REPLACE_ME") return null
try {
return stripe.webhooks.constructEvent(body, signature, secret)
} catch (error) {
console.error("Stripe webhook signature verification failed:", error)
return null
}
}