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 app —
ExpenseApp 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)
Open questions
Notes / technical details
Dependencies
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
Transaction, fold it into the group balance atomically, retry on optimistic-lock — currently lives insideConfirmationOKCallbackQueryUpdateProcessor. Extract it into a sharedExpenseServiceused by both the bot's confirm handler and the new endpoint (single source of truth for the money-write).POST /app/api/group/expense, guarded byAuthorizationPlugin { scope = EXPENSE }.{ recipients: [userId], amount, currency, title? }.initDatauser (matches the bot; prevents spoofing).amount > 0with sane scale; currency non-blank.GroupCurrenciesService(currencies used in the group) andGroupMembersService(recipient candidates → names).Frontend
ExpenseAppis a<h1>Expense</h1>stub andexpense.tsxhas noAppWrapper/i18n/TWA SDK./group/currencies) + ability to type a new one/group/members(names resolved)MainButton(the idiomatic Telegram affordance); disabled until valid.X-Auth-Errormapping).Digested from #294 (Mini App findings)
ExpenseAppis a stub;expense.tsxhas noAppWrapper/SDK/i18ngroup/currencies+group/membersendpoints have no client caller (consumed here)ErrorBoundaryMainButtonintegrationOpen questions
Notes / technical details
AuthorizationPluginwired in Architecture review: financial-integrity, correctness & Mini App findings #294): aHISTORYtoken must not reach this endpoint, and vice-versa.token_expired. Acceptable for deep-linked launch; revisit only if it bites.Dependencies
ExpenseServiceextraction (BE). Otherwise independent of History Mini App #125.