public IReadOnlyList <CostPeriod> Execute(
            DateTime startTimeInclusive,
            DateTime endTimeExclusive,
            IReadOnlyList <MergedSnapshot> snapshots,
            IReadOnlyList <CreatorPost> creatorPosts)
        {
            var            result       = new List <CostPeriod>();
            MergedSnapshot lastSnapshot = null;

            foreach (var snapshot in snapshots.Concat(new List <MergedSnapshot> {
                null
            }))
            {
                if (lastSnapshot != null)
                {
                    var cost = this.costCalculator.Execute(lastSnapshot, creatorPosts);

                    if (snapshot == null)
                    {
                        var period = new CostPeriod(lastSnapshot.Timestamp, endTimeExclusive, cost);
                        result.Add(period);
                    }
                    else
                    {
                        var period = new CostPeriod(lastSnapshot.Timestamp, snapshot.Timestamp, cost);
                        result.Add(period);
                    }
                }

                lastSnapshot = snapshot;
            }

            return(result);
        }
        public IReadOnlyList <MergedSnapshot> Execute(
            UserId subscriberId,
            UserId creatorId,
            DateTime startTimeInclusive,
            IReadOnlyList <ISnapshot> snapshots)
        {
            if (snapshots.Count == 0)
            {
                return(new List <MergedSnapshot>());
            }

            DateTime initialTimestamp = startTimeInclusive;
            var      firstTimestamp   = snapshots[0].Timestamp;

            if (firstTimestamp < startTimeInclusive)
            {
                initialTimestamp = firstTimestamp;
            }

            var creatorChannels          = CreatorChannelsSnapshot.Default(initialTimestamp, creatorId);
            var creatorFreeAccessUsers   = CreatorFreeAccessUsersSnapshot.Default(initialTimestamp, creatorId);
            var subscriberChannels       = SubscriberChannelsSnapshot.Default(initialTimestamp, subscriberId);
            var subscriber               = SubscriberSnapshot.Default(initialTimestamp, subscriberId);
            var calculatedAccountBalance = CalculatedAccountBalanceSnapshot.Default(initialTimestamp, subscriberId, LedgerAccountType.FifthweekCredit);

            var mergedSnapshots = new List <MergedSnapshot>();

            if (firstTimestamp > initialTimestamp)
            {
                mergedSnapshots.Add(new MergedSnapshot(creatorChannels, creatorFreeAccessUsers, subscriberChannels, subscriber, calculatedAccountBalance));
            }

            foreach (var snapshot in snapshots)
            {
                var assigned = this.TryAssign(snapshot, ref creatorChannels) ||
                               this.TryAssign(snapshot, ref creatorFreeAccessUsers) ||
                               this.TryAssign(snapshot, ref subscriberChannels) ||
                               this.TryAssign(snapshot, ref subscriber) ||
                               this.TryAssign(snapshot, ref calculatedAccountBalance);

                if (!assigned)
                {
                    throw new InvalidOperationException("Unknown snapshot type: " + snapshot.GetType().Name);
                }

                var newMergedSnapshot = new MergedSnapshot(creatorChannels, creatorFreeAccessUsers, subscriberChannels, subscriber, calculatedAccountBalance);

                if (mergedSnapshots.Count > 0 && mergedSnapshots.Last().Timestamp == snapshot.Timestamp)
                {
                    mergedSnapshots.RemoveAt(mergedSnapshots.Count - 1);
                }

                mergedSnapshots.Add(newMergedSnapshot);
            }

            return(mergedSnapshots);
        }
