From 62be4606438612baf596a8e5f96d353d087afaae Mon Sep 17 00:00:00 2001 From: Omair Saleh Date: Wed, 4 Mar 2026 22:29:49 +0800 Subject: [PATCH] Remove dead Stripe integration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- pledge-now-pay-later/package-lock.json | 28 -- pledge-now-pay-later/package.json | 2 - .../src/app/api/stripe/checkout/route.ts | 118 ------- .../src/app/api/stripe/webhook/route.ts | 88 ----- .../src/app/p/[token]/page.tsx | 10 +- .../app/p/[token]/steps/card-payment-step.tsx | 305 ------------------ .../app/p/[token]/steps/confirmation-step.tsx | 13 +- .../src/app/p/[token]/steps/payment-step.tsx | 18 +- .../src/app/p/success/page.tsx | 8 +- pledge-now-pay-later/src/app/page.tsx | 7 - pledge-now-pay-later/src/app/privacy/page.tsx | 2 +- pledge-now-pay-later/src/app/terms/page.tsx | 2 +- pledge-now-pay-later/src/lib/stripe.ts | 127 -------- 13 files changed, 12 insertions(+), 716 deletions(-) delete mode 100644 pledge-now-pay-later/src/app/api/stripe/checkout/route.ts delete mode 100644 pledge-now-pay-later/src/app/api/stripe/webhook/route.ts delete mode 100644 pledge-now-pay-later/src/app/p/[token]/steps/card-payment-step.tsx delete mode 100644 pledge-now-pay-later/src/lib/stripe.ts diff --git a/pledge-now-pay-later/package-lock.json b/pledge-now-pay-later/package-lock.json index 64c7752..043ae42 100644 --- a/pledge-now-pay-later/package-lock.json +++ b/pledge-now-pay-later/package-lock.json @@ -11,7 +11,6 @@ "@auth/prisma-adapter": "^2.11.1", "@prisma/adapter-pg": "^7.4.2", "@prisma/client": "^7.4.2", - "@stripe/stripe-js": "^8.8.0", "@types/bcryptjs": "^2.4.6", "@types/qrcode": "^1.5.6", "bcryptjs": "^3.0.3", @@ -29,7 +28,6 @@ "react": "^18", "react-dom": "^18", "sharp": "^0.34.5", - "stripe": "^20.4.0", "tailwind-merge": "^3.5.0", "zod": "^4.3.6" }, @@ -1794,15 +1792,6 @@ "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", "license": "MIT" }, - "node_modules/@stripe/stripe-js": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-8.8.0.tgz", - "integrity": "sha512-NNYuyW8qmLjyHnpyFgs/23wUrjB8k0xN9YIZFOMLewCa/pIkIji9e9aY/EgdNryEDDRptc6TcPIHRvG1R0ClFw==", - "license": "MIT", - "engines": { - "node": ">=12.16" - } - }, "node_modules/@swc/counter": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", @@ -7792,23 +7781,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/stripe": { - "version": "20.4.0", - "resolved": "https://registry.npmjs.org/stripe/-/stripe-20.4.0.tgz", - "integrity": "sha512-F/aN1IQ9vHmlyLNi3DkiIbyzQb6gyBG0uYFd/VrEVQSc9BLtlgknPUx0EvzZdBMRLFuRaPFIFd7Mxwtg7Pbwzw==", - "license": "MIT", - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "@types/node": ">=16" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, "node_modules/styled-jsx": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", diff --git a/pledge-now-pay-later/package.json b/pledge-now-pay-later/package.json index 6a34a12..af7d5f8 100644 --- a/pledge-now-pay-later/package.json +++ b/pledge-now-pay-later/package.json @@ -12,7 +12,6 @@ "@auth/prisma-adapter": "^2.11.1", "@prisma/adapter-pg": "^7.4.2", "@prisma/client": "^7.4.2", - "@stripe/stripe-js": "^8.8.0", "@types/bcryptjs": "^2.4.6", "@types/qrcode": "^1.5.6", "bcryptjs": "^3.0.3", @@ -30,7 +29,6 @@ "react": "^18", "react-dom": "^18", "sharp": "^0.34.5", - "stripe": "^20.4.0", "tailwind-merge": "^3.5.0", "zod": "^4.3.6" }, diff --git a/pledge-now-pay-later/src/app/api/stripe/checkout/route.ts b/pledge-now-pay-later/src/app/api/stripe/checkout/route.ts deleted file mode 100644 index 8c9ec5d..0000000 --- a/pledge-now-pay-later/src/app/api/stripe/checkout/route.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { NextRequest, NextResponse } from "next/server" -import prisma from "@/lib/prisma" -import { createCheckoutSession } from "@/lib/stripe" -import { generateReference } from "@/lib/reference" - -export async function POST(request: NextRequest) { - try { - const body = await request.json() - const { amountPence, donorName, donorEmail, donorPhone, giftAid, eventId, qrSourceId } = body - - if (!prisma) { - return NextResponse.json({ error: "Database not configured" }, { status: 503 }) - } - - // Get event + org - const event = await prisma.event.findUnique({ - where: { id: eventId }, - include: { organization: true }, - }) - if (!event) { - return NextResponse.json({ error: "Event not found" }, { status: 404 }) - } - - const org = event.organization - - // Generate reference - let reference = "" - let attempts = 0 - while (attempts < 10) { - reference = generateReference(org.refPrefix || "PNPL", amountPence) - const exists = await prisma.pledge.findUnique({ where: { reference } }) - if (!exists) break - attempts++ - } - - // Create pledge in DB - const pledge = await prisma.pledge.create({ - data: { - reference, - amountPence, - currency: "GBP", - rail: "card", - status: "new", - donorName: donorName || null, - donorEmail: donorEmail || null, - donorPhone: donorPhone || null, - giftAid: giftAid || false, - eventId, - qrSourceId: qrSourceId || null, - organizationId: org.id, - }, - }) - - // Track analytics - await prisma.analyticsEvent.create({ - data: { - eventType: "pledge_completed", - pledgeId: pledge.id, - eventId, - qrSourceId: qrSourceId || null, - metadata: { amountPence, rail: "card" }, - }, - }) - - // Try real Stripe checkout - const baseUrl = process.env.BASE_URL || "http://localhost:3000" - - const session = await createCheckoutSession({ - amountPence, - currency: "GBP", - pledgeId: pledge.id, - reference, - eventName: event.name, - organizationName: org.name, - donorEmail: donorEmail || undefined, - successUrl: `${baseUrl}/p/success?pledge_id=${pledge.id}&rail=card&session_id={CHECKOUT_SESSION_ID}`, - cancelUrl: `${baseUrl}/p/success?pledge_id=${pledge.id}&rail=card&cancelled=true`, - }) - - if (session) { - // Save Stripe session reference - await prisma.payment.create({ - data: { - pledgeId: pledge.id, - provider: "stripe", - providerRef: session.sessionId, - amountPence, - status: "pending", - matchedBy: "auto", - }, - }) - - await prisma.pledge.update({ - where: { id: pledge.id }, - data: { status: "initiated" }, - }) - - return NextResponse.json({ - mode: "live", - pledgeId: pledge.id, - reference, - checkoutUrl: session.checkoutUrl, - sessionId: session.sessionId, - }) - } - - // Fallback: no Stripe configured — return pledge for simulated flow - return NextResponse.json({ - mode: "simulated", - pledgeId: pledge.id, - reference, - id: pledge.id, - }) - } catch (error) { - console.error("Stripe checkout error:", error) - return NextResponse.json({ error: "Internal error" }, { status: 500 }) - } -} diff --git a/pledge-now-pay-later/src/app/api/stripe/webhook/route.ts b/pledge-now-pay-later/src/app/api/stripe/webhook/route.ts deleted file mode 100644 index 7296b0d..0000000 --- a/pledge-now-pay-later/src/app/api/stripe/webhook/route.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { NextRequest, NextResponse } from "next/server" -import prisma from "@/lib/prisma" -import { constructWebhookEvent } from "@/lib/stripe" - -export async function POST(request: NextRequest) { - try { - const body = await request.text() - const signature = request.headers.get("stripe-signature") || "" - - const event = constructWebhookEvent(body, signature) - if (!event) { - return NextResponse.json({ error: "Invalid signature" }, { status: 400 }) - } - - switch (event.type) { - case "checkout.session.completed": { - const session = event.data.object as { id: string; metadata: Record; payment_status: string } - const pledgeId = session.metadata?.pledge_id - - if (pledgeId && session.payment_status === "paid") { - await prisma.pledge.update({ - where: { id: pledgeId }, - data: { - status: "paid", - paidAt: new Date(), - }, - }) - - // Update payment record - await prisma.payment.updateMany({ - where: { - pledgeId, - providerRef: session.id, - }, - data: { - status: "confirmed", - receivedAt: new Date(), - }, - }) - - // Track analytics - await prisma.analyticsEvent.create({ - data: { - eventType: "payment_matched", - pledgeId, - metadata: { provider: "stripe", sessionId: session.id }, - }, - }) - } - break - } - - case "payment_intent.succeeded": { - const pi = event.data.object as { id: string; metadata: Record } - const pledgeId = pi.metadata?.pledge_id - - if (pledgeId) { - await prisma.pledge.update({ - where: { id: pledgeId }, - data: { - status: "paid", - paidAt: new Date(), - }, - }) - } - break - } - - case "payment_intent.payment_failed": { - const pi = event.data.object as { id: string; metadata: Record } - const pledgeId = pi.metadata?.pledge_id - - if (pledgeId) { - await prisma.pledge.update({ - where: { id: pledgeId }, - data: { status: "overdue" }, - }) - } - break - } - } - - return NextResponse.json({ received: true }) - } catch (error) { - console.error("Stripe webhook error:", error) - return NextResponse.json({ error: "Webhook handler failed" }, { status: 500 }) - } -} diff --git a/pledge-now-pay-later/src/app/p/[token]/page.tsx b/pledge-now-pay-later/src/app/p/[token]/page.tsx index 9275e68..9324c2d 100644 --- a/pledge-now-pay-later/src/app/p/[token]/page.tsx +++ b/pledge-now-pay-later/src/app/p/[token]/page.tsx @@ -9,10 +9,9 @@ import { IdentityStep } from "./steps/identity-step" import { ConfirmationStep } from "./steps/confirmation-step" import { BankInstructionsStep } from "./steps/bank-instructions-step" import { ExternalRedirectStep } from "./steps/external-redirect-step" -import { CardPaymentStep } from "./steps/card-payment-step" import { DirectDebitStep } from "./steps/direct-debit-step" -export type Rail = "bank" | "gocardless" | "card" +export type Rail = "bank" | "gocardless" export interface PledgeData { amountPence: number @@ -143,7 +142,7 @@ export default function PledgePage() { // Step 2: Payment method selected (only for "now" self-payment mode) const handleRailSelected = (rail: Rail) => { setPledgeData((d) => ({ ...d, rail })) - setStep(rail === "bank" ? 3 : rail === "card" ? 6 : 8) + setStep(rail === "bank" ? 3 : 8) } // Submit pledge (from identity step, or card/DD steps) @@ -243,20 +242,19 @@ export default function PledgePage() { installmentAmount={pledgeData.installmentCount ? Math.ceil(pledgeData.amountPence / pledgeData.installmentCount) : undefined} /> ), - 6: , 7: pledgeResult && , 8: , } const backableSteps = new Set([1, 2, 3, 6, 8]) const getBackStep = (s: number): number => { - if (s === 6 || s === 8) return 2 // card/DD → payment method + if (s === 8) return 2 // DD → payment method if (s === 3 && pledgeData.scheduleMode !== "now") return 1 // deferred identity → schedule if (s === 3) return 2 // bank identity → payment method return s - 1 } - const progressMap: Record = { 0: 8, 1: 25, 2: 40, 3: 60, 4: 100, 5: 100, 6: 60, 7: 100, 8: 60 } + const progressMap: Record = { 0: 8, 1: 25, 2: 40, 3: 60, 4: 100, 5: 100, 7: 100, 8: 60 } const progressPercent = progressMap[step] || 10 return ( diff --git a/pledge-now-pay-later/src/app/p/[token]/steps/card-payment-step.tsx b/pledge-now-pay-later/src/app/p/[token]/steps/card-payment-step.tsx deleted file mode 100644 index e112806..0000000 --- a/pledge-now-pay-later/src/app/p/[token]/steps/card-payment-step.tsx +++ /dev/null @@ -1,305 +0,0 @@ -"use client" - -import { useState, useRef } from "react" -import { Button } from "@/components/ui/button" -import { Input } from "@/components/ui/input" -import { Label } from "@/components/ui/label" -import { CreditCard, Lock } from "lucide-react" - -interface Props { - amount: number - eventName: string - eventId: string - qrSourceId: string | null - onComplete: (identity: { - donorName: string - donorEmail: string - donorPhone: string - giftAid: boolean - }) => void -} - -function formatCardNumber(value: string): string { - const digits = value.replace(/\D/g, "").slice(0, 16) - return digits.replace(/(\d{4})(?=\d)/g, "$1 ") -} - -function formatExpiry(value: string): string { - const digits = value.replace(/\D/g, "").slice(0, 4) - if (digits.length >= 3) return digits.slice(0, 2) + "/" + digits.slice(2) - return digits -} - -function luhnCheck(num: string): boolean { - const digits = num.replace(/\D/g, "") - if (digits.length < 13) return false - let sum = 0 - let alt = false - for (let i = digits.length - 1; i >= 0; i--) { - let n = parseInt(digits[i], 10) - if (alt) { - n *= 2 - if (n > 9) n -= 9 - } - sum += n - alt = !alt - } - return sum % 10 === 0 -} - -function getCardBrand(num: string): string { - const d = num.replace(/\D/g, "") - if (/^4/.test(d)) return "Visa" - if (/^5[1-5]/.test(d) || /^2[2-7]/.test(d)) return "Mastercard" - if (/^3[47]/.test(d)) return "Amex" - if (/^6(?:011|5)/.test(d)) return "Discover" - return "" -} - -export function CardPaymentStep({ amount, eventName, eventId, qrSourceId, onComplete }: Props) { - const [cardNumber, setCardNumber] = useState("") - const [expiry, setExpiry] = useState("") - const [cvc, setCvc] = useState("") - const [name, setName] = useState("") - const [email, setEmail] = useState("") - const [giftAid, setGiftAid] = useState(false) - const [processing, setProcessing] = useState(false) - const [errors, setErrors] = useState>({}) - - const expiryRef = useRef(null) - const cvcRef = useRef(null) - - const pounds = (amount / 100).toFixed(2) - const brand = getCardBrand(cardNumber) - - const validate = (): boolean => { - const errs: Record = {} - const digits = cardNumber.replace(/\D/g, "") - - if (!luhnCheck(digits)) errs.card = "Invalid card number" - if (digits.length < 13) errs.card = "Card number too short" - - const expiryDigits = expiry.replace(/\D/g, "") - if (expiryDigits.length < 4) { - errs.expiry = "Invalid expiry" - } else { - const month = parseInt(expiryDigits.slice(0, 2), 10) - const year = parseInt("20" + expiryDigits.slice(2, 4), 10) - const now = new Date() - if (month < 1 || month > 12) errs.expiry = "Invalid month" - else if (year < now.getFullYear() || (year === now.getFullYear() && month < now.getMonth() + 1)) - errs.expiry = "Card expired" - } - - if (cvc.length < 3) errs.cvc = "Invalid CVC" - if (!name.trim()) errs.name = "Name required" - if (!email.includes("@")) errs.email = "Valid email required" - - setErrors(errs) - return Object.keys(errs).length === 0 - } - - const handleSubmit = async () => { - if (!validate()) return - setProcessing(true) - - // Try real Stripe Checkout first - try { - const res = await fetch("/api/stripe/checkout", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - amountPence: amount, - donorName: name, - donorEmail: email, - donorPhone: "", - giftAid, - eventId, - qrSourceId, - }), - }) - const data = await res.json() - - if (data.mode === "live" && data.checkoutUrl) { - // Redirect to Stripe Checkout - window.location.href = data.checkoutUrl - return - } - - // Simulated mode — fall through to onComplete - } catch { - // Fall through to simulated - } - - // Simulated fallback - await new Promise((r) => setTimeout(r, 1500)) - onComplete({ - donorName: name, - donorEmail: email, - donorPhone: "", - giftAid, - }) - } - - const handleCardNumberChange = (value: string) => { - const formatted = formatCardNumber(value) - setCardNumber(formatted) - // Auto-advance to expiry when complete - if (formatted.replace(/\s/g, "").length === 16) { - expiryRef.current?.focus() - } - } - - const handleExpiryChange = (value: string) => { - const formatted = formatExpiry(value) - setExpiry(formatted) - if (formatted.length === 5) { - cvcRef.current?.focus() - } - } - - const isReady = cardNumber.replace(/\D/g, "").length >= 13 && expiry.length === 5 && cvc.length >= 3 && name.trim() && email.includes("@") - - return ( -
-
-

