diff --git a/src/config/website.tsx b/src/config/website.tsx index 28c9c22..83e781a 100644 --- a/src/config/website.tsx +++ b/src/config/website.tsx @@ -131,34 +131,39 @@ export const websiteConfig: WebsiteConfig = { }, credits: { enableCredits: true, + creditExpireDays: 30, + registerGiftCredits: { + enable: true, + credits: 100, + }, + freeMonthlyCredits: { + enable: true, + credits: 50, + }, packages: { basic: { id: 'basic', credits: 100, price: 990, popular: false, - description: 'Perfect for getting started', }, standard: { id: 'standard', credits: 200, price: 1490, popular: true, - description: 'Most popular package', }, premium: { id: 'premium', credits: 500, price: 3990, popular: false, - description: 'Best value for heavy users', }, enterprise: { id: 'enterprise', credits: 1000, price: 6990, popular: false, - description: 'Tailored for enterprises', }, }, }, diff --git a/src/credits/credits.ts b/src/credits/credits.ts index d5ca9f4..90592c5 100644 --- a/src/credits/credits.ts +++ b/src/credits/credits.ts @@ -1,13 +1,9 @@ import { randomUUID } from 'crypto'; +import { websiteConfig } from '@/config/website'; import { getDb } from '@/db'; import { creditTransaction, userCredit } from '@/db/schema'; import { addDays, isAfter } from 'date-fns'; import { and, asc, eq, or } from 'drizzle-orm'; -import { - CREDIT_EXPIRE_DAYS, - FREE_MONTHLY_CREDITS, - REGISTER_GIFT_CREDITS, -} from '../lib/constants'; import { CREDIT_TRANSACTION_TYPE } from './types'; /** @@ -45,7 +41,7 @@ export async function updateUserLastRefreshAt(userId: string, date: Date) { * Write a credit transaction record * @param params - Credit transaction parameters */ -export async function logCreditTransaction({ +export async function saveCreditTransaction({ userId, type, amount, @@ -61,11 +57,16 @@ export async function logCreditTransaction({ expirationDate?: Date; }) { if (!userId || !type || !description) { - console.error('Invalid params', userId, type, description); + console.error( + 'saveCreditTransaction, invalid params', + userId, + type, + description + ); throw new Error('Invalid params'); } if (!Number.isFinite(amount) || amount === 0) { - console.error('Invalid amount', userId, amount); + console.error('saveCreditTransaction, invalid amount', userId, amount); throw new Error('Invalid amount'); } const db = await getDb(); @@ -95,7 +96,7 @@ export async function addCredits({ type, description, paymentId, - expireDays = CREDIT_EXPIRE_DAYS, + expireDays = websiteConfig.credits.creditExpireDays.days, }: { userId: string; amount: number; @@ -105,15 +106,15 @@ export async function addCredits({ expireDays?: number; }) { if (!userId || !type || !description) { - console.error('Invalid params', userId, type, description); + console.error('addCredits, invalid params', userId, type, description); throw new Error('Invalid params'); } if (!Number.isFinite(amount) || amount <= 0) { - console.error('Invalid amount', userId, amount); + console.error('addCredits, invalid amount', userId, amount); throw new Error('Invalid amount'); } if (!Number.isFinite(expireDays) || expireDays <= 0) { - console.error('Invalid expire days', userId, expireDays); + console.error('addCredits, invalid expire days', userId, expireDays); throw new Error('Invalid expire days'); } // Process expired credits first @@ -150,7 +151,7 @@ export async function addCredits({ }); } // Write credit transaction record - await logCreditTransaction({ + await saveCreditTransaction({ userId, type, amount, @@ -189,18 +190,20 @@ export async function consumeCredits({ description: string; }) { if (!userId || !description) { - console.error('Invalid params', userId, description); + console.error('consumeCredits, invalid params', userId, description); throw new Error('Invalid params'); } if (!Number.isFinite(amount) || amount <= 0) { - console.error('Invalid amount', userId, amount); + console.error('consumeCredits, invalid amount', userId, amount); throw new Error('Invalid amount'); } // Process expired credits first await processExpiredCredits(userId); // Check balance if (!(await hasEnoughCredits({ userId, requiredCredits: amount }))) { - console.error( `Insufficient credits for user ${userId}, required: ${amount}` ); + console.error( + `Insufficient credits for user ${userId}, required: ${amount}` + ); throw new Error('Insufficient credits'); } // FIFO consumption: consume from the earliest unexpired credits first @@ -251,7 +254,7 @@ export async function consumeCredits({ .set({ currentCredits: newBalance, updatedAt: new Date() }) .where(eq(userCredit.userId, userId)); // Write usage record - await logCreditTransaction({ + await saveCreditTransaction({ userId, type: CREDIT_TRANSACTION_TYPE.USAGE, amount: -amount, @@ -319,7 +322,7 @@ export async function processExpiredCredits(userId: string) { .set({ currentCredits: newBalance, updatedAt: now }) .where(eq(userCredit.userId, userId)); // Write expire record - await logCreditTransaction({ + await saveCreditTransaction({ userId, type: CREDIT_TRANSACTION_TYPE.EXPIRE, amount: -expiredTotal, @@ -333,6 +336,10 @@ export async function processExpiredCredits(userId: string) { * @param userId - User ID */ export async function addRegisterGiftCredits(userId: string) { + if (!websiteConfig.credits.registerGiftCredits.enable) { + console.log('addRegisterGiftCredits, disabled'); + return; + } // Check if user has already received register gift credits const db = await getDb(); const record = await db @@ -347,11 +354,12 @@ export async function addRegisterGiftCredits(userId: string) { .limit(1); // add register gift credits if user has not received them yet if (record.length === 0) { + const credits = websiteConfig.credits.registerGiftCredits.credits; await addCredits({ userId, - amount: REGISTER_GIFT_CREDITS, + amount: credits, type: CREDIT_TRANSACTION_TYPE.REGISTER_GIFT, - description: `Register gift credits: ${REGISTER_GIFT_CREDITS}`, + description: `Register gift credits: ${credits}`, }); } } @@ -361,6 +369,10 @@ export async function addRegisterGiftCredits(userId: string) { * @param userId - User ID */ export async function addMonthlyFreeCredits(userId: string) { + if (!websiteConfig.credits.freeMonthlyCredits.enable) { + console.log('addMonthlyFreeCredits, disabled'); + return; + } // Check last refresh time const db = await getDb(); const record = await db @@ -381,11 +393,12 @@ export async function addMonthlyFreeCredits(userId: string) { } // add credits if it's a new month if (canAdd) { + const credits = websiteConfig.credits.freeMonthlyCredits.credits; await addCredits({ userId, - amount: FREE_MONTHLY_CREDITS, + amount: credits, type: CREDIT_TRANSACTION_TYPE.MONTHLY_REFRESH, - description: `Free monthly credits: ${FREE_MONTHLY_CREDITS} for ${now.getFullYear()}-${now.getMonth() + 1}`, + description: `Free monthly credits: ${credits} for ${now.getFullYear()}-${now.getMonth() + 1}`, }); } } diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 8acb5ca..39459fd 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -1,11 +1,2 @@ export const PLACEHOLDER_IMAGE = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAoJJREFUWEfFl4lu4zAMRO3cx/9/au6reMaOdkxTTl0grQFCRoqaT+SQotq2bV9N8rRt28xms87m83l553eZ/9vr9Wpkz+ezkT0ej+6dv1X81AFw7M4FBACPVn2c1Z3zLgDeJwHgeLFYdAARYioAEAKJEG2WAjl3gCwNYymQQ9b7/V4spmIAwO6Wy2VnAMikBWlDURBELf8CuN1uHQSrPwMAHK5WqwFELQ01AIXdAa7XawfAb3p6AOwK5+v1ugAoEq4FRSFLgavfQ49jAGQpAE5wjgGCeRrGdBArwHOPcwFcLpcGU1X0IsBuN5tNgYhaiFFwHTiAwq8I+O5xfj6fOz38K+X/fYAdb7fbAgFAjIJ6Aav3AYlQ6nfnDoDz0+lUxNiLALvf7XaDNGQ6GANQBKR85V27B4D3QQRw7hGIYlQKWGM79hSweyCUe1blXhEAogfABwHAXAcqSYkxCtHLUK3XBajSc4Dj8dilAeiSAgD2+30BAEKV4GKcAuDqB4TdYwBgPQByCgApUBoE4EJUGvxUjF3Q69/zLw3g/HA45ABKgdIQu+JPIyDnisCfAxAFNFM0EFNQ64gfS0EUoQP8ighrZSjn3oziZEQpauyKbfjbZchHUL/3AS/Dd30gAkxuRACgfO+EWQW8qwI1o+wseNuKcQiESjALvwNoMI0TcRzD4lFcPYwIM+JTF5x6HOs8yI7jeB5oKhpMRFH9UwaSCDB2Jmg4rc6E2TT0biIaG0rQhNqyhpHBcayTTSXH6vcDL7/sdqRK8LkwTsU499E8vRcAojHcZ4AxABdilgrp4lsXk8oVqgwh7+6H3phqd8J0Kk4vbx/+sZqCD/vNLya/5dT9fAH8g1WdNGgwbQAAAABJRU5ErkJggg=='; - -// free monthly credits (10% of the smallest package) -export const FREE_MONTHLY_CREDITS = 50; - -// register gift credits (for new user registration) -export const REGISTER_GIFT_CREDITS = 100; - -// default credit expiration days -export const CREDIT_EXPIRE_DAYS = 30; diff --git a/src/types/index.d.ts b/src/types/index.d.ts index 16706c3..8f88b35 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -155,6 +155,15 @@ export interface PriceConfig { */ export interface CreditsConfig { enableCredits: boolean; // Whether to enable credits + creditExpireDays: number; // The number of days to expire the credits + registerGiftCredits: { + enable: boolean; // Whether to enable register gift credits + credits: number; // The number of credits to give to the user + }; + freeMonthlyCredits: { + enable: boolean; // Whether to enable free monthly credits + credits: number; // The number of credits to give to the user + }; packages: Record; // Packages indexed by ID }