feat: implement Stripe checkout integration and add related API endpoints
This commit is contained in:
@@ -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 we’ll 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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user