diff --git a/src/app/core/layout/store-layout/store-header/store-header.component.html b/src/app/core/layout/store-layout/store-header/store-header.component.html index 2a385e4..b8a3115 100644 --- a/src/app/core/layout/store-layout/store-header/store-header.component.html +++ b/src/app/core/layout/store-layout/store-header/store-header.component.html @@ -179,7 +179,7 @@ @if (checkoutRemainingTime(); as remainingTime) {
diff --git a/src/app/features/store/pages/checkout-page/checkout-page.component.html b/src/app/features/store/pages/checkout-page/checkout-page.component.html index 57a7eb2..336fc3d 100644 --- a/src/app/features/store/pages/checkout-page/checkout-page.component.html +++ b/src/app/features/store/pages/checkout-page/checkout-page.component.html @@ -46,6 +46,17 @@
+ @if (checkoutRemainingTime(); as remainingTime) { +
+ Tiempo restante de compra: + {{ remainingTime }} +
+ } + { @@ -129,6 +130,35 @@ describe('CheckoutPageComponent payment validation', () => { return { fixture, component: fixture.componentInstance as any }; } + it('does not expose an active countdown until the purchase timing is loaded', async () => { + const countdown = TestBed.inject(CheckoutCountdownService); + countdown.synchronize({ + expires_at: null, + expires_in_seconds: 600, + server_time: new Date().toISOString(), + }); + checkoutServiceStub.getPurchase.mockResolvedValue({ + id: 25, + status: 'created', + expires_at: '2026-08-27T15:10:00.000Z', + expires_in_seconds: 600, + server_time: '2026-08-27T15:00:00.000Z', + items: [], + subtotal: '0.00', + total: '0.00', + }); + routeParamMap = convertToParamMap({ id: 25 }); + + const { component } = createComponent(); + + expect(component.checkoutRemainingTime()).toBeNull(); + + await Promise.resolve(); + + expect(countdown.remainingSeconds()).toBe(600); + expect(component.checkoutRemainingTime()).toBe('10:00'); + }); + it('polls QR after five seconds and navigates only when payment is paid', async () => { checkoutServiceStub.getPurchase .mockResolvedValueOnce({ status: 'pending_payment' }) diff --git a/src/app/features/store/pages/checkout-page/checkout-page.component.ts b/src/app/features/store/pages/checkout-page/checkout-page.component.ts index 2802068..5a29c14 100644 --- a/src/app/features/store/pages/checkout-page/checkout-page.component.ts +++ b/src/app/features/store/pages/checkout-page/checkout-page.component.ts @@ -97,6 +97,28 @@ export class CheckoutPageComponent implements OnInit, OnDestroy { protected readonly createdPurchase = signal(null); protected readonly isLoadingPurchase = signal(true); protected readonly checkoutStepIndex = signal(0); + protected readonly checkoutRemainingTime = computed(() => { + const purchase = this.createdPurchase(); + + if ( + !purchase || + (typeof purchase.expires_at !== 'string' && + typeof purchase.expires_in_seconds !== 'number') + ) { + return null; + } + + const remainingSeconds = this.checkoutCountdownService.remainingSeconds(); + + if (remainingSeconds === null) { + return null; + } + + const minutes = Math.floor(remainingSeconds / 60); + const seconds = remainingSeconds % 60; + + return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; + }); protected readonly cartSubtotal = computed(() => { const purchase = this.createdPurchase(); return purchase ? parseFloat(purchase.subtotal) : 0; @@ -163,8 +185,6 @@ export class CheckoutPageComponent implements OnInit, OnDestroy { } ngOnInit(): void { - this.checkoutCountdownService.clear(); - const purchaseId = Number(this.route.snapshot.paramMap.get('id')); if (!Number.isInteger(purchaseId) || purchaseId <= 0) { void this.router.navigate(['/']);