BillTracker/client/lib/paymentActions.ts

44 lines
1.7 KiB
TypeScript
Raw Normal View History

import { api, type ApiError } from '@/api';
import { toast } from 'sonner';
import { errMessage } from '@/lib/utils';
/**
* Create a manual payment, treating the server's "duplicate suspected" (409) as a
* question rather than an error. The deliberate manual-add path must never
* *silently* drop a legitimately-identical second payment (same bill, date,
* amount) losing a real payment is itself a money-integrity bug. So the server
* returns `409 DUPLICATE_SUSPECTED` instead of silently deduping, and here we
* surface an "add anyway?" toast; confirming resends with `allow_duplicate`.
*
* `onSuccess` runs after any *real* creation immediate OR confirmed-retry so
* callers put their toast/refresh/close there. Any non-duplicate error re-throws
* for the caller's existing `catch`.
*/
export async function createPaymentOrConfirm(
payload: Record<string, unknown>,
onSuccess: () => void | Promise<void>,
): Promise<void> {
const create = async (allowDuplicate: boolean) => {
await api.createPayment(allowDuplicate ? { ...payload, allow_duplicate: true } : payload);
await onSuccess();
};
try {
await create(false);
} catch (err) {
const e = err as ApiError;
if (e?.status === 409 && e?.code === 'DUPLICATE_SUSPECTED') {
toast.warning('A matching payment already exists for this bill, date, and amount.', {
description: 'Add it anyway?',
action: {
label: 'Add anyway',
onClick: () => {
create(true).catch(retryErr => toast.error(errMessage(retryErr, 'Failed to add payment')));
},
},
});
return; // handled — not an error the caller should re-toast
}
throw err;
}
}