I won't go into a ton of detail because payments companies are very protective of their secrets. But imagine the kind of workflow diagram that product managers tend to produce at the beginning of a project. It doesn't look too awful. Not a ton of decision points. But over time you discover edge cases and interactions. If you started by replicating the original diagram in your code, none of the changes seem to be very bad. Just add another if/then here and another one there. I don't know what the mathematical formula is, but there is some kind of tipping point where this goes from "this is fine" to "holy fucking hell this is a nightmare."

I was hired on to the team about 3 years after the nightmare tipping point. There were no tests. There were no real expected outcomes. Just angry customers sometimes. And angry product managers who were still pointing at the original workflow diagram telling us to just make it do that. And there were people who had been dealing with it who were all, "Yeah, that's not even close anymore."

So instead of trying to solve all that mess of code, I backed up the chain a little bit and looked at things from the point of view of what variables are getting passed into this shitshow and what are their possible valid values. I came up with about a dozen that matter. Really variable variables like the dollar amount of a transaction don't really matter. It's other things like has this transaction settled? Is this a chargeback? Etc. Lots of binary switches. Some of them could have 3-4 legit dispositions. But if you look at their sources and what they imply, the possible valid states isn't the combination of all possible positions of all 12 variables. Some of them are exclusive. So I figured out that there's about 100-ish valid ways for these 12 variables to be arranged. And that only those 12 variables were actually driving all the logical outcomes.

At that point I was able to go to the product managers with a spreadsheet and say, "Okay. Tell me what's supposed to happen when a=True, b=3, c=debit, d=whatever, etc." They would go huddle up for a day or two and then come back with an answer. X, Y, and Z should happen, but not A and B, and not-not-C. And over time we were able to get a mapping from all valid states to all valid outcomes.

We all know what a finite state machine is, and it would be trivial to implement in any language. But in this case, lots of different systems needed to be able to work with this, and the only thing they had in common was the ability to connect to the database. So that's where we did it.

I had two tables. One was the rule and one was the outcome. Each row of the rule table had vars/values of a valid state and a foreign key to the outcome table it mapped to. The outcome table columns where the names of transformation functions and bool flags on them. So the process was 1) query for the relevant variables, 2) bounce them off the rule table to find a match or error on no match (which was useful in finding bugs higher up the call chain), 3) query the outcome table from the foreign key and get the values of the transformations, 4) push all of that into a stored proc that had a totally flat if/else chain 5) exec state transformations where indicated. Very easy to grok, not hard to implement, and as I said, the rules and outcomes could be added to or changed very easily by the people who were expert in understanding the business rules.

I hope that's enough detail to be useful to the people who were asking for it. I don't really want to be more specific. I think it would be crude and maybe a tad unprofessional to go deeper than that.

If I may, however, I want to respond to the person that said logic wants to be code and not data. And that it's risky to do otherwise. Without getting on a LISPy high horse (I've ridden that horse only a few times), I disagree. Sometimes logic drives data and sometimes data drives logic. I don't think there's as clear a distinction as you are suggesting, and I don't understand how you draw the line.

E.g.,
if a ======== 1{ //No seriously, I mean really really equal
return a
}

What's happening here? Is your logic in the driver's seat? Or is the data in the driver's seat? Who is determining the behavior? Your holy code? Or the value that gets passed to it?

I feel like arguing about data and code is like arguing about the difference between putting a net in place on a river and letting the fish get caught and dragging a net behind a boat so fish get caught.

But I'm not going to dwell on it too much. I think it's kind of silly to get dogmatic about it.