Building the zenti-ruby Gem: The Story Behind a Quietly Powerful Payments Integration
Where It All Began: A Client With Growing Ambitions
A couple of years ago, I found myself back on a call with one of my longest-standing clients — an ecommerce group that managed multiple niche brands. They were gearing up for a new wave of growth: fresh product lines, marketing pushes, and even new international storefronts.
But as always with growing businesses, scale didn’t just mean more customers — it meant more payment processing volume, more scrutiny on fees, and more pressure to keep transactions flowing seamlessly.
They’d recently negotiated favorable terms with Zenti, a payment gateway that promised them excellent interchange rates and a hands-on account rep. From a finance perspective, it was a win. But from an engineering angle, it left them with one glaring hole:
Zenti had no official Ruby support.
No gem, no wrapper, no clean SDK. Just a PDF full of REST API endpoints.
The client’s entire tech stack revolved around Ruby on Rails. Their developers were comfortable with it, their CI pipelines were built for it, their error tracking, monitoring, and deployment strategies all assumed a familiar Ruby environment.
That’s when they turned to me, with the same look I’ve seen countless times before: “You’ve solved stranger problems for us… could you build something to make this work?”
I remember the moment clearly — sitting in my home office, the late afternoon sun streaming through the window, listening to the frustration in their voice. They’d just spent weeks negotiating this deal, only to hit this technical roadblock. It was one of those moments where you can feel the weight of a business decision hanging in the balance.
Why a Dedicated Gem — and Why Open Source?
It’s funny how quickly some decisions seem obvious in hindsight. At the time, we explored all sorts of options.
Maybe we could just write some lightweight service classes inside each Rails app, sprinkle in direct HTTP calls to Zenti’s API, and call it a day. But the more we talked, the more problems that created:
- They ran multiple Rails apps under different brands. Duplicating the same raw payment code everywhere was a recipe for mistakes and inconsistent behavior.
- Future upgrades — like adding stored cards or fraud signals — would become nightmares to coordinate across repos.
- Debugging issues in live payment flows is stressful enough without hunting through five different codebases trying to remember which app talks to Zenti in which way.
I remember sketching out the architecture on a whiteboard during one of our planning calls. The client’s CTO was there, and we kept running into the same question: “What happens when we need to add a new feature?” Every time, the answer pointed back to the same conclusion — we needed a centralized solution.
That’s when I pitched them the idea of building a dedicated Ruby gem. A single, versioned, reusable library that would become the canonical way all their apps talked to Zenti.
Even better, I suggested making it open source under their GitHub org, for a few reasons:
✅ It would demonstrate to partners and vendors that they took engineering seriously.
✅ It could help attract developers — a small but clear sign of technical maturity.
✅ It might even earn community contributions or scrutiny, improving quality over time.
The client immediately saw the appeal. With their blessing, I got to work.
I remember the excitement I felt that evening, knowing I was about to build something that would become a foundational piece of their infrastructure. There’s something special about creating tools that other developers will rely on day after day.
Getting Familiar With Zenti: Not Quite Plug-and-Play
You learn a lot about a payments provider by reading through their API documentation — and even more by actually trying to run transactions.
On paper, Zenti’s REST API was straightforward. Endpoints for purchases, refunds, captures. JSON requests and responses. The usual HTTPS setup.
But underneath, there were quirks that only revealed themselves through hours of testing:
- Some responses inconsistently wrapped data, forcing me to handle multiple shapes.
- Certain fields documented as always present would occasionally be
null
. - Their sandbox environment simulated failures in slightly awkward ways — useful, but it meant carefully crafted test flows to catch AVS mismatches or CVV declines.
I spent an entire weekend just running test transactions, methodically working through every possible scenario. My wife would walk by my office and see me hunched over the keyboard, muttering things like “Why is this field missing again?” or “That’s not what the documentation says!”
It became clear that this gem wouldn’t just be a thin transport layer. It would have to normalize Zenti’s eccentricities into something developers could predict.
I spent days running test transactions, deliberately triggering errors, inspecting headers, and documenting all the subtle deviations from the official spec. In payments, the devil is always in the details.
There was one particular moment that sticks with me — I was testing a declined transaction scenario, and the response format was completely different from what I expected. I had to dig through their support documentation, send a few emails, and eventually realize that their sandbox environment had some quirks that weren’t well documented. It was frustrating at the time, but it taught me to build the gem with a healthy dose of defensive programming.
Thinking About the Future Team, Not Just the Current Code
Throughout the project, I found myself thinking less about “how can I make this work right now”, and more about “how can I make this obvious to the next developer who has to troubleshoot a weird edge case at 2am?”
Because it’s rarely your own future self you’re protecting — it’s usually some engineer you’ll never meet, who just got hired or inherited the project. They’re the one who’ll be grateful you wrote clear error classes, consistent return objects, and meaningful logs that never exposed sensitive card data.
I remember thinking back to my own experiences debugging payment issues in the middle of the night. The panic of a failed transaction, the pressure to fix it quickly, the frustration of unclear error messages. I wanted to spare future developers that experience.
So even when no one asked, I made sure the gem was:
- Lightweight, to avoid adding huge maintenance burdens.
- Consistent in its data structures, so every success or failure looked the same from the caller’s perspective.
- Safe by default, especially around logging. In payments, leaking unmasked card numbers into logs is a compliance nightmare. I made sure that couldn’t happen by design.
I spent extra time on the error handling, creating specific exception classes for different types of failures. A network timeout should feel different from an invalid card number, and both should be different from a processor decline. Each error type carried meaningful context that would help with debugging.
The logging was particularly important. I remember reading horror stories about payment systems that accidentally logged full card numbers or CVV codes. I built the gem to automatically mask sensitive data, even if someone accidentally tried to log a transaction object. It was one of those “better safe than sorry” decisions that I’m glad I made.
The Little Things That Made It Robust
It’s funny — when people later look at a simple gem API, they might think it was easy to build. But the confidence comes from all the little defensive details baked inside.
I remember wrestling for hours with:
- Handling timeouts gracefully, so a transient Zenti hiccup wouldn’t blow up an entire checkout session.
- Ensuring the gem could retry on certain idempotent failures, but never accidentally double-charge a card.
- Navigating slightly inconsistent sandbox behaviors, so local tests could reliably simulate fraud flags or processor declines.
The retry logic was particularly tricky. I had to be absolutely certain that retrying a failed request wouldn’t result in a double charge. I spent hours reading through Zenti’s documentation about idempotency keys, testing various failure scenarios, and building safeguards into the gem.
There was one late night where I was testing the timeout handling, and I realized that a simple network hiccup could leave a customer’s checkout session in an uncertain state. I ended up implementing a more sophisticated retry mechanism that could distinguish between transient failures (worth retrying) and permanent failures (not worth retrying).
None of that shows up in a simple purchase
call signature. But it’s precisely why the client’s multiple storefronts could later adopt this gem without flinching. It worked the same everywhere, under load, across edge cases.
I remember the first time I showed the gem to the client’s lead developer. He looked at the simple API and said, “That’s it? It looks so simple.” I smiled and said, “That’s the point.” The complexity was hidden inside, where it belonged.
The Satisfying Rollout: Multiple Stores, One Payments Layer
Once the gem stabilized, rolling it out across the client’s family of brands was almost anticlimactic — in the best way possible.
Store after store integrated the gem, pointed it at the same Zenti credentials, and immediately gained a robust, consistent payment pipeline. They could now:
✅ Handle purchases, partial captures, and refunds through a unified layer.
✅ Log meaningful transaction IDs across systems, making it easier to trace disputes or reconcile settlements.
✅ Implement new fraud checks or tokenization with a single gem update — no chasing five different code branches.
I remember the first production deployment. We’d done extensive testing in staging, but there’s always that moment of truth when real money starts flowing through your code. I was monitoring the logs closely, watching for any signs of trouble. But everything worked smoothly from the first transaction.
From my side, it was quietly gratifying. It meant fewer late-night emergencies, fewer Slack pings from anxious managers, fewer “who wrote this code?” moments months down the line.
The client’s operations team was particularly happy with the unified logging. Before the gem, they had to check multiple systems to trace a transaction through their various storefronts. Now, every transaction had a consistent format and could be traced back to the original request.
Watching It Live: Millions Processed Through Code I Touched
There’s something a little surreal about seeing financial dashboards tally up millions of dollars in transactions, knowing that beneath it all, there’s a small bit of code you wrote — code that decides whether a card gets charged, how failures are retried, which transaction references are logged.
Most people browsing these online stores would never think twice about how their card info flows from a browser form to a gateway to a bank’s settlement batch. But engineers like us know it’s a delicate dance of security, compliance, and rigorous fault tolerance.
I remember the first time I saw the monthly transaction volume report. The numbers were staggering — millions of dollars flowing through this little gem I’d built. It was humbling and exciting at the same time. Every transaction represented a real customer, a real purchase, a real business transaction that depended on my code working correctly.
That’s why even a “small gem project” like this felt disproportionately important. The entire business relied on these transactions clearing. Any hidden bug could ripple out into lost revenue, upset customers, chargebacks, or compliance headaches.
Building it carefully, testing it obsessively, and rolling it out methodically wasn’t just good craftsmanship — it was essential risk mitigation for a business trusting me to keep their cash flowing.
I found myself thinking about the responsibility differently after that. It wasn’t just about writing clean code or following best practices. It was about ensuring that real businesses could operate smoothly, that customers could complete their purchases, that money could flow where it needed to go.
The Long-Tail Payoff: Quiet Tools That Just Keep Working
Years later, long after the initial engagement wrapped up, I still occasionally see the gem pop up in my GitHub notifications — a star here, a fork there, the odd developer question.
It’s a quiet legacy. No flashy product launches or press releases. Just a small, sturdy tool that keeps multiple businesses running smoothly, day after day.
That’s the funny thing about infrastructure work. When it goes right, no one notices. Orders keep flowing, dashboards keep ticking up, finance teams reconcile ledgers without drama. The fact that it’s unremarkable is the greatest compliment.
I sometimes wonder about the developers who are using the gem now, years after I wrote it. They probably don’t think much about who built it or why — they just expect it to work. And that’s exactly how it should be.
Every now and then, I’ll get a GitHub notification about a new issue or pull request. Sometimes it’s a bug report, sometimes it’s a feature request, sometimes it’s just a developer asking a question. I try to respond quickly and helpfully, because I remember what it was like to be on the other side of that equation.
Reflections: Why I Love Building These Under-the-Hood Tools
Looking back, I realize these are some of my favorite projects. Not the big splashy marketing sites or the campaigns that burn bright for a month then fade.
But the little gems, the middleware services, the infrastructure bridges — the tools that quietly move money, sync critical data, or keep warehouses humming without fanfare.
Because long after the campaign banners come down or the product lines shift, these foundational systems keep adding value. They’re like reliable plumbing hidden behind the walls, essential and trusted.
There’s something deeply satisfying about building tools that become part of a company’s infrastructure. You’re not just solving a problem for today — you’re building something that will continue to provide value long after you’ve moved on to other projects.
I think about the other infrastructure projects I’ve worked on over the years — the data synchronization services, the monitoring systems, the deployment pipelines. None of them were glamorous, but they all became essential parts of the businesses they served.
A Note to Fellow CTOs, PMs, and Engineers
If you’re reading this because you’re wrestling with integrating a new payment provider, or trying to decide whether to build a shared library versus sprinkling logic across your codebase — I’d encourage you to take the long view.
Small, well-designed shared tools often pay for themselves dozens of times over. They reduce duplicated effort, lower the cognitive load for future teams, and dramatically cut down your risk surface.
It doesn’t have to be glamorous. It just has to be thoughtfully built.
I’ve seen too many companies fall into the trap of duplicating code across multiple applications, only to find themselves maintaining the same logic in five different places. It’s a recipe for bugs, inconsistencies, and maintenance headaches.
The investment in building a shared library might seem like extra work upfront, but it pays dividends over time. Every new feature, every bug fix, every improvement gets automatically applied to all the applications that use the library.
And there’s something else — shared libraries force you to think more carefully about your API design. When you know that multiple applications will depend on your code, you’re more likely to get the interface right the first time.
If You Want Similar Systems Built
This kind of work — building clean, reliable integrations that become invisible assets in your tech stack — is exactly what I love doing.
If you’re planning a move to a new payment gateway, need a custom ecommerce integration, or just want a second set of eyes on how you’re processing financial data, I’d be glad to help.
You can see more of my technical stories at kevinhq.com or reach me directly at kevin@kevinhq.com.
Thanks for reading this long, nerdy behind-the-scenes journey.
This story is part of a series about the quiet infrastructure work that keeps businesses running. Sometimes the most impactful code is the code that nobody notices — until it stops working.