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