Zubair Akhtar logo — Muhammad Zubair Akhtar, Software Engineer

Blog

Payment Gateway Integration: Pre-Launch Checklist

Payment gateway integration checklist for online businesses: hosted or embedded checkout, PCI scope, signed webhooks, idempotency, and a sandbox test plan.

Muhammad Zubair Akhtar

Payment gateway integration is the gap between a pay click and a payout you can match to orders. I am Zubair Akhtar, a senior software engineer in Lahore, Pakistan. This is the list I want finished before a live card. The build steps stay in Stripe payment integration in Laravel and WooCommerce payment gateway integration. Platform choice is the ecommerce platform comparison.

For payment integration services on a store about to go live, start at ecommerce payment integration.

Payment flow: a pending order, the gateway, a verified webhook that marks the order paid, and a payout report that has to match those orders.

Figure 1. The return URL is a receipt. The webhook is the proof. The payout comes later.

I read the cited pages on 26 October 2026. I have not placed a payment for this article.

Payment gateway integration: choose the flow first

Name one flow. Write a pending order, send the amount and order id, and treat the browser return as a receipt. Fulfillment waits on a verified webhook, as in the Laravel and WooCommerce posts. A hosted page plus a form that also posts the card number mixes two scopes.

Hosted checkout

The shopper pays on the gateway's page: Stripe Checkout, PayPal's hosted approval, or an Adyen hosted page. Start here unless the card form must sit in your layout. Store the order id, amount, status, and gateway reference.

Embedded checkout

The fields stay on your URL, inside the gateway's iframe or component (Stripe Elements, Adyen Drop-in). Analytics or chat scripts beside that frame are part of the page. The Council's rules below decide the questionnaire.

A direct payment gateway API

A direct payment gateway API is the path where the card number hits your server. Stripe's PCI guide maps that API, for level 2–4 users, to SAQ D, and Checkout or Elements to SAQ A. Level 1 needs a Report on Compliance, not an SAQ. The acquirer confirms which form applies.

Compliance scope, as the sources state it

PCI DSS, without a homemade scope

The Council's payment-page FAQ says SAQ A needs every payment-page element to come only from a PCI DSS compliant provider, none from the merchant site. SAQ A-EP allows each element from the merchant site or a compliant provider, and from nowhere else. Every other criterion for that SAQ still has to be met.

The outsourcing FAQ says PCI DSS still applies when a third party processes the cards. You confirm the provider, keep a written agreement, check the status at least annually, and still validate, often with an SAQ such as SAQ A. Ask the acquirer which form.

That Stripe guide also maps old Stripe.js v2 to SAQ A-EP, Dashboard card entry to SAQ C-VT, and Terminal to SAQ C. Dashboard entry is not a routine phone-order channel. Submit the current Council SAQ for the flow you shipped.

SCA, and what 3-D Secure is

The FCA SCA page says the UK rules have applied since 14 September 2019, under the Payment Services Regulations 2017 and the technical standards. They apply when a payer starts an electronic payment, opens a payment account online, or takes a remote action that may imply payment-fraud risk, unless an exemption applies.

Directive (EU) 2015/2366 Article 4(30) defines strong customer authentication as two or more independent elements: knowledge, possession, and inherence. Article 97 requires it, and Article 97(2) links a remote payment to a specific amount and payee. UK and EU standards diverged after Brexit. Your code does not invent an exemption. The issuer and the gateway apply theirs.

EMV 3-D Secure is a protocol, not that rule. The issuer gets transaction, payment-method, and device data and may approve with no extra step, or challenge the cardholder. EMVCo says that in some regions the challenge supports SCA. A frictionless result is not proof SCA was met. Use the gateway's step, not a one-time code on your own form, and put a challenge in the test plan.

Keys, idempotency, and the webhook

Where each secret lives

A publishable key may load gateway JavaScript. A secret creates charges. A webhook secret only proves the POST. Stripe's keys page separates the two. The webhook guide says the signing secret differs for test and live, and per endpoint. Keep secrets on the server, not in git or the bundle. Do not point production at the test secret. If one leaked, rotate it. Stripe lets the old webhook secret overlap for up to 24 hours. A restricted key should only do that app's job.

Idempotency keys

A double-click or a retry after a timeout is a second charge unless the create sends an idempotency key. Stripe's idempotent requests accept one on POST. Use a v4 UUID, at most 255 characters, with no email or other personal data. After execution starts, Stripe stores the status and body, including a 500, and may drop the key after 24 hours. A dropped key can create a new object. The same key with different parameters is an error.

Mint the key with the pending order and reuse it only for that attempt. A real decline starts a new key. Do not copy the 24-hour window onto another payment gateway API. Stripe's error handling calls a connection error indeterminate: retrieve the payment or wait for the webhook, and retry with the same key and parameters.

Signature, retries, duplicates, and order

Verify the raw body, store the event, acknowledge, then do slow work. Expect duplicates. Do not require arrival order. The numbers below are from those vendors' pages on 26 October 2026.

Stripe: verify Stripe-Signature, return 2xx before slow work, and retry live events for up to three days (sandbox: three tries over a few hours). Dedupe on event id, not created. Two events for one happening are separated by data.object id plus event.type. A manual resend does not cancel automatic retries.

Adyen's handling guide: verify, store, answer 200 or 202, then run logic. No answer within 10 seconds joins the retry queue. Duplicates share eventCode and pspReference; keep the latest. Some payloads carry a timestamp or sequenceNumber for order.

PayPal's webhook guide: return HTTP 2xx or PayPal retries up to 25 times over 3 days. The preferred check signs transmission id, time, webhook id, and a CRC32 of the raw body, with the certificate in paypal-cert-url. Re-serialising the JSON changes the CRC.

Declines, captures, refunds, and disputes

A decline is an answer. A timeout is not

A decline is finished and refused. Leave the order unpaid. Do not loop it. A timeout, or a 500 with no body, stays pending until a retrieve or a verified webhook. A closed tab is not a failure, and a refreshed success URL is not a payment.

Partial capture

Stripe's hold guide captures the full amount unless you pass amount_to_capture. A partial capture releases the rest, and most payments allow only one capture. The capture API cancels an uncaptured PaymentIntent after a set number of days, 7 by default. Stripe's reporting categories book that release as partial_capture_reversal with type refund, which is not a customer refund.

Refunds and disputes

A refund is a new row, with its own id and amount. If the call times out, retrieve it and retry with the same idempotency key. A dispute, or chargeback, is the cardholder's bank pulling the funds. The evidence deadline is a field on that dispute, so I am not typing a day count. The issuer decides. Do not reship because you expect to win.

Currency, tax, and the payout

Minor units and one rounding step

Stripe's currency guide wants integers in the minor unit. 1000 is 10 USD. 10 is 10 JPY. JPY, KRW, and VND are zero-decimal. ISK and UGX keep a 00 fraction (5 ISK is 500). HUF and TWD can be charged with decimals and pay out as zero-decimal amounts. Do not multiply every ISO code by 100. Round tax once in that integer, and charge what the shopper saw. The tax rate itself is the business's legal question.

Payout against orders

A payout batches captures, refunds, disputes, and fees. It is not yesterday's orders. Read fees from the report, not from a price page. Each paid order should be on the report, each report line should have an order, and the total should match.

Sandbox, then live keys

Five pre-launch stages: name the checkout flow, confirm PCI and SCA scope, lock secrets and webhooks, rehearse failures in the sandbox, then switch to live keys and watch payouts.

Figure 2. Go-live is the last stage. A duplicate webhook should be boring on test keys.

The sandbox run

Use the published test cards: Stripe's testing page, Adyen's test card numbers, and PayPal's sandbox. Copy them that day. Cover a webhook-only success, a decline that does not double-charge, a passed and a failed challenge if you sell into the EU or UK, a replay that does not ship twice, and a partial capture, refund, and dispute. Check a two-decimal amount and, if you sell in one, a zero-decimal amount.

Stripe's Dashboard resend works for up to 15 days, and the CLI resend for up to 30 days. PayPal's simulator posts mocks. Postback verification does not apply to them, and the mock webhook id is WEBHOOK_ID. I have not run this list for this post.

Go-live

Install the live secret, live publishable key, and a separate live webhook secret, on a public HTTPS URL with no redirect in front of the handler. Adyen requires that. The business makes one small live payment on its own account, refunds it, and finds both on the next payout. This article does not claim that payment happened. Name someone to watch delivery failures for the first week.

StageDone when
FlowOne of hosted, embedded, or direct API is named. Card data stays off your systems unless you accepted that scope
PCI and SCAThe acquirer confirmed the SAQ. Challenges stay on the gateway
SecretsSecret and webhook secret are server-side, out of git, and different in live
IdempotencyOne attempt retries with the same key and the same parameters
WebhooksA bad signature is dropped. A replay does not ship twice. Order is not assumed
FailuresA timeout stays pending. Refunds and disputes are separate rows
MoneyThe charged integer matches checkout. The payout matches the report, fees included
SandboxDecline, challenge, replay, refund, and capture were run on test keys
Go-liveOne live payment and its refund matched. Delivery failures have an owner

What this list does not replace

It does not install Cashier or a WooCommerce plugin. Use the Stripe guide for Laravel webhooks and refunds, and the WooCommerce guide for the plugins, the paid callback, and local gateways such as JazzCash. Use the platform comparison if Shopify, WooCommerce, or a custom build is still open.

I do this ecommerce payment integration in the repository, not only as a slide. Start at ecommerce and payment integration services with the gateway account and the flow you think you are shipping.

Discuss this with Muhammad

Message Muhammad Zubair Akhtar on WhatsApp or email [email protected].