950 lines
44 KiB
C#
950 lines
44 KiB
C#
using CommunityToolkit.Maui;
|
|
using CommunityToolkit.Maui.Core.Extensions;
|
|
using CommunityToolkit.Maui.Views;
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
|
using CommunityToolkit.Mvvm.Input;
|
|
using CommunityToolkit.Mvvm.Messaging;
|
|
using gehGassiApp.Core.Interfaces;
|
|
using gehGassiApp.Core.Messaging;
|
|
using gehGassiApp.Domain.Common;
|
|
using gehGassiApp.Domain.Subscriptions;
|
|
using gehGassiApp.Models;
|
|
using gehGassiApp.Models.PopupResults;
|
|
using gehGassiApp.Resources;
|
|
using gehGassiApp.Services;
|
|
using gehGassiApp.Views;
|
|
using gehGassiApp.Views.More;
|
|
using gehGassiApp.Views.Popups;
|
|
using Plugin.InAppBilling;
|
|
using System.Collections.ObjectModel;
|
|
using System.Net;
|
|
using System.Text.Json;
|
|
using Platform = gehGassiApp.Domain.Common.Platform;
|
|
|
|
namespace gehGassiApp.ViewModels.Subscriptions
|
|
{
|
|
/// <summary>
|
|
/// Viewmmodel für die Anzeige der aktuellen und verfügbaren Abos
|
|
/// </summary>
|
|
public partial class SubscriptionsViewModel : MenuViewModel
|
|
{
|
|
private readonly ISubscriptionService _subscriptionService;
|
|
private readonly ISubscriptionValidationService _subscriptionValidationService;
|
|
private readonly IDispatcher _dispatcher;
|
|
private readonly IDialogService _dialogService;
|
|
private readonly IPopupService _popupService;
|
|
private bool _hasShownSuccessPopupThisSession;
|
|
|
|
/// <summary>
|
|
/// ERstellt eine Instanz
|
|
/// </summary>
|
|
/// <param name="subscriptionService">Instanz eines ISubscriptionService</param>
|
|
/// <param name="subscriptionValidationService">Instanz eines ISubscriptionValidationService</param>
|
|
/// <param name="dispatcher">Instanz eines IDispatcher</param>
|
|
/// <param name="dialogService">Instanz eines IDialogService</param>
|
|
public SubscriptionsViewModel(ISubscriptionService subscriptionService, ISubscriptionValidationService subscriptionValidationService, IDispatcher dispatcher, IDialogService dialogService, IPopupService popupService)
|
|
{
|
|
_subscriptionService = subscriptionService;
|
|
_subscriptionValidationService = subscriptionValidationService;
|
|
_dispatcher = dispatcher;
|
|
_dialogService = dialogService;
|
|
_popupService = popupService;
|
|
|
|
Title = Text.View_Title_Subscriptions;
|
|
SelectedMenu = MenuSelected.More;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Liste der gebuchten Abos
|
|
/// </summary>
|
|
[ObservableProperty]
|
|
private ObservableCollection<AppUserSubscription> _mySubscriptions;
|
|
|
|
/// <summary>
|
|
/// Liste der Abonnements
|
|
/// </summary>
|
|
[ObservableProperty]
|
|
private ObservableCollection<Subscription> _subscriptions;
|
|
/// <summary>
|
|
/// Liste der Abonnementmodels
|
|
/// </summary>
|
|
[ObservableProperty]
|
|
private ObservableCollection<SubscriptionModel> _subscriptionModels;
|
|
/// <summary>
|
|
/// Aktuell ausgewählte Subscription
|
|
/// </summary>
|
|
[ObservableProperty]
|
|
private SubscriptionModel _selectedSubscription;
|
|
/// <summary>
|
|
/// Abonnements die verfügbar sind anzeigen
|
|
/// </summary>
|
|
[ObservableProperty]
|
|
private bool _showSubscriptions;
|
|
|
|
#region Commands
|
|
/// <summary>
|
|
/// Command zum Auswählen einer Subscription
|
|
/// </summary>
|
|
/// <param name="subscriptionModel">Das SubscriptionModel</param>
|
|
/// <returns></returns>
|
|
[RelayCommand]
|
|
private void SelectSubscription(SubscriptionModel subscriptionModel)
|
|
{
|
|
if (subscriptionModel == null) return;
|
|
|
|
// Alle anderen deselektieren
|
|
if (Subscriptions != null)
|
|
{
|
|
foreach (var sub in SubscriptionModels)
|
|
{
|
|
sub.IsSelected = false;
|
|
}
|
|
}
|
|
|
|
// Die ausgewählte markieren
|
|
subscriptionModel.IsSelected = true;
|
|
SelectedSubscription = subscriptionModel;
|
|
}
|
|
/// <summary>
|
|
/// Command zum Buchen des ausgewählten Abos
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
[RelayCommand]
|
|
private async Task PurchaseSelectedSubscription()
|
|
{
|
|
if (SelectedSubscription?.Subscription?.Code == null)
|
|
return;
|
|
|
|
await PurchaseSubcription(SelectedSubscription.Subscription.Code);
|
|
}
|
|
/// <summary>
|
|
/// Command für die Anzeige des Datenschutzes
|
|
/// </summary>
|
|
[RelayCommand]
|
|
public async Task Privacy()
|
|
{
|
|
var menu = MenuSelected.More.ToString();
|
|
var title = Text.View_Title_Privacy;
|
|
await Shell.Current.GoToAsync($"{nameof(ShowTextPageView)}?code={Core.Common.Constants.PagePrivacy}&menu={menu}&title={title}", Core.Common.Constants.AnimateNavigation);
|
|
}
|
|
/// <summary>
|
|
/// Command zum Buchen eines Abos
|
|
/// </summary>
|
|
/// <param name="productId">ProduktId des Abos</param>
|
|
/// <returns></returns>
|
|
[RelayCommand]
|
|
private async Task PurchaseSubcription(string productId)
|
|
{
|
|
if (IsBusy)
|
|
return;
|
|
|
|
IsBusy = true;
|
|
try
|
|
{
|
|
// check internet first with Essentials
|
|
if (Connectivity.NetworkAccess != NetworkAccess.Internet)
|
|
{
|
|
await TrackEventAsync("Subscription_Purchase_NoInternet");
|
|
return;
|
|
}
|
|
|
|
// connect to the app store api
|
|
var connected = await CrossInAppBilling.Current.ConnectAsync();
|
|
if (!connected)
|
|
{
|
|
await TrackEventAsync("Subscription_Purchase_NoConnectionToInAppBilling");
|
|
return;
|
|
}
|
|
|
|
//try to make purchase, this will return a purchase, empty, or throw an exception
|
|
var purchase = await CrossInAppBilling.Current.PurchaseAsync(productId, ItemType.Subscription);
|
|
|
|
if (purchase == null)
|
|
{
|
|
//nothing was purchased
|
|
await TrackEventAsync("Subscription_Purchase_NothingPurchased");
|
|
return;
|
|
}
|
|
|
|
if (purchase.State == PurchaseState.Purchased)
|
|
{
|
|
await TrackEventAsync("Subscription_Purchase_Purchased");
|
|
|
|
string inAppBillingPurchaseInfo;
|
|
try
|
|
{
|
|
inAppBillingPurchaseInfo = JsonSerializer.Serialize(purchase);
|
|
inAppBillingPurchaseInfo = WebUtility.HtmlEncode(inAppBillingPurchaseInfo);
|
|
}
|
|
catch
|
|
{
|
|
inAppBillingPurchaseInfo = string.Empty;
|
|
}
|
|
|
|
//Set Subscription
|
|
var subscription = Subscriptions.FirstOrDefault(c => c.Code == productId);
|
|
using var cts = new CancellationTokenSource(Core.Common.Constants.CreateTimeout);
|
|
var platform = DeviceInfo.Platform == DevicePlatform.iOS ? Platform.Ios : Platform.Android;
|
|
var bookingResult = await _subscriptionService.CreateSubscriptionAsync(App.CurrentAppUser.Id, subscription.Id, SelectedLanguage, inAppBillingPurchaseInfo, null, platform, App.CurrentUser.AccessToken, cts.Token);
|
|
|
|
if (bookingResult.Success)
|
|
{
|
|
await TrackEventAsync("Subscription_Purchase_BookingCreated");
|
|
try
|
|
{
|
|
// It is required to acknowledge the purchase, else it will be refunded
|
|
if (DeviceInfo.Platform == DevicePlatform.Android)
|
|
{
|
|
var finalizeResults = await CrossInAppBilling.Current.FinalizePurchaseAsync([purchase.TransactionIdentifier], cts.Token);
|
|
var finalizeDict = finalizeResults.ToDictionary(finalizeResult => finalizeResult.Id, finalizeResult => finalizeResult.Success.ToString());
|
|
await TrackEventAsync("Subscription_Purchase_Finalize", finalizeDict);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await TrackErrorAsync(ex);
|
|
}
|
|
//Senden der Nachricht zum laden der Abos
|
|
WeakReferenceMessenger.Default.Send(new SubscriptionChangedMessage());
|
|
await TrackEventAsync("Subscription_Purchase_Success");
|
|
await LoadAsync();
|
|
if (MySubscriptions != null && MySubscriptions.Any())
|
|
{
|
|
await ShowSubscriptionSuccessPopupAsync(force: true);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var errDict = new Dictionary<string, string>
|
|
{
|
|
{ "errorMessage", bookingResult.ErrorMessage },
|
|
{ "errorCode", bookingResult.ErrorCode.ToString() }
|
|
};
|
|
await TrackEventAsync("Subscription_Purchase_BookingFailed", errDict);
|
|
var errorMessage = Resources.Text.Subscriptions_Error_BookingFailed;
|
|
if (!string.IsNullOrWhiteSpace(errorMessage))
|
|
await _dialogService.ShowAlertAsync(Resources.Text.Common_Error, errorMessage, Resources.Text.Button_Ok);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw new InAppBillingPurchaseException(PurchaseError.GeneralError);
|
|
}
|
|
}
|
|
catch (InAppBillingPurchaseException purchaseEx)
|
|
{
|
|
var errorMessage = string.Empty;
|
|
// Handle all the different error codes that can occure and do a pop up
|
|
switch (purchaseEx.PurchaseError)
|
|
{
|
|
case PurchaseError.AppStoreUnavailable:
|
|
errorMessage = Resources.Text.Subscriptions_Error_AppStoreUnavailable;
|
|
break;
|
|
case PurchaseError.DeveloperError:
|
|
errorMessage = Resources.Text.Subscriptions_Error_DeveloperError;
|
|
break;
|
|
case PurchaseError.ItemUnavailable:
|
|
errorMessage = Resources.Text.Subscriptions_Error_ItemUnavailable;
|
|
break;
|
|
case PurchaseError.GeneralError:
|
|
errorMessage = Resources.Text.Subscriptions_Error_GeneralError;
|
|
break;
|
|
case PurchaseError.UserCancelled:
|
|
errorMessage = Resources.Text.Subscriptions_Error_UserCancelled;
|
|
break;
|
|
case PurchaseError.BillingUnavailable:
|
|
errorMessage = Resources.Text.Subscriptions_Error_BillingUnavailable;
|
|
break;
|
|
case PurchaseError.PaymentNotAllowed:
|
|
errorMessage = Resources.Text.Subscriptions_Error_PaymentNotAllowed;
|
|
break;
|
|
case PurchaseError.PaymentInvalid:
|
|
errorMessage = Resources.Text.Subscriptions_Error_PaymentInvalid;
|
|
break;
|
|
case PurchaseError.InvalidProduct:
|
|
errorMessage = Resources.Text.Subscriptions_Error_InvalidProduct;
|
|
break;
|
|
case PurchaseError.ProductRequestFailed:
|
|
errorMessage = Resources.Text.Subscriptions_Error_ProductRequestFailed;
|
|
break;
|
|
case PurchaseError.RestoreFailed:
|
|
errorMessage = Resources.Text.Subscriptions_Error_RestoreFailed;
|
|
break;
|
|
case PurchaseError.NotOwned:
|
|
errorMessage = Resources.Text.Subscriptions_Error_NotOwned;
|
|
break;
|
|
case PurchaseError.AlreadyOwned:
|
|
errorMessage = Resources.Text.Subscriptions_Error_AlreadyOwned;
|
|
break;
|
|
case PurchaseError.ServiceUnavailable:
|
|
errorMessage = Resources.Text.Subscriptions_Error_ServiceUnavailable;
|
|
break;
|
|
case PurchaseError.FeatureNotSupported:
|
|
errorMessage = Resources.Text.Subscriptions_Error_FeatureNotSupported;
|
|
break;
|
|
case PurchaseError.ServiceDisconnected:
|
|
errorMessage = Resources.Text.Subscriptions_Error_ServiceDisconnected;
|
|
break;
|
|
case PurchaseError.ServiceTimeout:
|
|
errorMessage = Resources.Text.Subscriptions_Error_ServiceTimeout;
|
|
break;
|
|
case PurchaseError.AppleTermsConditionsChanged:
|
|
errorMessage = Resources.Text.Subscriptions_Error_AppleTermsConditionsChanged;
|
|
break;
|
|
default:
|
|
errorMessage = Resources.Text.Subscriptions_Error_GeneralError;
|
|
break;
|
|
}
|
|
|
|
await TrackErrorAsync(purchaseEx);
|
|
|
|
if (!string.IsNullOrWhiteSpace(errorMessage))
|
|
await _dialogService.ShowAlertAsync(Resources.Text.Common_Error, errorMessage, Resources.Text.Button_Ok);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// Handle a generic exception as something really went wrong
|
|
await TrackErrorAsync(ex);
|
|
var errorMessage = Resources.Text.Subscriptions_Error_GeneralError;
|
|
if (!string.IsNullOrWhiteSpace(errorMessage))
|
|
await _dialogService.ShowAlertAsync(Resources.Text.Common_Error, errorMessage, Resources.Text.Button_Ok);
|
|
}
|
|
finally
|
|
{
|
|
await CrossInAppBilling.Current.DisconnectAsync();
|
|
IsBusy = false;
|
|
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Command zum Managen eines Abos im App- oder Playstore
|
|
/// </summary>
|
|
/// <param name="productId">ProduktId des Abos</param>
|
|
/// <returns>Task</returns>
|
|
[RelayCommand]
|
|
private async Task ManageSubcription(string productId)
|
|
{
|
|
var url = string.Empty;
|
|
if (DeviceInfo.Platform == DevicePlatform.iOS)
|
|
{
|
|
url = "https://support.apple.com/HT202039";
|
|
}
|
|
else if (DeviceInfo.Platform == DevicePlatform.Android)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(productId))
|
|
{
|
|
url = "https://play.google.com/store/account/subscriptions";
|
|
}
|
|
else
|
|
{
|
|
url = $"https://play.google.com/store/account/subscriptions?sku={productId}&package={AppInfo.PackageName}";
|
|
}
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(url))
|
|
return;
|
|
|
|
await Browser.OpenAsync(url);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Command zum Wiederherstellen eines Abos
|
|
/// </summary>
|
|
/// <returns>Task</returns>
|
|
[RelayCommand]
|
|
private async Task Restore()
|
|
{
|
|
var subcriptionsFound = await RestoreSubscriptionsAsync();
|
|
if (subcriptionsFound == -1)
|
|
{
|
|
//Keine Verbindung
|
|
var errorMessage = Resources.Text.Subscriptions_Error_ServiceUnavailable;
|
|
if (!string.IsNullOrWhiteSpace(errorMessage))
|
|
await _dialogService.ShowAlertAsync(Resources.Text.Common_Error, errorMessage, Resources.Text.Button_Ok);
|
|
}
|
|
else if (subcriptionsFound == 0)
|
|
{
|
|
//Keine Abos gefunden
|
|
var gotoFeedback = await _dialogService.ShowAlertAsync(Text.Common_Info, Text.Subscription_NotFound_Info, Text.Button_Feedback, Text.Common_Ok);
|
|
if (gotoFeedback)
|
|
{
|
|
await Shell.Current.GoToAsync($"{nameof(FeedbackView)}", Core.Common.Constants.AnimateNavigation);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//Abos gefunden
|
|
WeakReferenceMessenger.Default.Send(new SubscriptionChangedMessage());
|
|
await LoadMySubscriptionsAsync();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Command für die Anzeige der AGB
|
|
/// </summary>
|
|
[RelayCommand]
|
|
public async Task ShowTerms()
|
|
{
|
|
try
|
|
{
|
|
var uri = new Uri("https://www.apple.com/legal/internet-services/itunes/dev/stdeula/");
|
|
await Browser.Default.OpenAsync(uri, BrowserLaunchMode.SystemPreferred);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// Fehlerbehandlung, falls kein Browser installiert ist oder ein anderer Fehler auftritt
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Laden der initialen Daten
|
|
/// </summary>
|
|
/// <returns>Task</returns>
|
|
private async Task LoadAsync()
|
|
{
|
|
IsBusy = true;
|
|
IsLoading = true;
|
|
|
|
//await CheckSubscriptionsAsync();
|
|
await LoadSubscriptionsAsync();
|
|
await LoadMySubscriptionsAsync();
|
|
|
|
await ValidateSubscriptionsAsync();
|
|
|
|
if (MySubscriptions == null || MySubscriptions.Count == 0)
|
|
{
|
|
var subcriptionsFound = await RestoreSubscriptionsAsync();
|
|
if (subcriptionsFound > 0)
|
|
{
|
|
//Abos gefunden
|
|
WeakReferenceMessenger.Default.Send(new SubscriptionChangedMessage());
|
|
await LoadMySubscriptionsAsync();
|
|
}
|
|
}
|
|
|
|
IsLoading = false;
|
|
IsBusy = false;
|
|
if (MySubscriptions != null && MySubscriptions.Any())
|
|
{
|
|
await ShowSubscriptionSuccessPopupAsync();
|
|
}
|
|
}
|
|
|
|
private async Task ShowSubscriptionSuccessPopupAsync(bool force = false)
|
|
{
|
|
if (_hasShownSuccessPopupThisSession && !force)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var popupResult = await _popupService.ShowPopupAsync<SubscriptionSuccessPopup, SubscriptionSuccessResult>(
|
|
Shell.Current
|
|
);
|
|
|
|
_hasShownSuccessPopupThisSession = true;
|
|
|
|
var result = popupResult.Result;
|
|
if (result != null && result.HasOpenedStrayz)
|
|
{
|
|
// User hat STRAYZ geöffnet
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Laden der verfügbaren Abos
|
|
/// </summary>
|
|
/// <returns>Task</returns>
|
|
private async Task LoadSubscriptionsAsync()
|
|
{
|
|
try
|
|
{
|
|
var subList = new List<SubscriptionModel>();
|
|
using var cts = new CancellationTokenSource(Core.Common.Constants.ListTimeout);
|
|
var subscriptionsResult = await _subscriptionService.GetSubscriptionsAsync(AppMode.DogOwner, SelectedLanguage, App.CurrentUser.AccessToken, cts.Token);
|
|
|
|
if (subscriptionsResult.Success && subscriptionsResult.Value.Any())
|
|
{
|
|
var subscriptionsToShow = new List<Subscription>();
|
|
var subscriptions = subscriptionsResult.Value;
|
|
foreach (var subscription in subscriptions)
|
|
{
|
|
if (subscription.Hidden)
|
|
continue;
|
|
|
|
if (subscription.MaxRegisteredDate.HasValue && App.CurrentAppUser.Created > subscription.MaxRegisteredDate.Value)
|
|
continue;
|
|
|
|
subscriptionsToShow.Add(subscription);
|
|
|
|
subList.Add(new SubscriptionModel(subscription));
|
|
}
|
|
|
|
Subscriptions = subscriptionsToShow.OrderBy(c => c.Name).ToObservableCollection();
|
|
SubscriptionModels = subList.OrderBy(c => c.Subscription.Length).ToObservableCollection();
|
|
if (SubscriptionModels.Any())
|
|
SubscriptionModels.Last().IsSelected = true;
|
|
}
|
|
else
|
|
{
|
|
Subscriptions = null;
|
|
SubscriptionModels = null;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await TrackErrorAsync(ex, true);
|
|
Subscriptions = null;
|
|
SubscriptionModels = null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Laden der gebuchten Abos
|
|
/// </summary>
|
|
/// <returns>Task</returns>
|
|
private async Task LoadMySubscriptionsAsync()
|
|
{
|
|
try
|
|
{
|
|
using var cts = new CancellationTokenSource(Core.Common.Constants.ListTimeout);
|
|
var subscriptionsResult = await _subscriptionService.GetMyActiveSubscriptionsAsync(SelectedLanguage, App.CurrentUser.AccessToken, cts.Token);
|
|
|
|
if (subscriptionsResult.Success && subscriptionsResult.Value.Any())
|
|
{
|
|
var subscriptions = subscriptionsResult.Value;
|
|
MySubscriptions = subscriptions.OrderBy(c => c.SubscriptionName).ToObservableCollection();
|
|
}
|
|
else
|
|
{
|
|
MySubscriptions = null;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await TrackErrorAsync(ex, true);
|
|
Subscriptions = null;
|
|
}
|
|
|
|
ShowSubscriptions = MySubscriptions == null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Prüfen der Abos wenn welche vorhanden
|
|
/// </summary>
|
|
/// <returns>Task</returns>
|
|
private async Task ValidateSubscriptionsAsync()
|
|
{
|
|
if (MySubscriptions != null && MySubscriptions.Any())
|
|
{
|
|
var hasChanges = false;
|
|
|
|
//Prüfen jedes Abos ob es wirklich noch passt...
|
|
if (IsBusy)
|
|
return;
|
|
|
|
IsBusy = true;
|
|
|
|
try
|
|
{
|
|
var connected = await CrossInAppBilling.Current.ConnectAsync();
|
|
if (!connected)
|
|
return;
|
|
|
|
var subscriptionsFound = await CrossInAppBilling.Current.GetPurchasesAsync(ItemType.Subscription);
|
|
|
|
if (subscriptionsFound != null && subscriptionsFound.Any())
|
|
{
|
|
foreach (var subscription in MySubscriptions.Where(c => c.Manual == false))
|
|
{
|
|
InAppBillingPurchase? recentSubscription = null;
|
|
var productSubscriptions = subscriptionsFound.Where(c => c.ProductId == subscription.SubscriptionCode);
|
|
if (productSubscriptions != null && productSubscriptions.Any())
|
|
{
|
|
var sorted = productSubscriptions.OrderByDescending(i => i.TransactionDateUtc).ToList();
|
|
recentSubscription = sorted.FirstOrDefault();
|
|
}
|
|
|
|
if (recentSubscription != null)
|
|
{
|
|
if (recentSubscription.State == PurchaseState.PaymentPending || recentSubscription.State == PurchaseState.Deferred || recentSubscription.State == PurchaseState.Purchasing || recentSubscription.State == PurchaseState.Restored)
|
|
continue;
|
|
}
|
|
|
|
if (recentSubscription != null && recentSubscription.State == PurchaseState.Purchased)
|
|
{
|
|
if (DeviceInfo.Platform == DevicePlatform.Android)
|
|
{
|
|
var date = recentSubscription.TransactionDateUtc;
|
|
while (date < DateTime.UtcNow)
|
|
date = subscription.AddLenght(date);
|
|
|
|
if (date > subscription.ExpirationDate)
|
|
{
|
|
//Abo aktualisieren
|
|
using var cts = new CancellationTokenSource(Core.Common.Constants.CreateTimeout);
|
|
var platform = DeviceInfo.Platform == DevicePlatform.iOS ? Platform.Ios : Platform.Android;
|
|
var result = await _subscriptionService.RenewSubscriptionAsync(subscription.Id, App.CurrentAppUser.Id, SelectedLanguage, platform, App.CurrentUser.AccessToken, cts.Token);
|
|
if (result.Success)
|
|
{
|
|
var dict = new Dictionary<string, string>
|
|
{
|
|
{ "Subscription", subscription.SubscriptionCode },
|
|
{ "Status", recentSubscription.State.ToString() },
|
|
{ "TransactionDateUtc", recentSubscription.TransactionDateUtc.ToString() },
|
|
{ "ExpirationDate", date.ToString() }
|
|
};
|
|
await TrackEventAsync("Subscription_Renew_Success", dict);
|
|
hasChanges = true;
|
|
}
|
|
else
|
|
{
|
|
var dict = new Dictionary<string, string>
|
|
{
|
|
{ "Subscription", subscription.SubscriptionCode },
|
|
{ "Status", recentSubscription.State.ToString() },
|
|
{ "TransactionDateUtc", recentSubscription.TransactionDateUtc.ToString() },
|
|
{ "ExpirationDate", date.ToString() }
|
|
};
|
|
await TrackEventAsync("Subscription_Renew_Failed", dict);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var dict = new Dictionary<string, string>
|
|
{
|
|
{ "Subscription", subscription.SubscriptionCode },
|
|
{ "Status", recentSubscription.State.ToString() },
|
|
{ "TransactionDateUtc", recentSubscription.TransactionDateUtc.ToString() },
|
|
{ "ExpirationDate", date.ToString() }
|
|
};
|
|
await TrackEventAsync("Subscription_Still_Valid", dict);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (recentSubscription.TransactionDateUtc > subscription.ExpirationDate)
|
|
{
|
|
//Abo aktualisieren
|
|
using var cts = new CancellationTokenSource(Core.Common.Constants.CreateTimeout);
|
|
var platform = DeviceInfo.Platform == DevicePlatform.iOS ? Platform.Ios : Platform.Android;
|
|
var result = await _subscriptionService.RenewSubscriptionAsync(subscription.Id, App.CurrentAppUser.Id, SelectedLanguage, platform, App.CurrentUser.AccessToken, cts.Token);
|
|
if (result.Success)
|
|
{
|
|
var dict = new Dictionary<string, string>
|
|
{
|
|
{ "Subscription", subscription.SubscriptionCode },
|
|
{ "Status", recentSubscription.State.ToString() },
|
|
{ "TransactionDateUtc", recentSubscription.TransactionDateUtc.ToString() }
|
|
};
|
|
await TrackEventAsync("Subscription_Renew_Success", dict);
|
|
hasChanges = true;
|
|
}
|
|
else
|
|
{
|
|
var dict = new Dictionary<string, string>
|
|
{
|
|
{ "Subscription", subscription.SubscriptionCode },
|
|
{ "Status", recentSubscription.State.ToString() },
|
|
{ "TransactionDateUtc", recentSubscription.TransactionDateUtc.ToString() }
|
|
};
|
|
await TrackEventAsync("Subscription_Renew_Failed", dict);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var dict = new Dictionary<string, string>
|
|
{
|
|
{ "Subscription", subscription.SubscriptionCode },
|
|
{ "TransactionDateUtc", recentSubscription.TransactionDateUtc.ToString() },
|
|
{ "Status", recentSubscription.State.ToString() }
|
|
};
|
|
await TrackEventAsync("Subscription_Still_Valid", dict);
|
|
}
|
|
}
|
|
|
|
if (DeviceInfo.Platform == DevicePlatform.Android && recentSubscription.IsAcknowledged == false)
|
|
{
|
|
try
|
|
{
|
|
using var cts = new CancellationTokenSource(Core.Common.Constants.CreateTimeout);
|
|
var finalizeResults = await CrossInAppBilling.Current.FinalizePurchaseAsync([recentSubscription.TransactionIdentifier], cts.Token);
|
|
var finalizeDict = finalizeResults.ToDictionary(finalizeResult => finalizeResult.Id, finalizeResult => finalizeResult.Success.ToString());
|
|
await TrackEventAsync("Subscription_Purchase_Finalize", finalizeDict);
|
|
hasChanges = true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await TrackErrorAsync(ex);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
using var cts = new CancellationTokenSource(Core.Common.Constants.CreateTimeout);
|
|
var platform = DeviceInfo.Platform == DevicePlatform.iOS ? Platform.Ios : Platform.Android;
|
|
var result = await _subscriptionService.CancelSubscriptionAsync(subscription.Id, App.CurrentAppUser.Id, SelectedLanguage, platform, App.CurrentUser.AccessToken, cts.Token);
|
|
if (result.Success)
|
|
{
|
|
await TrackEventAsync("Subscription_Cancel_Success");
|
|
hasChanges = true;
|
|
}
|
|
else
|
|
{
|
|
await TrackEventAsync("Subscription_Cancel_Failed");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//Es wurde keine aktive Subscription gefunden. Daher stornieren wir alle Abos die der AppUser hat
|
|
foreach (var subscription in MySubscriptions.Where(c => c.Manual == false))
|
|
{
|
|
using var cts = new CancellationTokenSource(Core.Common.Constants.CreateTimeout);
|
|
var platform = DeviceInfo.Platform == DevicePlatform.iOS ? Platform.Ios : Platform.Android;
|
|
var result = await _subscriptionService.CancelSubscriptionAsync(subscription.Id, App.CurrentAppUser.Id, SelectedLanguage, platform, App.CurrentUser.AccessToken, cts.Token);
|
|
if (result.Success)
|
|
{
|
|
await TrackEventAsync("Subscription_Cancel_Success");
|
|
hasChanges = true;
|
|
}
|
|
else
|
|
{
|
|
await TrackEventAsync("Subscription_Cancel_Failed");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await TrackErrorAsync(ex, true);
|
|
}
|
|
finally
|
|
{
|
|
await CrossInAppBilling.Current.DisconnectAsync();
|
|
IsBusy = false;
|
|
}
|
|
|
|
if (hasChanges)
|
|
{
|
|
WeakReferenceMessenger.Default.Send(new SubscriptionChangedMessage());
|
|
await LoadMySubscriptionsAsync();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Prüfen ob abos vorhanden sind, welche nicht am Server aber am App- und Playstore sind
|
|
/// </summary>
|
|
/// <returns>Task</returns>
|
|
private async Task<int> RestoreSubscriptionsAsync()
|
|
{
|
|
var subscriptionsFoundCount = 0;
|
|
if (Subscriptions != null && Subscriptions.Any())
|
|
{
|
|
var hasChanges = false;
|
|
|
|
//Prüfen jedes Abos ob es wirklich noch passt...
|
|
if (IsBusy)
|
|
return -1;
|
|
|
|
IsBusy = true;
|
|
|
|
try
|
|
{
|
|
var connected = await CrossInAppBilling.Current.ConnectAsync();
|
|
if (!connected)
|
|
return -1;
|
|
|
|
var subscriptionsFound = await CrossInAppBilling.Current.GetPurchasesAsync(ItemType.Subscription);
|
|
|
|
if (subscriptionsFound != null && subscriptionsFound.Any())
|
|
{
|
|
//Durchgehen aller verfügbaren Abos und prüfen ob in der Antwort ein passendes Abo gefunden wurde
|
|
foreach (var subscription in Subscriptions)
|
|
{
|
|
InAppBillingPurchase? recentSubscription = null;
|
|
var productSubscriptions = subscriptionsFound.Where(c => c.ProductId == subscription.Code);
|
|
if (productSubscriptions != null && productSubscriptions.Any())
|
|
{
|
|
var sorted = productSubscriptions.OrderByDescending(i => i.TransactionDateUtc).ToList();
|
|
recentSubscription = sorted.FirstOrDefault();
|
|
}
|
|
|
|
//Nun prüfen ob in den gebuchten Abos ein passendes Abo gefunden wurde und unterschiedlich behandeln
|
|
AppUserSubscription? mySubscription = null;
|
|
|
|
if (MySubscriptions != null)
|
|
mySubscription = MySubscriptions.FirstOrDefault(c => c.SubscriptionCode == subscription.Code);
|
|
|
|
if (mySubscription != null)
|
|
{
|
|
//Wird bereits mit ValidateSubscriptionAsync geprüft
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
//Es gibt keines. Hier muss ein neues Abo gebucht werden wenn die Antwort entsprechend lautet
|
|
if (recentSubscription == null)
|
|
continue;
|
|
|
|
if (recentSubscription.State == PurchaseState.Purchased || recentSubscription.State == PurchaseState.Restored)
|
|
{
|
|
//Anlegen eines neuen Abos
|
|
DateTime? expirationDate;
|
|
if (DeviceInfo.Platform == DevicePlatform.Android)
|
|
{
|
|
expirationDate = recentSubscription.TransactionDateUtc;
|
|
while (expirationDate < DateTime.UtcNow)
|
|
expirationDate = subscription.AddLenght(expirationDate.Value);
|
|
}
|
|
else
|
|
{
|
|
expirationDate = recentSubscription.TransactionDateUtc;
|
|
}
|
|
|
|
//Nun anlegen des Abos mit Ablaufdatum berechnet
|
|
string inAppBillingPurchaseInfo;
|
|
try
|
|
{
|
|
inAppBillingPurchaseInfo = JsonSerializer.Serialize(recentSubscription);
|
|
inAppBillingPurchaseInfo = WebUtility.HtmlEncode(inAppBillingPurchaseInfo);
|
|
}
|
|
catch
|
|
{
|
|
inAppBillingPurchaseInfo = string.Empty;
|
|
}
|
|
|
|
using var cts = new CancellationTokenSource(Core.Common.Constants.CreateTimeout);
|
|
var platform = DeviceInfo.Platform == DevicePlatform.iOS ? Platform.Ios : Platform.Android;
|
|
var bookingResult = await _subscriptionService.CreateSubscriptionAsync(App.CurrentAppUser.Id, subscription.Id, SelectedLanguage, inAppBillingPurchaseInfo, expirationDate, platform, App.CurrentUser.AccessToken, cts.Token);
|
|
if (bookingResult.Success)
|
|
{
|
|
var dict = new Dictionary<string, string>
|
|
{
|
|
{ "Subscription", subscription.Code },
|
|
{ "Status", recentSubscription.State.ToString() },
|
|
{ "TransactionDateUtc", recentSubscription.TransactionDateUtc.ToString() },
|
|
{ "ExpirationDate", expirationDate.ToString() }
|
|
};
|
|
await TrackEventAsync("Subscription_Purchase_BookingCreated", dict);
|
|
hasChanges = true;
|
|
subscriptionsFoundCount += 1;
|
|
}
|
|
else
|
|
{
|
|
var errDict = new Dictionary<string, string>
|
|
{
|
|
{ "Subscription", subscription.Code },
|
|
{ "errorMessage", bookingResult.ErrorMessage },
|
|
{ "errorCode", bookingResult.ErrorCode.ToString() }
|
|
};
|
|
await TrackEventAsync("Subscription_Purchase_BookingFailed", errDict);
|
|
}
|
|
|
|
if (DeviceInfo.Platform == DevicePlatform.Android && recentSubscription.IsAcknowledged == false)
|
|
{
|
|
try
|
|
{
|
|
var finalizeResults = await CrossInAppBilling.Current.FinalizePurchaseAsync([recentSubscription.TransactionIdentifier], cts.Token);
|
|
var finalizeDict = finalizeResults.ToDictionary(finalizeResult => finalizeResult.Id, finalizeResult => finalizeResult.Success.ToString());
|
|
await TrackEventAsync("Subscription_Purchase_Finalize", finalizeDict);
|
|
hasChanges = true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await TrackErrorAsync(ex);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await TrackErrorAsync(ex, true);
|
|
}
|
|
finally
|
|
{
|
|
await CrossInAppBilling.Current.DisconnectAsync();
|
|
IsBusy = false;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
return subscriptionsFoundCount;
|
|
}
|
|
|
|
#region Initialisierung
|
|
|
|
/// <summary>
|
|
/// Initialisieren des Viewmodels.
|
|
/// Soll überschrieben werden um lazy loading in Viewmodels ermöglicht wird
|
|
/// </summary>
|
|
/// <returns>Task</returns>
|
|
public override async Task InitializeAsync(object sender)
|
|
{
|
|
await base.InitializeAsync(sender);
|
|
IsBusy = true;
|
|
HasError = false;
|
|
ErrorMessage = string.Empty;
|
|
|
|
_hasShownSuccessPopupThisSession = false;
|
|
|
|
await TrackPageViewAsync("Subscriptions");
|
|
|
|
ShowSubscriptions = false;
|
|
IsBusy = true;
|
|
Subscriptions = null;
|
|
MySubscriptions = null;
|
|
await Task.Delay(Core.Common.Constants.AnimationDelayList);
|
|
await LoadAsync();
|
|
//#pragma warning disable CS4014
|
|
// Task.Run(LoadAsync);
|
|
//#pragma warning restore CS4014
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Entladen des Viewmodels.
|
|
/// Soll überschrieben werden
|
|
/// </summary>
|
|
/// <returns>Task</returns>
|
|
public override Task DisappearingAsync(object sender)
|
|
{
|
|
IsBusy = false;
|
|
HasError = false;
|
|
ErrorMessage = string.Empty;
|
|
|
|
_hasShownSuccessPopupThisSession = false;
|
|
|
|
return base.DisappearingAsync(sender);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Resumed
|
|
|
|
/// <summary>
|
|
/// Eventhandler wenn die App aus dem Sleepmode kommt
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
internal override async Task AppResumed()
|
|
{
|
|
await _dispatcher.DispatchAsync(async () =>
|
|
{
|
|
Subscriptions = null;
|
|
MySubscriptions = null;
|
|
await LoadAsync();
|
|
});
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|