feat: billing system — service plans, wallet, cost calculation
- Backend: billing module with ServicePlan, PricingRule, Wallet, WalletTransaction entities - Admin can CRUD service plans with hourly/monthly/yearly billing cycles - Each plan has flexible pricing rules (base_fee, cpu, memory, storage, db addon) - Wallet system: auto-created per user, charge, deduct, refund, transaction history - Cost calculation endpoint: cross-cycle conversion (hourly*720=monthly, monthly*12=yearly) - Frontend: admin billing management page (/dashboard/admin/billing) - Frontend: user wallet page with balance, quick-charge, transaction history - Deploy page: cost breakdown shown in Review step (hourly/monthly/yearly) - Navigation: Wallet link for users, Billing Plans link for admins
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Patch,
|
||||
Delete,
|
||||
Body,
|
||||
Param,
|
||||
Query,
|
||||
UseGuards,
|
||||
Request,
|
||||
} from '@nestjs/common';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { BillingService } from './billing.service';
|
||||
import {
|
||||
CreateServicePlanDto,
|
||||
UpdateServicePlanDto,
|
||||
ChargeWalletDto,
|
||||
CalculateCostDto,
|
||||
} from './dto/billing.dto';
|
||||
import { RolesGuard } from '../common/guards/roles.guard';
|
||||
import { Roles } from '../common/decorators/roles.decorator';
|
||||
import { UserRole } from '../common/enums';
|
||||
|
||||
@ApiTags('Billing')
|
||||
@ApiBearerAuth()
|
||||
@Controller('billing')
|
||||
@UseGuards(AuthGuard('jwt'), RolesGuard)
|
||||
export class BillingController {
|
||||
constructor(private readonly billingService: BillingService) {}
|
||||
|
||||
// ─── Service Plans (Admin) ────────────────────────────────────────
|
||||
|
||||
@Post('plans')
|
||||
@Roles(UserRole.ADMIN)
|
||||
@ApiOperation({ summary: 'Create a new service plan (Admin)' })
|
||||
async createPlan(@Body() dto: CreateServicePlanDto) {
|
||||
return this.billingService.createPlan(dto);
|
||||
}
|
||||
|
||||
@Get('plans')
|
||||
@ApiOperation({ summary: 'List all service plans' })
|
||||
async findAllPlans(@Request() req: any) {
|
||||
if (req.user.role === UserRole.ADMIN) {
|
||||
return this.billingService.findAllPlans();
|
||||
}
|
||||
return this.billingService.findActivePlans();
|
||||
}
|
||||
|
||||
@Get('plans/:id')
|
||||
@ApiOperation({ summary: 'Get service plan details' })
|
||||
async findPlan(@Param('id') id: string) {
|
||||
return this.billingService.findPlan(id);
|
||||
}
|
||||
|
||||
@Patch('plans/:id')
|
||||
@Roles(UserRole.ADMIN)
|
||||
@ApiOperation({ summary: 'Update a service plan (Admin)' })
|
||||
async updatePlan(@Param('id') id: string, @Body() dto: UpdateServicePlanDto) {
|
||||
return this.billingService.updatePlan(id, dto);
|
||||
}
|
||||
|
||||
@Delete('plans/:id')
|
||||
@Roles(UserRole.ADMIN)
|
||||
@ApiOperation({ summary: 'Delete a service plan (Admin)' })
|
||||
async deletePlan(@Param('id') id: string) {
|
||||
await this.billingService.deletePlan(id);
|
||||
return { message: 'Plan deleted' };
|
||||
}
|
||||
|
||||
// ─── Cost Calculation ─────────────────────────────────────────────
|
||||
|
||||
@Post('calculate')
|
||||
@ApiOperation({ summary: 'Calculate cost for an application configuration' })
|
||||
async calculateCost(@Body() dto: CalculateCostDto) {
|
||||
return this.billingService.calculateCost(dto);
|
||||
}
|
||||
|
||||
// ─── Wallet (User) ───────────────────────────────────────────────
|
||||
|
||||
@Get('wallet')
|
||||
@ApiOperation({ summary: 'Get my wallet balance' })
|
||||
async getBalance(@Request() req: any) {
|
||||
return this.billingService.getBalance(req.user.id);
|
||||
}
|
||||
|
||||
@Post('wallet/charge')
|
||||
@ApiOperation({ summary: 'Charge my wallet (self top-up)' })
|
||||
async chargeMyWallet(@Request() req: any, @Body() dto: ChargeWalletDto) {
|
||||
return this.billingService.chargeWallet(req.user.id, dto.amount, dto.description || 'Self top-up');
|
||||
}
|
||||
|
||||
@Get('wallet/transactions')
|
||||
@ApiOperation({ summary: 'Get my wallet transactions' })
|
||||
async getTransactions(@Request() req: any, @Query('limit') limit?: string) {
|
||||
return this.billingService.getTransactions(req.user.id, limit ? parseInt(limit, 10) : 50);
|
||||
}
|
||||
|
||||
@Post('wallet/pay/:applicationId')
|
||||
@ApiOperation({ summary: 'Pay for an application from wallet' })
|
||||
async payForApplication(
|
||||
@Request() req: any,
|
||||
@Param('applicationId') applicationId: string,
|
||||
@Body() body: { amount: number; cycle: string },
|
||||
) {
|
||||
return this.billingService.deductWallet(
|
||||
req.user.id,
|
||||
body.amount,
|
||||
`Payment for application (${body.cycle})`,
|
||||
applicationId,
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Wallet Admin ─────────────────────────────────────────────────
|
||||
|
||||
@Get('admin/wallets')
|
||||
@Roles(UserRole.ADMIN)
|
||||
@ApiOperation({ summary: 'List all wallets (Admin)' })
|
||||
async getAllWallets() {
|
||||
return this.billingService.getAllWallets();
|
||||
}
|
||||
|
||||
@Post('admin/wallets/:userId/charge')
|
||||
@Roles(UserRole.ADMIN)
|
||||
@ApiOperation({ summary: 'Charge a user\'s wallet (Admin)' })
|
||||
async adminChargeWallet(
|
||||
@Param('userId') userId: string,
|
||||
@Body() dto: ChargeWalletDto,
|
||||
) {
|
||||
return this.billingService.adminChargeWallet(userId, dto.amount, dto.description);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user