feat: update credit expiration handling and configuration
- Added expireDays property to credit packages and related configurations in website.tsx for better management of credit expiration. - Modified addCredits function to handle expireDays more flexibly, allowing for undefined values. - Updated functions for adding register gift and monthly free credits to utilize the new expireDays configuration. - Enhanced type definitions for credits to include optional expireDays for improved clarity. - Removed obsolete creditExpireDays from the credits configuration to streamline the codebase.
This commit is contained in:
parent
3e0861f883
commit
04f7f891a4
@ -131,20 +131,22 @@ export const websiteConfig: WebsiteConfig = {
|
||||
},
|
||||
credits: {
|
||||
enableCredits: true,
|
||||
creditExpireDays: 30,
|
||||
registerGiftCredits: {
|
||||
enable: true,
|
||||
credits: 100,
|
||||
expireDays: 30,
|
||||
},
|
||||
freeMonthlyCredits: {
|
||||
enable: true,
|
||||
credits: 50,
|
||||
expireDays: 30,
|
||||
},
|
||||
packages: {
|
||||
basic: {
|
||||
id: 'basic',
|
||||
popular: false,
|
||||
credits: 100,
|
||||
expireDays: 30,
|
||||
price: {
|
||||
priceId: process.env.NEXT_PUBLIC_STRIPE_PRICE_CREDITS_BASIC!,
|
||||
amount: 990,
|
||||
@ -156,6 +158,7 @@ export const websiteConfig: WebsiteConfig = {
|
||||
id: 'standard',
|
||||
popular: true,
|
||||
credits: 200,
|
||||
expireDays: 60,
|
||||
price: {
|
||||
priceId: process.env.NEXT_PUBLIC_STRIPE_PRICE_CREDITS_STANDARD!,
|
||||
amount: 1490,
|
||||
@ -167,6 +170,7 @@ export const websiteConfig: WebsiteConfig = {
|
||||
id: 'premium',
|
||||
popular: false,
|
||||
credits: 500,
|
||||
expireDays: 90,
|
||||
price: {
|
||||
priceId: process.env.NEXT_PUBLIC_STRIPE_PRICE_CREDITS_PREMIUM!,
|
||||
amount: 3990,
|
||||
@ -178,6 +182,7 @@ export const websiteConfig: WebsiteConfig = {
|
||||
id: 'enterprise',
|
||||
popular: false,
|
||||
credits: 1000,
|
||||
expireDays: 180,
|
||||
price: {
|
||||
priceId: process.env.NEXT_PUBLIC_STRIPE_PRICE_CREDITS_ENTERPRISE!,
|
||||
amount: 6990,
|
||||
|
@ -1,173 +0,0 @@
|
||||
# Credit Management System Implementation
|
||||
|
||||
## Overview
|
||||
|
||||
This document describes the credit management system implementation for the mksaas-template project, which allows users to purchase credits using Stripe payments.
|
||||
|
||||
## Features Implemented
|
||||
|
||||
### 1. Credit Packages
|
||||
- Defined credit packages with different tiers (Basic, Standard, Premium, Enterprise)
|
||||
- Each package includes credits amount, price, and description
|
||||
- Popular package highlighting
|
||||
|
||||
### 2. Payment Integration
|
||||
- Stripe PaymentIntent integration for credit purchases
|
||||
- Secure payment processing with webhook verification
|
||||
- Automatic credit addition upon successful payment
|
||||
|
||||
### 3. UI Components
|
||||
- Credit balance display with refresh functionality
|
||||
- Credit packages selection interface
|
||||
- Stripe payment form integration
|
||||
- Modern, responsive design
|
||||
|
||||
### 4. Database Integration
|
||||
- Credit transaction recording
|
||||
- User credit balance management
|
||||
- Proper error handling and validation
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
### Core Components
|
||||
- `src/components/dashboard/credit-packages.tsx` - Main credit packages interface
|
||||
- `src/components/dashboard/credit-balance.tsx` - Credit balance display
|
||||
- `src/components/dashboard/stripe-payment-form.tsx` - Stripe payment integration
|
||||
|
||||
### Actions & API
|
||||
- `src/actions/credits.action.ts` - Credit-related server actions
|
||||
- `src/app/api/webhooks/stripe/route.ts` - Stripe webhook handler
|
||||
- `src/payment/index.ts` - Payment provider interface (updated)
|
||||
- `src/payment/types.ts` - Payment types (updated)
|
||||
|
||||
### Configuration
|
||||
- `src/config/website.tsx` - Credit packages configuration
|
||||
- `env.example` - Environment variables template
|
||||
|
||||
### Pages
|
||||
- `src/app/[locale]/(protected)/settings/credits/page.tsx` - Credits management page
|
||||
|
||||
## Environment Variables Required
|
||||
|
||||
Add these to your `.env.local` file:
|
||||
|
||||
```env
|
||||
# Stripe Configuration
|
||||
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY="pk_test_..."
|
||||
STRIPE_SECRET_KEY="sk_test_..."
|
||||
STRIPE_WEBHOOK_SECRET="whsec_..."
|
||||
```
|
||||
|
||||
## Setup Instructions
|
||||
|
||||
### 1. Stripe Configuration
|
||||
1. Create a Stripe account at https://dashboard.stripe.com
|
||||
2. Get your API keys from the Stripe dashboard
|
||||
3. Set up a webhook endpoint pointing to `/api/webhooks/stripe`
|
||||
4. Copy the webhook secret and add it to your environment variables
|
||||
|
||||
### 2. Database Setup
|
||||
Make sure your database schema includes the required credit tables as defined in `src/db/schema.ts`.
|
||||
|
||||
### 3. Environment Variables
|
||||
Copy the required environment variables from `env.example` to your `.env.local` file and fill in the values.
|
||||
|
||||
## Usage
|
||||
|
||||
### For Users
|
||||
1. Navigate to `/settings/credits`
|
||||
2. View current credit balance
|
||||
3. Select a credit package
|
||||
4. Complete payment using Stripe
|
||||
5. Credits are automatically added to account
|
||||
|
||||
### For Developers
|
||||
```typescript
|
||||
// Get user credits
|
||||
const result = await getCreditsAction();
|
||||
|
||||
// Create payment intent
|
||||
const paymentIntent = await createCreditPaymentIntent({
|
||||
packageId: 'standard'
|
||||
});
|
||||
|
||||
// Add credits manually
|
||||
await addCredits({
|
||||
userId: 'user-id',
|
||||
amount: 100,
|
||||
type: 'PURCHASE',
|
||||
description: 'Credit purchase'
|
||||
});
|
||||
|
||||
// Access credit packages from config
|
||||
import { websiteConfig } from '@/config/website';
|
||||
const creditPackages = Object.values(websiteConfig.credits.packages);
|
||||
```
|
||||
|
||||
## Credit Packages Configuration
|
||||
|
||||
Edit `src/config/website.tsx` to modify available credit packages:
|
||||
|
||||
```typescript
|
||||
export const websiteConfig: WebsiteConfig = {
|
||||
// ... other config
|
||||
credits: {
|
||||
enableCredits: true,
|
||||
packages: {
|
||||
basic: {
|
||||
id: 'basic',
|
||||
credits: 100,
|
||||
price: 990, // Price in cents
|
||||
popular: false,
|
||||
description: 'Perfect for getting started',
|
||||
},
|
||||
// ... more packages
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## Webhook Events
|
||||
|
||||
The system handles these Stripe webhook events:
|
||||
- `payment_intent.succeeded` - Adds credits to user account upon successful payment
|
||||
|
||||
## Security Features
|
||||
|
||||
1. **Webhook Verification**: All webhook requests are verified using Stripe signatures
|
||||
2. **Payment Validation**: Amount and package validation before processing
|
||||
3. **User Authentication**: All credit operations require authenticated users
|
||||
4. **Metadata Validation**: Payment metadata is validated before processing
|
||||
|
||||
## Error Handling
|
||||
|
||||
The system includes comprehensive error handling for:
|
||||
- Invalid payment attempts
|
||||
- Network failures
|
||||
- Database errors
|
||||
- Authentication issues
|
||||
- Webhook verification failures
|
||||
|
||||
## Testing
|
||||
|
||||
To test the credit purchase flow:
|
||||
1. Use Stripe test cards (e.g., `4242424242424242`)
|
||||
2. Monitor webhook events in Stripe dashboard
|
||||
3. Check credit balance updates in the application
|
||||
|
||||
## Integration Notes
|
||||
|
||||
This implementation:
|
||||
- Uses Next.js server actions for secure server-side operations
|
||||
- Integrates with existing Drizzle ORM schema
|
||||
- Follows the existing payment provider pattern
|
||||
- Maintains consistency with the existing codebase architecture
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
Potential improvements:
|
||||
- Credit transaction history display
|
||||
- Credit expiration management
|
||||
- Bulk credit operations
|
||||
- Credit usage analytics
|
||||
- Subscription-based credit allocation
|
@ -96,7 +96,7 @@ export async function addCredits({
|
||||
type,
|
||||
description,
|
||||
paymentId,
|
||||
expireDays = websiteConfig.credits.creditExpireDays,
|
||||
expireDays,
|
||||
}: {
|
||||
userId: string;
|
||||
amount: number;
|
||||
@ -113,7 +113,10 @@ export async function addCredits({
|
||||
console.error('addCredits, invalid amount', userId, amount);
|
||||
throw new Error('Invalid amount');
|
||||
}
|
||||
if (!Number.isFinite(expireDays) || expireDays <= 0) {
|
||||
if (
|
||||
expireDays !== undefined &&
|
||||
(!Number.isFinite(expireDays) || expireDays <= 0)
|
||||
) {
|
||||
console.error('addCredits, invalid expire days', userId, expireDays);
|
||||
throw new Error('Invalid expire days');
|
||||
}
|
||||
@ -159,7 +162,7 @@ export async function addCredits({
|
||||
paymentId,
|
||||
// NOTE: there is no expiration date for PURCHASE type
|
||||
expirationDate:
|
||||
type === CREDIT_TRANSACTION_TYPE.PURCHASE
|
||||
type === CREDIT_TRANSACTION_TYPE.PURCHASE || expireDays === undefined
|
||||
? undefined
|
||||
: addDays(new Date(), expireDays),
|
||||
});
|
||||
@ -355,11 +358,13 @@ export async function addRegisterGiftCredits(userId: string) {
|
||||
// add register gift credits if user has not received them yet
|
||||
if (record.length === 0) {
|
||||
const credits = websiteConfig.credits.registerGiftCredits.credits;
|
||||
const expireDays = websiteConfig.credits.registerGiftCredits.expireDays;
|
||||
await addCredits({
|
||||
userId,
|
||||
amount: credits,
|
||||
type: CREDIT_TRANSACTION_TYPE.REGISTER_GIFT,
|
||||
description: `Register gift credits: ${credits}`,
|
||||
expireDays,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -394,11 +399,13 @@ export async function addMonthlyFreeCredits(userId: string) {
|
||||
// add credits if it's a new month
|
||||
if (canAdd) {
|
||||
const credits = websiteConfig.credits.freeMonthlyCredits.credits;
|
||||
const expireDays = websiteConfig.credits.freeMonthlyCredits.expireDays;
|
||||
await addCredits({
|
||||
userId,
|
||||
amount: credits,
|
||||
type: CREDIT_TRANSACTION_TYPE.MONTHLY_REFRESH,
|
||||
description: `Free monthly credits: ${credits} for ${now.getFullYear()}-${now.getMonth() + 1}`,
|
||||
expireDays,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -26,4 +26,5 @@ export interface CreditPackage {
|
||||
popular: boolean; // Whether the package is popular
|
||||
name?: string; // Display name of the package
|
||||
description?: string; // Description of the package
|
||||
expireDays?: number; // Number of days to expire the credits, undefined means no expire
|
||||
}
|
||||
|
3
src/types/index.d.ts
vendored
3
src/types/index.d.ts
vendored
@ -155,14 +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
|
||||
expireDays?: number; // The number of days to expire the credits, undefined means no expire
|
||||
};
|
||||
freeMonthlyCredits: {
|
||||
enable: boolean; // Whether to enable free monthly credits
|
||||
credits: number; // The number of credits to give to the user
|
||||
expireDays?: number; // The number of days to expire the credits, undefined means no expire
|
||||
};
|
||||
packages: Record<string, CreditPackage>; // Packages indexed by ID
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user