A checkout that works in Berlin can fail in Jakarta. A payout rail that clears in São Paulo may not exist in Moscow. Payments are not one problem with one API; they are dozens of regional problems wearing the same word. Card networks dominate parts of Western Europe, bank transfers and e-wallets dominate Southeast Asia, and local processors like YooKassa carry the load where Stripe and PayPal do not operate. If you sell software that ships to more than one country, the payment stack is not a detail you bolt on at the end. It is a variable that changes per deployment.
This matters most for OEM partners, who take a platform and stand it up for their own customers in their own markets. An OEM does not get to pick one processor and impose it on everyone. Each deployment lands in a region with its own rails, its own compliance regime, and its own customer expectations at the point of payment. The question is not "which PSP do we integrate?" It is "how do we integrate any PSP, per region, without forking the codebase every time?"
The obvious approach is also the expensive one. You pick a processor, you call its SDK directly from your checkout flow, and you read its webhook payloads directly in your billing logic. It ships fast and it works for the first market. Then the second market arrives.
Now the second processor has a different capture model. Its webhook JSON uses different field names and a different signature scheme. Its refund endpoint expects a different identifier. Every place your code touched the first processor's shape is a place you now branch, and those branches spread. Checkout grows a conditional. The billing engine grows a conditional. The invoice reconciliation grows one too. Six months later a third market appears and the conditionals multiply again. You are no longer maintaining a payment integration; you are maintaining an ever-growing pile of processor-specific special cases threaded through your core business logic.
The root cause is a dependency pointed the wrong way. When checkout depends on Stripe's concrete API, checkout knows too much. It knows Stripe exists, it knows Stripe's shapes, and it cannot be reasoned about without Stripe in the room. That coupling is what makes the second and third market cost as much as the first.
VBWD's architecture inverts that dependency. Payment providers are plugins that sit behind a common SDK adapter port, and the checkout flow and the billing engine depend on the port, never on a concrete processor. The port defines a small, stable contract that every provider plugin implements:
Stripe, PayPal, and YooKassa ship as plugins against this contract today. The important property is not the count of providers; it is that the core platform cannot tell them apart. Checkout calls authorize and capture. It does not know whether a card network, a redirect-based wallet, or a Russian bank rail is on the other side. When a new regional processor is needed, it arrives as one more plugin implementing the same four methods. It is a bounded, self-contained unit of work, not a rewrite that ripples through the codebase.
This is what "provider-agnostic" means in practice: the surface area a new provider touches is exactly one plugin directory. The core stays agnostic; only the plugin is gnostic about a specific processor's quirks.
The handle-webhook method deserves its own attention because it is where most naive integrations leak processor detail back into the core, and where most of them get security wrong.
A provider does not tell you a payment succeeded in your vocabulary. It tells you in its own, with its own event names, its own JSON, and its own signing scheme. The adapter's job is to translate that into domain-neutral events the rest of the platform understands: payment captured, payment failed. Everything downstream — the billing engine, invoice reconciliation, entitlement grants — subscribes to those neutral events and never sees the raw provider payload. That translation boundary is what keeps a new provider from becoming a new conditional in your billing code.
Two rules are non-negotiable inside that method. First, verify the signature before trusting a single field. A webhook endpoint is a public URL; anything on the internet can POST to it. The signature check is what separates a real settlement notice from a forged one, and it must run before the payload influences any state. Second, a plugin that writes to the database inside an event callback must commit its own session. The event dispatch path does not manage that transaction for you, and a plugin that assumes it does will silently drop writes. If your provider plugin records a payment in a captured-event callback, it owns that commit.
Taking money in is half the story. Marketplaces and multi-vendor OEM deployments also need to pay money out — to sellers, to referral partners, to sub-accounts. Payouts vary by region as much as intake does, so they get the same treatment: a provider-agnostic IConnectProvider port.
The core's payout logic depends on that port, not on Stripe Connect or any single provider's marketplace product. A deployment in one region can settle payouts through one connect provider; a deployment in another can use whatever local rail is available, implemented as a plugin against the same interface. The billing engine orchestrates the flow of funds; the provider plugin knows how a specific network actually moves them. Keeping intake and payout on symmetric, port-based designs means an OEM reasons about both the same way instead of learning two unrelated integration models.
Because providers are plugins and the core depends only on ports, which rails are active is a per-deployment decision, not a code decision. VBWD is self-hosted and source-available — Python/Flask with Postgres on the backend, Vue 3 on the frontend, Docker for delivery — so each OEM runs its own instance and enables the plugins that fit its market.
An OEM serving the EU enables the card and wallet rails its customers expect. An OEM serving customers in Russia enables YooKassa. An OEM operating across several regions runs several deployments, each with its own set of enabled payment plugins, all sharing the same core and the same checkout code. Nobody edits the billing engine to change which processor is live. Enabling or disabling a provider is configuration, and it is scoped to the deployment that needs it. The deployment docs cover how plugin enable state is persisted and mounted so all parts of a running instance agree on which rails are on.
This is also where the licensing model fits the OEM case cleanly. VBWD is BSL 1.1: free for commercial use while annual VBWD-attributable sales stay below the value of 6.7 BTC, with a commercial license required above that. It is source-available, not public-domain, and it is not CC0 — the source is open to read, run, and modify under those terms, which is exactly the posture an OEM needs when standing up a payment stack it has to audit and trust.
Stripe, PayPal, and YooKassa are the providers that ship today. The design exists so the list can grow without the core changing, and the roadmap points at the regional rails that matter for OEM reach: additional EU processors, ASEAN and wider-Asia rails, and LATAM networks. Each of those arrives the same way — one plugin implementing authorize, capture, handle-webhook, and refund, plus its payout counterpart against IConnectProvider where relevant.
The business consequence is worth stating plainly. When a partner asks "can you support our market's processor?", the answer is a scoped plugin project with a known contract, not an open-ended estimate against a codebase full of Stripe assumptions. The port bounds the work and makes it predictable. That predictability is the point of the whole design.
An adapter bounds the integration work. It does not erase a provider's inherent complexity, and it would be dishonest to imply otherwise.
Some processors require multi-step confirmation flows — 3-D Secure challenges, redirect-and-return dances, off-session retries. Some settle asynchronously, so "captured" arrives minutes or hours after the customer thinks they paid, and your webhook handling has to be correct because it is the only source of truth. Regional compliance adds its own weight: local tax reporting, data-residency rules, and know-your-customer requirements that live outside the payment API entirely. The port gives every provider the same shape at the boundary; it does not make a complicated provider simple on the inside.
Two operator responsibilities follow directly. Test every provider plugin against that provider's own sandbox before you route real money through it — the contract guarantees the interface, not that you configured the credentials, webhook secrets, and confirmation flow correctly for your account. And PCI scope and broader compliance obligations remain the operator's. Self-hosting means you own the deployment, which means you own the regulatory posture that comes with handling payments in your jurisdiction. VBWD gives you a clean integration seam; it does not sign your compliance paperwork.
Payments are regional, and any platform that pretends otherwise pushes the cost onto whoever deploys it into a second market. The fix is architectural and unglamorous: point checkout and billing at a stable adapter port, put every processor behind it as a plugin, translate and verify webhooks into domain-neutral events at the boundary, and mirror the same design for payouts through IConnectProvider. Do that, and a new market's processor is one plugin against a known contract — scoped, testable against a sandbox, and enabled per deployment — instead of a rewrite that threads a new special case through your core. For OEM partners shipping into different regions, that is the difference between a payment stack that scales with your footprint and one that fights you at every border.
Building an OEM deployment and weighing which rails to enable per region? Start with the architecture overview and the payment plugins catalogue.