feat: implement Stripe checkout integration and add related API endpoints

This commit is contained in:
Frank John Begornia
2025-11-08 01:47:14 +08:00
parent 86f9cf803a
commit 0ff41822af
12 changed files with 443 additions and 97 deletions

View File

@@ -5,6 +5,7 @@ import DesignerToolbar from "~/components/designer/DesignerToolbar.vue";
import TemplatePicker from "~/components/designer/TemplatePicker.vue";
import { useSlipmatDesigner } from "~/composables/useSlipmatDesigner";
import type { ExportedDesign } from "~/composables/useSlipmatDesigner";
const {
templates,
@@ -38,12 +39,88 @@ const {
resetZoom,
} = useSlipmatDesigner();
const DESIGN_PRICE_USD = 39.99;
const { user } = useAuth();
const loginModal = useLoginModal();
const isCheckoutPending = ref(false);
const checkoutError = ref<string | null>(null);
const lastExportedDesign = ref<ExportedDesign | null>(null);
const handleTemplateSelect = (templateId: string) => {
selectTemplate(templateId);
};
const handleExport = async () => {
await exportDesign();
const result = await exportDesign();
if (result) {
lastExportedDesign.value = result;
}
};
const handleCheckout = async () => {
if (!process.client) {
return;
}
if (!user.value) {
loginModal.value = true;
return;
}
checkoutError.value = null;
isCheckoutPending.value = true;
try {
let exportResult = await exportDesign();
if (!exportResult) {
exportResult = lastExportedDesign.value;
}
if (!exportResult) {
throw new Error("Unable to export the current design. Please try again.");
}
lastExportedDesign.value = exportResult;
const designId =
typeof crypto !== "undefined" && "randomUUID" in crypto
? crypto.randomUUID()
: `design-${Date.now()}`;
const successUrlTemplate = `${window.location.origin}/checkout/success?session_id={CHECKOUT_SESSION_ID}`;
const cancelUrl = window.location.href;
const session = await $fetch<{ id: string; url?: string | null }>("/api/checkout.session", {
method: "POST",
body: {
designId,
templateId: exportResult.templateId,
designName: templateLabel.value,
amount: DESIGN_PRICE_USD,
currency: "usd",
successUrl: successUrlTemplate,
cancelUrl,
customerEmail: user.value?.email,
},
});
if (!session?.id) {
throw new Error("Stripe session could not be created.");
}
if (session.url) {
window.location.href = session.url;
return;
}
const fallbackRedirect = successUrlTemplate.replace("{CHECKOUT_SESSION_ID}", session.id);
window.location.href = fallbackRedirect;
} catch (err: any) {
console.error("Checkout failed", err);
checkoutError.value = err?.message ?? "Unable to start checkout. Please try again.";
} finally {
isCheckoutPending.value = false;
}
};
</script>
@@ -58,11 +135,6 @@ const handleExport = async () => {
<h1 class="text-3xl font-bold text-white sm:text-4xl">
Craft custom slipmats ready for the pressing plant.
</h1>
<p class="max-w-3xl text-base text-slate-300">
Pick a template, drop in artwork, and well generate both a high-fidelity
preview and a print-ready PNG at exact specs. Everything stays within a
circular safe zone to ensure clean results on vinyl.
</p>
</header>
<section class="mt-10 grid gap-8 lg:grid-cols-[320px_minmax(0,1fr)]">
@@ -98,9 +170,13 @@ const handleExport = async () => {
:template-label="templateLabel"
:production-pixels="productionPixelSize"
:is-exporting="isExporting"
:is-checkout-pending="isCheckoutPending"
:checkout-price="DESIGN_PRICE_USD"
:checkout-error="checkoutError"
@export="handleExport"
@download-preview="downloadPreview"
@download-production="downloadProduction"
@checkout="handleCheckout"
/>
</div>