Merge pull request #46 from jcreek/45-checking-out-currently-creates-a-guest-customer-in-stripe

fix(#45): Enable customer creation when creating a stripe checkout session
This commit is contained in:
Olly
2024-10-22 19:33:16 +01:00
committed by GitHub
6 changed files with 118 additions and 33 deletions
+28 -1
View File
@@ -110,6 +110,31 @@ const createCustomerInStripe = async (uuid: string, email: string) => {
return newCustomer.id;
};
const getStripeCustomerId = async (email: string, uuid: string) => {
const { data: existingSupabaseCustomer, error: queryError } = await supabaseAdmin
.from('customers')
.select('*')
.eq('id', uuid)
.maybeSingle();
if (queryError) {
throw new Error(`Supabase customer lookup failed: ${queryError.message}`);
}
let stripeCustomerId: string | undefined;
if (existingSupabaseCustomer?.stripe_customer_id) {
const existingStripeCustomer = await stripeClient.customers.retrieve(
existingSupabaseCustomer.stripe_customer_id
);
stripeCustomerId = existingStripeCustomer.id;
} else {
const stripeCustomers = await stripeClient.customers.list({ email: email });
stripeCustomerId = stripeCustomers.data.length > 0 ? stripeCustomers.data[0].id : undefined;
}
return stripeCustomerId;
};
const createOrRetrieveCustomer = async ({ email, uuid }: { email: string; uuid: string }) => {
// Check if the customer already exists in Supabase
const { data: existingSupabaseCustomer, error: queryError } = await supabaseAdmin
@@ -453,5 +478,7 @@ export {
addCredits,
deductCredits,
getUserCredits,
getActiveProductsWithPrices
getActiveProductsWithPrices,
upsertCustomerToSupabase,
getStripeCustomerId
};
+24 -3
View File
@@ -1,5 +1,7 @@
import { type RequestHandler, redirect } from '@sveltejs/kit';
import { stripe as stripeClient } from '$lib/utils/stripe';
import { getStripeCustomerId } from '$lib/utils/supabase/admin';
import type Stripe from 'stripe';
// Create a checkout session
export const POST: RequestHandler = async ({ request, cookies, locals: { safeGetSession } }) => {
@@ -26,8 +28,16 @@ export const POST: RequestHandler = async ({ request, cookies, locals: { safeGet
quantity: item.quantity
}));
const checkoutSession = await stripeClient.checkout.sessions.create({
customer_email: userEmail,
let stripeCustomerId = null;
// Get the existing Stripe customer ID from Supabase, if it exists
try {
stripeCustomerId = await getStripeCustomerId(userEmail!, userId);
} catch (error) {
console.error('Error fetching Stripe customer ID:', error);
}
const stripeCheckoutSessionObject = {
line_items: lineItems,
mode: 'payment',
success_url: `${request.headers.get('origin')}/checkout/success`,
@@ -35,7 +45,18 @@ export const POST: RequestHandler = async ({ request, cookies, locals: { safeGet
metadata: {
userId: userId
}
});
} as Stripe.Checkout.SessionCreateParams;
if (stripeCustomerId) {
// If the user has a Stripe customer ID, attach it to the checkout session
stripeCheckoutSessionObject.customer = stripeCustomerId;
} else {
// If the user doesn't have a Stripe customer ID, create a new customer
stripeCheckoutSessionObject.customer_creation = 'always';
stripeCheckoutSessionObject.customer_email = userEmail;
}
const checkoutSession = await stripeClient.checkout.sessions.create(stripeCheckoutSessionObject);
// Store the checkout session.id for access from the frontend
cookies.set('checkout_session_id', checkoutSession.id, { path: '/' });
@@ -0,0 +1,16 @@
import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
import { upsertCustomerToSupabase } from '$lib/utils/supabase/admin';
export const GET: RequestHandler = async ({ url, locals: { safeGetSession } }) => {
const stripeCustomerId = url.searchParams.get('stripeCustomerId');
const { session } = await safeGetSession();
if (!session || !stripeCustomerId) {
return json({ error: 'Unauthorized' }, { status: 401 });
}
await upsertCustomerToSupabase(session?.user.id, stripeCustomerId);
return json({ message: 'Customer created' });
};
+28 -5
View File
@@ -1,5 +1,7 @@
import { type RequestHandler, redirect } from '@sveltejs/kit';
import { stripe as stripeClient } from '$lib/utils/stripe';
import { getStripeCustomerId } from '$lib/utils/supabase/admin';
import type Stripe from 'stripe';
// Create a subscription checkout session
export const POST: RequestHandler = async ({ request, cookies, locals: { safeGetSession } }) => {
@@ -11,16 +13,26 @@ export const POST: RequestHandler = async ({ request, cookies, locals: { safeGet
}
const userId = session.user.id;
const userEmail = session.user.email;
// Get the price ID from the request body
const { priceId } = await request.json();
// Get the price ID from the request form data
const formData = await request.formData();
const priceId = formData.get('priceId');
if (!priceId) {
return new Response('Price ID is required', { status: 400 });
}
// Create a subscription checkout session with Stripe
const checkoutSession = await stripeClient.checkout.sessions.create({
let stripeCustomerId = null;
// Get the existing Stripe customer ID from Supabase, if it exists
try {
stripeCustomerId = await getStripeCustomerId(userEmail!, userId);
} catch (error) {
console.error('Error fetching Stripe customer ID:', error);
}
const stripeCheckoutSessionObject = {
line_items: [
{
price: priceId,
@@ -33,7 +45,18 @@ export const POST: RequestHandler = async ({ request, cookies, locals: { safeGet
metadata: {
userId: userId
}
});
} as Stripe.Checkout.SessionCreateParams;
if (stripeCustomerId) {
// If the user has a Stripe customer ID, attach it to the checkout session
stripeCheckoutSessionObject.customer = stripeCustomerId;
} else {
// If the user doesn't have a Stripe customer ID, create a new customer with their email
stripeCheckoutSessionObject.customer_email = userEmail;
}
// Create a subscription checkout session with Stripe
const checkoutSession = await stripeClient.checkout.sessions.create(stripeCheckoutSessionObject);
// Store the checkout session.id for access from the frontend
cookies.set('checkout_session_id', checkoutSession.id, { path: '/' });
+11 -3
View File
@@ -2,8 +2,8 @@ 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');
export const load: PageLoad = async ({ fetch }) => {
const response = await fetch('/api/checkout/status');
if (!response.ok) {
console.error('Failed to fetch checkout status');
return;
@@ -18,10 +18,18 @@ export const load: PageLoad = async ({fetch}) => {
throw redirect(303, '/checkout/cancelled');
}
// Save the stripe customer id in the customers table
const stripeCustomerId = checkoutSession.customer as string;
const createCustomerResponse = await fetch(
'/api/checkout/create-customer?stripeCustomerId=' + stripeCustomerId
);
if (!createCustomerResponse.ok) {
console.error('Failed to create customer');
}
return {
checkoutSession,
lineItems,
paymentStatus
};
};
+11 -21
View File
@@ -50,20 +50,6 @@
});
}, 5000);
}
function startSubscription() {
fetch('/api/subscribe', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ priceId: item.priceId })
})
.then((response) => response.json())
.then((data) => {
console.log(data);
});
}
</script>
<section class="relative">
@@ -85,7 +71,8 @@
<h6
class="font-manrope font-semibold text-2xl leading-9 text-gray-900 pr-5 sm:border-r border-gray-200 mr-5"
>
£{item.price} {item.isSubscription ? `/ ${item.interval}` : ''}
£{item.price}
{item.isSubscription ? `/ ${item.interval}` : ''}
</h6>
<div class="flex items-center gap-2">
<div class="flex items-center gap-1">
@@ -373,12 +360,15 @@
{/if}
{#if item.isSubscription}
<div class="flex items-center gap-3">
<button
class="text-center w-full px-5 py-4 rounded-[100px] bg-indigo-600 flex items-center justify-center font-semibold text-lg text-white shadow-sm transition-all duration-500 hover:bg-indigo-700 hover:shadow-indigo-400"
on:click={startSubscription}
>
Subscribe
</button>
<form method="POST" action="/api/subscribe">
<input type="hidden" name="priceId" value={item.priceId} />
<button
type="submit"
class="text-center w-full px-5 py-4 rounded-[100px] bg-indigo-600 flex items-center justify-center font-semibold text-lg text-white shadow-sm transition-all duration-500 hover:bg-indigo-700 hover:shadow-indigo-400"
>
Subscribe
</button>
</form>
</div>
{/if}
{/if}