openapi: 3.1.0
info:
  title: Levfone Connect — Sales Authorization API
  version: 1.0.0
  summary: API implementada pela Levfone e consumida pelo PDV Parceiro.
  description: >
    # Sales Authorization API


    Esta é a **única** API do Levfone Connect implementada pela Levfone. O PDV

    Parceiro a consome no momento em que o vendedor seleciona **Levfone** como

    forma de pagamento.


    ## Direção de integração


    | Papel | Sistema |

    |---|---|

    | Provedor (implementa) | Levfone |

    | Consumidor (chama) | PDV Parceiro |


    ## Ciclo de vida da autorização


    ```

    authorize  ──►  AUTHORIZED (com TTL)  ──►  confirm  ──►  CONFIRMED
                          │                                      │
                          ├──► expira (TTL)  ──► EXPIRED         └──► cancel ──► CANCELLED
                          └──► cancel        ──► CANCELLED
    ```


    O `authorizationId` **expira**. Se o PDV Parceiro não confirmar o
    faturamento

    dentro do TTL, a autorização é invalidada e a proposta de crédito volta a
    ficar

    disponível. O TTL padrão é de **15 minutos** e vem em `expiresAt`.


    ## Idempotência


    `POST /sales/authorize` **exige** o header `Idempotency-Key`. Repetir a
    mesma

    chave dentro de 24 horas retorna a resposta original sem reprocessar —
    protege

    contra timeout de rede, retry automático e duplo clique do vendedor.
  contact:
    name: Levfone Connect — Partner Integrations
    url: https://developer.levfone.com/support
    email: connect@levfone.com
  license:
    name: Proprietary — Levfone
    identifier: LicenseRef-Levfone-Proprietary
servers:
  - url: https://api.sandbox.levfone.com/v1
    description: Sandbox
  - url: https://api.levfone.com/v1
    description: Produção
tags:
  - name: Authentication
    description: Emissão de token de acesso para o PDV Parceiro.
  - name: Authorization
    description: Autorização, confirmação e cancelamento de venda financiada.
security:
  - levfoneOAuth: []
