user.email account.plan_name - cleanup context and service construction - config for stripe callback - initial plan
This commit is contained in:
@@ -3,6 +3,7 @@ SUPABASE_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxxxxx.xxxxxx-xxxxx
|
|||||||
|
|
||||||
STRIPE_SECRET_KEY=sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
STRIPE_SECRET_KEY=sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
STRIPE_ENDPOINT_SECRET=whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
STRIPE_ENDPOINT_SECRET=whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
|
STRIPE_CALLBACK_URL=http://localhost:3000
|
||||||
|
|
||||||
# This was inserted by `prisma init`:
|
# This was inserted by `prisma init`:
|
||||||
# Environment variables declared in this file are automatically made available to Prisma.
|
# Environment variables declared in this file are automatically made available to Prisma.
|
||||||
|
|||||||
15
README.md
15
README.md
@@ -53,6 +53,13 @@ npm run preview
|
|||||||
|
|
||||||
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.
|
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.
|
||||||
|
|
||||||
|
## Config
|
||||||
|
### .env
|
||||||
|
Most of the .env settings are self explanatory and are usually secrets.
|
||||||
|
|
||||||
|
### Trial Plan
|
||||||
|
If you want a 'free trial period' set initialPlanName to an appropriate plan name in the DB and initialPlanActiveMonths to a positive value. If you don't want a free trial, set initialPlanName to an appropriate 'No Plan' plan in the DB and set the initialPlanActiveMonths to -1.
|
||||||
|
|
||||||
# Steps to Create
|
# Steps to Create
|
||||||
This is what I did to create the project including all the extra fiddly stuff. Putting this here so I don't forget.
|
This is what I did to create the project including all the extra fiddly stuff. Putting this here so I don't forget.
|
||||||
|
|
||||||
@@ -127,10 +134,10 @@ I set up a Stripe account with a couple of 'Products' with a single price each t
|
|||||||
- team invitation thingy (not required, admins can add new members to team)
|
- team invitation thingy (not required, admins can add new members to team)
|
||||||
- actions which mutate the current user account should update the context... (done)
|
- actions which mutate the current user account should update the context... (done)
|
||||||
- integration with stripe including web hooks (basics done).
|
- integration with stripe including web hooks (basics done).
|
||||||
-- add email to user record... capture from login same as user name
|
-- add email to user record... capture from login same as user name (done)
|
||||||
-- initial user should be created with an expired plan
|
-- initial user should be created with an expired plan (done, initial plan and plan period now controled via config to allow either a trial plan or a 'No Plan' for initial users)
|
||||||
-- add a pricing page....should be the default redirect from signup if the user has no active plan.. not sure whether to use a 'blank' plan or make plan nullable (basic pricing page is done)
|
-- add a pricing page....should be the default redirect from signup if the user has no active plan.. not sure whether to use a 'blank' plan or make plan nullable (basic pricing page is done - decided on 'no plan' plan)
|
||||||
-- figure out what to do with Plan Name. Could add Plan Name to account record and copy over at time of account creation or updation. could pull from the Plan record for display.... but makes it difficult to change... should be loosely coupled, maybe use first approach
|
-- figure out what to do with Plan Name. Could add Plan Name to account record and copy over at time of account creation or updation. could pull from the Plan record for display.... but makes it difficult to change... should be loosely coupled, maybe use first approach (done)
|
||||||
-- figure out when/how plan changes.. is it triggered by webhook?
|
-- figure out when/how plan changes.. is it triggered by webhook?
|
||||||
# Admin Functions Scenario (shitty test)
|
# Admin Functions Scenario (shitty test)
|
||||||
Pre-condition
|
Pre-condition
|
||||||
|
|||||||
@@ -1,33 +1,27 @@
|
|||||||
import { PrismaClient } from '@prisma/client';
|
import prisma_client from '~~/prisma/prisma.client';
|
||||||
|
|
||||||
export default class NotesService {
|
export default class NotesService {
|
||||||
private prisma: PrismaClient;
|
|
||||||
|
|
||||||
constructor( prisma: PrismaClient) {
|
|
||||||
this.prisma = prisma;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getAllNotes() {
|
async getAllNotes() {
|
||||||
return this.prisma.note.findMany();
|
return prisma_client.note.findMany();
|
||||||
}
|
}
|
||||||
|
|
||||||
async getNoteById(id: number) {
|
async getNoteById(id: number) {
|
||||||
return this.prisma.note.findUniqueOrThrow({ where: { id } });
|
return prisma_client.note.findUniqueOrThrow({ where: { id } });
|
||||||
}
|
}
|
||||||
|
|
||||||
async getNotesForAccountId(account_id: number) {
|
async getNotesForAccountId(account_id: number) {
|
||||||
return this.prisma.note.findMany({ where: { account_id } });
|
return prisma_client.note.findMany({ where: { account_id } });
|
||||||
}
|
}
|
||||||
|
|
||||||
async createNote( account_id: number, note_text: string ) {
|
async createNote( account_id: number, note_text: string ) {
|
||||||
return this.prisma.note.create({ data: { account_id, note_text }});
|
return prisma_client.note.create({ data: { account_id, note_text }});
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateNote(id: number, note_text: string) {
|
async updateNote(id: number, note_text: string) {
|
||||||
return this.prisma.note.update({ where: { id }, data: { note_text } });
|
return prisma_client.note.update({ where: { id }, data: { note_text } });
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteNote(id: number) {
|
async deleteNote(id: number) {
|
||||||
return this.prisma.note.delete({ where: { id } });
|
return prisma_client.note.delete({ where: { id } });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,16 @@
|
|||||||
import { ACCOUNT_ACCESS, PrismaClient, User, Membership, Account } from '@prisma/client';
|
import { ACCOUNT_ACCESS, User, Membership, Account } from '@prisma/client';
|
||||||
|
import prisma_client from '~~/prisma/prisma.client';
|
||||||
import { UtilService } from './util.service';
|
import { UtilService } from './util.service';
|
||||||
const config = useRuntimeConfig();
|
const config = useRuntimeConfig();
|
||||||
|
|
||||||
|
|
||||||
export type MembershipWithAccount = (Membership & {account: Account});
|
export type MembershipWithAccount = (Membership & {account: Account});
|
||||||
export type FullDBUser = (User & { memberships: MembershipWithAccount[]; });
|
export type FullDBUser = (User & { memberships: MembershipWithAccount[]; });
|
||||||
|
export type MembershipWithUser = (Membership & { user: User});
|
||||||
|
export type AccountWithMembers = (Account & {members: MembershipWithUser[]});
|
||||||
export default class UserAccountService {
|
export default class UserAccountService {
|
||||||
private prisma: PrismaClient;
|
|
||||||
|
|
||||||
constructor( prisma: PrismaClient) {
|
|
||||||
this.prisma = prisma;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getUserBySupabaseId(supabase_uid: string): Promise<FullDBUser | null> {
|
async getUserBySupabaseId(supabase_uid: string): Promise<FullDBUser | null> {
|
||||||
return this.prisma.user.findFirst({
|
return prisma_client.user.findFirst({
|
||||||
where: { supabase_uid },
|
where: { supabase_uid },
|
||||||
include: { memberships: {include: {
|
include: { memberships: {include: {
|
||||||
account: true
|
account: true
|
||||||
@@ -23,7 +19,7 @@ export default class UserAccountService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getFullUserBySupabaseId(supabase_uid: string): Promise<FullDBUser | null> {
|
async getFullUserBySupabaseId(supabase_uid: string): Promise<FullDBUser | null> {
|
||||||
return this.prisma.user.findFirst({
|
return prisma_client.user.findFirst({
|
||||||
where: { supabase_uid },
|
where: { supabase_uid },
|
||||||
include: { memberships: {include: {
|
include: { memberships: {include: {
|
||||||
account: true
|
account: true
|
||||||
@@ -32,7 +28,7 @@ export default class UserAccountService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getUserById(user_id: number): Promise<FullDBUser | null> {
|
async getUserById(user_id: number): Promise<FullDBUser | null> {
|
||||||
return this.prisma.user.findFirstOrThrow({
|
return prisma_client.user.findFirstOrThrow({
|
||||||
where: { id: user_id },
|
where: { id: user_id },
|
||||||
include: { memberships: {include: {
|
include: { memberships: {include: {
|
||||||
account: true
|
account: true
|
||||||
@@ -40,14 +36,17 @@ export default class UserAccountService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAccountById(account_id: number): Promise<Account> {
|
async getAccountById(account_id: number): Promise<AccountWithMembers> {
|
||||||
return this.prisma.account.findFirstOrThrow({
|
return prisma_client.account.findFirstOrThrow({
|
||||||
where: { id: account_id },
|
where: { id: account_id },
|
||||||
|
include: { members: {include: {
|
||||||
|
user: true
|
||||||
|
}} }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateAccountStipeCustomerId (account_id: number, stripe_customer_id: string){
|
async updateAccountStipeCustomerId (account_id: number, stripe_customer_id: string){
|
||||||
return await this.prisma.account.update({
|
return await prisma_client.account.update({
|
||||||
where: { id: account_id },
|
where: { id: account_id },
|
||||||
data: {
|
data: {
|
||||||
stripe_customer_id,
|
stripe_customer_id,
|
||||||
@@ -56,10 +55,10 @@ export default class UserAccountService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async updateStripeSubscriptionDetailsForAccount (stripe_customer_id: string, stripe_subscription_id: string, current_period_ends: Date){
|
async updateStripeSubscriptionDetailsForAccount (stripe_customer_id: string, stripe_subscription_id: string, current_period_ends: Date){
|
||||||
const account = await this.prisma.account.findFirstOrThrow({
|
const account = await prisma_client.account.findFirstOrThrow({
|
||||||
where: {stripe_customer_id}
|
where: {stripe_customer_id}
|
||||||
});
|
});
|
||||||
return await this.prisma.account.update({
|
return await prisma_client.account.update({
|
||||||
where: { id: account.id },
|
where: { id: account.id },
|
||||||
data: {
|
data: {
|
||||||
stripe_subscription_id,
|
stripe_subscription_id,
|
||||||
@@ -68,12 +67,13 @@ export default class UserAccountService {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async createUser( supabase_uid: string, display_name: string ): Promise<FullDBUser | null> {
|
async createUser( supabase_uid: string, display_name: string, email: string ): Promise<FullDBUser | null> {
|
||||||
const trialPlan = await this.prisma.plan.findFirstOrThrow({ where: { name: config.trialPlanName}});
|
const trialPlan = await prisma_client.plan.findFirstOrThrow({ where: { name: config.initialPlanName}});
|
||||||
return this.prisma.user.create({
|
return prisma_client.user.create({
|
||||||
data:{
|
data:{
|
||||||
supabase_uid: supabase_uid,
|
supabase_uid: supabase_uid,
|
||||||
display_name: display_name,
|
display_name: display_name,
|
||||||
|
email: email,
|
||||||
memberships: {
|
memberships: {
|
||||||
create: {
|
create: {
|
||||||
account: {
|
account: {
|
||||||
@@ -81,8 +81,9 @@ export default class UserAccountService {
|
|||||||
plan_id: trialPlan.id,
|
plan_id: trialPlan.id,
|
||||||
name: display_name,
|
name: display_name,
|
||||||
features: trialPlan.features, //copy in features from the plan, plan_id is a loose association and settings can change independently
|
features: trialPlan.features, //copy in features from the plan, plan_id is a loose association and settings can change independently
|
||||||
current_period_ends: UtilService.addMonths(new Date(),3),
|
current_period_ends: UtilService.addMonths(new Date(), config.initialPlanActiveMonths),
|
||||||
max_notes: trialPlan.max_notes,
|
max_notes: trialPlan.max_notes,
|
||||||
|
plan_name: trialPlan.name,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
access: ACCOUNT_ACCESS.OWNER
|
access: ACCOUNT_ACCESS.OWNER
|
||||||
@@ -96,11 +97,11 @@ export default class UserAccountService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async deleteUser(user_id: number) {
|
async deleteUser(user_id: number) {
|
||||||
return this.prisma.user.delete({ where: { id: user_id } });
|
return prisma_client.user.delete({ where: { id: user_id } });
|
||||||
}
|
}
|
||||||
|
|
||||||
async joinUserToAccount(user_id: number, account_id: number): Promise<MembershipWithAccount> {
|
async joinUserToAccount(user_id: number, account_id: number): Promise<MembershipWithAccount> {
|
||||||
return this.prisma.membership.create({
|
return prisma_client.membership.create({
|
||||||
data: {
|
data: {
|
||||||
user_id: user_id,
|
user_id: user_id,
|
||||||
account_id: account_id,
|
account_id: account_id,
|
||||||
@@ -113,8 +114,8 @@ export default class UserAccountService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async changeAccountPlan(account_id: number, plan_id: number) {
|
async changeAccountPlan(account_id: number, plan_id: number) {
|
||||||
const plan = await this.prisma.plan.findFirstOrThrow({ where: {id: plan_id}});
|
const plan = await prisma_client.plan.findFirstOrThrow({ where: {id: plan_id}});
|
||||||
return this.prisma.account.update({
|
return prisma_client.account.update({
|
||||||
where: { id: account_id},
|
where: { id: account_id},
|
||||||
data: {
|
data: {
|
||||||
plan_id: plan_id,
|
plan_id: plan_id,
|
||||||
@@ -130,7 +131,7 @@ export default class UserAccountService {
|
|||||||
// Existing OWNER memberships are downgraded to ADMIN
|
// Existing OWNER memberships are downgraded to ADMIN
|
||||||
// In future, some sort of Billing/Stripe tie in here e.g. changing email details on the Account, not sure.
|
// In future, some sort of Billing/Stripe tie in here e.g. changing email details on the Account, not sure.
|
||||||
async claimOwnershipOfAccount(user_id: number, account_id: number) {
|
async claimOwnershipOfAccount(user_id: number, account_id: number) {
|
||||||
const membership = await this.prisma.membership.findUniqueOrThrow({
|
const membership = await prisma_client.membership.findUniqueOrThrow({
|
||||||
where: {
|
where: {
|
||||||
user_id_account_id: {
|
user_id_account_id: {
|
||||||
user_id: user_id,
|
user_id: user_id,
|
||||||
@@ -145,7 +146,7 @@ export default class UserAccountService {
|
|||||||
throw new Error('UNAUTHORISED: only Admins can claim ownership');
|
throw new Error('UNAUTHORISED: only Admins can claim ownership');
|
||||||
}
|
}
|
||||||
|
|
||||||
const existing_owner_memberships = await this.prisma.membership.findMany({
|
const existing_owner_memberships = await prisma_client.membership.findMany({
|
||||||
where: {
|
where: {
|
||||||
account_id: account_id,
|
account_id: account_id,
|
||||||
access: ACCOUNT_ACCESS.OWNER,
|
access: ACCOUNT_ACCESS.OWNER,
|
||||||
@@ -153,7 +154,7 @@ export default class UserAccountService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
for(const existing_owner_membership of existing_owner_memberships) {
|
for(const existing_owner_membership of existing_owner_memberships) {
|
||||||
await this.prisma.membership.update({
|
await prisma_client.membership.update({
|
||||||
where: {
|
where: {
|
||||||
user_id_account_id: {
|
user_id_account_id: {
|
||||||
user_id: existing_owner_membership.user_id,
|
user_id: existing_owner_membership.user_id,
|
||||||
@@ -167,7 +168,7 @@ export default class UserAccountService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// finally update the ADMIN member to OWNER
|
// finally update the ADMIN member to OWNER
|
||||||
return this.prisma.membership.update({
|
return prisma_client.membership.update({
|
||||||
where: {
|
where: {
|
||||||
user_id_account_id: {
|
user_id_account_id: {
|
||||||
user_id: user_id,
|
user_id: user_id,
|
||||||
@@ -189,7 +190,7 @@ export default class UserAccountService {
|
|||||||
throw new Error('UNABLE TO UPDATE MEMBERSHIP: use claimOwnershipOfAccount method to change ownership');
|
throw new Error('UNABLE TO UPDATE MEMBERSHIP: use claimOwnershipOfAccount method to change ownership');
|
||||||
}
|
}
|
||||||
|
|
||||||
const membership = await this.prisma.membership.findUniqueOrThrow({
|
const membership = await prisma_client.membership.findUniqueOrThrow({
|
||||||
where: {
|
where: {
|
||||||
user_id_account_id: {
|
user_id_account_id: {
|
||||||
user_id: user_id,
|
user_id: user_id,
|
||||||
@@ -202,7 +203,7 @@ export default class UserAccountService {
|
|||||||
throw new Error('UNABLE TO UPDATE MEMBERSHIP: use claimOwnershipOfAccount method to change ownership');
|
throw new Error('UNABLE TO UPDATE MEMBERSHIP: use claimOwnershipOfAccount method to change ownership');
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.prisma.membership.update({
|
return prisma_client.membership.update({
|
||||||
where: {
|
where: {
|
||||||
user_id_account_id: {
|
user_id_account_id: {
|
||||||
user_id: user_id,
|
user_id: user_id,
|
||||||
|
|||||||
@@ -14,8 +14,10 @@ export default defineNuxtConfig({
|
|||||||
runtimeConfig:{
|
runtimeConfig:{
|
||||||
stripeSecretKey: process.env.STRIPE_SECRET_KEY,
|
stripeSecretKey: process.env.STRIPE_SECRET_KEY,
|
||||||
stripeEndpointSecret: process.env.STRIPE_ENDPOINT_SECRET,
|
stripeEndpointSecret: process.env.STRIPE_ENDPOINT_SECRET,
|
||||||
|
stripeCallbackUrl: process.env.STRIPE_CALLBACK_URL,
|
||||||
subscriptionGraceDays: 3,
|
subscriptionGraceDays: 3,
|
||||||
trialPlanName: '3 Month Trial',
|
initialPlanName: '3 Month Trial',
|
||||||
|
initialPlanActiveMonths: 3,
|
||||||
public: {
|
public: {
|
||||||
debugMode: true,
|
debugMode: true,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ datasource db {
|
|||||||
model User {
|
model User {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
supabase_uid String
|
supabase_uid String
|
||||||
|
email String
|
||||||
display_name String?
|
display_name String?
|
||||||
|
|
||||||
memberships Membership[]
|
memberships Membership[]
|
||||||
@@ -46,6 +47,7 @@ model Account {
|
|||||||
features String[]
|
features String[]
|
||||||
plan_id Int
|
plan_id Int
|
||||||
plan Plan @relation(fields: [plan_id], references: [id])
|
plan Plan @relation(fields: [plan_id], references: [id])
|
||||||
|
plan_name String
|
||||||
members Membership[]
|
members Membership[]
|
||||||
notes Note[]
|
notes Note[]
|
||||||
max_notes Int @default(100)
|
max_notes Int @default(100)
|
||||||
|
|||||||
@@ -22,5 +22,5 @@ export type AppRouter = typeof appRouter;
|
|||||||
export default createNuxtApiHandler({
|
export default createNuxtApiHandler({
|
||||||
router: appRouter,
|
router: appRouter,
|
||||||
createContext: createContext,
|
createContext: createContext,
|
||||||
onError({ error}) { console.error(error)}, // TODO - logging and reporting
|
onError({ error}) { console.error(error)},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,26 +1,24 @@
|
|||||||
import { Account } from '@prisma/client';
|
import { ACCOUNT_ACCESS } from '@prisma/client';
|
||||||
import Stripe from 'stripe';
|
import Stripe from 'stripe';
|
||||||
import UserAccountService from '~~/lib/services/user.account.service';
|
import UserAccountService, { AccountWithMembers } from '~~/lib/services/user.account.service';
|
||||||
import prisma_client from '~~/prisma/prisma.client';
|
|
||||||
|
|
||||||
const config = useRuntimeConfig();
|
const config = useRuntimeConfig();
|
||||||
const stripe = new Stripe(config.stripeSecretKey, { apiVersion: '2022-11-15' });
|
const stripe = new Stripe(config.stripeSecretKey, { apiVersion: '2022-11-15' });
|
||||||
|
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
const YOUR_DOMAIN = 'http://localhost:3000'; // TODO - pull from somewhere, this is shit
|
|
||||||
|
|
||||||
const body = await readBody(event)
|
const body = await readBody(event)
|
||||||
let { price_id, account_id} = body;
|
let { price_id, account_id} = body;
|
||||||
account_id = +account_id
|
account_id = +account_id
|
||||||
console.log(`session.post.ts recieved price_id:${price_id}, account_id:${account_id}`);
|
console.log(`session.post.ts recieved price_id:${price_id}, account_id:${account_id}`);
|
||||||
|
|
||||||
const userService = new UserAccountService(prisma_client);
|
const userService = new UserAccountService();
|
||||||
const account: Account = await userService.getAccountById(account_id);
|
const account: AccountWithMembers = await userService.getAccountById(account_id);
|
||||||
let customer_id: string
|
let customer_id: string
|
||||||
if(!account.stripe_customer_id){
|
if(!account.stripe_customer_id){
|
||||||
// need to pre-emptively create a Stripe user for this account (use name for now, just so is visible on dashboard) TODO - include Email
|
// need to pre-emptively create a Stripe user for this account so we know who they are when the webhook comes back
|
||||||
console.log(`Creating account with name ${account.name}`);
|
const owner = account.members.find(member => (member.access == ACCOUNT_ACCESS.OWNER))
|
||||||
const customer = await stripe.customers.create({ name: account.name });
|
console.log(`Creating account with name ${account.name} and email ${owner?.user.email}`);
|
||||||
|
const customer = await stripe.customers.create({ name: account.name, email: owner?.user.email });
|
||||||
customer_id = customer.id;
|
customer_id = customer.id;
|
||||||
userService.updateAccountStipeCustomerId(account_id, customer.id);
|
userService.updateAccountStipeCustomerId(account_id, customer.id);
|
||||||
} else {
|
} else {
|
||||||
@@ -39,15 +37,15 @@ export default defineEventHandler(async (event) => {
|
|||||||
// {CHECKOUT_SESSION_ID} is a string literal; do not change it!
|
// {CHECKOUT_SESSION_ID} is a string literal; do not change it!
|
||||||
// the actual Session ID is returned in the query parameter when your customer
|
// the actual Session ID is returned in the query parameter when your customer
|
||||||
// is redirected to the success page.
|
// is redirected to the success page.
|
||||||
success_url: `${YOUR_DOMAIN}/success?session_id={CHECKOUT_SESSION_ID}`,
|
success_url: `${config.stripeCallbackUrl}/success?session_id={CHECKOUT_SESSION_ID}`,
|
||||||
cancel_url: `${YOUR_DOMAIN}/cancel`,
|
cancel_url: `${config.stripeCallbackUrl}/cancel`,
|
||||||
customer: customer_id
|
customer: customer_id
|
||||||
});
|
});
|
||||||
|
|
||||||
if(session?.url){
|
if(session?.url){
|
||||||
return sendRedirect(event, session.url, 303);
|
return sendRedirect(event, session.url, 303);
|
||||||
} else {
|
} else {
|
||||||
return sendRedirect(event, `${YOUR_DOMAIN}/fail`, 303);
|
return sendRedirect(event, `${config.stripeCallbackUrl}/fail`, 303);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import Stripe from 'stripe';
|
import Stripe from 'stripe';
|
||||||
import UserAccountService from '~~/lib/services/user.account.service';
|
import UserAccountService from '~~/lib/services/user.account.service';
|
||||||
import prisma_client from '~~/prisma/prisma.client';
|
|
||||||
|
|
||||||
const config = useRuntimeConfig();
|
const config = useRuntimeConfig();
|
||||||
const stripe = new Stripe(config.stripeSecretKey, { apiVersion: '2022-11-15' });
|
const stripe = new Stripe(config.stripeSecretKey, { apiVersion: '2022-11-15' });
|
||||||
@@ -22,15 +21,15 @@ export default defineEventHandler(async (event) => {
|
|||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
throw createError({ statusCode: 400, statusMessage: `Webhook Error` }); // ${(err as Error).message}
|
throw createError({ statusCode: 400, statusMessage: `Error validating Webhook Event` });
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`****** Web Hook Recieved (${stripeEvent.type}) ******`);
|
|
||||||
|
|
||||||
if(stripeEvent.type && stripeEvent.type.startsWith('customer.subscription')){
|
if(stripeEvent.type && stripeEvent.type.startsWith('customer.subscription')){
|
||||||
|
console.log(`****** Web Hook Recieved (${stripeEvent.type}) ******`);
|
||||||
|
|
||||||
let subscription = stripeEvent.data.object as Stripe.Subscription;
|
let subscription = stripeEvent.data.object as Stripe.Subscription;
|
||||||
|
|
||||||
const userService = new UserAccountService(prisma_client);
|
const userService = new UserAccountService();
|
||||||
|
|
||||||
let current_period_ends: Date = new Date(subscription.current_period_end * 1000);
|
let current_period_ends: Date = new Date(subscription.current_period_end * 1000);
|
||||||
current_period_ends.setDate(current_period_ends.getDate() + config.subscriptionGraceDays);
|
current_period_ends.setDate(current_period_ends.getDate() + config.subscriptionGraceDays);
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { PrismaClient } from '@prisma/client';
|
|
||||||
import { inferAsyncReturnType, TRPCError } from '@trpc/server'
|
import { inferAsyncReturnType, TRPCError } from '@trpc/server'
|
||||||
import { H3Event } from 'h3';
|
import { H3Event } from 'h3';
|
||||||
import { serverSupabaseClient } from '#supabase/server';
|
import { serverSupabaseClient } from '#supabase/server';
|
||||||
@@ -6,7 +5,6 @@ import SupabaseClient from '@supabase/supabase-js/dist/module/SupabaseClient';
|
|||||||
import { User } from '@supabase/supabase-js';
|
import { User } from '@supabase/supabase-js';
|
||||||
import UserAccountService, { FullDBUser } from '~~/lib/services/user.account.service';
|
import UserAccountService, { FullDBUser } from '~~/lib/services/user.account.service';
|
||||||
|
|
||||||
let prisma: PrismaClient | undefined
|
|
||||||
let supabase: SupabaseClient | undefined
|
let supabase: SupabaseClient | undefined
|
||||||
|
|
||||||
export async function createContext(event: H3Event){
|
export async function createContext(event: H3Event){
|
||||||
@@ -19,32 +17,25 @@ export async function createContext(event: H3Event){
|
|||||||
if (!user) {
|
if (!user) {
|
||||||
({data: { user }} = await supabase.auth.getUser());
|
({data: { user }} = await supabase.auth.getUser());
|
||||||
}
|
}
|
||||||
if (!prisma) {
|
|
||||||
prisma = new PrismaClient()
|
|
||||||
}
|
|
||||||
if (!dbUser && user) {
|
if (!dbUser && user) {
|
||||||
const userService = new UserAccountService(prisma);
|
const userService = new UserAccountService();
|
||||||
dbUser = await userService.getFullUserBySupabaseId(user.id);
|
dbUser = await userService.getFullUserBySupabaseId(user.id);
|
||||||
|
|
||||||
if (!dbUser && user) {
|
if (!dbUser && user) {
|
||||||
dbUser = await userService.createUser( user.id, user.user_metadata.full_name );
|
dbUser = await userService.createUser( user.id, user.user_metadata.full_name, user.email?user.email:"no@email.supplied" );
|
||||||
console.log(`\n Created user \n ${JSON.stringify(dbUser)}\n`);
|
console.log(`\n Created user \n ${JSON.stringify(dbUser)}\n`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!supabase || !user || !prisma || !dbUser) {
|
if(!user || !dbUser) {
|
||||||
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: 'INTERNAL_SERVER_ERROR',
|
code: 'INTERNAL_SERVER_ERROR',
|
||||||
message: `Unable to fetch user data, please try again later. Missing ->[supabase:${(!supabase)},user:${(!user)},prisma:${(!prisma)},dbUser:${(!dbUser)}, ]`,
|
message: `Unable to fetch user data, please try again later. Missing ->[user:${(!user)},dbUser:${(!dbUser)}]`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO - This seems excessive, trim context when I have figured out what I actually need
|
|
||||||
return {
|
return {
|
||||||
supabase,
|
|
||||||
user,
|
user,
|
||||||
prisma,
|
|
||||||
dbUser,
|
dbUser,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export const notesRouter = router({
|
|||||||
getForCurrentUser: protectedProcedure
|
getForCurrentUser: protectedProcedure
|
||||||
.input(z.object({ account_id: z.number() }))
|
.input(z.object({ account_id: z.number() }))
|
||||||
.query(async ({ ctx, input }) => {
|
.query(async ({ ctx, input }) => {
|
||||||
const notesService = new NotesService(ctx.prisma);
|
const notesService = new NotesService();
|
||||||
const notes = await notesService.getNotesForAccountId(input.account_id);
|
const notes = await notesService.getNotesForAccountId(input.account_id);
|
||||||
return {
|
return {
|
||||||
notes,
|
notes,
|
||||||
@@ -15,7 +15,7 @@ export const notesRouter = router({
|
|||||||
getById: publicProcedure
|
getById: publicProcedure
|
||||||
.input(z.object({ note_id: z.number() }))
|
.input(z.object({ note_id: z.number() }))
|
||||||
.query(async ({ ctx, input }) => {
|
.query(async ({ ctx, input }) => {
|
||||||
const notesService = new NotesService(ctx.prisma);
|
const notesService = new NotesService();
|
||||||
const note = await notesService.getNoteById(input.note_id);
|
const note = await notesService.getNoteById(input.note_id);
|
||||||
return {
|
return {
|
||||||
note,
|
note,
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export const userAccountRouter = router({
|
|||||||
changeAccountPlan: adminProcedure
|
changeAccountPlan: adminProcedure
|
||||||
.input(z.object({ account_id: z.number(), plan_id: z.number() }))
|
.input(z.object({ account_id: z.number(), plan_id: z.number() }))
|
||||||
.query(async ({ ctx, input }) => {
|
.query(async ({ ctx, input }) => {
|
||||||
const uaService = new UserAccountService(ctx.prisma);
|
const uaService = new UserAccountService();
|
||||||
const account = await uaService.changeAccountPlan(input.account_id, input.plan_id);
|
const account = await uaService.changeAccountPlan(input.account_id, input.plan_id);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -23,7 +23,7 @@ export const userAccountRouter = router({
|
|||||||
joinUserToAccount: adminProcedure
|
joinUserToAccount: adminProcedure
|
||||||
.input(z.object({ account_id: z.number(), user_id: z.number() }))
|
.input(z.object({ account_id: z.number(), user_id: z.number() }))
|
||||||
.query(async ({ ctx, input }) => {
|
.query(async ({ ctx, input }) => {
|
||||||
const uaService = new UserAccountService(ctx.prisma);
|
const uaService = new UserAccountService();
|
||||||
const membership = (ctx.dbUser?.id)?await uaService.joinUserToAccount(input.user_id, input.account_id):null;
|
const membership = (ctx.dbUser?.id)?await uaService.joinUserToAccount(input.user_id, input.account_id):null;
|
||||||
return {
|
return {
|
||||||
membership,
|
membership,
|
||||||
@@ -32,7 +32,7 @@ export const userAccountRouter = router({
|
|||||||
changeUserAccessWithinAccount: adminProcedure
|
changeUserAccessWithinAccount: adminProcedure
|
||||||
.input(z.object({ user_id: z.number(), account_id: z.number(), access: z.enum([ACCOUNT_ACCESS.ADMIN, ACCOUNT_ACCESS.OWNER, ACCOUNT_ACCESS.READ_ONLY, ACCOUNT_ACCESS.READ_WRITE]) }))
|
.input(z.object({ user_id: z.number(), account_id: z.number(), access: z.enum([ACCOUNT_ACCESS.ADMIN, ACCOUNT_ACCESS.OWNER, ACCOUNT_ACCESS.READ_ONLY, ACCOUNT_ACCESS.READ_WRITE]) }))
|
||||||
.query(async ({ ctx, input }) => {
|
.query(async ({ ctx, input }) => {
|
||||||
const uaService = new UserAccountService(ctx.prisma);
|
const uaService = new UserAccountService();
|
||||||
const membership = await uaService.changeUserAccessWithinAccount(input.user_id, input.account_id, input.access);
|
const membership = await uaService.changeUserAccessWithinAccount(input.user_id, input.account_id, input.access);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -42,7 +42,7 @@ export const userAccountRouter = router({
|
|||||||
claimOwnershipOfAccount: adminProcedure
|
claimOwnershipOfAccount: adminProcedure
|
||||||
.input(z.object({ account_id: z.number() }))
|
.input(z.object({ account_id: z.number() }))
|
||||||
.query(async ({ ctx, input }) => {
|
.query(async ({ ctx, input }) => {
|
||||||
const uaService = new UserAccountService(ctx.prisma);
|
const uaService = new UserAccountService();
|
||||||
const membership = await uaService.claimOwnershipOfAccount(ctx.dbUser.id, input.account_id);
|
const membership = await uaService.claimOwnershipOfAccount(ctx.dbUser.id, input.account_id);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
Reference in New Issue
Block a user