feat(#29): Add ability to record that a user has purchased a product - part 2

This commit is contained in:
Josh Creek
2024-09-21 20:26:36 +01:00
parent d4f7f5041d
commit 05891ad510
5 changed files with 448 additions and 634 deletions
+4 -4
View File
@@ -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
+376 -624
View File
File diff suppressed because it is too large Load Diff
+22 -2
View File
@@ -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
}; };
+30 -4
View File
@@ -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);