Files

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, isActive
  • AdminGiftListResponse: totalCount, items, page, size, hasNext
  • AdminGiftListItemResponse: applicationNo, senderNickname, recipientNickname, sizeCode, sizeName, categoryName, classificationNumber, courierCompanyName, trackingNumber, status, statusName, availableActions
  • AdminGiftDetailResponse: applicationNo, senderInfo, recipientInfo, productInfo, inboundDeliveryInfo, status, statusName, availableActions
  • AdminGiftSizePriceResponse: sizeCode, name, basePriceCan, salePriceCan, isActive
  • AdminGiftOperationStatusResponse: 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로 내려간다.