발급 후 NFC 재고 카드 셀프 선점 개방 전환 백엔드 API 명세
이미 발급된 미배정 재고 카드(IN_STOCK)의 self_claim_enabled를 관리자가 나중에 켜고 끌 수 있게
하는 백엔드 계약을 정의한다. 발급 시점 설정은 이미 구현됐고(NFC 셀프 선점 백엔드 API §9),
이 문서는 발급 이후 전환만 다룬다.
상태: 구현 완료 (tapple-be).
기준일: 2026-07-23
1. 목적
현재 재고 카드의 셀프 선점 개방 여부는 발급(issue) 시점에만 정할 수 있다. 실수로 예약(닫힘)으로 발급했거나, 예약 재고를 공개 배포로 돌리려면 카드를 삭제하고 다시 발급하는 수밖에 없다. 이 명세는 발급 후에도 미배정 재고 카드의 개방 상태를 안전하게 전환하는 관리자 전용 계약을 정의한다.
필요한 기능
- 관리자가 특정
IN_STOCK카드의self_claim_enabled를true/false로 전환한다. - 전환은 미배정 재고(
IN_STOCK)에만 허용한다. 이미 배정·연결됐거나 사용 불가 상태인 카드는 거절한다. - 같은 값으로의 재요청은 멱등하게 성공한다.
- 전환 결과가 즉시 resolve 분기에 반영된다(개방 →
CLAIMABLE, 예약 →NOT_ISSUED). - 변경 이력(누가·언제·이전값→새값)을 감사 로그로 남긴다.
2. 범위
포함
- 재고 카드 셀프 선점 개방 전환 엔드포인트
- 전환 가능 상태 규칙과 거절 계약
- 오류 응답, 감사 로그, 계약 테스트
제외
- 발급(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 / REVOKED | ❌ | 409 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? }.
| HTTP | code | 의미 | 프론트 처리 |
|---|---|---|---|
| 404 | NF0001 | 카드 없음 | ”카드를 찾지 못했습니다” |
| 409 | NF0007 | 미배정 재고 카드가 아니어서 전환 불가 (신규 코드) | “미발급 재고 카드만 변경할 수 있습니다” |
| 422 | VALIDATION_FAILED | selfClaimable 누락·비boolean | 검증 오류 |
| 403 | FORBIDDEN | 관리자 권한 없음 | ”관리자 권한이 필요합니다” |
| 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. 프론트 소비 노트 (참고 — 이 문서 범위 밖)
useAdminCardOperations에setSelfClaimable({ cardId, selfClaimable })mutation 추가.- 성공 시 재고/요약 쿼리(
getAdminNfcCards, 카드 목록) 무효화 → 배지 즉시 갱신. UnconnectedCardRegistry의IN_STOCK카드에 개방/예약 전환 토글 노출.adminApiErrorMessages에NF0007한국어 메시지 추가.
8. 검증 시나리오 / 계약 테스트
IN_STOCK예약 카드 →{ selfClaimable: true }→ 200,selfClaimEnabled=true. 이후 resolve가CLAIMABLE.IN_STOCK개방 카드 →{ selfClaimable: false }→ 200,selfClaimEnabled=false. 이후 resolve가NOT_ISSUED.- 같은 값 재요청 → 200 멱등(변경 없음).
UNCONNECTED/CONNECTED카드 → 409NF0007.INACTIVE/LOST/REVOKED카드 → 409NF0007.- 존재하지 않는 카드 → 404
NF0001. selfClaimable누락/비boolean → 422VALIDATION_FAILED.- 비관리자 호출 → 403
FORBIDDEN.