feat(#59): Enable showing subscriptions and transactions

This commit is contained in:
Josh Creek
2024-11-01 20:12:08 +00:00
parent 3189d8932b
commit f1f2fe99d7
3 changed files with 143 additions and 38 deletions
+93 -1
View File
@@ -480,6 +480,96 @@ const getProductById = async (productId: string) => {
return product as Product; return product as Product;
}; };
const getUserSubscriptions = async (userId: string) => {
try {
// Step 1: Retrieve subscription data
const { data: subscriptions, error } = await supabaseAdmin
.from('subscriptions')
.select('*')
.eq('user_id', userId);
if (error) {
console.error('Error retrieving subscriptions:', error.message);
throw new Error('Failed to fetch subscriptions');
}
// Step 2: Map through each subscription and fetch product and price details
const subscriptionDetails = await Promise.all(
subscriptions.map(async (sub) => {
// Fetch product and price details from Stripe
const product = await stripeClient.products.retrieve(sub.product_id);
const price = await stripeClient.prices.retrieve(sub.price_id);
console.log(sub);
const expiryDate = new Date(sub.current_period_end);
// Format output data
return {
productName: product.name,
amount: (price.unit_amount / 100).toFixed(2),
currency: price.currency.toUpperCase(),
interval: price.recurring?.interval || 'one-time',
expiryDate: expiryDate.toLocaleDateString('en-GB', {
day: '2-digit',
month: '2-digit',
year: 'numeric'
}),
status: sub.status
};
})
);
return subscriptionDetails;
} catch (error) {
console.error('An error occurred while retrieving subscription details:', error);
throw new Error('Could not retrieve subscription details');
}
};
const getUserTransactions = async (userId: string) => {
try {
// Step 1: Retrieve the customer ID associated with the user
const { data, error } = await supabaseAdmin
.from('customers')
.select('stripe_customer_id')
.eq('id', userId)
.single();
if (error) {
console.error('Error retrieving user data:', error.message);
throw new Error('Failed to fetch user data');
}
const customerId = data?.stripe_customer_id;
if (!customerId) {
throw new Error('No Stripe customer ID found for this user');
}
// Step 2: Fetch charge transactions from Stripe
const charges = await stripeClient.charges.list({ customer: customerId });
// Step 3: Format transaction data
const transactions = charges.data.map((charge) => ({
amount: (charge.amount / 100).toFixed(2),
currency: charge.currency.toUpperCase(),
description: charge.description ?? 'No description provided',
status: charge.status,
created: new Date(charge.created * 1000).toLocaleDateString('en-GB', {
day: '2-digit',
month: '2-digit',
year: 'numeric'
}),
receipt_url: charge.receipt_url
}));
return transactions;
} catch (error) {
console.error('An error occurred while retrieving transactions:', error);
throw new Error('Could not retrieve transactions');
}
};
export { export {
upsertProductRecord, upsertProductRecord,
upsertPriceRecord, upsertPriceRecord,
@@ -495,5 +585,7 @@ export {
getActiveProductsWithPrices, getActiveProductsWithPrices,
getProductById, getProductById,
upsertCustomerToSupabase, upsertCustomerToSupabase,
getStripeCustomerId getStripeCustomerId,
getUserSubscriptions,
getUserTransactions
}; };
+14 -6
View File
@@ -1,5 +1,6 @@
import { fail, redirect } from '@sveltejs/kit'; import { fail, redirect } from '@sveltejs/kit';
import type { Actions, PageServerLoad } from './$types'; import type { Actions, PageServerLoad } from './$types';
import { getUserSubscriptions, getUserTransactions } from '$lib/utils/supabase/admin';
export const load: PageServerLoad = async ({ locals: { supabase, safeGetSession } }) => { export const load: PageServerLoad = async ({ locals: { supabase, safeGetSession } }) => {
const { session } = await safeGetSession(); const { session } = await safeGetSession();
@@ -14,7 +15,11 @@ export const load: PageServerLoad = async ({ locals: { supabase, safeGetSession
.eq('id', session.user.id) .eq('id', session.user.id)
.single(); .single();
return { session, user }; const subscriptions = await getUserSubscriptions(session.user.id);
const transactions = await getUserTransactions(session.user.id);
return { session, user, subscriptions, transactions };
}; };
export const actions: Actions = { export const actions: Actions = {
@@ -27,20 +32,23 @@ export const actions: Actions = {
if (!session) { if (!session) {
return fail(401, { name }); return fail(401, { name });
} }
const { error } = await supabase.from('users').update({ const { error } = await supabase
name: name, .from('users')
}).eq('id', session?.user.id); .update({
name: name
})
.eq('id', session?.user.id);
console.error(error); console.error(error);
if (error) { if (error) {
return fail(500, { return fail(500, {
name, name
}); });
} }
return { return {
name, name
}; };
}, },
signout: async ({ locals: { supabase, safeGetSession } }) => { signout: async ({ locals: { supabase, safeGetSession } }) => {
+36 -31
View File
@@ -5,7 +5,7 @@
export let data; export let data;
export let form; export let form;
let { session, supabase, user } = data; let { session, user, subscriptions, transactions } = data;
$: ({ session, supabase, user } = data); $: ({ session, supabase, user } = data);
let profileForm: HTMLFormElement; let profileForm: HTMLFormElement;
@@ -18,16 +18,6 @@
loading = false; loading = false;
}; };
}; };
const subscriptions = [
{ name: 'Premium Plan', nextBillingDate: '12th August 2021' },
{ name: 'Basic Plan', nextBillingDate: '12th August 2021' }
];
const billingHistory = [
{ id: 123, date: '12th August 2021', amount: 9.99, downloadLink: '#' },
{ id: 122, date: '12th July 2021', amount: 9.99, downloadLink: '#' }
];
</script> </script>
<div <div
@@ -35,7 +25,7 @@
> >
<!-- Flex container for two columns --> <!-- Flex container for two columns -->
<div class="flex flex-col lg:flex-row gap-6"> <div class="flex flex-col lg:flex-row gap-6">
<!-- Left Column: Account Overview, Active Subscriptions, and Billing History --> <!-- Left Column: Account Overview, Subscriptions, and Billing History -->
<div class="flex-1 space-y-6"> <div class="flex-1 space-y-6">
<!-- Account Overview --> <!-- Account Overview -->
<section class="space-y-4"> <section class="space-y-4">
@@ -83,35 +73,50 @@
<!-- Active Subscriptions --> <!-- Active Subscriptions -->
<section class="space-y-4"> <section class="space-y-4">
<h2 class="text-xl font-semibold">Active Subscriptions</h2> <h2 class="text-xl font-semibold">Subscriptions</h2>
<ul class="space-y-3"> {#if subscriptions.length === 0}
{#each subscriptions as subscription} <p class="text-gray-600">You do not have any subscriptions.</p>
<li class="p-4 border border-gray-200 rounded-md"> {:else}
<div class="flex items-center justify-between"> <ul class="space-y-3">
<div> {#each subscriptions as subscription}
<p class="font-semibold">{subscription.name}</p> <li class="p-4 border border-gray-200 rounded-md">
<p class="text-sm text-gray-500">Renewal Date: {subscription.nextBillingDate}</p> <div class="flex items-center justify-between">
<div>
<p class="font-semibold">{subscription.productName}</p>
<p class="text-sm text-gray-500">
Price: {subscription.amount}
{subscription.currency} / {subscription.interval}
</p>
<p class="text-sm text-gray-500">
Expiry Date: {subscription.expiryDate}
</p>
<p class="text-sm text-gray-500">
Status: {subscription.status}
</p>
</div>
<!-- <button class="btn btn-secondary btn-sm">Manage</button> -->
</div> </div>
<button class="btn btn-secondary btn-sm">Manage</button> </li>
</div> {/each}
</li> </ul>
{/each} {/if}
</ul>
</section> </section>
<!-- Billing History --> <!-- Billing History -->
<section class="space-y-4"> <section class="space-y-4">
<h2 class="text-xl font-semibold">Billing History</h2> <h2 class="text-xl font-semibold">Billing History</h2>
<ul class="space-y-3"> <ul class="space-y-3">
{#each billingHistory as invoice} {#each transactions as transaction}
<li class="flex justify-between p-4 border border-gray-200 rounded-md"> <li class="flex justify-between p-4 border border-gray-200 rounded-md">
<div> <div>
<p class="font-semibold">Invoice #{invoice.id}</p> <p class="text-sm text-gray-500">Date: {transaction.created}</p>
<p class="text-sm text-gray-500">Date: {invoice.date}</p> <p class="text-sm text-gray-500">
<p class="text-sm text-gray-500">Amount: £{invoice.amount}</p> Amount: {transaction.amount}
{transaction.currency}
</p>
</div> </div>
<a href={invoice.downloadLink} class="btn btn-outline btn-sm" target="_blank" <a href={transaction.receipt_url} class="btn btn-outline btn-sm" target="_blank"
>Download</a >Download Receipt</a
> >
</li> </li>
{/each} {/each}