import { ApiError, apiFetch, apiPath, type ApiSettings } from "@govoplan/core-webui"; export type PaymentStatus = "requested" | "paid"; export type EvidenceReference = { kind: string; owner_module: string; evidence_id: string; tenant_id: string; version?: string | null; checksum?: string | null; }; export type PaymentReconciliation = { reconciliation_id: string; mode: "manual"; amount_minor: number; currency: string; transaction_reference: string; evidence_ref: EvidenceReference; received_at: string; recorded_at: string; recorded_by_ref: string; }; export type PaymentEvent = { event_id: string; event_type: string; status: PaymentStatus; occurred_at: string; actor_ref: string; payload: Record; }; export type PaymentRequest = { payment_id: string; tenant_id: string; payment_reference: string; source: { module: string; resource_type: string; resource_id: string; }; amount_minor: number; currency: string; subject: string; status: PaymentStatus; requested_at: string; requested_by_ref: string; due_at?: string | null; settled_at?: string | null; context_refs: Record; metadata: Record; reconciliation?: PaymentReconciliation | null; events: PaymentEvent[]; replayed: boolean; }; export type PaymentRequestCreate = { source_module: string; source_resource_type: string; source_resource_id: string; amount_minor: number; currency: string; subject: string; idempotency_key: string; due_at?: string | null; context_refs: Record; metadata: Record; }; export type ManualPaymentReconciliationCreate = { amount_minor: number; currency: string; transaction_reference: string; evidence_ref: EvidenceReference; idempotency_key: string; received_at: string; metadata: Record; }; export async function listPaymentRequests( settings: ApiSettings, filters: { status?: PaymentStatus; sourceResourceId?: string; limit?: number } = {}, signal?: AbortSignal ): Promise { const response = await apiFetch<{ payments: PaymentRequest[] }>( settings, apiPath("/api/v1/payments/requests", { status: filters.status, source_resource_id: filters.sourceResourceId, limit: filters.limit ?? 200 }), { signal } ); return response.payments; } export function createPaymentRequest( settings: ApiSettings, payload: PaymentRequestCreate ): Promise { return apiFetch(settings, "/api/v1/payments/requests", { method: "POST", body: JSON.stringify(payload) }); } export function reconcileManualPayment( settings: ApiSettings, paymentId: string, payload: ManualPaymentReconciliationCreate ): Promise { return apiFetch( settings, `/api/v1/payments/requests/${encodeURIComponent(paymentId)}/manual-reconciliations`, { method: "POST", body: JSON.stringify(payload) } ); } export function paymentApiErrorMessage(reason: unknown): string { if (reason instanceof ApiError) { try { const payload = JSON.parse(reason.body) as { detail?: unknown }; if (typeof payload.detail === "string") return payload.detail; } catch { // The response body may be plain text. } if (reason.status === 409) return "The payment changed or this replay key is already bound to different evidence. Reload and review the current state."; if (reason.status === 403) return "Your current role does not permit this payment action."; } return reason instanceof Error ? reason.message : String(reason); }