17 KiB
선물하기 클라이언트 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`로 전체/보낸/받은 선물을 필터링한다. |
| 팬/크리에이터 | 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[] |
| 관리자 | GET | /api/v2/admin/gifts |
전체 선물함 목록을 조회하고 상태/신청번호/닉네임으로 검색한다. | query: status, applicationNo, nickname, page, size |
totalCount, items[], page, size, hasNext |
| 관리자 | GET | /api/v2/admin/gifts/{applicationNo} |
선물 상세와 발송인/수취인 개인정보, 상품/배송/상태 정보를 조회한다. | path: applicationNo |
applicationNo, senderInfo, recipientInfo, productInfo, inboundDeliveryInfo, status, statusName, availableActions |
| 관리자 | POST | /api/v2/admin/gifts/{applicationNo}/cancel |
운송장 등록 전 RECEIVED 선물을 취소하고 발신자에게 전액 리워드캔을 환불한다. |
path: applicationNo, body 없음 |
AdminGiftOperationStatusResponse: applicationNo, status, statusName, occurredAt (canceledAt UTC ISO Z) |
| 관리자 | POST | /api/v2/admin/gift-categories |
선물 카테고리를 등록한다. | body: classificationNumber, categoryCode, name, receiptCode, representativeItem, requiresDamageWaiver, isActive |
categoryId, classificationNumber, categoryCode, name, receiptCode, representativeItem, requiresDamageWaiver, isActive |
| 관리자 | PUT | /api/v2/admin/gift-categories/{categoryId} |
선물 카테고리를 수정한다. | path: categoryId, body: classificationNumber, categoryCode, name, receiptCode, representativeItem, requiresDamageWaiver, isActive |
categoryId, classificationNumber, categoryCode, name, receiptCode, representativeItem, requiresDamageWaiver, isActive |
| 관리자 | DELETE | /api/v2/admin/gift-categories/{categoryId} |
선물 카테고리를 비활성화한다. | path: categoryId |
categoryId, classificationNumber, categoryCode, name, receiptCode, representativeItem, 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,classificationNumber,categoryCode,name,receiptCode,representativeItem,requiresDamageWaiver,isActiveAdminGiftListResponse:totalCount,items,page,size,hasNextAdminGiftListItemResponse:applicationNo,senderNickname,recipientNickname,sizeCode,sizeName,categoryName,classificationNumber,courierCompanyName,trackingNumber,status,statusName,availableActionsAdminGiftDetailResponse:applicationNo,senderInfo,recipientInfo,productInfo,inboundDeliveryInfo,status,statusName,availableActionsAdminGiftSizePriceResponse:sizeCode,name,basePriceCan,salePriceCan,isActiveAdminGiftOperationStatusResponse:applicationNo,status,statusName,occurredAt
관리자 취소는 기존 hasRole('ADMIN') 권한을 따르며 관리자가 발신자가 아니어도 RECEIVED에서 취소할 수 있다. 같은 트랜잭션에서 GiftDelivery.canceledAt을 기록하고 기존 refundGiftCan으로 발신자에게 원 결제액 전액을 리워드캔으로 환불한다. 존재하지 않는 신청번호나 다른 상태는 기존 공통 invalid request이며 기존 ApiResponse envelope를 사용한다. 기존 사용자 취소 응답은 별도 계약이며 바뀌지 않는다. HTTP 성공 코드만으로 업무 성공을 판단하지 않고 envelope의 success를 확인한다.
관리자 선물함의 action: RECEIVED는 목록과 상세 모두 availableActions=["CANCEL"]; 그 외 상태는 기존 매핑을 유지한다. 특히 INSPECTION_COMPLETED의 COMPLETE_DELIVERY는 수취인 정보 완비 조건부 노출이다.
기존 관리자 화면에 취소 기능만 추가하는 작업은 관리자 취소 기능 추가 프롬프트를 사용한다. 전체 화면 구현용 admin-page-prompts.md는 변경하지 않는다.
Kotlin DTO 형태
아래는 JSON으로 변환되기 직전의 API request/response data class 형태다. companion object 변환 함수는 클라이언트 입력/출력 형태와 직접 관련이 없어 생략했다. 응답 시각 필드는 UTC ISO 문자열이다. 예: 2026-09-30T12:00:00Z.
사용자 Request DTO
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
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
)
// name은 사용자 폼 옵션에서만 "분류명/대표품목" 형태로 조합된다.
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 counterpartMemberId: Long,
val counterpartNickname: String,
val counterpartProfileImageUrl: String,
val recipientAddressRegistered: Boolean,
val recipientAddressDeadlineAt: String?,
val canceledAt: String?,
val createdAt: String?
)
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: String?,
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: String?
)
data class GiftAddressResponse(
val name: String,
val phoneNumber: String,
val address: String
)
data class GiftMailboxResponse(
val name: String,
val address: String,
val phoneNumber: String
)
// mailbox는 direction=SENT이고 status가 RECEIVED, TRACKING_REGISTERED,
// ARRIVED_AT_MAILBOX, INSPECTION_COMPLETED일 때만 값이 있다.
// DELIVERED, UNDELIVERABLE, CANCELED 또는 direction=RECEIVED이면 null이다.
data class GiftDeliveryInfoResponse(
val canceledAt: String?,
val undeliverableAt: String?,
val undeliverableReason: String?
)
data class GiftStatusTimelineResponse(
val status: String,
val statusName: String,
val occurredAt: String?
)
data class GiftApplicationResponse(
val applicationNo: String,
val status: String,
val statusName: String,
val priceCan: Int,
val trackingDeadlineAt: String
)
data class GiftCancellationResponse(
val applicationNo: String,
val status: String,
val statusName: String,
val priceCan: Int,
val canceledAt: String
)
data class GiftTrackingRegistrationResponse(
val applicationNo: String,
val status: String,
val statusName: String,
val courierCompanyName: String,
val trackingNumber: String,
val trackingRegisteredAt: String,
val recipientAddressDeadlineAt: String
)
data class GiftRecipientAddressRegistrationResponse(
val applicationNo: String,
val status: String,
val statusName: String,
val recipientAddressRegisteredAt: String
)
data class GiftDeliveryConfirmationResponse(
val applicationNo: String,
val status: String,
val statusName: String,
val deliveredAt: String
)
data class GiftReviewResponse(
val reviewId: Long,
val applicationNo: String,
val rating: Int,
val keywords: List<String>,
val comment: String?,
val createdAt: String?
)
관리자 Request DTO
data class AdminGiftCategoryRequest(
val classificationNumber: String,
val categoryCode: String,
val name: String,
val receiptCode: String,
val representativeItem: 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
data class AdminGiftCategoryResponse(
val categoryId: Long,
val classificationNumber: String,
val categoryCode: String,
val name: String,
val receiptCode: String,
val representativeItem: String,
val requiresDamageWaiver: Boolean,
val isActive: Boolean
)
data class AdminGiftListResponse(
val totalCount: Long,
val items: List<AdminGiftListItemResponse>,
val page: Int,
val size: Int,
val hasNext: Boolean
)
data class AdminGiftListItemResponse(
val applicationNo: String,
val senderNickname: String,
val recipientNickname: String,
val sizeCode: String,
val sizeName: String,
val categoryName: String,
val classificationNumber: String,
val courierCompanyName: String?,
val trackingNumber: String?,
val status: String,
val statusName: String,
val availableActions: List<String>
)
data class AdminGiftDetailResponse(
val applicationNo: String,
val senderInfo: AdminGiftMemberInfoResponse,
val recipientInfo: AdminGiftMemberInfoResponse,
val productInfo: AdminGiftProductInfoResponse,
val inboundDeliveryInfo: AdminGiftInboundDeliveryInfoResponse,
val status: String,
val statusName: String,
val availableActions: List<String>
)
data class AdminGiftMemberInfoResponse(
val nickname: String,
val name: String,
val phoneNumber: String,
val address: String
)
data class AdminGiftProductInfoResponse(
val sizeCode: String,
val sizeName: String,
val categoryName: String,
val classificationNumber: String
)
data class AdminGiftInboundDeliveryInfoResponse(
val courierCompanyName: String?,
val trackingNumber: String?
)
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: String
)
클라이언트 구현 참고
- 선물 푸시 딥링크는
deepLinkValue=GIFT_DETAIL,deepLinkId=applicationNo를 사용한다. - 딥링크 진입 후에는
GET /api/v2/gifts/{applicationNo}로 상세를 조회해direction,trackingRequired,recipientAddressRequired를 기준으로 화면과 CTA를 결정한다. - 운송장 미등록 자동취소는 서버 스케줄러가 처리하며, 팬 클라이언트는 상세 조회의
status=CANCELED와 푸시를 기준으로 표시한다. - 모든 응답은 기존
ApiResponse.ok(...)envelope 안의data로 내려간다.