openapi: 3.1.0
info:
  title: Users API - Loyalty, Permissions, Segments, and Cards
  version: 4.0.0
  description: |-
    # Introduction

    This document covers authenticated user profile, loyalty-enabled profile
    updates, registration flows, permissions caching, segmentation, CAPTCHA,
    and card management endpoints.

    ## Dynamic Configuration
    - `THROTTLING_SETTINGS`: Per-scope throttling limits (e.g. `{"register":{"count":5,"duration":60}}`).
    - `GUEST_USER_REGISTRATION_REQUIRES_EMAIL_VERIFICATION`: When enabled,
      newly registered users must verify email before login.
    - `CUSTOMER_LOYALTY_CARD_SERVICE`: Defines the loyalty card service class
      and connection details for loyalty-enabled registration and profile
      updates.
    - `PHONE_NUMBER_UNIQUE_VALIDATOR_ACTIVE`: When enabled, phone numbers must
      be unique across active users during registration/profile updates.
    - `THROTTLING_SETTINGS`: Per-scope limits for registration, permissions,
      profile update, and other throttled endpoints (e.g. `{"register":{"count":5,"duration":60}}`).

    ## System Configuration (requires deployment)
    - `USER_REGISTER_REQUIRED_FIELDS`: Extra required fields enforced during
      registration.
    - `USER_REQUIRED_FIELDS`: Extra required fields enforced during profile
      updates.
    - `USER_PROFILE_VALIDATIONS` / `REGISTER_VALIDATIONS`: Custom validation
      hooks applied on profile and registration payloads.
    - `REST_REGISTER_VIEW`: Selects the registration flow. Options:
      - `omnishop.users.views.RegisterSMSOtpView` (SMS 2FA)
      - `omnishop.users.views.RegisterKvkkView` (KVKK consent, SMS optional)
      - `omnishop.users.views.UserRegisterWithLoyaltyCardViewSet` (loyalty-capable, SMS verification)
      - `omnishop.users.views.RegisterRedirectView` (default, email-first)

    ## Authentication

    - Session cookie: send `Cookie: sessionid=<value>` (or site-specific session
      cookie) on authenticated endpoints.
    - CSRF protection: for unsafe methods (POST/PUT/PATCH/DELETE) include
      `X-CSRFToken` with the token issued by the site.

servers:
  - description: Default commerce site
    url: https://{commerce_url}
    variables:
      commerce_url:
        default: sandbox.akinon.com
        description: Commerce storefront hostname

security: []

tags:
  - name: Profile
    description: Authenticated profile retrieval and updates
  - name: Registration
    description: Registration flows with loyalty support
  - name: Permissions
    description: Cached consent preferences for communication channels
  - name: Segments
    description: Customer segmentation utilities
  - name: Cards
    description: Stored and saved card management
  - name: Security
    description: CAPTCHA helper

