Files
LCEssentials/API_Mobile_App.md
Daniel Arantes Loverde 0f5ad4c268 Payments
2026-02-15 08:18:06 -03:00

13 KiB

Atomenta Mobile App API Documentation

🔐 Authentication & Headers

The mobile app accesses distinct sets of APIs:

  1. App APIs (/api/app/*): For general app features like Home Screen and Orders listing.
  2. Customer APIs (/api/customer/*): For User Registration, Login, and Profile Management.
  3. Store External APIs (/api/store/*): For store-specific operations like Catalog and Checkout.

Common Headers

All requests should ideally include:

Accept: application/json
Content-Type: application/json

Authentication Strategies

1. Customer Auth (for App & Customer APIs)

Used for everything related to the logged-in user (Profile, Orders, etc).

  • Header: Authorization: Bearer <Users_JWT_Token>
  • Note: Obtained via /api/customer/login.

3. Hybrid Store Auth (for Store External APIs)

Used when interacting with a specific store (Catalog, Checkout).

  • Header 1: Atomenta-Token: 550e8400-e29b-41d4-a716-446655440008 (Store Module ID)
  • Header 2: Authorization: Bearer <Users_JWT_Token> (Required for Checkout/Orders)

👤 Customer Management

1. Register Customer

POST /api/customer

Headers:

  • Atomenta-Token: 550e8400-e29b-41d4-a716-44665544000a (Customer Module ID)

Body:

{
  "name": "Daniel Loverde",
  "email": "daniel@example.com",
  "phoneNumber": "+5511999999999",
  "birthDate": "1990-01-01T00:00:00Z" // Opcional
}

Response:

{
  "error": false,
  "code": "CUSTOMER_CREATED",
  "result": {
    "id": "cust_uuid...",
    "name": "Daniel Loverde",
    "email": "daniel@example.com"
  }
}

2. Login (Get Token)

POST /api/customer/login

Body (email + phoneNumber are mandatory):

{
  "email": "daniel@example.com",
  "phoneNumber": "+5511999999999"
}

Fluxo:

  • Passo 1: enviar email + phoneNumber para receber código por email.
  • Passo 2: enviar email + phoneNumber e o OTP no header Authorization: Bearer <OTP>.

Response:

{
  "error": false,
  "code": "LOGIN_SUCCESS",
  "result": {
    "token": "eyJhbGciOi...", // <--- Use as Bearer Token for other requests
    "customer": {
      "id": "cust_uuid...",
      "name": "Daniel Loverde",
      "email": "daniel@example.com"
    }
  }
}

3. Get Profile

GET /api/customer/profile

Headers:

  • Authorization: Bearer <JWT_Token>
  • Atomenta-Token: 550e8400-e29b-41d4-a716-44665544000a

Response:

{
  "error": false,
  "code": "CUSTOMER_PROFILE_RETRIEVED",
  "result": {
    "id": "cust_uuid...",
    "name": "Daniel Loverde",
    "email": "daniel@example.com",
    "phoneNumber": "+5511999999999",
    "favorites": ["store_abc...", "store_xyz..."],
    "address_book": [],
    "verified": true,
    "biometricsEnabled": false,
    "createdAt": "2026-01-21T22:37:56.472Z",
    "updatedAt": "2026-02-13T19:00:00.000Z"
  }
}

Campos internos não retornados para o app: keyHash, strongHash, source, isShadow, profile_metrics, behavioral_stats, otpHash, otpHashParams, otpExpiresAt.

4. Update Profile

POST /api/customer/:id

Headers:

  • Authorization: Bearer <JWT_Token>
  • Atomenta-Token: 550e8400-e29b-41d4-a716-44665544000a

Body:

{
  "name": "Daniel A. Loverde", // Fields to update
  "phoneNumber": "+5511988888888",
  "biometricsEnabled": true,
  "address_book": [
    {
      "label": "Casa",
      "type": "residential",
      "address": "Avenida das Andorinhas",
      "number": "477",
      "neighborhood": "Jardim Andorinhas",
      "city": "Campinas",
      "state": "SP",
      "zipCode": "13101-400",
      "country": "Brasil",
      "complement": "Apto 78",
      "lat_long": [-22.9064, -47.0616]
    }
  ]
}

Regra de coordenadas (address_book.lat_long):

  • Se existir zipCode válido e houver cache do CEP no Atomenta (/api/public/cep/:cep) para o mesmo usuário, a API valida o ponto enviado.
  • Se não houver cache local, o Atomenta consulta a AwesomeAPI e atualiza o cache antes de validar.
  • Se lat_long estiver ausente/inválido, a API preenche com as coordenadas do CEP cacheado.
  • Se a distância entre lat_long enviado e o ponto do CEP for maior que 1.5 km, a API substitui lat_long pelo ponto do CEP cacheado.
  • Objetivo: evitar discrepâncias grandes entre endereço e coordenada salva no perfil.

🏠 App Home Screen

List Categories

GET /api/public/categories

Headers:

  • Accept: application/json

Response:

{
  "error": false,
  "result": [
    { "id": "all", "name": "Todas", "icon": "🏪" },
    { "id": "lanches", "name": "Lanches", "icon": "🍔" },
    { "id": "pizza", "name": "Pizza", "icon": "🍕" }
  ]
}

List Stores (Smart Listing)

GET /api/app/stores

Headers:

  • Authorization: Bearer <Users_JWT_Token> (Customer obrigatório)

Lists stores based on user location, filtered by distance.

Query Parameters:

  • lat (Required): User Latitude (e.g., -23.550520)
  • lng (Required): User Longitude (e.g., -46.633308)
  • category (Optional): Filter by category name (e.g., Lanches)
  • search (Optional): Search by store name

Notes:

  • Sem login do customer, a listagem é bloqueada.
  • Se lat/lng não forem enviados, a API tenta usar o endereço salvo no perfil do customer (address_book.lat_long).
  • Lojas fora do raio/bairro de entrega não são exibidas.

Response:

{
  "error": false,
  "result": [
    {
      "id": "store_123...",
      "name": "Burger King",
      "logo": "https://...",
      "cover": "https://...",
      "category": "Lanches",
      "rating": 4.8,
      "deliveryTime": "30-45 min",
      "deliveryFee": 5.99,
      "distance": 1.2, // km
      "isOpen": true,
      "statusLabel": "Aberto"
    }
  ]
}

List User Orders

GET /api/app/orders

Headers:

  • Authorization: Bearer <JWT>

Response:

{
  "error": false,
  "result": [
    {
      "id": "ord_123...",
      "total": 54.90,
      "status": "completed",
      "createdAt": "2024-03-20T10:00:00Z",
      "items": [...]
    }
  ]
}

🏪 Store Integration (External API)

Base URL: /api/store/:storeId

1. Store Identity & Status

GET /api/store/:storeId/identity

Headers/Query:

  • x-user-lat / lat: User Latitude
  • x-user-lng / lng: User Longitude

Response:

{
  "error": false,
  "result": {
    "fantasyName": "Mc Donalds",
    "distance": 2.5,
    "deliveryTime": "30-45 min",
    "minOrder": 15.00
  }
}

2. Full Store Info

GET /api/store/:storeId/info

Retorna versão pública para app (sem dados administrativos/financeiros da loja).

Response:

{
  "error": false,
  "code": "STORE_INFO_RETRIEVED",
  "result": {
    "isOpen": true,
    "statusLabel": "Aberto",
    "fantasyName": "Mc Donalds",
    "specialty": "Lanches",
    "minOrder": 15,
    "deliveryTime": "30-45 min",
    "logo": "https://...",
    "cover": "https://...",
    "paymentMethods": {
      "paymentOnDelivery": true,
      "paymentOnPickup": true,
      "acceptCash": true,
      "acceptPix": true
    },
    "openingHours": { "...": "..." },
    "address": {
      "street": "Rua X",
      "neighborhood": "Centro",
      "city": "Campinas",
      "state": "SP",
      "zipcode": "13000-000",
      "latitude": -22.9,
      "longitude": -47.0
    }
  }
}

3. Product Catalog

GET /api/store/:storeId/catalog

Retorna categorias com produtos, incluindo diferenciação explícita de pizza.

Response:

{
  "error": false,
  "result": [
    {
      "id": "cat_lanches",
      "name": "Lanches",
      "isPizzaCategory": false,
      "products": [
        {
          "id": "prod_xburger",
          "type": "prepared",
          "name": "X-Burger",
          "description": "Pão, carne e queijo",
          "image": "/uploads/products/xburger.jpg",
          "price": "25.00",
          "addons": "yes",
          "addonGroups": []
        }
      ]
    },
    {
      "id": "cat_pizzas",
      "name": "Pizzas",
      "isPizzaCategory": true,
      "pizzaConfig": {
        "sizes": [{ "id": "broto", "name": "Broto", "slices": 4, "maxFlavors": 1 }],
        "doughs": [{ "id": "tradicional", "name": "Tradicional", "active": true }],
        "crusts": [{ "id": "catupiry", "name": "Catupiry", "active": true, "priceModifier": "10.00" }]
      },
      "products": [
        {
          "id": "prod_pizza_calabresa",
          "type": "pizza",
          "name": "Pizza Calabresa",
          "description": "Molho, mussarela e calabresa",
          "image": "/uploads/products/pizza-calabresa.jpg",
          "price": "0.00",
          "pizzaPrices": {
            "broto": "35.00",
            "grande": "55.00"
          },
          "addons": "yes",
          "addonGroups": []
        }
      ]
    }
  ]
}

Como identificar pizza no app:

  • Produto pizza: products[].type === "pizza".
  • Configuração de montagem/preço por tamanho: category.isPizzaCategory === true + category.pizzaConfig + product.pizzaPrices.

4. Validate Delivery Address (Checkout)

POST /api/store/:storeId/delivery/validate-address

Use este endpoint ao trocar endereço na tela de pagamento para validar cobertura antes de finalizar.

Body:

{
  "address": {
    "street": "Avenida das Andorinhas",
    "number": "477",
    "neighborhood": "Jardim Andorinhas",
    "city": "Campinas",
    "state": "SP",
    "zip": "13101-400",
    "lat": -22.918,
    "lng": -47.01813
  }
}

Resposta Exemplo:

{
  "error": false,
  "code": "DELIVERY_ADDRESS_VALIDATED",
  "result": {
    "deliveryAllowed": true,
    "reasonCode": "DELIVERY_ALLOWED",
    "reasonMessage": "Endereço válido para entrega",
    "deliveryMode": "KM",
    "distance": 1.3,
    "deliveryFee": 8,
    "deliveryTime": "35 min",
    "sameCity": false,
    "matchedKmRange": { "from": 1, "to": 2, "price": 8, "time": 35 },
    "matchedNeighborhood": null
  }
}

Regras de validação:

  • KM: valida distância até o limite da última faixa + tolerância de 0.5 km.
  • NEIGHBORHOOD: reprova se cidade do endereço for diferente da cidade da loja.
  • NEIGHBORHOOD: aprova se bairro configurado bater, ou se for mesma cidade (fallback).

5. Create Order (Checkout)

POST /api/store/:storeId/orders

Body:

{
  "customer": {
    "name": "Daniel Loverde",
    "phone": "11999999999",
    "email": "daniel@example.com",
    "asaasId": "cus_000005165985" // Opcional.
  },
  // Nota: O usuário DEVE estar autenticado via Bearer Token.
  // Os dados de 'customer' enviados aqui são usados preferencialmente para
  // registrar os dados de entrega/cobrança deste pedido específico no Asaas,
  // mas o 'userId' e o histórico são vinculados ao token do usuário logado.
  "items": [
    {
      "productId": "prod_123",
      "name": "X-Burger",
      "qty": 2,
      "price": 25.00,
      "addons": [
        {
          "addonId": "addon_egg",
          "name": "Ovo",
          "price": 2.50,
          "qty": 2
        }
      ]
    }
  ],
  "total": 50.00,
  "paymentMethod": "PIX", // "PIX" | "CREDIT_CARD" | "DEBIT_CARD" | "MONEY"
  "deliveryType": "DELIVERY", // "DELIVERY" | "PICKUP"
  "address": {
    "street": "Rua Exemplo",
    "number": "123",
    "neighborhood": "Centro"
  }
}

Regras para items[].addons:

  • qty do adicional é aceito no backend (ex.: 2 ovos).
  • Se qty não for enviado ou vier inválido, a API assume 1.
  • qty mínimo efetivo é 1.

Response:

{
  "error": false,
  "code": "ORDER_CREATED",
  "result": {
    "id": "ord_987...",
    "shortId": "1234",
    "status": "PAYMENT_PENDING",
    "paymentStatus": "PENDING",
    "paymentMethod": "PIX",
    "paymentPayload": {
      "copyPaste": "000201010212...",
      "qrCodeImage": "iVBORw0KGgoAAAANSUhEUgAA...",
      "expirationDate": "2026-02-14T22:30:00.000Z"
    },
    "payment": {
      "method": "PIX",
      "status": "PENDING",
      "pix": {
        "copyPaste": "000201010212...",
        "qrCodeImage": "iVBORw0KGgoAAAANSUhEUgAA...",
        "expirationDate": "2026-02-14T22:30:00.000Z"
      }
    }
  }
}

Observação de segurança: o Atomenta não retorna mais links de checkout/fatura do Asaas no payload de criação do pedido.

6. Realtime de Status do Pedido (Socket.IO)

Use este canal para acompanhar mudança de status do pedido em tempo real (sem depender de push notification).

Socket Endpoint:

  • ws://<host>:<port>/socket.io/ (em produção, usar wss:// se houver TLS no balanceador)

Auth no handshake:

  • Enviar o JWT do customer em auth.token (mesmo token do Authorization: Bearer ... das APIs HTTP).

Exemplo de conexão (conceitual):

{
  "auth": {
    "token": "Bearer <Users_JWT_Token>"
  }
}

Eventos recebidos pelo app:

  • order_update: enviado quando o pedido muda (pagamento aprovado, aceito pela loja, saiu para entrega, concluído, cancelado).

Payload típico de order_update:

{
  "id": "ord_987...",
  "shortId": "1234",
  "storeId": "store_123...",
  "userId": "cust_uuid...",
  "status": "CONFIRMED",
  "paymentStatus": "CONFIRMED",
  "updatedAt": "2026-02-15T20:10:00.000Z"
}

7. Fallback oficial (Polling)

Se o socket cair, o app deve continuar consultando o status até estado final.

Endpoint:

  • GET /api/public/orders/:orderId

Sugestão de estratégia:

  • primeiros 60s: a cada 3s
  • próximos 2min: a cada 5s
  • depois: a cada 10s
  • parar em status final (COMPLETED, CANCELED, REFUNDED) ou ao sair da tela.