Skip to content

Expense Mini App #305

Description

@madhead

A Expense Mini App to create a shared expense from a UI: amount, currency (suggested from the group's history + free entry), recipients, optional title. Launched via a group-bound, EXPENSE-scoped deep-link token. FE + BE validation.

Backend

  • No create-expense API exists yet. The creation logic — build the Transaction, fold it into the group balance atomically, retry on optimistic-lock — currently lives inside ConfirmationOKCallbackQueryUpdateProcessor. Extract it into a shared ExpenseService used by both the bot's confirm handler and the new endpoint (single source of truth for the money-write).
  • New endpoint: POST /app/api/group/expense, guarded by AuthorizationPlugin { scope = EXPENSE }.
    • Body: { recipients: [userId], amount, currency, title? }.
    • Payer is NOT client-supplied — it's the authenticated initData user (matches the bot; prevents spoofing).
    • BE validation: recipients non-empty and ⊆ group members; payer ∈ group members; amount > 0 with sane scale; currency non-blank.
  • Suggestions: reuse GroupCurrenciesService (currencies used in the group) and GroupMembersService (recipient candidates → names).

Frontend

  • Build the actual appExpenseApp is a <h1>Expense</h1> stub and expense.tsx has no AppWrapper/i18n/TWA SDK.
  • Form:
    • amount — decimal input, locale-aware
    • currency — combobox: suggested list (from /group/currencies) + ability to type a new one
    • recipients — multi-select from /group/members (names resolved)
    • title — optional
    • payer — shown as the current user (read-only)
  • Submit via MainButton (the idiomatic Telegram affordance); disabled until valid.
  • FE validation mirroring BE; success + error handling (reuse the X-Auth-Error mapping).

Digested from #294 (Mini App findings)

  • ExpenseApp is a stub; expense.tsx has no AppWrapper/SDK/i18n
  • group/currencies + group/members endpoints have no client caller (consumed here)
  • No loading/error/empty states, no ErrorBoundary
  • Missing MainButton integration

Open questions

  • Split model: equal-split only (default — matches the current domain: payer + recipient set, no weights) vs per-person shares? (shares = separate future feature)
  • "Pay on behalf of" someone else, or payer always = current user? (default: current user)
  • Default recipient selection: all members, or all-but-payer?
  • In-app confirmation step (like the bot's OK/cancel) or direct submit?
  • Idempotency key to guard against double-submit / network retry?

Notes / technical details

Dependencies

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions