{
  "openapi": "3.1.0",
  "info": {
    "title": "PaySync API",
    "description": "페이싱크 무통장입금 자동 확인 서비스의 공개 REST API 입니다.",
    "version": "1.2.0",
    "contact": {
      "name": "페이싱크",
      "url": "https://paysync.kr"
    }
  },
  "servers": [
    {
      "url": "https://api.paysync.kr",
      "description": "운영 서버"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "주문",
      "description": "주문(인보이스) 생성 및 관리 엔드포인트입니다. 입금자명 + 입금 금액이 모두 일치하면 자동으로 결제 완료 처리됩니다."
    },
    {
      "name": "입금",
      "description": "은행에서 수신된 입금 트랜잭션을 조회합니다."
    },
    {
      "name": "현금영수증",
      "description": "현금영수증 발급/조회/취소 엔드포인트입니다. 사용 전 대시보드에서 사업자 정보 + 팝빌 연동이 필요합니다."
    }
  ],
  "paths": {
    "/v1/invoices": {
      "post": {
        "tags": [
          "주문"
        ],
        "summary": "주문 생성",
        "description": "새로운 주문을 생성합니다. 등록된 계좌로 `입금자명` 과 `입금 금액` 이 모두 일치하는 입금이 들어오면 페이싱크가 자동으로 결제 완료 상태로 전환하고, 등록된 웹훅으로 `invoice.paid` 이벤트가 전송됩니다.",
        "operationId": "issueInvoice",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IssueInvoiceRequest"
              },
              "example": {
                "customer": {
                  "name": "홍길동",
                  "email": "hong@example.com",
                  "phoneNumber": "01012345678"
                },
                "amount": 50000,
                "cashReceipt": {
                  "type": "PERSONAL",
                  "identifier": "01012345678"
                },
                "expireAfter": "1d",
                "metadata": {
                  "orderId": "ORDER-2026-0001"
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "주문이 성공적으로 생성되었습니다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InvoiceResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "계좌 권한 또는 인증 상태 오류",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmptyResponse"
                },
                "examples": {
                  "INSUFFICIENT_PERMISSIONS": {
                    "value": {
                      "code": "INSUFFICIENT_PERMISSIONS",
                      "data": null
                    }
                  },
                  "BANK_ACCOUNT_NOT_VERIFIED": {
                    "value": {
                      "code": "BANK_ACCOUNT_NOT_VERIFIED",
                      "data": null
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "없는 계좌 ID",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmptyResponse"
                },
                "examples": {
                  "BANK_ACCOUNT_NOT_FOUND": {
                    "value": {
                      "code": "BANK_ACCOUNT_NOT_FOUND",
                      "data": null
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "입금자명과 금액이 같고 받을 계좌가 겹치는 결제 대기 주문이 이미 있습니다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmptyResponse"
                },
                "example": {
                  "code": "INVOICE_ALREADY_EXISTS",
                  "data": null
                }
              }
            }
          },
          "422": {
            "description": "요청 값이 유효하지 않습니다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmptyResponse"
                },
                "examples": {
                  "INVALID_AMOUNT": {
                    "value": {
                      "code": "INVALID_AMOUNT",
                      "data": null
                    }
                  },
                  "INVALID_CUSTOMER_NAME": {
                    "value": {
                      "code": "INVALID_CUSTOMER_NAME",
                      "data": null
                    }
                  },
                  "INVALID_EMAIL": {
                    "value": {
                      "code": "INVALID_EMAIL",
                      "data": null
                    }
                  },
                  "INVALID_PHONE_NUMBER": {
                    "value": {
                      "code": "INVALID_PHONE_NUMBER",
                      "data": null
                    }
                  },
                  "INVALID_DURATION": {
                    "value": {
                      "code": "INVALID_DURATION",
                      "data": null
                    }
                  },
                  "EXPIRE_AFTER_EXCEEDS_MAXIMUM": {
                    "value": {
                      "code": "EXPIRE_AFTER_EXCEEDS_MAXIMUM",
                      "data": null
                    }
                  },
                  "METADATA_KEY_VALUE_PAIR_LIMIT_EXCEEDED": {
                    "value": {
                      "code": "METADATA_KEY_VALUE_PAIR_LIMIT_EXCEEDED",
                      "data": null
                    }
                  },
                  "METADATA_KEY_LENGTH_LIMIT_EXCEEDED": {
                    "value": {
                      "code": "METADATA_KEY_LENGTH_LIMIT_EXCEEDED",
                      "data": null
                    }
                  },
                  "METADATA_VALUE_LENGTH_LIMIT_EXCEEDED": {
                    "value": {
                      "code": "METADATA_VALUE_LENGTH_LIMIT_EXCEEDED",
                      "data": null
                    }
                  }
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "주문"
        ],
        "summary": "주문 목록 조회",
        "description": "API 키와 연결된 계정의 주문 목록을 페이지 단위로 조회합니다.",
        "operationId": "listInvoices",
        "parameters": [
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "name": "search",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "주문 ID가 같거나, 입금자명, 이메일, 전화번호, 현금영수증 식별번호에 검색어가 들어간 주문 (대소문자 무시, 하이픈 무시)"
          },
          {
            "name": "paid",
            "in": "query",
            "schema": {
              "type": "boolean"
            },
            "description": "결제 완료 여부"
          },
          {
            "name": "expired",
            "in": "query",
            "schema": {
              "type": "boolean"
            },
            "description": "true면 만료된 주문, false면 만료 기간이 없거나 아직 만료되지 않은 주문"
          },
          {
            "name": "dateAfter",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "이 날짜(KST, 포함) 이후에 만든 주문"
          },
          {
            "name": "dateBefore",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "이 날짜(KST, 포함) 이전에 만든 주문"
          }
        ],
        "responses": {
          "200": {
            "description": "주문 목록",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InvoiceListResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "limit 이 100 을 초과했습니다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmptyResponse"
                },
                "example": {
                  "code": "LIMIT_EXCEEDS_MAXIMUM",
                  "data": null
                }
              }
            }
          },
          "422": {
            "description": "페이징/날짜 파라미터가 유효하지 않습니다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmptyResponse"
                },
                "examples": {
                  "INVALID_OFFSET": {
                    "value": {
                      "code": "INVALID_OFFSET",
                      "data": null
                    }
                  },
                  "INVALID_LIMIT": {
                    "value": {
                      "code": "INVALID_LIMIT",
                      "data": null
                    }
                  },
                  "INVALID_DATE_RANGE_FORMAT": {
                    "value": {
                      "code": "INVALID_DATE_RANGE_FORMAT",
                      "data": null
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/invoices/{id}": {
      "get": {
        "tags": [
          "주문"
        ],
        "summary": "주문 단건 조회",
        "operationId": "getInvoice",
        "parameters": [
          {
            "$ref": "#/components/parameters/InvoiceId"
          }
        ],
        "responses": {
          "200": {
            "description": "주문 정보",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InvoiceResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "주문을 찾을 수 없습니다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmptyResponse"
                },
                "example": {
                  "code": "INVOICE_NOT_FOUND",
                  "data": null
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "주문"
        ],
        "summary": "주문 삭제",
        "description": "결제 완료 상태가 아닌 주문만 삭제할 수 있습니다.",
        "operationId": "deleteInvoice",
        "parameters": [
          {
            "$ref": "#/components/parameters/InvoiceId"
          }
        ],
        "responses": {
          "200": {
            "description": "삭제 완료",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmptyResponse"
                },
                "example": {
                  "code": "DELETED",
                  "data": null
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "이미 결제 완료된 주문은 삭제할 수 없거나, 권한이 없습니다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmptyResponse"
                },
                "examples": {
                  "INVOICE_ALREADY_PAID": {
                    "value": {
                      "code": "INVOICE_ALREADY_PAID",
                      "data": null
                    }
                  },
                  "INSUFFICIENT_PERMISSIONS": {
                    "value": {
                      "code": "INSUFFICIENT_PERMISSIONS",
                      "data": null
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "주문을 찾을 수 없습니다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmptyResponse"
                },
                "example": {
                  "code": "INVOICE_NOT_FOUND",
                  "data": null
                }
              }
            }
          },
          "500": {
            "description": "삭제 처리 중 서버 내부 오류가 발생했습니다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmptyResponse"
                },
                "example": {
                  "code": "INTERNAL_SERVER_ERROR",
                  "data": null
                }
              }
            }
          }
        }
      }
    },
    "/v1/invoices/{id}/mark-as-paid": {
      "post": {
        "tags": [
          "주문"
        ],
        "summary": "주문 결제 완료 처리",
        "description": "현금 등 페이싱크 외부 경로로 결제가 이뤄진 경우, 주문을 수동으로 결제 완료 상태로 전환합니다. 등록된 웹훅으로 `invoice.paid` 이벤트가 전송됩니다.",
        "operationId": "markInvoiceAsPaid",
        "parameters": [
          {
            "$ref": "#/components/parameters/InvoiceId"
          }
        ],
        "responses": {
          "200": {
            "description": "결제 완료로 전환된 주문",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InvoiceResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "이미 결제된 주문이거나 권한이 없습니다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmptyResponse"
                },
                "examples": {
                  "INVOICE_ALREADY_PAID": {
                    "value": {
                      "code": "INVOICE_ALREADY_PAID",
                      "data": null
                    }
                  },
                  "INSUFFICIENT_PERMISSIONS": {
                    "value": {
                      "code": "INSUFFICIENT_PERMISSIONS",
                      "data": null
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "주문을 찾을 수 없습니다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmptyResponse"
                },
                "example": {
                  "code": "INVOICE_NOT_FOUND",
                  "data": null
                }
              }
            }
          }
        }
      }
    },
    "/v1/transactions": {
      "get": {
        "tags": [
          "입금"
        ],
        "summary": "입금 내역 목록 조회",
        "description": "API 키와 연결된 계정의 입금 트랜잭션 목록을 조회합니다.",
        "operationId": "listTransactions",
        "parameters": [
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "name": "search",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "입금 ID, 계좌 ID, 매칭된 주문 ID가 같거나, 계좌번호나 입금자명에 검색어가 들어간 입금 (대소문자 무시, 계좌번호 하이픈 무시)"
          },
          {
            "name": "bankAccountId",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "이 계좌(`bac_`)의 입금만. API 키의 접근 가능한 계좌 밖이면 INSUFFICIENT_PERMISSIONS"
          },
          {
            "name": "matched",
            "in": "query",
            "schema": {
              "type": "boolean"
            },
            "description": "주문 매칭 여부"
          },
          {
            "name": "hidden",
            "in": "query",
            "schema": {
              "type": "boolean"
            },
            "description": "대시보드 숨김 여부"
          },
          {
            "name": "dateAfter",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "이 날짜(KST, 포함) 이후에 받은 입금"
          },
          {
            "name": "dateBefore",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "이 날짜(KST, 포함) 이전에 받은 입금"
          }
        ],
        "responses": {
          "200": {
            "description": "입금 목록",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TransactionListResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "limit 이 100 을 초과했습니다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmptyResponse"
                },
                "example": {
                  "code": "LIMIT_EXCEEDS_MAXIMUM",
                  "data": null
                }
              }
            }
          },
          "422": {
            "description": "페이징/날짜 파라미터가 유효하지 않습니다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmptyResponse"
                },
                "examples": {
                  "INVALID_OFFSET": {
                    "value": {
                      "code": "INVALID_OFFSET",
                      "data": null
                    }
                  },
                  "INVALID_LIMIT": {
                    "value": {
                      "code": "INVALID_LIMIT",
                      "data": null
                    }
                  },
                  "INVALID_DATE_RANGE_FORMAT": {
                    "value": {
                      "code": "INVALID_DATE_RANGE_FORMAT",
                      "data": null
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/transactions/{id}": {
      "get": {
        "tags": [
          "입금"
        ],
        "summary": "입금 단건 조회",
        "operationId": "getTransaction",
        "parameters": [
          {
            "$ref": "#/components/parameters/TransactionId"
          }
        ],
        "responses": {
          "200": {
            "description": "입금 정보",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TransactionResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "입금 내역을 찾을 수 없습니다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmptyResponse"
                },
                "example": {
                  "code": "TRANSACTION_NOT_FOUND",
                  "data": null
                }
              }
            }
          }
        }
      }
    },
    "/v1/cash-receipts": {
      "post": {
        "tags": [
          "현금영수증"
        ],
        "summary": "현금영수증 발급",
        "description": "새 현금영수증 발급을 요청합니다. 국세청 연동을 거치므로 비동기로 처리되며, 응답의 `status` 는 항상 `PENDING` 입니다.",
        "operationId": "issueCashReceipt",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IssueCashReceiptRequest"
              },
              "example": {
                "type": "PERSONAL",
                "identifier": "01012345678",
                "amount": 50000
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "발급 요청이 접수되었습니다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CashReceiptResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "사업자 정보 미등록.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmptyResponse"
                },
                "example": {
                  "code": "BUSINESS_INFO_NOT_REGISTERED",
                  "data": null
                }
              }
            }
          },
          "422": {
            "description": "요청 값이 유효하지 않습니다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmptyResponse"
                },
                "examples": {
                  "INVALID_CASH_RECEIPT_IDENTIFIER": {
                    "value": {
                      "code": "INVALID_CASH_RECEIPT_IDENTIFIER",
                      "data": null
                    }
                  },
                  "INVALID_CASH_RECEIPT_AMOUNT": {
                    "value": {
                      "code": "INVALID_CASH_RECEIPT_AMOUNT",
                      "data": null
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "국세청 연동(팝빌) API 호출 실패.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmptyResponse"
                },
                "example": {
                  "code": "POPBILL_API_FAILED",
                  "data": null
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "현금영수증"
        ],
        "summary": "현금영수증 목록 조회",
        "description": "API 키와 연결된 계정의 현금영수증 발급 내역을 조회합니다.",
        "operationId": "listCashReceipts",
        "parameters": [
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "name": "search",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "현금영수증 ID, 관련 주문 ID, 사업자등록번호가 같거나, 식별번호에 검색어가 들어간 현금영수증 (하이픈 무시)"
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "PENDING",
                "ISSUED",
                "FAILED",
                "REVOKED"
              ]
            },
            "description": "발급 상태"
          },
          {
            "name": "type",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "PERSONAL",
                "CORPORATE"
              ]
            },
            "description": "현금영수증 종류"
          },
          {
            "name": "identifier",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "식별번호"
          },
          {
            "name": "corpNum",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "발급 사업자등록번호"
          },
          {
            "name": "dateAfter",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "이 날짜(KST, 포함) 이후 발급 요청"
          },
          {
            "name": "dateBefore",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "이 날짜(KST, 포함) 이전 발급 요청"
          }
        ],
        "responses": {
          "200": {
            "description": "현금영수증 목록",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CashReceiptListResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "권한 오류",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmptyResponse"
                },
                "example": {
                  "code": "LIMIT_EXCEEDS_MAXIMUM",
                  "data": null
                }
              }
            }
          },
          "422": {
            "description": "페이징/날짜 파라미터가 유효하지 않습니다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmptyResponse"
                },
                "examples": {
                  "INVALID_OFFSET": {
                    "value": {
                      "code": "INVALID_OFFSET",
                      "data": null
                    }
                  },
                  "INVALID_LIMIT": {
                    "value": {
                      "code": "INVALID_LIMIT",
                      "data": null
                    }
                  },
                  "INVALID_DATE_RANGE_FORMAT": {
                    "value": {
                      "code": "INVALID_DATE_RANGE_FORMAT",
                      "data": null
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/cash-receipts/{id}": {
      "get": {
        "tags": [
          "현금영수증"
        ],
        "summary": "현금영수증 단건 조회",
        "operationId": "getCashReceipt",
        "parameters": [
          {
            "$ref": "#/components/parameters/CashReceiptId"
          }
        ],
        "responses": {
          "200": {
            "description": "현금영수증 정보",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CashReceiptResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "현금영수증을 찾을 수 없습니다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmptyResponse"
                },
                "example": {
                  "code": "CASH_RECEIPT_NOT_FOUND",
                  "data": null
                }
              }
            }
          }
        }
      }
    },
    "/v1/cash-receipts/{id}/revoke": {
      "post": {
        "tags": [
          "현금영수증"
        ],
        "summary": "현금영수증 발급 취소",
        "description": "PENDING 상태의 현금영수증 발급을 취소합니다. 이미 처리된 현금영수증은 취소할 수 없습니다.",
        "operationId": "revokeCashReceipt",
        "parameters": [
          {
            "$ref": "#/components/parameters/CashReceiptId"
          }
        ],
        "responses": {
          "200": {
            "description": "취소 완료",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmptyResponse"
                },
                "example": {
                  "code": "OK",
                  "data": null
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "현금영수증을 찾을 수 없습니다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmptyResponse"
                },
                "example": {
                  "code": "CASH_RECEIPT_NOT_FOUND",
                  "data": null
                }
              }
            }
          },
          "409": {
            "description": "이미 처리 완료된(`PENDING` 이 아닌) 현금영수증입니다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmptyResponse"
                },
                "example": {
                  "code": "CASH_RECEIPT_ALREADY_ISSUED",
                  "data": null
                }
              }
            }
          },
          "500": {
            "description": "국세청 연동(팝빌) API 호출 실패.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmptyResponse"
                },
                "example": {
                  "code": "POPBILL_API_FAILED",
                  "data": null
                }
              }
            }
          }
        }
      }
    }
  },
  "webhooks": {
    "invoice.paid": {
      "post": {
        "summary": "주문 결제 완료",
        "description": "주문이 결제 완료 상태로 전환되면 등록된 웹훅 엔드포인트로 호출됩니다. 입금 자동 매칭 또는 수동 결제 처리(`POST /v1/invoices/{id}/mark-as-paid`) 모두에서 발생합니다.\n\n페이로드는 [Standard Webhooks](https://www.standardwebhooks.com/) 명세를 따르며, `webhook-id` / `webhook-timestamp` / `webhook-signature` 헤더를 검증해야 합니다.",
        "parameters": [
          {
            "name": "webhook-id",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "전송 건의 고유 ID (`whm_` 접두사). 멱등 처리에 활용하세요."
          },
          {
            "name": "webhook-timestamp",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "전송 시각 (Unix 초). 5분 이상 차이나면 거부 권장."
          },
          {
            "name": "webhook-signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "`v1,<base64>` 형식의 HMAC-SHA256 서명. 서명 대상은 `${webhook-id}.${webhook-timestamp}.${body}`."
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InvoicePaidEvent"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "수신 성공. 페이싱크는 **HTTP 200** 응답만 성공으로 처리합니다. 그 외(2xx 포함)는 실패로 기록됩니다."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "대시보드에서 발급한 API 키(`sk_live_...`)를 `Authorization: Bearer <API 키>` 형태로 보냅니다. 만료, 삭제, 비활성화된 키는 NOT_AUTHORIZED. 접근 가능한 계좌를 제한한 키는 그 계좌의 주문과 입금만 다룰 수 있습니다."
      }
    },
    "parameters": {
      "Offset": {
        "name": "offset",
        "in": "query",
        "required": true,
        "schema": {
          "type": "integer",
          "minimum": 0
        },
        "description": "건너뛸 아이템 수"
      },
      "Limit": {
        "name": "limit",
        "in": "query",
        "required": true,
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 100
        },
        "description": "한 번에 조회할 아이템 수 (최대 100)"
      },
      "InvoiceId": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        },
        "description": "주문 ID (`ivc_` 접두사)"
      },
      "TransactionId": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        },
        "description": "트랜잭션 ID (`trx_` 접두사)"
      },
      "CashReceiptId": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        },
        "description": "현금영수증 ID (`crt_` 접두사)"
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "API 키가 없거나 유효하지 않습니다.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/EmptyResponse"
            },
            "example": {
              "code": "NOT_AUTHORIZED",
              "data": null
            }
          }
        }
      },
      "Forbidden": {
        "description": "리소스에 접근할 권한이 없습니다.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/EmptyResponse"
            },
            "example": {
              "code": "INSUFFICIENT_PERMISSIONS",
              "data": null
            }
          }
        }
      }
    },
    "schemas": {
      "EmptyResponse": {
        "type": "object",
        "required": [
          "code"
        ],
        "properties": {
          "code": {
            "type": "string",
            "description": "결과 코드"
          },
          "data": {
            "type": "null"
          }
        }
      },
      "Customer": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 16,
            "description": "입금자명. 공백 없이 1~16자. 은행 알림의 입금자명과 같아야 자동 매칭됩니다. 은행이 자른 긴 이름은 이름 앞자리 매칭(매칭 어시스트)으로 처리합니다."
          },
          "email": {
            "type": "string",
            "format": "email",
            "minLength": 5,
            "maxLength": 255,
            "nullable": true
          },
          "phoneNumber": {
            "type": "string",
            "nullable": true,
            "pattern": "^d{10,11}$",
            "description": "하이픈 없는 숫자 10~11자리 (예: `01012345678`)"
          }
        }
      },
      "InvoiceCashReceipt": {
        "type": "object",
        "required": [
          "type",
          "identifier"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "PERSONAL",
              "CORPORATE"
            ],
            "description": "`PERSONAL` (소득공제용) 또는 `CORPORATE` (지출증빙용)"
          },
          "identifier": {
            "type": "string",
            "description": "개인: 휴대폰 번호, 사업자: 사업자등록번호"
          }
        }
      },
      "IssueInvoiceRequest": {
        "type": "object",
        "required": [
          "bankAccountIds",
          "customer",
          "amount"
        ],
        "properties": {
          "bankAccountIds": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "uniqueItems": true,
            "description": "입금받을 계좌 ID(`bac_`) 목록. 빈 배열이면 등록한 모든 계좌로 받습니다. 1원 인증을 마친 내 계좌만 넣을 수 있습니다.",
            "example": []
          },
          "customer": {
            "$ref": "#/components/schemas/Customer"
          },
          "amount": {
            "type": "integer",
            "minimum": 1,
            "description": "주문 금액 (원). 0보다 커야 합니다. 자동 매칭 시 입금 금액과 정확히 일치해야 합니다."
          },
          "cashReceipt": {
            "allOf": [
              {
                "$ref": "#/components/schemas/InvoiceCashReceipt"
              }
            ],
            "nullable": true,
            "description": "결제 완료 시 발급할 현금영수증 정보. 사업자 정보를 등록해야 발급됩니다. 주문 생성 때는 식별번호 형식을 검사하지 않습니다."
          },
          "expireAfter": {
            "type": "string",
            "nullable": true,
            "description": "주문 만료 기간. ISO-8601 duration (`PT30M`, `PT1H`, `P1D`, `P1DT6H20M`, `P7D`) 또는 간이 표기 (`30m`, `1h`, `1d 6h 20m`, `7d`) 를 지원합니다. 최대 365일.",
            "example": "1d"
          },
          "metadata": {
            "type": "object",
            "nullable": true,
            "additionalProperties": {
              "type": "string"
            },
            "description": "문자열 키/값. 최대 5쌍, 키 64자, 값 1024자. `CAFE24_`, `IMWEB_`로 시작하는 키는 쇼핑몰 연동용이라 쓰지 마세요."
          }
        }
      },
      "Invoice": {
        "type": "object",
        "required": [
          "id",
          "issuerId",
          "bankAccountIds",
          "customer",
          "amount",
          "paid",
          "issuedAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "주문 ID (`ivc_` 접두사)",
            "example": "ivc_a1b2c3d4e5f6g7h8i9j0k1l2"
          },
          "issuerId": {
            "type": "string",
            "description": "발행자 계정 ID"
          },
          "bankAccountIds": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "입금받을 계좌 ID 목록. 빈 배열이면 모든 계좌."
          },
          "customer": {
            "$ref": "#/components/schemas/Customer"
          },
          "cashReceipt": {
            "allOf": [
              {
                "$ref": "#/components/schemas/InvoiceCashReceipt"
              }
            ],
            "nullable": true
          },
          "amount": {
            "type": "integer"
          },
          "paid": {
            "type": "boolean",
            "description": "결제 완료 여부"
          },
          "metadata": {
            "type": "object",
            "nullable": true,
            "additionalProperties": {
              "type": "string"
            }
          },
          "issuedAt": {
            "type": "string",
            "format": "date-time"
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "deletedAt": {
            "type": "null",
            "description": "조회 가능한 주문에서는 항상 null."
          }
        }
      },
      "BankAccount": {
        "type": "object",
        "required": [
          "id",
          "active",
          "ownerId",
          "alias",
          "provider",
          "type",
          "number",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "계좌 ID (`bac_` 접두사)"
          },
          "active": {
            "type": "boolean"
          },
          "ownerId": {
            "type": "string",
            "description": "계좌를 등록한 계정 ID (`acc_`)"
          },
          "alias": {
            "type": "string",
            "description": "대시보드에서 등록한 계좌 별칭"
          },
          "provider": {
            "type": "string",
            "enum": [
              "INDUSTRIAL_BANK_OF_KOREA",
              "KB_KOOKMIN_BANK",
              "NH_NONGHYUP_BANK",
              "IM_BANK",
              "KEB_HANA_BANK",
              "SHINHAN_BANK",
              "K_BANK"
            ]
          },
          "type": {
            "type": "string",
            "enum": [
              "PERSONAL",
              "CORPORATE"
            ]
          },
          "number": {
            "type": "string",
            "description": "하이픈 없는 계좌번호"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "verifiedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "1원 인증 완료 시각"
          },
          "deletedAt": {
            "type": "null"
          }
        },
        "description": "입금을 받은 시점의 계좌 정보 스냅샷입니다."
      },
      "Transaction": {
        "type": "object",
        "required": [
          "id",
          "bankAccount",
          "amount",
          "description",
          "hidden",
          "receivedAt",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "트랜잭션 ID (`trx_` 접두사)",
            "example": "trx_a1b2c3d4e5f6g7h8i9j0k1l2"
          },
          "bankAccount": {
            "$ref": "#/components/schemas/BankAccount"
          },
          "matchedInvoiceId": {
            "type": "string",
            "nullable": true,
            "description": "매칭된 주문 ID (`ivc_` 접두사)"
          },
          "matchMethod": {
            "type": "string",
            "nullable": true,
            "enum": [
              "EXACT",
              "BRACKET_REMOVAL",
              "NAME_PREFIX",
              "MANUAL",
              null
            ],
            "description": "매칭 방식. 매칭되지 않았거나 기록 이전에 매칭된 입금은 null."
          },
          "amount": {
            "type": "integer",
            "description": "입금 금액 (원)"
          },
          "description": {
            "type": "string",
            "description": "은행 알림의 입금자명"
          },
          "hidden": {
            "type": "boolean",
            "description": "대시보드에서 숨긴 입금인지 여부. API로는 바꿀 수 없습니다."
          },
          "receivedAt": {
            "type": "string",
            "format": "date-time",
            "description": "은행 알림 기준 입금 시각"
          },
          "matchedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "페이싱크가 입금 알림을 받은 시각"
          },
          "deletedAt": {
            "type": "null"
          }
        }
      },
      "IssueCashReceiptRequest": {
        "type": "object",
        "required": [
          "type",
          "identifier",
          "amount"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "PERSONAL",
              "CORPORATE"
            ],
            "description": "`PERSONAL` (소득공제용) 또는 `CORPORATE` (지출증빙용)"
          },
          "identifier": {
            "type": "string",
            "description": "개인: 휴대폰 번호, 사업자: 사업자등록번호"
          },
          "amount": {
            "type": "integer",
            "minimum": 1,
            "description": "발급 금액 (원). 0보다 커야 합니다."
          }
        }
      },
      "CashReceipt": {
        "type": "object",
        "required": [
          "id",
          "status",
          "issuerId",
          "corpNum",
          "type",
          "identifier",
          "amount",
          "ntsConfirmNum",
          "issuedAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "현금영수증 ID (`crt_` 접두사)",
            "example": "crt_a1b2c3d4e5f6g7h8i9j0k1l2"
          },
          "status": {
            "type": "string",
            "enum": [
              "PENDING",
              "ISSUED",
              "FAILED",
              "REVOKED"
            ],
            "description": "발급 상태"
          },
          "issuerId": {
            "type": "string"
          },
          "corpNum": {
            "type": "string",
            "description": "발행자 사업자등록번호"
          },
          "type": {
            "type": "string",
            "enum": [
              "PERSONAL",
              "CORPORATE"
            ]
          },
          "identifier": {
            "type": "string"
          },
          "amount": {
            "type": "integer",
            "description": "부가세 포함 거래 금액 (원)"
          },
          "relatedInvoiceId": {
            "type": "string",
            "nullable": true,
            "description": "주문 결제 완료 시 자동 발급된 경우 연결된 주문 ID"
          },
          "ntsConfirmNum": {
            "type": "string",
            "description": "국세청 승인번호"
          },
          "ntsResultCode": {
            "type": "string",
            "nullable": true,
            "description": "국세청 결과 코드. 콜백 도착 전이면 null."
          },
          "issuedAt": {
            "type": "string",
            "format": "date-time"
          },
          "revokedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "deletedAt": {
            "type": "null"
          }
        }
      },
      "InvoiceResponse": {
        "type": "object",
        "required": [
          "code",
          "data"
        ],
        "properties": {
          "code": {
            "type": "string",
            "example": "OK"
          },
          "data": {
            "$ref": "#/components/schemas/Invoice"
          }
        }
      },
      "InvoiceListResponse": {
        "type": "object",
        "required": [
          "code",
          "totalItems",
          "data"
        ],
        "properties": {
          "code": {
            "type": "string",
            "example": "OK"
          },
          "totalItems": {
            "type": "integer"
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Invoice"
            }
          }
        }
      },
      "TransactionResponse": {
        "type": "object",
        "required": [
          "code",
          "data"
        ],
        "properties": {
          "code": {
            "type": "string",
            "example": "OK"
          },
          "data": {
            "$ref": "#/components/schemas/Transaction"
          }
        }
      },
      "TransactionListResponse": {
        "type": "object",
        "required": [
          "code",
          "totalItems",
          "data"
        ],
        "properties": {
          "code": {
            "type": "string",
            "example": "OK"
          },
          "totalItems": {
            "type": "integer"
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Transaction"
            }
          }
        }
      },
      "CashReceiptResponse": {
        "type": "object",
        "required": [
          "code",
          "data"
        ],
        "properties": {
          "code": {
            "type": "string",
            "example": "OK"
          },
          "data": {
            "$ref": "#/components/schemas/CashReceipt"
          }
        }
      },
      "CashReceiptListResponse": {
        "type": "object",
        "required": [
          "code",
          "totalItems",
          "data"
        ],
        "properties": {
          "code": {
            "type": "string",
            "example": "OK"
          },
          "totalItems": {
            "type": "integer"
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CashReceipt"
            }
          }
        }
      },
      "InvoicePaidEvent": {
        "type": "object",
        "required": [
          "type",
          "invoice",
          "trigger"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "invoice.paid"
            ]
          },
          "invoice": {
            "$ref": "#/components/schemas/Invoice"
          },
          "trigger": {
            "type": "string",
            "enum": [
              "AUTOMATIC_MATCHING",
              "MANUAL_MATCHING",
              "MANUAL_APPROVE",
              "API_CALL"
            ]
          }
        }
      }
    }
  }
}
