mirror of
https://github.com/jcreek/SvelteKitSaasBoilerplate.git
synced 2026-07-12 18:43:50 +00:00
feat(#29): Add ability to record that a user has purchased a product - part 2
This commit is contained in:
@@ -16,11 +16,11 @@ To get the types for the tables, you can follow these instructions taken from [h
|
|||||||
|
|
||||||
`npx supabase init`
|
`npx supabase init`
|
||||||
|
|
||||||
Replace `$PROJECT_REF` with your project reference:
|
Replace `$PROJECT_REF` with your project reference and replace `types/supabase.d.ts` with whatever file you want your types in:
|
||||||
`npx supabase gen types typescript --project-id "$PROJECT_REF" --schema public > types/supabase.ts`
|
`npx supabase gen types typescript --project-id "$PROJECT_REF" --schema public > src/lib/types/supabase.d.ts`
|
||||||
|
|
||||||
Replace `types/supabase.d.ts` with whatever file you want your types in:
|
If you are using a local Supabase instance, you can use the following command to generate the types:
|
||||||
`npx supabase gen types typescript --local > types/supabase.d.ts`
|
`npx supabase gen types typescript --local > src/lib/types/supabase.d.ts`
|
||||||
|
|
||||||
## Local DB Development
|
## Local DB Development
|
||||||
|
|
||||||
|
|||||||
Vendored
+376
-624
File diff suppressed because it is too large
Load Diff
@@ -11,7 +11,6 @@ const toDateTime = (secs: number) => {
|
|||||||
return t;
|
return t;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
type Product = Tables<'products'>;
|
type Product = Tables<'products'>;
|
||||||
type Price = Tables<'prices'>;
|
type Price = Tables<'prices'>;
|
||||||
|
|
||||||
@@ -245,11 +244,32 @@ const manageSubscriptionStatusChange = async (
|
|||||||
// }
|
// }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const recordProductPurchase = async (userId: string, productId: string, priceId: string) => {
|
||||||
|
console.log(
|
||||||
|
`Recording product purchase for user [${userId}] and product [${productId}] with price [${priceId}]`
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { error } = await supabaseAdmin
|
||||||
|
.from('purchases')
|
||||||
|
.insert([{ user_id: userId, product_id: productId, price_id: priceId }]);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
throw new Error(`Failed to record product purchase: ${error.message}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Product purchased recorded for user [${userId}] and product [${productId}]`);
|
||||||
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
upsertProductRecord,
|
upsertProductRecord,
|
||||||
upsertPriceRecord,
|
upsertPriceRecord,
|
||||||
deleteProductRecord,
|
deleteProductRecord,
|
||||||
deletePriceRecord,
|
deletePriceRecord,
|
||||||
createOrRetrieveCustomer,
|
createOrRetrieveCustomer,
|
||||||
manageSubscriptionStatusChange
|
manageSubscriptionStatusChange,
|
||||||
|
recordProductPurchase
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ import {
|
|||||||
deleteProductRecord,
|
deleteProductRecord,
|
||||||
manageSubscriptionStatusChange,
|
manageSubscriptionStatusChange,
|
||||||
upsertPriceRecord,
|
upsertPriceRecord,
|
||||||
upsertProductRecord
|
upsertProductRecord,
|
||||||
|
recordProductPurchase
|
||||||
} from '$lib/utils/supabase/admin';
|
} from '$lib/utils/supabase/admin';
|
||||||
import { stripe as stripeClient } from '$lib/utils/stripe';
|
import { stripe as stripeClient } from '$lib/utils/stripe';
|
||||||
|
|
||||||
@@ -34,7 +35,6 @@ export const POST: RequestHandler = async ({ request }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
// Product events
|
|
||||||
case 'product.created':
|
case 'product.created':
|
||||||
case 'product.updated':
|
case 'product.updated':
|
||||||
await upsertProductRecord(event.data.object as stripe.Product);
|
await upsertProductRecord(event.data.object as stripe.Product);
|
||||||
@@ -64,6 +64,7 @@ export const POST: RequestHandler = async ({ request }) => {
|
|||||||
}
|
}
|
||||||
case 'checkout.session.completed': {
|
case 'checkout.session.completed': {
|
||||||
const checkoutSession = event.data.object as stripe.Checkout.Session;
|
const checkoutSession = event.data.object as stripe.Checkout.Session;
|
||||||
|
|
||||||
if (checkoutSession.mode === 'subscription') {
|
if (checkoutSession.mode === 'subscription') {
|
||||||
const subscriptionId = checkoutSession.subscription;
|
const subscriptionId = checkoutSession.subscription;
|
||||||
await manageSubscriptionStatusChange(
|
await manageSubscriptionStatusChange(
|
||||||
@@ -72,8 +73,33 @@ export const POST: RequestHandler = async ({ request }) => {
|
|||||||
true
|
true
|
||||||
);
|
);
|
||||||
} else if (checkoutSession.mode === 'payment') {
|
} else if (checkoutSession.mode === 'payment') {
|
||||||
// TODO Record that they purchased the product
|
// Fetch session with expanded line_items
|
||||||
console.log(checkoutSession);
|
const sessionWithLineItems = await stripeClient.checkout.sessions.retrieve(
|
||||||
|
checkoutSession.id,
|
||||||
|
{
|
||||||
|
expand: ['line_items']
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const lineItems = sessionWithLineItems.line_items?.data;
|
||||||
|
const userId = checkoutSession.metadata!.userId;
|
||||||
|
|
||||||
|
if (!lineItems) {
|
||||||
|
throw new Error('No line items found in session');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!userId) {
|
||||||
|
throw new Error('No user ID found in session metadata');
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const item of lineItems ?? []) {
|
||||||
|
const productId = item.price?.product as string;
|
||||||
|
const priceId = item.price?.id as string;
|
||||||
|
|
||||||
|
if (productId && priceId) {
|
||||||
|
await recordProductPurchase(userId, productId, priceId);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -135,3 +135,19 @@ create table subscriptions (
|
|||||||
);
|
);
|
||||||
alter table subscriptions enable row level security;
|
alter table subscriptions enable row level security;
|
||||||
create policy "Can only view own subs data." on subscriptions for select using (auth.uid() = user_id);
|
create policy "Can only view own subs data." on subscriptions for select using (auth.uid() = user_id);
|
||||||
|
|
||||||
|
-- Purchases table to record product purchases.
|
||||||
|
create table purchases (
|
||||||
|
id uuid default uuid_generate_v4() primary key,
|
||||||
|
user_id uuid references auth.users not null,
|
||||||
|
product_id text references products(id) not null,
|
||||||
|
price_id text references prices(id) not null,
|
||||||
|
purchased_at timestamp with time zone not null default now()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Enabling row-level security for purchases.
|
||||||
|
alter table purchases enable row level security;
|
||||||
|
|
||||||
|
-- Creating a policy to ensure that users can only view their own purchase data.
|
||||||
|
create policy "Can view own purchase data." on purchases for select using (auth.uid() = user_id);
|
||||||
|
create policy "Can insert own purchase data." on purchases for insert with check (auth.uid() = user_id);
|
||||||
|
|||||||
Reference in New Issue
Block a user