Files
sodalive-backend-spring-boot/docs/20260929_크리에이터_선물하기/prd.md
T

42 KiB

PRD: 크리에이터 선물하기

문서 정보

항목 내용
문서 상태 구현 중
작성일 2026-09-29
최종 수정일 2026-09-29
대상 제품 크리에이터 선물하기
작성자·결정권자 사용자
관련 API Contract 별도 문서 없음. 이 문서의 8. API 계약을 기준으로 사용
관련 구현 계획 docs/20260929_크리에이터_선물하기/plan-task.md
관련 review 없음

1. Overview

팬이 내부 재화인 캔을 사용해 크리에이터에게 실물 선물을 보낼 수 있는 API를 제공한다. 팬은 선물 신청, 운송장 등록, 취소, 상세 조회와 리뷰 작성을 할 수 있고, 크리에이터는 운송장 등록 이후 받은 선물을 확인하고 배송지를 입력한 뒤 수령 확인을 할 수 있다. 관리자는 선물 카테고리와 사이즈별 가격을 관리하고, 운영자는 사서함 도착, 검수, 전달 완료/불가 상태를 처리한다.

2. Problem Statement

  • 현재 팬이 크리에이터에게 실물 선물을 보내는 신청, 결제, 배송 상태, 검수 상태를 서버에서 일관되게 추적할 수 없다.
  • 선물 가격은 사이즈별로 다르고 운영자가 변경해야 하므로 코드 상수만으로는 운영 요구를 충족할 수 없다.
  • 운송장 미등록, 크리에이터 배송지 미입력, 검수 실패 같은 종료 상태와 환불/푸시 정책이 명확히 정의되어야 한다.
  • 팬과 크리에이터가 서로 다른 시점에 약관과 개인정보 처리에 동의해야 하므로 신청 건 기준의 동의 스냅샷이 필요하다.

문제를 해결했다는 판단은 선물 신청부터 전달 완료 또는 종료 상태까지 모든 상태 전이가 API/스케줄링/운영 API로 추적되고, 캔 사용내역과 푸시가 상태별 정책대로 처리되는 것으로 한다.

3. Goals

  • 팬이 선물 사이즈, 카테고리, 발신자 정보, 약관 동의를 입력해 선물 보내기를 신청할 수 있다.
  • 선물 신청 시 캔을 즉시 차감하고 내부적으로 선물 신청과 캔 사용내역을 연결한다.
  • 팬은 운송장 등록 전 선물 신청을 취소할 수 있고, 3일 내 운송장을 등록하지 않으면 자동취소 및 전액 환불된다.
  • 크리에이터는 팬이 운송장을 등록한 선물부터 받은 선물 목록과 상세를 조회하고 7일 이내 배송지와 수령 약관 동의를 등록할 수 있다.
  • 팬과 크리에이터는 같은 선물함 목록 API에서 보낸/받은 내역을 함께 또는 개별 조회할 수 있다.
  • 운영자는 상태별 전용 관리자 API로 사서함 도착, 검수완료, 전달불가, 전달완료를 처리할 수 있다.
  • 선물 카테고리와 사이즈별 가격은 관리자 API로 관리할 수 있다.
  • 상태별 팬/크리에이터 푸시 문구와 딥링크를 명확히 정의한다.

4. Non-Goals

  • 약관 본문 제공 API는 포함하지 않는다. 약관 내용은 별도 API 또는 콘텐츠로 제공한다.
  • 실물 택배사 API 연동, 송장 유효성 실시간 조회, 배송 추적 자동화는 포함하지 않는다.
  • 선물 이미지, 첨부파일, 메시지 카드는 포함하지 않는다.
  • 선물 카테고리 hard delete는 허용하지 않는다.
  • 선물 사이즈 정의 자체를 관리자에서 추가/삭제하는 기능은 포함하지 않는다.
  • 캔 사용내역 화면에서 사용자가 선물 신청 상세로 이동하는 기능은 포함하지 않는다.

5. Target Users and Permissions

사용자 목표 주요 작업 권한
팬 크리에이터에게 선물을 보낸다 사이즈/카테고리 조회, 신청, 운송장 등록, 취소, 보낸 내역 조회, 리뷰 작성 인증 회원
크리에이터 받은 선물을 확인하고 배송지를 입력한다 받은 내역 조회, 배송지 입력, 수령 확인 인증 회원 중 크리에이터
관리자 선물 운영 설정을 관리한다 카테고리, 사이즈 가격 관리 관리자
운영자 선물 검수/전달 상태를 처리한다 사서함 도착, 검수완료, 전달불가, 전달완료 처리 관리자 또는 운영 권한
스케줄러 기한 초과 건을 자동 처리한다 운송장 미등록 자동취소, 배송지 미입력 전달불가 시스템

기본 권한 정책은 기존 인증 필요 API와 관리자 API 정책을 따른다. 팬과 크리에이터는 본인과 관련된 선물만 조회/변경할 수 있다.

6. 핵심 정책

6.1 선물 상태

상태 코드 표시명 의미 종료 상태
RECEIVED 접수 완료 팬이 신청하고 캔 결제를 완료한 최초 상태 아니오
TRACKING_REGISTERED 발송 확인 팬이 택배사와 운송장 번호를 등록한 상태 아니오
ARRIVED_AT_MAILBOX 사서함 도착 운영자가 소다라이브 사서함 도착을 확인한 상태 아니오
INSPECTION_COMPLETED 검수완료 운영자가 선물 검수를 통과 처리한 상태 아니오
DELIVERED 전달완료 크리에이터 수령확인 또는 운영자 전달완료 처리 상태 예
UNDELIVERABLE 전달불가 검수 실패 또는 배송지 미입력 기한 초과로 전달할 수 없는 상태 예
CANCELED 신청 취소 운송장 등록 전 사용자 취소 또는 자동취소 상태 예

상태 전이는 다음을 기본으로 한다.

