Biblioteca en .NET MAUI / C# para la validación y compra de licencias a través de la plataforma Apklis. Proporciona los servicios necesarios para verificar si un usuario tiene una licencia activa y para iniciar el pago de una licencia mediante Transfermóvil.
Este proyecto está basado en la biblioteca Kotlin original z17-cuba/ApklisLicenseValidator y en el plugin Flutter equivalente, adaptados a .NET MAUI con C#.
- .NET 9 + .NET MAUI
- Target platform: Android (API 21+)
- La aplicación Apklis instalada en el dispositivo del usuario
Copia la carpeta Apklis/ al directorio raíz de tu proyecto MAUI. Contiene:
Apklis/
├── Helpers/
│ ├── ApiHelpers.cs
│ ├── ApiService.cs
│ ├── ITransfermovilService.cs
│ ├── LoggingHandler.cs
│ ├── SignatureVerificationService.cs
│ ├── ToolsHelper.cs
│ └── TransfermovilService.cs
├── Models/
│ ├── ApklisAccountData.cs
│ ├── ApklisLicensePaymentStatus.cs
│ ├── LicenseRequest.cs
│ ├── PaymentRequest.cs
│ ├── QrCode.cs
│ └── VerifyLicenseResponse.cs
└── Services/
├── ApklisValidatorService.cs
├── IApklisDataService.cs
└── IPaymentResultCallback.cs
Además, copia el archivo Platforms/Android/ApklisDataGetter.cs.
Añade los siguientes paquetes NuGet a tu proyecto:
<PackageReference Include="QRCoder" Version="1.6.0" />
<PackageReference Include="SkiaSharp.Views.Maui.Controls" Version="3.116.1" />La clave pública generada para tu grupo de licencias debe colocarse como recurso embebido en:
Resources/Raw/license_private_key.pub
En el archivo .csproj, asegúrate de que el archivo esté configurado como EmbeddedResource:
<ItemGroup>
<EmbeddedResource Include="Resources\Raw\license_private_key.pub" />
</ItemGroup>
⚠️ Cada desarrollador recibe una clave única asociada a su grupo de licencias en Apklis. Sin esta clave, la verificación de firma de las respuestas de la API fallará.
Registra todos los servicios en el contenedor de inyección de dependencias de MAUI:
using ApklisLicenseValidator.Apklis.Helpers;
using ApklisLicenseValidator.Apklis.Services;
using ApklisLicenseValidator.Platforms.Android;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder.UseMauiApp<App>();
// Registro de servicios de Apklis
builder.Services.AddSingleton<IApklisDataService, ApklisDataGetter>();
builder.Services.AddSingleton<ApiService>();
builder.Services.AddSingleton<ApklisValidatorService>();
builder.Services.AddSingleton<ITransfermovilService, TransfermovilService>();
return builder.Build();
}
}Añade los permisos requeridos en Platforms/Android/AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="cu.uci.android.apklis.READ_ACCOUNT_DATA" />Todas las operaciones devuelven un objeto ApklisLicensePaymentStatus con el resultado de la operación.
| Propiedad | Tipo | Descripción |
|---|---|---|
Paid |
bool |
Indica si el usuario tiene una licencia activa o si el pago fue confirmado |
Username |
string? |
Nombre de usuario en Apklis |
License |
string? |
Identificador de la licencia activa |
Error |
string? |
Mensaje de error (nulo si la operación fue exitosa) |
StatusCode |
int? |
Código HTTP de error de la API (si aplica) |
ExpireIn |
DateTime? |
Fecha de expiración de la licencia |
QrCode |
QrCode? |
Datos del QR para pago por Transfermóvil (solo en PurchaseLicenseAsync) |
public class MyPage : ContentPage
{
private readonly ApklisValidatorService _licenseValidator;
private readonly ITransfermovilService _transfermovilService;
public MyPage(ApklisValidatorService licenseValidator, ITransfermovilService transfermovilService)
{
_licenseValidator = licenseValidator;
_transfermovilService = transfermovilService;
}
}Comprueba si el usuario tiene una licencia activa para el paquete indicado.
private async Task CheckLicenseAsync()
{
var result = await _licenseValidator.VerifyCurrentLicenseAsync("com.example.myapp");
if (string.IsNullOrEmpty(result.Error))
{
if (result.Paid)
{
// Licencia válida encontrada
Console.WriteLine($"Usuario: {result.Username}");
Console.WriteLine($"Licencia: {result.License}");
Console.WriteLine($"Expira: {result.ExpireIn}");
EnablePremiumFeatures();
}
else
{
// No tiene licencia activa
DisablePremiumFeatures();
}
}
else
{
// Error en la verificación
Console.WriteLine($"Error: {result.Error}");
switch (result.StatusCode)
{
case 402:
// El usuario debe comprar una licencia
break;
case 403:
// Credenciales expiradas — pedir al usuario que abra Apklis y se autentique
break;
case 404:
// El grupo de licencias no ha sido publicado aún
break;
}
}
}Inicia el proceso de compra de una licencia. Si la API responde con éxito, se obtiene el código QR de Transfermóvil para completar el pago.
private async Task PurchaseLicenseAsync()
{
// Paso 1: Obtener el código QR de pago desde la API de Apklis
var result = await _licenseValidator.PurchaseLicenseAsync("your-license-uuid");
if (string.IsNullOrEmpty(result.Error))
{
if (result.QrCode != null)
{
// Paso 2: Mostrar el QR al usuario
QrImageControl.Source = ToolsHelper.GenerateQr(result.QrCode);
string qrJson = result.QrCode.ToJsonString();
// Paso 3: Enviar el QR a la app de Transfermóvil y esperar confirmación
bool paymentConfirmed = await _transfermovilService.ProcessPaymentAsync(
qrData: qrJson,
licenseId: "your-license-uuid",
packageId: "com.example.myapp");
if (paymentConfirmed)
{
await DisplayAlert("Pago", "¡Pago confirmado!", "OK");
EnablePremiumFeatures();
}
else
{
await DisplayAlert("Pago", "Pago no confirmado o expiró el tiempo de espera.", "OK");
}
}
}
else
{
Console.WriteLine($"Error al iniciar compra: {result.Error}");
switch (result.StatusCode)
{
case 400:
// Error de pago (usualmente timeout). Reintentar resuelve el problema.
break;
case 403:
// Credenciales expiradas — pedir al usuario que abra Apklis y se autentique
break;
}
}
}ToolsHelper.GenerateQr() genera un ImageSource listo para usar en cualquier control Image de MAUI. Si existe el recurso apklist.png embebido, superpone el logotipo en el centro del QR.
// Genera el QR como ImageSource (admite logotipo embebido)
ImageSource qrImage = ToolsHelper.GenerateQr(result.QrCode);
MyQrImageControl.Source = qrImage;| Código | Descripción |
|---|---|
402 |
El usuario debe pagar una licencia para la aplicación |
403 |
Credenciales no reconocidas o expiradas. Se recomienda que el usuario abra Apklis y se autentique |
404 |
El grupo de licencias no ha sido publicado aún |
| Código | Descripción |
|---|---|
400 |
Error en el proceso de pago (usualmente timeout a la API). Reintentar suele resolver el problema |
403 |
Credenciales no reconocidas o expiradas. Se recomienda que el usuario abra Apklis y se autentique |
Apklis/
├── Helpers/
│ ├── ApiHelpers.cs — Tipos resultado de API: Success<T>, Error<T>, ExceptionResult<T>
│ ├── ApiService.cs — Cliente HTTP para los endpoints de licencias de Apklis
│ ├── ITransfermovilService.cs — Contrato del servicio de pago por Transfermóvil
│ ├── LoggingHandler.cs — Manejador HTTP que registra solicitudes/respuestas (debug)
│ ├── SignatureVerificationService.cs — Verificación de firma RSA-SHA256 de respuestas de la API
│ ├── ToolsHelper.cs — Utilidades: texto de expiración, generación de imagen QR
│ └── TransfermovilService.cs — Envío del QR a Transfermóvil + polling de confirmación
├── Models/
│ ├── ApklisAccountData.cs — Datos de cuenta del usuario (username, deviceId, token)
│ ├── ApklisLicensePaymentStatus.cs — Resultado de verificación o compra de licencia
│ ├── LicenseRequest.cs — Request de verificación (packageName + deviceId)
│ ├── PaymentRequest.cs — Request de pago (deviceId)
│ ├── QrCode.cs — Datos del QR de Transfermóvil
│ └── VerifyLicenseResponse.cs — Respuesta de la API al verificar licencia
└── Services/
├── ApklisValidatorService.cs — Servicio principal: PurchaseLicenseAsync + VerifyCurrentLicenseAsync
├── IApklisDataService.cs — Contrato del proveedor de datos de cuenta de Apklis
└── IPaymentResultCallback.cs — Callbacks de resultado de pago (completado, fallido, cerrado)
Platforms/Android/
└── ApklisDataGetter.cs — Implementación Android: lee datos del ContentProvider de Apklis
Reportado en dispositivos Xiaomi Redmi Note 11 con Android 11 y otros. Si agotaste las siguientes opciones:
- Iniciar sesión en Apklis
- Realizar alguna acción para refrescar el token expirado
- Cerrar sesión y volver a iniciar
- Asegurarte de que Apklis esté activo en segundo plano
- Verificar en Ajustes → Cuentas y sincronización que la cuenta de Apklis se creó correctamente
Entonces añade esta línea al AndroidManifest.xml de tu aplicación:
<queries>
<package android:name="cu.uci.android.apklis" />
</queries>Aplica al mismo problema pero en dispositivos con Android 16. Probado con Google Pixel 8 y Samsung S24:
<queries>
<intent>
<action android:name="android.accounts.AccountAuthenticator" />
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="apklis" />
</intent>
</queries>- La validación del certificado SSL está deshabilitada por defecto en
ApiService.cspara compatibilidad con el entorno de Apklis. Revisa si esto aplica a tu caso de uso en producción. - La clave pública
license_private_key.pubse embebe en el binario de la app. No la compartas ni la publiques en repositorios públicos. - La firma digital RSA-SHA256 de cada respuesta de la API se verifica automáticamente antes de dar por válido cualquier resultado.
Basado en el trabajo original de:
- z17-cuba/ApklisLicenseValidator — biblioteca Kotlin nativa para Android
- Plugin Flutter equivalente para la plataforma Apklis