{
  "info": {
    "name": "WeChat App Simulator (DEV) - scan / pay / status",
    "description": "Stands in for the WeChat app when you don't have a real phone. Use AFTER a WeChat Native QR has been generated (from a real device, or the Full-Chain collection's request 1a).\n\nWorkflow:\n  1. Scan QR   - paste the QR string from the cloud-layer / server logs. Validates it and returns the order details a payer sees before confirming. Captures m_ref_num.\n  2. Pay QR    - completes the payment: the simulator signs + AEAD-encrypts an APIv3 notification and POSTs it to the QR middle layer webhook, which verifies + decrypts + updates the transaction.\n  3. Transaction status - polls the QR middle layer for the current status.\n\nWhat reaches which service:\n  - Scan QR  -> simulator only (order lookup). Does not touch the cloud layer.\n  - Pay QR   -> simulator -> QR middle layer webhook (/api/wechat/notify_payment) -> DB. Does NOT propagate to the cloud layer or device (that leg is not built yet).\n  - Status   -> QR middle layer (/api/qrmorefun/status).\n\nQR string format:  weixin://wxpay/bizpayurl?pr=SIM<out_trade_no>   (the part after pr=SIM is the m_ref_num).",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "variable": [
    { "key": "base_url", "value": "http://demo.ctrmv.com:4012" },
    { "key": "qr_string", "value": "weixin://wxpay/bizpayurl?pr=SIMsh_PASTE_ME" },
    { "key": "trade_state", "value": "SUCCESS" },
    { "key": "m_ref_num", "value": "" }
  ],
  "item": [
    {
      "name": "1. Scan QR (acts as WeChat app opening the code)",
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "const json = pm.response.json();",
              "console.log('scan response:', JSON.stringify(json, null, 2));",
              "",
              "pm.test('QR string was valid', () => pm.expect(pm.response.code, JSON.stringify(json)).to.not.eql(422));",
              "pm.test('order was found', () => pm.expect(pm.response.code, JSON.stringify(json)).to.not.eql(404));",
              "",
              "if (json.out_trade_no) {",
              "  pm.collectionVariables.set('m_ref_num', json.out_trade_no);",
              "  console.log('Captured m_ref_num =', json.out_trade_no);",
              "}",
              "pm.test('order is payable', () => pm.expect(json.payable, 'order_status=' + json.order_status).to.eql(true));",
              "if (json.amount) console.log('Amount (minor units):', json.amount.total, json.amount.currency, '| merchant:', json.merchant && json.merchant.name);"
            ]
          }
        }
      ],
      "request": {
        "method": "POST",
        "header": [{ "key": "Content-Type", "value": "application/json" }],
        "url": { "raw": "{{base_url}}/sim/wechat/scan", "host": ["{{base_url}}"], "path": ["sim", "wechat", "scan"] },
        "body": {
          "mode": "raw",
          "raw": "{\n  \"qr_string\": \"{{qr_string}}\"\n}"
        },
        "description": "Paste the QR string into the `qr_string` variable first.\n\nThe simulator parses out_trade_no from the code_url, looks up the transaction, and returns:\n  status: SCANNED | NOT_PAYABLE | INVALID_QR | ORDER_NOT_FOUND\n  payable: true/false\n  out_trade_no, transaction_id, order_status\n  amount: { total (minor units), currency }\n  merchant: { mchid, appid, name }\n\nExpected on a fresh QR: HTTP 200, status SCANNED, payable true. The test captures out_trade_no into m_ref_num for step 2.\n\nAlso accepts {\"m_ref_num\":\"sh_...\"} or {\"out_trade_no\":\"sh_...\"} instead of qr_string."
      }
    },
    {
      "name": "2. Pay QR (acts as WeChat completing payment)",
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "const json = pm.response.json();",
              "console.log('pay response:', JSON.stringify(json, null, 2));",
              "pm.test('simulator accepted (200)', () => pm.expect(pm.response.code).to.eql(200));",
              "pm.test('webhook processed it (200)', () => pm.expect(json.webhook_status).to.eql(200));",
              "const st = pm.collectionVariables.get('trade_state');",
              "pm.test('webhook body reflects ' + st, () => pm.expect(JSON.stringify(json.webhook_response || '')).to.match(/SUCCESS|FAIL/));",
              "console.log(\"Verify: SELECT id, status, settlement_date_time FROM transactions WHERE m_ref_num = '\" + pm.collectionVariables.get('m_ref_num') + \"';\");"
            ]
          }
        }
      ],
      "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\": \"{{trade_state}}\"\n}"
        },
        "description": "Uses the m_ref_num captured by step 1. Set `trade_state`:\n  SUCCESS                     -> transaction becomes success\n  CLOSED / PAYERROR / REVOKED -> transaction becomes failed\n\nThe simulator builds a signed + AEAD_AES_256_GCM-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 runs:\n  UPDATE transactions SET status = 'success'|'failed', payload = <resource>, settlement_date_time = now\n  WHERE m_ref_num = ? AND status IN ('pending','QR_GENERATED')\n\nResponse: { sent_to, m_ref_num, trade_state, amount_total, webhook_status, webhook_response }.\nIf the transaction was not pending, webhook_response is still 200 but the server log says 'No pending transaction found'."
      }
    },
    {
      "name": "3. Transaction status (poll the QR middle layer)",
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "const json = pm.response.json();",
              "console.log('status response:', JSON.stringify(json, null, 2));",
              "pm.test('status endpoint responded 200', () => pm.expect(pm.response.code).to.eql(200));",
              "const data = json.data || {};",
              "pm.test('status field present', () => pm.expect(data).to.have.property('status'));",
              "console.log('Transaction status =', data.status, '| amount =', data.transaction_amount, '| txn id =', data.transaction_id);"
            ]
          }
        }
      ],
      "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": "Polls QRMoreFunController.status/2. Accepts either the transaction_ref_number or the m_ref_num (Transactions.get_transaction_by_ref_number/1 now matches both).\n\nResponse:\n  { code, msg, time, data: { transaction_ref_number, status, transaction_amount, transaction_id, merchant_refrence_number, created_at } }\n\nExpect status = pending before step 2, then success / failed after."
      }
    },
    {
      "name": "(helper) Simulator platform certificates",
      "request": {
        "method": "GET",
        "header": [],
        "url": { "raw": "{{base_url}}/sim/wechat/v3/certificates", "host": ["{{base_url}}"], "path": ["sim", "wechat", "v3", "certificates"] },
        "description": "The platform cert the webhook fetches to verify the Pay-QR callback signature. Check this if step 2's webhook_status is 500 with a signature error."
      }
    }
  ]
}
