feat(*): Add basket functionality

This commit is contained in:
Josh Creek
2024-05-05 12:32:37 +01:00
parent d7e24928fc
commit a46ed76fb9
5 changed files with 311 additions and 28 deletions
+18 -17
View File
@@ -1,28 +1,29 @@
import { type RequestHandler, redirect } from '@sveltejs/kit';
// This is a public sample test API key.
// Dont submit any personally identifiable information in requests made with this key.
// Sign in to see your own test API key embedded in code samples.
// eslint-disable-next-line @typescript-eslint/no-var-requires
import { PUBLIC_STRIPE_SECRET_KEY } from '$env/static/public';
import stripe from 'stripe';
const stripeSecretKey =
'sk_test_51PBMpZLiKlsU6INBHEsWwU2ekDfmotWTZghSl0XRLhtWp6mU7cBIfyBs6eBnG5kgC6MUhnvH5lUm2AbSWxgGfnKm00qTpaUnAP';
const stripeClient = new stripe(stripeSecretKey);
const stripeClient = new stripe(PUBLIC_STRIPE_SECRET_KEY);
// Create a checkout session
export const POST: RequestHandler = async ({ request }) => {
// const { priceId } = await request.json(); // Assuming you'll send the price ID from the client
const formData = new URLSearchParams(await request.text());
const items: { priceId: string; quantity: number }[] = [];
// Iterate over form data to extract basket items and their details
for (const [key, value] of formData) {
items.push({ priceId: key, quantity: parseInt(value, 10) });
}
const lineItems = items.map((item) => ({
price: item.priceId,
quantity: item.quantity
}));
const session = await stripeClient.checkout.sessions.create({
line_items: [
{
// Provide the exact Price ID (for example, pr_1234) of the product you want to sell
price: 'price_1PBNHRLiKlsU6INBjXMcRxxS', //priceId,
quantity: 1
}
],
line_items: lineItems,
mode: 'payment',
success_url: `${request.headers.get('origin')}/checkout/success`,
cancel_url: `${request.headers.get('origin')}/checkout/cancel`
cancel_url: `${request.headers.get('origin')}/checkout/cancelled`
});
return redirect(303, session.url as string);
+125 -10
View File
@@ -1,14 +1,129 @@
<script src="https://js.stripe.com/v3/"></script>
<script src="https://js.stripe.com/v3/" lang="ts">
import { onMount, onDestroy } from 'svelte';
import { basket, type Basket, type Item } from '$lib/stores/basket.js';
import BasketItem from '$lib/components/checkout/BasketItem.svelte';
<section>
<div class="product">
<img src="https://i.imgur.com/EHyR2nP.png" alt="The cover of Stubborn Attachments" />
<div class="description">
<h3>Stubborn Attachments</h3>
<h5>$20.00</h5>
let localBasket: Basket;
const unsubscribe = basket.subscribe((value) => {
localBasket = value;
});
onDestroy(unsubscribe);
onMount(() => {
calculateSubtotal();
});
function calculateSubtotal() {
let subtotal = 0;
localBasket.items.forEach((item) => {
subtotal += item.price * item.quantity;
});
localBasket.subtotal = subtotal;
}
function removeItem(event: CustomEvent<{ itemId: number }>) {
localBasket.items = localBasket.items.filter((item) => item.id !== event.detail.itemId);
basket.set(localBasket);
calculateSubtotal();
}
function changeQuantity(event: CustomEvent<{ itemId: number; quantity: number }>) {
localBasket.items = localBasket.items.map((item) => {
if (item.id === event.detail.itemId) {
item.quantity = event.detail.quantity;
}
return item;
});
basket.set(localBasket);
calculateSubtotal();
}
</script>
<section class="py-24 relative">
<div class="w-full max-w-7xl px-4 md:px-5 lg-6 mx-auto">
<h2 class="title font-manrope font-bold text-4xl leading-10 mb-8 text-center text-black">
Basket
</h2>
<div class="hidden lg:grid grid-cols-2 py-6">
<div class="font-normal text-xl leading-8 text-gray-500">Product</div>
<p class="font-normal text-xl leading-8 text-gray-500 flex items-center justify-between">
<span class="w-full max-w-[260px] text-center">Quantity</span>
<span class="w-full max-w-[200px] text-center">Total</span>
</p>
</div>
{#each localBasket.items as item (item.id)}
<BasketItem
itemCategoryDescription={item.categoryDescription}
itemId={item.id}
itemName={item.name}
imgAlt={item.imgAlt}
imgSrc={item.imgSrc}
price={item.price}
quantity={item.quantity}
on:changeQuantity={changeQuantity}
on:removeItem={removeItem}
/>
{/each}
<div class="bg-gray-50 rounded-xl p-6 w-full mb-8 max-lg:max-w-xl max-lg:mx-auto">
<div class="flex items-center justify-between w-full pb-6 border-b border-gray-200">
<p class="font-normal text-base leading-8 text-gray-400">
Shipping, taxes and discounts calculated at checkout
</p>
</div>
<div class="flex items-center justify-between w-full py-6">
<p class="font-manrope font-medium text-2xl leading-9 text-gray-900">Subtotal</p>
<h6 class="font-manrope font-medium text-2xl leading-9 text-indigo-500">
£{localBasket.subtotal.toFixed(2)}
</h6>
</div>
</div>
<form action="/api/checkout" method="POST">
{#each localBasket.items as item (item.id)}
<input type="hidden" name={item.priceId} value={item.quantity} />
{/each}
<div class="flex items-center flex-col sm:flex-row justify-center gap-3 mt-8">
<a href="/" class="btn">
Continue shopping
<svg
xmlns="http://www.w3.org/2000/svg"
width="22"
height="22"
viewBox="0 0 22 22"
fill="none"
>
<path
d="M8.25324 5.49609L13.7535 10.9963L8.25 16.4998"
stroke="#4F46E5"
stroke-width="1.6"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</a>
<button
type="submit"
class="btn btn-primary"
disabled={localBasket.items && localBasket.items.length === 0}
>Continue to Payment
<svg
class="ml-2"
xmlns="http://www.w3.org/2000/svg"
width="23"
height="22"
viewBox="0 0 23 22"
fill="none"
>
<path
d="M8.75324 5.49609L14.2535 10.9963L8.75 16.4998"
stroke="white"
stroke-width="1.6"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</button>
</div>
</form>
</div>
<form action="/api/checkout" method="POST">
<button type="submit" id="checkout-button">Checkout</button>
</form>
</section>