v1 API 상세: 회원
구매자가 쇼핑몰에 직접 가입하는 흐름이에요. 상점의 가입 정책(약관·수집 항목·중복·탈퇴 후 재가입 제한)을 모두 거쳐요.
고객 등록(POST /v1/users/join)은 가맹점이 기존 회원을 이관하는 서버 동기화 API예요. 가입 정책을 거치지 않고 external_uid·그룹·가입일을 서버가 정해요.
아래 POST /v1/users/signup은 신규 구매자 가입이고, 브라우저 입력을 BFF가 그대로 넘기는 경로라 식별자·권한 값을 받지 않아요.
1가입 정책 조회
가입 화면을 그리기 전에 어떤 약관을 받아야 하고 어떤 항목이 필수인지 서버에서 받아요. 약관 ID를 화면에 하드코딩하면 상점이 약관을 바꿨을 때 가입이 통째로 막혀요.
필요 권한: user:user_signup_policy · 고객 JWT는 필요 없어요.
{
"signup_enabled": true,
"membership_type": "id",
"supported_member_types": ["individual"],
"fields": {
"name": { "required": true },
"email": { "required": true },
"phone": { "required": false },
"address": { "required": true }
},
"terms": [
{
"term_id": "67e4b4425ec892162491d0ec",
"required": true,
"title": "이용약관",
"desc": "서비스 이용에 대한 약관이에요",
"content": "<p>제1조 ...</p>",
"use_url": false,
"url": null,
"sort": 0,
"updated_at": "2026-08-01T10:00:00+09:00"
}
],
"policy_version": "3f2a1c8e9b4d7a06"
}jsonmembership_type—id면 아이디로,email이면 이메일로 가입받는 상점이에요.fields— 상점 정보 응답의 수집 설정에서 파생한 값이라 두 응답이 갈라지지 않아요.terms— 상점이 만든 약관과 Bootpay 고정 약관을 합쳐sort순으로 줘요. 고정 약관은 모든 상점에 공통이에요.use_url이true면content가 비어 있고url로 약관을 열어야 해요. 이 경우content만 그리면 빈 약관이 나와요.updated_at— 약관에는 별도 버전 번호가 없어서 변경 시각을 버전 대신 써요.
signup_enabled가 false인 상점(가입 비활성)은 이 API가 MALL_INACTIVE로 거절해요.
policy_version으로 약관 변경 감지
응답의 policy_version은 약관 목록과 수집 설정의 지문이에요. 가입 요청에 그대로 실어 보내면, 화면을 띄운 뒤 상점이 약관을 바꿨을 때 409로 막아요. 동의받지 않은 약관에 조용히 동의 처리되는 것(silent agreement)을 막는 장치예요.
{
"error_code": "SIGNUP_POLICY_VERSION_CHANGED",
"payload": { "policy_version": "a71b0c3d5e8f2419" }
}json이때는 정책을 다시 조회해 바뀐 약관을 보여주고 다시 동의받아요. policy_version을 보내지 않으면 검사하지 않아요(선택 항목).
표시 순서(sort)만 바꾼 경우에는 버전이 바뀌지 않아요 — 동의 대상이 같기 때문이에요.
2가입 요청
필요 권한: user:user_create · 고객 JWT는 필요 없어요.
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
name |
String | 필수 | 회원 이름 (50자 이하) |
password |
String | 필수 | 비밀번호 (8자 이상, 72바이트 이하) |
login_id |
String | 선택 | membership_type이 id면 필수 (영문/숫자 5~30자) |
email |
String | 선택 | fields.email.required면 필수 |
phone |
String | 선택 | fields.phone.required면 필수 |
address |
Object | 선택 | fields.address.required면 필수. 아래 참조 |
term_ids |
Array | 선택 | 동의한 약관 ID. 필수 약관은 모두 포함해야 해요 |
policy_version |
String | 선택 | 정책 조회 응답의 값 |
address 객체는 세 키만 받아요.
{
"address": {
"zipcode": "06164",
"address": "서울특별시 강남구 테헤란로 123",
"address_detail": "101동 101호"
}
}jsonuser_group_id·role 같은 다른 키가 섞이면 권한 주입이 되므로 조용히 버리지 않고 거절해요. 최상위 user_id·login_pw·group·membership_type·status 같은 식별자·권한 값도 같은 방식으로 거절해요.
use_membership_collect_address가 켜진 상점에서는 address 없이 가입할 수 없어요. 2026-09-21 이전에는 이 필드를 보낼 방법이 없어 해당 상점의 v1 가입이 항상 실패했어요.
응답
가입 결과는 상점의 가입 방식에 따라 세 가지예요.
{ "status": "finish", "user_id": "67e4...", "requires_login": true }json| status | 뜻 | 함께 오는 값 |
|---|---|---|
finish |
가입 완료 | user_id |
standby |
이메일 인증 대기 | user_standby_id, verification_context |
pending_approval |
관리자 승인 대기 | user_id |
어떤 경우에도 고객 JWT를 발급하지 않아요. requires_login: true를 받으면 로그인을 따로 호출해요.
3이메일 인증 (standby)
이메일로 가입받는 상점은 인증 메일을 보내고 대기 상태로 만들어요. 가입 응답의 verification_context는 그 대기 건의 소유 증명이니 BFF가 보관해요.
상태 조회
{
"status": 0,
"auth_email": false,
"email": "u***@example.com",
"user_standby_id": "67e4...",
"verification_state": "pending",
"requires_login": true,
"client_key": "..."
}jsonverification_state는 pending · verified · expired 중 하나예요. 기존 status·auth_email·pending_approval 필드는 그대로 유지돼요.
조회는 인증한 상점의 대기 건만 볼 수 있어요. 다른 상점의 ID를 넣으면 USER_STAND_BY_NOT_FOUND예요. verification_context를 함께 보내면 소유까지 확인하고, 보내지 않으면 상점 범위 확인까지만 해요.
인증 완료
메일 링크로 받은 token_key를 보내요. 잘못된 Base64는 500이 아니라 API_PARAM_INVALID로 응답해요.
인증이 끝나면 대기 문서의 약관 동의(약관 ID·정책 버전·동의 시각)가 회원 계정으로 그대로 이식돼요.
인증 메일 재발송
필요 권한: user:user_signup_resend
verification_context가 필수예요. 없거나 틀리면 SIGNUP_CONTEXT_INVALID예요.
{ "accepted": true, "resend_after": "2026-09-21T10:01:00+09:00" }json재발송은 60초 쿨다운이 있고, 인증 유효시간은 발송 후 30분이에요. 이미 인증을 마쳤거나 만료된 대기 건은 USER_EMAIL_AUTHENTICATE_EXPIRED로 거절해요.
오류
| error_code | 상황 |
|---|---|
MALL_INACTIVE |
가입 비활성 상점 |
API_PARAM_INVALID |
허용하지 않는 키, address가 객체가 아님 |
TERM_NOT_FOUND |
목록에 없는 약관 ID |
REQUIRED_PARAMETER_IS_MISSING (131) |
필수 약관 미동의, 필수 수집 항목 누락 |
SIGNUP_POLICY_VERSION_CHANGED (10800) |
조회 이후 약관·수집 설정 변경 |
USER_ADDRESS_BLANK (421) |
주소 수집 상점인데 주소 누락 |
USER_ID_EXIST (404) / USER_EMAIL_EXIST (405) |
중복 가입 |
MEMBER_LEFT_RECENTLY |
탈퇴 후 재가입 제한 기간 |
SIGNUP_CONTEXT_INVALID (10801) |
재발송의 verification_context 누락·불일치 |
USER_STAND_BY_NOT_FOUND (411) |
없는 대기 건, 다른 상점의 대기 건 |
USER_EMAIL_RECENTLY_SENT (413) |
재발송 쿨다운 |
선택 약관은 동의하지 않아도 가입할 수 있어요. 필수 약관만 term_ids에 모두 들어가면 돼요.