{
  "info": {
    "_postman_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "name": "VEXPay — Marketplace Settlement (Split)",
    "description": "End-to-end local flow to test marketplace settlement / split payouts.\n\n**Flow**\n1. Merchants — register + verify payout methods (skip if already done)\n2. Collect — fund tenant float via Débito Inmediato (happy path `monto` 100.00)\n3. Settle — single payout or batch split\n\n**Auth:** `x-api-key` collection/environment variable. Default `baseUrl` is `https://api.banking.chuventures.com` — replace `apiKey` with your tenant key.\n\n**Docs:** /postman · /marketplace-settlement · /debito-inmediato",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "variable": [
    { "key": "baseUrl", "value": "https://api.banking.chuventures.com" },
    { "key": "apiKey", "value": "vk_live_replace_me" },
    { "key": "merchantId", "value": "" },
    { "key": "merchantId2", "value": "" },
    { "key": "merchantExternalRef", "value": "seller_split_demo_001" },
    { "key": "merchantExternalRef2", "value": "partner_split_demo_001" },
    { "key": "payoutMethodId", "value": "" },
    { "key": "payoutMethodId2", "value": "" },
    { "key": "operationId", "value": "" },
    { "key": "paymentId", "value": "" },
    { "key": "paymentExternalRef", "value": "bid_split_demo_001" },
    { "key": "payoutId", "value": "" },
    { "key": "payoutExternalRef", "value": "po_split_smoke_001" },
    { "key": "batchExternalRef", "value": "settle_split_demo_001" },
    { "key": "verifyAmount1", "value": "0.37" },
    { "key": "verifyAmount2", "value": "0.82" }
  ],
  "auth": {
    "type": "apikey",
    "apikey": [
      { "key": "key", "value": "x-api-key", "type": "string" },
      { "key": "value", "value": "{{apiKey}}", "type": "string" },
      { "key": "in", "value": "header", "type": "string" }
    ]
  },
  "item": [
    {
      "name": "0. Helpers",
      "item": [
        {
          "name": "List banks",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{baseUrl}}/v1/banks",
            "description": "Use 4-digit SIMF (`code` padded, e.g. `0134`) as `banco` on Débito Inmediato and as `bankCode` on merchant payout methods."
          }
        },
        {
          "name": "Get FX quote",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/quote?usdAmount=12.50",
              "host": ["{{baseUrl}}"],
              "path": ["v1", "quote"],
              "query": [{ "key": "usdAmount", "value": "12.50" }]
            },
            "description": "Optional: preview VES for a USD amount. Débito Inmediato `monto` is already in Bs. (VES)."
          }
        }
      ]
    },
    {
      "name": "1. Merchants",
      "item": [
        {
          "name": "List merchants",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const json = pm.response.json();",
                  "if (json.items && json.items.length > 0) {",
                  "  const verified = json.items.filter(m => m.status === 'verified' && m.isActive);",
                  "  if (verified[0]) pm.collectionVariables.set('merchantId', verified[0].merchantId);",
                  "  if (verified[1]) pm.collectionVariables.set('merchantId2', verified[1].merchantId);",
                  "  if (verified[0]) console.log('merchantId =', verified[0].merchantId);",
                  "  if (verified[1]) console.log('merchantId2 =', verified[1].merchantId);",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/merchants?limit=25",
              "host": ["{{baseUrl}}"],
              "path": ["v1", "merchants"],
              "query": [{ "key": "limit", "value": "25" }]
            },
            "description": "Lists merchants. Test script auto-fills `merchantId` / `merchantId2` from the first two verified active merchants."
          }
        },
        {
          "name": "Create merchant (seller)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const json = pm.response.json();",
                  "if (json.merchantId) {",
                  "  pm.collectionVariables.set('merchantId', json.merchantId);",
                  "  console.log('merchantId =', json.merchantId);",
                  "}",
                  "if (json.externalRef) {",
                  "  pm.collectionVariables.set('merchantExternalRef', json.externalRef);",
                  "}",
                  "if (json.payoutMethods && json.payoutMethods[0]) {",
                  "  pm.collectionVariables.set('payoutMethodId', json.payoutMethods[0].payoutMethodId);",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"externalRef\": \"seller_split_demo_001\",\n  \"name\": \"Maria Perez\",\n  \"identification\": \"V12345678\",\n  \"contactEmail\": \"maria@mail.com\",\n  \"contactPhone\": \"04141234567\",\n  \"bankCode\": \"0134\",\n  \"phone\": \"04145555555\"\n}"
            },
            "url": "{{baseUrl}}/v1/merchants",
            "description": "Registers a merchant and first Pago Móvil payout method. Replace identification/phone with real values. Saves `merchantId`."
          }
        },
        {
          "name": "Create merchant (partner)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const json = pm.response.json();",
                  "if (json.merchantId) {",
                  "  pm.collectionVariables.set('merchantId2', json.merchantId);",
                  "  console.log('merchantId2 =', json.merchantId);",
                  "}",
                  "if (json.externalRef) {",
                  "  pm.collectionVariables.set('merchantExternalRef2', json.externalRef);",
                  "}",
                  "if (json.payoutMethods && json.payoutMethods[0]) {",
                  "  pm.collectionVariables.set('payoutMethodId2', json.payoutMethods[0].payoutMethodId);",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"externalRef\": \"partner_split_demo_001\",\n  \"name\": \"Jose Partner\",\n  \"identification\": \"V87654321\",\n  \"contactEmail\": \"jose@mail.com\",\n  \"contactPhone\": \"04149876543\",\n  \"bankCode\": \"0102\",\n  \"phone\": \"04141234567\"\n}"
            },
            "url": "{{baseUrl}}/v1/merchants",
            "description": "Second verified merchant needed for a real batch split. Saves `merchantId2`."
          }
        },
        {
          "name": "Get merchant",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{baseUrl}}/v1/merchants/{{merchantId}}",
            "description": "Fetch by gateway UUID. Prefer **Get merchant by externalRef** when your system stores the seller/user id instead of `merchantId`."
          }
        },
        {
          "name": "Get merchant by externalRef",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const json = pm.response.json();",
                  "if (json.merchantId) {",
                  "  pm.collectionVariables.set('merchantId', json.merchantId);",
                  "  console.log('merchantId =', json.merchantId, 'externalRef =', json.externalRef);",
                  "}",
                  "if (json.payoutMethods && json.payoutMethods[0]) {",
                  "  pm.collectionVariables.set('payoutMethodId', json.payoutMethods[0].payoutMethodId);",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/merchants?externalRef={{merchantExternalRef}}",
              "host": ["{{baseUrl}}"],
              "path": ["v1", "merchants"],
              "query": [
                {
                  "key": "externalRef",
                  "value": "{{merchantExternalRef}}",
                  "description": "Your tenant-scoped seller/user id from POST /v1/merchants"
                }
              ]
            },
            "description": "Look up a merchant for the authenticated tenant by the `externalRef` you registered (unique per tenant).\n\n- Auth: `x-api-key` scopes the search to that tenant only.\n- Returns the same single-merchant detail as GET /v1/merchants/:id (including `payoutMethods[]`).\n- 404 if the ref does not exist under this tenant.\n- Without `externalRef`, GET /v1/merchants returns a paginated list instead.\n\nSet `merchantExternalRef` (default matches Create merchant seller: `seller_split_demo_001`). Partner: use `merchantExternalRef2`."
          }
        },
        {
          "name": "Get merchant (partner) by externalRef",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const json = pm.response.json();",
                  "if (json.merchantId) {",
                  "  pm.collectionVariables.set('merchantId2', json.merchantId);",
                  "  console.log('merchantId2 =', json.merchantId, 'externalRef =', json.externalRef);",
                  "}",
                  "if (json.payoutMethods && json.payoutMethods[0]) {",
                  "  pm.collectionVariables.set('payoutMethodId2', json.payoutMethods[0].payoutMethodId);",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/merchants?externalRef={{merchantExternalRef2}}",
              "host": ["{{baseUrl}}"],
              "path": ["v1", "merchants"],
              "query": [
                {
                  "key": "externalRef",
                  "value": "{{merchantExternalRef2}}",
                  "description": "Partner merchant externalRef"
                }
              ]
            },
            "description": "Same as **Get merchant by externalRef**, for the partner seller (`partner_split_demo_001`). Saves `merchantId2` / `payoutMethodId2`."
          }
        },
        {
          "name": "List payout methods",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const json = pm.response.json();",
                  "const items = json.items || json;",
                  "if (Array.isArray(items) && items[0]) {",
                  "  pm.collectionVariables.set('payoutMethodId', items[0].payoutMethodId);",
                  "  console.log('payoutMethodId =', items[0].payoutMethodId);",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{baseUrl}}/v1/merchants/{{merchantId}}/payout-methods",
            "description": "Saves the first method as `payoutMethodId`."
          }
        },
        {
          "name": "Add payout method",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const json = pm.response.json();",
                  "if (json.payoutMethodId) {",
                  "  pm.collectionVariables.set('payoutMethodId2', json.payoutMethodId);",
                  "  pm.collectionVariables.set('payoutMethodId', json.payoutMethodId);",
                  "  console.log('Created payoutMethodId =', json.payoutMethodId, '(also saved as payoutMethodId2)');",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"bankCode\": \"0105\",\n  \"phone\": \"04145551234\"\n}"
            },
            "url": "{{baseUrl}}/v1/merchants/{{merchantId}}/payout-methods",
            "description": "Register another Pago Móvil destination on an existing merchant.\n\n1. Set `merchantId` (from Register / Get merchant).\n2. Edit `bankCode` + `phone` in the body to the real Pago Móvil destination.\n3. Send — saves the new id into `payoutMethodId` and `payoutMethodId2`.\n4. Run **Start verify payout method** → **Confirm verify payout method**.\n\nStarts as `pending_verification`. Duplicate bankCode+phone → 409 `payout_method_exists`."
          }
        },
        {
          "name": "Start verify payout method",
          "request": {
            "method": "POST",
            "header": [],
            "url": "{{baseUrl}}/v1/merchants/{{merchantId}}/payout-methods/{{payoutMethodId}}/verify/start",
            "description": "Triggers micro-deposits via R4 CreditoInmediato for `payoutMethodId` (set by List or Add). Merchant reads amount1/amount2 from their phone (never returned by API)."
          }
        },
        {
          "name": "Confirm verify payout method",
          "request": {
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"amount1\": \"{{verifyAmount1}}\",\n  \"amount2\": \"{{verifyAmount2}}\"\n}"
            },
            "url": "{{baseUrl}}/v1/merchants/{{merchantId}}/payout-methods/{{payoutMethodId}}/verify",
            "description": "Set `verifyAmount1` / `verifyAmount2` collection vars to the exact 0.XX amounts from the merchant's phone."
          }
        },
        {
          "name": "Set default payout method",
          "request": {
            "method": "POST",
            "header": [],
            "url": "{{baseUrl}}/v1/merchants/{{merchantId}}/payout-methods/{{payoutMethodId}}/default",
            "description": "Marks `payoutMethodId` as default. Top-level merchant bankCode/destination mirror it; payouts without payoutMethodId prefer the default verified method."
          }
        },
        {
          "name": "Delete payout method",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": "{{baseUrl}}/v1/merchants/{{merchantId}}/payout-methods/{{payoutMethodId}}",
            "description": "Removes the method in `payoutMethodId` (204). If it was default, another method is promoted (verified preferred). Historical payouts keep bank/phone snapshots.\n\nTip: run **List payout methods** first, or paste the id you want to remove into the `payoutMethodId` collection variable."
          }
        },
        {
          "name": "Deactivate merchant",
          "request": {
            "method": "PATCH",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"isActive\": false\n}"
            },
            "url": "{{baseUrl}}/v1/merchants/{{merchantId}}",
            "description": "Soft off-switch. Blocks new payouts but keeps history and externalRef. Re-activate with `{ \"isActive\": true }` (no re-verification).\n\nUse this when the merchant already has payout history — DELETE will return 409 `merchant_has_payouts`."
          }
        },
        {
          "name": "Delete merchant",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": "{{baseUrl}}/v1/merchants/{{merchantId}}",
            "description": "Hard-deletes the merchant and its payout methods when there is **no payout history**. Frees `externalRef` for re-registration.\n\n**200** `{ \"deleted\": true, \"merchantId\", \"externalRef\" }`\n**409** `{ \"error\": \"merchant_has_payouts\", \"payoutCount\": N }` — use **Deactivate merchant** instead.\n**404** merchant not found for this tenant.\n\nDocs: /merchants-payouts#delete-merchant"
          }
        },
        {
          "name": "Start verify (legacy default method)",
          "request": {
            "method": "POST",
            "header": [],
            "url": "{{baseUrl}}/v1/merchants/{{merchantId}}/verify/start",
            "description": "Legacy shortcut that verifies the merchant's default payout method."
          }
        },
        {
          "name": "Confirm verify (legacy default method)",
          "request": {
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"amount1\": \"{{verifyAmount1}}\",\n  \"amount2\": \"{{verifyAmount2}}\"\n}"
            },
            "url": "{{baseUrl}}/v1/merchants/{{merchantId}}/verify"
          }
        }
      ]
    },
    {
      "name": "2. Collect (fund float)",
      "item": [
        {
          "name": "Debit OTP (request bank code)",
          "request": {
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"banco\": \"0102\",\n  \"monto\": 100.00,\n  \"telefono\": \"584121234567\",\n  \"cedula\": \"V87654321\"\n}"
            },
            "url": "{{baseUrl}}/v1/payments/debit/otp",
            "description": "Asks the payer bank to SMS a 6–8 digit OTP.\n\n`banco` = 4-digit SIMF (from GET /v1/banks). `telefono` = 58 + 10 digits. `monto` is Bs. (VES). Happy-path sample uses 100.00 so a small platform fixed USD fee still leaves float for a Bs 10 payout.\n\nTypical success: `code` 202. Then collect the OTP from the customer and run Débito Inmediato."
          }
        },
        {
          "name": "Débito Inmediato",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const json = pm.response.json();",
                  "if (json.paymentId) {",
                  "  pm.collectionVariables.set('paymentId', json.paymentId);",
                  "  console.log('paymentId =', json.paymentId, 'code =', json.code);",
                  "}",
                  "const opId = json.Id || json.id;",
                  "if (json.code === 'AC00' && opId) {",
                  "  pm.collectionVariables.set('operationId', opId);",
                  "  console.log('AC00 pending — operationId =', opId);",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"banco\": \"0102\",\n  \"monto\": 100.00,\n  \"telefono\": \"584121234567\",\n  \"cedula\": \"V87654321\",\n  \"nombre\": \"Jose Partner\",\n  \"otp\": \"123456\",\n  \"concepto\": \"Fund float demo\",\n  \"externalRef\": \"{{paymentExternalRef}}\"\n}"
            },
            "url": "{{baseUrl}}/v1/payments/debit",
            "description": "Replace `otp` with the real bank SMS code. Use the same payer fields as Debit OTP.\n\n- `ACCP` — accepted; float funded; `payment.completed` webhook\n- `AC00` — pending; gateway auto-polls; optional manual poll via GET /v1/payments/operations/:id\n\nOn success, tenant available balance increases by `monto` minus platform ledger fees. With Local Dev fees 2.5% + $0.05 fixed and reserve 0, expect roughly Bs 63–66 available after a 100.00 collect."
          }
        },
        {
          "name": "Poll pending operation (AC00)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const json = pm.response.json();",
                  "console.log('operation poll code =', json.code, 'reference =', json.reference);",
                  "if (json.paymentId) {",
                  "  pm.collectionVariables.set('paymentId', json.paymentId);",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{baseUrl}}/v1/payments/operations/{{operationId}}",
            "description": "Optional. Only needed when Débito Inmediato returned `AC00` and you want a manual status check. Prefer waiting for `payment.completed` / `payment.failed` webhooks."
          }
        },
        {
          "name": "Get payment by externalRef",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{baseUrl}}/v1/payments/by-ref/{{paymentExternalRef}}",
            "description": "Confirm `method` is DEBITO_INMEDIATO and `status` is completed before settling payouts."
          }
        },
        {
          "name": "Get payment by id",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{baseUrl}}/v1/payments/{{paymentId}}"
          }
        }
      ]
    },
    {
      "name": "3. Settle (payouts / split)",
      "item": [
        {
          "name": "Single payout (smoke test)",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const stamp = Date.now().toString(36);",
                  "pm.collectionVariables.set('payoutExternalRef', 'po_split_smoke_' + stamp);"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const json = pm.response.json();",
                  "if (json.payoutId) {",
                  "  pm.collectionVariables.set('payoutId', json.payoutId);",
                  "  console.log('payoutId =', json.payoutId, 'status =', json.status);",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"merchantId\": \"{{merchantId}}\",\n  \"paymentId\": \"{{paymentId}}\",\n  \"monto\": \"10.00\",\n  \"concepto\": \"Split smoke test\",\n  \"externalRef\": \"{{payoutExternalRef}}\"\n}"
            },
            "url": "{{baseUrl}}/v1/payouts",
            "description": "Pays one verified merchant linked to the funding payment. `monto` must be exact two-decimal VES string. `paymentId` is set by the Débito collect step — omit it only for float-only payouts.\n\nPre-request script generates a unique `externalRef` each run. Optional: add `\"payoutMethodId\": \"{{payoutMethodId}}\"`."
          }
        },
        {
          "name": "Batch split (multi-merchant)",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const stamp = Date.now().toString(36);",
                  "pm.collectionVariables.set('batchExternalRef', 'settle_split_' + stamp);",
                  "pm.collectionVariables.set('payoutExternalRef', 'po_seller_' + stamp);",
                  "pm.variables.set('partnerExternalRef', 'po_partner_' + stamp);"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"externalRef\": \"{{batchExternalRef}}\",\n  \"paymentId\": \"{{paymentId}}\",\n  \"items\": [\n    {\n      \"merchantId\": \"{{merchantId}}\",\n      \"monto\": \"40.00\",\n      \"concepto\": \"Seller net\",\n      \"externalRef\": \"{{payoutExternalRef}}\"\n    },\n    {\n      \"merchantId\": \"{{merchantId2}}\",\n      \"monto\": \"30.00\",\n      \"concepto\": \"Partner share\",\n      \"externalRef\": \"{{partnerExternalRef}}\"\n    }\n  ]\n}"
            },
            "url": "{{baseUrl}}/v1/payouts/batch",
            "description": "Splits settlement across multiple verified merchants against the funding `paymentId`. Remaining net = vesAmount − feeVes − PENDING/COMPLETED legs. Results are **per item** (not all-or-nothing).\n\n1. Run collect so `paymentId` is set, then **List merchants** for `merchantId` / `merchantId2`\n2. Ensure available balance ≥ sum of montos and sum ≤ remaining for that payment\n3. Adjust montos to fit (defaults assume Débito ~100.00 after platform fee)\n\nMarketplace app fee = money you do **not** include in any item."
          }
        },
        {
          "name": "List payouts",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/payouts?status=completed&limit=25",
              "host": ["{{baseUrl}}"],
              "path": ["v1", "payouts"],
              "query": [
                { "key": "status", "value": "completed" },
                { "key": "limit", "value": "25" }
              ]
            }
          }
        },
        {
          "name": "Get payout by externalRef",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{baseUrl}}/v1/payouts/by-ref/{{payoutExternalRef}}"
          }
        },
        {
          "name": "Get payout by id",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{baseUrl}}/v1/payouts/{{payoutId}}"
          }
        }
      ]
    }
  ]
}
