feat(purchase-status): implement purchase status page with loading and error handling
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
(paymentMethodChange)="selectPaymentMethod($event)"
|
||||
(copyTransferValue)="copyTransferValue($event.field, $event.value)"
|
||||
(cancelStep)="onCancel()"
|
||||
(finalize)="onFinalize()"
|
||||
/>
|
||||
</app-step>
|
||||
</app-stepper>
|
||||
|
||||
@@ -240,4 +240,26 @@ export class CheckoutPageComponent implements OnInit {
|
||||
this.copiedTransferField.set(null);
|
||||
}
|
||||
}
|
||||
|
||||
protected async onFinalize(): Promise<void> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,6 +142,7 @@
|
||||
type="button"
|
||||
hostClass="checkout-payment__action-btn"
|
||||
[disabled]="isGeneratingIntent()"
|
||||
(click)="finalize.emit()"
|
||||
>
|
||||
Finalizar compra
|
||||
</app-button>
|
||||
|
||||
@@ -25,6 +25,7 @@ export class CheckoutPaymentStepComponent {
|
||||
readonly paymentMethodChange = output<PaymentMethod>();
|
||||
readonly copyTransferValue = output<{ field: TransferField; value: string }>();
|
||||
readonly cancelStep = output<void>();
|
||||
readonly finalize = output<void>();
|
||||
|
||||
protected selectPaymentMethod(method: PaymentMethod): void {
|
||||
this.paymentMethodChange.emit(method);
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<div class="status-content">
|
||||
@if (isLoading()) {
|
||||
<div class="status-content__icon status-content__icon--loading">
|
||||
<i class="fa-solid fa-spinner fa-spin"></i>
|
||||
</div>
|
||||
<h2 class="status-content__title">Cargando estado...</h2>
|
||||
} @else if (status() === 'approved') {
|
||||
<div class="status-content__icon status-content__icon--success">
|
||||
<i class="fa-solid fa-check"></i>
|
||||
</div>
|
||||
<h2 class="status-content__title">COMPRA REALIZADA!</h2>
|
||||
<p class="status-content__subtitle">Tu compra fue realizada con éxito</p>
|
||||
|
||||
<hr class="status-content__divider" />
|
||||
|
||||
<p class="status-content__message">Comunicate con nosotros para coordinar el envío.</p>
|
||||
|
||||
<a [href]="getWhatsAppLink()" target="_blank" rel="noopener noreferrer" class="status-content__button">
|
||||
<i class="fa-brands fa-whatsapp"></i> WhatsApp
|
||||
</a>
|
||||
} @else if (status() === 'pending') {
|
||||
<div class="status-content__icon status-content__icon--warning">
|
||||
<i class="fa-solid fa-clock"></i>
|
||||
</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>
|
||||
<h2 class="status-content__title">HUBO UN ERROR</h2>
|
||||
<p class="status-content__subtitle">No pudimos obtener el estado de tu compra</p>
|
||||
}
|
||||
</div>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<void> {
|
||||
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}`;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user