Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,31 @@ describe('useMantecaDepositPolling', () => {
await waitFor(() => expect(result.current.status).toBe('failed'))
})

it('reports "processing" for an intermediate settling status', async () => {
mockGetDepositStatus.mockResolvedValue({ data: { id: 'dep-4', status: 'PROCESSING' } })
it('keeps "pending" on PROCESSING at stage 1 — QR live, user has NOT paid (the vanished-QR bug)', async () => {
// Manteca flips the synthetic ACTIVE (→ intent PROCESSING) seconds after
// creation; treating that as "settling" hid the QR before anyone could pay.
mockGetDepositStatus.mockResolvedValue({ data: { id: 'dep-4', status: 'PROCESSING', stage: 1 } })
const { result } = renderHook(() => useMantecaDepositPolling('dep-4', jest.fn()), { wrapper })

await waitFor(() => expect(mockGetDepositStatus).toHaveBeenCalledWith('dep-4'))
expect(result.current.status).toBe('pending')
})

it('reports "processing" only once stage >= 2 (fiat received)', async () => {
mockGetDepositStatus.mockResolvedValue({ data: { id: 'dep-5', status: 'PROCESSING', stage: 2 } })
const { result } = renderHook(() => useMantecaDepositPolling('dep-5', jest.fn()), { wrapper })

await waitFor(() => expect(result.current.status).toBe('processing'))
})

it('degrades to "pending" when stage is absent (older BE / legacy row)', async () => {
mockGetDepositStatus.mockResolvedValue({ data: { id: 'dep-6', status: 'PROCESSING', stage: null } })
const { result } = renderHook(() => useMantecaDepositPolling('dep-6', jest.fn()), { wrapper })

await waitFor(() => expect(mockGetDepositStatus).toHaveBeenCalledWith('dep-6'))
expect(result.current.status).toBe('pending')
})

it('does not query when depositId is undefined', () => {
const { result } = renderHook(() => useMantecaDepositPolling(undefined, jest.fn()), { wrapper })

Expand Down
12 changes: 9 additions & 3 deletions src/components/AddMoney/hooks/useMantecaDepositPolling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ const POLLING_INTERVAL = 5000

// Terminal TransactionIntentStatus values returned by GET /manteca/deposit/:id/status.
const TERMINAL_STATUSES = ['COMPLETED', 'FAILED', 'CANCELLED', 'REFUNDED']
// Non-terminal states that mean "payment detected, settling" — show a processing screen.
const PROCESSING_STATUSES = ['PROCESSING', 'AWAITING_SETTLEMENT']
// "Payment detected" is signalled by the synthetic's stage, NOT the intent status:
// Manteca flips a ramp-on to ACTIVE (→ intent PROCESSING) seconds after creation,
// while the user hasn't paid — stage 1 is DEPOSIT (QR live, awaiting fiat), and
// only stage >= 2 (ORDER/WITHDRAW) means the fiat actually arrived.
const PAID_STAGE = 2

type MantecaDepositPollStatus = 'pending' | 'processing' | 'completed' | 'failed'

Expand All @@ -34,9 +37,12 @@ export function useMantecaDepositPolling(depositId: string | undefined, onComple

const status: MantecaDepositPollStatus = useMemo(() => {
const s = data?.data?.status
const stage = data?.data?.stage
if (s === 'COMPLETED') return 'completed'
if (s && TERMINAL_STATUSES.includes(s)) return 'failed'
if (s && PROCESSING_STATUSES.includes(s)) return 'processing'
// stage may be null/absent (older BE, legacy row) — then stay 'pending'
// so the QR keeps showing rather than stranding the user on a loader.
if (typeof stage === 'number' && stage >= PAID_STAGE) return 'processing'
return 'pending'
}, [data])

Expand Down
5 changes: 4 additions & 1 deletion src/services/manteca.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,10 @@ export const mantecaApi = {
}
},

getDepositStatus: async (depositId: string): Promise<{ data?: { id: string; status: string }; error?: string }> => {
getDepositStatus: async (
depositId: string
// stage: 1 = QR live awaiting fiat, >= 2 = fiat received (settling), null = no snapshot
): Promise<{ data?: { id: string; status: string; stage?: number | null }; error?: string }> => {
try {
const response = await serverFetch(`/manteca/deposit/${depositId}/status`)
const data = await response.json()
Expand Down
Loading