Harden platform security, reliability, and CI after full audit.
Close deployment IDOR and gate stub payment endpoints, add production secret validation, health probes, Redis-backed build progress, GitHub Actions CI, expanded tests, billing/k8s refactors, and ops runbooks. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Patch,
|
||||
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 { BillingOpsService } from './billing-ops.service';
|
||||
import { assertStubGatewayAllowed } from './payment-gateway.util';
|
||||
import {
|
||||
ChargeWalletDto,
|
||||
InitiateInvoicePaymentDto,
|
||||
VerifyInvoiceGatewayDto,
|
||||
UpdateInvoiceStatusDto,
|
||||
} from './dto/billing.dto';
|
||||
import { RolesGuard } from '../common/guards/roles.guard';
|
||||
import { Roles } from '../common/decorators/roles.decorator';
|
||||
import { UserRole, InvoiceStatus, PaymentMethod } from '../common/enums';
|
||||
|
||||
@ApiTags('Billing')
|
||||
@ApiBearerAuth()
|
||||
@Controller('billing')
|
||||
@UseGuards(AuthGuard('jwt'), RolesGuard)
|
||||
export class BillingInvoicesController {
|
||||
constructor(
|
||||
private readonly billingService: BillingService,
|
||||
private readonly billingOpsService: BillingOpsService,
|
||||
) {}
|
||||
|
||||
// ─── Invoices ─────────────────────────────────────────────────────
|
||||
|
||||
@Get('invoices')
|
||||
@ApiOperation({ summary: 'List my invoices' })
|
||||
async listMyInvoices(
|
||||
@Request() req: any,
|
||||
@Query('status') status?: InvoiceStatus,
|
||||
@Query('applicationId') applicationId?: string,
|
||||
@Query('limit') limit?: string,
|
||||
) {
|
||||
return this.billingService.listInvoices(req.user, {
|
||||
status,
|
||||
applicationId,
|
||||
limit: limit ? parseInt(limit, 10) : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
@Get('invoices/:id')
|
||||
@ApiOperation({ summary: 'Get one invoice with line items and transactions' })
|
||||
async getInvoice(@Request() req: any, @Param('id') id: string) {
|
||||
return this.billingService.getInvoiceForUser(id, req.user);
|
||||
}
|
||||
|
||||
@Post('invoices/:id/pay/mixed')
|
||||
@ApiOperation({ summary: 'Pay invoice with wallet first, then gateway for the remaining amount' })
|
||||
async initiateInvoiceMixed(
|
||||
@Request() req: any,
|
||||
@Param('id') id: string,
|
||||
@Body() dto: InitiateInvoicePaymentDto,
|
||||
) {
|
||||
const result = await this.billingService.initiateInvoiceMixedPayment(id, req.user, dto.callbackUrl);
|
||||
const effect = await this.billingOpsService.completePaidInvoiceEffect(result.invoice);
|
||||
return { ...result, effect };
|
||||
}
|
||||
|
||||
@Post('invoices/:id/gateway/verify')
|
||||
@ApiOperation({ summary: 'Verify invoice gateway payment' })
|
||||
async verifyInvoiceGateway(
|
||||
@Request() req: any,
|
||||
@Param('id') id: string,
|
||||
@Body() dto: VerifyInvoiceGatewayDto,
|
||||
) {
|
||||
assertStubGatewayAllowed();
|
||||
const result = await this.billingService.verifyInvoiceGatewayPayment(
|
||||
id,
|
||||
req.user,
|
||||
dto.trackingCode,
|
||||
dto.amount,
|
||||
);
|
||||
const effect = await this.billingOpsService.completePaidInvoiceEffect(result.invoice);
|
||||
return { ...result, effect };
|
||||
}
|
||||
|
||||
// ─── Invoice Admin ────────────────────────────────────────────────
|
||||
|
||||
@Get('admin/invoices')
|
||||
@Roles(UserRole.ADMIN)
|
||||
@ApiOperation({ summary: 'List all invoices (Admin)' })
|
||||
async listAdminInvoices(
|
||||
@Request() req: any,
|
||||
@Query('status') status?: InvoiceStatus,
|
||||
@Query('userId') userId?: string,
|
||||
@Query('applicationId') applicationId?: string,
|
||||
@Query('paymentMethod') paymentMethod?: PaymentMethod,
|
||||
@Query('search') search?: string,
|
||||
@Query('limit') limit?: string,
|
||||
) {
|
||||
return this.billingService.listInvoices(req.user, {
|
||||
status,
|
||||
userId,
|
||||
applicationId,
|
||||
paymentMethod,
|
||||
search,
|
||||
limit: limit ? parseInt(limit, 10) : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
@Get('admin/invoices/:id')
|
||||
@Roles(UserRole.ADMIN)
|
||||
@ApiOperation({ summary: 'Get invoice details (Admin)' })
|
||||
async getAdminInvoice(@Request() req: any, @Param('id') id: string) {
|
||||
return this.billingService.getInvoiceForUser(id, req.user);
|
||||
}
|
||||
|
||||
@Patch('admin/invoices/:id/status')
|
||||
@Roles(UserRole.ADMIN)
|
||||
@ApiOperation({ summary: 'Update invoice status with reason (Admin)' })
|
||||
async updateAdminInvoiceStatus(
|
||||
@Param('id') id: string,
|
||||
@Body() dto: UpdateInvoiceStatusDto,
|
||||
) {
|
||||
return this.billingService.updateInvoiceStatus(id, dto.status, dto.reason);
|
||||
}
|
||||
|
||||
// ─── 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