--- name: payment-gateway-expert description: "Expert guide for integrating payment gateways (Stripe, PayPal, Xendit, Midtrans, DOKU SNAP BI) and secure webhooks into SaaS platforms / Panduan ahli integrasi payment gateway dan webhook aman." author: "Roedy Rustam" version: "4.4.2" --- # Payment Gateway Expert / Ahli Payment Gateway [English](#english) | [Bahasa Indonesia](#bahasa-indonesia) --- ## English ### Orchestration & Integration - **Gateways & Payment Integrations**: `doku-payment-gateway`, `saas-billing`, `agentic-m2m-settlement`, `agentic-micro-economy-architect` - **AI Agentic Commerce & MCP**: `doku-mcp-server`, `mcp-server-architect`, `ai-llm-integration-expert` - **Database & Atomic Idempotency**: `database-orm-expert`, `saas-multi-tenant` - **Security & Post-Quantum Crypto**: `post-quantum-crypto-migrator`, `zero-trust-secret-vault`, `autonomous-red-teamer` - **Background Jobs & Observability**: `async-queue-temporal-expert`, `data-telemetry-expert` - **Lifecycle & Governance**: `brainstorming`, `zero-to-prod-orchestrator`, `session-memory-manager`, `anti-slop` ### Description Expert guide for integrating major payment gateways (Stripe, PayPal, Xendit, Midtrans, DOKU SNAP BI) into modern SaaS platforms. Covers checkout flows, secure raw-body webhook handling, atomic idempotency to prevent race conditions, subscription state machines, and local database synchronization. ### Core Principles - **Security First**: Always validate webhook signatures using the raw, unparsed request body before processing any payment event. Never trust client-side data for prices or payment status. For Indonesian gateways (DOKU, Midtrans, Xendit), accurately apply the required signature protocol: **DOKU Checkout** uses **HMAC-SHA256 with body Digest**, while **SNAP BI Direct APIs** enforce **HMAC-SHA512** with ISO8601 timestamps. - **Idempotency & Race Condition Prevention**: Implement idempotency keys for all payment creation requests. For webhook ingestion, **DO NOT rely solely on `findUnique` checks**, as concurrent webhook retries cause race conditions. Always use **Atomic Updates** (`UPDATE ... WHERE status = 'PENDING'`) or database pessimistic locks (`SELECT ... FOR UPDATE`). - **Raw Body Ingestion**: Webhook signature verification fails if JSON is parsed or re-serialized with modified key ordering. Extract the exact raw string buffer (`req.text()` in Fetch API or `express.raw({ type: 'application/json' })` in Express). - **Early 200 OK Acknowledgment**: If a webhook event is verified but already processed, immediately return `200 OK` so the payment gateway halts retries. - **State Synchronization**: Ensure the local database (e.g., PostgreSQL, Supabase via Drizzle/Prisma) is updated transactionally upon receiving successful webhook events. - **Subscription Management**: Map provider subscription statuses (`trialing`, `active`, `past_due`, `canceled`) accurately to internal SaaS state machines. ### Implementation Checklist - [ ] Create dedicated raw Webhook endpoint (e.g., `/api/webhooks/stripe`, `/api/webhooks/doku/checkout`). - [ ] Use raw request body string for signature verification (never parsed JSON). - [ ] Verify signature using proper algorithm (HMAC-SHA256 + Digest for DOKU Checkout, Stripe, PayPal; HMAC-SHA512 for SNAP BI Direct API). - [ ] Implement Atomic Update idempotency (`WHERE status = 'PENDING'`) to eliminate race conditions. - [ ] Acknowledge duplicate webhooks immediately with HTTP `200 OK`. - [ ] Offload heavy post-payment operations (invoicing, emails, webhooks) to background job queues (BullMQ/Temporal). ### Example: SNAP BI Webhook & Atomic Idempotency (Next.js App Router) ```typescript import { headers } from 'next/headers'; import crypto from 'crypto'; import { db } from '@/lib/db'; export async function POST(req: Request) { const rawBody = await req.text(); // Raw body is mandatory const headerList = await headers(); const timestamp = headerList.get('x-timestamp') || ''; const receivedSig = headerList.get('x-signature') || ''; const authHeader = headerList.get('authorization') || ''; const token = authHeader.replace(/^Bearer\s+/i, ''); const path = new URL(req.url).pathname; // 1. SNAP BI Body Hash & Signature Verification (HMAC-SHA512) const bodyHash = crypto.createHash('sha256').update(rawBody, 'utf8').digest('hex').toLowerCase(); const stringToSign = `POST:${path}:${token}:${bodyHash}:${timestamp}`; const calculatedSig = crypto .createHmac('sha512', process.env.DOKU_SECRET_KEY!) .update(stringToSign, 'utf8') .digest('base64'); const isValid = crypto.timingSafeEqual(Buffer.from(receivedSig), Buffer.from(calculatedSig)); if (!isValid) { return new Response('Invalid Signature', { status: 401 }); } const payload = JSON.parse(rawBody); const invoiceNumber = payload.order?.invoice_number; // 2. Atomic Update: Eliminates Race Conditions by locking on 'PENDING' status const updated = await db.transaction.updateMany({ where: { invoiceNumber: invoiceNumber, status: 'PENDING', // Key race condition prevention gate }, data: { status: 'PAID', paidAt: new Date(), externalReference: payload.transaction?.original_reference_no, }, }); // 3. Duplicate Webhook Handling (Idempotency) if (updated.count === 0) { // Already processed or invalid invoice — return 200 to halt gateway retry spam return new Response(JSON.stringify({ status: 'ALREADY_PROCESSED' }), { status: 200 }); } // 4. Fulfillment (Execute only once) await grantUserSubscription(invoiceNumber); return new Response(JSON.stringify({ status: 'SUCCESS' }), { status: 200 }); } async function grantUserSubscription(invoiceNumber: string) { // Safe business logic execution } ``` ### Example 2: Multi-Provider Timing-Safe Webhook Signature Verifier (Stripe + DOKU Checkout + Midtrans) ```typescript import crypto from 'node:crypto'; export type PaymentProvider = 'stripe' | 'doku-checkout' | 'midtrans'; export function verifyGatewayWebhookSignature(params: { readonly provider: PaymentProvider; readonly rawBody: string; readonly signatureHeader: string; readonly secret: string; readonly clientId?: string; readonly requestId?: string; readonly requestTimestamp?: string; readonly requestTarget?: string; }): boolean { let expected = ''; if (params.provider === 'stripe') { expected = crypto.createHmac('sha256', params.secret).update(params.rawBody, 'utf8').digest('hex'); } else if (params.provider === 'doku-checkout') { const digest = crypto.createHash('sha256').update(params.rawBody, 'utf8').digest('base64'); const component = [ `Client-Id:${params.clientId ?? ''}`, `Request-Id:${params.requestId ?? ''}`, `Request-Timestamp:${params.requestTimestamp ?? ''}`, `Request-Target:${params.requestTarget ?? ''}`, `Digest:${digest}` ].join('\n'); const hmac = crypto.createHmac('sha256', params.secret).update(component, 'utf8').digest('base64'); expected = `HMACSHA256=${hmac}`; } else { expected = crypto.createHash('sha512').update(params.rawBody + params.secret, 'utf8').digest('hex'); } const a = Buffer.from(params.signatureHeader, 'utf8'); const b = Buffer.from(expected, 'utf8'); if (a.byteLength !== b.byteLength) { return false; } return crypto.timingSafeEqual(a, b); } ``` ### Example 3: PostgreSQL Atomic Webhook Inbox & Outbox Locking DDL (`SKIP LOCKED`) ```sql CREATE TABLE IF NOT EXISTS payment_webhook_inbox ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL, provider VARCHAR(32) NOT NULL, idempotency_key VARCHAR(128) NOT NULL UNIQUE, raw_payload JSONB NOT NULL, status VARCHAR(24) NOT NULL DEFAULT 'PENDING', attempt_count INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), processed_at TIMESTAMPTZ ); CREATE INDEX IF NOT EXISTS idx_webhook_inbox_pending ON payment_webhook_inbox (status, created_at) WHERE status = 'PENDING'; ALTER TABLE payment_webhook_inbox ENABLE ROW LEVEL SECURITY; ``` ### Trigger Conditions Active whenever the user is working on billing integration, payment checkout, webhook handling, or integrating platforms like PayPal, Stripe, Xendit, Midtrans, or DOKU SNAP BI. ### Sovereign Execution, Token Efficiency & Invariants | Dimension | Sovereign Mandate & Deterministic Contract | | :--- | :--- | | **Frontier Model Cascade** | High Fast / High (`Grok 4.7 High Fast`, `Claude Sonnet 5.5 High`, `Gemini 4 Pro`) | | **Availability Fallback** | `Gemini 4 Pro` -> `Gemini 3.8 Flash`; `Claude Opus 5.5` -> `Claude Opus 4.6`; `Grok 4.7` -> `Grok 4.6 High Fast` (Zero unhandled missing-model errors) | | **KV-Cache & Token Protocol** | Pin static instructions & schemas at prompt prefix (`kv-cache-prefix-optimizer`); pass surgical `CONTEXT_MAP.md` slices (<250 tokens/call) during Swarm Fan-Out | | **SaaT CLI & MCP Dispatch** | CLI: `vibes skill exec payment-gateway-expert ` / MCP: `vibes_skill_dispatch`, `vibes_critic_validate`, `vibes_self_heal`, `vibes_eval_sandbox`, `vibes_verify_invariants` | | **NFR & Anti-Slop Invariants** | Zero OWASP Top 10 vectors, 0 hardcoded secrets, >= 90% branch coverage on domain logic, 0 Anti-Slop violations; 100% complete zero-placeholder output verified via `node scripts/check-anti-slop.js --strict` | --- --- ## Bahasa Indonesia ### Integrasi Orkestrasi - **Gateway & Integrasi Pembayaran**: `doku-payment-gateway`, `saas-billing`, `agentic-m2m-settlement`, `agentic-micro-economy-architect` - **AI Agentic Commerce & MCP**: `doku-mcp-server`, `mcp-server-architect`, `ai-llm-integration-expert` - **Database & Idempotensi Atomik**: `database-orm-expert`, `saas-multi-tenant` - **Keamanan & Kriptografi Pasca-Kuantum**: `post-quantum-crypto-migrator`, `zero-trust-secret-vault`, `autonomous-red-teamer` - **Antrean Latar Belakang & Observabilitas**: `async-queue-temporal-expert`, `data-telemetry-expert` - **Siklus Hidup & Tata Kelola**: `brainstorming`, `zero-to-prod-orchestrator`, `session-memory-manager`, `anti-slop` ### Deskripsi Panduan ahli untuk mengintegrasikan payment gateway utama (Stripe, PayPal, Xendit, Midtrans, DOKU SNAP BI) ke platform SaaS modern. Mencakup alur checkout, penanganan webhook raw-body aman, pencegahan race condition melalui atomic update, state machine langganan, dan sinkronisasi database lokal. ### Kondisi Pemicu Aktif setiap kali pengguna sedang mengerjakan integrasi billing, checkout pembayaran, penanganan webhook, atau mengintegrasikan platform seperti PayPal, Stripe, Xendit, Midtrans, atau DOKU SNAP BI. --- ### Prinsip Utama & Kode Implementasi Webhook Idempoten (TypeScript) ```typescript import { headers } from 'next/headers'; import crypto from 'crypto'; import { db } from '@/lib/db'; export async function POST(req: Request) { const rawBody = await req.text(); // Wajib membaca raw body mentah sebelum diparse const headerList = await headers(); const timestamp = headerList.get('x-timestamp') || ''; const signatureDiterima = headerList.get('x-signature') || ''; const authHeader = headerList.get('authorization') || ''; const token = authHeader.replace(/^Bearer\s+/i, ''); const path = new URL(req.url).pathname; // 1. Verifikasi Signature HMAC-SHA512 SNAP BI const bodyHash = crypto.createHash('sha256').update(rawBody, 'utf8').digest('hex').toLowerCase(); const stringToSign = `POST:${path}:${token}:${bodyHash}:${timestamp}`; const signatureDihitung = crypto .createHmac('sha512', process.env.DOKU_SECRET_KEY!) .update(stringToSign, 'utf8') .digest('base64'); if (!crypto.timingSafeEqual(Buffer.from(signatureDiterima), Buffer.from(signatureDihitung))) { return new Response('Tanda Tangan Tidak Valid', { status: 401 }); } const payload = JSON.parse(rawBody); const nomorInvoice = payload.order?.invoice_number; // 2. Atomic Update: Kunci hanya baris dengan status 'PENDING' untuk mencegah race condition const diperbarui = await db.transaksi.updateMany({ where: { nomorInvoice, status: 'PENDING' }, data: { status: 'PAID', dibayarPada: new Date() }, }); // 3. Penanganan Webhook Duplikat (Idempotensi) if (diperbarui.count === 0) { return new Response(JSON.stringify({ status: 'SUDAH_DIPROSES' }), { status: 200 }); } // 4. Aktivasi Layanan Pengguna (Hanya dieksekusi 1 kali) await aktifkanLangganan(nomorInvoice); return new Response(JSON.stringify({ status: 'BERHASIL' }), { status: 200 }); } ``` ### Matriks Eksekusi Berdaulat, Efisiensi Token & Invarian | Dimensi | Mandat Berdaulat & Kontrak Deterministik | | :--- | :--- | | **Kaskade Model Frontier** | High Fast / High (`Grok 4.7 High Fast`, `Claude Sonnet 5.5 High`, `Gemini 4 Pro`) | | **Invarian Fallback Ketersediaan** | `Gemini 4 Pro` -> `Gemini 3.8 Flash`; `Claude Opus 5.5` -> `Claude Opus 4.6`; `Grok 4.7` -> `Grok 4.6 High Fast` (Bebas gagal saat model belum tersedia/kuota penuh) | | **Protokol Hemat Token & KV-Cache** | Kunci instruksi & skema statis di prefix awal (`kv-cache-prefix-optimizer`); kirim potongan `CONTEXT_MAP.md` (<250 token/panggilan) saat Fan-Out | | **Eksekusi SaaT CLI & MCP** | CLI: `vibes skill exec payment-gateway-expert ` / MCP: `vibes_skill_dispatch`, `vibes_critic_validate`, `vibes_self_heal`, `vibes_eval_sandbox`, `vibes_verify_invariants` | | **Invarian NFR & Anti-Slop** | Nol celah OWASP Top 10, 0 kredensial hardcoded, cakupan pengujian >= 90% pada logika domain, 0 pelanggaran Anti-Slop; kode 100% tuntas tanpa placeholder diverifikasi melalui `node scripts/check-anti-slop.js --strict` |