{
  "info": {
    "_postman_id": "8bd3ae66-4033-4a4f-997d-3a0bb91a1bd0",
    "name": "TRC20 Payment Gateway Merchant API",
    "description": "Signed Merchant REST API requests for TRON Nile staging. Configure the API host's mTLS client certificate in Postman Settings > Certificates before sending. Store the Ed25519 PKCS#8 PEM only in the selected environment's secret current value; never sync or export it.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "noauth"
  },
  "event": [
    {
      "listen": "prerequest",
      "script": {
        "type": "text/javascript",
        "exec": [
          "const nacl = pm.require('npm:tweetnacl@1.0.3');",
          "const CryptoJS = pm.require('npm:crypto-js@4.2.0');",
          "const pem = pm.environment.get('apiPrivateKeyPem');",
          "const keyId = pm.environment.get('apiKeyId');",
          "if (!pem || !keyId) throw new Error('Set secret apiPrivateKeyPem and apiKeyId environment values.');",
          "const b64 = pem.replace(/-----BEGIN PRIVATE KEY-----|-----END PRIVATE KEY-----|\\s/g, '');",
          "const binary = atob(b64);",
          "const der = Uint8Array.from(binary, c => c.charCodeAt(0));",
          "const prefix = '302e020100300506032b657004220420';",
          "const actualPrefix = Array.from(der.slice(0, 16), b => b.toString(16).padStart(2, '0')).join('');",
          "if (der.length !== 48 || actualPrefix !== prefix) throw new Error('apiPrivateKeyPem must be an unencrypted Ed25519 PKCS#8 PEM.');",
          "const seed = der.slice(16);",
          "const keypair = nacl.sign.keyPair.fromSeed(seed);",
          "const method = pm.request.method.toUpperCase();",
          "const path = pm.variables.replaceIn(pm.request.url.getPathWithQuery());",
          "let body = pm.request.body && pm.request.body.mode === 'raw' ? pm.variables.replaceIn(pm.request.body.raw || '') : '';",
          "if (pm.request.body && pm.request.body.mode === 'raw') pm.request.body.raw = body;",
          "const timestamp = String(Math.floor(Date.now() / 1000));",
          "const nonce = pm.variables.replaceIn('{{$guid}}').replace(/-/g, '').slice(0, 32);",
          "let idempotencyKey = method === 'POST' ? pm.variables.replaceIn(pm.request.headers.get('Idempotency-Key') || '') : '';",
          "if (method === 'POST' && !idempotencyKey) throw new Error('POST requires Idempotency-Key.');",
          "if (method === 'POST') pm.request.headers.upsert({ key: 'Idempotency-Key', value: idempotencyKey });",
          "const bodyHash = CryptoJS.SHA256(CryptoJS.enc.Utf8.parse(body)).toString(CryptoJS.enc.Hex);",
          "const canonical = [method, path, timestamp, nonce, idempotencyKey, bodyHash].join('\\n');",
          "const utf8 = Uint8Array.from(unescape(encodeURIComponent(canonical)), c => c.charCodeAt(0));",
          "const signature = nacl.sign.detached(utf8, keypair.secretKey);",
          "const signatureBase64 = btoa(String.fromCharCode(...signature));",
          "pm.request.headers.upsert({ key: 'X-Key-Id', value: keyId });",
          "pm.request.headers.upsert({ key: 'X-Timestamp', value: timestamp });",
          "pm.request.headers.upsert({ key: 'X-Nonce', value: nonce });",
          "pm.request.headers.upsert({ key: 'X-Signature', value: signatureBase64 });"
        ]
      }
    }
  ],
  "item": [
    {
      "name": "Capabilities",
      "request": {
        "method": "GET",
        "header": [{ "key": "Accept", "value": "application/json" }],
        "url": "{{baseUrl}}/v1/capabilities",
        "description": "Verify mTLS plus Ed25519 authentication and inspect immutable environment capabilities."
      },
      "event": [
        {
          "listen": "test",
          "script": {
            "exec": [
              "pm.test('200 OK', () => pm.response.to.have.status(200));",
              "pm.test('arbitrary signing is disabled', () => pm.expect(pm.response.json().arbitrary_signing_supported).to.eql(false));"
            ]
          }
        }
      ]
    },
    {
      "name": "Create deposit order",
      "request": {
        "method": "POST",
        "header": [
          { "key": "Accept", "value": "application/json" },
          { "key": "Content-Type", "value": "application/json" },
          { "key": "Idempotency-Key", "value": "{{orderIdempotencyKey}}" }
        ],
        "body": {
          "mode": "raw",
          "raw": "{\"order_ref\":\"{{orderRef}}\",\"expected_amount\":\"{{orderAmount}}\",\"metadata\":{\"source\":\"postman\"}}",
          "options": { "raw": { "language": "json" } }
        },
        "url": "{{baseUrl}}/v1/orders",
        "description": "Allocates one one-time USDT-TRC20 deposit address. Exact replay returns 200; a new order returns 201."
      },
      "event": [
        {
          "listen": "test",
          "script": {
            "exec": [
              "pm.test('created or exact replay', () => pm.expect([200, 201]).to.include(pm.response.code));",
              "pm.test('deposit address returned', () => pm.expect(pm.response.json().deposit_address).to.be.a('string').and.not.empty);"
            ]
          }
        }
      ]
    },
    {
      "name": "Query deposit order",
      "request": {
        "method": "GET",
        "header": [{ "key": "Accept", "value": "application/json" }],
        "url": "{{baseUrl}}/v1/orders/{{orderRef}}",
        "description": "Authoritative recovery query for deposit status."
      },
      "event": [
        {
          "listen": "test",
          "script": {
            "exec": [
              "pm.test('200 OK', () => pm.response.to.have.status(200));",
              "pm.test('reference matches', () => pm.expect(pm.response.json().order_ref).to.eql(pm.variables.get('orderRef')));"
            ]
          }
        }
      ]
    },
    {
      "name": "Create withdrawal intent",
      "request": {
        "method": "POST",
        "header": [
          { "key": "Accept", "value": "application/json" },
          { "key": "Content-Type", "value": "application/json" },
          { "key": "Idempotency-Key", "value": "{{withdrawalIdempotencyKey}}" }
        ],
        "body": {
          "mode": "raw",
          "raw": "{\"withdrawal_ref\":\"{{withdrawalRef}}\",\"merchant_user_ref\":\"{{merchantUserRef}}\",\"destination_address\":\"{{withdrawalDestination}}\",\"amount\":\"{{withdrawalAmount}}\",\"metadata\":{\"source\":\"postman\"}}",
          "options": { "raw": { "language": "json" } }
        },
        "url": "{{baseUrl}}/v1/withdrawals",
        "description": "Records an intent only. Signing and broadcasting are asynchronous and policy-controlled."
      },
      "event": [
        {
          "listen": "test",
          "script": {
            "exec": [
              "pm.test('accepted or exact replay', () => pm.expect([200, 202]).to.include(pm.response.code));",
              "pm.test('reference matches', () => pm.expect(pm.response.json().withdrawal_ref).to.eql(pm.variables.get('withdrawalRef')));"
            ]
          }
        }
      ]
    },
    {
      "name": "Query withdrawal",
      "request": {
        "method": "GET",
        "header": [{ "key": "Accept", "value": "application/json" }],
        "url": "{{baseUrl}}/v1/withdrawals/{{withdrawalRef}}",
        "description": "Authoritative recovery query for withdrawal lifecycle and TRON transaction ID."
      },
      "event": [
        {
          "listen": "test",
          "script": {
            "exec": [
              "pm.test('200 OK', () => pm.response.to.have.status(200));",
              "pm.test('status present', () => pm.expect(pm.response.json().status).to.be.a('string').and.not.empty);"
            ]
          }
        }
      ]
    }
  ],
  "variable": [
    {
      "key": "baseUrl",
      "value": "https://<merchant-api-host>",
      "type": "string"
    },
    { "key": "orderRef", "value": "ORDER-POSTMAN-0001", "type": "string" },
    { "key": "orderAmount", "value": "25.500000", "type": "string" },
    {
      "key": "orderIdempotencyKey",
      "value": "idem-order-postman-0001",
      "type": "string"
    },
    { "key": "withdrawalRef", "value": "WD-POSTMAN-0001", "type": "string" },
    { "key": "merchantUserRef", "value": "member-postman-0001", "type": "string" },
    {
      "key": "withdrawalDestination",
      "value": "",
      "type": "string"
    },
    { "key": "withdrawalAmount", "value": "10.000000", "type": "string" },
    {
      "key": "withdrawalIdempotencyKey",
      "value": "idem-withdrawal-postman-0001",
      "type": "string"
    }
  ]
}