RECEIVED -> TRACKING_REGISTERED -> ARRIVED_AT_MAILBOX -> INSPECTION_COMPLETED -> DELIVERED
RECEIVED -> CANCELED
TRACKING_REGISTERED -> UNDELIVERABLE
ARRIVED_AT_MAILBOX -> UNDELIVERABLE
INSPECTION_COMPLETED -> UNDELIVERABLE

6.2 기한 정책

  • 팬은 선물 신청 후 3일 이내에 운송장 번호를 등록해야 한다.
  • 운송장 등록 기한 24시간 전 팬에게 안내 푸시를 보낸다.
  • 운송장 등록 기한을 넘긴 RECEIVED 선물은 스케줄러가 CANCELED로 변경하고 사용 캔을 전액 환불한다.
  • 크리에이터는 팬이 운송장을 등록한 뒤 7일 이내에 배송지를 입력해야 한다.
  • 배송지 입력 기한 24시간 전 크리에이터에게 안내 푸시를 보낸다.
  • 배송지 입력 기한을 넘긴 선물은 스케줄러가 UNDELIVERABLE로 변경하고 전달 불가 사유를 배송지 미입력 기한 초과로 저장한다.

6.3 신청번호 정책

  • 신청번호 applicationNo는 내부 기본키와 별도로 발급한다.
  • 형식은 G{yyyyMMdd}{dailySequence6}로 한다.
  • yyyyMMdd는 Asia/Seoul 기준 신청일이다.
  • dailySequence6는 해당 날짜 안에서 1부터 증가하는 6자리 숫자다.
  • 예시는 G20260929000001이다.
  • applicationNo는 unique이며 생성 후 변경하지 않는다.
  • 동시에 여러 선물 신청이 발생해도 같은 applicationNo가 발급되지 않아야 한다.
  • 구현은 DB unique 제약, 원자적 채번, 충돌 시 재시도 중 하나 이상의 방식으로 경합을 처리해야 한다.

6.4 선물 사이즈와 가격

선물 사이즈는 고정 정책이므로 코드 enum으로 관리한다.

코드 표시명 기준 초기 기본금액
SMALL 소형 가로+세로+높이 100cm 이하, 무게 5kg 이하 100캔
MEDIUM 중형 가로+세로+높이 120cm 이하, 무게 15kg 이하 150캔
LARGE 대형 가로+세로+높이 160cm 이하, 무게 20kg 이하 200캔

가격은 관리자 설정 DB에서 관리한다.

  • basePriceCan: 기본 금액. 클라이언트가 취소선 표시 등에 사용할 수 있다.
  • salePriceCan: 실제 표시/결제 금액. 할인 금액이 아니라 최종 결제 금액이다.
  • 할인 없음은 salePriceCan == basePriceCan으로 표현한다.
  • 선물 신청 시 실제 차감 금액은 선택한 사이즈 가격 설정의 salePriceCan이다.
  • 선물 신청 건에는 신청 시점의 최종 결제 금액인 salePriceCan만 스냅샷으로 저장한다.

6.5 선물 카테고리

카테고리는 관리자 페이지에서 등록, 수정, 논리 삭제할 수 있다.

필드 타입 제약 설명
categoryCode string 50자 이하, unique 내부 분류번호. 사용자에게 표시하지 않음
name string 50자 이하 사용자 표시명
requiresDamageWaiver boolean DDL은 tinyint(1) 파손면책 동의 필요 여부
isActive boolean DDL은 tinyint(1) 활성 여부

requiresDamageWaiver == true인 카테고리를 선택한 팬은 선물 신청 시 damageWaiverAgreed=true를 추가로 보내야 한다.

6.6 약관 동의

약관 본문은 이번 API 범위 밖이다. API는 동의 여부만 확인하고 선물 신청 건에 동의 스냅샷을 저장한다.

팬 신청 동의는 선물 보내기 등록 시점에 저장한다.

API 필드 표시명 저장 시각
senderTermsAgreed 상품 전달 이용약관 동의 senderTermsAgreedAt
senderPrivacyAgreed 상품 전달을 위한 개인정보 수집 및 이용 동의 senderPrivacyAgreedAt

크리에이터 수령 동의는 배송지 입력 시점에 저장한다.

API 필드 표시명 저장 시각
recipientTermsAgreed 상품 전달 이용약관 동의 recipientTermsAgreedAt
recipientPrivacyAgreed 상품 전달을 위한 개인정보 수집 및 이용 동의 recipientPrivacyAgreedAt

각 동의 필드가 true가 아니면 해당 API는 실패한다.

6.7 결제와 환불

  • 결제는 내부 재화인 캔으로 한다.
  • 캔 사용내역에 선물하기 용도를 추가한다.
  • 선물 보내기 등록 API 성공 시 즉시 salePriceCan만큼 캔을 차감한다.
  • 선물 신청과 캔 사용내역은 내부적으로 연결되어야 한다.
  • 사용자 캔 사용내역 화면에서 선물 신청으로 이동할 수 없어도 된다.
  • 운송장 등록 전 사용자 취소 또는 3일 자동취소 시 사용 캔을 전액 환불한다.
  • 운송장 등록 후에는 팬이 취소할 수 없다.

