From 4cb81ed70d7a45d76b5e29765b96b48f1b1ec45e Mon Sep 17 00:00:00 2001 From: ncoronel Date: Tue, 28 Jul 2026 17:02:12 -0300 Subject: [PATCH] feat: implement payment verification error component and update checkout flow --- .../checkout-page.component.spec.ts | 12 +- .../checkout-page/checkout-page.component.ts | 1 - .../checkout-payment-step.component.html | 17 +- .../checkout-payment-qr.component.html | 21 +- .../checkout-payment-qr.component.scss | 45 +--- .../checkout-payment-qr.component.spec.ts | 6 +- .../checkout-payment-qr.component.ts | 23 +- .../checkout-payment-transfer.component.html | 199 +++++++++--------- .../checkout-payment-transfer.component.scss | 14 -- .../checkout-payment-transfer.component.ts | 33 ++- .../payment-verification-error.component.html | 17 ++ .../payment-verification-error.component.scss | 35 +++ .../payment-verification-error.component.ts | 32 +++ 13 files changed, 243 insertions(+), 212 deletions(-) create mode 100644 src/app/features/store/pages/checkout-page/components/payment-verification-error/payment-verification-error.component.html create mode 100644 src/app/features/store/pages/checkout-page/components/payment-verification-error/payment-verification-error.component.scss create mode 100644 src/app/features/store/pages/checkout-page/components/payment-verification-error/payment-verification-error.component.ts 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 3186cad..4924c6f 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 @@ -189,7 +189,7 @@ describe('CheckoutPageComponent payment validation', () => { expect(checkoutServiceStub.getPurchase).not.toHaveBeenCalled(); }); - it('checks a transfer once and redirects to purchase status while pending', async () => { + it('shows the verification error without redirecting while a transfer is pending', async () => { const { component } = createComponent(); component.selectedPaymentMethod.set('transfer'); @@ -197,13 +197,13 @@ describe('CheckoutPageComponent payment validation', () => { expect(checkoutServiceStub.getPurchase).toHaveBeenCalledTimes(1); expect(component.transferValidationStatus()).toBe('pending'); expect(cartServiceStub.clearCart).not.toHaveBeenCalled(); - expect(routerStub.navigate).toHaveBeenCalledWith(['/checkout/status', 25]); + expect(routerStub.navigate).not.toHaveBeenCalled(); await vi.advanceTimersByTimeAsync(30_000); expect(checkoutServiceStub.getPurchase).toHaveBeenCalledTimes(1); await component.onComplete(); - expect(checkoutServiceStub.getPurchase).toHaveBeenCalledTimes(1); + expect(checkoutServiceStub.getPurchase).toHaveBeenCalledTimes(2); }); it('navigates after a transfer is confirmed as paid', async () => { @@ -360,11 +360,7 @@ describe('CheckoutPageComponent payment validation', () => { expect(component.checkoutStepIndex()).toBe(1); expect(component.selectedPaymentMethod()).toBe('qr'); - expect(checkoutServiceStub.generatePaymentIntent).toHaveBeenCalledWith( - 'tenant-test', - 25, - 'qr', - ); + expect(checkoutServiceStub.generatePaymentIntent).toHaveBeenCalledWith('tenant-test', 25, 'qr'); expect(component.qrData()).toBe('qr-value'); expect(component.qrPaymentStatus()).toBe('waiting'); }); 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 e5fa990..35f0296 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 @@ -447,7 +447,6 @@ export class CheckoutPageComponent implements OnInit, OnDestroy { if (purchase.status === 'pending_payment') { this.transferValidationStatus.set('pending'); - this.navigateToPurchaseStatus(purchaseId); return; } 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 2778b24..5726668 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 @@ -1,14 +1,13 @@
-

Selecciona el metodo de pago

+

+ Selecciona el metodo de pago +

@for (method of paymentMethods(); track method.id) { -
- diff --git a/src/app/features/store/pages/checkout-page/components/checkout-payment-qr/checkout-payment-qr.component.html b/src/app/features/store/pages/checkout-page/components/checkout-payment-qr/checkout-payment-qr.component.html index 0cfb35d..820d125 100644 --- a/src/app/features/store/pages/checkout-page/components/checkout-payment-qr/checkout-payment-qr.component.html +++ b/src/app/features/store/pages/checkout-page/components/checkout-payment-qr/checkout-payment-qr.component.html @@ -1,22 +1,9 @@
@if (paymentStatus() === 'timed_out') { - + } @else {

Ingresa a tu billetera y escanea el siguiente QR

diff --git a/src/app/features/store/pages/checkout-page/components/checkout-payment-qr/checkout-payment-qr.component.scss b/src/app/features/store/pages/checkout-page/components/checkout-payment-qr/checkout-payment-qr.component.scss index 6ec30a9..26e513f 100644 --- a/src/app/features/store/pages/checkout-page/components/checkout-payment-qr/checkout-payment-qr.component.scss +++ b/src/app/features/store/pages/checkout-page/components/checkout-payment-qr/checkout-payment-qr.component.scss @@ -57,7 +57,9 @@ rgba(17, 17, 17, 0.95) 84% 100% ); background-size: 18px 18px; - background-position: 0 0, 9px 9px; + background-position: + 0 0, + 9px 9px; display: flex; justify-content: center; align-items: center; @@ -86,7 +88,9 @@ height: 38px; border: 5px solid #111111; background: #ffffff; - box-shadow: inset 0 0 0 8px #ffffff, inset 0 0 0 14px #111111; + box-shadow: + inset 0 0 0 8px #ffffff, + inset 0 0 0 14px #111111; &--tl { top: 10px; @@ -116,43 +120,6 @@ } } -.payment-timeout { - display: flex; - flex-direction: column; - align-items: center; - width: 100%; - padding-top: 0.85rem; - - &__icon { - display: grid; - width: 84px; - height: 84px; - place-items: center; - border: 1px solid var(--tenant-danger, #dc3545); - border-radius: 50%; - color: var(--tenant-danger, #dc3545); - font-size: 2.7rem; - } - - &__title { - max-width: 250px; - margin: 1.35rem 0 0; - color: var(--tenant-danger, #dc3545); - font-size: 1.05rem; - font-weight: 700; - line-height: 1.3; - } - - &__message { - max-width: 250px; - margin: 2rem 0 1.75rem; - color: #666666; - font-size: 1rem; - line-height: 1.25; - } - -} - .payment-verification { position: absolute; inset: 0; diff --git a/src/app/features/store/pages/checkout-page/components/checkout-payment-qr/checkout-payment-qr.component.spec.ts b/src/app/features/store/pages/checkout-page/components/checkout-payment-qr/checkout-payment-qr.component.spec.ts index f6f31dd..7eba580 100644 --- a/src/app/features/store/pages/checkout-page/components/checkout-payment-qr/checkout-payment-qr.component.spec.ts +++ b/src/app/features/store/pages/checkout-page/components/checkout-payment-qr/checkout-payment-qr.component.spec.ts @@ -40,10 +40,12 @@ describe('CheckoutPaymentQrComponent', () => { fixture.detectChanges(); const element = fixture.nativeElement as HTMLElement; - const whatsapp = element.querySelector('.payment-timeout__whatsapp'); + const whatsapp = Array.from(element.querySelectorAll('button')).find((button) => + button.textContent?.includes('WhatsApp'), + ); expect(element.textContent).toMatch(/No pudimos verificar el pago de \$\s*300\.000\./); expect(element.textContent).toContain('Por favor contactate con nosotros para resolverlo.'); - expect(whatsapp?.href).toBe('https://wa.me/543412602222'); + expect(whatsapp).toBeDefined(); expect(element.textContent).not.toContain('Volver a verificar'); }); }); diff --git a/src/app/features/store/pages/checkout-page/components/checkout-payment-qr/checkout-payment-qr.component.ts b/src/app/features/store/pages/checkout-page/components/checkout-payment-qr/checkout-payment-qr.component.ts index 93b19a1..221c01e 100644 --- a/src/app/features/store/pages/checkout-page/components/checkout-payment-qr/checkout-payment-qr.component.ts +++ b/src/app/features/store/pages/checkout-page/components/checkout-payment-qr/checkout-payment-qr.component.ts @@ -1,15 +1,15 @@ -import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core'; -import { ButtonComponent } from '../../../../../../shared/components/button/button.component'; +import { ChangeDetectionStrategy, Component, input } from '@angular/core'; import { QRCodeComponent } from '../../../../../../shared/components/qrcode/qrcode.component'; import { QrPaymentStatus } from '../../checkout-page.models'; +import { PaymentVerificationErrorComponent } from '../payment-verification-error/payment-verification-error.component'; @Component({ selector: 'app-checkout-payment-qr', standalone: true, - imports: [ButtonComponent, QRCodeComponent], + imports: [PaymentVerificationErrorComponent, QRCodeComponent], templateUrl: './checkout-payment-qr.component.html', styleUrl: './checkout-payment-qr.component.scss', - changeDetection: ChangeDetectionStrategy.OnPush + changeDetection: ChangeDetectionStrategy.OnPush, }) export class CheckoutPaymentQrComponent { readonly qrData = input(null); @@ -17,19 +17,4 @@ export class CheckoutPaymentQrComponent { readonly isCheckingPayment = input(false); readonly paymentAmount = input(0); readonly whatsappUrl = input(null); - protected readonly formattedAmount = computed(() => - new Intl.NumberFormat('es-AR', { - style: 'currency', - currency: 'ARS', - maximumFractionDigits: 0, - }).format(this.paymentAmount()), - ); - - protected openWhatsApp(): void { - const url = this.whatsappUrl(); - - if (url) { - window.open(url, '_blank', 'noopener,noreferrer'); - } - } } 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 482eea6..1808163 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 @@ -1,105 +1,106 @@
-
-

Ingresá DNI de quién va a transferir

-
-
- -
-
- @if (isDniDisabled()) { - - Modificar - - } @else { - - Continuar - - } -
-
-
- - @if (transferAccount() && !isEditing()) { -
- diff --git a/src/app/features/store/pages/checkout-page/components/checkout-payment-transfer/checkout-payment-transfer.component.scss b/src/app/features/store/pages/checkout-page/components/checkout-payment-transfer/checkout-payment-transfer.component.scss index b65c086..def5cf0 100644 --- a/src/app/features/store/pages/checkout-page/components/checkout-payment-transfer/checkout-payment-transfer.component.scss +++ b/src/app/features/store/pages/checkout-page/components/checkout-payment-transfer/checkout-payment-transfer.component.scss @@ -115,17 +115,3 @@ height: 100%; } } - -.payment-validation { - margin: 0.75rem 0 0; - font-size: 0.8rem; - line-height: 1.35; - - &--pending { - color: #8a681d; - } - - &--error { - color: #b42318; - } -} 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 f52e02a..29aed7b 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 @@ -1,23 +1,46 @@ -import { ChangeDetectionStrategy, Component, computed, input, output, signal, OnInit, effect, untracked } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + computed, + input, + output, + signal, + OnInit, + effect, + untracked, +} from '@angular/core'; import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms'; import { IconButtonComponent } from '../../../../../../shared/components/icon-button/icon-button.component'; import { ButtonComponent } from '../../../../../../shared/components/button/button.component'; import { InputComponent } from '../../../../../../shared/components/input/input.component'; -import { TransferAccount, TransferField, TransferValidationStatus } from '../../checkout-page.models'; +import { + TransferAccount, + TransferField, + TransferValidationStatus, +} from '../../checkout-page.models'; +import { PaymentVerificationErrorComponent } from '../payment-verification-error/payment-verification-error.component'; @Component({ selector: 'app-checkout-payment-transfer', standalone: true, - imports: [IconButtonComponent, ButtonComponent, InputComponent, ReactiveFormsModule], + imports: [ + IconButtonComponent, + ButtonComponent, + InputComponent, + ReactiveFormsModule, + PaymentVerificationErrorComponent, + ], templateUrl: './checkout-payment-transfer.component.html', styleUrl: './checkout-payment-transfer.component.scss', - changeDetection: ChangeDetectionStrategy.OnPush + changeDetection: ChangeDetectionStrategy.OnPush, }) export class CheckoutPaymentTransferComponent implements OnInit { readonly transferAccount = input(null); readonly transferDni = input(''); readonly copiedTransferField = input(null); readonly validationStatus = input('idle'); + readonly paymentAmount = input(0); + readonly whatsappUrl = input(null); readonly copyTransferValue = output<{ field: TransferField; value: string }>(); readonly submitDni = output(); @@ -25,7 +48,7 @@ export class CheckoutPaymentTransferComponent implements OnInit { protected readonly dniControl = new FormControl('', { nonNullable: true, - validators: [Validators.required, Validators.pattern(/^\d{7,8}$/)] + validators: [Validators.required, Validators.pattern(/^\d{7,8}$/)], }); protected readonly isEditing = signal(false); 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 new file mode 100644 index 0000000..64e0374 --- /dev/null +++ b/src/app/features/store/pages/checkout-page/components/payment-verification-error/payment-verification-error.component.html @@ -0,0 +1,17 @@ + diff --git a/src/app/features/store/pages/checkout-page/components/payment-verification-error/payment-verification-error.component.scss b/src/app/features/store/pages/checkout-page/components/payment-verification-error/payment-verification-error.component.scss new file mode 100644 index 0000000..a82da35 --- /dev/null +++ b/src/app/features/store/pages/checkout-page/components/payment-verification-error/payment-verification-error.component.scss @@ -0,0 +1,35 @@ +.payment-timeout { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + padding-top: 0.85rem; + + &__icon { + display: grid; + width: 84px; + height: 84px; + place-items: center; + border: 1px solid var(--tenant-danger, #dc3545); + border-radius: 50%; + color: var(--tenant-danger, #dc3545); + font-size: 2.7rem; + } + + &__title { + max-width: 250px; + margin: 1.35rem 0 0; + color: var(--tenant-danger, #dc3545); + font-size: 1.05rem; + font-weight: 700; + line-height: 1.3; + } + + &__message { + max-width: 250px; + margin: 2rem 0 1.75rem; + color: #666666; + font-size: 1rem; + line-height: 1.25; + } +} diff --git a/src/app/features/store/pages/checkout-page/components/payment-verification-error/payment-verification-error.component.ts b/src/app/features/store/pages/checkout-page/components/payment-verification-error/payment-verification-error.component.ts new file mode 100644 index 0000000..7ec1e4f --- /dev/null +++ b/src/app/features/store/pages/checkout-page/components/payment-verification-error/payment-verification-error.component.ts @@ -0,0 +1,32 @@ +import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core'; + +import { ButtonComponent } from '../../../../../../shared/components/button/button.component'; + +@Component({ + selector: 'app-payment-verification-error', + standalone: true, + imports: [ButtonComponent], + templateUrl: './payment-verification-error.component.html', + styleUrl: './payment-verification-error.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class PaymentVerificationErrorComponent { + readonly paymentAmount = input(0); + readonly whatsappUrl = input(null); + + protected readonly formattedAmount = computed(() => + new Intl.NumberFormat('es-AR', { + style: 'currency', + currency: 'ARS', + maximumFractionDigits: 0, + }).format(this.paymentAmount()), + ); + + protected openWhatsApp(): void { + const url = this.whatsappUrl(); + + if (url) { + window.open(url, '_blank', 'noopener,noreferrer'); + } + } +}