{
  "info": {
    "_postman_id": "c6f0af86-e966-5b98-9963-52e5f38b2731",
    "name": "ZenithPay Master Collection — PWT + DVA",
    "description": "One merchant collection for the currently reviewed legacy PWT and Dedicated Virtual Account APIs. Import the included empty private environment and select it. Set your existing Developer API token; web login is session-based and does not issue this token. No credentials, real BVNs, real account numbers or customer records are included. Use current Postman with pm.execution.skipRequest support. Choose requests individually; do not run the entire collection blindly against production. DVA assignment and PWT initialization can create real provider accounts; status queries can contact the provider; manual callback retry sends a real notification. Examples are illustrative and not proof of a sandbox or fixed fee schedule. Settlement follows the existing D+1 next-working-day 06:00 Africa/Lagos policy.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
    "version": "1.0.0"
  },
  "auth": {
    "type": "bearer",
    "bearer": [
      {
        "key": "token",
        "value": "{{apiToken}}",
        "type": "string"
      }
    ]
  },
  "protocolProfileBehavior": {
    "followRedirects": false
  },
  "item": [
    {
      "name": "1. Dedicated Virtual Accounts (DVA)",
      "description": "Reusable customer account assignment, required BVN and business approval, merchant callback configuration, per-payment deduplication. Read the DVA sections of the guide before assigning an account.",
      "item": [
        {
          "name": "Assign Dedicated Virtual Account",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/dedicated_account/assign",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "dedicated_account",
                "assign"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"bvn\": \"{{dvaBVN}}\",\n  \"account_name\": \"{{dvaAccountName}}\",\n  \"first_name\": \"{{dvaFirstName}}\",\n  \"last_name\": \"{{dvaLastName}}\",\n  \"email\": \"{{dvaEmail}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "description": "Creates a customer DVA using the approved merchant's existing Developer bearer token. JSON and form-urlencoded are accepted by the reviewed Laravel request; this request uses JSON. Required fields: bvn (11-digit string), account_name, first_name, last_name, email. Ownership comes from authentication: do not send user_id or authenticated_user_id. Merchant account must be active, business approved, and business name present. The current BVN validation also rejects a BVN found in users.bvn. Creation is not a payment and does not credit a wallet. Save returned account fields; the response can also contain private identity data. Callback URL is configured for the merchant, not passed in this request. No DVA initialization idempotency guarantee is documented: investigate an uncertain outcome before resending. A JSON error can be returned under HTTP 200 by legacy checks; inspect status and response_code."
          },
          "protocolProfileBehavior": {
            "followRedirects": false
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "(() => {",
                  "function stop(message) { console.warn('Request not sent: ' + message); pm.execution.skipRequest(); throw new Error(message); }",
                  "function value(name) { const v = pm.environment.get(name); return v == null ? '' : String(v); }",
                  "function needed(name) { const v = value(name); if (!v.trim() || v.includes('{{')) stop('Set ' + name + ' in the selected private environment.'); return v; }",
                  "const base = needed('baseUrl').replace(/\\/$/, '');",
                  "if (base !== 'https://zenithpay.ng') stop('This collection documents https://zenithpay.ng. Confirm a different environment separately.');",
                  "const token = needed('apiToken');",
                  "if (/YOUR_|REPLACE_|EXAMPLE_/i.test(token)) stop('Replace the API-token placeholder privately.');",
                  "// Bind the inspected environment value directly; other variable scopes cannot change this destination or token.",
                  "pm.request.url.update(base + '/api/dedicated_account/assign');",
                  "pm.request.headers.upsert({key: 'Authorization', value: 'Bearer ' + token});",
                  "pm.request.headers.upsert({key: 'Accept', value: 'application/json'});",
                  "const bvn = needed('dvaBVN');",
                  "if (!/^[0-9]{11}$/.test(bvn)) stop('dvaBVN must be an 11-digit string belonging to the customer.');",
                  "const body = {bvn: bvn, account_name: needed('dvaAccountName'), first_name: needed('dvaFirstName'), last_name: needed('dvaLastName'), email: needed('dvaEmail')};",
                  "if (!/^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$/.test(body.email) || body.email.endsWith('.invalid')) stop('Set the actual customer email privately.');",
                  "for (const key of ['dvaAccountReference','dvaAccountNumber','dvaReturnedAccountName']) pm.environment.unset(key);",
                  "pm.request.body.update({mode:'raw',raw:JSON.stringify(body, null, 2),options:{raw:{language:'json'}}});",
                  "})();"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "(() => {",
                  "let result;",
                  "pm.test('Response body is a nonempty JSON object', function () {",
                  "  const text = pm.response.text();",
                  "  if (!text.trim()) throw new Error('Empty response: no success or account creation can be inferred.');",
                  "  result = pm.response.json();",
                  "  if (!result || typeof result !== 'object' || Array.isArray(result)) throw new Error('Expected a JSON object.');",
                  "});",
                  "if (!result || typeof result !== 'object' || Array.isArray(result)) return;",
                  "let accepted = false;",
                  "pm.test('DVA assignment actually succeeded (HTTP 200, status true, code 00)', function () {",
                  "  if (pm.response.code !== 200 || result.status !== true || result.response_code !== '00') throw new Error('DVA assignment was not confirmed. Inspect the response message/reason/errors.');",
                  "  for (const key of ['accountNumber','accountName','bankName']) if (typeof result[key] !== 'string' || !result[key]) throw new Error('Missing returned ' + key);",
                  "  accepted = true;",
                  "});",
                  "if (accepted) {",
                  "  pm.environment.set('dvaAccountReference', typeof result.accountReference === 'string' ? result.accountReference : '');",
                  "  pm.environment.set('dvaAccountNumber', result.accountNumber);",
                  "  pm.environment.set('dvaReturnedAccountName', result.accountName);",
                  "}",
                  "})();"
                ]
              }
            }
          ],
          "response": [
            {
              "name": "Illustrative success subset; identity values withheld",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/dedicated_account/assign",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "dedicated_account",
                    "assign"
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"bvn\": \"{{dvaBVN}}\",\n  \"account_name\": \"{{dvaAccountName}}\",\n  \"first_name\": \"{{dvaFirstName}}\",\n  \"last_name\": \"{{dvaLastName}}\",\n  \"email\": \"{{dvaEmail}}\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                },
                "description": "Creates a customer DVA using the approved merchant's existing Developer bearer token. JSON and form-urlencoded are accepted by the reviewed Laravel request; this request uses JSON. Required fields: bvn (11-digit string), account_name, first_name, last_name, email. Ownership comes from authentication: do not send user_id or authenticated_user_id. Merchant account must be active, business approved, and business name present. The current BVN validation also rejects a BVN found in users.bvn. Creation is not a payment and does not credit a wallet. Save returned account fields; the response can also contain private identity data. Callback URL is configured for the merchant, not passed in this request. No DVA initialization idempotency guarantee is documented: investigate an uncertain outcome before resending. A JSON error can be returned under HTTP 200 by legacy checks; inspect status and response_code."
              },
              "status": "OK",
              "code": 200,
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"status\": true,\n  \"response_code\": \"00\",\n  \"accountReference\": \"EXAMPLE_DVA_ACCOUNT_REFERENCE\",\n  \"accountName\": \"ABBA DATABUY (ZenithPay)\",\n  \"customerEmail\": \"customer@example.invalid\",\n  \"customerName\": \"EXAMPLE_CUSTOMER_NAME\",\n  \"bankName\": \"PALMPAY\",\n  \"accountNumber\": \"EXAMPLE_ACCOUNT_NUMBER\",\n  \"identityType\": \"bvn\",\n  \"accountStatus\": \"PROVIDER_RETURNED_STATUS\"\n}"
            },
            {
              "name": "Illustrative validation failure",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/dedicated_account/assign",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "dedicated_account",
                    "assign"
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"bvn\": \"{{dvaBVN}}\",\n  \"account_name\": \"{{dvaAccountName}}\",\n  \"first_name\": \"{{dvaFirstName}}\",\n  \"last_name\": \"{{dvaLastName}}\",\n  \"email\": \"{{dvaEmail}}\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                },
                "description": "Creates a customer DVA using the approved merchant's existing Developer bearer token. JSON and form-urlencoded are accepted by the reviewed Laravel request; this request uses JSON. Required fields: bvn (11-digit string), account_name, first_name, last_name, email. Ownership comes from authentication: do not send user_id or authenticated_user_id. Merchant account must be active, business approved, and business name present. The current BVN validation also rejects a BVN found in users.bvn. Creation is not a payment and does not credit a wallet. Save returned account fields; the response can also contain private identity data. Callback URL is configured for the merchant, not passed in this request. No DVA initialization idempotency guarantee is documented: investigate an uncertain outcome before resending. A JSON error can be returned under HTTP 200 by legacy checks; inspect status and response_code."
              },
              "status": "Validation error",
              "code": 422,
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Validation failed.\",\n  \"errors\": {\n    \"bvn\": [\n      \"The bvn field is required.\"\n    ]\n  }\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "2. Pay With Transfer (PWT)",
      "description": "Payment-specific initialization, owned-order status and callback diagnostics. Use one stable initialization key/body.",
      "item": [
        {
          "name": "Initialize Pay With Transfer",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{pwtIdempotencyKey}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/transaction/initialize",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "transaction",
                "initialize"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"amount\": \"{{pwtAmount}}\",\n  \"email\": \"{{pwtEmail}}\",\n  \"callback_url\": \"{{pwtCallbackUrl}}\",\n  \"fee_bearer\": \"{{pwtFeeBearer}}\",\n  \"meta_data\": {\n    \"customer_id\": \"{{pwtCustomerId}}\",\n    \"order_id\": \"{{pwtMerchantOrderId}}\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "description": "Initializes a payment-specific transfer account. amount is legacy NAIRA, not kobo. No customer BVN is required for this request. Use returned amounts, fees, bank/account details, currency and expiry; this response does not prove payment. The script retains one idempotency key and checks that the same key reuses the same body. It does not send extra requests or automatically retry. Keep the original key/body when the provider outcome is uncertain. Intentionally clear the key only for a genuinely new payment. Customer/order metadata is private; do not export a filled environment. The displayed sample is an illustrative subset, not a universal fee quote."
          },
          "protocolProfileBehavior": {
            "followRedirects": false
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "(() => {",
                  "function stop(message) { console.warn('Request not sent: ' + message); pm.execution.skipRequest(); throw new Error(message); }",
                  "function value(name) { const v = pm.environment.get(name); return v == null ? '' : String(v); }",
                  "function needed(name) { const v = value(name); if (!v.trim() || v.includes('{{')) stop('Set ' + name + ' in the selected private environment.'); return v; }",
                  "const base = needed('baseUrl').replace(/\\/$/, '');",
                  "if (base !== 'https://zenithpay.ng') stop('This collection documents https://zenithpay.ng. Confirm a different environment separately.');",
                  "const token = needed('apiToken');",
                  "if (/YOUR_|REPLACE_|EXAMPLE_/i.test(token)) stop('Replace the API-token placeholder privately.');",
                  "// Bind the inspected environment value directly; other variable scopes cannot change this destination or token.",
                  "pm.request.url.update(base + '/api/transaction/initialize');",
                  "pm.request.headers.upsert({key: 'Authorization', value: 'Bearer ' + token});",
                  "pm.request.headers.upsert({key: 'Accept', value: 'application/json'});",
                  "const amountText = needed('pwtAmount');",
                  "if (!/^[0-9]+(?:\\.[0-9]{1,2})?$/.test(amountText) || Number(amountText) <= 0 || !Number.isFinite(Number(amountText)) || !Number.isSafeInteger(Math.round(Number(amountText) * 100))) stop('pwtAmount is a positive naira amount, with at most two decimal places.');",
                  "const email = needed('pwtEmail');",
                  "if (!/^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$/.test(email) || email.endsWith('.invalid')) stop('Set the actual customer email privately.');",
                  "const callback = needed('pwtCallbackUrl');",
                  "if (!/^https:\\/\\/[^\\s]+$/.test(callback) || callback.includes('#')) stop('Use your public HTTPS callback endpoint without a fragment. ZenithPay enforces its full destination policy.');",
                  "const feeBearer = needed('pwtFeeBearer');",
                  "if (!['customer','merchant'].includes(feeBearer)) stop('pwtFeeBearer must be customer or merchant.');",
                  "const body = {amount: Number(amountText), email: email, callback_url: callback, fee_bearer: feeBearer};",
                  "const meta = {};",
                  "if (value('pwtCustomerId')) meta.customer_id = value('pwtCustomerId');",
                  "if (value('pwtMerchantOrderId')) meta.order_id = value('pwtMerchantOrderId');",
                  "if (Object.keys(meta).length) body.meta_data = meta;",
                  "let key = value('pwtIdempotencyKey');",
                  "if (!key) key = pm.variables.replaceIn('{{$guid}}');",
                  "if (!key || key.includes('{{') || key.length > 120 || /[\\x00-\\x20\\x7f]/.test(key)) stop('Use a stable, nonempty idempotency key of at most 120 characters.');",
                  "const serialized = JSON.stringify(body);",
                  "const bindingText = value('pwtKeyBinding');",
                  "let binding = null;",
                  "if (bindingText) { try { binding = JSON.parse(bindingText); } catch (_) { stop('pwtKeyBinding is invalid. Preserve the original key/body and review before retrying.'); } }",
                  "if (binding && binding.key === key && binding.body !== serialized) stop('The initialization body changed under the same key. Restore it; do not bypass an uncertain payment.');",
                  "if (!binding || binding.key !== key) for (const name of ['pwtOrderNo','pwtOrderId','pwtCheckoutLink','pwtStatusUrl']) pm.environment.unset(name);",
                  "pm.environment.set('pwtIdempotencyKey', key);",
                  "pm.environment.set('pwtKeyBinding', JSON.stringify({key:key,body:serialized}));",
                  "pm.request.headers.upsert({key:'Idempotency-Key',value:key});",
                  "pm.request.body.update({mode:'raw',raw:JSON.stringify(body, null, 2),options:{raw:{language:'json'}}});",
                  "})();"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "(() => {",
                  "let result;",
                  "pm.test('Response body is a nonempty JSON object', function () {",
                  "  const text = pm.response.text();",
                  "  if (!text.trim()) throw new Error('Empty response: no success or account creation can be inferred.');",
                  "  result = pm.response.json();",
                  "  if (!result || typeof result !== 'object' || Array.isArray(result)) throw new Error('Expected a JSON object.');",
                  "});",
                  "if (!result || typeof result !== 'object' || Array.isArray(result)) return;",
                  "let accepted = false;",
                  "pm.test('PWT initialization actually succeeded (HTTP 200 and status true)', function () {",
                  "  if (pm.response.code !== 200 || result.status !== true) throw new Error('Initialization was not confirmed. Preserve the key/body for conflict, in-flight or uncertain outcomes.');",
                  "  for (const key of ['orderNo','checkout_link','status_url']) if (typeof result[key] !== 'string' || !result[key]) throw new Error('Missing returned ' + key);",
                  "  accepted = true;",
                  "});",
                  "if (accepted) {",
                  "  pm.environment.set('pwtOrderNo', result.orderNo);",
                  "  pm.environment.set('pwtCheckoutLink', result.checkout_link);",
                  "  pm.environment.set('pwtStatusUrl', result.status_url);",
                  "  const id = result.data && result.data.transaction_details && result.data.transaction_details.order_id;",
                  "  pm.environment.set('pwtOrderId', typeof id === 'string' ? id : '');",
                  "}",
                  "})();"
                ]
              }
            }
          ],
          "response": [
            {
              "name": "Illustrative success subset",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/transaction/initialize",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "transaction",
                    "initialize"
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"amount\": \"{{pwtAmount}}\",\n  \"email\": \"{{pwtEmail}}\",\n  \"callback_url\": \"{{pwtCallbackUrl}}\",\n  \"fee_bearer\": \"{{pwtFeeBearer}}\",\n  \"meta_data\": {\n    \"customer_id\": \"{{pwtCustomerId}}\",\n    \"order_id\": \"{{pwtMerchantOrderId}}\"\n  }\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                },
                "description": "Initializes a payment-specific transfer account. amount is legacy NAIRA, not kobo. No customer BVN is required for this request. Use returned amounts, fees, bank/account details, currency and expiry; this response does not prove payment. The script retains one idempotency key and checks that the same key reuses the same body. It does not send extra requests or automatically retry. Keep the original key/body when the provider outcome is uncertain. Intentionally clear the key only for a genuinely new payment. Customer/order metadata is private; do not export a filled environment. The displayed sample is an illustrative subset, not a universal fee quote."
              },
              "status": "OK",
              "code": 200,
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"status\": true,\n  \"message\": \"Payment initialized successfully.\",\n  \"orderNo\": \"EXAMPLE_PROVIDER_ORDER_NUMBER\",\n  \"checkout_link\": \"https://zenithpay.ng/checkout/EXAMPLE_PROVIDER_ORDER_NUMBER\",\n  \"status_url\": \"https://zenithpay.ng/checkout/status/EXAMPLE_PROVIDER_ORDER_NUMBER\",\n  \"data\": {\n    \"currency\": \"NGN\",\n    \"account_details\": {\n      \"bank_name\": \"PROVIDER_RETURNED_BANK\",\n      \"account_number\": \"EXAMPLE_ACCOUNT_NUMBER\"\n    },\n    \"transaction_details\": {\n      \"order_id\": \"EXAMPLE_PWT_ORDER_ID\",\n      \"payment_status\": \"pending\"\n    }\n  }\n}"
            }
          ]
        },
        {
          "name": "Query PWT Payment Status",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/transaction/status",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "transaction",
                "status"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"order_no\": \"{{pwtOrderNo}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "description": "Queries the authenticated merchant's PWT order using order_no, or order_id when order_no is empty. This request can contact the provider and update local collection status; it is not a settlement or wallet-balance query. Common states: pending, success, failed, expired. Confirmed success is preserved against stale downgrades; legitimate verified late success remains possible. Returned status is collection status, not proof of settled availability. Respect the deployed rate limit (reviewed route: 30 requests/minute)."
          },
          "protocolProfileBehavior": {
            "followRedirects": false
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "(() => {",
                  "function stop(message) { console.warn('Request not sent: ' + message); pm.execution.skipRequest(); throw new Error(message); }",
                  "function value(name) { const v = pm.environment.get(name); return v == null ? '' : String(v); }",
                  "function needed(name) { const v = value(name); if (!v.trim() || v.includes('{{')) stop('Set ' + name + ' in the selected private environment.'); return v; }",
                  "const base = needed('baseUrl').replace(/\\/$/, '');",
                  "if (base !== 'https://zenithpay.ng') stop('This collection documents https://zenithpay.ng. Confirm a different environment separately.');",
                  "const token = needed('apiToken');",
                  "if (/YOUR_|REPLACE_|EXAMPLE_/i.test(token)) stop('Replace the API-token placeholder privately.');",
                  "// Bind the inspected environment value directly; other variable scopes cannot change this destination or token.",
                  "pm.request.url.update(base + '/api/transaction/status');",
                  "pm.request.headers.upsert({key: 'Authorization', value: 'Bearer ' + token});",
                  "pm.request.headers.upsert({key: 'Accept', value: 'application/json'});",
                  "const orderNo = value('pwtOrderNo');",
                  "const orderId = value('pwtOrderId');",
                  "if (!orderNo && !orderId) stop('Set pwtOrderNo or pwtOrderId for your merchant-owned order.');",
                  "pm.request.body.update({mode:'raw',raw:JSON.stringify(orderNo ? {order_no:orderNo} : {order_id:orderId}, null, 2),options:{raw:{language:'json'}}});",
                  "})();"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "(() => {",
                  "let result;",
                  "pm.test('Response body is a nonempty JSON object', function () {",
                  "  const text = pm.response.text();",
                  "  if (!text.trim()) throw new Error('Empty response: no success or account creation can be inferred.');",
                  "  result = pm.response.json();",
                  "  if (!result || typeof result !== 'object' || Array.isArray(result)) throw new Error('Expected a JSON object.');",
                  "});",
                  "if (!result || typeof result !== 'object' || Array.isArray(result)) return;",
                  "pm.test('Status query succeeded', function () {",
                  "  if (pm.response.code !== 200 || result.status !== true || !result.data || !result.data.order_no || typeof result.data.status !== 'string') throw new Error('No confirmed PWT order-status response.');",
                  "});",
                  "})();"
                ]
              }
            }
          ],
          "response": [
            {
              "name": "Illustrative successful lookup",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/transaction/status",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "transaction",
                    "status"
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"order_no\": \"{{pwtOrderNo}}\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                },
                "description": "Queries the authenticated merchant's PWT order using order_no, or order_id when order_no is empty. This request can contact the provider and update local collection status; it is not a settlement or wallet-balance query. Common states: pending, success, failed, expired. Confirmed success is preserved against stale downgrades; legitimate verified late success remains possible. Returned status is collection status, not proof of settled availability. Respect the deployed rate limit (reviewed route: 30 requests/minute)."
              },
              "status": "OK",
              "code": 200,
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"status\": true,\n  \"data\": {\n    \"order_no\": \"EXAMPLE_PROVIDER_ORDER_NUMBER\",\n    \"order_id\": \"EXAMPLE_PWT_ORDER_ID\",\n    \"status\": \"success\"\n  }\n}"
            }
          ]
        },
        {
          "name": "Read PWT Callback Diagnostics",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/transaction/callback-diagnostics",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "transaction",
                "callback-diagnostics"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"order_no\": \"{{pwtOrderNo}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "description": "Reads callback fields for an owned PWT order. Fields include callback_attempts, callback_last_error, callback_last_attempt_at and callback_notified_at. Order-level diagnostics are not a complete per-event delivery ledger; an HTTP acknowledgement does not prove one merchant business credit. Reviewed route limit: 60/minute. Do not share responses containing private callback URLs or business references."
          },
          "protocolProfileBehavior": {
            "followRedirects": false
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "(() => {",
                  "function stop(message) { console.warn('Request not sent: ' + message); pm.execution.skipRequest(); throw new Error(message); }",
                  "function value(name) { const v = pm.environment.get(name); return v == null ? '' : String(v); }",
                  "function needed(name) { const v = value(name); if (!v.trim() || v.includes('{{')) stop('Set ' + name + ' in the selected private environment.'); return v; }",
                  "const base = needed('baseUrl').replace(/\\/$/, '');",
                  "if (base !== 'https://zenithpay.ng') stop('This collection documents https://zenithpay.ng. Confirm a different environment separately.');",
                  "const token = needed('apiToken');",
                  "if (/YOUR_|REPLACE_|EXAMPLE_/i.test(token)) stop('Replace the API-token placeholder privately.');",
                  "// Bind the inspected environment value directly; other variable scopes cannot change this destination or token.",
                  "pm.request.url.update(base + '/api/transaction/callback-diagnostics');",
                  "pm.request.headers.upsert({key: 'Authorization', value: 'Bearer ' + token});",
                  "pm.request.headers.upsert({key: 'Accept', value: 'application/json'});",
                  "const orderNo = value('pwtOrderNo');",
                  "const orderId = value('pwtOrderId');",
                  "if (!orderNo && !orderId) stop('Set pwtOrderNo or pwtOrderId for your merchant-owned order.');",
                  "pm.request.body.update({mode:'raw',raw:JSON.stringify(orderNo ? {order_no:orderNo} : {order_id:orderId}, null, 2),options:{raw:{language:'json'}}});",
                  "})();"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "(() => {",
                  "let result;",
                  "pm.test('Response body is a nonempty JSON object', function () {",
                  "  const text = pm.response.text();",
                  "  if (!text.trim()) throw new Error('Empty response: no success or account creation can be inferred.');",
                  "  result = pm.response.json();",
                  "  if (!result || typeof result !== 'object' || Array.isArray(result)) throw new Error('Expected a JSON object.');",
                  "});",
                  "if (!result || typeof result !== 'object' || Array.isArray(result)) return;",
                  "pm.test('Callback diagnostics succeeded', function () {",
                  "  if (pm.response.code !== 200 || result.status !== true || !result.data || !result.data.order_no) throw new Error('No confirmed callback-diagnostics response.');",
                  "});",
                  "})();"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "3. Manual support operation",
      "description": "A real notification resend; disabled by default and never an automatic acceptance test.",
      "item": [
        {
          "name": "Manual PWT Callback Retry — deliberate resend only",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/transaction/callback-retry",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "transaction",
                "callback-retry"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"order_no\": \"{{pwtOrderNo}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "description": "SENDS a real merchant callback. Disabled by the default environment; enable allowManualPwtCallbackRetry for one deliberate send after receiver repair and read-only review. The flag is reset before sending. A lost acknowledgement can still mean the merchant already processed the payment; retain the same logical event/payment identity and deduplicate durably. Do not treat a resend as permission for another credit or use live callbacks as test fixtures. Reviewed route limit: 20/minute. HTTP 502 can reflect receiver non-2xx; investigate actual diagnostics rather than assuming the payment failed."
          },
          "protocolProfileBehavior": {
            "followRedirects": false
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "(() => {",
                  "function stop(message) { console.warn('Request not sent: ' + message); pm.execution.skipRequest(); throw new Error(message); }",
                  "function value(name) { const v = pm.environment.get(name); return v == null ? '' : String(v); }",
                  "function needed(name) { const v = value(name); if (!v.trim() || v.includes('{{')) stop('Set ' + name + ' in the selected private environment.'); return v; }",
                  "const base = needed('baseUrl').replace(/\\/$/, '');",
                  "if (base !== 'https://zenithpay.ng') stop('This collection documents https://zenithpay.ng. Confirm a different environment separately.');",
                  "const token = needed('apiToken');",
                  "if (/YOUR_|REPLACE_|EXAMPLE_/i.test(token)) stop('Replace the API-token placeholder privately.');",
                  "// Bind the inspected environment value directly; other variable scopes cannot change this destination or token.",
                  "pm.request.url.update(base + '/api/transaction/callback-retry');",
                  "pm.request.headers.upsert({key: 'Authorization', value: 'Bearer ' + token});",
                  "pm.request.headers.upsert({key: 'Accept', value: 'application/json'});",
                  "const orderNo = value('pwtOrderNo');",
                  "const orderId = value('pwtOrderId');",
                  "if (!orderNo && !orderId) stop('Set pwtOrderNo or pwtOrderId for your merchant-owned order.');",
                  "pm.request.body.update({mode:'raw',raw:JSON.stringify(orderNo ? {order_no:orderNo} : {order_id:orderId}, null, 2),options:{raw:{language:'json'}}});",
                  "if (value('allowManualPwtCallbackRetry') !== 'true') stop('Manual resend is disabled. Repair and review the receiver first; set allowManualPwtCallbackRetry=true only for a deliberate owned-order resend.');",
                  "pm.environment.set('allowManualPwtCallbackRetry','false');",
                  "})();"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "(() => {",
                  "let result;",
                  "pm.test('Response body is a nonempty JSON object', function () {",
                  "  const text = pm.response.text();",
                  "  if (!text.trim()) throw new Error('Empty response: no success or account creation can be inferred.');",
                  "  result = pm.response.json();",
                  "  if (!result || typeof result !== 'object' || Array.isArray(result)) throw new Error('Expected a JSON object.');",
                  "});",
                  "if (!result || typeof result !== 'object' || Array.isArray(result)) return;",
                  "pm.test('Manual callback retry returned an accepted result', function () {",
                  "  if (pm.response.code !== 200 || result.status !== true) throw new Error('Manual delivery was not confirmed. Inspect diagnostics and receiver state before considering any further resend.');",
                  "});",
                  "})();"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "4. Webhook reference — read only",
      "description": "These are incoming merchant notifications, not requests to send to ZenithPay. Do not call provider-notify endpoints from merchant integration tests. PWT: X-Zenithpay-Timestamp, X-Zenithpay-Signature, X-Zenithpay-Event, X-Zenithpay-Event-Id, and configured X-Zenithpay-Source-IP. DVA: X-ZenithPay-Timestamp, X-ZenithPay-Signature, X-ZenithPay-Server-IP; PWT event headers are not required. For both, verify HMAC-SHA256(timestamp + \".\" + exact_raw_body, existing merchant Developer signing token), then parse JSON. Header names are case-insensitive. Neither callback sends the signing token in Authorization. DVA expects HTTP 200; PWT accepts 2xx, with 200 a common choice. Deduplicate each DVA payment by transaction_id, not accountReference/account number. Retain payment-reference uniqueness for PWT as well as delivery event identity. Authentication is not business-credit idempotency. See the included guide and anonymized examples.",
      "item": []
    }
  ]
}
