openapi: 3.1.0
info:
  title: GMPay Edge Merchant API
  version: 1.0.0
  description: >-
    GMPay Edge is a single-deployment, single-tenant gateway. GMPay is the
    primary merchant protocol, and EPay is a compatibility adapter over the
    same credential, order service, checkout, payment processor, and Webhook
    outbox. Internal operations use /admin and are outside this merchant API.
servers:
  - url: https://pay.example.com
paths:
  /payments/gmpay/v1/order/create-transaction:
    post:
      operationId: createGmpayTransaction
      summary: Create a GMPay transaction
      description: Accepts JSON or form data. Sign all non-empty fields except signature by sorting key=value pairs by key, joining them with &, appending the API Secret, and calculating lowercase MD5.
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/GmpayCreateRequest" }
          application/x-www-form-urlencoded:
            schema: { $ref: "#/components/schemas/GmpayCreateRequest" }
      responses:
        "200":
          description: Transaction created
          content:
            application/json:
              schema: { $ref: "#/components/schemas/GmpayCreateResponse" }
        "400": { $ref: "#/components/responses/GatewayError" }
        "401": { $ref: "#/components/responses/GatewayError" }
        "429": { $ref: "#/components/responses/GatewayError" }
        "500": { $ref: "#/components/responses/GatewayError" }
        "502": { $ref: "#/components/responses/GatewayError" }
        "503": { $ref: "#/components/responses/GatewayError" }
      callbacks:
        orderNotification:
          '{$request.body#/notify_url}':
            post:
              summary: GMPay order notification
              description: The signature is lowercase MD5 of the sorted non-empty callback fields plus the same API Secret. Return plain text ok with HTTP 200.
              requestBody:
                required: true
                content:
                  application/json:
                    schema: { $ref: "#/components/schemas/GmpayNotification" }
              responses:
                "200":
                  description: Plain text ok acknowledgement
                  content:
                    text/plain:
                      schema: { type: string, const: ok }
  /payments/gmpay/v1/order/query:
    get:
      operationId: queryGmpayTransaction
      summary: Query a GMPay transaction
      description: Query by exactly one trade_id or order_id. Sign all non-empty query fields except signature using the same lowercase MD5 contract as order creation.
      parameters:
        - { name: pid, in: query, required: true, schema: { type: string } }
        - { name: trade_id, in: query, required: false, schema: { type: string, maxLength: 128 } }
        - { name: order_id, in: query, required: false, schema: { type: string, maxLength: 128 } }
        - { name: signature, in: query, required: true, schema: { type: string, pattern: "^[0-9a-f]{32}$" } }
      responses:
        "200":
          description: Transaction status and immutable payment snapshot
          content:
            application/json:
              schema: { $ref: "#/components/schemas/GmpayCreateResponse" }
        "400": { $ref: "#/components/responses/GatewayError" }
        "401": { $ref: "#/components/responses/GatewayError" }
        "404": { $ref: "#/components/responses/GatewayError" }
        "429": { $ref: "#/components/responses/GatewayError" }
        "500": { $ref: "#/components/responses/GatewayError" }
  /payments/epay/v1/order/create-transaction/submit.php:
    get:
      operationId: createEpayTransactionByQuery
      summary: Create an EPay-compatible transaction
      parameters:
        - { $ref: "#/components/parameters/EpayPid" }
        - { $ref: "#/components/parameters/EpayMoney" }
        - { $ref: "#/components/parameters/EpayOrderNo" }
        - { $ref: "#/components/parameters/EpayNotifyUrl" }
        - { $ref: "#/components/parameters/EpayReturnUrl" }
        - { $ref: "#/components/parameters/EpayName" }
        - { $ref: "#/components/parameters/EpayType" }
        - { $ref: "#/components/parameters/EpayParam" }
        - { $ref: "#/components/parameters/EpayClientIp" }
        - { $ref: "#/components/parameters/EpayDevice" }
        - { $ref: "#/components/parameters/EpaySign" }
        - { $ref: "#/components/parameters/EpaySignType" }
      responses:
        "200":
          description: Created; open data.payment_url to enter the unified checkout
          content:
            application/json:
              schema: { $ref: "#/components/schemas/GmpayCreateResponse" }
        "400": { $ref: "#/components/responses/GatewayError" }
        "401": { $ref: "#/components/responses/GatewayError" }
        "429": { $ref: "#/components/responses/GatewayError" }
        "500": { $ref: "#/components/responses/GatewayError" }
        "502": { $ref: "#/components/responses/GatewayError" }
        "503": { $ref: "#/components/responses/GatewayError" }
    post:
      operationId: createEpayTransactionByForm
      summary: Create an EPay-compatible transaction
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema: { $ref: "#/components/schemas/EpayCreateRequest" }
      responses:
        "200":
          description: Created; open data.payment_url to enter the unified checkout
          content:
            application/json:
              schema: { $ref: "#/components/schemas/GmpayCreateResponse" }
        "400": { $ref: "#/components/responses/GatewayError" }
        "401": { $ref: "#/components/responses/GatewayError" }
        "429": { $ref: "#/components/responses/GatewayError" }
        "500": { $ref: "#/components/responses/GatewayError" }
        "502": { $ref: "#/components/responses/GatewayError" }
        "503": { $ref: "#/components/responses/GatewayError" }
  /payments/epay/v1/order/create-transaction/mapi.php:
    get:
      operationId: createEpayMApiTransactionByQuery
      summary: Create an EPay transaction and return a Pro-compatible response
      parameters:
        - { $ref: "#/components/parameters/EpayPid" }
        - { $ref: "#/components/parameters/EpayMoney" }
        - { $ref: "#/components/parameters/EpayOrderNo" }
        - { $ref: "#/components/parameters/EpayNotifyUrl" }
        - { $ref: "#/components/parameters/EpayReturnUrl" }
        - { $ref: "#/components/parameters/EpayName" }
        - { $ref: "#/components/parameters/EpayType" }
        - { $ref: "#/components/parameters/EpayParam" }
        - { $ref: "#/components/parameters/EpayClientIp" }
        - { $ref: "#/components/parameters/EpayDevice" }
        - { $ref: "#/components/parameters/EpaySign" }
        - { $ref: "#/components/parameters/EpaySignType" }
      responses:
        "200":
          description: Transaction created
          content:
            application/json:
              schema: { $ref: "#/components/schemas/EpayCreateResponse" }
        "400": { $ref: "#/components/responses/EpayError" }
        "401": { $ref: "#/components/responses/EpayError" }
        "429": { $ref: "#/components/responses/EpayError" }
        "500": { $ref: "#/components/responses/EpayError" }
        "502": { $ref: "#/components/responses/EpayError" }
        "503": { $ref: "#/components/responses/EpayError" }
    post:
      operationId: createEpayMApiTransactionByForm
      summary: Create an EPay transaction and return a Pro-compatible response
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema: { $ref: "#/components/schemas/EpayCreateRequest" }
      responses:
        "200":
          description: Transaction created
          content:
            application/json:
              schema: { $ref: "#/components/schemas/EpayCreateResponse" }
        "400": { $ref: "#/components/responses/EpayError" }
        "401": { $ref: "#/components/responses/EpayError" }
        "429": { $ref: "#/components/responses/EpayError" }
        "500": { $ref: "#/components/responses/EpayError" }
        "502": { $ref: "#/components/responses/EpayError" }
        "503": { $ref: "#/components/responses/EpayError" }
  /payments/epay/v1/order/create-transaction/api.php:
    get:
      operationId: queryEpayTransaction
      summary: Query an EPay transaction
      description: Set act=order and sign the non-empty query fields with the merchant API secret. Plain-text keys are not accepted in URLs.
      parameters:
        - { name: act, in: query, required: true, schema: { const: order } }
        - { name: pid, in: query, required: true, schema: { type: string } }
        - { name: trade_no, in: query, required: false, schema: { type: string, maxLength: 128 } }
        - { name: out_trade_no, in: query, required: false, schema: { type: string, maxLength: 128 } }
        - { name: sign, in: query, required: true, schema: { type: string, pattern: "^[0-9a-f]{32}$" } }
        - { name: sign_type, in: query, required: false, schema: { const: MD5 } }
      responses:
        "200":
          description: EPay transaction status
          content:
            application/json:
              schema: { $ref: "#/components/schemas/EpayQueryResponse" }
        "400": { $ref: "#/components/responses/EpayError" }
        "401": { $ref: "#/components/responses/EpayError" }
        "404": { $ref: "#/components/responses/EpayError" }
        "429": { $ref: "#/components/responses/EpayError" }
        "500": { $ref: "#/components/responses/EpayError" }
