/// <summary>
        /// Purchases additional seats for an existing subscription the customer has already bought.
        /// </summary>
        /// <param name="subscriptionId">The ID of the subscription for which to increase its quantity.</param>
        /// <param name="seatsToPurchase">The number of new seats to purchase on top of the existing ones.</param>
        /// <returns>A transaction result which summarizes its outcome.</returns>
        public async Task <TransactionResult> PurchaseAdditionalSeatsAsync(string subscriptionId, int seatsToPurchase)
        {
            // validate inputs
            subscriptionId.AssertNotEmpty("subscriptionId");
            seatsToPurchase.AssertPositive("seatsToPurchase");

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

            // determine the prorated seat charge
            var subscriptionToAugment = await this.GetSubscriptionAsync(subscriptionId);

            var partnerOffer = await this.ApplicationDomain.OffersRepository.RetrieveAsync(subscriptionToAugment.PartnerOfferId);

            // 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), 2);
            decimal totalCharge        = Math.Round(proratedSeatCharge * seatsToPurchase, 2);

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

            subTransactions.Add(paymentAuthorization);

            // configure a purchase additional seats transaction with the requested seats to purchase
            subTransactions.Add(new PurchaseExtraSeats(
                                    this.ApplicationDomain.PartnerCenterClient.Customers.ById(this.CustomerId).Subscriptions.ById(subscriptionId),
                                    seatsToPurchase));

            DateTime rightNow = DateTime.UtcNow;

            // record the purchase in our purchase store
            subTransactions.Add(new RecordPurchase(
                                    this.ApplicationDomain.CustomerPurchasesRepository,
                                    new CustomerPurchaseEntity(CommerceOperationType.AdditionalSeatsPurchase, Guid.NewGuid().ToString(), this.CustomerId, subscriptionId, seatsToPurchase, proratedSeatCharge, rightNow)));

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

            // build an aggregated transaction from the previous steps and execute it as a whole
            await CommerceOperations.RunAggregatedTransaction(subTransactions);

            var additionalSeatsPurchaseResult = new TransactionResultLineItem(
                subscriptionId,
                subscriptionToAugment.PartnerOfferId,
                seatsToPurchase,
                proratedSeatCharge,
                seatsToPurchase * proratedSeatCharge);

            return(new TransactionResult(
                       totalCharge,
                       new TransactionResultLineItem[] { additionalSeatsPurchaseResult },
                       rightNow));
        }
        /// <summary>
        /// Purchases one or more partner offers.
        /// </summary>
        /// <param name="order">The order to execute.</param>
        /// <returns>A transaction result which summarizes its outcome.</returns>
        public async Task <TransactionResult> PurchaseAsync(OrderViewModel order)
        {
            // use the normalizer to validate the order.
            OrderNormalizer orderNormalizer = new OrderNormalizer(this.ApplicationDomain, order);

            order = await orderNormalizer.NormalizePurchaseSubscriptionOrderAsync();

            // build the purchase line items.
            List <PurchaseLineItem> purchaseLineItems = new List <PurchaseLineItem>();

            foreach (var orderItem in order.Subscriptions)
            {
                string offerId  = orderItem.OfferId;
                int    quantity = orderItem.Quantity;

                purchaseLineItems.Add(new PurchaseLineItem(offerId, quantity));
            }

            // associate line items in order to partner offers.
            var lineItemsWithOffers = await this.AssociateWithPartnerOffersAsync(purchaseLineItems);

            ICollection <IBusinessTransaction> subTransactions = new List <IBusinessTransaction>();

            // prepare payment authorization
            var paymentAuthorization = new AuthorizePayment(this.PaymentGateway);

            subTransactions.Add(paymentAuthorization);

            // build the Partner Center order and pass it to the place order transaction
            Order partnerCenterPurchaseOrder = this.BuildPartnerCenterOrder(lineItemsWithOffers);

            var placeOrder = new PlaceOrder(
                this.ApplicationDomain.PartnerCenterClient.Customers.ById(this.CustomerId),
                partnerCenterPurchaseOrder);

            subTransactions.Add(placeOrder);

            // configure a transaction to save the new resulting subscriptions and purchases into persistence
            var persistSubscriptionsAndPurchases = new PersistNewlyPurchasedSubscriptions(
                this.CustomerId,
                this.ApplicationDomain.CustomerSubscriptionsRepository,
                this.ApplicationDomain.CustomerPurchasesRepository,
                () => new Tuple <Order, IEnumerable <PurchaseLineItemWithOffer> >(placeOrder.Result, lineItemsWithOffers));

            subTransactions.Add(persistSubscriptionsAndPurchases);

            // configure a capture payment transaction and let it read the auth code from the payment authorization output
            var capturePayment = new CapturePayment(this.PaymentGateway, () => paymentAuthorization.Result);

            subTransactions.Add(capturePayment);

            // build an aggregated transaction from the previous steps and execute it as a whole
            await CommerceOperations.RunAggregatedTransaction(subTransactions);

            return(new TransactionResult(persistSubscriptionsAndPurchases.Result, DateTime.UtcNow));
        }
        /// <summary>
        /// Purchases additional seats for an existing subscription the customer has already bought.
        /// </summary>
        /// <param name="order">The order to execute.</param>
        /// <returns>A transaction result which summarizes its outcome.</returns>
        public async Task <TransactionResult> PurchaseAdditionalSeatsAsync(OrderViewModel order)
        {
            // use the normalizer to validate the order.
            OrderNormalizer orderNormalizer = new OrderNormalizer(this.ApplicationDomain, order);

            order = await orderNormalizer.NormalizePurchaseAdditionalSeatsOrderAsync();

            List <OrderSubscriptionItemViewModel> orderSubscriptions = order.Subscriptions.ToList();
            string  subscriptionId     = orderSubscriptions.First().SubscriptionId;
            int     seatsToPurchase    = orderSubscriptions.First().Quantity;
            decimal proratedSeatCharge = orderSubscriptions.First().SeatPrice;
            string  partnerOfferId     = orderSubscriptions.First().PartnerOfferId;

            // 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
            var paymentAuthorization = new AuthorizePayment(this.PaymentGateway);

            subTransactions.Add(paymentAuthorization);

            // configure a purchase additional seats transaction with the requested seats to purchase
            subTransactions.Add(new PurchaseExtraSeats(
                                    this.ApplicationDomain.PartnerCenterClient.Customers.ById(this.CustomerId).Subscriptions.ById(subscriptionId),
                                    seatsToPurchase));

            DateTime rightNow = DateTime.UtcNow;

            // record the purchase in our purchase store
            subTransactions.Add(new RecordPurchase(
                                    this.ApplicationDomain.CustomerPurchasesRepository,
                                    new CustomerPurchaseEntity(CommerceOperationType.AdditionalSeatsPurchase, Guid.NewGuid().ToString(), this.CustomerId, subscriptionId, seatsToPurchase, proratedSeatCharge, rightNow)));

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

            // build an aggregated transaction from the previous steps and execute it as a whole
            await CommerceOperations.RunAggregatedTransaction(subTransactions);

            var additionalSeatsPurchaseResult = new TransactionResultLineItem(
                subscriptionId,
                partnerOfferId,
                seatsToPurchase,
                proratedSeatCharge,
                seatsToPurchase * proratedSeatCharge);

            return(new TransactionResult(
                       new TransactionResultLineItem[] { additionalSeatsPurchaseResult },
                       rightNow));
        }
        /// <summary>
        /// Purchases one or more partner offers.
        /// </summary>
        /// <param name="purchaseLineItems">A collection of purchase lines items to buy.</param>
        /// <returns>A transaction result which summarizes its outcomes.</returns>
        public async Task <TransactionResult> PurchaseAsync(IEnumerable <PurchaseLineItem> purchaseLineItems)
        {
            purchaseLineItems.AssertNotNull(nameof(purchaseLineItems));

            if (purchaseLineItems.Count() <= 0)
            {
                throw new ArgumentException("PurchaseLineItems should have at least one entry", nameof(purchaseLineItems));
            }

            var lineItemsWithOffers = await this.AssociateWithPartnerOffersAsync(purchaseLineItems);

            ICollection <IBusinessTransaction> subTransactions = new List <IBusinessTransaction>();

            decimal orderTotalPrice = Math.Round(this.CalculateOrderTotalPrice(lineItemsWithOffers), 2);

            // we have the order total, prepare payment authorization
            var paymentAuthorization = new AuthorizePayment(this.PaymentGateway, orderTotalPrice);

            subTransactions.Add(paymentAuthorization);

            // build the Partner Center order and pass it to the place order transaction
            Order partnerCenterPurchaseOrder = this.BuildPartnerCenterOrder(lineItemsWithOffers);

            var placeOrder = new PlaceOrder(
                this.ApplicationDomain.PartnerCenterClient.Customers.ById(this.CustomerId),
                partnerCenterPurchaseOrder);

            subTransactions.Add(placeOrder);

            // configure a transaction to save the new resulting subscriptions and purchases into persistence
            var persistSubscriptionsAndPurchases = new PersistNewlyPurchasedSubscriptions(
                this.CustomerId,
                this.ApplicationDomain.CustomerSubscriptionsRepository,
                this.ApplicationDomain.CustomerPurchasesRepository,
                () => new Tuple <Order, IEnumerable <PurchaseLineItemWithOffer> >(placeOrder.Result, lineItemsWithOffers));

            subTransactions.Add(persistSubscriptionsAndPurchases);

            // configure a capture payment transaction and let it read the auth code from the payment authorization output
            var capturePayment = new CapturePayment(this.PaymentGateway, () => paymentAuthorization.Result);

            subTransactions.Add(capturePayment);

            // build an aggregated transaction from the previous steps and execute it as a whole
            await CommerceOperations.RunAggregatedTransaction(subTransactions);

            return(new TransactionResult(orderTotalPrice, persistSubscriptionsAndPurchases.Result, DateTime.UtcNow));
        }
Exemple #5
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
            var subscriptionToAugment = await this.GetSubscriptionAsync(subscriptionId, order.CustomerId);

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

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

            // retrieve the subscription from Partner Center
            var subscriptionOperations    = ApplicationDomain.Instance.PartnerCenterClient.Customers.ById(order.CustomerId).Subscriptions.ById(subscriptionId);
            var partnerCenterSubscription = await subscriptionOperations.GetAsync();

            // 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>();

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

            orderResult.Subscriptions = resultOrderSubscriptions;
            return(await Task.FromResult(orderResult));
        }
        /// <summary>
        /// Renews an existing subscription for a customer.
        /// </summary>
        /// <param name="subscriptionId">The ID of the subscription to renew.</param>
        /// <returns>A transaction result which summarizes its outcome.</returns>
        public async Task <TransactionResult> RenewSubscriptionAsync(string subscriptionId)
        {
            // validate inputs
            subscriptionId.AssertNotEmpty("subscriptionId");

            // grab the customer subscription from our store
            var subscriptionToAugment = await this.GetSubscriptionAsync(subscriptionId);

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

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

            // retrieve the subscription from Partner Center
            var subscriptionOperations    = this.ApplicationDomain.PartnerCenterClient.Customers.ById(this.CustomerId).Subscriptions.ById(subscriptionId);
            var partnerCenterSubscription = await subscriptionOperations.GetAsync();

            // we will add up the transactions here
            ICollection <IBusinessTransaction> subTransactions = new List <IBusinessTransaction>();
            decimal totalCharge = Math.Round(partnerCenterSubscription.Quantity * partnerOffer.Price, 2);

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

            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(
                                    this.ApplicationDomain.CustomerPurchasesRepository,
                                    new CustomerPurchaseEntity(CommerceOperationType.Renewal, Guid.NewGuid().ToString(), this.CustomerId, subscriptionId, partnerCenterSubscription.Quantity, partnerOffer.Price, rightNow)));

            // extend the expiry date by one year
            subTransactions.Add(new UpdatePersistedSubscription(
                                    this.ApplicationDomain.CustomerSubscriptionsRepository,
                                    new CustomerSubscriptionEntity(this.CustomerId, subscriptionId, partnerOffer.Id, subscriptionToAugment.ExpiryDate.AddYears(1))));

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

            // run the pipeline
            await CommerceOperations.RunAggregatedTransaction(subTransactions);

            var renewSubscriptionResult = new TransactionResultLineItem(
                subscriptionId,
                partnerOffer.Id,
                partnerCenterSubscription.Quantity,
                partnerOffer.Price,
                totalCharge);

            return(new TransactionResult(
                       totalCharge,
                       new TransactionResultLineItem[] { renewSubscriptionResult },
                       rightNow));
        }
        /// <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(this.ApplicationDomain, order);

            order = await orderNormalizer.NormalizeRenewSubscriptionOrderAsync();

            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
            var subscriptionOperations    = this.ApplicationDomain.PartnerCenterClient.Customers.ById(this.CustomerId).Subscriptions.ById(subscriptionId);
            var partnerCenterSubscription = await subscriptionOperations.GetAsync();

            // 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
            var paymentAuthorization = new AuthorizePayment(this.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(
                                    this.ApplicationDomain.CustomerPurchasesRepository,
                                    new CustomerPurchaseEntity(CommerceOperationType.Renewal, Guid.NewGuid().ToString(), this.CustomerId, subscriptionId, partnerCenterSubscription.Quantity, partnerOfferPrice, rightNow)));

            // extend the expiry date by one year
            subTransactions.Add(new UpdatePersistedSubscription(
                                    this.ApplicationDomain.CustomerSubscriptionsRepository,
                                    new CustomerSubscriptionEntity(this.CustomerId, subscriptionId, partnerOfferId, subscriptionExpiryDate.AddYears(1))));

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

            // run the pipeline
            await CommerceOperations.RunAggregatedTransaction(subTransactions);

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

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