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
}
}