From 061b4ccbbc4c1e3fc686853f30c0ce13610b237f Mon Sep 17 00:00:00 2001 From: mrjvs Date: Wed, 12 Aug 2026 21:07:49 +0200 Subject: [PATCH] feat: add unsubscribe to API methods --- server/api/account/unsubscribe.post.ts | 33 ++++++++++++++++++++++++++ server/api/auth/me.get.ts | 7 ++++++ server/middleware/auth.ts | 3 ++- server/utils/enforceAuth.ts | 1 + shared/api-types.ts | 5 ++++ 5 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 server/api/account/unsubscribe.post.ts diff --git a/server/api/account/unsubscribe.post.ts b/server/api/account/unsubscribe.post.ts new file mode 100644 index 0000000..704096b --- /dev/null +++ b/server/api/account/unsubscribe.post.ts @@ -0,0 +1,33 @@ +import { usePapr } from '~~/server/utils/papr'; + +export default defineEventHandler(async (event): Promise => { + const auth = enforceLoggedIn(event); + const papr = await usePapr(event); + const stripe = useStripe(event); + if (!stripe || !papr) { + throw createApiError('INTEGRATION_DISABLED'); + } + + if (!auth.stripeSubscriptionId) { + return; // No subscription, do nothing + } + + await stripe.subscriptions.cancel(auth.stripeSubscriptionId); + + let newAccessLevel = 0; + if (auth.accessLevel >= 2) { + newAccessLevel = auth.accessLevel; // Staff shouldn't be downgraded on unsubscribe + } + + await papr.Pnid.updateOne({ pid: auth.pid }, { + $unset: { + 'connections.stripe.subscription_id': 1, + 'connections.stripe.price_id': 1, + 'connections.stripe.tier_name': 1 + }, + $set: { + 'connections.stripe.tier_level': 0, + 'access_level': newAccessLevel + } + }); +}); diff --git a/server/api/auth/me.get.ts b/server/api/auth/me.get.ts index ee779ef..d320fb1 100644 --- a/server/api/auth/me.get.ts +++ b/server/api/auth/me.get.ts @@ -12,6 +12,13 @@ export default defineEventHandler(async (event): Promise => { accessLevel: data.accessLevel, serverAccessLevel: data.serverAccessLevel as any, discordId: data.connections?.discord?.id ?? null, + stripeTier: data.connections?.stripe?.subscriptionId + ? { + subscriptionId: data.connections.stripe.subscriptionId, + priceId: data.connections.stripe.priceId ?? '', + tierName: data.connections.stripe.tierName ?? '' + } + : null, mii: data.mii ? { imageUrl: `${config.public.cdnBaseUrl}/mii/${data.pid}/normal_face.png`, diff --git a/server/middleware/auth.ts b/server/middleware/auth.ts index 242b84f..e4dba9a 100644 --- a/server/middleware/auth.ts +++ b/server/middleware/auth.ts @@ -18,7 +18,8 @@ export default defineEventHandler(async (event) => { username: userData.username, token: token, accessLevel: userData.accessLevel, - email: userData.emailAddress + email: userData.emailAddress, + stripeSubscriptionId: userData.connections?.stripe?.subscriptionId ?? null }); } catch (err) { console.error('Failed to request user data: ', err); diff --git a/server/utils/enforceAuth.ts b/server/utils/enforceAuth.ts index 1d1c02e..c77b99a 100644 --- a/server/utils/enforceAuth.ts +++ b/server/utils/enforceAuth.ts @@ -6,6 +6,7 @@ export type AuthContext = { token: string; email: string; accessLevel: number; + stripeSubscriptionId: string | null; }; export function setAuthContext(event: H3Event, context: AuthContext | null): void { diff --git a/shared/api-types.ts b/shared/api-types.ts index 6c9d6d3..9823e32 100644 --- a/shared/api-types.ts +++ b/shared/api-types.ts @@ -6,6 +6,11 @@ export type GetApiAuthMe = { accessLevel: number; serverAccessLevel: 'dev' | 'test' | 'prod'; discordId: string | null; + stripeTier: { + subscriptionId: string; + priceId: string; + tierName: string; + } | null; mii: { imageUrl: string; name: string;