mirror of
https://github.com/jcreek/SvelteKitSaasBoilerplate.git
synced 2026-07-13 02:53: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`
|
||||
|
||||
Replace `$PROJECT_REF` with your project reference:
|
||||
`npx supabase gen types typescript --project-id "$PROJECT_REF" --schema public > types/supabase.ts`
|
||||
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 > src/lib/types/supabase.d.ts`
|
||||
|
||||
Replace `types/supabase.d.ts` with whatever file you want your types in:
|
||||
`npx supabase gen types typescript --local > types/supabase.d.ts`
|
||||
If you are using a local Supabase instance, you can use the following command to generate the types:
|
||||
`npx supabase gen types typescript --local > src/lib/types/supabase.d.ts`
|
||||
|
||||
## 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;
|
||||
};
|
||||
|
||||
|
||||
type Product = Tables<'products'>;
|
||||
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 {
|
||||
upsertProductRecord,
|
||||
upsertPriceRecord,
|
||||
deleteProductRecord,
|
||||
deletePriceRecord,
|
||||
createOrRetrieveCustomer,
|
||||
manageSubscriptionStatusChange
|
||||
manageSubscriptionStatusChange,
|
||||
recordProductPurchase
|
||||
};
|
||||
|
||||
@@ -6,7 +6,8 @@ import {
|
||||
deleteProductRecord,
|
||||
manageSubscriptionStatusChange,
|
||||
upsertPriceRecord,
|
||||
upsertProductRecord
|
||||
upsertProductRecord,
|
||||
recordProductPurchase
|
||||
} from '$lib/utils/supabase/admin';
|
||||
import { stripe as stripeClient } from '$lib/utils/stripe';
|
||||
|
||||
@@ -34,7 +35,6 @@ export const POST: RequestHandler = async ({ request }) => {
|
||||
}
|
||||
|
||||
switch (event.type) {
|
||||
// Product events
|
||||
case 'product.created':
|
||||
case 'product.updated':
|
||||
await upsertProductRecord(event.data.object as stripe.Product);
|
||||
@@ -64,6 +64,7 @@ export const POST: RequestHandler = async ({ request }) => {
|
||||
}
|
||||
case 'checkout.session.completed': {
|
||||
const checkoutSession = event.data.object as stripe.Checkout.Session;
|
||||
|
||||
if (checkoutSession.mode === 'subscription') {
|
||||
const subscriptionId = checkoutSession.subscription;
|
||||
await manageSubscriptionStatusChange(
|
||||
@@ -72,8 +73,33 @@ export const POST: RequestHandler = async ({ request }) => {
|
||||
true
|
||||
);
|
||||
} else if (checkoutSession.mode === 'payment') {
|
||||
// TODO Record that they purchased the product
|
||||
console.log(checkoutSession);
|
||||
// Fetch session with expanded line_items
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -135,3 +135,19 @@ create table subscriptions (
|
||||
);
|
||||
alter table subscriptions enable row level security;
|
||||
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