import { useState, useCallback } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import apiFetch from '@wordpress/api-fetch'; import { captureException, captureMessage, addBreadcrumb, withSpan } from '../sentry'; import { getApiErrorMessage } from '../utils'; import type { PrePayBalance, GenericPaymentResponse, GenericErrorResponse, PrepayPaymentResponse, PrepayOrderResponse } from '../../types'; import { TopUpOrder } from '../../types'; import { PrepayAPI } from 'types/payment'; export interface UsePrepayReturn { isPrepayLoading: boolean; prepayError: string | null; fetchBalance: PrepayAPI['fetchBalance']; makePrepayPayment: PrepayAPI['makePrepayPayment']; createTopUp: PrepayAPI['createTopUp']; } export default function usePrepay(): UsePrepayReturn { const [isPrepayLoading, setPrepayLoading] = useState(false); const [prepayError, setPrepayError] = useState(null); // ── Shared request wrapper ────────── const run = useCallback(async (fn: () => Promise): Promise => { setPrepayLoading(true); setPrepayError(null); try { return await fn(); } catch (err: unknown) { const msg = getApiErrorMessage(err, __('An unexpected error occurred.', 'parcel2go-shipping')); setPrepayError(msg); return null; } finally { setPrepayLoading(false); } }, []); // ── fetchBalance ─────────── const fetchBalance = useCallback(async (): Promise => { return run(async () => { const balance = await withSpan( 'Fetch prepay balance', 'http.client', () => apiFetch({ path: '/parcel2go-shipping/v1/payment/prepay/balance' }) as Promise ); if (!balance) { captureMessage( __('Error: prepay balance fetch failed', 'parcel2go-shipping'), 'warning', { context: 'fetchBalance' } ); throw new Error('Empty balance response'); } return { balance: balance.balance / 100, currency: balance.currency || 'GBP', freePercentage: balance.freePercentage, minimumTopUp: balance.minimumTopUp / 100, freeThreshold: balance.freeThreshold / 100, }; }); }, [run]); // ── makePrepayPayment ────────── const makePrepayPayment = useCallback(async ( orderId: string, hash: string, ): Promise => { return run(async () => { const response = await withSpan( `Make prepay payment: ${orderId}`, 'http.client', () => apiFetch({ path: '/parcel2go-shipping/v1/payment/prepay', method: 'POST', data: { orderId, hash }, }) as Promise ); if (response.success && response.result.completeHash) { return response.result; } if (response.validationErrors?.length > 0) { const detail = response.validationErrors[0]?.detail ?? ''; addBreadcrumb('Prepay payment validation error', { orderId, detail }, 'payment'); throw new Error(detail); } captureException(new Error('Prepay payment failed: unexpected response'), { tags: { action: 'make_payment' }, extra: { orderId, response }, }); throw new Error(__('PrePay payment failed.', 'parcel2go-shipping')); }); }, [run]); // ── createTopUp ────────────── const createTopUp = useCallback(async ( amount: number, minimumTopUp: number, ): Promise => { const amountInUnits = Math.round( !amount || isNaN(amount) || amount < minimumTopUp || amount > 1_000_000 ? minimumTopUp * 100 // converted to smallest currency unit (pence/cents) : amount * 100 ); addBreadcrumb( 'Creating prepay top-up order', { amount, amountInUnits }, 'prepay' ); return run(async () => { const data = await withSpan( `Create prepay top-up order: ${amountInUnits}`, 'http.client', () => apiFetch({ path: '/parcel2go-shipping/v1/payment/prepay/create', method: 'POST', data: { amount: amountInUnits }, }) as Promise ); if (!data.result?.orderId || !data.result?.hash) { captureException( new Error('Invalid prepay order response: missing orderId or hash'), { tags: { action: 'create_top_up' }, extra: { amount, amountInUnits, response: data }, } ); throw new Error(__('Failed to create pre-pay order.', 'parcel2go-shipping')); } return { order: data, amount }; }); }, [run]); return { isPrepayLoading, prepayError, fetchBalance, makePrepayPayment, createTopUp }; }