public async Task ignore_duplicate_month_for_sub_gift()
        {
            User gifter = MockUser("gifter", monthsSubscribed: 2, SubscriptionTier.Prime, loyaltyLeague: 2);
            const SubscriptionTier tier = SubscriptionTier.Tier3;
            User recipient = MockUser("recipient", monthsSubscribed: 1, subscriptionTier: tier, loyaltyLeague: 5);
            Mock <IBank <User> >   bankMock              = new();
            Mock <IUserRepo>       userRepoMock          = new();
            ISubscriptionProcessor subscriptionProcessor = new SubscriptionProcessor(
                NullLogger <SubscriptionProcessor> .Instance,
                bankMock.Object, userRepoMock.Object, Mock.Of <ISubscriptionLogRepo>(), Mock.Of <ILinkedAccountRepo>());

            SubscriptionInfo subscriptionInfo = new(recipient, NumMonths : 1, StreakMonths : 0, tier, "Sub Plan Name",
                                                    Instant.MinValue, "sub message", ImmutableList <EmoteOccurrence> .Empty);

            (ISubscriptionProcessor.SubResult subResult, ISubscriptionProcessor.SubGiftResult subGiftResult) =
                await subscriptionProcessor.ProcessSubscriptionGift(
                    new SubscriptionGiftInfo(subscriptionInfo, gifter, 1, false));

            Assert.IsInstanceOf <ISubscriptionProcessor.SubGiftResult.SameMonth>(subGiftResult);
            var sameMonthGiftResult = (ISubscriptionProcessor.SubGiftResult.SameMonth)subGiftResult;

            Assert.That(sameMonthGiftResult.Month, Is.EqualTo(1));
            bankMock.VerifyNoOtherCalls();

            Assert.IsInstanceOf <ISubscriptionProcessor.SubResult.SameMonth>(subResult);
            var sameMonthSubResult = (ISubscriptionProcessor.SubResult.SameMonth)subResult;

            Assert.That(sameMonthSubResult.Month, Is.EqualTo(1));
            bankMock.VerifyNoOtherCalls();
        }
Пример #2
0
 internal ConsoleProfile(string gamertag, Xuid onlineXuid, Xuid offlineXuid, SubscriptionTier tier, XboxLiveCountry country, IXboxConsole console, IProfileSupport profileSupport) : base(gamertag, onlineXuid)
 {
     this.OfflineXuid    = offlineXuid;
     this.Tier           = tier;
     this.Country        = country;
     this.Console        = console;
     this.profileSupport = profileSupport;
     //this.Friends = FriendsManagerFactory.CreateFriendsManager(this);
 }
 internal ConsoleProfile(string gamertag, Xuid onlineXuid, Xuid offlineXuid, SubscriptionTier tier, XboxLiveCountry country, IXboxConsole console, IProfileSupport profileSupport)
     : base(gamertag, onlineXuid)
 {
     this.OfflineXuid = offlineXuid;
     this.Tier = tier;
     this.Country = country;
     this.Console = console;
     this.profileSupport = profileSupport;
     //this.Friends = FriendsManagerFactory.CreateFriendsManager(this);
 }
        private static async Task <bool> TrySharePaymentWithReferrer(BitcornContext dbContext,
                                                                     SubscriptionResponse output,
                                                                     SubRequest subRequest,
                                                                     Subscription subInfo,
                                                                     SubscriptionTier requestedTierInfo,
                                                                     User user,
                                                                     int subscriptionPaymentRecipientId,
                                                                     decimal cost,
                                                                     SubscriptionState previousSubState,
                                                                     SubTx subTx)
        {
            //if (previousSubState != SubscriptionState.None) return false;
            //if subscription referrar share is defined and its between 0 and 1
            //get subscriber userreferral info
            var userReferral = await dbContext.UserReferral.FirstOrDefaultAsync(r => r.UserId == user.UserId);

            if (userReferral != null && userReferral.ReferralId != 0)
            {
                //get info of the person who referred the subscriber
                var referrer = await dbContext.Referrer.FirstOrDefaultAsync(u => u.ReferralId == userReferral.ReferralId);

                //check if referrer can get rewards
                if (ReferralUtils.IsValidReferrer(referrer))
                {
                    var referrerUser = await dbContext.JoinUserModels().FirstOrDefaultAsync(u => u.UserId == referrer.UserId);

                    if (referrerUser != null && !referrerUser.IsBanned)
                    {
                        bool success = false;
                        if (subInfo.ReferrerPercentage != null && subInfo.ReferrerPercentage > 0 && subInfo.ReferrerPercentage <= 1)
                        {
                            //get the user who received the subscription payment
                            var subscriptionPaymentRecipient = await dbContext.JoinUserModels().FirstOrDefaultAsync(u => u.UserId == subscriptionPaymentRecipientId);

                            success = await ShareSubscriptionPaymentWithReferrer(dbContext,
                                                                                 subInfo,
                                                                                 cost,
                                                                                 subTx,
                                                                                 referrer,
                                                                                 referrerUser,
                                                                                 subscriptionPaymentRecipient);
                        }

                        if (previousSubState == SubscriptionState.None && subInfo.Name == "BITCORNFarms")
                        {
                            await ReferralUtils.BonusPayout(dbContext, userReferral, referrer, user, referrerUser, referrerUser.UserStat);
                        }

                        return(success);
                    }
                }
            }

            return(false);
        }