Esempio n. 3
0
        public int Execute(MergedSnapshot snapshot, IReadOnlyList <CreatorPost> creatorPosts)
        {
            // No subscribed channels.
            if (snapshot.SubscriberChannels.SubscribedChannels.Count == 0)
            {
                return(0);
            }

            // On creator guestlist.
            if (snapshot.Subscriber.Email != null &&
                snapshot.CreatorFreeAccessUsers.FreeAccessUserEmails.Contains(snapshot.Subscriber.Email))
            {
                return(0);
            }

            // No money in account.
            if (snapshot.CalculatedAccountBalance.Amount <= 0)
            {
                return(0);
            }

            // Get all subscribed channels...
            var creatorSubscriptions
                        = from s in snapshot.SubscriberChannels.SubscribedChannels
                  let c = snapshot.CreatorChannels.CreatorChannels.FirstOrDefault(v => v.ChannelId.Equals(s.ChannelId))
                          where c != null
                          select new
                {
                Subscription = s,
                Channel      = c
                };

            // ...where creator has posted in the subscriber's channel billing week...
            var subscriptionsWithPosts = creatorPosts == null
                ? creatorSubscriptions
                : creatorSubscriptions.Where(
                v => this.CreatorPostedInBillingWeek(
                    v.Subscription.SubscriptionStartDate,
                    v.Subscription.ChannelId,
                    creatorPosts));

            // ...and the current price has been accepted...
            var acceptedSubscriptions = subscriptionsWithPosts.Where(v => v.Subscription.AcceptedPrice >= v.Channel.Price);

            // ...calculate the total price.
            var totalCost = acceptedSubscriptions.Aggregate(0, (a, s) => a + s.Channel.Price);

            return(totalCost);
        }
        public IReadOnlyList <MergedSnapshot> Execute(
            DateTime startTimeInclusive,
            IReadOnlyList <MergedSnapshot> snapshots)
        {
            if (snapshots.Count == 0 || snapshots[0].Timestamp == startTimeInclusive)
            {
                return(snapshots);
            }

            if (snapshots[0].Timestamp > startTimeInclusive)
            {
                throw new InvalidOperationException("Initial merged snapshot is unexpectedly ahead of start time.");
            }

            // If any snapshot is located at the start time, ignore everything before.
            for (int i = 0; i < snapshots.Count; i++)
            {
                if (snapshots[i].Timestamp == startTimeInclusive)
                {
                    return(snapshots.Skip(i).ToList());
                }
            }

            // We have timestamps either side of the start time, so find the first one which is
            // earlier, bump it to the start time, and ignore any before.
            for (int i = snapshots.Count - 1; i >= 0; i--)
            {
                var snapshot = snapshots[i];
                if (snapshot.Timestamp < startTimeInclusive)
                {
                    var result = snapshots.Skip(i).ToList();
                    result[0] = new MergedSnapshot(
                        startTimeInclusive,
                        snapshot.CreatorChannels,
                        snapshot.CreatorFreeAccessUsers,
                        snapshot.SubscriberChannels,
                        snapshot.Subscriber,
                        snapshot.CalculatedAccountBalance);

                    return(result);
                }
            }

            throw new InvalidOperationException("Unexpectedly did not find required timestamps for trimming.");
        }
        public void WhenOnlyCalculatedAccountBalanceSnapshot_ShouldReturnMergedSnapshot()
        {
            var calculatedAccountBalanceSnapshot = new CalculatedAccountBalanceSnapshot(Now, SubscriberId1, LedgerAccountType.FifthweekCredit, 10);

            var expectedResult = new MergedSnapshot(
                Now,
                CreatorChannelsSnapshot.Default(Now, CreatorId1),
                CreatorFreeAccessUsersSnapshot.Default(Now, CreatorId1),
                SubscriberChannelsSnapshot.Default(Now, SubscriberId1),
                SubscriberSnapshot.Default(Now, SubscriberId1),
                calculatedAccountBalanceSnapshot);

            var result = this.target.Execute(SubscriberId1, CreatorId1, Now, new List <ISnapshot> {
                calculatedAccountBalanceSnapshot
            });

            CollectionAssert.AreEqual(new List <MergedSnapshot> {
                expectedResult
            }, result.ToList());
        }
        public void WhenOnlySubscriberChannelSnapshot_ShouldReturnMergedSnapshot()
        {
            var subscriberChannelSnapshot = new SubscriberChannelsSnapshot(Now, SubscriberId1, new List <SubscriberChannelsSnapshotItem> {
                new SubscriberChannelsSnapshotItem(new ChannelId(Guid.NewGuid()), 100, Now)
            });

            var expectedResult = new MergedSnapshot(
                Now,
                CreatorChannelsSnapshot.Default(Now, CreatorId1),
                CreatorFreeAccessUsersSnapshot.Default(Now, CreatorId1),
                subscriberChannelSnapshot,
                SubscriberSnapshot.Default(Now, SubscriberId1),
                CalculatedAccountBalanceSnapshot.Default(Now, SubscriberId1, LedgerAccountType.FifthweekCredit));

            var result = this.target.Execute(SubscriberId1, CreatorId1, Now, new List <ISnapshot> {
                subscriberChannelSnapshot
            });

            CollectionAssert.AreEqual(new List <MergedSnapshot> {
                expectedResult
            }, result.ToList());
        }
