Невозможно отправить OTP через Twilio в Django

Я работаю над реализацией системы OTP в моем Django-проекте с использованием Twilio. Я успешно получил номер телефона Twilio, но сообщение OTP не отправляется на мобильный номер пользователя. Ниже приведены детали моей установки:

Реализация кода

  1. views.py:
def send_otp_view(request):
    if request.method == "POST":
        data = json.loads(request.body)
        phone_number = data.get('phone_number')

        # Generate a random 6-digit OTP
        otp = random.randint(100000, 999999)

        # Send OTP via Twilio (or any other SMS provider)
        try:
            client = Client(settings.TWILIO_ACCOUNT_SID, settings.TWILIO_AUTH_TOKEN)
            message = client.messages.create(
                body=f"Your OTP is {otp}. Please use this to verify your number.",
                from_=settings.TWILIO_PHONE_NUMBER,
                to=phone_number
            )

            # Optionally, store the OTP in a session or database
            request.session['otp'] = otp
            request.session['phone_number'] = phone_number

            return JsonResponse({'status': 'success', 'otp': otp})  # Return OTP for validation
        except Exception as e:
            return JsonResponse({'status': 'error', 'message': str(e)})

    return JsonResponse({'status': 'error', 'message': 'Invalid request'})



def address_view(request):
    if request.user.is_authenticated:
        user_addresses = Address.objects.filter(user=request.user)

        if request.method == "POST":
            # Get the OTP from the form
            entered_otp = request.POST.get("otp")

            # Check if the OTP matches
            if entered_otp == str(request.session.get('otp')):
                # Proceed with saving the address if OTP is correct
                mobile = request.POST.get("mobile")
                email = request.POST.get("email")
                pin = request.POST.get("pin")
                region = request.POST.get("region")
                address = request.POST.get("address")
                landmark = request.POST.get("landmark")
                name = request.POST.get("name")

                new_address = Address.objects.create(
                    user=request.user,
                    mobile=mobile,
                    email=email,
                    pin=pin,
                    region=region,
                    address=address,
                    landmark=landmark,
                    name=name,
                )
                messages.success(request, "Address Added Successfully.")
                return redirect("core:address")
            else:
                messages.error(request, "Invalid OTP. Please try again.")
                return redirect("core:address")

        context = {
            "user_addresses": user_addresses,
        }
        return render(request, 'others/address-book.html', context)
    else:
        print("User is not authenticated")
        return redirect("core:login")

2. HTML-форма:

<div class="gl-inline">
    <div class="u-s-m-b-30">
        <label class="gl-label" for="address-phone">PHONE *</label>
        <input 
            class="input-text input-text--primary-style" 
            type="text" 
            id="address-phone" 
            name="mobile" 
            placeholder="Enter 10-digit phone number" 
            oninput="validatePhoneNumber()" 
            maxlength="10">
        <button type="button" id="send-otp-btn" style="display:none;" onclick="sendOTP()">Send OTP</button>
    </div>
    <div id="otp-box" style="display: none;" class="u-s-m-b-30">
        <label class="gl-label" for="otp-input">ENTER OTP *</label>
        <input 
            class="input-text input-text--primary-style" 
            type="text" 
            id="otp-input" 
            name="otp" 
            placeholder="Enter OTP" 
            maxlength="6" 
            oninput="validateOTP()">
        <span id="otp-feedback" style="display: none; font-size: 14px;"></span>
    </div>
    <div class="u-s-m-b-30">
        <label class="gl-label" for="address-street">STREET ADDRESS *</label>
        <input class="input-text input-text--primary-style" type="text" id="address-street" placeholder="House Name and Street" name="address">
    </div>
</div>

3. Настройки Twilio (settings.py):

TWILIO_ACCOUNT_SID = 'YOUR_ACCOUNT_SID'
TWILIO_AUTH_TOKEN = 'YOUR_AUTH_TOKEN'
TWILIO_PHONE_NUMBER = '+1234567890'  # Twilio number with country code

4. Внешняя проверка (JavaScript):

console.log("OTP");

