44 lines
1.7 KiB
TypeScript
44 lines
1.7 KiB
TypeScript
|
|
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;
|
||
|
|
}
|
||
|
|
}
|