{
  "info": {
    "_postman_id": "8ad2e0c0-c692-4a4f-b971-8eab536ef031",
    "name": "TenkiPay Merchant API",
    "description": "Server-to-server merchant integration for hosted checkout, business transfers, agent cash pickup, invoices, TenkiPass, and business operations. Replace the collection credentials before sending requests. The pre-request script uses Web Crypto to sign the exact resolved request path and raw JSON body with HMAC-SHA256 v2. Never export a collection containing a live secret.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "event": [
    {
      "listen": "prerequest",
      "script": {
        "type": "text/javascript",
        "exec": [
          "const publicKey = pm.collectionVariables.get('public_key');",
          "const secretKey = pm.collectionVariables.get('secret_key');",
          "if (!publicKey || !secretKey || publicKey.includes('REPLACE_ME') || secretKey.includes('REPLACE_ME')) {",
          "  throw new Error('Set public_key and secret_key in the collection variables before sending requests.');",
          "}",
          "const timestamp = new Date().toISOString();",
          "const method = pm.request.method.toUpperCase();",
          "const path = pm.variables.replaceIn(pm.request.url.getPath());",
          "const body = pm.request.body && pm.request.body.mode === 'raw'",
          "  ? pm.variables.replaceIn(pm.request.body.raw || '')",
          "  : '';",
          "const canonical = [timestamp, method, path, body].join('\\n');",
          "const encoder = new TextEncoder();",
          "const hmacKey = await crypto.subtle.importKey(",
          "  'raw', encoder.encode(secretKey), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']",
          ");",
          "const signatureBytes = await crypto.subtle.sign('HMAC', hmacKey, encoder.encode(canonical));",
          "const signature = Array.from(new Uint8Array(signatureBytes))",
          "  .map(byte => byte.toString(16).padStart(2, '0'))",
          "  .join('');",
          "pm.request.headers.upsert({ key: 'Accept', value: 'application/json' });",
          "pm.request.headers.upsert({ key: 'X-TenkiPay-Key', value: publicKey });",
          "pm.request.headers.upsert({ key: 'X-TenkiPay-Timestamp', value: timestamp });",
          "pm.request.headers.upsert({ key: 'X-TenkiPay-Signature', value: signature });"
        ]
      }
    }
  ],
  "variable": [
    { "key": "recipient_wallet", "value": "REPLACE_WITH_RECIPIENT_WALLET", "type": "string" },
    { "key": "transfer_id", "value": "00000000-0000-0000-0000-000000000000", "type": "string" },
    { "key": "transfer_total_debit", "value": "RUN_QUOTE_FIRST", "type": "string" },
    { "key": "base_url", "value": "https://me.tenkipay.com", "type": "string" },
    { "key": "public_key", "value": "pk_test_REPLACE_ME", "type": "string" },
    { "key": "secret_key", "value": "sk_test_REPLACE_ME", "type": "secret" },
    { "key": "session_id", "value": "CS-REPLACE_ME", "type": "string" },
    { "key": "checkout_url", "value": "", "type": "string" },
    { "key": "invoice_number", "value": "INV-REPLACE_ME", "type": "string" },
    { "key": "entitlement_token", "value": "TICKET-REPLACE_ME", "type": "string" },
    { "key": "workspace_id", "value": "00000000-0000-0000-0000-000000000000", "type": "string" },
    { "key": "item_id", "value": "00000000-0000-0000-0000-000000000000", "type": "string" },
    { "key": "sale_id", "value": "00000000-0000-0000-0000-000000000000", "type": "string" },
    { "key": "room_id", "value": "00000000-0000-0000-0000-000000000000", "type": "string" },
    { "key": "stay_id", "value": "00000000-0000-0000-0000-000000000000", "type": "string" }
  ],
  "item": [
    {
      "name": "Transfers and disbursements",
      "description": "Reuse the existing checkout key and approved webhook. Subscribe to transfer.ready_for_pickup, transfer.completed, transfer.failed, and transfer.reversed. Set recipient_wallet to an exact wallet number. Run the matching quote before create. Agent cash pickup requires a legal name, contact phone, purpose, funding source, and relationship; no recipient wallet account is required. Retrieve the signed transfer endpoint for the sensitive claim code. For mobile money use destination.type ORANGE_MONEY or QMONEY and destination.account as the recipient phone. Sandbox never moves real funds. HTTP 202 is queued, not paid. Reuse the same idempotency key and body after an uncertain response.",
      "item": [
{
  "name": "Quote agent cash pickup",
  "request": {
    "method": "POST",
    "header": [
      {
        "key": "Content-Type",
        "value": "application/json"
      }
    ],
    "url": "{{base_url}}/api/v1/merchant/transfers/quote",
    "body": {
      "mode": "raw",
      "raw": "{\n  \"amount\": \"100.00\",\n  \"currency\": \"SLE\",\n  \"destination\": {\n    \"type\": \"AGENT_CASH\",\n    \"account\": \"077123456\",\n    \"name\": \"Recipient full legal name\",\n    \"payment_purpose\": \"GOODS_AND_SERVICES\",\n    \"source_of_funds\": \"BUSINESS_INCOME\",\n    \"relationship\": \"CUSTOMER\"\n  }\n}",
      "options": {
        "raw": {
          "language": "json"
        }
      }
    }
  },
  "event": [
    {
      "listen": "test",
      "script": {
        "type": "text/javascript",
        "exec": [
          "if (pm.response.code >= 200 && pm.response.code < 300) {",
          "  pm.collectionVariables.set('transfer_total_debit', pm.response.json().data.total_debit);",
          "}"
        ]
      }
    }
  ]
},
{
  "name": "Create agent cash pickup",
  "request": {
    "method": "POST",
    "header": [
      {
        "key": "Content-Type",
        "value": "application/json"
      },
      {
        "key": "Idempotency-Key",
        "value": "clubmoni-pickup-1001-v1"
      }
    ],
    "url": "{{base_url}}/api/v1/merchant/transfers",
    "body": {
      "mode": "raw",
      "raw": "{\n  \"amount\": \"100.00\",\n  \"currency\": \"SLE\",\n  \"destination\": {\n    \"type\": \"AGENT_CASH\",\n    \"account\": \"077123456\",\n    \"name\": \"Recipient full legal name\",\n    \"payment_purpose\": \"GOODS_AND_SERVICES\",\n    \"source_of_funds\": \"BUSINESS_INCOME\",\n    \"relationship\": \"CUSTOMER\"\n  },\n  \"reference\": \"clubmoni-pickup-1001\",\n  \"max_total_debit\": \"{{transfer_total_debit}}\",\n  \"description\": \"Seller settlement\"\n}",
      "options": {
        "raw": {
          "language": "json"
        }
      }
    }
  },
  "event": [
    {
      "listen": "test",
      "script": {
        "type": "text/javascript",
        "exec": [
          "if (pm.response.code >= 200 && pm.response.code < 300) {",
          "  pm.collectionVariables.set('transfer_id', pm.response.json().data.id);",
          "}"
        ]
      }
    }
  ]
},
        {
          "name": "Quote transfer",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": "{{base_url}}/api/v1/merchant/transfers/quote",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"amount\": \"100.00\",\n  \"currency\": \"SLE\",\n  \"destination\": {\n    \"type\": \"TENKIPAY\",\n    \"account\": \"{{recipient_wallet}}\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code >= 200 && pm.response.code < 300) {",
                  "  pm.collectionVariables.set('transfer_total_debit', pm.response.json().data.total_debit);",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "Create transfer",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "seller-payment-1001-v1"
              }
            ],
            "url": "{{base_url}}/api/v1/merchant/transfers",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"amount\": \"100.00\",\n  \"currency\": \"SLE\",\n  \"destination\": {\n    \"type\": \"TENKIPAY\",\n    \"account\": \"{{recipient_wallet}}\"\n  },\n  \"reference\": \"seller-payment-1001\",\n  \"max_total_debit\": \"{{transfer_total_debit}}\",\n  \"description\": \"Seller settlement\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code >= 200 && pm.response.code < 300) {",
                  "  pm.collectionVariables.set('transfer_id', pm.response.json().data.id);",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "Get transfer",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{base_url}}/api/v1/merchant/transfers/{{transfer_id}}"
          }
        },
        {
          "name": "Get business balance",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{base_url}}/api/v1/merchant/balance"
          }
        }
      ]
    },
    {
      "name": "TenkiPay Checkout",
      "description": "Create a fixed-amount hosted checkout on your server, then redirect the customer browser to data.checkout_url.",
      "item": [
        {
          "name": "Create checkout session",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Content-Type", "value": "application/json" },
              { "key": "Idempotency-Key", "value": "checkout-order-1001-v1" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"merchant_reference\": \"ORDER-1001\",\n  \"amount\": \"250.00\",\n  \"currency\": \"SLE\",\n  \"description\": \"Online store order 1001\",\n  \"success_url\": \"https://shop.example.com/orders/1001/paid\",\n  \"cancel_url\": \"https://shop.example.com/orders/1001\",\n  \"customer_name\": \"Aminata Kamara\",\n  \"customer_phone\": \"+23276000000\"\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": "{{base_url}}/api/v1/merchant/checkout/sessions",
            "description": "Requires checkout.sessions:write. Keep the Idempotency-Key stable when retrying this exact order attempt."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Checkout session created', () => pm.expect(pm.response.code).to.be.oneOf([200, 201]));",
                  "const payload = pm.response.json();",
                  "if (payload.data && payload.data.session_id) pm.collectionVariables.set('session_id', payload.data.session_id);",
                  "if (payload.data && payload.data.checkout_url) pm.collectionVariables.set('checkout_url', payload.data.checkout_url);"
                ]
              }
            }
          ]
        },
        {
          "name": "Retrieve checkout session",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{base_url}}/api/v1/merchant/checkout/sessions/{{session_id}}",
            "description": "Requires checkout.sessions:read. Treat only status ACSC as paid."
          }
        }
      ]
    },
    {
      "name": "Invoices",
      "item": [
        {
          "name": "Create invoice",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Content-Type", "value": "application/json" },
              { "key": "Idempotency-Key", "value": "invoice-erp-1042-v1" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"external_reference\": \"INV-ERP-1042\",\n  \"customer_name\": \"Aminata Kamara\",\n  \"customer_email\": \"aminata@example.com\",\n  \"description\": \"August services\",\n  \"currency\": \"SLE\",\n  \"allow_partial_payments\": false,\n  \"publish\": true,\n  \"items\": [\n    { \"description\": \"Professional services\", \"quantity\": 1, \"unit_amount\": \"850.00\" }\n  ]\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": "{{base_url}}/api/v1/merchant/invoices",
            "description": "Requires invoices:write."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Invoice created', () => pm.expect(pm.response.code).to.be.oneOf([200, 201]));",
                  "const payload = pm.response.json();",
                  "if (payload.data && payload.data.invoice_number) pm.collectionVariables.set('invoice_number', payload.data.invoice_number);"
                ]
              }
            }
          ]
        },
        {
          "name": "Retrieve invoice",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{base_url}}/api/v1/merchant/invoices/{{invoice_number}}",
            "description": "Requires invoices:read. The path accepts invoice number, collection reference, or external reference."
          }
        }
      ]
    },
    {
      "name": "TenkiPass",
      "description": "Live-approved credentials are required for entitlement verification and redemption.",
      "item": [
        {
          "name": "Verify entitlement",
          "request": {
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"token\": \"{{entitlement_token}}\"\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": "{{base_url}}/api/v1/merchant/entitlements/verify"
          }
        },
        {
          "name": "Redeem entitlement",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Content-Type", "value": "application/json" },
              { "key": "Idempotency-Key", "value": "redeem-entitlement-1001-v1" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"token\": \"{{entitlement_token}}\"\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": "{{base_url}}/api/v1/merchant/entitlements/redeem"
          }
        }
      ]
    },
    {
      "name": "TenkiOps Business Operations",
      "description": "Tenant- and environment-isolated APIs for hotel, retail, fuel, and pharmacy operations.",
      "item": [
        {
          "name": "List workspaces",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{base_url}}/api/v1/merchant/operations/workspaces"
          }
        },
        {
          "name": "Create workspace",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Content-Type", "value": "application/json" },
              { "key": "Idempotency-Key", "value": "workspace-demo-hotel-v1" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Demo Hotel\",\n  \"operation_type\": \"HOTEL\",\n  \"currency\": \"SLE\"\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": "{{base_url}}/api/v1/merchant/operations/workspaces"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const payload = pm.response.json();",
                  "const id = payload.data && (payload.data.id || payload.data.workspace_id);",
                  "if (id) pm.collectionVariables.set('workspace_id', id);"
                ]
              }
            }
          ]
        },
        {
          "name": "List catalog",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{base_url}}/api/v1/merchant/operations/workspaces/{{workspace_id}}/catalog"
          }
        },
        {
          "name": "Create catalog item",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Content-Type", "value": "application/json" },
              { "key": "Idempotency-Key", "value": "catalog-room-service-v1" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"sku\": \"ROOM-SERVICE-001\",\n  \"name\": \"Room service breakfast\",\n  \"category\": \"Food\",\n  \"item_type\": \"FOOD\",\n  \"unit\": \"UNIT\",\n  \"sale_price\": \"75.00\",\n  \"track_inventory\": false,\n  \"opening_stock\": \"0\",\n  \"reorder_level\": \"0\"\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": "{{base_url}}/api/v1/merchant/operations/workspaces/{{workspace_id}}/catalog"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const payload = pm.response.json();",
                  "const id = payload.data && (payload.data.id || payload.data.item_id);",
                  "if (id) pm.collectionVariables.set('item_id', id);"
                ]
              }
            }
          ]
        },
        {
          "name": "Adjust stock",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Content-Type", "value": "application/json" },
              { "key": "Idempotency-Key", "value": "stock-receipt-1001-v1" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"direction\": \"IN\",\n  \"quantity\": \"20\",\n  \"unit_cost\": \"40.00\",\n  \"note\": \"Opening delivery\"\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": "{{base_url}}/api/v1/merchant/operations/workspaces/{{workspace_id}}/catalog/{{item_id}}/stock-adjustments"
          }
        },
        {
          "name": "List sales",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{base_url}}/api/v1/merchant/operations/workspaces/{{workspace_id}}/sales"
          }
        },
        {
          "name": "Create sale",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Content-Type", "value": "application/json" },
              { "key": "Idempotency-Key", "value": "sale-demo-1001-v1" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"customer_name\": \"Aminata Kamara\",\n  \"customer_phone\": \"+23276000000\",\n  \"tax_amount\": \"0.00\",\n  \"discount_amount\": \"0.00\",\n  \"items\": [\n    { \"catalog_item_id\": \"{{item_id}}\", \"quantity\": \"1\" }\n  ]\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": "{{base_url}}/api/v1/merchant/operations/workspaces/{{workspace_id}}/sales"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const payload = pm.response.json();",
                  "const id = payload.data && (payload.data.id || payload.data.sale_id);",
                  "if (id) pm.collectionVariables.set('sale_id', id);"
                ]
              }
            }
          ]
        },
        {
          "name": "Retrieve sale",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{base_url}}/api/v1/merchant/operations/sales/{{sale_id}}"
          }
        },
        {
          "name": "Create sale checkout",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Content-Type", "value": "application/json" },
              { "key": "Idempotency-Key", "value": "sale-checkout-1001-v1" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"success_url\": \"https://shop.example.com/sales/1001/paid\",\n  \"cancel_url\": \"https://shop.example.com/sales/1001\"\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": "{{base_url}}/api/v1/merchant/operations/sales/{{sale_id}}/checkout"
          }
        },
        {
          "name": "List hotel rooms",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{base_url}}/api/v1/merchant/operations/workspaces/{{workspace_id}}/hotel/rooms"
          }
        },
        {
          "name": "Create hotel room",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Content-Type", "value": "application/json" },
              { "key": "Idempotency-Key", "value": "hotel-room-101-v1" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"room_number\": \"101\",\n  \"room_type\": \"Standard\",\n  \"nightly_rate\": \"450.00\",\n  \"capacity\": 2,\n  \"amenities\": [\"Wi-Fi\", \"Air conditioning\"]\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": "{{base_url}}/api/v1/merchant/operations/workspaces/{{workspace_id}}/hotel/rooms"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const payload = pm.response.json();",
                  "const id = payload.data && (payload.data.id || payload.data.room_id);",
                  "if (id) pm.collectionVariables.set('room_id', id);"
                ]
              }
            }
          ]
        },
        {
          "name": "List hotel stays",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{base_url}}/api/v1/merchant/operations/workspaces/{{workspace_id}}/hotel/stays"
          }
        },
        {
          "name": "Check in hotel guest",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Content-Type", "value": "application/json" },
              { "key": "Idempotency-Key", "value": "hotel-stay-1001-v1" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"room_id\": \"{{room_id}}\",\n  \"guest_name\": \"Aminata Kamara\",\n  \"guest_phone\": \"+23276000000\",\n  \"adults\": 1,\n  \"children\": 0,\n  \"check_in_at\": \"2026-08-05T14:00:00Z\",\n  \"check_out_at\": \"2026-08-07T10:00:00Z\",\n  \"payment_timing\": \"CHECK_OUT\"\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": "{{base_url}}/api/v1/merchant/operations/workspaces/{{workspace_id}}/hotel/stays"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const payload = pm.response.json();",
                  "const id = payload.data && (payload.data.id || payload.data.stay_id);",
                  "if (id) pm.collectionVariables.set('stay_id', id);"
                ]
              }
            }
          ]
        },
        {
          "name": "Add hotel folio charge",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Content-Type", "value": "application/json" },
              { "key": "Idempotency-Key", "value": "hotel-charge-breakfast-1001-v1" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"catalog_item_id\": \"{{item_id}}\",\n  \"description\": \"Room service breakfast\",\n  \"quantity\": \"1\"\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": "{{base_url}}/api/v1/merchant/operations/hotel/stays/{{stay_id}}/charges"
          }
        },
        {
          "name": "Check out hotel guest",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Content-Type", "value": "application/json" },
              { "key": "Idempotency-Key", "value": "hotel-checkout-1001-v1" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": { "raw": { "language": "json" } }
            },
            "url": "{{base_url}}/api/v1/merchant/operations/hotel/stays/{{stay_id}}/checkout"
          }
        }
      ]
    }
  ]
}
