{
  "docVersion": 1,
  "id": "template-payment-gateway-integration",
  "name": "Payment gateway integration",
  "purpose": "PSP integration with an explicit auth→capture→settle state machine, idempotent calls, signed webhook ingestion, daily reconciliation and chargeback compensation.",
  "nodes": {
    "order": {
      "type": "server",
      "label": "Order Service",
      "x": 20,
      "y": 240,
      "runtime": "wants: charge £49"
    },
    "orch": {
      "type": "worker",
      "label": "Payment Orchestrator",
      "x": 290,
      "y": 240,
      "runtime": "state machine owner",
      "color": "blue",
      "notes": "Drives authorize → capture → settle. Every transition is recorded before the external call is made."
    },
    "state": {
      "type": "database",
      "label": "Payment State",
      "x": 290,
      "y": 470,
      "runtime": "one row per attempt",
      "patterns": [
        "backup"
      ]
    },
    "psp": {
      "type": "payment",
      "label": "PSP",
      "x": 570,
      "y": 90,
      "runtime": "external — Stripe/Adyen",
      "notes": "External and eventually consistent with reality. Trust the webhook + the settlement file, not the HTTP response alone."
    },
    "hook": {
      "type": "webhook",
      "label": "Webhook Receiver",
      "x": 570,
      "y": 330,
      "runtime": "verify signature · dedupe",
      "patterns": [
        "stateless"
      ],
      "notes": "Webhooks are at-least-once and unordered: verify the signature, dedupe on event id, then apply to the state machine."
    },
    "ledger": {
      "type": "ledger",
      "label": "Internal Ledger",
      "x": 860,
      "y": 330,
      "runtime": "double-entry postings"
    },
    "files": {
      "type": "storage",
      "label": "Settlement Files",
      "x": 1130,
      "y": 90,
      "runtime": "PSP daily reports"
    },
    "recon": {
      "type": "cron",
      "label": "Reconciliation",
      "x": 1130,
      "y": 330,
      "runtime": "ledger vs PSP · daily",
      "color": "amber",
      "notes": "Every settled payment must appear in both books. Mismatches open cases instead of silently drifting."
    },
    "cbk": {
      "type": "worker",
      "label": "Chargeback Handler",
      "x": 860,
      "y": 540,
      "runtime": "dispute → compensation",
      "color": "red"
    }
  },
  "edges": {
    "e1": {
      "from": "order",
      "to": "orch",
      "fromSide": "right",
      "toSide": "left",
      "label": "charge · Idempotency-Key",
      "animated": true,
      "patterns": [
        "idempotent"
      ]
    },
    "e2": {
      "from": "orch",
      "to": "state",
      "fromSide": "bottom",
      "toSide": "top",
      "label": "record transition first"
    },
    "e3": {
      "from": "orch",
      "to": "psp",
      "fromSide": "right",
      "toSide": "left",
      "label": "authorize / capture",
      "animated": true,
      "patterns": [
        "retry",
        "timeout",
        "circuit-breaker"
      ]
    },
    "e4": {
      "from": "psp",
      "to": "hook",
      "fromSide": "bottom",
      "toSide": "top",
      "label": "signed events",
      "style": "dashed",
      "patterns": [
        "authenticated",
        "dedup"
      ]
    },
    "e5": {
      "from": "hook",
      "to": "orch",
      "fromSide": "left",
      "toSide": "right",
      "label": "apply: captured / failed",
      "style": "dashed"
    },
    "e6": {
      "from": "orch",
      "to": "ledger",
      "fromSide": "right",
      "toSide": "left",
      "label": "post on settle"
    },
    "e7": {
      "from": "files",
      "to": "recon",
      "fromSide": "bottom",
      "toSide": "top",
      "label": "pull daily report"
    },
    "e8": {
      "from": "ledger",
      "to": "recon",
      "fromSide": "right",
      "toSide": "left",
      "label": "compare"
    },
    "e9": {
      "from": "hook",
      "to": "cbk",
      "fromSide": "bottom",
      "toSide": "top",
      "label": "dispute.created",
      "style": "dashed"
    },
    "e10": {
      "from": "cbk",
      "to": "ledger",
      "fromSide": "top",
      "toSide": "bottom",
      "label": "reverse posting",
      "style": "dashed"
    }
  },
  "boxes": {
    "b1": {
      "x": 820,
      "y": 250,
      "w": 580,
      "h": 440,
      "label": "the books — must agree with the psp's books",
      "color": "purple",
      "opacity": 0.04
    }
  },
  "texts": {
    "t1": {
      "x": 20,
      "y": 430,
      "text": "rule of PSP integrations: the HTTP response is an opinion,\nthe webhook is a rumor, the settlement file is the truth.\nDesign for all three to disagree.",
      "color": "amber",
      "size": 12
    }
  },
  "meta": {
    "createdAt": "2026-07-13T09:00:00.000Z",
    "modifiedAt": "2026-07-13T09:00:00.000Z"
  }
}