PCI DSS Fundamentals: Processing Payment Data Securely
AI generated
OWASP
0x00
Security · PCI DSS · Payment · Compliance
PCI DSS Fundamentals: Processing Payment Data Securely
Scope reduction through tokenization and hosted fields

Storing or processing card data directly inside your own store multiplies the effort for audits, network segmentation and annual certification. Hosted fields, iframe tokenization and redirect payments keep Magento stores consistently outside PCI scope, reduce self-assessment overhead to SAQ A, and protect customers from data theft and skimming attacks at the same time.

14 min read PCI DSS · SAQ A/A-EP/D · Tokenization Magento 2.4.8 · Hyvä Theme · Adyen/Stripe/Braintree

1. What PCI DSS is and why scope decides everything

The Payment Card Industry Data Security Standard (PCI DSS) is maintained by the PCI Security Standards Council, founded by Visa, Mastercard, American Express, Discover and JCB. It applies to every merchant, service provider or payment processor that stores, processes or transmits card data, regardless of company size or revenue. The central terms are the PAN (Primary Account Number, the card number itself) and SAD (Sensitive Authentication Data, meaning CVV, PIN and the full magnetic stripe or chip content).

The decisive concept is PCI scope: every system component that stores, processes or transmits PAN, or that is connected to such a component without adequate protection, counts as part of the Cardholder Data Environment (CDE) and therefore falls under the audit scope. The smaller this scope, the fewer systems need to be secured, documented and re-assessed every year against the twelve PCI DSS core requirements. Scope reduction is therefore the most important strategic decision made before any payment integration, not an afterthought bolted on later.

2. Merchant levels and the four PCI DSS compliance tiers

The four merchant levels are based on annual transaction volume and are assigned by the acquiring bank, not chosen by the merchant. Level 1 (over 6 million transactions per year, or following a data breach) requires an annual Report on Compliance (ROC) performed on-site by a Qualified Security Assessor (QSA). Level 2 (1 to 6 million), Level 3 (20,000 to 1 million e-commerce transactions) and Level 4 (under 20,000) typically self-certify through a Self-Assessment Questionnaire (SAQ).

The vast majority of Magento stores in Germany and Europe fall into Level 4: self-assessment instead of an external QSA audit. That means less external scrutiny, not less responsibility. Liability for a data breach remains fully with the merchant. Acquirers and payment service providers such as Adyen, Mollie or Ratepay regularly request annual proof of SAQ compliance through their own compliance portal, and can impose higher transaction fees or terminate the merchant agreement if it is not provided.

3. SAQ types explained: SAQ A, SAQ A-EP, SAQ D

SAQ A, with roughly 22 requirements, applies to merchants that outsource the entire payment flow to a PCI-validated third party: either a full redirect to the provider's own payment page, or an iframe loaded directly from the payment service provider (PSP) whose content the merchant cannot influence. The merchant's own server never sees PAN, CVV or any other card data.

SAQ A-EP, with roughly 139 requirements, applies to e-commerce merchants whose own page does not receive PAN but does actively influence the payment flow, for example because their own JavaScript assembles the iframe configuration, styles the form fields, or handles events from the payment widget. The much larger requirement catalog exists because of the risk of skimming attacks like Magecart, where manipulated JavaScript on the merchant's own page intercepts card data before it reaches the PSP.

SAQ D, with roughly 300 requirements, essentially all twelve PCI DSS core requirements, becomes mandatory as soon as a merchant stores, processes or transmits PAN itself, for example through a custom payment form without iframe isolation. SAQ D covers network segmentation, encryption, cryptographic key management, twelve months of logging and much more, and comes with significant ongoing effort and cost.

4. Tokenization and hosted fields: never touch card data

With hosted fields, the browser loads the input fields for card number, expiry date and CVV directly from an iframe served by the PSP's own domain, for example *.adyen.com or js.stripe.com. The entered values never leave the iframe toward the merchant's page. Instead, the merchant page only receives a result object via postMessage containing an opaque token or payment reference, never PAN or CVV in plain text.

Tokenization differs from encryption in that a token is not a mathematically reversible transformation of the card number, but merely a pointer to a record that exists solely on the PSP's side. This token can be reused for subsequent payments, recurring subscriptions or instant purchase features, without the actual card number ever needing to be transmitted again. Important for the SAQ classification: an iframe only counts as fully scope reducing if the merchant's own page has no cross-origin control whatsoever over its content or styling.


<!-- Hyva checkout template: mounts hosted payment fields served from the PSP domain. -->
<!-- This container never receives raw PAN, CVV or expiry values; only a payment -->
<!-- result reference reaches this page via postMessage from the isolated iframe. -->
<div x-data="hostedPaymentFields()" x-init="mount()" class="rounded-xl border border-slate-200 p-4">
    <div id="adyen-dropin-container"></div>
    <p class="text-xs text-slate-500 mt-2" x-show="error" x-text="error"></p>