Пример #5
0
        public async Task <SubscriptionLog> LogSubscription(
            string userId, Instant timestamp, int monthsStreak, int monthsNumPrev,
            int monthsNumNew, int monthsDifference, int loyaltyLeaguePrev, int loyaltyLeagueNew, int loyaltyCompletions,
            int rewardTokens, string?subMessage, SubscriptionTier subPlan, string subPlanName)
        {
            var item = new SubscriptionLog(
                string.Empty, userId, timestamp,
                monthsStreak, monthsNumPrev, monthsNumNew, monthsDifference,
                loyaltyLeaguePrev, loyaltyLeagueNew, loyaltyCompletions, rewardTokens,
                subMessage, subPlan, subPlanName);
            await Collection.InsertOneAsync(item);

            Debug.Assert(item.Id.Length > 0, "The MongoDB driver injected a generated ID");
            return(item);
        }
Пример #6
0
 public ConsoleProfile CreateConsoleProfile(bool online, XboxLiveCountry country, SubscriptionTier tier, string gamertag)
 {
     return(this.profileSupport.CreateConsoleProfile(online, country, tier, gamertag));
 }
Пример #7
0
 public ConsoleProfile CreateConsoleProfile(bool online, XboxLiveCountry country, SubscriptionTier tier)
 {
     return(this.CreateConsoleProfile(online, country, tier, null));
 }
        public static async Task <decimal> CalculateUsdtToCornCost(BitcornContext dbContext, SubscriptionTier tier)
        {
            var price = await ProbitApi.GetCornPriceAsync(dbContext);

            return(CalculateUsdtToCornCost(price, tier));
        }
 public static decimal CalculateUsdtToCornCost(decimal cornUsdt, SubscriptionTier tier)
 {
     return(Math.Ceiling(tier.CostUsdt.Value / cornUsdt));
 }
        private static async Task <SubscriptionResponse> ProcessSubscription(BitcornContext dbContext, SubscriptionResponse output, SubRequest subRequest, Subscription subInfo, SubscriptionTier requestedTierInfo, User user)
        {
            decimal cost = 0;

            //if tier usdt cost has been initialized, the corn cost has to be calculated
            if (requestedTierInfo.CostUsdt != null && requestedTierInfo.CostUsdt > 0)
            {
                cost = await CalculateUsdtToCornCost(dbContext, requestedTierInfo);
            }
            // check if cost is initialized properly
            else if (requestedTierInfo.CostCorn != null && requestedTierInfo.CostCorn > 0)
            {
                cost = requestedTierInfo.CostCorn.Value;
            }
            else
            {
                throw new ArgumentException($"Invalid cost setting on subscription tier id:{requestedTierInfo.SubscriptionId}");
            }
            //set the amount that will be removed from subscriber to the response object
            output.Cost = cost;
            //initialize array of existing subscriptions
            UserSubcriptionTierInfo[] existingSubscriptions = new UserSubcriptionTierInfo[0];
            if (user != null)
            {
                //set data to existing subscriptions array
                existingSubscriptions = await GetUserSubscriptions(dbContext, user)
                                        .Where(t => t.SubscriptionTier.SubscriptionId == subInfo.SubscriptionId).ToArrayAsync();
            }
            //initialize reference to existing subtierinfo
            UserSubcriptionTierInfo existingSubscription = null;
            //initialize current substate
            var subState = SubscriptionState.None;

            //if any subscriptions were found
            if (existingSubscriptions.Any())
            {
                //set existing subtierinfo
                existingSubscription = existingSubscriptions[0];
                //if sub has expired, set substate to expired
                if (subInfo.HasExpired(existingSubscription.UserSubscription))
                {
                    subState = SubscriptionState.Expired;
                }
                //if existing sub has not expired, but the tier is below, set subState to TierDown
                else if (existingSubscription.SubscriptionTier.Tier < requestedTierInfo.Tier)
                {
                    subState = SubscriptionState.TierDown;
                }
                //else, the user is subscribed
                else
                {
                    subState = SubscriptionState.Subscribed;
                }
            }
            //initialize reference to usersubscription & tx request
            UserSubscription sub       = null;
            TxRequest        txRequest = null;
            //if current user sub state is not subscribed & the client confirmed the cost to be equal to the cost amount, attempt to subscribe
            var costDiff = Math.Abs(subRequest.Amount - cost);

            if (subState != SubscriptionState.Subscribed && costDiff <= 100000)//subRequest.Amount == cost)
            {
                //initialize recipient of the transaction
                string[] to = new string[1];
                //default to bitcornhub if no subscription owner has been set
                int recipientId = TxUtils.BitcornHubPK;
                //if subscription owner is set, overwrite bitcornhub
                if (subInfo.OwnerUserId != null && subInfo.OwnerUserId > 0)
                {
                    recipientId = subInfo.OwnerUserId.Value;
                }

                to[0] = $"userid|{recipientId}";
                //initialize tx request
                txRequest = new TxRequest(user, cost, subRequest.Platform, "$sub", to);
                //prepare transaction for saving
                var processInfo = await TxUtils.ProcessRequest(txRequest, dbContext);

                var transactions = processInfo.Transactions;
                if (transactions != null && transactions.Length > 0)
                {
                    StringBuilder sql = new StringBuilder();
                    //check if transaction can be executed
                    if (processInfo.WriteTransactionOutput(sql))
                    {
                        //transaction is ready to be saved
                        switch (subState)
                        {
                        case SubscriptionState.None:
                            //user was previously not subscribed, create instance of usersubscription and point it to the user
                            sub = new UserSubscription();
                            sub.SubscriptionId     = subInfo.SubscriptionId;
                            sub.SubscriptionTierId = requestedTierInfo.SubscriptionTierId;
                            sub.UserId             = user.UserId;
                            sub.FirstSubDate       = DateTime.Now;
                            sub.SubCount           = 1;
                            dbContext.UserSubscription.Add(sub);
                            break;

                        case SubscriptionState.TierDown:
                        case SubscriptionState.Expired:
                            //previous subscription was found, update subscription tier
                            existingSubscription.UserSubscription.SubscriptionTierId = requestedTierInfo.SubscriptionTierId;
                            existingSubscription.UserSubscription.SubCount          += 1;
                            sub = existingSubscription.UserSubscription;
                            break;

                        default:
                            break;
                        }
                        //set subscription date to now
                        sub.LastSubDate = DateTime.Now;

                        await DbOperations.ExecuteSqlRawAsync(dbContext, sql.ToString());

                        await dbContext.SaveAsync(IsolationLevel.RepeatableRead);

                        //create subtx that will link user, corntx and usersubscription together
                        var subTx = new SubTx();
                        subTx.UserId             = user.UserId;
                        subTx.SubTxId            = transactions[0].TxId.Value;
                        subTx.UserSubscriptionId = sub.UserSubscriptionId;
                        dbContext.SubTx.Add(subTx);

                        //if user was not subscribed before, attempt to share the payment with a referrer
                        if (!await TrySharePaymentWithReferrer(dbContext, output, subRequest, subInfo, requestedTierInfo, user, recipientId, cost, subState, subTx))
                        {
                            await dbContext.SaveAsync();
                        }

                        subState = SubscriptionState.Subscribed;
                    }
                    //append receipt object with what client requested
                    await TxUtils.AppendTxs(transactions, dbContext, subRequest.Columns);

                    var tx = transactions[0];

                    output.TxId = tx.TxId;
                    output.User = tx.From;
                }
            }
            //couldn't process transaction
            if (txRequest == null)
            {
                //fill out response object
                await PopuplateUserResponse(dbContext, subRequest, output, user);

                if (existingSubscription != null)
                {
                    sub = existingSubscription.UserSubscription;
                }
            }

            if (subState == SubscriptionState.Subscribed && sub != null)
            {
                var end = output.SubscriptionEndTime = sub.LastSubDate.Value.AddDays(subInfo.Duration);
                //calculate days left
                output.DaysLeft = Math.Ceiling((end.Value - DateTime.Now).TotalDays);
                //setup sub info
                output.UserSubscriptionInfo = await GetUserSubscriptions(dbContext, user)
                                              .Where(t => t.SubscriptionTier.SubscriptionId == subInfo.SubscriptionId).FirstOrDefaultAsync();
            }
            return(output);
        }
 /// The "rank" of a subscription tier is one increment per $5 worth of subscriptions. This allows the tiers
 /// to be processed numerically instead of having to deal with every possible tier individually.
 public static int ToRank(this SubscriptionTier tier) =>
 tier switch
 {
        public async Task handle_regular_subscription()
        {
            // GIVEN
            const SubscriptionTier subscriptionTier = SubscriptionTier.Tier2;
            Instant subscribedAt = Instant.FromUnixTimeSeconds(123);
            User    user         = MockUser("user", monthsSubscribed: 2, subscriptionTier, loyaltyLeague: 4);

            Mock <IBank <User> >        bankMock                = new();
            Mock <IUserRepo>            userRepoMock            = new();
            Mock <ISubscriptionLogRepo> subscriptionLogRepoMock = new();
            ISubscriptionProcessor      subscriptionProcessor   = new SubscriptionProcessor(
                NullLogger <SubscriptionProcessor> .Instance,
                bankMock.Object, userRepoMock.Object, subscriptionLogRepoMock.Object, Mock.Of <ILinkedAccountRepo>());

            userRepoMock.Setup(r => r.SetIsSubscribed(user, It.IsAny <bool>())).ReturnsAsync(user);
            userRepoMock.Setup(r => r.SetSubscriptionInfo(user, It.IsAny <int>(), It.IsAny <SubscriptionTier>(),
                                                          It.IsAny <int>(), It.IsAny <Instant>())).ReturnsAsync(user);

            // WHEN
            ISubscriptionProcessor.SubResult subResult = await subscriptionProcessor.ProcessSubscription(
                new SubscriptionInfo(
                    user, NumMonths : 3, StreakMonths : 2, subscriptionTier, PlanName : "Tier 2",
                    subscribedAt, Message : "HeyGuys", ImmutableList <EmoteOccurrence> .Empty));

            // THEN
            const int expectedTokens = 10 + (2 * 4) + 10 + (2 * 5); // per rank: 10 base tokens + 2 tokens per league

            // verify result
            Assert.IsInstanceOf <ISubscriptionProcessor.SubResult.Ok>(subResult);
            var okResult = (ISubscriptionProcessor.SubResult.Ok)subResult;

            Assert.That(okResult.CumulativeMonths, Is.EqualTo(3));
            Assert.That(okResult.DeltaTokens, Is.EqualTo(expectedTokens));
            Assert.That(okResult.OldLoyaltyLeague, Is.EqualTo(4));
            Assert.That(okResult.NewLoyaltyLeague, Is.EqualTo(6));
            Assert.IsFalse(okResult.SubCountCorrected);

            // verify tokens were awarded
            IDictionary <string, object?> expectedData = new Dictionary <string, object?>
            {
                ["previous_months_subscribed"] = 2,
                ["new_months_subscribed"]      = 3,
                ["months_difference"]          = 1,
                ["previous_loyalty_tier"]      = 4,
                ["new_loyalty_tier"]           = 6,
                ["loyalty_completions"]        = 2,
            };

            bankMock.Verify(b =>
                            b.PerformTransaction(new Transaction <User>(user, expectedTokens, "subscription", expectedData),
                                                 CancellationToken.None), Times.Once);

            // verify user data was adjusted
            userRepoMock.Verify(r => r.SetIsSubscribed(user, true), Times.Once);
            userRepoMock.Verify(r => r.SetSubscriptionInfo(user, 3, subscriptionTier, 6, subscribedAt), Times.Once);

            // verify subscription was logged
            subscriptionLogRepoMock.Verify(r => r.LogSubscription(
                                               user.Id, subscribedAt,
                                               2, 2, 3, 1,
                                               4, 6, 2, expectedTokens,
                                               "HeyGuys", subscriptionTier, "Tier 2"),
                                           Times.Once);
        }