Auto Top-Up
Auto Top-Up Monthly auto top-up is fully implemented via Stripe subscriptions. Low-balance auto top-up is planned but not yet built. Why This Exists Users ne...
Auto Top-Up
Monthly auto top-up is fully implemented via Stripe subscriptions. Low-balance auto top-up is planned but not yet built.
Why This Exists
Users need automated credit replenishment so they do not run out mid-conversation. Monthly subscriptions provide predictable billing; low-balance triggers (future) would prevent interruptions for heavy users.
How It Works
Monthly Auto Top-Up (Implemented)
Signup flow:
- User completes initial credit purchase.
- Auto top-up screen presented (
AutoTopUpTopContent.svelte+AutoTopUpBottomContent.svelte). - User toggles on, selects tier (intelligent suggestion: promotes 10K tier if user bought 1K).
POST /v1/payments/save-payment-methodencrypts Stripe payment method ID with user’s vault key.POST /v1/payments/create-subscriptioncreates a Stripe subscription with the saved method.
Monthly renewal (automated via webhooks):
- Stripe sends
invoice.payment_succeededwebhook. - Backend looks up user by
stripe_subscription_id. - Calculates total credits = base + bonus (from
pricing.ymltier lookup). - Adds credits to user account (encrypted balance in Directus).
- Broadcasts
user_credits_updatedvia WebSocket for real-time UI update.
Subscription lifecycle webhooks:
customer.subscription.updated– updates status and next billing date.customer.subscription.deleted– marks subscription as canceled.invoice.payment_failed– updates status topast_due.
API Endpoints
| Endpoint | Purpose |
|---|---|
POST /v1/payments/save-payment-method |
Encrypt and store Stripe payment method ID |
POST /v1/payments/create-subscription |
Create monthly Stripe subscription |
GET /v1/payments/subscription |
Get active subscription details with tier info |
POST /v1/payments/cancel-subscription |
Cancel at period end |
POST /v1/payments/webhook |
Handle all Stripe webhook events |
Database Schema
Stored on user record (backend/core/directus/schemas/users.yml):
encrypted_payment_method_id– Stripe payment method (encrypted with vault key)stripe_subscription_id– Stripe subscription ID (cleartext)subscription_status–active,canceled,past_duesubscription_credits– base credit amount for tier lookupsubscription_currency–eur,usd,jpynext_billing_date– ISO 8601 timestamp
Pricing Configuration
Each tier in shared/config/pricing.yml specifies base credits, multi-currency prices, and monthly_auto_top_up_extra_credits (bonus). Only tiers with bonus > 0 support subscriptions.
Security
- Payment method IDs encrypted with user-specific vault keys (zero-knowledge).
- All webhooks verified via Stripe signature validation with timestamp replay protection.
- Subscription operations require authenticated user session.
Edge Cases
- Payment method save failure does not block signup completion – user can set up later in settings.
- Subscription creation failure logged but user can continue and retry from settings.
- WebSocket disconnected during credit update: credit is still added server-side; user sees it on next session.
- Webhook replay: idempotency via order status cache prevents duplicate credit additions.
Future: Low-Balance Auto Top-Up
Not yet implemented. Planned approach: integrate balance checking into BillingService.charge_user_credits() so detection happens inline with credit deduction (no polling needed). Would use a one-time Stripe PaymentIntent (not subscription) with the saved payment method. Fixed threshold of 100 credits, configurable top-up amount, 1-hour cooldown, max 1/day, disable after 3 consecutive failures. Requires 2FA to enable. Settings UI only (not part of signup).
Related Docs
- Payment Processing – Stripe integration, tier system, chargeback handling
- Signup & Auth – signup flow context