NFC 카드 API 맥락
스펙 원본은 frontmatter
spec:. 구현 전체 계약은 as-built 통합 구현 명세 §4와 self-claim 명세 — self-claim(2026-07-23, V9)이 as-built(07-22)보다 최신이므로 resolve 상태는 self-claim 명세가 기준. 이 문서는 왜 그렇게 정했는지와 연동 시 밟기 쉬운 것만.
한눈에
| 엔드포인트 | 역할 한 줄 |
|---|---|
GET /v1/public/nfc-tags/{token} | 태그 → 4상태(CONNECTED/CLAIMABLE/NEEDS_CONNECT/UNAVAILABLE) 판정 |
POST /v1/me/nfc-cards/connect | 연결. 미배정 개방 카드면 선점+연결을 원자적으로 수행 |
POST /v1/me/nfc-cards/{cardId}/report-lost · /recover | 분실 모드 ON/OFF |
POST /v1/admin/nfc-cards · /{cardId}/assign · /self-claim | 발급(선점 개방 플래그), 사전 배정, 발급 후 개방 전환 |
왜 이렇게 생겼나
- 원시 상태를 소수 상태로 접는다. 판정은 BE
NfcTagResolutionPolicy한 곳 — FE는 result별 분기만. 카드 없음·비활성·분실·폐기·명함 비공개는 전부UNAVAILABLE + reason. - 소유(claim)와 연결(connect)은 다른 개념. claim =
assigned_member_id(소유권), connect =business_card_id(명함 링크). 관리자assign은 claim만 해서NEEDS_CONNECT의 원천이 되고, self-claim 경로는 “회원은 명함을 정확히 1개 갖는다” 불변식 덕에 claim+connect가 한 요청에서 원자적으로 함께 일어난다. CLAIMABLE은 opt-in. 미배정 재고가 무조건 선점 가능하면 법인용 예약 재고를 제3자가 가로챈다 → 카드별self_claim_enabled(기본false)로 공개 배포용만 개방. 예약 재고는 계속UNAVAILABLE + NOT_ISSUED.- NFC 상태와 명함 공개 상태는 분리. 카드 분실·폐기여도 명함
PUBLIC이면/card/{slug}직접 URL은 열린다. NFC는 유입 경로지 접근 권한이 아니다.
불변조건
- result별 필수 필드:
CONNECTED.slug/CLAIMABLE.token/NEEDS_CONNECT.token/UNAVAILABLE.reason— FE가 런타임 검증, 위반은 재시도 오류 처리. reason∈NOT_FOUND | NOT_ISSUED | INACTIVE | LOST | REVOKED | NOT_PUBLISHED.- 저장소 timeout·내부 오류를
UNAVAILABLE로 변환하지 않는다 — 실제 5xx를 반환해야 FE가 재시도 화면과 사용 불가 안내를 구분. - first-tap-wins는 조건부 UPDATE로만:
UPDATE ... WHERE assigned_member_id IS NULL AND self_claim_enabled = true, 영향 행 0 = 경합 패배. read-then-write 분기 금지(경합 창 생김). 같은 회원 재시도는 멱등(같은CONNECTED응답). - 소유권 경합·타인 소유는 전부
NF0004로 수렴(403 타인 소유, 409 경합 패배 — code 동일, status만 다름). - connect 성공 시 미발행 명함 자동 발행 — 셀프 선점한 신규 회원이 방문자에게
NOT_PUBLISHED로 막히지 않게. 이미 발행된 명함의 발행 시각은 유지(의도적 비공개 존중). - NFC token은 로그에 원문으로 남기지 않는다(마스킹).
엣지케이스 · gotcha
visitId멱등성: React Query 재시도가NFC_TAP을 중복 집계하던 문제 → FE는sessionStorage의analyticsNfcVisitId:{token}값을 모든 재시도에 동일하게 전송, BE는nfc:{visitId}키로 중복 제거. 기록 조건 3개 전부 필요:CONNECTED+businessCardId확인 +visitId가 UUID/ULID 형식.visitId형식이 잘못돼도 resolve는 정상 수행 — 이벤트만 안 남는다. 분석 수치 누락이면 이것부터 의심.NF0006(명함 데이터 비정상) 경로에서 선점은 이미 확정된 채 남는다 — 연결만 실패. 운영에서 발견 시 데이터 정합 확인 필요.- 발급 후 개방 전환(
POST /v1/admin/nfc-cards/{cardId}/self-claim)은IN_STOCK에만 허용 — 배정·연결·불능 상태는 409NF0007.cardId는 admin 식별자지token(cardUid)이 아니다. - 비로그인
NEEDS_CONNECT/CLAIMABLE흐름: 복귀 경로 저장 →/login→ 같은/nfc/{token}재진입.
관련 문서
- 구현 심화(스포크): as-built §4 · self-claim · 발급 후 전환
- FE 라우팅: business-card-nfc-routing.md
- 유입 분석: analytics.md · 착지 화면: public-card.md