|
| 1 | +{ |
| 2 | + "version": "1.0", |
| 3 | + "metadata": { |
| 4 | + "name": "Stripe Payments via Operational Gateway", |
| 5 | + "industry": "fintech", |
| 6 | + "description": "Reference manifest demonstrating Stripe payment integration migrated from direct API calls to the Operational Gateway abstraction layer. Includes outbound/inbound mappings, provider connection, and instruction routing." |
| 7 | + }, |
| 8 | + "instruments": [ |
| 9 | + { |
| 10 | + "code": "GBP", |
| 11 | + "name": "British Pound Sterling", |
| 12 | + "type": "INSTRUMENT_TYPE_FIAT", |
| 13 | + "dimensions": { |
| 14 | + "unit": "GBP", |
| 15 | + "precision": 2 |
| 16 | + } |
| 17 | + }, |
| 18 | + { |
| 19 | + "code": "USD", |
| 20 | + "name": "United States Dollar", |
| 21 | + "type": "INSTRUMENT_TYPE_FIAT", |
| 22 | + "dimensions": { |
| 23 | + "unit": "USD", |
| 24 | + "precision": 2 |
| 25 | + } |
| 26 | + }, |
| 27 | + { |
| 28 | + "code": "EUR", |
| 29 | + "name": "Euro", |
| 30 | + "type": "INSTRUMENT_TYPE_FIAT", |
| 31 | + "dimensions": { |
| 32 | + "unit": "EUR", |
| 33 | + "precision": 2 |
| 34 | + } |
| 35 | + } |
| 36 | + ], |
| 37 | + "accountTypes": [ |
| 38 | + { |
| 39 | + "code": "CUSTOMER_CURRENT", |
| 40 | + "name": "Customer Current Account", |
| 41 | + "normalBalance": "NORMAL_BALANCE_DEBIT", |
| 42 | + "allowedInstruments": ["GBP", "USD", "EUR"], |
| 43 | + "policies": { |
| 44 | + "validation": "amount > 0", |
| 45 | + "bucketing": "" |
| 46 | + } |
| 47 | + }, |
| 48 | + { |
| 49 | + "code": "PAYMENT_CLEARING", |
| 50 | + "name": "Payment Clearing Account", |
| 51 | + "normalBalance": "NORMAL_BALANCE_CREDIT", |
| 52 | + "allowedInstruments": ["GBP", "USD", "EUR"], |
| 53 | + "policies": { |
| 54 | + "validation": "", |
| 55 | + "bucketing": "" |
| 56 | + } |
| 57 | + } |
| 58 | + ], |
| 59 | + "valuationRules": [], |
| 60 | + "sagas": [ |
| 61 | + { |
| 62 | + "name": "stripe_payment_via_gateway", |
| 63 | + "trigger": "api:/v1/payments/stripe", |
| 64 | + "script": "# Saga: stripe_payment_via_gateway\n# Version: 1.0.0\n# Previous: none\n# Changed: Migrated from direct Stripe API calls to Operational Gateway dispatch\n# Author: Platform Team\n# Date: 2026-03-02\n#\n# This saga demonstrates the migration from direct payment_order.send_to_gateway\n# calls to operational_gateway.dispatch_instruction. The Operational Gateway\n# handles payload transformation (via MappingDefinitions), authentication,\n# retry logic, and circuit breaking transparently.\n#\n# The key change is Step 3: instead of calling payment_order.send_to_gateway\n# (which directly invokes the Stripe API), we dispatch an instruction of type\n# \"payment.collect\" to the Operational Gateway. The gateway resolves the\n# instruction route, applies the outbound mapping (payment-collect-to-stripe)\n# to transform the payload into Stripe's format, dispatches to the configured\n# provider connection (stripe-payments), and applies the inbound mapping\n# (stripe-response-to-ack) to normalize the response.\n#\n# Steps:\n# 1. get_payment_method - Resolve party's default Stripe payment method\n# 2. create_lien - Reserve funds with payment_attributes\n# 3. dispatch_to_gateway - Send via Operational Gateway (replaces send_to_gateway)\n# 4. post_ledger - Post double-entry ledger records (webhook-triggered)\n# 5. execute_lien - Finalize lien after ledger posted (webhook-triggered)\n#\n# Input parameters (from input_data dict):\n# - party_id: string (required) - party whose default payment method to use\n# - payment_order_id: string (required)\n# - debtor_account_id: string (required)\n# - creditor_reference: string (required)\n# - amount_cents: int64 (required)\n# - currency: string (required, e.g., \"GBP\", \"USD\")\n# - idempotency_key: string (required)\n# - instrument_code: string (optional, for bucket evaluation)\n# - payment_attributes: dict (optional, base attributes for CEL bucket expression)\n# - should_post_ledger: bool (optional, default false - set by webhook)\n# - should_execute_lien: bool (optional, default false - set by webhook)\n# - internal_clearing_enabled: bool (optional, for 4-posting ledger flow)\n\ndef stripe_payment_via_gateway():\n ctx = input_data\n\n # Step 1: Resolve the party's default payment method\n step(name=\"get_payment_method\")\n pm_result = party.get_default_payment_method(\n party_id=ctx.get(\"party_id\"),\n )\n\n # Build payment_attributes by merging resolved payment method details\n payment_attrs = dict(ctx.get(\"payment_attributes\") or {})\n payment_attrs[\"provider\"] = pm_result.provider\n payment_attrs[\"provider_customer_id\"] = pm_result.provider_customer_id\n payment_attrs[\"provider_method_id\"] = pm_result.provider_method_id\n payment_attrs[\"method_type\"] = pm_result.method_type\n\n # Step 2: Reserve funds via lien with payment method attributes\n step(name=\"create_lien\")\n lien_result = payment_order.create_lien(\n account_id=ctx.get(\"debtor_account_id\"),\n amount_cents=ctx.get(\"amount_cents\"),\n currency=ctx.get(\"currency\"),\n payment_order_id=ctx.get(\"payment_order_id\"),\n instrument_code=ctx.get(\"instrument_code\", \"\"),\n payment_attributes=payment_attrs,\n )\n\n lien_id = lien_result.lien_id\n\n # Step 3: Dispatch payment via Operational Gateway\n # This replaces the direct payment_order.send_to_gateway call.\n # The Operational Gateway will:\n # a) Resolve instruction route for \"payment.collect\" -> stripe-payments connection\n # b) Apply outbound mapping \"payment-collect-to-stripe\" (amount -> cents, currency -> lowercase)\n # c) Dispatch HTTPS POST to https://api.stripe.com/v1/payment_intents\n # d) Apply inbound mapping \"stripe-response-to-ack\" (normalize Stripe status)\n # e) Handle retries (3 attempts, exponential backoff) and circuit breaking\n step(name=\"dispatch_to_gateway\")\n gateway_result = operational_gateway.dispatch_instruction(\n instruction_type=\"payment.collect\",\n payload={\n \"payment_order_id\": ctx.get(\"payment_order_id\"),\n \"amount_cents\": ctx.get(\"amount_cents\"),\n \"currency\": ctx.get(\"currency\"),\n \"customer_id\": pm_result.provider_customer_id,\n \"payment_method_id\": pm_result.provider_method_id,\n \"creditor_reference\": ctx.get(\"creditor_reference\"),\n \"metadata\": {\n \"payment_order_id\": ctx.get(\"payment_order_id\"),\n \"debtor_account_id\": ctx.get(\"debtor_account_id\"),\n },\n },\n priority=\"HIGH\",\n correlation_id=ctx.get(\"payment_order_id\"),\n )\n\n instruction_id = gateway_result.instruction_id\n\n result = {\n \"lien_id\": lien_id,\n \"instruction_id\": instruction_id,\n \"gateway_status\": gateway_result.status,\n \"provider\": pm_result.provider,\n \"provider_customer_id\": pm_result.provider_customer_id,\n \"provider_method_id\": pm_result.provider_method_id,\n }\n\n # Step 4: Post ledger entries (conditional - triggered by webhook)\n if ctx.get(\"should_post_ledger\", False):\n step(name=\"post_ledger\")\n ledger_result = payment_order.post_ledger_entries(\n payment_order_id=ctx.get(\"payment_order_id\"),\n debtor_account_id=ctx.get(\"debtor_account_id\"),\n gateway_reference_id=instruction_id,\n amount_cents=ctx.get(\"amount_cents\"),\n currency=ctx.get(\"currency\"),\n idempotency_key=ctx.get(\"idempotency_key\"),\n internal_clearing_enabled=ctx.get(\"internal_clearing_enabled\", False),\n )\n result[\"booking_log_id\"] = ledger_result.booking_log_id\n\n # Step 5: Execute lien (conditional - triggered by webhook after ledger posted)\n if ctx.get(\"should_execute_lien\", False):\n if lien_id:\n step(name=\"execute_lien\")\n execution_result = payment_order.execute_lien(\n lien_id=lien_id,\n )\n result[\"lien_execution_status\"] = execution_result.execution_status\n\n return result\n\n# Execute the saga\noutput = stripe_payment_via_gateway()\n" |
| 65 | + } |
| 66 | + ], |
| 67 | + "paymentRails": [], |
| 68 | + "mappings": [ |
| 69 | + { |
| 70 | + "id": "00000000-0000-0000-0000-000000000001", |
| 71 | + "tenantId": "00000000-0000-0000-0000-000000000000", |
| 72 | + "name": "payment-collect-to-stripe", |
| 73 | + "targetService": "external.stripe", |
| 74 | + "targetRpc": "CreatePaymentIntent", |
| 75 | + "version": 1, |
| 76 | + "status": "MAPPING_STATUS_ACTIVE", |
| 77 | + "externalSchema": "{\"type\":\"object\",\"properties\":{\"amount\":{\"type\":\"integer\"},\"currency\":{\"type\":\"string\"},\"customer\":{\"type\":\"string\"},\"payment_method\":{\"type\":\"string\"},\"confirm\":{\"type\":\"boolean\"},\"metadata\":{\"type\":\"object\"}},\"required\":[\"amount\",\"currency\"]}", |
| 78 | + "fields": [ |
| 79 | + { |
| 80 | + "externalPath": "amount", |
| 81 | + "internalPath": "amount_cents", |
| 82 | + "transform": { |
| 83 | + "cel": { |
| 84 | + "inboundCel": "int(value)", |
| 85 | + "outboundCel": "int(value)" |
| 86 | + } |
| 87 | + } |
| 88 | + }, |
| 89 | + { |
| 90 | + "externalPath": "currency", |
| 91 | + "internalPath": "currency", |
| 92 | + "transform": { |
| 93 | + "cel": { |
| 94 | + "inboundCel": "value.upperAscii()", |
| 95 | + "outboundCel": "value.lowerAscii()" |
| 96 | + } |
| 97 | + } |
| 98 | + }, |
| 99 | + { |
| 100 | + "externalPath": "customer", |
| 101 | + "internalPath": "customer_id" |
| 102 | + }, |
| 103 | + { |
| 104 | + "externalPath": "payment_method", |
| 105 | + "internalPath": "payment_method_id" |
| 106 | + }, |
| 107 | + { |
| 108 | + "externalPath": "metadata.payment_order_id", |
| 109 | + "internalPath": "metadata.payment_order_id" |
| 110 | + }, |
| 111 | + { |
| 112 | + "externalPath": "metadata.debtor_account_id", |
| 113 | + "internalPath": "metadata.debtor_account_id" |
| 114 | + } |
| 115 | + ], |
| 116 | + "outboundComputedFields": [ |
| 117 | + { |
| 118 | + "targetPath": "confirm", |
| 119 | + "celExpression": "true" |
| 120 | + } |
| 121 | + ], |
| 122 | + "inboundComputedFields": [], |
| 123 | + "inboundValidationCel": "has(payload.amount_cents) && payload.amount_cents > 0", |
| 124 | + "outboundValidationCel": "", |
| 125 | + "idempotency": { |
| 126 | + "sourceSelector": "metadata.payment_order_id", |
| 127 | + "useContentHash": false, |
| 128 | + "contentHashFields": [] |
| 129 | + } |
| 130 | + }, |
| 131 | + { |
| 132 | + "id": "00000000-0000-0000-0000-000000000002", |
| 133 | + "tenantId": "00000000-0000-0000-0000-000000000000", |
| 134 | + "name": "stripe-response-to-ack", |
| 135 | + "targetService": "internal.operational_gateway", |
| 136 | + "targetRpc": "AcknowledgeInstruction", |
| 137 | + "version": 1, |
| 138 | + "status": "MAPPING_STATUS_ACTIVE", |
| 139 | + "externalSchema": "{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\"},\"status\":{\"type\":\"string\"},\"amount\":{\"type\":\"integer\"},\"currency\":{\"type\":\"string\"}}}", |
| 140 | + "fields": [ |
| 141 | + { |
| 142 | + "externalPath": "id", |
| 143 | + "internalPath": "provider_reference_id" |
| 144 | + }, |
| 145 | + { |
| 146 | + "externalPath": "status", |
| 147 | + "internalPath": "provider_status", |
| 148 | + "transform": { |
| 149 | + "enumMapping": { |
| 150 | + "values": { |
| 151 | + "requires_payment_method": "PENDING", |
| 152 | + "requires_confirmation": "PENDING", |
| 153 | + "requires_action": "PENDING", |
| 154 | + "processing": "PROCESSING", |
| 155 | + "requires_capture": "DELIVERED", |
| 156 | + "succeeded": "ACKNOWLEDGED", |
| 157 | + "canceled": "FAILED" |
| 158 | + }, |
| 159 | + "fallback": "UNKNOWN" |
| 160 | + } |
| 161 | + } |
| 162 | + }, |
| 163 | + { |
| 164 | + "externalPath": "amount", |
| 165 | + "internalPath": "amount_cents", |
| 166 | + "transform": { |
| 167 | + "cel": { |
| 168 | + "inboundCel": "int(value)", |
| 169 | + "outboundCel": "int(value)" |
| 170 | + } |
| 171 | + } |
| 172 | + }, |
| 173 | + { |
| 174 | + "externalPath": "currency", |
| 175 | + "internalPath": "currency", |
| 176 | + "transform": { |
| 177 | + "cel": { |
| 178 | + "inboundCel": "value.upperAscii()", |
| 179 | + "outboundCel": "value.lowerAscii()" |
| 180 | + } |
| 181 | + } |
| 182 | + } |
| 183 | + ], |
| 184 | + "inboundComputedFields": [], |
| 185 | + "outboundComputedFields": [], |
| 186 | + "inboundValidationCel": "has(payload.id) && has(payload.status)", |
| 187 | + "outboundValidationCel": "" |
| 188 | + } |
| 189 | + ], |
| 190 | + "operationalGateway": { |
| 191 | + "providerConnections": [ |
| 192 | + { |
| 193 | + "connectionId": "stripe-payments", |
| 194 | + "providerName": "Stripe", |
| 195 | + "providerType": "payment_gateway", |
| 196 | + "protocol": "PROVIDER_PROTOCOL_HTTPS", |
| 197 | + "baseUrl": "https://api.stripe.com/v1", |
| 198 | + "auth": { |
| 199 | + "apiKey": { |
| 200 | + "headerName": "Authorization", |
| 201 | + "apiKeySecretRef": "sm://stripe/api_key", |
| 202 | + "_note": "Secret must include 'Bearer ' prefix, e.g. 'Bearer sk_live_xxx'. APIKeyAuth sets the header value verbatim." |
| 203 | + } |
| 204 | + }, |
| 205 | + "retryPolicy": { |
| 206 | + "maxAttempts": 3, |
| 207 | + "initialBackoffSeconds": 1, |
| 208 | + "maxBackoffSeconds": 30, |
| 209 | + "backoffMultiplier": 2.0 |
| 210 | + }, |
| 211 | + "rateLimit": { |
| 212 | + "requestsPerSecond": 25.0, |
| 213 | + "burstSize": 50 |
| 214 | + } |
| 215 | + } |
| 216 | + ], |
| 217 | + "instructionRoutes": [ |
| 218 | + { |
| 219 | + "instructionType": "payment.collect", |
| 220 | + "connectionId": "stripe-payments", |
| 221 | + "outboundMappingId": "payment-collect-to-stripe", |
| 222 | + "inboundMappingId": "stripe-response-to-ack", |
| 223 | + "httpMethod": "POST", |
| 224 | + "pathTemplate": "/payment_intents" |
| 225 | + } |
| 226 | + ] |
| 227 | + } |
| 228 | +} |
0 commit comments