""" NHN Cloud 알림톡 발송 서비스 마일리지 적립 완료 등 알림톡 발송 + SQLite 로깅 """ import os import json import logging from datetime import datetime, timezone, timedelta import requests logger = logging.getLogger(__name__) # NHN Cloud 알림톡 설정 APPKEY = os.getenv('NHN_ALIMTALK_APPKEY', 'u0TLUaXXY9bfQFkY') SECRET_KEY = os.getenv('NHN_ALIMTALK_SECRET', 'naraGEUJfpkRu1fgirKewJtwADqWQ5gY') SENDER_KEY = os.getenv('NHN_ALIMTALK_SENDER', '341352077bce225195ccc2697fb449f723e70982') API_BASE = f'https://api-alimtalk.cloud.toast.com/alimtalk/v2.3/appkeys/{APPKEY}' # KST 타임존 KST = timezone(timedelta(hours=9)) def _log_to_db(template_code, recipient_no, success, result_message, template_params=None, user_id=None, trigger_source='unknown', transaction_id=None): """발송 결과를 SQLite에 저장""" try: from db.dbsetup import db_manager conn = db_manager.get_sqlite_connection() cursor = conn.cursor() cursor.execute(""" INSERT INTO alimtalk_logs (template_code, recipient_no, user_id, trigger_source, template_params, success, result_message, transaction_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?) """, ( template_code, recipient_no, user_id, trigger_source, json.dumps(template_params, ensure_ascii=False) if template_params else None, success, result_message, transaction_id )) conn.commit() except Exception as e: logger.warning(f"알림톡 로그 DB 저장 실패: {e}") def _send_alimtalk(template_code, recipient_no, template_params): """ 알림톡 발송 공통 함수 Args: template_code: 템플릿 코드 recipient_no: 수신 번호 (01012345678) template_params: 템플릿 변수 딕셔너리 Returns: tuple: (성공 여부, 메시지) """ url = f'{API_BASE}/messages' headers = { 'Content-Type': 'application/json;charset=UTF-8', 'X-Secret-Key': SECRET_KEY } data = { 'senderKey': SENDER_KEY, 'templateCode': template_code, 'recipientList': [ { 'recipientNo': recipient_no, 'templateParameter': template_params } ] } try: resp = requests.post(url, headers=headers, json=data, timeout=10) result = resp.json() if resp.status_code == 200 and result.get('header', {}).get('isSuccessful'): logger.info(f"알림톡 발송 성공: {template_code} → {recipient_no}") return (True, "발송 성공") else: # 상세 에러 추출: sendResults[0].resultMessage 우선, 없으면 header.resultMessage header_msg = result.get('header', {}).get('resultMessage', '') send_results = result.get('message', {}).get('sendResults', []) detail_msg = send_results[0].get('resultMessage', '') if send_results else '' # 상세 에러가 있으면 그걸 사용, 없으면 header 에러 error_msg = detail_msg if detail_msg and detail_msg != 'SUCCESS' else header_msg if not error_msg: error_msg = str(result) logger.warning(f"알림톡 발송 실패: {template_code} → {recipient_no}: {error_msg}") return (False, error_msg) except requests.exceptions.Timeout: logger.warning(f"알림톡 발송 타임아웃: {template_code} → {recipient_no}") return (False, "타임아웃") except Exception as e: logger.warning(f"알림톡 발송 오류: {template_code} → {recipient_no}: {e}") return (False, str(e)) def build_item_summary(items): """구매 품목 요약 문자열 생성 (예: '타이레놀 외 3건') Note: 카카오 알림톡 템플릿 변수는 14자 제한 (에러: "Blacklist can't use more than 14 characters in template value.") 특수문자(%, 괄호 등)는 문제없이 발송 가능! """ if not items: return "약국 구매" first = items[0]['name'] first = first.strip() if len(items) == 1: # 단일 품목: 14자 제한 (그냥 자름) return first[:14] # 복수 품목: "외 N건" 붙으므로 전체 14자 맞춤 suffix = f" 외 {len(items) - 1}건" max_first = 14 - len(suffix) return f"{first[:max_first]}{suffix}" def send_mileage_claim_alimtalk(phone, name, points, balance, items=None, user_id=None, trigger_source='kiosk', transaction_id=None): """ 마일리지 적립 완료 알림톡 발송 Args: phone: 수신 전화번호 (01012345678) name: 고객명 points: 적립 포인트 balance: 적립 후 총 잔액 items: 구매 품목 리스트 [{'name': ..., 'qty': ..., 'total': ...}, ...] user_id: 사용자 ID (로그용) trigger_source: 발송 주체 ('kiosk', 'admin', 'manual') transaction_id: 거래 ID (로그용) Returns: tuple: (성공 여부, 메시지) """ now_kst = datetime.now(KST).strftime('%m/%d %H:%M') item_summary = build_item_summary(items) # MILEAGE_CLAIM_V3 (발송 근거 + 구매품목 포함) 우선 시도 template_code = 'MILEAGE_CLAIM_V3' params = { '고객명': name, '구매품목': item_summary, '적립포인트': f'{points:,}', '총잔액': f'{balance:,}', '적립일시': now_kst, '전화번호': phone } success, msg = _send_alimtalk(template_code, phone, params) # 결과 로그 (V3만 사용, V2 폴백 제거 - V2 반려 상태) _log_to_db(template_code, phone, success, msg, template_params=params, user_id=user_id, trigger_source=trigger_source, transaction_id=transaction_id) return (success, msg) def get_nhn_send_history(start_date, end_date, page=1, page_size=15): """ NHN Cloud API에서 실제 발송 내역 조회 Args: start_date: 시작일 (YYYY-MM-DD HH:mm) end_date: 종료일 (YYYY-MM-DD HH:mm) Returns: list: 발송 메시지 목록 """ url = (f'{API_BASE}/messages' f'?startRequestDate={start_date}' f'&endRequestDate={end_date}' f'&pageNum={page}&pageSize={page_size}') headers = { 'Content-Type': 'application/json;charset=UTF-8', 'X-Secret-Key': SECRET_KEY } try: resp = requests.get(url, headers=headers, timeout=10) data = resp.json() if data.get('messageSearchResultResponse'): return data['messageSearchResultResponse'].get('messages', []) return [] except Exception as e: logger.warning(f"NHN 발송내역 조회 실패: {e}") return []