This commit is contained in:
2026-10-23 12:19:40 -04:00
parent 6cf0f0525f
commit faf6c5f2b3
19 changed files with 785 additions and 12 deletions
+106 -1
View File
@@ -3,6 +3,9 @@
Apache serves the static eom.dev website. A separate Django service provides
public accounts, Dex sign-in, and Stripe membership under **/members/**.
Optional SHKeeper support adds prepaid crypto membership alongside Stripe.
Both payment methods feed the same LDAP group reconciliation and local expiry.
## Membership flow
1. Anyone can request a free account. A one-hour, single-use email link allows
@@ -146,6 +149,103 @@ No production credentials or live account changes are made by the test suite.
## Operations and limitations
### SHKeeper prepaid crypto
Stripe remains the automatically recurring payment method. SHKeeper accepts
**one-time payments for one calendar month** of the same tiers, at the USD
amounts in [membership/tiers.eom.json](membership/tiers.eom.json). It does not
debit a wallet or automatically renew. The user must create and pay a new
invoice for each month. Amounts are server-side configuration, never supplied by
the browser and never parsed from the display-price label. Keep `usd_amount`
aligned with your Stripe prices when changing prices; existing orders retain
the original amount.
To enable:
1. Set `SHKEEPER_URL` to your actual HTTPS origin, `SHKEEPER_API_KEY` to its
invoice API key, and `SHKEEPER_CURRENCIES` to a comma-separated allowlist of
the API currency identifiers enabled on **your** instance (e.g. BTC or XMR).
These identifiers are network-specific for tokens (e.g. ETH-USDT is not
interchangeable with TRX-USDT). No instance URL, wallets or keys are assumed.
2. For an internal CA, mount the trusted CA file read-only into both web and
worker containers and set `SHKEEPER_CA_CERT_FILE`. TLS verification is always
enabled. API requests have timeouts, disallow redirects, and cap response
size; the API key never reaches the browser. No payout/UI administrator
credentials or wallet private keys are needed. Restrict network/API access
because SHKeeper's API key may expose more than invoice endpoints.
3. Review SHKeeper fee, exchange-rate recalculation, payment-tolerance and
confirmation settings. This integration requires a full invoice amount and
confirmed transactions; it intentionally does not grant access for partial,
underpaid or unconfirmed transfers, even if a provider tolerance says paid.
The payment page identifies its initial quote as such; a partial/late
payment may require administrator assistance with the updated remainder.
4. Each new invoice supplies the callback URL
`https://eom.dev/members/shkeeper/callback/`. Permit SHKeeper to reach it.
**SHKeeper's documented callbacks have no signature.** This endpoint only
acknowledges them: it never trusts amounts/status/IDs, writes payment state,
or triggers an API request. The worker polls known local invoice UUIDs over
authenticated HTTPS, so forged, repeated, missing or reordered callbacks
cannot grant access or extend a term.
5. Redeploy the image and run the migration service, which applies
[membership/members/migrations/0002_crypto_invoices.py](membership/members/migrations/0002_crypto_invoices.py).
Keep the membership worker running; it independently refreshes Stripe and
SHKeeper, and performs a local-only expiry sweep before remote requests.
6. Smoke-test a low-value invoice on your actual deployment before offering
public crypto payments. The adapter follows the documented SHKeeper 2.1 API:
`POST /api/v1/{crypto}/payment_request` and
`GET /api/v1/invoices/{external_id}`. Require the GET response to include
`external_id`, `fiat`, `amount_fiat`, `balance_fiat`, `status` and `txs`
(transaction status, asset identifier, address and txid). Unsupported or
ambiguous responses do not grant access. Different SHKeeper versions must
be verified against these fields, especially transaction crypto identifiers.
Payment lifecycle:
- An authenticated user selects the currency, then sees a private invoice
with its deposit address and quote. One unfinished invoice is allowed per
account. Repeated submissions reopen that same invoice, even if a second
tab requests another tier or currency. The UUID and USD amount are committed
**before** contacting SHKeeper; retrying after a timeout reuses SHKeeper's
documented create/update-by-external-ID operation with the same parameters.
- There is no fabricated invoice deadline: SHKeeper deposit addresses can
receive late transfers. Unpaid/partial orders remain polled and cannot be
abandoned/switched from the UI. Contact the administrator if the wrong
network was selected; do not delete orders with potentially in-flight funds.
- Access begins when the worker first verifies a fully paid invoice with
matching USD order, amount, receiving address and asset and confirmed txs.
A renewal of the **same tier** starts at the later of now or that tier's
previous prepaid end and adds one calendar month in UTC. Month-end dates
clamp to the last day of the next month. Queued future terms do not grant
early access. Delayed worker verification starts a new term at verification,
not at an untrusted callback timestamp.
- Each invoice can grant **exactly one term**. Overpayments, repeated callbacks
and additional transfers to a paid address never buy extra months. Create a
new invoice for renewal. Refund/overpayment handling is manual; this app does
not issue cryptocurrency payouts.
- Active/queued paid crypto invoices are rechecked until their end. If an
invoice reverts to unpaid/partial/unconfirmed (e.g. a chain reorganization),
its grant is removed. Recovery restores only its original term, not another
month. During API outages, the last verified term remains valid only until
its stored expiry; local expiry never waits for provider recovery. Alert on
stale `last_shkeeper_sync`, failed polls and worker errors.
- LDAP desired groups are the **union** of all currently valid Stripe and
SHKeeper terms. One provider's failure/cancellation cannot revoke a still
valid grant from another. Removed tiers are revoked regardless of provider.
- To avoid accidental double payments, a current Stripe subscription or open
Stripe Checkout blocks a new crypto invoice. Unfinished or unexpired crypto
orders block a new Stripe subscription; another crypto tier must expire
before switching tiers. Same-tier crypto renewals remain available. This
does not cancel existing Stripe recurring charges: finish/cancel them through
Stripe's billing portal before changing methods. Provider-side/manual changes
can still create overlapping payments; grants remain a safe union.
- Leaving SHKeeper configuration empty hides new crypto checkouts. Previously
verified prepaid grants still expire locally, but remote status cannot be
rechecked while the provider is disabled. Do not delete payment history or
change your SHKeeper server/key to an unrelated instance while orders exist.
`CryptoInvoice` protects its parent Account from accidental deletion.
### General operations
- Keep `membership-worker` running. Alert on its errors and on accounts whose
`last_ldap_sync` or `last_billing_sync` is stale. A one-shot
`sync_memberships` command exits nonzero on a failed sweep. Logs record account
@@ -185,6 +285,11 @@ webhooks, paid invoice periods, cancellation, failed renewals, unavailable Strip
LDAP retries, tier removal, multiple subscriptions, and the last member's removal
from a `groupOfUniqueNames` using its sentinel. Live Stripe, Dex, SMTP and OpenLDAP
must still be smoke-tested together in a non-production environment before
enabling public registration/payment.
enabling public registration/payment. SHKeeper regressions additionally cover
API-key/TLS transport, immutable prices and account-scoped invoices, callback
spoofing, partial/unconfirmed/underpaid transfers, duplicate confirmation,
calendar-month renewals, overpayments, reorg recovery, provider outages, and
cross-provider overlap. PostgreSQL also verifies that concurrent workers credit
one crypto invoice only once.