feat(tipping): let a user set their own minimum tip - #1360
Merged
Conversation
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
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
marked this pull request as ready for review
August 28, 2026 15:44
This was referenced Aug 28, 2026
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.setMinDmChatInitFeedid lack themergeLocalProfileits siblings have, so asave 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: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.
AmountEntryDelegategained that min-only Info branch.AmountEntryDelegategained aconfirmEnabledgate for this; it defaults to always-on, so thefive 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.UpdateUserProfiletooknameSourceplusincludeName/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 ownparameters:
Name(source)needs aDisplayNameSource, the others need nothing. Minimum tip is astep 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 comesafter this one, the same rule the existing steps already apply by hand.