Skip to content

Portal·채널 Backend 계약

목표

Company가 소유하는 여러 디지털 채널에 동일한 Identity·권한·이벤트 정책을 적용한다. 인증 사용자와 업무 주체를 분리하며 Company·Office·임대차·점유·직원 관계로 접근 범위를 파생한다.

권장 모델

text
identity_users
  id, email, phone, status, verified_at

identity_relationships
  id, user_id, relationship_type, subject_type, subject_id
  valid_from, valid_until, verification_status

company_channels
  id, company_id, channel_type, display_name, primary_domain
  status, config_version, activated_at, suspended_at

channel_domains
  id, channel_id, hostname, verification_method
  verification_status, verified_at, tls_status

channel_clients
  id, channel_id, client_type, client_id, redirect_uris
  status, rotated_at

channel_role_bindings
  id, channel_id, identity_id, role_key, office_id
  valid_from, valid_until

channel_webhook_endpoints
  id, channel_id, event_scope, endpoint_url
  secret_reference, status, last_delivery_at

Community 콘텐츠·활동은 위 ERP channel registry와 별도 저장소/스키마에 둔다. 공개 식별자에서 ERP의 Company·Office·고객 PK를 역추적할 수 없어야 한다.

text
community_identities
  id, identity_id(nullable), display_name, status

community_contents
  id, content_type, public_slug, visibility, status, published_at

community_activities
  id, community_identity_id, content_id, activity_type
  # ERP payload나 계산기 원본 입력값 저장 금지

growth_activation_progress
  id, identity_id, company_id(nullable), requirement_key
  verified_at, evidence_reference, revoked_at

crm_consent_receipts
  id, identity_id(nullable), channel, purpose, consent_version
  granted_at, revoked_at, source_request_id

Secret, client secret, webhook signing key는 DB 평문으로 저장하지 않고 Secret Manager reference만 보관한다.

API

http
GET    /v1/companies/{companyId}/channels
GET    /v1/companies/{companyId}/channels/{channelId}
POST   /v1/companies/{companyId}/channels
PATCH  /v1/companies/{companyId}/channels/{channelId}
POST   /v1/companies/{companyId}/channels/{channelId}/verify-domain
POST   /v1/companies/{companyId}/channels/{channelId}/activate
POST   /v1/companies/{companyId}/channels/{channelId}/suspend
GET    /v1/companies/{companyId}/channels/{channelId}/health
GET    /v1/companies/{companyId}/channels/{channelId}/audit-events

create·activate·suspend는 Company 관리자와 최근 승인 정책을 요구한다. 모든 변경 요청은 idempotency key를 받고 감사 이력을 남긴다.

상태 머신

text
planned -> prototype -> ready -> available
                         |          |
                         v          v
                      failed     degraded
                                      |
                                      v
                                  suspended

available로의 전환은 다음을 모두 통과해야 한다.

  1. 도메인 소유 및 TLS 검증
  2. OIDC/OAuth client·redirect URI 검증
  3. 표준 역할과 Office 범위 정책 검증
  4. 개인정보 처리방침·동의 버전 연결
  5. API·webhook 헬스체크
  6. 비상 중지·회복 절차 등록

이벤트

text
channel.created
channel.domain_verified
channel.activated
channel.degraded
channel.suspended
channel.identity_linked
channel.role_changed
portal.inquiry_submitted
portal.booking_submitted
portal.payment_requested
community.crm_consent_granted
community.activation_requirement_verified
community.activation_trial_eligible
community.activation_trial_issued

community.crm_consent_granted가 없는 커뮤니티 게시물·활동은 CRM에 적재하지 않는다. portal.payment_requested는 결제·수납 확정 이벤트가 아니며 실제 PSP·수납 결과를 따로 검증한다.

전환과 혜택 발급

Frontend의 체크 상태를 혜택 발급 근거로 사용하지 않는다. Backend가 사업자 중복, 기존 혜택 이력, Office 생성, 업무 데이터 등록, 팀원 초대, 상품 선택을 각 source of truth에서 다시 검증한다. 모든 조건을 통과한 뒤에도 먼저 eligible 상태를 만들고 사용자의 신청/결제수단 정책을 거쳐 issued로 전환한다.

michelan.kr 문의 → CRM seam

  1. 브랜드 사이트가 문의와 선택 동의 원문·버전을 저장한다.
  2. consent receipt를 만든 뒤 community.crm_consent_granted를 발행한다.
  3. CRM adapter는 이름, 연락처, 문의 유형, 목적, source request ID만 lead candidate로 적재한다.
  4. 커뮤니티 게시물, 계산기 입력, 열람 이력, ERP PK는 payload에서 거부한다.
  5. 동의 철회 시 신규 후속 연락을 중지하고 보존 의무·삭제 정책에 따라 처리한다.

보안·감사

  • token audience는 채널별로 분리하고 다른 Portal의 token을 재사용하지 않는다.
  • 외부 Portal에 ERP 내부 PK·원본 DTO를 노출하지 않는다.
  • 대리인·소유자·임차인 관계는 유효기간과 검증 상태를 요구한다.
  • 인증, 권한 변경, 대량 조회, 문서 다운로드를 불변 감사 이력에 남긴다.
  • 아이디 열거 방지, rate limit, 재인증, 비정상 다운로드 감지를 적용한다.