{
  "info": {
    "_postman_id": "7d9d7d4e-4a75-4b0b-9f80-b77f4bd26a3d",
    "name": "IIMMPACT API - API Key HMAC Auth",
    "description": "Import this collection with an IIMMPACT environment file. The collection-level pre-request script signs every request with API Key + HMAC-SHA256 authentication.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "event": [
    {
      "listen": "prerequest",
      "script": {
        "type": "text/javascript",
        "exec": [
          "const cryptoJs = require(\"crypto-js\");",
          "const apiKey = pm.environment.get(\"apiKey\") || pm.collectionVariables.get(\"apiKey\");",
          "const hmacSecret = pm.environment.get(\"hmacSecret\") || pm.collectionVariables.get(\"hmacSecret\");",
          "",
          "if (!apiKey || apiKey.includes(\"YOUR_\")) {",
          "  throw new Error(\"Set apiKey in your active Postman environment.\");",
          "}",
          "",
          "if (!hmacSecret || hmacSecret.includes(\"YOUR_\")) {",
          "  throw new Error(\"Set hmacSecret in your active Postman environment.\");",
          "}",
          "",
          "const timestamp = Math.floor(Date.now() / 1000).toString();",
          "const nonce = `req-${timestamp}-${pm.variables.replaceIn(\"{{$randomUUID}}\").toLowerCase()}`;",
          "const method = pm.request.method.toUpperCase();",
          "",
          "let body = \"\";",
          "if (pm.request.body) {",
          "  if (pm.request.body.mode === \"raw\") {",
          "    body = pm.variables.replaceIn(pm.request.body.raw || \"\");",
          "  } else if (pm.request.body.mode && pm.request.body.mode !== \"raw\") {",
          "    throw new Error(`IIMMPACT HMAC signing only supports raw JSON bodies in this collection. Unsupported mode: ${pm.request.body.mode}`);",
          "  }",
          "}",
          "",
          "const bodyHash = cryptoJs.enc.Base64.stringify(cryptoJs.SHA256(body));",
          "const queryParams = pm.request.url.query ? pm.request.url.query.all() : [];",
          "const sortedQuery = queryParams",
          "  .filter((param) => !param.disabled && param.key && typeof param.value !== \"undefined\")",
          "  .map((param) => ({",
          "    key: pm.variables.replaceIn(param.key),",
          "    value: pm.variables.replaceIn(param.value || \"\"),",
          "  }))",
          "  .sort((left, right) => left.key.localeCompare(right.key))",
          "  .map((param) => `${param.key}=${param.value}`)",
          "  .join(\"&\");",
          "",
          "const canonical = `v1:${timestamp}:${nonce}:${method}:${sortedQuery}:${bodyHash}`;",
          "const secretBytes = cryptoJs.enc.Base64.parse(hmacSecret);",
          "const signature = cryptoJs.enc.Base64.stringify(cryptoJs.HmacSHA256(canonical, secretBytes));",
          "",
          "pm.request.headers.upsert({ key: \"X-Api-Key\", value: apiKey });",
          "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: `v1=${signature}` });",
          "",
          "if (body) {",
          "  pm.request.headers.upsert({ key: \"Content-Type\", value: \"application/json\" });",
          "}",
          "",
          "console.log(\"IIMMPACT HMAC canonical string\", canonical);"
        ]
      }
    }
  ],
  "variable": [
    {
      "key": "baseUrl",
      "value": "https://staging.iimmpact.com",
      "type": "string"
    },
    {
      "key": "billProduct",
      "value": "TNB",
      "type": "string"
    },
    {
      "key": "billAccount",
      "value": "220108271001",
      "type": "string"
    },
    {
      "key": "billBillerCode",
      "value": "818625",
      "type": "string"
    },
    {
      "key": "billRef2",
      "value": "",
      "type": "string"
    },
    {
      "key": "billAmount",
      "value": "1.00",
      "type": "string"
    },
    {
      "key": "networkProduct",
      "value": "TNB",
      "type": "string"
    },
    {
      "key": "productCode",
      "value": "D",
      "type": "string"
    },
    {
      "key": "subproductProductCode",
      "value": "JOMPAY",
      "type": "string"
    },
    {
      "key": "subproductAccount",
      "value": "",
      "type": "string"
    },
    {
      "key": "transactionDate",
      "value": "2026-04-30",
      "type": "string"
    },
    {
      "key": "balanceStatementDate",
      "value": "2026-04-30",
      "type": "string"
    },
    {
      "key": "topupAccount",
      "value": "0123456789",
      "type": "string"
    },
    {
      "key": "topupAmount",
      "value": "5.00",
      "type": "string"
    },
    {
      "key": "jompayAccount",
      "value": "1234567890",
      "type": "string"
    },
    {
      "key": "jompayAmount",
      "value": "150.00",
      "type": "string"
    },
    {
      "key": "jompayBillerCode",
      "value": "818625",
      "type": "string"
    },
    {
      "key": "jompayIcNumber",
      "value": "941123045001",
      "type": "string"
    },
    {
      "key": "jompayRef2",
      "value": "",
      "type": "string"
    },
    {
      "key": "ptptnAccount",
      "value": "1234567890",
      "type": "string"
    },
    {
      "key": "ptptnAmount",
      "value": "50.00",
      "type": "string"
    },
    {
      "key": "ptptnSubproductCode",
      "value": "1234567890",
      "type": "string"
    },
    {
      "key": "ptptnIcNumber",
      "value": "941123045001",
      "type": "string"
    },
    {
      "key": "mobileDataAccount",
      "value": "0178855286",
      "type": "string"
    },
    {
      "key": "mobileDataAmount",
      "value": "40.00",
      "type": "string"
    },
    {
      "key": "mobileDataSubproductCode",
      "value": "Unlimited data with hotspot and calls 30-days (3Mbps) H",
      "type": "string"
    }
  ],
  "item": [
    {
      "name": "Account",
      "item": [
        {
          "name": "Get Balance",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"request succeeded\", () => {",
                  "  pm.expect(pm.response.code).to.be.oneOf([200]);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v2/balance",
              "host": ["{{baseUrl}}"],
              "path": ["v2", "balance"]
            },
            "description": "Checks the authenticated account balance. This is the safest request to test API-key authentication."
          },
          "response": []
        },
        {
          "name": "Get Balance Statement",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v2/balance-statement?date={{balanceStatementDate}}&limit=20",
              "host": ["{{baseUrl}}"],
              "path": ["v2", "balance-statement"],
              "query": [
                {
                  "key": "date",
                  "value": "{{balanceStatementDate}}"
                },
                {
                  "key": "limit",
                  "value": "20"
                }
              ]
            },
            "description": "Returns running balance entries for a single day. Update the date variable if you want a different range."
          },
          "response": []
        }
      ]
    },
    {
      "name": "Catalog and Products",
      "item": [
        {
          "name": "Get Product Catalog",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v2/catalog",
              "host": ["{{baseUrl}}"],
              "path": ["v2", "catalog"]
            },
            "description": "Returns the dynamic product catalog."
          },
          "response": []
        },
        {
          "name": "Get Options",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v2/options?product_code={{productCode}}&field_id=amount",
              "host": ["{{baseUrl}}"],
              "path": ["v2", "options"],
              "query": [
                {
                  "key": "product_code",
                  "value": "{{productCode}}"
                },
                {
                  "key": "field_id",
                  "value": "amount"
                }
              ]
            },
            "description": "Example request with query parameters. The pre-request script sorts query parameters before signing."
          },
          "response": []
        },
        {
          "name": "Get Product Listing (Deprecated)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v2/product-list",
              "host": ["{{baseUrl}}"],
              "path": ["v2", "product-list"]
            },
            "description": "Legacy product listing endpoint. Kept here because many integrators still use it."
          },
          "response": []
        },
        {
          "name": "Get Sub Products (Deprecated)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v2/subproducts?product_code={{subproductProductCode}}&limit=5",
              "host": ["{{baseUrl}}"],
              "path": ["v2", "subproducts"],
              "query": [
                {
                  "key": "product_code",
                  "value": "{{subproductProductCode}}"
                },
                {
                  "key": "limit",
                  "value": "5"
                },
                {
                  "key": "account_number",
                  "value": "{{subproductAccount}}",
                  "disabled": true
                }
              ]
            },
            "description": "Legacy subproducts endpoint. Default example uses JOMPAY billers; enable account_number only for products that need it."
          },
          "response": []
        },
        {
          "name": "Get Network Status",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v2/networkstatus?product={{networkProduct}}",
              "host": ["{{baseUrl}}"],
              "path": ["v2", "networkstatus"],
              "query": [
                {
                  "key": "product",
                  "value": "{{networkProduct}}"
                }
              ]
            },
            "description": "Checks service health before payment."
          },
          "response": []
        },
        {
          "name": "Bill Presentment - TNB",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"bill presentment request succeeded\", () => {",
                  "  pm.expect(pm.response.code).to.be.oneOf([200]);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v2/bill-presentment?product={{billProduct}}&account={{billAccount}}",
              "host": ["{{baseUrl}}"],
              "path": ["v2", "bill-presentment"],
              "query": [
                {
                  "key": "product",
                  "value": "{{billProduct}}"
                },
                {
                  "key": "account",
                  "value": "{{billAccount}}"
                }
              ]
            },
            "description": "Ready-to-run bill presentment example using the TNB account values that were validated during testing."
          },
          "response": []
        },
        {
          "name": "Bill Presentment - JomPAY Example",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v2/bill-presentment?product=JOMPAY&account={{billAccount}}&biller_code={{billBillerCode}}&amount={{billAmount}}",
              "host": ["{{baseUrl}}"],
              "path": ["v2", "bill-presentment"],
              "query": [
                {
                  "key": "product",
                  "value": "JOMPAY"
                },
                {
                  "key": "account",
                  "value": "{{billAccount}}"
                },
                {
                  "key": "biller_code",
                  "value": "{{billBillerCode}}"
                },
                {
                  "key": "amount",
                  "value": "{{billAmount}}"
                },
                {
                  "key": "ref2",
                  "value": "{{billRef2}}",
                  "disabled": true
                }
              ]
            },
            "description": "JomPAY bill presentment example. Enable ref2 when the biller requires it."
          },
          "response": []
        }
      ]
    },
    {
      "name": "Transactions",
      "item": [
        {
          "name": "Get Transaction History",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v2/transactions?limit=20&date={{transactionDate}}",
              "host": ["{{baseUrl}}"],
              "path": ["v2", "transactions"],
              "query": [
                {
                  "key": "limit",
                  "value": "20"
                },
                {
                  "key": "date",
                  "value": "{{transactionDate}}"
                }
              ]
            },
            "description": "Returns recent transaction history for a given day."
          },
          "response": []
        },
        {
          "name": "Topup Example",
          "request": {
            "method": "POST",
            "header": [],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"refid\": \"postman-{{$timestamp}}\",\n  \"product\": \"D\",\n  \"account\": \"{{topupAccount}}\",\n  \"amount\": \"{{topupAmount}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/v2/topup",
              "host": ["{{baseUrl}}"],
              "path": ["v2", "topup"]
            },
            "description": "Basic signed topup example for products that do not need extras. Review the values before sending because topup requests can create real transactions."
          },
          "response": []
        },
        {
          "name": "Topup Example - JomPAY",
          "request": {
            "method": "POST",
            "header": [],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"refid\": \"postman-jompay-{{$timestamp}}\",\n  \"product\": \"JOMPAY\",\n  \"account\": \"{{jompayAccount}}\",\n  \"amount\": \"{{jompayAmount}}\",\n  \"extras\": {\n    \"biller_code\": \"{{jompayBillerCode}}\",\n    \"ic_number\": \"{{jompayIcNumber}}\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/v2/topup",
              "host": ["{{baseUrl}}"],
              "path": ["v2", "topup"]
            },
            "description": "JomPAY topup example with extras.biller_code and extras.ic_number. Add extras.ref2 when the biller requires it."
          },
          "response": []
        },
        {
          "name": "Topup Example - JomPAY With Ref2",
          "request": {
            "method": "POST",
            "header": [],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"refid\": \"postman-jompay-ref2-{{$timestamp}}\",\n  \"product\": \"JOMPAY\",\n  \"account\": \"{{jompayAccount}}\",\n  \"amount\": \"{{jompayAmount}}\",\n  \"extras\": {\n    \"biller_code\": \"{{jompayBillerCode}}\",\n    \"ic_number\": \"{{jompayIcNumber}}\",\n    \"ref2\": \"{{jompayRef2}}\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/v2/topup",
              "host": ["{{baseUrl}}"],
              "path": ["v2", "topup"]
            },
            "description": "Use this variant when the JomPAY biller requires ref2 in addition to biller_code and ic_number."
          },
          "response": []
        },
        {
          "name": "Topup Example - PTPTN",
          "request": {
            "method": "POST",
            "header": [],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"refid\": \"postman-ptptn-{{$timestamp}}\",\n  \"product\": \"PTPTN\",\n  \"account\": \"{{ptptnAccount}}\",\n  \"amount\": \"{{ptptnAmount}}\",\n  \"extras\": {\n    \"subproduct_code\": \"{{ptptnSubproductCode}}\",\n    \"ic_number\": \"{{ptptnIcNumber}}\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/v2/topup",
              "host": ["{{baseUrl}}"],
              "path": ["v2", "topup"]
            },
            "description": "PTPTN topup example with extras.subproduct_code and extras.ic_number."
          },
          "response": []
        },
        {
          "name": "Topup Example - Mobile Data",
          "request": {
            "method": "POST",
            "header": [],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"refid\": \"postman-mobile-data-{{$timestamp}}\",\n  \"product\": \"HI\",\n  \"account\": \"{{mobileDataAccount}}\",\n  \"amount\": \"{{mobileDataAmount}}\",\n  \"extras\": {\n    \"subproduct_code\": \"{{mobileDataSubproductCode}}\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/v2/topup",
              "host": ["{{baseUrl}}"],
              "path": ["v2", "topup"]
            },
            "description": "Mobile data topup example with extras.subproduct_code for internet plans."
          },
          "response": []
        }
      ]
    }
  ]
}
