/// <summary> /// Normalizes an order to purchase net new subscriptions. /// </summary> /// <returns>Normalized order.</returns> public async Task <OrderViewModel> NormalizePurchaseSubscriptionOrderAsync() { OrderViewModel order = this.Order; order.CustomerId.AssertNotEmpty(nameof(order.CustomerId)); if (order.OperationType != CommerceOperationType.NewPurchase) { 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 ArgumentException(Resources.NotEnoughItemsInOrderErrorMessage, nameof(order.Subscriptions)); } // retrieve all the partner offers to match against them IEnumerable <PartnerOffer> allPartnerOffers = await ApplicationDomain.Instance.OffersRepository.RetrieveAsync(); List <OrderSubscriptionItemViewModel> resultOrderSubscriptions = new List <OrderSubscriptionItemViewModel>(); foreach (var lineItem in orderSubscriptions) { PartnerOffer offerToPurchase = allPartnerOffers.Where(offer => offer.Id == lineItem.SubscriptionId).FirstOrDefault(); if (offerToPurchase == null) { // oops, this offer Id is unknown to us throw new PartnerDomainException(ErrorCode.PartnerOfferNotFound).AddDetail("Id", lineItem.SubscriptionId); } else if (offerToPurchase.IsInactive) { // purchasing deleted offers is prohibited throw new PartnerDomainException(ErrorCode.PurchaseDeletedOfferNotAllowed).AddDetail("Id", offerToPurchase.Id); } // populate details for each order subscription item to purchase. resultOrderSubscriptions.Add(new OrderSubscriptionItemViewModel() { OfferId = offerToPurchase.Id, SubscriptionId = offerToPurchase.Id, Quantity = lineItem.Quantity, SeatPrice = offerToPurchase.Price, SubscriptionName = offerToPurchase.Title }); } orderResult.Subscriptions = resultOrderSubscriptions; return(await Task.FromResult(orderResult)); }
/// <summary> /// Updates an existing partner offer. /// </summary> /// <param name="partnerOfferUpdate">The partner offer to update.</param> /// <returns>The updated partner offer.</returns> public async Task <PartnerOffer> UpdateAsync(PartnerOffer partnerOfferUpdate) { if (partnerOfferUpdate == null) { throw new ArgumentNullException(nameof(partnerOfferUpdate)); } IList <PartnerOffer> allPartnerOffers = new List <PartnerOffer>(await this.RetrieveAsync()); new PartnerOfferNormalizer().Normalize(partnerOfferUpdate); var existingPartnerOffer = allPartnerOffers.Where(offer => offer.Id == partnerOfferUpdate.Id).FirstOrDefault(); if (existingPartnerOffer == null) { throw new PartnerDomainException(ErrorCode.PartnerOfferNotFound, "Offer not found"); } if (existingPartnerOffer.MicrosoftOfferId != partnerOfferUpdate.MicrosoftOfferId) { // we do not allow changing the Microsoft offer association since there may be existing purchases that purchased the original Microsoft offer throw new PartnerDomainException(ErrorCode.MicrosoftOfferImmutable, "Microsoft offer is not allowed to be updated. Create a new offer instead."); } allPartnerOffers[allPartnerOffers.IndexOf(existingPartnerOffer)] = partnerOfferUpdate; await this.UpdateAsync(allPartnerOffers); return(partnerOfferUpdate); }
/// <summary> /// Applies business rules to a partner offer. /// </summary> /// <param name="partnerOffer">The partner offer to normalize.</param> public void Normalize(PartnerOffer partnerOffer) { partnerOffer.AssertNotNull(nameof(partnerOffer)); // ensure the Microsoft offer ID and other required properties are set Guid offerId; if (!Guid.TryParse(partnerOffer.Id, out offerId)) { throw new PartnerDomainException(ErrorCode.InvalidInput, Resources.IdMustBeAValidGUID).AddDetail("Field", "Id"); } if (string.IsNullOrWhiteSpace(partnerOffer.MicrosoftOfferId)) { throw new PartnerDomainException(ErrorCode.InvalidInput, Resources.MicrosoftOfferIdMustBeSet).AddDetail("Field", "MicrosoftOfferId"); } partnerOffer.Title.AssertNotEmpty("Offer title"); if (partnerOffer.Price <= 0) { throw new PartnerDomainException(ErrorCode.InvalidInput, Resources.OfferPriceShouldBeMoreThanZero).AddDetail("Field", "Price"); } // flatten the offer price based on locale decimal settings. partnerOffer.Price = Math.Round(partnerOffer.Price, Resources.Culture.NumberFormat.CurrencyDecimalDigits, MidpointRounding.AwayFromZero); partnerOffer.Features = PartnerOfferNormalizer.CleanupEmptyEntries(partnerOffer.Features); partnerOffer.Summary = PartnerOfferNormalizer.CleanupEmptyEntries(partnerOffer.Summary); }
/// <summary> /// Binds each purchase line item with the partner offer it is requesting. /// </summary> /// <param name="purchaseLineItems">A collection of purchase line items.</param> /// <returns>The requested association.</returns> private async Task <IEnumerable <PurchaseLineItemWithOffer> > AssociateWithPartnerOffersAsync(IEnumerable <PurchaseLineItem> purchaseLineItems) { // retrieve all the partner offers to match against them IEnumerable <PartnerOffer> allPartnerOffers = await this.ApplicationDomain.OffersRepository.RetrieveAsync(); ICollection <PurchaseLineItemWithOffer> lineItemToOfferAssociations = new List <PurchaseLineItemWithOffer>(); foreach (var lineItem in purchaseLineItems) { if (lineItem == null) { throw new ArgumentException("a line item is null"); } PartnerOffer offerToPurchase = allPartnerOffers.Where(offer => offer.Id == lineItem.PartnerOfferId).FirstOrDefault(); if (offerToPurchase == null) { // oops, this offer Id is unknown to us throw new PartnerDomainException(ErrorCode.PartnerOfferNotFound).AddDetail("Id", lineItem.PartnerOfferId); } else if (offerToPurchase.IsInactive) { // purchasing deleted offers is prohibited throw new PartnerDomainException(ErrorCode.PurchaseDeletedOfferNotAllowed).AddDetail("Id", offerToPurchase.Id); } // associate the line item with the partner offer lineItemToOfferAssociations.Add(new PurchaseLineItemWithOffer(lineItem, offerToPurchase)); } return(lineItemToOfferAssociations); }
/// <summary> /// Updates an existing partner offer. /// </summary> /// <param name="partnerOfferUpdate">The partner offer to update.</param> /// <returns>The updated partner offer.</returns> public async Task <PartnerOffer> UpdateAsync(PartnerOffer partnerOfferUpdate) { partnerOfferUpdate.AssertNotNull(nameof(partnerOfferUpdate)); IList <PartnerOffer> allPartnerOffers = new List <PartnerOffer>(await RetrieveAsync().ConfigureAwait(false)); new PartnerOfferNormalizer().Normalize(partnerOfferUpdate); PartnerOffer existingPartnerOffer = allPartnerOffers.FirstOrDefault(offer => offer.Id == partnerOfferUpdate.Id); if (existingPartnerOffer == null) { throw new PartnerDomainException(ErrorCode.PartnerOfferNotFound, Resources.OfferNotFound); } if (existingPartnerOffer.MicrosoftOfferId != partnerOfferUpdate.MicrosoftOfferId) { // we do not allow changing the Microsoft offer association since there may be existing purchases that purchased the original Microsoft offer throw new PartnerDomainException(ErrorCode.MicrosoftOfferImmutable, Resources.MicrosoftOfferImmutableErrorMessage); } allPartnerOffers[allPartnerOffers.IndexOf(existingPartnerOffer)] = partnerOfferUpdate; await UpdateAsync(allPartnerOffers).ConfigureAwait(false); return(partnerOfferUpdate); }
/// <summary> /// Applies business rules to a partner offer. /// </summary> /// <param name="partnerOffer">The partner offer to normalize.</param> public void Normalize(PartnerOffer partnerOffer) { partnerOffer.AssertNotNull(nameof(partnerOffer)); // ensure the Microsoft offer ID and other required properties are set Guid offerId; if (!Guid.TryParse(partnerOffer.Id, out offerId)) { throw new PartnerDomainException(ErrorCode.InvalidInput, "Id must be a valid GUID").AddDetail("Field", "Id"); } if (string.IsNullOrWhiteSpace(partnerOffer.MicrosoftOfferId)) { throw new PartnerDomainException(ErrorCode.InvalidInput, "MicrosoftOfferId must be set").AddDetail("Field", "MicrosoftOfferId"); } partnerOffer.Title.AssertNotEmpty("Offer title"); if (partnerOffer.Price <= 0) { throw new PartnerDomainException(ErrorCode.InvalidInput, "Offer price should be more than zero").AddDetail("Field", "Price"); } partnerOffer.Features = PartnerOfferNormalizer.CleanupEmptyEntries(partnerOffer.Features); partnerOffer.Summary = PartnerOfferNormalizer.CleanupEmptyEntries(partnerOffer.Summary); }
/// <summary> /// Initializes a new instance of the <see cref="PurchaseLineItemWithOffer"/> class. /// </summary> /// <param name="purchaseLineItem">The purchase line item.</param> /// <param name="partnerOffer">The partner offer.</param> public PurchaseLineItemWithOffer(PurchaseLineItem purchaseLineItem, PartnerOffer partnerOffer) { purchaseLineItem.AssertNotNull(nameof(purchaseLineItem)); partnerOffer.AssertNotNull(nameof(partnerOffer)); this.PurchaseLineItem = purchaseLineItem; this.PartnerOffer = partnerOffer; }
/// <summary> /// Retrieves a specific partner offer using its ID. /// </summary> /// <param name="partnerOfferId">The ID of the partner offer to look for.</param> /// <returns>The matching partner offer.</returns> public async Task <PartnerOffer> RetrieveAsync(string partnerOfferId) { partnerOfferId.AssertNotEmpty(nameof(partnerOfferId)); PartnerOffer matchingPartnerOffer = (await RetrieveAsync().ConfigureAwait(false)).FirstOrDefault(offer => offer.Id == partnerOfferId); if (matchingPartnerOffer != null) { return(matchingPartnerOffer); } else { throw new PartnerDomainException(ErrorCode.PartnerOfferNotFound, Resources.OfferNotFound); } }
/// <summary> /// Retrieves a specific partner offer using its ID. /// </summary> /// <param name="partnerOfferId">The ID of the partner offer to look for.</param> /// <returns>The matching partner offer.</returns> public async Task <PartnerOffer> RetrieveAsync(string partnerOfferId) { partnerOfferId.AssertNotEmpty(nameof(partnerOfferId)); PartnerOffer matchingPartnerOffer = (await this.RetrieveAsync()).Where(offer => offer.Id == partnerOfferId).FirstOrDefault(); if (matchingPartnerOffer != null) { return(matchingPartnerOffer); } else { throw new PartnerDomainException(ErrorCode.PartnerOfferNotFound, "Offer not found"); } }
/// <summary> /// Adds a new partner offer to the repository. /// </summary> /// <param name="newPartnerOffer">The partner offer to add.</param> /// <returns>The added partner offer.</returns> public async Task <PartnerOffer> AddAsync(PartnerOffer newPartnerOffer) { newPartnerOffer.AssertNotNull(nameof(newPartnerOffer)); newPartnerOffer.Id = Guid.NewGuid().ToString(); ICollection <PartnerOffer> allPartnerOffers = new List <PartnerOffer>(await RetrieveAsync().ConfigureAwait(false)); new PartnerOfferNormalizer().Normalize(newPartnerOffer); allPartnerOffers.Add(newPartnerOffer); await UpdateAsync(allPartnerOffers).ConfigureAwait(false); return(newPartnerOffer); }
/// <summary> /// Adds a new partner offer to the repository. /// </summary> /// <param name="newPartnerOffer">The partner offer to add.</param> /// <returns>The added partner offer.</returns> public async Task <PartnerOffer> AddAsync(PartnerOffer newPartnerOffer) { if (newPartnerOffer == null) { throw new ArgumentNullException(nameof(newPartnerOffer)); } newPartnerOffer.Id = Guid.NewGuid().ToString(); ICollection <PartnerOffer> allPartnerOffers = new List <PartnerOffer>(await RetrieveAsync().ConfigureAwait(false)); new PartnerOfferNormalizer().Normalize(newPartnerOffer); allPartnerOffers.Add(newPartnerOffer); await UpdateAsync(allPartnerOffers).ConfigureAwait(false); return(newPartnerOffer); }
/// <summary> /// Records all the resulting subscriptions as well as their initial purchase history into persistence. /// </summary> /// <returns>A task.</returns> public async Task ExecuteAsync() { Tuple <Order, IEnumerable <PurchaseLineItemWithOffer> > inputs = AcquireInput.Invoke(); Order partnerCenterPurchaseOrder = inputs.Item1; IEnumerable <PurchaseLineItemWithOffer> purchaseLineItems = inputs.Item2; ICollection <TransactionResultLineItem> transactionResultLineItems = new List <TransactionResultLineItem>(); ICollection <IBusinessTransaction> persistenceTransactions = new List <IBusinessTransaction>(); DateTime rightNow = DateTime.UtcNow; foreach (OrderLineItem orderLineItem in partnerCenterPurchaseOrder.LineItems) { PartnerOffer matchingPartnerOffer = purchaseLineItems.ElementAt(orderLineItem.LineItemNumber).PartnerOffer; // add a record new customer subscription transaction for the current line item persistenceTransactions.Add(new RecordNewCustomerSubscription( CustomerSubscriptionsRepository, new CustomerSubscriptionEntity(CustomerId, orderLineItem.SubscriptionId, matchingPartnerOffer.Id, rightNow.AddYears(1)))); // add a record purchase history for the current line item persistenceTransactions.Add(new RecordPurchase( CustomerPurchasesRepository, new CustomerPurchaseEntity(CommerceOperationType.NewPurchase, Guid.NewGuid().ToString(), CustomerId, orderLineItem.SubscriptionId, orderLineItem.Quantity, matchingPartnerOffer.Price, rightNow))); // build the transaction result line item transactionResultLineItems.Add(new TransactionResultLineItem( orderLineItem.SubscriptionId, matchingPartnerOffer.Id, orderLineItem.Quantity, matchingPartnerOffer.Price, matchingPartnerOffer.Price * orderLineItem.Quantity)); } // bundle up all the transactions together bulkSubscriptionPersistenceTransaction = new SequentialAggregateTransaction(persistenceTransactions); // execute it! await bulkSubscriptionPersistenceTransaction.ExecuteAsync().ConfigureAwait(false); // store the reuslting transaction line items Result = transactionResultLineItems; }
/// <summary> /// Binds each purchase line item with the partner offer it is requesting. /// </summary> /// <param name="purchaseLineItems">A collection of purchase line items.</param> /// <returns>The requested association.</returns> private async Task <IEnumerable <PurchaseLineItemWithOffer> > AssociateWithPartnerOffersAsync(IEnumerable <PurchaseLineItem> purchaseLineItems) { // retrieve all the partner offers to match against them IEnumerable <PartnerOffer> allPartnerOffers = await ApplicationDomain.OffersRepository.RetrieveAsync().ConfigureAwait(false); ICollection <PurchaseLineItemWithOffer> lineItemToOfferAssociations = new List <PurchaseLineItemWithOffer>(); foreach (PurchaseLineItem lineItem in purchaseLineItems) { if (lineItem == null) { throw new ArgumentException("a line item is null"); } PartnerOffer offerToPurchase = allPartnerOffers.FirstOrDefault(offer => offer.Id == lineItem.PartnerOfferId); // associate the line item with the partner offer lineItemToOfferAssociations.Add(new PurchaseLineItemWithOffer(lineItem, offerToPurchase)); } return(lineItemToOfferAssociations); }
public async Task <PartnerOffer> UpdateOffer(PartnerOffer partnerOffer) { return(await ApplicationDomain.Instance.OffersRepository.UpdateAsync(partnerOffer).ConfigureAwait(false)); }
public async Task <PartnerOffer> AddOffer(PartnerOffer newPartnerOffer) { return(await ApplicationDomain.Instance.OffersRepository.AddAsync(newPartnerOffer).ConfigureAwait(false)); }
/// <summary> /// Gets the summary of subscriptions for a portal customer. /// </summary> /// <param name="customerId">The customer Id.</param> /// <returns>Subscription Summary.</returns> private async Task <SubscriptionsSummary> GetSubscriptionSummaryAsync(string customerId) { DateTime startTime = DateTime.Now; IEnumerable <CustomerSubscriptionEntity> customerSubscriptions = await ApplicationDomain.Instance.CustomerSubscriptionsRepository.RetrieveAsync(customerId).ConfigureAwait(false); IEnumerable <CustomerPurchaseEntity> customerSubscriptionsHistory = await ApplicationDomain.Instance.CustomerPurchasesRepository.RetrieveAsync(customerId).ConfigureAwait(false); IEnumerable <PartnerOffer> allPartnerOffers = await ApplicationDomain.Instance.OffersRepository.RetrieveAsync().ConfigureAwait(false); IEnumerable <MicrosoftOffer> currentMicrosoftOffers = await ApplicationDomain.Instance.OffersRepository.RetrieveMicrosoftOffersAsync().ConfigureAwait(false); // start building the summary. decimal summaryTotal = 0; // format all responses to client using portal locale. CultureInfo responseCulture = new CultureInfo(ApplicationDomain.Instance.PortalLocalization.Locale); List <SubscriptionViewModel> customerSubscriptionsView = new List <SubscriptionViewModel>(); // iterate through and build the list of customer's subscriptions. foreach (CustomerSubscriptionEntity subscription in customerSubscriptions) { decimal subscriptionTotal = 0; int licenseTotal = 0; List <SubscriptionHistory> historyItems = new List <SubscriptionHistory>(); // collect the list of history items for this subcription. IOrderedEnumerable <CustomerPurchaseEntity> subscriptionHistoryList = customerSubscriptionsHistory .Where(historyItem => historyItem.SubscriptionId == subscription.SubscriptionId) .OrderBy(historyItem => historyItem.TransactionDate); // iterate through and build the SubsriptionHistory for this subscription. foreach (CustomerPurchaseEntity historyItem in subscriptionHistoryList) { decimal orderTotal = Math.Round(historyItem.SeatPrice * historyItem.SeatsBought, responseCulture.NumberFormat.CurrencyDecimalDigits); historyItems.Add(new SubscriptionHistory() { OrderTotal = orderTotal.ToString("C", responseCulture), // Currency format. PricePerSeat = historyItem.SeatPrice.ToString("C", responseCulture), // Currency format. SeatsBought = historyItem.SeatsBought.ToString("G", responseCulture), // General format. OrderDate = historyItem.TransactionDate.ToLocalTime().ToString("d", responseCulture), // Short date format. OperationType = GetOperationType(historyItem.PurchaseType) // Localized Operation type string. }); // Increment the subscription total. licenseTotal += historyItem.SeatsBought; // Increment the subscription total. subscriptionTotal += orderTotal; } PartnerOffer partnerOfferItem = allPartnerOffers.FirstOrDefault(offer => offer.Id == subscription.PartnerOfferId); string subscriptionTitle = partnerOfferItem.Title; string portalOfferId = partnerOfferItem.Id; decimal portalOfferPrice = partnerOfferItem.Price; DateTime subscriptionExpiryDate = subscription.ExpiryDate.ToUniversalTime(); int remainingDays = (subscriptionExpiryDate.Date - DateTime.UtcNow.Date).Days; bool isRenewable = remainingDays <= 30; bool isEditable = DateTime.UtcNow.Date <= subscriptionExpiryDate.Date; // TODO :: Handle Microsoft offer being pulled back due to EOL. // Temporarily mark this partnerOffer item as inactive and dont allow store front customer to manage this subscription. MicrosoftOffer alignedMicrosoftOffer = currentMicrosoftOffers.FirstOrDefault(offer => offer.Offer.Id == partnerOfferItem.MicrosoftOfferId); if (alignedMicrosoftOffer == null) { partnerOfferItem.IsInactive = true; } if (partnerOfferItem.IsInactive) { // in case the offer is inactive (marked for deletion) then dont allow renewals or editing on this subscription tied to this offer. isRenewable = false; isEditable = false; } // Compute the pro rated price per seat for this subcription & return for client side processing during updates. decimal proratedPerSeatPrice = Math.Round(CommerceOperations.CalculateProratedSeatCharge(subscription.ExpiryDate, portalOfferPrice), responseCulture.NumberFormat.CurrencyDecimalDigits); SubscriptionViewModel subscriptionItem = new SubscriptionViewModel() { SubscriptionId = subscription.SubscriptionId, FriendlyName = subscriptionTitle, PortalOfferId = portalOfferId, PortalOfferPrice = portalOfferPrice.ToString("C", responseCulture), IsRenewable = isRenewable, // IsRenewable is true if subscription is going to expire in 30 days. IsEditable = isEditable, // IsEditable is true if today is lesser or equal to subscription expiry date. LicensesTotal = licenseTotal.ToString("G", responseCulture), // General format. SubscriptionTotal = subscriptionTotal.ToString("C", responseCulture), // Currency format. SubscriptionExpiryDate = subscriptionExpiryDate.Date.ToString("d", responseCulture), // Short date format. SubscriptionOrderHistory = historyItems, SubscriptionProRatedPrice = proratedPerSeatPrice }; // add this subcription to the customer's subscription list. customerSubscriptionsView.Add(subscriptionItem); // Increment the summary total. summaryTotal += subscriptionTotal; } // Capture the request for the customer summary for analysis. Dictionary <string, string> eventProperties = new Dictionary <string, string> { { "CustomerId", customerId } }; // Track the event measurements for analysis. Dictionary <string, double> eventMetrics = new Dictionary <string, double> { { "ElapsedMilliseconds", DateTime.Now.Subtract(startTime).TotalMilliseconds }, { "NumberOfSubscriptions", customerSubscriptionsView.Count } }; ApplicationDomain.Instance.TelemetryService.Provider.TrackEvent("GetSubscriptionSummaryAsync", eventProperties, eventMetrics); // Sort List of subscriptions based on portal offer name. return(new SubscriptionsSummary() { Subscriptions = customerSubscriptionsView.OrderBy(subscriptionItem => subscriptionItem.FriendlyName), SummaryTotal = summaryTotal.ToString("C", responseCulture) // Currency format. }); }
/// <summary> /// Gets the subscriptions managed by customers and partners /// </summary> /// <returns>returns managed subscriptions view model</returns> private async Task <ManagedSubscriptionsViewModel> GetManagedSubscriptions() { DateTime startTime = DateTime.Now; string clientCustomerId = Principal.PartnerCenterCustomerId; // responseCulture determines decimals, currency and such CultureInfo responseCulture = new CultureInfo(ApplicationDomain.Instance.PortalLocalization.Locale); // localeSpecificApiClient allows pulling offer names localized to supported portal locales compatible with Offer API supported locales. IPartner localeSpecificPartnerCenterClient = ApplicationDomain.Instance.PartnerCenterClient.With(RequestContextFactory.Instance.Create(ApplicationDomain.Instance.PortalLocalization.OfferLocale)); // Get all subscriptions of customer from PC ResourceCollection <Subscription> customerAllSubscriptions = await localeSpecificPartnerCenterClient.Customers.ById(clientCustomerId).Subscriptions.GetAsync().ConfigureAwait(false); IEnumerable <CustomerSubscriptionEntity> customerSubscriptions = await ApplicationDomain.Instance.CustomerSubscriptionsRepository.RetrieveAsync(clientCustomerId).ConfigureAwait(false); IEnumerable <PartnerOffer> allPartnerOffers = await ApplicationDomain.Instance.OffersRepository.RetrieveAsync().ConfigureAwait(false); IEnumerable <MicrosoftOffer> currentMicrosoftOffers = await ApplicationDomain.Instance.OffersRepository.RetrieveMicrosoftOffersAsync().ConfigureAwait(false); List <SubscriptionViewModel> customerSubscriptionsView = new List <SubscriptionViewModel>(); // iterate through and build the list of customer's subscriptions. foreach (CustomerSubscriptionEntity subscription in customerSubscriptions) { PartnerOffer partnerOfferItem = allPartnerOffers.FirstOrDefault(offer => offer.Id == subscription.PartnerOfferId); string subscriptionTitle = partnerOfferItem.Title; string portalOfferId = partnerOfferItem.Id; decimal portalOfferPrice = partnerOfferItem.Price; DateTime subscriptionExpiryDate = subscription.ExpiryDate.ToUniversalTime(); int remainingDays = (subscriptionExpiryDate.Date - DateTime.UtcNow.Date).Days; bool isRenewable = remainingDays <= 30; // IsRenewable is true if subscription is going to expire in 30 days. bool isEditable = DateTime.UtcNow.Date <= subscriptionExpiryDate.Date; // IsEditable is true if today is lesser or equal to subscription expiry date. // Temporarily mark this partnerOffer item as inactive and dont allow store front customer to manage this subscription. MicrosoftOffer alignedMicrosoftOffer = currentMicrosoftOffers.FirstOrDefault(offer => offer.Offer.Id == partnerOfferItem.MicrosoftOfferId); if (alignedMicrosoftOffer == null) { // The offer is inactive (marked for deletion) then dont allow renewals or editing on this subscription tied to this offer. partnerOfferItem.IsInactive = true; isRenewable = false; isEditable = false; } BrandingConfiguration portalBranding = await ApplicationDomain.Instance.PortalBranding.RetrieveAsync().ConfigureAwait(false); // Compute the pro rated price per seat for this subcription & return for client side processing during updates. decimal proratedPerSeatPrice = Math.Round(CommerceOperations.CalculateProratedSeatCharge(subscription.ExpiryDate, portalOfferPrice, portalBranding.BillingCycle), Resources.Culture.NumberFormat.CurrencyDecimalDigits); SubscriptionViewModel subscriptionItem = new SubscriptionViewModel() { SubscriptionId = subscription.SubscriptionId, FriendlyName = subscriptionTitle, PortalOfferId = portalOfferId, PortalOfferPrice = portalOfferPrice.ToString("C", responseCulture), IsRenewable = isRenewable, IsEditable = isEditable, SubscriptionExpiryDate = subscriptionExpiryDate.Date.ToString("d", responseCulture), SubscriptionProRatedPrice = proratedPerSeatPrice }; // add this subcription to the customer's subscription list. customerSubscriptionsView.Add(subscriptionItem); } List <CustomerSubscriptionModel> customerManagedSubscriptions = new List <CustomerSubscriptionModel>(); List <PartnerSubscriptionModel> partnerManagedSubscriptions = new List <PartnerSubscriptionModel>(); // Divide the subscriptions by customer and partner foreach (Subscription customerSubscriptionFromPC in customerAllSubscriptions.Items) { SubscriptionViewModel subscription = customerSubscriptionsView.FirstOrDefault(sub => sub.SubscriptionId == customerSubscriptionFromPC.Id); // Customer managed subscription found if (subscription != null) { CustomerSubscriptionModel customerSubscription = new CustomerSubscriptionModel() { SubscriptionId = customerSubscriptionFromPC.Id, LicensesTotal = customerSubscriptionFromPC.Quantity.ToString("G", responseCulture), Status = GetStatusType(customerSubscriptionFromPC.Status), CreationDate = customerSubscriptionFromPC.CreationDate.ToString("d", responseCulture), FriendlyName = subscription.FriendlyName, IsRenewable = subscription.IsRenewable, IsEditable = subscription.IsEditable, PortalOfferId = subscription.PortalOfferId, SubscriptionProRatedPrice = subscription.SubscriptionProRatedPrice }; customerManagedSubscriptions.Add(customerSubscription); } else { PartnerSubscriptionModel partnerSubscription = new PartnerSubscriptionModel() { Id = customerSubscriptionFromPC.Id, OfferName = customerSubscriptionFromPC.OfferName, Quantity = customerSubscriptionFromPC.Quantity.ToString("G", responseCulture), Status = GetStatusType(customerSubscriptionFromPC.Status), CreationDate = customerSubscriptionFromPC.CreationDate.ToString("d", responseCulture), }; partnerManagedSubscriptions.Add(partnerSubscription); } } ManagedSubscriptionsViewModel managedSubscriptions = new ManagedSubscriptionsViewModel() { CustomerManagedSubscriptions = customerManagedSubscriptions.OrderByDescending(customerManagedSubscription => customerManagedSubscription.CreationDate), PartnerManagedSubscriptions = partnerManagedSubscriptions.OrderByDescending(partnerManagedSubscription => partnerManagedSubscription.CreationDate) }; // Capture the request for customer managed subscriptions and partner managed subscriptions for analysis. Dictionary <string, string> eventProperties = new Dictionary <string, string> { { "CustomerId", clientCustomerId } }; // Track the event measurements for analysis. Dictionary <string, double> eventMetrics = new Dictionary <string, double> { { "ElapsedMilliseconds", DateTime.Now.Subtract(startTime).TotalMilliseconds }, { "CustomerManagedSubscriptions", customerManagedSubscriptions.Count }, { "PartnerManagedSubscriptions", partnerManagedSubscriptions.Count } }; ApplicationDomain.Instance.TelemetryService.Provider.TrackEvent("GetManagedSubscriptions", eventProperties, eventMetrics); return(managedSubscriptions); }
/// <summary> /// Normalizes an order to add seats to a subscription. /// </summary> /// <returns>Normalized order.</returns> public async Task <OrderViewModel> NormalizePurchaseAdditionalSeatsOrderAsync() { OrderViewModel order = 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); // 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); }
/// <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); }