Volvé a ingresar más tarde. Si el problema sigue, comunicate con nosotros.
+ Volvé a ingresar más tarde. Si el problema sigue, comunicate con nosotros.
+
diff --git a/src/app/features/store/pages/purchase-status-page/purchase-status-page.component.spec.ts b/src/app/features/store/pages/purchase-status-page/purchase-status-page.component.spec.ts
index 6dcf23c..1b8fcdc 100644
--- a/src/app/features/store/pages/purchase-status-page/purchase-status-page.component.spec.ts
+++ b/src/app/features/store/pages/purchase-status-page/purchase-status-page.component.spec.ts
@@ -67,9 +67,13 @@ describe('PurchaseStatusPageComponent', () => {
TestBed.resetTestingModule();
});
- async function render(hasGeneratedTickets: boolean, forcedStatus?: string) {
+ async function render(
+ hasGeneratedTickets: boolean,
+ forcedStatus?: string,
+ purchaseResponse = purchase(hasGeneratedTickets),
+ ) {
const checkoutService = {
- getPurchase: vi.fn().mockResolvedValue(purchase(hasGeneratedTickets)),
+ getPurchase: vi.fn().mockResolvedValue(purchaseResponse),
withCustomLoading() {
return this;
},
@@ -91,9 +95,7 @@ describe('PurchaseStatusPageComponent', () => {
useValue: {
snapshot: {
paramMap: convertToParamMap({ id: '42' }),
- queryParamMap: convertToParamMap(
- forcedStatus ? { status: forcedStatus } : {},
- ),
+ queryParamMap: convertToParamMap(forcedStatus ? { status: forcedStatus } : {}),
},
},
},
@@ -159,6 +161,29 @@ describe('PurchaseStatusPageComponent', () => {
expect(checkoutService.getPurchase).not.toHaveBeenCalled();
});
+ it('shows the primary transfer candidate issue while the purchase is in review', async () => {
+ const { element } = await render(false, undefined, {
+ status: 'in_review',
+ payment_verification: {
+ status: 'candidate',
+ candidate_count: 2,
+ primary: {
+ reason: 'exact_dni_near_amount',
+ payment_amount: '49000.00',
+ purchase_amount: '50000.00',
+ amount_difference: '1000.00',
+ confidence: 'high',
+ detected_at: '2026-08-27T18:00:00-03:00',
+ },
+ reasons: ['exact_dni_near_amount', 'exact_amount_different_dni'],
+ },
+ } as PurchaseDetailResponse);
+
+ expect(element.textContent).toContain('Encontramos 2 transferencias posibles.');
+ expect(element.textContent).toMatch(/diferencia de \$\s*1\.000/);
+ expect(element.textContent).toContain('Estamos revisando el pago.');
+ });
+
it('polls every five seconds while the payment is pending and shows the confirmation', async () => {
vi.useFakeTimers();
diff --git a/src/app/features/store/pages/purchase-status-page/purchase-status-page.component.ts b/src/app/features/store/pages/purchase-status-page/purchase-status-page.component.ts
index f04a492..154381a 100644
--- a/src/app/features/store/pages/purchase-status-page/purchase-status-page.component.ts
+++ b/src/app/features/store/pages/purchase-status-page/purchase-status-page.component.ts
@@ -13,6 +13,7 @@ import { ActivatedRoute, Router } from '@angular/router';
import {
CheckoutService,
+ PurchasePaymentVerificationResponse,
PurchaseStatusResponse,
} from '../../../../core/services/checkout.service';
import { CartService } from '../../../../core/services/cart/cart.service';
@@ -48,6 +49,7 @@ export class PurchaseStatusPageComponent implements OnInit, OnDestroy {
protected readonly isLoading = signal(true);
protected readonly status = signal
('pending');
protected readonly hasGeneratedTickets = signal(false);
+ protected readonly paymentIssueMessage = signal(null);
protected readonly ticketsRoute = computed(() => {
if (!this.hasGeneratedTickets()) {
return null;
@@ -105,6 +107,7 @@ export class PurchaseStatusPageComponent implements OnInit, OnDestroy {
const status = this.resolveStatus(purchase);
this.status.set(status);
this.hasGeneratedTickets.set(purchase.has_generated_tickets === true);
+ this.paymentIssueMessage.set(this.resolvePaymentIssueMessage(purchase.payment_verification));
if (status === 'approved') {
this.cartService.clearCart();
@@ -169,6 +172,41 @@ export class PurchaseStatusPageComponent implements OnInit, OnDestroy {
return 'pending';
}
+ private resolvePaymentIssueMessage(
+ verification?: PurchasePaymentVerificationResponse,
+ ): string | null {
+ const primary = verification?.primary;
+
+ if (!primary) {
+ return null;
+ }
+
+ const primaryMessage = (() => {
+ switch (primary.reason) {
+ case 'ambiguous_exact_match':
+ return 'Encontramos una transferencia que también coincide con otra compra.';
+ case 'exact_dni_near_amount':
+ return `El DNI coincide, pero el monto transferido tiene una diferencia de ${this.formatCurrency(primary.amount_difference)}.`;
+ case 'exact_amount_different_dni':
+ return 'El monto coincide, pero el DNI del pagador es diferente.';
+ }
+ })();
+
+ if (verification.candidate_count > 1) {
+ return `Encontramos ${verification.candidate_count} transferencias posibles. ${primaryMessage}`;
+ }
+
+ return primaryMessage;
+ }
+
+ private formatCurrency(amount: string): string {
+ return new Intl.NumberFormat('es-AR', {
+ style: 'currency',
+ currency: 'ARS',
+ maximumFractionDigits: 2,
+ }).format(Number(amount));
+ }
+
private isPurchaseExpiredError(error: unknown): boolean {
if (typeof error !== 'object' || error === null || !('error' in error)) {
return false;