fix(payments): harden payment flow with idempotency keys, amount validation, and magic byte file validation

- Add dedicated idempotencyKey column with unique constraint (userId, provider, idempotencyKey) to prevent duplicate payments at DB level
- Add @Min(1) @Max(100B) validators on amountVND in CreatePaymentDto to reject invalid amounts at API boundary
- Replace read-check-write callback handler with atomic updateIfStatus to eliminate race condition on concurrent callbacks
- Add magic byte verification in FileValidationPipe to validate file content matches declared MIME type server-side

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Ho Ngoc Hai
2026-04-08 06:18:26 +07:00
parent be0deddeed
commit 9583d1cb66
9 changed files with 148 additions and 43 deletions

View File

@@ -0,0 +1,5 @@
-- AlterTable
ALTER TABLE "Payment" ADD COLUMN "idempotencyKey" TEXT;
-- CreateIndex
CREATE UNIQUE INDEX "Payment_idempotency_unique" ON "Payment"("userId", "provider", "idempotencyKey");

View File

@@ -368,11 +368,13 @@ model Payment {
type PaymentType
amountVND BigInt
status PaymentStatus @default(PENDING)
providerTxId String?
callbackData Json?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
providerTxId String?
callbackData Json?
idempotencyKey String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([userId, provider, idempotencyKey], name: "Payment_idempotency_unique")
@@index([userId])
@@index([transactionId])
@@index([status])