public async Task <PurchaseAttemptResult> PurchaseAttempt(string userId, string platform, string productId, string purchaseToken)
        {
            var subscription = await Repository.GetByPurchaseToken(purchaseToken);

            if (subscription is not null)
            {
                throw new Exception($"An existing subscription found for token '{purchaseToken}'.");
            }

            var storeConnector   = StoreConnectorResolver.Resolve(platform);
            var subscriptionInfo = await storeConnector.GetSubscriptionInfo(new SubscriptionInfoArgs
            {
                UserId        = userId,
                ProductId     = productId,
                PurchaseToken = purchaseToken
            });

            if (subscriptionInfo.Status == SubscriptionQueryStatus.NotFound)
            {
                return(PurchaseAttemptResult.Failed);
            }
            if (await IsSubscriptionMismatched(userId, subscriptionInfo))
            {
                return(PurchaseAttemptResult.UserMismatched);
            }

            subscription = await Repository.GetByTransactionId(subscriptionInfo.TransactionId);

            if (subscription is not null)
            {
                throw new Exception($"An existing subscription found for transaction '{subscriptionInfo.TransactionId}'.");
            }

            await Repository.AddSubscription(new Subscription
            {
                Id               = Guid.NewGuid().ToString(),
                UserId           = userId,
                Platform         = platform,
                ProductId        = productId,
                TransactionId    = subscriptionInfo.TransactionId,
                TransactionDate  = subscriptionInfo.SubscriptionDate,
                PurchaseToken    = purchaseToken,
                LastUpdate       = LocalTime.UtcNow,
                SubscriptionDate = subscriptionInfo.SubscriptionDate,
                ExpirationDate   = subscriptionInfo.ExpirationDate,
                CancellationDate = subscriptionInfo.CancellationDate,
                AutoRenews       = subscriptionInfo.AutoRenews
            });

            return(PurchaseAttemptResult.Succeeded);
        }
        public async Task <VerifyPurchaseResult> VerifyPurchase(string userId, string platform, string productId, string transactionId, string receiptData)
        {
            var storeConnector = StoreConnectorResolver.Resolve(platform);
            var status         = await storeConnector.VerifyPurchase(new VerifyPurchaseArgs
            {
                UserId      = userId,
                ReceiptData = receiptData
            });

            if (status != PurchaseVerificationStatus.Verified)
            {
                return(VerifyPurchaseResult.From(status));
            }

            var subscription = await Repository.GetByTransactionId(transactionId);

            if (subscription is not null)
            {
                if (subscription.UserId != userId)
                {
                    throw new Exception("Provided purchase token is associated with another user!");
                }

                if (subscription.Platform != platform)
                {
                    throw new Exception("Provided purchase token is associated with another platform!");
                }

                if (subscription.ProductId != productId)
                {
                    throw new Exception("Provided purchase token is associated with another product!");
                }
            }
            else
            {
                await Repository.AddSubscription(new Subscription
                {
                    Id            = Guid.NewGuid().ToString(),
                    UserId        = userId,
                    Platform      = platform,
                    ProductId     = productId,
                    TransactionId = transactionId,
                    ReceiptData   = receiptData,
                    LastUpdate    = LocalTime.UtcNow
                });
            }

            return(VerifyPurchaseResult.Succeeded());
        }