From 910f367851261908ca6456eb038aa61646b43acc Mon Sep 17 00:00:00 2001 From: ncoronel Date: Tue, 7 Jul 2026 11:21:27 -0300 Subject: [PATCH] feat(purchase-status): implement purchase status page with loading and error handling --- src/app/core/services/checkout.service.ts | 10 +++ .../checkout-page.component.html | 1 + .../checkout-page/checkout-page.component.ts | 22 +++++ .../checkout-payment-step.component.html | 1 + .../checkout-payment-step.component.ts | 1 + .../purchase-status-page.component.html | 38 ++++++++ .../purchase-status-page.component.scss | 86 +++++++++++++++++++ .../purchase-status-page.component.ts | 65 ++++++++++++++ src/app/features/store/store.routes.ts | 13 +++ 9 files changed, 237 insertions(+) create mode 100644 src/app/features/store/pages/purchase-status-page/purchase-status-page.component.html create mode 100644 src/app/features/store/pages/purchase-status-page/purchase-status-page.component.scss create mode 100644 src/app/features/store/pages/purchase-status-page/purchase-status-page.component.ts diff --git a/src/app/core/services/checkout.service.ts b/src/app/core/services/checkout.service.ts index 73983d0..99a7159 100644 --- a/src/app/core/services/checkout.service.ts +++ b/src/app/core/services/checkout.service.ts @@ -37,4 +37,14 @@ export class CheckoutService { } return response; } + + async finalizePurchase(tenantCode: string, purchaseId: number): Promise<{ payment_status: string; status: string }> { + const response = await firstValueFrom( + this.http.post<{ data: { payment_status: string; status: string } }>(`${environment.url}tenants/${tenantCode}/compras/${purchaseId}/finalize`, {}) + ); + if (!response?.data) { + throw new Error('Error al finalizar la compra.'); + } + return response.data; + } } 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 a499ad7..63c9dd2 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 @@ -21,6 +21,7 @@ (paymentMethodChange)="selectPaymentMethod($event)" (copyTransferValue)="copyTransferValue($event.field, $event.value)" (cancelStep)="onCancel()" + (finalize)="onFinalize()" /> 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 241392e..53d6303 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 @@ -240,4 +240,26 @@ export class CheckoutPageComponent implements OnInit { this.copiedTransferField.set(null); } } + + protected async onFinalize(): Promise { + const purchaseId = this.createdPurchaseId(); + const tenant = this.tenantService.tenant(); + + if (!purchaseId || !tenant) { + return; + } + + try { + this.isGeneratingIntent.set(true); // Re-use this flag for loading state + await this.checkoutService.finalizePurchase(tenant.codigo, purchaseId); + void this.router.navigate(['/checkout/status', purchaseId]); + } catch (error) { + console.error('Failed to finalize purchase:', error); + // Even on failure, we probably want to navigate to the status page to show 'pending' or whatever state it is in. + // But maybe the endpoint returns error if not paid. For now, navigate anyway so the status page handles the state. + void this.router.navigate(['/checkout/status', purchaseId]); + } finally { + this.isGeneratingIntent.set(false); + } + } } 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 454f702..4484b33 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 @@ -142,6 +142,7 @@ type="button" hostClass="checkout-payment__action-btn" [disabled]="isGeneratingIntent()" + (click)="finalize.emit()" > Finalizar compra 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 3b28b98..fd7505c 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 @@ -25,6 +25,7 @@ export class CheckoutPaymentStepComponent { readonly paymentMethodChange = output(); readonly copyTransferValue = output<{ field: TransferField; value: string }>(); readonly cancelStep = output(); + readonly finalize = output(); protected selectPaymentMethod(method: PaymentMethod): void { this.paymentMethodChange.emit(method); 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 new file mode 100644 index 0000000..303332b --- /dev/null +++ b/src/app/features/store/pages/purchase-status-page/purchase-status-page.component.html @@ -0,0 +1,38 @@ +
+ @if (isLoading()) { +
+ +
+

Cargando estado...

+ } @else if (status() === 'approved') { +
+ +
+

COMPRA REALIZADA!

+

Tu compra fue realizada con éxito

+ +
+ +

Comunicate con nosotros para coordinar el envío.

+ + + WhatsApp + + } @else if (status() === 'pending') { +
+ +
+

PAGO PENDIENTE

+

Estamos esperando la confirmación de tu pago

+ +
+ +

Una vez que el pago sea procesado, actualizaremos el estado de tu compra.

+ } @else { +
+ +
+

HUBO UN ERROR

+

No pudimos obtener el estado de tu compra

+ } +
diff --git a/src/app/features/store/pages/purchase-status-page/purchase-status-page.component.scss b/src/app/features/store/pages/purchase-status-page/purchase-status-page.component.scss new file mode 100644 index 0000000..63cace8 --- /dev/null +++ b/src/app/features/store/pages/purchase-status-page/purchase-status-page.component.scss @@ -0,0 +1,86 @@ +.status-content { + display: flex; + flex-direction: column; + align-items: center; + gap: 1rem; + text-align: center; + padding: 2rem 1rem; + + &__icon { + display: flex; + justify-content: center; + align-items: center; + width: 80px; + height: 80px; + border-radius: 50%; + font-size: 2rem; + margin-bottom: 1rem; + + &--success { + background-color: rgba(var(--color-success-rgb), 0.1); + color: var(--color-success); + border: 2px solid var(--color-success); + } + + &--warning { + background-color: rgba(var(--color-warning-rgb), 0.1); + color: var(--color-warning); + border: 2px solid var(--color-warning); + } + + &--error { + background-color: rgba(var(--color-danger-rgb), 0.1); + color: var(--color-danger); + border: 2px solid var(--color-danger); + } + + &--loading { + background-color: rgba(var(--color-primary-rgb), 0.1); + color: var(--color-primary); + border: 2px solid var(--color-primary); + } + } + + &__title { + font-size: 1.5rem; + font-weight: 700; + color: var(--color-primary); + margin: 0; + } + + &__subtitle { + font-size: 1rem; + color: var(--color-text-muted); + margin: 0; + } + + &__divider { + width: 100%; + border: none; + border-top: 1px solid var(--color-border); + margin: 1.5rem 0; + } + + &__message { + font-size: 0.9rem; + color: var(--color-text); + margin-bottom: 1.5rem; + } + + &__button { + display: inline-flex; + align-items: center; + gap: 0.5rem; + padding: 0.75rem 1.5rem; + border-radius: var(--border-radius-md); + background-color: #25D366; /* WhatsApp color */ + color: white; + font-weight: 600; + text-decoration: none; + transition: background-color 0.2s ease; + + &:hover { + background-color: #1EBE5D; + } + } +} 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 new file mode 100644 index 0000000..2fea395 --- /dev/null +++ b/src/app/features/store/pages/purchase-status-page/purchase-status-page.component.ts @@ -0,0 +1,65 @@ +import { ChangeDetectionStrategy, Component, inject, OnInit, signal } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { HttpClient } from '@angular/common/http'; +import { firstValueFrom } from 'rxjs'; + +import { environment } from '../../../../../environments/environment'; +import { TenantService } from '../../../../core/services/tenant.service'; + +@Component({ + selector: 'app-purchase-status-page', + standalone: true, + imports: [], + templateUrl: './purchase-status-page.component.html', + styleUrl: './purchase-status-page.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class PurchaseStatusPageComponent implements OnInit { + private readonly route = inject(ActivatedRoute); + private readonly router = inject(Router); + private readonly http = inject(HttpClient); + private readonly tenantService = inject(TenantService); + + protected readonly isLoading = signal(true); + protected readonly status = signal<'approved' | 'pending' | 'error'>('pending'); + + ngOnInit(): void { + const purchaseId = this.route.snapshot.paramMap.get('id'); + const tenant = this.tenantService.tenant(); + + if (!purchaseId || !tenant) { + void this.router.navigate(['/']); + return; + } + + this.checkPurchaseStatus(tenant.codigo, purchaseId); + } + + private async checkPurchaseStatus(tenantCode: string, purchaseId: string): Promise { + try { + this.isLoading.set(true); + const response = await firstValueFrom( + this.http.get<{ data: { payment_status: string } }>(`${environment.url}tenants/${tenantCode}/compras/${purchaseId}`) + ); + + const paymentStatus = response?.data?.payment_status; + + if (paymentStatus === 'approved') { + this.status.set('approved'); + } else { + this.status.set('pending'); + } + } catch (error) { + console.error('Failed to fetch purchase status:', error); + this.status.set('error'); + } finally { + this.isLoading.set(false); + } + } + + protected getWhatsAppLink(): string { + const phone = '5493416658247'; + const message = encodeURIComponent('Hola! Mi compra fue realizada con éxito.'); + return `https://wa.me/${phone}?text=${message}`; + } +} diff --git a/src/app/features/store/store.routes.ts b/src/app/features/store/store.routes.ts index 073ed14..804d26e 100644 --- a/src/app/features/store/store.routes.ts +++ b/src/app/features/store/store.routes.ts @@ -52,6 +52,19 @@ export const routes: Routes = [ import('./pages/checkout-page/checkout-page.component').then( (m) => m.CheckoutPageComponent ) + }, + { + path: 'checkout/status', + component: SimpleLayoutComponent, + children: [ + { + path: ':id', + loadComponent: () => + import('./pages/purchase-status-page/purchase-status-page.component').then( + (m) => m.PurchaseStatusPageComponent + ) + } + ] } ] }