openapi: 3.1.0
info:
  title: CashLink Short URL Payment API
  version: 2.1.0
  description: Unified BCH checkout API for bound custom short URLs and random URL batches.
servers:
  - url: https://cashlink.tw
paths:
  /api/v2/short-url-payment-products/{product}/quote:
    get:
      operationId: quoteShortUrlPaymentProduct
      parameters:
        - in: path
          name: product
          required: true
          schema: { type: string, enum: [custom_code, random_batch] }
        - in: query
          name: quantity
          schema: { type: integer }
        - in: query
          name: custom_code
          description: Required when product is custom_code. Pricing uses an exact, case-insensitive common-word tier.
          schema: { type: string, minLength: 3, maxLength: 32, pattern: '^[A-Za-z0-9][A-Za-z0-9_-]*$' }
      responses:
        '200': { $ref: '#/components/responses/Success' }
        '422': { $ref: '#/components/responses/Problem' }
  /api/v2/short-url-payment-orders:
    post:
      operationId: createShortUrlPaymentOrder
      parameters: [ { $ref: '#/components/parameters/IdempotencyKey' } ]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [product_type, refund_address, refund_address_confirmation, terms_version, terms_accepted]
              properties:
                product_type: { type: string, enum: [custom_code, random_batch] }
                quantity: { type: integer }
                custom_code: { type: string, minLength: 3, maxLength: 32, pattern: '^[A-Za-z0-9][A-Za-z0-9_-]*$' }
                original_url: { type: string, format: uri, maxLength: 2048 }
                generate_qr: { type: boolean, default: false }
                refund_address: { type: string }
                refund_address_confirmation: { type: string }
                terms_version: { type: string }
                terms_accepted: { type: boolean }
                webhook_url: { type: string, format: uri }
              allOf:
                - if:
                    properties: { product_type: { const: custom_code } }
                  then:
                    required: [custom_code, original_url]
                - if:
                    properties: { product_type: { const: random_batch } }
                  then:
                    required: [quantity]
      responses:
        '201': { $ref: '#/components/responses/Success' }
        '409': { $ref: '#/components/responses/Problem' }
        '422': { $ref: '#/components/responses/Problem' }
  /api/v2/short-url-payment-orders/{order}:
    get:
      operationId: showShortUrlPaymentOrder
      security: [ { bearerOrderToken: [] } ]
      parameters: [ { $ref: '#/components/parameters/OrderId' } ]
      responses:
        '200': { $ref: '#/components/responses/Success' }
        '404': { $ref: '#/components/responses/Problem' }
  /api/v2/short-url-payment-orders/{order}/activate:
    post:
      operationId: activateShortUrlPaymentOrder
      security: [ { bearerOrderToken: [] } ]
      parameters:
        - { $ref: '#/components/parameters/OrderId' }
        - { $ref: '#/components/parameters/IdempotencyKey' }
      responses:
        '200': { $ref: '#/components/responses/Success' }
        '409': { $ref: '#/components/responses/Problem' }
  /api/v2/short-url-payment-orders/{order}/events:
    get:
      operationId: streamShortUrlPaymentOrder
      security: [ { bearerOrderToken: [] } ]
      parameters: [ { $ref: '#/components/parameters/OrderId' } ]
      responses:
        '200':
          description: Versioned SSE envelope with revision cursor.
          content: { text/event-stream: { schema: { type: string } } }
  /api/v2/short-url-payment-orders/{order}/qr:
    get:
      operationId: paymentOrderQr
      security: [ { bearerOrderToken: [] } ]
      parameters: [ { $ref: '#/components/parameters/OrderId' } ]
      responses:
        '200': { description: Isolated SVG QR response }
  /api/v2/short-url-payment-orders/{order}/redemptions:
    post:
      operationId: redeemCustomCodeOrder
      description: Legacy compatibility for unbound custom-code credits. New custom_code orders are bound at creation and activate automatically after payment.
      security: [ { bearerOrderToken: [] } ]
      parameters:
        - { $ref: '#/components/parameters/OrderId' }
        - { $ref: '#/components/parameters/IdempotencyKey' }
      responses:
        '201': { $ref: '#/components/responses/Success' }
  /api/v2/short-url-payment-orders/{order}/refunds:
    get:
      operationId: listPaymentOrderRefunds
      security: [ { bearerOrderToken: [] } ]
      parameters: [ { $ref: '#/components/parameters/OrderId' } ]
      responses:
        '200': { $ref: '#/components/responses/Success' }
  /api/v2/short-url-payment-orders/{order}/batches:
    get:
      operationId: listRandomBatches
      security: [ { bearerOrderToken: [] } ]
      parameters: [ { $ref: '#/components/parameters/OrderId' } ]
      responses: { '200': { $ref: '#/components/responses/Success' } }
    post:
      operationId: createRandomBatch
      security: [ { bearerOrderToken: [] } ]
      parameters:
        - { $ref: '#/components/parameters/OrderId' }
        - { $ref: '#/components/parameters/IdempotencyKey' }
      responses: { '202': { $ref: '#/components/responses/Success' } }
  /api/v2/short-url-payment-orders/{order}/batches/{batch}:
    get:
      operationId: showRandomBatch
      security: [ { bearerOrderToken: [] } ]
      parameters:
        - { $ref: '#/components/parameters/OrderId' }
        - in: path
          name: batch
          required: true
          schema: { type: string, format: uuid }
      responses: { '200': { $ref: '#/components/responses/Success' } }
  /api/v2/short-url-payment-orders/{order}/batches/{batch}/items/{index}/qr:
    get:
      operationId: randomBatchItemQr
      security: [ { bearerOrderToken: [] } ]
      parameters:
        - { $ref: '#/components/parameters/OrderId' }
        - in: path
          name: batch
          required: true
          schema: { type: string, format: uuid }
        - in: path
          name: index
          required: true
          schema: { type: integer, minimum: 0 }
      responses: { '200': { description: Isolated SVG QR response } }
components:
  securitySchemes:
    bearerOrderToken: { type: http, scheme: bearer }
  parameters:
    OrderId:
      in: path
      name: order
      required: true
      schema: { type: string, format: uuid }
    IdempotencyKey:
      in: header
      name: Idempotency-Key
      required: true
      schema: { type: string, minLength: 16, maxLength: 200 }
  responses:
    Success:
      description: Successful response
      content:
        application/json:
          schema:
            type: object
            required: [data, links, meta]
            properties:
              data: { type: object }
              links: { type: object }
              meta: { type: object }
    Problem:
      description: RFC 9457 problem details
      content:
        application/problem+json:
          schema:
            type: object
            required: [type, title, status, detail, code]
            properties:
              type: { type: string, format: uri }
              title: { type: string }
              status: { type: integer }
              detail: { type: string }
              code: { type: string }
              request_id: { type: string }
