openapi: 3.0.3
info:
  title: MANJO QRIS API
  version: 1.0.0
  description: |
    Dokumentasi Swagger untuk API QRIS MANJO.

    Endpoint yang tersedia:
    - Create QRIS: `POST /api/v1.0/qr/qr-mpm-generate`
    - Check Status Transaction: `POST /api/v1.0/qr/qr-mpm-query`

servers:
  - url: /
    description: Same origin as Swagger UI host

tags:
  - name: QRIS
    description: QRIS transaction operations

paths:
  /api/v1.0/qr/qr-mpm-generate:
    post:
      tags: [QRIS]
      summary: Create QRIS (MPM Generate)
      description: |
        Membuat transaksi QRIS baru dengan status awal `PENDING`.
        Endpoint ini idempoten berdasarkan kombinasi merchant + partnerReferenceNo atau externalId.
      operationId: generateQris
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/GenerateQrisRequest'
            examples:
              sample:
                value:
                  merchantId: MRC-PTABC-001
                  partnerReferenceNo: PARTNER-REF-9999
                  externalId: EXT-MANJO-9999
                  amount:
                    value: 150000
                    currency: IDR
                  additionalInfo:
                    storeId: STR-JKT-01
                    terminalId: TRM-JKT-01
                    expiredInMinutes: 30
      responses:
        '200':
          description: Successful
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenerateQrisSuccessResponse'
              examples:
                created:
                  value:
                    responseCode: "2004700"
                    responseMessage: Successful.
                    partnerReferenceNo: PARTNER-REF-9999
                    externalId: EXT-MANJO-9999
                    transactionNo: TXN-20260725103001-AB12CD
                    manjoReferenceNo: MANJO-20260725103001-A1B2
                    qrContent: QRIS|MANJO|TXN-20260725103001-AB12CD|1ab2cd3ef4gh5ij6kl7m
                    transactionStatus: PENDING
                    amount:
                      value: "150000.00"
                      currency: IDR
                    expiredAt: "2026-07-25T11:00:01+00:00"
                idempotent:
                  value:
                    responseCode: "2004700"
                    responseMessage: Successful. Existing transaction returned.
                    partnerReferenceNo: PARTNER-REF-9999
                    externalId: EXT-MANJO-9999
                    transactionNo: TXN-20260725103001-AB12CD
                    manjoReferenceNo: MANJO-20260725103001-A1B2
                    qrContent: QRIS|MANJO|TXN-20260725103001-AB12CD|1ab2cd3ef4gh5ij6kl7m
                    transactionStatus: PENDING
                    amount:
                      value: "150000.00"
                      currency: IDR
                    expiredAt: "2026-07-25T11:00:01+00:00"
        '404':
          description: Merchant not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                responseCode: "4044701"
                responseMessage: Merchant not found.
        '422':
          description: Validation or business validation failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                unsupportedCurrency:
                  value:
                    responseCode: "4224701"
                    responseMessage: Unsupported currency. Only IDR is allowed.
                validationFailed:
                  value:
                    message: The given data was invalid.
                    errors:
                      amount.value:
                        - The amount.value field is required.
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/v1.0/qr/qr-mpm-query:
    post:
      tags: [QRIS]
      summary: Check Transaction Status (MPM Query)
      description: |
        Mengecek status transaksi QRIS berdasarkan salah satu identifier:
        - partnerReferenceNo, atau
        - transactionNo, atau
        - externalId
      operationId: queryQris
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/QueryQrisRequest'
            examples:
              byPartnerReference:
                value:
                  merchantId: MRC-PTABC-001
                  partnerReferenceNo: PARTNER-REF-9999
              byTransactionNo:
                value:
                  merchantId: MRC-PTABC-001
                  transactionNo: TXN-20260725103001-AB12CD
              byExternalId:
                value:
                  merchantId: MRC-PTABC-001
                  externalId: EXT-MANJO-9999
      responses:
        '200':
          description: Successful query
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/QueryQrisSuccessResponse'
              examples:
                pending:
                  value:
                    responseCode: "2025100"
                    responseMessage: Transaction is still pending.
                    merchantId: MRC-PTABC-001
                    partnerReferenceNo: PARTNER-REF-9999
                    externalId: EXT-MANJO-9999
                    transactionNo: TXN-20260725103001-AB12CD
                    manjoReferenceNo: MANJO-20260725103001-A1B2
                    amount:
                      value: "150000.00"
                      currency: IDR
                    transactionStatus: PENDING
                    paidAt: null
                    expiredAt: "2026-07-25T11:00:01+00:00"
                success:
                  value:
                    responseCode: "2005100"
                    responseMessage: Transaction is successful.
                    merchantId: MRC-PTABC-001
                    partnerReferenceNo: PARTNER-REF-1000
                    externalId: EXT-MANJO-1000
                    transactionNo: TXN-20260725100001-QW12ER
                    manjoReferenceNo: MANJO-20260725100001-Z9X8
                    amount:
                      value: "220000.00"
                      currency: IDR
                    transactionStatus: SUCCESS
                    paidAt: "2026-07-25T10:15:01+00:00"
                    expiredAt: "2026-07-25T10:30:01+00:00"
        '404':
          description: Merchant or transaction not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                merchantNotFound:
                  value:
                    responseCode: "4045101"
                    responseMessage: Merchant not found.
                transactionNotFound:
                  value:
                    responseCode: "4045100"
                    responseMessage: Transaction not found.
        '422':
          description: Validation failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                missingIdentifier:
                  value:
                    responseCode: "4225101"
                    responseMessage: One of partnerReferenceNo, transactionNo, or externalId is required.
                laravelValidation:
                  value:
                    message: The given data was invalid.
                    errors:
                      merchantId:
                        - The merchantId field is required.
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/v1.0/qr/qr-mpm-callback:
    post:
      tags: [QRIS]
      summary: Receive QRIS Callback
      description: |
        Endpoint callback untuk update status transaksi dari provider/acquirer.
        Jika `QRIS_CALLBACK_SECRET` diisi, header `X-Signature` wajib valid.
      operationId: callbackQris
      parameters:
        - in: header
          name: X-Signature
          required: false
          schema:
            type: string
          description: HMAC-SHA512 signature dari raw request body.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CallbackQrisRequest'
            example:
              merchantId: MRC-PTABC-001
              transactionNo: TXN-MANJO-0001
              transactionStatus: SUCCESS
              responseCode: "2005100"
              responseMessage: Transaction is successful.
              paidAt: "2026-07-25T12:45:00+07:00"
              additionalInfo:
                merchantCallbackUrl: https://merchant.test/callback/payment
      responses:
        '200':
          description: Callback accepted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CallbackQrisSuccessResponse'
              examples:
                accepted:
                  value:
                    responseCode: "2005200"
                    responseMessage: Callback accepted.
                    transactionNo: TXN-MANJO-0001
                    transactionStatus: SUCCESS
                duplicate:
                  value:
                    responseCode: "2005200"
                    responseMessage: Callback accepted (duplicate).
                    transactionNo: TXN-MANJO-0001
                    transactionStatus: SUCCESS
        '401':
          description: Invalid signature
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                responseCode: "4015201"
                responseMessage: Invalid callback signature.
        '404':
          description: Merchant or transaction not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                merchantNotFound:
                  value:
                    responseCode: "4045201"
                    responseMessage: Merchant not found.
                transactionNotFound:
                  value:
                    responseCode: "4045200"
                    responseMessage: Transaction not found.
        '422':
          description: Validation failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                responseCode: "4225201"
                responseMessage: "One identifier is required: transactionNo, externalId, or partnerReferenceNo."
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/v1.0/qr/qr-mpm-settlement-query:
    post:
      tags: [QRIS]
      summary: Query Settlement
      description: Mengecek hasil settlement dari transaksi QRIS.
      operationId: querySettlementQris
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SettlementQueryRequest'
            example:
              merchantId: MRC-PTABC-001
              transactionNo: TXN-MANJO-0002
      responses:
        '200':
          description: Settlement result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SettlementQueryResponse'
              examples:
                found:
                  value:
                    responseCode: "2005300"
                    responseMessage: Settlement found.
                    transactionNo: TXN-MANJO-0002
                    settlementRef: STL-TXN-MANJO-0002
                    settlementStatus: SETTLED
                    settleAmount: "220000.00"
                    feeAmount: "0.00"
                    settlementDate: "2026-07-25T12:30:00+07:00"
                pending:
                  value:
                    responseCode: "2025300"
                    responseMessage: Settlement not available yet.
                    transactionNo: TXN-MANJO-0001
                    settlementStatus: PENDING
        '404':
          description: Merchant or transaction not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                merchantNotFound:
                  value:
                    responseCode: "4045301"
                    responseMessage: Merchant not found.
                transactionNotFound:
                  value:
                    responseCode: "4045300"
                    responseMessage: Transaction not found.
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/v1.0/qr/merchant-notification-dispatch:
    post:
      tags: [QRIS]
      summary: Dispatch Merchant Notification
      description: |
        Memproses outbox merchant notification untuk sebuah transaksi.
        Dapat disimulasikan gagal menggunakan `simulateFailure=true`.
      operationId: dispatchMerchantNotification
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MerchantNotificationDispatchRequest'
            example:
              transactionNo: TXN-MANJO-0002
              destinationUrl: https://merchant.test/callback/payment
              simulateFailure: false
      responses:
        '200':
          description: Dispatch result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MerchantNotificationDispatchResponse'
              examples:
                sent:
                  value:
                    responseCode: "2005400"
                    responseMessage: Notification dispatched.
                    transactionNo: TXN-MANJO-0002
                    deliveryStatus: SENT
                    deliveredAt: "2026-07-25T12:31:00+07:00"
                    destinationUrl: https://merchant.test/callback/payment
                failed:
                  value:
                    responseCode: "2025401"
                    responseMessage: Notification dispatch failed (simulated).
                    transactionNo: TXN-MANJO-0002
                    deliveryStatus: FAILED
                    retryCount: 1
                    nextRetryAt: "2026-07-25T12:36:00+07:00"
        '404':
          description: Transaction or outbox not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                transactionNotFound:
                  value:
                    responseCode: "4045400"
                    responseMessage: Transaction not found.
                outboxNotFound:
                  value:
                    responseCode: "4045401"
                    responseMessage: No notification outbox found.
        '500':
          $ref: '#/components/responses/InternalServerError'

