SillaAMP_V2/A_core/sms_utils.py

225 lines
8.9 KiB
Python
Raw Permalink Normal View History

import requests
import json
import time
import hashlib
import hmac
import base64
from django.conf import settings
from typing import Dict, Any, Optional
class NaverCloudSMS:
"""네이버 클라우드 플랫폼 SMS 서비스 클래스"""
def __init__(self):
self.access_key = getattr(settings, 'NAVER_CLOUD_ACCESS_KEY', '')
self.secret_key = getattr(settings, 'NAVER_CLOUD_SECRET_KEY', '')
self.service_id = getattr(settings, 'NAVER_CLOUD_SMS_SERVICE_ID', '')
self.sender_phone = getattr(settings, 'NAVER_CLOUD_SMS_SENDER_PHONE', '')
# API 엔드포인트
self.base_url = "https://sens.apigw.ntruss.com"
self.sms_url = f"{self.base_url}/sms/v2/services/{self.service_id}/messages"
def _make_signature(self, timestamp: str) -> str:
"""네이버 클라우드 API 서명 생성"""
space = " "
new_line = "\n"
method = "POST"
url = f"/sms/v2/services/{self.service_id}/messages"
message = method + space + url + new_line + timestamp + new_line + self.access_key
message = message.encode('utf-8')
signing_key = base64.b64encode(
hmac.new(
self.secret_key.encode('utf-8'),
message,
digestmod=hashlib.sha256
).digest()
).decode('utf-8')
return signing_key
def send_sms(self, phone_number: str, message: str) -> Dict[str, Any]:
"""SMS 발송"""
try:
timestamp = str(int(time.time() * 1000))
signature = self._make_signature(timestamp)
headers = {
'Content-Type': 'application/json; charset=utf-8',
'x-ncp-apigw-timestamp': timestamp,
'x-ncp-iam-access-key': self.access_key,
'x-ncp-apigw-signature-v2': signature
}
data = {
'type': 'SMS',
'contentType': 'COMM',
'countryCode': '82',
'from': self.sender_phone,
'content': message,
'messages': [
{
'to': phone_number
}
]
}
response = requests.post(
self.sms_url,
headers=headers,
data=json.dumps(data)
)
if response.status_code == 202:
result = response.json()
return {
'success': True,
'request_id': result.get('requestId'),
'status_code': result.get('statusCode'),
'status_name': result.get('statusName')
}
else:
return {
'success': False,
'error': f'HTTP {response.status_code}: {response.text}'
}
except Exception as e:
return {
'success': False,
'error': str(e)
}
def send_verification_code(self, phone_number: str, verification_code: str) -> Dict[str, Any]:
"""인증번호 SMS 발송"""
message = f"[신라AMP] 인증번호는 [{verification_code}] 입니다. 3분 이내에 입력해주세요."
# 발신번호가 설정되지 않은 경우 에러 반환
if not self.sender_phone:
print(f"[ERROR] 발신번호가 설정되지 않았습니다.")
return {
'success': False,
'error': '발신번호가 설정되지 않았습니다. .env 파일을 확인해주세요.'
}
print(f"[INFO] 실제 SMS 발송 시도: {phone_number} - {verification_code}")
return self.send_sms(phone_number, message)
def send_withdrawal_approval_sms(self, phone_number: str, name: str) -> Dict[str, Any]:
"""탈퇴 승인 SMS 발송"""
message = f"[신라AMP] {name}님의 회원탈퇴 요청이 처리되었습니다."
if not self.sender_phone:
print(f"[ERROR] 발신번호가 설정되지 않았습니다.")
return {
'success': False,
'error': '발신번호가 설정되지 않았습니다.'
}
print(f"[INFO] 탈퇴 승인 SMS 발송: {phone_number} - {name}")
return self.send_sms(phone_number, message)
def send_withdrawal_rejection_sms(self, phone_number: str, name: str, reason: str = None) -> Dict[str, Any]:
"""탈퇴 거부 SMS 발송"""
if reason:
# message = f"[신라AMP] {name}님의 회원탈퇴 요청이 거부되었습니다. 사유: {reason}"
message = f"[신라AMP] {name}님의 회원탈퇴 요청이 거부되었습니다. 자세한 내용은 관리자에게 문의해주세요."
else:
message = f"[신라AMP] {name}님의 회원탈퇴 요청이 거부되었습니다. 자세한 내용은 관리자에게 문의해주세요."
if not self.sender_phone:
print(f"[ERROR] 발신번호가 설정되지 않았습니다.")
return {
'success': False,
'error': '발신번호가 설정되지 않았습니다.'
}
print(f"[INFO] 탈퇴 거부 SMS 발송: {phone_number} - {name}")
return self.send_sms(phone_number, message)
# 전역 인스턴스
sms_service = NaverCloudSMS()
def send_verification_sms(phone_number: str, verification_code: str) -> Dict[str, Any]:
"""인증번호 SMS 발송 함수 (편의 함수)"""
# SMS 테스트 모드 확인
sms_test_mode = getattr(settings, 'SMS_TEST_MODE', False)
if sms_test_mode:
# 테스트 모드: 콘솔에 인증번호 출력
print("=" * 60)
print("🔔 [SMS 테스트 모드] 인증번호 발송")
print(f"📱 수신번호: {phone_number}")
print(f"🔑 인증번호: {verification_code}")
print(f"📝 메시지: [신라AMP] 인증번호는 [{verification_code}]입니다. 3분 이내에 입력해주세요.")
print("=" * 60)
return {
'success': True,
'message': 'SMS 테스트 모드: 콘솔에 인증번호가 출력되었습니다.',
'test_mode': True,
'verification_code': verification_code # 테스트 모드에서만 포함
}
else:
# 실제 SMS 발송 모드
print(f"[DEBUG] 실제 SMS 발송 시도: {phone_number} - {verification_code}")
print(f"[DEBUG] Access Key: {sms_service.access_key}")
print(f"[DEBUG] Service ID: {sms_service.service_id}")
print(f"[DEBUG] Sender Phone: {sms_service.sender_phone}")
result = sms_service.send_verification_code(phone_number, verification_code)
print(f"[DEBUG] SMS 발송 결과: {result}")
return result
def send_withdrawal_approval_sms(phone_number: str, name: str) -> Dict[str, Any]:
"""탈퇴 승인 SMS 발송 함수 (편의 함수)"""
sms_test_mode = getattr(settings, 'SMS_TEST_MODE', False)
if sms_test_mode:
message = f"[신라AMP] {name}님의 회원탈퇴가 승인되었습니다. 그동안 이용해주셔서 감사했습니다."
print("=" * 60)
print("🔔 [SMS 테스트 모드] 탈퇴 승인 알림")
print(f"📱 수신번호: {phone_number}")
print(f"👤 대상자: {name}")
print(f"📝 메시지: {message}")
print("=" * 60)
return {
'success': True,
'message': 'SMS 테스트 모드: 탈퇴 승인 알림이 콘솔에 출력되었습니다.',
'test_mode': True
}
else:
return sms_service.send_withdrawal_approval_sms(phone_number, name)
def send_withdrawal_rejection_sms(phone_number: str, name: str, reason: str = None) -> Dict[str, Any]:
"""탈퇴 거부 SMS 발송 함수 (편의 함수)"""
sms_test_mode = getattr(settings, 'SMS_TEST_MODE', False)
if sms_test_mode:
if reason:
message = f"[신라AMP] {name}님의 회원탈퇴 요청이 거부되었습니다. 사유: {reason}"
else:
message = f"[신라AMP] {name}님의 회원탈퇴 요청이 거부되었습니다. 자세한 사항은 관리자에게 문의해주세요."
print("=" * 60)
print("🔔 [SMS 테스트 모드] 탈퇴 거부 알림")
print(f"📱 수신번호: {phone_number}")
print(f"👤 대상자: {name}")
print(f"📝 메시지: {message}")
if reason:
print(f"❌ 거부 사유: {reason}")
print("=" * 60)
return {
'success': True,
'message': 'SMS 테스트 모드: 탈퇴 거부 알림이 콘솔에 출력되었습니다.',
'test_mode': True
}
else:
return sms_service.send_withdrawal_rejection_sms(phone_number, name, reason)