diff --git a/apps/epic-web/schemas/documents/product.tsx b/apps/epic-web/schemas/documents/product.tsx index e8a615f1a1..90ae396870 100644 --- a/apps/epic-web/schemas/documents/product.tsx +++ b/apps/epic-web/schemas/documents/product.tsx @@ -62,28 +62,10 @@ export default defineType({ }, }), defineField({ - name: 'revenueSplits', - title: 'Revenue Splits', - type: 'array', - of: [{type: 'productRevenueSplit'}], - validation: (Rule) => - Rule.custom((revenueSplits) => { - if (!revenueSplits || !Array.isArray(revenueSplits)) return true - - const total = revenueSplits.reduce( - (sum, split) => sum + (split.percentage || 0), - 0, - ) - - if (total > 1) { - return 'The total of all revenue splits cannot exceed 100% (1.0)' - } - - return true - }), - options: { - layout: 'tags', - }, + name: 'contributors', + type: 'contributors', + title: 'Contributors', + validation: (Rule) => Rule.required(), }), defineField({ name: 'state', diff --git a/apps/epic-web/src/inngest/functions/sanity/product/index.ts b/apps/epic-web/src/inngest/functions/sanity/product/index.ts index 3551d57b95..1badcf3cf2 100644 --- a/apps/epic-web/src/inngest/functions/sanity/product/index.ts +++ b/apps/epic-web/src/inngest/functions/sanity/product/index.ts @@ -32,7 +32,11 @@ export const loadSanityProduct = async ( }, image{ url - } + }, + "instructor": contributors[0].contributor->{ + userId, + name, + }, }`, ) => { const sanityProductData = await sanityWriteClient.fetch(query, {id}) @@ -67,6 +71,10 @@ export const BaseSanityProductSchema = z.object({ }) .nullable() .optional(), + instructor: z + .object({userId: z.string(), name: z.string()}) + .optional() + .nullable(), }) export type BaseSanityProduct = z.infer diff --git a/apps/epic-web/src/inngest/functions/sanity/product/sanity-product-created.ts b/apps/epic-web/src/inngest/functions/sanity/product/sanity-product-created.ts index 8aaa9de1c7..b8df60e20c 100644 --- a/apps/epic-web/src/inngest/functions/sanity/product/sanity-product-created.ts +++ b/apps/epic-web/src/inngest/functions/sanity/product/sanity-product-created.ts @@ -35,6 +35,7 @@ export const sanityProductCreated = inngest.createFunction( upgradableTo, state, type, + instructor, } = sanityProduct const productStatus = state === 'active' ? 1 : 0 @@ -82,6 +83,114 @@ export const sanityProductCreated = inngest.createFunction( }) }) + type SplitInfo = { + type: string + percent: number + userId: string | undefined | null + } + + type Splits = { + skill: SplitInfo + owner: SplitInfo + contributor?: SplitInfo + } + + const defineSplits = await step.run( + 'define splits', + async (): Promise => { + const OWNER_ID = '4ef27e5f-00b4-4aa3-b3c4-4a58ae76f50b' + const isOwnerInstructor = instructor?.userId === OWNER_ID + const isSelfPaced = type === 'self-paced' + + let splits: Splits = { + skill: { + type: 'skill', + percent: 0, + userId: null, + }, + owner: { + type: 'owner', + percent: 0, + userId: OWNER_ID, + }, + } + + if (isOwnerInstructor) { + if (isSelfPaced) { + splits.skill.percent = 0.4 + splits.owner.percent = 0.6 + } else { + splits.skill.percent = 0.15 + splits.owner.percent = 0.85 + } + } else { + if (isSelfPaced) { + splits.skill.percent = 0.5 + splits.owner.percent = 0.2 + splits.contributor = { + type: 'contributor', + percent: 0.3, + userId: instructor?.userId, + } + } else { + splits.skill.percent = 0.15 + splits.owner.percent = 0.15 + splits.contributor = { + type: 'contributor', + percent: 0.7, + userId: instructor?.userId, + } + } + } + + return splits + }, + ) + + const createSplits = await step.run( + 'create splits in database', + async () => { + const skillSplit = await prisma.productRevenueSplit.create({ + data: { + id: v4(), + type: defineSplits.skill.type, + productId: product.id, + percent: defineSplits.skill.percent, + userId: defineSplits.skill.userId, + }, + }) + + const ownerSplit = await prisma.productRevenueSplit.create({ + data: { + id: v4(), + type: defineSplits.owner.type, + productId: product.id, + percent: defineSplits.owner.percent, + userId: defineSplits.owner.userId, + }, + }) + + let contributorSplit = null + if (defineSplits.contributor) { + contributorSplit = await prisma.productRevenueSplit.create({ + data: { + id: v4(), + type: defineSplits.contributor.type, + productId: product.id, + percent: defineSplits.contributor.percent, + userId: defineSplits.contributor.userId, + }, + }) + } + + return { + skillSplit, + ownerSplit, + contributorSplit, + } + }, + ) + const merchantAccount = await step.run('get merchant account', async () => { return await prisma.merchantAccount.findFirst({ where: { @@ -167,6 +276,7 @@ export const sanityProductCreated = inngest.createFunction( price, stripeProduct, stripePrice, + createSplits, } } else { throw new Error('No merchant account found') diff --git a/apps/epic-web/src/inngest/functions/sanity/product/sanity-product-deleted.ts b/apps/epic-web/src/inngest/functions/sanity/product/sanity-product-deleted.ts index bb2399f708..1c9b3bd2eb 100644 --- a/apps/epic-web/src/inngest/functions/sanity/product/sanity-product-deleted.ts +++ b/apps/epic-web/src/inngest/functions/sanity/product/sanity-product-deleted.ts @@ -155,5 +155,13 @@ export const sanityProductDeleted = inngest.createFunction( }), ) }) + + await step.run('delete current splits', async () => { + return await prisma.productRevenueSplit.deleteMany({ + where: { + productId: product.id, + }, + }) + }) }, ) diff --git a/apps/epic-web/src/inngest/functions/sanity/product/sanity-product-updated.ts b/apps/epic-web/src/inngest/functions/sanity/product/sanity-product-updated.ts index 2ed56746c9..4d1b0a481f 100644 --- a/apps/epic-web/src/inngest/functions/sanity/product/sanity-product-updated.ts +++ b/apps/epic-web/src/inngest/functions/sanity/product/sanity-product-updated.ts @@ -41,6 +41,7 @@ export const sanityProductUpdated = inngest.createFunction( upgradableTo, state, type, + instructor, } = sanityProduct if (!productId) { @@ -257,6 +258,169 @@ export const sanityProductUpdated = inngest.createFunction( }, ) + const currentSplits = await step.run('get current splits', async () => { + return await prisma.productRevenueSplit.findMany({ + where: { + productId: product.id, + }, + }) + }) + + const splitsNeedUpdate = ( + currentSplits: any[], + instructor: any, + type: string, + ): boolean => { + const OWNER_ID = '4ef27e5f-00b4-4aa3-b3c4-4a58ae76f50b' + + const currentContributorSplit = currentSplits.find( + (split) => split.type === 'contributor', + ) + + const isOwnerInstructor = instructor?.userId === OWNER_ID + const wasOwnerInstructor = !currentContributorSplit + + if (isOwnerInstructor !== wasOwnerInstructor) { + return true + } + + if ( + currentContributorSplit && + instructor?.userId !== currentContributorSplit.userId + ) { + return true + } + + const currentProductType = + currentSplits.length > 0 ? (currentSplits[0] as any).productType : null + + if (currentProductType !== type) { + return true + } + + return false + } + + const needsSplitUpdate = await step.run( + 'check if splits need update', + async () => { + return splitsNeedUpdate(currentSplits, instructor, type) + }, + ) + + type SplitInfo = { + type: string + percent: number + userId: string | undefined | null + } + + type Splits = { + skill: SplitInfo + owner: SplitInfo + contributor?: SplitInfo + } + + if (needsSplitUpdate) { + const defineSplits = await step.run( + 'define splits', + async (): Promise => { + const OWNER_ID = '4ef27e5f-00b4-4aa3-b3c4-4a58ae76f50b' + const isOwnerInstructor = instructor?.userId === OWNER_ID + const isSelfPaced = type === 'self-paced' + + let splits: Splits = { + skill: { + type: 'skill', + percent: 0, + userId: null, + }, + owner: { + type: 'owner', + percent: 0, + userId: OWNER_ID, + }, + } + + if (isOwnerInstructor) { + if (isSelfPaced) { + splits.skill.percent = 0.4 + splits.owner.percent = 0.6 + } else { + splits.skill.percent = 0.15 + splits.owner.percent = 0.85 + } + } else { + if (isSelfPaced) { + splits.skill.percent = 0.5 + splits.owner.percent = 0.2 + splits.contributor = { + type: 'contributor', + percent: 0.3, + userId: instructor?.userId, + } + } else { + splits.skill.percent = 0.15 + splits.owner.percent = 0.15 + splits.contributor = { + type: 'contributor', + percent: 0.7, + userId: instructor?.userId, + } + } + } + + return splits + }, + ) + + await step.run('update splits in database', async () => { + await prisma.productRevenueSplit.deleteMany({ + where: { + productId: product.id, + }, + }) + + const skillSplit = await prisma.productRevenueSplit.create({ + data: { + id: v4(), + type: defineSplits.skill.type, + productId: product.id, + percent: defineSplits.skill.percent, + userId: defineSplits.skill.userId, + }, + }) + + const ownerSplit = await prisma.productRevenueSplit.create({ + data: { + id: v4(), + type: defineSplits.owner.type, + productId: product.id, + percent: defineSplits.owner.percent, + userId: defineSplits.owner.userId, + }, + }) + + let contributorSplit = null + if (defineSplits.contributor) { + contributorSplit = await prisma.productRevenueSplit.create({ + data: { + id: v4(), + type: defineSplits.contributor.type, + productId: product.id, + percent: defineSplits.contributor.percent, + userId: defineSplits.contributor.userId, + }, + }) + } + + return { + skillSplit, + ownerSplit, + contributorSplit, + } + }) + } + return {updatedProduct, updatedStripeProduct} }, )