<script` lang="ts">
import { PUBLIC_STRIPE_KEY } from '$env/static/public';
import { onMount } from 'svelte';
import { loadStripe, type Stripe, type StripeError } from '@stripe/stripe-js';
import {
Button,
Column,
FluidForm,
Grid,
Loading,
Row,
TextInput,
Tile
} from 'carbon-components-svelte';
import { onDestroy } from 'svelte';
import { CardCvc, CardExpiry, CardNumber, Elements } from 'svelte-stripe';
import { goto } from '$app/navigation';
import { cart, type CartProduct } from '$lib/stores';
import Product from '../../components/Product.svelte';
import { prevent_default } from 'svelte/internal';
let elements: any;
let clientSecret: string | null = null;
let stripe: Stripe | null = null;
let cartItems: CartProduct[];
let cardElement: any;
let email = '';
let processing = false;
let error: StripeError | null;
let price: number | null;
const fetchSecret = async () => {
stripe = await loadStripe(PUBLIC_STRIPE_KEY);
let response = await fetch('/api/stripe/create-payment-intent', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ items: cartItems })
});
let data = await response.json();
clientSecret = data.client_secret;
price = data.total_price;
};
const unsubscribe = cart.subscribe((value) => (cartItems = value));
onMount(() => {
fetchSecret();
});
async function submit() {
// avoid processing duplicates
if (!stripe) {
return;
}
if (!clientSecret) {
return;
}
if (processing) return;
processing = true;
// confirm payment with stripe
const result = await stripe.confirmCardPayment(clientSecret, {
payment_method: {
card: cardElement,
billing_details: {
name: 'Oscar'
}
}
});
if (result.error) {
// payment failed, notify user
error = result.error;
processing = false;
} else {
// payment succeeded, redirect to "thank you" page
cart.clear();
goto('/thanks');
}
}
onDestroy(unsubscribe);
</script>
<svelte:head>Checkout</svelte:head>
{#if cartItems.length > 0}
{#if error != null}
{error}
{:else if clientSecret != null && stripe != null && price != null}
<Grid>
<Row>
<Column>
{#each cartItems as item}
<Product product={item.product} />
{/each}
</Column>
<Column>
<FluidForm
on:submit={async (e) => {
prevent_default(e);
await submit();
}}
>
<Elements
theme="night"
rules={{
'.Input': {
color: '#f4f4f4'
}
}}
bind:elements
{clientSecret}
{stripe}
>
<h3>{(price / 100).toFixed(1)} kr</h3>
<TextInput
labelText="Email"
bind:value={email}
placeholder="[email protected]"
required
/>
<div class="bx--form-item bx--text-input-wrapper">
<label for="cardnumber" class="bx--label">Card number</label>
<CardNumber
classes={{ base: 'bx--text-input cardinput' }}
bind:element={cardElement}
/>
</div>
<div class="flex">
<div class="bx--form-item bx--text-input-wrapper">
<label for="cardnumber" class="bx--label">Card expiration</label>
<CardExpiry classes={{ base: 'bx--text-input cardinput' }} />
</div>
<div class="bx--form-item bx--text-input-wrapper">
<label for="cardnumber" class="bx--label">CVC</label>
<CardCvc classes={{ base: 'bx--text-input cardinput' }} />
</div>
</div>
<Button type="submit" id="submit" class="my-4">Submit</Button>
</Elements>
</FluidForm>
</Column>
</Row>
</Grid>
{:else}
<Loading />
{/if}
{:else}
<div class="flex items-center justify-center">
<h2>Your cart is empty</h2>
</div>
{/if}
<style>
:global(.cardinput) {
color: #f4f4f4 !important;
}
</style>