diff --git a/dashboard/server/api/pay/redeem_code.post.ts b/dashboard/server/api/pay/redeem_code.post.ts new file mode 100644 index 0000000..414ed01 --- /dev/null +++ b/dashboard/server/api/pay/redeem_code.post.ts @@ -0,0 +1,51 @@ +import { getPlanFromId } from "@data/PREMIUM"; +import StripeService from '~/server/services/StripeService'; +import { PREMIUM_PLAN } from "../../../../shared/data/PREMIUM"; +import { checkAppsumoCode, useAppsumoCode } from "~/server/services/AppsumoService"; + + + +function getPlanToActivate(current_plan_id: number) { + if (current_plan_id === PREMIUM_PLAN.FREE.ID) { + return PREMIUM_PLAN.APPSUMO_INCUBATION; + } + // if (current_plan_id === PREMIUM_PLAN.INCUBATION.ID) { + // return PREMIUM_PLAN.APPSUMO_ACCELERATION; + // } + // if (current_plan_id === PREMIUM_PLAN.ACCELERATION.ID) { + // return PREMIUM_PLAN.APPSUMO_GROWTH; + // } + if (current_plan_id === PREMIUM_PLAN.APPSUMO_INCUBATION.ID) { + return PREMIUM_PLAN.APPSUMO_ACCELERATION; + } + if (current_plan_id === PREMIUM_PLAN.APPSUMO_ACCELERATION.ID) { + return PREMIUM_PLAN.APPSUMO_GROWTH; + } +} + +export default defineEventHandler(async event => { + + const data = await getRequestData(event, { requireSchema: false, allowGuests: false, allowLitlyx: false }); + if (!data) return; + + const { project, pid } = data; + + const body = await readBody(event); + + const { code } = body; + + const valid = await checkAppsumoCode(code); + + if (!valid) return setResponseStatus(event, 400, 'Current plan not found'); + + const currentPlan = getPlanFromId(project.premium_type); + if (!currentPlan) return setResponseStatus(event, 400, 'Current plan not found'); + const planToActivate = getPlanToActivate(currentPlan.ID); + if (!planToActivate) return setResponseStatus(event, 400, 'Cannot use code on current plan'); + + await StripeService.deleteSubscription(project.subscription_id); + await StripeService.createSubscription(project.customer_id, planToActivate.ID); + + await useAppsumoCode(code); + +}); \ No newline at end of file diff --git a/dashboard/server/services/AppsumoService.ts b/dashboard/server/services/AppsumoService.ts new file mode 100644 index 0000000..4ff0b9f --- /dev/null +++ b/dashboard/server/services/AppsumoService.ts @@ -0,0 +1,13 @@ + + +import { AppsumoCodeModel } from '@schema/AppsumoCode' + +export async function checkAppsumoCode(code: string) { + const target = await AppsumoCodeModel.exists({ code, used_at: { $exists: false } }); + return target; +} + + +export async function useAppsumoCode(code: string) { + await AppsumoCodeModel.updateOne({ code }, { used_at: Date.now() }); +} \ No newline at end of file diff --git a/dashboard/server/services/StripeService.ts b/dashboard/server/services/StripeService.ts index 9884f30..67451a8 100644 --- a/dashboard/server/services/StripeService.ts +++ b/dashboard/server/services/StripeService.ts @@ -168,6 +168,23 @@ class StripeService { return false; } + async createSubscription(customer_id: string, planId: number) { + if (this.disabledMode) return; + if (!this.stripe) throw Error('Stripe not initialized'); + + const PLAN = getPlanFromId(planId); + if (!PLAN) throw Error('Plan not found'); + + const subscription = await this.stripe.subscriptions.create({ + customer: customer_id, + items: [ + { price: this.testMode ? PLAN.PRICE_TEST : PLAN.PRICE, quantity: 1 } + ], + }); + + return subscription; + } + async createOneTimeSubscriptionDummy(customer_id: string, planId: number) { if (this.disabledMode) return; if (!this.stripe) throw Error('Stripe not initialized'); diff --git a/shared/data/PREMIUM.ts b/shared/data/PREMIUM.ts index 851b5dd..00a502e 100644 --- a/shared/data/PREMIUM.ts +++ b/shared/data/PREMIUM.ts @@ -14,7 +14,10 @@ export const PREMIUM_TAGS = [ 'SCALING', 'UNICORN', 'LIFETIME_GROWTH_ONETIME', - 'GROWTH_DUMMY' + 'GROWTH_DUMMY', + 'APPSUMO_INCUBATION', + 'APPSUMO_ACCELERATION', + 'APPSUMO_GROWTH', ] as const; @@ -123,7 +126,31 @@ export const PREMIUM_PLAN: Record = { PRICE: 'price_1PvgoRB2lPUiVs9VC51YBT7J', PRICE_TEST: 'price_1PvgRTB2lPUiVs9V3kFSNC3G', COST: 0 - } + }, + APPSUMO_INCUBATION: { + ID: 6001, + COUNT_LIMIT: 50_000, + AI_MESSAGE_LIMIT: 30, + PRICE: '', + PRICE_TEST: '', + COST: 0 + }, + APPSUMO_ACCELERATION: { + ID: 6002, + COUNT_LIMIT: 150_000, + AI_MESSAGE_LIMIT: 100, + PRICE: '', + PRICE_TEST: '', + COST: 0 + }, + APPSUMO_GROWTH: { + ID: 6003, + COUNT_LIMIT: 500_000, + AI_MESSAGE_LIMIT: 3_000, + PRICE: '', + PRICE_TEST: '', + COST: 0 + }, } try { diff --git a/shared/schema/AppsumoCode.ts b/shared/schema/AppsumoCode.ts new file mode 100644 index 0000000..ec594c3 --- /dev/null +++ b/shared/schema/AppsumoCode.ts @@ -0,0 +1,16 @@ +import { model, Schema, Types } from 'mongoose'; + +export type TAppsumoCode = { + _id: Schema.Types.ObjectId, + code: string, + used_at: Date, + created_at?: Date, +} + +const AppsumoCodeSchema = new Schema({ + code: { type: String, index: 1 }, + created_at: { type: Date, default: () => Date.now() }, + used_at: { type: Date, required: false }, +}); + +export const AppsumoCodeModel = model('appsumo_codes', AppsumoCodeSchema);