+
+
+
+
+
+
+
+
+
+ Active Subscriptions
+
+ {#each subscriptions as subscription}
+ -
+
+
+
{subscription.name}
+
Renewal Date: {subscription.nextBillingDate}
+
+
+
+
+ {/each}
+
+
+
+
+
+ Billing History
+
+ {#each billingHistory as invoice}
+ -
+
+
Invoice #{invoice.id}
+
Date: {invoice.date}
+
Amount: £{invoice.amount}
+
+ Download
+
+ {/each}
+
+
-
-
-
-
+
+
+
+
+ Notification Preferences
+
+
-
+
From f1f2fe99d755f562f8be27120d83257da4251c73 Mon Sep 17 00:00:00 2001
From: Josh Creek <8179928+jcreek@users.noreply.github.com>
Date: Fri, 1 Nov 2024 20:12:08 +0000
Subject: [PATCH 2/6] feat(#59): Enable showing subscriptions and transactions
---
src/lib/utils/supabase/admin.ts | 94 +++++++++++++++++++++++++++++-
src/routes/account/+page.server.ts | 20 +++++--
src/routes/account/+page.svelte | 67 +++++++++++----------
3 files changed, 143 insertions(+), 38 deletions(-)
diff --git a/src/lib/utils/supabase/admin.ts b/src/lib/utils/supabase/admin.ts
index 4052695..4ae82d2 100644
--- a/src/lib/utils/supabase/admin.ts
+++ b/src/lib/utils/supabase/admin.ts
@@ -480,6 +480,96 @@ const getProductById = async (productId: string) => {
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 {
upsertProductRecord,
upsertPriceRecord,
@@ -495,5 +585,7 @@ export {
getActiveProductsWithPrices,
getProductById,
upsertCustomerToSupabase,
- getStripeCustomerId
+ getStripeCustomerId,
+ getUserSubscriptions,
+ getUserTransactions
};
diff --git a/src/routes/account/+page.server.ts b/src/routes/account/+page.server.ts
index d839775..ba26c6a 100644
--- a/src/routes/account/+page.server.ts
+++ b/src/routes/account/+page.server.ts
@@ -1,5 +1,6 @@
import { fail, redirect } from '@sveltejs/kit';
import type { Actions, PageServerLoad } from './$types';
+import { getUserSubscriptions, getUserTransactions } from '$lib/utils/supabase/admin';
export const load: PageServerLoad = async ({ locals: { supabase, safeGetSession } }) => {
const { session } = await safeGetSession();
@@ -14,7 +15,11 @@ export const load: PageServerLoad = async ({ locals: { supabase, safeGetSession
.eq('id', session.user.id)
.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 = {
@@ -27,20 +32,23 @@ export const actions: Actions = {
if (!session) {
return fail(401, { name });
}
- const { error } = await supabase.from('users').update({
- name: name,
- }).eq('id', session?.user.id);
+ const { error } = await supabase
+ .from('users')
+ .update({
+ name: name
+ })
+ .eq('id', session?.user.id);
console.error(error);
if (error) {
return fail(500, {
- name,
+ name
});
}
return {
- name,
+ name
};
},
signout: async ({ locals: { supabase, safeGetSession } }) => {
diff --git a/src/routes/account/+page.svelte b/src/routes/account/+page.svelte
index 5435c0d..a97b25e 100644
--- a/src/routes/account/+page.svelte
+++ b/src/routes/account/+page.svelte
@@ -5,7 +5,7 @@
export let data;
export let form;
- let { session, supabase, user } = data;
+ let { session, user, subscriptions, transactions } = data;
$: ({ session, supabase, user } = data);
let profileForm: HTMLFormElement;
@@ -18,16 +18,6 @@
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: '#' }
- ];
-
+
@@ -83,35 +73,50 @@
- Active Subscriptions
-
- {#each subscriptions as subscription}
- -
-
-
-
{subscription.name}
-
Renewal Date: {subscription.nextBillingDate}
+
Subscriptions
+ {#if subscriptions.length === 0}
+
You do not have any subscriptions.
+ {:else}
+
+ {#each subscriptions as subscription}
+ -
+
+
+
{subscription.productName}
+
+ Price: {subscription.amount}
+ {subscription.currency} / {subscription.interval}
+
+
+ Expiry Date: {subscription.expiryDate}
+
+
+ Status: {subscription.status}
+
+
+
-
-
-
- {/each}
-
+
+ {/each}
+
+ {/if}
Billing History
- {#each billingHistory as invoice}
+ {#each transactions as transaction}
-
-
Invoice #{invoice.id}
-
Date: {invoice.date}
-
Amount: £{invoice.amount}
+
Date: {transaction.created}
+
+ Amount: {transaction.amount}
+ {transaction.currency}
+
- DownloadDownload Receipt
{/each}
From 3bbbd340c5f179db69e489779f6dd11c17e34bb9 Mon Sep 17 00:00:00 2001
From: Josh Creek <8179928+jcreek@users.noreply.github.com>
Date: Mon, 4 Nov 2024 18:50:19 +0000
Subject: [PATCH 3/6] chore(#59): Improve clarity of error logs
---
src/routes/account/+page.server.ts | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/src/routes/account/+page.server.ts b/src/routes/account/+page.server.ts
index ba26c6a..1bb24b5 100644
--- a/src/routes/account/+page.server.ts
+++ b/src/routes/account/+page.server.ts
@@ -39,11 +39,13 @@ export const actions: Actions = {
})
.eq('id', session?.user.id);
- console.error(error);
+ console.error('Failed to update user:', error);
if (error) {
return fail(500, {
- name
+ name,
+ message: 'Failed to update profile',
+ error: error.message
});
}
From de565bb965b2cdcba35a03e590ab4af02ec931a1 Mon Sep 17 00:00:00 2001
From: Josh Creek <8179928+jcreek@users.noreply.github.com>
Date: Mon, 4 Nov 2024 18:51:57 +0000
Subject: [PATCH 4/6] chore(#59): Remove debugging log
---
src/lib/utils/supabase/admin.ts | 2 --
1 file changed, 2 deletions(-)
diff --git a/src/lib/utils/supabase/admin.ts b/src/lib/utils/supabase/admin.ts
index 4ae82d2..9709c98 100644
--- a/src/lib/utils/supabase/admin.ts
+++ b/src/lib/utils/supabase/admin.ts
@@ -500,8 +500,6 @@ const getUserSubscriptions = async (userId: string) => {
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
From 506ceefe26fc4d38292c70d8bc3bbd9943de5ef7 Mon Sep 17 00:00:00 2001
From: Josh Creek <8179928+jcreek@users.noreply.github.com>
Date: Mon, 4 Nov 2024 18:56:56 +0000
Subject: [PATCH 5/6] refactor(#59): Validate receipt links before showing the
buttons
---
src/routes/account/+page.svelte | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/src/routes/account/+page.svelte b/src/routes/account/+page.svelte
index a97b25e..df1c19e 100644
--- a/src/routes/account/+page.svelte
+++ b/src/routes/account/+page.svelte
@@ -115,9 +115,13 @@
{transaction.currency}
- Download Receipt
+ {#if transaction.receipt_url}
+
+ Download Receipt
+
+ {:else}
+ Receipt unavailable
+ {/if}
{/each}
From ad30f040789d520fbb97a4243ec2fb39ad3523c9 Mon Sep 17 00:00:00 2001
From: Josh Creek <8179928+jcreek@users.noreply.github.com>
Date: Mon, 4 Nov 2024 19:00:23 +0000
Subject: [PATCH 6/6] refactor(#59): Verify that 'transactions' is always
defined before iteration
---
src/routes/account/+page.svelte | 38 +++++++++++++++++----------------
1 file changed, 20 insertions(+), 18 deletions(-)
diff --git a/src/routes/account/+page.svelte b/src/routes/account/+page.svelte
index df1c19e..b5f8524 100644
--- a/src/routes/account/+page.svelte
+++ b/src/routes/account/+page.svelte
@@ -106,24 +106,26 @@
Billing History
- {#each transactions as transaction}
- -
-
-
Date: {transaction.created}
-
- Amount: {transaction.amount}
- {transaction.currency}
-
-
- {#if transaction.receipt_url}
-
- Download Receipt
-
- {:else}
- Receipt unavailable
- {/if}
-
- {/each}
+ {#if transactions}
+ {#each transactions as transaction}
+ -
+
+
Date: {transaction.created}
+
+ Amount: {transaction.amount}
+ {transaction.currency}
+
+
+ {#if transaction.receipt_url}
+
+ Download Receipt
+
+ {:else}
+ Receipt unavailable
+ {/if}
+
+ {/each}
+ {/if}