diff --git a/CHANGELOG.md b/CHANGELOG.md
index e8768699..df0ca37b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
+### Added
+
+- Improved zvonok verification call @sreway ([#3768](https://github.com/grafana/oncall/pull/3768))
+
### Changed
- Allow mobile app to access escalation options endpoints @imtoori ([#3847](https://github.com/grafana/oncall/pull/3847))
diff --git a/docs/sources/open-source/_index.md b/docs/sources/open-source/_index.md
index 6491cc7b..d25c35b8 100644
--- a/docs/sources/open-source/_index.md
+++ b/docs/sources/open-source/_index.md
@@ -224,7 +224,9 @@ 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 process the call status, it is required to add a postback with the GET/POST method on the side of the zvonok.com
+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.`.
+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 324bc762..48c3ac06 100644
--- a/engine/apps/base/models/live_setting.py
+++ b/engine/apps/base/models/live_setting.py
@@ -70,6 +70,7 @@ class LiveSetting(models.Model):
"ZVONOK_POSTBACK_STATUS",
"ZVONOK_POSTBACK_USER_CHOICE",
"ZVONOK_POSTBACK_USER_CHOICE_ACK",
+ "ZVONOK_VERIFICATION_TEMPLATE",
)
DESCRIPTIONS = {
@@ -167,6 +168,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).",
}
SECRET_SETTING_NAMES = (
diff --git a/engine/apps/zvonok/phone_provider.py b/engine/apps/zvonok/phone_provider.py
index 49d7c67b..e19e771f 100644
--- a/engine/apps/zvonok/phone_provider.py
+++ b/engine/apps/zvonok/phone_provider.py
@@ -1,5 +1,6 @@
import logging
from random import randint
+from string import Template
from typing import Optional
import requests
@@ -104,15 +105,25 @@ class ZvonokPhoneProvider(PhoneProvider):
return f"Failed make call to {number}"
def make_verification_call(self, number: str):
- code = str(randint(100000, 999999))
+ 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
+ )
+ else:
+ message = f"Your verification code is {codewspaces}"
try:
- response = self._call_create(number, f"Your verification code is {codewspaces}", speaker)
+ response = self._call_create(
+ number,
+ message,
+ speaker,
+ )
response.raise_for_status()
body = response.json()
if not body:
@@ -139,6 +150,9 @@ class ZvonokPhoneProvider(PhoneProvider):
def _cache_key(self, number):
return f"zvonok_provider_{number}"
+ def _generate_verification_code(self):
+ return str(randint(100000, 999999))
+
@property
def flags(self) -> ProviderFlags:
return ProviderFlags(
diff --git a/engine/apps/zvonok/tests/__init__.py b/engine/apps/zvonok/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/engine/apps/zvonok/tests/test_zvonok_provider.py b/engine/apps/zvonok/tests/test_zvonok_provider.py
new file mode 100644
index 00000000..04eab9da
--- /dev/null
+++ b/engine/apps/zvonok/tests/test_zvonok_provider.py
@@ -0,0 +1,57 @@
+from unittest.mock import MagicMock, patch
+
+import pytest
+from django.test import override_settings
+
+from apps.zvonok.phone_provider import ZvonokPhoneProvider
+
+
+@pytest.fixture
+def provider():
+ return ZvonokPhoneProvider()
+
+
+@pytest.mark.django_db
+def test_make_verification_call_with_template_set(provider):
+ verification_code = "123456"
+ 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):
+ 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)
+
+
+@pytest.mark.django_db
+def test_make_verification_call_with_invalid_template_set(provider):
+ verification_code = "123456"
+ 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)
+ 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"
+ 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):
+ 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)
diff --git a/engine/settings/base.py b/engine/settings/base.py
index 1c5b930a..f3a96eb6 100644
--- a/engine/settings/base.py
+++ b/engine/settings/base.py
@@ -842,6 +842,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)
DETACHED_INTEGRATIONS_SERVER = getenv_boolean("DETACHED_INTEGRATIONS_SERVER", default=False)