feat(#1): use stripe session to populate checkout success

This commit is contained in:
OllyNicholass
2024-05-15 01:26:57 +01:00
parent c4bd0d78bc
commit f5a6733698
6 changed files with 83 additions and 20 deletions
@@ -1,10 +1,12 @@
<script lang="ts"> <script lang="ts">
import { formatCurrency } from '$lib/utils/currency';
export let imgAlt = ''; export let imgAlt = '';
export let imgSrc = ''; export let imgSrc = '';
export let itemCategoryDescription = ''; // export let itemCategoryDescription = '';
export let itemName = ''; export let itemName = '';
export let price = 0; export let price = 0;
export let quantity = 0; export let quantity = 0;
export let currency = 'gbp';
</script> </script>
<div class="grid grid-cols-1 lg:grid-cols-2 min-[550px]:gap-6 border-b border-gray-200 py-6"> <div class="grid grid-cols-1 lg:grid-cols-2 min-[550px]:gap-6 border-b border-gray-200 py-6">
@@ -18,17 +20,17 @@
<h5 class="font-semibold text-xl leading-8 text-black max-[550px]:text-center"> <h5 class="font-semibold text-xl leading-8 text-black max-[550px]:text-center">
{itemName} {itemName}
</h5> </h5>
<p <!-- <p
class="font-normal text-lg leading-8 text-gray-500 my-2 min-[550px]:my-3 max-[550px]:text-center" class="font-normal text-lg leading-8 text-gray-500 my-2 min-[550px]:my-3 max-[550px]:text-center"
> >
{itemCategoryDescription} {itemCategoryDescription}
</p> </p> -->
<div class="flex items-center"> <div class="flex items-center">
<h6 <h6
class="font-medium text-lg leading-8 text-indigo-600 max-[550px]:text-center pr-4 mr-4 border-r border-gray-200" class="font-medium text-lg leading-8 text-indigo-600 max-[550px]:text-center pr-4 mr-4 border-r border-gray-200"
> >
£ {price.toFixed(2)} { formatCurrency(price, currency) }
</h6> </h6>
<p class="font-medium text-base leading-7 text-black"> <p class="font-medium text-base leading-7 text-black">
Qty: <span class="text-gray-500">{quantity}</span> Qty: <span class="text-gray-500">{quantity}</span>
@@ -43,7 +45,7 @@
<h6 <h6
class="text-indigo-600 font-manrope font-bold text-2xl leading-9 w-full max-w-[176px] text-center" class="text-indigo-600 font-manrope font-bold text-2xl leading-9 w-full max-w-[176px] text-center"
> >
£{(price * quantity).toFixed(2)} { formatCurrency(price, currency) }
</h6> </h6>
</div> </div>
</div> </div>
+6
View File
@@ -0,0 +1,6 @@
export function formatCurrency(amount: number, currency: string): string {
return new Intl.NumberFormat('en-GB', {
style: 'currency',
currency: currency,
}).format(amount);
}
+3 -4
View File
@@ -5,7 +5,7 @@ import stripe from 'stripe';
const stripeClient = new stripe(PUBLIC_STRIPE_SECRET_KEY); const stripeClient = new stripe(PUBLIC_STRIPE_SECRET_KEY);
// Create a checkout session // 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 formData = new URLSearchParams(await request.text());
const items: { priceId: string; quantity: number }[] = []; const items: { priceId: string; quantity: number }[] = [];
@@ -26,8 +26,7 @@ export const POST: RequestHandler = async ({ request }) => {
cancel_url: `${request.headers.get('origin')}/checkout/cancelled` cancel_url: `${request.headers.get('origin')}/checkout/cancelled`
}); });
// TODO - Store the checkout session.id for access from the frontend // Store the checkout session.id for access from the frontend
// https://docs.stripe.com/api/checkout/sessions/retrieve cookies.set('checkout_session_id', session.id, { path: '/' });
return redirect(303, session.url as string); return redirect(303, session.url as string);
}; };
+21
View File
@@ -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);
};
+19 -11
View File
@@ -2,19 +2,27 @@
import { onMount, onDestroy } from 'svelte'; import { onMount, onDestroy } from 'svelte';
import { basket, type Basket, type Item } from '$lib/stores/basket.js'; import { basket, type Basket, type Item } from '$lib/stores/basket.js';
import OrderConfirmationItem from '$lib/components/checkout/OrderConfirmationItem.svelte'; 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; let localBasket: Basket;
const unsubscribe = basket.subscribe((value) => { const unsubscribe = basket.subscribe((value) => {
localBasket = value; localBasket = value;
}); });
let {
checkoutSession,
lineItems,
paymentStatus
} = data;
onDestroy(unsubscribe); onDestroy(unsubscribe);
onMount(() => { onMount(() => {
calculateSubtotal(); calculateSubtotal();
// Create a deep copy of the basket to store the order confirmation then clear the basket // Clear the basket
orderConfirmationBasket = JSON.parse(JSON.stringify(localBasket));
localBasket = { items: [], subtotal: 0 }; localBasket = { items: [], subtotal: 0 };
basket.set(localBasket); basket.set(localBasket);
unsubscribe(); unsubscribe();
@@ -60,19 +68,17 @@
> >
</div> --> </div> -->
<div class="w-full px-3 min-[400px]:px-6"> <div class="w-full px-3 min-[400px]:px-6">
{#if orderConfirmationBasket == undefined} {#if lineItems.length == 0}
<p class="font-semibold text-lg leading-7 text-black text-center"> <p class="font-semibold text-lg leading-7 text-black text-center">
No items in the basket No items in the basket
</p> </p>
{:else} {:else}
{#each orderConfirmationBasket.items as item (item.id)} {#each lineItems as item (item.id)}
<OrderConfirmationItem <OrderConfirmationItem
itemCategoryDescription={item.categoryDescription} itemName={item.description}
itemName={item.name} price={item.amount_total / 100}
imgAlt={item.imgAlt}
imgSrc={item.imgSrc}
price={item.price}
quantity={item.quantity} quantity={item.quantity}
currency={item.currency}
/> />
{/each} {/each}
{/if} {/if}
@@ -82,7 +88,9 @@
> >
<div class="flex flex-col sm:flex-row items-center max-lg:border-b border-gray-200"></div> <div class="flex flex-col sm:flex-row items-center max-lg:border-b border-gray-200"></div>
<p class="font-semibold text-lg text-black py-6"> <p class="font-semibold text-lg text-black py-6">
Total Price: <span class="text-indigo-600"> $xx.xx</span> {#if checkoutSession?.amount_total != null}
Total Price: <span class="text-indigo-600"> {formatCurrency(checkoutSession?.amount_total / 100, checkoutSession?.currency ?? 'gbp')}</span>
{/if}
</p> </p>
</div> </div>
</div> </div>
+27
View File
@@ -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
};
};