Review a batch invoice settlement job

src/invoice-settlement-types.tsTypeScript
@@ -0,0 +1,51 @@
1+export interface DueInvoice {
2+ id: string;
3+ accountId: string;
4+ totalCents: number;
5+}
6+
7+export interface ChargeReceipt {
8+ chargeId: string;
9+ acceptedAt: Date;
10+}
11+
12+export interface PaymentProvider {
13+ charge(request: {
14+ accountId: string;
15+ amountCents: number;
16+ idempotencyKey: string;
17+ }): Promise<ChargeReceipt>;
18+}
19+
20+export class PaymentProviderError extends Error {
21+ constructor(
22+ message: string,
23+ readonly kind: "declined" | "temporarily-unavailable",
24+ ) {
25+ super(message);
26+ this.name = "PaymentProviderError";
27+ }
28+}
29+
30+export interface SettlementTransaction {
31+ loadCheckpoint(): Promise<string | null>;
32+ listDueAfter(checkpoint: string | null, limit: number): Promise<DueInvoice[]>;
33+ getPayableTotal(invoiceId: string): Promise<number>;
34+ markPaid(invoiceId: string, receipt: ChargeReceipt): Promise<void>;
35+ recordFailure(invoiceId: string, message: string, retryable: boolean): Promise<void>;
36+ saveCheckpoint(invoiceId: string): Promise<void>;
37+}
38+
39+export interface SettlementDependencies {
40+ ids: { next(): string };
41+ payments: PaymentProvider;
42+ repository: {
43+ withBatchLock<T>(work: (transaction: SettlementTransaction) => Promise<T>): Promise<T>;
44+ };
45+}
46+
47+export interface SettlementResult {
48+ attempted: number;
49+ paid: number;
50+ failed: number;
51+}