fix payment service + appsumo + ui

This commit is contained in:
Emily
2025-04-22 18:42:18 +02:00
parent f631c29fb2
commit a9bbc58ad1
13 changed files with 121 additions and 65 deletions

View File

@@ -109,6 +109,9 @@ const avgSessionDuration = computed(() => {
seconds += avg * 60;
while (seconds >= 60) { seconds -= 60; minutes += 1; }
while (minutes >= 60) { minutes -= 60; hours += 1; }
if (hours == 0 && minutes == 0 && seconds < 10) return `0m ~10s`
return `${hours > 0 ? hours + 'h ' : ''}${minutes}m ${seconds.toFixed()}s`
});

View File

@@ -32,7 +32,7 @@ const { data: pendingInvites, refresh: refreshInvites } = useFetch('/api/project
headers: useComputedHeaders({})
});
const { userRoles } = useLoggedUser();
const { userRoles, isPremium } = useLoggedUser();
const { projectList } = useProject();
const debugMode = process.dev;
@@ -94,6 +94,9 @@ async function generatePDF() {
const { actions } = useProject();
const { showDrawer } = useDrawer();
const modal = useModal();
@@ -138,13 +141,6 @@ function openPendingInvites() {
}">
<div class="py-4 pb-2 px-2 gap-6 flex flex-col w-full">
<!-- <div class="flex px-2" v-if="!isPremium">
<LyxUiButton type="primary" class="w-full text-center text-[.8rem] font-medium" @click="pricingDrawer.visible.value = true;">
Upgrade plan
</LyxUiButton>
</div> -->
<div class="flex px-2 flex-col">
<div class="flex items-center gap-2 w-full">
@@ -304,6 +300,16 @@ function openPendingInvites() {
<div class="grow"></div>
<LyxUiCard class="w-full mb-4" v-if="!isPremium">
<div class="flex flex-col gap-3">
<div class="text-center"> Upgrade to premium </div>
<LyxUiButton type="primary" class="w-full text-center text-[.8rem] font-medium"
@click="showDrawer('PRICING')">
Upgrade
</LyxUiButton>
</div>
</LyxUiCard>
<div v-if="pendingInvites && pendingInvites.length > 0" @click="openPendingInvites()"
class="w-full bg-[#fbbf2422] p-4 rounded-lg text-[.9rem] flex flex-col justify-center cursor-pointer">
<div class="poppins font-medium dark:text-lyx-text text-lyx-lightmode-text">
@@ -316,13 +322,18 @@ function openPendingInvites() {
</div>
</div>
<LyxUiSeparator class="px-4 mb-2"></LyxUiSeparator>
<div class="flex px-1 w-full">
<LayoutVerticalBottomMenu></LayoutVerticalBottomMenu>
<!-- <LyxUiSeparator class="px-4 mb-2"></LyxUiSeparator> -->
<LyxUiCard class="flex py-1 px-1 w-full relative">
<div class="absolute top-[-22%] right-0">
<div @click="showDrawer('PRICING')"
class="flex items-center gap-1 poppins text-[.5rem] bg-[#fbbe244f] outline outline-[1px] outline-[#fbbf24] px-3 py-[.12rem] rounded-sm">
<i class="far fa-crown mb-[2px]"></i>
<div>Premium</div>
</div>
</div>
<LayoutVerticalBottomMenu></LayoutVerticalBottomMenu>
</LyxUiCard>

View File

@@ -54,7 +54,7 @@ async function redeemCode() {
Redeemed codes: {{ valid_codes.data.value?.count || '0' }}
</div>
<div class="poppins text-[1.1rem] text-lyx-lightmode-text dark:text-yellow-400 mb-2">
*Plan upgrades are applicable exclusively to this project(workspace).
*Plan upgrades are applied to account level.
</div>
</template>
</SettingsTemplate>

View File

@@ -6,7 +6,8 @@ const selfhosted = useSelfhosted();
const items = [
{ label: 'General', slot: 'general', tab: 'general' },
{ label: 'Domains', slot: 'domains', tab: 'domains' }
{ label: 'Domains', slot: 'domains', tab: 'domains' },
{ label: 'Codes', slot: 'codes', tab: 'codes' },
]
</script>
@@ -30,7 +31,7 @@ const items = [
Billing disabled in self-host mode
</div>
</template> -->
<!-- <template #codes>
<template #codes>
<SettingsCodes v-if="!selfhosted" :key="refreshKey"></SettingsCodes>
<div class="flex popping text-[1.2rem] font-semibold justify-center mt-[20vh] text-lyx-lightmode-text dark:text-lyx-text"
v-if="selfhosted">
@@ -39,7 +40,7 @@ const items = [
</template>
<template #account>
<SettingsAccount :key="refreshKey"></SettingsAccount>
</template> -->
</template>
</CustomTab>
</div>

View File

@@ -1,17 +1,13 @@
import { getPlanFromId, PREMIUM_PLAN } from "@data/PREMIUM";
import { canTryAppsumoCode, checkAppsumoCode, useAppsumoCode, useTryAppsumoCode } from "~/server/services/AppsumoService";
import { PaymentServiceHelper } from "~/server/services/PaymentServiceHelper";
import { PremiumModel } from "~/shared/schema/PremiumSchema";
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;
}
@@ -38,13 +34,18 @@ export default defineEventHandler(async event => {
const valid = await checkAppsumoCode(code);
if (!valid) return setResponseStatus(event, 400, 'Code not valid');
// 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');
const currentPremiumData = await PremiumModel.findOne({ user_id: user.id });
if (!currentPremiumData) return setResponseStatus(event, 400, 'Error finding user');
// await StripeService.createSubscription(project.customer_id, planToActivate.ID);
const currentPlan = getPlanFromId(currentPremiumData.premium_type);
if (!currentPlan) return setResponseStatus(event, 400, 'Current plan not found');
// await useAppsumoCode(pid, code);
const planToActivate = getPlanToActivate(currentPlan.ID);
if (!planToActivate) return setResponseStatus(event, 400, 'Cannot use code on current plan');
const sub = await PaymentServiceHelper.create_subscription(user.id, planToActivate.TAG);
console.log(sub);
await useAppsumoCode(pid, code);
});

View File

@@ -15,6 +15,8 @@ import { TeamMemberModel } from "~/shared/schema/TeamMemberSchema";
import { PasswordModel } from "~/shared/schema/PasswordSchema";
import { PremiumModel } from "~/shared/schema/PremiumSchema";
import { PaymentServiceHelper } from "~/server/services/PaymentServiceHelper";
import { VisitModel } from "~/shared/schema/metrics/VisitSchema";
import { EventModel } from "~/shared/schema/metrics/EventSchema";
export default defineEventHandler(async event => {
@@ -24,9 +26,8 @@ export default defineEventHandler(async event => {
const projects = await ProjectModel.find({ owner: userData.id });
const premium = await PremiumModel.findOne({ user_id: userData.id });
if (!premium) return;
if (premium.premium_type > 0) return setResponseStatus(event, 400, 'Cannot delete an account with a premium project');
if (premium && premium.premium_type > 0) return setResponseStatus(event, 400, 'Cannot delete an account with a premium project');
const membersDeletation = await TeamMemberModel.deleteMany({ user_id: userData.id });
const membersEmailDeletation = await TeamMemberModel.deleteMany({ email: userData.user.email });
@@ -36,24 +37,31 @@ export default defineEventHandler(async event => {
const limitdeletation = await UserLimitModel.deleteMany({ user_id: userData.id });
const notifiesDeletation = await LimitNotifyModel.deleteMany({ user_id: userData.id });
await PaymentServiceHelper.delete_customer(premium.customer_id);
if (premium) PaymentServiceHelper.delete_customer(premium.customer_id);
for (const project of projects) {
const project_id = project._id;
const projectDeletation = await ProjectModel.deleteOne({ _id: project_id });
const userSettingsDeletation = await UserSettingsModel.deleteOne({ project_id });
const countDeletation = await ProjectCountModel.deleteMany({ project_id });
const sessionsDeletation = await SessionModel.deleteMany({ project_id });
const aiChatsDeletation = await AiChatModel.deleteMany({ project_id });
const sessionsDeletation = SessionModel.deleteMany({ project_id });
const visitsDeletation = VisitModel.deleteMany({ project_id });
const eventsDeletation = EventModel.deleteMany({ project_id });
const aiChatsDeletation = AiChatModel.deleteMany({ project_id });
//Shields
const addressBlacklistDeletation = await AddressBlacklistModel.deleteMany({ project_id });
const botTrafficOptionsDeletation = await BotTrafficOptionModel.deleteMany({ project_id });
const countryBlacklistDeletation = await CountryBlacklistModel.deleteMany({ project_id });
const domainWhitelistDeletation = await DomainWhitelistModel.deleteMany({ project_id });
const addressBlacklistDeletation = AddressBlacklistModel.deleteMany({ project_id });
const botTrafficOptionsDeletation = BotTrafficOptionModel.deleteMany({ project_id });
const countryBlacklistDeletation = CountryBlacklistModel.deleteMany({ project_id });
const domainWhitelistDeletation = DomainWhitelistModel.deleteMany({ project_id });
}
const premiumDeletation = await PremiumModel.deleteOne({ user_id: userData.id });
const userDeletation = await UserModel.deleteOne({ _id: userData.id });
return { ok: true };

View File

@@ -31,6 +31,10 @@ export class PaymentServiceHelper {
return await this.send('/create_customer', { user_id });
}
static async create_subscription(user_id: string, plan_tag: string): PaymentServiceResponse<{ ok: true }> {
return await this.send('/create_subscription', { user_id, plan_tag });
}
static async create_payment(user_id: string, plan_id: number): PaymentServiceResponse<{ url: string }> {
return await this.send('/create_payment', { user_id, plan_id });
}

View File

@@ -24,6 +24,8 @@ if (!TOKEN || TOKEN.length == 0) {
process.exit();
}
app.use('/webhook', webhookRouter);
app.use((req, res, next) => {
const token = req.header('x-litlyx-token');
if (token != TOKEN) {
@@ -34,8 +36,6 @@ app.use((req, res, next) => {
next();
});
app.use('/webhook', webhookRouter);
app.use('/payment', paymentRouter);
const port = parseInt(process.env.PORT);

View File

@@ -162,3 +162,29 @@ paymentRouter.post('/delete_customer', json(), async (req, res) => {
res.status(500).json({ error: ex.message });
}
});
export const ZBodyCreateSubscription = z.object({
user_id: z.string(),
plan_tag: z.string()
});
paymentRouter.post('/create_subscription', json(), async (req, res) => {
try {
const createSubscriptionData = ZBodyCreateSubscription.parse(req.body);
const premiumData = await PremiumModel.findOne({ user_id: createSubscriptionData.user_id });
if (!premiumData) return sendJson(res, 400, { error: 'user not found' });
if (!premiumData.customer_id) return sendJson(res, 400, { error: 'user have no customer_id' });
await StripeService.createSubscription(
premiumData.customer_id,
createSubscriptionData.plan_tag
);
return sendJson(res, 200, { ok: true });
} catch (ex) {
console.error(ex);
res.status(500).json({ error: ex.message });
}
});

View File

@@ -153,22 +153,21 @@ class StripeService {
// return false;
// }
// async createSubscription(customer_id: string, planId: number) {
// if (this.disabledMode) return;
// if (!this.stripe) throw Error('Stripe not initialized');
async createSubscription(customer_id: string, planTag: string) {
if (!this.stripe) throw Error('Stripe not initialized');
// const PLAN = getPlanFromId(planId);
// if (!PLAN) throw Error('Plan not found');
const PLAN_DATA = getPlanFromTag(planTag as any);
if (!PLAN_DATA) 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 }
// ],
// });
const subscription = await this.stripe.subscriptions.create({
customer: customer_id,
items: [
{ price: this.testMode ? PLAN_DATA.PRICE_TEST : PLAN_DATA.PRICE, quantity: 1 }
],
});
// return subscription;
// }
return subscription;
}
// async createOneTimeSubscriptionDummy(customer_id: string, planId: number) {
// if (this.disabledMode) return;

View File

@@ -610,6 +610,8 @@ function isBot(userAgent: string) {
export async function isAllowedToLog(project_id: string, website: string, ip: string, userAgent: string) {
console.log({ userAgent });
const blacklistData = await AddressBlacklistModel.find({ project_id }, { address: 1 });
for (const blacklistedData of blacklistData) {
if (blacklistedData.address == ip) return false;

View File

@@ -16,6 +16,7 @@ const streamName = requireEnv('STREAM_NAME');
import DeprecatedRouter from "./deprecated";
import { isAllowedToLog } from "./controller";
import { connectDatabase } from "./shared/services/DatabaseService";
app.use('/v1', DeprecatedRouter);
app.post('/event', express.json(jsonOptions), async (req, res) => {

View File

@@ -144,7 +144,7 @@ export const PREMIUM_PLAN: Record<PLAN_TAG, PLAN_DATA> = {
COUNT_LIMIT: 50_000,
AI_MESSAGE_LIMIT: 30,
PRICE: 'price_1QIXwbB2lPUiVs9VKSsoksaU',
PRICE_TEST: '',
PRICE_TEST: 'price_1RBIUsB2lPUiVs9VojGan6WH',
COST: 0,
TAG: 'APPSUMO_INCUBATION'
},
@@ -153,7 +153,7 @@ export const PREMIUM_PLAN: Record<PLAN_TAG, PLAN_DATA> = {
COUNT_LIMIT: 150_000,
AI_MESSAGE_LIMIT: 100,
PRICE: 'price_1QIXxRB2lPUiVs9VrjaVRoOl',
PRICE_TEST: '',
PRICE_TEST: 'price_1RBIV5B2lPUiVs9VKQyxvhst',
COST: 0,
TAG: 'APPSUMO_ACCELERATION'
},
@@ -162,7 +162,7 @@ export const PREMIUM_PLAN: Record<PLAN_TAG, PLAN_DATA> = {
COUNT_LIMIT: 500_000,
AI_MESSAGE_LIMIT: 3_000,
PRICE: 'price_1QIXy8B2lPUiVs9VQBOUPAoE',
PRICE_TEST: '',
PRICE_TEST: 'price_1RBIVFB2lPUiVs9VsMoldAu3',
COST: 0,
TAG: 'APPSUMO_GROWTH'
},