mirror of
https://github.com/jcreek/SvelteKitSaasBoilerplate.git
synced 2026-07-12 18:43:50 +00:00
Merge branch 'master' into 11-add-the-description-and-fetching-the-images-from-stripe-on-checkout-success-page
This commit is contained in:
@@ -110,6 +110,31 @@ const createCustomerInStripe = async (uuid: string, email: string) => {
|
|||||||
return newCustomer.id;
|
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 }) => {
|
const createOrRetrieveCustomer = async ({ email, uuid }: { email: string; uuid: string }) => {
|
||||||
// Check if the customer already exists in Supabase
|
// Check if the customer already exists in Supabase
|
||||||
const { data: existingSupabaseCustomer, error: queryError } = await supabaseAdmin
|
const { data: existingSupabaseCustomer, error: queryError } = await supabaseAdmin
|
||||||
@@ -468,5 +493,7 @@ export {
|
|||||||
deductCredits,
|
deductCredits,
|
||||||
getUserCredits,
|
getUserCredits,
|
||||||
getActiveProductsWithPrices,
|
getActiveProductsWithPrices,
|
||||||
getProductById
|
getProductById,
|
||||||
|
upsertCustomerToSupabase,
|
||||||
|
getStripeCustomerId
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { type RequestHandler, redirect } from '@sveltejs/kit';
|
import { type RequestHandler, redirect } from '@sveltejs/kit';
|
||||||
import { stripe as stripeClient } from '$lib/utils/stripe';
|
import { stripe as stripeClient } from '$lib/utils/stripe';
|
||||||
|
import { getStripeCustomerId } from '$lib/utils/supabase/admin';
|
||||||
|
import type Stripe from 'stripe';
|
||||||
|
|
||||||
// Create a checkout session
|
// Create a checkout session
|
||||||
export const POST: RequestHandler = async ({ request, cookies, locals: { safeGetSession } }) => {
|
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
|
quantity: item.quantity
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const checkoutSession = await stripeClient.checkout.sessions.create({
|
let stripeCustomerId = null;
|
||||||
customer_email: userEmail,
|
|
||||||
|
// 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,
|
line_items: lineItems,
|
||||||
mode: 'payment',
|
mode: 'payment',
|
||||||
success_url: `${request.headers.get('origin')}/checkout/success`,
|
success_url: `${request.headers.get('origin')}/checkout/success`,
|
||||||
@@ -35,7 +45,18 @@ export const POST: RequestHandler = async ({ request, cookies, locals: { safeGet
|
|||||||
metadata: {
|
metadata: {
|
||||||
userId: userId
|
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
|
// Store the checkout session.id for access from the frontend
|
||||||
cookies.set('checkout_session_id', checkoutSession.id, { path: '/' });
|
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' });
|
||||||
|
};
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
import { type RequestHandler, redirect } from '@sveltejs/kit';
|
import { type RequestHandler, redirect } from '@sveltejs/kit';
|
||||||
import { stripe as stripeClient } from '$lib/utils/stripe';
|
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
|
// Create a subscription checkout session
|
||||||
export const POST: RequestHandler = async ({ request, cookies, locals: { safeGetSession } }) => {
|
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 userId = session.user.id;
|
||||||
|
const userEmail = session.user.email;
|
||||||
|
|
||||||
// Get the price ID from the request body
|
// Get the price ID from the request form data
|
||||||
const { priceId } = await request.json();
|
const formData = await request.formData();
|
||||||
|
const priceId = formData.get('priceId');
|
||||||
|
|
||||||
if (!priceId) {
|
if (!priceId) {
|
||||||
return new Response('Price ID is required', { status: 400 });
|
return new Response('Price ID is required', { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a subscription checkout session with Stripe
|
let stripeCustomerId = null;
|
||||||
const checkoutSession = await stripeClient.checkout.sessions.create({
|
|
||||||
|
// 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: [
|
line_items: [
|
||||||
{
|
{
|
||||||
price: priceId,
|
price: priceId,
|
||||||
@@ -33,7 +45,18 @@ export const POST: RequestHandler = async ({ request, cookies, locals: { safeGet
|
|||||||
metadata: {
|
metadata: {
|
||||||
userId: userId
|
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
|
// Store the checkout session.id for access from the frontend
|
||||||
cookies.set('checkout_session_id', checkoutSession.id, { path: '/' });
|
cookies.set('checkout_session_id', checkoutSession.id, { path: '/' });
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { redirect } from '@sveltejs/kit';
|
|||||||
import Stripe from 'stripe';
|
import Stripe from 'stripe';
|
||||||
import { getProductById } from '$lib/utils/supabase/admin';
|
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');
|
const response = await fetch('/api/checkout/status');
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
console.error('Failed to fetch checkout status');
|
console.error('Failed to fetch checkout status');
|
||||||
@@ -24,6 +24,15 @@ export const load = async ({ fetch }) => {
|
|||||||
lineItem.productDescription = product.description ?? '';
|
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 {
|
return {
|
||||||
checkoutSession,
|
checkoutSession,
|
||||||
lineItems
|
lineItems
|
||||||
|
|||||||
@@ -50,20 +50,6 @@
|
|||||||
});
|
});
|
||||||
}, 5000);
|
}, 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>
|
</script>
|
||||||
|
|
||||||
<section class="relative">
|
<section class="relative">
|
||||||
@@ -85,7 +71,8 @@
|
|||||||
<h6
|
<h6
|
||||||
class="font-manrope font-semibold text-2xl leading-9 text-gray-900 pr-5 sm:border-r border-gray-200 mr-5"
|
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>
|
</h6>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
@@ -373,12 +360,15 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{#if item.isSubscription}
|
{#if item.isSubscription}
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<button
|
<form method="POST" action="/api/subscribe">
|
||||||
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"
|
<input type="hidden" name="priceId" value={item.priceId} />
|
||||||
on:click={startSubscription}
|
<button
|
||||||
>
|
type="submit"
|
||||||
Subscribe
|
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"
|
||||||
</button>
|
>
|
||||||
|
Subscribe
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
Reference in New Issue
Block a user