Skip to main content
Current support: Solana only. Sui subscriptions and Sui wallet payments are marked coming soon in the dashboard and disabled on the backend. The existing one-off Sui address/QR payment flow still works; only Sui subscriptions and Sui wallet-pay are disabled.
Subscriptions are a separate, flagged engine. Existing one-off payments, payment links, QR, refunds and payouts are unchanged.
The engine ships off by default in the repo. Enable it with SUBSCRIPTIONS_ENABLED=true and SUBSCRIPTIONS_SOLANA_ENABLED=true. Test-mode subscriptions (livemode=false) use a deterministic sandbox and never touch a chain.

Concepts

  • Plan — amount in USD-pegged USDC (6 decimals), interval (day|week|month|year), optional trial, optional max cycles, accepted chains/assets. Price is immutable: create a new plan to change it.
  • Subscription — one customer on one plan. States: incomplete → trialing → active → past_due → unpaid, plus paused, canceled, expired.
  • Mandate — the customer’s on-chain authorization. Solana: a capped SPL delegate approval (never unlimited).
  • Invoice — one per billing period. Gross, platform fee and net are stored in integer base units.
  • Charge attempt — one signed send with a unique idempotency key. Ambiguous sends are reconciled on-chain before any retry.
  • Delegate wallet — a dedicated Solana hot wallet that customers approve to pull USDC. It pays network fees and must hold SOL. It is never the vault or treasury key.

How Solana subscriptions work

  1. Merchant creates a plan.
  2. Merchant sends the hosted subscribe link (or creates a checkout through the API).
  3. Customer connects a Solana wallet and reviews the exact amount, interval, first charge and maximum authorization.
  4. Customer signs one ApproveChecked transaction granting the Tribridge delegate a capped allowance: amount × min(max_cycles, default_cap_cycles). The default cap is 12 cycles.
  5. Backend verifies the delegate, allowance, balance and token account.
  6. No trial: the first invoice is charged immediately. Trial: first charge at trial_end.
  7. On renewal: re-check the mandate, then the delegate signs one TransferChecked for exactly one cycle to the treasury.
  8. If the delegate was replaced or revoked, the allowance is too low, or the balance is insufficient, the backend does not send and starts dunning + a needs-reauthorization notice.
The customer can revoke the approval at any time. Cancellation in the portal is manual only.

Create a plan

Create a hosted subscription checkout

Returns { subscription_id, checkout_url, subscription }. Send the customer to checkout_url.

API vs dashboard

API: create the plan once, then create one checkout per customer from your backend. Listen to signed webhooks. Best when subscriptions are embedded in your own product. Dashboard: create a plan, copy the hosted subscribe link, manage pause/resume/cancel, copy the customer portal link, view invoices and retry failed charges. Best for no-code merchants, testing and operations. Both use the same engine and tables.

Lifecycle

  1. Customer reviews the terms and authorizes in their wallet.
  2. No trial: first invoice charged immediately. Failure → incomplete (expires after 24h).
  3. Trial: trialing; first invoice at trial_end.
  4. Renewals: invoice at next_billing_at, mandate re-verified, charge executed.
  5. Failures: typed reason, retry at +1/+3/+5 days, past_due after the first failure, invoice.failed webhook, then canceled/unpaid per plan policy.
  6. max_cycles reached → expired.
Month math is UTC anchor-based (Jan 31 → Feb 28/29 → Mar 31, no drift).

Endpoints

Customer cancel is manual: cancel-at-period-end is confirmed in a Tribridge modal, then the subscription cancels at the boundary.

Webhooks

Reuses the existing HMAC-SHA256 delivery and retry pipeline (X-Tribridge-Signature). subscription.created, subscription.active, subscription.trial_will_end, subscription.past_due, subscription.paused, subscription.resumed, subscription.canceled, subscription.expired, invoice.upcoming, invoice.paid, invoice.failed, mandate.needs_reauthorization. Payloads include subscription_id, invoice_id, plan_id, livemode (as is_test), and a data object with the full subscription/plan/invoice snapshot.

Fees and ledger

The existing platform fee is used (max($0.10, 1%)). The invoice stores gross, fee and net; the ledger credits the merchant net through a completed payments row, so payouts and balances work unchanged. Subscription charges appear in Transactions with source subscription, and lifecycle events appear in Logs → Subscription.

Test mode

Test subscriptions are livemode=false and always use the sandbox adapter:
Supported injections: insufficient_balance, allowance_exhausted, delegate_revoked, account_closed, rpc_error, ambiguous. Send {"failure":null} to clear.

Mainnet requirements (Solana)

Required backend env:
The delegate is a new dedicated hot wallet funded with SOL. The treasury can be your existing Solana vault address; it only receives USDC. See Admin → Delegated Wallets for balances and configuration status.

Refunds

Normal automatic refunds cover underpaid, overpaid and wrong-token deposits. If funds arrived on-chain but the payment was not recorded: a super admin can open Admin → Payments → payment drawer → Refund unrecorded payment. It refunds the actual deposit-wallet balance to the payer, signs a real transaction, marks the payment refunded, and writes an audit log. It refuses completed/test payments and zero balances.