feat(purchase-status): enhance purchase status handling with polling and improved UI feedback
This commit is contained in:
@@ -243,23 +243,11 @@ export class CheckoutPageComponent implements OnInit {
|
||||
|
||||
protected async onFinalize(): Promise<void> {
|
||||
const purchaseId = this.createdPurchaseId();
|
||||
const tenant = this.tenantService.tenant();
|
||||
|
||||
if (!purchaseId || !tenant) {
|
||||
if (!purchaseId) {
|
||||
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);
|
||||
}
|
||||
void this.router.navigate(['/checkout/status', purchaseId]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,20 +30,106 @@
|
||||
</app-button>
|
||||
</div>
|
||||
} @else if (status() === 'pending') {
|
||||
<div class="status-content__icon status-content__icon--warning">
|
||||
<i class="fa-solid fa-clock"></i>
|
||||
<div class="status-content__section status-content__section--primary">
|
||||
<div class="status-content__icon status-content__icon--warning">
|
||||
<i class="fa-solid fa-clock"></i>
|
||||
</div>
|
||||
<h2 class="status-content__title">ESTAMOS VERIFICANDO TU PAGO</h2>
|
||||
<p class="status-content__subtitle">Tu compra ya fue registrada y estamos esperando la confirmación del pago.</p>
|
||||
</div>
|
||||
<h2 class="status-content__title">PAGO PENDIENTE</h2>
|
||||
<p class="status-content__subtitle">Estamos esperando la confirmación de tu pago</p>
|
||||
|
||||
<hr class="status-content__divider" />
|
||||
|
||||
<p class="status-content__message">Una vez que el pago sea procesado, actualizaremos el estado de tu compra.</p>
|
||||
} @else {
|
||||
<div class="status-content__icon status-content__icon--error">
|
||||
<i class="fa-solid fa-triangle-exclamation"></i>
|
||||
<div class="status-content__section status-content__section--secondary">
|
||||
<p class="status-content__message">Esta pantalla se actualiza automáticamente cuando el pago impacta.</p>
|
||||
@if (isRefreshing()) {
|
||||
<p class="status-content__hint">Actualizando estado...</p>
|
||||
}
|
||||
|
||||
<app-button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
hostClass="status-content__button d-block"
|
||||
buttonClass="status-content__button-control d-inline-flex align-items-center justify-content-center py-2"
|
||||
[disabled]="isRefreshing()"
|
||||
(click)="retryStatusCheck()"
|
||||
>
|
||||
Actualizar estado
|
||||
</app-button>
|
||||
</div>
|
||||
} @else if (status() === 'rejected') {
|
||||
<div class="status-content__section status-content__section--primary">
|
||||
<div class="status-content__icon status-content__icon--error">
|
||||
<i class="fa-solid fa-triangle-exclamation"></i>
|
||||
</div>
|
||||
<h2 class="status-content__title">NO PUDIMOS CONFIRMAR EL PAGO</h2>
|
||||
<p class="status-content__subtitle">Revisá el medio de pago o comunicate con nosotros para continuar.</p>
|
||||
</div>
|
||||
|
||||
<hr class="status-content__divider" />
|
||||
|
||||
<div class="status-content__section status-content__section--secondary">
|
||||
<p class="status-content__message">Si ya pagaste, podés reintentar la consulta o escribirnos para revisarlo.</p>
|
||||
|
||||
<div class="status-content__actions">
|
||||
<app-button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
hostClass="status-content__button d-block"
|
||||
buttonClass="status-content__button-control d-inline-flex align-items-center justify-content-center py-2"
|
||||
(click)="retryStatusCheck()"
|
||||
>
|
||||
Reintentar
|
||||
</app-button>
|
||||
|
||||
<app-button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
hostClass="status-content__button d-block"
|
||||
buttonClass="status-content__button-control d-inline-flex align-items-center justify-content-center gap-2 py-2"
|
||||
(click)="openWhatsApp()"
|
||||
>
|
||||
<i class="fa-brands fa-whatsapp status-content__button-icon"></i>
|
||||
<span>WhatsApp</span>
|
||||
</app-button>
|
||||
</div>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="status-content__section status-content__section--primary">
|
||||
<div class="status-content__icon status-content__icon--error">
|
||||
<i class="fa-solid fa-triangle-exclamation"></i>
|
||||
</div>
|
||||
<h2 class="status-content__title">NO PUDIMOS OBTENER EL ESTADO</h2>
|
||||
<p class="status-content__subtitle">Hubo un problema al consultar tu compra.</p>
|
||||
</div>
|
||||
|
||||
<hr class="status-content__divider" />
|
||||
|
||||
<div class="status-content__section status-content__section--secondary">
|
||||
<p class="status-content__message">Reintentá en unos segundos. Si el problema sigue, comunicate con nosotros.</p>
|
||||
|
||||
<div class="status-content__actions">
|
||||
<app-button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
hostClass="status-content__button d-block"
|
||||
buttonClass="status-content__button-control d-inline-flex align-items-center justify-content-center py-2"
|
||||
(click)="retryStatusCheck()"
|
||||
>
|
||||
Reintentar
|
||||
</app-button>
|
||||
|
||||
<app-button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
hostClass="status-content__button d-block"
|
||||
buttonClass="status-content__button-control d-inline-flex align-items-center justify-content-center gap-2 py-2"
|
||||
(click)="openWhatsApp()"
|
||||
>
|
||||
<i class="fa-brands fa-whatsapp status-content__button-icon"></i>
|
||||
<span>WhatsApp</span>
|
||||
</app-button>
|
||||
</div>
|
||||
</div>
|
||||
<h2 class="status-content__title">HUBO UN ERROR</h2>
|
||||
<p class="status-content__subtitle">No pudimos obtener el estado de tu compra</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -79,6 +79,7 @@
|
||||
font-weight: 700;
|
||||
line-height: 1.35;
|
||||
color: #8a8a8a;
|
||||
max-width: 320px;
|
||||
}
|
||||
|
||||
&__divider {
|
||||
@@ -92,7 +93,7 @@
|
||||
}
|
||||
|
||||
&__message {
|
||||
max-width: 280px;
|
||||
max-width: 320px;
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
font-weight: 325;
|
||||
@@ -100,10 +101,29 @@
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
&__hint {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
color: #8a8a8a;
|
||||
}
|
||||
|
||||
&__actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__button {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
&__button-control {
|
||||
min-width: 180px;
|
||||
}
|
||||
|
||||
&__button-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -113,4 +133,4 @@
|
||||
font-size: 20px;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { ChangeDetectionStrategy, Component, inject, OnInit, signal } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit, inject, signal } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
|
||||
import { CheckoutService, PurchaseStatusResponse } from '../../../../core/services/checkout.service';
|
||||
import { TenantService } from '../../../../core/services/tenant.service';
|
||||
import { CheckoutService } from '../../../../core/services/checkout.service';
|
||||
import { ButtonComponent } from '../../../../shared/components/button/button.component';
|
||||
|
||||
type PurchaseStatusView = 'approved' | 'pending' | 'rejected' | 'error';
|
||||
|
||||
@Component({
|
||||
selector: 'app-purchase-status-page',
|
||||
standalone: true,
|
||||
@@ -13,14 +15,20 @@ import { ButtonComponent } from '../../../../shared/components/button/button.com
|
||||
styleUrl: './purchase-status-page.component.scss',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class PurchaseStatusPageComponent implements OnInit {
|
||||
export class PurchaseStatusPageComponent implements OnInit, OnDestroy {
|
||||
private readonly route = inject(ActivatedRoute);
|
||||
private readonly router = inject(Router);
|
||||
private readonly checkoutService = inject(CheckoutService);
|
||||
private readonly tenantService = inject(TenantService);
|
||||
|
||||
private readonly pollIntervalMs = 5000;
|
||||
private purchaseId: string | null = null;
|
||||
private tenantCode: string | null = null;
|
||||
private pollTimeoutId: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
protected readonly isLoading = signal(true);
|
||||
protected readonly status = signal<'approved' | 'pending' | 'error'>('pending');
|
||||
protected readonly isRefreshing = signal(false);
|
||||
protected readonly status = signal<PurchaseStatusView>('pending');
|
||||
|
||||
ngOnInit(): void {
|
||||
const purchaseId = this.route.snapshot.paramMap.get('id');
|
||||
@@ -31,35 +39,90 @@ export class PurchaseStatusPageComponent implements OnInit {
|
||||
return;
|
||||
}
|
||||
|
||||
this.checkPurchaseStatus(tenant.codigo, purchaseId);
|
||||
this.purchaseId = purchaseId;
|
||||
this.tenantCode = tenant.codigo;
|
||||
|
||||
void this.refreshStatus(true);
|
||||
}
|
||||
|
||||
private async checkPurchaseStatus(tenantCode: string, purchaseId: string): Promise<void> {
|
||||
try {
|
||||
this.isLoading.set(true);
|
||||
const purchase = await this.checkoutService.getPurchase(tenantCode, purchaseId);
|
||||
const paymentStatus = purchase.payment_status;
|
||||
ngOnDestroy(): void {
|
||||
this.clearScheduledPoll();
|
||||
}
|
||||
|
||||
if (paymentStatus === 'approved') {
|
||||
this.status.set('approved');
|
||||
} else {
|
||||
this.status.set('pending');
|
||||
protected retryStatusCheck(): void {
|
||||
void this.refreshStatus(false);
|
||||
}
|
||||
|
||||
private async refreshStatus(showLoader: boolean): Promise<void> {
|
||||
if (!this.purchaseId || !this.tenantCode) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.clearScheduledPoll();
|
||||
|
||||
if (showLoader) {
|
||||
this.isLoading.set(true);
|
||||
} else {
|
||||
this.isRefreshing.set(true);
|
||||
}
|
||||
|
||||
try {
|
||||
const purchase = await this.checkoutService.getPurchase(this.tenantCode, this.purchaseId);
|
||||
const resolvedStatus = this.resolveStatus(purchase);
|
||||
|
||||
this.status.set(resolvedStatus);
|
||||
|
||||
if (resolvedStatus === 'pending') {
|
||||
this.scheduleNextPoll();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch purchase status:', error);
|
||||
this.status.set('error');
|
||||
} finally {
|
||||
this.isLoading.set(false);
|
||||
this.isRefreshing.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
private resolveStatus(purchase: PurchaseStatusResponse): PurchaseStatusView {
|
||||
if (purchase.payment_status === 'approved' || purchase.status === 'paid') {
|
||||
return 'approved';
|
||||
}
|
||||
|
||||
if (purchase.payment_status === 'rejected' || purchase.status === 'cancelled') {
|
||||
return 'rejected';
|
||||
}
|
||||
|
||||
return 'pending';
|
||||
}
|
||||
|
||||
private scheduleNextPoll(): void {
|
||||
if (this.pollTimeoutId !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.pollTimeoutId = setTimeout(() => {
|
||||
this.pollTimeoutId = null;
|
||||
void this.refreshStatus(false);
|
||||
}, this.pollIntervalMs);
|
||||
}
|
||||
|
||||
private clearScheduledPoll(): void {
|
||||
if (this.pollTimeoutId === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
clearTimeout(this.pollTimeoutId);
|
||||
this.pollTimeoutId = null;
|
||||
}
|
||||
|
||||
protected getWhatsAppLink(): string {
|
||||
const phone = '5493416658247';
|
||||
const message = encodeURIComponent('Hola! Mi compra fue realizada con \u00e9xito.');
|
||||
const message = encodeURIComponent('Hola! Mi compra fue realizada con \u00E9xito.');
|
||||
return `https://wa.me/${phone}?text=${message}`;
|
||||
}
|
||||
|
||||
protected openWhatsApp(): void {
|
||||
window.open(this.getWhatsAppLink(), '_blank', 'noopener,noreferrer');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user