/** * Hook: checkout validation API call. * Payload-agnostic: the caller is responsible for building the * CheckoutValidateRequest payload and passing it to validate(). * * Supports cancellation: when resetValidation() is called (e.g. on modal close), * in-flight requests are ignored and validation state is cleared so Book buttons * are not left disabled. */ import { useState, useCallback, useRef } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import apiFetch from '@wordpress/api-fetch'; import type { ValidationResponse, CheckoutValidateRequest, } from '../../types'; import { getApiErrorMessage } from '../utils'; import { captureException, addBreadcrumb, withSpan, captureMessage, } from '../sentry'; export interface UseCheckoutValidationReturn { validate: ( payload: CheckoutValidateRequest ) => Promise; validationResult: ValidationResponse | null; isValidating: boolean; resetValidation: () => void; } export function useCheckoutValidation(): UseCheckoutValidationReturn { const [isValidating, setIsValidating] = useState(false); const [validationResult, setValidationResult] = useState(null); const requestIdRef = useRef(0); const validate = useCallback( async ( payload: CheckoutValidateRequest ): Promise => { requestIdRef.current += 1; const currentId = requestIdRef.current; setIsValidating(true); setValidationResult(null); try { const response = (await withSpan( 'Checkout validation', 'http.client', async () => apiFetch({ path: '/parcel2go-shipping/v1/checkout/validate', method: 'POST', data: payload, }) as Promise, { payload: payload } )) as ValidationResponse; if (currentId !== requestIdRef.current) return response; if (!response.success && !!response.error) { setValidationResult(response); captureMessage('checkout_validation_failed', 'warning', { payload: payload, response: response, }); setIsValidating(false); return response; } setValidationResult(response); setIsValidating(false); return response; } catch (err: unknown) { if (currentId !== requestIdRef.current) return null; captureException( err instanceof Error ? err : new Error(String(err)) ); addBreadcrumb( 'Checkout validation failed', { payload: payload }, 'api' ); const message = getApiErrorMessage( err, __( 'Validation failed. Please try again.', 'parcel2go-shipping' ) ); } finally { if (currentId === requestIdRef.current) { setIsValidating(false); } } return null; }, [] // no external deps — pure API state machine ); const resetValidation = useCallback(() => { requestIdRef.current += 1; setValidationResult(null); setIsValidating(false); }, []); return { validate, validationResult, isValidating, resetValidation, }; }