private AuthorizeNet.Order CreatePaymentOrder(IOrderGroup orderGroup, TransactionData transactionData, ITokenizedPayment payment, decimal orderAmount) { var customer = _authorizeTokenExService.CreateCustomer(transactionData.invoiceNum, payment.BillingAddress); var address = Utilities.ToAuthorizeNetAddress(payment.BillingAddress); customer.BillingAddress = address; _authorizeTokenExService.UpdateCustomer(customer); var shippingAddress = orderGroup.Forms.First().Shipments.First().ShippingAddress; if (shippingAddress == null) { throw new PaymentException(PaymentException.ErrorType.ConfigurationError, "", "Shipping address was not specified."); } var paymentProfileId = AddCreditCard(orderGroup, customer.ProfileID, payment.Token, payment.ExpirationMonth, payment.ExpirationYear); var shippingAddressId = AddShippingAddress(customer.ProfileID, Utilities.ToAuthorizeNetAddress(shippingAddress)); var currency = orderGroup.Currency; var market = _marketService.GetMarket(orderGroup.MarketId); var merchantCurrency = _authorizeTokenExService.GetMerchantCurrency(); var shippingTotal = Utilities.ConvertMoney(orderGroup.GetShippingTotal(_orderGroupCalculator), merchantCurrency); var salesTaxTotal = Utilities.ConvertMoney( new Money(orderGroup.Forms.Sum(form => form.Shipments.Sum(shipment => _shippingCalculator.GetSalesTax(shipment, market, currency).Amount)), currency).Round(), merchantCurrency); var taxTotal = Utilities.ConvertMoney(orderGroup.GetTaxTotal(_orderGroupCalculator), merchantCurrency); var order = new AuthorizeNet.Order(customer.ProfileID, paymentProfileId, shippingAddressId) { Amount = orderAmount - taxTotal, SalesTaxAmount = salesTaxTotal, PONumber = transactionData.purchaseOrderNum, InvoiceNumber = transactionData.invoiceNum, ShippingAmount = shippingTotal + (orderGroup.PricesIncludeTax ? 0 : taxTotal - salesTaxTotal) }; var orderedLineItems = orderGroup.GetAllLineItems().OrderBy(x => x.Quantity).ToList(); var largestQuantityItem = orderedLineItems.LastOrDefault(); var lineItemsAmount = 0m; var amountWithoutLargestQtyItem = 0m; var settleSubTotalExclTax = orderAmount - order.ShippingAmount - order.SalesTaxAmount; var isFirstLineItem = true; var factor = (decimal)Math.Pow(10, DecimalDigit); var roundingDelta = 1 / factor; var itemPriceExcludingTaxMapping = GetPriceExcludingTax(orderGroup); foreach (var lineItem in orderedLineItems) { var itemCode = lineItem.Code; var itemQuantity = Convert.ToInt32(lineItem.Quantity); var displayName = string.IsNullOrEmpty(lineItem.DisplayName) ? itemCode : lineItem.DisplayName; var description = (lineItem as Mediachase.Commerce.Orders.LineItem)?.Description ?? string.Empty; if (lineItem.IsGift) { itemCode = $"Gift: {itemCode}"; displayName = $"Gift: {displayName}"; description = $"Gift: {description}"; } itemCode = Utilities.StripPreviewText(itemCode, 31); displayName = Utilities.StripPreviewText(displayName, 31); description = Utilities.StripPreviewText(description, 255); // Calculate unit price. var unitPrice = Utilities.ConvertMoney( new Money(currency.Round(itemPriceExcludingTaxMapping[lineItem.LineItemId] / lineItem.Quantity, DecimalDigit), currency), merchantCurrency); lineItemsAmount += unitPrice * lineItem.Quantity; if (lineItem.LineItemId != largestQuantityItem.LineItemId) { amountWithoutLargestQtyItem = lineItemsAmount; } // In case we have Rounding Differences between rounding Total Amount (orderAmount - amount requested for settlement) // and rounding each lineItem unit price (amount authorized), need to recalculate unit price of item has largest quantity. if (lineItem.LineItemId == largestQuantityItem.LineItemId && lineItemsAmount < settleSubTotalExclTax) { // Choose largestQuantityItem to make the unitPrice difference before and after recalculate smallest. unitPrice = (settleSubTotalExclTax - amountWithoutLargestQtyItem) / largestQuantityItem.Quantity; // Round up to make sure amount authorized >= requested for settlement amount. unitPrice = merchantCurrency.Round(unitPrice + roundingDelta, DecimalDigit); } if (!isFirstLineItem) { // If the lineitem price to add to the total exceed the Amount chosen // for the payment. The line item price is adjusted, since it will be covered by other payment method. if (unitPrice * lineItem.Quantity + order.Total > order.Amount) { var lineItemPrice = orderAmount - order.Total; // If this is the last item, then we need to round up to make sure amount authorized >= requested for settlement amount. if (lineItem.LineItemId == largestQuantityItem.LineItemId) { unitPrice = merchantCurrency.Round(lineItemPrice / lineItem.Quantity + roundingDelta, DecimalDigit); } else { unitPrice = merchantCurrency.Round(lineItemPrice / lineItem.Quantity, DecimalDigit); } } } // Note that order total calculated from authorized.net order constructor is recalculated by adding line items. order.AddLineItem(itemCode, displayName, description, itemQuantity, unitPrice, null); // We need to handle the case of first line item after being added to the order // since order total is recalculated when is added to the order. if (isFirstLineItem) { // If with the first line item the amount chosen for the payment is exceeding // the price added to the line item then should be the amount chosen. if (order.Total > orderAmount) { // In case the order has only one line item, then we need to round up to make sure amount authorized >= requested for settlement amount. if (lineItem.LineItemId == largestQuantityItem.LineItemId) { unitPrice = merchantCurrency.Round(settleSubTotalExclTax / lineItem.Quantity + roundingDelta, DecimalDigit); } else { unitPrice = merchantCurrency.Round(settleSubTotalExclTax / lineItem.Quantity, DecimalDigit); } order.RemoveLineItem(itemCode); order.AddLineItem(itemCode, displayName, description, itemQuantity, unitPrice, null); } } isFirstLineItem = false; } payment.Properties[AuthorizeTokenExGateway.ProviderProfileIdPropertyName] = customer.ProfileID; return(order); }