예약/결제
사용자의 예약 조회, 결제 진행, 취소 및 내역 확인 API입니다.
결제 흐름
결제는 3단계로 진행됩니다:
예약 불가 리스트 조회
GET /reservations
지점/날짜/시간 기준으로 예약 불가능한 슬롯 목록을 조회합니다. 클라이언트는 이 데이터를 기반으로 예약 가능한 시간대를 표시합니다.
Headers
| 이름 | 필수 | 설명 |
|---|---|---|
Cookie | X | 세션 쿠키 (비로그인 접근 가능) |
Query Parameters
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
branch | String | O | 지점 영문명 (지점 코드 참고) |
base_date | String | O | 기준 날짜 (yyyyMMdd) |
base_hour | String | O | 기준 시간 (0 ~ 23) |
GET /api/reservations?branch=Gwanghwamun&base_date=20260305&base_hour=14
Response
- 200 성공
- 400 파라미터 오류
{
"status": "success",
"errorType": null,
"errorCode": null,
"message": null,
"data": {
"booking": ["1-202603051400", "1-202603051410"],
"paid": ["2-202603051500", "2-202603051510", "2-202603051520"],
"bookingMargin": ["1-202603051350"],
"paidMargin": ["2-202603051450", "2-202603051530"],
"disabledRooms": ["5"]
}
}
data 필드
| 필드 | 타입 | 설명 |
|---|---|---|
booking | String[] | 예약 진행 중인 슬롯 ({room}-{YYYYMMDDHHmm}) |
paid | String[] | 결제 완료 슬롯 |
bookingMargin | String[] | 예약 진행 중 마진 슬롯 (전후 10분 버퍼) |
paidMargin | String[] | 결제 완료 마진 슬롯 (전후 10분 버퍼) |
disabledRooms | String[] | 비활성화된 룸 번호 |
슬롯은 {방번호}-{YYYYMMDDHHmm} 형식입니다. 10분 단위로 표현되며, 예를 들어 2-202603051500은 2번 방의 15:00~15:10 슬롯입니다.
마진 슬롯은 예약 전후 10분 버퍼입니다. 예약 14:00~15:00이면 마진은 13:50(전) + 15:00(후)입니다. 클라이언트는 마진 슬롯도 선택 불가로 표시해야 합니다.
{
"status": "error",
"errorType": "IllegalArguemntError",
"errorCode": null,
"message": "유효하지 않은 지점입니다.",
"data": null
}
V2 캘린더 조회
GET /v2/branches/{branch}/calendar
지점의 월별 날짜 상태(영업/휴무/공휴일)를 조회합니다.
Path Parameters
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
branch | String | O | 지점 영문명 (지점 코드 참고) |
Query Parameters
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
yearMonth | String | O | 조회 연월 (YYYY-MM) |
GET /api/v2/branches/Gwanghwamun/calendar?yearMonth=2026-03
Response
- 200 성공
- 400 파라미터 오류
{
"status": "success",
"errorType": null,
"errorCode": null,
"message": null,
"data": {
"yearMonth": "2026-03",
"dates": [
{ "date": "2026-03-01", "status": "available", "reason": null },
{ "date": "2026-03-02", "status": "closed", "reason": "정기 휴무" },
{ "date": "2026-03-03", "status": "available", "reason": null }
]
}
}
data 필드
| 필드 | 타입 | 설명 |
|---|---|---|
yearMonth | String | 조회한 연월 |
dates | Array | 날짜별 상태 목록 |
dates 항목
| 필드 | 타입 | Nullable | 설명 |
|---|---|---|---|
date | String | N | 날짜 (YYYY-MM-DD) |
status | String | N | available / closed / holiday |
reason | String | Y | 휴무/공휴일 사유 |
{
"status": "error",
"errorType": "IllegalArguemntError",
"errorCode": null,
"message": "유효하지 않은 지점입니다.",
"data": null
}
V2 룸/슬롯 조회
GET /v2/branches/{branch}/reservations
지점의 룸 목록과 시간대별 예약 불가 슬롯을 조회합니다. 룸 정보에는 등급(grade)과 가격이 포함됩니다.
Path Parameters
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
branch | String | O | 지점 영문명 (지점 코드 참고) |
Query Parameters
| 파라미터 | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
date | String | O | - | 기준 날짜 (YYYY-MM-DD) |
baseHour | int | O | - | 시작 시간 (0~23) |
hours | int | X | 4 | 표시할 시간 범위 |
GET /api/v2/branches/Gwanghwamun/reservations?date=2026-03-05&baseHour=14&hours=4
Response
- 200 성공
- 400 파라미터 오류
{
"status": "success",
"errorType": null,
"errorCode": null,
"message": null,
"data": {
"rooms": [
{
"roomNo": "1",
"machine": "Capsule A",
"grade": "Stella",
"enabled": true,
"pricing": {
"min10": 2500,
"min30": 6000,
"min60": 10000
}
},
{
"roomNo": "2",
"machine": "Capsule B",
"grade": "Premium",
"enabled": true,
"pricing": {
"min10": 2000,
"min30": 5000,
"min60": 8000
}
}
],
"unavailableSlots": {
"paid": ["1-202603051500", "1-202603051510", "1-202603051520"],
"booking": ["2-202603051400"],
"paidMargin": ["1-202603051450", "1-202603051530"],
"bookingMargin": ["2-202603051350"],
"breakTime": [],
"closedTime": []
}
}
}
rooms 항목
| 필드 | 타입 | Nullable | 설명 |
|---|---|---|---|
roomNo | String | N | 방 번호 |
machine | String | N | 머신(침대) 이름 |
grade | String | Y | 등급 (Stella / Crystal / Superb / Premium / Gold / Standard / Normal) |
enabled | boolean | N | 활성 여부 |
pricing | Object | N | 가격 정보 |
pricing
| 필드 | 타입 | Nullable | 설명 |
|---|---|---|---|
min10 | Integer | Y | 10분당 가격 |
min30 | Integer | Y | 30분당 가격 |
min60 | Integer | Y | 60분당 가격 |
unavailableSlots
| 필드 | 타입 | 설명 |
|---|---|---|
paid | String[] | 결제 완료 슬롯 |
booking | String[] | 예약 진행 중 슬롯 |
paidMargin | String[] | 결제 완료 마진 슬롯 |
bookingMargin | String[] | 예약 진행 중 마진 슬롯 |
breakTime | String[] | 휴게시간 슬롯 |
closedTime | String[] | 영업 종료 슬롯 |
rooms는 가격 내림차순 → 같은 가격일 경우 방번호(숫자) 오름차순으로 정렬됩니다.
{
"status": "error",
"errorType": "IllegalArguemntError",
"errorCode": null,
"message": "유효하지 않은 지점입니다.",
"data": null
}
예약 진행 (Step 1)
POST /reservation
예약 가능 여부를 검증하고, PAY_SESSIONID 쿠키를 발급합니다. 이 쿠키는 이후 결제 과정 에서 예약 정보를 식별하는 데 사용됩니다.
예약 진행 후 사용자가 뒤로가기를 누르면 반드시 DELETE /reservation을 호출하여 Redis 점유 자원을 해제해야 합니다.
Headers
| 이름 | 필수 | 설명 |
|---|---|---|
Content-Type | O | application/json |
Request Body
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
branch | String | O | 지점 영문명 (지점 코드 참고) |
room | int | O | 방 번호 |
startDateTime | String | O | 시작 시간 (YYYYMMDDHHmm) |
endDateTime | String | O | 종료 시간 (YYYYMMDDHHmm) |
{
"branch": "Gwanghwamun",
"room": 1,
"startDateTime": "202603051400",
"endDateTime": "202603051600"
}
Response
- 200 성공
- 400 검증 실패
- 409 슬롯 점유
성공 시 PAY_SESSIONID 쿠키가 Set-Cookie 헤더에 포함됩니다.
Set-Cookie: PAY_SESSIONID=abc123; Path=/; HttpOnly
{
"status": "success",
"errorType": null,
"errorCode": null,
"message": null,
"data": null
}
{
"status": "error",
"errorType": "IllegalArguemntError",
"errorCode": null,
"message": "최대 예약 시간은 5시간입니다.",
"data": null
}
다른 사용자가 이미 해당 슬롯을 예약 진행 중인 경우:
{
"status": "error",
"errorType": "ResourceAlreadyExistException",
"errorCode": null,
"message": "이미 예약 진행 중인 시간대입니다.",
"data": null
}
최대 예약 시간은 5시간입니다. 초과 시 400 에러가 반환됩니다.
결제 등록 (Step 2)
POST /payment
토스 위젯 호출 전 결제 데이터를 등록합니다. Step 1에서 발급된 PAY_SESSIONID 쿠키가 필요합니다.
Headers
| 이름 | 필수 | 설명 |
|---|---|---|
Content-Type | O | application/json |
Cookie | O | PAY_SESSIONID (Step 1에서 발급) |
Request Body
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
amount | int | O | 결제 금액 |
name | String | O | 예약자 이름 (최대 50자) |
mobileNum | String | O | 휴대폰 번호 (최대 20자) |
email | String | X | 이메일 |
{
"amount": 12000,
"name": "홍길동",
"mobileNum": "01012345678",
"email": "user@example.com"
}
Response
- 200 성공
- 401 세션 만료
{
"status": "success",
"errorType": null,
"errorCode": null,
"message": null,
"data": {
"orderId": "ORD-20260305-abc123"
}
}
data 필드
| 필드 | 타입 | 설명 |
|---|---|---|
orderId | String | 토스 위젯에 전달할 주문 ID |
PAY_SESSIONID 쿠키가 만료된 경우 (TTL 5분):
{
"status": "error",
"errorType": "ResourceTimeOutException",
"errorCode": null,
"message": "예약 세션이 만료되었습니다.",
"data": null
}
결제 확인 (Step 3)
POST /payment/confirm
토스 위젯 결제 완료 후 서버에서 최종 승인합니다. 성공 시 doorlockKey가 생성되고, 카카오 알림톡이 발송됩니다.
Headers
| 이름 | 필수 | 설명 |
|---|---|---|
Content-Type | O | application/json |
Request Body
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
paymentKey | String | O | 토스 결제 키 |
orderId | String | O | Step 2에서 받은 주문 ID |
amount | int | O | 결제 금액 (위변조 검증용) |
{
"paymentKey": "tgen_20260305_abc123",
"orderId": "ORD-20260305-abc123",
"amount": 12000
}
Response
- 200 카드 결제
- 200 가상계좌
- 409 금액 불일치
{
"status": "success",
"errorType": null,
"errorCode": null,
"message": null,
"data": {
"branch": "Gwanghwamun",
"roomNo": "1",
"startDateTime": "202603051400",
"endDateTime": "202603051550",
"refundableDateTime": "202603051330",
"mobileNum": "01012345678",
"email": "user@example.com",
"paymentMethod": "카드",
"virtualaccountAccountNumber": null,
"virtualaccountBankName": null,
"virtualaccountAmount": null,
"virtualaccountCustomerName": null,
"virtualaccountDueDate": null
}
}
{
"status": "success",
"errorType": null,
"errorCode": null,
"message": null,
"data": {
"branch": "Gwanghwamun",
"roomNo": "1",
"startDateTime": "202603051400",
"endDateTime": "202603051550",
"refundableDateTime": "202603051330",
"mobileNum": "01012345678",
"email": null,
"paymentMethod": "가상계좌",
"virtualaccountAccountNumber": "12345678901234",
"virtualaccountBankName": "신한은행",
"virtualaccountAmount": 12000,
"virtualaccountCustomerName": "홍길동",
"virtualaccountDueDate": "2026-03-05T14:30:00"
}
}
{
"status": "error",
"errorType": "TossDataIntegrityException",
"errorCode": null,
"message": "결제 금액이 일치하지 않습니다.",
"data": null
}
data 필드
| 필드 | 타입 | Nullable | 설명 |
|---|---|---|---|
branch | String | N | 지점 영문명 |
roomNo | String | N | 방 번호 |
startDateTime | String | N | 시작 시간 (YYYYMMDDHHmm) |
endDateTime | String | N | 종료 시간 (YYYYMMDDHHmm) |
refundableDateTime | String | N | 환불 가능 시한 (YYYYMMDDHHmm) |
mobileNum | String | N | 휴대폰 번호 |
email | String | Y | 이메일 |
paymentMethod | String | N | 카드 / 간편결제 / 가상계좌 / 휴대폰 |
virtualaccountAccountNumber | String | Y | 가상계좌 번호 (가상계좌 결제 시) |
virtualaccountBankName | String | Y | 가상계좌 은행명 |
virtualaccountAmount | Integer | Y | 입금 금액 |
virtualaccountCustomerName | String | Y | 예금주명 |
virtualaccountDueDate | String | Y | 입금 기한 (ISO 8601) |
DB에 저장되는 endDateTime은 실제 종료 시간보다 10분 앞선 시간입니다. 예를 들어 실제 종료가 16:00이면 DB에는 15:50으로 저장됩니다.
카드 결제 완료 시 doorlockKey(UUID)가 생성되어 Pay 테이블에 저장됩니다. 이 키는 도어락 해제에 사용됩니다.
가상계좌 결제의 경우 입금 완료 후에 doorlockKey가 생성됩니다.