openapi: 3.1.0
info:
  title: PayLumia Partner Payment API — Hosted
  version: '1.10'
  description: |
    Hosted-checkout surface of the PayLumia Partner Payment API (v1).
    Source of truth: PayLumia spec `partner-api` (02-PARTNER-API.md).
servers:
  - url: https://api.sandbox.paylumia.com
    description: Sandbox (mock only, plm_test_)
  - url: https://api.paylumia.com
    description: Production (live Orange, plm_live_)
security:
  - bearer: []
tags:
  - name: Auth
  - name: Capabilities
  - name: Payments
  - name: Subscriptions
paths:
  /oauth2/token:
    post:
      tags: [Auth]
      summary: Get an access token
      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, examples: [plm_test_a1b2c3d4e5f6] }
                client_secret: { type: string }
      responses:
        '200':
          description: Token (one hour)
          content:
            application/json:
              schema:
                type: object
                properties:
                  access_token: { type: string }
                  token_type: { type: string }
                  expires_in: { type: integer, examples: [3600] }
  /v1/capabilities:
    get:
      tags: [Capabilities]
      summary: List capabilities
      parameters:
        - { name: countryCode, in: query, required: true, schema: { type: string, examples: [TN] } }
        - { name: operator, in: query, required: false, schema: { type: string, examples: [ORANGE_TN] } }
      responses:
        '200':
          description: Capabilities
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Envelope'
                  - properties:
                      data: { $ref: '#/components/schemas/Capabilities' }
  /v1/payments:
    post:
      tags: [Payments]
      summary: Create a hosted payment
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/CreateHostedPayment' }
      responses:
        '201':
          description: Payment created
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Envelope'
                  - properties:
                      data: { $ref: '#/components/schemas/Payment' }
        default: { $ref: '#/components/responses/Error' }
  /v1/payments/{paymentUid}:
    get:
      tags: [Payments]
      summary: Retrieve a payment
      parameters: [{ $ref: '#/components/parameters/PaymentUid' }]
      responses:
        '200':
          description: Payment
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Envelope'
                  - properties:
                      data: { $ref: '#/components/schemas/Payment' }
        default: { $ref: '#/components/responses/Error' }
  /v1/payments/{paymentUid}/cancel:
    post:
      tags: [Payments]
      summary: Cancel a payment
      parameters: [{ $ref: '#/components/parameters/PaymentUid' }]
      responses:
        '200':
          description: Canceled payment
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Envelope'
                  - properties:
                      data: { $ref: '#/components/schemas/Payment' }
        default: { $ref: '#/components/responses/Error' }
  /v1/subscriptions/{subscriptionUid}:
    get:
      tags: [Subscriptions]
      summary: Retrieve a subscription
      parameters: [{ $ref: '#/components/parameters/SubscriptionUid' }]
      responses:
        '200':
          description: Subscription
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Envelope'
                  - properties:
                      data: { $ref: '#/components/schemas/Subscription' }
        default: { $ref: '#/components/responses/Error' }
  /v1/subscriptions/{subscriptionUid}/cancel:
    post:
      tags: [Subscriptions]
      summary: Cancel a subscription
      parameters: [{ $ref: '#/components/parameters/SubscriptionUid' }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [when]
              additionalProperties: false
              properties:
                when: { type: string, enum: [NOW, PERIOD_END] }
      responses:
        '200':
          description: Subscription
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Envelope'
                  - properties:
                      data: { $ref: '#/components/schemas/Subscription' }
        default: { $ref: '#/components/responses/Error' }
  /v1/subscriptions/{subscriptionUid}/resume:
    post:
      tags: [Subscriptions]
      summary: Resume a subscription (withdraw PERIOD_END cancel)
      parameters: [{ $ref: '#/components/parameters/SubscriptionUid' }]
      responses:
        '200':
          description: Subscription
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Envelope'
                  - properties:
                      data: { $ref: '#/components/schemas/Subscription' }
        default: { $ref: '#/components/responses/Error' }
webhooks:
  paymentEvent:
    post:
      summary: Event sent to your environment's webhook URL
      parameters:
        - name: PayLumia-Signature
          in: header
          required: true
          description: 't={unix},v1={hmac} — HMAC over "{t}.{rawBody}" with the environment secret; 5-minute window'
          schema: { type: string }
      requestBody:
        content:
          application/json:
            schema: { $ref: '#/components/schemas/WebhookEvent' }
      responses:
        '200': { description: Acknowledged }
components:
  securitySchemes:
    bearer:
      type: http
      scheme: bearer
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      schema: { type: string, minLength: 8, maxLength: 255 }
    PaymentUid:
      name: paymentUid
      in: path
      required: true
      schema: { type: string, examples: [pay_4mK9pQeRw2Ns6TfXb1Zy0a] }
    SubscriptionUid:
      name: subscriptionUid
      in: path
      required: true
      schema: { type: string }
  responses:
    Error:
      description: Error
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
  schemas:
    Envelope:
      type: object
      required: [success, data]
      properties:
        success: { type: boolean, const: true }
        data: {}
    Error:
      type: object
      properties:
        success: { type: boolean, const: false }
        error:
          type: object
          required: [code, message]
          properties:
            code: { type: string, pattern: '^[A-Z]{3}-[0-9]{3}$' }
            message: { type: string }
            retryable: { type: boolean }
            retryAfterSeconds: { type: integer }
    Capabilities:
      type: object
      properties:
        countryCode: { type: string }
        operator: { type: string }
        modes: { type: array, items: { type: string, enum: [HOSTED, API] } }
        consentMethods: { type: array, items: { type: string, enum: [HEADER_ENRICHMENT, PIN, MO_SMS] } }
        operatorConsentPage: { type: boolean }
        instruments: { type: array, items: { type: string } }
    CreateHostedPayment:
      type: object
      required: [mode, offerUid, returnUrl]
      additionalProperties: false
      properties:
        mode: { type: string, const: HOSTED }
        offerUid: { type: string }
        returnUrl: { type: string, format: uri }
        presentation: { type: string, enum: [redirect, embedded], default: redirect }
        prefill:
          type: object
          additionalProperties: false
          properties:
            msisdn: { type: string, description: E.164 }
            msisdnEditable: { type: boolean, default: true }
        operator: { type: string }
        metadata: { type: object, maxProperties: 20 }
    Payment:
      type: object
      properties:
        object: { type: string, const: payment }
        paymentUid: { type: string }
        mode: { type: string, enum: [HOSTED, API] }
        status: { type: string, enum: [REQUIRES_ACTION, REQUIRES_START, REQUIRES_CODE, PROCESSING, SUCCEEDED, FAILED, EXPIRED, CANCELED] }
        checkoutUrl: { type: string, format: uri }
        productUid: { type: string }
        offerUid: { type: string }
        expiresAt: { type: string, format: date-time }
        metadata: { type: object }
        orderId: { type: string }
        subscriberUid: { type: string }
    Subscription:
      type: object
      properties:
        subscriptionUid: { type: string }
        status: { type: string }
        productUid: { type: string }
        offerUid: { type: string }
        subscriberUid: { type: string }
        currentPeriodEnd: { type: string, format: date-time }
        cancelAt: { type: [string, 'null'], format: date-time }
    WebhookEvent:
      type: object
      properties:
        event: { type: string }
        paymentUid: { type: string }
        status: { type: string }
        orderId: { type: string }
        productUid: { type: string }
        offerUid: { type: string }
        subscriberUid: { type: string }
        amountCollected: { $ref: '#/components/schemas/Money' }
        periodOutstanding: { $ref: '#/components/schemas/Money' }
        metadata: { type: object }
        occurredAt: { type: string, format: date-time }
    Money:
      type: object
      required: [amount, currency]
      properties:
        amount: { type: integer, description: Minor units (TND = millimes) }
        currency: { type: string, description: ISO 4217 }
