diff --git a/docs/sources/set-up/open-source/index.md b/docs/sources/set-up/open-source/index.md index b925c7b3..734ec8fc 100644 --- a/docs/sources/set-up/open-source/index.md +++ b/docs/sources/set-up/open-source/index.md @@ -234,8 +234,7 @@ Zvonok.com, complete the following steps: to the variable `ZVONOK_AUDIO_ID` (optional step). 6. To make a call with a specific voice, you can set the `ZVONOK_SPEAKER_ID`. By default, the ID used is `Salli` (optional step). -7. To change the voice message for phone verification, you can set the variable `ZVONOK_VERIFICATION_TEMPLATE` - with the following format (optional step): `Your verification code is $verification_code, have a nice day.`. +7. Create phone number verification campaign with type `tellcode` and assign its ID value to `ZVONOK_VERIFICATION_CAMPAIGN_ID`. 8. To process the call status, it is required to add a postback with the GET/POST method on the side of the zvonok.com service with the following format (optional step): `${ONCALL_BASE_URL}/zvonok/call_status_events?campaign_id={ct_campaign_id}&call_id={ct_call_id}&status={ct_status}&user_choice={ct_user_choice}` diff --git a/engine/apps/base/models/live_setting.py b/engine/apps/base/models/live_setting.py index f6e8b7e5..7c9d39b0 100644 --- a/engine/apps/base/models/live_setting.py +++ b/engine/apps/base/models/live_setting.py @@ -71,7 +71,7 @@ class LiveSetting(models.Model): "ZVONOK_POSTBACK_STATUS", "ZVONOK_POSTBACK_USER_CHOICE", "ZVONOK_POSTBACK_USER_CHOICE_ACK", - "ZVONOK_VERIFICATION_TEMPLATE", + "ZVONOK_VERIFICATION_CAMPAIGN_ID", ) DESCRIPTIONS = { @@ -170,7 +170,7 @@ class LiveSetting(models.Model): "ZVONOK_POSTBACK_STATUS": "'Postback' status (ct_status) query parameter name to validate a postback request.", "ZVONOK_POSTBACK_USER_CHOICE": "'Postback' user choice (ct_user_choice) query parameter name (optional).", "ZVONOK_POSTBACK_USER_CHOICE_ACK": "'Postback' user choice (ct_user_choice) query parameter value for acknowledge alert group (optional).", - "ZVONOK_VERIFICATION_TEMPLATE": "The message template used for phone number verification (optional).", + "ZVONOK_VERIFICATION_CAMPAIGN_ID": "The phone number verification campaign ID. You can get it after verification campaign creation.", } SECRET_SETTING_NAMES = ( diff --git a/engine/apps/zvonok/phone_provider.py b/engine/apps/zvonok/phone_provider.py index e19e771f..4af75b6b 100644 --- a/engine/apps/zvonok/phone_provider.py +++ b/engine/apps/zvonok/phone_provider.py @@ -1,6 +1,5 @@ import logging from random import randint -from string import Template from typing import Optional import requests @@ -12,6 +11,7 @@ from apps.phone_notifications.phone_provider import PhoneProvider, ProviderFlags from apps.zvonok.models.phone_call import ZvonokCallStatuses, ZvonokPhoneCall ZVONOK_CALL_URL = "https://zvonok.com/manager/cabapi_external/api/v1/phones/call/" +ZVONOK_VERIFICATION_CALL_URL = "https://zvonok.com/manager/cabapi_external/api/v1/phones/tellcode/" logger = logging.getLogger(__name__) @@ -96,6 +96,15 @@ class ZvonokPhoneProvider(PhoneProvider): return requests.post(ZVONOK_CALL_URL, params=params) + def _verification_call_create(self, number: str, code: int): + params = { + "public_key": live_settings.ZVONOK_API_KEY, + "campaign_id": live_settings.ZVONOK_VERIFICATION_CAMPAIGN_ID, + "phone": number, + "pincode": code, + } + return requests.post(ZVONOK_VERIFICATION_CALL_URL, params=params) + def _get_graceful_msg(self, body, number): if body: status = body.get("status") @@ -105,34 +114,19 @@ class ZvonokPhoneProvider(PhoneProvider): return f"Failed make call to {number}" def make_verification_call(self, number: str): + body = None code = self._generate_verification_code() cache.set(self._cache_key(number), code, timeout=10 * 60) - codewspaces = " ".join(code) - body = None - speaker = live_settings.ZVONOK_SPEAKER_ID - - if live_settings.ZVONOK_VERIFICATION_TEMPLATE: - message = Template(live_settings.ZVONOK_VERIFICATION_TEMPLATE).safe_substitute( - verification_code=codewspaces + if not live_settings.ZVONOK_VERIFICATION_CAMPAIGN_ID: + raise FailedToStartVerification( + graceful_msg="Failed make verification call, verification campaign id not set." ) - else: - message = f"Your verification code is {codewspaces}" + try: - response = self._call_create( - number, - message, - speaker, - ) - response.raise_for_status() + response = self._verification_call_create(number, code) body = response.json() - if not body: - logger.error("ZvonokPhoneProvider.make_verification_call: failed, empty body") - raise FailedToMakeCall(graceful_msg=f"Failed make verification call to {number}, empty body") - - call_id = body.get("call_id") - if not call_id: - raise FailedToStartVerification(graceful_msg=self._get_graceful_msg(body, number)) + response.raise_for_status() except requests.exceptions.HTTPError as http_err: logger.error(f"ZvonokPhoneProvider.make_verification_call: failed {http_err}") raise FailedToStartVerification(graceful_msg=self._get_graceful_msg(body, number)) diff --git a/engine/apps/zvonok/tests/test_zvonok_provider.py b/engine/apps/zvonok/tests/test_zvonok_provider.py index 04eab9da..5e25045f 100644 --- a/engine/apps/zvonok/tests/test_zvonok_provider.py +++ b/engine/apps/zvonok/tests/test_zvonok_provider.py @@ -3,6 +3,7 @@ from unittest.mock import MagicMock, patch import pytest from django.test import override_settings +from apps.phone_notifications.exceptions import FailedToStartVerification from apps.zvonok.phone_provider import ZvonokPhoneProvider @@ -12,46 +13,37 @@ def provider(): @pytest.mark.django_db -def test_make_verification_call_with_template_set(provider): - verification_code = "123456" +def test_make_verification_call(provider): + verification_code = "123456789" number = "1234567890" - speaker_id = "Salli" - template_value = 'Your code is $verification_code' - excepted_message = 'Your code is 1 2 3 4 5 6' - - with override_settings(ZVONOK_VERIFICATION_TEMPLATE=template_value, ZVONOK_SPEAKER_ID=speaker_id): + campaign_id = "123456" + with override_settings(ZVONOK_VERIFICATION_CAMPAIGN_ID=campaign_id): with patch("django.core.cache.cache.set"): - provider._call_create = MagicMock(return_value=MagicMock(json=lambda: {"call_id": "12345"})) + provider._verification_call_create = MagicMock(return_value=MagicMock(json=lambda: {"status": "ok"})) provider._generate_verification_code = MagicMock(return_value=verification_code) provider.make_verification_call(number) - provider._call_create.assert_called_once_with(number, excepted_message, speaker_id) + provider._verification_call_create.assert_called_once_with(number, verification_code) @pytest.mark.django_db -def test_make_verification_call_with_invalid_template_set(provider): - verification_code = "123456" +def test_make_verification_call_without_campaign_id(provider): number = "1234567890" - speaker_id = "Salli" - template_value = "Your code is" - excepted_message = "Your code is" - - with override_settings(ZVONOK_VERIFICATION_TEMPLATE=template_value, ZVONOK_SPEAKER_ID=speaker_id): - with patch("django.core.cache.cache.set"): - provider._call_create = MagicMock(return_value=MagicMock(json=lambda: {"call_id": "12345"})) - provider._generate_verification_code = MagicMock(return_value=verification_code) + with patch("django.core.cache.cache.set"): + with pytest.raises(FailedToStartVerification): provider.make_verification_call(number) - provider._call_create.assert_called_once_with(number, excepted_message, speaker_id) @pytest.mark.django_db -def test_make_verification_call_without_template_set(provider): - verification_code = "123456" +def test_make_verification_call_with_error(provider): number = "1234567890" - speaker_id = "Salli" - excepted_message = "Your verification code is 1 2 3 4 5 6" - with override_settings(ZVONOK_SPEAKER_ID=speaker_id): + campaign_id = "123456" + + with override_settings(ZVONOK_VERIFICATION_CAMPAIGN_ID=campaign_id): with patch("django.core.cache.cache.set"): - provider._call_create = MagicMock(return_value=MagicMock(json=lambda: {"call_id": "12345"})) - provider._generate_verification_code = MagicMock(return_value=verification_code) - provider.make_verification_call(number) - provider._call_create.assert_called_once_with(number, excepted_message, speaker_id) + with pytest.raises(FailedToStartVerification): + provider._verification_call_create = MagicMock( + return_value=MagicMock( + json={"status": "error", "data": "Form isn't valid: * campaign_id\n * Invalid campaign type"} + ) + ) + provider.make_verification_call(number) diff --git a/engine/settings/base.py b/engine/settings/base.py index 48d3fbdd..c528ba3c 100644 --- a/engine/settings/base.py +++ b/engine/settings/base.py @@ -912,7 +912,7 @@ ZVONOK_POSTBACK_CAMPAIGN_ID = os.getenv("ZVONOK_POSTBACK_CAMPAIGN_ID", "campaign ZVONOK_POSTBACK_STATUS = os.getenv("ZVONOK_POSTBACK_STATUS", "status") ZVONOK_POSTBACK_USER_CHOICE = os.getenv("ZVONOK_POSTBACK_USER_CHOICE", None) ZVONOK_POSTBACK_USER_CHOICE_ACK = os.getenv("ZVONOK_POSTBACK_USER_CHOICE_ACK", None) -ZVONOK_VERIFICATION_TEMPLATE = os.getenv("ZVONOK_VERIFICATION_TEMPLATE", None) +ZVONOK_VERIFICATION_CAMPAIGN_ID = os.getenv("ZVONOK_VERIFICATION_CAMPAIGN_ID", None) DETACHED_INTEGRATIONS_SERVER = getenv_boolean("DETACHED_INTEGRATIONS_SERVER", default=False)