77 lines
2.9 KiB
JavaScript
77 lines
2.9 KiB
JavaScript
/**
|
|
* Checkout service — the only place the frontend talks to Stripe / the payment
|
|
* API. The UI calls these functions; it never handles card data.
|
|
*
|
|
* `startCheckout` follows Stripe's recommended hosted-checkout flow:
|
|
* 1. Ask our backend to create a Checkout Session (amount resolved server-side).
|
|
* 2. Redirect the browser to the Stripe-hosted `session.url`.
|
|
* 3. Stripe handles payment, then redirects back to success_url / cancel_url.
|
|
*
|
|
* A Stripe.js `redirectToCheckout({ sessionId })` fallback (which uses the
|
|
* publishable key) is kept for older API behavior, but `session.url` is the
|
|
* current default and avoids loading Stripe.js for the redirect.
|
|
*/
|
|
|
|
import { loadStripe } from '@stripe/stripe-js';
|
|
import { stripeConfig } from '../config/stripeConfig';
|
|
|
|
let stripePromise;
|
|
const getStripe = () => {
|
|
if (!stripePromise) stripePromise = loadStripe(stripeConfig.publishableKey);
|
|
return stripePromise;
|
|
};
|
|
|
|
/**
|
|
* Create a session for an invoice and redirect to Stripe's hosted page.
|
|
* Throws (with a friendly message) if the backend isn't configured/reachable.
|
|
*/
|
|
export async function startCheckout({ invoiceId, customerEmail, fallback } = {}) {
|
|
if (!stripeConfig.isConfigured) {
|
|
const err = new Error(
|
|
'Stripe is not configured yet. Add your publishable key to enable payments.'
|
|
);
|
|
err.code = 'stripe_not_configured';
|
|
throw err;
|
|
}
|
|
|
|
const res = await fetch(`${stripeConfig.apiBase}/create-checkout-session`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
// `fallback` carries the invoice's amount/title for demo invoices the
|
|
// owner created in localStorage (the server's static map won't have them).
|
|
body: JSON.stringify({ invoiceId, customerEmail, fallback }),
|
|
});
|
|
|
|
const data = await res.json().catch(() => ({}));
|
|
if (!res.ok) {
|
|
throw new Error(data.error || 'Could not start checkout. Please try again.');
|
|
}
|
|
|
|
// Preferred: redirect straight to the Stripe-hosted Checkout URL.
|
|
if (data.url) {
|
|
window.location.assign(data.url);
|
|
return;
|
|
}
|
|
|
|
// Fallback: Stripe.js redirect using the session id + publishable key.
|
|
const stripe = await getStripe();
|
|
if (!stripe) throw new Error('Unable to initialize Stripe.');
|
|
const { error } = await stripe.redirectToCheckout({ sessionId: data.id });
|
|
if (error) throw new Error(error.message);
|
|
}
|
|
|
|
/**
|
|
* Fetch the verified status of a completed Checkout Session (used by the
|
|
* success return page). Returns the trimmed status object from the backend.
|
|
*/
|
|
export async function getSessionStatus(sessionId) {
|
|
const res = await fetch(
|
|
`${stripeConfig.apiBase}/session-status?session_id=${encodeURIComponent(sessionId)}`
|
|
);
|
|
const data = await res.json().catch(() => ({}));
|
|
if (!res.ok) {
|
|
throw new Error(data.error || 'Could not verify payment status.');
|
|
}
|
|
return data;
|
|
}
|