6.8 목록 노출

  • 선물함 목록 API는 type=ALL|SENT|RECEIVED를 지원한다.
  • 기본값은 ALL이다.
  • 응답 item에는 direction=SENT|RECEIVED를 포함한다.
  • 보낸 선물은 신청 직후부터 팬에게 노출한다.
  • 받은 선물은 팬이 운송장 번호를 등록해 상태가 TRACKING_REGISTERED 이상이 된 뒤 크리에이터에게 노출한다.
  • 배송지 미입력 상태라면 받은 선물 item/detail에 recipientAddressRequired=true, recipientAddressDeadlineAt을 내려준다.
  • 상세 조회는 팬과 크리에이터가 서로의 개인정보를 알 필요 없이 선물을 보내거나 받을 수 있게 설계한다.
  • 상세 조회에서 상대방 정보는 닉네임만 노출한다. 로그인 회원 본인이 신청 또는 입력한 개인정보는 본인에게만 노출한다.
  • 로그인 회원이 받는 크리에이터이면 보내는 팬의 닉네임만 알 수 있고, 팬이 신청 시 등록한 이름/휴대폰 번호/주소는 알 수 없다.
  • 로그인 회원이 보내는 팬이면 받는 크리에이터의 닉네임만 알 수 있고, 크리에이터가 입력한 이름/휴대폰 번호/주소는 알 수 없다.
  • 보내는 팬 상세에는 팬 본인이 신청 시 등록한 이름, 휴대폰 번호, 주소를 내려준다.
  • 받는 크리에이터 상세에는 크리에이터 본인이 입력한 받는 주소의 이름, 휴대폰 번호, 주소를 내려준다.
  • 주소 형식은 (우편번호) 주소, 상세주소 문자열로 내려준다.
  • 로그인 회원이 알아야 하는 데이터가 아니거나 아직 입력되지 않은 데이터는 null로 내려준다.
  • 클라이언트의 입력 필요 상태 판단은 null 값이 아니라 행동 필요 flag로 한다.
  • 상세 조회는 trackingRequired와 recipientAddressRequired를 내려준다.
  • trackingRequired=true는 direction=SENT인 팬이 아직 운송장 번호를 등록해야 하는 상태임을 의미한다.
  • recipientAddressRequired=true는 direction=RECEIVED인 크리에이터가 아직 받는 주소를 입력해야 하는 상태임을 의미한다.
  • 현재 로그인 회원이 처리할 일이 아니거나 이미 처리할 수 없는 상태이면 해당 flag는 false다.
  • 상세 조회는 정상 진행 상태 5개(RECEIVED, TRACKING_REGISTERED, ARRIVED_AT_MAILBOX, INSPECTION_COMPLETED, DELIVERED)의 날짜/시간을 순서대로 내려준다.
  • CANCELED, UNDELIVERABLE은 정상 진행 타임라인 상태가 아니며, 각각 별도 종료 정보로 canceledAt, undeliverableAt, undeliverableReason을 내려준다.

6.9 리뷰

  • 리뷰는 선물을 보낸 팬만 작성할 수 있다.
  • 리뷰는 DELIVERED 상태 이후 선물 건당 1회만 작성할 수 있다.
  • 별점은 1~5점이다.
  • 키워드는 여러 개 선택해 등록할 수 있다.
  • 각 키워드는 최대 255자다.
  • 추가의견은 최대 255자다.

6.10 푸시와 딥링크

  • 푸시 본문의 \n은 줄바꿈 문자로 처리한다.
  • 푸시 터치 시 선물 상세 페이지로 이동하도록 단일 딥링크를 작성한다.
  • 딥링크는 deepLinkValue=GIFT_DETAIL, deepLinkId=applicationNo를 사용한다.
  • 보낸 선물/받은 선물 판정은 딥링크가 아니라 선물 상세 조회 API가 로그인 회원 기준으로 수행한다.
  • 상세 조회 API는 로그인 회원이 senderMemberId이면 direction=SENT, recipientMemberId이고 받은 선물 노출 조건을 만족하면 direction=RECEIVED로 응답한다.
  • 둘 다 아니거나 받은 선물 노출 조건을 만족하지 않으면 권한 오류 또는 미존재 오류를 반환한다.

6.11 택배사 입력 정책

  • 현재 외부 택배 조회, PG 에스크로, 커머스 플랫폼 연동 계획은 없다.
  • 국가 단일 택배사 코드 표준이나 외부 provider별 코드를 이번 PRD의 기준으로 삼지 않는다.
  • 클라이언트는 직접 입력이 아니라 별도 Dropdown으로 택배사 선택지를 제공한다.
  • 운송장 등록 API는 선택된 택배사의 한글 표시명 courierCompanyName을 받는다.
  • 서버는 courierCompanyName을 표시/확인용 스냅샷으로 그대로 저장한다.
  • courierCompanyName은 필수이며 최대 50자로 제한한다.
  • 외부 배송조회/PG/커머스 연동이 후속 범위로 추가되면 그때 courierCompanyCode와 provider별 매핑 정책을 별도 PRD에서 정의한다.
  • 크리에이터가 수령확인 API로 DELIVERED 처리해도 팬에게는 전달 완료 푸시를 보낸다.
  • 크리에이터가 직접 수령확인한 경우 크리에이터에게는 배송 완료/전달완료 푸시를 보내지 않는다.

7. 기능 요구사항