paths:
  /users/permissions/:
    post:
      tags: [Permissions]
      operationId: updateUserPermissions
      summary: Update cached user permission flags
      description: |-
        Stores communication permission flags for a basket/user pair.

        Throttling scope: `user-permissions`.
      parameters:
        - $ref: '#/components/parameters/SessionCookieHeader'
        - $ref: '#/components/parameters/CSRFTokenHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UserPermissionRequest'
      responses:
        '200':
          description: Permissions updated
        '400':
          description: Validation error

  /users/profile/:
    get:
      tags: [Profile]
      operationId: getProfile
      summary: Retrieve user profile
      security:
        - SessionCookie: []
      parameters:
        - $ref: '#/components/parameters/SessionCookieHeader'
      responses:
        '200':
          description: Profile retrieved
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserProfile'
        '401':
          description: Authentication required
    put:
      tags: [Profile]
      operationId: updateProfile
      summary: Update user profile
      security:
        - SessionCookie: []
      parameters:
        - $ref: '#/components/parameters/SessionCookieHeader'
        - $ref: '#/components/parameters/CSRFTokenHeader'
      description: |-
        Updates profile fields. Required fields are driven by
        `USER_REQUIRED_FIELDS` (System Configuration).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UserProfileUpdate'
      responses:
        '200':
          description: Profile updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserProfile'
        '400':
          description: Validation error
        '401':
          description: Authentication required
    patch:
      tags: [Profile]
      operationId: partialUpdateProfile
      summary: Partially update user profile
      security:
        - SessionCookie: []
      parameters:
        - $ref: '#/components/parameters/SessionCookieHeader'
        - $ref: '#/components/parameters/CSRFTokenHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UserProfileUpdate'
      responses:
        '200':
          description: Profile updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserProfile'
        '400':
          description: Validation error
        '401':
          description: Authentication required

  /users/profile-with-loyalty/:
    get:
      tags: [Profile]
      operationId: getProfileWithLoyalty
      summary: Retrieve loyalty-enabled profile
      security:
        - SessionCookie: []
      parameters:
        - $ref: '#/components/parameters/SessionCookieHeader'
      responses:
        '200':
          description: Profile retrieved
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserProfileWithLoyalty'
        '401':
          description: Authentication required
    put:
      tags: [Profile]
      operationId: updateProfileWithLoyalty
      summary: Update profile with loyalty sync
      security:
        - SessionCookie: []
      parameters:
        - $ref: '#/components/parameters/SessionCookieHeader'
        - $ref: '#/components/parameters/CSRFTokenHeader'
      description: |-
        Updates profile and, when configured, synchronizes loyalty data using
        `CUSTOMER_LOYALTY_CARD_SERVICE` (Dynamic Configuration). Phone changes
        require SMS verification (`code` + `resend` flow).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UserProfileWithLoyaltyUpdate'
      responses:
        '200':
          description: Profile updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserProfileWithLoyalty'
        '202':
          description: Verification code sent for phone confirmation
        '400':
          description: Validation error
        '401':
          description: Authentication required
    patch:
      tags: [Profile]
      operationId: partialUpdateProfileWithLoyalty
      summary: Partially update profile with loyalty sync
      security:
        - SessionCookie: []
      parameters:
        - $ref: '#/components/parameters/SessionCookieHeader'
        - $ref: '#/components/parameters/CSRFTokenHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UserProfileWithLoyaltyUpdate'
      responses:
        '200':
          description: Profile updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserProfileWithLoyalty'
        '202':
          description: Verification code sent for phone confirmation
        '400':
          description: Validation error
        '401':
          description: Authentication required

  /users/pz-captcha/:
    get:
      tags: [Security]
      operationId: getPzCaptcha
      summary: Serve CAPTCHA challenge
      description: |-
        Renders CAPTCHA form and returns redirect location upon success.
      responses:
        '200':
          description: CAPTCHA rendered or redirect provided
        '302':
          description: Redirect after successful CAPTCHA

  /users/register-with-loyalty/:
    post:
      tags: [Registration]
      operationId: registerWithLoyalty
      summary: Register user with optional loyalty enrollment
      description: |-
        Creates a user account and optionally enrolls loyalty. Phone must be
        unique when `PHONE_NUMBER_UNIQUE_VALIDATOR_ACTIVE` is enabled. Loyalty
        behavior is driven by `CUSTOMER_LOYALTY_CARD_SERVICE`.

        Throttling scope: `register-with-loyalty`.
        Two-factor: SMS verification via `code`/`resend`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RegisterWithLoyaltyRequest'
      responses:
        '201':
          description: Registration succeeded
        '202':
          description: Verification code sent
        '400':
          description: Validation error

  /users/registration/:
    post:
      tags: [Registration]
      operationId: registerUser
      summary: Register user
      description: |-
        Creates a user account. When
        `GUEST_USER_REGISTRATION_REQUIRES_EMAIL_VERIFICATION` is enabled,
        login is blocked until email is verified.

        The registration flow is selected via `REST_REGISTER_VIEW`
        (System Configuration):
        - `RegisterSMSOtpView`: Requires phone + SMS code (2FA).
        - `RegisterKvkkView`: Requires consent fields; may request SMS code.
        - `UserRegisterWithLoyaltyCardViewSet`: Supports loyalty enrollment; phone verification via SMS.
        - `RegisterRedirectView`: Default email-first flow.

        2FA coverage:
        - SMS-based second factor is enforced when `RegisterSMSOtpView` is active.
        - Loyalty registration enforces SMS verification for phone changes and enrollment.
        - When `RegisterSMSOtpView` is configured, this endpoint requires `phone`
          plus SMS `code`/`resend` values for completion.

        Throttling scope: `register`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RegisterRequest'
            examples:
              defaultFlow:
                summary: Default registration (email-first)
                value:
                  first_name: Jane
                  last_name: Doe
                  email: jane.doe@example.com
                  password: "StrongPass!234"
                  confirm: true
                  email_allowed: true
                  sms_allowed: false
                  whatsapp_allowed: false
                  call_allowed: false
                  gender: female
                  date_of_birth: "1990-01-15"
              sms2fa:
                summary: SMS 2FA registration (RegisterSMSOtpView / RegisterKvkkView)
                value:
                  first_name: Jane
                  last_name: Doe
                  email: jane.doe@example.com
                  password: "StrongPass!234"
                  confirm: true
                  phone: "+15551234567"
                  code: "123456"
                  resend: false
                  email_allowed: true
                  sms_allowed: true
                  whatsapp_allowed: false
                  call_allowed: false
                  gender: female
                  date_of_birth: "1990-01-15"
      responses:
        '201':
          description: Registration succeeded
        '400':
          description: Validation error

  /users/registration/account-confirm-email/{key}/:
    get:
      tags: [Registration]
      operationId: confirmEmail
      summary: Confirm email
      parameters:
        - in: path
          name: key
          required: true
          schema:
            type: string
          description: Email confirmation key
      responses:
        '302':
          description: Email confirmed and redirected
        '400':
          description: Invalid or expired key

  /users/saved-cards/:
    get:
      tags: [Cards]
      operationId: listSavedCards
      summary: List saved cards
      security:
        - SessionCookie: []
      parameters:
        - $ref: '#/components/parameters/SessionCookieHeader'
      responses:
        '200':
          description: Saved cards listed
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/SavedCard'
        '401':
          description: Authentication required

  /users/saved-cards/{pk}/:
    get:
      tags: [Cards]
      operationId: retrieveSavedCard
      summary: Retrieve saved card
      security:
        - SessionCookie: []
      parameters:
        - $ref: '#/components/parameters/SessionCookieHeader'
        - in: path
          name: pk
          required: true
          schema:
            type: integer
      responses:
        '200':
          description: Saved card retrieved
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SavedCard'
        '401':
          description: Authentication required
        '404':
          description: Card not found
    patch:
      tags: [Cards]
      operationId: updateSavedCard
      summary: Update saved card
      security:
        - SessionCookie: []
      parameters:
        - $ref: '#/components/parameters/SessionCookieHeader'
        - $ref: '#/components/parameters/CSRFTokenHeader'
        - in: path
          name: pk
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SavedCardUpdate'
      responses:
        '200':
          description: Saved card updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SavedCard'
        '401':
          description: Authentication required
        '404':
          description: Card not found
    delete:
      tags: [Cards]
      operationId: deleteSavedCard
      summary: Delete saved card
      security:
        - SessionCookie: []
      parameters:
        - $ref: '#/components/parameters/SessionCookieHeader'
        - $ref: '#/components/parameters/CSRFTokenHeader'
        - in: path
          name: pk
          required: true
          schema:
            type: integer
      responses:
        '204':
          description: Saved card deleted
        '401':
          description: Authentication required
        '404':
          description: Card not found

  /users/segments/:
    get:
      tags: [Segments]
      operationId: listSegments
      summary: List available segments for user
      security:
        - SessionCookie: []
      parameters:
        - $ref: '#/components/parameters/SessionCookieHeader'
      description: |-
        Returns segments available to the authenticated user based on
        segmentation rules.
      responses:
        '200':
          description: Available segments
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/SegmentSummary'
        '401':
          description: Authentication required

  /users/segments/{segment_id}/set/:
    post:
      tags: [Segments]
      operationId: setSegment
      summary: Assign segment
      security:
        - SessionCookie: []
      parameters:
        - $ref: '#/components/parameters/SessionCookieHeader'
        - $ref: '#/components/parameters/CSRFTokenHeader'
        - in: path
          name: segment_id
          required: true
          schema:
            type: integer
      responses:
        '200':
          description: Segment assigned
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SegmentCurrent'
        '401':
          description: Authentication required
        '404':
          description: Segment not found

  /users/segments/recalculate_segment/:
    post:
      tags: [Segments]
      operationId: recalculateSegment
      summary: Recalculate segment (idempotent)
      description: |-
        Re-evaluates segmentation rules and assigns the best matching segment.
        This action is available without authentication.
      responses:
        '200':
          description: Segment recalculated
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    example: Segment successfully assigned. segment:employee

  /users/stored-cards/:
    get:
      tags: [Cards]
      operationId: listStoredCards
      summary: List stored cards
      security:
        - SessionCookie: []
      parameters:
        - $ref: '#/components/parameters/SessionCookieHeader'
      description: |-
        Returns stored cards for the authenticated user.
      responses:
        '200':
          description: Stored cards listed
          content:
            application/json:
              schema:
                type: object
                properties:
                  cards:
                    type: array
                    items:
                      $ref: '#/components/schemas/StoredCard'
        '401':
          description: Authentication required

