Compare commits
3 Commits
e2605116c5
...
05ada5f613
| Author | SHA1 | Date | |
|---|---|---|---|
| 05ada5f613 | |||
| 5969818478 | |||
| c54eb105cb |
@@ -12,6 +12,11 @@ export interface CreatePurchasePayload {
|
|||||||
email: string;
|
email: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PurchaseStatusResponse {
|
||||||
|
payment_status: string | null;
|
||||||
|
status: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
@@ -20,12 +25,16 @@ export class CheckoutService {
|
|||||||
|
|
||||||
async createPurchase(tenantCode: string, payload: CreatePurchasePayload): Promise<{ id: number }> {
|
async createPurchase(tenantCode: string, payload: CreatePurchasePayload): Promise<{ id: number }> {
|
||||||
const response = await firstValueFrom(
|
const response = await firstValueFrom(
|
||||||
this.http.post<{ data: { id: number } }>(`${environment.url}tenants/${tenantCode}/compras`, payload)
|
this.http.post<{ data?: { id: number }; id?: number }>(`${environment.url}tenants/${tenantCode}/compras`, payload)
|
||||||
);
|
);
|
||||||
if (!response?.data) {
|
|
||||||
|
const purchase = this.extractResponseData<{ id: number }>(response);
|
||||||
|
|
||||||
|
if (!purchase?.id) {
|
||||||
throw new Error('Error al crear la compra.');
|
throw new Error('Error al crear la compra.');
|
||||||
}
|
}
|
||||||
return response.data;
|
|
||||||
|
return purchase;
|
||||||
}
|
}
|
||||||
|
|
||||||
async generatePaymentIntent(tenantCode: string, purchaseId: number, method: 'qr' | 'transfer' | 'telepagos'): Promise<any> {
|
async generatePaymentIntent(tenantCode: string, purchaseId: number, method: 'qr' | 'transfer' | 'telepagos'): Promise<any> {
|
||||||
@@ -38,23 +47,49 @@ export class CheckoutService {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
async finalizePurchase(tenantCode: string, purchaseId: number): Promise<{ payment_status: string; status: string }> {
|
async finalizePurchase(tenantCode: string, purchaseId: number): Promise<PurchaseStatusResponse> {
|
||||||
const response = await firstValueFrom(
|
const response = await firstValueFrom(
|
||||||
this.http.post<{ data: { payment_status: string; status: string } }>(`${environment.url}tenants/${tenantCode}/compras/${purchaseId}/finalize`, {})
|
this.http.post<{ data?: PurchaseStatusResponse } | PurchaseStatusResponse>(`${environment.url}tenants/${tenantCode}/compras/${purchaseId}/finalize`, {})
|
||||||
);
|
);
|
||||||
if (!response?.data) {
|
|
||||||
|
const purchase = this.extractResponseData<PurchaseStatusResponse>(response);
|
||||||
|
|
||||||
|
if (!purchase) {
|
||||||
throw new Error('Error al finalizar la compra.');
|
throw new Error('Error al finalizar la compra.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
payment_status: purchase.payment_status ?? null,
|
||||||
|
status: purchase.status ?? null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async getPurchase(tenantCode: string, purchaseId: string | number): Promise<PurchaseStatusResponse> {
|
||||||
|
const response = await firstValueFrom(
|
||||||
|
this.http.get<{ data?: PurchaseStatusResponse } | PurchaseStatusResponse>(`${environment.url}tenants/${tenantCode}/compras/${purchaseId}`)
|
||||||
|
);
|
||||||
|
|
||||||
|
const purchase = this.extractResponseData<PurchaseStatusResponse>(response);
|
||||||
|
|
||||||
|
if (!purchase) {
|
||||||
|
throw new Error('Error al obtener la compra.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
payment_status: purchase.payment_status ?? null,
|
||||||
|
status: purchase.status ?? null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private extractResponseData<T>(response: T | { data?: T } | null | undefined): T | null {
|
||||||
|
if (!response) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof response === 'object' && 'data' in response && response.data) {
|
||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getPurchase(tenantCode: string, purchaseId: string | number): Promise<{ payment_status: string }> {
|
return response as T;
|
||||||
const response = await firstValueFrom(
|
|
||||||
this.http.get<{ data: { payment_status: string } }>(`${environment.url}tenants/${tenantCode}/compras/${purchaseId}`)
|
|
||||||
);
|
|
||||||
if (!response?.data) {
|
|
||||||
throw new Error('Error al obtener la compra.');
|
|
||||||
}
|
|
||||||
return response.data;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -243,23 +243,11 @@ export class CheckoutPageComponent implements OnInit {
|
|||||||
|
|
||||||
protected async onFinalize(): Promise<void> {
|
protected async onFinalize(): Promise<void> {
|
||||||
const purchaseId = this.createdPurchaseId();
|
const purchaseId = this.createdPurchaseId();
|
||||||
const tenant = this.tenantService.tenant();
|
|
||||||
|
|
||||||
if (!purchaseId || !tenant) {
|
if (!purchaseId) {
|
||||||
return;
|
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]);
|
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ describe('LoginPageComponent', () => {
|
|||||||
TestBed.resetTestingModule();
|
TestBed.resetTestingModule();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('submits credentials and navigates to home on success', async () => {
|
it('submits credentials and redirects to home with a full page reload on success', async () => {
|
||||||
const authService = {
|
const authService = {
|
||||||
login: vi.fn().mockReturnValue(
|
login: vi.fn().mockReturnValue(
|
||||||
of({
|
of({
|
||||||
@@ -30,6 +30,7 @@ describe('LoginPageComponent', () => {
|
|||||||
const component = fixture.componentInstance as any;
|
const component = fixture.componentInstance as any;
|
||||||
const router = TestBed.inject(Router);
|
const router = TestBed.inject(Router);
|
||||||
const navigateSpy = vi.spyOn(router, 'navigate').mockResolvedValue(true);
|
const navigateSpy = vi.spyOn(router, 'navigate').mockResolvedValue(true);
|
||||||
|
const redirectSpy = vi.spyOn(component, 'redirectToHome').mockImplementation(() => undefined);
|
||||||
|
|
||||||
component.form.setValue({
|
component.form.setValue({
|
||||||
email: 'ada@example.com',
|
email: 'ada@example.com',
|
||||||
@@ -42,7 +43,8 @@ describe('LoginPageComponent', () => {
|
|||||||
email: 'ada@example.com',
|
email: 'ada@example.com',
|
||||||
password: 'secret123'
|
password: 'secret123'
|
||||||
});
|
});
|
||||||
expect(navigateSpy).toHaveBeenCalledWith(['/']);
|
expect(redirectSpy).toHaveBeenCalled();
|
||||||
|
expect(navigateSpy).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('surfaces backend login errors', async () => {
|
it('surfaces backend login errors', async () => {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { DOCUMENT } from '@angular/common';
|
||||||
import { ChangeDetectionStrategy, Component, inject, signal } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, inject, signal } from '@angular/core';
|
||||||
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
|
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
@@ -20,6 +21,7 @@ export class LoginPageComponent {
|
|||||||
private readonly formBuilder = inject(FormBuilder);
|
private readonly formBuilder = inject(FormBuilder);
|
||||||
private readonly router = inject(Router);
|
private readonly router = inject(Router);
|
||||||
private readonly authService = inject(AuthService);
|
private readonly authService = inject(AuthService);
|
||||||
|
private readonly document = inject(DOCUMENT);
|
||||||
|
|
||||||
private readonly submittedState = signal(false);
|
private readonly submittedState = signal(false);
|
||||||
private readonly serverErrorState = signal<string | null>(null);
|
private readonly serverErrorState = signal<string | null>(null);
|
||||||
@@ -51,7 +53,7 @@ export class LoginPageComponent {
|
|||||||
this.authService.login(this.form.getRawValue()).subscribe({
|
this.authService.login(this.form.getRawValue()).subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
this.isSubmittingState.set(false);
|
this.isSubmittingState.set(false);
|
||||||
void this.router.navigate(['/']);
|
this.redirectToHome();
|
||||||
},
|
},
|
||||||
error: (error: unknown) => {
|
error: (error: unknown) => {
|
||||||
this.isSubmittingState.set(false);
|
this.isSubmittingState.set(false);
|
||||||
@@ -100,6 +102,11 @@ export class LoginPageComponent {
|
|||||||
return 'El valor ingresado no es valido.';
|
return 'El valor ingresado no es valido.';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected redirectToHome(): void {
|
||||||
|
const homeUrl = this.router.serializeUrl(this.router.createUrlTree(['/']));
|
||||||
|
this.document.location.assign(homeUrl);
|
||||||
|
}
|
||||||
|
|
||||||
private resolveErrorMessage(error: unknown): string {
|
private resolveErrorMessage(error: unknown): string {
|
||||||
const errorPayload =
|
const errorPayload =
|
||||||
typeof error === 'object' && error !== null && 'error' in error
|
typeof error === 'object' && error !== null && 'error' in error
|
||||||
|
|||||||
@@ -30,20 +30,106 @@
|
|||||||
</app-button>
|
</app-button>
|
||||||
</div>
|
</div>
|
||||||
} @else if (status() === 'pending') {
|
} @else if (status() === 'pending') {
|
||||||
|
<div class="status-content__section status-content__section--primary">
|
||||||
<div class="status-content__icon status-content__icon--warning">
|
<div class="status-content__icon status-content__icon--warning">
|
||||||
<i class="fa-solid fa-clock"></i>
|
<i class="fa-solid fa-clock"></i>
|
||||||
</div>
|
</div>
|
||||||
<h2 class="status-content__title">PAGO PENDIENTE</h2>
|
<h2 class="status-content__title">ESTAMOS VERIFICANDO TU PAGO</h2>
|
||||||
<p class="status-content__subtitle">Estamos esperando la confirmación de tu pago</p>
|
<p class="status-content__subtitle">Tu compra ya fue registrada y estamos esperando la confirmación del pago.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<hr class="status-content__divider" />
|
<hr class="status-content__divider" />
|
||||||
|
|
||||||
<p class="status-content__message">Una vez que el pago sea procesado, actualizaremos el estado de tu compra.</p>
|
<div class="status-content__section status-content__section--secondary">
|
||||||
} @else {
|
<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">
|
<div class="status-content__icon status-content__icon--error">
|
||||||
<i class="fa-solid fa-triangle-exclamation"></i>
|
<i class="fa-solid fa-triangle-exclamation"></i>
|
||||||
</div>
|
</div>
|
||||||
<h2 class="status-content__title">HUBO UN ERROR</h2>
|
<h2 class="status-content__title">NO PUDIMOS CONFIRMAR EL PAGO</h2>
|
||||||
<p class="status-content__subtitle">No pudimos obtener el estado de tu compra</p>
|
<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>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@@ -47,15 +47,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&--warning {
|
&--warning {
|
||||||
background-color: rgba(var(--color-warning-rgb), 0.1);
|
background-color: color-mix(in srgb, white 92%, var(--color-primary));
|
||||||
color: var(--color-warning);
|
color: var(--color-primary) !important;
|
||||||
border: 2px solid var(--color-warning);
|
border: 1.5px solid color-mix(in srgb, white 20%, var(--color-primary));
|
||||||
}
|
}
|
||||||
|
|
||||||
&--error {
|
&--error {
|
||||||
background-color: rgba(var(--color-danger-rgb), 0.1);
|
background-color: color-mix(in srgb, white 92%, var(--color-primary));
|
||||||
color: var(--color-danger);
|
color: var(--color-primary) !important;
|
||||||
border: 2px solid var(--color-danger);
|
border: 1.5px solid color-mix(in srgb, white 20%, var(--color-primary));
|
||||||
}
|
}
|
||||||
|
|
||||||
&--loading {
|
&--loading {
|
||||||
@@ -79,6 +79,7 @@
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
line-height: 1.35;
|
line-height: 1.35;
|
||||||
color: #8a8a8a;
|
color: #8a8a8a;
|
||||||
|
max-width: 320px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__divider {
|
&__divider {
|
||||||
@@ -92,7 +93,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__message {
|
&__message {
|
||||||
max-width: 280px;
|
max-width: 320px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 325;
|
font-weight: 325;
|
||||||
@@ -100,10 +101,29 @@
|
|||||||
color: var(--color-text);
|
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 {
|
&__button {
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__button-control {
|
||||||
|
min-width: 180px;
|
||||||
|
}
|
||||||
|
|
||||||
&__button-icon {
|
&__button-icon {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -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 { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
|
||||||
|
import { CheckoutService, PurchaseStatusResponse } from '../../../../core/services/checkout.service';
|
||||||
import { TenantService } from '../../../../core/services/tenant.service';
|
import { TenantService } from '../../../../core/services/tenant.service';
|
||||||
import { CheckoutService } from '../../../../core/services/checkout.service';
|
|
||||||
import { ButtonComponent } from '../../../../shared/components/button/button.component';
|
import { ButtonComponent } from '../../../../shared/components/button/button.component';
|
||||||
|
|
||||||
|
type PurchaseStatusView = 'approved' | 'pending' | 'rejected' | 'error';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-purchase-status-page',
|
selector: 'app-purchase-status-page',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
@@ -13,14 +15,20 @@ import { ButtonComponent } from '../../../../shared/components/button/button.com
|
|||||||
styleUrl: './purchase-status-page.component.scss',
|
styleUrl: './purchase-status-page.component.scss',
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
})
|
})
|
||||||
export class PurchaseStatusPageComponent implements OnInit {
|
export class PurchaseStatusPageComponent implements OnInit, OnDestroy {
|
||||||
private readonly route = inject(ActivatedRoute);
|
private readonly route = inject(ActivatedRoute);
|
||||||
private readonly router = inject(Router);
|
private readonly router = inject(Router);
|
||||||
private readonly checkoutService = inject(CheckoutService);
|
private readonly checkoutService = inject(CheckoutService);
|
||||||
private readonly tenantService = inject(TenantService);
|
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 isLoading = signal(true);
|
||||||
protected readonly status = signal<'approved' | 'pending' | 'error'>('pending');
|
protected readonly isRefreshing = signal(false);
|
||||||
|
protected readonly status = signal<PurchaseStatusView>('pending');
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
const purchaseId = this.route.snapshot.paramMap.get('id');
|
const purchaseId = this.route.snapshot.paramMap.get('id');
|
||||||
@@ -31,31 +39,86 @@ export class PurchaseStatusPageComponent implements OnInit {
|
|||||||
return;
|
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> {
|
ngOnDestroy(): void {
|
||||||
try {
|
this.clearScheduledPoll();
|
||||||
this.isLoading.set(true);
|
}
|
||||||
const purchase = await this.checkoutService.getPurchase(tenantCode, purchaseId);
|
|
||||||
const paymentStatus = purchase.payment_status;
|
|
||||||
|
|
||||||
if (paymentStatus === 'approved') {
|
protected retryStatusCheck(): void {
|
||||||
this.status.set('approved');
|
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 {
|
} else {
|
||||||
this.status.set('pending');
|
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) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch purchase status:', error);
|
console.error('Failed to fetch purchase status:', error);
|
||||||
this.status.set('error');
|
this.status.set('error');
|
||||||
} finally {
|
} finally {
|
||||||
this.isLoading.set(false);
|
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 {
|
protected getWhatsAppLink(): string {
|
||||||
const phone = '5493416658247';
|
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}`;
|
return `https://wa.me/${phone}?text=${message}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user