ID 상태 요구사항 수용 기준 계획 연결
GIFT-001 확정 팬은 활성 카테고리와 사이즈별 가격을 조회할 수 있다 비활성 카테고리는 사용자 조회에 노출되지 않고, 사이즈 응답에는 basePriceCan, salePriceCan이 포함된다 P1
GIFT-002 확정 팬은 선물 보내기 등록 시 캔을 즉시 결제하고 고유 신청번호를 받는다 성공 시 상태는 RECEIVED, 캔 사용내역 선물하기가 생성되고 선물과 연결되며 동시 신청에도 중복 없는 applicationNo가 발급된다 P2
GIFT-003 확정 팬 신청 약관 2개와 조건부 파손면책 동의를 검증한다 필수 동의가 true가 아니면 신청이 실패한다 P2
GIFT-004 확정 팬은 운송장 등록 전 신청을 취소할 수 있다 RECEIVED 상태에서만 취소되고 전액 환불된다 P2
GIFT-005 확정 팬은 3일 내 운송장을 등록해야 한다 등록 성공 시 상태는 TRACKING_REGISTERED, 크리에이터 받은 목록에 노출된다 P3
GIFT-006 확정 스케줄러는 운송장 미등록 건을 자동취소한다 기한 초과 RECEIVED가 CANCELED로 바뀌고 전액 환불된다 P4
GIFT-007 확정 크리에이터는 운송장 등록 후 받은 선물을 조회하고 배송지를 입력한다 배송지 입력 시 수령자 정보와 수령 약관 2개를 저장한다 P3
GIFT-008 확정 스케줄러는 배송지 미입력 기한 초과 건을 전달불가 처리한다 기한 초과 건이 UNDELIVERABLE로 바뀌고 사유가 저장된다 P4
GIFT-009 확정 운영자는 상태별 관리자 API로 선물 상태를 처리한다 잘못된 이전 상태에서 상태변경 API를 호출하면 실패한다 P5
GIFT-010 확정 팬은 DELIVERED 이후 리뷰를 1회 작성할 수 있다 중복 리뷰, 비전달완료 리뷰, 수신자 리뷰는 실패한다 P6
GIFT-011 확정 팬/크리에이터 대상 푸시를 상태별로 분리 발송한다 각 상태 전이에서 지정된 수신자에게만 지정 문구와 딥링크가 발송된다 P7
GIFT-012 확정 관리자는 카테고리를 등록/수정/논리삭제할 수 있다 삭제는 isActive=false만 수행한다 P1
GIFT-013 확정 관리자는 사이즈별 가격을 수정할 수 있다 새 신청에는 변경 가격이 적용되고 기존 신청 스냅샷은 바뀌지 않는다 P1

8. API 계약

8.1 공통 규칙

  • 인증: 기존 인증 필요 API와 동일한 인증 헤더를 사용한다.
  • 관리자 API: 기존 관리자 인증/권한 정책을 따른다.
  • 날짜/시간 응답: UTC ISO-8601 문자열을 사용한다.
  • 페이징: page 기본값 0, size 기본값 20, 허용 범위 20..50으로 보정한다.
  • 목록 응답은 items, page, size, hasNext를 포함한다.
  • 오류 envelope와 메시지 키는 기존 공통 오류 정책을 따른다.

8.2 선물 신청 폼 옵션 조회

GET /api/v2/gifts/form-options

선물 신청 페이지 진입 시 필요한 팬용 사이즈와 카테고리 옵션을 한 번에 조회한다.

Response:

{
  "sizes": [
    {
      "sizeCode": "SMALL",
      "name": "소형",
      "maxTotalLengthCm": 100,
      "maxWeightKg": 5,
      "basePriceCan": 100,
      "salePriceCan": 80
    }
  ],
  "categories": [
    {
      "categoryId": 1,
      "name": "인형",
      "requiresDamageWaiver": true
    }
  ]
}

사용자 API는 categoryCode를 응답하지 않고 활성 카테고리만 내려준다.

8.3 선물 보내기 등록

POST /api/v2/gifts

Request:

{
  "recipientMemberId": 100,
  "senderName": "홍길동",
  "senderPhoneNumber": "01012345678",
  "senderZipCode": "06234",
  "senderAddress": "서울시 강남구 ...",
  "senderAddressDetail": "101동 1001호",
  "sizeCode": "SMALL",
  "categoryId": 1,
  "senderTermsAgreed": true,
  "senderPrivacyAgreed": true,
  "damageWaiverAgreed": true
}

Response:

{
  "applicationNo": "G20260929000001",
  "status": "RECEIVED",
  "statusName": "접수 완료",
  "priceCan": 80,
  "trackingDeadlineAt": "2026-10-02T03:00:00Z"
}

요구사항:

  • 수신자는 크리에이터 회원이어야 한다.
  • 발신자 이름, 휴대폰 번호, 우편번호, 주소는 필수다.
  • 선택한 카테고리가 비활성이면 실패한다.
  • 선택한 카테고리가 requiresDamageWaiver=true이면 damageWaiverAgreed=true가 필수다.
  • senderTermsAgreed와 senderPrivacyAgreed가 모두 true여야 한다.
  • 성공 시 salePriceCan만큼 캔을 차감한다.

8.4 선물함 목록 조회

GET /api/v2/gifts?type=ALL&page=0&size=20

Request Query:

이름 필수 기본값 설명
type 아니오 ALL ALL, SENT, RECEIVED
page 아니오 0 0 기반 page index
size 아니오 20 20..50 보정

Response:

{
  "items": [
    {
      "applicationNo": "G20260929000001",
      "direction": "SENT",
      "status": "TRACKING_REGISTERED",
      "statusName": "발송 확인",
      "counterpartMemberId": 100,
      "counterpartName": "크리에이터닉네임",
      "sizeName": "소형",
      "categoryName": "인형",
      "priceCan": 80,
      "recipientAddressRequired": false,
      "recipientAddressDeadlineAt": null,
      "createdAt": "2026-09-29T03:00:00Z"
    }
  ],
  "page": 0,
  "size": 20,
  "hasNext": false
}

받은 선물은 TRACKING_REGISTERED 이상 상태부터 노출한다.

8.5 선물 상세 조회

GET /api/v2/gifts/{applicationNo}

Response:

{
  "applicationNo": "G20260929000001",
  "direction": "SENT",
  "status": "TRACKING_REGISTERED",
  "statusName": "발송 확인",
  "giftInfo": {
    "recipientCreatorNickname": "크리에이터닉네임",
    "senderNickname": null,
    "sizeName": "소형",
    "categoryName": "인형",
    "applicationNo": "G20260929000001",
    "paidCan": 80,
    "tracking": "CJ대한통운_1234567890",
    "shippingRequestedAt": null
  },
  "senderInfo": {
    "name": "홍길동",
    "phoneNumber": "01012345678",
    "address": "(06234) 서울시 강남구 ..., 101동 1001호"
  },
  "recipientAddress": null,
  "mailbox": {
    "name": "소다라이브 사서함",
    "address": "구현 후 제공",
    "phoneNumber": "구현 후 제공"
  },
  "trackingRequired": false,
  "recipientAddressRequired": false,
  "recipientAddressDeadlineAt": "2026-10-09T03:00:00Z",
  "delivery": {
    "canceledAt": null,
    "undeliverableAt": null,
    "undeliverableReason": null
  },
  "statusTimeline": [
    {
      "status": "RECEIVED",
      "statusName": "접수 완료",
      "occurredAt": "2026-09-29T03:00:00Z"
    },
    {
      "status": "TRACKING_REGISTERED",
      "statusName": "발송 확인",
      "occurredAt": "2026-10-02T03:00:00Z"
    },
    {
      "status": "ARRIVED_AT_MAILBOX",
      "statusName": "사서함 도착",
      "occurredAt": null
    },
    {
      "status": "INSPECTION_COMPLETED",
      "statusName": "검수완료",
      "occurredAt": null
    },
    {
      "status": "DELIVERED",
      "statusName": "전달완료",
      "occurredAt": null
    }
  ],
  "review": null
}