let sentOTP = null; // Store the sent OTP

function validatePhoneNumber() {
    const phoneInput = document.getElementById('address-phone');
    const otpBox = document.getElementById('otp-box');
    const sendOtpBtn = document.getElementById('send-otp-btn');

    // Check if the phone number is 10 digits long
    if (phoneInput.value.length === 10) {
        otpBox.style.display = 'block'; // Show OTP box
        sendOtpBtn.style.display = 'inline-block'; // Show OTP button
    } else {
        otpBox.style.display = 'none'; // Hide OTP box
        sendOtpBtn.style.display = 'none'; // Hide OTP button
    }
}

function sendOTP() {
    const phoneNumber = document.getElementById('address-phone').value;

    // Send an AJAX request to the Django view to generate and send OTP
    fetch('/send-otp/', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'X-CSRFToken': getCookie('csrftoken') // Handle CSRF token for security
        },
        body: JSON.stringify({ phone_number: phoneNumber })
    })
    .then(response => response.json())
    .then(data => {
        if (data.status === 'success') {
            sentOTP = data.otp; // Save the OTP for later validation
            console.log("OTP sent successfully");
        } else {
            alert("Error sending OTP");
        }
    })
    .catch(error => {
        console.error('Error:', error);
        alert('Failed to send OTP');
    });
}

function validateOTP() {
    const otpInput = document.getElementById('otp-input');
    const feedback = document.getElementById('otp-feedback');

    // Compare entered OTP with the sent OTP
    if (otpInput.value === sentOTP) {
        feedback.style.display = 'block';
        feedback.style.color = 'green';
        feedback.textContent = 'OTP is correct!';
    } else if (otpInput.value.length === sentOTP.length) {
        feedback.style.display = 'block';
        feedback.style.color = 'red';
        feedback.textContent = 'Incorrect OTP, please try again.';
    } else {
        feedback.style.display = 'none'; // Hide feedback when incomplete
    }
}

// Utility to get the CSRF token from cookies
function getCookie(name) {
    const cookieValue = document.cookie.match('(^|;)\\s*' + name + '\\s*=\\s*([^;]+)');
    return cookieValue ? cookieValue.pop() : '';
}

Проблема:

  1. В форму вводится правильный номер мобильного телефона, и POST-запрос достигает представления Django.
  2. Вызываются методы Client и messages.create Twilio, но SMS не отправляется.
  3. В журналах Django я вижу следующую ошибку:
Error sending OTP: TwilioRestException: [Error message here]

Что я пробовал:

  1. Убедитесь, что учетные данные Twilio (SID аккаунта, Auth Token и номер телефона) верны.
  2. Убедитесь, что номер телефона имеет формат E.164 с правильным кодом страны.
  3. Подтвердите, что пробная учетная запись Twilio проверила номер телефона получателя.
  4. Проверили журналы сообщений Twilio, но никаких записей о неудачном сообщении не появилось.
  5. Проверил функциональность Twilio с помощью отдельного скрипта, который работает:
from twilio.rest import Client

client = Client('YOUR_ACCOUNT_SID', 'YOUR_AUTH_TOKEN')
message = client.messages.create(
    body="Test message",
    from_='+1234567890',
    to='+9876543210'
)
print(message.sid)

Ожидаемое поведение: Сообщение OTP должно быть отправлено на введенный номер мобильного телефона.

Вопрос: Что может быть причиной этой проблемы и как я могу ее решить? Может ли это быть связано с настройками моего аккаунта Twilio или конфигурацией Django? Любая помощь или понимание будут оценены по достоинству!

Дополнительные примечания:

  1. Я использую пробную учетную запись Twilio.
  2. Номер телефона получателя проверен на панели управления Twilio.
  3. В моей настройке нет ограничений сети или брандмауэра.

Основные улучшения и шаги по устранению неполадок:

1. Форматирование телефонных номеров:

Добавлена правильная проверка и форматирование телефонных номеров с помощью библиотеки phonenumbers

Убедитесь, что номера имеют формат E.164, требуемый Twilio

