openapi: 3.0.3
info:
  title: QRwey! API
  description: >
    La API de **QRwey!** permite a los negocios generar códigos QR seguros y
    efímeros 

    para representar intenciones de factura. 


    - Los comercios registran una venta enviando los datos de la transacción.

    - La API retorna un **código QR** con un enlace seguro (deep link).

    - Los clientes escanean el QR con la app móvil de QRwey! y pueden consultar
    los detalles 
      de la venta para generar un CFDi
    - La API asegura la integridad de cada transacción usando **tokens opacos**
    firmados 

    con **HMAC-SHA256**, con control de expiración y protección contra reuso.
  version: 1.2.0
servers:
  - url: https://clients.qrwey.com
    description: Entorno de Producción
  - url: https://api-dev.qrwey.com
    description: Entorno de Desarrollo/Pruebas
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: |-
        API Key proporcionada por QRwey! para autenticación

        Ejemplo: 17e82f0a-4693-40f0-974f-b7607c575cb8
  schemas:
    TaxItem:
      type: object
      description: Representa un impuesto aplicable a un concepto.
      properties:
        type:
          type: string
          example: Traslado
          description: >-
            Define el tipo de impuesto. Valores válidos: 'Traslado' o
            'Retencion'.
          enum:
            - Traslado
            - Retencion
        tax:
          type: string
          example: '002'
          description: >-
            La clave del impuesto según el catálogo del SAT (ej. '002' para IVA,
            '001' para ISR).
        factor:
          type: string
          example: Tasa
          description: >-
            El tipo de factor aplicable según el catálogo del SAT: Tasa, Cuota,
            Exento.
          enum:
            - Tasa
            - Cuota
            - Exento
        rate:
          type: string
          example: '0.160000'
          description: >-
            La tasa o cuota del impuesto en su valor numérico (ej. 0.160000 para
            el 16% de IVA).
        taxable_base:
          type: number
          format: double
          example: 112
          description: >-
            La base imponible sobre la cual se calcula el impuesto. Generalmente
            es quantity * unit_price. Precisión: Hasta nueve enteros con seis
            decimales (15, 6).
        amount:
          type: number
          format: double
          example: 17.92
          description: >-
            El monto total del impuesto ya calculado (taxable_base * rate),
            listo para ser timbrado. Precisión: Hasta nueve enteros con seis
            decimales (15, 6).
      required:
        - type
        - tax
        - factor
        - rate
        - taxable_base
        - amount
    OperationItem:
      type: object
      description: Representa un producto o servicio dentro de la transacción.
      properties:
        sku:
          type: string
          example: GAS87
          description: Identificador único de producto (SKU) para control interno.
        description:
          type: string
          example: Gasolina Magna
          description: Descripción del producto o servicio.
        amount:
          type: number
          format: double
          example: 115.08
          description: >-
            Monto total del concepto, es la multiplicación del precio unitario
            por la cantidad (unit_price * quantity). Precisión: Hasta nueve
            enteros con seis decimales (15, 6).
        quantity:
          type: number
          format: double
          example: 5.662
          description: >-
            Número de unidades vendidas. Precisión: Hasta nueve enteros con seis
            decimales (15, 6).
        sat_unit_key:
          type: string
          example: LTR
          maxLength: 3
          description: >-
            Clave de Unidad de hasta 3 caracteres según el catálogo del SAT
            (clave_unidad).
        unit_price:
          type: number
          format: double
          example: 20.325
          description: >-
            Precio unitario del producto. Precisión: Hasta nueve enteros con
            seis decimales (15, 6).
        sat_product_service_key:
          type: string
          example: '15101515'
          minLength: 8
          maxLength: 8
          description: >-
            Clave de Producto/Servicio de 8 dígitos según el catálogo del SAT
            (clave_prod_serv).
        sat_tax_object_key:
          type: string
          example: '02'
          description: >-
            Clave que indica si el concepto es objeto de impuesto según el
            catálogo del SAT (objeto_imp).
        taxes:
          type: array
          description: >-
            Arreglo de impuestos aplicables al concepto. Si el valor de
            sat_tax_object_key es '01' este valor no debe ser informado, en el
            resto de opciones aplican las reglas del SAT.
          items:
            $ref: '#/components/schemas/TaxItem'
      required:
        - description
        - amount
        - quantity
        - sat_unit_key
        - unit_price
        - sat_product_service_key
        - sat_tax_object_key
    GenerateQRRequest:
      type: object
      properties:
        store:
          type: string
          example: 'PetroStation #45'
          description: Identificador único de la tienda que realiza la venta.
        issuer_rfc:
          type: string
          example: EKU9003173C9
          description: >-
            RFC del emisor de la factura. Útil cuando el comercio tiene más de
            un emisor y no desea usar el predeterminado.
        amount:
          type: number
          format: double
          example: 133
          description: >-
            Monto total de la transacción. Debe coincidir con la suma de los
            productos. Precisión: Hasta nueve enteros con seis decimales (15,
            6).
        subtotal:
          type: number
          format: double
          example: 115.08
          description: >-
            Subtotal de la transacción. Precisión: Hasta nueve enteros con seis
            decimales (15, 6).
        currency:
          type: string
          example: MXN
          minLength: 3
          maxLength: 3
          description: Código de moneda de 3 letras en formato ISO 4217 (ej. 'MXN', 'USD').
        sat_payment_method:
          type: string
          example: '04'
          minLength: 2
          maxLength: 2
          description: >-
            Clave de Forma de Pago de 2 caracteres según el catálogo del SAT
            (forma_pago).
        expires_at:
          type: string
          format: date-time
          example: '2025-11-01T23:59:59Z'
          description: >-
            Fecha y hora de expiración del QR en formato ISO 8601. Después de
            este momento, el QR será inválido.
        operation:
          type: string
          enum:
            - INVOICE
            - PAYMENT
            - REFUND
          example: INVOICE
          description: 'Tipo de operación: INVOICE, PAYMENT, REFUND.'
        operation_data:
          type: array
          description: >-
            Un arreglo de objetos, donde cada objeto representa un producto o
            servicio.
          items:
            $ref: '#/components/schemas/OperationItem'
      required:
        - store
        - amount
        - subtotal
        - currency
        - sat_payment_method
        - expires_at
        - operation
        - operation_data
    GenerateQRResponse:
      type: object
      properties:
        qr_base64:
          type: string
          example: data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...
          description: >-
            La imagen del QR codificada en base64, lista para ser incrustada o
            impresa.
        qr_url:
          type: string
          example: >-
            https://clients.qrwey.com/qr.png?t=YWFh...&exp=1696200000&sig=YmJi...
          description: >-
            Una URL pública, firmada y de corta duración para ver o descargar la
            imagen PNG del código QR. Evita la enumeración de QRs.
        qr_content:
          type: string
          example: https://clients.qrwey.com/go?v=1&t=YWFh...&m=YmJi...
          description: >-
            La URL segura (deep link) contenida en el QR, que incluye el token
            (t) y la firma HMAC (m).
        created_at:
          type: string
          format: date-time
          example: '2025-09-11T18:25:43Z'
          description: Fecha y hora de creación de la transacción en formato ISO 8601.
        expires_at:
          type: string
          format: date-time
          example: '2025-09-30T23:59:59Z'
          description: Fecha y hora de expiración del código QR en formato ISO 8601.
      required:
        - qr_base64
        - qr_url
        - qr_content
        - created_at
        - expires_at
    ResolvedOperationItem:
      type: object
      description: >-
        Representa un producto o servicio resuelto con descripciones del
        catálogo del SAT.
      properties:
        sku:
          type: string
          example: GAS87
          description: Identificador único de producto (SKU) para control interno.
        description:
          type: string
          example: Gasolina Magna
          description: Descripción del producto o servicio.
        amount:
          type: number
          format: double
          example: 115.08
          description: >-
            Monto total del concepto (unit_price * quantity). Precisión: Hasta
            nueve enteros con seis decimales (15, 6).
        quantity:
          type: number
          format: double
          example: 30
          description: >-
            Número de unidades vendidas. Precisión: Hasta nueve enteros con seis
            decimales (15, 6).
        sat_unit_key:
          type: string
          example: LTR
          description: Clave de Unidad según el catálogo del SAT.
        sat_unit_desc:
          type: string
          example: Litro
          description: Descripción de la clave de unidad del SAT.
        unit_price:
          type: number
          format: double
          example: 20.325
          description: >-
            Precio unitario del producto. Precisión: Hasta nueve enteros con
            seis decimales (15, 6).
        sat_product_service_key:
          type: string
          example: '15101515'
          description: Clave de Producto/Servicio según el catálogo del SAT.
        sat_product_service_desc:
          type: string
          example: Gasolina premium mayor o igual a 91 octanos
          description: Descripción de la clave de producto/servicio del SAT.
        sat_tax_object_key:
          type: string
          example: '02'
          description: Clave que indica si el concepto es objeto de impuesto.
        sat_tax_object_desc:
          type: string
          example: Sí objeto de impuesto
          description: Descripción de la clave de objeto de impuesto del SAT.
        taxes:
          type: array
          description: Arreglo de impuestos aplicables al concepto.
          items:
            $ref: '#/components/schemas/TaxItem'
      required:
        - description
        - amount
        - quantity
        - sat_unit_key
        - sat_unit_desc
        - unit_price
        - sat_product_service_key
        - sat_product_service_desc
        - sat_tax_object_key
        - sat_tax_object_desc
    ResolveQRResponse:
      type: object
      description: Detalles completos de la transacción resuelta.
      properties:
        id:
          type: string
          example: tra_4aca0361cfcd41c097663db44008fbb9
          description: ID único de la transacción.
        store:
          type: string
          example: 'PetroStation #45'
          description: Identificador de la tienda.
        issuer_rfc:
          type: string
          example: EKU9003173C9
          description: RFC del emisor.
        issuer_name:
          type: string
          example: ESCUELA KEMPER URGATE
          description: Nombre o razón social del emisor.
        amount:
          type: number
          format: double
          example: 133
          description: >-
            Monto total de la transacción. Precisión: Hasta nueve enteros con
            seis decimales (15, 6).
        subtotal:
          type: number
          format: double
          example: 115.08
          description: >-
            Subtotal de la transacción. Precisión: Hasta nueve enteros con seis
            decimales (15, 6).
        currency:
          type: string
          example: MXN
          description: Código de moneda ISO 4217.
        sat_payment_method:
          type: string
          example: '04'
          description: Clave de Forma de Pago del SAT.
        sat_payment_method_desc:
          type: string
          example: Tarjeta de crédito
          description: Descripción de la forma de pago según el catálogo del SAT.
        created_at:
          type: string
          format: date-time
          example: '2025-09-29T13:00:00Z'
          description: Fecha y hora de creación de la transacción en formato ISO 8601.
        expires_at:
          type: string
          format: date-time
          example: '2025-09-30T23:59:59Z'
          description: Fecha y hora de expiración del código QR en formato ISO 8601.
        operation:
          type: string
          example: INVOICE
          description: 'Tipo de operación: INVOICE, PAYMENT, REFUND.'
        operation_data:
          type: array
          items:
            $ref: '#/components/schemas/ResolvedOperationItem'
      required:
        - id
        - store
        - amount
        - subtotal
        - currency
        - sat_payment_method
        - created_at
        - expires_at
        - operation
        - operation_data
    ErrorResponse:
      type: object
      properties:
        error:
          type: string
          description: Tipo de error
        code:
          type: integer
          description: Código de estado HTTP
        message:
          type: string
          description: Descripción detallada del error
      required:
        - error
        - code
        - message
    ErrorResponse400:
      allOf:
        - $ref: '#/components/schemas/ErrorResponse'
      example:
        error: Bad Request
        code: 400
        message: >-
          Error de Validación. Ocurre si faltan campos, los tipos de datos son
          incorrectos, la firma HMAC es inválida o expires_at es una fecha
          pasada.
    ErrorResponse401:
      allOf:
        - $ref: '#/components/schemas/ErrorResponse'
      example:
        error: Unauthorized
        code: 401
        message: API Key inválida o ausente
    ErrorResponse404:
      allOf:
        - $ref: '#/components/schemas/ErrorResponse'
      example:
        error: Not Found
        code: 404
        message: >-
          Token no Encontrado. El token proporcionado nunca fue emitido, fue
          revocado o ya fue purgado de la base de datos después de expirar.
    ErrorResponse409:
      allOf:
        - $ref: '#/components/schemas/ErrorResponse'
      example:
        error: Conflict
        code: 409
        message: >-
          Token ya Consumido. El token es válido pero ya fue utilizado para
          generar una factura (cuando la política es de un solo uso).
    ErrorResponse410:
      allOf:
        - $ref: '#/components/schemas/ErrorResponse'
      example:
        error: Gone
        code: 410
        message: >-
          QR Expirado. El cliente intentó escanear el QR después de la fecha y
          hora indicadas en expires_at.
    ErrorResponse422:
      allOf:
        - $ref: '#/components/schemas/ErrorResponse'
      example:
        error: Unprocessable Entity
        code: 422
        message: >-
          Error de Negocio. Ocurre si la empresa asociada al issuer_rfc no está
          configurada correctamente en el sistema.
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      description: >-
        Clave única (ej. UUID) generada por el cliente para garantizar que la
        solicitud pueda reintentarse de forma segura sin crear transacciones
        duplicadas. Si el servidor ve la misma clave, devolverá la respuesta
        original.
      schema:
        type: string
        format: uuid
      example: a8f2d7a6-c5f1-4a94-9a1c-1f7a2c4e6a3b