받는 크리에이터 관점 응답 예시는 다음과 같다.

{
  "applicationNo": "G20260929000001",
  "direction": "RECEIVED",
  "status": "INSPECTION_COMPLETED",
  "statusName": "검수완료",
  "giftInfo": {
    "recipientCreatorNickname": null,
    "senderNickname": "팬닉네임",
    "sizeName": "소형",
    "categoryName": "인형",
    "applicationNo": null,
    "paidCan": null,
    "tracking": null,
    "shippingRequestedAt": "2026-10-03T03:00:00Z"
  },
  "senderInfo": null,
  "recipientAddress": {
    "name": "김소다",
    "phoneNumber": "01098765432",
    "address": "(04524) 서울시 중구 ..., 202호"
  },
  "mailbox": null,
  "trackingRequired": false,
  "recipientAddressRequired": false,
  "recipientAddressDeadlineAt": "2026-10-09T03:00:00Z",
  "delivery": {
    "canceledAt": null,
    "undeliverableAt": null,
    "undeliverableReason": null
  },
  "statusTimeline": [
    {
      "status": "RECEIVED",
      "statusName": "접수 완료",
      "occurredAt": "2026-09-29T03:00:00Z"
    },
    {
      "status": "TRACKING_REGISTERED",
      "statusName": "발송 확인",
      "occurredAt": "2026-10-02T03:00:00Z"
    },
    {
      "status": "ARRIVED_AT_MAILBOX",
      "statusName": "사서함 도착",
      "occurredAt": "2026-10-03T03:00:00Z"
    },
    {
      "status": "INSPECTION_COMPLETED",
      "statusName": "검수완료",
      "occurredAt": "2026-10-04T03:00:00Z"
    },
    {
      "status": "DELIVERED",
      "statusName": "전달완료",
      "occurredAt": null
    }
  ],
  "review": null
}

팬과 크리에이터 모두 같은 상세 API를 사용하되 권한에 따라 본인 관련 선물만 조회할 수 있다. direction=SENT이면 보내는 팬 관점의 상세 응답이다. giftInfo.recipientCreatorNickname에는 받는 크리에이터 닉네임을 내려주고, giftInfo.senderNickname, giftInfo.shippingRequestedAt, recipientAddress는 null이다. senderInfo에는 팬 본인이 신청 시 등록한 이름, 휴대폰 번호, 주소를 내려준다. mailbox는 코드상에서 정한 사서함 이름, 주소, 연락처를 내려준다. 구현 후 값이 제공되면 이 문서를 갱신한다. direction=SENT이고 현재 상태가 RECEIVED이면 trackingRequired=true이며, 팬 클라이언트는 운송장 등록 CTA를 표시한다. direction=RECEIVED이면 받는 크리에이터 관점의 상세 응답이다. giftInfo.senderNickname에는 발송인인 팬 닉네임을 내려주고, giftInfo.recipientCreatorNickname, giftInfo.applicationNo, giftInfo.paidCan, giftInfo.tracking, senderInfo, mailbox는 null이다. giftInfo.shippingRequestedAt에는 배송신청일을 내려준다. recipientAddress에는 크리에이터 본인이 입력한 이름, 휴대폰 번호, 주소를 내려준다. direction=RECEIVED이고 받은 주소가 미입력이며 종료 상태가 아니면 recipientAddressRequired=true이며, 크리에이터 클라이언트는 받는 주소 입력 CTA를 표시한다. 주소는 발신자/수신자 모두 (우편번호) 주소, 상세주소 형식의 단일 문자열로 내려준다. 운송장 미입력, 받는 주소 미입력 상태의 상세 보완은 API 개발 후 후속으로 처리한다. 로그인 회원이 알아야 하는 데이터가 아니면 null로 내려준다. 정상 진행 5개 상태의 statusTimeline은 항상 같은 순서로 내려주며, 아직 도달하지 않은 상태의 occurredAt은 null이다. CANCELED와 UNDELIVERABLE 상태에서도 statusTimeline은 도달한 정상 진행 상태의 시각을 유지하고, 종료 시각/사유는 delivery.canceledAt, delivery.undeliverableAt, delivery.undeliverableReason으로 확인한다.

8.6 운송장 번호 등록

POST /api/v2/gifts/{applicationNo}/tracking

Request:

{
  "courierCompanyName": "CJ대한통운",
  "trackingNumber": "1234567890"
}

Response:

{
  "applicationNo": "G20260929000001",
  "status": "TRACKING_REGISTERED",
  "recipientAddressDeadlineAt": "2026-10-09T03:00:00Z"
}

요구사항:

  • 보낸 팬만 호출할 수 있다.
  • RECEIVED 상태에서만 성공한다.
  • 성공 시 크리에이터 받은 선물 목록에 노출된다.
  • 성공 시 크리에이터에게 팬 운송장 등록 완료 푸시를 보낸다.

8.7 선물 보내기 취소

POST /api/v2/gifts/{applicationNo}/cancel

Request: 없음

Response:

{
  "applicationNo": "G20260929000001",
  "status": "CANCELED",
  "refundedCan": 80
}

