chore: add server/payments/stripe.ts

This commit is contained in:
notshop 2026-04-26 16:36:19 +00:00
parent f948547f03
commit c9f4d63e70

97
server/payments/stripe.ts Normal file
View file

@ -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<string, string>;
}): Promise<StripePaymentIntentResult> {
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 };
}
}