feat(checkout): improve error handling for expired purchases and refactor toast service usage

This commit is contained in:
2026-08-21 10:29:47 -03:00
parent 11df4dbe72
commit f056c32f49
2 changed files with 59 additions and 8 deletions

View File

@@ -14,7 +14,6 @@ import { GlobalLoadingService } from '../../../../core/services/global-loading/g
import { TenantService } from '../../../../core/services/tenant.service'; import { TenantService } from '../../../../core/services/tenant.service';
import { ToastService } from '../../../../core/services/toast.service'; import { ToastService } from '../../../../core/services/toast.service';
import { CartEditingPolicy } from '../../../../core/services/tenant.interface'; import { CartEditingPolicy } from '../../../../core/services/tenant.interface';
import { ToastService } from '../../../../core/services/toast.service';
import { CheckoutPageComponent } from './checkout-page.component'; import { CheckoutPageComponent } from './checkout-page.component';
describe('CheckoutPageComponent payment validation', () => { describe('CheckoutPageComponent payment validation', () => {

View File

@@ -69,7 +69,6 @@ export class CheckoutPageComponent implements OnInit, OnDestroy {
private readonly globalLoadingService = inject(GlobalLoadingService); private readonly globalLoadingService = inject(GlobalLoadingService);
private readonly toastService = inject(ToastService); private readonly toastService = inject(ToastService);
private readonly qrPollingIntervalMs = 5_000; private readonly qrPollingIntervalMs = 5_000;
private readonly toastService = inject(ToastService);
private readonly qrPollingMaxAttempts = 9; private readonly qrPollingMaxAttempts = 9;
private readonly transferPollingIntervalMs = 3_000; private readonly transferPollingIntervalMs = 3_000;
private readonly transferPollingMaxAttempts = 4; private readonly transferPollingMaxAttempts = 4;
@@ -194,9 +193,9 @@ export class CheckoutPageComponent implements OnInit, OnDestroy {
return; return;
} }
<<<<<<< HEAD const tenant = this.tenantService.tenant();
if (editing && !this.canModifyCart()) { const purchaseId = this.createdPurchaseId();
const tenant = this.tenantService.tenant(); if (!tenant || !purchaseId) {
return; return;
} }
@@ -217,9 +216,9 @@ export class CheckoutPageComponent implements OnInit, OnDestroy {
} finally { } finally {
this.globalLoadingService.stop(); this.globalLoadingService.stop();
this.isRestoringCart.set(false); this.isRestoringCart.set(false);
>>>>>>> feature/restore-cart-on-modify
} }
} }
protected async onStep1Continue(): Promise<void> { protected async onStep1Continue(): Promise<void> {
if (this.form.invalid || this.isUpdatingPurchase()) return; if (this.form.invalid || this.isUpdatingPurchase()) return;
@@ -409,10 +408,16 @@ export class CheckoutPageComponent implements OnInit, OnDestroy {
} }
if (purchase.status === 'paid') { if (purchase.status === 'paid') {
this.navigateToPurchaseStatus(purchaseId);
return;
}
this.scheduleTransferPoll(runId); this.scheduleTransferPoll(runId);
console.error('Failed to submit transfer payment for review:', error); } catch (error) {
this.showRequestError(error, 'No se pudo enviar el pago para su validación.'); const expired = this.handleCheckoutError(
error,
'No se pudo enviar el pago para su validación.',
);
if (!expired && runId === this.transferPollingRunId) { if (!expired && runId === this.transferPollingRunId) {
this.transferValidationStatus.set('error'); this.transferValidationStatus.set('error');
@@ -660,4 +665,51 @@ export class CheckoutPageComponent implements OnInit, OnDestroy {
this.toastService.danger(message); this.toastService.danger(message);
} }
private handleCheckoutError(error: unknown, fallbackMessage: string): boolean {
if (!(error instanceof HttpErrorResponse)) {
this.toastService.danger(fallbackMessage);
return false;
}
const response = error.error as ApiErrorResponse | null;
if (response?.code === 'purchase.expired' || this.hasExpiredPurchase()) {
this.navigationStarted = true;
this.stopQrPolling();
this.stopTransferPolling();
this.toastService.danger(
response?.code === 'purchase.expired' && response.message
? response.message
: 'La compra venció. Iniciá una nueva compra.',
);
void this.router.navigate(['/']);
return true;
}
const validationMessage = response?.errors
? Object.values(response.errors).flat().find(Boolean)
: undefined;
this.toastService.danger(validationMessage ?? response?.message ?? fallbackMessage);
return false;
}
private hasExpiredPurchase(): boolean {
const purchase = this.createdPurchase();
if (!purchase) {
return false;
}
if (purchase.status === 'expired') {
return true;
}
if (!purchase.expires_at) {
return false;
}
return Date.parse(purchase.expires_at) <= Date.now();
}
} }