Esempio n. 7
0
        /// <summary>
        /// This method "rolls forward" subscriptions, which means if the user unsubscribed we
        /// make sure the subscription is billed until the end of their billing period by extending
        /// the subscription length here.
        /// </summary>
        public IReadOnlyList <MergedSnapshot> Execute(
            DateTime endTimeExclusive,
            IReadOnlyList <MergedSnapshot> inputSnapshots)
        {
            var snapshots           = inputSnapshots.ToList();
            var activeSubscriptions = new Dictionary <ChannelId, ActiveSubscription>();

            for (int i = 0; i < snapshots.Count; i++)
            {
                var snapshot = snapshots[i];

                foreach (var subscription in snapshot.SubscriberChannels.SubscribedChannels)
                {
                    var billingWeekEndDateExclusive = BillingWeekUtilities.CalculateBillingWeekEndDateExclusive(
                        subscription.SubscriptionStartDate,
                        snapshot.Timestamp);

                    ActiveSubscription activeSubscription;
                    if (activeSubscriptions.TryGetValue(subscription.ChannelId, out activeSubscription))
                    {
                        // The following condition is a relaxation to allow people to briefly re-subscribe without
                        // trigging a whole new billing week, and therefore to reduce anticipated complaints.
                        // If they restarted their subscription before the minumum end date of their
                        // previous subscription, then we do not reset the minimum end date yet.
                        // However if they exceed that billing week then the new subscription's billing week will be used.
                        if (snapshot.Timestamp < activeSubscription.BillingWeekEndDateExclusive)
                        {
                            billingWeekEndDateExclusive = activeSubscription.BillingWeekEndDateExclusive;
                        }

                        activeSubscription = new ActiveSubscription(billingWeekEndDateExclusive, subscription);
                        activeSubscriptions[subscription.ChannelId] = activeSubscription;
                    }
                    else
                    {
                        activeSubscription = new ActiveSubscription(billingWeekEndDateExclusive, subscription);
                        activeSubscriptions.Add(subscription.ChannelId, activeSubscription);
                    }
                }

                // Copy the list so we can modify activeSubscriptions within the loop.
                foreach (var activeSubscription in activeSubscriptions.Values.ToList())
                {
                    var activeChannelId = activeSubscription.Subscription.ChannelId;
                    if (snapshot.SubscriberChannels.SubscribedChannels.All(v => !v.ChannelId.Equals(activeChannelId)))
                    {
                        // The subscriber has unsubscribed from this channel.
                        var billingWeekFinalSnapshotTime = this.GetBillingWeekFinalSnapshotTime(activeSubscription.BillingWeekEndDateExclusive);

                        var subscriptionDuration = snapshot.Timestamp - activeSubscription.Subscription.SubscriptionStartDate;
                        if (subscriptionDuration.TotalDays > DaysInWeek)
                        {
                            // We have exceeded the minimum subscripion period, so there is nothing to adjust.
                            activeSubscriptions.Remove(activeSubscription.Subscription.ChannelId);
                        }
                        else if (snapshot.Timestamp == billingWeekFinalSnapshotTime)
                        {
                            // We are at the end of the billing week, so there is nothing to adjust.
                            activeSubscriptions.Remove(activeSubscription.Subscription.ChannelId);
                        }
                        else if (snapshot.Timestamp < billingWeekFinalSnapshotTime)
                        {
                            if (snapshot.CreatorChannels.CreatorChannels.All(v => !v.ChannelId.Equals(activeChannelId)))
                            {
                                // We are within the billing week, but the channel is no longer published.
                                // So we stop billing at this point.
                                activeSubscriptions.Remove(activeSubscription.Subscription.ChannelId);
                            }
                            else
                            {
                                // We are still within their billing week and the creator still publishes the channel.
                                // We must extend their subscription to this snapshot.
                                var newSnapshot = new MergedSnapshot(
                                    snapshot.Timestamp,
                                    snapshot.CreatorChannels,
                                    snapshot.CreatorFreeAccessUsers,
                                    new SubscriberChannelsSnapshot(
                                        snapshot.SubscriberChannels.Timestamp,
                                        snapshot.SubscriberChannels.SubscriberId,
                                        snapshot.SubscriberChannels.SubscribedChannels.Concat(new[] { activeSubscription.Subscription }).ToList()),
                                    snapshot.Subscriber,
                                    snapshot.CalculatedAccountBalance);

                                snapshots[i] = newSnapshot;

                                if ((i == snapshots.Count - 1 && activeSubscription.BillingWeekEndDateExclusive < endTimeExclusive) ||
                                    (i < snapshots.Count - 1 && snapshots[i + 1].Timestamp > billingWeekFinalSnapshotTime))
                                {
                                    // Either this is the last snapshot and the billing week ends before the end date of
                                    // our search range, or the next snapshot will be in the next billing week.
                                    // Either way, we need to insert a snapshot to end the biling at the correct time.
                                    var endSnapshot = new MergedSnapshot(
                                        billingWeekFinalSnapshotTime,
                                        snapshot.CreatorChannels,
                                        snapshot.CreatorFreeAccessUsers,
                                        snapshot.SubscriberChannels,
                                        snapshot.Subscriber,
                                        snapshot.CalculatedAccountBalance);

                                    snapshots.Insert(i + 1, endSnapshot);
                                }

                                snapshot = snapshots[i];
                            }
                        }
                    }
                }
            }

            return(snapshots);
        }