From f5a6733698b4351bd6e0fed1dd93c3f7d2e782b7 Mon Sep 17 00:00:00 2001 From: OllyNicholass Date: Wed, 15 May 2024 01:26:57 +0100 Subject: [PATCH] feat(#1): use stripe session to populate checkout success --- .../checkout/OrderConfirmationItem.svelte | 12 ++++---- src/lib/utils/currency.ts | 6 ++++ src/routes/api/checkout/+server.ts | 7 ++--- src/routes/api/checkout/status/+server.ts | 21 +++++++++++++ src/routes/checkout/success/+page.svelte | 30 ++++++++++++------- src/routes/checkout/success/+page.ts | 27 +++++++++++++++++ 6 files changed, 83 insertions(+), 20 deletions(-) create mode 100644 src/lib/utils/currency.ts create mode 100644 src/routes/api/checkout/status/+server.ts create mode 100644 src/routes/checkout/success/+page.ts diff --git a/src/lib/components/checkout/OrderConfirmationItem.svelte b/src/lib/components/checkout/OrderConfirmationItem.svelte index abdca69..2a9bb5a 100644 --- a/src/lib/components/checkout/OrderConfirmationItem.svelte +++ b/src/lib/components/checkout/OrderConfirmationItem.svelte @@ -1,10 +1,12 @@
@@ -18,17 +20,17 @@
{itemName}
-

{itemCategoryDescription} -

+

-->
- £ {price.toFixed(2)} + { formatCurrency(price, currency) }

Qty: {quantity} @@ -43,7 +45,7 @@

- £{(price * quantity).toFixed(2)} + { formatCurrency(price, currency) }
diff --git a/src/lib/utils/currency.ts b/src/lib/utils/currency.ts new file mode 100644 index 0000000..952be19 --- /dev/null +++ b/src/lib/utils/currency.ts @@ -0,0 +1,6 @@ +export function formatCurrency(amount: number, currency: string): string { + return new Intl.NumberFormat('en-GB', { + style: 'currency', + currency: currency, + }).format(amount); +} \ No newline at end of file diff --git a/src/routes/api/checkout/+server.ts b/src/routes/api/checkout/+server.ts index 8b6c306..28f0a8d 100644 --- a/src/routes/api/checkout/+server.ts +++ b/src/routes/api/checkout/+server.ts @@ -5,7 +5,7 @@ import stripe from 'stripe'; const stripeClient = new stripe(PUBLIC_STRIPE_SECRET_KEY); // Create a checkout session -export const POST: RequestHandler = async ({ request }) => { +export const POST: RequestHandler = async ({ request, cookies }) => { const formData = new URLSearchParams(await request.text()); const items: { priceId: string; quantity: number }[] = []; @@ -26,8 +26,7 @@ export const POST: RequestHandler = async ({ request }) => { cancel_url: `${request.headers.get('origin')}/checkout/cancelled` }); - // TODO - Store the checkout session.id for access from the frontend - // https://docs.stripe.com/api/checkout/sessions/retrieve - + // Store the checkout session.id for access from the frontend + cookies.set('checkout_session_id', session.id, { path: '/' }); return redirect(303, session.url as string); }; diff --git a/src/routes/api/checkout/status/+server.ts b/src/routes/api/checkout/status/+server.ts new file mode 100644 index 0000000..dde7781 --- /dev/null +++ b/src/routes/api/checkout/status/+server.ts @@ -0,0 +1,21 @@ +import { json } from '@sveltejs/kit'; +import { PUBLIC_STRIPE_SECRET_KEY } from '$env/static/public'; +import stripe from 'stripe'; +import type { RequestHandler } from './$types'; +const stripeClient = new stripe(PUBLIC_STRIPE_SECRET_KEY); + +export const GET: RequestHandler = async ({ cookies }) => { + const sessionId = cookies.get('checkout_session_id'); + if (!sessionId) { + return json({ error: 'No checkout session found' }, { status: 400 }); + } + const checkoutSession = await stripeClient.checkout.sessions.retrieve(sessionId, { expand: ['line_items'] }); + + const statusResponse = { + checkoutSession: checkoutSession, + paymentStatus: checkoutSession.payment_status, + lineItems: checkoutSession.line_items + }; + + return json(statusResponse); +}; diff --git a/src/routes/checkout/success/+page.svelte b/src/routes/checkout/success/+page.svelte index cd8fab6..45d89b7 100644 --- a/src/routes/checkout/success/+page.svelte +++ b/src/routes/checkout/success/+page.svelte @@ -2,19 +2,27 @@ import { onMount, onDestroy } from 'svelte'; import { basket, type Basket, type Item } from '$lib/stores/basket.js'; import OrderConfirmationItem from '$lib/components/checkout/OrderConfirmationItem.svelte'; + import { formatCurrency } from '$lib/utils/currency'; + import type { PageData } from './$types'; - let orderConfirmationBasket: Basket; + export let data: PageData; let localBasket: Basket; const unsubscribe = basket.subscribe((value) => { localBasket = value; }); + + let { + checkoutSession, + lineItems, + paymentStatus + } = data; + onDestroy(unsubscribe); onMount(() => { calculateSubtotal(); - // Create a deep copy of the basket to store the order confirmation then clear the basket - orderConfirmationBasket = JSON.parse(JSON.stringify(localBasket)); + // Clear the basket localBasket = { items: [], subtotal: 0 }; basket.set(localBasket); unsubscribe(); @@ -60,19 +68,17 @@ > -->
- {#if orderConfirmationBasket == undefined} + {#if lineItems.length == 0}

No items in the basket

{:else} - {#each orderConfirmationBasket.items as item (item.id)} + {#each lineItems as item (item.id)} {/each} {/if} @@ -82,7 +88,9 @@ >

- Total Price: $xx.xx + {#if checkoutSession?.amount_total != null} + Total Price: {formatCurrency(checkoutSession?.amount_total / 100, checkoutSession?.currency ?? 'gbp')} + {/if}

diff --git a/src/routes/checkout/success/+page.ts b/src/routes/checkout/success/+page.ts new file mode 100644 index 0000000..82c4ce4 --- /dev/null +++ b/src/routes/checkout/success/+page.ts @@ -0,0 +1,27 @@ +import type { PageLoad } from './$types'; +import { redirect } from '@sveltejs/kit'; +import Stripe from 'stripe'; + +export const load: PageLoad = async ({fetch}) => { + const response = await fetch('/api/checkout/status'); + if (!response.ok) { + console.error('Failed to fetch checkout status'); + return; + } + + const responseJson = await response.json(); + const checkoutSession: Stripe.Checkout.Session = responseJson.checkoutSession; + const lineItems: Stripe.LineItem[] = responseJson.lineItems.data; + const paymentStatus: Stripe.Checkout.Session.PaymentStatus = responseJson.paymentStatus; + + if (paymentStatus == 'unpaid') { + throw redirect(303, '/checkout/cancelled'); + } + + return { + checkoutSession, + lineItems, + paymentStatus + }; +}; +