openapi: 3.0.3
info:
  title: hwinpay Trade Gateway Merchant API
  version: 1.0.0
  description: |
    hwinpay 外卡收单商户 API，所有商户服务端请求统一通过 Gateway 访问。

    接入时请在调用环境中配置 apiKeyId、merchantPrivateKey、platformPublicKey，
    并按签名验签规范生成请求头与校验平台响应签名。
servers:
  - url: http://api.test.hwinpay.com
    description: 测试环境 Gateway
  - url: https://api.hwinpay.com
    description: 生产环境 Gateway
  - url: "{{baseUrl}}"
    description: Environment variable
tags:
  - name: Payment
    description: 直连支付与支付查询
  - name: Refund
    description: 退款接口
  - name: Checkout
    description: Hosted Checkout 收银台
security:
  - MerchantGatewayHeaders: []
paths:
  /gateway/trade/payment/submit:
    post:
      tags:
        - Payment
      summary: 直连支付
      description: 商户服务端通过 Gateway 发起直连银行卡支付。请求 body 不需要传 gwExtInfo，Gateway 会根据请求头自动注入。
      operationId: submitPayment
      parameters:
        - $ref: "#/components/parameters/XApiKeyId"
        - $ref: "#/components/parameters/XRequestId"
        - $ref: "#/components/parameters/XTimestamp"
        - $ref: "#/components/parameters/XNonce"
        - $ref: "#/components/parameters/XApiVersion"
        - $ref: "#/components/parameters/XSign"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PaymentSubmitRequest"
            examples:
              default:
                summary: Direct card payment
                value:
                  orderId: ORD-20260428-0001
                  merchant:
                    merchantNo: MCH202604241741549084
                    appId: WEB260003
                  instruction:
                    method: CARD
                    paymentInstrument:
                      cardNumber: "4111111111111111"
                      firstName: TOM
                      lastName: GREEN
                      expiryDate:
                        month: 12
                        year: 2028
                      cvc: "123"
                      billingAddress:
                        email: tom@example.com
                        address1: 1 Main Street
                        city: Los Angeles
                        state: CA
                        postalCode: "90001"
                        countryCode: US
                    value:
                      amount: 10.99
                      currency: USD
                    threeDS:
                      challenge:
                        returnUrl: https://merchant.example.com/3ds-return
                        windowSize: "03"
                  device:
                    ip: 1.1.1.1
                    acceptHeader: text/html,application/xhtml+xml
                    userAgentHeader: Mozilla/5.0
                    browserLanguage: en-US
                    browserJavascriptEnabled: true
                    browserScreenHeight: 1080
                    browserScreenWidth: 1920
                    timeZone: Asia/Shanghai
      responses:
        "200":
          description: 业务成功或业务失败均可能返回 HTTP 200，请以 code 判断结果。
          headers:
            X-Request-Id:
              $ref: "#/components/headers/XRequestId"
            X-Response-Id:
              $ref: "#/components/headers/XResponseId"
            X-Timestamp:
              $ref: "#/components/headers/XTimestamp"
            X-Nonce:
              $ref: "#/components/headers/XNonce"
            X-Sign:
              $ref: "#/components/headers/XSign"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PaymentResponseWrapper"
              examples:
                processing:
                  value:
                    code: "000000"
                    msg: ""
                    data:
                      paymentId: P202604280001
                      orderId: ORD-20260428-0001
                      tradeStatus: PROCESSING
                      reasonCode: null
                      reasonMessage: null
                      threeDS: null
                businessError:
                  value:
                    code: "020009"
                    message: 当前商户接入模式不支持直连支付接口

  /gateway/trade/payment/query:
    post:
      tags:
        - Payment
      summary: 支付结果查询
      description: 查询指定商户订单与平台支付单的当前支付状态。
      operationId: queryPayment
      parameters:
        - $ref: "#/components/parameters/XApiKeyId"
        - $ref: "#/components/parameters/XRequestId"
        - $ref: "#/components/parameters/XTimestamp"
        - $ref: "#/components/parameters/XNonce"
        - $ref: "#/components/parameters/XApiVersion"
        - $ref: "#/components/parameters/XSign"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PaymentQueryRequest"
            examples:
              default:
                value:
                  merchant:
                    merchantNo: MCH202604241741549084
                    appId: WEB260003
                  orderId: ORD-20260428-0001
                  paymentId: P202604280001
      responses:
        "200":
          description: 查询结果。
          headers:
            X-Request-Id:
              $ref: "#/components/headers/XRequestId"
            X-Response-Id:
              $ref: "#/components/headers/XResponseId"
            X-Timestamp:
              $ref: "#/components/headers/XTimestamp"
            X-Nonce:
              $ref: "#/components/headers/XNonce"
            X-Sign:
              $ref: "#/components/headers/XSign"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PaymentResponseWrapper"
              examples:
                success:
                  value:
                    code: "000000"
                    msg: ""
                    data:
                      paymentId: P202604280001
                      orderId: ORD-20260428-0001
                      tradeStatus: SUCCESS
                      reasonCode: null
                      reasonMessage: null
                      threeDS:
                        eci: "05"

  /gateway/trade/refund/submit:
    post:
      tags:
        - Refund
      summary: 退款提交
      description: 针对原支付单提交退款申请。
      operationId: submitRefund
      parameters:
        - $ref: "#/components/parameters/XApiKeyId"
        - $ref: "#/components/parameters/XRequestId"
        - $ref: "#/components/parameters/XTimestamp"
        - $ref: "#/components/parameters/XNonce"
        - $ref: "#/components/parameters/XApiVersion"
        - $ref: "#/components/parameters/XSign"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RefundSubmitRequest"
            examples:
              default:
                value:
                  merchant:
                    merchantNo: MCH202604241741549084
                    appId: WEB260003
                  orderId: ORD-20260428-0001
                  paymentId: P202604280001
                  refundReference: RFD-20260428-0001
                  amount: 5.00
                  currency: USD
                  reason: customer request
      responses:
        "200":
          description: 退款结果。
          headers:
            X-Request-Id:
              $ref: "#/components/headers/XRequestId"
            X-Response-Id:
              $ref: "#/components/headers/XResponseId"
            X-Timestamp:
              $ref: "#/components/headers/XTimestamp"
            X-Nonce:
              $ref: "#/components/headers/XNonce"
            X-Sign:
              $ref: "#/components/headers/XSign"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RefundResponseWrapper"
              examples:
                processing:
                  value:
                    code: "000000"
                    msg: ""
                    data:
                      refundId: R202604280001
                      paymentId: P202604280001
                      orderId: ORD-20260428-0001
                      refundReference: RFD-20260428-0001
                      refundType: PARTIAL
                      tradeStatus: PROCESSING
                      amount: 5.00
                      currency: USD
                      reasonCode: null
                      reasonMessage: null

  /gateway/trade/checkout/create-session:
    post:
      tags:
        - Checkout
      summary: 创建收银台会话
      description: 创建 Hosted Checkout 会话，响应中的 url 可供商户前端浏览器跳转。
      operationId: createCheckoutSession
      parameters:
        - $ref: "#/components/parameters/XApiKeyId"
        - $ref: "#/components/parameters/XRequestId"
        - $ref: "#/components/parameters/XTimestamp"
        - $ref: "#/components/parameters/XNonce"
        - $ref: "#/components/parameters/XApiVersion"
        - $ref: "#/components/parameters/XSign"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CheckoutSessionCreateRequest"
            examples:
              default:
                value:
                  orderId: ORD-20260428-0002
                  merchant:
                    merchantNo: MCH202604241741549084
                    appId: WEB260003
                  value:
                    amount: 88.50
                    currency: USD
                  description: hwinpay hosted checkout order
                  shipping:
                    firstName: TOM
                    lastName: GREEN
                    email: tom@example.com
                    phone: "13800000000"
                    country: US
                    state: CA
                    city: Los Angeles
                    addressLine1: 1 Main Street
                    postalCode: "90001"
                    fee: 3.00
                  products:
                    - productNo: SKU001
                      description: hwinpay Demo Product
                      currency: USD
                      amount: 85.50
                      quantity: 1
                      tax: 0.00
                  resultURLs:
                    successURL: https://merchant.example.com/pay/success
                    failureURL: https://merchant.example.com/pay/failure
                    pendingURL: https://merchant.example.com/pay/pending
                    cancelURL: https://merchant.example.com/pay/cancel
                    expiryURL: https://merchant.example.com/pay/expired
                  expiry: 1800
                  locale: en-US
      responses:
        "200":
          description: 收银台会话创建结果。
          headers:
            X-Request-Id:
              $ref: "#/components/headers/XRequestId"
            X-Response-Id:
              $ref: "#/components/headers/XResponseId"
            X-Timestamp:
              $ref: "#/components/headers/XTimestamp"
            X-Nonce:
              $ref: "#/components/headers/XNonce"
            X-Sign:
              $ref: "#/components/headers/XSign"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CheckoutSessionResponseWrapper"
              examples:
                created:
                  value:
                    code: "000000"
                    msg: ""
                    data:
                      checkoutSessionId: CS20260428103902640201
                      paymentId: P202604280002
                      orderId: ORD-20260428-0002
                      tradeStatus: INIT
                      url: http://api.test.hwinpay.com/gateway/trade/checkout/page?token=9b7a9053547945ae8136d32b7b422598

  /gateway/trade/checkout/page:
    get:
      tags:
        - Checkout
      summary: 打开收银台页面
      description: 浏览器打开创建会话返回的 url。该页面入口使用 token，不走商户 API Key 签名。
      operationId: openCheckoutPage
      security: []
      parameters:
        - name: token
          in: query
          required: true
          description: 创建收银台会话接口返回 url 中携带的会话令牌。
          schema:
            type: string
          example: 9b7a9053547945ae8136d32b7b422598
      responses:
        "200":
          description: HTML checkout page.
          content:
            text/html:
              schema:
                type: string

