feat: add Paddle-backed BYOC subscriptions #378

Merged
caution-bot merged 41 commits from refs/pull/378/head into main 2026-07-16 22:28:22 +00:00
caution-bot commented 2026-07-12 18:07:06 +00:00 (Migrated from codeberg.org)

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:
    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:
    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.
## 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.
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
public/platform!378
No description provided.