components:
  securitySchemes:
    SessionCookie:
      type: apiKey
      in: header
      name: Cookie
      description: Session cookie (e.g., `sessionid=abc123`)

  parameters:
    SessionCookieHeader:
      name: Cookie
      in: header
      required: true
      description: Session cookie header (e.g. `sessionid=abc123`)
      schema:
        type: string
    CSRFTokenHeader:
      name: X-CSRFToken
      in: header
      required: true
      description: CSRF token header for unsafe methods (POST/PUT/PATCH/DELETE)
      schema:
        type: string

  schemas:
    UserPermissionRequest:
      type: object
      required:
        - uid
        - basket
      properties:
        uid:
          type: string
          description: User identifier used by permission cache
        basket:
          type: integer
          description: Active basket identifier
        sms_allowed:
          type: boolean
          description: SMS communication consent
        call_allowed:
          type: boolean
          description: Call communication consent
        email_allowed:
          type: boolean
          description: Email communication consent
        whatsapp_allowed:
          type: boolean
          description: WhatsApp communication consent

    UserProfile:
      type: object
      required:
        - id
        - first_name
        - last_name
        - email_allowed
        - sms_allowed
        - whatsapp_allowed
        - call_allowed
        - email
        - phone
        - gender
        - language_code
        - attributes
        - date_joined
      properties:
        id:
          type: integer
        first_name:
          type: string
          maxLength: 50
        last_name:
          type: string
          maxLength: 50
        email_allowed:
          type: boolean
        sms_allowed:
          type: boolean
        whatsapp_allowed:
          type: boolean
        call_allowed:
          type:
            - boolean
            - 'null'
        avatar:
          type:
            - string
            - 'null'
          format: binary
        email:
          type: string
          format: email
        phone:
          type:
            - string
            - 'null'
        date_of_birth:
          type:
            - string
            - 'null'
          format: date
        gender:
          type:
            - string
            - 'null'
          enum:
            - male
            - female
        genders:
          type: array
          description: Allowed gender values
          items:
            type: string
            enum:
              - male
              - female
        language_code:
          type: string
        attributes:
          type: object
          additionalProperties: true
        date_joined:
          type: string
          format: date-time

    UserProfileUpdate:
      allOf:
        - $ref: '#/components/schemas/UserProfile'

    UserProfileWithLoyalty:
      allOf:
        - $ref: '#/components/schemas/UserProfile'
        - type: object
          required:
            - code
            - resend
          properties:
            code:
              type:
                - string
                - 'null'
              description: Verification code for phone changes
            resend:
              type: boolean
              description: Request a new verification code

    UserProfileWithLoyaltyUpdate:
      allOf:
        - $ref: '#/components/schemas/UserProfileWithLoyalty'

    RegisterRequest:
      type: object
      required:
        - first_name
        - last_name
        - email
        - password
        - confirm
      properties:
        first_name:
          type: string
          maxLength: 50
        last_name:
          type: string
          maxLength: 50
        email:
          type: string
          format: email
        password:
          type: string
          format: password
        email_allowed:
          type: boolean
          default: false
        sms_allowed:
          type: boolean
          default: false
        whatsapp_allowed:
          type: boolean
          default: false
        call_allowed:
          type:
            - boolean
            - 'null'
        confirm:
          type: boolean
          description: Confirms acceptance of policies
        code:
          type:
            - string
            - 'null'
          description: SMS verification code (used in SMS OTP/KVKK flows)
        resend:
          type:
            - boolean
            - 'null'
          default: false
          description: Request a new SMS verification code (SMS OTP/KVKK flows)
        gender:
          type:
            - string
            - 'null'
          enum:
            - male
            - female
        date_of_birth:
          type:
            - string
            - 'null'
          format: date
        username:
          type:
            - string
            - 'null'
          maxLength: 150
        user_type:
          type: string
          description: User type enumeration value
        phone:
          type:
            - string
            - 'null'
        attributes:
          type: object
          additionalProperties: true

    RegisterWithLoyaltyRequest:
      allOf:
        - $ref: '#/components/schemas/RegisterRequest'
        - type: object
          required:
            - phone
          properties:
            add_loyalty:
              type: boolean
              default: false
            code:
              type:
                - string
                - 'null'
            resend:
              type: boolean
              default: false
            phone:
              type: string
              maxLength: 60
            gender:
              type:
                - string
                - 'null'
              enum:
                - male
                - female
            date_of_birth:
              type:
                - string
                - 'null'
              format: date

    SavedCard:
      type: object
      description: Saved card resource returned by saved-cards endpoints. Fields mirror `omnishop.payments.models.SavedCard`.
      required:
        - pk
        - masked_card_number
        - token
        - user
      properties:
        pk:
          type: integer
          description: Saved card identifier
        name:
          type:
            - string
            - 'null'
          description: User-defined card label
          example: Personal Visa
        token:
          type: string
          description: Gateway token for the saved card (opaque)
        user:
          type: integer
          description: Owner user ID
        masked_card_number:
          type: string
          description: Masked PAN (e.g., **** **** **** 1234)
          example: "**** **** **** 1234"
        pos:
          type:
            - integer
            - 'null'
          description: POS identifier associated with the card
        created_date:
          type: string
          format: date-time
          description: Creation timestamp
        modified_date:
          type: string
          format: date-time
          description: Last update timestamp

    SavedCardUpdate:
      type: object
      description: Fields allowed for saved card update
      properties:
        name:
          type:
            - string
            - 'null'
          description: User-defined card label

    StoredCard:
      type: object
      description: Stored card resource returned by stored-cards endpoint
      additionalProperties: true

    SegmentSummary:
      type: object
      required:
        - pk
        - name
        - priority
      properties:
        pk:
          type: integer
        name:
          type: string
        priority:
          type: integer

    SegmentCurrent:
      type: object
      required:
        - pk
        - name
        - priority
      properties:
        pk:
          type: integer
        name:
          type: string
        priority:
          type: integer
        price_list:
          type:
            - integer
            - 'null'


