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); }
/// <summary> /// Processes the payment. /// </summary> /// <param name="orderGroup">The order group.</param> /// <param name="payment">The payment.</param> public override PaymentProcessingResult ProcessPayment(IOrderGroup orderGroup, IPayment payment) { if (orderGroup is null) { throw new ArgumentException("Order group should not be null."); } if (!(payment is ITokenizedPayment tokenPayment)) { throw new PaymentException(PaymentException.ErrorType.ProviderError, "", "Payment is not tokenization."); } var message = string.Empty; var transactionRequest = new TransactionRequest( GetSetting(UserParameterName), GetSetting(TransactionKeyParameterName), GetTestModeSetting()); var authorizeNetService = new AuthorizeTokenExService(transactionRequest, new TokenExGateway()); //Process purchase order if (IsRegularTransaction(orderGroup)) { var address = tokenPayment.BillingAddress; if (address == null) { throw new PaymentException(PaymentException.ErrorType.ConfigurationError, "", "Billing address was not specified."); } var transactionData = CreateTransactionDataForRegularTransaction(orderGroup, tokenPayment); var merchantCurrency = authorizeNetService.GetMerchantCurrency(); var amount = Utilities.ConvertMoney(new Money(tokenPayment.Amount, orderGroup.Currency), merchantCurrency); var manager = new AuthorizeTokenExCimBasedManager( authorizeNetService, ServiceLocator.Current.GetInstance <IOrderGroupCalculator>(), ServiceLocator.Current.GetInstance <IShippingCalculator>(), ServiceLocator.Current.GetInstance <ILineItemCalculator>(), ServiceLocator.Current.GetInstance <IMarketService>()); var response = manager.Process(orderGroup, transactionRequest, transactionData, amount, tokenPayment); if (response.IsTransactionCapturedOrSettled) { PostProcessPayment(tokenPayment); return(PaymentProcessingResult.CreateSuccessfulResult(string.Empty)); } var result = (GatewayResponse)response.Response; if (result.Error || result.Declined) { var exception = new PaymentException(PaymentException.ErrorType.ProviderError, result.ResponseCode, result.Message); exception.ResponseMessages.Add("CSCResponse", result.CAVResponse); exception.ResponseMessages.Add("ReasonCode", result.ResponseReasonCode); exception.ResponseMessages.Add("Subcode", $"{result.SubCode}"); exception.ResponseMessages.Add("AVSResult", result.AVSResponse); throw exception; } tokenPayment.AuthorizationCode = tokenPayment.ValidationCode = response.Response.AuthorizationCode; if (transactionData.type == TransactionType.Authorization) { tokenPayment.TransactionID = response.Response.TransactionID; } // When doing capture, refund, etc... transactions, Authorize.Net will return a new Transaction Id. We need to store this to ProviderTransactionID // instead of TransactionID, because TransactionID should be the Authorization transaction Id, and ProviderTransactionID will be used when we want to refund. tokenPayment.ProviderTransactionID = response.Response.TransactionID; message = response.Response.Message; } PostProcessPayment(tokenPayment); //TODO Supports Payment plan return(PaymentProcessingResult.CreateSuccessfulResult(message)); }