diff --git a/README.md b/README.md index 2fef77b..16f7dd5 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ TDD(Today Delicious Delivery)는 이름 그대로 '오늘의 맛있는 배달' | 분류 | 상세 | |---------------------|----------------------------------------------------------------------------| | **Back-End** | Java 21, Spring Boot 3.5.6, Spring Data JPA, Querydsl 7.0, Spring Security | -| **Database** | PostgreSQL 18.0 | + **Database** | PostgreSQL 17.6 | | **Build Tool** | Gradle | | **Infra** | Docker compose, Github Actions(CI) | | **open API** | Google GenAI API, Naver Map API | @@ -76,7 +76,7 @@ docker exec -it tdd-db psql -U test -d tdd-db ## 1. ERD -![erd](https://github.com/user-attachments/assets/96efb039-16e5-4f0d-95e6-b0b7e0a40cbb) +![erd](https://github.com/user-attachments/assets/5a08601b-8d4d-4993-a7f2-833bdfa86d16) ## 2. 도메인 다이어그램 @@ -98,6 +98,124 @@ docker exec -it tdd-db psql -U test -d tdd-db ## 1. 도메인별 핵심 기능 상세 구현 +### (1) Order + +![Order diagram](https://github.com/user-attachments/assets/6caa1364-0a1b-4e17-8706-25f320bb0004) + +- Order 도메인 + - DDD 원칙에 따라 주문의 상태 전이(`nextStatus`, `changeOrderStatus`) 로직을 엔티티 내부에 위치 + - Order 와 Menu 의 다대다 관계를 OrderMenu 로 풀어내 도메인 응집도 강화 + - Mapper 를 통해 Service 는 트랜잭션 중심의 조합 역할만 수행 +- getOrders(주문 목록 조회) + - QueryDSL을 이용한 동적 검색 (조건 null 시 필터 미적용) + - MANAGER, MASTER 권한 외 사용자는 본인 주문만 조회 (권한 검증) + - Page 처리로 ID 목록 우선 조회 후 fetch join으로 세부 데이터 일괄 로딩 (N+1 방지) +- nextOrderStatus(주문 단계 변경) + - OWNER 가 가능한 주문 단계변경 메서드로 시스템의 규칙에 따라서만 변경이 가능하도록 nextStatus 로 동작 + - 관리자 권한의 사용자들은 changeOrderStatus 를 통하여 임의 변경가능하도록 별도 API 구성 + +- PageableHandlerMethodArgumentResolver + - 공통 페이징 정책을 일괄 적용하기 위해 WebMvcConfig 에 정책 등록 + - 기본 정렬 기준(`createdAt DESC`)을 지정하여 일관된 정렬 정책 유지 + +### (2) Authentication + +**회원가입** + +- username, password를 이용한 신규 사용자 등록 +- 회원가입 시점에서 권한(CUSTOMER, OWNER, MANAGER, MASTER) 설정 가능 +- 즉시 로그인 처리되어 Access Token(Header), Refresh Token(Cookie) 자동 발급 +- 비밀번호는 BCryptPasswordEncoder를 통해 암호화 저장 + +**로그인** + +- username, password를 이용한 인증 처리 +- `PasswordEncoder.matches()`를 통한 비밀번호 검증 +- Access Token(Header), Refresh Token(Cookie) 발급 + +**로그아웃** + +- 현재 사용 중인 Access Token과 Refresh Token을 블랙리스트에 추가 +- 로컬 캐시 기반 블랙리스트 관리로 빠른 토큰 무효화 처리 +- Refresh Token 쿠키 무효화 + +**회원 탈퇴** + +- Soft Delete 방식으로 사용자 및 연관 데이터 삭제 +- 권한별 차등 삭제 로직: + - OWNER: 소유 가게 → 메뉴 → 리뷰 → 리뷰 댓글 등 연관 데이터 일괄 삭제 + - CUSTOMER: 리뷰, 주문 등 개인 데이터 삭제 +- 탈퇴 후 자동 로그아웃 처리 (토큰 블랙리스트 추가) + +### (3) JWT 기반 인증 시스템 + +**토큰 관리 전략** + +- Access Token: HTTP Authorization Header로 전달 (짧은 유효기간) +- Refresh Token: HttpOnly Cookie로 전달 (긴 유효기간) +- JWT 라이브러리: `jjwt` 사용 + +**토큰 재발급 (RTR 기법 적용)** + +- Refresh Token Rotation (RTR) 기법을 통한 보안 강화 + - 토큰 재발급 시 Refresh Token도 함께 재발급 + - 기존 Refresh Token은 블랙리스트에 추가 + - 탈취된 Refresh Token의 장기 사용 방지 +- Refresh Token의 최대 유효기간 유지 + - 재발급 시에도 원본 Refresh Token의 만료 시간을 그대로 적용 + - 무한 갱신 방지 및 주기적 재로그인 유도 +- Access Token, Refresh Token 모두 만료 시 재로그인 필요 + +**인증 필터 처리** + +- `JwtAuthenticationFilter`: + - 요청 헤더 및 쿠키에서 토큰 추출 + - 토큰 검증 및 사용자 인증 정보 생성 + - SecurityContextHolder에 인증 정보 저장 + - 토큰이 없는 경우 public URL로 판단 +- `JwtExceptionFilter`: JWT 필터에서 발생하는 예외 처리 +- `JwtTokenProvider`: 토큰 발급, 검증, 디코딩 담당 + +### (3) Coupon + +![Coupon diagram](https://github.com/user-attachments/assets/8d1595ff-be64-4cad-b62c-baabb2bc6658) + +**Coupon** + +- 가게 쿠폰 목록 조회 + - 가게의 모든 쿠폰을 조회 +- Store 쿠폰 등록 + - 반드시 `SCOPE`가 `STORE`여야 함 + - `TYPE` 설정을 통해 고정된 할인(FIXED) 혹은 %할인(PERCENT) 선택 가능 + - OWNER는 반드시 해당 가게 OWNER만 가능함 +- Master 쿠폰 등록 + - 반드시 `SCOPE`가 `MASTER`여야 함 + - 해당 기능은 MASTER만 가능 +- 쿠폰 수정 + - 유저가 1회라도 발급받았다면, 쿠폰 수정은 불가 + - OWNER는 반드시 해당 가게 OWNER만 가능함 +- 쿠폰 삭제 + - 만료된 쿠폰은 스케쥴러를 통해 soft delete + - OWNER는 반드시 해당 가게 OWNER만 가능함 + +**UserCoupon** + +- 내 쿠폰 목록 조회 + - CouponStatus에 대하여 ACTIVE > USED/EXPIRED 순으로 정렬하되, 각 그룹에 대하여 생성일 순으로 정렬하여 조회 +- 쿠폰 발급 + - 쿠폰 발급 기능 + - exist 설정을 통해 중복 발급 방지 + - 사용자가 몰려 해당 기능을 수행할 경우를 대비해야 함(동시성 문제) + +**기타** + +- 모든 API에 대하여 @PreAuthorize를 통해 허용된 권한만 접근 가능하도록 설정 +- SRT 원칙을 지키며 각 클래스가 명확한 책임을 가지도록 메서드 단위를 최소화하여 개발 +- 추가 로직 + - UserCoupon 만료/만료된 Coupon 삭제 처리를 하나의 트랜잭션 작업으로 묶어서 매일 자정 스케쥴링 + - 만료된지 7일 된 UserCoupon에 대한 삭제 처리 스케쥴링 + - Order에서 주문 생성 시 쿠폰을 적용할 수 있는 로직 추가(최소 주문 금액에 맞는지 확인 및 총 결제금액 계산까지 확장 가능) + ## 2. 트러블슈팅 ### (1) CustomPageableResolver Bean 등록 미적용 문제 @@ -354,11 +472,11 @@ class StoreRepositoryTest extends RepositoryTest { # 팀원 소개 -| 팀원 | 깃허브 | 역할 | -|-----|--------------------------------------------|-----------------------------------------------| -| 박성민 | [@dnjsals45](https://github.com/dnjsals45) | Auth, Payment 도메인 개발 및 테스트코드 작성 | -| 김민수 | [@Doritosch](https://github.com/Doritosch) | User, AI, Address 도메인 개발 및 테스코드 작성 | -| 김채연 | [@yeon-22k](https://github.com/yeon-22k) | Menu, Coupon 도메인 개발 및 테스트코드 작성 | -| 박주찬 | [@p990805](https://github.com/p990805) | Review, ReviewReply, Cart 도메인 개발 및 테스트코드 작성 | -| 변영재 | [@bbangjae](https://github.com/bbangjae) | Store, Point 도메인 개발 및 테스트코드 작성 | -| 송의현 | [@yawning5](https://github.com/yawning5) | Order, OrderMenu 도메인 개발 및 테스트코드 작성, 페이징 정책 적용 | \ No newline at end of file +| 팀원 | 깃허브 | 역할 | +|-----|--------------------------------------------|----------------------------------------------------------------------------------| +| 박성민 | [@dnjsals45](https://github.com/dnjsals45) | Auth, Payment 도메인 개발 및 테스트코드 작성 | +| 김민수 | [@Doritosch](https://github.com/Doritosch) | AI 메뉴 소개 생성, 주소 및 유저 도메인 개발 및 테스트코드 작성 | +| 김채연 | [@yeon-22k](https://github.com/yeon-22k) | Menu, Coupon 도메인 개발 및 테스트코드 작성, Coupon 스케쥴링 만료/삭제 구현, ReadMe 통합 작업 | +| 박주찬 | [@p990805](https://github.com/p990805) | Review, ReviewReply, Cart 도메인 개발 및 테스트코드 작성 | +| 변영재 | [@bbangjae](https://github.com/bbangjae) | Store 도메인 개발 및 테스트 코드 작성, Point 로직(AOP 기반), AuditorAware를 통한 생성자/수정자 자동 관리 기능 구현 | +| 송의현 | [@yawning5](https://github.com/yawning5) | Order, OrderMenu 도메인 개발 및 테스트코드 작성, 페이징 정책 적용 | \ No newline at end of file diff --git a/api-docs.md b/api-docs.md index 070bb23..8b2bd97 100644 --- a/api-docs.md +++ b/api-docs.md @@ -2,53 +2,1314 @@ ## 도메인 개요 -- **Address**: 가게와 사용자 주소를 등록 및 관리합니다. -- **AI**: Google GenAI 를 활용한 글 생성을 제공합니다. -- **Auth**:사용자의 회원가입, 로그인, 토큰 발급 및 검증을 포함한 인증과 인가 로직을 담당합니다. -- **Cart**:장바구니는 주문 전에 사용자가 선택한 메뉴 아이템을 임시로 저장하고 관리합니다. -- **Coupon**: MASTER 및 STORE 쿠폰 발행, 수정, 삭제, 조회와 사용자의 쿠폰 발급 및 상태를 관리합니다. -- **Menu**: 가게별 메뉴의 등록, 수정, 삭제, 조회를 관리하며 메뉴의 노출상태와 가격 등의 정보를 제공합니다. -- **Order**: 사용자의 주문 요청부터 상태 변경까지의 주문 처리 전 과정을 제어합니다. -- **OrderMenu**: 개별 주문에 포함된 메뉴 항목을 관리하며, 주문-메뉴 간 다대다 관계를 매핑하는 역할을 수행합니다. -- **Payment**: 주문에 대한 결제 정보를 관리하며, 결제 요청/승인/취소 등의 프로세스를 처리합니다. -- **Review**: 고객의 리뷰 작성, 수정, 삭제와 점주의 답글 기능을 지원하여 양방향 피드백 시스템을 제공합니다. -- **Store**: 가게의 등록, 수정, 검색, 상세조회 기능을 담당하며 점주와 고객 간의 매장 정보 접근 제어를 수행합니다. -- **User**: 사용자의 권한(Role) 및 프로필 정보를 관리하며 이용자 유형에 따른 접근 가능 리소스를 제한합니다. +| 도메인 | 설명 | +|---------------|-------------------------------------------------------------| +| **Address** | 가게와 사용자 주소를 등록 및 관리합니다. | +| **AI** | Google GenAI 를 활용한 글 생성을 제공합니다. | +| **Auth** | 사용자의 회원가입, 로그인, 토큰 발급 및 검증을 포함한 인증과 인가 로직을 담당합니다. | +| **Cart** | 장바구니는 주문 전에 사용자가 선택한 메뉴 아이템을 임시로 저장하고 관리합니다. | +| **Coupon** | MASTER 및 STORE 쿠폰 발행, 수정, 삭제, 조회와 사용자의 쿠폰 발급 및 상태를 관리합니다. | +| **Menu** | 가게별 메뉴의 등록, 수정, 삭제, 조회를 관리하며 메뉴의 노출상태와 가격 등의 정보를 제공합니다. | +| **Order** | 사용자의 주문 요청부터 상태 변경까지의 주문 처리 전 과정을 제어합니다. | +| **OrderMenu** | 개별 주문에 포함된 메뉴 항목을 관리하며, 주문-메뉴 간 다대다 관계를 매핑하는 역할을 수행합니다. | +| **Payment** | 주문에 대한 결제 정보를 관리하며, 결제 요청/승인/취소 등의 프로세스를 처리합니다. | +| **Review** | 고객의 리뷰 작성, 수정, 삭제와 점주의 답글 기능을 지원하여 양방향 피드백 시스템을 제공합니다. | +| **Store** | 가게의 등록, 수정, 검색, 상세조회 기능을 담당하며 점주와 고객 간의 매장 정보 접근 제어를 수행합니다. | +| **User** | 사용자의 권한(Role) 및 프로필 정보를 관리하며 이용자 유형에 따른 접근 가능 리소스를 제한합니다. | + +## API 명세서 ![GET](https://img.shields.io/badge/GET-2196F3?style=flat) ![POST](https://img.shields.io/badge/POST-4CAF50?style=flat) ![PATCH](https://img.shields.io/badge/PATCH-FFC107?style=flat) ![DELETE](https://img.shields.io/badge/DELETE-F44336?style=flat) -## API 명세서 - - - - - - - + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
도메인기능메서드URIRequestResponse
Address가게 주소 등록메서드/v1/address/store
{  
+  
도메인기능메서드URIRequestResponse
Address가게 주소 등록POST/v1/address/store
{
 "roadAddress": "경기도 성남시 분당구 불정로 6 NAVER그린팩토리",
 "jibunAddress": "경기도 성남시 분당구 정자동 178-1 NAVER그린팩토리",
-“detailAddress”: “101동”,
-"latitude": "127.1052160",
+"detailAddress": "101동",
+"latitude": "127.105216",
+"longitude": "37.3595033"
+}
{
+"uuid": "90553608-0f86-441f-811f-8dc7abf70e9b",
+"jibunAddress": "경기도 성남시 분당구 정자동 178-1 NAVER그린팩토리",
+"roadAddress": "경기도 성남시 분당구 불정로 6 NAVER그린팩토리",
+"detailAddress": "101동",
+"latitude": "127.105216",
 "longitude": "37.3595033"
+}
Address가게 주소 수정PATCH/v1/address/store/{storeId}

+{
+"uuid": "90553608-0f86-441f-811f-8dc7abf70e9b",
+"jibunAddress": "서울특별시 구로구 구로동 5-1",
+"roadAddress": "서울특별시 구로구 새말로 89",
+"detailAddress": "2층",
+"latitude": "126.8896815",
+"longitude": "37.505573"
 }
 

+{
+"uuid": "90553608-0f86-441f-811f-8dc7abf70e9b",
+"jibunAddress": "서울특별시 구로구 구로동 5-1",
+"roadAddress": "서울특별시 구로구 새말로 89",
+"detailAddress": "2층",
+"latitude": "126.8896815",
+"longitude": "37.505573"
+}
+
Address가게 주소 삭제DELETE/v1/address/store/{storeId}
Address회원 주소 등록POST/v1/address/user
{
+  "roadAddress": "경기도 성남시 분당구 불정로 6 NAVER그린팩토리",
+  "jibunAddress": "경기도 성남시 분당구 정자동 178-1 NAVER그린팩토리",
+  "detailAddress": "101동",
+  "alias": "집",
+  "latitude": "127.1052160",
+  "longitude": "37.3595033"
+}

+{
+"uuid": "2c92e6c7-6906-4f06-b34e-124b51210520",
+"jibunAddress": "경기도 성남시 분당구 정자동 178-1 NAVER그린팩토리",
+"roadAddress": "경기도 성남시 분당구 불정로 6 NAVER그린팩토리",
+"detailAddress": "101동",
+"alias": "우리집",
+"latitude": "127.105216",
+"longitude": "37.3595033"
+}
+
Address전체 회원 주소 목록 조회GET/v1/address/user/all?page=0&size=10

+{
+"content": [
+{
+"uuid": "90553608-0f86-441f-811f-8dc7abf70e9b",
+"jibunAddress": "서울특별시 구로구 구로동 5-1",
+"roadAddress": "서울특별시 구로구 새말로 89",
+"detailAddress": "2층",
+"latitude": "126.8896815",
+"longitude": "37.505573"
+}
+],
+"pageable": {
+"pageNumber": 0,
+"pageSize": 10,
+"sort": {
+"empty": false,
+"unsorted": false,
+"sorted": true
+},
+"offset": 0,
+"unpaged": false,
+"paged": true
+},
+"last": true,
+"totalPages": 1,
+"totalElements": 1,
+"first": true,
+"size": 10,
+"number": 0,
+"sort": {
+"empty": false,
+"unsorted": false,
+"sorted": true
+},
+"numberOfElements": 1,
+"empty": false
+}
+
Address가게 주소 수정메서드/v1/address/store/{storeId}
{  
+    
Address회원 주소 조회GET/v1/address/user

+[
+{
+"uuid": "d06857dd-0090-4821-a1a3-ffbb662b40c6",
+"jibunAddress": "경기도 성남시 분당구 정자동 178-1 NAVER그린팩토리",
+"roadAddress": "경기도 성남시 분당구 불정로 6 NAVER그린팩토리",
+"detailAddress": "101동",
+"alias": "우리집",
+"latitude": "127.105216",
+"longitude": "37.3595033"
+},
+{
+"uuid": "6174753c-6b24-43fe-b526-754825ff1b1f",
+"jibunAddress": "서울특별시 구로구 구로동 5-1",
+"roadAddress": "서울특별시 구로구 새말로 89",
+"detailAddress": "2층",
+"alias": "회사",
+"latitude": "126.8896815",
+"longitude": "37.505573"
+}
+]
+
Address회원 주소 수정PATCH/v1/address/user/{addressId}
{
+  "roadAddress": "경기도 성남시 분당구 불정로 6 NAVER그린팩토리",
+  "jibunAddress": "경기도 성남시 분당구 정자동 178-1 NAVER그린팩토리",
+  "detailAddress": "102동",
+  "alias": "회사",
+  "latitude": "127.1052160",
+  "longitude": "37.3595033"
+}
+{
+"uuid": "d06857dd-0090-4821-a1a3-ffbb662b40c6",
+"jibunAddress": "경기도 성남시 분당구 정자동 178-1 NAVER그린팩토리",
 "roadAddress": "경기도 성남시 분당구 불정로 6 NAVER그린팩토리",
+"detailAddress": "101동",
+"alias": "회사2",
+"latitude": "127.105216",
+"longitude": "37.3595033"
+}
Address회원 주소 삭제DELETE/v1/address/user/{addressId}
AddressAPI 주소 조회GET/v1/address?query={address}

+{
+"content": [
+{
 "jibunAddress": "경기도 성남시 분당구 정자동 178-1 NAVER그린팩토리",
-“detailAddress”: “101동”,
+"roadAddress": "경기도 성남시 분당구 불정로 6 NAVER그린팩토리",
 "latitude": "127.1052160",
 "longitude": "37.3595033"
 }
+],
+"pageable": {
+"pageNumber": 0,
+"pageSize": 10,
+"sort": {
+"empty": false,
+"unsorted": false,
+"sorted": true
+},
+"offset": 0,
+"unpaged": false,
+"paged": true
+},
+"last": true,
+"totalPages": 1,
+"totalElements": 1,
+"first": true,
+"size": 10,
+"number": 0,
+"sort": {
+"empty": false,
+"unsorted": false,
+"sorted": true
+},
+"numberOfElements": 1,
+"empty": false
+}
Auth회원가입POST/v1/auth/signup
{
+  "username": "username",
+  "password": "password"
+}
{
+  "userId": 1
+}
Auth유저네임 중복확인GET/v1/auth/exists?username={username}
{
+  "exists": true
+}
Auth로그인POST/v1/auth/login
{
+  "username": "username",
+  "password": "password"
+}
{
+  "userId": 1
+}
Auth로그아웃POST/v1/auth/logout
Auth회원 탈퇴DELETE/v1/auth/withdrawal
Auth토큰 재발급POST/v1/auth/token/reissue
Cart장바구니 조회GET/v1/cart
{
+  "cartId": "UUID",
+  "userId": 1,
+  "items": [
+    {
+      "cartItemId": "UUID",
+      "menuId": "UUID",
+      "menuName": "김치찌개",
+      "price": 8000,
+      "quantity": 2,
+      "totalPrice": 16000,
+      "storeId": "UUID",
+      "storeName": "맛있는식당"
+    }
+  ],
+  "totalPrice": 16000,
+  "storeId": "UUID"
+}
Cart장바구니에 아이템 추가POST/v1/cart/items
{
+  "menuId": "UUID",
+  "quantity": 2
+}
{
+  "cartId": "UUID",
+  "userId": 1,
+  "items": [
+    {
+      "cartItemId": "UUID",
+      "menuId": "UUID",
+      "menuName": "김치찌개",
+      "price": 8000,
+      "quantity": 2,
+      "totalPrice": 16000,
+      "storeId": "UUID",
+      "storeName": "맛있는식당"
+    }
+  ],
+  "totalPrice": 16000,
+  "storeId": "UUID"
+}
Cart장바구니 아이템 수량 수정PATCH/v1/cart/items/{cartItemId}?quantity={quantity}
/v1/cart/items/{cartItemId}?quantity=5
{
+  "cartId": "UUID",
+  "userId": 1,
+  "items": [],
+  "totalPrice": 0,
+  "storeId": null
+}
Cart장바구니 아이템 삭제DELETE/v1/cart/items/{cartItemId}
/v1/cart/items/{cartItemId}

+{
+"cartId": “UUID",
+"userId": 1,
+"items": [],
+"totalPrice": 0,
+"storeId": null
+}
 
Cart장바구니 비우기DELETE/v1/cart
Coupon매장 쿠폰 목록 조회GET/v1/coupon/list/{storeId}
[
+  {
+    "couponId": "9f2d9a2e-1b4a-45a3-9b7f-2f9e6b2d2f00",
+    "name": "주말 10% 할인",
+    "type": "PERCENT",
+    "scope": "STORE",
+    "discountValue": 10,
+    "minOrderPrice": 15000,
+    "quantity": 500,
+    "issuedCount": 132,
+    "expiredAt": "2025-12-31T23:59:59"
+  },
+  {
+    "couponId": "74a7c6c5-8a18-4e1e-9a3b-0c2b4f8f9c11",
+    "name": "3천원 즉시할인",
+    "type": "FIXED",
+    "scope": "STORE",
+    "discountValue": 3000,
+    "minOrderPrice": 12000,
+    "quantity": 300,
+    "issuedCount": 98,
+    "expiredAt": "2025-11-30T23:59:59"
+  }
+]
Coupon내 쿠폰 목록 조회GET/v1/coupon/my/list
[
+  {
+    "userCouponId": "a2b0a5ef-2d9c-4c6d-9b0e-1a2b3c4d5e6f",
+    "userId": 101,
+    "couponId": "9f2d9a2e-1b4a-45a3-9b7f-2f9e6b2d2f00",
+    "couponStatus": "ACTIVE"
+  },
+  {
+    "userCouponId": "e3c1d2f4-5678-49ab-9cde-0123456789ab",
+    "userId": 101,
+    "couponId": "74a7c6c5-8a18-4e1e-9a3b-0c2b4f8f9c11",
+    "couponStatus": "USED"
+  }
+]
CouponStore 쿠폰 등록POST/v1/coupon/{storeId}
{
+  "name": "2000원 할인",
+  "type": "FIXED",
+  "scope": "STORE",
+  "discountValue": 2000,
+  "minOrderPrice": 10000,
+  "quantity": 1000,
+  "expiredAt": "2025-12-31T23:59:59"
+}
{
+  "couponId": "2c3b4a5d-6e7f-4a8b-9c0d-1e2f3a4b5c6d",
+  "name": "2000원 할인",
+  "type": "FIXED",
+  "scope": "STORE",
+  "discountValue": 2000,
+  "minOrderPrice": 10000,
+  "quantity": 1000,
+  "issuedCount": 0,
+  "expiredAt": "2025-12-31T23:59:59"
+}
CouponMASTER 쿠폰 등록POST/v1/coupon/{couponId}
{
+  "name": "전매장 5천원 할인",
+  "type": "FIXED",
+  "scope": "MASTER",
+  "discountValue": 5000,
+  "minOrderPrice": 20000,
+  "quantity": 10000,
+  "expiredAt": "2026-01-31T23:59:59"
+}
{
+  "couponId": "d4f1a2b3-c4d5-4e6f-8a90-b1c2d3e4f5a6",
+  "name": "전매장 5천원 할인",
+  "type": "FIXED",
+  "scope": "MASTER",
+  "discountValue": 5000,
+  "minOrderPrice": 20000,
+  "quantity": 10000,
+  "issuedCount": 0,
+  "expiredAt": "2026-01-31T23:59:59"
+}
CouponMaster 유저 쿠폰 등록POST/v1/coupon/{storeId}
{
+  "userCouponId": "ab12cd34-ef56-7890-ab12-cd34ef567890",
+  "userId": 101,
+  "couponId": "d4f1a2b3-c4d5-4e6f-8a90-b1c2d3e4f5a6",
+  "couponStatus": "ACTIVE"
+}
Coupon쿠폰 수정PATCH/v1/coupon/{couponId}
{
+  "name": "2000원 할인",
+  "type": "FIXED",
+  "scope": "STORE",
+  "discountValue": 2000,
+  "minOrderPrice": 10000,
+  "quantity": 1000,
+  "expiredAt": "2025-12-31T23:59:59"
+}
Coupon쿠폰 삭제DELETE/v1/coupon/{couponId}
Menu메뉴 조회(목록)GET/v1/store/{storeId}/menu
[
+  {
+    "menuId": "550e8400-e29b-41d4-a716-446655440000",
+    "name": "치킨버거",
+    "description": "바삭한 치킨과 신선한 야채가 들어간 버거",
+    "price": 8900,
+    "imageUrl": "https://example.com/images/chicken-burger.jpg",
+    "isHidden": false
+  },
+  {
+    "menuId": "551e8400-e29b-41d4-a716-446655440001",
+    "name": "새우버거",
+    "description": "통통한 새우와 타르타르소스의 조화",
+    "price": 9500,
+    "imageUrl": "https://example.com/images/shrimp-burger.jpg",
+    "isHidden": false
+  }
+]
Menu메뉴 조회(개별)GET/v1/store/{storeId}/menu/{menuId}
{
+  "menuId": "550e8400-e29b-41d4-a716-446655440000",
+  "storeId": "660e8400-e29b-41d4-a716-446655440000",
+  "name": "치킨버거",
+  "description": "바삭한 치킨과 신선한 야채가 들어간 버거",
+  "price": 8900,
+  "imageUrl": "https://example.com/images/chicken-burger.jpg",
+  "isHidden": false
+}
Menu메뉴 등록POST/v1/store/{storeId}/menu
{
+  "name": "치킨버거",
+  "description": "바삭한 치킨과 신선한 야채가 들어간 버거",
+  "price": 8900,
+  "imageUrl": "https://example.com/images/chicken-burger.jpg",
+  "isHidden": false
+}
{
+  "menuId": "550e8400-e29b-41d4-a716-446655440000",
+  "restaurantId": "660e8400-e29b-41d4-a716-446655440000",
+  "name": "치킨버거",
+  "description": "바삭한 치킨과 신선한 야채가 들어간 버거",
+  "price": 8900,
+  "imageUrl": "https://example.com/images/chicken-burger.jpg",
+  "isHidden": false,
+  "createdAt": "2025-09-29T12:00:00.000000000"
+}
Menu메뉴 수정PATCH/v1/store/{storeId}/menu/{menuId}
{ "name": "수정버거" }
Menu메뉴 상태 변경PATCH/v1/store/{storeId}/menu/{menuId}/status
{ "isHidden": true }
Menu메뉴 삭제DELETE/v1/store/{storeId}/menu/{menuId}
Order주문 목록 조회GET/v1/orders?from={startDate}&to={endDate}&status={status}&userId={userId}&storeId={storeId}&sort={sort}&page={page}&size={size}
{ 
+  "totalElements": 0,
+  "totalPages": 0,
+  "size": 0,
+  "content": [
+    {
+      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
+      "customerName": "string",
+      "storeName": "string",
+      "price": 0,
+      "address": "string",
+      "orderMenuList": [
+        {
+          "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
+          "name": "string",
+          "price": 0,
+          "quantity": 0
+        }
+      ],
+      "createdAt": "2025-10-17T08:14:04.380Z",
+      "orderStatus": "CANCELLED"
+    }
+  ],
+  "number": 0,
+  "sort": {
+    "empty": true,
+    "sorted": true,
+    "unsorted": true
+  },
+  "first": true,
+  "last": true,
+  "numberOfElements": 0,
+  "pageable": {
+    "offset": 0,
+    "sort": {
+      "empty": true,
+      "sorted": true,
+      "unsorted": true
+    }
+  }
+}
Order주문 조회GET/v1/orders/{orderId}
{ 
+  "id": 1002,
+  "customerName": "c",
+  "storeName": "d",
+  "price": 45000,
+  "address": "부산시 어떤가 456",
+  "orderMenuList": [
+    { 
+      "id": "UUID", 
+      "name": "탕수육", 
+      "price": 20000, 
+      "quantity": 1 
+    },
+    { 
+      "id": "UUID", 
+      "name": "깐풍기", 
+      "price": 25000, 
+      "quantity": 2 
+    }
+  ],
+  "createdAt": "2025-09-29T12:34:56",
+  "orderStatus": "DELIVERED"
+}
Order주문 등록POST/v1/orders
{
+  "address": "부산시 어떤가 456",
+  "customerName": "c",
+  "storeId": "UUID",
+  "storeName": "d",
+  "price": 45000,
+  "menu": [
+    { 
+      "id": "UUID", 
+      "name": "탕수육", 
+      "price": 20000, 
+      "quantity": 1 
+    },
+    { 
+      "id": "UUID", 
+      "name": "깐풍기", 
+      "price": 25000, 
+      "quantity": 2 
+    }
+  ]
+}
{
+  "id": 1002,
+  "customerName": "c",
+  "storeName": "d",
+  "price": 45000,
+  "address": "부산시 어떤가 456",
+  "orderMenuList": [
+    { 
+      "id": "UUID", 
+      "name": "탕수육", 
+      "price": 20000, 
+      "quantity": 1 
+    },
+    { 
+      "id": "UUID", 
+      "name": "깐풍기", 
+      "price": 25000, 
+      "quantity": 2 
+    }
+  ],
+  "createdAt": "2025-09-29T12:34:56",
+  "orderStatus": "DELIVERED"
+}
Order주문 상태 변경(관리자)PATCH/v1/orders/{orderId}
{ "status": "DELIVERED" }
{
+"id": 1002,
+"customerName": "c",
+"storeName": "d",
+"price": 45000,
+"address": "부산시 어딘가 456",
+"orderMenuList": [
+{ “id”: UUID, "name": "탕수육", "price": 20000, "quantity": 1 },
+{”id”: UUID,  "name": "깐풍기", "price": 25000, "quantity": 2 }
+],
+"createdAt": "2025-09-29T12:34:56",
+”oderStatus”: “DELIVERED”
+}
Order주문 상태 변경(가게주인, 관리자)PATCH/orderId/next-status
{
+"id": 1002,
+"customerName": "c",
+"storeName": "d",
+"price": 45000,
+"address": "부산시 어딘가 456",
+"orderMenuList": [
+{ “id”: UUID, "name": "탕수육", "price": 20000, "quantity": 1 },
+{”id”: UUID,  "name": "깐풍기", "price": 25000, "quantity": 2 }
+],
+"createdAt": "2025-09-29T12:34:56",
+”oderStatus”: “DELIVERED”
+}
+
Order주문 취소PATCH/cancel/{orderId}
{ 
+  "id": 1002,
+  "customerName": "c",
+  "storeName": "d",
+  "price": 45000,
+  "address": "부산시 어떤가 456",
+  "orderMenuList": [
+    { 
+      "id": "UUID", 
+      "name": "탕수육", 
+      "price": 20000, 
+      "quantity": 1 
+    },
+    { 
+      "id": "UUID", 
+      "name": "깐풍기", 
+      "price": 25000, 
+      "quantity": 2 
+    }
+  ],
+  "createdAt": "2025-09-29T12:34:56",
+  "orderStatus": "CANCELLED"
+}
Payment결제내역 조회GET/v1/payments?page=1&size=10&orderBy={정렬조건}&keyword={검색조건}
{
+    “content”: [
+      {
+           “id”: UUID
+        “paymentNumber”: “12312312”,
+        “status”: “결제 성공”,
+         “storeName”: “음식점1”,
+         “price”: 10000
+      },
+      …
+    ],
+    "pageable": {
+         "pageNumber": 0,
+         "pageSize": 10,
+         "sort": {
+              "empty": false,
+              "sorted": true,
+              "unsorted": false
+      },
+      "offset": 0,
+      "paged": true,
+      "unpaged": false
+},
+    "totalPages": 1,
+    "last": true,
+    "totalElements": 8,
+    "size": 10,
+    "number": 0,
+    "sort": {
+        "empty": false,
+        "sorted": true,
+        "unsorted": false
+    },
+    "first": true,
+    "numberOfElements": 8,
+    "empty": false
+}
Payment결제내역 상세 조회GET/v1/payments/{paymentId}
{
+        “paymentNumber”: “1q2w3e4r!”,
+        “price”: 10000,
+        “cardCompany”: “XX카드”,
+        “cardNumber”: “1111 **** **** 4444”,
+        “processedAt”: "2025-09-29T10:15:00"
+        “restaurant”: {
+            “id”: 1,
+            “storeName”: “음식점”,
+        },
+        “orderItem” [
+            {
+                “id”: 5,
+                “menuName”: “음식1”,
+                “quantity”: 1
+                “price”: 5000,
+                “totalPrice”: 5000
+            },
+            …
+        ]
+}
Payment결제 상태 변경PATCH/v1/payments/status/{paymentId}
{
+    “status”: “COMPLETED”
+}
Payment결제 요청POST/v1/payments
{
+    “orderId”: UUID,
+    “cardCompany”: “SAMSUNG”,
+    “cardNumber”: “1111 **** **** 4444”
+}
Review리뷰 등록POST/v1/reviews/order/{orderId}
{
+  "content": "리뷰 내용",
+  "storeId": 1,
+  "rating": 3,
+  "photos": "abc.png"
+}
{
+  "reviewId": 123,
+  "storeId": 1,
+  "content": "너무 맛있어요!",
+  "rating": 3,
+  "userId": "user01",
+  "photos": "abc.png",
+  "createdAt": "2025-09-29T10:15:00",
+  "modifiedAt": "2025-09-29T10:20:00"
+}
Review리뷰 수정PATCH/v1/reviews/{reviewId}
{
+  "content": "리뷰 내용",
+  "rating": 3,
+  "photos": "abc.png"
+}
{
+  "reviewId": 123,
+  "storeId": 1,
+  "content": "너무 맛있어요!",
+  "rating": 3,
+  "userId": "user01",
+  "photos": "abc.png",
+  "createdAt": "2025-09-29T10:15:00",
+  "modifiedAt": "2025-09-29T10:20:00"
+}
Review리뷰 개별 조회GET/v1/reviews/{reviewId}
{
+  "reviewId": 123,
+  "storeId": 1,
+  "content": "너무 맛있어요!",
+  "rating": 3,
+  "userId": "user01",
+  "photos": "abc.png",
+  "createdAt": "2025-09-29T10:15:00",
+  "modifiedAt": "2025-09-29T10:20:00"
+}
Review리뷰 목록 조회GET/v1/reviews/{storeId}?page=1&size=10
{
+  "reviews": [
+    {
+      "reviewId": 101,
+      "storeId": 1,
+      "content": "음식이 맛있어요!",
+      "rating": 3,
+      "userId": "user01",
+      "photos": "abc.png",
+      "createdAt": "2025-09-25T14:30:00",
+      "modifiedAt": "2025-09-29T10:20:00",
+      "reply": {
+        "content": "이용해주셔서 감사합니다"
+      }
+    },
+    {
+      "reviewId": 102,
+      "storeId": 1,
+      "content": "서비스가 친절했어요.",
+      "rating": 3,
+      "userId": "user02",
+      "photos": "abc.png",
+      "createdAt": "2025-09-26T16:45:00",
+      "modifiedAt": "2025-09-29T10:20:00",
+      "reply": {
+        "content": "다음에도 오세요"
+      }
+    }
+  ],
+  "pageInfo": {
+    "page": 1,
+    "size": 10,
+    "totalElements": 52,
+    "totalPages": 6,
+    "hasNext": true
+  }
+}
Review리뷰 삭제DELETE/v1/reviews/{reviewId}
{
+  "status": 200,
+  "message": "리뷰가 성공적으로 삭제되었습니다."
+}
Review리뷰 답글 등록POST/v1/reviews/{reviewId}/reply
{ "content": "사장님 답글 내용" }
{
+  "replyId": 88,
+  "reviewId": 123,
+  "storeId": 1,
+  "ownerId": 1,
+  "content": "리뷰 감사합니다! 또 이용해주세요",
+  "createdAt": "2025-09-29T10:15:00",
+  "modifiedAt": "2025-09-29T10:20:00"
+}
Review리뷰 답글 수정PATCH/v1/reviews/{reviewId}/reply
{ "content": "사장님 수정 답글 내용" }
{
+  "replyId": 88,
+  "reviewId": 123,
+  "storeId": 1,
+  "ownerId": 1,
+  "content": "리뷰 감사합니다! 또 이용해주세요",
+  "createdAt": "2025-09-29T10:15:00",
+  "modifiedAt": "2025-09-29T10:20:00"
+}
Review리뷰 답글 삭제DELETE/v1/reviews/{reviewId}/reply
Review리뷰 목록 조회(사장님 답글 추가 시)GET/v1/reviews/{storeId}?page=1&size=10
{
+  "reviews": [
+  {
+    "reviewId": 101,
+    “storeId”: 1,
+    "content": "음식이 맛있어요!",
+    “rating”: 3,
+    "userId": "user01",
+    “photos”: “abc.png”,
+    "createdAt": "2025-09-25T14:30:00",
+    "modifiedAt": "2025-09-29T10:20:00”,
+    “reply”: { 
+      “conetent”: 이용해주셔서 감사합니다
+    }
+},
+{
+"reviewId": 102,
+“storeId”: 1,
+"content": "서비스가 친절했어요.",
+“rating”: 3,
+"userId": "user02",
+“photos”: “abc.png”,
+"createdAt": "2025-09-26T16:45:00",
+"modifiedAt": "2025-09-29T10:20:00”
+“reply”: {
+“conetent”: 다음에 또 오세요
+}
+}
+],
+"pageInfo": {
+"page": 1,
+"size": 10,
+"totalElements": 52,
+"totalPages": 6,
+"hasNext": true
+}
+}
Store음식점 검색GET/v1/search?name={keyword}&category={keyword}&description={keyword}&sort=createdAt,desc&page=2&size=30
{ "String": "keyword" }
{
+  "content": [
+    {
+      "id": "d9756dfd-99e9-480d-a7f7-4e38b00d5163",
+      "name": "맛스터치 강남점",
+      "ownerName": "jae",
+      "category": "CHICKEN",
+      "description": "강남 최고 치킨버거",
+      "imageUrl": "https://example.com/images/chicken-burger.jpg",
+      "avgRating": 0,
+      "reviewCount": 0,
+      "orderCount": null,
+      "menus": [
+        {
+          "menuId": "bf03b171-1ccb-4173-80b6-e890a176f69b2",
+          "name": "싸이버거",
+          "description": "맘스터치 대표 메뉴",
+          "price": 5500,
+          "imageUrl": null,
+          "isHidden": null
+        },
+        {
+          "menuId": "704c5850-d379-454d-8f66-6fdba7e5e22",
+          "name": "감자튀김",
+          "description": "바삭한 감자튀김",
+          "price": 2500,
+          "imageUrl": null,
+          "isHidden": null
+        }
+      ]
+    }
+  ]
+}
Store음식점 상세 조회GET/v1/store/{storeId}
{ "storeId": "storeId" }
{
+  "status": 200,
+  "message": "음식점 상세 조회에 성공했습니다.",
+  "data": {
+    "name": "name",
+    "category": "category",
+    "description": "description",
+    "address": "address",
+    "imageUrl": "imageUrl",
+    "avgRating": 4.5,
+    "reviewCount": 500
+  }
+}
Store음식점 등록POST/v1/store
{
+  "name": "name",
+  "category": "category",
+  "description": "description",
+  "address": "address",
+  "imageUrl": "imageUrl"
+}
Store음식점 수정PATCH/v1/store/{storeId}
{
+  "storeId": "storeId",
+  "name": "newName",
+  "category": "newCategory",
+  "description": "newDescription",
+  "address": "newAddress",
+  "imageUrl": "newImageUrl"
+}
Store음식점 삭제DELETE/v1/store/{storeId}{ +”storeId”:”storeId” +}
User회원 정보 조회GET/v1/users/{userId}
{
+  "userId": "userId",
+  "username": "username"
+}
User회원 닉네임 수정PATCH/v1/users/{userId}/nickname
{
+  "nickname": "nickname"
+}
{ +"id": 1, +"username": "test01", +"password": "$2a$10$FYt/selrPyrJDEBzyjIyAu3z21285Kzjxh10GuLO9NYQDod7WkTOC", +"nickname": "테스트입니다", +"authority": "고객" +}
User회원 비밀번호 수정PATCH/v1/users/{userId}/password
{
+  "password": "password"
+}
{
+"id": 1,
+"username": "test01",
+"password": "$2a$10$OZnji3urkIVAufCRiwIu8e4t.JWZUZMSe.dM4qAu9r95A7qzkpFN2",
+"nickname": "테스트입니다",
+"authority": "고객"
+}
User회원 리뷰 목록 조회GET/v1/users/{userId}/reviews
{
+  "data": [
+    {
+      "page": 1,
+      "size": 1,
+      "reviews": {
+        "reviewId": "reviewId",
+        "content": "content",
+        "createdAt": "2025-09-29T10:15:00",
+        "storeName": "storeName"
+      }
+    }
+  ]
+}
User회원 목록 조회GET/v1/users
{
+"users": {
+"content": [
+{
+"id": 5,
+"username": "master1",
+"password": "$2a$10$yr4rLG.uvjLECZLn1AMP..5o1tRO0gzhEvf5a7LiZ5ImS9Mx4YW2y",
+"nickname": "owner",
+"authority": "최종 관리자"
+},
+…
+{
+"id": 1,
+"username": "test01",
+"password": "$2a$10$OZnji3urkIVAufCRiwIu8e4t.JWZUZMSe.dM4qAu9r95A7qzkpFN2",
+"nickname": "테스트입니다",
+"authority": "고객"
+}
+],
+"pageable": {
+"pageNumber": 0,
+"pageSize": 10,
+"sort": {
+"empty": false,
+"unsorted": false,
+"sorted": true
+},
+"offset": 0,
+"unpaged": false,
+"paged": true
+},
+"last": true,
+"totalPages": 1,
+"totalElements": 5,
+"first": true,
+"size": 10,
+"number": 0,
+"sort": {
+"empty": false,
+"unsorted": false,
+"sorted": true
+},
+"numberOfElements": 5,
+"empty": false
+}
+}
User회원 주문 목록 조회GET/v1/users/{userId}/orders
{
+"content": [
+{
+"id": "072979c1-8eaf-49c1-a36c-306c5c0792d7",
+"customerName": "test01",
+"storeName": "한식당",
+"price": 13000,
+"address": "string",
+"orderMenuList": [
+{
+"id": "56167b37-e9b1-4855-b1c9-f5ea54506fa8",
+"name": "칼국수",
+"price": 5000,
+"quantity": 1
+},
+{
+"id": "687e2748-1860-4c0a-bdd6-c77b0275f28b",
+"name": "김밥",
+"price": 2000,
+"quantity": 4
+}
+],
+"createdAt": "2025-10-18T13:34:46.945571",
+"orderStatus": "PENDING"
+},
+{
+"id": "c39792a8-60bd-4075-841e-6233bfe5c731",
+"customerName": "test01",
+"storeName": "한식당",
+"price": 15000,
+"address": "string",
+"orderMenuList": [
+{
+"id": "8093a169-80e7-43a5-8fd2-9a997c200f4f",
+"name": "칼국수",
+"price": 5000,
+"quantity": 3
+}
+],
+"createdAt": "2025-10-18T13:33:40.164406",
+"orderStatus": "PENDING"
+}
+],
+"pageable": {
+"pageNumber": 0,
+"pageSize": 10,
+"sort": {
+"empty": false,
+"unsorted": false,
+"sorted": true
+},
+"offset": 0,
+"unpaged": false,
+"paged": true
+},
+"last": true,
+"totalPages": 1,
+"totalElements": 2,
+"first": true,
+"size": 10,
+"number": 0,
+"sort": {
+"empty": false,
+"unsorted": false,
+"sorted": true
+},
+"numberOfElements": 2,
+"empty": false
+}
User매니저 권한 부여PATCH/v1/users/{userId}/authority
{
+"id": 1,
+"username": "test01",
+"password": "$2a$10$OZnji3urkIVAufCRiwIu8e4t.JWZUZMSe.dM4qAu9r95A7qzkpFN2",
+"nickname": "테스트입니다",
+"authority": "관리자"
+}