paths:
  /api/v1/qrs:
    post:
      summary: Generar Código QR
      description: >
        Crea una nueva intención de factura y genera su correspondiente código
        QR seguro y efímero.


        El flujo de operación:

        1. La aplicación del comercio envía los detalles de una venta y su API
        Key

        2. La API valida la API Key y guarda la información de la transacción

        3. Genera un token opaco y único, firmado con HMAC para garantizar su
        integridad

        4. Almacena una correspondencia entre el token y los detalles de la
        transacción

        5. Aplica un TTL (Time To Live) basado en la fecha de expiración
        (expires_at)

        6. Genera y devuelve el código QR para intento de autofactura en tres
        formatos: imagen base64, URL de descarga firmada y contenido de texto
      operationId: generateQR
      security:
        - ApiKeyAuth: []
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/GenerateQRRequest'
            example:
              store: 'PetroStation #45'
              issuer_rfc: EKU9003173C9
              amount: 133
              subtotal: 115.08
              currency: MXN
              sat_payment_method: '04'
              expires_at: '2025-11-01T23:59:59Z'
              operation: INVOICE
              operation_data:
                - sku: GAS87
                  description: Gasolina Magna
                  amount: 115.08
                  quantity: 5.662
                  sat_unit_key: LTR
                  unit_price: 20.325
                  sat_product_service_key: '15101515'
                  sat_tax_object_key: '02'
                  taxes:
                    - type: Traslado
                      tax: '002'
                      factor: Tasa
                      rate: '0.160000'
                      taxable_base: 112
                      amount: 17.92
      responses:
        '201':
          description: QR generado exitosamente
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenerateQRResponse'
              example:
                qr_base64: data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...
                qr_url: >-
                  https://clients.qrwey.com/qr.png?t=YWFh...&exp=1696200000&sig=YmJi...
                qr_content: https://clients.qrwey.com/go?v=1&t=YWFh...&m=YmJi...
                created_at: '2025-09-11T18:25:43Z'
                expires_at: '2025-09-30T23:59:59Z'
        '400':
          description: Error de validación
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse400'
        '401':
          description: API Key inválida
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse401'
        '409':
          description: Token ya Consumido
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse409'
        '422':
          description: Error de Negocio
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse422'
      tags:
        - QRs
    get:
      summary: Resolver Código QR
      description: >
        Este endpoint es invocado cuando un cliente final escanea el QR. Valida
        el token y devuelve los detalles de la transacción.


        El flujo de operación:

        1. El cliente escanea el código QR desde la aplicación QRwey!

        2. La aplicación extrae los parámetros de la URL (v, t, m)

        3. La aplicación solicita al API el detalle de la venta

        4. El API valida la firma HMAC para asegurar que el token no ha sido
        alterado

        5. Verifica que el token no haya expirado y que no haya sido consumido

        6. Si todas las validaciones son correctas, responde con los detalles
        completos de la transacción


        El sistema utiliza:

        - **Tokens Opacos**: Identificadores aleatorios sin secuencia

        - **Integridad (Anti-manipulación)**: Firmado con HMAC-SHA256 truncado

        - **Expiración Forzada**: Basada en el campo expires_at

        - **Control de Reutilización (Anti-replay)**: Puede configurarse para un
        solo uso
      operationId: resolveQR
      security:
        - ApiKeyAuth: []
      parameters:
        - in: query
          name: v
          required: true
          schema:
            type: string
            example: '1'
          description: Versión del formato del QR (actualmente '1').
        - in: query
          name: t
          required: true
          schema:
            type: string
            example: YWFhYWFh...
          description: El token opaco (Base64URL) extraído del QR.
        - in: query
          name: m
          required: true
          schema:
            type: string
            example: YmJiYmJi...
          description: >-
            El Message Authentication Code (Base64URL), que es la firma HMAC
            truncada que valida la integridad del token.
      responses:
        '200':
          description: Transacción resuelta exitosamente
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ResolveQRResponse'
              example:
                id: tra_4aca0361cfcd41c097663db44008fbb9
                store: 'PetroStation #45'
                issuer_rfc: EKU9003173C9
                issuer_name: ESCUELA KEMPER URGATE
                amount: 133
                subtotal: 115.08
                currency: MXN
                sat_payment_method: '04'
                sat_payment_method_desc: Tarjeta de crédito
                created_at: '2025-09-29T13:00:00Z'
                expires_at: '2025-09-30T23:59:59Z'
                operation: INVOICE
                operation_data:
                  - sku: GAS87
                    description: Gasolina Magna
                    amount: 115.08
                    quantity: 30
                    sat_unit_key: LTR
                    sat_unit_desc: Litro
                    unit_price: 20.325
                    sat_product_service_key: '15101515'
                    sat_product_service_desc: Gasolina premium mayor o igual a 91 octanos
                    sat_tax_object_key: '02'
                    sat_tax_object_desc: Sí objeto de impuesto
                    taxes:
                      - type: Traslado
                        tax: '002'
                        factor: Tasa
                        rate: '0.160000'
                        taxable_base: 112
                        amount: 17.92
        '400':
          description: Error de validación (ej. HMAC inválido)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse400'
        '401':
          description: API Key inválida
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse401'
        '404':
          description: Token no encontrado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse404'
        '409':
          description: Token ya consumido
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse409'
        '410':
          description: QR expirado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse410'
        '422':
          description: Error de negocio
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse422'
      tags:
        - QRs
security:
  - ApiKeyAuth: []
tags:
  - name: QRs
    description: Generación y resolución de códigos QR (intenciones de autofactura).
x-tagGroups:
  - name: Operaciones principales
    tags:
      - QRs