paths:
  /oauth/token:
    post:
      tags:
        - Authentication
      operationId: issueLevfoneToken
      summary: Emite token de acesso para o PDV Parceiro
      security: []
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required:
                - grant_type
                - client_id
                - client_secret
              properties:
                grant_type:
                  type: string
                  const: client_credentials
                client_id:
                  type: string
                client_secret:
                  type: string
                  format: password
                  writeOnly: true
                scope:
                  type: string
                  examples:
                    - sales:authorize
      responses:
        '200':
          description: Token emitido
          content:
            application/json:
              schema:
                type: object
                required:
                  - access_token
                  - token_type
                  - expires_in
                properties:
                  access_token:
                    type: string
                  token_type:
                    type: string
                    const: Bearer
                  expires_in:
                    type: integer
                    examples:
                      - 3600
                  scope:
                    type: string
        '401':
          $ref: '#/components/responses/Unauthorized'
  /sales/authorize:
    post:
      tags:
        - Authorization
      operationId: authorizeSale
      summary: Autoriza uma venda financiada pela Levfone
      description: >
        Chamada pelo PDV Parceiro **após** o carrinho estar montado e a forma de

        pagamento Levfone selecionada.


        ### Chamada única e autocontida


        Não há passo prévio obrigatório. O PDV pede a autorização e recebe ou a

        autorização ou o motivo da recusa, em uma única ida e volta.


        A Levfone **não** consulta o PDV Parceiro durante o processamento desta

        chamada. O PDV é a fonte da verdade sobre estoque e é ele quem está

        afirmando o IMEI nesta requisição — reconsultá-lo seria circular. A

        validação ocorre contra os registros da própria Levfone. Ver ADR-007.


        ### Ordem de validação


        1. Existe proposta **aprovada e vigente** para o CPF na loja informada,
           ou proposta reaproveitável por troca (ver ADR-008).
        2. O `planId` corresponde ao plano da proposta.

        3. Os produtos e serviços estão contemplados pela proposta.

        4. O **valor total** confere com o valor aprovado.

        5. O IMEI tem formato válido e não está vinculado a outro contrato
        ativo.


        **O PDV Parceiro só pode concluir o faturamento após `authorized:
        true`.**


        ### Troca


        Se esta requisição for a nova venda de uma troca, a proposta original é

        reaproveitada **sem nova análise de crédito**. A Levfone infere a troca
        —

        o PDV Parceiro não envia nada diferente e não muda nada no seu fluxo.

        Condições em ADR-008.


        ### Reconciliação de valores


        O campo `totals.grandTotal` deve ser **exatamente** a soma de

        `items[].totalPrice` + `services[].price`. Divergência de qualquer
        centavo

        retorna `total_mismatch` — a Levfone não arredonda nem ajusta.
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
        - $ref: '#/components/parameters/XRequestId'
        - $ref: '#/components/parameters/XCorrelationId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AuthorizationRequest'
      responses:
        '200':
          description: >
            Requisição processada. Verifique `data.authorized` — uma **recusa de

            crédito é `200`**, não erro. Códigos 4xx indicam problema na
            requisição.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Envelope'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/AuthorizationResult'
              examples:
                aprovada:
                  summary: Venda autorizada
                  value:
                    success: true
                    message: Venda autorizada.
                    timestamp: '2026-07-23T14:32:10.412Z'
                    data:
                      authorized: true
                      authorizationId: auth_01J8XK4T7ZQ9
                      status: AUTHORIZED
                      expiresAt: '2026-07-23T14:47:10.412Z'
                      message: Venda autorizada. Conclua o faturamento.
                    error: null
                recusada:
                  summary: Venda não autorizada
                  value:
                    success: true
                    message: Venda não autorizada.
                    timestamp: '2026-07-23T14:32:10.412Z'
                    data:
                      authorized: false
                      authorizationId: null
                      status: DECLINED
                      declineReason: total_mismatch
                      message: Valor do carrinho difere do valor aprovado na proposta.
                    error: null
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          description: |
            `Idempotency-Key` reutilizada com corpo de requisição diferente.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Envelope'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /sales/{authorizationId}/confirm:
    post:
      tags:
        - Authorization
      operationId: confirmSale
      summary: Confirma o faturamento da venda autorizada
      description: >
        Notifica a Levfone de que a venda foi **efetivamente faturada** no PDV

        Parceiro, encerrando o ciclo da autorização.


        Este endpoint é **recomendado, não obrigatório**. Se o PDV Parceiro não
        o

        implementar, a Levfone recorre ao polling de `POST /sales/search` na

        Partner PDV API. A confirmação ativa é preferível: elimina a janela em
        que

        o crédito está comprometido sem que a Levfone saiba o desfecho, e reduz

        drasticamente o volume de polling. Ver ADR-005.
      parameters:
        - $ref: '#/components/parameters/AuthorizationIdPath'
        - $ref: '#/components/parameters/IdempotencyKey'
        - $ref: '#/components/parameters/XRequestId'
        - $ref: '#/components/parameters/XCorrelationId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - saleNumber
                - invoiceNumber
                - nfeAccessKey
                - invoicedAt
              properties:
                saleNumber:
                  type: string
                orderNumber:
                  type: string
                invoiceNumber:
                  type: string
                nfeAccessKey:
                  type: string
                  pattern: ^[0-9]{44}$
                invoicedAt:
                  type: string
                  format: date-time
      responses:
        '200':
          description: Faturamento registrado
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Envelope'
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          authorizationId:
                            type: string
                          status:
                            type: string
                            const: CONFIRMED
                          contractId:
                            type: string
                            description: Identificador do crediário formalizado.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: Autorização expirada ou já em estado terminal.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Envelope'
  /sales/{authorizationId}/cancel:
    post:
      tags:
        - Authorization
      operationId: cancelSale
      summary: Notifica o cancelamento de uma venda
      description: >
        Notifica a Levfone de que a venda foi cancelada no PDV Parceiro.


        **A Levfone nunca inicia um cancelamento.** O gatilho é sempre o
        vendedor,

        no PDV. Este endpoint apenas comunica um cancelamento **já efetivado** —

        pedido cancelado, nota cancelada e estoque liberado.


        A Levfone confirma o cancelamento contra `POST /sales/search` antes de

        cancelar o financiamento. Divergência entre a notificação e a consulta
        faz

        prevalecer **a consulta**.


        ### Troca


        Não existe endpoint de troca. Se o PDV Parceiro trata troca como

        cancelamento + nova venda, a Levfone faz o mesmo: `cancel` seguido de
        novo

        `authorize`.


        O valor `reason: EXCHANGE` é o **gatilho do reaproveitamento da
        proposta**.

        Cancelamento marcado como troca permite que o novo `authorize` reutilize
        a

        aprovação original sem nova análise de crédito, dentro dos limites do

        ADR-008. Qualquer outro `reason` encerra a proposta.


        Se o PDV Parceiro não distingue troca de cancelamento comum, envie

        `EXCHANGE` sempre que houver venda subsequente ao cliente na mesma
        visita.
      parameters:
        - $ref: '#/components/parameters/AuthorizationIdPath'
        - $ref: '#/components/parameters/IdempotencyKey'
        - $ref: '#/components/parameters/XRequestId'
        - $ref: '#/components/parameters/XCorrelationId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - cancelledAt
                - reason
              properties:
                cancelledAt:
                  type: string
                  format: date-time
                reason:
                  type: string
                  enum:
                    - CUSTOMER_REQUEST
                    - EXCHANGE
                    - OPERATIONAL_ERROR
                    - FRAUD_SUSPICION
                    - OTHER
                notes:
                  type: string
                  maxLength: 500
      responses:
        '200':
          description: Cancelamento registrado
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Envelope'
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          authorizationId:
                            type: string
                          status:
                            type: string
                            const: CANCELLED
                          financingCancelled:
                            type: boolean
                            description: |
                              `false` enquanto a Levfone ainda não confirmou o
                              cancelamento via `POST /sales/search`.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