components:
  schemas:
    AmountObject:
      type: object
      required: [value, currency]
      properties:
        value:
          type: number
          format: float
          example: 150000
        currency:
          type: string
          example: IDR

    GenerateAdditionalInfo:
      type: object
      properties:
        storeId:
          type: string
          example: STR-JKT-01
        terminalId:
          type: string
          example: TRM-JKT-01
        expiredInMinutes:
          type: integer
          minimum: 1
          maximum: 180
          example: 30

    GenerateQrisRequest:
      type: object
      required:
        - merchantId
        - partnerReferenceNo
        - externalId
        - amount
      properties:
        merchantId:
          type: string
          example: MRC-PTABC-001
        partnerReferenceNo:
          type: string
          example: PARTNER-REF-9999
        externalId:
          type: string
          example: EXT-MANJO-9999
        amount:
          $ref: '#/components/schemas/AmountObject'
        additionalInfo:
          $ref: '#/components/schemas/GenerateAdditionalInfo'

    QueryQrisRequest:
      type: object
      required:
        - merchantId
      properties:
        merchantId:
          type: string
          example: MRC-PTABC-001
        partnerReferenceNo:
          type: string
          example: PARTNER-REF-9999
        transactionNo:
          type: string
          example: TXN-20260725103001-AB12CD
        externalId:
          type: string
          example: EXT-MANJO-9999

    CallbackQrisRequest:
      type: object
      required:
        - merchantId
        - transactionStatus
      properties:
        merchantId:
          type: string
          example: MRC-PTABC-001
        transactionNo:
          type: string
          nullable: true
          example: TXN-MANJO-0001
        externalId:
          type: string
          nullable: true
          example: EXT-MANJO-0001
        partnerReferenceNo:
          type: string
          nullable: true
          example: PARTNER-REF-0001
        transactionStatus:
          type: string
          enum: [PENDING, SUCCESS, FAILED, EXPIRED, REJECTED]
        responseCode:
          type: string
          nullable: true
          example: "2005100"
        responseMessage:
          type: string
          nullable: true
          example: Transaction is successful.
        paidAt:
          type: string
          format: date-time
          nullable: true
        additionalInfo:
          type: object
          properties:
            merchantCallbackUrl:
              type: string
              format: uri
              nullable: true

    CallbackQrisSuccessResponse:
      type: object
      required:
        - responseCode
        - responseMessage
        - transactionNo
        - transactionStatus
      properties:
        responseCode:
          type: string
          example: "2005200"
        responseMessage:
          type: string
          example: Callback accepted.
        transactionNo:
          type: string
          example: TXN-MANJO-0001
        transactionStatus:
          type: string
          enum: [PENDING, SUCCESS, FAILED, EXPIRED, REJECTED]

    SettlementQueryRequest:
      type: object
      required: [merchantId, transactionNo]
      properties:
        merchantId:
          type: string
          example: MRC-PTABC-001
        transactionNo:
          type: string
          example: TXN-MANJO-0002

    SettlementQueryResponse:
      type: object
      required:
        - responseCode
        - responseMessage
        - transactionNo
        - settlementStatus
      properties:
        responseCode:
          type: string
          example: "2005300"
        responseMessage:
          type: string
          example: Settlement found.
        transactionNo:
          type: string
          example: TXN-MANJO-0002
        settlementRef:
          type: string
          nullable: true
          example: STL-TXN-MANJO-0002
        settlementStatus:
          type: string
          enum: [PENDING, SETTLED, FAILED]
        settleAmount:
          type: string
          nullable: true
          example: "220000.00"
        feeAmount:
          type: string
          nullable: true
          example: "0.00"
        settlementDate:
          type: string
          format: date-time
          nullable: true

    MerchantNotificationDispatchRequest:
      type: object
      required: [transactionNo]
      properties:
        transactionNo:
          type: string
          example: TXN-MANJO-0002
        destinationUrl:
          type: string
          format: uri
          nullable: true
          example: https://merchant.test/callback/payment
        simulateFailure:
          type: boolean
          default: false

    MerchantNotificationDispatchResponse:
      type: object
      required:
        - responseCode
        - responseMessage
        - transactionNo
        - deliveryStatus
      properties:
        responseCode:
          type: string
          example: "2005400"
        responseMessage:
          type: string
          example: Notification dispatched.
        transactionNo:
          type: string
          example: TXN-MANJO-0002
        deliveryStatus:
          type: string
          enum: [PENDING, SENT, FAILED]
        deliveredAt:
          type: string
          format: date-time
          nullable: true
        destinationUrl:
          type: string
          format: uri
          nullable: true
        retryCount:
          type: integer
          nullable: true
        nextRetryAt:
          type: string
          format: date-time
          nullable: true
        payload:
          type: object
          nullable: true

    GenerateQrisSuccessResponse:
      type: object
      required:
        - responseCode
        - responseMessage
        - partnerReferenceNo
        - externalId
        - transactionNo
        - manjoReferenceNo
        - qrContent
        - transactionStatus
        - amount
        - expiredAt
      properties:
        responseCode:
          type: string
          example: "2004700"
        responseMessage:
          type: string
          example: Successful.
        partnerReferenceNo:
          type: string
          example: PARTNER-REF-9999
        externalId:
          type: string
          example: EXT-MANJO-9999
        transactionNo:
          type: string
          example: TXN-20260725103001-AB12CD
        manjoReferenceNo:
          type: string
          example: MANJO-20260725103001-A1B2
        qrContent:
          type: string
          example: QRIS|MANJO|TXN-20260725103001-AB12CD|1ab2cd3ef4gh5ij6kl7m
        transactionStatus:
          type: string
          enum: [PENDING, SUCCESS, FAILED, EXPIRED, REJECTED]
        amount:
          type: object
          required: [value, currency]
          properties:
            value:
              type: string
              example: "150000.00"
            currency:
              type: string
              example: IDR
        expiredAt:
          type: string
          format: date-time
          nullable: true

    QueryQrisSuccessResponse:
      type: object
      required:
        - responseCode
        - responseMessage
        - merchantId
        - partnerReferenceNo
        - externalId
        - transactionNo
        - amount
        - transactionStatus
      properties:
        responseCode:
          type: string
          example: "2025100"
        responseMessage:
          type: string
          example: Transaction is still pending.
        merchantId:
          type: string
          example: MRC-PTABC-001
        partnerReferenceNo:
          type: string
          example: PARTNER-REF-9999
        externalId:
          type: string
          example: EXT-MANJO-9999
        transactionNo:
          type: string
          example: TXN-20260725103001-AB12CD
        manjoReferenceNo:
          type: string
          nullable: true
          example: MANJO-20260725103001-A1B2
        amount:
          type: object
          required: [value, currency]
          properties:
            value:
              type: string
              example: "150000.00"
            currency:
              type: string
              example: IDR
        transactionStatus:
          type: string
          enum: [PENDING, SUCCESS, FAILED, EXPIRED, REJECTED]
        paidAt:
          type: string
          format: date-time
          nullable: true
        expiredAt:
          type: string
          format: date-time
          nullable: true

    ErrorResponse:
      type: object
      properties:
        responseCode:
          type: string
          example: "4045100"
        responseMessage:
          type: string
          example: Transaction not found.

  responses:
    InternalServerError:
      description: Internal server error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            responseCode: "5000000"
            responseMessage: Internal server error.
