97 lines
2.6 KiB
TypeScript
97 lines
2.6 KiB
TypeScript
/**
|
|
* 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 };
|
|
}
|
|
}
|