--forcepushed--fp
  • Home
  • Articles
  • Resources
  • Projects

Build smarter, ship faster, and stand out from the crowd.

Subscribe or follow on X for updates when new posts go live.

Follow on X

Implementing a Fully-Outsourced iFrame Payment Flow

Implementing a Fully-Outsourced iFrame Payment Flow

When online payment systems are built or integrated over long periods of time, a recurring tension typically appears between security, operational control, and user convenience.

Years ago, a shared payment system was required that needed to balance these constraints precisely.

The specification mandated the use of USA ePay as the payment solution provider and, critically, required that no PCI-sensitive authentication data (SAD) ever touch merchant-controlled servers or client-side applications.

The intention was not to reinvent existing payment flows. Instead, a straightforward but strict set of goals was defined:

  • ✅ Allow customers to pay online.
  • ✅ Avoid PCI scope.
  • ✅ Keep operational costs minimal.
  • ✅ Preserve the appearance of a branded payment page.

In effect, the resulting form needed to appear native to the merchant experience while remaining fully isolated from the merchant’s infrastructure.

The Design Goal: “Payments That Aren’t Ours”

The approach resembles what modern platforms such as Stripe Elements or Polar Payments now abstract: embeddable components that tokenize card data before it ever enters the merchant environment.

At the time this system was created, the concept was less polished, but the objective was identical—ensure sensitive data remained outside merchant systems without undermining user experience.

This requirement aligned directly with the PCI DSS Fully Outsourced iFrame model.

Under this model, an <iframe> hosted entirely by the payment provider is embedded within the checkout page.

The iFrame displays the credit card form, collects the data, and communicates directly with the payment provider’s gateway—bypassing merchant infrastructure entirely.

To the end user, the process appears seamless.
From a compliance perspective, merchant exposure is reduced to near zero.

The following official PCI diagram illustrates the model:

Figure: Fully outsourced iFrame payment model. Source and further reading

Blue represents merchant infrastructure, green represents the payment solution provider, and grey represents the customer’s browser.

Notably, the merchant environment never interacts with raw cardholder data.

Step by Step: How the Flow Works

  1. Customer loads the checkout page — Delivered over TLS as a standard merchant page.
  2. The payment provider’s iFrame loads — Fully isolated, hosted externally.
  3. Card data is entered — Contained entirely within the iFrame.
  4. Data is transmitted directly to the provider — Merchant servers remain unaware.
  5. A result is returned — Typically as a token, webhook, or redirect response.

At the end of the transaction, only a tokenized result or status is returned to the merchant, not any cardholder information.

This resembles modern “payment intent” confirmation flows, though with tighter isolation boundaries and less developer flexibility.

Designing the Implementation

Because the solution needed to support multiple business lines, the architecture was separated into clear bounded domains:

  • Order domain – Manages items, totals, and fulfillment logic.
  • Customer domain – Manages customer identity and associations.
  • Payment domain – Manages transactions and communication with the provider.

These domains interacted where necessary but did not share responsibilities.

In practice, the payment domain functioned as an independent microservice capable of evolving separately from the others.

.NET Core Service Layer

The primary server-side logic was implemented in .NET Core.

The payment service exposed a small set of endpoints:

  • POST /payments – Create a payment request (order reference, amount).
  • GET /payments/{id} – Retrieve payment status or details.
  • PUT /payments/{id} – Handle provider callbacks and updates.

Each endpoint was structured to ensure that no sensitive card data was ever stored or processed.

Only opaque transaction identifiers returned from the provider were persisted.

This separation allowed the payment domain to operate strictly as a business-logic layer, while the infrastructure layer handled external API communication. State transitions were modeled in forms such as:

Initialized → Authorized → Captured → Refunded

Messaging and MassTransit

MassTransit was used as the message bus to keep other domains synchronized.

Instead of using direct RPC interfaces, each payment transition emitted an event:

public record PaymentAuthorized(Guid PaymentId, string ProviderTransactionId);
public record PaymentFailed(Guid PaymentId, string Reason);

The Ordering Domain subscribed to these events and updated order status accordingly—marking orders as paid, triggering invoice generation, or initiating retries after failures.

This pattern ensured the Payment Domain remained provider-agnostic. Payment providers could be replaced (e.g., migrating from USA ePay to Stripe, Authorize.net, or Polar) without requiring structural changes in other domains.

Frontend Integration: React, Redux, and iFrames

On the frontend, a consistent checkout experience was required despite the presence of an externally hosted iFrame.

React and Redux were used to manage application state and rendering, while the iFrame was embedded as a visually integrated component:

<iframe
  src="https://secure.paymentprovider.com/iframe/payment-form"
  sandbox="allow-scripts allow-same-origin"
  frameBorder="0"
/>

The customer completed the entire checkout experience—including cart, summary, confirmation, and finalization—within the main application interface, while all sensitive operations occurred inside the external domain.

Communication Between Domains: JSONP and CORS Realities

CORS limitations at the time introduced significant challenges.

USA ePay did not provide granular CORS response headers, making direct cross-origin XHR impossible.

The viable workaround was JSONP (JSON-with-Padding).

JSONP injected a <script> element that referenced a provider endpoint and included a callback name:

function handlePaymentResponse(data) {
  console.log('Payment result:', data);
}

const script = document.createElement('script');
script.src =
  `https://secure.provider.com/payment?callback=handlePaymentResponse`;
document.body.appendChild(script);

Script tags are not restricted by CORS, enabling the provider to return a JavaScript response that executed the callback with transaction results.

While JSONP is not considered ideal today, it provided a functioning mechanism for securely passing non-sensitive data from the provider to the merchant page without introducing PCI risk.

Modern providers now rely on postMessage, tokenized SDKs, or standardized CORS configurations to support similar workflows safely.

Security and Compliance: Outsourcing Risk the Right Way

From a PCI DSS standpoint, this architecture is highly advantageous.

Because the merchant never processes, stores, or transmits cardholder data, PCI scope is reduced to SAQ-A, the simplest compliance level.

Key Takeaways

  • TLS must be used for all interactions.
  • Card data and tokens must not be stored in browser storage, state management tools, or cookies.
  • Sensitive interactions must occur entirely within the provider-hosted iFrame.
  • Only tokenized or non-sensitive results should be returned to the merchant.

Modern solutions such as Stripe Elements or Polar Components implement an evolved version of this approach, providing more refined APIs while maintaining similar security boundaries.

Lessons Learned and Modern Parallels

This implementation was successful due to several core architectural principles:

  • No PCI exposure to merchant systems.
  • Clear domain separation.
  • Reliable, event-driven communication.
  • Consistent and uninterrupted user experience.

A system built today would likely use more modern abstractions such as Payment Intents, drop-in components, or tokenized JavaScript SDKs. These tools provide improved flexibility, better cross-origin communication, and more comprehensive developer ergonomics.

However, the foundational principles remain relevant:

  • Maintain strict domain boundaries.
  • Delegate sensitive operations to specialized providers.
  • Preserve a cohesive checkout experience.
  • Design the payment layer to be provider-agnostic.

Closing Thoughts

The fully-outsourced iFrame pattern may appear dated compared to modern component-based payment SDKs, but it remains a reliable approach for scenarios where strict PCI isolation is required. It provides predictable behavior, minimal compliance burden, and a well-understood risk profile.

For organizations constrained by legacy providers or regulatory environments, this model continues to deliver a practical, low-risk payment integration strategy—one that offers stability even when newer tools are unavailable or unsuitable.