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)
- User opens Account and chooses credits.
- Stripe Checkout is created.
- Stripe sends
checkout.session.completedwebhook. - App creates a
PaymentRecordand a CreditHistory entry (+credits).
Buy credits (Telegram Stars)
- User opens Account and chooses credits.
- App creates an invoice link via Telegram.
- Telegram sends
successful_paymentwebhook. - App creates a
PaymentRecordand a CreditHistory entry (+credits).
Spend credits (create a plan)
- User submits a plan.
- App deducts fractional credits based on token usage and pricing.
- 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).