| name | payment-gateways-stripe-adyen-integration |
| description | Architect and implement secure, PCI-DSS compliant payment processing integrations with Stripe and Adyen, featuring idempotent webhook handling, tokenization, multi-currency processing, automatic retries, and ledger-based reconciliation. |
Payment Gateways Integration Architecture (Stripe & Adyen)
This skill package provides enterprise-grade patterns, architectural principles, PCI-DSS v4.0 compliance guidelines, and production code implementations for orchestrating payment processing across Stripe and Adyen.
1. Core Architectural & Compliance Standards
PCI-DSS v4.0 Compliance & Tokenization
- Out-of-Scope Architecture (SAQ A / SAQ A-EP): Never ingest, process, or store Raw Primary Account Numbers (PAN), Card Verification Values (CVV/CVC), or PINs on your infrastructure. Use client-side SDKs (Stripe Elements / Adyen Web Drop-in) to tokenization endpoints directly owned by payment service providers (PSPs).
- Network & Transport Layer Security: All API endpoints and client SDK communications must enforce TLS 1.3 with strong cipher suites (e.g.,
TLS_AES_256_GCM_SHA384).
- Data Sanitization & Zero Logging: Redact all sensitive fields from application logs, trace contexts, and error payloads. Never log header signatures, API tokens, raw webhook payloads with PII, or tokens susceptible to account takeover.
Webhook Idempotency & Replay Protection
- Idempotency Keying: Webhook events must be processed exactly once using distributed locking (e.g., Redis
SET key value NX PX) keyed by the unique Event ID (event.id for Stripe, eventCode + pspReference for Adyen).
- Time-Window & Signature Verification: Reject webhooks missing cryptographic HMAC signatures or with timestamps older than 300 seconds to prevent replay attacks.
- Asynchronous Execution: Webhook HTTP handlers must validate signatures, enqueue the event into an isolated message queue (e.g., Kafka / RabbitMQ), and return an immediate HTTP
200 OK within 500ms to avoid PSP retry loops.
Reconciliation & Ledger Architecture
- Double-Entry Bookkeeping: Model payments as state transitions across double-entry ledger accounts (Asset: PSP Clearing, Revenue, Expense: Gateway Fees, Liability: Customer Balance).
- State Machine Integrity: Enforce strict status transitions (
AUTHORIZED -> CAPTURED -> SETTLED / REFUNDED / DISPUTED).
2. PSP Capabilities Comparison Matrix
| Capability | Stripe Integration | Adyen Integration |
|---|
| Client Tokenization | Stripe Elements / PaymentIntents API | Adyen Drop-in / Components API |
| Webhook Verification | HMAC-SHA256 (Stripe-Signature header) | HMAC-SHA256 Base64 encoded payload validation |
| Capture Model | Immediate or Manual (capture_method: 'manual') | Automatic, Manual, or Delayed Capture rules |
| Payout & Settlement | Balance Transactions & Payouts API | Financial Reports (Settlement Detail Report) |
| Global Payment Methods | iDEAL, SEPA, Klarna, Alipay via PaymentIntents | 250+ local payment methods via /payments |
3. Production Code Implementations
Python Implementation: Production-Grade Webhook Handler with Redis Idempotency & HMAC Validation
import base64
import hashlib
import hmac
import json
import time
import logging
from typing import Dict, Any, Optional
import redis
import stripe
logger = logging.getLogger("payment_orchestrator")
logger.setLevel(logging.INFO)
class PaymentWebhookProcessor:
def __init__(self, stripe_webhook_secret: str, adyen_hmac_key: str, redis_client: redis.Redis):
self.stripe_webhook_secret = stripe_webhook_secret
self.adyen_hmac_key = adyen_hmac_key
self.redis = redis_client
self.lock_ttl_ms = 60000
def process_stripe_webhook(self, raw_body: bytes, sig_header: str) -> Dict[str, Any]:
"""
Validates Stripe signature, enforces idempotency, and queues event.
"""
try:
event = stripe.Webhook.construct_event(
payload=raw_body,
sig_header=sig_header,
secret=self.stripe_webhook_secret,
tolerance=300
)
except stripe.error.SignatureVerificationError as e:
logger.error("Invalid Stripe webhook signature", exc_info=)
ValueError() e
event_id = event[]
event_type = event[]
._acquire_idempotency_lock():
logger.info()
{: , : event_id}
:
._route_stripe_event(event_type, event[][])
{: , : event_id}
Exception e:
.redis.delete()
e
() -> :
item = notification_item.get(, {})
hmac_signature = item.get(, {}).get()
hmac_signature:
keys = [
, , , ,
, , ,
]
signed_vals = []
k keys:
val = (item.get(k, ) )
escaped_val = val.replace(, ).replace(, )
signed_vals.append(escaped_val)
payload_to_sign = .join(signed_vals).encode()
hmac_key_bytes = .fromhex(.adyen_hmac_key)
calculated_hmac = base64.b64encode(
hmac.new(hmac_key_bytes, payload_to_sign, hashlib.sha256).digest()
).decode()
hmac.compare_digest(calculated_hmac, hmac_signature)
() -> [, ]:
items = notification_payload.get(, [])
processed = []
container items:
item = container.get(, {})
.verify_adyen_hmac(container):
logger.error()
psp_ref = item.get()
event_code = item.get()
idempotency_key =
._acquire_idempotency_lock(idempotency_key):
logger.info()
processed.append({: psp_ref, : })
._route_adyen_event(event_code, item)
processed.append({: psp_ref, : })
{: , : processed}
() -> :
(.redis.(lock_key, , nx=, px=.lock_ttl_ms))
():
event_type == :
payment_intent_id = data_object[]
amount = data_object[]
currency = data_object[]
logger.info()
event_type == :
dispute_id = data_object[]
logger.warning()
():
event_code == item.get() == :
psp_ref = item[]
val = item.get(, {}).get()
currency = item.get(, {}).get()
logger.info()
TypeScript Implementation: Modern Payment Intent & Checkout Orchestration
import stripe from 'stripe';
import crypto from 'crypto';
export interface PaymentRequest {
merchantReference: string;
amountCents: number;
currency: string;
paymentMethodToken: string;
customerId: string;
}
export interface PaymentResult {
transactionId: string;
status: 'AUTHORIZED' | 'CAPTURED' | 'REQUIRES_ACTION' | 'FAILED';
clientSecret?: string;
rawResponse: Record<string, unknown>;
}
export class StripePaymentProvider {
private stripeClient: stripe;
constructor(apiKey: string) {
this.stripeClient = new stripe(apiKey, {
apiVersion: '2023-10-16',
typescript: true,
});
}
(: ): <> {
idempotencyKey = crypto
.()
.()
.();
{
intent = ...(
{
: req.,
: req..(),
: req.,
: req.,
: ,
: ,
: ,
: ,
: {
: req.,
},
},
{
idempotencyKey,
}
);
(intent. === || intent. === ) {
{
: intent.,
: ,
: intent. ?? ,
: intent <, >,
};
}
(intent. === ) {
{
: intent.,
: ,
: intent <, >,
};
}
();
} (: ) {
();
}
}
(: , ?: ): <> {
: stripe. = {};
(amountToCaptureCents) {
captureOptions. = amountToCaptureCents;
}
intent = ...(paymentIntentId, captureOptions);
{
: intent.,
: ,
: intent <, >,
};
}
}
4. Anti-Patterns & Pitfalls
Critical Architectural Mistakes
-
Processing Webhooks Synchronously in HTTP Worker Threads
- Impact: Gateways like Stripe/Adyen time out after 5-10 seconds and re-send webhooks, causing cascading duplicate executions and worker thread starvation.
- Remediation: Accept payload, verify HMAC, publish event to broker (Kafka/AWS SQS), return HTTP 200 immediately.
-
Using Floating-Point Numbers for Currency Operations
- Impact: IEEE 754 floating-point rounding errors (
0.1 + 0.2 = 0.30000000000000004) lead to balance mismatches in accounting ledgers.
- Remediation: Always store and compute amounts as 64-bit integers in minor currency units (cents, pence) or fixed-point decimal objects (
Decimal).
-
Ignoring Client-Side 3D-Secure (3DS2) Challenge Flows
- Impact: Soft-deletes and authorization rejections increase due to PSD2 SCA requirements in Europe and worldwide mandate compliance.
- Remediation: Correctly propagate
requires_action states back to client SDKs to render 3DS authentication modals.
-
Storing API Keys or Webhook Secrets in Code Repositories
- Impact: Secrets leak, leading to unauthorized refund triggers, customer data breach, or fraud.
- Remediation: Inject secrets using KMS (HashiCorp Vault, AWS Secrets Manager) and support seamless secret rotation.
5. Verification & Testing Playbook