Moonpig: a billing system that doesn't suck
blog.plover.com
blog.plover.com
<rant> Every time I see floats being used for stuff like currency, which is still more often than not, I'm reminded how very immature and unprofessional the business of programming still is. This situation has barely improved in the 25+ years I've been in IT.
For fuck sake, we're not talking "advanced" stuff like architecture, algorithms, unit testing, SOLID, or whatever, but basic use of simple tools that's equivalent to understanding that you don't use a hammer to drive in a screw.
I can't imagine any other profession where basic stuff is executed so cluelessly unless it's a deliberate scam.
Right now, as we speak, clients and/or employers are paying many thousands of dollars/euros/whatever to have brand new software build by programmers who will use floats for what are supposed to be exact amounts of money the core business of their employer/client depends on.
It's a pathetic state of affairs which makes the phrase "software is eating the world" an utterly terrifying idea.
And now there's a movement that wants to teach everyone "how to code".
The apocalypse will be caused by a rounding error. </rant>
http://stackoverflow.com/questions/3730019/why-not-use-doubl...
> This situation has barely improved in the 25+ years I've been in IT.
Improved? 25+ years ago this would have been written in COBOL using decimal arithmetic as a matter of course — because 55 years ago the designers of COBOL knew better than to put floating point in a business language.(This is not a defense of COBOL, which is terrible in so many ways, because they didn't know much about programming in 1955 — but they knew they had to understand the problem domain.)
Off the top of my head: Electricians. I've seen first-day-on-the-job-level mistakes in every single property I've rented. Most licensed electricians (nb: I am in the UK) don't seem to be able to safely wire a simple light switch.
Just save billing stuff in cents and do internal billing math using arbitrary precision integer math and round to %0.2f at the end. Nobody cares about mistakes that involve fractions of a cent, or whether their subscription lasts 1 year or 365 days or 52 weeks of 7 days. Just pro-rate in the easiest way possible and round in favor of the customer. You just don't need any of this fraction-of-a-cent BS.
If your billing math is easy you can even explain how it works in the FAQ so customers won't feel cheated. If your billing is complex and uses millicents in an misguided attempt to "do the right thing" then you can't explain to your customers how it works and why it's fair. That's no good from the customer's point of view.
Billing systems are easy to over-engineer and in my experience you just want a system that's straightforward and correct. That way you can focus your energy on more important stuff.
If people have a small amount of credit just give them an extra day of service. If they have a small negative credit either ignore it or subtract one day the next time they renew.
If there is a case for using milicents I sure haven't seen it.
I'm not sure I understand the value of a system like that though. In the systems I've used and built, most customers see their service as an annual thing that renews on a particular date every year, for which they pay an annual fee. Doing it as a daily fee simplifies the special cases like converting between plans but makes it harder to deal with "your annual plan always renews on this date".
Let's say you have a service that is $20 per year. That is 5.475¢ per day (I'm assuming a year of 365.242 days; if you do an integral number of days and account for leap years, then it will be a different daily rate in regular years and leap years). If you are doing calculations which pro-rate certain things per day, such as allowing people to transition between different subscription types while applying their existing balance to the new subscription, and you rounded that down to 5¢ per day, then when you multiplied that back out to a year it would only be $18.26 per year, not $20 per year. That's a pretty substantial difference.
Where you fix your precision depends on how much error you're willing to deal with. In the article, he describes how using millicents leads to an error of 165m¢ (.165¢) per year, if you apply the rounded daily rate and multiply by the number of days, which is deemed acceptable for their purposes. For a much more extreme example of precision, Tarsnap quotes their prices in picodollars per byte per month (https://www.tarsnap.com/picoUSD-why.html) and performs all calculations in attodollars per byte per day.
(I get the idea that they generally wanted to be able to report the remaining subscription in terms of days)
[1]: https://moonpig.com
Seconded.
Second, that's not relevant, but is relevant to the rest of my comment. Trademark law does not bar two companies from having the same name. There are tens of thousands of companies with the same name. Most words have hundreds of separate listings in the trademark registry. Ever heard of Delta the airline, Delta the faucet company, Acme the laboratory, Acme the furniture company, Acme the grocery store chain, Apple the computer company or Apple the record label?
Trademarks are limited both regionally and by the categories of goods and services the marks are registered under. If there is no likelihood of consumer confusion, there is not going to be infringement. Moonpig's trademark is limited exclusively to "printed matter, namely, books in the field of humor, stationery, greeting cards, and calendars".
Guess what, any decent relational database will have a MONEY or at least a fixed precision DECIMAL type, as well as DATETIME and all the ancillary functions for computing intervals and adding and subtracting time. Half of your problems were because you weren't using a data store that properly represented your data.
But apparently "moonpig" is not just a totally random name, if two different groups can come up with it.
The author initially states that all relational databases are good for is speed but then goes on to highlight flaws in their object store that could all have been mitigated by using an RDS:
* schema changes without needing to write programs to update all your data
* data aggregation (and in fact any sort of adhoc reporting)
* data storage independence.
There's also no mention of data integrity but this is a big risk as soon as the original authors are no longer involved.
Object oriented data stores are hierarchical/network databases. Relational databases were introduced to replace these years ago and for good reason.
Just use the right tool for the job.
Yeah, so, that tells me that sadly, regardless of the rest, it sucks (sorry). Not supporting multiple currencies, as he says, does vastly simplify things, but is also a massive feature-gap, as any merchant of scale will deal in multiple currencies. Now try doing it with multiple currencies, and still avoiding floating point arithmetic. Oh, and also ensure that it'll deal with charging sales tax/VAT on transactions of £0.01 correctly.
Although now you are arguing that they should have designed the system with the possibility of multiple currencies, rather that actually supporting them. There's not enough information in the article to determine if support for multiple currencies is possible or not, so I guess you read the source?
Specifically, that ledger internally uses an arbitrary precision library to store data, then only cuts down or rounds numbers at the display level, and the use of libraries and a database vs. a file format and highly optimized code.
But that is current me being a slight code snob as I did use Perl as my prime language for a few years but that was 13 years ago.
As someone who have worked with many payment and billing systems I can see some of the pain addressed. But also reinventing the flat wheel is something I try to avoid and keeping things simple so for example these days I use BigDecimal, BigInteger datatypes and similar in more modern languages.
Ps. as someone who just ordered cards from moonpig.com, the name was initially confusing.
(The book is available for free online, btw: http://hop.perl.plover.com/
There's a link there to a page translating the concepts into Javascript, btw -- there's a similar link for Ruby but it doesn't work, nor does a revised link -- changes the word "category" to "categories" in the URL -- that I found Googling, but this does: https://web.archive.org/web/20130315020240/http://blog.grayp... )
Also, hire a branding expert - "moonpig" is awful in every way possible.