발급 후 NFC 재고 카드 셀프 선점 개방 전환 백엔드 API 명세

이미 발급된 미배정 재고 카드(IN_STOCK)의 self_claim_enabled를 관리자가 나중에 켜고 끌 수 있게 하는 백엔드 계약을 정의한다. 발급 시점 설정은 이미 구현됐고(NFC 셀프 선점 백엔드 API §9), 이 문서는 발급 이후 전환만 다룬다.

상태: 구현 완료 (tapple-be).

기준일: 2026-07-23

1. 목적

현재 재고 카드의 셀프 선점 개방 여부는 발급(issue) 시점에만 정할 수 있다. 실수로 예약(닫힘)으로 발급했거나, 예약 재고를 공개 배포로 돌리려면 카드를 삭제하고 다시 발급하는 수밖에 없다. 이 명세는 발급 후에도 미배정 재고 카드의 개방 상태를 안전하게 전환하는 관리자 전용 계약을 정의한다.

필요한 기능

  • 관리자가 특정 IN_STOCK 카드의 self_claim_enabledtrue/false로 전환한다.
  • 전환은 미배정 재고(IN_STOCK)에만 허용한다. 이미 배정·연결됐거나 사용 불가 상태인 카드는 거절한다.
  • 같은 값으로의 재요청은 멱등하게 성공한다.
  • 전환 결과가 즉시 resolve 분기에 반영된다(개방 → CLAIMABLE, 예약 → NOT_ISSUED).
  • 변경 이력(누가·언제·이전값→새값)을 감사 로그로 남긴다.

2. 범위

포함

  1. 재고 카드 셀프 선점 개방 전환 엔드포인트
  2. 전환 가능 상태 규칙과 거절 계약
  3. 오류 응답, 감사 로그, 계약 테스트

제외

  • 발급(issue) 시점 개방 설정 — 이미 구현됨(backend-nfc-self-claim-spec.md §9)
  • resolve / connect 계약 변경 — 기존 계약 그대로 사용
  • 대량(bulk) 전환
  • 데이터 모델 변경 — 기존 self_claim_enabled 컬럼 재사용(마이그레이션 없음)
  • 프론트 화면 구현(레지스트리 토글 UI)

3. 전환 가능 상태 규칙

self_claim_enabled미배정 재고에서만 의미가 있다. 배정(assign)되거나 명함에 연결되면 소유자가 이미 정해져 셀프 선점이 무의미하므로 전환을 막는다.

카드 상태전환 허용?결과
IN_STOCK (미배정 재고)✅ 허용self_claim_enabled 변경
UNCONNECTED (관리자 배정)409 NF0007
CONNECTED (명함 연결)409 NF0007
INACTIVE / LOST / REVOKED409 NF0007
카드 없음404 NF0001
flowchart LR
  A["IN_STOCK · 예약<br/>self_claim_enabled=false"] -->|open| B["IN_STOCK · 개방<br/>self_claim_enabled=true"]
  B -->|close| A
  A -.->|assign / connect 이후| C["전환 불가 (409 NF0007)"]
  B -.->|assign / connect 이후| C

4. 엔드포인트 계약 — POST /v1/admin/nfc-cards/{cardId}/self-claim

ROLE_ADMIN. 기존 관리자 액션 엔드포인트(/assign, /connect) 컨벤션을 따른다.

요청

POST /v1/admin/nfc-cards/{cardId}/self-claim
Authorization: Bearer <accessToken>

{ "selfClaimable": true }   // 필수 boolean
  • cardId: 관리자 카드 식별자. resolve/connect의 token(cardUid)이 아니라 admin 카드 id를 쓴다.
  • selfClaimable: 설정할 개방 상태. true=공개 배포(셀프 선점 허용), false=예약.

응답 200 — 갱신된 카드

assign/connect와 동일하게 갱신된 AdminNfcCard 전체를 반환한다.

type AdminNfcCard = {
  id: string
  cardUid: string
  label: string
  status: 'IN_STOCK' | 'CONNECTED' | 'UNCONNECTED' | 'INACTIVE' | 'LOST' | 'REVOKED'
  selfClaimEnabled: boolean // 전환 결과가 반영된 값
  assignedMemberId: string | null
  assignedAt: string | null
  businessCardId: string | null
  businessCardSlug: string | null
  activatedAt: string | null
  lastTaggedAt: string | null
}

예시

POST /v1/admin/nfc-cards/nfc_stock_01/self-claim
{ "selfClaimable": true }

200 { "id": "nfc_stock_01", "status": "IN_STOCK", "selfClaimEnabled": true, ... }

멱등성

  • 이미 요청한 값과 같은 상태면 변경 없이 200과 현재 카드를 반환한다(no-op 성공).

오류 응답

오류 봉투는 기존 admin 계약과 동일하다: HTTP status + { code, message, data? }.

HTTPcode의미프론트 처리
404NF0001카드 없음”카드를 찾지 못했습니다”
409NF0007미배정 재고 카드가 아니어서 전환 불가 (신규 코드)“미발급 재고 카드만 변경할 수 있습니다”
422VALIDATION_FAILEDselfClaimable 누락·비boolean검증 오류
403FORBIDDEN관리자 권한 없음”관리자 권한이 필요합니다”
401미인증로그인 유도

NF0007은 이 기능에서 새로 추가된 도메인 코드다. 프론트 adminApiErrorMessages에 매핑을 추가한다. 권한 거부는 DB0004가 아니라 FORBIDDEN 코드로 내려온다.

5. 감사 로그

  • nfc_card_id, actor_member_id(관리자), before(이전 값), after(새 값), changed_at을 기록한다.
  • 발급 시점의 개방 설정(assigned_via/issue 로그)과 구분 가능해야 한다.

6. 데이터 모델

  • 신규 컬럼·마이그레이션 없음. 기존 nfc_cards.self_claim_enabled(V9) 값을 갱신한다.

7. 프론트 소비 노트 (참고 — 이 문서 범위 밖)

  • useAdminCardOperationssetSelfClaimable({ cardId, selfClaimable }) mutation 추가.
  • 성공 시 재고/요약 쿼리(getAdminNfcCards, 카드 목록) 무효화 → 배지 즉시 갱신.
  • UnconnectedCardRegistryIN_STOCK 카드에 개방/예약 전환 토글 노출.
  • adminApiErrorMessagesNF0007 한국어 메시지 추가.

8. 검증 시나리오 / 계약 테스트

  • IN_STOCK 예약 카드 → { selfClaimable: true } → 200, selfClaimEnabled=true. 이후 resolve가 CLAIMABLE.
  • IN_STOCK 개방 카드 → { selfClaimable: false } → 200, selfClaimEnabled=false. 이후 resolve가 NOT_ISSUED.
  • 같은 값 재요청 → 200 멱등(변경 없음).
  • UNCONNECTED / CONNECTED 카드 → 409 NF0007.
  • INACTIVE / LOST / REVOKED 카드 → 409 NF0007.
  • 존재하지 않는 카드 → 404 NF0001.
  • selfClaimable 누락/비boolean → 422 VALIDATION_FAILED.
  • 비관리자 호출 → 403 FORBIDDEN.