feat: add submitPurchaseForReview method and update checkout flow to handle purchase review status
This commit is contained in:
@@ -201,6 +201,25 @@ export class CheckoutService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async submitPurchaseForReview(
|
||||||
|
tenantCode: string,
|
||||||
|
purchaseId: number,
|
||||||
|
): Promise<PurchaseStatusResponse> {
|
||||||
|
const response = await firstValueFrom(
|
||||||
|
this.http.post<{ data?: PurchaseStatusResponse } | PurchaseStatusResponse>(
|
||||||
|
`${environment.url}tenants/${tenantCode}/compras/${purchaseId}/review`,
|
||||||
|
{},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const purchase = this.extractResponseData<PurchaseStatusResponse>(response);
|
||||||
|
if (!purchase) {
|
||||||
|
throw new Error('Error al enviar la compra a revisi\u00f3n.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return { status: purchase.status ?? null };
|
||||||
|
}
|
||||||
|
|
||||||
async cancelPurchase(tenantCode: string, purchaseId: number): Promise<PurchaseStatusResponse> {
|
async cancelPurchase(tenantCode: string, purchaseId: number): Promise<PurchaseStatusResponse> {
|
||||||
const response = await firstValueFrom(
|
const response = await firstValueFrom(
|
||||||
this.http.post<{ data?: PurchaseStatusResponse } | PurchaseStatusResponse>(
|
this.http.post<{ data?: PurchaseStatusResponse } | PurchaseStatusResponse>(
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ describe('CheckoutPageComponent payment validation', () => {
|
|||||||
cancelPurchase: ReturnType<typeof vi.fn>;
|
cancelPurchase: ReturnType<typeof vi.fn>;
|
||||||
generatePaymentIntent: ReturnType<typeof vi.fn>;
|
generatePaymentIntent: ReturnType<typeof vi.fn>;
|
||||||
getPurchase: ReturnType<typeof vi.fn>;
|
getPurchase: ReturnType<typeof vi.fn>;
|
||||||
|
submitPurchaseForReview: ReturnType<typeof vi.fn>;
|
||||||
};
|
};
|
||||||
let cartServiceStub: {
|
let cartServiceStub: {
|
||||||
cart: ReturnType<typeof signal>;
|
cart: ReturnType<typeof signal>;
|
||||||
@@ -58,6 +59,7 @@ describe('CheckoutPageComponent payment validation', () => {
|
|||||||
qr_data: { qr_code: 'qr-value' },
|
qr_data: { qr_code: 'qr-value' },
|
||||||
}),
|
}),
|
||||||
getPurchase: vi.fn().mockResolvedValue({ status: 'pending_payment' }),
|
getPurchase: vi.fn().mockResolvedValue({ status: 'pending_payment' }),
|
||||||
|
submitPurchaseForReview: vi.fn().mockResolvedValue({ status: 'in_review' }),
|
||||||
};
|
};
|
||||||
cartServiceStub = {
|
cartServiceStub = {
|
||||||
cart: signal({
|
cart: signal({
|
||||||
@@ -189,25 +191,19 @@ describe('CheckoutPageComponent payment validation', () => {
|
|||||||
expect(checkoutServiceStub.getPurchase).not.toHaveBeenCalled();
|
expect(checkoutServiceStub.getPurchase).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows the verification error without redirecting while a transfer is pending', async () => {
|
it('submits a transfer for review and navigates to its status page', async () => {
|
||||||
const { component } = createComponent();
|
const { component } = createComponent();
|
||||||
component.selectedPaymentMethod.set('transfer');
|
component.selectedPaymentMethod.set('transfer');
|
||||||
|
|
||||||
await component.onComplete();
|
await component.onComplete();
|
||||||
expect(checkoutServiceStub.getPurchase).toHaveBeenCalledTimes(1);
|
|
||||||
expect(component.transferValidationStatus()).toBe('pending');
|
expect(checkoutServiceStub.submitPurchaseForReview).toHaveBeenCalledWith('tenant-test', 25);
|
||||||
expect(cartServiceStub.clearCart).not.toHaveBeenCalled();
|
expect(cartServiceStub.clearCart).not.toHaveBeenCalled();
|
||||||
expect(routerStub.navigate).not.toHaveBeenCalled();
|
expect(routerStub.navigate).toHaveBeenCalledWith(['/checkout/status', 25]);
|
||||||
|
|
||||||
await vi.advanceTimersByTimeAsync(30_000);
|
|
||||||
expect(checkoutServiceStub.getPurchase).toHaveBeenCalledTimes(1);
|
|
||||||
|
|
||||||
await component.onComplete();
|
|
||||||
expect(checkoutServiceStub.getPurchase).toHaveBeenCalledTimes(2);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('navigates after a transfer is confirmed as paid', async () => {
|
it('navigates after a transfer is confirmed as paid', async () => {
|
||||||
checkoutServiceStub.getPurchase.mockResolvedValue({ status: 'paid' });
|
checkoutServiceStub.submitPurchaseForReview.mockResolvedValue({ status: 'paid' });
|
||||||
const { component } = createComponent();
|
const { component } = createComponent();
|
||||||
component.selectedPaymentMethod.set('transfer');
|
component.selectedPaymentMethod.set('transfer');
|
||||||
|
|
||||||
@@ -219,7 +215,7 @@ describe('CheckoutPageComponent payment validation', () => {
|
|||||||
|
|
||||||
it('shows a retryable state when transfer validation fails', async () => {
|
it('shows a retryable state when transfer validation fails', async () => {
|
||||||
vi.spyOn(console, 'error').mockImplementation(() => undefined);
|
vi.spyOn(console, 'error').mockImplementation(() => undefined);
|
||||||
checkoutServiceStub.getPurchase.mockRejectedValue(new Error('network error'));
|
checkoutServiceStub.submitPurchaseForReview.mockRejectedValue(new Error('network error'));
|
||||||
const { component } = createComponent();
|
const { component } = createComponent();
|
||||||
component.selectedPaymentMethod.set('transfer');
|
component.selectedPaymentMethod.set('transfer');
|
||||||
|
|
||||||
@@ -365,7 +361,7 @@ describe('CheckoutPageComponent payment validation', () => {
|
|||||||
expect(component.qrPaymentStatus()).toBe('waiting');
|
expect(component.qrPaymentStatus()).toBe('waiting');
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each(['paid', 'cancelled', 'rejected', 'expired'])(
|
it.each(['in_review', 'paid', 'cancelled', 'rejected', 'expired'])(
|
||||||
'redirects a %s purchase to its status page',
|
'redirects a %s purchase to its status page',
|
||||||
async (status) => {
|
async (status) => {
|
||||||
routeQueryParamMap = convertToParamMap({ purchase: 25 });
|
routeQueryParamMap = convertToParamMap({ purchase: 25 });
|
||||||
|
|||||||
@@ -438,21 +438,19 @@ export class CheckoutPageComponent implements OnInit, OnDestroy {
|
|||||||
this.transferValidationStatus.set('checking');
|
this.transferValidationStatus.set('checking');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const purchase = await this.checkoutService.getPurchase(tenant.codigo, purchaseId);
|
const purchase = await this.checkoutService.submitPurchaseForReview(
|
||||||
|
tenant.codigo,
|
||||||
|
purchaseId,
|
||||||
|
);
|
||||||
|
|
||||||
if (purchase.status === 'paid') {
|
if (purchase.status === 'in_review' || purchase.status === 'paid') {
|
||||||
this.handleConfirmedPayment(purchaseId);
|
this.navigateToPurchaseStatus(purchaseId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (purchase.status === 'pending_payment') {
|
this.transferValidationStatus.set('error');
|
||||||
this.transferValidationStatus.set('pending');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.transferValidationStatus.set('pending');
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to validate transfer payment:', error);
|
console.error('Failed to submit purchase for review:', error);
|
||||||
this.transferValidationStatus.set('error');
|
this.transferValidationStatus.set('error');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -580,6 +578,7 @@ export class CheckoutPageComponent implements OnInit, OnDestroy {
|
|||||||
const purchase = await this.checkoutService.getPurchase(tenant.codigo, purchaseId);
|
const purchase = await this.checkoutService.getPurchase(tenant.codigo, purchaseId);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
purchase.status === 'in_review' ||
|
||||||
purchase.status === 'paid' ||
|
purchase.status === 'paid' ||
|
||||||
purchase.status === 'cancelled' ||
|
purchase.status === 'cancelled' ||
|
||||||
purchase.status === 'rejected' ||
|
purchase.status === 'rejected' ||
|
||||||
|
|||||||
Reference in New Issue
Block a user