From c9f4d63e70d1ba73ce3d4266721ee88bb85df4f1 Mon Sep 17 00:00:00 2001 From: notshop Date: Sun, 26 Apr 2026 16:36:19 +0000 Subject: [PATCH] chore: add server/payments/stripe.ts --- server/payments/stripe.ts | 97 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 server/payments/stripe.ts diff --git a/server/payments/stripe.ts b/server/payments/stripe.ts new file mode 100644 index 0000000..f63ab01 --- /dev/null +++ b/server/payments/stripe.ts @@ -0,0 +1,97 @@ +/** + * Stripe payment integration + * + * Configured via STRIPE_SECRET_KEY env var. + * STRIPE_PUBLISHABLE_KEY is exposed to the frontend via /api/config. + * + * Stripe is available as a secondary payment processor. + * Authorize.net is recommended as the primary for seamless checkout. + */ + +import Stripe from "stripe"; + +let _stripe: Stripe | null = null; + +function getStripe(): Stripe { + if (!_stripe) { + if (!process.env.STRIPE_SECRET_KEY) { + throw new Error("STRIPE_SECRET_KEY is not configured"); + } + _stripe = new Stripe(process.env.STRIPE_SECRET_KEY, { + apiVersion: "2024-06-20" as any, + }); + } + return _stripe; +} + +export function isStripeConfigured(): boolean { + return !!process.env.STRIPE_SECRET_KEY; +} + +export interface StripePaymentIntentResult { + success: boolean; + clientSecret?: string; + paymentIntentId?: string; + error?: string; +} + +/** + * Create a PaymentIntent for use with Stripe Elements on the frontend. + */ +export async function createPaymentIntent(params: { + amountCents: number; + currency?: string; + email: string; + metadata?: Record; +}): Promise { + try { + const stripe = getStripe(); + const intent = await stripe.paymentIntents.create({ + amount: params.amountCents, + currency: params.currency || "usd", + receipt_email: params.email, + metadata: params.metadata, + }); + return { + success: true, + clientSecret: intent.client_secret!, + paymentIntentId: intent.id, + }; + } catch (err: any) { + return { success: false, error: err.message }; + } +} + +/** + * Retrieve a PaymentIntent by ID and check if it succeeded. + */ +export async function verifyPaymentIntent( + paymentIntentId: string +): Promise<{ success: boolean; status?: string; error?: string }> { + try { + const stripe = getStripe(); + const intent = await stripe.paymentIntents.retrieve(paymentIntentId); + return { success: intent.status === "succeeded", status: intent.status }; + } catch (err: any) { + return { success: false, error: err.message }; + } +} + +/** + * Issue a refund against a PaymentIntent. + */ +export async function refundPaymentIntent(params: { + paymentIntentId: string; + amountCents?: number; +}): Promise<{ success: boolean; refundId?: string; error?: string }> { + try { + const stripe = getStripe(); + const refund = await stripe.refunds.create({ + payment_intent: params.paymentIntentId, + ...(params.amountCents && { amount: params.amountCents }), + }); + return { success: refund.status === "succeeded", refundId: refund.id }; + } catch (err: any) { + return { success: false, error: err.message }; + } +}