/// <summary>
        /// Rolls back the order that was placed.
        /// </summary>
        /// <returns>A task.</returns>
        public async Task RollbackAsync()
        {
            if (Result != null)
            {
                // suspend all subscriptions that resulted from placing the order
                IEnumerable <Task> suspensionTasks = Result.LineItems.Select <OrderLineItem, Task>(orderLineItem => new TaskFactory().StartNew(async() =>
                {
                    try
                    {
                        Subscriptions.ISubscription subscriptionOperations = Customer.Subscriptions.ById(orderLineItem.SubscriptionId);
                        Subscription subscriptionToSuspend = await subscriptionOperations.GetAsync().ConfigureAwait(false);

                        subscriptionToSuspend.Status       = SubscriptionStatus.Suspended;
                        subscriptionToSuspend.FriendlyName = subscriptionToSuspend.FriendlyName.Replace(Resources.UnpaidSubscriptionSuffix, string.Empty) + Resources.UnpaidSubscriptionSuffix;

                        Subscription patchedSubscription = await subscriptionOperations.PatchAsync(subscriptionToSuspend).ConfigureAwait(false);

                        Trace.TraceInformation("Suspended subscription: {0}", orderLineItem.SubscriptionId);
                    }
                    catch (Exception suspensionProblem)
                    {
                        if (suspensionProblem.IsFatal())
                        {
                            throw;
                        }

                        Trace.TraceError("PlaceOrder.RollbackAsync: failed to suspend a subscription: {0}, ID: {1}", suspensionProblem, orderLineItem.SubscriptionId);

                        // TODO: Notify the system integrity recovery component
                    }
                }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.FromCurrentSynchronizationContext()));

                try
                {
                    await Task.WhenAll(suspensionTasks).ConfigureAwait(false);
                }
                catch (Exception exception)
                {
                    if (exception.IsFatal())
                    {
                        throw;
                    }

                    Trace.TraceError("PlaceOrder.RollbackAsync: awaiting all suspension tasks failed: {0}", exception);
                }

                Result = null;
            }
        }
        /// <summary>
        /// Normalizes an order to renew a subscription.
        /// </summary>
        /// <returns>Normalized order.</returns>
        public async Task <OrderViewModel> NormalizeRenewSubscriptionOrderAsync()
        {
            OrderViewModel order = Order;

            order.CustomerId.AssertNotEmpty(nameof(order.CustomerId));
            if (order.OperationType != CommerceOperationType.Renewal)
            {
                throw new PartnerDomainException(ErrorCode.InvalidInput, Resources.InvalidOperationForOrderMessage).AddDetail("Field", "OperationType");
            }

            // create result order object prefilling it with operation type & customer id.
            OrderViewModel orderResult = new OrderViewModel()
            {
                CustomerId    = order.CustomerId,
                OrderId       = order.OrderId,
                OperationType = order.OperationType
            };

            order.Subscriptions.AssertNotNull(nameof(order.Subscriptions));
            List <OrderSubscriptionItemViewModel> orderSubscriptions = order.Subscriptions.ToList();

            if (orderSubscriptions.Count != 1)
            {
                throw new PartnerDomainException(ErrorCode.InvalidInput, Resources.MoreThanOneSubscriptionUpdateErrorMessage);
            }

            string subscriptionId = orderSubscriptions.First().SubscriptionId;

            subscriptionId.AssertNotEmpty(nameof(subscriptionId)); // is Required for the commerce operation.

            // grab the customer subscription from our store
            CustomerSubscriptionEntity subscriptionToAugment = await GetSubscriptionAsync(subscriptionId, order.CustomerId).ConfigureAwait(false);

            // retrieve the partner offer this subscription relates to, we need to know the current price
            PartnerOffer partnerOffer = await ApplicationDomain.Instance.OffersRepository.RetrieveAsync(subscriptionToAugment.PartnerOfferId).ConfigureAwait(false);

            if (partnerOffer.IsInactive)
            {
                // renewing deleted offers is prohibited
                throw new PartnerDomainException(ErrorCode.PurchaseDeletedOfferNotAllowed).AddDetail("Id", partnerOffer.Id);
            }

            // retrieve the subscription from Partner Center
            Subscriptions.ISubscription subscriptionOperations = ApplicationDomain.Instance.PartnerCenterClient.Customers.ById(order.CustomerId).Subscriptions.ById(subscriptionId);
            PartnerCenter.Models.Subscriptions.Subscription partnerCenterSubscription = await subscriptionOperations.GetAsync().ConfigureAwait(false);

            List <OrderSubscriptionItemViewModel> resultOrderSubscriptions = new List <OrderSubscriptionItemViewModel>
            {
                new OrderSubscriptionItemViewModel()
                {
                    OfferId                = subscriptionId,
                    SubscriptionId         = subscriptionId,
                    PartnerOfferId         = subscriptionToAugment.PartnerOfferId,
                    SubscriptionExpiryDate = subscriptionToAugment.ExpiryDate,
                    Quantity               = partnerCenterSubscription.Quantity,
                    SeatPrice              = partnerOffer.Price,
                    SubscriptionName       = partnerOffer.Title
                }
            };

            orderResult.Subscriptions = resultOrderSubscriptions;

            return(orderResult);
        }
