fix: make invoice payment activation idempotent (#18)

Closes #2

## Summary
- make `markInvoicePaid` idempotent for already-paid invoices and reject invalid terminal transitions
- add admin actor metadata and audit-log writes for `mark-paid`, including replayed no-op calls
- add focused DB tests for first activation, replay safety, and invalid transition handling
- document the current payment system, including invoice creation, manual activation, quota reset, and current limitations

## 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

Co-authored-by: sirily <sirily@git.shararam.party>
Reviewed-on: #18
This commit was merged in pull request #18.
This commit is contained in:
2026-03-10 17:53:00 +03:00
parent 431a60f9c8
commit 1b2a4a076a
5 changed files with 628 additions and 13 deletions

View File

@@ -5,6 +5,7 @@ import {
} from "node:http";
import { loadConfig } from "@nproxy/config";
import {
BillingError,
createPrismaAccountStore,
createPrismaAuthStore,
createPrismaBillingStore,
@@ -211,7 +212,13 @@ const server = createServer(async (request, response) => {
}
const invoiceId = decodeURIComponent(invoiceMarkPaidMatch[1] ?? "");
const invoice = await billingStore.markInvoicePaid({ invoiceId });
const invoice = await billingStore.markInvoicePaid({
invoiceId,
actor: {
type: "web_admin",
ref: authenticatedSession.user.id,
},
});
sendJson(response, 200, {
invoice: serializeBillingInvoice(invoice),
});
@@ -672,6 +679,23 @@ function handleRequestError(
return;
}
if (error instanceof BillingError) {
const statusCode =
error.code === "invoice_not_found"
? 404
: error.code === "invoice_transition_not_allowed"
? 409
: 400;
sendJson(response, statusCode, {
error: {
code: error.code,
message: error.message,
},
});
return;
}
if (error instanceof GenerationRequestError) {
const statusCode =
error.code === "missing_active_subscription"