Summary
- add monthly Paddle-backed BYOC tiers for one through five enclaves, with six or more routed to enterprise handling
- treat Paddle as the commercial source of truth while projecting fail-closed deployment entitlements locally
- preserve grandfathered credit-backed BYOC and support operator-managed enterprise limits
- add provider idempotency keys, durable subscription intents, stale-event ordering, catalog validation, and guarded production catalog provisioning
- add a signed-webhook E2E test covering activation, API visibility, stale events, cancellation precedence, duplicate delivery, and entitlement removal
Related to #315.
Test plan
cargo test --workspace --all-targetscargo check -p apicargo check -p meteringcargo test -p caution-config pricing -- --nocapture— 10 passedcargo test -p metering webhooks -- --nocapture— 6 passedbash tests/test_provision_paddle.shbash -n utils/provision-paddle.sh tests/test_provision_paddle.sh tests/e2e/test_paddle_subscriptions.shmake test-e2e-paddle-subscriptions— passedgit diff --check- Paddle sandbox authentication and five-tier catalog reconciliation — passed
Production rollout
The feature flag should remain off until the database, Paddle catalog, shared pricing file, and webhook are ready.
- Back up the production database and the deployed
prices.json. - Deploy migration
045_paddle_byoc_subscriptions.sqlbefore enabling the feature. It preserves legacy subscriptions and fails explicitly if duplicate non-canceled legacy rows need reconciliation. - Prepare the production pricing document with catalog version
1and the five monthly tiers fromprices.json.example, initially leaving Paddle IDs null. - Reconcile without mutations first:
PADDLE_API_KEY=... utils/provision-paddle.sh sync-catalog \ --environment production \ --config /secure/path/prices.json - After reviewing the plan, create/reuse the product and prices and atomically write their IDs into the pricing document:
PADDLE_API_KEY=... utils/provision-paddle.sh sync-catalog \ --environment production \ --config /secure/path/prices.json \ --apply --confirm-production - Configure Paddle's production notification destination for
/webhooks/paddleand subscribe it to the subscription lifecycle events used here (subscription.created,updated,activated,resumed,paused, andcanceled). Keep the existing transaction events enabled. - Mount the same generated
prices.jsoninto API and metering. Configure secrets through the deployment secret store, never the image or repository:- API:
PADDLE_API_URL=https://api.paddle.com,PADDLE_API_KEY,PADDLE_CLIENT_TOKEN - Metering:
PADDLE_API_URL=https://api.paddle.com,PADDLE_API_KEY,PADDLE_WEBHOOK_SECRET
- API:
- Set
BYOC_PADDLE_SUBSCRIPTIONS_ENABLED=truefor both API and metering, then restart metering and API. - Verify health, inspect the subscription tiers endpoint, perform one controlled checkout, and confirm the signed webhook projects an active entitlement with the expected enclave limit before broad availability.
Rollback is to unset BYOC_PADDLE_SUBSCRIPTIONS_ENABLED and restart the services. Do not delete Paddle products/prices or projected subscription rows during rollback; retaining them preserves reconciliation and audit history.
Deferred follow-ups
- Historical catalog lookup is not yet a general multi-version registry. Keep version-1 Paddle price IDs stable; introduce retained historical mappings before rotating IDs for a later catalog version.
- Existing transaction-email handlers would benefit from a transactional notification outbox; that broader billing concern predates this subscription path.
## Summary
- add monthly Paddle-backed BYOC tiers for one through five enclaves, with six or more routed to enterprise handling
- treat Paddle as the commercial source of truth while projecting fail-closed deployment entitlements locally
- preserve grandfathered credit-backed BYOC and support operator-managed enterprise limits
- add provider idempotency keys, durable subscription intents, stale-event ordering, catalog validation, and guarded production catalog provisioning
- add a signed-webhook E2E test covering activation, API visibility, stale events, cancellation precedence, duplicate delivery, and entitlement removal
Related to #315.
## Test plan
- `cargo test --workspace --all-targets`
- `cargo check -p api`
- `cargo check -p metering`
- `cargo test -p caution-config pricing -- --nocapture` — 10 passed
- `cargo test -p metering webhooks -- --nocapture` — 6 passed
- `bash tests/test_provision_paddle.sh`
- `bash -n utils/provision-paddle.sh tests/test_provision_paddle.sh tests/e2e/test_paddle_subscriptions.sh`
- `make test-e2e-paddle-subscriptions` — passed
- `git diff --check`
- Paddle sandbox authentication and five-tier catalog reconciliation — passed
## Production rollout
The feature flag should remain off until the database, Paddle catalog, shared pricing file, and webhook are ready.
1. Back up the production database and the deployed `prices.json`.
2. Deploy migration `045_paddle_byoc_subscriptions.sql` before enabling the feature. It preserves legacy subscriptions and fails explicitly if duplicate non-canceled legacy rows need reconciliation.
3. Prepare the production pricing document with catalog version `1` and the five monthly tiers from `prices.json.example`, initially leaving Paddle IDs null.
4. Reconcile without mutations first:
```bash
PADDLE_API_KEY=... utils/provision-paddle.sh sync-catalog \
--environment production \
--config /secure/path/prices.json
```
5. After reviewing the plan, create/reuse the product and prices and atomically write their IDs into the pricing document:
```bash
PADDLE_API_KEY=... utils/provision-paddle.sh sync-catalog \
--environment production \
--config /secure/path/prices.json \
--apply --confirm-production
```
6. Configure Paddle's production notification destination for `/webhooks/paddle` and subscribe it to the subscription lifecycle events used here (`subscription.created`, `updated`, `activated`, `resumed`, `paused`, and `canceled`). Keep the existing transaction events enabled.
7. Mount the same generated `prices.json` into API and metering. Configure secrets through the deployment secret store, never the image or repository:
- API: `PADDLE_API_URL=https://api.paddle.com`, `PADDLE_API_KEY`, `PADDLE_CLIENT_TOKEN`
- Metering: `PADDLE_API_URL=https://api.paddle.com`, `PADDLE_API_KEY`, `PADDLE_WEBHOOK_SECRET`
8. Set `BYOC_PADDLE_SUBSCRIPTIONS_ENABLED=true` for both API and metering, then restart metering and API.
9. Verify health, inspect the subscription tiers endpoint, perform one controlled checkout, and confirm the signed webhook projects an active entitlement with the expected enclave limit before broad availability.
Rollback is to unset `BYOC_PADDLE_SUBSCRIPTIONS_ENABLED` and restart the services. Do not delete Paddle products/prices or projected subscription rows during rollback; retaining them preserves reconciliation and audit history.
## Deferred follow-ups
- Historical catalog lookup is not yet a general multi-version registry. Keep version-1 Paddle price IDs stable; introduce retained historical mappings before rotating IDs for a later catalog version.
- Existing transaction-email handlers would benefit from a transactional notification outbox; that broader billing concern predates this subscription path.