Реализация активации eSIM-профиля через мобильное приложение
Активация eSIM — самый болезненный пользовательский опыт в телекоме, если реализована неправильно. Пользователь вводит activation code, видит спиннер 30 секунд, и получает «Ошибка активации». Что пошло не так — приложение не знает. SM-DP+ сервер вернул SGP.22 error code, LPA его обработал, платформа завернула в непрозрачный resultCode. Задача — докопаться до реальной причины и показать человеку понятное сообщение.
Activation Code: форматы и источники
SGP.22 определяет два способа передачи activation code пользователю:
QR-код — стандартный формат: LPA:1$smdp-plus.operator.com$ABC123XYZ. Сканирование — нативными возможностями платформы или встроенным сканером в приложении. На iOS можно открыть системный процесс активации напрямую.
Manual Entry — тот же строковый код, вводится вручную. Должна быть валидация формата перед отправкой: наличие LPA:1$, корректный FQDN SM-DP+, Matching ID без запрещённых символов.
// Валидация формата activation code
fun validateActivationCode(code: String): Boolean {
val pattern = Regex("""^LPA:1\$[a-zA-Z0-9\-.]+\$[a-zA-Z0-9\-]+(\$[a-zA-Z0-9.]+(\$[01])?)?$""")
return pattern.matches(code.trim())
}
Android: пошаговая активация с обработкой ошибок
EuiccManager.downloadSubscription() — основной метод для carrier-privileged приложений:
private fun activateEsimProfile(activationCode: String) {
val subscription = DownloadableSubscription.forActivationCode(activationCode)
// Флаг switchAfterDownload: true — профиль активируется сразу после загрузки
// false — загружается в disabled состоянии, активируется отдельным вызовом
euiccManager.downloadSubscription(
subscription,
switchAfterDownload = true,
cancellationSignal = CancellationSignal(),
executor = mainExecutor
) { resultCode, extras ->
when (resultCode) {
EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK -> {
val subscriptionId = extras?.getInt(EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_ID) ?: -1
onActivationSuccess(subscriptionId)
}
EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR -> {
// Нужно пользовательское подтверждение
val intent = extras?.getParcelable<PendingIntent>(
EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_RESOLUTION_INTENT
)
intent?.send() // Открывает системный диалог согласия
}
EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_ERROR -> {
val detailedCode = extras?.getInt(
EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE, -1
) ?: -1
val operationCode = detailedCode and 0xFF
val errorCode = (detailedCode shr 8) and 0xFF
showActivationError(operationCode, errorCode)
}
}
}
}
Детализация ошибок через EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE:
| Operation Code | Error Code | Причина | Что сказать пользователю |
|---|---|---|---|
| 1 (DOWNLOAD) | 2 | Неверный activation code | «Проверьте код активации» |
| 1 (DOWNLOAD) | 3 | Код уже использован | «Этот код уже активирован» |
| 2 (INSTALL) | 8 | Нет места на eUICC | «Удалите неиспользуемый профиль» |
| 3 (SWITCH) | 1 | Ошибка сети SM-DP+ | «Проверьте подключение к интернету» |
iOS: системный UI и QR
На iOS без carrier entitlement — только системный диалог активации. Открывается через URL:
func activateViaNativeUI(activationCode: String) {
// Формируем URL для системной активации
let urlString = "com.apple.esim://\(activationCode)"
if let url = URL(string: urlString), UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url) { success in
if success {
// Системный UI открылся, ждём результата через Universal Link callback
startPollingForNewProfile()
}
}
}
}
После активации через системный UI — нет прямого callback в приложение. Нужно полить CTSubscriptionManager или слушать уведомление CTSubscriberTokenRefreshed.
Для carrier приложений с entitlement — CTSubscriptionManagerDelegate:
import CoreTelephony
class ESIMActivationManager: NSObject, CTSubscriptionManagerDelegate {
let subscriptionManager = CTSubscriptionManager()
func activateProfile(activationCode: String) {
subscriptionManager.delegate = self
subscriptionManager.setAccountIdentifier(activationCode)
}
func subscriptionManagerCompleted(_ manager: CTSubscriptionManager) {
DispatchQueue.main.async {
self.handleActivationComplete()
}
}
}
Состояния и UX загрузки
Активация eSIM занимает от 15 секунд до 3 минут — SM-DP+ сервер генерирует профиль, LPA скачивает и устанавливает (обычно 300–500 KB зашифрованных данных). За это время пользователь не должен видеть статичный спиннер.
Правильный UX: шаги с текущим состоянием:
- Проверка activation code → Валидация формата
- Подключение к оператору → Запрос к SM-DP+
- Загрузка профиля → Progress bar (если LPA предоставляет прогресс)
- Установка → Запись в eUICC
- Активация → Переключение на новый профиль
На Android downloadSubscription не предоставляет прогресс по шагам. Фейковый прогресс с оценкой по времени выглядит лучше, чем статичный спиннер. Реальный прогресс — только если используется собственный LPA через TelephonyManager.setPreferredOpportunisticDataSubscription.
Сроки
Активация eSIM через Intent/системный UI с обработкой кодов ошибок: 1–2 недели. Полная интеграция с EuiccManager для carrier-приложения (Android) + CoreTelephony для iOS: 1–3 месяца включая согласование с оператором и доступы.