Esempio n. 3
0
        /// <summary>
        /// Normalizes an order to add seats to a subscription.
        /// </summary>
        /// <returns>Normalized order.</returns>
        public async Task <OrderViewModel> NormalizePurchaseAdditionalSeatsOrderAsync()
        {
            OrderViewModel order = this.Order;

            order.CustomerId.AssertNotEmpty(nameof(order.CustomerId));
            if (order.OperationType != CommerceOperationType.AdditionalSeatsPurchase)
            {
                throw new PartnerDomainException(ErrorCode.InvalidInput, Resources.InvalidOperationForOrderMessage).AddDetail("Field", "OperationType");
            }

            // create result order object prefilling it with operation type & customer id.
            OrderViewModel orderResult = new OrderViewModel()
            {
                CustomerId    = order.CustomerId,
                OrderId       = order.OrderId,
                OperationType = order.OperationType
            };

            order.Subscriptions.AssertNotNull(nameof(order.Subscriptions));
            List <OrderSubscriptionItemViewModel> orderSubscriptions = order.Subscriptions.ToList();

            if (!(orderSubscriptions.Count == 1))
            {
                throw new PartnerDomainException(ErrorCode.InvalidInput).AddDetail("ErrorMessage", Resources.MoreThanOneSubscriptionUpdateErrorMessage);
            }

            string subscriptionId  = orderSubscriptions.First().SubscriptionId;
            int    seatsToPurchase = orderSubscriptions.First().Quantity;

            subscriptionId.AssertNotEmpty(nameof(subscriptionId)); // is Required for the commerce operation.
            seatsToPurchase.AssertPositive("seatsToPurchase");

            // grab the customer subscription from our store
            CustomerSubscriptionEntity subscriptionToAugment = await GetSubscriptionAsync(subscriptionId, order.CustomerId).ConfigureAwait(false);

            // retrieve the partner offer this subscription relates to, we need to know the current price
            PartnerOffer partnerOffer = await ApplicationDomain.Instance.OffersRepository.RetrieveAsync(subscriptionToAugment.PartnerOfferId).ConfigureAwait(false);

            if (partnerOffer.IsInactive)
            {
                // renewing deleted offers is prohibited
                throw new PartnerDomainException(ErrorCode.PurchaseDeletedOfferNotAllowed).AddDetail("Id", partnerOffer.Id);
            }

            // retrieve the subscription from Partner Center
            Subscriptions.ISubscription subscriptionOperations = ApplicationDomain.Instance.PartnerCenterClient.Customers.ById(order.CustomerId).Subscriptions.ById(subscriptionId);
            PartnerCenter.Models.Subscriptions.Subscription partnerCenterSubscription = await subscriptionOperations.GetAsync().ConfigureAwait(false);

            // if subscription expiry date.Date is less than today's UTC date then subcription has expired.
            if (subscriptionToAugment.ExpiryDate.Date < DateTime.UtcNow.Date)
            {
                // this subscription has already expired, don't permit adding seats until the subscription is renewed
                throw new PartnerDomainException(ErrorCode.SubscriptionExpired);
            }

            decimal proratedSeatCharge = Math.Round(CommerceOperations.CalculateProratedSeatCharge(subscriptionToAugment.ExpiryDate, partnerOffer.Price), Resources.Culture.NumberFormat.CurrencyDecimalDigits);
            decimal totalCharge        = Math.Round(proratedSeatCharge * seatsToPurchase, Resources.Culture.NumberFormat.CurrencyDecimalDigits);

            List <OrderSubscriptionItemViewModel> resultOrderSubscriptions = new List <OrderSubscriptionItemViewModel>
            {
                new OrderSubscriptionItemViewModel()
                {
                    OfferId                = subscriptionId,
                    SubscriptionId         = subscriptionId,
                    PartnerOfferId         = subscriptionToAugment.PartnerOfferId,
                    SubscriptionExpiryDate = subscriptionToAugment.ExpiryDate,
                    Quantity               = seatsToPurchase,
                    SeatPrice              = proratedSeatCharge,
                    SubscriptionName       = partnerOffer.Title
                }
            };

            orderResult.Subscriptions = resultOrderSubscriptions;

            return(orderResult);
        }