Правильно обрабатывает коды разных стран

from django.http import JsonResponse
from django.conf import settings
from twilio.rest import Client
from twilio.base.exceptions import TwilioRestException
import random
import json
import phonenumbers

def format_phone_number(phone_number, country_code='US'):
    try:
        parsed_number = phonenumbers.parse(phone_number, country_code)
        if not phonenumbers.is_valid_number(parsed_number):
            return None
        return phonenumbers.format_number(parsed_number, 
phonenumbers.PhoneNumberFormat.E164)
    except phonenumbers.NumberParseException:
        return None

def send_otp_view(request):
    if request.method != "POST":
        return JsonResponse({'status': 'error', 'message': 'Invalid 
request method'})

    try:
        data = json.loads(request.body)
        phone_number = data.get('phone_number')
    
        if not phone_number:
            return JsonResponse({'status': 'error', 'message': 'Phone 
number is required'})
    
        formatted_number = format_phone_number(phone_number)
        if not formatted_number:
            return JsonResponse({
                'status': 'error', 
                'message': 'Invalid phone number format. Please include 
country code.'
            })
    
        otp = str(random.randint(100000, 999999))
    
        try:
            client = Client(settings.TWILIO_ACCOUNT_SID, 
settings.TWILIO_AUTH_TOKEN)
            message = client.messages.create(
                body=f"Your verification code is: {otp}",
                from_=settings.TWILIO_PHONE_NUMBER,
                to=formatted_number
            )
        
            request.session['otp'] = otp
            request.session['phone_number'] = formatted_number
        
            return JsonResponse({
                'status': 'success',
                'message': 'OTP sent successfully',
                'otp': otp
            })
        
        except TwilioRestException as e:
            print(f"Twilio Error: {str(e)}")
            error_message = 'Failed to send OTP. '
            if e.code == 20003:
                error_message += 'Authentication failed. Check Twilio 
credentials.'
            elif e.code == 21211:
                error_message += 'Invalid phone number format.'
            elif e.code == 21608:
                error_message += 'Phone number not verified in trial 
account.'
            else:
                error_message += str(e)
            
            return JsonResponse({'status': 'error', 'message': 
error_message})
        
    except Exception as e:
        return JsonResponse({'status': 'error', 'message': f'Server error: 
{str(e)}'})

2. Обработка ошибок:

Добавлена специальная обработка ошибок для распространенных кодов ошибок Twilio

Предоставляет четкие сообщения об ошибках для отладки

Правильно ловит и сообщает о проблемах аутентификации

3. Улучшения в области безопасности:

Улучшенная обработка сессий для хранения OTP

Более надежная проверка ввода

Правильная санация сообщений об ошибках

Для реализации этого решения:

# Установите необходимые пакеты:

pip install twilio phonenumbers

# Во фронтенде JavaScript измените обработку ошибок:

function sendOTP() {
    const phoneNumber = document.getElementById('address-phone').value;
    // Add country code if not provided by user
    const formattedNumber = phoneNumber.startsWith('+') ? phoneNumber : 
`+${phoneNumber}`;

    fetch('/send-otp/', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'X-CSRFToken': getCookie('csrftoken')
        },
        body: JSON.stringify({ phone_number: formattedNumber })
    })
    .then(response => response.json())
    .then(data => {
        if (data.status === 'success') {
            alert('OTP sent successfully!');
        } else {
            alert(`Error: ${data.message}`);
        }
    })
    .catch(error => {
        console.error('Error:', error);
        alert('Failed to send OTP. Please try again.');
    });
}

Дополнительные шаги по устранению неисправностей:

# Проверьте свою консоль Twilio, чтобы убедиться:

- Ваш счет активен и имеет достаточное количество кредитов

Номер получателя проверен (требуется для пробных счетов)

- Ваш телефонный номер Twilio способен отправлять SMS

# Проверьте настройки Django:

- Все учетные данные Twilio установлены правильно

- Режим EBUG включен во время тестирования

- На вашем сервере есть доступ в Интернет для доступа к API Twilio

Вернуться на верх