diff --git a/src/lib/utils/supabase/admin.ts b/src/lib/utils/supabase/admin.ts index dff5cc0..4052695 100644 --- a/src/lib/utils/supabase/admin.ts +++ b/src/lib/utils/supabase/admin.ts @@ -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 @@ -468,5 +493,7 @@ export { deductCredits, getUserCredits, getActiveProductsWithPrices, - getProductById + getProductById, + upsertCustomerToSupabase, + getStripeCustomerId }; diff --git a/src/routes/api/checkout/+server.ts b/src/routes/api/checkout/+server.ts index 3753ec7..05d3272 100644 --- a/src/routes/api/checkout/+server.ts +++ b/src/routes/api/checkout/+server.ts @@ -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: '/' }); diff --git a/src/routes/api/checkout/create-customer/+server.ts b/src/routes/api/checkout/create-customer/+server.ts new file mode 100644 index 0000000..e8926f4 --- /dev/null +++ b/src/routes/api/checkout/create-customer/+server.ts @@ -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' }); +}; diff --git a/src/routes/api/subscribe/+server.ts b/src/routes/api/subscribe/+server.ts index ca7dafc..bcd7168 100644 --- a/src/routes/api/subscribe/+server.ts +++ b/src/routes/api/subscribe/+server.ts @@ -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: '/' }); diff --git a/src/routes/checkout/success/+page.server.ts b/src/routes/checkout/success/+page.server.ts index 54a41c5..fd0a112 100644 --- a/src/routes/checkout/success/+page.server.ts +++ b/src/routes/checkout/success/+page.server.ts @@ -2,7 +2,7 @@ import { redirect } from '@sveltejs/kit'; import Stripe from 'stripe'; import { getProductById } from '$lib/utils/supabase/admin'; -export const load = async ({ fetch }) => { +export const load: PageLoad = async ({ fetch }) => { const response = await fetch('/api/checkout/status'); if (!response.ok) { console.error('Failed to fetch checkout status'); @@ -24,6 +24,15 @@ export const load = async ({ fetch }) => { lineItem.productDescription = product.description ?? ''; } + // 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 diff --git a/src/routes/products/[productId]/+page.svelte b/src/routes/products/[productId]/+page.svelte index a481043..54c6e1d 100644 --- a/src/routes/products/[productId]/+page.svelte +++ b/src/routes/products/[productId]/+page.svelte @@ -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); - }); - }
@@ -85,7 +71,8 @@
- £{item.price} {item.isSubscription ? `/ ${item.interval}` : ''} + £{item.price} + {item.isSubscription ? `/ ${item.interval}` : ''}
@@ -373,12 +360,15 @@ {/if} {#if item.isSubscription}
- +
+ + +
{/if} {/if}