Esempio n. 4
0
        /// <summary>
        /// Renews an existing subscription for a customer.
        /// </summary>
        /// <param name="order">The order to execute.</param>
        /// <returns>A transaction result which summarizes its outcome.</returns>
        public async Task <TransactionResult> RenewSubscriptionAsync(OrderViewModel order)
        {
            // use the normalizer to validate the order.
            OrderNormalizer orderNormalizer = new OrderNormalizer(ApplicationDomain, order);

            order = await orderNormalizer.NormalizeRenewSubscriptionOrderAsync().ConfigureAwait(false);

            List <OrderSubscriptionItemViewModel> orderSubscriptions = order.Subscriptions.ToList();
            string   subscriptionId         = orderSubscriptions.First().SubscriptionId;
            string   partnerOfferId         = orderSubscriptions.First().PartnerOfferId;
            decimal  partnerOfferPrice      = orderSubscriptions.First().SeatPrice;
            DateTime subscriptionExpiryDate = orderSubscriptions.First().SubscriptionExpiryDate;
            int      quantity    = orderSubscriptions.First().Quantity;
            decimal  totalCharge = Math.Round(quantity * partnerOfferPrice, Resources.Culture.NumberFormat.CurrencyDecimalDigits);

            // retrieve the subscription from Partner Center
            Subscriptions.ISubscription subscriptionOperations = ApplicationDomain.PartnerCenterClient.Customers.ById(CustomerId).Subscriptions.ById(subscriptionId);
            PartnerCenter.Models.Subscriptions.Subscription partnerCenterSubscription = await subscriptionOperations.GetAsync().ConfigureAwait(false);

            // we will add up the transactions here
            ICollection <IBusinessTransaction> subTransactions = new List <IBusinessTransaction>();

            // configure a transaction to charge the payment gateway with the prorated rate
            AuthorizePayment paymentAuthorization = new AuthorizePayment(PaymentGateway);

            subTransactions.Add(paymentAuthorization);

            // add a renew subscription transaction to the pipeline
            subTransactions.Add(new RenewSubscription(
                                    subscriptionOperations,
                                    partnerCenterSubscription));

            DateTime rightNow = DateTime.UtcNow;

            // record the renewal in our purchase store
            subTransactions.Add(new RecordPurchase(
                                    ApplicationDomain.CustomerPurchasesRepository,
                                    new CustomerPurchaseEntity(CommerceOperationType.Renewal, Guid.NewGuid().ToString(), CustomerId, subscriptionId, partnerCenterSubscription.Quantity, partnerOfferPrice, rightNow)));

            DateTime expirationDate = subscriptionExpiryDate;
            BrandingConfiguration portalBranding = await ApplicationDomain.Instance.PortalBranding.RetrieveAsync().ConfigureAwait(false);

            switch (portalBranding.BillingCycle)
            {
            case BillingCycleType.Annual:
                expirationDate = expirationDate.AddYears(1);
                break;

            case BillingCycleType.Monthly:
                expirationDate = expirationDate.AddMonths(1);
                break;

            default:
                throw new NotImplementedException($"Billing cycle {portalBranding.BillingCycle} is not implemented");
            }

            // extend the expiry date by one month or one year
            subTransactions.Add(new UpdatePersistedSubscription(
                                    ApplicationDomain.CustomerSubscriptionsRepository,
                                    new CustomerSubscriptionEntity(CustomerId, subscriptionId, partnerOfferId, expirationDate)));

            // add a capture payment to the transaction pipeline
            subTransactions.Add(new CapturePayment(PaymentGateway, () => paymentAuthorization.Result));

            // run the pipeline
            await RunAggregatedTransaction(subTransactions).ConfigureAwait(false);

            TransactionResultLineItem renewSubscriptionResult = new TransactionResultLineItem(
                subscriptionId,
                partnerOfferId,
                partnerCenterSubscription.Quantity,
                partnerOfferPrice,
                totalCharge);

            return(new TransactionResult(
                       new TransactionResultLineItem[] { renewSubscriptionResult },
                       rightNow));
        }