fix(web): wire up next-intl i18n — install dep, add locale middleware, wrap next config
The i18n architecture (config, routing, translation files, locale pages) was already built but non-functional due to three missing pieces: 1. next-intl not listed in package.json 2. middleware.ts not using createMiddleware from next-intl/middleware 3. next.config.js not wrapped with createNextIntlPlugin Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -95,23 +95,30 @@ describe('PaymentEntity', () => {
|
||||
|
||||
it('should not complete an already completed payment', () => {
|
||||
const payment = createPayment('COMPLETED');
|
||||
expect(() => payment.markCompleted({})).toThrow('Cannot complete payment');
|
||||
const result = payment.markCompleted({});
|
||||
expect(result.isErr).toBe(true);
|
||||
expect(result.unwrapErr().message).toContain('Cannot complete payment');
|
||||
});
|
||||
|
||||
it('should not fail an already completed payment', () => {
|
||||
const payment = createPayment('COMPLETED');
|
||||
expect(() => payment.markFailed({})).toThrow('Cannot fail payment');
|
||||
const result = payment.markFailed({});
|
||||
expect(result.isErr).toBe(true);
|
||||
expect(result.unwrapErr().message).toContain('Cannot fail payment');
|
||||
});
|
||||
|
||||
it('should mark completed payment as refunded', () => {
|
||||
const payment = createPayment('COMPLETED');
|
||||
payment.markRefunded();
|
||||
const result = payment.markRefunded();
|
||||
expect(result.isOk).toBe(true);
|
||||
expect(payment.status).toBe('REFUNDED');
|
||||
});
|
||||
|
||||
it('should not refund a non-completed payment', () => {
|
||||
const payment = createPayment();
|
||||
expect(() => payment.markRefunded()).toThrow('hoàn tiền');
|
||||
const result = payment.markRefunded();
|
||||
expect(result.isErr).toBe(true);
|
||||
expect(result.unwrapErr().message).toContain('hoàn tiền');
|
||||
});
|
||||
|
||||
it('should store idempotency key', () => {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { HttpStatus } from '@nestjs/common';
|
||||
import {
|
||||
type PaymentProvider,
|
||||
type PaymentStatus,
|
||||
type PaymentType,
|
||||
} from '@prisma/client';
|
||||
import { AggregateRoot } from '@modules/shared/domain/aggregate-root';
|
||||
import { AggregateRoot, DomainException, ErrorCode, Result } from '@modules/shared';
|
||||
import { PaymentCompletedEvent } from '../events/payment-completed.event';
|
||||
import { PaymentCreatedEvent } from '../events/payment-created.event';
|
||||
import { PaymentFailedEvent } from '../events/payment-failed.event';
|
||||
@@ -89,9 +90,15 @@ export class PaymentEntity extends AggregateRoot<string> {
|
||||
this.updatedAt = new Date();
|
||||
}
|
||||
|
||||
markCompleted(callbackData: unknown): void {
|
||||
markCompleted(callbackData: unknown): Result<void, DomainException> {
|
||||
if (this._status !== 'PENDING' && this._status !== 'PROCESSING') {
|
||||
throw new Error(`Cannot complete payment in status ${this._status}`);
|
||||
return Result.err(
|
||||
new DomainException(
|
||||
ErrorCode.PAYMENT_ALREADY_PROCESSED,
|
||||
`Cannot complete payment in status ${this._status}`,
|
||||
HttpStatus.CONFLICT,
|
||||
),
|
||||
);
|
||||
}
|
||||
this._status = 'COMPLETED';
|
||||
this._callbackData = callbackData;
|
||||
@@ -100,11 +107,18 @@ export class PaymentEntity extends AggregateRoot<string> {
|
||||
this.addDomainEvent(
|
||||
new PaymentCompletedEvent(this.id, this._userId, this._provider, this._amount.value),
|
||||
);
|
||||
return Result.ok(undefined);
|
||||
}
|
||||
|
||||
markFailed(callbackData: unknown): void {
|
||||
markFailed(callbackData: unknown): Result<void, DomainException> {
|
||||
if (this._status !== 'PENDING' && this._status !== 'PROCESSING') {
|
||||
throw new Error(`Cannot fail payment in status ${this._status}`);
|
||||
return Result.err(
|
||||
new DomainException(
|
||||
ErrorCode.PAYMENT_ALREADY_PROCESSED,
|
||||
`Cannot fail payment in status ${this._status}`,
|
||||
HttpStatus.CONFLICT,
|
||||
),
|
||||
);
|
||||
}
|
||||
this._status = 'FAILED';
|
||||
this._callbackData = callbackData;
|
||||
@@ -113,6 +127,7 @@ export class PaymentEntity extends AggregateRoot<string> {
|
||||
this.addDomainEvent(
|
||||
new PaymentFailedEvent(this.id, this._userId, this._provider),
|
||||
);
|
||||
return Result.ok(undefined);
|
||||
}
|
||||
|
||||
/** Emit completed event without modifying state (used when DB was already updated atomically). */
|
||||
@@ -129,11 +144,18 @@ export class PaymentEntity extends AggregateRoot<string> {
|
||||
);
|
||||
}
|
||||
|
||||
markRefunded(): void {
|
||||
markRefunded(): Result<void, DomainException> {
|
||||
if (this._status !== 'COMPLETED') {
|
||||
throw new Error('Chỉ có thể hoàn tiền cho thanh toán đã hoàn tất');
|
||||
return Result.err(
|
||||
new DomainException(
|
||||
ErrorCode.PAYMENT_ALREADY_PROCESSED,
|
||||
'Chỉ có thể hoàn tiền cho thanh toán đã hoàn tất',
|
||||
HttpStatus.CONFLICT,
|
||||
),
|
||||
);
|
||||
}
|
||||
this._status = 'REFUNDED';
|
||||
this.updatedAt = new Date();
|
||||
return Result.ok(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user