{"templateId":"markdown","sharedDataIds":{"sidebar":"sidebar-parceiros/sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":[]},"type":"markdown"},"seo":{"title":"Simulador CLI","description":"APIs públicas da Movvia para parceiros, estabelecimentos comerciais e clientes de dados veiculares.","meta":[{"name":"theme-color","content":"#7E3DEE"},{"name":"apple-mobile-web-app-title","content":"Movvia Docs"},{"name":"application-name","content":"Movvia Docs"}],"llmstxt":{"hide":false,"sections":[{"title":"Table of contents","includeFiles":["**/*"],"excludeFiles":[]}],"excludeFiles":[]}},"dynamicMarkdocComponents":[],"compilationErrors":[],"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"simulador-cli","__idx":0},"children":["Simulador CLI"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["::: info Ferramenta de exploração local"," ","Este simulador é uma ferramenta de teste rápido para entender o funcionamento da API. ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Não é um SDK e não deve ser usado em produção."]}," O propósito é \"rode e veja a API responder em 2 minutos, sem montar requests à mão\"."," ",":::"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["O simulador é um único script (sem dependências externas além do runtime) que cobre todos os endpoints da API v2 de Parceiros: descoberta de identidade, webhooks, placas, transações e pedidos. Está disponível em ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["5 linguagens"]},", todas com a mesma experiência: mesmo menu, mesmo formato de output, mesmo ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":[".env"]},"."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"pré-requisitos","__idx":1},"children":["Pré-requisitos"]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Linguagem"},"children":["Linguagem"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Runtime mínimo"},"children":["Runtime mínimo"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Node.js"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Node 18+"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Python"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Python 3.8+"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Go"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Go 1.21+"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Java"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Java 11+"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Bash"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Bash 4+ + ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["curl"]}," + ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["jq"]}]}]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Você também precisa de credenciais de homologação. Veja ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/compartilhado/sandbox"},"children":["Sandbox"]}," para solicitar."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"passo-1--criar-a-pasta","__idx":2},"children":["Passo 1 — Criar a pasta"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"sh","header":{"controls":{"copy":{}}},"source":"mkdir simulador-movvia && cd simulador-movvia\n","lang":"sh"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"passo-2--salvar-o-simulador","__idx":3},"children":["Passo 2 — Salvar o simulador"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Escolha uma das linguagens abaixo e copie o conteúdo do code block para o arquivo correspondente."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"js","header":{"controls":{"copy":{}}},"source":"#!/usr/bin/env node\n// Utilitário de teste para API Movvia Arrecada+ (PE)\n// Requer Node.js 18+\n\nimport { createInterface } from 'readline';\nimport { readFileSync, existsSync } from 'fs';\nimport { resolve, dirname } from 'path';\nimport { fileURLToPath } from 'url';\n\nconst __dir = dirname(fileURLToPath(import.meta.url));\n\n// ─── Carregar .env ────────────────────────────────────────────────────────────\nconst envPath = resolve(__dir, '.env');\nif (existsSync(envPath)) {\n  for (const line of readFileSync(envPath, 'utf-8').split('\\n')) {\n    const t = line.trim();\n    if (!t || t.startsWith('#')) continue;\n    const eq = t.indexOf('=');\n    if (eq < 1) continue;\n    const key = t.slice(0, eq).trim();\n    const val = t.slice(eq + 1).trim().replace(/^[\"']|[\"']$/g, '');\n    if (key && !process.env[key]) process.env[key] = val;\n  }\n}\n\nconst BASE_URL =\n  process.env.BASE_URL ||\n  'https://hml.api.pedagioeletronico.com.br/gestao-webhooks-api/v1';\n\n// ─── Identidade do parceiro ───────────────────────────────────────────────────\n// Resolvido no startup: lê do .env (PE_PARCEIRO_ID) ou chama GET /me\nlet cachedParceiroId = process.env.PE_PARCEIRO_ID || null;\n\nasync function resolveParceiroId() {\n  if (cachedParceiroId) return cachedParceiroId;\n\n  const res = await fetch(`${BASE_URL}/me`, {\n    method: 'GET',\n    headers: { Authorization: authHeader(), 'Content-Type': 'application/json' },\n  });\n  const json = await res.json().catch(() => null);\n\n  if (!res.ok || !json?.data?.parceiroId) {\n    console.error('❌  Não foi possível descobrir o parceiroId via GET /me');\n    console.error('   ', JSON.stringify(json));\n    process.exit(1);\n  }\n\n  cachedParceiroId = String(json.data.parceiroId);\n  console.log(`  ℹ️  parceiroId descoberto via GET /me: ${cachedParceiroId}`);\n  return cachedParceiroId;\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\nfunction authHeader() {\n  const u = process.env.PE_USERNAME;\n  const p = process.env.PE_PASSWORD;\n  if (!u || !p) {\n    console.error('\\n❌  Configure PE_USERNAME e PE_PASSWORD no arquivo .env');\n    console.error('   Copie .env.example → .env e preencha as credenciais.\\n');\n    process.exit(1);\n  }\n  return 'Basic ' + Buffer.from(`${u}:${p}`).toString('base64');\n}\n\nasync function req(method, path, body, queryParams, customHeaders = {}) {\n  let url = `${BASE_URL}${path}`;\n  if (queryParams) {\n    const qs = new URLSearchParams(\n      Object.entries(queryParams).filter(([, v]) => v !== '' && v != null)\n    ).toString();\n    if (qs) url += '?' + qs;\n  }\n\n  // GET /me não precisa de x-parceiro-id (parceiro é inferido do Basic Auth)\n  const headers = { Authorization: authHeader(), 'Content-Type': 'application/json' };\n  if (path !== '/me') headers['x-parceiro-id'] = await resolveParceiroId();\n  Object.assign(headers, customHeaders);\n\n  const options = { method, headers };\n  if (body) options.body = JSON.stringify(body);\n\n  console.log(`\\n→ ${method} ${url}`);\n  if (body) console.log('  Body:', JSON.stringify(body));\n  if (Object.keys(customHeaders).length) console.log('  Headers extras:', JSON.stringify(customHeaders));\n\n  let res;\n  try {\n    res = await fetch(url, options);\n  } catch (e) {\n    console.error('❌  Erro de rede:', e.message);\n    return null;\n  }\n\n  const text = await res.text();\n  let json;\n  try { json = JSON.parse(text); } catch { json = text; }\n\n  console.log(`${res.ok ? '✅' : '❌'} ${res.status} ${res.statusText}`);\n  console.log(JSON.stringify(json, null, 2));\n  return { ok: res.ok, status: res.status, json };\n}\n\n// ─── Prompt readline ─────────────────────────────────────────────────────────\nconst rl = createInterface({ input: process.stdin, output: process.stdout });\nconst ask = (q) => new Promise((r) => rl.question(q, r));\nconst askOpt = async (q) => { const v = (await ask(q)).trim(); return v || null; };\n\n// ─── Ações ───────────────────────────────────────────────────────────────────\n\nasync function descobrirParceiro() {\n  await req('GET', '/me');\n}\n\nconst EVENTOS_STATIC = [\n  'pe.pedido.criado',\n  'pe.pedido.confirmado',\n  'pe.pedido.cancelado',\n  'pe.pedido.expirado',\n  'pe.pedido.erro',\n  'pe.transacao.recebida',\n  'pe.transacao.atualizada',\n  'pe.transacao.cancelada',\n];\n\nasync function listarEventosDisponiveis() {\n  return await req('GET', '/webhook/eventos');\n}\n\nasync function cadastrarWebhook() {\n  const url = (await ask('  URL HTTPS do endpoint: ')).trim();\n  const chaveSecreta = (await ask('  Chave secreta HMAC: ')).trim();\n\n  console.log('\\n  Buscando eventos disponíveis...');\n  const res = await listarEventosDisponiveis();\n  let eventos = EVENTOS_STATIC;\n  if (res && res.ok && Array.isArray(res.json?.data)) {\n    eventos = res.json.data.map((e) => (typeof e === 'string' ? e : e.evento));\n  }\n\n  console.log('\\n  Eventos disponíveis:');\n  eventos.forEach((e, i) => console.log(`    ${i + 1}. ${e}`));\n\n  const rawChoice = (await ask('\\n  Escolha o número do evento ou digite o nome: ')).trim();\n  const idx = parseInt(rawChoice, 10);\n  const tipo = (!isNaN(idx) && idx > 0 && idx <= eventos.length)\n    ? eventos[idx - 1]\n    : rawChoice || (await ask('  Tipo do evento: ')).trim();\n\n  if (!tipo) { console.log('❌ Tipo inválido.'); return; }\n  await req('POST', '/webhook', { url, chaveSecreta, tipo });\n}\n\nasync function listarWebhooks() {\n  await req('GET', '/webhook');\n}\n\nasync function testarConectividadeWebhook() {\n  const tipo = (await ask('  Tipo do evento (ex: pe.pedido.confirmado): ')).trim();\n  if (!tipo) return;\n  await req('POST', `/webhook/teste-conectividade/${encodeURIComponent(tipo)}`);\n}\n\nasync function removerWebhook() {\n  const tipo = (await ask('  Tipo do webhook a remover (ex: pe.pedido.confirmado): ')).trim();\n  if (!tipo) return;\n  await req('DELETE', `/webhook/${encodeURIComponent(tipo)}`);\n}\n\nasync function cadastrarPlaca() {\n  const placa = (await ask('  Placa (ex: ABC1D23): ')).trim().toUpperCase();\n  const dataInicio = await askOpt('  Data início monitoramento ISO 8601 (Enter p/ pular): ');\n  const dataFim = await askOpt('  Data fim monitoramento ISO 8601 (Enter p/ pular): ');\n  const body = { placa };\n  if (dataInicio) body.dataInicioMonitoramento = dataInicio;\n  if (dataFim) body.dataFimMonitoramento = dataFim;\n  await req('POST', '/placas', body);\n}\n\nasync function removerPlaca() {\n  const placa = (await ask('  Placa a remover (ex: ABC1D23): ')).trim().toUpperCase();\n  await req('DELETE', `/placas/${placa}`);\n}\n\nasync function listarTransacoes() {\n  const placa = await askOpt('  Filtrar por placa (Enter p/ pular): ');\n  const status = await askOpt('  Status [PENDENTE/PAGO/CANCELADA] (Enter p/ pular): ');\n  const dataInicio = await askOpt('  Data início ISO 8601 (Enter p/ pular): ');\n  const dataFim = await askOpt('  Data fim ISO 8601 (Enter p/ pular): ');\n  const pagina = (await askOpt('  Página (Enter = 0): ')) || '0';\n  const tamanhoPagina = (await askOpt('  Tamanho página (Enter = 50): ')) || '50';\n  await req('GET', '/transacoes', null, { placa, status, dataInicio, dataFim, pagina, tamanhoPagina });\n}\n\nasync function criarPedido() {\n  const raw = (await ask('  IDs de transações separados por vírgula: ')).trim();\n  const transacoes = raw.split(',').map((s) => ({ transacaoId: s.trim() })).filter((t) => t.transacaoId);\n  const idempotencia = await askOpt('  Chave de idempotência (Opcional): ');\n  const headers = idempotencia ? { 'x-chave-idempotencia': idempotencia } : {};\n  await req('POST', '/pedidos', { transacoes }, null, headers);\n}\n\nasync function consultarPedido() {\n  const pedidoId = (await ask('  ID do pedido: ')).trim();\n  await req('GET', `/pedidos/${pedidoId}`);\n}\n\nasync function confirmarPagamento() {\n  const pedidoId = (await ask('  ID do pedido: ')).trim();\n  const referenciaExterna = await askOpt('  Referência externa (Enter p/ pular): ');\n  const dataLiquidacaoExterna = await askOpt('  Data liquidação externa ISO 8601 (Enter p/ pular): ');\n  const idempotencia = await askOpt('  Chave de idempotência (Opcional): ');\n  const body = {};\n  if (referenciaExterna) body.referenciaExterna = referenciaExterna;\n  if (dataLiquidacaoExterna) body.dataLiquidacaoExterna = dataLiquidacaoExterna;\n  const headers = idempotencia ? { 'x-chave-idempotencia': idempotencia } : {};\n  await req('POST', `/pedidos/${pedidoId}/confirmar`, Object.keys(body).length ? body : undefined, null, headers);\n}\n\nasync function cancelarPedido() {\n  const pedidoId = (await ask('  ID do pedido: ')).trim();\n  const motivo = await askOpt('  Motivo [DESISTENCIA_PARCEIRO/ERRO_INTERNO/OUTROS] (Enter p/ pular): ');\n  const observacao = await askOpt('  Observação até 500 chars (Enter p/ pular): ');\n  const idempotencia = await askOpt('  Chave de idempotência (Opcional): ');\n  const body = {};\n  if (motivo) body.motivo = motivo.toUpperCase();\n  if (observacao) body.observacao = observacao;\n  const headers = idempotencia ? { 'x-chave-idempotencia': idempotencia } : {};\n  await req('POST', `/pedidos/${pedidoId}/cancelar`, Object.keys(body).length ? body : undefined, null, headers);\n}\n\n// ─── Menu ─────────────────────────────────────────────────────────────────────\nconst MENU = [\n  { label: '── Primeiros Passos ─────────────────', action: null },\n  { label: 'Descobrir parceiroId (GET /me)',       action: descobrirParceiro },\n  { label: '── Webhooks Config ──────────────────', action: null },\n  { label: 'Listar eventos disponíveis',           action: listarEventosDisponiveis },\n  { label: 'Cadastrar webhook',                    action: cadastrarWebhook },\n  { label: 'Listar webhooks',                      action: listarWebhooks },\n  { label: 'Testar conectividade webhook',         action: testarConectividadeWebhook },\n  { label: 'Remover webhook',                      action: removerWebhook },\n  { label: '── Placas ───────────────────────────', action: null },\n  { label: 'Cadastrar placa',                      action: cadastrarPlaca },\n  { label: 'Remover placa',                        action: removerPlaca },\n  { label: '── Transações ───────────────────────', action: null },\n  { label: 'Listar transações',                    action: listarTransacoes },\n  { label: '── Pedidos ──────────────────────────', action: null },\n  { label: 'Criar pedido',                         action: criarPedido },\n  { label: 'Consultar pedido',                     action: consultarPedido },\n  { label: 'Confirmar pagamento',                  action: confirmarPagamento },\n  { label: 'Cancelar pedido',                      action: cancelarPedido },\n];\n\nfunction printMenu() {\n  console.log('\\n╔══════════════════════════════════════════════╗');\n  console.log('║   Movvia Arrecada+ — PE API Tester           ║');\n  const urlShort = BASE_URL.replace('https://', '');\n  const urlLine = urlShort.length > 43 ? urlShort.slice(0, 40) + '...' : urlShort.padEnd(43);\n  console.log(`║   ${urlLine}║`);\n  const pid = cachedParceiroId ? `parceiroId: ${cachedParceiroId}` : 'parceiroId: (auto via GET /me)';\n  console.log(`║   ${pid.padEnd(43)}║`);\n  console.log('╠══════════════════════════════════════════════╣');\n  let idx = 1;\n  for (const item of MENU) {\n    if (!item.action) {\n      console.log(`║  ${item.label.padEnd(44)}║`);\n    } else {\n      console.log(`║   ${String(idx).padStart(2)}.  ${item.label.padEnd(39)}║`);\n      idx++;\n    }\n  }\n  console.log('║                                              ║');\n  console.log('║    0.  Sair                                  ║');\n  console.log('╚══════════════════════════════════════════════╝');\n}\n\nasync function main() {\n  // Passo zero: resolver parceiroId antes de qualquer interação com o menu\n  if (!cachedParceiroId) {\n    console.log('\\n  ℹ️  PE_PARCEIRO_ID não configurado — chamando GET /me para descobrir...');\n    await resolveParceiroId();\n  }\n\n  const actions = MENU.filter((m) => m.action).map((m) => m.action);\n\n  while (true) {\n    printMenu();\n    const choice = (await ask('\\nEscolha uma opção: ')).trim();\n    if (choice === '0' || choice.toLowerCase() === 'sair') break;\n\n    const n = parseInt(choice, 10);\n    if (isNaN(n) || n < 1 || n > actions.length) {\n      console.log('Opção inválida.');\n      continue;\n    }\n\n    try {\n      await actions[n - 1]();\n    } catch (e) {\n      console.error('Erro inesperado:', e.message);\n    }\n\n    await ask('\\n[Enter para voltar ao menu]');\n  }\n\n  console.log('\\nAté logo!\\n');\n  rl.close();\n}\n\nmain();\n","lang":"js"},"children":[]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"python","header":{"controls":{"copy":{}}},"source":"#!/usr/bin/env python3\n# Utilitário de teste para API Movvia Arrecada+ (PE)\n# Requer Python 3.8+\n\nimport base64\nimport json\nimport os\nimport sys\nimport urllib.error\nimport urllib.parse\nimport urllib.request\nfrom pathlib import Path\n\n# ─── Carregar .env ────────────────────────────────────────────────────────────\nENV_PATH = Path(__file__).resolve().parent / \".env\"\nif ENV_PATH.exists():\n    for line in ENV_PATH.read_text(encoding=\"utf-8\").splitlines():\n        t = line.strip()\n        if not t or t.startswith(\"#\") or \"=\" not in t:\n            continue\n        key, val = t.split(\"=\", 1)\n        key = key.strip()\n        val = val.strip().strip('\"').strip(\"'\")\n        if key and key not in os.environ:\n            os.environ[key] = val\n\nBASE_URL = os.environ.get(\"BASE_URL\") or \"https://hml.api.pedagioeletronico.com.br/gestao-webhooks-api/v1\"\n\n# ─── Identidade do parceiro ───────────────────────────────────────────────────\ncached_parceiro_id = os.environ.get(\"PE_PARCEIRO_ID\") or None\n\n\ndef auth_header() -> str:\n    u = os.environ.get(\"PE_USERNAME\")\n    p = os.environ.get(\"PE_PASSWORD\")\n    if not u or not p:\n        print(\"\\n❌  Configure PE_USERNAME e PE_PASSWORD no arquivo .env\")\n        print(\"   Copie .env.example → .env e preencha as credenciais.\\n\")\n        sys.exit(1)\n    raw = f\"{u}:{p}\".encode(\"utf-8\")\n    return \"Basic \" + base64.b64encode(raw).decode(\"ascii\")\n\n\ndef resolve_parceiro_id() -> str:\n    global cached_parceiro_id\n    if cached_parceiro_id:\n        return cached_parceiro_id\n    res = req(\"GET\", \"/me\", quiet=True)\n    if not res or not res.get(\"ok\"):\n        print(\"❌  Não foi possível descobrir o parceiroId via GET /me\")\n        sys.exit(1)\n    body = res.get(\"json\")\n    pid = (body or {}).get(\"data\", {}).get(\"parceiroId\") if isinstance(body, dict) else None\n    if not pid:\n        print(\"❌  Resposta sem data.parceiroId\")\n        sys.exit(1)\n    cached_parceiro_id = str(pid)\n    print(f\"  ℹ️  parceiroId descoberto via GET /me: {cached_parceiro_id}\")\n    return cached_parceiro_id\n\n\ndef req(method, path, body=None, query=None, headers=None, quiet=False):\n    url = BASE_URL + path\n    if query:\n        items = [(k, v) for k, v in query.items() if v not in (None, \"\")]\n        if items:\n            url += \"?\" + urllib.parse.urlencode(items)\n\n    h = {\"Authorization\": auth_header(), \"Content-Type\": \"application/json\"}\n    if path != \"/me\":\n        h[\"x-parceiro-id\"] = resolve_parceiro_id()\n    if headers:\n        h.update(headers)\n\n    data = json.dumps(body).encode(\"utf-8\") if body is not None else None\n\n    if not quiet:\n        print(f\"\\n→ {method} {url}\")\n        if body is not None:\n            print(f\"  Body: {json.dumps(body, ensure_ascii=False)}\")\n        if headers:\n            print(f\"  Headers extras: {json.dumps(headers, ensure_ascii=False)}\")\n\n    request = urllib.request.Request(url, data=data, method=method, headers=h)\n    try:\n        with urllib.request.urlopen(request) as resp:\n            status = resp.status\n            text = resp.read().decode(\"utf-8\")\n    except urllib.error.HTTPError as e:\n        status = e.code\n        text = e.read().decode(\"utf-8\")\n    except Exception as e:\n        print(f\"❌  Erro de rede: {e}\")\n        return None\n\n    try:\n        parsed = json.loads(text)\n    except Exception:\n        parsed = text\n\n    if not quiet:\n        ok = 200 <= status < 400\n        print(f\"{'✅' if ok else '❌'} {status}\")\n        if isinstance(parsed, (dict, list)):\n            print(json.dumps(parsed, ensure_ascii=False, indent=2))\n        else:\n            print(parsed)\n\n    return {\"ok\": 200 <= status < 400, \"status\": status, \"json\": parsed}\n\n\n# ─── Prompts ──────────────────────────────────────────────────────────────────\ndef ask(q):\n    return input(q)\n\n\ndef ask_opt(q):\n    v = input(q).strip()\n    return v or None\n\n\n# ─── Ações ────────────────────────────────────────────────────────────────────\ndef descobrir_parceiro():\n    req(\"GET\", \"/me\")\n\n\nEVENTOS_STATIC = [\n    \"pe.pedido.criado\",\n    \"pe.pedido.confirmado\",\n    \"pe.pedido.cancelado\",\n    \"pe.pedido.expirado\",\n    \"pe.pedido.erro\",\n    \"pe.transacao.recebida\",\n    \"pe.transacao.atualizada\",\n    \"pe.transacao.cancelada\",\n]\n\n\ndef listar_eventos_disponiveis():\n    return req(\"GET\", \"/webhook/eventos\")\n\n\ndef cadastrar_webhook():\n    url = ask(\"  URL HTTPS do endpoint: \").strip()\n    chave = ask(\"  Chave secreta HMAC: \").strip()\n    print(\"\\n  Buscando eventos disponíveis...\")\n    res = listar_eventos_disponiveis()\n    eventos = EVENTOS_STATIC\n    if res and res.get(\"ok\"):\n        body = res.get(\"json\")\n        data = body.get(\"data\") if isinstance(body, dict) else None\n        if isinstance(data, list) and data:\n            eventos = [e if isinstance(e, str) else (e.get(\"evento\") if isinstance(e, dict) else None) for e in data]\n            eventos = [e for e in eventos if e]\n    print(\"\\n  Eventos disponíveis:\")\n    for i, e in enumerate(eventos, start=1):\n        print(f\"    {i}. {e}\")\n    raw = ask(\"\\n  Escolha o número do evento ou digite o nome: \").strip()\n    tipo = raw\n    try:\n        idx = int(raw)\n        if 1 <= idx <= len(eventos):\n            tipo = eventos[idx - 1]\n    except ValueError:\n        pass\n    if not tipo:\n        tipo = ask(\"  Tipo do evento: \").strip()\n    if not tipo:\n        print(\"❌ Tipo inválido.\")\n        return\n    req(\"POST\", \"/webhook\", {\"url\": url, \"chaveSecreta\": chave, \"tipo\": tipo})\n\n\ndef listar_webhooks():\n    req(\"GET\", \"/webhook\")\n\n\ndef testar_conectividade_webhook():\n    tipo = ask(\"  Tipo do evento (ex: pe.pedido.confirmado): \").strip()\n    if not tipo:\n        return\n    req(\"POST\", f\"/webhook/teste-conectividade/{urllib.parse.quote(tipo, safe='')}\")\n\n\ndef remover_webhook():\n    tipo = ask(\"  Tipo do webhook a remover (ex: pe.pedido.confirmado): \").strip()\n    if not tipo:\n        return\n    req(\"DELETE\", f\"/webhook/{urllib.parse.quote(tipo, safe='')}\")\n\n\ndef cadastrar_placa():\n    placa = ask(\"  Placa (ex: ABC1D23): \").strip().upper()\n    di = ask_opt(\"  Data início monitoramento ISO 8601 (Enter p/ pular): \")\n    df = ask_opt(\"  Data fim monitoramento ISO 8601 (Enter p/ pular): \")\n    body = {\"placa\": placa}\n    if di:\n        body[\"dataInicioMonitoramento\"] = di\n    if df:\n        body[\"dataFimMonitoramento\"] = df\n    req(\"POST\", \"/placas\", body)\n\n\ndef remover_placa():\n    placa = ask(\"  Placa a remover (ex: ABC1D23): \").strip().upper()\n    req(\"DELETE\", f\"/placas/{placa}\")\n\n\ndef listar_transacoes():\n    placa = ask_opt(\"  Filtrar por placa (Enter p/ pular): \")\n    status = ask_opt(\"  Status [PENDENTE/PAGO/CANCELADA] (Enter p/ pular): \")\n    di = ask_opt(\"  Data início ISO 8601 (Enter p/ pular): \")\n    df = ask_opt(\"  Data fim ISO 8601 (Enter p/ pular): \")\n    pagina = ask_opt(\"  Página (Enter = 0): \") or \"0\"\n    tamanho = ask_opt(\"  Tamanho página (Enter = 50): \") or \"50\"\n    req(\"GET\", \"/transacoes\", query={\n        \"placa\": placa, \"status\": status,\n        \"dataInicio\": di, \"dataFim\": df,\n        \"pagina\": pagina, \"tamanhoPagina\": tamanho,\n    })\n\n\ndef criar_pedido():\n    raw = ask(\"  IDs de transações separados por vírgula: \").strip()\n    transacoes = [{\"transacaoId\": s.strip()} for s in raw.split(\",\") if s.strip()]\n    idemp = ask_opt(\"  Chave de idempotência (Opcional): \")\n    headers = {\"x-chave-idempotencia\": idemp} if idemp else None\n    req(\"POST\", \"/pedidos\", {\"transacoes\": transacoes}, headers=headers)\n\n\ndef consultar_pedido():\n    pid = ask(\"  ID do pedido: \").strip()\n    req(\"GET\", f\"/pedidos/{pid}\")\n\n\ndef confirmar_pagamento():\n    pid = ask(\"  ID do pedido: \").strip()\n    ref = ask_opt(\"  Referência externa (Enter p/ pular): \")\n    dt = ask_opt(\"  Data liquidação externa ISO 8601 (Enter p/ pular): \")\n    idemp = ask_opt(\"  Chave de idempotência (Opcional): \")\n    body = {}\n    if ref:\n        body[\"referenciaExterna\"] = ref\n    if dt:\n        body[\"dataLiquidacaoExterna\"] = dt\n    headers = {\"x-chave-idempotencia\": idemp} if idemp else None\n    req(\"POST\", f\"/pedidos/{pid}/confirmar\", body or None, headers=headers)\n\n\ndef cancelar_pedido():\n    pid = ask(\"  ID do pedido: \").strip()\n    motivo = ask_opt(\"  Motivo [DESISTENCIA_PARCEIRO/ERRO_INTERNO/OUTROS] (Enter p/ pular): \")\n    obs = ask_opt(\"  Observação até 500 chars (Enter p/ pular): \")\n    idemp = ask_opt(\"  Chave de idempotência (Opcional): \")\n    body = {}\n    if motivo:\n        body[\"motivo\"] = motivo.upper()\n    if obs:\n        body[\"observacao\"] = obs\n    headers = {\"x-chave-idempotencia\": idemp} if idemp else None\n    req(\"POST\", f\"/pedidos/{pid}/cancelar\", body or None, headers=headers)\n\n\n# ─── Menu ─────────────────────────────────────────────────────────────────────\nMENU = [\n    (\"── Primeiros Passos ─────────────────\", None),\n    (\"Descobrir parceiroId (GET /me)\", descobrir_parceiro),\n    (\"── Webhooks Config ──────────────────\", None),\n    (\"Listar eventos disponíveis\", listar_eventos_disponiveis),\n    (\"Cadastrar webhook\", cadastrar_webhook),\n    (\"Listar webhooks\", listar_webhooks),\n    (\"Testar conectividade webhook\", testar_conectividade_webhook),\n    (\"Remover webhook\", remover_webhook),\n    (\"── Placas ───────────────────────────\", None),\n    (\"Cadastrar placa\", cadastrar_placa),\n    (\"Remover placa\", remover_placa),\n    (\"── Transações ───────────────────────\", None),\n    (\"Listar transações\", listar_transacoes),\n    (\"── Pedidos ──────────────────────────\", None),\n    (\"Criar pedido\", criar_pedido),\n    (\"Consultar pedido\", consultar_pedido),\n    (\"Confirmar pagamento\", confirmar_pagamento),\n    (\"Cancelar pedido\", cancelar_pedido),\n]\n\n\ndef print_menu():\n    print(\"\\n╔══════════════════════════════════════════════╗\")\n    print(\"║   Movvia Arrecada+ — PE API Tester           ║\")\n    short = BASE_URL.replace(\"https://\", \"\")\n    line = (short[:40] + \"...\") if len(short) > 43 else short.ljust(43)\n    print(f\"║   {line}║\")\n    pid = f\"parceiroId: {cached_parceiro_id}\" if cached_parceiro_id else \"parceiroId: (auto via GET /me)\"\n    print(f\"║   {pid.ljust(43)}║\")\n    print(\"╠══════════════════════════════════════════════╣\")\n    idx = 1\n    for label, action in MENU:\n        if action is None:\n            print(f\"║  {label.ljust(44)}║\")\n        else:\n            print(f\"║   {str(idx).rjust(2)}.  {label.ljust(39)}║\")\n            idx += 1\n    print(\"║                                              ║\")\n    print(\"║    0.  Sair                                  ║\")\n    print(\"╚══════════════════════════════════════════════╝\")\n\n\ndef main():\n    if not cached_parceiro_id:\n        print(\"\\n  ℹ️  PE_PARCEIRO_ID não configurado — chamando GET /me para descobrir...\")\n        resolve_parceiro_id()\n\n    actions = [a for _, a in MENU if a is not None]\n\n    while True:\n        print_menu()\n        choice = input(\"\\nEscolha uma opção: \").strip().lower()\n        if choice in (\"0\", \"sair\"):\n            break\n        try:\n            n = int(choice)\n        except ValueError:\n            print(\"Opção inválida.\")\n            continue\n        if n < 1 or n > len(actions):\n            print(\"Opção inválida.\")\n            continue\n        try:\n            actions[n - 1]()\n        except Exception as e:\n            print(f\"Erro inesperado: {e}\")\n        input(\"\\n[Enter para voltar ao menu]\")\n\n    print(\"\\nAté logo!\\n\")\n\n\nif __name__ == \"__main__\":\n    main()\n","lang":"python"},"children":[]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"go","header":{"controls":{"copy":{}}},"source":"// Utilitário de teste para API Movvia Arrecada+ (PE)\n// Requer Go 1.21+. Roda com: go run simulador.go\npackage main\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n)\n\nvar (\n\tbaseURL          = \"\"\n\tcachedParceiroID = \"\"\n\tstdin            = bufio.NewReader(os.Stdin)\n)\n\n// ─── Carregar .env ────────────────────────────────────────────────────────────\nfunc loadEnv() {\n\tdata, err := os.ReadFile(\".env\")\n\tif err != nil {\n\t\treturn\n\t}\n\tfor _, line := range strings.Split(string(data), \"\\n\") {\n\t\tt := strings.TrimSpace(line)\n\t\tif t == \"\" || strings.HasPrefix(t, \"#\") {\n\t\t\tcontinue\n\t\t}\n\t\tidx := strings.Index(t, \"=\")\n\t\tif idx < 1 {\n\t\t\tcontinue\n\t\t}\n\t\tkey := strings.TrimSpace(t[:idx])\n\t\tval := strings.TrimSpace(t[idx+1:])\n\t\tval = strings.Trim(val, \"\\\"'\")\n\t\tif key != \"\" && os.Getenv(key) == \"\" {\n\t\t\tos.Setenv(key, val)\n\t\t}\n\t}\n}\n\nfunc getenvDefault(k, d string) string {\n\tif v := os.Getenv(k); v != \"\" {\n\t\treturn v\n\t}\n\treturn d\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\nfunc authHeader() string {\n\tu := os.Getenv(\"PE_USERNAME\")\n\tp := os.Getenv(\"PE_PASSWORD\")\n\tif u == \"\" || p == \"\" {\n\t\tfmt.Println(\"\\n❌  Configure PE_USERNAME e PE_PASSWORD no arquivo .env\")\n\t\tfmt.Println(\"   Copie .env.example → .env e preencha as credenciais.\")\n\t\tfmt.Println()\n\t\tos.Exit(1)\n\t}\n\treturn \"Basic \" + base64.StdEncoding.EncodeToString([]byte(u+\":\"+p))\n}\n\ntype result struct {\n\tok     bool\n\tstatus int\n\tbody   string\n}\n\nfunc req(method, path string, body any, query map[string]string, headers map[string]string, quiet bool) *result {\n\tu := baseURL + path\n\tif len(query) > 0 {\n\t\tq := url.Values{}\n\t\tfor k, v := range query {\n\t\t\tif v != \"\" {\n\t\t\t\tq.Set(k, v)\n\t\t\t}\n\t\t}\n\t\tif encoded := q.Encode(); encoded != \"\" {\n\t\t\tu += \"?\" + encoded\n\t\t}\n\t}\n\n\tvar bodyReader io.Reader\n\tvar bodyJSON []byte\n\tif body != nil {\n\t\tvar err error\n\t\tbodyJSON, err = json.Marshal(body)\n\t\tif err != nil {\n\t\t\tfmt.Printf(\"❌  Erro ao serializar body: %v\\n\", err)\n\t\t\treturn nil\n\t\t}\n\t\tbodyReader = bytes.NewReader(bodyJSON)\n\t}\n\n\tr, err := http.NewRequest(method, u, bodyReader)\n\tif err != nil {\n\t\tfmt.Printf(\"❌  Erro ao montar request: %v\\n\", err)\n\t\treturn nil\n\t}\n\tr.Header.Set(\"Authorization\", authHeader())\n\tr.Header.Set(\"Content-Type\", \"application/json\")\n\tif path != \"/me\" {\n\t\tr.Header.Set(\"x-parceiro-id\", resolveParceiroID())\n\t}\n\tfor k, v := range headers {\n\t\tr.Header.Set(k, v)\n\t}\n\n\tif !quiet {\n\t\tfmt.Printf(\"\\n→ %s %s\\n\", method, u)\n\t\tif bodyJSON != nil {\n\t\t\tfmt.Printf(\"  Body: %s\\n\", string(bodyJSON))\n\t\t}\n\t\tif len(headers) > 0 {\n\t\t\th, _ := json.Marshal(headers)\n\t\t\tfmt.Printf(\"  Headers extras: %s\\n\", string(h))\n\t\t}\n\t}\n\n\tresp, err := http.DefaultClient.Do(r)\n\tif err != nil {\n\t\tfmt.Printf(\"❌  Erro de rede: %v\\n\", err)\n\t\treturn nil\n\t}\n\tdefer resp.Body.Close()\n\traw, _ := io.ReadAll(resp.Body)\n\n\tif !quiet {\n\t\tok := resp.StatusCode < 400\n\t\tmark := \"❌\"\n\t\tif ok {\n\t\t\tmark = \"✅\"\n\t\t}\n\t\tfmt.Printf(\"%s %d %s\\n\", mark, resp.StatusCode, http.StatusText(resp.StatusCode))\n\t\tvar pretty bytes.Buffer\n\t\tif err := json.Indent(&pretty, raw, \"\", \"  \"); err == nil {\n\t\t\tfmt.Println(pretty.String())\n\t\t} else {\n\t\t\tfmt.Println(string(raw))\n\t\t}\n\t}\n\n\treturn &result{ok: resp.StatusCode < 400, status: resp.StatusCode, body: string(raw)}\n}\n\nfunc resolveParceiroID() string {\n\tif cachedParceiroID != \"\" {\n\t\treturn cachedParceiroID\n\t}\n\tr := req(\"GET\", \"/me\", nil, nil, nil, true)\n\tif r == nil || !r.ok {\n\t\tfmt.Println(\"❌  Não foi possível descobrir o parceiroId via GET /me\")\n\t\tos.Exit(1)\n\t}\n\tvar parsed struct {\n\t\tData struct {\n\t\t\tParceiroID json.RawMessage `json:\"parceiroId\"`\n\t\t} `json:\"data\"`\n\t}\n\tif err := json.Unmarshal([]byte(r.body), &parsed); err != nil {\n\t\tfmt.Printf(\"❌  Erro ao parsear resposta de /me: %v\\n\", err)\n\t\tos.Exit(1)\n\t}\n\tpid := strings.Trim(string(parsed.Data.ParceiroID), `\"`)\n\tif pid == \"\" || pid == \"null\" {\n\t\tfmt.Println(\"❌  Resposta sem data.parceiroId\")\n\t\tos.Exit(1)\n\t}\n\tcachedParceiroID = pid\n\tfmt.Printf(\"  ℹ️  parceiroId descoberto via GET /me: %s\\n\", pid)\n\treturn pid\n}\n\n// ─── Prompts ──────────────────────────────────────────────────────────────────\nfunc prompt(p string) string {\n\tfmt.Print(p)\n\tline, _ := stdin.ReadString('\\n')\n\treturn strings.TrimRight(line, \"\\r\\n\")\n}\n\nfunc promptOpt(p string) string {\n\treturn strings.TrimSpace(prompt(p))\n}\n\n// ─── Ações ───────────────────────────────────────────────────────────────────\nfunc descobrirParceiro() { req(\"GET\", \"/me\", nil, nil, nil, false) }\nfunc listarWebhooks()    { req(\"GET\", \"/webhook\", nil, nil, nil, false) }\nfunc listarEventosDisponiveis() *result {\n\treturn req(\"GET\", \"/webhook/eventos\", nil, nil, nil, false)\n}\n\nvar eventosStatic = []string{\n\t\"pe.pedido.criado\",\n\t\"pe.pedido.confirmado\",\n\t\"pe.pedido.cancelado\",\n\t\"pe.pedido.expirado\",\n\t\"pe.pedido.erro\",\n\t\"pe.transacao.recebida\",\n\t\"pe.transacao.atualizada\",\n\t\"pe.transacao.cancelada\",\n}\n\nfunc cadastrarWebhook() {\n\turlStr := strings.TrimSpace(prompt(\"  URL HTTPS do endpoint: \"))\n\tchave := strings.TrimSpace(prompt(\"  Chave secreta HMAC: \"))\n\tfmt.Println(\"\\n  Buscando eventos disponíveis...\")\n\tres := listarEventosDisponiveis()\n\teventos := eventosStatic\n\tif res != nil && res.ok {\n\t\tvar parsed struct {\n\t\t\tData []json.RawMessage `json:\"data\"`\n\t\t}\n\t\tif err := json.Unmarshal([]byte(res.body), &parsed); err == nil && len(parsed.Data) > 0 {\n\t\t\tev := []string{}\n\t\t\tfor _, e := range parsed.Data {\n\t\t\t\ts := string(e)\n\t\t\t\tif strings.HasPrefix(s, \"{\") {\n\t\t\t\t\tvar obj struct {\n\t\t\t\t\t\tEvento string `json:\"evento\"`\n\t\t\t\t\t}\n\t\t\t\t\tif json.Unmarshal(e, &obj) == nil && obj.Evento != \"\" {\n\t\t\t\t\t\tev = append(ev, obj.Evento)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tvar str string\n\t\t\t\t\tif json.Unmarshal(e, &str) == nil && str != \"\" {\n\t\t\t\t\t\tev = append(ev, str)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif len(ev) > 0 {\n\t\t\t\teventos = ev\n\t\t\t}\n\t\t}\n\t}\n\n\tfmt.Println(\"\\n  Eventos disponíveis:\")\n\tfor i, e := range eventos {\n\t\tfmt.Printf(\"    %d. %s\\n\", i+1, e)\n\t}\n\traw := strings.TrimSpace(prompt(\"\\n  Escolha o número do evento ou digite o nome: \"))\n\ttipo := raw\n\tif n, err := strconv.Atoi(raw); err == nil && n >= 1 && n <= len(eventos) {\n\t\ttipo = eventos[n-1]\n\t}\n\tif tipo == \"\" {\n\t\ttipo = strings.TrimSpace(prompt(\"  Tipo do evento: \"))\n\t}\n\tif tipo == \"\" {\n\t\tfmt.Println(\"❌ Tipo inválido.\")\n\t\treturn\n\t}\n\treq(\"POST\", \"/webhook\", map[string]any{\n\t\t\"url\":          urlStr,\n\t\t\"chaveSecreta\": chave,\n\t\t\"tipo\":         tipo,\n\t}, nil, nil, false)\n}\n\nfunc testarConectividadeWebhook() {\n\ttipo := strings.TrimSpace(prompt(\"  Tipo do evento (ex: pe.pedido.confirmado): \"))\n\tif tipo == \"\" {\n\t\treturn\n\t}\n\treq(\"POST\", \"/webhook/teste-conectividade/\"+url.PathEscape(tipo), nil, nil, nil, false)\n}\n\nfunc removerWebhook() {\n\ttipo := strings.TrimSpace(prompt(\"  Tipo do webhook a remover (ex: pe.pedido.confirmado): \"))\n\tif tipo == \"\" {\n\t\treturn\n\t}\n\treq(\"DELETE\", \"/webhook/\"+url.PathEscape(tipo), nil, nil, nil, false)\n}\n\nfunc cadastrarPlaca() {\n\tplaca := strings.ToUpper(strings.TrimSpace(prompt(\"  Placa (ex: ABC1D23): \")))\n\tdi := promptOpt(\"  Data início monitoramento ISO 8601 (Enter p/ pular): \")\n\tdf := promptOpt(\"  Data fim monitoramento ISO 8601 (Enter p/ pular): \")\n\tbody := map[string]any{\"placa\": placa}\n\tif di != \"\" {\n\t\tbody[\"dataInicioMonitoramento\"] = di\n\t}\n\tif df != \"\" {\n\t\tbody[\"dataFimMonitoramento\"] = df\n\t}\n\treq(\"POST\", \"/placas\", body, nil, nil, false)\n}\n\nfunc removerPlaca() {\n\tplaca := strings.ToUpper(strings.TrimSpace(prompt(\"  Placa a remover (ex: ABC1D23): \")))\n\treq(\"DELETE\", \"/placas/\"+placa, nil, nil, nil, false)\n}\n\nfunc listarTransacoes() {\n\tq := map[string]string{\n\t\t\"placa\":      promptOpt(\"  Filtrar por placa (Enter p/ pular): \"),\n\t\t\"status\":     promptOpt(\"  Status [PENDENTE/PAGO/CANCELADA] (Enter p/ pular): \"),\n\t\t\"dataInicio\": promptOpt(\"  Data início ISO 8601 (Enter p/ pular): \"),\n\t\t\"dataFim\":    promptOpt(\"  Data fim ISO 8601 (Enter p/ pular): \"),\n\t}\n\tpagina := promptOpt(\"  Página (Enter = 0): \")\n\tif pagina == \"\" {\n\t\tpagina = \"0\"\n\t}\n\ttamanho := promptOpt(\"  Tamanho página (Enter = 50): \")\n\tif tamanho == \"\" {\n\t\ttamanho = \"50\"\n\t}\n\tq[\"pagina\"] = pagina\n\tq[\"tamanhoPagina\"] = tamanho\n\treq(\"GET\", \"/transacoes\", nil, q, nil, false)\n}\n\nfunc criarPedido() {\n\traw := strings.TrimSpace(prompt(\"  IDs de transações separados por vírgula: \"))\n\ttransacoes := []map[string]string{}\n\tfor _, s := range strings.Split(raw, \",\") {\n\t\ts = strings.TrimSpace(s)\n\t\tif s != \"\" {\n\t\t\ttransacoes = append(transacoes, map[string]string{\"transacaoId\": s})\n\t\t}\n\t}\n\tidemp := promptOpt(\"  Chave de idempotência (Opcional): \")\n\theaders := map[string]string{}\n\tif idemp != \"\" {\n\t\theaders[\"x-chave-idempotencia\"] = idemp\n\t}\n\treq(\"POST\", \"/pedidos\", map[string]any{\"transacoes\": transacoes}, nil, headers, false)\n}\n\nfunc consultarPedido() {\n\tpid := strings.TrimSpace(prompt(\"  ID do pedido: \"))\n\treq(\"GET\", \"/pedidos/\"+pid, nil, nil, nil, false)\n}\n\nfunc confirmarPagamento() {\n\tpid := strings.TrimSpace(prompt(\"  ID do pedido: \"))\n\tref := promptOpt(\"  Referência externa (Enter p/ pular): \")\n\tdt := promptOpt(\"  Data liquidação externa ISO 8601 (Enter p/ pular): \")\n\tidemp := promptOpt(\"  Chave de idempotência (Opcional): \")\n\tbody := map[string]any{}\n\tif ref != \"\" {\n\t\tbody[\"referenciaExterna\"] = ref\n\t}\n\tif dt != \"\" {\n\t\tbody[\"dataLiquidacaoExterna\"] = dt\n\t}\n\theaders := map[string]string{}\n\tif idemp != \"\" {\n\t\theaders[\"x-chave-idempotencia\"] = idemp\n\t}\n\tvar bodyArg any\n\tif len(body) > 0 {\n\t\tbodyArg = body\n\t}\n\treq(\"POST\", \"/pedidos/\"+pid+\"/confirmar\", bodyArg, nil, headers, false)\n}\n\nfunc cancelarPedido() {\n\tpid := strings.TrimSpace(prompt(\"  ID do pedido: \"))\n\tmotivo := promptOpt(\"  Motivo [DESISTENCIA_PARCEIRO/ERRO_INTERNO/OUTROS] (Enter p/ pular): \")\n\tobs := promptOpt(\"  Observação até 500 chars (Enter p/ pular): \")\n\tidemp := promptOpt(\"  Chave de idempotência (Opcional): \")\n\tbody := map[string]any{}\n\tif motivo != \"\" {\n\t\tbody[\"motivo\"] = strings.ToUpper(motivo)\n\t}\n\tif obs != \"\" {\n\t\tbody[\"observacao\"] = obs\n\t}\n\theaders := map[string]string{}\n\tif idemp != \"\" {\n\t\theaders[\"x-chave-idempotencia\"] = idemp\n\t}\n\tvar bodyArg any\n\tif len(body) > 0 {\n\t\tbodyArg = body\n\t}\n\treq(\"POST\", \"/pedidos/\"+pid+\"/cancelar\", bodyArg, nil, headers, false)\n}\n\n// ─── Menu ─────────────────────────────────────────────────────────────────────\ntype menuItem struct {\n\tlabel  string\n\taction func()\n}\n\nfunc menu() []menuItem {\n\treturn []menuItem{\n\t\t{\"── Primeiros Passos ─────────────────\", nil},\n\t\t{\"Descobrir parceiroId (GET /me)\", descobrirParceiro},\n\t\t{\"── Webhooks Config ──────────────────\", nil},\n\t\t{\"Listar eventos disponíveis\", func() { listarEventosDisponiveis() }},\n\t\t{\"Cadastrar webhook\", cadastrarWebhook},\n\t\t{\"Listar webhooks\", listarWebhooks},\n\t\t{\"Testar conectividade webhook\", testarConectividadeWebhook},\n\t\t{\"Remover webhook\", removerWebhook},\n\t\t{\"── Placas ───────────────────────────\", nil},\n\t\t{\"Cadastrar placa\", cadastrarPlaca},\n\t\t{\"Remover placa\", removerPlaca},\n\t\t{\"── Transações ───────────────────────\", nil},\n\t\t{\"Listar transações\", listarTransacoes},\n\t\t{\"── Pedidos ──────────────────────────\", nil},\n\t\t{\"Criar pedido\", criarPedido},\n\t\t{\"Consultar pedido\", consultarPedido},\n\t\t{\"Confirmar pagamento\", confirmarPagamento},\n\t\t{\"Cancelar pedido\", cancelarPedido},\n\t}\n}\n\nfunc padRight(s string, n int) string {\n\tif len(s) >= n {\n\t\treturn s\n\t}\n\treturn s + strings.Repeat(\" \", n-len(s))\n}\n\nfunc printMenu() {\n\titems := menu()\n\tfmt.Println(\"\\n╔══════════════════════════════════════════════╗\")\n\tfmt.Println(\"║   Movvia Arrecada+ — PE API Tester           ║\")\n\tshort := strings.Replace(baseURL, \"https://\", \"\", 1)\n\tif len(short) > 43 {\n\t\tshort = short[:40] + \"...\"\n\t}\n\tfmt.Printf(\"║   %s║\\n\", padRight(short, 43))\n\tpid := \"parceiroId: (auto via GET /me)\"\n\tif cachedParceiroID != \"\" {\n\t\tpid = \"parceiroId: \" + cachedParceiroID\n\t}\n\tfmt.Printf(\"║   %s║\\n\", padRight(pid, 43))\n\tfmt.Println(\"╠══════════════════════════════════════════════╣\")\n\tidx := 1\n\tfor _, it := range items {\n\t\tif it.action == nil {\n\t\t\tfmt.Printf(\"║  %s║\\n\", padRight(it.label, 44))\n\t\t} else {\n\t\t\tline := fmt.Sprintf(\"%2d.  %s\", idx, it.label)\n\t\t\tfmt.Printf(\"║   %s║\\n\", padRight(line, 43))\n\t\t\tidx++\n\t\t}\n\t}\n\tfmt.Println(\"║                                              ║\")\n\tfmt.Println(\"║    0.  Sair                                  ║\")\n\tfmt.Println(\"╚══════════════════════════════════════════════╝\")\n}\n\nfunc main() {\n\tloadEnv()\n\tbaseURL = getenvDefault(\"BASE_URL\", \"https://hml.api.pedagioeletronico.com.br/gestao-webhooks-api/v1\")\n\tcachedParceiroID = os.Getenv(\"PE_PARCEIRO_ID\")\n\n\tif cachedParceiroID == \"\" {\n\t\tfmt.Println(\"\\n  ℹ️  PE_PARCEIRO_ID não configurado — chamando GET /me para descobrir...\")\n\t\tresolveParceiroID()\n\t}\n\n\titems := menu()\n\tactions := []func(){}\n\tfor _, it := range items {\n\t\tif it.action != nil {\n\t\t\tactions = append(actions, it.action)\n\t\t}\n\t}\n\n\tfor {\n\t\tprintMenu()\n\t\tchoice := strings.ToLower(strings.TrimSpace(prompt(\"\\nEscolha uma opção: \")))\n\t\tif choice == \"0\" || choice == \"sair\" {\n\t\t\tbreak\n\t\t}\n\t\tn, err := strconv.Atoi(choice)\n\t\tif err != nil || n < 1 || n > len(actions) {\n\t\t\tfmt.Println(\"Opção inválida.\")\n\t\t\tcontinue\n\t\t}\n\t\tfunc() {\n\t\t\tdefer func() {\n\t\t\t\tif r := recover(); r != nil {\n\t\t\t\t\tfmt.Printf(\"Erro inesperado: %v\\n\", r)\n\t\t\t\t}\n\t\t\t}()\n\t\t\tactions[n-1]()\n\t\t}()\n\t\tprompt(\"\\n[Enter para voltar ao menu]\")\n\t}\n\n\tfmt.Println(\"\\nAté logo!\")\n}\n","lang":"go"},"children":[]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"java","header":{"controls":{"copy":{}}},"source":"// Utilitário de teste para API Movvia Arrecada+ (PE)\n// Requer Java 11+. Roda com: java Simulador.java\n\nimport java.io.IOException;\nimport java.net.URI;\nimport java.net.URLEncoder;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.ArrayList;\nimport java.util.Base64;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Scanner;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\npublic class Simulador {\n\n    static final HttpClient HTTP = HttpClient.newHttpClient();\n    static final Scanner IN = new Scanner(System.in);\n    static final List<String> EVENTOS_STATIC = List.of(\n        \"pe.pedido.criado\", \"pe.pedido.confirmado\", \"pe.pedido.cancelado\",\n        \"pe.pedido.expirado\", \"pe.pedido.erro\",\n        \"pe.transacao.recebida\", \"pe.transacao.atualizada\", \"pe.transacao.cancelada\"\n    );\n\n    static String baseUrl;\n    static String cachedParceiroId;\n\n    static class Result {\n        final boolean ok;\n        final int status;\n        final String body;\n        Result(boolean ok, int status, String body) { this.ok = ok; this.status = status; this.body = body; }\n    }\n\n    // ─── Carregar .env ────────────────────────────────────────────────────────\n    static void loadEnv() {\n        Path p = Paths.get(\".env\");\n        if (!Files.exists(p)) return;\n        try {\n            for (String line : Files.readAllLines(p, StandardCharsets.UTF_8)) {\n                String t = line.trim();\n                if (t.isEmpty() || t.startsWith(\"#\")) continue;\n                int eq = t.indexOf('=');\n                if (eq < 1) continue;\n                String key = t.substring(0, eq).trim();\n                String val = t.substring(eq + 1).trim().replaceAll(\"^[\\\"']|[\\\"']$\", \"\");\n                if (!key.isEmpty() && env(key) == null) {\n                    System.setProperty(key, val);\n                }\n            }\n        } catch (IOException ignored) { /* arquivo opcional */ }\n    }\n\n    static String env(String k) {\n        String v = System.getProperty(k);\n        if (v == null || v.isEmpty()) v = System.getenv(k);\n        return (v == null || v.isEmpty()) ? null : v;\n    }\n\n    static String envOr(String k, String d) {\n        String v = env(k);\n        return v == null ? d : v;\n    }\n\n    // ─── Helpers ──────────────────────────────────────────────────────────────\n    static String authHeader() {\n        String u = env(\"PE_USERNAME\"), p = env(\"PE_PASSWORD\");\n        if (u == null || p == null) {\n            System.out.println(\"\\n❌  Configure PE_USERNAME e PE_PASSWORD no arquivo .env\");\n            System.out.println(\"   Copie .env.example → .env e preencha as credenciais.\\n\");\n            System.exit(1);\n        }\n        return \"Basic \" + Base64.getEncoder().encodeToString((u + \":\" + p).getBytes(StandardCharsets.UTF_8));\n    }\n\n    /** Constrói JSON inline a partir de pares chave/valor.\n     *  Strings ganham aspas; numbers, booleans e null não. */\n    static String json(Object... kv) {\n        StringBuilder sb = new StringBuilder(\"{\");\n        for (int i = 0; i < kv.length; i += 2) {\n            if (i > 0) sb.append(',');\n            sb.append('\"').append(escape(String.valueOf(kv[i]))).append(\"\\\":\").append(jsonValue(kv[i + 1]));\n        }\n        return sb.append('}').toString();\n    }\n\n    static String jsonValue(Object v) {\n        if (v == null) return \"null\";\n        if (v instanceof Number || v instanceof Boolean) return v.toString();\n        return \"\\\"\" + escape(String.valueOf(v)) + \"\\\"\";\n    }\n\n    static String escape(String s) {\n        return s.replace(\"\\\\\", \"\\\\\\\\\").replace(\"\\\"\", \"\\\\\\\"\").replace(\"\\n\", \"\\\\n\").replace(\"\\r\", \"\\\\r\");\n    }\n\n    // ─── Função abstratora única ──────────────────────────────────────────────\n    static Result req(String method, String path) { return req(method, path, null, null, null, false); }\n\n    static Result req(String method, String path, String body, Map<String,String> query,\n                      Map<String,String> extraHeaders, boolean quiet) {\n        StringBuilder u = new StringBuilder(baseUrl).append(path);\n        if (query != null && !query.isEmpty()) {\n            StringBuilder qs = new StringBuilder();\n            for (Map.Entry<String,String> e : query.entrySet()) {\n                if (e.getValue() == null || e.getValue().isEmpty()) continue;\n                if (qs.length() > 0) qs.append('&');\n                qs.append(URLEncoder.encode(e.getKey(), StandardCharsets.UTF_8))\n                  .append('=')\n                  .append(URLEncoder.encode(e.getValue(), StandardCharsets.UTF_8));\n            }\n            if (qs.length() > 0) u.append('?').append(qs);\n        }\n\n        HttpRequest.Builder b = HttpRequest.newBuilder(URI.create(u.toString()))\n            .header(\"Authorization\", authHeader())\n            .header(\"Content-Type\", \"application/json\");\n        if (!\"/me\".equals(path)) b.header(\"x-parceiro-id\", resolveParceiroId());\n        if (extraHeaders != null) extraHeaders.forEach(b::header);\n\n        HttpRequest.BodyPublisher pub = body == null\n            ? HttpRequest.BodyPublishers.noBody()\n            : HttpRequest.BodyPublishers.ofString(body, StandardCharsets.UTF_8);\n        b.method(method, pub);\n\n        if (!quiet) {\n            System.out.println(\"\\n→ \" + method + \" \" + u);\n            if (body != null) System.out.println(\"  Body: \" + body);\n            if (extraHeaders != null && !extraHeaders.isEmpty())\n                System.out.println(\"  Headers extras: \" + extraHeaders);\n        }\n\n        try {\n            HttpResponse<String> resp = HTTP.send(b.build(), HttpResponse.BodyHandlers.ofString());\n            boolean ok = resp.statusCode() < 400;\n            if (!quiet) {\n                System.out.println((ok ? \"✅\" : \"❌\") + \" \" + resp.statusCode());\n                System.out.println(resp.body());\n            }\n            return new Result(ok, resp.statusCode(), resp.body());\n        } catch (IOException | InterruptedException e) {\n            System.out.println(\"❌  Erro de rede: \" + e.getMessage());\n            return null;\n        }\n    }\n\n    static String resolveParceiroId() {\n        if (cachedParceiroId != null && !cachedParceiroId.isEmpty()) return cachedParceiroId;\n        Result r = req(\"GET\", \"/me\", null, null, null, true);\n        if (r == null || !r.ok) {\n            System.out.println(\"❌  Não foi possível descobrir o parceiroId via GET /me\");\n            System.exit(1);\n        }\n        Matcher m = Pattern.compile(\"\\\"parceiroId\\\"\\\\s*:\\\\s*\\\"([^\\\"]+)\\\"\").matcher(r.body);\n        if (!m.find()) {\n            System.out.println(\"❌  Resposta sem data.parceiroId: \" + r.body);\n            System.exit(1);\n        }\n        cachedParceiroId = m.group(1);\n        System.out.println(\"  ℹ️  parceiroId descoberto via GET /me: \" + cachedParceiroId);\n        return cachedParceiroId;\n    }\n\n    // ─── Prompts ──────────────────────────────────────────────────────────────\n    static String prompt(String q) {\n        System.out.print(q);\n        return IN.hasNextLine() ? IN.nextLine() : \"\";\n    }\n\n    static String promptOpt(String q) {\n        String v = prompt(q).trim();\n        return v.isEmpty() ? null : v;\n    }\n\n    // ─── Ações ────────────────────────────────────────────────────────────────\n    static void descobrirParceiro() { req(\"GET\", \"/me\"); }\n\n    static Result listarEventosDisponiveis() { return req(\"GET\", \"/webhook/eventos\"); }\n\n    static void cadastrarWebhook() {\n        String url = prompt(\"  URL HTTPS do endpoint: \").trim();\n        String chave = prompt(\"  Chave secreta HMAC: \").trim();\n        System.out.println(\"\\n  Buscando eventos disponíveis...\");\n        Result r = listarEventosDisponiveis();\n        List<String> eventos = EVENTOS_STATIC;\n        if (r != null && r.ok) {\n            List<String> parsed = new ArrayList<>();\n            // formato {data: [{evento: \"...\"}]}\n            Matcher me = Pattern.compile(\"\\\"evento\\\"\\\\s*:\\\\s*\\\"([^\\\"]+)\\\"\").matcher(r.body);\n            while (me.find()) parsed.add(me.group(1));\n            // fallback: data: [\"...\", \"...\"]\n            if (parsed.isEmpty()) {\n                Matcher md = Pattern.compile(\"\\\"data\\\"\\\\s*:\\\\s*\\\\[([^\\\\]]+)\\\\]\").matcher(r.body);\n                if (md.find()) {\n                    Matcher ms = Pattern.compile(\"\\\"([^\\\"]+)\\\"\").matcher(md.group(1));\n                    while (ms.find()) parsed.add(ms.group(1));\n                }\n            }\n            if (!parsed.isEmpty()) eventos = parsed;\n        }\n        System.out.println(\"\\n  Eventos disponíveis:\");\n        for (int i = 0; i < eventos.size(); i++)\n            System.out.println(\"    \" + (i + 1) + \". \" + eventos.get(i));\n        String raw = prompt(\"\\n  Escolha o número do evento ou digite o nome: \").trim();\n        String tipo = raw;\n        try {\n            int idx = Integer.parseInt(raw);\n            if (idx >= 1 && idx <= eventos.size()) tipo = eventos.get(idx - 1);\n        } catch (NumberFormatException ignored) {}\n        if (tipo.isEmpty()) tipo = prompt(\"  Tipo do evento: \").trim();\n        if (tipo.isEmpty()) { System.out.println(\"❌ Tipo inválido.\"); return; }\n        req(\"POST\", \"/webhook\", json(\"url\", url, \"chaveSecreta\", chave, \"tipo\", tipo), null, null, false);\n    }\n\n    static void listarWebhooks() { req(\"GET\", \"/webhook\"); }\n\n    static void testarConectividadeWebhook() {\n        String tipo = prompt(\"  Tipo do evento (ex: pe.pedido.confirmado): \").trim();\n        if (tipo.isEmpty()) return;\n        req(\"POST\", \"/webhook/teste-conectividade/\" + URLEncoder.encode(tipo, StandardCharsets.UTF_8),\n            null, null, null, false);\n    }\n\n    static void removerWebhook() {\n        String tipo = prompt(\"  Tipo do webhook a remover (ex: pe.pedido.confirmado): \").trim();\n        if (tipo.isEmpty()) return;\n        req(\"DELETE\", \"/webhook/\" + URLEncoder.encode(tipo, StandardCharsets.UTF_8),\n            null, null, null, false);\n    }\n\n    static void cadastrarPlaca() {\n        String placa = prompt(\"  Placa (ex: ABC1D23): \").trim().toUpperCase();\n        String di = promptOpt(\"  Data início monitoramento ISO 8601 (Enter p/ pular): \");\n        String df = promptOpt(\"  Data fim monitoramento ISO 8601 (Enter p/ pular): \");\n        StringBuilder body = new StringBuilder(\"{\\\"placa\\\":\\\"\").append(escape(placa)).append(\"\\\"\");\n        if (di != null) body.append(\",\\\"dataInicioMonitoramento\\\":\\\"\").append(escape(di)).append(\"\\\"\");\n        if (df != null) body.append(\",\\\"dataFimMonitoramento\\\":\\\"\").append(escape(df)).append(\"\\\"\");\n        body.append(\"}\");\n        req(\"POST\", \"/placas\", body.toString(), null, null, false);\n    }\n\n    static void removerPlaca() {\n        String placa = prompt(\"  Placa a remover (ex: ABC1D23): \").trim().toUpperCase();\n        req(\"DELETE\", \"/placas/\" + placa);\n    }\n\n    static void listarTransacoes() {\n        Map<String,String> q = new LinkedHashMap<>();\n        q.put(\"placa\", promptOpt(\"  Filtrar por placa (Enter p/ pular): \"));\n        q.put(\"status\", promptOpt(\"  Status [PENDENTE/PAGO/CANCELADA] (Enter p/ pular): \"));\n        q.put(\"dataInicio\", promptOpt(\"  Data início ISO 8601 (Enter p/ pular): \"));\n        q.put(\"dataFim\", promptOpt(\"  Data fim ISO 8601 (Enter p/ pular): \"));\n        String pagina = promptOpt(\"  Página (Enter = 0): \");  if (pagina == null) pagina = \"0\";\n        String tamanho = promptOpt(\"  Tamanho página (Enter = 50): \"); if (tamanho == null) tamanho = \"50\";\n        q.put(\"pagina\", pagina);\n        q.put(\"tamanhoPagina\", tamanho);\n        q.values().removeIf(Objects::isNull);\n        req(\"GET\", \"/transacoes\", null, q, null, false);\n    }\n\n    static void criarPedido() {\n        String raw = prompt(\"  IDs de transações separados por vírgula: \").trim();\n        StringBuilder transacoes = new StringBuilder(\"[\");\n        boolean first = true;\n        for (String s : raw.split(\",\")) {\n            String id = s.trim();\n            if (id.isEmpty()) continue;\n            if (!first) transacoes.append(',');\n            transacoes.append(\"{\\\"transacaoId\\\":\\\"\").append(escape(id)).append(\"\\\"}\");\n            first = false;\n        }\n        transacoes.append(\"]\");\n        String idemp = promptOpt(\"  Chave de idempotência (Opcional): \");\n        Map<String,String> headers = idemp != null ? Map.of(\"x-chave-idempotencia\", idemp) : null;\n        req(\"POST\", \"/pedidos\", \"{\\\"transacoes\\\":\" + transacoes + \"}\", null, headers, false);\n    }\n\n    static void consultarPedido() {\n        String pid = prompt(\"  ID do pedido: \").trim();\n        req(\"GET\", \"/pedidos/\" + pid);\n    }\n\n    static void confirmarPagamento() {\n        String pid = prompt(\"  ID do pedido: \").trim();\n        String ref = promptOpt(\"  Referência externa (Enter p/ pular): \");\n        String dt = promptOpt(\"  Data liquidação externa ISO 8601 (Enter p/ pular): \");\n        String idemp = promptOpt(\"  Chave de idempotência (Opcional): \");\n        StringBuilder body = new StringBuilder(\"{\");\n        boolean first = true;\n        if (ref != null) {\n            body.append(\"\\\"referenciaExterna\\\":\\\"\").append(escape(ref)).append(\"\\\"\");\n            first = false;\n        }\n        if (dt != null) {\n            if (!first) body.append(',');\n            body.append(\"\\\"dataLiquidacaoExterna\\\":\\\"\").append(escape(dt)).append(\"\\\"\");\n        }\n        body.append(\"}\");\n        Map<String,String> headers = idemp != null ? Map.of(\"x-chave-idempotencia\", idemp) : null;\n        String b = body.length() > 2 ? body.toString() : null;\n        req(\"POST\", \"/pedidos/\" + pid + \"/confirmar\", b, null, headers, false);\n    }\n\n    static void cancelarPedido() {\n        String pid = prompt(\"  ID do pedido: \").trim();\n        String motivo = promptOpt(\"  Motivo [DESISTENCIA_PARCEIRO/ERRO_INTERNO/OUTROS] (Enter p/ pular): \");\n        String obs = promptOpt(\"  Observação até 500 chars (Enter p/ pular): \");\n        String idemp = promptOpt(\"  Chave de idempotência (Opcional): \");\n        StringBuilder body = new StringBuilder(\"{\");\n        boolean first = true;\n        if (motivo != null) {\n            body.append(\"\\\"motivo\\\":\\\"\").append(escape(motivo.toUpperCase())).append(\"\\\"\");\n            first = false;\n        }\n        if (obs != null) {\n            if (!first) body.append(',');\n            body.append(\"\\\"observacao\\\":\\\"\").append(escape(obs)).append(\"\\\"\");\n        }\n        body.append(\"}\");\n        Map<String,String> headers = idemp != null ? Map.of(\"x-chave-idempotencia\", idemp) : null;\n        String b = body.length() > 2 ? body.toString() : null;\n        req(\"POST\", \"/pedidos/\" + pid + \"/cancelar\", b, null, headers, false);\n    }\n\n    // ─── Menu ─────────────────────────────────────────────────────────────────\n    static class Item {\n        final String label;\n        final Runnable action;\n        Item(String l, Runnable a) { label = l; action = a; }\n    }\n\n    static List<Item> menu() {\n        return List.of(\n            new Item(\"── Primeiros Passos ─────────────────\", null),\n            new Item(\"Descobrir parceiroId (GET /me)\", Simulador::descobrirParceiro),\n            new Item(\"── Webhooks Config ──────────────────\", null),\n            new Item(\"Listar eventos disponíveis\", () -> listarEventosDisponiveis()),\n            new Item(\"Cadastrar webhook\", Simulador::cadastrarWebhook),\n            new Item(\"Listar webhooks\", Simulador::listarWebhooks),\n            new Item(\"Testar conectividade webhook\", Simulador::testarConectividadeWebhook),\n            new Item(\"Remover webhook\", Simulador::removerWebhook),\n            new Item(\"── Placas ───────────────────────────\", null),\n            new Item(\"Cadastrar placa\", Simulador::cadastrarPlaca),\n            new Item(\"Remover placa\", Simulador::removerPlaca),\n            new Item(\"── Transações ───────────────────────\", null),\n            new Item(\"Listar transações\", Simulador::listarTransacoes),\n            new Item(\"── Pedidos ──────────────────────────\", null),\n            new Item(\"Criar pedido\", Simulador::criarPedido),\n            new Item(\"Consultar pedido\", Simulador::consultarPedido),\n            new Item(\"Confirmar pagamento\", Simulador::confirmarPagamento),\n            new Item(\"Cancelar pedido\", Simulador::cancelarPedido)\n        );\n    }\n\n    static String padRight(String s, int n) {\n        return s.length() >= n ? s : s + \" \".repeat(n - s.length());\n    }\n\n    static void printMenu() {\n        System.out.println(\"\\n╔══════════════════════════════════════════════╗\");\n        System.out.println(\"║   Movvia Arrecada+ — PE API Tester           ║\");\n        String shortUrl = baseUrl.replace(\"https://\", \"\");\n        if (shortUrl.length() > 43) shortUrl = shortUrl.substring(0, 40) + \"...\";\n        System.out.println(\"║   \" + padRight(shortUrl, 43) + \"║\");\n        String pid = cachedParceiroId != null\n            ? \"parceiroId: \" + cachedParceiroId\n            : \"parceiroId: (auto via GET /me)\";\n        System.out.println(\"║   \" + padRight(pid, 43) + \"║\");\n        System.out.println(\"╠══════════════════════════════════════════════╣\");\n        int idx = 1;\n        for (Item it : menu()) {\n            if (it.action == null) {\n                System.out.println(\"║  \" + padRight(it.label, 44) + \"║\");\n            } else {\n                System.out.println(\"║   \" + padRight(String.format(\"%2d.  %s\", idx, it.label), 43) + \"║\");\n                idx++;\n            }\n        }\n        System.out.println(\"║                                              ║\");\n        System.out.println(\"║    0.  Sair                                  ║\");\n        System.out.println(\"╚══════════════════════════════════════════════╝\");\n    }\n\n    public static void main(String[] args) {\n        loadEnv();\n        baseUrl = envOr(\"BASE_URL\", \"https://hml.api.pedagioeletronico.com.br/gestao-webhooks-api/v1\");\n        cachedParceiroId = env(\"PE_PARCEIRO_ID\");\n\n        if (cachedParceiroId == null) {\n            System.out.println(\"\\n  ℹ️  PE_PARCEIRO_ID não configurado — chamando GET /me para descobrir...\");\n            resolveParceiroId();\n        }\n\n        List<Runnable> actions = new ArrayList<>();\n        for (Item it : menu()) if (it.action != null) actions.add(it.action);\n\n        while (true) {\n            printMenu();\n            String choice = prompt(\"\\nEscolha uma opção: \").trim().toLowerCase();\n            if (choice.equals(\"0\") || choice.equals(\"sair\")) break;\n            int n;\n            try { n = Integer.parseInt(choice); }\n            catch (NumberFormatException e) { System.out.println(\"Opção inválida.\"); continue; }\n            if (n < 1 || n > actions.size()) { System.out.println(\"Opção inválida.\"); continue; }\n            try { actions.get(n - 1).run(); }\n            catch (Exception e) { System.out.println(\"Erro inesperado: \" + e.getMessage()); }\n            prompt(\"\\n[Enter para voltar ao menu]\");\n        }\n        System.out.println(\"\\nAté logo!\\n\");\n    }\n}\n","lang":"java"},"children":[]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"#!/usr/bin/env bash\n# Utilitário de teste para API Movvia Arrecada+ (PE)\n# Requer: bash 4+, curl, jq\nset -o pipefail\n\n# ─── Pré-requisitos ───────────────────────────────────────────────────────────\ncommand -v curl >/dev/null 2>&1 || {\n    echo \"❌  curl não encontrado. Instale curl e rode novamente.\"\n    exit 1\n}\ncommand -v jq >/dev/null 2>&1 || {\n    echo \"❌  jq não encontrado. Instale jq (brew install jq / apt install jq) e rode novamente.\"\n    exit 1\n}\n\n# ─── Carregar .env ────────────────────────────────────────────────────────────\nDIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nif [ -f \"$DIR/.env\" ]; then\n    while IFS= read -r line || [ -n \"$line\" ]; do\n        case \"$line\" in\n            ''|\\#*) continue ;;\n            *=*)\n                key=\"${line%%=*}\"\n                val=\"${line#*=}\"\n                key=\"$(echo \"$key\" | tr -d '[:space:]')\"\n                val=\"${val#\\\"}\"; val=\"${val%\\\"}\"\n                val=\"${val#\\'}\"; val=\"${val%\\'}\"\n                [ -n \"$key\" ] && [ -z \"${!key:-}\" ] && export \"$key=$val\"\n                ;;\n        esac\n    done < \"$DIR/.env\"\nfi\n\nBASE_URL=\"${BASE_URL:-https://hml.api.pedagioeletronico.com.br/gestao-webhooks-api/v1}\"\nCACHED_PARCEIRO_ID=\"${PE_PARCEIRO_ID:-}\"\n\ncheck_creds() {\n    if [ -z \"${PE_USERNAME:-}\" ] || [ -z \"${PE_PASSWORD:-}\" ]; then\n        echo\n        echo \"❌  Configure PE_USERNAME e PE_PASSWORD no arquivo .env\"\n        echo \"   Copie .env.example → .env e preencha as credenciais.\"\n        echo\n        exit 1\n    fi\n}\n\nauth_header() {\n    printf 'Basic %s' \"$(printf '%s:%s' \"$PE_USERNAME\" \"$PE_PASSWORD\" | base64 | tr -d '\\n')\"\n}\n\nuri_encode() {\n    jq -rn --arg s \"$1\" '$s|@uri'\n}\n\n# ─── Resolução de parceiroId ──────────────────────────────────────────────────\nresolve_parceiro_id() {\n    if [ -n \"$CACHED_PARCEIRO_ID\" ]; then\n        printf '%s' \"$CACHED_PARCEIRO_ID\"\n        return\n    fi\n    local body status\n    status=$(curl -sS -o /tmp/movvia_me_body -w '%{http_code}' \\\n        -H \"Authorization: $(auth_header)\" \\\n        -H \"Content-Type: application/json\" \\\n        \"$BASE_URL/me\" 2>/tmp/movvia_me_err) || {\n        echo \"❌  Erro de rede em GET /me: $(cat /tmp/movvia_me_err)\" >&2\n        exit 1\n    }\n    if [ \"$status\" -ge 400 ]; then\n        echo \"❌  Não foi possível descobrir o parceiroId via GET /me ($status)\" >&2\n        cat /tmp/movvia_me_body >&2\n        exit 1\n    fi\n    body=$(cat /tmp/movvia_me_body)\n    local pid\n    pid=$(echo \"$body\" | jq -r '.data.parceiroId // empty')\n    if [ -z \"$pid\" ]; then\n        echo \"❌  Resposta sem data.parceiroId: $body\" >&2\n        exit 1\n    fi\n    CACHED_PARCEIRO_ID=\"$pid\"\n    echo \"  ℹ️  parceiroId descoberto via GET /me: $CACHED_PARCEIRO_ID\" >&2\n    printf '%s' \"$pid\"\n}\n\n# ─── Função abstratora ────────────────────────────────────────────────────────\n# req METHOD PATH [BODY] [QUERY_STRING] [EXTRA_HEADER...]\nreq() {\n    local method=\"$1\" path=\"$2\" body=\"${3:-}\" query=\"${4:-}\"\n    if [ \"$#\" -ge 4 ]; then shift 4; else shift \"$#\"; fi\n\n    local url=\"$BASE_URL$path\"\n    [ -n \"$query\" ] && url=\"$url?$query\"\n\n    local -a headers\n    headers=(-H \"Authorization: $(auth_header)\" -H \"Content-Type: application/json\")\n    if [ \"$path\" != \"/me\" ]; then\n        local pid\n        pid=$(resolve_parceiro_id)\n        headers+=(-H \"x-parceiro-id: $pid\")\n    fi\n    while [ \"$#\" -gt 0 ]; do\n        headers+=(-H \"$1\")\n        shift\n    done\n\n    echo\n    echo \"→ $method $url\"\n    [ -n \"$body\" ] && echo \"  Body: $body\"\n\n    local -a curl_args\n    curl_args=(-sS -o /tmp/movvia_resp_body -w '%{http_code}' -X \"$method\" \"$url\" \"${headers[@]}\")\n    [ -n \"$body\" ] && curl_args+=(-d \"$body\")\n\n    local status\n    status=$(curl \"${curl_args[@]}\" 2>/tmp/movvia_resp_err) || {\n        echo \"❌  Erro de rede: $(cat /tmp/movvia_resp_err)\"\n        return 1\n    }\n\n    local mark=\"❌\"\n    [ \"$status\" -lt 400 ] && mark=\"✅\"\n    echo \"$mark $status\"\n    if [ -s /tmp/movvia_resp_body ]; then\n        if jq -e . /tmp/movvia_resp_body >/dev/null 2>&1; then\n            jq . /tmp/movvia_resp_body\n        else\n            cat /tmp/movvia_resp_body\n        fi\n    fi\n}\n\n# ─── Prompts ──────────────────────────────────────────────────────────────────\nask() {\n    local q=\"$1\" v\n    read -r -p \"$q\" v\n    printf '%s' \"$v\"\n}\n\n# ─── Ações ────────────────────────────────────────────────────────────────────\nEVENTOS_STATIC=(\n    \"pe.pedido.criado\"\n    \"pe.pedido.confirmado\"\n    \"pe.pedido.cancelado\"\n    \"pe.pedido.expirado\"\n    \"pe.pedido.erro\"\n    \"pe.transacao.recebida\"\n    \"pe.transacao.atualizada\"\n    \"pe.transacao.cancelada\"\n)\n\ndescobrir_parceiro() { req GET \"/me\"; }\nlistar_eventos_disponiveis() { req GET \"/webhook/eventos\"; }\nlistar_webhooks() { req GET \"/webhook\"; }\n\ncadastrar_webhook() {\n    local url chave\n    url=$(ask \"  URL HTTPS do endpoint: \")\n    chave=$(ask \"  Chave secreta HMAC: \")\n    echo\n    echo \"  Buscando eventos disponíveis...\"\n    local body status\n    local pid\n    pid=$(resolve_parceiro_id)\n    body=$(curl -sS \\\n        -H \"Authorization: $(auth_header)\" \\\n        -H \"x-parceiro-id: $pid\" \\\n        \"$BASE_URL/webhook/eventos\" 2>/dev/null) || body=\"\"\n    local -a eventos\n    if [ -n \"$body\" ]; then\n        mapfile -t eventos < <(echo \"$body\" | jq -r '\n            .data | if type == \"array\" then\n                .[] | (if type == \"string\" then . else .evento end)\n            else empty end' 2>/dev/null)\n    fi\n    if [ \"${#eventos[@]}\" -eq 0 ]; then\n        eventos=(\"${EVENTOS_STATIC[@]}\")\n    fi\n    echo\n    echo \"  Eventos disponíveis:\"\n    local i=1\n    for e in \"${eventos[@]}\"; do\n        printf \"    %d. %s\\n\" \"$i\" \"$e\"\n        i=$((i + 1))\n    done\n    echo\n    local raw tipo\n    raw=$(ask \"  Escolha o número do evento ou digite o nome: \")\n    if [[ \"$raw\" =~ ^[0-9]+$ ]] && [ \"$raw\" -ge 1 ] && [ \"$raw\" -le \"${#eventos[@]}\" ]; then\n        tipo=\"${eventos[$((raw - 1))]}\"\n    else\n        tipo=\"$raw\"\n    fi\n    if [ -z \"$tipo\" ]; then\n        echo \"❌ Tipo inválido.\"\n        return\n    fi\n    local body_json\n    body_json=$(jq -nc --arg url \"$url\" --arg c \"$chave\" --arg t \"$tipo\" \\\n        '{url:$url, chaveSecreta:$c, tipo:$t}')\n    req POST \"/webhook\" \"$body_json\"\n}\n\ntestar_conectividade_webhook() {\n    local tipo enc\n    tipo=$(ask \"  Tipo do evento (ex: pe.pedido.confirmado): \")\n    [ -z \"$tipo\" ] && return\n    enc=$(uri_encode \"$tipo\")\n    req POST \"/webhook/teste-conectividade/$enc\"\n}\n\nremover_webhook() {\n    local tipo enc\n    tipo=$(ask \"  Tipo do webhook a remover (ex: pe.pedido.confirmado): \")\n    [ -z \"$tipo\" ] && return\n    enc=$(uri_encode \"$tipo\")\n    req DELETE \"/webhook/$enc\"\n}\n\ncadastrar_placa() {\n    local placa di df body\n    placa=$(ask \"  Placa (ex: ABC1D23): \")\n    placa=$(echo \"$placa\" | tr '[:lower:]' '[:upper:]')\n    di=$(ask \"  Data início monitoramento ISO 8601 (Enter p/ pular): \")\n    df=$(ask \"  Data fim monitoramento ISO 8601 (Enter p/ pular): \")\n    body=$(jq -nc --arg p \"$placa\" --arg di \"$di\" --arg df \"$df\" '\n        {placa:$p}\n        + (if $di != \"\" then {dataInicioMonitoramento:$di} else {} end)\n        + (if $df != \"\" then {dataFimMonitoramento:$df} else {} end)')\n    req POST \"/placas\" \"$body\"\n}\n\nremover_placa() {\n    local placa\n    placa=$(ask \"  Placa a remover (ex: ABC1D23): \")\n    placa=$(echo \"$placa\" | tr '[:lower:]' '[:upper:]')\n    req DELETE \"/placas/$placa\"\n}\n\nbuild_query() {\n    local out=\"\"\n    while [ \"$#\" -gt 0 ]; do\n        local k=\"$1\" v=\"$2\"\n        shift 2\n        [ -z \"$v\" ] && continue\n        local enc_k enc_v\n        enc_k=$(uri_encode \"$k\")\n        enc_v=$(uri_encode \"$v\")\n        [ -n \"$out\" ] && out=\"$out&\"\n        out=\"$out$enc_k=$enc_v\"\n    done\n    printf '%s' \"$out\"\n}\n\nlistar_transacoes() {\n    local placa status di df pagina tamanho q\n    placa=$(ask \"  Filtrar por placa (Enter p/ pular): \")\n    status=$(ask \"  Status [PENDENTE/PAGO/CANCELADA] (Enter p/ pular): \")\n    di=$(ask \"  Data início ISO 8601 (Enter p/ pular): \")\n    df=$(ask \"  Data fim ISO 8601 (Enter p/ pular): \")\n    pagina=$(ask \"  Página (Enter = 0): \"); pagina=\"${pagina:-0}\"\n    tamanho=$(ask \"  Tamanho página (Enter = 50): \"); tamanho=\"${tamanho:-50}\"\n    q=$(build_query placa \"$placa\" status \"$status\" \\\n        dataInicio \"$di\" dataFim \"$df\" \\\n        pagina \"$pagina\" tamanhoPagina \"$tamanho\")\n    req GET \"/transacoes\" \"\" \"$q\"\n}\n\ncriar_pedido() {\n    local raw idemp body\n    raw=$(ask \"  IDs de transações separados por vírgula: \")\n    body=$(jq -nc --arg s \"$raw\" '{\n        transacoes: ($s | split(\",\")\n            | map(. | gsub(\"^\\\\s+|\\\\s+$\"; \"\"))\n            | map(select(length > 0))\n            | map({transacaoId: .}))\n    }')\n    idemp=$(ask \"  Chave de idempotência (Opcional): \")\n    if [ -n \"$idemp\" ]; then\n        req POST \"/pedidos\" \"$body\" \"\" \"x-chave-idempotencia: $idemp\"\n    else\n        req POST \"/pedidos\" \"$body\"\n    fi\n}\n\nconsultar_pedido() {\n    local pid\n    pid=$(ask \"  ID do pedido: \")\n    req GET \"/pedidos/$pid\"\n}\n\nconfirmar_pagamento() {\n    local pid ref dt idemp body\n    pid=$(ask \"  ID do pedido: \")\n    ref=$(ask \"  Referência externa (Enter p/ pular): \")\n    dt=$(ask \"  Data liquidação externa ISO 8601 (Enter p/ pular): \")\n    idemp=$(ask \"  Chave de idempotência (Opcional): \")\n    body=$(jq -nc --arg ref \"$ref\" --arg dt \"$dt\" '\n        (if $ref != \"\" then {referenciaExterna:$ref} else {} end)\n        + (if $dt != \"\" then {dataLiquidacaoExterna:$dt} else {} end)')\n    [ \"$body\" = \"{}\" ] && body=\"\"\n    if [ -n \"$idemp\" ]; then\n        req POST \"/pedidos/$pid/confirmar\" \"$body\" \"\" \"x-chave-idempotencia: $idemp\"\n    else\n        req POST \"/pedidos/$pid/confirmar\" \"$body\"\n    fi\n}\n\ncancelar_pedido() {\n    local pid motivo obs idemp body\n    pid=$(ask \"  ID do pedido: \")\n    motivo=$(ask \"  Motivo [DESISTENCIA_PARCEIRO/ERRO_INTERNO/OUTROS] (Enter p/ pular): \")\n    obs=$(ask \"  Observação até 500 chars (Enter p/ pular): \")\n    idemp=$(ask \"  Chave de idempotência (Opcional): \")\n    if [ -n \"$motivo\" ]; then\n        motivo=$(echo \"$motivo\" | tr '[:lower:]' '[:upper:]')\n    fi\n    body=$(jq -nc --arg m \"$motivo\" --arg o \"$obs\" '\n        (if $m != \"\" then {motivo:$m} else {} end)\n        + (if $o != \"\" then {observacao:$o} else {} end)')\n    [ \"$body\" = \"{}\" ] && body=\"\"\n    if [ -n \"$idemp\" ]; then\n        req POST \"/pedidos/$pid/cancelar\" \"$body\" \"\" \"x-chave-idempotencia: $idemp\"\n    else\n        req POST \"/pedidos/$pid/cancelar\" \"$body\"\n    fi\n}\n\n# ─── Menu ─────────────────────────────────────────────────────────────────────\nMENU_LABELS=(\n    \"── Primeiros Passos ─────────────────\"\n    \"Descobrir parceiroId (GET /me)\"\n    \"── Webhooks Config ──────────────────\"\n    \"Listar eventos disponíveis\"\n    \"Cadastrar webhook\"\n    \"Listar webhooks\"\n    \"Testar conectividade webhook\"\n    \"Remover webhook\"\n    \"── Placas ───────────────────────────\"\n    \"Cadastrar placa\"\n    \"Remover placa\"\n    \"── Transações ───────────────────────\"\n    \"Listar transações\"\n    \"── Pedidos ──────────────────────────\"\n    \"Criar pedido\"\n    \"Consultar pedido\"\n    \"Confirmar pagamento\"\n    \"Cancelar pedido\"\n)\n\nMENU_ACTIONS=(\n    \"\"\n    \"descobrir_parceiro\"\n    \"\"\n    \"listar_eventos_disponiveis\"\n    \"cadastrar_webhook\"\n    \"listar_webhooks\"\n    \"testar_conectividade_webhook\"\n    \"remover_webhook\"\n    \"\"\n    \"cadastrar_placa\"\n    \"remover_placa\"\n    \"\"\n    \"listar_transacoes\"\n    \"\"\n    \"criar_pedido\"\n    \"consultar_pedido\"\n    \"confirmar_pagamento\"\n    \"cancelar_pedido\"\n)\n\nprint_menu() {\n    echo\n    echo \"╔══════════════════════════════════════════════╗\"\n    echo \"║   Movvia Arrecada+ — PE API Tester           ║\"\n    local short=\"${BASE_URL#https://}\"\n    if [ \"${#short}\" -gt 43 ]; then\n        short=\"${short:0:40}...\"\n    fi\n    printf \"║   %-43s║\\n\" \"$short\"\n    local pid\n    if [ -n \"$CACHED_PARCEIRO_ID\" ]; then\n        pid=\"parceiroId: $CACHED_PARCEIRO_ID\"\n    else\n        pid=\"parceiroId: (auto via GET /me)\"\n    fi\n    printf \"║   %-43s║\\n\" \"$pid\"\n    echo \"╠══════════════════════════════════════════════╣\"\n    local idx=1\n    for i in \"${!MENU_LABELS[@]}\"; do\n        if [ -z \"${MENU_ACTIONS[$i]}\" ]; then\n            printf \"║  %-44s║\\n\" \"${MENU_LABELS[$i]}\"\n        else\n            printf \"║   %2d.  %-39s║\\n\" \"$idx\" \"${MENU_LABELS[$i]}\"\n            idx=$((idx + 1))\n        fi\n    done\n    echo \"║                                              ║\"\n    echo \"║    0.  Sair                                  ║\"\n    echo \"╚══════════════════════════════════════════════╝\"\n}\n\nmain() {\n    check_creds\n    if [ -z \"$CACHED_PARCEIRO_ID\" ]; then\n        echo\n        echo \"  ℹ️  PE_PARCEIRO_ID não configurado — chamando GET /me para descobrir...\"\n        CACHED_PARCEIRO_ID=$(resolve_parceiro_id)\n    fi\n\n    local -a active=()\n    for a in \"${MENU_ACTIONS[@]}\"; do\n        [ -n \"$a\" ] && active+=(\"$a\")\n    done\n\n    while true; do\n        print_menu\n        echo\n        local choice\n        read -r -p \"Escolha uma opção: \" choice\n        choice=\"${choice,,}\"\n        if [ \"$choice\" = \"0\" ] || [ \"$choice\" = \"sair\" ]; then\n            break\n        fi\n        if ! [[ \"$choice\" =~ ^[0-9]+$ ]]; then\n            echo \"Opção inválida.\"\n            continue\n        fi\n        if [ \"$choice\" -lt 1 ] || [ \"$choice\" -gt \"${#active[@]}\" ]; then\n            echo \"Opção inválida.\"\n            continue\n        fi\n        \"${active[$((choice - 1))]}\" || true\n        echo\n        read -r -p \"[Enter para voltar ao menu] \" _\n    done\n\n    echo\n    echo \"Até logo!\"\n    echo\n}\n\nmain\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"passo-3--salvar-arquivos-auxiliares","__idx":4},"children":["Passo 3 — Salvar arquivos auxiliares"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Crie ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":[".env.example"]}," com o template de credenciais:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"# Credenciais Basic Auth (fornecidas pela Movvia no onboarding)\nPE_USERNAME=seu_usuario\nPE_PASSWORD=sua_senha\n\n# ID do Parceiro — OPCIONAL. Se omitido, o script chama GET /me automaticamente\n# para descobrir o parceiroId vinculado às credenciais acima.\n# PE_PARCEIRO_ID=prc_conectcar\n\n# URL base — padrão é homologação (descomente para sobrescrever)\n# BASE_URL=https://hml.api.pedagioeletronico.com.br/gestao-webhooks-api/v1\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["E o ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["start.sh"]},", que pede usuário/senha na primeira run e despacha pra linguagem escolhida:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"#!/bin/sh\n# Bootstrap do simulador. Onboarding de credenciais na primeira run e despacho\n# pra linguagem escolhida via --lang=node|python|go|java|bash (default node).\nDIR=\"$(cd \"$(dirname \"$0\")\" && pwd)\"\ncd \"$DIR\" || exit 1\n\nSIM_LANG=\"node\"\nfor arg in \"$@\"; do\n    case \"$arg\" in\n        --lang=*) SIM_LANG=\"${arg#--lang=}\" ;;\n        -h|--help)\n            echo \"Uso: $0 [--lang=node|python|go|java|bash]\"\n            echo \"Default: node\"\n            exit 0\n            ;;\n    esac\ndone\n\nif [ ! -f .env ]; then\n    echo \"Primeira execução — configurando credenciais.\"\n    printf \"Usuário: \"; read -r u\n    printf \"Senha:   \"; stty -echo; read -r p; stty echo; echo\n    printf \"PE_USERNAME=%s\\nPE_PASSWORD=%s\\n\" \"$u\" \"$p\" > .env\n    echo \"Credenciais salvas em .env.\"\nfi\n\ncase \"$SIM_LANG\" in\n    node)        node simulador.js ;;\n    python|py)   python3 simulador.py ;;\n    go)          go run simulador.go ;;\n    java)        java Simulador.java ;;\n    bash|sh)     bash simulador.sh ;;\n    *)\n        echo \"Linguagem inválida: $SIM_LANG\"\n        echo \"Uso: $0 [--lang=node|python|go|java|bash]\"\n        exit 1\n        ;;\nesac\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"passo-4--configurar-credenciais-e-rodar","__idx":5},"children":["Passo 4 — Configurar credenciais e rodar"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"sh","header":{"controls":{"copy":{}}},"source":"chmod +x start.sh\n./start.sh                 # roda Node.js (default)\n./start.sh --lang=python   # Python\n./start.sh --lang=go       # Go\n./start.sh --lang=java     # Java\n./start.sh --lang=bash     # Bash + curl + jq\n","lang":"sh"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Na primeira execução, o ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["start.sh"]}," pergunta usuário e senha, salva no ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":[".env"]}," e abre o menu. As próximas execuções usam o ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":[".env"]}," direto."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Se preferir invocar a linguagem manualmente, sem o ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["start.sh"]},":"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"sh","header":{"controls":{"copy":{}}},"source":"node simulador.js\n","lang":"sh"},"children":[]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"sh","header":{"controls":{"copy":{}}},"source":"python3 simulador.py\n","lang":"sh"},"children":[]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"sh","header":{"controls":{"copy":{}}},"source":"go run simulador.go\n","lang":"sh"},"children":[]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"sh","header":{"controls":{"copy":{}}},"source":"java Simulador.java\n","lang":"sh"},"children":[]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"sh","header":{"controls":{"copy":{}}},"source":"bash simulador.sh\n","lang":"sh"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"o-que-o-simulador-cobre","__idx":6},"children":["O que o simulador cobre"]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Grupo"},"children":["Grupo"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Operações"},"children":["Operações"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Primeiros Passos"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Descobrir ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["parceiroId"]}," via ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["GET /me"]}," (automático no startup)"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Webhooks"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Listar eventos, cadastrar, listar, testar conectividade, remover"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Placas"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Cadastrar, remover"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Transações"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Listar com filtros (paginação inicia em 0)"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Pedidos"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Criar, consultar, confirmar, cancelar"]}]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Toda chamada HTTP é impressa no formato:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"header":{"controls":{"copy":{}}},"source":"→ METHOD URL\n  Body: { ... }\n✅ 200\n{ \"data\": ... }\n"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"exemplo-do-menu","__idx":7},"children":["Exemplo do menu"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"header":{"controls":{"copy":{}}},"source":"╔══════════════════════════════════════════════╗\n║   Movvia Arrecada+ — PE API Tester           ║\n║   hml.api.pedagioeletronico.com.br/...       ║\n║   parceiroId: par_7f3a1b2c                   ║\n╠══════════════════════════════════════════════╣\n║  ── Primeiros Passos ─────────────────       ║\n║    1.  Descobrir parceiroId (GET /me)        ║\n║  ── Webhooks Config ──────────────────       ║\n║    2.  Listar eventos disponíveis            ║\n║    3.  Cadastrar webhook                     ║\n║    4.  Listar webhooks                       ║\n║    5.  Testar conectividade webhook          ║\n║    6.  Remover webhook                       ║\n║  ── Placas ───────────────────────────       ║\n║    7.  Cadastrar placa                       ║\n║    8.  Remover placa                         ║\n║  ── Transações ───────────────────────       ║\n║    9.  Listar transações                     ║\n║  ── Pedidos ──────────────────────────       ║\n║   10.  Criar pedido                          ║\n║   11.  Consultar pedido                      ║\n║   12.  Confirmar pagamento                   ║\n║   13.  Cancelar pedido                       ║\n║                                              ║\n║    0.  Sair                                  ║\n╚══════════════════════════════════════════════╝\n"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"próximos-passos","__idx":8},"children":["Próximos passos"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/parceiros/tutorials/validar-hmac"},"children":["Validar HMAC-SHA256"]}," — verifique a assinatura dos webhooks que vão chegar."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/parceiros/tutorials/confirmar-pedido"},"children":["Confirmar um pedido"]}," — fluxo completo de criação e confirmação."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/apis/parceiros/openapi"},"children":["Referência da API"]}," — todos os endpoints e schemas."]}]}]},"headings":[{"value":"Simulador CLI","id":"simulador-cli","depth":1},{"value":"Pré-requisitos","id":"pré-requisitos","depth":2},{"value":"Passo 1 — Criar a pasta","id":"passo-1--criar-a-pasta","depth":2},{"value":"Passo 2 — Salvar o simulador","id":"passo-2--salvar-o-simulador","depth":2},{"value":"Passo 3 — Salvar arquivos auxiliares","id":"passo-3--salvar-arquivos-auxiliares","depth":2},{"value":"Passo 4 — Configurar credenciais e rodar","id":"passo-4--configurar-credenciais-e-rodar","depth":2},{"value":"O que o simulador cobre","id":"o-que-o-simulador-cobre","depth":2},{"value":"Exemplo do menu","id":"exemplo-do-menu","depth":2},{"value":"Próximos passos","id":"próximos-passos","depth":2}],"frontmatter":{"title":"Simulador CLI — Parceiros Movvia","description":"Menu interativo em Node.js, Python, Go, Java ou Bash para testar todos os endpoints da API de Parceiros sem montar requests à mão.","seo":{"title":"Simulador CLI"}},"lastModified":"2026-04-25T17:57:41.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/parceiros/tutorials/simulador-cli","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}