Building Software That Runs Cross-Border
What we've learned shipping production systems across regulatory boundaries, time zones, and infrastructure constraints — and why most cross-border failures aren't technical.
By Igor Riera
Most teams think cross-border software means adding a language picker and a currency dropdown. That’s localization. It’s the surface. The actual complexity lives underneath — in regulatory rules that change per jurisdiction, payment processors that don’t behave like Stripe or other established players in the country of origin, tax structures that reshape your data models, and infrastructure constraints you won’t find in any API doc.
We’ve shipped production systems across these boundaries. PayTable processes orders and payments for restaurants in Mexico. Our insurance integration work has spanned carriers and enrollment platforms across multiple US states, each with its own filing requirements and compliance timelines. The pattern is consistent: the hardest problems aren’t in the code. They’re operational, and they require a blend of understanding that cuts across multiple facets and domains.
The failures nobody debugs
When a payment fails in Mexico because your settlement window doesn’t align with the processor’s clearing schedule, that’s not a bug. It’s a business rule you didn’t know existed. When an insurance eligibilility rule results in different output because two states define it differently, your integration tests might all pass, but the result might not match reality.
These are operational implementation failures that don’t show up in staging. They show up on day 30 in production, usually at the worst possible time.
Each market is a deployment context
The approach we’ve landed on: treat every market, every jurisdiction, every regulatory environment as its own deployment context. Not a feature flag or a config toggle buried in a settings page, but rather a first-class context that shapes how the system behaves from the data layer up.
In practice, that looks like this:
# market-context.yaml
markets:
mx:
payment_processor: "t1_pagos"
tax_model: "inclusive"
tax_rate: 0.16
invoicing: "cfdi"
settlement_window_hours: 48
supported_languages: "auto-detect"
us:
payment_processor: "stripe"
tax_model: "additive"
tax_rate: null # resolved per state
invoicing: "standard"
settlement_window_hours: 24
supported_languages: ["en", "es"]
Tax alone illustrates the gap. In Mexico, IVA at 16% is inclusive. The price on the menu is the price the customer pays. In the US, tax is additive and varies by state, county, sometimes city. That single difference changes your receipt rendering, your reporting, your refund logic, and your accounting integration. One config value, four downstream systems affected.
What the API boundary looks like
We structure market-specific behavior behind a consistent interface so the rest of the system doesn’t need to care which jurisdiction it’s operating in:
public interface IMarketContext
{
string MarketCode { get; }
Task<TaxResult> CalculateTax(OrderItems items);
Task<PaymentResult> ProcessPayment(PaymentRequest request);
Task<InvoiceResult> GenerateInvoice(CompletedOrder order);
}
Each market gets its own implementation. Mexico’s ProcessPayment talks to T1 Pagos and handles CFDI invoicing. The US implementation resolves state-level tax rates and routes to Stripe. The calling code doesn’t branch on country, instead it asks the context.
This isn’t clever architecture for its own sake, but rather it’s what we arrived at after watching brittle if-else chains break every time a new regulatory requirement landed.
Why this matters if you’re expanding internationally
If your software works in one market and you’re planning to enter another, the question isn’t “how do we translate the UI?” It’s “how many assumptions have we hardcoded about how payments, taxes, compliance, and infrastructure work?”
Every assumption is a future production incident.
We’ve learned this the slow way — through countless integrations, through building a payments platform that had to actually clear transactions in Mexico, through insurance data feeds that had to satisfy regulators in different states. The lesson is always the same: cross-border is not just feature but an architecture decision that touches everything.
If you’re building for multiple markets, start from the deployment context. Everything else follows from there.