요구사항:

  • 보낸 팬만 호출할 수 있다.
  • RECEIVED 상태에서만 성공한다.
  • 성공 시 사용 캔을 전액 환불한다.

8.8 크리에이터 배송지 입력

POST /api/v2/gifts/{applicationNo}/recipient-address

Request:

{
  "recipientName": "김소다",
  "recipientPhoneNumber": "01098765432",
  "recipientZipCode": "04524",
  "recipientAddress": "서울시 중구 ...",
  "recipientAddressDetail": "202호",
  "recipientTermsAgreed": true,
  "recipientPrivacyAgreed": true
}

Response:

{
  "applicationNo": "G20260929000001",
  "recipientAddressRegisteredAt": "2026-10-03T03:00:00Z"
}

요구사항:

  • 받는 크리에이터만 호출할 수 있다.
  • TRACKING_REGISTERED 이상, 종료 상태가 아닌 선물에만 입력할 수 있다.
  • 이름, 휴대폰 번호, 우편번호, 주소는 필수다.
  • recipientTermsAgreed, recipientPrivacyAgreed가 모두 true여야 한다.

8.9 크리에이터 수령확인

POST /api/v2/gifts/{applicationNo}/delivery-complete

Request: 없음

Response:

{
  "applicationNo": "G20260929000001",
  "status": "DELIVERED",
  "deliveredAt": "2026-10-10T03:00:00Z"
}

요구사항:

  • 받는 크리에이터만 호출할 수 있다.
  • INSPECTION_COMPLETED 상태에서만 성공한다.
  • 성공 시 팬에게 전달 완료 푸시를 보낸다.
  • 성공 시 크리에이터에게는 배송 완료/전달완료 푸시를 보내지 않는다.

8.10 리뷰 작성

POST /api/v2/gifts/{applicationNo}/review

Request:

{
  "rating": 5,
  "keywords": ["배송이 빨라요", "안내가 친절해요"],
  "comment": "선물 전달 과정이 만족스러웠습니다."
}

Response:

{
  "reviewId": 1,
  "applicationNo": "G20260929000001",
  "rating": 5,
  "keywords": ["배송이 빨라요", "안내가 친절해요"],
  "comment": "선물 전달 과정이 만족스러웠습니다.",
  "createdAt": "2026-10-11T03:00:00Z"
}

요구사항:

  • 보낸 팬만 호출할 수 있다.
  • DELIVERED 상태에서만 작성할 수 있다.
  • 선물 건당 1회만 작성할 수 있다.
  • rating은 1 이상 5 이하여야 한다.
  • keywords는 배열이며, 각 keyword는 255자 이하여야 한다.
  • comment는 255자 이하여야 한다.

8.11 관리자 카테고리 API

Method Path 설명
GET /api/v2/admin/gift-categories 카테고리 목록 조회
POST /api/v2/admin/gift-categories 카테고리 등록
PUT /api/v2/admin/gift-categories/{categoryId} 카테고리 수정
DELETE /api/v2/admin/gift-categories/{categoryId} 카테고리 논리 삭제

등록/수정 Request:

{
  "categoryCode": "DOLL",
  "name": "인형",
  "requiresDamageWaiver": true,
  "isActive": true
}

삭제는 isActive=false로만 처리한다.

8.12 관리자 사이즈 가격 API

Method Path 설명
GET /api/v2/admin/gift-size-prices 사이즈별 가격 목록 조회
PUT /api/v2/admin/gift-size-prices/{sizeCode} 사이즈별 가격 수정

수정 Request:

{
  "basePriceCan": 100,
  "salePriceCan": 80,
  "isActive": true
}

salePriceCan은 0보다 커야 하고 basePriceCan보다 클 수 없다.

8.13 운영 상태변경 API

Method Path 이전 상태 변경 상태 필수 입력
POST /api/v2/admin/gifts/{applicationNo}/arrive-mailbox TRACKING_REGISTERED ARRIVED_AT_MAILBOX 없음
POST /api/v2/admin/gifts/{applicationNo}/complete-inspection ARRIVED_AT_MAILBOX INSPECTION_COMPLETED 없음
POST /api/v2/admin/gifts/{applicationNo}/mark-undeliverable TRACKING_REGISTERED, ARRIVED_AT_MAILBOX, INSPECTION_COMPLETED UNDELIVERABLE reason
POST /api/v2/admin/gifts/{applicationNo}/complete-delivery INSPECTION_COMPLETED DELIVERED 없음

전달불가 Request:

{
  "reason": "선물 접수 규격에 맞지 않습니다."
}

8.14 자동 스케줄링 작업

작업 대상 처리 푸시
운송장 등록 기한 24시간 전 안내 RECEIVED, 마감 24시간 전 상태 변경 없음 팬에게 안내
운송장 미등록 자동취소 RECEIVED, 신청 후 3일 초과 CANCELED, 전액 환불 팬에게 자동취소 안내
배송지 입력 기한 24시간 전 안내 TRACKING_REGISTERED 이상, 배송지 미입력, 마감 24시간 전 상태 변경 없음 크리에이터에게 안내
배송지 미입력 전달불가 배송지 미입력, 운송장 등록 후 7일 초과 UNDELIVERABLE, 사유 저장 팬에게 전달불가 안내. 크리에이터에게 추가 푸시 없음

스케줄러는 중복 실행되어도 같은 선물에 중복 환불 또는 중복 푸시가 발생하지 않아야 한다.

9. 푸시 알림 정책

9.1 팬 대상 푸시

