Управление подписками¶
Подписки (subscriptions) — это связи между абонентами и пакетами каналов, которые определяют, к каким каналам имеет доступ каждый абонент. Система подписок является ключевым механизмом монетизации IPTV-сервиса в Catena.
Что такое подписка¶
Подписка в Catena — это активная связь между абонентом и пакетом каналов. Когда у абонента есть подписка на пакет, он автоматически получает доступ ко всем каналам, входящим в этот пакет.
Ключевая концепция:
Абонент → Подписка → Пакет → Каналы → Просмотр
- Абонент подписывается на один или несколько пакетов
- Каждый пакет содержит набор каналов
- Абонент получает доступ ко всем каналам из всех своих пакетов
- При попытке просмотра система проверяет наличие подписки
Основные возможности:
- Гибкое управление доступом — подключение и отключение пакетов в реальном времени
- Множественные подписки — абонент может быть подписан на несколько пакетов одновременно
- Бесплатные пакеты — автоматический доступ к базовому контенту для всех абонентов
- Журналирование — полная история всех изменений подписок
- API-first подход — простая интеграция с биллинговыми системами
Типичный workflow:
- Биллинговая система получает оплату от пользователя
- Биллинг вызывает API Catena для создания подписки
- Catena немедленно предоставляет доступ к каналам пакета
- Абонент начинает смотреть каналы
- По окончании периода биллинг отключает подписку
- Доступ к платным каналам автоматически блокируется
Жизненный цикл подписки¶
Создание подписки¶
Когда создаётся подписка:
- При оплате пакета через биллинговую систему
- При ручном подключении администратором
- При активации промо-кода или бонуса
- При предоставлении пробного периода
Что происходит при создании:
- Создаётся запись в базе данных о связи абонент-пакет
- Абонент немедленно получает доступ ко всем каналам пакета
- Запись добавляется в журнал операций (тип
createPackageSubscriber
) - При следующем запросе плеера список доступных каналов обновляется
Активная подписка¶
Во время действия подписки:
- Абонент может смотреть все каналы из пакета без ограничений
- Система логирует все сеансы просмотра
- Поле
packages
у абонента содержит ID активных пакетов - Streaming-сервер проверяет права доступа при каждом запросе потока
Отключение подписки¶
Когда отключается подписка:
- По истечении оплаченного периода
- При отмене подписки пользователем
- При блокировке абонента администратором
- При удалении пакета из системы
Что происходит при отключении:
- Удаляется запись о связи абонент-пакет
- Абонент немедленно теряет доступ к каналам этого пакета
- Запись добавляется в журнал операций (тип
deletePackageSubscriber
) - Активные сеансы просмотра каналов пакета прерываются
Создание подписки¶
Через веб-интерфейс¶
- Откройте карточку абонента в разделе "Абоненты"
- Перейдите на вкладку "Подписки" или "Пакеты"
- Нажмите "Добавить подписку"
- Выберите пакет из выпадающего списка доступных пакетов
- Подтвердите добавление
Абонент немедленно получит доступ ко всем каналам выбранного пакета.
Через Management API¶
Создать подписку абонента на пакет:
curl -X POST https://your-catena-domain.com/tv-management/api/v1/packages-subscribers \
-H "X-Auth-Token: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"subscriberId": "sKl9SW3AAAE.",
"packageId": "pKl9SW3AAAE."
}'
Параметры запроса:
- subscriberId (обязательно) — ID абонента, которому подключается пакет
- packageId (обязательно) — ID пакета для подключения
Ответ:
{
"subscriberId": "sKl9SW3AAAE.",
"packageId": "pKl9SW3AAAE.",
"portalId": "pKl9SW3AAAE."
}
Важные моменты:
- Абонент и пакет должны принадлежать одному порталу
- Если подписка уже существует, API вернёт ошибку
- Изменения вступают в силу немедленно
- Операция записывается в журнал
Удаление подписки¶
Через веб-интерфейс¶
- Откройте карточку абонента
- Перейдите на вкладку "Подписки"
- Найдите пакет в списке активных подписок
- Нажмите "Удалить" или "Отключить"
- Подтвердите отключение
Абонент немедленно потеряет доступ к каналам этого пакета.
Через Management API¶
Удалить подписку абонента на пакет:
curl -X DELETE https://your-catena-domain.com/tv-management/api/v1/packages-subscribers \
-H "X-Auth-Token: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"subscriberId": "sKl9SW3AAAE.",
"packageId": "pKl9SW3AAAE."
}'
Параметры запроса:
- subscriberId (обязательно) — ID абонента
- packageId (обязательно) — ID пакета для отключения
Ответ:
HTTP 201 - подписка удалена
Важные моменты:
- Если подписки не существует, API вернёт ошибку
- Активные сеансы просмотра будут прерваны
- Изменения вступают в силу немедленно
- Операция записывается в журнал
Просмотр подписок¶
Подписки конкретного абонента¶
Получить список пакетов абонента:
curl -X GET https://your-catena-domain.com/tv-management/api/v1/subscribers/sKl9SW3AAAE. \
-H "X-Auth-Token: your-api-key"
Ответ:
{
"subscriberId": "sKl9SW3AAAE.",
"portalId": "pKl9SW3AAAE.",
"name": "Иван Петров",
"phoneCountryCode": "7",
"phone": "9161234567",
"playback_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"packages": ["pKl9SW3AAAE.", "sportKl9SW3AAAE."]
}
Поле packages
содержит массив ID всех пакетов, на которые подписан абонент.
Подписчики конкретного пакета¶
К сожалению, прямого API для получения списка абонентов пакета нет. Используйте журнал операций или получите всех абонентов и отфильтруйте по packages
:
# Получить всех абонентов
curl -X GET https://your-catena-domain.com/tv-management/api/v1/subscribers \
-H "X-Auth-Token: your-api-key" \
| jq '.subscribers[] | select(.packages[] | contains("pKl9SW3AAAE."))'
История подписок через журнал операций¶
Получить все операции с подписками конкретного абонента:
curl -X GET "https://your-catena-domain.com/tv-management/api/v1/operations?subscriberId=sKl9SW3AAAE.&type=createPackageSubscriber&type=deletePackageSubscriber" \
-H "X-Auth-Token: your-api-key"
Получить операции с конкретным пакетом:
curl -X GET "https://your-catena-domain.com/tv-management/api/v1/operations?packageId=pKl9SW3AAAE." \
-H "X-Auth-Token: your-api-key"
Ответ:
{
"operations": [
{
"operationId": "opKl9SW3AAAE.",
"type": "createPackageSubscriber",
"subscriberId": "sKl9SW3AAAE.",
"packageId": "pKl9SW3AAAE.",
"portalId": "pKl9SW3AAAE.",
"createdAt": "2024-10-16T10:05:00Z",
"payload": {
"packageId": "pKl9SW3AAAE.",
"subscriberId": "sKl9SW3AAAE."
}
},
{
"operationId": "opKl9SW3AAAB.",
"type": "deletePackageSubscriber",
"subscriberId": "sKl9SW3AAAE.",
"packageId": "pKl9SW3AAAE.",
"portalId": "pKl9SW3AAAE.",
"createdAt": "2024-10-20T15:30:00Z",
"payload": {
"packageId": "pKl9SW3AAAE.",
"subscriberId": "sKl9SW3AAAE."
}
}
],
"next": "cursor-for-next-page"
}
Типы операций:
createPackageSubscriber
— создание подпискиdeletePackageSubscriber
— удаление подпискиautoCreateSubscriber
— автоматическое создание абонента (может включать подписку на базовый пакет)
Бесплатные пакеты портала¶
Catena поддерживает концепцию "бесплатных пакетов" — пакетов, которые автоматически доступны всем абонентам портала без явного создания подписки.
Концепция бесплатных пакетов¶
Как это работает:
- В настройках портала определяется список бесплатных пакетов
- Все абоненты портала автоматически получают доступ к каналам из этих пакетов
- Не требуется создавать индивидуальные подписки для каждого абонента
- Идеально для базового контента, демо-каналов, рекламных каналов
Применение:
- Базовый контент — федеральные каналы, доступные всем
- Пробный период — демо-контент для новых пользователей
- Промо-каналы — рекламные и информационные каналы
- Социально значимые каналы — обязательные к распространению каналы
Управление бесплатными пакетами¶
Просмотр бесплатных пакетов портала:
curl -X GET https://your-catena-domain.com/tv-management/api/v1/portal \
-H "X-Auth-Token: your-api-key"
Ответ:
{
"portalId": "pKl9SW3AAAE.",
"name": "my-iptv-portal",
"domain": "iptv.example.com",
"freePackages": ["basicKl9SW3AAAE.", "demoKl9SW3AAAE."],
"branding": {
"title": "My IPTV Service",
"description": "Premium IPTV streaming"
}
}
Добавить пакет в список бесплатных:
curl -X POST https://your-catena-domain.com/tv-management/api/v1/portal/free-packages/basicKl9SW3AAAE. \
-H "X-Auth-Token: your-api-key"
Удалить пакет из списка бесплатных:
curl -X DELETE https://your-catena-domain.com/tv-management/api/v1/portal/free-packages/basicKl9SW3AAAE. \
-H "X-Auth-Token: your-api-key"
Важно:
- Изменения в бесплатных пакетах применяются ко всем абонентам мгновенно
- При добавлении — все абоненты получают доступ к каналам пакета
- При удалении — доступ теряют только те, у кого нет явной подписки
Интеграция с биллинговыми системами¶
Архитектура интеграции¶
Типичная схема:
[Биллинговая система] ←→ [API Catena] ←→ [Streaming сервер]
↓ ↓ ↓
Платежи Подписки Доступ к каналам
Ответственность биллинга:
- Приём платежей от пользователей
- Управление тарифами и периодами подписки
- Отслеживание окончания подписок
- Вызов API Catena для подключения/отключения пакетов
Ответственность Catena:
- Управление доступом к каналам
- Проверка прав при просмотре
- Логирование активности абонентов
- Предоставление статистики просмотров
Примеры интеграции¶
Пример 1: Webhook при оплате¶
Биллинг отправляет webhook в ваш сервис при успешной оплате:
from flask import Flask, request
import requests
app = Flask(__name__)
CATENA_API_URL = "https://catena.example.com/tv-management/api/v1"
CATENA_API_KEY = "your-api-key"
@app.route('/billing-webhook', methods=['POST'])
def billing_webhook():
data = request.json
if data['event'] == 'payment.success':
# Получили оплату - активируем подписку
subscriber_id = get_subscriber_id(data['user_phone'])
package_id = get_package_id(data['tariff_name'])
# Создаём подписку в Catena
response = requests.post(
f"{CATENA_API_URL}/packages-subscribers",
headers={"X-Auth-Token": CATENA_API_KEY},
json={
"subscriberId": subscriber_id,
"packageId": package_id
}
)
if response.status_code == 200:
return {"status": "ok", "message": "Subscription activated"}
else:
return {"status": "error", "message": response.text}, 500
elif data['event'] == 'subscription.expired':
# Подписка истекла - деактивируем
subscriber_id = get_subscriber_id(data['user_phone'])
package_id = get_package_id(data['tariff_name'])
# Удаляём подписку в Catena
response = requests.delete(
f"{CATENA_API_URL}/packages-subscribers",
headers={"X-Auth-Token": CATENA_API_KEY},
json={
"subscriberId": subscriber_id,
"packageId": package_id
}
)
return {"status": "ok", "message": "Subscription deactivated"}
return {"status": "ok"}
def get_subscriber_id(phone):
"""Получить ID абонента Catena по номеру телефона"""
response = requests.get(
f"{CATENA_API_URL}/subscribers",
headers={"X-Auth-Token": CATENA_API_KEY}
)
subscribers = response.json()['subscribers']
for sub in subscribers:
full_phone = f"+{sub['phoneCountryCode']}{sub['phone']}"
if full_phone == phone:
return sub['subscriberId']
# Если абонент не найден - создаём
return create_subscriber(phone)
def get_package_id(tariff_name):
"""Сопоставить название тарифа с ID пакета"""
tariff_mapping = {
"basic": "basicKl9SW3AAAE.",
"premium": "premiumKl9SW3AAAE.",
"sport": "sportKl9SW3AAAE."
}
return tariff_mapping.get(tariff_name)
if __name__ == '__main__':
app.run(port=5000)
Пример 2: Периодическая синхронизация¶
Регулярная проверка и синхронизация подписок:
import requests
from datetime import datetime, timedelta
CATENA_API_URL = "https://catena.example.com/tv-management/api/v1"
CATENA_API_KEY = "your-api-key"
BILLING_DB = "postgresql://billing_db"
def sync_subscriptions():
"""Синхронизация подписок между биллингом и Catena"""
# 1. Получить активные подписки из биллинга
active_billing_subscriptions = get_active_subscriptions_from_billing()
# 2. Получить всех абонентов Catena
response = requests.get(
f"{CATENA_API_URL}/subscribers",
headers={"X-Auth-Token": CATENA_API_KEY}
)
catena_subscribers = response.json()['subscribers']
# 3. Сравнить и синхронизировать
for billing_sub in active_billing_subscriptions:
phone = billing_sub['phone']
package_id = get_package_id(billing_sub['tariff'])
# Найти абонента в Catena
catena_sub = find_subscriber_by_phone(catena_subscribers, phone)
if catena_sub:
# Проверить, есть ли нужная подписка
if package_id not in catena_sub['packages']:
# Подписки нет - создаём
create_subscription(catena_sub['subscriberId'], package_id)
print(f"Activated: {phone} -> {package_id}")
else:
# Абонента нет - создаём с подпиской
create_subscriber_with_package(phone, package_id)
print(f"Created subscriber: {phone}")
# 4. Отключить истекшие подписки
for catena_sub in catena_subscribers:
phone = f"+{catena_sub['phoneCountryCode']}{catena_sub['phone']}"
for package_id in catena_sub['packages']:
if not has_active_billing_subscription(phone, package_id):
# В биллинге подписки нет - удаляем из Catena
delete_subscription(catena_sub['subscriberId'], package_id)
print(f"Deactivated: {phone} -> {package_id}")
def create_subscription(subscriber_id, package_id):
requests.post(
f"{CATENA_API_URL}/packages-subscribers",
headers={"X-Auth-Token": CATENA_API_KEY},
json={
"subscriberId": subscriber_id,
"packageId": package_id
}
)
def delete_subscription(subscriber_id, package_id):
requests.delete(
f"{CATENA_API_URL}/packages-subscribers",
headers={"X-Auth-Token": CATENA_API_KEY},
json={
"subscriberId": subscriber_id,
"packageId": package_id
}
)
# Запускать эту функцию по расписанию (например, каждый час)
if __name__ == '__main__':
sync_subscriptions()
Пример 3: Пробный период¶
Автоматическое предоставление пробного периода новым абонентам:
import requests
from datetime import datetime, timedelta
def activate_trial_subscription(phone, trial_days=7):
"""Активировать пробную подписку на N дней"""
# 1. Создать или получить абонента
subscriber_id = get_or_create_subscriber(phone)
# 2. Подключить пробный пакет
trial_package_id = "trialKl9SW3AAAE."
response = requests.post(
f"{CATENA_API_URL}/packages-subscribers",
headers={"X-Auth-Token": CATENA_API_KEY},
json={
"subscriberId": subscriber_id,
"packageId": trial_package_id
}
)
if response.status_code == 200:
# 3. Запланировать автоматическое отключение
schedule_subscription_cancellation(
subscriber_id,
trial_package_id,
datetime.now() + timedelta(days=trial_days)
)
return {
"success": True,
"message": f"Trial activated for {trial_days} days",
"expires_at": (datetime.now() + timedelta(days=trial_days)).isoformat()
}
return {"success": False, "error": response.text}
def schedule_subscription_cancellation(subscriber_id, package_id, cancel_date):
"""Запланировать отключение подписки"""
# Сохранить в базу задач или использовать планировщик
# Например, Celery, APScheduler, или cron job
pass
Типичные сценарии использования¶
Подписка с автопродлением¶
Задача: Реализовать месячную подписку с автоматическим продлением
Решение:
- Биллинг списывает оплату каждый месяц
- При успешном списании биллинг проверяет наличие подписки в Catena
- Если подписка есть — ничего не делать (она уже активна)
- Если подписки нет — создать её через API
- При неудачном списании — удалить подписку через API
def process_monthly_renewal(user_id, package_name):
"""Обработка ежемесячного продления"""
# Попытка списания
payment_success = billing_charge(user_id, get_package_price(package_name))
subscriber_id = get_subscriber_id_by_user(user_id)
package_id = get_package_id(package_name)
if payment_success:
# Платёж успешен - убедиться что подписка активна
ensure_subscription_active(subscriber_id, package_id)
else:
# Платёж не прошёл - отключить подписку
deactivate_subscription(subscriber_id, package_id)
send_notification(user_id, "payment_failed")
Семейная подписка¶
Задача: Один платёж — доступ для нескольких абонентов (семейный аккаунт)
Решение:
- В биллинге создать семейный тариф
- При оплате подключить пакет всем абонентам семьи
- Хранить связь между абонентами в биллинге
def activate_family_subscription(family_id, package_name):
"""Активировать семейную подписку"""
package_id = get_package_id(package_name)
# Получить всех членов семьи из биллинга
family_members = get_family_members(family_id)
for member in family_members:
subscriber_id = get_subscriber_id(member['phone'])
# Подключить пакет каждому
requests.post(
f"{CATENA_API_URL}/packages-subscribers",
headers={"X-Auth-Token": CATENA_API_KEY},
json={
"subscriberId": subscriber_id,
"packageId": package_id
}
)
return {"activated": len(family_members)}
Временная акция¶
Задача: Дать доступ к премиум каналам на выходные
Решение:
- В пятницу вечером подключить промо-пакет всем активным абонентам
- В понедельник утром отключить промо-пакет
#!/bin/bash
# friday-promo.sh - запускается по cron в пятницу в 18:00
PROMO_PACKAGE_ID="weekendKl9SW3AAAE."
# Получить всех абонентов
SUBSCRIBERS=$(curl -s -X GET "$CATENA_API_URL/subscribers" \
-H "X-Auth-Token: $CATENA_API_KEY" \
| jq -r '.subscribers[].subscriberId')
# Подключить промо-пакет каждому
for SUBSCRIBER_ID in $SUBSCRIBERS; do
curl -X POST "$CATENA_API_URL/packages-subscribers" \
-H "X-Auth-Token: $CATENA_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"subscriberId\": \"$SUBSCRIBER_ID\",
\"packageId\": \"$PROMO_PACKAGE_ID\"
}"
done
echo "Promo activated for $(echo "$SUBSCRIBERS" | wc -l) subscribers"
#!/bin/bash
# monday-cleanup.sh - запускается по cron в понедельник в 06:00
PROMO_PACKAGE_ID="weekendKl9SW3AAAE."
SUBSCRIBERS=$(curl -s -X GET "$CATENA_API_URL/subscribers" \
-H "X-Auth-Token: $CATENA_API_KEY" \
| jq -r '.subscribers[] | select(.packages[] | contains("'$PROMO_PACKAGE_ID'")) | .subscriberId')
for SUBSCRIBER_ID in $SUBSCRIBERS; do
curl -X DELETE "$CATENA_API_URL/packages-subscribers" \
-H "X-Auth-Token: $CATENA_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"subscriberId\": \"$SUBSCRIBER_ID\",
\"packageId\": \"$PROMO_PACKAGE_ID\"
}"
done
echo "Promo deactivated for $(echo "$SUBSCRIBERS" | wc -l) subscribers"
Понижение тарифа¶
Задача: Абонент переходит с премиум на базовый тариф
Решение:
def downgrade_subscription(subscriber_id, from_package, to_package):
"""Понизить тариф абонента"""
from_package_id = get_package_id(from_package)
to_package_id = get_package_id(to_package)
# 1. Отключить премиум пакет
requests.delete(
f"{CATENA_API_URL}/packages-subscribers",
headers={"X-Auth-Token": CATENA_API_KEY},
json={
"subscriberId": subscriber_id,
"packageId": from_package_id
}
)
# 2. Подключить базовый пакет
requests.post(
f"{CATENA_API_URL}/packages-subscribers",
headers={"X-Auth-Token": CATENA_API_KEY},
json={
"subscriberId": subscriber_id,
"packageId": to_package_id
}
)
# 3. Записать в биллинг
billing_record_downgrade(subscriber_id, from_package, to_package)
return {"success": True, "new_package": to_package}
Лучшие практики¶
Проектирование пакетов¶
Рекомендации по структуре пакетов:
- Базовый пакет — минимальный набор каналов для всех
- Тематические пакеты — спорт, кино, детские, новости
- Премиум пакеты — эксклюзивный контент, HD/4K каналы
- Комбо-пакеты — несколько тематик в одном (экономия для абонента)
Избегайте:
- Слишком много мелких пакетов — усложняет выбор
- Дублирование каналов между пакетами — путаница в биллинге
- Пересечения пакетов без логики — один канал в 5 разных пакетах
Обработка ошибок¶
При интеграции с биллингом:
def safe_create_subscription(subscriber_id, package_id, retry_count=3):
"""Создание подписки с повторными попытками"""
for attempt in range(retry_count):
try:
response = requests.post(
f"{CATENA_API_URL}/packages-subscribers",
headers={"X-Auth-Token": CATENA_API_KEY},
json={
"subscriberId": subscriber_id,
"packageId": package_id
},
timeout=10
)
if response.status_code == 200:
return {"success": True}
elif response.status_code == 409:
# Подписка уже существует - это OK
return {"success": True, "already_exists": True}
else:
# Другая ошибка
error_msg = response.json().get('message', 'Unknown error')
log_error(f"Failed to create subscription: {error_msg}")
except requests.exceptions.Timeout:
log_warning(f"Timeout on attempt {attempt + 1}")
if attempt < retry_count - 1:
time.sleep(2 ** attempt) # Exponential backoff
continue
except Exception as e:
log_error(f"Unexpected error: {str(e)}")
break
# Все попытки неудачны - сохранить для ручной обработки
save_failed_operation("create_subscription", subscriber_id, package_id)
return {"success": False, "error": "Failed after retries"}
Синхронизация состояния¶
Регулярная сверка данных:
def audit_subscriptions():
"""Проверка согласованности подписок между системами"""
discrepancies = []
# Получить данные из обеих систем
billing_subscriptions = get_billing_subscriptions()
catena_subscriptions = get_catena_subscriptions()
# Найти расхождения
for billing_sub in billing_subscriptions:
if not exists_in_catena(billing_sub, catena_subscriptions):
discrepancies.append({
"type": "missing_in_catena",
"subscriber": billing_sub['phone'],
"package": billing_sub['package']
})
for catena_sub in catena_subscriptions:
if not exists_in_billing(catena_sub, billing_subscriptions):
discrepancies.append({
"type": "missing_in_billing",
"subscriber": catena_sub['phone'],
"package": catena_sub['package']
})
if discrepancies:
# Отправить уведомление администратору
send_audit_report(discrepancies)
# Опционально: автоматически исправить
auto_fix_discrepancies(discrepancies)
return discrepancies
Логирование и мониторинг¶
Что логировать:
- Все создания и удаления подписок
- Ошибки при вызове API
- Время отклика API Catena
- Несоответствия между биллингом и Catena
Метрики для отслеживания:
import prometheus_client as prom
# Метрики Prometheus
subscription_creations = prom.Counter(
'catena_subscription_creations_total',
'Total number of subscription creations',
['package_name', 'status']
)
subscription_deletions = prom.Counter(
'catena_subscription_deletions_total',
'Total number of subscription deletions',
['package_name', 'status']
)
api_latency = prom.Histogram(
'catena_api_latency_seconds',
'Latency of Catena API calls',
['endpoint', 'method']
)
def monitored_create_subscription(subscriber_id, package_id):
"""Создание подписки с мониторингом"""
package_name = get_package_name(package_id)
with api_latency.labels('/packages-subscribers', 'POST').time():
try:
response = requests.post(...)
if response.status_code == 200:
subscription_creations.labels(package_name, 'success').inc()
return {"success": True}
else:
subscription_creations.labels(package_name, 'error').inc()
return {"success": False}
except Exception as e:
subscription_creations.labels(package_name, 'exception').inc()
raise
Уведомления абонентам¶
Когда отправлять уведомления:
- При активации подписки — "Добро пожаловать! Теперь доступны каналы: ..."
- За 3 дня до окончания — "Ваша подписка истекает через 3 дня"
- При продлении — "Подписка продлена до ..."
- При отключении — "Подписка отключена. Для продления..."
- При ошибке оплаты — "Не удалось списать оплату. Проверьте..."
def notify_subscription_activated(subscriber_id, package_name):
"""Уведомление об активации подписки"""
subscriber = get_subscriber(subscriber_id)
phone = f"+{subscriber['phoneCountryCode']}{subscriber['phone']}"
# Получить список каналов пакета
package = get_package(get_package_id(package_name))
channels = ", ".join(package['channels'][:5]) # Первые 5 каналов
message = f"""
🎉 Подписка активирована!
Пакет: {package_name}
Доступные каналы: {channels} и другие
Приятного просмотра!
"""
send_sms(phone, message)
Устранение проблем¶
Подписка не создаётся¶
Возможные причины:
- Неверный
subscriberId
илиpackageId
- Абонент и пакет из разных порталов
- Подписка уже существует
- Проблемы с авторизацией API
Решение:
- Проверьте существование абонента:
GET /subscribers/{id}
- Проверьте существование пакета:
GET /packages/{id}
- Убедитесь, что
portalId
совпадает - Проверьте текущие подписки абонента
- Проверьте валидность API ключа
Абонент не видит каналы после создания подписки¶
Возможные причины:
- Пакет не содержит каналов
- Приложение не обновило список каналов
- Проблемы со streaming-сервером
Решение:
- Проверьте содержимое пакета:
GET /packages/{id}
- Убедитесь, что в пакете есть каналы
- Попросите абонента перезапустить приложение
- Проверьте
playback_token
абонента - Проверьте логи streaming-сервера
Подписка не удаляется¶
Возможные причины:
- Подписки не существует (уже удалена)
- Неверные параметры запроса
- Это бесплатный пакет портала (удалить нельзя)
Решение:
- Проверьте текущие подписки абонента
- Убедитесь, что это не бесплатный пакет портала
- Проверьте правильность
subscriberId
иpackageId
- Посмотрите журнал операций для этого абонента
Расхождения между биллингом и Catena¶
Проблема: В биллинге подписка активна, в Catena — нет (или наоборот)
Решение:
- Реализуйте регулярную синхронизацию (каждые 15-60 минут)
- Используйте журнал операций для выявления проблем
- При расхождении приоритет имеет биллинг (источник истины)
- Логируйте все изменения для анализа
def fix_sync_issue(subscriber_id):
"""Исправить рассинхронизацию для абонента"""
# 1. Получить "правду" из биллинга
billing_packages = get_billing_packages(subscriber_id)
# 2. Получить текущее состояние в Catena
subscriber = get_catena_subscriber(subscriber_id)
catena_packages = subscriber['packages']
# 3. Синхронизировать
for package_id in billing_packages:
if package_id not in catena_packages:
# Должна быть, но нет - добавляем
create_subscription(subscriber_id, package_id)
log_info(f"Fixed: added {package_id} to {subscriber_id}")
for package_id in catena_packages:
if package_id not in billing_packages:
# Есть, но не должна быть - удаляем
delete_subscription(subscriber_id, package_id)
log_info(f"Fixed: removed {package_id} from {subscriber_id}")
См. также¶
- Управление абонентами — создание и настройка учётных записей абонентов
- Управление пакетами каналов — создание и настройка пакетов
- Управление каналами — добавление каналов в пакеты
- Журнал операций — отслеживание всех изменений подписок
- Настройка портала — конфигурация бесплатных пакетов