fix(checkout): restore cart on every exit
This commit is contained in:
@@ -587,14 +587,29 @@ describe('CheckoutPageComponent payment validation', () => {
|
||||
expect(component.stepper.next).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('keeps the checkout purchase intact when navigating away', async () => {
|
||||
it('restores the cart when navigating away from checkout', async () => {
|
||||
const { component } = createComponent();
|
||||
|
||||
await expect(component.canDeactivate()).resolves.toBe(true);
|
||||
|
||||
expect(checkoutServiceStub.cancelPurchase).not.toHaveBeenCalled();
|
||||
expect(cartServiceStub.loadCart).toHaveBeenCalled();
|
||||
expect(checkoutServiceStub.cancelPurchase).toHaveBeenCalledWith('tenant-test', 25);
|
||||
expect(cartServiceStub.loadCart).toHaveBeenCalledOnce();
|
||||
expect(cartServiceStub.clearCart).not.toHaveBeenCalled();
|
||||
expect(component.createdPurchaseId()).toBeNull();
|
||||
expect(globalLoadingServiceStub.start).toHaveBeenCalledOnce();
|
||||
expect(globalLoadingServiceStub.stop).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('prevents leaving checkout when the cart cannot be restored', async () => {
|
||||
checkoutServiceStub.cancelPurchase.mockRejectedValue({
|
||||
error: { message: 'No se pudo cancelar la compra.' },
|
||||
});
|
||||
const { component } = createComponent();
|
||||
|
||||
await expect(component.canDeactivate()).resolves.toBe(false);
|
||||
|
||||
expect(cartServiceStub.loadCart).not.toHaveBeenCalled();
|
||||
expect(toastServiceStub.danger).toHaveBeenCalledWith('No se pudo cancelar la compra.');
|
||||
expect(component.createdPurchaseId()).toBe(25);
|
||||
});
|
||||
|
||||
|
||||
@@ -81,6 +81,7 @@ export class CheckoutPageComponent implements OnInit, OnDestroy {
|
||||
private transferPollingRunId = 0;
|
||||
private paymentMethodRequestId = 0;
|
||||
private navigationStarted = false;
|
||||
private restoreCartPromise: Promise<boolean> | null = null;
|
||||
|
||||
@ViewChild(StepperComponent) stepper!: StepperComponent;
|
||||
|
||||
@@ -184,7 +185,6 @@ export class CheckoutPageComponent implements OnInit, OnDestroy {
|
||||
})),
|
||||
quantity: item.quantity,
|
||||
variantId: item.source_variant_id,
|
||||
variants: item.variants,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -193,29 +193,16 @@ export class CheckoutPageComponent implements OnInit, OnDestroy {
|
||||
return;
|
||||
}
|
||||
|
||||
const tenant = this.tenantService.tenant();
|
||||
const purchaseId = this.createdPurchaseId();
|
||||
if (!tenant || !purchaseId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isRestoringCart.set(true);
|
||||
this.globalLoadingService.start();
|
||||
try {
|
||||
await this.checkoutService.cancelPurchase(tenant.codigo, purchaseId);
|
||||
this.createdPurchaseId.set(null);
|
||||
this.createdPurchase.set(null);
|
||||
await firstValueFrom(this.cartService.loadCart());
|
||||
this.navigationStarted = true;
|
||||
const restored = await this.restoreCheckoutCart();
|
||||
if (!restored) return;
|
||||
|
||||
await this.router.navigate(['/'], {
|
||||
queryParams: { openCart: true },
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to restore cart for editing:', error);
|
||||
this.showRequestError(error, 'No se pudo recuperar el carrito para modificar la compra.');
|
||||
} finally {
|
||||
this.globalLoadingService.stop();
|
||||
this.isRestoringCart.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,29 +238,71 @@ export class CheckoutPageComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
protected async onCancel(): Promise<void> {
|
||||
if (await this.canDeactivate()) {
|
||||
void this.router.navigate(['/']);
|
||||
}
|
||||
void this.router.navigate(['/']);
|
||||
}
|
||||
|
||||
public async canDeactivate(): Promise<boolean> {
|
||||
this.stopQrPolling();
|
||||
this.stopTransferPolling();
|
||||
|
||||
if (this.restoreCartPromise) {
|
||||
return this.restoreCartPromise;
|
||||
}
|
||||
|
||||
if (this.navigationStarted) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// The checkout cart remains attached to its purchase. Once the user adds a
|
||||
// new item, the cart API creates a separate active cart automatically.
|
||||
this.globalLoadingService.start();
|
||||
try {
|
||||
await firstValueFrom(this.cartService.loadCart());
|
||||
} catch (error) {
|
||||
console.error('Failed to load the active cart after leaving checkout:', error);
|
||||
this.showRequestError(error, 'No se pudo cargar el carrito.');
|
||||
return await this.restoreCheckoutCart();
|
||||
} finally {
|
||||
this.globalLoadingService.stop();
|
||||
}
|
||||
}
|
||||
|
||||
private restoreCheckoutCart(): Promise<boolean> {
|
||||
if (this.restoreCartPromise) {
|
||||
return this.restoreCartPromise;
|
||||
}
|
||||
|
||||
return true;
|
||||
this.restoreCartPromise = this.performCartRestore().finally(() => {
|
||||
this.restoreCartPromise = null;
|
||||
});
|
||||
|
||||
return this.restoreCartPromise;
|
||||
}
|
||||
|
||||
private async performCartRestore(): Promise<boolean> {
|
||||
const tenant = this.tenantService.tenant();
|
||||
const purchaseId = this.createdPurchaseId();
|
||||
if (!tenant || !purchaseId) {
|
||||
return true;
|
||||
}
|
||||
|
||||
this.isRestoringCart.set(true);
|
||||
|
||||
try {
|
||||
await this.checkoutService.cancelPurchase(tenant.codigo, purchaseId);
|
||||
this.createdPurchaseId.set(null);
|
||||
this.createdPurchase.set(null);
|
||||
|
||||
try {
|
||||
await firstValueFrom(this.cartService.loadCart());
|
||||
} catch (error) {
|
||||
console.error('Failed to load the restored cart:', error);
|
||||
this.showRequestError(error, 'La compra se canceló, pero no se pudo cargar el carrito.');
|
||||
}
|
||||
|
||||
this.navigationStarted = true;
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Failed to restore the checkout cart:', error);
|
||||
this.showRequestError(error, 'No se pudo recuperar el carrito.');
|
||||
return false;
|
||||
} finally {
|
||||
this.isRestoringCart.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
protected async selectPaymentMethod(method: PaymentMethod): Promise<void> {
|
||||
@@ -608,6 +637,8 @@ export class CheckoutPageComponent implements OnInit, OnDestroy {
|
||||
return;
|
||||
}
|
||||
|
||||
this.createdPurchaseId.set(purchaseId);
|
||||
|
||||
try {
|
||||
const purchase = await this.checkoutService
|
||||
.withCustomLoading()
|
||||
@@ -675,7 +706,6 @@ export class CheckoutPageComponent implements OnInit, OnDestroy {
|
||||
const response = error.error as ApiErrorResponse | null;
|
||||
|
||||
if (response?.code === 'purchase.expired' || this.hasExpiredPurchase()) {
|
||||
this.navigationStarted = true;
|
||||
this.stopQrPolling();
|
||||
this.stopTransferPolling();
|
||||
this.toastService.danger(
|
||||
|
||||
Reference in New Issue
Block a user