Pay by Card

-

- Pledge: £{pounds}{" "} - for {eventName} -

-
- - {/* Card form */} -
- {/* Card number */} -
- -
- - handleCardNumberChange(e.target.value)} - inputMode="numeric" - autoComplete="cc-number" - className={`pl-11 font-mono text-base ${errors.card ? "border-red-500" : ""}`} - /> - {brand && ( - - {brand} - - )} -
- {errors.card &&

{errors.card}

} -
- - {/* Expiry + CVC row */} -
-
- - handleExpiryChange(e.target.value)} - inputMode="numeric" - autoComplete="cc-exp" - maxLength={5} - className={`font-mono text-base ${errors.expiry ? "border-red-500" : ""}`} - /> - {errors.expiry &&

{errors.expiry}

} -
-
- - setCvc(e.target.value.replace(/\D/g, "").slice(0, 4))} - inputMode="numeric" - autoComplete="cc-csc" - maxLength={4} - className={`font-mono text-base ${errors.cvc ? "border-red-500" : ""}`} - /> - {errors.cvc &&

{errors.cvc}

} -
-
- - {/* Cardholder name */} -
- - setName(e.target.value)} - autoComplete="cc-name" - className={errors.name ? "border-red-500" : ""} - /> - {errors.name &&

{errors.name}

} -
- - {/* Email */} -
- - setEmail(e.target.value)} - autoComplete="email" - inputMode="email" - className={errors.email ? "border-red-500" : ""} - /> - {errors.email &&

{errors.email}

} -
-
- - {/* Gift Aid */} - - - {/* Pay button */} - - -
- - Secured with 256-bit encryption -
-
- ) -} diff --git a/pledge-now-pay-later/src/app/p/[token]/steps/confirmation-step.tsx b/pledge-now-pay-later/src/app/p/[token]/steps/confirmation-step.tsx index f2f38c9..a3837ac 100644 --- a/pledge-now-pay-later/src/app/p/[token]/steps/confirmation-step.tsx +++ b/pledge-now-pay-later/src/app/p/[token]/steps/confirmation-step.tsx @@ -61,7 +61,6 @@ export function ConfirmationStep({ pledge, amount, rail, eventName, shareUrl, do const railLabels: Record = { bank: "Bank Transfer", gocardless: "Direct Debit", - card: "Card Payment", } const deferredMessage = isDeferred @@ -73,7 +72,6 @@ export function ConfirmationStep({ pledge, amount, rail, eventName, shareUrl, do const nextStepMessages: Record = { bank: deferredMessage || "We've sent you payment instructions. Transfer at your convenience — we'll confirm once received.", gocardless: `Your Direct Debit mandate is set up. £${(amount / 100).toFixed(2)} will be collected automatically in 3-5 working days. Protected by the Direct Debit Guarantee.`, - card: "Your card payment has been processed. Confirmation email is on its way.", } // Send WhatsApp receipt if phone provided @@ -140,7 +138,7 @@ export function ConfirmationStep({ pledge, amount, rail, eventName, shareUrl, do

{isDeferred ? "Pledge Locked In!" - : rail === "card" ? "Payment Complete!" : rail === "gocardless" ? "Mandate Set Up!" : "Pledge Received!"} + : rail === "gocardless" ? "Mandate Set Up!" : "Pledge Received!"}

Thank you for your generous support of{" "} @@ -178,14 +176,7 @@ export function ConfirmationStep({ pledge, amount, rail, eventName, shareUrl, do )} - {rail === "card" && !isDeferred && ( -

- Status - - Paid - -
- )} + diff --git a/pledge-now-pay-later/src/app/p/[token]/steps/payment-step.tsx b/pledge-now-pay-later/src/app/p/[token]/steps/payment-step.tsx index 32b2896..6309d88 100644 --- a/pledge-now-pay-later/src/app/p/[token]/steps/payment-step.tsx +++ b/pledge-now-pay-later/src/app/p/[token]/steps/payment-step.tsx @@ -1,9 +1,9 @@ "use client" -import { Building2, CreditCard, Landmark, Shield, CheckCircle2 } from "lucide-react" +import { Building2, Landmark, Shield, CheckCircle2 } from "lucide-react" interface Props { - onSelect: (rail: "bank" | "gocardless" | "card") => void + onSelect: (rail: "bank" | "gocardless") => void amount: number } @@ -40,20 +40,6 @@ export function PaymentStep({ onSelect, amount }: Props) { highlight: false, benefits: ["No action needed", "DD Guarantee"], }, - { - id: "card" as const, - icon: CreditCard, - title: "Card Payment", - subtitle: "Visa, Mastercard, Amex — instant", - tag: "Instant", - tagClass: "bg-purple-100 text-purple-700", - detail: "Powered by Stripe. Receipt emailed instantly.", - fee: "1.4% + 20p", - feeClass: "text-muted-foreground", - iconBg: "bg-midnight", - highlight: false, - benefits: ["Instant confirmation", "All major cards"], - }, ] return ( diff --git a/pledge-now-pay-later/src/app/p/success/page.tsx b/pledge-now-pay-later/src/app/p/success/page.tsx index 61a0422..75a6755 100644 --- a/pledge-now-pay-later/src/app/p/success/page.tsx +++ b/pledge-now-pay-later/src/app/p/success/page.tsx @@ -19,7 +19,7 @@ interface PledgeInfo { function SuccessContent() { const searchParams = useSearchParams() const pledgeId = searchParams.get("pledge_id") - const rail = searchParams.get("rail") || "card" + const rail = searchParams.get("rail") || "bank" const cancelled = searchParams.get("cancelled") === "true" const [pledge, setPledge] = useState(null) const [loading, setLoading] = useState(true) @@ -74,16 +74,12 @@ function SuccessContent() { } const railLabels: Record = { - card: "Card Payment", gocardless: "Direct Debit", - fpx: "FPX Online Banking", bank: "Bank Transfer", } const nextStepMessages: Record = { - card: "Your card payment has been processed. You'll receive a confirmation email shortly.", gocardless: "Your Direct Debit mandate has been set up. The payment will be collected automatically in 3-5 working days.", - fpx: "Your FPX payment has been received and verified.", bank: "Please complete the bank transfer using the reference provided.", } @@ -127,7 +123,7 @@ function SuccessContent() { )}

What happens next?

-

{nextStepMessages[rail] || nextStepMessages.card}

+

{nextStepMessages[rail] || nextStepMessages.bank}

Need help? Contact the charity directly.{pledge && <> Ref: {pledge.reference}} diff --git a/pledge-now-pay-later/src/app/page.tsx b/pledge-now-pay-later/src/app/page.tsx index 646446f..caa9fe6 100644 --- a/pledge-now-pay-later/src/app/page.tsx +++ b/pledge-now-pay-later/src/app/page.tsx @@ -663,13 +663,6 @@ export default function HomePage() { desc: "UK-native charity platform. Gift Aid handled at their end or ours — your choice.", tag: "Fundraising", }, - { - name: "Stripe", - logo: "/images/logos/stripe.svg", - color: "#635BFF", - desc: "Accept card payments directly. PCI compliant. Money lands in your Stripe account.", - tag: "Card payments", - }, { name: "UK Bank Transfer", logo: null, diff --git a/pledge-now-pay-later/src/app/privacy/page.tsx b/pledge-now-pay-later/src/app/privacy/page.tsx index a042262..8e23476 100644 --- a/pledge-now-pay-later/src/app/privacy/page.tsx +++ b/pledge-now-pay-later/src/app/privacy/page.tsx @@ -112,7 +112,7 @@ export default function PrivacyPage() {

10. Third-Party Services

  • GoCardless — for Direct Debit mandate processing (if enabled by charity)
  • -
  • Stripe — for card payment processing (if enabled by charity)
  • +
  • OpenAI — for AI-powered features (amount suggestions, reminder copy). No donor PII is sent to OpenAI — only anonymised context.
diff --git a/pledge-now-pay-later/src/app/terms/page.tsx b/pledge-now-pay-later/src/app/terms/page.tsx index 8ec1cd1..294a4bd 100644 --- a/pledge-now-pay-later/src/app/terms/page.tsx +++ b/pledge-now-pay-later/src/app/terms/page.tsx @@ -38,7 +38,7 @@ export default function TermsPage() {

5. Payment Processing

-

PNPL is not a payment processor. We facilitate pledge tracking and follow-up. Actual payment flows through your bank account, GoCardless, or Stripe. We are not liable for payment disputes, chargebacks, or failed transactions.

+

PNPL is not a payment processor. We facilitate pledge tracking and follow-up. Actual payment flows through your bank account or GoCardless. We are not liable for payment disputes, chargebacks, or failed transactions.

diff --git a/pledge-now-pay-later/src/lib/stripe.ts b/pledge-now-pay-later/src/lib/stripe.ts deleted file mode 100644 index 1c732e0..0000000 --- a/pledge-now-pay-later/src/lib/stripe.ts +++ /dev/null @@ -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 - } -}