components:
  securitySchemes:
    MerchantGatewayHeaders:
      type: apiKey
      in: header
      name: X-Api-Key-Id
      description: 商户 Gateway API Key ID。完整验签还需要 X-Request-Id、X-Timestamp、X-Nonce、X-Api-Version、X-Sign。
  parameters:
    XApiKeyId:
      name: X-Api-Key-Id
      in: header
      required: true
      description: Gateway 分配的 API Key ID，由商户在调用环境中配置。
      schema:
        type: string
      example: "{{apiKeyId}}"
    XRequestId:
      name: X-Request-Id
      in: header
      required: true
      description: 请求流水号，建议全局唯一，由客户端生成并写入。
      schema:
        type: string
      example: "{{requestId}}"
    XTimestamp:
      name: X-Timestamp
      in: header
      required: true
      description: UTC ISO-8601 时间，例如 2026-04-28T10:30:45Z，由客户端生成并写入。
      schema:
        type: string
      example: "{{timestamp}}"
    XNonce:
      name: X-Nonce
      in: header
      required: true
      description: 防重放随机串，由客户端生成并写入。
      schema:
        type: string
      example: "{{nonce}}"
    XApiVersion:
      name: X-Api-Version
      in: header
      required: true
      description: 接口版本，当前传 v1。
      schema:
        type: string
        default: v1
      example: "{{apiVersion}}"
    XSign:
      name: X-Sign
      in: header
      required: true
      description: 商户私钥对请求签名串生成的 RSA-SHA256 Base64 签名，由客户端生成并写入。
      schema:
        type: string
      example: "{{sign}}"
  headers:
    XRequestId:
      description: 回传请求流水号。
      schema:
        type: string
    XResponseId:
      description: Gateway 响应流水号。
      schema:
        type: string
    XTimestamp:
      description: Gateway 响应签名时间。
      schema:
        type: string
    XNonce:
      description: Gateway 响应随机串。
      schema:
        type: string
    XSign:
      description: Gateway 使用平台私钥生成的响应签名。
      schema:
        type: string
  schemas:
    Merchant:
      type: object
      required:
        - merchantNo
        - appId
      properties:
        merchantNo:
          type: string
          example: MCH202604241741549084
        appId:
          type: string
          example: WEB260003
    Value:
      type: object
      required:
        - amount
        - currency
      properties:
        amount:
          type: number
          format: double
          minimum: 0.01
          example: 10.99
        currency:
          type: string
          description: ISO 4217 三位币种。
          example: USD
    ExpiryDate:
      type: object
      required:
        - month
        - year
      properties:
        month:
          type: integer
          example: 12
        year:
          type: integer
          example: 2028
    BillingAddress:
      type: object
      properties:
        email:
          type: string
          example: tom@example.com
        address1:
          type: string
          example: 1 Main Street
        address2:
          type: string
        city:
          type: string
          example: Los Angeles
        state:
          type: string
          example: CA
        postalCode:
          type: string
          example: "90001"
        countryCode:
          type: string
          example: US
    PaymentInstrument:
      type: object
      required:
        - cardNumber
        - firstName
        - lastName
        - expiryDate
        - cvc
      properties:
        cardNumber:
          type: string
          example: "4111111111111111"
        firstName:
          type: string
          example: TOM
        lastName:
          type: string
          example: GREEN
        expiryDate:
          $ref: "#/components/schemas/ExpiryDate"
        cvc:
          type: string
          example: "123"
        billingAddress:
          $ref: "#/components/schemas/BillingAddress"
    ThreeDSChallenge:
      type: object
      properties:
        returnUrl:
          type: string
          description: 直连支付必填；收银台模式使用 resultURLs.successURL 作为最终商户回跳地址。
          example: https://merchant.example.com/3ds-return
        windowSize:
          type: string
          description: 3DS Challenge Window Size，合法值：01=250x400，02=390x400，03=500x600，04=600x400，05=全屏/最大可用区域；未传默认 03。
          enum: ["01", "02", "03", "04", "05"]
          example: "03"
    ThreeDS:
      type: object
      properties:
        mode:
          type: string
          description: 3DS 模式，optional=非强制，required=强制 3DS；未传默认 optional。
          enum: [optional, required]
          example: optional
        challenge:
          $ref: "#/components/schemas/ThreeDSChallenge"
    ThreeDSResult:
      type: object
      nullable: true
      properties:
        eci:
          type: string
          example: "05"
        actionUrl:
          type: string
          nullable: true
    Instruction:
      type: object
      required:
        - method
        - paymentInstrument
        - value
      properties:
        method:
          type: string
          example: CARD
        paymentInstrument:
          $ref: "#/components/schemas/PaymentInstrument"
        value:
          $ref: "#/components/schemas/Value"
        threeDS:
          $ref: "#/components/schemas/ThreeDS"
    Device:
      type: object
      properties:
        ip:
          type: string
          example: 1.1.1.1
        referer:
          type: string
        acceptHeader:
          type: string
          example: text/html,application/xhtml+xml
        userAgentHeader:
          type: string
          example: Mozilla/5.0
        browserLanguage:
          type: string
          example: en-US
        browserJavaEnabled:
          type: boolean
        browserJavascriptEnabled:
          type: boolean
          example: true
        browserColorDepth:
          type: string
        browserScreenHeight:
          type: integer
          example: 1080
        browserScreenWidth:
          type: integer
          example: 1920
        timeZone:
          type: string
          example: Asia/Shanghai
    Shipping:
      type: object
      properties:
        firstName:
          type: string
          example: TOM
        lastName:
          type: string
          example: GREEN
        email:
          type: string
          example: tom@example.com
        phone:
          type: string
          example: "13800000000"
        country:
          type: string
          example: US
        state:
          type: string
          example: CA
        city:
          type: string
          example: Los Angeles
        addressLine1:
          type: string
          example: 1 Main Street
        addressLine2:
          type: string
        postalCode:
          type: string
          example: "90001"
        fee:
          type: number
          format: double
          example: 3.00
    Product:
      type: object
      properties:
        productNo:
          type: string
          example: SKU001
        description:
          type: string
          example: hwinpay Demo Product
        currency:
          type: string
          example: USD
        amount:
          type: number
          format: double
          example: 85.50
        quantity:
          type: integer
          example: 1
        pictureUrl:
          type: string
        tax:
          type: number
          format: double
          example: 0.00
    ResultURLs:
      type: object
      required:
        - successURL
        - failureURL
      properties:
        successURL:
          type: string
          example: https://merchant.example.com/pay/success
        failureURL:
          type: string
          example: https://merchant.example.com/pay/failure
        pendingURL:
          type: string
          example: https://merchant.example.com/pay/pending
        errorURL:
          type: string
          example: https://merchant.example.com/pay/error
        cancelURL:
          type: string
          example: https://merchant.example.com/pay/cancel
        expiryURL:
          type: string
          example: https://merchant.example.com/pay/expired
    PaymentSubmitRequest:
      type: object
      required:
        - orderId
        - merchant
        - instruction
      properties:
        orderId:
          type: string
          example: ORD-20260428-0001
        merchant:
          $ref: "#/components/schemas/Merchant"
        instruction:
          $ref: "#/components/schemas/Instruction"
        device:
          $ref: "#/components/schemas/Device"
        shipping:
          $ref: "#/components/schemas/Shipping"
        products:
          type: array
          items:
            $ref: "#/components/schemas/Product"
    PaymentQueryRequest:
      type: object
      required:
        - merchant
        - orderId
        - paymentId
      properties:
        merchant:
          $ref: "#/components/schemas/Merchant"
        orderId:
          type: string
          example: ORD-20260428-0001
        paymentId:
          type: string
          example: P202604280001
    RefundSubmitRequest:
      type: object
      required:
        - merchant
        - orderId
        - paymentId
        - refundReference
        - amount
        - currency
      properties:
        merchant:
          $ref: "#/components/schemas/Merchant"
        orderId:
          type: string
          example: ORD-20260428-0001
        paymentId:
          type: string
          example: P202604280001
        refundReference:
          type: string
          example: RFD-20260428-0001
        amount:
          type: number
          format: double
          minimum: 0.01
          example: 5.00
        currency:
          type: string
          example: USD
        reason:
          type: string
          example: customer request
    CheckoutSessionCreateRequest:
      type: object
      required:
        - orderId
        - merchant
        - value
        - resultURLs
      properties:
        orderId:
          type: string
          example: ORD-20260428-0002
        merchant:
          $ref: "#/components/schemas/Merchant"
        value:
          $ref: "#/components/schemas/Value"
        description:
          type: string
          example: hwinpay hosted checkout order
        shipping:
          $ref: "#/components/schemas/Shipping"
        products:
          type: array
          items:
            $ref: "#/components/schemas/Product"
        resultURLs:
          $ref: "#/components/schemas/ResultURLs"
        expiry:
          type: integer
          maximum: 1800
          example: 1800
        locale:
          type: string
          example: en-US
    PaymentData:
      type: object
      nullable: true
      properties:
        paymentId:
          type: string
          example: P202604280001
        orderId:
          type: string
          example: ORD-20260428-0001
        tradeStatus:
          $ref: "#/components/schemas/TradeStatus"
        reasonCode:
          type: string
          nullable: true
        reasonMessage:
          type: string
          nullable: true
        threeDS:
          $ref: "#/components/schemas/ThreeDSResult"
    RefundData:
      type: object
      nullable: true
      properties:
        refundId:
          type: string
          example: R202604280001
        paymentId:
          type: string
          example: P202604280001
        orderId:
          type: string
          example: ORD-20260428-0001
        refundReference:
          type: string
          example: RFD-20260428-0001
        refundType:
          type: string
          enum:
            - FULL
            - PARTIAL
          example: PARTIAL
        tradeStatus:
          $ref: "#/components/schemas/TradeStatus"
        amount:
          type: number
          format: double
          example: 5.00
        currency:
          type: string
          example: USD
        reasonCode:
          type: string
          nullable: true
        reasonMessage:
          type: string
          nullable: true
    CheckoutSessionData:
      type: object
      nullable: true
      properties:
        checkoutSessionId:
          type: string
          example: CS20260428103902640201
        paymentId:
          type: string
          example: P202604280002
        orderId:
          type: string
          example: ORD-20260428-0002
        tradeStatus:
          $ref: "#/components/schemas/TradeStatus"
        url:
          type: string
          example: http://api.test.hwinpay.com/gateway/trade/checkout/page?token=9b7a9053547945ae8136d32b7b422598
    TradeStatus:
      type: string
      enum:
        - INIT
        - PROCESSING
        - SUCCESS
        - FAILED
        - EXPIRED
    PaymentResponseWrapper:
      allOf:
        - $ref: "#/components/schemas/CommonResponse"
        - type: object
          properties:
            data:
              $ref: "#/components/schemas/PaymentData"
    RefundResponseWrapper:
      allOf:
        - $ref: "#/components/schemas/CommonResponse"
        - type: object
          properties:
            data:
              $ref: "#/components/schemas/RefundData"
    CheckoutSessionResponseWrapper:
      allOf:
        - $ref: "#/components/schemas/CommonResponse"
        - type: object
          properties:
            data:
              $ref: "#/components/schemas/CheckoutSessionData"
    CommonResponse:
      type: object
      properties:
        code:
          type: string
          example: "000000"
        msg:
          type: string
          nullable: true
          example: ""
        message:
          type: string
          nullable: true
          description: 交易业务错误时可能返回 message。
        requestId:
          type: string
          nullable: true
          description: 网关层错误响应中返回的请求流水号。
        responseId:
          type: string
          nullable: true
          description: 网关层错误响应中返回的响应流水号。
        data:
          nullable: true
      example:
        code: "010002"
        msg: signature verify failed
        requestId: REQ202604280001
        responseId: GTW202604280001
        data: null
