Skip to content

feat(tipping): let a user set their own minimum tip - #1360

Merged
bmc08gt merged 2 commits into
code/cashfrom
feat/min-tip
Aug 28, 2026
Merged

feat(tipping): let a user set their own minimum tip#1360
bmc08gt merged 2 commits into
code/cashfrom
feat/min-tip

Conversation

@bmc08gt

@bmc08gt bmc08gt commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

A user can now set the fee another user has to pay to open a DM with them — the profile's
minDmChatInitFee, which the design calls "Minimum Tip" because a tip is what opens the DM.
Nodes 9641:16759, 9641:16761, 9541:10951, 9553:113170, 9544:20160.

The whole services stack for this was already in place; only the app layer was missing.
ProfileController.setMinDmChatInitFee did lack the mergeLocalProfile its siblings have, so a
save wouldn't show up in the UI until the next profile refresh — that's fixed here.

Two ways in

The "Finish Your Profile" checklist on the You tab gains a Minimum Tip row next to Profile Picture,
and My Account gains one between Profile Picture and Require Biometrics (node 9544:18478). Both
read the fee straight off the profile, so setting it in one place closes the other.

The checklist now disappears entirely once both its steps are done, rather than sitting at 2/2 with
two ticks.

Entry screen

Reuses AmountEntryScreen. Two things differ from the send-side tip entry:

  • There's no ceiling. What you ask to be paid isn't bounded by anyone's balance, so the preset
    minimum is the only bound — and with no max to describe, it reads as a standing "$1.00 minimum"
    hint rather than only appearing on error. AmountEntryDelegate gained that min-only Info branch.
  • Confirm is a save, so it stays inert until the entry differs from what's stored.
    AmountEntryDelegate gained a confirmEnabled gate for this; it defaults to always-on, so the
    five existing callers are unaffected.

Below the minimum, confirm raises the standard alert (node 9544:20160). Leaving without saving
discards the entry — there's no draft held, so an abandoned amount just never reaches the profile.

Profile flow takes a list of steps

AppRoute.UpdateUserProfile took nameSource plus includeName/includePhoto/includeUsername.
Every caller had to name a display-name source even when it skipped the name step — the
username-only call carried a comment saying so. Minimum tip would have made it four booleans.

It now takes steps: List<UpdateProfileStep> in render order, with each step carrying its own
parameters: Name(source) needs a DisplayNameSource, the others need nothing. Minimum tip is a
step rather than its own route, which is where it belongs — it edits the profile like the other
three. That also deletes MinimumTipSource: "Next" vs "Save" follows whether another step comes
after this one, the same rule the existing steps already apply by hand.

The "You" tab's profile checklist has carried a minimum-tip row that did
nothing, because there was no way to set one — the amount came from
server-supplied regional presets and the row never completed. The
contract has since gained SetMinDmChatInitFee and the profile carries
min_dm_chat_init_fee, so the row now opens an amount entry and closes
once a fee is stored. The same screen is reachable from My Account,
where the fee is a standing setting rather than a setup step.

The entry point only decides how the confirm button reads: "Next" from
the checklist, "Save" from My Account. Save stays inert until the entry
differs from what is stored, so opening a prefilled screen and leaving
it alone cannot re-submit the same amount.

Two changes to AmountEntryDelegate carry that:

- `confirmEnabled`, an extra gate ANDed with "something was entered".
  Every existing caller defaults it to always-allowed.
- With a minimum and no maximum, the floor now shows as a standing Info
  hint instead of appearing only once the entry breaks it. The five
  existing callers all pass a maximum, so the branch is new-flow only.

ProfileController.setMinDmChatInitFee also merges the saved fee into the
local profile, which its siblings (setDisplayName, setUsername,
setProfilePicture) already did. Without it the checklist row and the My
Account row would not reflect a save until the next profile refresh.

Node 9553:113241 asks that abandoning a change discards it; there is no
draft to persist, so that falls out of the screen having no saved state.
@bmc08gt bmc08gt self-assigned this Aug 28, 2026
@github-actions github-actions Bot added type: feature New functionality area: network gRPC, connectivity, API, exchange rates labels Aug 28, 2026
@bmc08gt
bmc08gt marked this pull request as draft August 28, 2026 15:32
`UpdateUserProfile` took a `nameSource` plus `includeName`/`includePhoto`/
`includeUsername`, so every caller had to name a display-name source even when it
skipped the name step — the username-only call at MenuScreenViewModel carried a
comment saying exactly that. Adding minimum tip would have made it four booleans.

The route now takes `steps: List<UpdateProfileStep>` in render order, and each
step carries its own parameters: `Name(source)` needs a `DisplayNameSource`,
the others need nothing. A caller asks for the steps it wants and names nothing
else.

Minimum tip becomes a step rather than its own route, which is where it belongs
— it edits the profile like the other three. That deletes `MinimumTipSource`:
"Next" vs "Save" now follows whether another step comes after this one, the same
rule the existing steps already follow by hand.

The "Finish Your Profile" checklist disappears once both its steps are done,
instead of standing there at 2/2 with two ticks.
@bmc08gt
bmc08gt marked this pull request as ready for review August 28, 2026 15:44
@bmc08gt
bmc08gt merged commit 682a14a into code/cash Aug 28, 2026
3 checks passed
bmc08gt added a commit that referenced this pull request Aug 28, 2026
… DM (#1366)

* feat(tipping): charge the recipient's DM-init fee on the tip that opens the chat

Both the in-chat amount entry and the custom tip entry floored at the regional
preset minimum, which is not the amount the recipient set. #1360 gave users
their own minimum, `UserProfile.minDmChatInitFee` — the fee another user pays to
open a DM with them — and neither entry read it.

The fee buys the conversation, so only the payment that opens it pays:

- `TipPaymentDelegate.minimumToOpenDmWith` is that floor, converted through USD
  into the currency the sender is entering in, falling back to the regional
  preset when the recipient charges nothing or a rate is missing.
- `minimumTipFor(userId, ...)` charges it while no DM with that user exists and
  the system minimum once one does. The tip card follows it: presets below the
  floor are dropped from the modal, and `confirmTip` checks it, since a preset
  chip never passes through the amount entry.
- The in-chat entry applies it to the send that opens the chat and drops the
  floor entirely afterwards. A contact DM never had one.

The standing hint reads "$5 minimum" rather than "Minimum tip $5", matching the
prompt that blocks a send below it.

* fix(tipping): match the below-minimum prompt to its design

The prompt read "$5.00 Minimum / Increase the amount to send"; node 9553:20236
has "$5.00 Minimum Tip / Please enter a higher amount". Both strings are shared
by the three places that raise it — the chat send, the tip card, and the custom
amount entry.

The custom entry raised it as a destructive alert while the other two used the
info style, so it also moves to info: nothing has failed, the amount is just
under the floor.

* feat(messenger): call it a send once the tip DM exists

The in-chat entry said "Swipe to Tip" for the whole life of a tip DM. Only the
first payment is a tip: it pays the recipient's fee to open the conversation.
Every one after it is an ordinary send with no minimum, so it now reads "Swipe
to Send" and drops the floor hint, the same as a contact DM.

The label and the floor were already asking the same question separately; both
now read one flow, so they cannot disagree.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: network gRPC, connectivity, API, exchange rates area: onboarding type: feature New functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant