mirror of
https://github.com/jcreek/SvelteKitSaasBoilerplate.git
synced 2026-07-12 18:43: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,
|
||||
name: product.name,
|
||||
description: product.description ?? null,
|
||||
image: product.images?.[0] ?? null,
|
||||
metadata: product.metadata
|
||||
features: product.marketing_features.map(({ name }) => name || '').filter((name) => !!name),
|
||||
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]);
|
||||
@@ -46,6 +49,8 @@ const upsertPriceRecord = async (price: stripe.Price, retryCount = 0, maxRetries
|
||||
product_id: typeof price.product === 'string' ? price.product : '',
|
||||
active: price.active,
|
||||
currency: price.currency,
|
||||
description: price.nickname ?? null,
|
||||
metadata: price.metadata,
|
||||
type: price.type,
|
||||
unit_amount: price.unit_amount ?? null,
|
||||
interval: price.recurring?.interval ?? null,
|
||||
@@ -208,9 +213,7 @@ const manageSubscriptionStatusChange = async (
|
||||
metadata: subscription.metadata,
|
||||
status: subscription.status,
|
||||
price_id: subscription.items.data[0].price.id,
|
||||
//TODO check quantity on subscription
|
||||
|
||||
quantity: subscription.quantity,
|
||||
quantity: 1, //subscription.quantity,
|
||||
cancel_at_period_end: subscription.cancel_at_period_end,
|
||||
cancel_at: subscription.cancel_at ? toDateTime(subscription.cancel_at).toISOString() : null,
|
||||
canceled_at: subscription.canceled_at
|
||||
@@ -232,13 +235,14 @@ const manageSubscriptionStatusChange = async (
|
||||
if (upsertError) throw new Error(`Subscription insert/update failed: ${upsertError.message}`);
|
||||
console.log(`Inserted/updated subscription [${subscription.id}] for user [${uuid}]`);
|
||||
|
||||
// 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.
|
||||
if (createAction && subscription.default_payment_method && uuid)
|
||||
await copyBillingDetailsToCustomer(
|
||||
uuid,
|
||||
subscription.default_payment_method as stripe.PaymentMethod
|
||||
);
|
||||
// // 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.
|
||||
// if (createAction && subscription.default_payment_method && uuid) {
|
||||
// await copyBillingDetailsToCustomer(
|
||||
// uuid,
|
||||
// subscription.default_payment_method as stripe.PaymentMethod
|
||||
// );
|
||||
// }
|
||||
};
|
||||
|
||||
export {
|
||||
|
||||
+16
-8
@@ -6,16 +6,24 @@
|
||||
$: ({ supabase, session } = data);
|
||||
</script>
|
||||
|
||||
<div class="hero bg-base-100 my-36">
|
||||
<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} />
|
||||
{#if session !== null}
|
||||
<div class="hero bg-base-100 my-36">
|
||||
Welcome back {session.user.email}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="hero bg-base-100 my-36">
|
||||
<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>
|
||||
{/if}
|
||||
|
||||
<div
|
||||
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 stripe from 'stripe';
|
||||
import { PUBLIC_STRIPE_SECRET_KEY } from '$env/static/public';
|
||||
import { PUBLIC_STRIPE_ENDPOINT_SECRET } from '$env/static/public';
|
||||
import { STRIPE_SECRET_KEY } from '$env/static/private';
|
||||
import { STRIPE_ENDPOINT_SECRET } from '$env/static/private';
|
||||
import {
|
||||
deletePriceRecord,
|
||||
deleteProductRecord,
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
upsertProductRecord
|
||||
} 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 }) => {
|
||||
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.
|
||||
// Otherwise use the basic event deserialized with JSON.parse
|
||||
if (PUBLIC_STRIPE_ENDPOINT_SECRET && signature) {
|
||||
if (STRIPE_ENDPOINT_SECRET && signature) {
|
||||
try {
|
||||
event = stripeClient.webhooks.constructEvent(
|
||||
body,
|
||||
signature,
|
||||
PUBLIC_STRIPE_ENDPOINT_SECRET
|
||||
);
|
||||
event = stripeClient.webhooks.constructEvent(body, signature, STRIPE_ENDPOINT_SECRET);
|
||||
} catch (err) {
|
||||
console.error(`⚠️ Webhook signature verification failed.`, err.message);
|
||||
return {
|
||||
@@ -44,6 +40,9 @@ export const POST: RequestHandler = async ({ request }) => {
|
||||
case 'product.updated':
|
||||
await upsertProductRecord(event.data.object as stripe.Product);
|
||||
break;
|
||||
case 'product.deleted':
|
||||
await deleteProductRecord(event.data.object as stripe.Product);
|
||||
break;
|
||||
case 'price.created':
|
||||
case 'price.updated':
|
||||
await upsertPriceRecord(event.data.object as stripe.Price);
|
||||
@@ -51,12 +50,11 @@ export const POST: RequestHandler = async ({ request }) => {
|
||||
case 'price.deleted':
|
||||
await deletePriceRecord(event.data.object as stripe.Price);
|
||||
break;
|
||||
case 'product.deleted':
|
||||
await deleteProductRecord(event.data.object as stripe.Product);
|
||||
break;
|
||||
case 'customer.subscription.created':
|
||||
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;
|
||||
await manageSubscriptionStatusChange(
|
||||
subscription.id,
|
||||
@@ -86,10 +84,7 @@ export const POST: RequestHandler = async ({ request }) => {
|
||||
status: 200
|
||||
});
|
||||
} catch (err) {
|
||||
console.log(`Webhook Error: ${err.message}`);
|
||||
return {
|
||||
status: 400,
|
||||
body: `Webhook Error: ${err.message}`
|
||||
};
|
||||
const message = err instanceof Error ? err.message : 'An unknown error has occurred';
|
||||
return new Response(`Webhook Error: ${message}`, { status: 500 });
|
||||
}
|
||||
};
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@
|
||||
"moduleResolution": "bundler",
|
||||
"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
|
||||
//
|
||||
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
||||
|
||||
Reference in New Issue
Block a user