> ## Documentation Index
> Fetch the complete documentation index at: https://devs.tapipay.la/llms.txt
> Use this file to discover all available pages before exploring further.

# Crear link de pago

> Genera una liga de pago para cobrar sin conocer al deudor de antemano.



## OpenAPI

````yaml /api-reference/openapi.yaml post /payment-links
openapi: 3.0.0
info:
  title: TapiPay Facade API
  version: 1.0.0
  description: >
    Facade API pública de la plataforma de cobranzas TapiPay.  Ofrece un
    contrato limpio y desacoplado del modelo interno del OFU,  permitiendo a los
    clientes crear y gestionar cobros (deudas puntuales,  suscripciones
    recurrentes, links de pago, productos y contactos)  sin conocer detalles
    internos de Company, Modalities o GenerationData.
  contact:
    name: TapiPay Team
    url: https://www.tapipay.com
  license:
    name: Proprietary
servers:
  - url: https://tapipay-facade.dev.tapila.cloud
    description: Desarrollo
security:
  - apiKeyAuth: []
    tapiAuth: []
paths:
  /payment-links:
    post:
      tags:
        - Payment Links
      summary: Crear link de pago
      description: Genera una liga de pago para cobrar sin conocer al deudor de antemano.
      operationId: createPaymentLink
      parameters:
        - name: Idempotency-Key
          in: header
          schema:
            type: string
          description: >
            Clave de idempotencia (opcional). Se usa como llave única
            (`externalRequestId`) del PaymentLink. El `externalRequestId` es una
            llave **permanente** sin TTL (constraint UNIQUE en el OFU). Una
            request repetida con la misma key devuelve **error 409
            RESOURCE_CONFLICT** (no es replay/idempotencia clásica; es rechazo
            de duplicado por constraint UNIQUE). Si no se envía, el backend
            genera un `externalRequestId` único. Ver ADR-004 v1.1 para detalles
            de semántica.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PaymentLinkCreateRequest'
      responses:
        '201':
          description: Link de pago creado
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/PaymentLink'
                  requestId:
                    type: string
        '400':
          description: Validación fallida
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: No autenticado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: externalPaymentLinkId ya existe
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: Error de resolución
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
components:
  schemas:
    PaymentLinkCreateRequest:
      type: object
      required:
        - amount
        - description
        - expiresAt
      properties:
        amount:
          type: number
          minimum: 0.01
          multipleOf: 0.01
          description: Monto en pesos (hasta 2 decimales, mínimo 0.01)
        currency:
          $ref: '#/components/schemas/Currency'
          description: >-
            Código de moneda ISO 4217 (MXN, ARS, PEN, COP, CLP, USD). Default
            MXN.
          default: MXN
        description:
          type: string
          description: Descripción visible en la página de pago
        externalPaymentLinkId:
          type: string
          description: >
            Llave natural del link. Única por organización. Usada para
            idempotencia y matching externo.
        expiresAt:
          type: string
          format: date-time
          description: >
            Fecha/hora de expiración (ISO 8601, formato RFC3339). Debe ser en el
            futuro. Campo obligatorio.
        successUrl:
          type: string
          format: uri
          description: URL de redirección al completar el pago (opcional)
        singleUse:
          type: boolean
          default: true
          description: >
            Si true, el link se desactiva tras el primer pago. Si false, acepta
            múltiples pagos (reutilizable). Default true.
        metadata:
          type: object
          description: Metadatos arbitrarios del cliente (opcional)
        externalClientId:
          type: string
          description: >
            Identificador del deudor (cliente conocido). Mutuamente excluyente
            con contactData. Si se proporciona, la Facade resuelve el contacto
            existente por externalClientId en la organización. El paymentUrl
            será opaco (no expone identidad si privateLinks=true).
        contactData:
          $ref: '#/components/schemas/ContactInput'
          description: >
            Datos del deudor inline. Mutuamente excluyente con externalClientId.
            Si se proporciona, se crea un nuevo contacto (o se reutiliza si
            externalClientId ya existe). El paymentUrl será opaco (no expone
            identidad si privateLinks=true).
        productName:
          type: string
          description: >
            Nombre del producto a asociar (opcional).  Se resuelve por name en
            el OFU o se crea inline si no existe. Identificado por name único
            por companyCode.
        identifierValue:
          type: string
          maxLength: 64
          description: >
            Identificador explícito del link de pago (opcional). Si se
            proporciona, se sanitiza a URL-safe  (alfanumérico + guiones,
            lowercase, máx 64 chars). Si tras sanear queda vacío, retorna 400
            INVALID_REQUEST. Precedencia de resolución: identifierValue
            explícito > externalClientId (deudor) > sintético. Si se envía
            identifierValue: se usa ese valor (saneado). Si NO se envía y hay
            deudor conocido  (externalClientId/contactData): se usa el
            identificador del deudor. Si NO se envía y NO hay deudor:  se genera
            sintético {3letras}-{random}. Exposición en respuesta (PII-safe):
            SIN deudor conocido (anónimo/reutilizable) se expone
            identifierValue  en respuesta. CON deudor conocido: NO se expone
            (omitido), incluso si el cliente mandó identifierValue  explícito.
            El paymentUrl usa shortCode opaco si privateLinks=true, o
            externalClientId si privateLinks=false.
        additionalData:
          type: object
          description: Escape hatch para datos adicionales
    PaymentLink:
      type: object
      required:
        - paymentLinkId
        - status
        - amount
        - currency
        - description
        - singleUse
        - createdAt
        - paymentUrl
      properties:
        paymentLinkId:
          type: string
          pattern: ^plk_[a-zA-Z0-9]+$
          description: ID interno del PaymentLink (prefijo plk_)
        externalPaymentLinkId:
          type: string
          description: Llave externa del link (única por organización)
        status:
          $ref: '#/components/schemas/PaymentLinkStatus'
          description: Estado del link (ACTIVE, PAID, EXPIRED, CANCELLED)
        amount:
          type: number
          description: Monto en pesos (hasta 2 decimales, inmutable tras creación)
        currency:
          $ref: '#/components/schemas/Currency'
          description: Moneda del link (inmutable tras creación)
        description:
          type: string
          description: Descripción visible en la página de pago (actualizable)
        singleUse:
          type: boolean
          description: Si true, se desactiva tras el primer pago (inmutable tras creación)
        expiresAt:
          type: string
          format: date-time
          nullable: true
          description: >
            Fecha/hora de expiración (ISO 8601, RFC3339). Null si no expira.
            Actualizable, pero solo a valores futuros válidos.
        successUrl:
          type: string
          format: uri
          nullable: true
          description: URL de redirección post-pago exitoso (inmutable tras creación)
        metadata:
          type: object
          description: Metadatos arbitrarios (actualizable)
        paymentUrl:
          type: string
          format: uri
          nullable: true
          description: >
            URL pública del portal de pago. Comportamiento HÍBRIDO según deudor
            conocido o no:

            **SIN DEUDOR CONOCIDO (anónimo/reutilizable):** - Si
            privateLinks=true (default): URL opaca con shortCode
            anti-enumeración:
              `https://app.tapipay.la/s/{slug}/portal/{shortCode}/`
              donde shortCode no expone identidad. Null si falla integración.
            - Si privateLinks=false (legacy): URL con identifierValue (PII si es
            externalClientId):
              `https://app.tapipay.la/s/{slug}/portal/{identifierValue}/`
              donde identifierValue = sanitized org name prefix + random (ej. acm-x3kM9pQr). Null si falla.

            **CON DEUDOR CONOCIDO (externalClientId o contactData):** - Si
            privateLinks=true: URL opaca con shortCode:
              `https://app.tapipay.la/s/{slug}/portal/{shortCode}/`
              (no expone PII del deudor). Null si falla.
            - Si privateLinks=false: URL legacy con identifierValue =
            externalClientId (PII):
              `https://app.tapipay.la/s/{slug}/portal/{externalClientId}/`
              Null si falla.
        identifierValue:
          type: string
          nullable: true
          description: >
            **SOLO PARA LINKS SIN DEUDOR CONOCIDO (anónimo/reutilizable).**
            Null/omitido para links con deudor conocido (PII-safe).

            Identificador sintético usado como último segmento de `paymentUrl`
            (cuando privateLinks=false). Patrón: {primeras 3 letras sanitizadas
            del nombre org}-{random alfanumérico ≥8} Ejemplo: acm-x3kM9pQr

            Generación: se normaliza org name (diacríticos→ASCII,
            no-alfanuméricos→eliminados, lowercase), se toman primeras 3 letras;
            si quedan <3, se usa fallback "lnk". Ver ADR-006 y BR-007 para edge
            cases.
        contact:
          $ref: '#/components/schemas/ContactWithInline'
          nullable: true
          description: >
            (no implementado en v1, reservado para v2) Contacto resuelto o
            creado inline (si fue proporcionado externalClientId o contactData).
            Null en v1.
        product:
          $ref: '#/components/schemas/ProductWithInline'
          nullable: true
          description: >
            Producto asociado si fue referenciado en creación (via productName).
            Se resuelve o crea inline igual que en Debt y Subscription. Null si
            no se especificó producto.
        createdAt:
          type: string
          format: date-time
          description: Timestamp de creación (ISO 8601, RFC3339)
    Error:
      type: object
      required:
        - error
        - requestId
      properties:
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
              enum:
                - INVALID_REQUEST
                - AUTHENTICATION_FAILED
                - RESOURCE_NOT_FOUND
                - RESOURCE_CONFLICT
                - RESOLUTION_ERROR
                - BUSINESS_RULE_VIOLATION
                - RATE_LIMITED
                - INTERNAL_ERROR
              description: Código de error estándar
            message:
              type: string
              description: Mensaje legible del error
            details:
              type: array
              description: Array de detalles adicionales (validaciones por campo, etc.)
              items:
                type: object
                properties:
                  field:
                    type: string
                  issue:
                    type: string
        requestId:
          type: string
          pattern: ^req_[a-zA-Z0-9]+$
          description: ID único del request para trazabilidad
    Currency:
      type: string
      enum:
        - MXN
        - ARS
        - PEN
        - COP
        - CLP
        - USD
      default: MXN
      description: >
        Código de moneda ISO 4217. Whitelist de monedas aceptadas por la Facade.
        Si no se envía, se usa el default de la organización (fallback: MXN). Si
        se envía un valor fuera de este whitelist, la Facade devuelve 400
        INVALID_REQUEST.
    ContactInput:
      type: object
      required:
        - externalClientId
        - name
        - email
      properties:
        externalClientId:
          type: string
          description: Llave natural del contacto. Única por organización.
        name:
          type: string
          description: Nombre del deudor
        email:
          type: string
          format: email
          description: Email del deudor (requerido)
        phones:
          type: array
          description: Lista de teléfonos del deudor (opcional)
          items:
            $ref: '#/components/schemas/Phone'
    PaymentLinkStatus:
      type: string
      enum:
        - ACTIVE
        - PAID
        - EXPIRED
        - CANCELLED
    ContactWithInline:
      type: object
      required:
        - contactId
        - externalClientId
        - createdInline
      properties:
        contactId:
          type: string
          pattern: ^con_[a-zA-Z0-9]+$
          description: ID interno del contacto (prefijo con_)
        externalClientId:
          type: string
          description: Llave natural del contacto
        createdInline:
          type: boolean
          description: >
            Indica si el contacto fue creado inline en esta operación. NOTA:
            Este es un embedded thin contact (no incluye
            name/email/phones/active/updatedAt del contacto completo). Para
            obtener datos completos del contacto, consultá GET /contacts/{id}.
    ProductWithInline:
      type: object
      required:
        - productId
        - name
        - active
        - createdInline
      properties:
        productId:
          type: string
          description: ID del producto en el OFU (sin prefijo prod_)
        name:
          type: string
          description: Nombre del producto
        active:
          type: boolean
          description: Estado del producto
        createdInline:
          type: boolean
          description: Indica si el producto fue creado inline en esta operación
    Phone:
      type: object
      required:
        - number
        - type
      properties:
        number:
          type: string
          description: Número de teléfono. Ej +5215512345678
        type:
          $ref: '#/components/schemas/PhoneType'
        primary:
          type: boolean
          default: false
          description: >
            Indica si es el número telefónico principal. Opcional (default:
            false). El facade mapea a OFU.
        description:
          type: string
          description: >
            Descripción o etiqueta del teléfono. Opcional (default: type cuando
            no se especifica). El facade mapea a OFU.
    PhoneType:
      type: string
      enum:
        - MAIN
        - MOBILE
        - WORK
        - RELATIVE
        - OTHER
  securitySchemes:
    apiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key
      description: >
        API key del API Gateway de Tapi. Requerida en todas las operaciones.
        Distinta por ambiente (desarrollo, homologación, producción).
    tapiAuth:
      type: apiKey
      in: header
      name: x-authorization-token
      description: >
        Token TAPI JWT. Requerido en todas las operaciones. Se envía sin prefijo
        "Bearer". Ej: x-authorization-token: eyJhbGci...

````