# 후향적 고객 매핑 및 마일리지 적립 시스템 기획서 > **버전**: 1.0 > **작성일**: 2026-01-23 > **목적**: POS 판매 후 QR 코드 + 카카오 로그인을 통한 후향적 고객 매핑 및 마일리지 적립 시스템 --- ## 1. 개요 ### 1-1. 배경 및 문제점 현재 약국 POS 시스템의 고객 데이터 현황: | 구분 | 비율 | 설명 | |------|------|------| | **비고객 판매** | ~80% | 고객 정보 없이 판매 (SL_CD_custom = NULL) | | **이름만 기록** | ~15% | 고객명만 있고 코드 없음 (SL_NM_custom만 기록) | | **완전 매핑** | ~5% | 고객코드로 CD_PERSON과 연결됨 | **문제점**: - 대부분의 거래에서 구매자 정보가 누락됨 - 단골 고객 분석/마케팅 불가 - 고객 충성도 프로그램 운영 어려움 ### 1-2. 솔루션 개요 **핵심 컨셉**: 영수증 QR → 카카오 로그인 → 후향적 고객 매핑 → 마일리지 적립 ``` ┌─────────────────────────────────────────────────────────────┐ │ 후향적 고객 매핑 흐름 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ [POS 판매] │ │ ↓ │ │ 영수증/라벨에 QR 인쇄 (claim_token 포함) │ │ ↓ │ │ (시간 경과... 고객이 나중에 QR 촬영) │ │ ↓ │ │ 웹앱 랜딩 → 카카오 간편로그인 │ │ ↓ │ │ 토큰 검증 → 거래(transaction) 매핑 │ │ ↓ │ │ ✅ 마일리지 적립 + CD_PERSON 연결 │ │ │ └─────────────────────────────────────────────────────────────┘ ``` --- ## 2. 현재 DB 구조 분석 ### 2-1. 데이터베이스 구성 ``` ┌──────────────────────────────────────────────────────────────┐ │ PM_PRES DB (판매 데이터) │ ├──────────────────────────────────────────────────────────────┤ │ │ │ SALE_MAIN (판매 주문 헤더) │ │ ├── SL_NO_order: 주문번호 (예: 20251024000002) ← 고유값 │ │ ├── SL_DAY_SERIAL: 당일 거래 순번 (1, 2, 3...) │ │ ├── SL_DT_appl: 판매일자 (YYYYMMDD) │ │ ├── SL_NM_custom: 고객명 (대부분 NULL) │ │ ├── SL_CD_custom: 고객코드 → CD_PERSON.CUSCODE │ │ ├── SL_MY_total: 총 매출액 │ │ ├── SL_MY_discount: 할인액 │ │ └── InsertTime: 등록시간 │ │ │ │ │ └──> SALE_SUB (판매 상세 품목) │ │ ├── SL_NO_order: 주문번호 (FK) │ │ ├── DrugCode: 약품코드 │ │ ├── SL_TOTAL_PRICE: 판매가 │ │ ├── SL_MY_in_cost: 매입가 │ │ └── SL_NM_item: 수량 │ │ │ │ CD_SUNAB (결제 정보) │ │ ├── PRESERIAL: 주문번호 참조 ← SALE_MAIN.SL_NO_order │ │ ├── OTC_CARD: 카드 결제금액 │ │ └── OTC_CASH: 현금 결제금액 │ │ │ └──────────────────────────────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────────────────┐ │ PM_BASE DB (고객 마스터) │ ├──────────────────────────────────────────────────────────────┤ │ │ │ CD_PERSON (고객 정보) │ │ ├── CUSCODE: 고객코드 (PK) ← SALE_MAIN.SL_CD_custom │ │ ├── PANAME: 고객명 │ │ ├── PANUM: 주민번호 (개인식별) │ │ ├── TEL_NO: 전화번호 1 (집) │ │ ├── PHONE: 전화번호 2 (휴대폰) │ │ ├── PHONE2: 전화번호 3 (대체) │ │ └── CUSETC: 고객 메모/특이사항 (2000자) │ │ │ └──────────────────────────────────────────────────────────────┘ ``` ### 2-2. 테이블 상세 구조 #### SALE_MAIN (판매 주문 헤더) | 컬럼명 | 타입 | 설명 | 예시 | |--------|------|------|------| | SL_NO_order | VARCHAR(20) | 주문번호 (PK) | `20251024000002` | | SL_DAY_SERIAL | INT | 당일 거래 순번 | `1`, `2`, `3`... | | SL_DT_appl | VARCHAR(8) | 판매일자 | `20251024` | | SL_NM_custom | VARCHAR(50) | 고객명 | `김철수` 또는 NULL | | SL_CD_custom | VARCHAR(10) | 고객코드 | `0000012345` 또는 NULL | | SL_MY_total | DECIMAL | 총 매출액 | `50000` | | SL_MY_discount | DECIMAL | 할인액 | `5000` | | SL_MY_sale | DECIMAL | 실 판매액 | `45000` | | InsertTime | DATETIME | 등록시간 | `2025-10-24 14:30:00` | #### CD_PERSON (고객 정보) | 컬럼명 | 타입 | 설명 | 예시 | |--------|------|------|------| | CUSCODE | VARCHAR(10) | 고객코드 (PK) | `0000012345` | | INSCODE | VARCHAR(10) | 기관코드 | `0000000001` | | SEQ | INT | 순번 | `1` | | INDATE | VARCHAR(8) | 등록일 | `20251024` | | PANAME | VARCHAR(20) | 고객명 | `김철수` | | PANUM | VARCHAR(13) | 주민번호 | `800101-1******` | | TEL_NO | VARCHAR(20) | 전화번호 1 | `02-123-4567` | | PHONE | VARCHAR(20) | 휴대폰 | `010-1234-5678` | | PHONE2 | VARCHAR(20) | 대체번호 | `010-9876-5432` | | CUSETC | VARCHAR(2000) | 메모 | `당뇨병 주의` | ### 2-3. 현재 고객-판매 연결 방식 ```sql -- 고객이 식별된 판매 (약 5%) SELECT M.SL_NO_order, M.SL_NM_custom, -- '김철수' M.SL_CD_custom, -- '0000012345' P.PANAME, -- CD_PERSON에서 조회 P.PHONE -- 전화번호 FROM SALE_MAIN M LEFT JOIN CD_PERSON P ON M.SL_CD_custom = P.CUSCODE WHERE M.SL_CD_custom IS NOT NULL AND M.SL_CD_custom != '' -- 비고객 판매 (약 80%) SELECT * FROM SALE_MAIN WHERE SL_CD_custom IS NULL OR SL_CD_custom = '' ``` --- ## 3. 신규 테이블 설계 (SQLite - mileage.db) ### 3-1. DB 분리 구조 ``` ┌─────────────────────────┐ ┌─────────────────────────────┐ │ MSSQL (기존) │ │ SQLite (신규: mileage.db) │ ├─────────────────────────┤ ├─────────────────────────────┤ │ │ │ │ │ PM_PRES │ │ users │ │ ├── SALE_MAIN ─────────┼──────────┼─→ kakao_user_id (카카오 키) │ │ ├── SALE_SUB │ 주문번호 │ │ │ └── CD_SUNAB │ 연결 │ customer_identities │ │ │ │ └── provider='kakao' │ ├─────────────────────────┤ │ │ │ PM_BASE │ │ claim_tokens │ │ └── CD_PERSON ─────────┼──────────┼─→ transaction_id (주문번호) │ │ (CUSCODE) │ 고객코드 │ └── token_hash │ │ │ 연결 │ │ └─────────────────────────┘ │ mileage_ledger │ │ ├── user_id │ │ ├── transaction_id │ │ └── points (+/-) │ │ │ │ pos_customer_links │ │ ├── user_id │ │ └── cuscode (CD_PERSON) │ │ │ └─────────────────────────────┘ ``` ### 3-2. 테이블 DDL (SQLite) #### users (카카오 로그인 계정) ```sql CREATE TABLE users ( id INTEGER PRIMARY KEY AUTOINCREMENT, nickname VARCHAR(100), profile_image_url VARCHAR(500), email VARCHAR(200), is_email_verified BOOLEAN DEFAULT FALSE, phone VARCHAR(20), -- 직접 입력 또는 카카오 (비즈앱) mileage_balance INTEGER DEFAULT 0, -- 잔액 캐시 (성능용) created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ); ``` #### customer_identities (외부 로그인 매핑) ```sql CREATE TABLE customer_identities ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL REFERENCES users(id), provider VARCHAR(20) NOT NULL, -- 'kakao', 'naver', 'google' 등 provider_user_id VARCHAR(100) NOT NULL, -- 카카오 user id provider_data TEXT, -- JSON (추가 정보) created_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE(provider, provider_user_id) -- 중복 방지 ); CREATE INDEX idx_identities_user ON customer_identities(user_id); ``` #### claim_tokens (영수증 QR 토큰) ```sql CREATE TABLE claim_tokens ( id INTEGER PRIMARY KEY AUTOINCREMENT, transaction_id VARCHAR(20) NOT NULL, -- SALE_MAIN.SL_NO_order pharmacy_id VARCHAR(20), -- 약국 식별자 (다중 약국 대비) token_hash VARCHAR(64) NOT NULL, -- SHA256 해시 (원문 저장 X) total_amount INTEGER NOT NULL, -- 거래 금액 claimable_points INTEGER NOT NULL, -- 적립 가능 포인트 expires_at DATETIME NOT NULL, -- 만료시간 (14~30일) claimed_at DATETIME, -- 적립 완료 시간 claimed_by_user_id INTEGER REFERENCES users(id), created_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE(transaction_id), -- 1거래 1토큰 UNIQUE(token_hash) ); CREATE INDEX idx_tokens_hash ON claim_tokens(token_hash); CREATE INDEX idx_tokens_expires ON claim_tokens(expires_at); ``` #### mileage_ledger (마일리지 원장) ```sql CREATE TABLE mileage_ledger ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL REFERENCES users(id), transaction_id VARCHAR(20), -- SALE_MAIN.SL_NO_order (적립 시) points INTEGER NOT NULL, -- + 적립, - 사용 balance_after INTEGER NOT NULL, -- 거래 후 잔액 reason VARCHAR(50) NOT NULL, -- 'PURCHASE_CLAIM', 'POINT_USE', 'EVENT_BONUS' 등 description TEXT, -- 상세 설명 created_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE(transaction_id) -- 1거래 1회 적립 보장 ); CREATE INDEX idx_ledger_user ON mileage_ledger(user_id); CREATE INDEX idx_ledger_transaction ON mileage_ledger(transaction_id); ``` #### pos_customer_links (POS 고객 ↔ 카카오 계정 연결) ```sql CREATE TABLE pos_customer_links ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL REFERENCES users(id), pharmacy_id VARCHAR(20), -- 약국 식별자 cuscode VARCHAR(10), -- CD_PERSON.CUSCODE (기존 고객코드) customer_name VARCHAR(50), -- 고객명 (캐시) linked_at DATETIME DEFAULT CURRENT_TIMESTAMP, is_active BOOLEAN DEFAULT TRUE, UNIQUE(user_id, pharmacy_id) -- 약국별 1:1 연결 ); CREATE INDEX idx_links_cuscode ON pos_customer_links(cuscode); ``` --- ## 4. 후향적 매핑 흐름 상세 ### 4-1. 전체 시퀀스 다이어그램 ``` ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ POS │ │ 영수증 │ │ 고객 │ │ 웹앱 │ │ 서버 │ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │ │ │ │ │ 1. 판매 완료 │ │ │ │ │───────────────┼───────────────┼───────────────┼──────────────>│ │ │ │ │ │ │ │ 2. QR 인쇄 │ │ │ │ │<──────────────┼───────────────┼───────────────│ │ │ (claim_token) │ │ │ │ │ │ │ │ │ │ │ 3. QR 촬영 │ │ │ │ │──────────────>│ │ │ │ │ │ │ │ │ │ │ 4. 토큰 검증 │ │ │ │ │──────────────>│ │ │ │ │ │ │ │ │ │ 5. 카카오로그인│ │ │ │ │<─────────────>│ │ │ │ │ │ │ │ │ │ 6. 적립 요청 │ │ │ │ │──────────────>│ │ │ │ │ │ │ │ │ │ 7. 마일리지 │ │ │ │ │ 적립 완료 │ │ │ │ │<──────────────│ │ │ │ │ │ ``` ### 4-2. 단계별 상세 #### Step 1: POS 판매 완료 ```python # MSSQL SALE_MAIN에 INSERT 발생 # 주문번호(SL_NO_order) 생성: 20251024000042 ``` #### Step 2: QR 토큰 생성 및 인쇄 ```python # 토큰 생성 로직 import hashlib import secrets import datetime def generate_claim_token(transaction_id, total_amount, mileage_rate=0.03): # 1. 랜덤 nonce 생성 nonce = secrets.token_hex(16) # 2. 토큰 원문 생성 token_raw = f"{transaction_id}:{nonce}:{datetime.datetime.now().isoformat()}" # 3. 해시 생성 (원문 저장 X) token_hash = hashlib.sha256(token_raw.encode()).hexdigest() # 4. QR URL 생성 qr_url = f"https://pharmacy.example.com/claim?t={token_raw}" # 5. 적립 포인트 계산 claimable_points = int(total_amount * mileage_rate) # 6. DB 저장 # INSERT INTO claim_tokens (transaction_id, token_hash, total_amount, # claimable_points, expires_at) # VALUES (?, ?, ?, ?, datetime('now', '+30 days')) return qr_url ``` ``` QR 코드 내용 (URL): https://pharmacy.example.com/claim?t=20251024000042:a1b2c3d4e5f6:2025-10-24T14:30:00 영수증 출력 예시: ┌─────────────────────────────────┐ │ 양구청춘약국 │ │ 2025-10-24 14:30 │ │ │ │ 타이레놀 500mg ×2 3,000원 │ │ 밴드 ×1 2,000원 │ │ ─────────────────────────────│ │ 합계 5,000원 │ │ │ │ [QR 코드] │ │ │ │ QR 촬영하고 │ │ 150P 적립받으세요! │ │ (유효기간: 30일) │ └─────────────────────────────────┘ ``` #### Step 3~4: QR 촬영 및 토큰 검증 ```python @app.route('/claim', methods=['GET']) def claim_landing(): token = request.args.get('t') # 토큰 파싱 parts = token.split(':') transaction_id = parts[0] # 해시 계산 token_hash = hashlib.sha256(token.encode()).hexdigest() # DB 검증 claim = db.query(""" SELECT * FROM claim_tokens WHERE token_hash = ? AND expires_at > datetime('now') AND claimed_at IS NULL """, [token_hash]) if not claim: return render_template('claim_error.html', error="만료되었거나 이미 사용된 영수증입니다.") # 세션에 토큰 정보 저장 session['claim_token'] = token session['claim_info'] = { 'transaction_id': claim['transaction_id'], 'claimable_points': claim['claimable_points'] } # 로그인 페이지로 리다이렉트 return redirect('/auth/kakao/login') ``` #### Step 5: 카카오 로그인 ```python @app.route('/auth/kakao/callback') def kakao_callback(): code = request.args.get('code') # 액세스 토큰 발급 access_token = kakao_api.get_access_token(code) # 사용자 정보 조회 kakao_user = kakao_api.get_user_info(access_token) # { # "id": 1234567890, # "kakao_account": { # "email": "user@example.com", # "profile": {"nickname": "홍길동"} # } # } # users 테이블에서 찾기 또는 생성 user = get_or_create_user( provider='kakao', provider_user_id=str(kakao_user['id']), nickname=kakao_user['kakao_account']['profile']['nickname'], email=kakao_user['kakao_account'].get('email') ) # 세션에 user_id 저장 session['user_id'] = user['id'] # 클레임 진행 중이면 적립 페이지로 if session.get('claim_token'): return redirect('/claim/confirm') return redirect('/mypage') ``` #### Step 6~7: 마일리지 적립 ```python @app.route('/claim/confirm', methods=['POST']) def claim_confirm(): user_id = session.get('user_id') claim_info = session.get('claim_info') if not user_id or not claim_info: return jsonify({'error': '세션이 만료되었습니다.'}), 400 transaction_id = claim_info['transaction_id'] points = claim_info['claimable_points'] try: with db.transaction(): # 1. 토큰 사용 처리 db.execute(""" UPDATE claim_tokens SET claimed_at = datetime('now'), claimed_by_user_id = ? WHERE transaction_id = ? AND claimed_at IS NULL """, [user_id, transaction_id]) # 2. 마일리지 적립 current_balance = db.query( "SELECT mileage_balance FROM users WHERE id = ?", [user_id] )['mileage_balance'] new_balance = current_balance + points db.execute(""" INSERT INTO mileage_ledger (user_id, transaction_id, points, balance_after, reason, description) VALUES (?, ?, ?, ?, 'PURCHASE_CLAIM', ?) """, [user_id, transaction_id, points, new_balance, f"영수증 QR 적립 ({transaction_id})"]) # 3. 잔액 업데이트 db.execute(""" UPDATE users SET mileage_balance = ? WHERE id = ? """, [new_balance, user_id]) # 4. POS 고객 연결 (선택적) link_pos_customer(user_id, transaction_id) # 세션 정리 session.pop('claim_token', None) session.pop('claim_info', None) return jsonify({ 'success': True, 'points_earned': points, 'new_balance': new_balance }) except sqlite3.IntegrityError: # transaction_id UNIQUE 제약 위반 = 이미 적립됨 return jsonify({'error': '이미 적립된 영수증입니다.'}), 400 ``` --- ## 5. API 설계 ### 5-1. 토큰 관련 API #### POST /api/claim/token/generate 영수증 QR 토큰 생성 (POS에서 호출) **Request**: ```json { "transaction_id": "20251024000042", "total_amount": 50000, "pharmacy_id": "YANGGU001" } ``` **Response**: ```json { "success": true, "qr_url": "https://pharmacy.example.com/claim?t=...", "claimable_points": 1500, "expires_at": "2025-11-23T14:30:00" } ``` #### GET /api/claim?t={token} 토큰 검증 및 정보 조회 **Response (유효)**: ```json { "valid": true, "transaction_id": "20251024000042", "claimable_points": 1500, "total_amount": 50000, "expires_at": "2025-11-23T14:30:00" } ``` **Response (무효)**: ```json { "valid": false, "error": "expired|claimed|not_found" } ``` #### POST /api/claim/confirm 적립 실행 (로그인 후) **Request**: ```json { "token": "20251024000042:a1b2c3d4:..." } ``` **Response**: ```json { "success": true, "points_earned": 1500, "new_balance": 3500, "transaction_id": "20251024000042" } ``` ### 5-2. 마일리지 조회 API #### GET /api/me/mileage 내 마일리지 잔액 조회 **Response**: ```json { "user_id": 123, "nickname": "홍길동", "mileage_balance": 3500, "total_earned": 10000, "total_used": 6500 } ``` #### GET /api/me/mileage/ledger 적립/사용 내역 조회 **Query Parameters**: - `limit`: 조회 개수 (기본 20) - `offset`: 페이지네이션 **Response**: ```json { "ledger": [ { "id": 45, "points": 1500, "balance_after": 3500, "reason": "PURCHASE_CLAIM", "description": "영수증 QR 적립 (20251024000042)", "created_at": "2025-10-24T15:30:00" }, { "id": 44, "points": -2000, "balance_after": 2000, "reason": "POINT_USE", "description": "결제 시 사용", "created_at": "2025-10-20T10:00:00" } ], "total_count": 25, "has_more": true } ``` ### 5-3. 인증 API #### GET /auth/kakao/login 카카오 로그인 페이지로 리다이렉트 #### GET /auth/kakao/callback 카카오 콜백 처리 #### POST /auth/logout 로그아웃 --- ## 6. 보안 및 부정 사용 방지 ### 6-1. 토큰 보안 | 보안 요소 | 구현 방법 | |-----------|-----------| | **1회성 사용** | `claimed_at IS NULL` 체크 후 즉시 업데이트 | | **만료 시간** | `expires_at` 필드로 14~30일 제한 | | **해시 저장** | 토큰 원문 저장 X, SHA256 해시만 저장 | | **중복 적립 방지** | `mileage_ledger.transaction_id UNIQUE` | ### 6-2. 부정 사용 시나리오 및 대응 | 시나리오 | 대응 방법 | |----------|-----------| | QR 사진 공유 | 1회성 토큰 + 로그인 필수 | | 토큰 위조 | 서버 서명 검증 (HMAC) | | 중복 적립 시도 | DB UNIQUE 제약 | | 만료 토큰 사용 | `expires_at` 검증 | | 타인 영수증 도용 | (완벽 방지 어려움) 영수증 일부 숫자 확인 옵션 | ### 6-3. 적립률 정책 예시 ```python MILEAGE_CONFIG = { 'default_rate': 0.03, # 기본 3% 'vip_rate': 0.05, # VIP 5% 'min_points': 10, # 최소 적립 10P 'max_points': 10000, # 최대 적립 10,000P 'expiry_days': 30, # 토큰 유효기간 30일 'excluded_categories': [], # 적립 제외 품목 (있으면) } ``` --- ## 7. POS 관리화면 연동 ### 7-1. 거래 상세 화면 ``` ┌─────────────────────────────────────────────────────────────┐ │ 거래 상세 - 20251024000042 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ 판매일시: 2025-10-24 14:30:00 │ │ 총 금액: 50,000원 │ │ 결제 방법: 카드 │ │ │ │ ┌─────────────────────────────────────────────────────────┐│ │ │ 멤버십 상태: ✅ 연결됨 ││ │ │ 연결 고객: 홍길동 (카카오) ││ │ │ 연결 일시: 2025-10-24 16:00:00 ││ │ │ 적립 포인트: 1,500P ││ │ └─────────────────────────────────────────────────────────┘│ │ │ │ 품목: │ │ - 타이레놀 500mg × 2 3,000원 │ │ - 밴드 × 1 2,000원 │ │ │ └─────────────────────────────────────────────────────────────┘ ``` ### 7-2. 고객 목록 필터 ``` ┌─────────────────────────────────────────────────────────────┐ │ 고객 관리 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ 필터: [전체 ▼] [카카오 연결 고객만 ☑] │ │ │ │ ┌───────┬──────────┬─────────────┬───────────┬───────────┐ │ │ │ 고객명 │ 카카오 연결│ 마일리지 잔액│ 총 구매액 │ 방문 횟수 │ │ │ ├───────┼──────────┼─────────────┼───────────┼───────────┤ │ │ │ 홍길동 │ ✅ │ 3,500P │ 150,000원 │ 12회 │ │ │ │ 김철수 │ ✅ │ 1,200P │ 80,000원 │ 5회 │ │ │ │ 이영희 │ ❌ │ - │ 200,000원 │ 20회 │ │ │ └───────┴──────────┴─────────────┴───────────┴───────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘ ``` ### 7-3. CD_PERSON 연동 ```python def link_pos_customer(user_id, transaction_id): """거래 정보를 기반으로 POS 고객과 연결""" # 1. MSSQL에서 거래 정보 조회 sale = mssql_query(""" SELECT SL_CD_custom, SL_NM_custom FROM SALE_MAIN WHERE SL_NO_order = ? """, [transaction_id]) if not sale or not sale['SL_CD_custom']: # 고객코드가 없으면 연결 스킵 return cuscode = sale['SL_CD_custom'] customer_name = sale['SL_NM_custom'] # 2. pos_customer_links에 저장 sqlite_execute(""" INSERT OR IGNORE INTO pos_customer_links (user_id, cuscode, customer_name) VALUES (?, ?, ?) """, [user_id, cuscode, customer_name]) ``` --- ## 8. 카카오 로그인 정책 참고 ### 8-1. 수집 가능한 정보 | 정보 | 기본 제공 | 추가 심사 필요 | |------|-----------|----------------| | kakao_user_id | ✅ | - | | 닉네임 | ✅ (동의 시) | - | | 프로필 이미지 | ✅ (동의 시) | - | | 이메일 | ⚠️ (동의 시) | - | | 성별/연령대 | - | ✅ | | **전화번호** | - | ✅ (비즈앱) | ### 8-2. 전화번호 수집 방법 1. **카카오 비즈앱 전환** 필요 2. **전화번호 권한 심사** 신청 3. 로그인 시 **명시적 동의** 팝업 4. 서비스 이용 목적, 개인정보처리방침 제출 **권장**: - 초기에는 `kakao_user_id`만 사용 - 전화번호는 직접 입력 UI로 수집 (선택) - 규모 확대 후 비즈앱 심사 진행 --- ## 9. 향후 확장 계획 ### 9-1. Phase 1 (MVP) - [x] SQLite 테이블 설계 - [ ] 영수증 QR 생성 API - [ ] 카카오 로그인 연동 - [ ] 마일리지 적립 기능 - [ ] 내 마일리지 조회 페이지 ### 9-2. Phase 2 (확장) - [ ] 마일리지 사용 (결제 시 차감) - [ ] POS 관리화면 연동 - [ ] 이벤트 보너스 포인트 - [ ] 푸시 알림 (적립 완료) ### 9-3. Phase 3 (고도화) - [ ] 다중 약국 지원 - [ ] 알림톡 연동 (적립 완료 알림) - [ ] VIP 등급제 - [ ] 포인트 양도/선물 --- ## 10. 참고: 기존 코드 위치 | 기능 | 파일 위치 | 비고 | |------|-----------|------| | 카카오 로그인 | `full/src/kakao_login_example.py` | KakaoLoginAPI 클래스 | | 카카오 설정 | `full/src/kakao_config.py` | REST_API_KEY 등 | | QR 코드 생성 | `print_label.py` | Brother QL-710W 프린터 | | MSSQL 연결 | `dbsetup.py` | PM_PRES, PM_BASE 연결 | | OTC 판매 API | `otc_stats_api.py` | sales-details 엔드포인트 | | 고객 분석 | `customer_analytics_api.py` | 단골 고객 조회 | --- ## 부록 A: 용어 정리 | 용어 | 설명 | |------|------| | **후향적 매핑** | 판매 시점이 아닌, 판매 후 나중에 고객 정보를 연결하는 방식 | | **claim_token** | 영수증에 인쇄되는 1회성 토큰 (QR 코드에 포함) | | **mileage_ledger** | 마일리지 적립/사용 내역을 기록하는 원장 테이블 | | **CUSCODE** | CD_PERSON 테이블의 고객 코드 (기존 POS 고객 식별자) | | **kakao_user_id** | 카카오 로그인 시 발급되는 고유 사용자 ID | --- ## 부록 B: 관련 문서 - [CLAUDE.md](../CLAUDE.md) - 프로젝트 가이드 - [POS 입고 기능 개선 정리](./pos/관련정리.md) - drug_code, 바코드 구조 - [OTC 통계 API 문서](./dev-guide/otc-stats-api-documentation.md)