</div>

<script>
function hostedPaymentFields() {
    return {
        error: null,
        mount() {
            // Adyen Web Components render the actual card fields inside an isolated
            // iframe hosted on adyen.com. Raw card data never touches this origin.
            const checkout = new AdyenCheckout({
                environment: 'live',
                clientKey: window.checkoutConfig.adyenClientKey,
                onPaymentCompleted: (result) => {
                    // Only an opaque result code / payment reference is sent to our backend
                    fetch('/rest/V1/adyen/payments/result', {
                        method: 'POST',
                        headers: { 'Content-Type': 'application/json' },
                        body: JSON.stringify({ resultCode: result.resultCode, ref: result.pspReference })
                    });
                },
                onError: (error) => { this.error = error.message; }
            });
            checkout.create('dropin').mount('#adyen-dropin-container');
        }
    };
}
</script>

5. Magento integration patterns that stay out of PCI scope

Magento's Vault module stores only PSP tokens in the payment_token entity, never card data. Combined with the Payment Gateway Command Pool pattern, authorize, capture and vault commands can be cleanly separated: each command class builds requests using only token references and amounts, never PAN. The Instant Purchase feature uses stored tokens to let returning customers check out without re-entering their card, without Magento itself ever handling card data in plain text.

The HTTP client responsible for the actual API communication with the PSP should be strictly hardened against accidental logging of sensitive fields: request and response loggers must never persist unfiltered payloads that could potentially contain PAN-like values. A clean DI setup with a dedicated command pool per payment method keeps responsibilities clearly separated and makes later audits significantly easier, because the card data flow can be traced end to end through the code.


<?php

declare(strict_types=1);

namespace Mironsoft\PaymentGateway\Gateway\Http\Client;

use Magento\Payment\Gateway\Http\ClientInterface;
use Magento\Payment\Gateway\Http\TransferInterface;
use Magento\Payment\Gateway\Http\ClientException;
use Psr\Log\LoggerInterface;

/**
 * Sends authorize/capture requests to the PSP API using only the previously
 * issued vault token. Raw card data (PAN, CVV) is never present in this
 * class, in its request payload, or in any log statement it produces.
 */
final class TokenPaymentClient implements ClientInterface
{
    public function __construct(
        private readonly GatewayHttpClientInterface $httpClient,
        private readonly LoggerInterface $logger
    ) {
    }

    /**
     * Builds and sends the authorize request using an opaque payment token.
     *
     * @param TransferInterface $transferObject
     * @return array
     * @throws ClientException
     */
    public function placeRequest(TransferInterface $transferObject): array
    {
        $request = $transferObject->getBody();

        // Only a vault token reference is transmitted, never PAN or CVV
        $payload = [
            'amount' => $request['amount'],
            'currency' => $request['currency'],
            'paymentToken' => $request['payment_token'], // opaque PSP token
            'merchantReference' => $request['order_increment_id'],
        ];

        try {
            return $this->httpClient->post('/payments/authorise', $payload);
        } catch (\Throwable $e) {
            // Never log $payload here, even the token reference should stay out of plain logs
            $this->logger->critical('Gateway authorize failed: ' . $e->getMessage());
            throw new ClientException(__('Payment authorization failed.'));
        }
    }
}

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <!-- Vault-enabled payment method: stores only the PSP token, never raw card data -->
    <virtualType name="MironsoftGatewayVaultFacade" type="Magento\Payment\Model\Method\Adapter">
        <arguments>
            <argument name="code" xsi:type="const">Mironsoft\PaymentGateway\Model\Ui\ConfigProvider::CODE</argument>
            <argument name="formBlockType" xsi:type="string">Magento\Vault\Block\Form\Vault</argument>
            <argument name="infoBlockType" xsi:type="string">Magento\Vault\Block\Info\Vault</argument>
            <argument name="commandPool" xsi:type="object">MironsoftGatewayCommandPool</argument>
        </arguments>
    </virtualType>

    <virtualType name="MironsoftGatewayCommandPool" type="Magento\Payment\Gateway\Command\CommandPool">
        <arguments>
            <argument name="commands" xsi:type="array">
                <item name="authorize" xsi:type="string">MironsoftGatewayAuthorizeCommand</item>
                <item name="capture" xsi:type="string">MironsoftGatewayCaptureCommand</item>
                <item name="vault_authorize" xsi:type="string">MironsoftGatewayVaultAuthorizeCommand</item>
            </argument>
        </arguments>
    </virtualType>
</config>

6. Why storing card data yourself multiplies the burden

As soon as a Magento store runs a custom payment form without iframe isolation and stores PAN in its own database, the merchant automatically falls under SAQ D, or in the worst case a full ROC. That means: mandatory network segmentation between the CDE and other systems, quarterly external vulnerability scans by an Approved Scanning Vendor (ASV), annual penetration testing, cryptographic key management with dual control and split knowledge, and logging of every access to card data for at least twelve months with the most recent three months immediately available.

An often overlooked point: storing SAD (in particular CVV) after authorization is never permitted under any SAQ type, not even encrypted, not even for a short period. The cost difference is significant: while a SAQ A merchant usually incurs no external assessment costs, annual costs for QSA consulting, ASV scans and penetration testing under SAQ D quickly reach several thousand to tens of thousands of euros, on top of the internal effort for documentation and evidence.

7. Network segmentation and CSP as a protective layer

Even a store with minimal PCI scope benefits from the basic principle of network segmentation: systems that have no functional reason to communicate with the payment page should be technically prevented from doing so. For the checkout page itself, a strict Content Security Policy (CSP) is the most effective protective layer against skimming attacks like Magecart: the frame-src directive should allow only the domains of the payment providers in use, script-src should not permit generic third-party scripts on the payment page, and connect-src restricts where data can even be sent via fetch or XMLHttpRequest.

API keys and webhook secrets for the payment gateway must never end up in source code or in a versioned configuration file. They are injected exclusively through environment variables or a secrets manager at deploy time, with strict access limited to the deploy pipeline and the production container. Leaking an API key is not a PCI scope incident in the strict sense, but it can enable fraud at significant scale and must be handled with the same care as an actual card data incident.


# compose.prod.yaml - payment gateway secrets are injected at deploy time,
# never baked into the image and never committed to version control.
services:
  magento-app:
    image: mironsoft/magento:2.4.8-p4
    environment:
      ADYEN_CLIENT_KEY: ${ADYEN_CLIENT_KEY}
    env_file:
      - ./secrets/adyen.env   # git-ignored, populated by the secrets manager
    labels:
      # Documents that this service intentionally never handles raw card data
      - "com.mironsoft.pci-scope=out-of-scope"
      - "com.mironsoft.saq-type=SAQ-A"

8. Vulnerability scanning and penetration tests by SAQ

Quarterly external ASV scans (Approved Scanning Vendor) are mandatory for SAQ A-EP and above, but generally not required for a pure SAQ A merchant without an internet-facing CDE component, since the merchant itself does not operate any system component that would even need to be scanned. Internal scans, annual penetration testing under Requirement 11.4, and segmentation testing (if network segmentation is claimed as a scope reduction) become relevant starting at SAQ A-EP.

With PCI DSS 4.0, fully mandatory since March 2025, additional requirements were introduced: authenticated internal scans instead of unauthenticated network scans, and so-called Targeted Risk Analyses, which require merchants to justify and document the frequency of certain controls themselves. Particularly relevant for SAQ A-EP: Requirement 6.4.3 requires an inventory and integrity check of all scripts running on the payment page, and Requirement 11.6.1 requires a change and tamper detection mechanism for the HTTP headers and script content of the checkout page.

9. Common audit failures in Magento stores

In practice, Magento stores rarely fail because of the payment integration itself, but because of side issues: debug logging that accidentally writes full request payloads, including potential card data, into var/log/debug.log, staging environments populated out of convenience with a copy of the production database including real order data, and outdated third-party extensions on the checkout page that unintentionally push the SAQ A status toward SAQ A-EP because they inject additional JavaScript into the payment flow.

Another frequent finding: webhook endpoints that accept payment confirmations from the PSP but do not verify the signature of the incoming payload. Without signature verification, an attacker can send forged success notifications and have orders marked as paid without actually paying. Every webhook handler must therefore verify the HMAC signature provided by the PSP using a constant-time comparison before the payload is processed at all.


<?php

declare(strict_types=1);

namespace Mironsoft\PaymentGateway\Controller\Webhook;

use Magento\Framework\App\Action\HttpPostActionInterface;
use Magento\Framework\App\RequestInterface;
use Magento\Framework\Controller\Result\JsonFactory;

/**
 * Handles inbound payment notification webhooks. The HMAC signature is
 * verified with a constant-time comparison before the payload is trusted
 * or used to change any order state.
 */
final class Notify implements HttpPostActionInterface
{
    public function __construct(
        private readonly RequestInterface $request,
        private readonly JsonFactory $resultJsonFactory,
        private readonly string $webhookHmacKey
    ) {
    }

    /**
     * @return \Magento\Framework\Controller\Result\Json
     */
    public function execute()
    {
        $rawBody = $this->request->getContent();
        $signatureHeader = (string) $this->request->getHeader('X-Gateway-Signature');

        $expectedSignature = hash_hmac('sha256', $rawBody, $this->webhookHmacKey);

        // Constant-time comparison prevents timing side-channel attacks
        if (!hash_equals($expectedSignature, $signatureHeader)) {
            $result = $this->resultJsonFactory->create();
            return $result->setHttpResponseCode(401)->setData(['error' => 'invalid signature']);
        }

        $payload = json_decode($rawBody, true, 512, JSON_THROW_ON_ERROR);
        // Only after signature verification: process the payment result
        // ... update order status based on $payload['status'] and $payload['merchantReference']

        return $this->resultJsonFactory->create()->setData(['received' => true]);
    }
}
Dimension Storing card data yourself Hosted fields / tokenization
PCI scope Very large: entire CDE including app, database, network Minimal: only the iframe/redirect integration
Required SAQ SAQ D (roughly 300 requirements) SAQ A (roughly 22 requirements)
Network segmentation Mandatory, audited annually Not required
Vulnerability scans Quarterly ASV scans mandatory Usually not required
Breach liability Fully with the merchant, including fines Primarily with the PSP, merchant stays responsible for integration
Annual audit costs Several thousand to tens of thousands of euros Usually no external assessment costs

Mironsoft

PCI DSS consulting, payment integration and scope reduction for Magento stores

Process payment data securely without growing your scope?

We analyze your existing payment integration, identify SAQ-relevant risks, and implement hosted fields or tokenization solutions that keep your Magento store consistently out of PCI scope.

PCI scope audit

Analysis of the card data flow and determination of the correct SAQ type

Gateway integration

Hosted fields, vault tokenization and webhook hardening for Adyen, Stripe, Braintree

CSP & monitoring

Content Security Policy and script integrity checks against Magecart skimming

10. Summary

The PCI DSS fundamentals for Magento stores come down to one principle: scope reduction is cheaper than compliance within a large scope. Hosted fields and iframe tokenization ensure that card data never reaches the merchant's own server, which reduces the merchant to SAQ A with roughly 22 requirements instead of SAQ D with roughly 300. Owning JavaScript control over the payment flow shifts the status to SAQ A-EP with a substantially larger requirement catalog, and storing card data yourself forces SAQ D with network segmentation, quarterly scans and annual penetration tests.

For Magento operators this means, in concrete terms: choose a payment gateway that supports hosted fields or redirect flows from the outset, use the Vault module for tokenization, consistently verify webhook signatures, and enforce a strict Content Security Policy on the checkout page. These decisions affect not only annual compliance costs, but also the attack surface that a Magecart-style skimming attack would even find in the first place.

PCI DSS Fundamentals: Processing Payment Data Securely: The Key Takeaways

Scope reduction first

Plan for hosted fields and tokenization from the start, not as a later retrofit. This determines SAQ A instead of SAQ D.

Determine the correct SAQ type

SAQ A instead of SAQ A-EP only with clean iframe isolation and no custom JS influence on the payment flow.

Never store card data yourself

PAN and SAD belong exclusively in the PCI-validated PSP's systems, CVV never after authorization.

CSP & monitoring

Content Security Policy and script integrity checks protect against skimming attacks even outside the scope.

11. FAQ: PCI DSS Fundamentals for Payment Security

1What is PCI DSS and who does it apply to?
The security standard for card data processing maintained by the PCI Security Standards Council. Applies to every merchant, service provider or payment processor, regardless of company size.
2What does PCI scope mean in practice?
Every system component that stores, processes, transmits or is unprotectedly connected to PAN. A smaller scope means fewer systems must be assessed.
3Difference between SAQ A and SAQ A-EP?
SAQ A: fully outsourced payment with no own control, roughly 22 requirements. SAQ A-EP: own JavaScript influences the payment flow, roughly 139 requirements.
4When do I need to complete SAQ D?
As soon as PAN is stored, processed or transmitted directly. Covers essentially all twelve PCI DSS core requirements, including network segmentation.
5Tokenization vs. encryption?
A token only points to a record on the PSP's side, with no reversible transformation. Encrypted data remains mathematically linked to the original.
6Am I allowed to store CVV after authorization?
No. Never permitted under any SAQ type, not even encrypted or for a short period.
7Does an iframe automatically reduce my scope?
Only without any own control over content or behavior. Own JavaScript in the payment flow shifts the status to SAQ A-EP.
8Do I need quarterly scans as SAQ A?
Usually not. Starting at SAQ A-EP, quarterly external ASV scans are mandatory.
9What does Magecart have to do with PCI DSS?
Magecart injects manipulated JavaScript that intercepts card data before it reaches the PSP. PCI DSS 4.0 requires script integrity checks starting at SAQ A-EP.
10How do I choose the right integration approach?
Redirect or isolated iframe with vault tokenization, consistent webhook signature verification, and a strict Content Security Policy for the checkout page.