트리거 제목 본문 딥링크
선물 신청 후 자동 취소까지 24시간 남은 경우 운송장 번호를 입력해 주세요 24시간 이내에 운송장 번호를 등록해 주세요.\n기한 내 등록하지 않으면 신청이 자동 취소됩니다. GIFT_DETAIL, applicationNo
선물 자동 취소 선물 신청이 자동 취소됐어요 신청 후 3일 이내에 운송장 번호가 등록되지 않아 신청이 취소됐어요.\n사용한 캔은 모두 환불되었습니다. GIFT_DETAIL, applicationNo
사서함 도착 선물이 사서함에 도착했어요 보내주신 선물이 소다라이브 사서함에 도착했어요.\n안전하게 전달할 수 있도록 선물을 확인하고 있어요. GIFT_DETAIL, applicationNo
전달 완료 선물이 전달됐어요 보내주신 선물이 크리에이터에게 안전하게 전달됐어요. GIFT_DETAIL, applicationNo
전달 불가 선물을 전달할 수 없어요 도착한 선물이 선물 접수 규격에 맞지 않아 크리에이터에게 전달할 수 없어요.\n자세한 사유는 신청 상세에서 확인해 주세요. GIFT_DETAIL, applicationNo

9.2 크리에이터 대상 푸시

트리거 제목 본문 딥링크
팬 운송장 등록 완료 팬이 보낸 선물이 있어요! 7일 이내에 배송지를 입력해 주세요.\n기간이 지나면 선물을 받을 수 없어요 GIFT_DETAIL, applicationNo
배송지 입력 기한 24시간 전 배송지 입력 기한이 1일 남았어요 24시간 안에 배송지를 입력해 주세요.\n입력하지 않으면 선물은 반송되지 않고 폐기돼요 GIFT_DETAIL, applicationNo
사서함 도착 · 검수 중 선물이 사서함에 도착했어요 팬이 보낸 선물을 검수하고 있어요.\n검수가 끝나면 다시 알려드릴게요 GIFT_DETAIL, applicationNo
검수 완료 (통과) 선물 검수가 완료됐어요 확인된 선물을 빠른 시일 내에 입력하신 배송지로 보내드릴게요. GIFT_DETAIL, applicationNo
검수 완료 (전달 불가) 전달할 수 없는 선물이 있어요 검수 결과 선물 정책에 맞지 않아 해당 선물은 배송되지 않아요.\n자세한 사유를 확인해 주세요. GIFT_DETAIL, applicationNo
배송 완료 선물이 도착했어요! 팬이 보낸 선물이 배송지에 도착했어요.\n지금 확인해 보세요. GIFT_DETAIL, applicationNo

크리에이터가 직접 수령확인 API를 호출해 DELIVERED가 된 경우에는 크리에이터 대상 배송 완료 푸시를 보내지 않는다.

10. 데이터 모델 요구사항

10.1 Gift

필드 설명
id 내부 기본키
applicationNo 신청번호. 기본키와 별도이며 사용자/운영 식별자로 사용
senderMemberId 보내는 팬 회원번호
recipientMemberId 받는 크리에이터 회원번호
status 선물 상태
sizeCode 신청 시 선택한 사이즈 코드
categoryId 신청 시 선택한 카테고리 ID
categoryNameSnapshot 신청 시 카테고리명 스냅샷
salePriceCan 신청 시 실제 결제 금액 스냅샷
canUsageId 캔 사용내역 연결 ID
senderTermsAgreed, senderPrivacyAgreed 팬 동의 여부
senderTermsAgreedAt, senderPrivacyAgreedAt 팬 동의 시각
recipientTermsAgreed, recipientPrivacyAgreed 크리에이터 동의 여부
recipientTermsAgreedAt, recipientPrivacyAgreedAt 크리에이터 동의 시각
damageWaiverAgreed 팬 파손면책 동의 여부
createdAt, updatedAt 생성/수정 시각

10.2 GiftDelivery

필드 설명
giftId 선물 ID
senderName, senderPhoneNumber 발신자 이름/휴대폰 번호
senderZipCode, senderAddress, senderAddressDetail 발신자 주소
recipientName, recipientPhoneNumber 수신자 이름/휴대폰 번호
recipientZipCode, recipientAddress, recipientAddressDetail 수신자 주소
courierCompanyName, trackingNumber 택배사 한글 표시명/운송장 번호
trackingDeadlineAt 운송장 등록 기한
recipientAddressDeadlineAt 크리에이터 배송지 입력 기한
trackingRegisteredAt 발송 확인 시각
arrivedAtMailboxAt 사서함 도착 시각
inspectionCompletedAt 검수완료 시각
deliveredAt 전달완료 시각
canceledAt 신청 취소 시각
undeliverableAt 전달불가 시각
undeliverableReason 전달불가 사유

10.3 GiftCategory

필드 설명
id 내부 기본키
categoryCode 50자 이하 내부 분류번호
name 50자 이하 카테고리명
requiresDamageWaiver 파손면책 동의 필요 여부. DDL은 tinyint(1)
isActive 활성 여부. DDL은 tinyint(1)
createdAt, updatedAt 생성/수정 시각

10.4 GiftSizePrice

필드 설명
id 내부 기본키
sizeCode SMALL, MEDIUM, LARGE
basePriceCan 기본 금액
salePriceCan 실제 표시/결제 금액
isActive 활성 여부. DDL은 tinyint(1)
createdAt, updatedAt 생성/수정 시각

10.5 GiftReview

필드 설명
id 내부 기본키
giftId 선물 ID. 선물당 1개 unique
senderMemberId 리뷰 작성 팬 회원번호
rating 1~5점
keywords 선택한 리뷰 키워드 목록. 각 키워드는 최대 255자
comment 최대 255자
createdAt, updatedAt 생성/수정 시각

