mirror of
https://github.com/jcreek/SvelteKitSaasBoilerplate.git
synced 2026-07-13 02:53:50 +00:00
Merge branch '29-refactor-stripe-and-supabase-implementation' of https://github.com/jcreek/SvelteKitSaasBoilerplate into 29-refactor-stripe-and-supabase-implementation
This commit is contained in:
@@ -31,8 +31,11 @@ const upsertProductRecord = async (product: stripe.Product) => {
|
|||||||
active: product.active,
|
active: product.active,
|
||||||
name: product.name,
|
name: product.name,
|
||||||
description: product.description ?? null,
|
description: product.description ?? null,
|
||||||
image: product.images?.[0] ?? null,
|
features: product.marketing_features.map(({ name }) => name || '').filter((name) => !!name),
|
||||||
metadata: product.metadata
|
images: product.images ?? null,
|
||||||
|
metadata: product.metadata,
|
||||||
|
created: new Date(product.created * 1000).toISOString(),
|
||||||
|
updated: new Date(product.updated * 1000).toISOString()
|
||||||
};
|
};
|
||||||
|
|
||||||
const { error: upsertError } = await supabaseAdmin.from('products').upsert([productData]);
|
const { error: upsertError } = await supabaseAdmin.from('products').upsert([productData]);
|
||||||
@@ -46,6 +49,8 @@ const upsertPriceRecord = async (price: stripe.Price, retryCount = 0, maxRetries
|
|||||||
product_id: typeof price.product === 'string' ? price.product : '',
|
product_id: typeof price.product === 'string' ? price.product : '',
|
||||||
active: price.active,
|
active: price.active,
|
||||||
currency: price.currency,
|
currency: price.currency,
|
||||||
|
description: price.nickname ?? null,
|
||||||
|
metadata: price.metadata,
|
||||||
type: price.type,
|
type: price.type,
|
||||||
unit_amount: price.unit_amount ?? null,
|
unit_amount: price.unit_amount ?? null,
|
||||||
interval: price.recurring?.interval ?? null,
|
interval: price.recurring?.interval ?? null,
|
||||||
@@ -208,9 +213,7 @@ const manageSubscriptionStatusChange = async (
|
|||||||
metadata: subscription.metadata,
|
metadata: subscription.metadata,
|
||||||
status: subscription.status,
|
status: subscription.status,
|
||||||
price_id: subscription.items.data[0].price.id,
|
price_id: subscription.items.data[0].price.id,
|
||||||
//TODO check quantity on subscription
|
quantity: 1, //subscription.quantity,
|
||||||
|
|
||||||
quantity: subscription.quantity,
|
|
||||||
cancel_at_period_end: subscription.cancel_at_period_end,
|
cancel_at_period_end: subscription.cancel_at_period_end,
|
||||||
cancel_at: subscription.cancel_at ? toDateTime(subscription.cancel_at).toISOString() : null,
|
cancel_at: subscription.cancel_at ? toDateTime(subscription.cancel_at).toISOString() : null,
|
||||||
canceled_at: subscription.canceled_at
|
canceled_at: subscription.canceled_at
|
||||||
@@ -232,13 +235,14 @@ const manageSubscriptionStatusChange = async (
|
|||||||
if (upsertError) throw new Error(`Subscription insert/update failed: ${upsertError.message}`);
|
if (upsertError) throw new Error(`Subscription insert/update failed: ${upsertError.message}`);
|
||||||
console.log(`Inserted/updated subscription [${subscription.id}] for user [${uuid}]`);
|
console.log(`Inserted/updated subscription [${subscription.id}] for user [${uuid}]`);
|
||||||
|
|
||||||
// For a new subscription copy the billing details to the customer object.
|
// // For a new subscription copy the billing details to the customer object.
|
||||||
// NOTE: This is a costly operation and should happen at the very end.
|
// // NOTE: This is a costly operation and should happen at the very end.
|
||||||
if (createAction && subscription.default_payment_method && uuid)
|
// if (createAction && subscription.default_payment_method && uuid) {
|
||||||
await copyBillingDetailsToCustomer(
|
// await copyBillingDetailsToCustomer(
|
||||||
uuid,
|
// uuid,
|
||||||
subscription.default_payment_method as stripe.PaymentMethod
|
// subscription.default_payment_method as stripe.PaymentMethod
|
||||||
);
|
// );
|
||||||
|
// }
|
||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|||||||
+16
-8
@@ -6,16 +6,24 @@
|
|||||||
$: ({ supabase, session } = data);
|
$: ({ supabase, session } = data);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="hero bg-base-100 my-36">
|
{#if session !== null}
|
||||||
<div class="hero-content flex-col lg:flex-row-reverse">
|
<div class="hero bg-base-100 my-36">
|
||||||
<div class="text-center lg:text-left">
|
Welcome back {session.user.email}
|
||||||
<h1 class="text-5xl font-bold">Sign up for this amazing SaaS right now - don't miss out!</h1>
|
</div>
|
||||||
</div>
|
{:else}
|
||||||
<div id="sign-up" class="card shrink-0 w-full max-w-sm shadow-2xl">
|
<div class="hero bg-base-100 my-36">
|
||||||
<SignUp {supabase} />
|
<div class="hero-content flex-col lg:flex-row-reverse">
|
||||||
|
<div class="text-center lg:text-left">
|
||||||
|
<h1 class="text-5xl font-bold">
|
||||||
|
Sign up for this amazing SaaS right now - don't miss out!
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
<div id="sign-up" class="card shrink-0 w-full max-w-sm shadow-2xl">
|
||||||
|
<SignUp {supabase} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{/if}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="md:container md:mx-auto justify-self-center flex justify-center items-center h-full flex-col mb-10"
|
class="md:container md:mx-auto justify-self-center flex justify-center items-center h-full flex-col mb-10"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { RequestHandler } from '@sveltejs/kit';
|
import type { RequestHandler } from '@sveltejs/kit';
|
||||||
import stripe from 'stripe';
|
import stripe from 'stripe';
|
||||||
import { PUBLIC_STRIPE_SECRET_KEY } from '$env/static/public';
|
import { STRIPE_SECRET_KEY } from '$env/static/private';
|
||||||
import { PUBLIC_STRIPE_ENDPOINT_SECRET } from '$env/static/public';
|
import { STRIPE_ENDPOINT_SECRET } from '$env/static/private';
|
||||||
import {
|
import {
|
||||||
deletePriceRecord,
|
deletePriceRecord,
|
||||||
deleteProductRecord,
|
deleteProductRecord,
|
||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
upsertProductRecord
|
upsertProductRecord
|
||||||
} from '$lib/utils/supabase/admin';
|
} from '$lib/utils/supabase/admin';
|
||||||
|
|
||||||
const stripeClient = new stripe(PUBLIC_STRIPE_SECRET_KEY);
|
const stripeClient = new stripe(STRIPE_SECRET_KEY);
|
||||||
|
|
||||||
export const POST: RequestHandler = async ({ request }) => {
|
export const POST: RequestHandler = async ({ request }) => {
|
||||||
const body = await request.text();
|
const body = await request.text();
|
||||||
@@ -23,13 +23,9 @@ export const POST: RequestHandler = async ({ request }) => {
|
|||||||
|
|
||||||
// Only verify the event if you have an endpoint secret defined.
|
// Only verify the event if you have an endpoint secret defined.
|
||||||
// Otherwise use the basic event deserialized with JSON.parse
|
// Otherwise use the basic event deserialized with JSON.parse
|
||||||
if (PUBLIC_STRIPE_ENDPOINT_SECRET && signature) {
|
if (STRIPE_ENDPOINT_SECRET && signature) {
|
||||||
try {
|
try {
|
||||||
event = stripeClient.webhooks.constructEvent(
|
event = stripeClient.webhooks.constructEvent(body, signature, STRIPE_ENDPOINT_SECRET);
|
||||||
body,
|
|
||||||
signature,
|
|
||||||
PUBLIC_STRIPE_ENDPOINT_SECRET
|
|
||||||
);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`⚠️ Webhook signature verification failed.`, err.message);
|
console.error(`⚠️ Webhook signature verification failed.`, err.message);
|
||||||
return {
|
return {
|
||||||
@@ -44,6 +40,9 @@ export const POST: RequestHandler = async ({ request }) => {
|
|||||||
case 'product.updated':
|
case 'product.updated':
|
||||||
await upsertProductRecord(event.data.object as stripe.Product);
|
await upsertProductRecord(event.data.object as stripe.Product);
|
||||||
break;
|
break;
|
||||||
|
case 'product.deleted':
|
||||||
|
await deleteProductRecord(event.data.object as stripe.Product);
|
||||||
|
break;
|
||||||
case 'price.created':
|
case 'price.created':
|
||||||
case 'price.updated':
|
case 'price.updated':
|
||||||
await upsertPriceRecord(event.data.object as stripe.Price);
|
await upsertPriceRecord(event.data.object as stripe.Price);
|
||||||
@@ -51,12 +50,11 @@ export const POST: RequestHandler = async ({ request }) => {
|
|||||||
case 'price.deleted':
|
case 'price.deleted':
|
||||||
await deletePriceRecord(event.data.object as stripe.Price);
|
await deletePriceRecord(event.data.object as stripe.Price);
|
||||||
break;
|
break;
|
||||||
case 'product.deleted':
|
|
||||||
await deleteProductRecord(event.data.object as stripe.Product);
|
|
||||||
break;
|
|
||||||
case 'customer.subscription.created':
|
case 'customer.subscription.created':
|
||||||
case 'customer.subscription.updated':
|
case 'customer.subscription.updated':
|
||||||
case 'customer.subscription.deleted': {
|
case 'customer.subscription.deleted':
|
||||||
|
case 'customer.subscription.paused':
|
||||||
|
case 'customer.subscription.resumed': {
|
||||||
const subscription = event.data.object as stripe.Subscription;
|
const subscription = event.data.object as stripe.Subscription;
|
||||||
await manageSubscriptionStatusChange(
|
await manageSubscriptionStatusChange(
|
||||||
subscription.id,
|
subscription.id,
|
||||||
@@ -86,10 +84,7 @@ export const POST: RequestHandler = async ({ request }) => {
|
|||||||
status: 200
|
status: 200
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(`Webhook Error: ${err.message}`);
|
const message = err instanceof Error ? err.message : 'An unknown error has occurred';
|
||||||
return {
|
return new Response(`Webhook Error: ${message}`, { status: 500 });
|
||||||
status: 400,
|
|
||||||
body: `Webhook Error: ${err.message}`
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
+1
-1
@@ -12,7 +12,7 @@
|
|||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"module": "es2015"
|
"module": "es2015"
|
||||||
},
|
},
|
||||||
"include": ["src/**/*", "node_modules/**/*.d.ts"]
|
"include": ["src/**/*", "node_modules/**/*.d.ts", ".svelte-kit/ambient.d.ts"]
|
||||||
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
|
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
|
||||||
//
|
//
|
||||||
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
||||||
|
|||||||
Reference in New Issue
Block a user