299 lines
12 KiB
Markdown
299 lines
12 KiB
Markdown
# 선물하기 클라이언트 API 요약
|
|
|
|
## 문서 목적
|
|
|
|
- 이 문서는 클라이언트 구현 프롬프트 작성용 빠른 참조다.
|
|
- 상세 request/response 계약은 `prd.md`의 `8. API 계약`을 기준으로 한다.
|
|
- 여기서는 사용자 역할별 URI, 역할, 핵심 request/response 필드만 요약한다.
|
|
|
|
## 문서 분리 결정
|
|
|
|
PRD에는 상세 API 계약, request/response 예시, 요구사항을 유지한다. 클라이언트 프롬프트용 역할별 인벤토리는 별도 문서로 둔다.
|
|
|
|
- 이유: PRD에 같은 URI 정보를 역할별로 다시 넣으면 상세 계약과 요약표를 동시에 갱신해야 한다.
|
|
- 기준: PRD는 제품 요구사항과 API 계약의 원본, 이 문서는 클라이언트 구현을 위한 탐색용 색인이다.
|
|
|
|
## 공통/조회 API
|
|
|
|
| 사용자 | Method | URI | 역할 | Request | Response |
|
|
|---|---:|---|---|---|---|
|
|
| 비회원/회원 | GET | `/api/v2/gifts/form-options` | 선물 신청 폼에 필요한 활성 카테고리와 사이즈별 가격 옵션을 조회한다. | 없음 | `sizes[]`, `categories[]` |
|
|
| 팬/크리에이터 | GET | `/api/v2/gifts?type=ALL&page=0&size=20` | 내 선물함 목록을 조회하고 `type=ALL|SENT|RECEIVED`로 전체/보낸/받은 선물을 필터링한다. | query: `type`, `page`, `size` | `items[]`, `page`, `size`, `hasNext` |
|
|
| 팬/크리에이터 | GET | `/api/v2/gifts/{applicationNo}` | 선물 상세를 조회하며 로그인 회원이 팬인지 크리에이터인지에 따라 노출 정보와 CTA flag가 달라진다. | path: `applicationNo` | `applicationNo`, `direction`, `status`, `statusName`, `giftInfo`, `senderInfo`, `recipientAddress`, `mailbox`, `trackingRequired`, `recipientAddressRequired`, `delivery`, `statusTimeline`, `review` |
|
|
|
|
## 팬 API
|
|
|
|
| 사용자 | Method | URI | 역할 | Request | Response |
|
|
|---|---:|---|---|---|---|
|
|
| 팬 | POST | `/api/v2/gifts` | 크리에이터에게 선물을 신청하고 최종 결제 금액만큼 캔을 차감한다. | body: `recipientMemberId`, `senderName`, `senderPhoneNumber`, `senderZipCode`, `senderAddress`, `senderAddressDetail`, `sizeCode`, `categoryId`, `senderTermsAgreed`, `senderPrivacyAgreed`, `damageWaiverAgreed` | `applicationNo`, `status`, `statusName`, `priceCan`, `trackingDeadlineAt` |
|
|
| 팬 | POST | `/api/v2/gifts/{applicationNo}/cancel` | 운송장 등록 전 선물 신청을 취소하고 전액 환불한다. | path: `applicationNo` | `applicationNo`, `status`, `statusName`, `priceCan`, `canceledAt` |
|
|
| 팬 | POST | `/api/v2/gifts/{applicationNo}/tracking` | 발송 후 택배사와 운송장 번호를 등록한다. | path: `applicationNo`, body: `courierCompanyName`, `trackingNumber` | `applicationNo`, `status`, `statusName`, `courierCompanyName`, `trackingNumber`, `trackingRegisteredAt`, `recipientAddressDeadlineAt` |
|
|
| 팬 | POST | `/api/v2/gifts/{applicationNo}/review` | 전달 완료된 선물에 리뷰를 작성한다. | path: `applicationNo`, body: `rating`, `keywords`, `comment` | `reviewId`, `applicationNo`, `rating`, `keywords`, `comment`, `createdAt` |
|
|
|
|
## 크리에이터 API
|
|
|
|
| 사용자 | Method | URI | 역할 | Request | Response |
|
|
|---|---:|---|---|---|---|
|
|
| 크리에이터 | POST | `/api/v2/gifts/{applicationNo}/recipient-address` | 운송장 등록 이후 배송지와 수령 약관 동의를 등록한다. | path: `applicationNo`, body: `recipientName`, `recipientPhoneNumber`, `recipientZipCode`, `recipientAddress`, `recipientAddressDetail`, `recipientTermsAgreed`, `recipientPrivacyAgreed` | `applicationNo`, `status`, `statusName`, `recipientAddressRegisteredAt` |
|
|
| 크리에이터 | POST | `/api/v2/gifts/{applicationNo}/delivery-complete` | 검수 완료된 선물을 직접 수령 완료 처리한다. | path: `applicationNo` | `applicationNo`, `status`, `statusName`, `deliveredAt` |
|
|
|
|
## 관리자 API
|
|
|
|
| 사용자 | Method | URI | 역할 | Request | Response |
|
|
|---|---:|---|---|---|---|
|
|
| 관리자 | GET | `/api/v2/admin/gift-categories` | 선물 카테고리 목록을 조회한다. | 없음 | `AdminGiftCategoryResponse[]` |
|
|
| 관리자 | POST | `/api/v2/admin/gift-categories` | 선물 카테고리를 등록한다. | body: `categoryCode`, `name`, `requiresDamageWaiver`, `isActive` | `categoryId`, `categoryCode`, `name`, `requiresDamageWaiver`, `isActive` |
|
|
| 관리자 | PUT | `/api/v2/admin/gift-categories/{categoryId}` | 선물 카테고리를 수정한다. | path: `categoryId`, body: `categoryCode`, `name`, `requiresDamageWaiver`, `isActive` | `categoryId`, `categoryCode`, `name`, `requiresDamageWaiver`, `isActive` |
|
|
| 관리자 | DELETE | `/api/v2/admin/gift-categories/{categoryId}` | 선물 카테고리를 비활성화한다. | path: `categoryId` | `categoryId`, `categoryCode`, `name`, `requiresDamageWaiver`, `isActive=false` |
|
|
| 관리자 | GET | `/api/v2/admin/gift-size-prices` | 사이즈별 기본가와 판매가를 조회한다. | 없음 | `AdminGiftSizePriceResponse[]` |
|
|
| 관리자 | PUT | `/api/v2/admin/gift-size-prices/{sizeCode}` | 사이즈별 기본가와 판매가를 수정한다. | path: `sizeCode`, body: `basePriceCan`, `salePriceCan`, `isActive` | `sizeCode`, `name`, `basePriceCan`, `salePriceCan`, `isActive` |
|
|
| 관리자 | POST | `/api/v2/admin/gifts/{applicationNo}/arrive-mailbox` | 발송 확인 선물을 사서함 도착 처리한다. | path: `applicationNo` | `applicationNo`, `status`, `statusName`, `occurredAt` |
|
|
| 관리자 | POST | `/api/v2/admin/gifts/{applicationNo}/complete-inspection` | 사서함 도착 선물을 검수 완료 처리한다. | path: `applicationNo` | `applicationNo`, `status`, `statusName`, `occurredAt` |
|
|
| 관리자 | POST | `/api/v2/admin/gifts/{applicationNo}/mark-undeliverable` | 진행 중 선물을 전달 불가 처리하고 사유를 저장한다. | path: `applicationNo`, body: `reason` | `applicationNo`, `status`, `statusName`, `occurredAt` |
|
|
| 관리자 | POST | `/api/v2/admin/gifts/{applicationNo}/complete-delivery` | 검수 완료 선물을 운영자가 전달 완료 처리한다. | path: `applicationNo` | `applicationNo`, `status`, `statusName`, `occurredAt` |
|
|
|
|
관리자 응답 DTO 요약:
|
|
|
|
- `AdminGiftCategoryResponse`: `categoryId`, `categoryCode`, `name`, `requiresDamageWaiver`, `isActive`
|
|
- `AdminGiftSizePriceResponse`: `sizeCode`, `name`, `basePriceCan`, `salePriceCan`, `isActive`
|
|
- `AdminGiftOperationStatusResponse`: `applicationNo`, `status`, `statusName`, `occurredAt`
|
|
|
|
## Kotlin DTO 형태
|
|
|
|
아래는 JSON으로 변환되기 직전의 API request/response `data class` 형태다. `companion object` 변환 함수는 클라이언트 입력/출력 형태와 직접 관련이 없어 생략했다.
|
|
|
|
### 사용자 Request DTO
|
|
|
|
```kotlin
|
|
data class GiftApplicationRequest(
|
|
val recipientMemberId: Long,
|
|
val senderName: String,
|
|
val senderPhoneNumber: String,
|
|
val senderZipCode: String,
|
|
val senderAddress: String,
|
|
val senderAddressDetail: String?,
|
|
val sizeCode: GiftSize,
|
|
val categoryId: Long,
|
|
val senderTermsAgreed: Boolean,
|
|
val senderPrivacyAgreed: Boolean,
|
|
val damageWaiverAgreed: Boolean
|
|
)
|
|
|
|
data class GiftTrackingRegistrationRequest(
|
|
val courierCompanyName: String,
|
|
val trackingNumber: String
|
|
)
|
|
|
|
data class GiftRecipientAddressRegistrationRequest(
|
|
val recipientName: String,
|
|
val recipientPhoneNumber: String,
|
|
val recipientZipCode: String,
|
|
val recipientAddress: String,
|
|
val recipientAddressDetail: String?,
|
|
val recipientTermsAgreed: Boolean,
|
|
val recipientPrivacyAgreed: Boolean
|
|
)
|
|
|
|
data class GiftReviewRequest(
|
|
val rating: Int,
|
|
val keywords: List<String>,
|
|
val comment: String?
|
|
)
|
|
```
|
|
|
|
### 사용자 Response DTO
|
|
|
|
```kotlin
|
|
data class GiftFormOptionsResponse(
|
|
val sizes: List<GiftSizeOptionResponse>,
|
|
val categories: List<GiftCategoryOptionResponse>
|
|
)
|
|
|
|
data class GiftSizeOptionResponse(
|
|
val sizeCode: String,
|
|
val name: String,
|
|
val basePriceCan: Int,
|
|
val salePriceCan: Int
|
|
)
|
|
|
|
data class GiftCategoryOptionResponse(
|
|
val categoryId: Long,
|
|
val name: String,
|
|
val requiresDamageWaiver: Boolean
|
|
)
|
|
|
|
data class GiftListResponse(
|
|
val totalCount: Long,
|
|
val items: List<GiftListItemResponse>,
|
|
val page: Int,
|
|
val size: Int,
|
|
val hasNext: Boolean
|
|
)
|
|
|
|
data class GiftListItemResponse(
|
|
val applicationNo: String,
|
|
val direction: String,
|
|
val status: String,
|
|
val statusName: String,
|
|
val priceCan: Int,
|
|
val categoryName: String,
|
|
val sizeName: String,
|
|
val createdAt: LocalDateTime?
|
|
)
|
|
|
|
data class GiftDetailResponse(
|
|
val applicationNo: String,
|
|
val direction: String,
|
|
val status: String,
|
|
val statusName: String,
|
|
val giftInfo: GiftDetailInfoResponse,
|
|
val senderInfo: GiftAddressResponse?,
|
|
val recipientAddress: GiftAddressResponse?,
|
|
val mailbox: GiftMailboxResponse?,
|
|
val trackingRequired: Boolean,
|
|
val recipientAddressRequired: Boolean,
|
|
val recipientAddressDeadlineAt: LocalDateTime?,
|
|
val delivery: GiftDeliveryInfoResponse,
|
|
val statusTimeline: List<GiftStatusTimelineResponse>
|
|
)
|
|
|
|
data class GiftDetailInfoResponse(
|
|
val recipientCreatorNickname: String?,
|
|
val senderNickname: String?,
|
|
val sizeName: String,
|
|
val categoryName: String,
|
|
val applicationNo: String?,
|
|
val paidCan: Int?,
|
|
val tracking: String?,
|
|
val shippingRequestedAt: LocalDateTime?
|
|
)
|
|
|
|
data class GiftAddressResponse(
|
|
val name: String,
|
|
val phoneNumber: String,
|
|
val address: String
|
|
)
|
|
|
|
data class GiftMailboxResponse(
|
|
val name: String,
|
|
val address: String,
|
|
val phoneNumber: String
|
|
)
|
|
|
|
data class GiftDeliveryInfoResponse(
|
|
val canceledAt: LocalDateTime?,
|
|
val undeliverableAt: LocalDateTime?,
|
|
val undeliverableReason: String?
|
|
)
|
|
|
|
data class GiftStatusTimelineResponse(
|
|
val status: String,
|
|
val statusName: String,
|
|
val occurredAt: LocalDateTime?
|
|
)
|
|
|
|
data class GiftApplicationResponse(
|
|
val applicationNo: String,
|
|
val status: String,
|
|
val statusName: String,
|
|
val priceCan: Int,
|
|
val trackingDeadlineAt: LocalDateTime
|
|
)
|
|
|
|
data class GiftCancellationResponse(
|
|
val applicationNo: String,
|
|
val status: String,
|
|
val statusName: String,
|
|
val priceCan: Int,
|
|
val canceledAt: LocalDateTime
|
|
)
|
|
|
|
data class GiftTrackingRegistrationResponse(
|
|
val applicationNo: String,
|
|
val status: String,
|
|
val statusName: String,
|
|
val courierCompanyName: String,
|
|
val trackingNumber: String,
|
|
val trackingRegisteredAt: LocalDateTime,
|
|
val recipientAddressDeadlineAt: LocalDateTime
|
|
)
|
|
|
|
data class GiftRecipientAddressRegistrationResponse(
|
|
val applicationNo: String,
|
|
val status: String,
|
|
val statusName: String,
|
|
val recipientAddressRegisteredAt: LocalDateTime
|
|
)
|
|
|
|
data class GiftDeliveryConfirmationResponse(
|
|
val applicationNo: String,
|
|
val status: String,
|
|
val statusName: String,
|
|
val deliveredAt: LocalDateTime
|
|
)
|
|
|
|
data class GiftReviewResponse(
|
|
val reviewId: Long,
|
|
val applicationNo: String,
|
|
val rating: Int,
|
|
val keywords: List<String>,
|
|
val comment: String?,
|
|
val createdAt: LocalDateTime?
|
|
)
|
|
```
|
|
|
|
### 관리자 Request DTO
|
|
|
|
```kotlin
|
|
data class AdminGiftCategoryRequest(
|
|
val categoryCode: String,
|
|
val name: String,
|
|
val requiresDamageWaiver: Boolean,
|
|
val isActive: Boolean
|
|
)
|
|
|
|
data class AdminGiftSizePriceRequest(
|
|
val basePriceCan: Int,
|
|
val salePriceCan: Int,
|
|
val isActive: Boolean
|
|
)
|
|
|
|
data class AdminGiftMarkUndeliverableRequest(
|
|
val reason: String
|
|
)
|
|
```
|
|
|
|
### 관리자 Response DTO
|
|
|
|
```kotlin
|
|
data class AdminGiftCategoryResponse(
|
|
val categoryId: Long,
|
|
val categoryCode: String,
|
|
val name: String,
|
|
val requiresDamageWaiver: Boolean,
|
|
val isActive: Boolean
|
|
)
|
|
|
|
data class AdminGiftSizePriceResponse(
|
|
val sizeCode: String,
|
|
val name: String,
|
|
val basePriceCan: Int,
|
|
val salePriceCan: Int,
|
|
val isActive: Boolean
|
|
)
|
|
|
|
data class AdminGiftOperationStatusResponse(
|
|
val applicationNo: String,
|
|
val status: GiftStatus,
|
|
val statusName: String,
|
|
val occurredAt: LocalDateTime
|
|
)
|
|
```
|
|
|
|
## 클라이언트 구현 참고
|
|
|
|
- 선물 푸시 딥링크는 `deepLinkValue=GIFT_DETAIL`, `deepLinkId=applicationNo`를 사용한다.
|
|
- 딥링크 진입 후에는 `GET /api/v2/gifts/{applicationNo}`로 상세를 조회해 `direction`, `trackingRequired`, `recipientAddressRequired`를 기준으로 화면과 CTA를 결정한다.
|
|
- 운송장 미등록 자동취소는 서버 스케줄러가 처리하며, 팬 클라이언트는 상세 조회의 `status=CANCELED`와 푸시를 기준으로 표시한다.
|
|
- 모든 응답은 기존 `ApiResponse.ok(...)` envelope 안의 `data`로 내려간다.
|