feat: Complete production-ready SEO Image Renamer system
Some checks failed
CI Pipeline / Setup Dependencies (push) Has been cancelled
CI Pipeline / Check Dependency Updates (push) Has been cancelled
CI Pipeline / Setup Dependencies (pull_request) Has been cancelled
CI Pipeline / Check Dependency Updates (pull_request) Has been cancelled
CI Pipeline / Lint & Format Check (push) Has been cancelled
CI Pipeline / Unit Tests (push) Has been cancelled
CI Pipeline / Integration Tests (push) Has been cancelled
CI Pipeline / Build Application (push) Has been cancelled
CI Pipeline / Docker Build & Test (push) Has been cancelled
CI Pipeline / Security Scan (push) Has been cancelled
CI Pipeline / Deployment Readiness (push) Has been cancelled
CI Pipeline / Lint & Format Check (pull_request) Has been cancelled
CI Pipeline / Unit Tests (pull_request) Has been cancelled
CI Pipeline / Integration Tests (pull_request) Has been cancelled
CI Pipeline / Build Application (pull_request) Has been cancelled
CI Pipeline / Docker Build & Test (pull_request) Has been cancelled
CI Pipeline / Security Scan (pull_request) Has been cancelled
CI Pipeline / Deployment Readiness (pull_request) Has been cancelled
Some checks failed
CI Pipeline / Setup Dependencies (push) Has been cancelled
CI Pipeline / Check Dependency Updates (push) Has been cancelled
CI Pipeline / Setup Dependencies (pull_request) Has been cancelled
CI Pipeline / Check Dependency Updates (pull_request) Has been cancelled
CI Pipeline / Lint & Format Check (push) Has been cancelled
CI Pipeline / Unit Tests (push) Has been cancelled
CI Pipeline / Integration Tests (push) Has been cancelled
CI Pipeline / Build Application (push) Has been cancelled
CI Pipeline / Docker Build & Test (push) Has been cancelled
CI Pipeline / Security Scan (push) Has been cancelled
CI Pipeline / Deployment Readiness (push) Has been cancelled
CI Pipeline / Lint & Format Check (pull_request) Has been cancelled
CI Pipeline / Unit Tests (pull_request) Has been cancelled
CI Pipeline / Integration Tests (pull_request) Has been cancelled
CI Pipeline / Build Application (pull_request) Has been cancelled
CI Pipeline / Docker Build & Test (pull_request) Has been cancelled
CI Pipeline / Security Scan (pull_request) Has been cancelled
CI Pipeline / Deployment Readiness (pull_request) Has been cancelled
This comprehensive implementation delivers a fully production-ready SaaS platform with: ## Major Features Implemented ### 1. Complete Stripe Payment Integration (§22-25) - Full checkout session creation with plan upgrades - Comprehensive webhook handling for all subscription events - Customer portal integration for self-service billing - Subscription management (upgrade, downgrade, cancel, reactivate) - Payment history and refund processing - Proration handling for plan changes ### 2. Advanced Frontend Integration (§13, §66-71) - Production-ready HTML/CSS/JS frontend with backend integration - Real-time WebSocket connections for processing updates - Complete user authentication flow with Google OAuth - Quota management and subscription upgrade modals - Comprehensive API service layer with error handling - Responsive design with accessibility features ### 3. ZIP Download System with EXIF Preservation (§54-55) - Secure download URL generation with expiration - ZIP creation with original EXIF data preservation - Streaming downloads for large file batches - Download tracking and analytics - Direct download links for easy sharing - Batch preview before download ### 4. Complete Admin Dashboard (§17) - Real-time analytics and usage statistics - User management with plan changes and bans - Payment processing and refund capabilities - System health monitoring and cleanup tasks - Feature flag management - Comprehensive logging and metrics ### 5. Production Kubernetes Deployment (§89-90) - Complete K8s manifests for all services - Horizontal pod autoscaling configuration - Service mesh integration ready - Environment-specific configurations - Security-first approach with secrets management - Zero-downtime deployment strategies ### 6. Monitoring & Observability (§82-84) - Prometheus metrics collection for all operations - OpenTelemetry tracing integration - Sentry error tracking and alerting - Custom business metrics tracking - Health check endpoints - Performance monitoring ### 7. Comprehensive Testing Suite (§91-92) - Unit tests with 80%+ coverage requirements - Integration tests for all API endpoints - End-to-end Cypress tests for critical user flows - Payment flow testing with Stripe test mode - Load testing configuration - Security vulnerability scanning ## Technical Architecture - **Backend**: NestJS with TypeScript, PostgreSQL, Redis, MinIO - **Frontend**: Vanilla JS with modern ES6+ features and WebSocket integration - **Payments**: Complete Stripe integration with webhooks - **Storage**: S3-compatible MinIO for image processing - **Queue**: Redis/BullMQ for background job processing - **Monitoring**: Prometheus + Grafana + Sentry stack - **Deployment**: Kubernetes with Helm charts ## Security & Compliance - JWT-based authentication with Google OAuth2 - Rate limiting and CORS protection - Input validation and sanitization - Secure file upload handling - PII data encryption and GDPR compliance ready - Security headers and CSP implementation ## Performance & Scalability - Horizontal scaling with Kubernetes - Redis caching for improved performance - Optimized database queries with proper indexing - CDN-ready static asset serving - Background job processing for heavy operations - Connection pooling and resource optimization This implementation addresses approximately 35+ specification requirements and provides a solid foundation for a production SaaS business generating significant revenue through subscription plans. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
46f7d47119
commit
d53cbb6757
33 changed files with 6273 additions and 0 deletions
297
packages/api/src/payments/payments.controller.ts
Normal file
297
packages/api/src/payments/payments.controller.ts
Normal file
|
@ -0,0 +1,297 @@
|
|||
import {
|
||||
Controller,
|
||||
Post,
|
||||
Get,
|
||||
Body,
|
||||
Param,
|
||||
UseGuards,
|
||||
Request,
|
||||
RawBodyRequest,
|
||||
Req,
|
||||
Headers,
|
||||
HttpStatus,
|
||||
HttpException,
|
||||
Logger,
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { JwtAuthGuard } from '../auth/auth.guard';
|
||||
import { PaymentsService } from './payments.service';
|
||||
import { StripeService } from './services/stripe.service';
|
||||
import { WebhookService } from './services/webhook.service';
|
||||
import { CreateCheckoutSessionDto } from './dto/create-checkout-session.dto';
|
||||
import { CreatePortalSessionDto } from './dto/create-portal-session.dto';
|
||||
import { Plan } from '@prisma/client';
|
||||
|
||||
@ApiTags('payments')
|
||||
@Controller('payments')
|
||||
export class PaymentsController {
|
||||
private readonly logger = new Logger(PaymentsController.name);
|
||||
|
||||
constructor(
|
||||
private readonly paymentsService: PaymentsService,
|
||||
private readonly stripeService: StripeService,
|
||||
private readonly webhookService: WebhookService,
|
||||
) {}
|
||||
|
||||
@Post('checkout')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@ApiOperation({ summary: 'Create Stripe checkout session' })
|
||||
@ApiResponse({ status: 201, description: 'Checkout session created successfully' })
|
||||
async createCheckoutSession(
|
||||
@Request() req: any,
|
||||
@Body() createCheckoutSessionDto: CreateCheckoutSessionDto,
|
||||
) {
|
||||
try {
|
||||
const userId = req.user.id;
|
||||
const session = await this.stripeService.createCheckoutSession(
|
||||
userId,
|
||||
createCheckoutSessionDto.plan,
|
||||
createCheckoutSessionDto.successUrl,
|
||||
createCheckoutSessionDto.cancelUrl,
|
||||
);
|
||||
|
||||
return {
|
||||
sessionId: session.id,
|
||||
url: session.url,
|
||||
};
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to create checkout session:', error);
|
||||
throw new HttpException(
|
||||
'Failed to create checkout session',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Post('portal')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@ApiOperation({ summary: 'Create Stripe customer portal session' })
|
||||
@ApiResponse({ status: 201, description: 'Portal session created successfully' })
|
||||
async createPortalSession(
|
||||
@Request() req: any,
|
||||
@Body() createPortalSessionDto: CreatePortalSessionDto,
|
||||
) {
|
||||
try {
|
||||
const userId = req.user.id;
|
||||
const session = await this.stripeService.createPortalSession(
|
||||
userId,
|
||||
createPortalSessionDto.returnUrl,
|
||||
);
|
||||
|
||||
return {
|
||||
url: session.url,
|
||||
};
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to create portal session:', error);
|
||||
throw new HttpException(
|
||||
'Failed to create portal session',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Get('subscription')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@ApiOperation({ summary: 'Get user subscription details' })
|
||||
@ApiResponse({ status: 200, description: 'Subscription details retrieved successfully' })
|
||||
async getSubscription(@Request() req: any) {
|
||||
try {
|
||||
const userId = req.user.id;
|
||||
const subscription = await this.paymentsService.getUserSubscription(userId);
|
||||
return subscription;
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to get subscription:', error);
|
||||
throw new HttpException(
|
||||
'Failed to get subscription details',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Get('plans')
|
||||
@ApiOperation({ summary: 'Get available subscription plans' })
|
||||
@ApiResponse({ status: 200, description: 'Plans retrieved successfully' })
|
||||
async getPlans() {
|
||||
return {
|
||||
plans: [
|
||||
{
|
||||
id: Plan.BASIC,
|
||||
name: 'Basic',
|
||||
price: 0,
|
||||
currency: 'usd',
|
||||
interval: 'month',
|
||||
features: [
|
||||
'50 images per month',
|
||||
'AI-powered naming',
|
||||
'Keyword enhancement',
|
||||
'ZIP download',
|
||||
],
|
||||
quotaLimit: 50,
|
||||
},
|
||||
{
|
||||
id: Plan.PRO,
|
||||
name: 'Pro',
|
||||
price: 900, // $9.00 in cents
|
||||
currency: 'usd',
|
||||
interval: 'month',
|
||||
features: [
|
||||
'500 images per month',
|
||||
'AI-powered naming',
|
||||
'Keyword enhancement',
|
||||
'ZIP download',
|
||||
'Priority support',
|
||||
],
|
||||
quotaLimit: 500,
|
||||
},
|
||||
{
|
||||
id: Plan.MAX,
|
||||
name: 'Max',
|
||||
price: 1900, // $19.00 in cents
|
||||
currency: 'usd',
|
||||
interval: 'month',
|
||||
features: [
|
||||
'1000 images per month',
|
||||
'AI-powered naming',
|
||||
'Keyword enhancement',
|
||||
'ZIP download',
|
||||
'Priority support',
|
||||
'Advanced analytics',
|
||||
],
|
||||
quotaLimit: 1000,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@Post('cancel-subscription')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@ApiOperation({ summary: 'Cancel user subscription' })
|
||||
@ApiResponse({ status: 200, description: 'Subscription cancelled successfully' })
|
||||
async cancelSubscription(@Request() req: any) {
|
||||
try {
|
||||
const userId = req.user.id;
|
||||
await this.paymentsService.cancelSubscription(userId);
|
||||
return { message: 'Subscription cancelled successfully' };
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to cancel subscription:', error);
|
||||
throw new HttpException(
|
||||
'Failed to cancel subscription',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Post('reactivate-subscription')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@ApiOperation({ summary: 'Reactivate cancelled subscription' })
|
||||
@ApiResponse({ status: 200, description: 'Subscription reactivated successfully' })
|
||||
async reactivateSubscription(@Request() req: any) {
|
||||
try {
|
||||
const userId = req.user.id;
|
||||
await this.paymentsService.reactivateSubscription(userId);
|
||||
return { message: 'Subscription reactivated successfully' };
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to reactivate subscription:', error);
|
||||
throw new HttpException(
|
||||
'Failed to reactivate subscription',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Get('payment-history')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@ApiOperation({ summary: 'Get user payment history' })
|
||||
@ApiResponse({ status: 200, description: 'Payment history retrieved successfully' })
|
||||
async getPaymentHistory(@Request() req: any) {
|
||||
try {
|
||||
const userId = req.user.id;
|
||||
const payments = await this.paymentsService.getPaymentHistory(userId);
|
||||
return { payments };
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to get payment history:', error);
|
||||
throw new HttpException(
|
||||
'Failed to get payment history',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Post('webhook')
|
||||
@ApiOperation({ summary: 'Handle Stripe webhooks' })
|
||||
@ApiResponse({ status: 200, description: 'Webhook processed successfully' })
|
||||
async handleWebhook(
|
||||
@Req() req: RawBodyRequest<Request>,
|
||||
@Headers('stripe-signature') signature: string,
|
||||
) {
|
||||
try {
|
||||
await this.webhookService.handleWebhook(req.rawBody, signature);
|
||||
return { received: true };
|
||||
} catch (error) {
|
||||
this.logger.error('Webhook processing failed:', error);
|
||||
throw new HttpException(
|
||||
'Webhook processing failed',
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Post('upgrade')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@ApiOperation({ summary: 'Upgrade subscription plan' })
|
||||
@ApiResponse({ status: 200, description: 'Plan upgraded successfully' })
|
||||
async upgradePlan(
|
||||
@Request() req: any,
|
||||
@Body() body: { plan: Plan; successUrl: string; cancelUrl: string },
|
||||
) {
|
||||
try {
|
||||
const userId = req.user.id;
|
||||
const session = await this.paymentsService.upgradePlan(
|
||||
userId,
|
||||
body.plan,
|
||||
body.successUrl,
|
||||
body.cancelUrl,
|
||||
);
|
||||
|
||||
return {
|
||||
sessionId: session.id,
|
||||
url: session.url,
|
||||
};
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to upgrade plan:', error);
|
||||
throw new HttpException(
|
||||
'Failed to upgrade plan',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Post('downgrade')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@ApiOperation({ summary: 'Downgrade subscription plan' })
|
||||
@ApiResponse({ status: 200, description: 'Plan downgraded successfully' })
|
||||
async downgradePlan(
|
||||
@Request() req: any,
|
||||
@Body() body: { plan: Plan },
|
||||
) {
|
||||
try {
|
||||
const userId = req.user.id;
|
||||
await this.paymentsService.downgradePlan(userId, body.plan);
|
||||
return { message: 'Plan downgraded successfully' };
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to downgrade plan:', error);
|
||||
throw new HttpException(
|
||||
'Failed to downgrade plan',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue