Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

PCI DSS Essentials for Developers

By Kokil Thapa | Last reviewed: August 2026

If your application touches payments—even indirectly—you need PCI DSS Essentials for Developers on your checklist before you ship checkout code. PCI DSS (Payment Card Industry Data Security Standard) is not a certificate you paste on a footer; it is a set of technical and operational controls that define how cardholder data must be protected from the moment it enters your system until it reaches a qualified processor. On real client projects—Laravel eCommerce stores, legal-tech portals with deposit payments, WooCommerce sites accepting international cards—I have seen teams assume that using Stripe or Khalti means PCI is "handled." That is only half true. The gateway handles processing; you still own scope, logging, server hardening, and how checkout is wired. This guide maps what developers actually control, with patterns that work on Laravel payment integrations and similar stacks in 2026.

What is PCI DSS and why does it matter to application developers?

PCI DSS is a global security standard maintained by the PCI Security Standards Council. Any organisation that stores, processes, or transmits cardholder data must comply. For developers, the practical impact is architectural: your code, database schema, logs, backups, and third-party integrations all determine scope—how much of your infrastructure falls inside the Cardholder Data Environment (CDE).

PCI DSS 4.0 is the current baseline in 2026. Version 4.0 introduced stricter requirements around multi-factor authentication, targeted risk analysis for vulnerability management, and more explicit expectations for custom software. You do not need to memorise all 300+ sub-requirements, but you must understand the twelve requirement domains at a high level and know which ones your code directly affects.

PCI DSS Domains — Developer ImpactBuild & MaintainReq 6, 11 — Secure SDLCProtect DataReq 3, 4 — EncryptionRestrict AccessReq 7, 8 — Auth & RBACMonitor & TestReq 10, 11 — Logging, scansNetwork SecurityReq 1, 2 — Firewalls, hardeningYour Code Decides CDE ScopeTokenization + hosted checkout = smallest footprint
PCI DSS requirement domains mapped to areas where application developers have direct engineering responsibility

The twelve domains span network security, access control, vulnerability management, monitoring, and policy. Developers touch Requirements 3 (protect stored cardholder data), 4 (encrypt transmission), 6 (secure development), 8 (identify and authenticate), and 10 (log and monitor). Ignore these during sprint planning and you will expand scope, fail an audit, or—worse—leak card data into logs and backups that nobody thought to encrypt.

Cardholder data vs sensitive authentication data

PCI defines two categories you must never confuse:

  • Cardholder data (CHD): Primary Account Number (PAN), cardholder name, expiration date, service code.
  • Sensitive authentication data (SAD): Full magnetic stripe data, CAV2/CVC2/CVV2/CID, PIN/PIN blocks. SAD must never be stored after authorization—even encrypted.

You may store PAN if you have a documented business need and strong encryption (Requirement 3), but most web applications should never store PAN at all. Tokenization—replacing PAN with a non-reversible or gateway-scoped token—is the standard pattern on production Laravel and WooCommerce builds I maintain.

How do you reduce PCI scope in a web application?

Scope reduction is the highest-leverage PCI work a developer can do. Every server, container, database table, queue worker, log aggregator, and backup vault that touches cardholder data sits inside the CDE. Connected systems that could affect CDE security fall into connected-to-scope. Everything else is out of scope.

The goal is simple: card numbers never touch your application layer. Achieve that and you may qualify for SAQ A or SAQ A-EP instead of the full SAQ D (hundreds of controls).

Checkout Architecture & PCI ScopeLow Scope — Redirect / HostedHigher Scope — Direct PANYour AppGateway PageYour AppYour ServerDBSAQ A EligibleeSewa redirect checkoutStripe Checkout SessionKhalti hosted paymentPAN never hits your serverSAQ D TerritoryCustom card form POSTPAN stored in MySQLCVV logged in LaravelFull CDE + annual audit burden
Redirect and hosted checkout keep card data off your servers; direct PAN handling expands PCI scope dramatically

Proven scope-reduction patterns

  1. Hosted payment page or redirect: Customer pays on the gateway domain (eSewa, Khalti, Stripe Checkout). Your app receives only a token or transaction reference via callback.
  2. Hosted fields / iframe tokenization: Card UI appears embedded but PAN goes directly to the gateway JavaScript SDK. Stripe Elements and similar tools keep PAN off your origin.
  3. Server-side token charges only: Your Laravel backend stores pm_xxx or gateway tokens—not PAN—and calls charge APIs over TLS.
  4. Segment networks: Place payment microservices on isolated subnets with strict firewall rules (Requirement 1).

On a digital gift card platform built with Laravel, we used Stripe Checkout exclusively. The application database held order IDs and Stripe payment intent references—never card numbers. That architecture kept the merchant on SAQ A, which is manageable for a small team. Compare that with a custom card form posting to /api/pay—suddenly PHP-FPM workers, MySQL, Redis session stores, and log files all need PCI-grade controls.

Which SAQ type applies to your integration architecture?

Self-Assessment Questionnaires (SAQs) determine which PCI controls your merchant must attest to annually. Developers influence SAQ eligibility through architecture choices. Pick the wrong integration pattern and you force the business into SAQ D-Merchant—roughly 300 controls instead of 30.

SAQ TypeTypical IntegrationDeveloper ResponsibilityApprox. Control Count
SAQ AFully outsourced: redirect, iframe, or JS that sends PAN directly to processorEnsure your servers never receive PAN; secure callback/webhook endpoints~30
SAQ A-EPEmbedded checkout where your site affects delivery of payment page (some JS on your domain)Secure JS delivery, CSP headers, integrity checks, no PAN in your logs~180
SAQ D — MerchantYou store, process, or transmit PAN on your systemsFull secure SDLC, encryption, access control, quarterly scans, pen tests~300+

Local gateways in Nepal—eSewa, Khalti, IME Pay, ConnectIPS—typically use redirect or API-token models where card data stays with banks and wallets. That is favourable for scope. International Stripe/PayPal integrations follow the same principle: use Checkout Sessions or Payment Element rather than rolling your own card form. For a detailed comparison of Nepal gateway flows, see the guide on eCommerce payment gateway options for Nepal.

Which SAQ Does Your Architecture Qualify For?Does your server receive PAN?NoYesDoes your JS handle PAN?Store or log PAN?NoYesSAQ ALowest burdenSAQ A-EPModerate burdenSAQ D — Full ComplianceQuarterly ASV scans, pen test, QSADocument the decision in your architecture diagram before launchRe-evaluate whenever checkout code changes
Decision tree for SAQ eligibility based on whether PAN touches your servers, JavaScript, or storage layer

What secure coding practices does PCI DSS require from developers?

Requirement 6 is where PCI DSS meets your daily pull requests. It mandates secure development practices, code reviews, separation of duties, and protection against common software attacks (SQL injection, XSS, CSRF). PCI DSS 4.0 adds Requirement 6.4.3—change detection on payment pages to detect unauthorised script injection, often implemented via Subresource Integrity (SRI) hashes and Content-Security-Policy headers.

Never log cardholder data

This is the most common developer violation I encounter during production debugging. Someone adds Log::debug($request->all()) on a checkout controller and suddenly CVV values sit in storage/logs/laravel.log, get shipped to centralized logging, and appear in backup snapshots. PCI Requirement 10 explicitly covers log protection; Requirement 3 covers what must not appear in those logs at all.

/* Laravel — safe payment logging pattern */
Log::info('Payment initiated', [
    'order_id'   => $order->id,
    'gateway'    => 'stripe',
    'amount'     => $order->total,
    'last_four'  => $paymentMethod->card->last4, /* OK — not full PAN */
    'intent_id'  => $paymentIntent->id,
]);

/* NEVER do this */
Log::debug('Checkout payload', $request->all());

Configure log redaction at the infrastructure level too. Filter patterns matching 13–19 digit sequences before logs leave the application server. Run secrets scanning in Git and CI with gitleaks to catch accidental commits of API keys and test card numbers.

Encrypt data in transit and at rest

Requirement 4 mandates strong cryptography for PAN in transit—TLS 1.2 or higher (TLS 1.3 preferred in 2026). Disable legacy protocols on Nginx or Apache. Use HSTS headers. For any stored PAN you cannot avoid, apply database encryption at rest and in transit with AES-256 and proper key management—never hard-code encryption keys in .env files committed to Git.

# Nginx — minimum TLS for payment endpoints
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

Validate and authenticate every payment callback

Webhook endpoints for Stripe, Khalti, or eSewa callbacks must verify cryptographic signatures before updating order status. Treat callbacks as untrusted input. Use idempotency keys to prevent duplicate charges on retry. Follow established patterns in a webhook design guide for reliability—PCI cares about integrity and non-repudiation, not just uptime.

// Laravel — verify Stripe webhook signature
$payload = $request->getContent();
$sigHeader = $request->header('Stripe-Signature');

try {
    $event = \Stripe\Webhook::constructEvent(
        $payload,
        $sigHeader,
        config('services.stripe.webhook_secret')
    );
} catch (\UnexpectedValueException | \Stripe\Exception\SignatureVerificationException $e) {
    abort(400, 'Invalid signature');
}

Apply OWASP controls to payment routes

Requirement 6.2.4 expects protection against OWASP Top 10 vulnerabilities. Payment forms are high-value XSS and CSRF targets. Use Laravel Form Requests, CSRF tokens on every state-changing route, parameterized queries via Eloquent, and output encoding in Blade. Rate-limit checkout and webhook endpoints. The practical checklist in secure Laravel OWASP Top 10 in practice aligns closely with PCI Requirement 6 expectations.

How do you handle card data storage, retention, and database design?

Requirement 3 defines storage rules. Default answer for web developers: do not store cardholder data. Let the payment gateway vault it. Store only tokens and the last four digits for customer support display.

If the business insists on storing PAN—for recurring billing without a gateway vault, for example—you need:

  • Strong encryption with documented key rotation (Requirement 3.5–3.7)
  • Truncated or masked display (show at most first six and last four)
  • Access restricted via RBAC on a need-to-know basis (Requirement 7)
  • Data retention limits with automated purge jobs (Requirement 3.1)
  • Encrypted backups that are included in scope (a frequent surprise)

Schema design should make violations difficult:

/* Good — orders table */
Schema::create('orders', function (Blueprint $table) {
    $table->id();
    $table->string('gateway')->index();
    $table->string('gateway_transaction_id')->unique();
    $table->string('payment_method_token')->nullable();
    $table->char('card_last_four', 4)->nullable();
    $table->unsignedInteger('amount_paisa');
    $table->string('status');
    $table->timestamps();
});

/* Bad — never create columns like these */
/* card_number, cvv, expiry, track_data */

Redis and queue payloads deserve the same scrutiny. Serializing a full request object into a Laravel queue job can copy PAN into Redis memory and persistence files. Strip payment fields before dispatching jobs.

Compliant Token-Based Payment FlowBrowserCustomerYour LaravelApp (no PAN)Gateway SDKTLS 1.3ProcessorStripe / Khalti123Step-by-Step (PAN stays at steps 1 and 3 only)1. Customer enters card into gateway-hosted iframe or redirect page2. Your app receives token + last4 via signed callback — stores token only3. Server-side charge uses token ID over TLS — no PAN in MySQL or logs4. Order marked paid — audit trail uses gateway transaction referencetoken only
Token-based payment flow where PCI cardholder data never reaches the merchant application database

What infrastructure and DevOps tasks fall under developer influence?

PCI is not purely an application concern. Requirement 2 covers secure system configuration. Requirement 11 covers vulnerability scanning and penetration testing. Developers who also handle deployment—common on Nepal client projects—must understand how their choices affect compliance cost.

Server hardening checklist

  • Disable default accounts and unnecessary services on Ubuntu web servers
  • Apply security patches within 30 days for critical CVEs (Requirement 6.3.3)
  • Configure UFW or cloud security groups to allow only 443, 22 (restricted IP), and database ports internally
  • Run quarterly Approved Scanning Vendor (ASV) external scans on all in-scope IP addresses
  • Separate production and staging; never use real PAN in staging—even "test" card numbers belong in sandbox environments only

Shared hosting is generally a poor fit for anything beyond SAQ A redirect checkout because you cannot prove network segmentation or patch control. VPS or cloud instances where you control the OS—paired with guidance from how to secure your website and server in Nepal—give you auditable configuration.

Third-party and supply chain risk

Requirement 12.8 expects due diligence on service providers. If you embed a JavaScript payment library from a CDN, document the vendor, pin versions, and use SRI. PCI DSS 4.0 Requirement 6.4.3 expects you to maintain an inventory of payment page scripts and justify each one. A compromised npm package on checkout is a card skimmer.

Top PCI Mistakes Developers MakeLogging full request bodiesCVV ends up in Laravel logs + S3 backupsCustom card form POSTExpands CDE to entire app stackTLS 1.0 / 1.1 enabledFails Requirement 4 instantlyReal cards in staging DBStaging becomes in-scope CDEUnverified webhooksForged callback marks order paidAPI keys in Git historyRequirement 8 credential exposureFix: hosted checkout + redacted logs + TLS 1.3 + webhook signaturesReview before every payment feature PR
Common PCI DSS violations introduced during application development and how to prevent them

Testing and change management

Requirement 6.3.2 mandates pre-production separation. Your CI pipeline should include static analysis (PHPStan level 6+), dependency scanning (Composer audit), and DAST on staging. Document every change to payment pages in your change log—auditors will ask. PCI DSS 4.0 also expects multi-factor authentication for all access into the CDE; if developers SSH into payment servers, MFA is not optional.

Official reference material lives on the PCI Security Standards Council document library. Download the SAQ appropriate to your architecture and the PCI DSS Quick Reference Guide for exact wording—not blog summaries—before attestation.

What should you document before launching a payment feature?

Compliance is partly evidence. Before go-live, produce an architecture diagram showing data flows, list every system component in scope, and record which SAQ you believe applies with written justification from your QSA or acquiring bank. Developers should contribute:

  1. Data flow diagram: Where PAN enters, which systems touch it, where it is stored (hopefully nowhere).
  2. Integration inventory: Gateway name, API version, authentication method, webhook URLs.
  3. Logging policy: Fields allowed in logs; redaction rules; retention period.
  4. Encryption inventory: TLS versions, database encryption status, backup encryption.
  5. Access matrix: Who can SSH to production, who can read payment configs, MFA status.
  6. Incident response contact: What to do if logs show suspected PAN exposure.

On legal-tech portals where clients pay consultation deposits, I treat payment documentation the same as document-upload security policies—business stakeholders sign off, engineering implements, and both sides keep copies for annual SAQ completion. The engineering work is not exotic; the failure mode is skipping documentation until the bank asks for evidence.

Put PCI DSS Essentials for Developers into your next checkout sprint

Payment compliance is an architecture decision first and a checklist second. The developers who stay out of trouble choose hosted checkout, store tokens instead of PAN, verify every webhook, redact logs aggressively, and run TLS 1.3 on every payment route. They know which SAQ their code qualifies for—and they re-evaluate when someone proposes a "simple custom card form." That discipline is the core of PCI DSS Essentials for Developers: shrink scope, write secure code, document data flows, and treat cardholder data as radioactive unless you have a vault built for it.

If you are building or auditing a payment-enabled Laravel app, WooCommerce store, or Nepal eCommerce platform and want a second pair of eyes on scope and integration patterns, get in touch through the contact page. I review checkout architecture, gateway wiring, and server configuration on production systems regularly—and catching a PAN-in-logs mistake before launch beats explaining it to an acquirer after a breach.

Frequently Asked Questions

PCI DSS is the Payment Card Industry Data Security Standard — twelve requirements governing how cardholder data is stored, transmitted, and protected. Developers care because architectural choices made during integration determine whether your application enters PCI scope. If your code touches primary account numbers, CVV, or magnetic stripe data, you inherit compliance obligations that affect hosting, logging, access control, and audit cost. Treat PCI as a design constraint from day one, not a checklist added before launch.

Rs 15,000–75,000/year (~USD 110–550) for SAQ A merchants using hosted checkout; Rs 200,000–800,000+/year (~USD 1,500–6,000+) if you store or process card data directly.

Whenever your application stores, processes, or transmits cardholder data — or could expose it through logs, backups, or misconfigured forms.

PCI scope includes every system component that stores, processes, or transmits cardholder data, plus anything connected to those systems. The practical developer goal is scope reduction: use hosted payment pages (Stripe Checkout, PayPal Smart Buttons), iframe-based card fields, or server-side tokenization so PAN never touches your application server. On Laravel projects I've worked on, redirecting checkout to a PCI-compliant gateway and storing only tokens and last-four digits keeps you on SAQ A or SAQ A-EP instead of the heavier SAQ D.

SAQ A applies when all card data is handled entirely by a third-party iframe or redirect — your site never sees PAN. SAQ A-EP covers merchants whose website affects payment flow but card data still goes directly to the gateway (common with JavaScript SDK integrations like Stripe Elements). SAQ D is the full self-assessment for merchants storing, processing, or routing card data through their own servers — hundreds of controls, quarterly scans, and significantly higher cost. Pick your integration pattern based on which SAQ you can honestly qualify for.

No — not without entering full PCI scope and meeting encryption key management requirements under PCI DSS 4.0 Requirement 3. Storing PAN, even encrypted with AES-256, makes your database, backups, application logs, and admin access part of the cardholder data environment. I've seen production Laravel apps where developers encrypted PAN "for convenience" and unknowingly triggered SAQ D obligations and quarterly ASV scans. Use your gateway's tokenization instead: store gateway tokens like pm_xxx or cus_xxx, never raw card numbers.

Never log full payment payloads in production. PAN, CVV, expiry dates, and magnetic stripe data must not appear in application logs, error trackers like Sentry, or web server access logs. PCI DSS Requirement 3.2 explicitly prohibits storing sensitive authentication data after authorization — CVV must never be stored at all, even encrypted. In Laravel, audit your Log:: calls, exception handlers, and queue job serialization. Use gateway-provided test card numbers in sandbox environments only, and redact card fields before any logging statement.

PCI DSS 4.0, mandatory since March 2025, adds stronger authentication requirements, explicit inventory of scripts loaded on payment pages (Requirement 6.4.3), and enhanced monitoring for e-commerce skimming attacks. Developers must maintain an inventory of every JavaScript file on checkout pages and justify each script's business need. Custom payment forms now need more rigorous change control and vulnerability scanning. If you're still on PCI DSS 3.2.1 assumptions — especially around shared hosting and legacy JavaScript — audit your checkout flow against the 4.0 Self-Assessment Questionnaire mapping.

For most Laravel and PHP applications I build, hosted checkout or Stripe Elements is the correct default. Hosted checkout (redirect to Stripe Checkout or PayPal) qualifies for SAQ A with minimal PCI burden. Stripe Elements keeps PAN in Stripe-hosted iframes on your domain — typically SAQ A-EP. Direct API integration where card numbers POST to your server requires SAQ D-level controls: network segmentation, quarterly ASV scans, file integrity monitoring, and formal penetration testing. The direct route is rarely worth it unless you have a dedicated compliance team.

Most Nepal gateways — eSewa, Khalti, IME Pay, ConnectIPS — redirect users to their hosted payment page or use server-to-server verification without your app ever receiving card PAN. That keeps you out of direct PCI cardholder scope for those channels. Your obligations shift to securing API keys, validating callback signatures, using HTTPS everywhere, and not logging payment tokens or customer PII unnecessarily. Document which payment methods touch card data versus wallet balance, because mixed flows affect your SAQ type if you also accept international cards through Stripe.

ASV scans are required for most SAQ types except SAQ A when you fully outsource card processing with zero card-data exposure on your infrastructure. SAQ A-EP and SAQ D merchants need quarterly external vulnerability scans by an Approved Scanning Vendor. Scans must pass on all in-scope IP addresses and domains — no critical or high findings unresolved. For a typical Laravel app on a single VPS accepting cards via Stripe Elements, you'll likely need ASV at roughly Rs 25,000–50,000/quarter (~USD 185–370) through vendors like Trustwave or Qualys.reseller partners.

Logging full request bodies containing card data, storing CVV "temporarily" in session or cache, emailing payment receipts with full card numbers, using HTTP on any checkout redirect step, and copying production card data into staging databases. Another frequent one: loading analytics or chat widgets on checkout pages without inventorying them under PCI DSS 4.0 script requirements. I've fixed Laravel apps where Telescope or Debugbar was accidentally enabled in staging pointed at production-like data — both are PCI incidents waiting to happen.

HTTPS (TLS 1.2 or higher) is necessary but covers only PCI Requirement 4 — protecting card data in transit. Compliance also requires secure coding (Requirement 6), access control (Requirement 7), logging and monitoring (Requirement 10), quarterly scans, and policies for key personnel. A form posting card numbers over HTTPS to your own server is encrypted in transit but still puts you in full PCI scope for storage and processing. HTTPS plus hosted fields or redirect checkout is the combination that actually minimizes developer compliance burden.

Shared hosting is a poor fit for anything beyond SAQ A redirect checkout because you cannot control network segmentation, file integrity monitoring, or patch management on neighbouring tenants. Cloudflare can sit in front of checkout pages for SAQ A or A-EP flows where card data never hits your origin, but you must document Cloudflare in your compliance scope and ensure TLS termination meets Requirement 4. For SAQ D or stored-card scenarios, use dedicated VPS or cloud instances with hardened Ubuntu 22/24, UFW, fail2ban, and a defined patch schedule — the same baseline I use for production Laravel deployments.

Use gateway sandbox environments exclusively — Stripe test mode, PayPal sandbox, eSewa staging — with documented test card numbers provided by each gateway. Never use real card numbers in development, CI pipelines, or staging databases. Disable or redact payment fields in Laravel Telescope, Debugbar, and log channels outside production. If you need realistic end-to-end tests, mock the gateway HTTP layer with fixtures rather than replaying captured production payloads. Rotate any API keys that accidentally appear in git history, and treat that as a potential compliance incident.

Share this article

Quick Contact Options
Choose how you want to connect me: