feat: add renewal invoice sweep (#20)

Refs #9

## Summary
- add a worker-side renewal invoice sweep that creates one invoice 72 hours before subscription expiry
- expire elapsed pending invoices automatically and email users when an automatic renewal invoice is created
- stop auto-recreating invoices for the same paid cycle once any invoice already exists for that cycle
- document the current renewal-invoice and pending-invoice expiry behavior

## Testing
- built `infra/docker/web.Dockerfile`
- ran `pnpm --filter @nproxy/db test` inside the built container
- verified `@nproxy/db build` and `@nproxy/web build` during the image build
- built `infra/docker/worker.Dockerfile`

Co-authored-by: sirily <sirily@git.shararam.party>
Reviewed-on: #20
This commit was merged in pull request #20.
This commit is contained in:
2026-03-11 12:33:03 +03:00
parent 624c5809b6
commit 9641678fa3
5 changed files with 1137 additions and 188 deletions

View File

@@ -17,8 +17,18 @@ export interface CreatedProviderInvoice {
expiresAt: Date;
}
export type ProviderInvoiceStatus = "pending" | "paid" | "expired" | "canceled";
export interface ProviderInvoiceStatusRecord {
providerInvoiceId: string;
status: ProviderInvoiceStatus;
paidAt?: Date;
expiresAt?: Date;
}
export interface PaymentProviderAdapter {
createInvoice(input: PaymentInvoiceDraft): Promise<CreatedProviderInvoice>;
getInvoiceStatus(providerInvoiceId: string): Promise<ProviderInvoiceStatusRecord>;
}
export function createPaymentProviderAdapter(config: {
@@ -37,6 +47,13 @@ export function createPaymentProviderAdapter(config: {
expiresAt: new Date(Date.now() + 30 * 60 * 1000),
};
},
async getInvoiceStatus(providerInvoiceId) {
return {
providerInvoiceId,
status: "pending",
};
},
};
}
@@ -51,5 +68,12 @@ export function createPaymentProviderAdapter(config: {
expiresAt: new Date(Date.now() + 30 * 60 * 1000),
};
},
async getInvoiceStatus(providerInvoiceId) {
return {
providerInvoiceId,
status: "pending",
};
},
};
}