Skip to main content

Payment providers

Dashboard operations

Paths beginning with /api/billing/ run on https://usagey.com and require an authenticated Usagey operator session. Only the provider webhook URLs documented below use the public production or sandbox API hosts.

Provider connections belong to one billing workspace. Configure sandbox credentials in Sandbox and live credentials in Production.

Usagey provider settings with billing profile and provider connection controls
Provider settings separates workspace billing metadata from environment-specific payment connections.

Usagey currently has adapters for PayPal, Stripe, and Paystack. Provider capabilities are not identical, so unsupported recurring operations return explicit capability errors rather than silently changing only local state.

Recurring capability matrix​

The dashboard and customer portal read the same state-aware capability contract returned by the subscriptions API. Controls are disabled with an explanation before an unsupported request is sent.

ProviderCancellationReactivationPlan and seat changes
StripeStops renewal at period endSupported before period endUpgrades/increases are immediate with proration; downgrades/decreases apply at renewal
PayPalImmediate cancellationNot supported; start a new subscriptionSupported through a revision that may require customer approval
PaystackStops renewal at period endSupported for a resolvable individual subscriptionNot supported because shared-plan updates can affect other subscribers
InternalLocal cancellationNot supported; create a new subscriptionApplied by Usagey's local proration policy

GET /api/billing/subscriptions includes capabilities.cancel, capabilities.reactivate, capabilities.planChange, and capabilities.seatChange. Each action reports whether it is supported, whether it is currently available, its timing, and a customer-safe reason when unavailable.

Credential storage and rotation​

Provider management runs inside the selected operator workspace transaction. Secret values are stored in versioned AES-256-GCM envelopes and API responses expose only masked placeholders. Listing a connection never returns its encrypted envelope or readable credentials.

When updating a connection, omit a secret or submit an empty password field to keep the existing encrypted value. Supplying a new secret rotates the complete envelope. Public provider settings such as plan mappings, merchant identifiers, and display names can be updated independently without clearing checkout or webhook credentials.

Credential encryption supports a versioned key ring. During rotation, every runtime must receive both the old and new keys before the active write version changes. A platform administrator can inspect GET /api/admin/provider-credentials/migrate, run the migration with POST /api/admin/provider-credentials/migrate, and remove the old decryption key only after the report returns pending: 0. Migration also scrubs recognized secrets from legacy public configuration fields.

Subscription changes​

Immediate Stripe plan upgrades and seat increases are applied to the existing subscription item with invoiced proration. Add stripePriceId to each plan's metadata, or configure planReferences on the Stripe connection keyed by the Usagey plan ID or slug. Usagey requests error_if_incomplete, so a payment that needs additional customer action fails without granting the upgraded local entitlement.

Internally managed downgrades and seat decreases remain scheduled for the next renewal. Stripe-managed next-cycle changes use a two-phase hourly synchronizer: Usagey updates the provider price with no proration shortly before renewal, then changes local entitlements only when the effective boundary is reached. Claimed, prepared, and failed states are retained in subscription metadata for safe retries.

Internally managed upgrades and seat increases take effect immediately. Usagey subtracts the unused current-plan value from the remaining target-plan value, then creates one pending, tax-aware subscription transaction for the net amount. The transaction records the policy version, both sides of the calculation, period boundaries, plan and seat changes, and the manual-tax snapshot so invoices and receipts remain auditable. Idempotent request replay returns the original transaction rather than creating another charge.

PayPal revisions require customer re-consent and take effect on the next billing cycle without automatic proration. A successful change request can return requiresCustomerAction: true and an approvalUrl. Redirect the customer to that URL. Usagey keeps the local subscription on its current plan until a verified PayPal subscription webhook confirms the new provider plan, then applies the prepared local change at the renewal boundary.

Each PayPal plan must provide a paypalPlanId in plan metadata, or the provider connection must define planReferences keyed by the Usagey plan ID or slug. Configure the connection with clientId, clientSecret, and the PayPal webhookId. Older connections containing only a webhook secret fail closed until a webhook ID and client credentials are saved.

All PayPal subscription, order, capture, refund, and webhook-verification requests use the credentials and API mode from the selected workspace connection. The billing-domain adapter does not fall back to Usagey's legacy global PayPal credentials. Successful order capture stores both the PayPal order ID and capture ID so later full or partial refunds target the correct captured payment.

Paystack recurring checkout is supported through a transaction initialized with the plan's paystackPlanCode. Paystack cancellation uses the individual subscription code and email token and marks the local subscription as active but not renewing until Paystack emits subscription.disable at the billing boundary. Reactivation uses the matching individual enable operation.

Paystack's documented plan update can affect all subscriptions on a shared plan. Usagey therefore continues to block provider-managed plan and seat changes rather than changing every customer attached to that plan.

For recurring providers, add the matching identifier when creating a Usagey plan: stripePriceId, paypalPlanId, or paystackPlanCode. Provider-connection planReferences remain available for API-managed mappings keyed by Usagey plan ID or slug.

Webhooks​

Configure the matching endpoint with each provider:

  • PayPal: https://api.usagey.com/v1/webhooks/paypal
  • Stripe: https://api.usagey.com/v1/webhooks/stripe
  • Paystack: https://api.usagey.com/v1/webhooks/paystack

Use the same paths on https://sandbox.usagey.com for sandbox provider connections. The webhook host limits candidate reconciliation to the matching Usagey workspace environment.

Usagey validates webhook signatures before changing subscription, payment, refund, or settlement state. PayPal verification is performed server-to-server with PayPal using the transmission headers, webhook event body, and configured webhook ID; a header value is never treated as a shared secret.

After a provider reference identifies the candidate connection, signature verification completes before any billing state changes. Matched webhook-event persistence, subscription reconciliation, refunds, settlements, and developer-event audit records then run in one workspace-scoped transaction with an explicit SYSTEM reason. Failed signatures and processing failures are committed as failed audit events before the endpoint returns an error.

PayPal refund webhooks are correlated by the original capture ID. This keeps refund reconciliation aligned with the payment identifier returned by the Payments API instead of relying on the checkout order ID.

Paystack lifecycle reconciliation handles subscription.create, subscription.not_renew, subscription.disable, invoice.payment_failed, and successful invoice.update events when they reference the stored individual subscription code.

Environment safety

Never copy a sandbox secret into a Production provider connection. Complete a signed webhook test before accepting live checkout traffic.