The "Contested" Flag

By: dreev
Spec level:
Gissue: #2921

Changelog

2023-10-10: Add stuff to open questions from Nicky
2023-10-04: Improved exposition of the countdown
2023-10-03: More detail on the countdown/countup
2023-08-26: Added section for weekly invoices after all, bunch of cleanup
2023-08-25: Decided we don't need the notes on weekly invoices here
2023-08-23: Notes on how it'd work with weekly invoices
2023-08-22: Initial braindump by dreev

This is inspired by TaskRatchet’s system where replying to a legit check email pauses the automatic charge. Basically you conservatively assume the charge is contested if there’s any reply at all from the user to the legit check email. Support workerbees want this because it de-stresses support a bit. You don’t have to always rush to get to every email within the window, which might be pretty short if the user takes most of the 24 hours to reply! So unless we were literally in the inbox 24/7 jumping on everything instantly, this should cut down on refunds.

Here’s how we want to implement this, rather than as a literal flag:

We have a tcharge field for every goal. It stands for time of charge, analogous to other fields like tini, tfin, tmin, etc. A goal’s tcharge is normally null but when you derail it gets set to the derail time + 24 hours.

If the user replies to the legit check then tcharge is automatically set to infinity (aka 2099-12-31). Workerbees are trained to never (asterisk) let it stay at infinity.

Promininently in the support inbox sidebar — what we call the raplet — we want to display two things:

  1. Countup: time since the derailment, i.e., the event precipitating the charge
  2. Countdown: time till the charge

For example, if the email thread is about a derailment that happened a bit more than halfway into the 24-hour window, it might look like this:

    DERAILED     13h 36m 03s  AGO
    CHARGING IN  10h 23m 57s

When the charge is charged, a few things happen to that countdown:

  1. The “CHARGING” becomes “CHARGED” with an “AGO” on the end
  2. It changes color, from red to gray because there’s no longer urgency — the charge already happened
  3. It just keeps counting up.

If the charge happened immediately — which might be the case if it was debited as honey money! — then both timers will be in lock step, counting up from the derailment.

Seeing “CHARGING IN INFINITY” or “CHARGING IN 76 YEARS” (if we go with 2099-12-31 = infinity) should be a big red flag for workerbees (and should be suitably up-popped). The workerbee should either cancel the charge or set a new charge time and communicate that explicitly to the user. You never want a charge languishing in the to-be-charged-in-forever state.

Open Questions and To-Dos

  1. First question is whether this is all moot with honey money. Can we just drop the delay, always deduct honey immediately, and accept that that’ll mean more refunds from people not on board with honey money but we’ll just get as many people as possible on board. Overall, we should have fewer refunds because most people will be fine having non-legit derails credited back as honey.
  2. How do we use HelpScout’s tools to get the countup/countdown into the raplet?
  3. What’s a low-friction interface for workerbees to put the countdown back to derail + 24 hours or derail + 48 hours?
  4. Anything on the backend to generalize to handle arbitrary charge times, including at infinity? (See also IOU-NWO.)
  5. Anything wrong with using 2099-12-31 as a proxy for infinity? (Seems fine.)
  6. How does the raplet know which goal the email thread is about? Currently it only knows who the user is, based on their email address.
  7. How do we set up the webhook or whatever with Mailgun or SendGrid to change the charge time when an email is received?
  8. What changes in the IOU-NWO? In IOU-NWO, there’s no longer necessarily a credit card charge corresponding to a derailment payment. There’s definitely a honey debit, which we do immediately, and then maybe a post-dated charge if the honey debit put you negative, or if you’ve opted not to use your honey for derailments. But we might’ve only debited your honey. In that case there’s no deadline to worry about — we can credit back honey any time for free. Proposed generalization: tcharge isn’t a database field in the Goals table but a calculated value: query for any scheduled charge associated with this goal and get the timestamp for it, or let it be null if it already happened. (Probably that needs more careful spec’ing.)
  9. How do we generalize this to multiple derails? Like now, when support delays a charge by 48 hours that also gives the user another 48 hours in which they can’t rack up another derail. Of course, perhaps that is a feature and not a bug? If the user replies “not legit” and then isn’t paying attention to anything and the goal derails again, perhaps it is generally good that we don’t keep racking up charges when they’re already being nonresponsive about the one that already happened?
  10. How do we implement a failsafe or fallback in case workerbees accidentally close a thread with a goal in the charging-in-infinity state? (Tempting to say “just go with 48 hours instead of infinity” but I think that’s an anti-magic violation. The system would effectively be like “this derail might be contested, waiting for human workerbee to decide… [time passes] ok, human workerbee hasn’t weighed in so let’s just assume the derail was legit and charge it”. We want to force the workerbee to make the call — having the charge just go through automatically after some longer delay is a bad way to do that.)
  11. What about preemptively manually delaying big charges when we auto-CC ourselves on them?
  12. The Christmas Button: Nicky has proposed a kind of panic button to delay all charges site-wide, like if something goes horribly wrong (site outage, support workerbees hit by a bus, or just for a big holiday — hence calling it the Christmas Button).
  13. Make sure we preserve this, per Nicky: “It’s really night and day between Beeminder and other places how our users communicate about money with us. Now and then we’ll have someone who blows up instantly, of course; there are always assholes and also always situations where a big charge terrifies someone and they blow up. But the conversation very rarely starts with “you assholes, give me back my money”, and almost never with an established user (the ones who instantly fly off the handle usually don’t know us yet). I think that’s a really good thing about Beeminder and something we should try to keep, and being nice and explicit (and always giving people a chance to get back to us) helps there so much.”
  14. If we did for whatever reason let a nextcharge stay at infinity and the user derailed that same goal again and didn’t reply to the legit check, what would happen? Are they just forever blocked from paying pledges on that goal?

Appendix: How it works in the weekly invoices world

Ignore this section until we actually do weekly invoices. I just wanted to make sure everything above still works.

[OOPS THIS IS ALL WRONG. It doesn’t account for multiple derails in the same week. We’re hashing that out as part of the honey money spec.]

Let’s say that the weekly invoice time is noon on Fridays. Maybe that’s specific to the user, maybe they can even choose it — doesn’t matter for our purposes here. As above, we have a tcharge field for every goal that’s normally null but when you derail it gets set to a timestamp in the future.

When you derail we set tcharge to the upcoming Friday noon.

(If necessary we’ll complicate that slightly. Namely, IF the upcoming Friday noon is less than 24 hours away then add a week. Without that clause, derailing at 11:58am on Friday would mean paying for it 2 minutes later. That may or may not be ok! It would mean that on average you’ll pay exactly 3.5 days after derailing.)

So every Friday at noon, we collect all your goals with tcharge ≤ now and charge you for them all at once, setting them back to null.

Regardless of all that, when the user replies to a legit check, that sets tcharge to infinity. Support workerbees should have a simple UI for setting tcharge back to the next invoice time, or the invoice after that if the next invoice time is too imminent. It’s also fine to set the tcharge to anything sooner than the next invoice time. That’s the same as setting it to the next invoice time. It’ll be overdue but will still happen at invoice time.

For now workerbees would just be setting tcharge back to derail time + 24 hours or + 48 hours and even in a weekly invoices world, that works.

Related Links