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 { ToastService } from '../../../../core/services/toast.service';
import { CartEditingPolicy } from '../../../../core/services/tenant.interface';
import { ToastService } from '../../../../core/services/toast.service';
import { CheckoutPageComponent } from './checkout-page.component';
describe('CheckoutPageComponent payment validation', () => {

View File

@@ -69,7 +69,6 @@ export class CheckoutPageComponent implements OnInit, OnDestroy {
private readonly globalLoadingService = inject(GlobalLoadingService);
private readonly toastService = inject(ToastService);
private readonly qrPollingIntervalMs = 5_000;
private readonly toastService = inject(ToastService);
private readonly qrPollingMaxAttempts = 9;
private readonly transferPollingIntervalMs = 3_000;
private readonly transferPollingMaxAttempts = 4;
@@ -194,9 +193,9 @@ export class CheckoutPageComponent implements OnInit, OnDestroy {
return;
}
<<<<<<< HEAD
if (editing && !this.canModifyCart()) {
const tenant = this.tenantService.tenant();
const tenant = this.tenantService.tenant();
const purchaseId = this.createdPurchaseId();
if (!tenant || !purchaseId) {
return;
}
@@ -217,9 +216,9 @@ export class CheckoutPageComponent implements OnInit, OnDestroy {
} finally {
this.globalLoadingService.stop();
this.isRestoringCart.set(false);
>>>>>>> feature/restore-cart-on-modify
}
}
protected async onStep1Continue(): Promise<void> {
if (this.form.invalid || this.isUpdatingPurchase()) return;
@@ -409,10 +408,16 @@ export class CheckoutPageComponent implements OnInit, OnDestroy {
}
if (purchase.status === 'paid') {
this.navigateToPurchaseStatus(purchaseId);
return;
}
this.scheduleTransferPoll(runId);
console.error('Failed to submit transfer payment for review:', error);
this.showRequestError(error, 'No se pudo enviar el pago para su validación.');
} catch (error) {
const expired = this.handleCheckoutError(
error,
'No se pudo enviar el pago para su validación.',
);
if (!expired && runId === this.transferPollingRunId) {
this.transferValidationStatus.set('error');
@@ -660,4 +665,51 @@ export class CheckoutPageComponent implements OnInit, OnDestroy {
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();
}
}