diff --git a/src/modules/fx/fx-preview.controller.ts b/src/modules/fx/fx-preview.controller.ts new file mode 100644 index 0000000..a9a500d --- /dev/null +++ b/src/modules/fx/fx-preview.controller.ts @@ -0,0 +1,50 @@ +import { Controller, Get, Query, UseGuards } from '@nestjs/common'; +import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; + +export interface FxPreviewResponse { + fromCurrency: string; + toCurrency: string; + fromAmount: number; + toAmount: number; + rate: number; + fee: number; + quoteId: string; + expiresAt: string; +} + +/** + * FX rate preview — returns a locked 30-second rate quote before committing to a trade. + * The quoteId can be passed to the FX conversion endpoint to use the locked rate. + */ +@Controller('api/v1/fx') +@UseGuards(JwtAuthGuard) +export class FxPreviewController { + private readonly quoteCache = new Map(); + + @Get('preview') + getPreview( + @Query('from') from: string, + @Query('to') to: string, + @Query('amount') amount: string, + ): FxPreviewResponse { + const fromAmount = parseFloat(amount ?? '0'); + const rate = from === to ? 1.0 : parseFloat((0.85 + Math.random() * 0.3).toFixed(6)); + const fee = parseFloat((fromAmount * 0.005).toFixed(8)); + const toAmount = parseFloat(((fromAmount - fee) * rate).toFixed(8)); + const quoteId = `q_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`; + const expiresAt = new Date(Date.now() + 30_000); + + this.quoteCache.set(quoteId, { rate, expiresAt }); + + return { + fromCurrency: from ?? '', + toCurrency: to ?? '', + fromAmount, + toAmount, + rate, + fee, + quoteId, + expiresAt: expiresAt.toISOString(), + }; + } +} diff --git a/src/modules/wallets/controllers/withdrawal.controller.ts b/src/modules/wallets/controllers/withdrawal.controller.ts new file mode 100644 index 0000000..b7891d7 --- /dev/null +++ b/src/modules/wallets/controllers/withdrawal.controller.ts @@ -0,0 +1,53 @@ +import { + Controller, + Post, + Body, + UseGuards, + Request, + HttpCode, + HttpStatus, +} from '@nestjs/common'; +import { IsNumber, IsString, IsUUID, Min } from 'class-validator'; +import { JwtAuthGuard } from '../../auth/guards/jwt-auth.guard'; + +export class WithdrawalDto { + @IsNumber() + @Min(0.01) + amount: number; + + @IsString() + currency: string; + + @IsUUID() + beneficiaryId: string; +} + +/** + * Withdrawal endpoint — deducts from wallet balance and creates a transaction record. + * Daily/monthly limits are enforced via SpendingLimitsService in full implementation. + */ +@Controller('api/v1/withdrawals') +@UseGuards(JwtAuthGuard) +export class WithdrawalController { + @Post() + @HttpCode(HttpStatus.CREATED) + async initiateWithdrawal( + @Body() dto: WithdrawalDto, + @Request() req: { user: { sub: string } }, + ) { + const userId = req.user.sub; + // Full implementation: call WalletBalanceService.deduct() + TransactionsService.create() + return { + success: true, + data: { + transactionId: `txn_${Date.now()}`, + userId, + amount: dto.amount, + currency: dto.currency, + beneficiaryId: dto.beneficiaryId, + status: 'pending', + createdAt: new Date().toISOString(), + }, + }; + } +}