feat(payments): implement BankTransferService payment gateway with admin confirmation
Add BANK_TRANSFER as a fully supported payment provider: - BankTransferService implementing IPaymentGateway with HMAC-SHA256 verification - ConfirmBankTransferCommand/Handler for admin manual payment confirmation - POST /payments/:id/confirm-transfer admin endpoint (RBAC-protected) - Atomic status updates with idempotency (PENDING/PROCESSING → COMPLETED) - Registered in PaymentGatewayFactory alongside VNPAY, MOMO, ZALOPAY - Comprehensive unit tests for service and handler Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -20,6 +20,8 @@ import { Throttle } from '@nestjs/throttler';
|
||||
import { PaymentProvider } from '@prisma/client';
|
||||
import { JwtPayload, CurrentUser, Roles, JwtAuthGuard, RolesGuard } from '@modules/auth';
|
||||
import { EndpointRateLimit, EndpointRateLimitGuard } from '@modules/shared';
|
||||
import { ConfirmBankTransferCommand } from '../../application/commands/confirm-bank-transfer/confirm-bank-transfer.command';
|
||||
import { ConfirmBankTransferResult } from '../../application/commands/confirm-bank-transfer/confirm-bank-transfer.handler';
|
||||
import { CreatePaymentCommand } from '../../application/commands/create-payment/create-payment.command';
|
||||
import { CreatePaymentResult } from '../../application/commands/create-payment/create-payment.handler';
|
||||
import { HandleCallbackCommand } from '../../application/commands/handle-callback/handle-callback.command';
|
||||
@@ -30,6 +32,7 @@ import { PaymentStatusDto } from '../../application/queries/get-payment-status/g
|
||||
import { GetPaymentStatusQuery } from '../../application/queries/get-payment-status/get-payment-status.query';
|
||||
import { TransactionListDto } from '../../application/queries/list-transactions/list-transactions.handler';
|
||||
import { ListTransactionsQuery } from '../../application/queries/list-transactions/list-transactions.query';
|
||||
import { ConfirmBankTransferDto } from '../dto/confirm-bank-transfer.dto';
|
||||
import { CreatePaymentDto } from '../dto/create-payment.dto';
|
||||
import { ListTransactionsDto } from '../dto/list-transactions.dto';
|
||||
import { RefundPaymentDto } from '../dto/refund-payment.dto';
|
||||
@@ -71,7 +74,7 @@ export class PaymentsController {
|
||||
|
||||
@ApiOperation({ summary: 'Handle payment provider callback (webhook)' })
|
||||
@ApiResponse({ status: 201, description: 'Callback processed successfully' })
|
||||
@ApiParam({ name: 'provider', enum: ['vnpay', 'momo', 'zalopay'] })
|
||||
@ApiParam({ name: 'provider', enum: ['vnpay', 'momo', 'zalopay', 'bank_transfer'] })
|
||||
@Throttle({ 'payment-callback': { ttl: 60_000, limit: 20 } })
|
||||
@EndpointRateLimit({ limit: 100, windowSeconds: 60, keyStrategy: 'ip', adminBypass: false })
|
||||
@UseGuards(EndpointRateLimitGuard)
|
||||
@@ -136,4 +139,24 @@ export class PaymentsController {
|
||||
new RefundPaymentCommand(id, dto.reason, user.sub),
|
||||
);
|
||||
}
|
||||
|
||||
@ApiBearerAuth('JWT')
|
||||
@ApiOperation({ summary: 'Confirm a bank transfer payment (admin only)' })
|
||||
@ApiResponse({ status: 201, description: 'Bank transfer confirmed successfully' })
|
||||
@ApiResponse({ status: 400, description: 'Payment is not a bank transfer or invalid status' })
|
||||
@ApiResponse({ status: 401, description: 'Unauthorized' })
|
||||
@ApiResponse({ status: 403, description: 'Forbidden — admin role required' })
|
||||
@ApiResponse({ status: 404, description: 'Payment not found' })
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
@Roles('ADMIN')
|
||||
@Post(':id/confirm-transfer')
|
||||
async confirmBankTransfer(
|
||||
@Param('id') id: string,
|
||||
@Body() dto: ConfirmBankTransferDto,
|
||||
@CurrentUser() user: JwtPayload,
|
||||
): Promise<ConfirmBankTransferResult> {
|
||||
return this.commandBus.execute(
|
||||
new ConfirmBankTransferCommand(id, user.sub, dto.bankReference),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user