11. 보안과 데이터 취급

  • 발신자/수신자 이름, 휴대폰 번호, 주소, 우편번호는 개인정보로 취급한다.
  • 운영 로그, 오류 로그, 푸시 본문에는 주소 전체와 전화번호 전체를 남기지 않는다.
  • 상세 조회는 본인 관련 선물 또는 관리자/운영 권한에서만 허용한다.
  • 상세 조회에서 상대방의 이름, 휴대폰 번호, 주소, 우편번호, 상세주소는 노출하지 않고 상대방 닉네임만 노출한다.
  • 팬 상세에서는 팬 본인이 신청 시 등록한 발신자 정보와 사서함 정보만 노출하고, 크리에이터 배송지는 노출하지 않는다.
  • 크리에이터 상세에서는 크리에이터 본인이 입력한 받는 주소만 노출하고, 팬이 신청 시 등록한 발신자 정보는 노출하지 않는다.
  • 내부 운영 화면에서는 필요한 권한을 가진 사용자에게만 개인정보를 노출한다.
  • 약관 동의 여부와 동의시각은 선물 신청 건 감사 근거로 보존한다.

12. 테스트와 품질 요구사항

  • 선물 신청은 캔 부족, 비활성 카테고리, 필수 약관 미동의, 파손면책 미동의, 최종 결제 금액 스냅샷을 테스트한다.
  • 취소/자동취소는 RECEIVED에서만 가능하고 환불이 1회만 발생하는지 테스트한다.
  • 운송장 등록은 받은 선물 목록 노출과 크리에이터 배송지 입력 기한 생성을 테스트한다.
  • 배송지 입력은 크리에이터 권한, 필수 배송지, 수령 약관 2개를 테스트한다.
  • 상태별 운영 API는 허용 이전 상태와 금지 이전 상태를 모두 테스트한다.
  • 리뷰는 작성 권한, 상태, 중복 작성, 글자수 제한을 테스트한다.
  • 푸시는 팬/크리에이터 수신자 분리와 크리에이터 수령확인 시 크리에이터 푸시 억제를 테스트한다.
  • 스케줄러는 중복 실행 시 중복 환불/중복 푸시가 없는지 테스트한다.

13. 성공 기준

  • 팬이 선물 신청 시 약관/파손면책/캔 결제를 포함해 RECEIVED 선물을 만들 수 있다.
  • 팬이 운송장 등록 전 직접 취소하거나 스케줄러가 자동취소하면 사용 캔이 전액 환불된다.
  • 운송장 등록 후 크리에이터 받은 선물 목록에 노출되고 배송지 입력 CTA에 필요한 기한 정보가 응답된다.
  • 크리에이터는 배송지와 수령 약관 2개를 등록할 수 있다.
  • 운영자는 상태별 전용 API로 상태를 전이하고, 잘못된 상태 전이는 실패한다.
  • 팬은 DELIVERED 이후 리뷰를 1회 작성할 수 있다.
  • 관리자 카테고리 논리삭제와 사이즈별 가격 변경이 새 신청에만 반영된다.
  • 팬/크리에이터 푸시가 지정된 문구, 수신자, 딥링크로 발송된다.

14. Open Questions

없음. 구현 중 관리자 권한명의 정확한 값이 필요하면 기존 코드 패턴 확인 후 이 PRD를 먼저 갱신한다.

15. 요구사항 추적표

요구사항 계획 Phase 자동 검증 수동 검증
GIFT-001, GIFT-012, GIFT-013 1 관리자 설정 API 테스트 관리자 설정 조회/수정 확인
GIFT-002~005 2 신청/취소/운송장 API 테스트 팬 선물 신청 흐름 확인
GIFT-007~008 3 받은 목록/배송지/스케줄러 테스트 크리에이터 받은 선물 흐름 확인
GIFT-009 4 운영 상태변경 API 테스트 운영 상태 전이 확인
GIFT-010 5 리뷰 API 테스트 전달완료 후 리뷰 작성 확인
GIFT-011 6 푸시 이벤트 테스트 딥링크와 수신자 확인

16. Decision Log

날짜 ID 상태 결정 근거 영향 요구사항
2026-09-29 DEC-001 확정 1차 PRD는 사용자 API, 관리자 설정/API, 운영 상태변경, 자동 스케줄링, 푸시까지 포함한다 사용자 선택 A 전체
2026-09-29 DEC-002 확정 크리에이터 배송지는 팬 운송장 등록 후 크리에이터가 7일 이내 입력한다 사용자 선택 A GIFT-005, GIFT-007, GIFT-008
2026-09-29 DEC-003 확정 팬 약관 동의는 별도 회원 약관 이력이 아니라 선물 신청 건에 스냅샷 저장한다 사용자 선택 A GIFT-002, GIFT-003
2026-09-29 DEC-004 확정 선물 신청 시 캔을 즉시 차감하고 취소/자동취소 시 전액 환불한다 사용자 선택 A GIFT-002, GIFT-004, GIFT-006
2026-09-29 DEC-005 확정 리뷰는 보낸 팬이 DELIVERED 이후 선물 건당 1회 작성한다 사용자 선택 B GIFT-010
2026-09-29 DEC-006 확정 크리에이터 수령확인 API는 DELIVERED로 변경하고 팬에게 전달 완료 푸시를 보낸다 사용자 정정 GIFT-009, GIFT-011
2026-09-29 DEC-007 확정 배송지 입력 기한 초과는 UNDELIVERABLE, 사유 배송지 미입력 기한 초과로 처리한다 사용자 선택 A GIFT-008
2026-09-29 DEC-008 확정 선물함 목록은 `type=ALL SENT RECEIVED` 단일 API로 제공한다
2026-09-29 DEC-009 확정 팬 발신자 이름/휴대폰 번호/우편번호/주소를 모두 필수로 받는다 사용자 선택 A GIFT-002
2026-09-29 DEC-010 확정 가격은 basePriceCan과 실제 결제 금액인 salePriceCan으로 표현한다 사용자 정정 GIFT-001, GIFT-002, GIFT-013
2026-09-29 DEC-011 확정 크리에이터 배송지 입력 시에도 약관 2개 동의와 동의시각을 별도 저장한다 사용자 정정 GIFT-007
2026-09-29 DEC-012 확정 파손면책 동의 필요 카테고리는 팬 신청 시 damageWaiverAgreed=true를 요구한다 사용자 선택 A GIFT-003