{
  "info": {
    "name": "WeChat Pay Simulator - Full Chain (DEV)",
    "description": "Exercises the whole WeChat payment path against the local DEV simulator:\n\n  device/cloud layer  ->  QR middle layer  ->  WeChat simulator  ->  webhook  ->  DB  (-> device via MQTT, not yet implemented)\n\nFOLDER 1 \"Full chain\" enters at the cloud-layer endpoint (/api/Iotmsgtest/createQrMf), exactly like the SoundBox does.\nFOLDER 2 \"Direct middle layer\" skips the cloud layer and hits /api/processTransaction directly (useful for debugging).\nFOLDER 3 \"Helpers\" are the raw simulator endpoints.\n\nRequires: app running in dev with :dev_routes enabled and :wechat_api_base_url pointed at the simulator.\nThe cloud-layer controller forwards to the middle layer at http://demo.ctrmv.com:4004/api/processTransaction (hard-coded in qr_more_fun_controller.ex), so that instance must also be simulator-configured.\n\nNOTE: there is no device leg yet. The middle layer only updates the transactions row; nothing is pushed to the SoundBox. Confirm the result with the SQL in each request's description, or the status request (which currently relies on an undefined helper and may 500).",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "variable": [
    { "key": "base_url", "value": "http://demo.ctrmv.com:4012" },
    { "key": "device_id", "value": "61250415798001" },
    { "key": "wechat_provider_code", "value": "15" },
    { "key": "provider_id", "value": "7" },
    { "key": "stid", "value": "401" },
    { "key": "amount", "value": "2.50" },
    { "key": "transaction_refid", "value": "" },
    { "key": "m_ref_num", "value": "" }
  ],
  "item": [
    {
      "name": "1. Full chain (cloud layer -> qrmid -> WeChat sim)",
      "item": [
        {
          "name": "1a. Cloud layer: Create WeChat QR (createQrMf)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const json = pm.response.json();",
                  "pm.test('cloud layer 200 / code 0000', () => {",
                  "  pm.expect(pm.response.code).to.eql(200);",
                  "  pm.expect(json.code).to.eql('0000');",
                  "});",
                  "const orderSn = json.data && json.data.order_sn;",
                  "pm.test('order_sn is a weixin:// url', () => pm.expect(orderSn || '').to.include('weixin://'));",
                  "// simulator code_url looks like weixin://wxpay/bizpayurl?pr=SIM<m_ref_num>",
                  "const m = (orderSn || '').match(/pr=SIM(.+)$/);",
                  "if (m) {",
                  "  pm.collectionVariables.set('m_ref_num', m[1]);",
                  "  console.log('Captured m_ref_num =', m[1]);",
                  "} else {",
                  "  console.warn('Could not parse m_ref_num from order_sn:', orderSn);",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "url": { "raw": "{{base_url}}/api/Iotmsgtest/createQrMf", "host": ["{{base_url}}"], "path": ["api", "Iotmsgtest", "createQrMf"] },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"device_id\": \"{{device_id}}\",\n  \"provider\": \"{{wechat_provider_code}}\",\n  \"request_data\": \"{{amount}}\"\n}"
            },
            "description": "Acts as the SoundBox + cloud layer. Internally: validate_provider (code 15 -> WeChat Pay id 7) -> POST http://demo.ctrmv.com:4004/api/processTransaction -> WeChat.generate -> /sim/wechat/v3/pay/transactions/native -> code_url. Response to device: {\"code\":\"0000\",\"msg\":\"Success\",\"data\":{\"order_sn\":\"weixin://...\"}}. The test script parses m_ref_num out of order_sn for the next step."
          }
        },
        {
          "name": "1b. WeChat sim: customer pays (trigger-callback SUCCESS)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const json = pm.response.json();",
                  "pm.test('simulator accepted (200)', () => pm.expect(pm.response.code).to.eql(200));",
                  "pm.test('webhook returned 200', () => pm.expect(json.webhook_status).to.eql(200));",
                  "pm.test('webhook body is SUCCESS', () => pm.expect(JSON.stringify(json.webhook_response)).to.include('SUCCESS'));",
                  "console.log('webhook_response =', json.webhook_response);"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "url": { "raw": "{{base_url}}/sim/wechat/trigger-callback", "host": ["{{base_url}}"], "path": ["sim", "wechat", "trigger-callback"] },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"m_ref_num\": \"{{m_ref_num}}\",\n  \"trade_state\": \"SUCCESS\"\n}"
            },
            "description": "Acts as WeChat Pay. Builds a signed + AEAD-encrypted APIv3 notification and POSTs it to /api/wechat/notify_payment. The webhook verifies the signature against the simulator platform cert, decrypts the resource, and sets the transaction to success.\n\nVerify:\n  SELECT id, status, settlement_date_time FROM transactions WHERE m_ref_num = '<m_ref_num>';   -- expect success"
          }
        },
        {
          "name": "1c. (optional) Device: poll result (qrmorefun/status)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// NOTE: QRMoreFunController.status/2 calls Transactions.get_transaction_by_ref_number/1,",
                  "// which is not defined in the codebase, so this endpoint may 500. Use the SQL check instead.",
                  "console.log('status response:', pm.response.code, pm.response.text());",
                  "pm.test('status endpoint responded', () => pm.expect(pm.response.code).to.be.oneOf([200, 404, 500]));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "url": { "raw": "{{base_url}}/api/qrmorefun/status", "host": ["{{base_url}}"], "path": ["api", "qrmorefun", "status"] },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"transaction_ref_number\": \"{{m_ref_num}}\"\n}"
            },
            "description": "Stands in for the device polling for the payment result. The cloud layer never returns the internal REF_... ref to the device, and get_transaction_by_ref_number/1 is undefined, so treat this as best-effort. Authoritative check is the SQL in 1b."
          }
        },
        {
          "name": "1d. WeChat sim: FAILURE variant (trigger-callback CLOSED)",
          "request": {
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "url": { "raw": "{{base_url}}/sim/wechat/trigger-callback", "host": ["{{base_url}}"], "path": ["sim", "wechat", "trigger-callback"] },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"m_ref_num\": \"{{m_ref_num}}\",\n  \"trade_state\": \"CLOSED\"\n}"
            },
            "description": "Run 1a again first to get a fresh pending m_ref_num, then this. trade_state CLOSED / PAYERROR / REVOKED all drive the transaction to failed.\n\nVerify:\n  SELECT status FROM transactions WHERE m_ref_num = '<m_ref_num>';   -- expect failed"
          }
        }
      ]
    },
    {
      "name": "2. Direct middle layer (skip cloud layer)",
      "item": [
        {
          "name": "2a. Generate WeChat QR (processTransaction)",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": ["pm.collectionVariables.set('transaction_refid', 'pm_wechat_' + Date.now());"]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const json = pm.response.json();",
                  "pm.test('status success', () => pm.expect(json.status).to.eql('success'));",
                  "const data = json.data || {};",
                  "if (data.mRefId) { pm.collectionVariables.set('m_ref_num', data.mRefId); console.log('Captured m_ref_num =', data.mRefId); }",
                  "pm.test('qrId present', () => pm.expect(data.qrId, JSON.stringify(json)).to.be.ok);"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "url": { "raw": "{{base_url}}/api/processTransaction", "host": ["{{base_url}}"], "path": ["api", "processTransaction"] },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"transaction_refid\": \"{{transaction_refid}}\",\n  \"amount\": {{amount}},\n  \"additionalData\": {},\n  \"deviceId\": \"{{device_id}}\",\n  \"provider\": {{provider_id}},\n  \"stid\": \"{{stid}}\"\n}"
            },
            "description": "Bypasses the cloud layer. provider here is the numeric provider *id* (7), not the provider_code (15)."
          }
        },
        {
          "name": "2b. WeChat sim: customer pays (trigger-callback SUCCESS)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const json = pm.response.json();",
                  "pm.test('webhook returned 200', () => pm.expect(json.webhook_status).to.eql(200));",
                  "console.log('webhook_response =', json.webhook_response);"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "url": { "raw": "{{base_url}}/sim/wechat/trigger-callback", "host": ["{{base_url}}"], "path": ["sim", "wechat", "trigger-callback"] },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"m_ref_num\": \"{{m_ref_num}}\",\n  \"trade_state\": \"SUCCESS\"\n}"
            }
          }
        },
        {
          "name": "2c. WeChat sim: FAILURE variant (trigger-callback CLOSED)",
          "request": {
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "url": { "raw": "{{base_url}}/sim/wechat/trigger-callback", "host": ["{{base_url}}"], "path": ["sim", "wechat", "trigger-callback"] },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"m_ref_num\": \"{{m_ref_num}}\",\n  \"trade_state\": \"CLOSED\",\n  \"amount_total\": 145\n}"
            }
          }
        }
      ]
    },
    {
      "name": "3. Helpers",
      "item": [
        {
          "name": "Simulator platform certificates",
          "request": {
            "method": "GET",
            "header": [],
            "url": { "raw": "{{base_url}}/sim/wechat/v3/certificates", "host": ["{{base_url}}"], "path": ["sim", "wechat", "v3", "certificates"] }
          }
        },
        {
          "name": "Raw Native order create",
          "request": {
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "url": { "raw": "{{base_url}}/sim/wechat/v3/pay/transactions/native", "host": ["{{base_url}}"], "path": ["sim", "wechat", "v3", "pay", "transactions", "native"] },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"out_trade_no\": \"{{m_ref_num}}\",\n  \"description\": \"postman test\",\n  \"amount\": { \"total\": 250, \"currency\": \"AED\" }\n}"
            }
          }
        }
      ]
    }
  ]
}