components:
  securitySchemes:
    levfoneOAuth:
      type: oauth2
      flows:
        clientCredentials:
          tokenUrl: https://api.sandbox.levfone.com/v1/oauth/token
          scopes:
            sales:authorize: Autorizar, confirmar e cancelar vendas financiadas
  parameters:
    AuthorizationIdPath:
      name: authorizationId
      in: path
      required: true
      schema:
        type: string
        maxLength: 64
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      description: |
        UUID v4 gerado pelo PDV Parceiro. Retenção de 24 horas.
      schema:
        type: string
        format: uuid
    XRequestId:
      name: X-Request-Id
      in: header
      required: true
      schema:
        type: string
        format: uuid
    XCorrelationId:
      name: X-Correlation-Id
      in: header
      required: true
      schema:
        type: string
        format: uuid
  schemas:
    Envelope:
      type: object
      required:
        - success
        - message
        - timestamp
      properties:
        success:
          type: boolean
        message:
          type: string
        timestamp:
          type: string
          format: date-time
        requestId:
          type: string
          format: uuid
        data:
          type: object
        error:
          oneOf:
            - $ref: '#/components/schemas/Error'
            - type: 'null'
    Error:
      type: object
      required:
        - code
        - message
      properties:
        code:
          type: string
          examples:
            - proposal_not_found
        legacyCode:
          type: string
          pattern: ^LF[0-9]{4}$
        message:
          type: string
        details:
          type: array
          items:
            type: object
            properties:
              field:
                type: string
              issue:
                type: string
        docsUrl:
          type: string
          format: uri
    Money:
      type: object
      required:
        - amount
        - currency
      properties:
        amount:
          type: integer
          description: Valor em centavos.
        currency:
          type: string
          const: BRL
    AuthorizationRequest:
      type: object
      required:
        - taxId
        - storeId
        - planId
        - imei
        - items
        - totals
      properties:
        taxId:
          type: string
          pattern: ^[0-9]{11}$
          description: CPF do cliente, sem máscara.
        storeId:
          type: string
        sellerId:
          type: string
        planId:
          type: string
        imei:
          type: string
          pattern: ^[0-9]{15}$
          description: IMEI do smartphone financiado.
        items:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/CartItem'
        services:
          type: array
          items:
            $ref: '#/components/schemas/CartService'
        totals:
          $ref: '#/components/schemas/CartTotals'
    CartItem:
      type: object
      required:
        - sku
        - quantity
        - unitPrice
        - totalPrice
      properties:
        sku:
          type: string
        productType:
          type: string
          enum:
            - SMARTPHONE
            - ACCESSORY
        name:
          type: string
        quantity:
          type: integer
          minimum: 1
        unitPrice:
          $ref: '#/components/schemas/Money'
        totalPrice:
          $ref: '#/components/schemas/Money'
    CartService:
      type: object
      required:
        - serviceId
        - price
      properties:
        serviceId:
          type: string
        name:
          type: string
        type:
          type: string
          enum:
            - EXTENDED_WARRANTY
            - INSURANCE
            - PROTECTION
        price:
          $ref: '#/components/schemas/Money'
    CartTotals:
      type: object
      required:
        - productsTotal
        - grandTotal
      properties:
        productsTotal:
          $ref: '#/components/schemas/Money'
        servicesTotal:
          $ref: '#/components/schemas/Money'
        discountTotal:
          $ref: '#/components/schemas/Money'
        grandTotal:
          $ref: '#/components/schemas/Money'
    AuthorizationResult:
      type: object
      required:
        - authorized
        - status
        - message
      properties:
        authorized:
          type: boolean
        authorizationId:
          oneOf:
            - type: string
            - type: 'null'
          description: Presente apenas quando `authorized` é `true`.
        status:
          type: string
          enum:
            - AUTHORIZED
            - DECLINED
            - CONFIRMED
            - CANCELLED
            - EXPIRED
        expiresAt:
          oneOf:
            - type: string
              format: date-time
            - type: 'null'
        declineReason:
          type: string
          description: Código semântico do motivo da recusa.
          examples:
            - proposal_not_found
            - total_mismatch
            - imei_already_financed
        message:
          type: string
          description: Texto pronto para exibição ao vendedor.
  responses:
    Unauthorized:
      description: Token ausente, inválido ou expirado.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Envelope'
    NotFound:
      description: Autorização não encontrada.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Envelope'
    UnprocessableEntity:
      description: Requisição semanticamente inválida.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Envelope'
    TooManyRequests:
      description: Limite de requisições excedido.
      headers:
        Retry-After:
          schema:
            type: integer
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Envelope'