components:
  parameters:
    EpayPid: { name: pid, in: query, required: true, schema: { type: string } }
    EpayMoney: { name: money, in: query, required: true, schema: { type: string, pattern: "^(?!0(?:\\.0+)?$)(?:0|[1-9]\\d*)(?:\\.\\d{1,8})?$" } }
    EpayOrderNo: { name: out_trade_no, in: query, required: true, schema: { type: string, maxLength: 128 } }
    EpayNotifyUrl: { name: notify_url, in: query, required: true, schema: { type: string, format: uri } }
    EpayReturnUrl: { name: return_url, in: query, required: false, schema: { type: string, format: uri } }
    EpayName: { name: name, in: query, required: false, schema: { type: string, maxLength: 500 } }
    EpayType: { name: type, in: query, required: false, description: alipay keeps the existing selectable compatibility behavior; asset.network selects a payment method., schema: { type: string } }
    EpayParam: { name: param, in: query, required: false, description: Opaque merchant context returned unchanged in notifications, redirects, and queries., schema: { type: string, maxLength: 500 } }
    EpayClientIp: { name: clientip, in: query, required: false, schema: { type: string, maxLength: 64 } }
    EpayDevice: { name: device, in: query, required: false, schema: { type: string, maxLength: 64 } }
    EpaySign: { name: sign, in: query, required: true, schema: { type: string, pattern: "^[0-9a-f]{32}$" } }
    EpaySignType: { name: sign_type, in: query, required: false, schema: { const: MD5 } }
  responses:
    GatewayError:
      description: Gateway error
      content:
        application/json:
          schema: { $ref: "#/components/schemas/GatewayEnvelope" }
    EpayError:
      description: EPay compatibility error
      content:
        application/json:
          schema: { $ref: "#/components/schemas/EpayError" }
  schemas:
    OrderStatus:
      type: string
      enum: [pending, confirming, partially_paid, paid, overpaid, expired, cancelled, failed, refunded]
    GmpayCreateRequest:
      type: object
      required: [pid, order_id, currency, amount, notify_url, signature]
      properties:
        pid: { type: string, description: API credential PID }
        order_id: { type: string, minLength: 1, maxLength: 128 }
        currency: { type: string, minLength: 3, maxLength: 3 }
        token: { type: string, pattern: "^[A-Za-z0-9_-]{2,20}$", description: Omit together with network to let checkout select a payment method. }
        network: { type: string, pattern: "^[A-Za-z0-9-]{2,32}$", description: Omit together with token to let checkout select a payment method. }
        amount: { type: string, pattern: "^(?!0(?:\\.0+)?$)(?:0|[1-9]\\d*)(?:\\.\\d{1,8})?$" }
        notify_url: { type: string, format: uri, pattern: "^https://" }
        redirect_url: { type: string, format: uri, pattern: "^https://" }
        name: { type: string, maxLength: 500 }
        signature: { type: string, pattern: "^[0-9a-f]{32}$" }
      example:
        pid: "100000000001"
        order_id: invoice-1001
        currency: USD
        amount: "12.50"
        notify_url: https://merchant.example/notify
        signature: 0123456789abcdef0123456789abcdef
    GmpayCreateData:
      type: object
      required: [trade_id, order_id, amount, currency, actual_amount, receive_address, token, network, status, expiration_time, payment_url]
      properties:
        trade_id: { type: string, pattern: "^[0-9]{20}$" }
        order_id: { type: string }
        amount: { type: string }
        currency: { type: string }
        actual_amount: { type: string }
        receive_address: { type: string }
        token: { type: string }
        network: { type: string }
        status: { $ref: "#/components/schemas/OrderStatus" }
        expiration_time: { type: integer }
        payment_url: { type: string, format: uri }
    GmpayCreateResponse:
      allOf:
        - { $ref: "#/components/schemas/GatewayEnvelope" }
        - type: object
          properties:
            data: { $ref: "#/components/schemas/GmpayCreateData" }
    GmpayNotification:
      type: object
      required: [pid, trade_id, order_id, amount, actual_amount, receive_address, token, block_transaction_id, status, signature]
      properties:
        pid: { type: string }
        trade_id: { type: string, pattern: "^[0-9]{20}$" }
        order_id: { type: string }
        amount: { type: string }
        actual_amount: { type: string }
        receive_address: { type: string }
        token: { type: string }
        block_transaction_id: { type: string }
        status: { $ref: "#/components/schemas/OrderStatus" }
        signature: { type: string, pattern: "^[0-9a-f]{32}$" }
      example:
        pid: "100000000001"
        trade_id: "26071406211234567890"
        order_id: invoice-1001
        amount: "12.50"
        actual_amount: "12.50"
        receive_address: TExampleAddress
        token: USDT
        block_transaction_id: transaction-hash
        status: paid
        signature: 0123456789abcdef0123456789abcdef
    EpayCreateRequest:
      type: object
      required: [pid, money, out_trade_no, notify_url, sign]
      properties:
        pid: { type: string }
        money: { type: string }
        out_trade_no: { type: string, maxLength: 128 }
        notify_url: { type: string, format: uri }
        return_url: { type: string, format: uri }
        name: { type: string, maxLength: 500 }
        type: { type: string, description: alipay or asset.network }
        param: { type: string, maxLength: 500 }
        clientip: { type: string, maxLength: 64 }
        device: { type: string, maxLength: 64 }
        sign: { type: string, pattern: "^[0-9a-f]{32}$" }
        sign_type: { const: MD5 }
    EpayError:
      type: object
      required: [code, msg]
      properties:
        code: { type: integer, const: -1 }
        msg: { type: string }
    EpayCreateResponse:
      type: object
      required: [code, msg, trade_no, payurl, qrcode, img, param]
      properties:
        code: { type: integer, const: 1 }
        msg: { type: string, const: success }
        trade_no: { type: string }
        payurl: { type: string, format: uri }
        qrcode: { type: string, format: uri }
        img: { type: string, format: uri }
        param: { type: string }
    EpayQueryResponse:
      type: object
      required: [code, msg, trade_no, out_trade_no, type, name, money, status, trade_status, param]
      properties:
        code: { type: integer, const: 1 }
        msg: { type: string, const: success }
        trade_no: { type: string }
        out_trade_no: { type: string }
        type: { type: string }
        name: { type: string }
        money: { type: string }
        status: { type: integer, enum: [0, 1] }
        trade_status: { type: string }
        param: { type: string }
    GatewayEnvelope:
      type: object
      required: [status_code, message, data, request_id]
      properties:
        status_code: { type: integer }
        message: { type: string }
        data: {}
        request_id: { type: string }
      example:
        status_code: 10009
        message: invalid parameters
        data: null
        request_id: 73e3648b-b257-47e6-a510-12772cac0448
