Skip to content

User accounts and billing (database)

These tables support OAuth logins, API keys, and credit‑based billing.


UserAccount

Represents a user in PlanExe.

Fields: - id (UUID, primary key) - email, name, given_name, family_name - locale, avatar_url - is_admin (bool) - free_plan_used (bool) - credits_balance (numeric, fractional credits supported) - last_login_at, created_at, updated_at


UserProvider

Links a user to an OAuth provider identity.

Fields: - id (UUID, primary key) - user_id (UUID, foreign key to UserAccount) - provider (string, e.g. google/github/discord) - provider_user_id (string) - email (string) - raw_profile (JSON) - created_at, last_login_at


UserApiKey

API key record for MCP usage.

Fields: - id (UUID, primary key) - user_id (UUID, foreign key) - key_hash (sha256 hash) - key_prefix (short prefix for display) - created_at, last_used_at, revoked_at

Notes: - Only the hash is stored. The full key is shown once at creation.


CreditHistory

Append‑only ledger of credit changes.

Fields: - id (UUID, primary key) - user_id (UUID, foreign key) - delta (numeric, positive or negative) - reason (string) - source (string, e.g. stripe/telegram/mcp/web) - external_id (string) - created_at


PaymentRecord

Stores completed payment details.

Fields: - id (UUID, primary key) - user_id (UUID, foreign key) - provider (string, stripe/telegram) - provider_payment_id (string) - credits (numeric) - amount (int, minor units) - currency (string) - status (string) - raw_payload (JSON) - created_at


Payment and refund flows

Buy credits (Stripe)

  1. User opens Account and chooses credits.
  2. Stripe Checkout is created.
  3. Stripe sends checkout.session.completed webhook.
  4. App creates a PaymentRecord and a CreditHistory entry (+credits).

Buy credits (Telegram Stars)

  1. User opens Account and chooses credits.
  2. App creates an invoice link via Telegram.
  3. Telegram sends successful_payment webhook.
  4. App creates a PaymentRecord and a CreditHistory entry (+credits).

Spend credits (create a plan)

  1. User submits a plan.
  2. App deducts fractional credits based on token usage and pricing.
  3. A CreditHistory entry is created with the exact delta.

Close account (user wants money back)

Typical approach: - If credits are unused, issue a refund in Stripe/Telegram. - Add a CreditHistory entry to remove credits (negative delta) or to zero the balance. - Keep the ledger history intact (do not delete rows).

Refund / correction

If something went wrong: - Process the refund with the payment provider (Stripe/Telegram). This is the only step that moves real money. - Add a CreditHistory entry that reverses the original credit grant. This only changes internal credits. - Optionally update PaymentRecord.status (e.g., refunded).