From 4997665da605a36b6a7850a8801160f45114fdf3 Mon Sep 17 00:00:00 2001 From: ncoronel Date: Thu, 27 Aug 2026 15:38:23 -0300 Subject: [PATCH 1/5] feat(checkout): show transfer candidate issue --- src/app/core/services/checkout.service.ts | 22 +++++++++++ .../purchase-status-page.component.html | 36 ++++++++++++++---- .../purchase-status-page.component.spec.ts | 35 ++++++++++++++--- .../purchase-status-page.component.ts | 38 +++++++++++++++++++ 4 files changed, 118 insertions(+), 13 deletions(-) diff --git a/src/app/core/services/checkout.service.ts b/src/app/core/services/checkout.service.ts index ace291b..ef214b5 100644 --- a/src/app/core/services/checkout.service.ts +++ b/src/app/core/services/checkout.service.ts @@ -74,6 +74,28 @@ export interface PurchaseStatusResponse { expires_at: string | null; expires_in_seconds: number | null; server_time: string; + payment_verification?: PurchasePaymentVerificationResponse; +} + +export type PurchasePaymentCandidateReason = + | 'ambiguous_exact_match' + | 'exact_dni_near_amount' + | 'exact_amount_different_dni'; + +export interface PurchasePaymentCandidatePrimaryResponse { + reason: PurchasePaymentCandidateReason; + payment_amount: string; + purchase_amount: string; + amount_difference: string; + confidence: 'exact' | 'high' | 'medium'; + detected_at: string | null; +} + +export interface PurchasePaymentVerificationResponse { + status: 'pending' | 'candidate'; + candidate_count: number; + primary: PurchasePaymentCandidatePrimaryResponse | null; + reasons: PurchasePaymentCandidateReason[]; } export interface PurchaseSummaryResponse extends PurchaseStatusResponse { diff --git a/src/app/features/store/pages/purchase-status-page/purchase-status-page.component.html b/src/app/features/store/pages/purchase-status-page/purchase-status-page.component.html index 9018c1f..0c3b067 100644 --- a/src/app/features/store/pages/purchase-status-page/purchase-status-page.component.html +++ b/src/app/features/store/pages/purchase-status-page/purchase-status-page.component.html @@ -17,7 +17,9 @@
@if (ticketsRoute()) { -

A continuación, vas a poder ver los tickets que debés presentar en el evento.

+

+ A continuación, vas a poder ver los tickets que debés presentar en el evento. +

Mis tickets } @else if (whatsappUrl()) { -

Comunicate con nosotros para coordinar el envío.

+

+ Comunicate con nosotros para coordinar el envío. +

ESTAMOS VERIFICANDO TU PAGO

-

Tu compra ya fue registrada y estamos esperando la confirmación del pago.

+

+ Tu compra ya fue registrada y estamos esperando la confirmación del pago. +


-

Te avisaremos cuando el pago sea confirmado.

+ @if (paymentIssueMessage()) { +

+ {{ paymentIssueMessage() }} Estamos revisando el pago. +

+ } @else { +

Te avisaremos cuando el pago sea confirmado.

+ }
} @else if (status() === 'expired') {
@@ -65,13 +77,17 @@

LA COMPRA VENCIÓ

-

El plazo de pago terminó y liberamos el stock reservado.

+

+ El plazo de pago terminó y liberamos el stock reservado. +


-

Podés volver a la tienda e iniciar una nueva compra.

+

+ Podés volver a la tienda e iniciar una nueva compra. +

} @else if (status() === 'rejected') {
@@ -79,7 +95,9 @@

NO PUDIMOS CONFIRMAR EL PAGO

-

Revisá el medio de pago o comunicate con nosotros para continuar.

+

+ Revisá el medio de pago o comunicate con nosotros para continuar. +


@@ -114,7 +132,9 @@
-

Volvé a ingresar más tarde. Si el problema sigue, comunicate con nosotros.

+

+ Volvé a ingresar más tarde. Si el problema sigue, comunicate con nosotros. +

@if (whatsappUrl()) {
diff --git a/src/app/features/store/pages/purchase-status-page/purchase-status-page.component.spec.ts b/src/app/features/store/pages/purchase-status-page/purchase-status-page.component.spec.ts index 6dcf23c..1b8fcdc 100644 --- a/src/app/features/store/pages/purchase-status-page/purchase-status-page.component.spec.ts +++ b/src/app/features/store/pages/purchase-status-page/purchase-status-page.component.spec.ts @@ -67,9 +67,13 @@ describe('PurchaseStatusPageComponent', () => { TestBed.resetTestingModule(); }); - async function render(hasGeneratedTickets: boolean, forcedStatus?: string) { + async function render( + hasGeneratedTickets: boolean, + forcedStatus?: string, + purchaseResponse = purchase(hasGeneratedTickets), + ) { const checkoutService = { - getPurchase: vi.fn().mockResolvedValue(purchase(hasGeneratedTickets)), + getPurchase: vi.fn().mockResolvedValue(purchaseResponse), withCustomLoading() { return this; }, @@ -91,9 +95,7 @@ describe('PurchaseStatusPageComponent', () => { useValue: { snapshot: { paramMap: convertToParamMap({ id: '42' }), - queryParamMap: convertToParamMap( - forcedStatus ? { status: forcedStatus } : {}, - ), + queryParamMap: convertToParamMap(forcedStatus ? { status: forcedStatus } : {}), }, }, }, @@ -159,6 +161,29 @@ describe('PurchaseStatusPageComponent', () => { expect(checkoutService.getPurchase).not.toHaveBeenCalled(); }); + it('shows the primary transfer candidate issue while the purchase is in review', async () => { + const { element } = await render(false, undefined, { + status: 'in_review', + payment_verification: { + status: 'candidate', + candidate_count: 2, + primary: { + reason: 'exact_dni_near_amount', + payment_amount: '49000.00', + purchase_amount: '50000.00', + amount_difference: '1000.00', + confidence: 'high', + detected_at: '2026-08-27T18:00:00-03:00', + }, + reasons: ['exact_dni_near_amount', 'exact_amount_different_dni'], + }, + } as PurchaseDetailResponse); + + expect(element.textContent).toContain('Encontramos 2 transferencias posibles.'); + expect(element.textContent).toMatch(/diferencia de \$\s*1\.000/); + expect(element.textContent).toContain('Estamos revisando el pago.'); + }); + it('polls every five seconds while the payment is pending and shows the confirmation', async () => { vi.useFakeTimers(); diff --git a/src/app/features/store/pages/purchase-status-page/purchase-status-page.component.ts b/src/app/features/store/pages/purchase-status-page/purchase-status-page.component.ts index f04a492..154381a 100644 --- a/src/app/features/store/pages/purchase-status-page/purchase-status-page.component.ts +++ b/src/app/features/store/pages/purchase-status-page/purchase-status-page.component.ts @@ -13,6 +13,7 @@ import { ActivatedRoute, Router } from '@angular/router'; import { CheckoutService, + PurchasePaymentVerificationResponse, PurchaseStatusResponse, } from '../../../../core/services/checkout.service'; import { CartService } from '../../../../core/services/cart/cart.service'; @@ -48,6 +49,7 @@ export class PurchaseStatusPageComponent implements OnInit, OnDestroy { protected readonly isLoading = signal(true); protected readonly status = signal('pending'); protected readonly hasGeneratedTickets = signal(false); + protected readonly paymentIssueMessage = signal(null); protected readonly ticketsRoute = computed(() => { if (!this.hasGeneratedTickets()) { return null; @@ -105,6 +107,7 @@ export class PurchaseStatusPageComponent implements OnInit, OnDestroy { const status = this.resolveStatus(purchase); this.status.set(status); this.hasGeneratedTickets.set(purchase.has_generated_tickets === true); + this.paymentIssueMessage.set(this.resolvePaymentIssueMessage(purchase.payment_verification)); if (status === 'approved') { this.cartService.clearCart(); @@ -169,6 +172,41 @@ export class PurchaseStatusPageComponent implements OnInit, OnDestroy { return 'pending'; } + private resolvePaymentIssueMessage( + verification?: PurchasePaymentVerificationResponse, + ): string | null { + const primary = verification?.primary; + + if (!primary) { + return null; + } + + const primaryMessage = (() => { + switch (primary.reason) { + case 'ambiguous_exact_match': + return 'Encontramos una transferencia que también coincide con otra compra.'; + case 'exact_dni_near_amount': + return `El DNI coincide, pero el monto transferido tiene una diferencia de ${this.formatCurrency(primary.amount_difference)}.`; + case 'exact_amount_different_dni': + return 'El monto coincide, pero el DNI del pagador es diferente.'; + } + })(); + + if (verification.candidate_count > 1) { + return `Encontramos ${verification.candidate_count} transferencias posibles. ${primaryMessage}`; + } + + return primaryMessage; + } + + private formatCurrency(amount: string): string { + return new Intl.NumberFormat('es-AR', { + style: 'currency', + currency: 'ARS', + maximumFractionDigits: 2, + }).format(Number(amount)); + } + private isPurchaseExpiredError(error: unknown): boolean { if (typeof error !== 'object' || error === null || !('error' in error)) { return false; From 07500f699da21540fd49bcb97213408e0aec2640 Mon Sep 17 00:00:00 2001 From: ncoronel Date: Thu, 27 Aug 2026 16:04:13 -0300 Subject: [PATCH 2/5] feat(checkout): show candidate DNI distance --- src/app/core/services/checkout.service.ts | 1 + .../purchase-status-page.component.spec.ts | 1 + .../purchase-status-page/purchase-status-page.component.ts | 4 +++- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/app/core/services/checkout.service.ts b/src/app/core/services/checkout.service.ts index ef214b5..5a5eb76 100644 --- a/src/app/core/services/checkout.service.ts +++ b/src/app/core/services/checkout.service.ts @@ -84,6 +84,7 @@ export type PurchasePaymentCandidateReason = export interface PurchasePaymentCandidatePrimaryResponse { reason: PurchasePaymentCandidateReason; + dni_distance: number | null; payment_amount: string; purchase_amount: string; amount_difference: string; diff --git a/src/app/features/store/pages/purchase-status-page/purchase-status-page.component.spec.ts b/src/app/features/store/pages/purchase-status-page/purchase-status-page.component.spec.ts index 1b8fcdc..8a22780 100644 --- a/src/app/features/store/pages/purchase-status-page/purchase-status-page.component.spec.ts +++ b/src/app/features/store/pages/purchase-status-page/purchase-status-page.component.spec.ts @@ -169,6 +169,7 @@ describe('PurchaseStatusPageComponent', () => { candidate_count: 2, primary: { reason: 'exact_dni_near_amount', + dni_distance: 0, payment_amount: '49000.00', purchase_amount: '50000.00', amount_difference: '1000.00', diff --git a/src/app/features/store/pages/purchase-status-page/purchase-status-page.component.ts b/src/app/features/store/pages/purchase-status-page/purchase-status-page.component.ts index 154381a..b74f216 100644 --- a/src/app/features/store/pages/purchase-status-page/purchase-status-page.component.ts +++ b/src/app/features/store/pages/purchase-status-page/purchase-status-page.component.ts @@ -188,7 +188,9 @@ export class PurchaseStatusPageComponent implements OnInit, OnDestroy { case 'exact_dni_near_amount': return `El DNI coincide, pero el monto transferido tiene una diferencia de ${this.formatCurrency(primary.amount_difference)}.`; case 'exact_amount_different_dni': - return 'El monto coincide, pero el DNI del pagador es diferente.'; + return primary.dni_distance === null + ? 'El monto coincide, pero el DNI del pagador es diferente.' + : `El monto coincide, pero el DNI del pagador presenta ${primary.dni_distance} ${primary.dni_distance === 1 ? 'diferencia' : 'diferencias'} de escritura.`; } })(); From 65e8436ae1d50613dfd721576340a45527fb22e7 Mon Sep 17 00:00:00 2001 From: ncoronel Date: Thu, 27 Aug 2026 16:55:13 -0300 Subject: [PATCH 3/5] refactor(checkout): rename transfer candidate reasons --- src/app/core/services/checkout.service.ts | 2 +- .../purchase-status-page/purchase-status-page.component.spec.ts | 2 +- .../purchase-status-page/purchase-status-page.component.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/core/services/checkout.service.ts b/src/app/core/services/checkout.service.ts index 5a5eb76..4cad0da 100644 --- a/src/app/core/services/checkout.service.ts +++ b/src/app/core/services/checkout.service.ts @@ -80,7 +80,7 @@ export interface PurchaseStatusResponse { export type PurchasePaymentCandidateReason = | 'ambiguous_exact_match' | 'exact_dni_near_amount' - | 'exact_amount_different_dni'; + | 'exact_amount_near_dni'; export interface PurchasePaymentCandidatePrimaryResponse { reason: PurchasePaymentCandidateReason; diff --git a/src/app/features/store/pages/purchase-status-page/purchase-status-page.component.spec.ts b/src/app/features/store/pages/purchase-status-page/purchase-status-page.component.spec.ts index 8a22780..895968a 100644 --- a/src/app/features/store/pages/purchase-status-page/purchase-status-page.component.spec.ts +++ b/src/app/features/store/pages/purchase-status-page/purchase-status-page.component.spec.ts @@ -176,7 +176,7 @@ describe('PurchaseStatusPageComponent', () => { confidence: 'high', detected_at: '2026-08-27T18:00:00-03:00', }, - reasons: ['exact_dni_near_amount', 'exact_amount_different_dni'], + reasons: ['exact_dni_near_amount', 'exact_amount_near_dni'], }, } as PurchaseDetailResponse); diff --git a/src/app/features/store/pages/purchase-status-page/purchase-status-page.component.ts b/src/app/features/store/pages/purchase-status-page/purchase-status-page.component.ts index b74f216..6560292 100644 --- a/src/app/features/store/pages/purchase-status-page/purchase-status-page.component.ts +++ b/src/app/features/store/pages/purchase-status-page/purchase-status-page.component.ts @@ -187,7 +187,7 @@ export class PurchaseStatusPageComponent implements OnInit, OnDestroy { return 'Encontramos una transferencia que también coincide con otra compra.'; case 'exact_dni_near_amount': return `El DNI coincide, pero el monto transferido tiene una diferencia de ${this.formatCurrency(primary.amount_difference)}.`; - case 'exact_amount_different_dni': + case 'exact_amount_near_dni': return primary.dni_distance === null ? 'El monto coincide, pero el DNI del pagador es diferente.' : `El monto coincide, pero el DNI del pagador presenta ${primary.dni_distance} ${primary.dni_distance === 1 ? 'diferencia' : 'diferencias'} de escritura.`; From eb524c7d99bfe7677fa2ea48a3d0e46b5c4e8730 Mon Sep 17 00:00:00 2001 From: ncoronel Date: Thu, 27 Aug 2026 16:55:21 -0300 Subject: [PATCH 4/5] feat(checkout): show transfer candidate reason after polling --- .../checkout-page.component.html | 1 + .../checkout-page.component.spec.ts | 46 +++++++++++++++---- .../checkout-page/checkout-page.component.ts | 27 +++++++++-- .../checkout-payment-step.component.html | 1 + .../checkout-payment-step.component.ts | 1 + .../checkout-payment-transfer.component.html | 1 + ...heckout-payment-transfer.component.spec.ts | 19 ++++++++ .../checkout-payment-transfer.component.ts | 1 + .../payment-verification-error.component.html | 2 +- .../payment-verification-error.component.ts | 4 ++ 10 files changed, 90 insertions(+), 13 deletions(-) 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 336fc3d..27c6d57 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 @@ -34,6 +34,7 @@ [qrPaymentAmount]="cartTotal()" [whatsappUrl]="whatsappUrl()" [transferValidationStatus]="transferValidationStatus()" + [transferVerificationErrorTitle]="transferVerificationErrorTitle()" (paymentMethodChange)="selectPaymentMethod($event)" (copyTransferValue)="copyTransferValue($event.field, $event.value)" (cancelStep)="onCancel()" diff --git a/src/app/features/store/pages/checkout-page/checkout-page.component.spec.ts b/src/app/features/store/pages/checkout-page/checkout-page.component.spec.ts index c93fd86..ace59a0 100644 --- a/src/app/features/store/pages/checkout-page/checkout-page.component.spec.ts +++ b/src/app/features/store/pages/checkout-page/checkout-page.component.spec.ts @@ -225,7 +225,7 @@ describe('CheckoutPageComponent payment validation', () => { expect(checkoutServiceStub.getPurchase).not.toHaveBeenCalled(); }); - it('polls a transfer every three seconds up to four attempts', async () => { + it('polls a transfer every three seconds for one minute', async () => { checkoutServiceStub.getPurchase.mockResolvedValue({ status: 'pending_payment' }); const { component } = createComponent(); component.selectedPaymentMethod.set('transfer'); @@ -235,14 +235,12 @@ describe('CheckoutPageComponent payment validation', () => { expect(checkoutServiceStub.submitPurchaseForReview).toHaveBeenCalledWith('tenant-test', 25); expect(checkoutServiceStub.getPurchase).not.toHaveBeenCalled(); - for (let attempt = 1; attempt <= 3; attempt += 1) { - await vi.advanceTimersByTimeAsync(3_000); - expect(checkoutServiceStub.getPurchase).toHaveBeenCalledTimes(attempt); - expect(component.transferValidationStatus()).toBe('checking'); - } + await vi.advanceTimersByTimeAsync(57_000); + expect(checkoutServiceStub.getPurchase).toHaveBeenCalledTimes(19); + expect(component.transferValidationStatus()).toBe('checking'); await vi.advanceTimersByTimeAsync(3_000); - expect(checkoutServiceStub.getPurchase).toHaveBeenCalledTimes(4); + expect(checkoutServiceStub.getPurchase).toHaveBeenCalledTimes(20); expect(checkoutServiceStub.getPurchase).toHaveBeenLastCalledWith('tenant-test', 25); expect(component.transferValidationStatus()).toBe('error'); expect(routerStub.navigate).not.toHaveBeenCalled(); @@ -286,13 +284,43 @@ describe('CheckoutPageComponent payment validation', () => { component.selectedPaymentMethod.set('transfer'); await component.onComplete(); - await vi.advanceTimersByTimeAsync(12_000); + await vi.advanceTimersByTimeAsync(60_000); - expect(checkoutServiceStub.getPurchase).toHaveBeenCalledTimes(4); + expect(checkoutServiceStub.getPurchase).toHaveBeenCalledTimes(20); expect(component.transferValidationStatus()).toBe('error'); expect(routerStub.navigate).not.toHaveBeenCalled(); }); + it('uses the primary candidate reason when transfer polling times out', async () => { + checkoutServiceStub.getPurchase.mockResolvedValue({ + status: 'in_review', + payment_verification: { + status: 'candidate', + candidate_count: 1, + primary: { + reason: 'exact_amount_near_dni', + dni_distance: 1, + payment_amount: '300000.00', + purchase_amount: '300000.00', + amount_difference: '0.00', + confidence: 'medium', + detected_at: '2026-08-27T18:00:00-03:00', + }, + reasons: ['exact_amount_near_dni'], + }, + }); + const { component } = createComponent(); + component.selectedPaymentMethod.set('transfer'); + + await component.onComplete(); + await vi.advanceTimersByTimeAsync(60_000); + + expect(component.transferValidationStatus()).toBe('error'); + expect(component.transferVerificationErrorTitle()).toBe( + 'El DNI no corresponde con el de la transferencia', + ); + }); + it('does not poll when submitting a transfer for review fails', async () => { vi.spyOn(console, 'error').mockImplementation(() => undefined); checkoutServiceStub.submitPurchaseForReview.mockRejectedValue(new Error('network error')); 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 f580edd..bd8e320 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 @@ -16,8 +16,10 @@ import { firstValueFrom, startWith } from 'rxjs'; import { TenantService } from '../../../../core/services/tenant.service'; import { CheckoutService, + PurchasePaymentCandidateReason, PurchaseDetailItemResponse, PurchaseDetailResponse, + PurchaseStatusResponse, } from '../../../../core/services/checkout.service'; import { AuthService } from '../../../../core/services/auth/auth.service'; import { GlobalLoadingService } from '../../../../core/services/global-loading/global-loading.service'; @@ -73,7 +75,7 @@ export class CheckoutPageComponent implements OnInit, OnDestroy { private readonly qrPollingIntervalMs = 5_000; private readonly qrPollingMaxAttempts = 120; private readonly transferPollingIntervalMs = 3_000; - private readonly transferPollingMaxAttempts = 209; + private readonly transferPollingMaxAttempts = 20; private qrPollingTimeoutId: ReturnType | null = null; private qrPollingAttempts = 0; @@ -102,8 +104,7 @@ export class CheckoutPageComponent implements OnInit, OnDestroy { if ( !purchase || - (typeof purchase.expires_at !== 'string' && - typeof purchase.expires_in_seconds !== 'number') + (typeof purchase.expires_at !== 'string' && typeof purchase.expires_in_seconds !== 'number') ) { return null; } @@ -159,6 +160,14 @@ export class CheckoutPageComponent implements OnInit, OnDestroy { protected readonly qrPaymentStatus = signal('idle'); protected readonly isCheckingQrPayment = signal(false); protected readonly transferValidationStatus = signal('idle'); + private readonly transferPrimaryCandidateReason = signal( + null, + ); + protected readonly transferVerificationErrorTitle = computed(() => + this.transferPrimaryCandidateReason() === 'exact_amount_near_dni' + ? 'El DNI no corresponde con el de la transferencia' + : null, + ); protected readonly whatsappUrl = computed( () => this.tenantService @@ -410,6 +419,7 @@ export class CheckoutPageComponent implements OnInit, OnDestroy { this.transferDni.set(dni); this.stopTransferPolling(); this.transferValidationStatus.set('idle'); + this.transferPrimaryCandidateReason.set(null); this.isGeneratingIntent.set(true); try { const response = await this.checkoutService @@ -466,6 +476,7 @@ export class CheckoutPageComponent implements OnInit, OnDestroy { this.stopTransferPolling(); this.hasSubmittedTransfer.set(true); this.transferValidationStatus.set('checking'); + this.transferPrimaryCandidateReason.set(null); this.transferPollingAttempts = 0; const runId = this.transferPollingRunId; @@ -480,6 +491,7 @@ export class CheckoutPageComponent implements OnInit, OnDestroy { } this.checkoutCountdownService.synchronize(purchase); + this.captureTransferCandidateReason(purchase); if (purchase.status === 'paid') { this.navigateToPurchaseStatus(purchaseId); @@ -531,6 +543,7 @@ export class CheckoutPageComponent implements OnInit, OnDestroy { } this.checkoutCountdownService.synchronize(purchase); + this.captureTransferCandidateReason(purchase); if (purchase.status === 'paid') { this.navigateToPurchaseStatus(purchaseId); @@ -562,6 +575,14 @@ export class CheckoutPageComponent implements OnInit, OnDestroy { this.scheduleTransferPoll(runId); } + private captureTransferCandidateReason(purchase: PurchaseStatusResponse): void { + const reason = purchase.payment_verification?.primary?.reason; + + if (reason) { + this.transferPrimaryCandidateReason.set(reason); + } + } + private stopTransferPolling(): void { this.transferPollingRunId += 1; diff --git a/src/app/features/store/pages/checkout-page/checkout-payment-step.component.html b/src/app/features/store/pages/checkout-page/checkout-payment-step.component.html index 7970925..553115f 100644 --- a/src/app/features/store/pages/checkout-page/checkout-payment-step.component.html +++ b/src/app/features/store/pages/checkout-page/checkout-payment-step.component.html @@ -53,6 +53,7 @@ [validationStatus]="transferValidationStatus()" [paymentAmount]="qrPaymentAmount()" [whatsappUrl]="whatsappUrl()" + [verificationErrorTitle]="transferVerificationErrorTitle()" (copyTransferValue)="requestCopy($event.field, $event.value)" (submitDni)="generateTransferIntent.emit($event)" (completePurchase)="complete.emit()" diff --git a/src/app/features/store/pages/checkout-page/checkout-payment-step.component.ts b/src/app/features/store/pages/checkout-page/checkout-payment-step.component.ts index e43cb1a..3b77621 100644 --- a/src/app/features/store/pages/checkout-page/checkout-payment-step.component.ts +++ b/src/app/features/store/pages/checkout-page/checkout-payment-step.component.ts @@ -33,6 +33,7 @@ export class CheckoutPaymentStepComponent { readonly qrPaymentAmount = input(0); readonly whatsappUrl = input(null); readonly transferValidationStatus = input('idle'); + readonly transferVerificationErrorTitle = input(null); readonly paymentMethodChange = output(); readonly copyTransferValue = output<{ field: TransferField; value: string }>(); diff --git a/src/app/features/store/pages/checkout-page/components/checkout-payment-transfer/checkout-payment-transfer.component.html b/src/app/features/store/pages/checkout-page/components/checkout-payment-transfer/checkout-payment-transfer.component.html index 46167a6..3fdc97d 100644 --- a/src/app/features/store/pages/checkout-page/components/checkout-payment-transfer/checkout-payment-transfer.component.html +++ b/src/app/features/store/pages/checkout-page/components/checkout-payment-transfer/checkout-payment-transfer.component.html @@ -3,6 +3,7 @@ } @else {
diff --git a/src/app/features/store/pages/checkout-page/components/checkout-payment-transfer/checkout-payment-transfer.component.spec.ts b/src/app/features/store/pages/checkout-page/components/checkout-payment-transfer/checkout-payment-transfer.component.spec.ts index ec21f16..d8967f9 100644 --- a/src/app/features/store/pages/checkout-page/components/checkout-payment-transfer/checkout-payment-transfer.component.spec.ts +++ b/src/app/features/store/pages/checkout-page/components/checkout-payment-transfer/checkout-payment-transfer.component.spec.ts @@ -47,4 +47,23 @@ describe('CheckoutPaymentTransferComponent', () => { expect(whatsapp).toBeDefined(); expect(element.querySelector('.payment-verification')).toBeNull(); }); + + it('shows a custom validation title for a near DNI candidate', async () => { + await TestBed.configureTestingModule({ + imports: [CheckoutPaymentTransferComponent], + }).compileComponents(); + + const fixture = TestBed.createComponent(CheckoutPaymentTransferComponent); + fixture.componentRef.setInput('validationStatus', 'error'); + fixture.componentRef.setInput( + 'verificationErrorTitle', + 'El DNI no corresponde con el de la transferencia', + ); + fixture.detectChanges(); + + expect(fixture.nativeElement.textContent).toContain( + 'El DNI no corresponde con el de la transferencia', + ); + expect(fixture.nativeElement.textContent).not.toContain('No pudimos verificar el pago de'); + }); }); diff --git a/src/app/features/store/pages/checkout-page/components/checkout-payment-transfer/checkout-payment-transfer.component.ts b/src/app/features/store/pages/checkout-page/components/checkout-payment-transfer/checkout-payment-transfer.component.ts index 29aed7b..5ebeb56 100644 --- a/src/app/features/store/pages/checkout-page/components/checkout-payment-transfer/checkout-payment-transfer.component.ts +++ b/src/app/features/store/pages/checkout-page/components/checkout-payment-transfer/checkout-payment-transfer.component.ts @@ -41,6 +41,7 @@ export class CheckoutPaymentTransferComponent implements OnInit { readonly validationStatus = input('idle'); readonly paymentAmount = input(0); readonly whatsappUrl = input(null); + readonly verificationErrorTitle = input(null); readonly copyTransferValue = output<{ field: TransferField; value: string }>(); readonly submitDni = output(); diff --git a/src/app/features/store/pages/checkout-page/components/payment-verification-error/payment-verification-error.component.html b/src/app/features/store/pages/checkout-page/components/payment-verification-error/payment-verification-error.component.html index 64e0374..fcf033f 100644 --- a/src/app/features/store/pages/checkout-page/components/payment-verification-error/payment-verification-error.component.html +++ b/src/app/features/store/pages/checkout-page/components/payment-verification-error/payment-verification-error.component.html @@ -2,7 +2,7 @@ -

No pudimos verificar el pago de {{ formattedAmount() }}.

+

{{ displayTitle() }}

Por favor contactate con nosotros para resolverlo.

@if (whatsappUrl()) { (0); readonly whatsappUrl = input(null); + readonly title = input(null); protected readonly formattedAmount = computed(() => new Intl.NumberFormat('es-AR', { @@ -21,6 +22,9 @@ export class PaymentVerificationErrorComponent { maximumFractionDigits: 0, }).format(this.paymentAmount()), ); + protected readonly displayTitle = computed( + () => this.title() ?? `No pudimos verificar el pago de ${this.formattedAmount()}.`, + ); protected openWhatsApp(): void { const url = this.whatsappUrl(); From 848c3b280fec5a188931b9590f6ee9e4531758a7 Mon Sep 17 00:00:00 2001 From: ncoronel Date: Fri, 28 Aug 2026 11:26:32 -0300 Subject: [PATCH 5/5] fix(product-vertical-with-cart-card): change overflow to visible for improved layout --- .../product-vertical-with-cart-card.component.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/shared/components/product-vertical-with-cart-card/product-vertical-with-cart-card.component.scss b/src/app/shared/components/product-vertical-with-cart-card/product-vertical-with-cart-card.component.scss index 053295e..7dbf216 100644 --- a/src/app/shared/components/product-vertical-with-cart-card/product-vertical-with-cart-card.component.scss +++ b/src/app/shared/components/product-vertical-with-cart-card/product-vertical-with-cart-card.component.scss @@ -17,7 +17,7 @@ height: 100%; box-sizing: border-box; padding: 30px 20px; - overflow: hidden; + overflow: visible; background-color: #ffffff; border: 1px solid var(--border-color); border-radius: 7px;