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:
@@ -115,6 +115,20 @@ export class PaymentEntity extends AggregateRoot<string> {
|
||||
);
|
||||
}
|
||||
|
||||
/** Emit completed event without modifying state (used when DB was already updated atomically). */
|
||||
emitCompleted(): void {
|
||||
this.addDomainEvent(
|
||||
new PaymentCompletedEvent(this.id, this._userId, this._provider, this._amount.value),
|
||||
);
|
||||
}
|
||||
|
||||
/** Emit failed event without modifying state (used when DB was already updated atomically). */
|
||||
emitFailed(): void {
|
||||
this.addDomainEvent(
|
||||
new PaymentFailedEvent(this.id, this._userId, this._provider),
|
||||
);
|
||||
}
|
||||
|
||||
markRefunded(): void {
|
||||
if (this._status !== 'COMPLETED') {
|
||||
throw new Error('Chỉ có thể hoàn tiền cho thanh toán đã hoàn tất');
|
||||
|
||||
@@ -14,4 +14,6 @@ export interface IPaymentRepository {
|
||||
}): Promise<{ items: PaymentEntity[]; total: number }>;
|
||||
save(payment: PaymentEntity): Promise<void>;
|
||||
update(payment: PaymentEntity): Promise<void>;
|
||||
/** Atomically update payment status only if it is currently in one of the expected statuses. Returns null if no matching row. */
|
||||
updateIfStatus(id: string, expectedStatuses: PaymentStatus[], data: { status: PaymentStatus; providerTxId?: string; callbackData?: unknown }): Promise<PaymentEntity | null>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user