Not an Adapter, a Runtime: HSRC Pay Sandbox Network and Provider Strategy Architecture
One of the biggest misconceptions in payment systems is this: "We add one more provider, and we're done."
In reality, it never works that way.
One PSP wants a new auth model. Another expects a form-urlencoded body. One bank speaks XML/SOAP. Another provider exposes a JSON API but reads its error model not from HTTP status, but from a custom field inside the response body. One provider returns an HTML form in a 3DS flow, another produces a redirect URL, another carries SDK state. Capture behavior differs. Refund semantics differ. The fields required for void differ. Webhook retry models differ. And the sandbox environment is often a simplified, incomplete, or randomly behaving copy of production.
That is why thinking of HSRC Pay as merely a "payment gateway" or a "collection of provider adapters" falls short.
HSRC Pay's real claim lives elsewhere: moving payment behavior into a programmable, testable, and provider-independent runtime layer.
We did not build a payment form; we built a runtime that makes payment behavior programmable.
Inside this runtime, payment method, provider, provider config, transaction mapping, routing plan, sandbox issuer, 3DS action, decline/error normalization, capture/refund/void lifecycle, and webhook delivery are all treated as parts of the same model. The goal is not to connect one more provider; it is to turn provider complexity into a manageable execution layer.
Why classic payment integration falls apart
The first provider integration usually looks fast. Install an SDK, write a few endpoints, take a payment. When the second provider arrives, the need for abstraction begins. By the third provider, the adapter pattern kicks in. After the fifth provider, the adapter alone is no longer enough.
Because payment providers are not just different endpoints. Each one is a different behavior set.
- Auth model differs: API key, HMAC signature, basic auth, body hash, timestamp signature.
- Request model differs: JSON, form post, XML, SOAP, SDK call, redirect flow.
- Response model differs: success field, status code, provider-specific error code, nested transaction object.
- Lifecycle differs: auth, capture, void, refund, query payment do not run in the same order on every provider.
- 3DS differs: redirect, hosted form, challenge HTML, callback GET/POST, provider session token.
- Sandbox differs: some providers do not behave like production in sandbox, some offer limited decline scenarios.
- Webhook behavior differs: signature, retry, event type, payload shape, and ordering guarantees all change.
At this point, the "let's write one class per provider" approach stops carrying growth. As the number of classes grows, payment behavior gets embedded in code. Code grows, the test surface fragments, switching providers becomes harder, and merchant-specific routing/failover becomes expensive.
HSRC Pay moves up to the runtime level instead of staying at the adapter level.
Payment method and provider are not the same thing
One of the critical distinctions in HSRC Pay architecture is separating payment method from provider.
Payment method refers to the customer's payment instrument or channel: credit card, debit card, prepaid card, bank transfer, wallet, alternative payment method, and similar.
Provider is the rail, PSP, bank, sandbox strategy, or execution backend that processes that payment flow.
This distinction looks small but has large consequences in system design. Because the same payment method can be processed through different providers. The same provider can support multiple networks or methods. The same merchant can have different provider configs for the same payment.
When a payment request arrives, the runtime asks:
Which provider configs can process this payment method, with this currency, this secure mode, this installment, and this issuer/country information?
This question is richer than classic adapter architecture. There is no simple "call the Stripe class" or "run the bank adapter" here. Provider capability, payment method metadata, merchant config, routing rule, and runtime strategy are evaluated together.
That is why HSRC Pay's provider model is practically thought of in these layers:
Provider (catalog)
-> Provider method
-> Transaction mapping
-> strategy type: SDK | MAPPER | CONFIG
-> strategyIdentifier (points to a registered strategy in code)
-> capabilities: secure mode, installment, issuer, country, currencyIn short: a payment coming from the external API is matched with an eligible paymentProviderConfig candidate. On the provider side, the correct transaction mapping row is selected based on context such as payment method, currency, secure mode, and auto-capture (and therefore transaction type). The strategyIdentifier on that row is matched with strategies registered inside the runtime. So rather than a single "call adapter" line, the flow is catalog, mapping, strategy selection, charge creation, and calling the strategy's pay / resume.
Transaction mapping and strategy selection (orchestration runtime)
A purely declarative, file-centric addition model like "drop a JSON or YAML file and add the new provider to the system" is not the reality this text describes. In the flow, the provider side is modeled with catalog, configuration, and mapping data; outward-facing integration carriers are collected in provider strategy classes (typed as SDK / MAPPER / CONFIG). strategyIdentifier and environment (sandbox / prod) determine which strategy instance is selected. Payment execution proceeds through PaymentOrchestrator over a candidate list, applying the relevant strategy after the charge is created.
The following is a condensed public-facing concept summary for readability, not an internal database field name or full mapping implementation:
- Provider method and transaction mapping carry which transaction type and capability cluster (secure mode, installment, country / currency) is meaningful for the payment in question.
- Strategy type (
SDK/MAPPER/CONFIG) andstrategyIdentifiercome together with the mapping row. - The candidate processor searches registered strategies for a match using the matching
strategyIdentifierand the sandbox / prod distinction; if there is no match, progress at this point turns into a typical error outcome. - The "runtime" emphasis here is that the decision is made through the routing plan, charge, and strategy call pipeline, not distributed into opaque class hierarchies.
The same core question remains:
Is this payment
TXN_PAYwithauto_capture, or theTXN_AUTHcurve? Given method, network, secure mode, and installment context, does this candidate's mapping allow it?
If there is no fit, moving to the next routing candidate may be in play. This is not a gateway pinned to a single endpoint, but a payment orchestration approach that proceeds through planned candidates and a normalized result contract.
SDK, Mapper, and Config strategies
HSRC Pay does not lock itself to a single integration pattern on the provider execution side. The model classifies strategies as EStrategyType: SDK, MAPPER, and CONFIG (these exist as types in the code contract; the full addition story does not reduce to this alone).
The SDK strategy suits sides where the provider requires its own client or a custom call arrangement.
The MAPPER strategy is for sides where request / response transformation runs inside the strategy through a shared mapper contract.
The CONFIG label sits in the model as a third class; the idea is not to force every heavy integration into the same shape. This is not a declarative addition promise in the sense of "fill it with JSON" from the outside; which type of strategy comes into play when is bound through transaction mapping and strategy registration.
Some providers want a custom client, some go through an HTTP-based mapper pipeline, some may later split into arrangements that look at the CONFIG class. The difference is that what opens the framework is the runtime and strategy dispatcher: capability match, then apply the registered strategy, then convert into normalized kind outcomes like success / requires_action / declined / error.
Control still stays in the central model: which mapping for which transaction type, which strategyIdentifier, and how this payment / charge / webhook lifetime is held in a single contract.
Sandbox Network: not a toy, a pre-production engineering space
In most payment systems, sandbox stays at the level of "enter a test card, get success."
HSRC Pay does not see sandbox that way.
The sandbox payment network approach focuses on simulating payment lifecycle discipline without moving real money. Here, simulated issuers, BIN/IIN records, test cards, sandbox-only providers, and fake bank/wallet institutions work together.
These institutions do not claim to be real financial institutions. They are sandbox-only simulated issuer and provider models. The goal is to make real-world diversity testable without touching production.
For example, different networks and issuer profiles can be modeled inside sandbox:
- Network distinctions similar to VISA / Mastercard / TROY / AMEX / Discover
- Country/issuer contexts such as TR, US, EU
- Method types such as credit, debit, prepaid
- Rails that support or do not support installment
- Scenarios that require or do not require 3DS
- Error behaviors such as decline, soft decline, timeout, temporary failure
In testing, sandbox cards or BIN derivatives separated by network and profile are shared through documentation and interface guidance. This blog does not include full or exact card numbers, BIN lists, or production-related sensitive number details; the goal is to keep examples from leaking outward. All these examples are not production cards; they exist only to produce simulated behavior in sandbox.
The value of this approach is here: the merchant does not only test "does the payment succeed?" They also test:
- If a 3DS challenge arrives, what does checkout do?
- If the provider returns a decline, how does the UI behave?
- If the first provider fails, does the routing plan move to the next provider?
- When a refund is created, does the system preserve the lifecycle correctly?
- Is webhook delivery retry behavior idempotent?
- Do sandbox and prod data boundaries get mixed?
When sandbox is designed this way, it becomes a pre-production engineering field, not a demo area.
Prod and sandbox separation
In the HSRC Pay monorepo structure, sandbox / prod separation is not just a toggle in the interface. On the use-case and repository side, separate data paths, provider strategy environment (sandbox / prod), and catalog matches are selected according to the sandboxMode flag; this separation is felt end to end.
The runtime selects the correct repository and the correct strategy environment with sandboxMode context. This way, sandbox payments run on sandbox provider catalog and sandbox data, while the production side stays in a separate repository and a separate provider strategy world.
This separation also provides critical discipline from a regulatory perspective. Sandbox does not move real money. Sandbox issuers and fake providers do not claim to be real financial institutions. But they simulate production-like lifecycle concepts:
- Payment created
- Requires payment method
- Requires action
- Authorized
- Captured
- Declined
- Refunded
- Expired
- Webhook delivery
- Routing plan attempts
This language matters. HSRC Pay does not say "we are a bank in sandbox." It says "we simulate payment behavior inside the sandbox environment." That is both technically correct and a trust-building frame.
Routing: payment flow not tied to a single provider
The heart of payment orchestration is routing.
In HSRC Pay, the routing plan holds provider config candidates to be executed during payment as a snapshot. The runtime can process this candidate list in order. Each candidate can have a provider config, provider identifier, score, reason, and execution information such as timeout.
Routing supports two main modes:
Merchant Direct: If there is a single eligible config, that config runs directly.
Autopilot: If there are multiple eligible provider configs, the runtime produces a candidate list through scoring and routing rule logic.
This model gives the merchant operational independence. The merchant is not locked to a single provider. Provider cost, payment method support, installment capability, issuer/country matching, and merchant routing rules can be evaluated together.
This makes the following possible:
Which provider is the best fit for this payment?
But more importantly:
Is this provider actually suitable for this transaction?
HSRC Pay does capability matching first, then produces the routing plan. This difference matters. Because a low-cost provider is not a good candidate if it does not support that payment method or secure mode. The runtime accepts payment reality first, then optimizes.
3DS, requires action, and the resume flow
3DS is one of the most complex areas in payment systems. Because a payment may not finish in a single request-response. The provider may request an action. The user may be redirected. The callback may arrive via GET or POST. An HTML form may be returned. The runtime may need to continue from where it left off.
HSRC Pay treats this not as a normal payment deviation, but as part of the payment lifecycle.
When the strategy result returns requires_action, the runtime converts it into the core system's next_action model. Payment and charge state are updated accordingly. When the callback arrives, the resume flow rebuilds charge, payment method, payment, routing plan, used provider, and used config context. Then the relevant strategy's resume behavior runs.
This way, 3DS becomes a state the runtime expects, not "a special hack" on the UI side.
This approach makes a big difference for developers. Because the developer wants:
- One confirmation model
- One next_action model
- One callback/resume approach
- One normalized result
- One event/webhook behavior
Whatever the provider returns, this is what the merchant wants to see: is the payment successful now, declined, failed, or does the user still need to complete an action?
Decline, error, and retry separation
In payment systems, saying "failed" is not enough.
A network timeout is not the same as a card declined for insufficient funds. When a provider returns 500, retry may make sense. But if the card returns a hard decline, trying another provider may be meaningless. Soft decline may be retried in some scenarios. If 3DS is required, the payment is not failed; it is waiting for action.
HSRC Pay runtime normalizes this separation through result kind:
successrequires_actiondeclinederror
This looks simple, but there is a critical model behind it. The provider-specific error world is converted into a merchant-facing normalized model. So the developer does not have to parse errors separately for every provider.
Conditions such as timeout, network error, and temporary failure from the provider response can be treated as retryable error. Conditions such as business decline, invalid card, and insufficient funds can be normalized as declined.
This separation improves payment quality. Because the system knows more clearly when to move to another candidate, when to close the payment lifecycle as declined, and when to wait for user action.
Capture, refund, void lifecycle
In the HSRC Pay model, payment is not just "pay."
Inside a payment lifecycle, transaction types such as auth, capture, void, refund, and query payment carry separate meaning. When auto-capture is enabled, a successful payment can move to captured state. When auto-capture is off, an authorized state is created and capture becomes a separate operation. Refund runs on a captured charge and has its own status model.
The existence of this model matters for the merchant. Because real payment operations do not end at the checkout screen. Operations wants refunds. Finance wants to see captured/refunded amounts. Support wants to understand which charge was processed with which provider config. Developers want to track state changes through webhooks.
HSRC Pay's charge model includes separation of authorized, captured, and refunded amounts. Routing plan and provider config snapshot information is preserved at the charge level. This creates a strong foundation for operational traceability.
Webhook and event delivery
The way a payment runtime speaks to the outside world is through webhooks.
On the HSRC Pay side, webhook delivery is handled with a separate worker and queue logic. When event payloads are sent to merchant endpoints, concepts such as idempotency, retry attempt, and delivery lifecycle become important.
The goal here is not just "let's send an HTTP POST." A payment event can trigger financial operations in the merchant system. That is why delivery behavior must be traceable, retriable, and as idempotent as possible.
The value for developers is clear: whatever the provider, the event model must be normalized. The merchant side should not have to learn every provider's webhook payload separately.
What the monorepo tells us
The HSRC Pay monorepo structure also shows that the product is not just a dashboard.
apps/backend carries the payment API, domain resources, use cases, routing, and orchestration logic.
apps/checkout-session is a separate surface for the hosted checkout experience.
apps/dashboard represents the merchant operations area.
apps/homepage is the product's external face.
apps/worker carries async work such as webhook/event delivery.
apps/vault and packages/vault-client show the idea of a separate service for sensitive data and tokenization boundaries.
apps/system-dashboard points to the provider catalog and system-level management surface.
apps/auth-server shows that the auth boundary is thought of separately.
packages/providers-spec is the package that holds shared ETxnType, transaction mapping, and (for example) method - transaction - capability types as a TypeScript contract. This is not a separate "definition engine" that works by dropping JSON / YAML externally; it is unification of types in the monorepo.
packages/prisma carries the persistent schema of the domain model and lifecycle states.
packages/jobs shares async job contracts.
packages/api-client and packages/backend-api-client support the developer experience side.
Areas such as monitoring, infra, data, and diagrams show that this is thought of as an infrastructure-focused fintech platform, not a single-screen SaaS.
This architectural language tells the investor: HSRC Pay is not just a website that takes payments; it is an infrastructure company approach that manages provider complexity at the runtime level.
Value for developers
What developers want in payment integration is simplicity, but that simplicity must not deny provider reality.
HSRC Pay offers developers this idea:
One API. One lifecycle. One normalized response/event model.
The provider may differ. 3DS may differ. Refund behavior may differ. But the contract the developer wants to see at the application layer should stay the same.
This reduces integration cost. Because the developer does not have to implement every provider's custom error codes, webhook payload, capture/refund variations, and 3DS callback details separately. The runtime handles this complexity in the central model.
Value for merchants
For merchants, payment infrastructure is a matter of lost sales, operational load, and provider dependency.
Staying tied to a single provider is risky. If the provider has downtime, checkout is affected. If cost changes, bargaining power drops. Entering a new market may require a different payment method. If sandbox is insufficient, real scenarios cannot be tested before going to production.
HSRC Pay aims to offer merchants an operational layer that reduces provider dependency:
- Multi-provider payments
- Payment routing
- Sandbox payment network
- 3DS simulation
- Decline scenario testing
- Refund/capture lifecycle
- Webhook delivery observability
- Provider-independent payment model
This does not squeeze the merchant's payment operations into a single integration. It moves payment into a manageable infrastructure layer.
Value for partners and investors
Fintech infrastructure companies do not just sell endpoints. They sell a model. They sell a runtime. They sell operational leverage.
HSRC Pay's strategic side is strong here: as provider diversity grows, the value of the normalized lifecycle and strategy model grows. Every boundary can be written as a new standalone class; it can also be reused through transaction mapping, capability contract, and strategy registration. Specialized edges can be carried with SDK or mapper strategies. The sandbox network aims to provide a controlled closure area before these go live with real money.
This is a scalable payment orchestration architecture.
HSRC Pay's goal is not to replace a provider; it is to make provider complexity manageable, testable, and programmable.
In today's payment world, the winner will not be just the system that "takes payments." The winners will be infrastructures that reduce provider dependency, turn sandbox into a serious engineering space, give merchants routing and lifecycle control, and offer developers a single model.
What this post is not
- This post does not expose real provider credentials, signatures, keys, or private integration secrets.
- Sandbox providers, issuer profiles, and network-specific fictional names should not be read as real financial institutions or network agreements; they are for simulation.
- The sandbox network does not produce real money movement, settlement, or card network reconciliation; production capability, licensing, and regulatory topics are outside the scope of this text.
- The public narrative is for concepts; full repository schema details, internal mapping tables, complete registry lists, and operational internal processes are intentionally left out.
Not a payment gateway.
A payment runtime.