Record scoped-keys + settle-tripwire work; document boundary and TODOs
Update Current state for the two P1 fixes done this session (source-only, awaiting :55). Document the advisory settle-amount tripwire in payments.md. Add Open TODOs: split audit:read into its own scope tier, and build the admin API-keys management panel (both deferred to later sessions).
This commit is contained in:
+22
-4
@@ -65,10 +65,28 @@ webhook body's claim. `api/webhook.rs::handle_inner` re-fetches
|
||||
taking ANY settle-derived action (license issuance, tier-change, subscription
|
||||
renewal — the guard sits ahead of all of them). On a provider-API error it acks
|
||||
`200` without issuing — the reconcile loop re-confirms and issues on its next tick
|
||||
(fail-closed on issuance, and a 2xx avoids a provider retry-storm). **Not yet
|
||||
done**: a literal paid-amount/currency check (the trait exposes only a status
|
||||
enum); trusting the provider's own `Settled` determination is the current
|
||||
boundary — see the auditor's open P1.
|
||||
(fail-closed on issuance, and a 2xx avoids a provider retry-storm).
|
||||
|
||||
**Settle-amount tripwire (advisory, not a gate).** `get_invoice_status` returns a
|
||||
`ProviderInvoiceSnapshot { status, amount }`; on a confirmed settle,
|
||||
`audit_settle_amount` (shared by the webhook + reconcile issue paths) compares the
|
||||
provider's reported sat amount against the invoice's `amount_sats` and, on
|
||||
mismatch, logs a WARN + writes an `invoice.amount_mismatch` audit row — then
|
||||
**issues anyway**. It is deliberately NOT a hard gate: a literal "amount actually
|
||||
paid" check is redundant with the `Settled` requirement (both providers only
|
||||
report `Settled` for a paid-in-full invoice — Zaprite maps `UNDERPAID` →
|
||||
`Pending`), and a strict `paid >= owed` gate would false-reject operators running
|
||||
a BTCPay payment tolerance (a deliberate config we must not second-guess). The
|
||||
tripwire catches *drift* — settling a SAT invoice for an amount other than the
|
||||
`amount_sats` we recorded. It applies ONLY to SAT-denominated settles: one-shot
|
||||
purchases and SAT subscriptions always charge `Money::sats` (= `amount_sats`), but
|
||||
fiat-priced subscription RENEWALS (`subscriptions.rs`) create the order in the
|
||||
listed fiat currency, where `amount_sats` is not the charged amount — those report
|
||||
a non-SAT currency and are **skipped** (no clean comparison basis; `Settled`
|
||||
already covers them). `amount` is likewise `None`/skipped when the provider
|
||||
response carries no parseable positive amount. Regression tests in `tests/api.rs`:
|
||||
`settled_amount_mismatch_issues_license_but_audits`,
|
||||
`settled_non_sat_settle_skips_amount_tripwire`.
|
||||
|
||||
## Provider connect
|
||||
|
||||
|
||||
Reference in New Issue
Block a user