/// <summary> /// Static entry point to calculate amount paid and due. /// </summary> /// <param name="context">The request context.</param> /// <param name="salesTransaction">The sales transaction.</param> public static void CalculateAmountPaidAndDue(RequestContext context, SalesTransaction salesTransaction) { ThrowIf.Null(context, "context"); ThrowIf.Null(salesTransaction, "salesTransaction"); decimal paymentRequiredAmount; salesTransaction.AmountPaid = SalesTransactionTotaler.GetPaymentsSum(salesTransaction.TenderLines); // decides what is expected to be paid for this transaction switch (salesTransaction.CartType) { case CartType.CustomerOrder: paymentRequiredAmount = SalesTransactionTotaler.CalculateRequiredPaymentAmount(context, salesTransaction); break; case CartType.Shopping: case CartType.Checkout: case CartType.AccountDeposit: paymentRequiredAmount = salesTransaction.TotalAmount; break; case CartType.IncomeExpense: paymentRequiredAmount = salesTransaction.IncomeExpenseTotalAmount; break; default: throw new DataValidationException( DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_InvalidRequest, string.Format("SalesTransactionTotaler::CalculateAmountPaidAndDue: CartType '{0}' not supported.", salesTransaction.CartType)); } salesTransaction.SalesPaymentDifference = paymentRequiredAmount - salesTransaction.AmountPaid; salesTransaction.AmountDue = paymentRequiredAmount - salesTransaction.AmountPaid; TenderLine lastTenderLine = null; if (!salesTransaction.TenderLines.IsNullOrEmpty()) { lastTenderLine = salesTransaction.ActiveTenderLines.LastOrDefault(); } if (lastTenderLine != null) { // Calculate the expected (rounded) amount due for last payment. decimal amountDueBeforeLastPayment = paymentRequiredAmount - salesTransaction.ActiveTenderLines.Take(salesTransaction.ActiveTenderLines.Count - 1).Sum(t => t.Amount); GetPaymentRoundedValueServiceRequest roundAmountDueBeforeLastPaymentRequest = new GetPaymentRoundedValueServiceRequest(amountDueBeforeLastPayment, lastTenderLine.TenderTypeId, isChange: false); GetRoundedValueServiceResponse roundAmountDueBeforeLastPaymentResponse = context.Execute <GetRoundedValueServiceResponse>(roundAmountDueBeforeLastPaymentRequest); // Set amont due to zero if payment amount equals to expected rounded payment amount. Otherwise another payment should be required (that could use different rounding settings). if (roundAmountDueBeforeLastPaymentResponse.RoundedValue == lastTenderLine.Amount) { salesTransaction.AmountDue = decimal.Zero; } } // When required amount is positive, amount due must be zero or negative (overtender), otherwise (e.g. for refunds or exchanges) exact amount has to refunded (zero balance). salesTransaction.IsRequiredAmountPaid = (paymentRequiredAmount > 0 && salesTransaction.AmountDue <= 0) || (paymentRequiredAmount <= 0 && salesTransaction.AmountDue == 0); }
/// <summary> /// Gets the change. /// </summary> /// <param name="request">The request.</param> /// <returns> /// A response containing the change tender line. /// </returns> private static GetChangePaymentServiceResponse GetChange(GetChangePaymentServiceRequest request) { if (request == null) { throw new ArgumentNullException("request"); } if (request.ChangeTenderTypeId == null) { throw new ArgumentException("request.TenderType is null", "request"); } string creditMemoId = IssueCreditMemo(request.RequestContext, request.ChangeAmount, request.CurrencyCode, request.Transaction.Id, request.Transaction.ReceiptId); var changeTenderLine = new TenderLine { CreditMemoId = creditMemoId, Amount = decimal.Negate(request.ChangeAmount), // change tender line must have negative amount Currency = request.CurrencyCode, TenderLineId = Guid.NewGuid().ToString("N"), TenderTypeId = request.ChangeTenderTypeId, Status = TenderLineStatus.Committed, IsVoidable = false, IsChangeLine = true }; return(new GetChangePaymentServiceResponse(changeTenderLine)); }
/// <summary> /// Gets the change. /// </summary> /// <param name="request">The request.</param> /// <returns> /// A response containing the change tender line. /// </returns> private static GetChangePaymentServiceResponse GetChange(GetChangePaymentServiceRequest request) { if (request == null) { throw new ArgumentNullException("request"); } if (string.IsNullOrWhiteSpace(request.ChangeTenderTypeId)) { throw new PaymentException(PaymentErrors.Microsoft_Dynamics_Commerce_Runtime_InvalidPaymentRequest, "request.TenderTypeId is null or empty."); } var changeTenderLine = new TenderLine { Amount = decimal.Negate(request.ChangeAmount), // change tender line must have negative amount Currency = request.CurrencyCode, TenderLineId = Guid.NewGuid().ToString("N"), TenderTypeId = request.ChangeTenderTypeId, Status = TenderLineStatus.Committed, IsVoidable = true, IsChangeLine = true }; return(new GetChangePaymentServiceResponse(changeTenderLine)); }
/// <summary> /// Update tender line amounts with cash back for company and channel currencies. /// </summary> /// <param name="tenderLine">The tender line to update.</param> /// <param name="context">The request context.</param> private static void CalculateAmountsWithCashBack(TenderLine tenderLine, RequestContext context) { tenderLine.Amount += tenderLine.CashBackAmount; // In case of cashback, tendered currency is always equal to the channel currency. tenderLine.AmountInTenderedCurrency += tenderLine.CashBackAmount; string companyCurrencyCode = context.GetChannelConfiguration().CompanyCurrency; if (!tenderLine.Currency.Equals(companyCurrencyCode, StringComparison.OrdinalIgnoreCase)) { // Convert cashback from tendered to company currency. GetCurrencyValueServiceResponse cashBackConversionInfo = ConvertCurrencyAmount( tenderLine.CashBackAmount, tenderLine.Currency, companyCurrencyCode, context); tenderLine.AmountInCompanyCurrency += cashBackConversionInfo.ConvertedAmount; } else { tenderLine.AmountInCompanyCurrency += tenderLine.CashBackAmount; } tenderLine.CashBackAmount = 0M; }
/// <summary> /// Update tender line with amounts and exchange rates for company and channel currencies. /// </summary> /// <param name="tenderLine">Tender line to update.</param> /// <param name="context">Request context.</param> private static void CalculateTenderLineCurrencyAmounts(TenderLine tenderLine, RequestContext context) { string channelCurrencyCode = context.GetChannelConfiguration().Currency; if (!tenderLine.Currency.Equals(channelCurrencyCode, StringComparison.OrdinalIgnoreCase)) { if (tenderLine.AmountInTenderedCurrency == 0) { throw new DataValidationException(DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_InvalidRequest, "Currency on tender line is different from store currency but AmountInTenderedCurrency is not specified."); } IEnumerable <string> supportedCurrencies = GetCurrencyCodeSupportedByChannel(context); if (supportedCurrencies.FirstOrDefault(c => string.Equals(c, tenderLine.Currency, StringComparison.OrdinalIgnoreCase)) == null) { throw new DataValidationException( DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_CurrencyConversionFailed, string.Format("Currency code '{0}' is not supported by current channel.", tenderLine.Currency)); } // Convert amount and get exchange rate from tendered currency to channel currency. GetCurrencyValueServiceResponse channelConversionInfo = ConvertCurrencyAmount( tenderLine.AmountInTenderedCurrency, tenderLine.Currency, channelCurrencyCode, context); tenderLine.ExchangeRate = channelConversionInfo.ExchangeRate; tenderLine.Amount = channelConversionInfo.RoundedConvertedAmount; // Round the amount in tendered currency. tenderLine.AmountInTenderedCurrency = RoundAmountByCurrency(context, tenderLine.AmountInTenderedCurrency, tenderLine.Currency); } else { tenderLine.ExchangeRate = 1m; tenderLine.AmountInTenderedCurrency = tenderLine.Amount; } string companyCurrencyCode = context.GetChannelConfiguration().CompanyCurrency; if (!tenderLine.Currency.Equals(companyCurrencyCode, StringComparison.OrdinalIgnoreCase)) { // Convert tendered amount to company amount, and get exchange rate from tendered to company currencies. GetCurrencyValueServiceResponse tenderConversionInfo = ConvertCurrencyAmount( tenderLine.AmountInTenderedCurrency, tenderLine.Currency, companyCurrencyCode, context); tenderLine.CompanyCurrencyExchangeRate = tenderConversionInfo.ExchangeRate; tenderLine.AmountInCompanyCurrency = tenderConversionInfo.RoundedConvertedAmount; } else { tenderLine.CompanyCurrencyExchangeRate = 1m; tenderLine.AmountInCompanyCurrency = PaymentManagerService.RoundAmountByCurrency(context, tenderLine.AmountInTenderedCurrency, tenderLine.Currency); } }
/// <summary> /// Adds the or update reason code lines. /// </summary> /// <param name="tenderLine">The tender line.</param> /// <param name="cartTenderLine">The cart tender line.</param> /// <param name="transactionId">The transaction id.</param> public static void AddOrUpdateReasonCodeLinesOnTenderLine(TenderLine tenderLine, TenderLineBase cartTenderLine, string transactionId) { ThrowIf.Null(tenderLine, "tenderLine"); ThrowIf.Null(cartTenderLine, "cartTenderLine"); if (cartTenderLine.ReasonCodeLines != null && cartTenderLine.ReasonCodeLines.Any()) { AddOrUpdateReasonCodeLinesHelper( reasonCodeLinesToUpdate: tenderLine.ReasonCodeLines, reasonCodeLines: cartTenderLine.ReasonCodeLines, transactionId: transactionId, parentLineId: tenderLine.TenderLineId, reasonCodeLineType: ReasonCodeLineType.Payment); } }
/// <summary> /// Calculate specific reason codes on a tender line and add them to the incoming collection. /// </summary> /// <param name="request">The request object.</param> /// <param name="requiredReasonCodes">The collection to which required reason codes are added.</param> /// <param name="reasonCodeRequirements">The required specific reason codes map.</param> /// <param name="tenderLine">The tenderLine on which to calculate required reason codes.</param> private static void CalculateRequiredSpecificReasonCodesOnTenderLine( CalculateRequiredReasonCodesServiceRequest request, IDictionary <string, ReasonCode> requiredReasonCodes, HashSet <ReasonCodeRequirement> reasonCodeRequirements, TenderLine tenderLine) { if (tenderLine.Status == TenderLineStatus.Historical) { return; } // if tenderline is a card, refrelation3 becomes tenderline.cardtypeid and tablerefid is creditcard var getChannelTenderTypesDataRequest = new GetChannelTenderTypesDataRequest(request.RequestContext.GetPrincipal().ChannelId, QueryResultSettings.AllRecords); var tenderTypes = request.RequestContext.Runtime.Execute <EntityDataServiceResponse <TenderType> >(getChannelTenderTypesDataRequest, request.RequestContext).PagedEntityCollection.Results; ReasonCodeTableRefType tableRef = ReasonCodeTableRefType.Tender; string refRelation3 = string.Empty; TenderType tenderType = tenderTypes.Where(type => type.TenderTypeId == tenderLine.TenderTypeId).SingleOrDefault(); if (tenderType.OperationId == (int)RetailOperation.PayCard) { refRelation3 = tenderLine.CardTypeId; tableRef = ReasonCodeTableRefType.CreditCard; } CalculateReasonCodesSpecificToEntity( request, tenderLine.TenderTypeId, tableRef, request.SalesTransaction.StoreId, tenderLine.TenderTypeId, refRelation3, requiredReasonCodes, reasonCodeRequirements, tenderLine.ReasonCodeLines, null); }
/// <summary> /// Gets a value indicating whether a tender is card refund. /// </summary> /// <param name="tenderType">The tender type.</param> /// <param name="tenderLine">The tender line.</param> /// <returns>The result value.</returns> private static bool IsCardRefund(TenderType tenderType, TenderLine tenderLine) { return(tenderType.OperationType == RetailOperation.PayCard && tenderLine.Amount < 0); }
/// <summary> /// Validate that all payments made with same tender type do not exceed limits defined for tender type. /// </summary> /// <param name="salesTransaction">Sales transaction.</param> /// <param name="tenderLine">Tender line to validate.</param> /// <param name="tenderType">Tender type information.</param> private static void ValidateTransactionLimits(SalesTransaction salesTransaction, TenderLine tenderLine, TenderType tenderType) { if (salesTransaction == null) { throw new ArgumentNullException("salesTransaction"); } if (tenderLine == null) { throw new ArgumentNullException("tenderLine"); } if (tenderType == null) { throw new ArgumentNullException("tenderType"); } decimal amountAlreadyPaidWithSameTenderType = salesTransaction.TenderLines.Where(t => t.Status != TenderLineStatus.Voided && t.TenderTypeId == tenderLine.TenderTypeId).Sum(t => t.Amount); decimal totalAmountPaidWithTenderType = tenderLine.Amount + amountAlreadyPaidWithSameTenderType; var validationFailures = new Collection <DataValidationFailure>(); // Card refund does not honor MaximumAmountPerTransaction. // Only check the limit when the tender is not card refund. if (!IsCardRefund(tenderType, tenderLine)) { if (tenderType.MaximumAmountPerTransaction > 0 && Math.Abs(totalAmountPaidWithTenderType) > tenderType.MaximumAmountPerTransaction) { validationFailures.Add(new DataValidationFailure(DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_PaymentExceedsMaximumAmountPerTransaction, "tenderLine.Amount")); } } if (validationFailures.Any()) { throw new DataValidationException(DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_AggregateValidationError, validationFailures, "Payment amount exceeds limits defined per transaction."); } }
/// <summary> /// Validates that tender line fulfills limits configured for card type. /// </summary> /// <param name="context">The request context.</param> /// <param name="tenderLine">Payment information.</param> /// <returns>Collection of <see cref="DataValidationFailure"/>.</returns> private static IEnumerable <DataValidationFailure> ValidateCardTypeLimits(RequestContext context, TenderLine tenderLine) { List <DataValidationFailure> failures = new List <DataValidationFailure>(); CardTypeInfo cardTypeInfo = CardTypeHelper.GetCardTypeConfiguration(tenderLine.CardTypeId, context); if (cardTypeInfo == null) { failures.Add(new DataValidationFailure(DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_ObjectNotFound, "Card type with id '{0}' not found", tenderLine.CardTypeId)); } if (tenderLine.CashBackAmount < 0) { failures.Add(new DataValidationFailure(DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_InvalidCashBackAmount, "Cash back amount cannot be negative.")); } if (tenderLine.CashBackAmount != 0 && tenderLine.Amount < 0) { failures.Add(new DataValidationFailure(DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_AmountDueMustBePaidBeforeCheckout, "Cash back not allowed for refunds.")); } if (cardTypeInfo != null && tenderLine.CashBackAmount > cardTypeInfo.CashBackLimit) { string message = string.Format("Cash back amount for card type '{0}' exceed maximum allowed value {1}.", cardTypeInfo.TypeId, cardTypeInfo.CashBackLimit); failures.Add(new DataValidationFailure(DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_InvalidCashBackAmount, message)); } return(failures); }
/// <summary> /// Validate that amount on tender line do not exceed limits defined for tender type. /// </summary> /// <param name="context">The request context.</param> /// <param name="salesTransaction">Sales transaction.</param> /// <param name="tenderLine">Tender line to validate.</param> /// <param name="tenderType">Tender type information.</param> private static void ValidateTenderLineLimits(RequestContext context, SalesTransaction salesTransaction, TenderLine tenderLine, TenderType tenderType) { if (salesTransaction == null) { throw new ArgumentNullException("salesTransaction"); } if (tenderLine == null) { throw new ArgumentNullException("tenderLine"); } if (tenderType == null) { throw new ArgumentNullException("tenderType"); } decimal amountDue = RoundAmountByTenderType(context, tenderLine.TenderTypeId, salesTransaction.AmountDue, isChange: false); var validationFailures = new Collection <DataValidationFailure>(); if (tenderLine.Amount != 0 && (Math.Sign(amountDue) != Math.Sign(tenderLine.Amount))) { validationFailures.Add(new DataValidationFailure(DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_IncorrectPaymentAmountSign, "tenderLine.Amount")); } if (tenderType.MaximumAmountPerLine > 0 && Math.Abs(tenderLine.Amount) > tenderType.MaximumAmountPerLine) { // 1396 = The maximum amount allowed is: validationFailures.Add(new DataValidationFailure(DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_PaymentExceedsMaximumAmountPerLine, "tenderLine.Amount")); } if (tenderType.MinimumAmountPerLine > 0 && Math.Abs(tenderLine.Amount) < tenderType.MinimumAmountPerLine) { // 1397 = The minimum amount allowed is: validationFailures.Add(new DataValidationFailure(DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_PaymentExceedsMinimumAmountPerLine, "tenderLine.Amount")); } // Card refund does not honor overtender/undertender limits. // Only check those limits when the tender is not card refund. if (!IsCardRefund(tenderType, tenderLine)) { if (Math.Abs(amountDue) < Math.Abs(tenderLine.Amount)) { if (!tenderType.IsOvertenderAllowed) { // 1391 = No change allowed: validationFailures.Add(new DataValidationFailure(DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_ChangebackIsNotAllowed, "tenderLine.Amount")); } if (tenderType.MaximumOvertenderAmount > 0 && tenderType.MaximumOvertenderAmount < (Math.Abs(tenderLine.Amount) - Math.Abs(amountDue))) { validationFailures.Add(new DataValidationFailure(DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_OvertenderAmountExceedsMaximumAllowedValue, "tenderLine.Amount")); } } else if (Math.Abs(amountDue) > Math.Abs(tenderLine.Amount)) { if (!tenderType.IsUndertenderAllowed) { // 1394 = This payment must be used to finalize the transaction: validationFailures.Add(new DataValidationFailure(DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_PaymentMustBeUsedToFinalizeTransaction, "tenderLine.Amount")); } if (tenderType.MaximumUndertenderAmount > 0 && (Math.Abs(amountDue) - Math.Abs(tenderLine.Amount)) > tenderType.MaximumUndertenderAmount) { validationFailures.Add(new DataValidationFailure(DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_BalanceAmountExceedsMaximumAllowedValue, "tenderLine.Amount")); } if (tenderLine.CashBackAmount > 0) { validationFailures.Add(new DataValidationFailure(DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_PaymentMustBeUsedToFinalizeTransaction, "tenderLine.Amount")); } } } if (tenderType.OperationType == RetailOperation.PayCard) { validationFailures.AddRange(ValidateCardTypeLimits(context, tenderLine)); } if (validationFailures.Any()) { throw new DataValidationException(DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_AggregateValidationError, validationFailures, "Payment amount exceeds limits defined per transaction."); } }
/// <summary> /// Validates sufficient funds on account. /// </summary> /// <param name="context">The service request context.</param> /// <param name="customer">The customer.</param> /// <param name="useInvoiceAccount">Whether to use invoice account.</param> /// <param name="tenderLine">The tender line.</param> private static void CheckIfPaymentExceedsBalance(RequestContext context, Customer customer, bool useInvoiceAccount, TenderLine tenderLine) { // Search location set to all in order to retrieve pending transactions anchor from AX var localBalanceServiceRequest = new GetCustomerBalanceServiceRequest( customer.AccountNumber, customer.InvoiceAccount, SearchLocation.All); CustomerBalances pendingAccountBalances = context.Execute <GetCustomerBalanceServiceResponse>(localBalanceServiceRequest).Balance; // Total amount to verify is the sum of amount on the tender line and pending customer account balance // where pending balances = (tendered amounts - any deposits made) which is not yet uploaded to AX. decimal amountToVerify = tenderLine.Amount + (useInvoiceAccount ? pendingAccountBalances.InvoiceAccountPendingBalance : pendingAccountBalances.PendingBalance); var validateCustomerAccountPaymentRealtimeRequest = new ValidateCustomerAccountPaymentRealtimeRequest(tenderLine.CustomerId, amountToVerify, tenderLine.Currency); context.Execute <NullResponse>(validateCustomerAccountPaymentRealtimeRequest); }
/// <summary> /// Authorizes the payment. /// </summary> /// <param name="request">The request.</param> /// <returns>A response containing the authorized tender line.</returns> private static AuthorizePaymentServiceResponse AuthorizePayment(AuthorizePaymentServiceRequest request) { if (request == null) { throw new ArgumentNullException("request"); } TenderLine tenderLine = request.TenderLine; if (tenderLine == null) { throw new PaymentException(PaymentErrors.Microsoft_Dynamics_Commerce_Runtime_InvalidPaymentRequest, "Customer account payment requires tender line."); } if (string.IsNullOrWhiteSpace(tenderLine.CustomerId)) { throw new PaymentException(PaymentErrors.Microsoft_Dynamics_Commerce_Runtime_InvalidPaymentRequest, "Customer account payment requires CustomerId to be set on tender line."); } SalesTransaction transaction = request.Transaction; if (transaction.CartType == CartType.CustomerOrder && (transaction.CustomerOrderMode == CustomerOrderMode.CustomerOrderCreateOrEdit || transaction.CustomerOrderMode == CustomerOrderMode.Cancellation)) { throw new PaymentException( PaymentErrors.Microsoft_Dynamics_Commerce_Runtime_CustomerAccountPaymentIsNotAllowedForCustomerOrderDepositAndCancellation, string.Format("Customer account payment cannot be used to pay customer order deposit or cancellation (current mode {0}).", transaction.CustomerOrderMode)); } if (string.IsNullOrWhiteSpace(transaction.CustomerId)) { throw new PaymentException(PaymentErrors.Microsoft_Dynamics_Commerce_Runtime_InvalidPaymentRequest, "Customer account payment requires CustomerId to be set on cart."); } Customer customerOnTransaction = GetCustomer(request.RequestContext, transaction.CustomerId); if (customerOnTransaction == null) { throw new PaymentException( PaymentErrors.Microsoft_Dynamics_Commerce_Runtime_InvalidPaymentRequest, string.Format(CultureInfo.InvariantCulture, "Customer with id {0} was not found.", transaction.CustomerId)); } ValidateCustomerForPayment(customerOnTransaction); bool useInvoiceAccount = !string.IsNullOrWhiteSpace(customerOnTransaction.InvoiceAccount); string customerAccountToCharge = useInvoiceAccount ? customerOnTransaction.InvoiceAccount : customerOnTransaction.AccountNumber; if (!tenderLine.CustomerId.Equals(customerAccountToCharge, StringComparison.OrdinalIgnoreCase)) { // Someone is trying to pay with unathorized account throw new PaymentException(PaymentErrors.Microsoft_Dynamics_Commerce_Runtime_PaymentUsingUnauthorizedAccount, "Customer account payment requires its own account or matching invoice account on a tender line."); } CheckIfPaymentExceedsBalance(request.RequestContext, customerOnTransaction, useInvoiceAccount, tenderLine); // Looks like request has successfully validated tenderLine.Status = TenderLineStatus.Committed; tenderLine.IsVoidable = true; return(new AuthorizePaymentServiceResponse(tenderLine)); }
public Task <Cart> AddPreprocessedTenderLine(string id, TenderLine preprocessedTenderLine) { return(Task.Run(() => OrderManager.Create(CommerceRuntimeManager.Runtime).AddTenderLine(id, preprocessedTenderLine))); }
public static SalesOrder GetSalesOrderFromInfo(CustomerOrderInfo orderInfo, ChannelConfiguration channelConfiguration, RequestContext context) { // Stores the local copy. There is high probability of having the same shipping/delivery address on all the lines. Dictionary <long, Address> shippingAddressDictionary = new Dictionary <long, Address>(); decimal shippingChargeAmount; ColumnSet columnSet = new ColumnSet(); var salesOrder = new SalesOrder { SalesId = orderInfo.Id, TransactionType = SalesTransactionType.CustomerOrder, CustomerOrderMode = CustomerOrderMode.OrderRecalled, CartType = CartType.CustomerOrder, CustomerOrderType = orderInfo.OrderType, StoreId = orderInfo.StoreId, IsTaxIncludedInPrice = Convert.ToBoolean(orderInfo.IsTaxIncludedInPrice) }; switch (orderInfo.OrderType) { case CustomerOrderType.Quote: salesOrder.Status = Utilities.GetSalesStatus((SalesQuotationStatus)orderInfo.Status); break; case CustomerOrderType.SalesOrder: salesOrder.Status = Utilities.GetSalesStatus((SalesOrderStatus)orderInfo.Status, (DocumentStatus)orderInfo.DocumentStatus); break; default: salesOrder.Status = SalesStatus.Unknown; break; } DateTimeOffset currentChannelDate = context.GetNowInChannelTimeZone(); salesOrder.RequestedDeliveryDate = Utilities.ParseDateStringAsDateTimeOffset(orderInfo.RequestedDeliveryDateString, currentChannelDate.Date, currentChannelDate.Offset); salesOrder.QuotationExpiryDate = Utilities.ParseDateStringAsDateTimeOffset(orderInfo.ExpiryDateString, currentChannelDate.Date, currentChannelDate.Offset); // CreationDate is stored in UTC. It needs to be converted to local time zone where order is accessed. salesOrder.BeginDateTime = Utilities.ParseDateString(orderInfo.CreationDateString, currentChannelDate.ToUniversalTime().DateTime, DateTimeStyles.AssumeUniversal); salesOrder.Comment = orderInfo.Comment; // Header delivery salesOrder.InventoryLocationId = orderInfo.WarehouseId; salesOrder.DeliveryMode = orderInfo.DeliveryMode; foreach (var discountCode in orderInfo.DiscountCodes) { salesOrder.DiscountCodes.Add(discountCode); } // Customer info salesOrder.CustomerId = orderInfo.CustomerAccount; long addressRecordIdLong = 0; if (long.TryParse(orderInfo.AddressRecordId, out addressRecordIdLong)) { var dataServiceRequest = new GetAddressDataRequest(addressRecordIdLong, columnSet); SingleEntityDataServiceResponse <Address> dataServiceResponse = context.Execute <SingleEntityDataServiceResponse <Address> >(dataServiceRequest); if (dataServiceResponse.Entity == null) { Utilities.DownloadCustomerData(context, salesOrder.CustomerId); dataServiceResponse = context.Execute <SingleEntityDataServiceResponse <Address> >(dataServiceRequest); } if (dataServiceResponse.Entity != null) { salesOrder.ShippingAddress = dataServiceResponse.Entity; shippingAddressDictionary.Add(salesOrder.ShippingAddress.RecordId, salesOrder.ShippingAddress); } } if (!string.IsNullOrEmpty(orderInfo.SalespersonStaffId)) { // Sets the sales person id and name according to AX values // This is done because we do not know whether the sales person information is available on this store salesOrder.StaffId = orderInfo.SalespersonStaffId; } salesOrder.ChannelReferenceId = orderInfo.ChannelReferenceId; salesOrder.LoyaltyCardId = orderInfo.LoyaltyCardId; salesOrder.ReceiptEmail = orderInfo.Email; string shippingChargeCode = channelConfiguration.ShippingChargeCode; // Items int lineId = 0; foreach (ItemInfo item in orderInfo.Items) { lineId++; var lineItem = new SalesLine { LineId = lineId.ToString(CultureInfo.InvariantCulture), Found = true, RecordId = item.RecId, ItemId = item.ItemId, Comment = item.Comment, Quantity = item.Quantity, ReturnQuantity = item.Quantity, SalesOrderUnitOfMeasure = item.Unit, UnitOfMeasureSymbol = item.Unit, Price = item.Price, NetAmount = item.NetAmount, QuantityOrdered = item.Quantity, QuantityInvoiced = item.QuantityPicked, DeliveryMode = item.DeliveryMode, RequestedDeliveryDate = Utilities.ParseDateStringAsDateTimeOffset(item.RequestedDeliveryDateString, currentChannelDate.Date, currentChannelDate.Offset), FulfillmentStoreId = item.FulfillmentStoreId, InventoryLocationId = item.WarehouseId, SerialNumber = item.SerialId, BatchId = item.BatchId, Status = TransactionStatus.Normal, SalesStatus = Utilities.GetSalesStatus((SalesOrderStatus)item.Status) }; // Copy charges to line and calculates total shipping charge amount lineItem.ChargeLines.AddRange(SalesOrderHelper.CreateChargeLines(item.Charges, shippingChargeCode, salesOrder.BeginDateTime, out shippingChargeAmount)); lineItem.DeliveryModeChargeAmount = shippingChargeAmount; // Line level discount amounts lineItem.LineDiscount = item.LineDscAmount; lineItem.PeriodicDiscount = item.PeriodicDiscount; lineItem.PeriodicPercentageDiscount = item.PeriodicPercentageDiscount; lineItem.LineManualDiscountAmount = item.LineManualDiscountAmount; lineItem.LineManualDiscountPercentage = item.LineManualDiscountPercentage; lineItem.TotalDiscount = item.TotalDiscount; lineItem.TotalPercentageDiscount = item.TotalPctDiscount; // Copy discounts to line lineItem.DiscountLines.AddRange(SalesOrderHelper.CreateDiscountLines(item.Discounts)); long itemAddressRecordIdLong; if (long.TryParse(item.AddressRecordId, out itemAddressRecordIdLong)) { Address lineLevelshippingAddress = new Address(); if (!shippingAddressDictionary.TryGetValue(itemAddressRecordIdLong, out lineLevelshippingAddress)) { var dataServiceRequest = new GetAddressDataRequest(itemAddressRecordIdLong, columnSet); SingleEntityDataServiceResponse <Address> dataServiceResponse = context.Execute <SingleEntityDataServiceResponse <Address> >(dataServiceRequest); // If address not found download and get. if (dataServiceResponse.Entity == null) { Utilities.DownloadCustomerData(context, salesOrder.CustomerId); dataServiceResponse = context.Execute <SingleEntityDataServiceResponse <Address> >(dataServiceRequest); } if (dataServiceResponse.Entity != null) { lineItem.ShippingAddress = dataServiceResponse.Entity; shippingAddressDictionary.Add(lineItem.ShippingAddress.RecordId, lineItem.ShippingAddress); } } else { lineItem.ShippingAddress = lineLevelshippingAddress; } } Utilities.SetUpVariantAndProduct(context, item.InventDimensionId, lineItem.ItemId, lineItem); lineItem.DiscountAmount = item.Discount; // Set tax info after defaults, as it may have been overridden. lineItem.SalesTaxGroupId = item.SalesTaxGroup ?? string.Empty; lineItem.ItemTaxGroupId = item.ItemTaxGroup ?? string.Empty; // Add it to the transaction salesOrder.SalesLines.Add(lineItem); } // Charges for the header salesOrder.ChargeLines.AddRange(SalesOrderHelper.CreateChargeLines(orderInfo.Charges, shippingChargeCode, salesOrder.BeginDateTime, out shippingChargeAmount)); salesOrder.DeliveryModeChargeAmount = shippingChargeAmount; // Payments // - total up amounts // - add history entries decimal nonPrepayments = decimal.Zero; decimal prepaymentAmountPaid = decimal.Zero; int tenderLineId = 0; foreach (PaymentInfo payment in orderInfo.Payments) { if (salesOrder.TenderLines == null) { salesOrder.TenderLines = new Collection <TenderLine>(); } decimal amount = 0M; if (string.IsNullOrWhiteSpace(payment.Currency) || payment.Currency.Equals(channelConfiguration.Currency, StringComparison.OrdinalIgnoreCase)) { amount = payment.Amount; } else { GetCurrencyValueServiceRequest currencyValueRequest = new GetCurrencyValueServiceRequest(payment.Currency, channelConfiguration.Currency, payment.Amount); GetCurrencyValueServiceResponse currencyValueResponse = context.Execute <GetCurrencyValueServiceResponse>(currencyValueRequest); amount = currencyValueResponse.RoundedConvertedAmount; } if (payment.Prepayment) { // Sum prepayments to track total deposits paid prepaymentAmountPaid += amount; } else { // Sum non-prepayments as base for calculating deposits applied to pickups nonPrepayments += amount; } tenderLineId++; var tenderLine = new TenderLine { TenderLineId = tenderLineId.ToString(CultureInfo.InvariantCulture), Amount = payment.Amount, Currency = payment.Currency, CardTypeId = string.Empty, Status = TenderLineStatus.Historical, IsVoidable = false, TenderDate = Utilities.ParseDateString( payment.DateString, currentChannelDate.Date, DateTimeStyles.None) // On channel timezone }; salesOrder.TenderLines.Add(tenderLine); } if (orderInfo.Affiliations != null && orderInfo.Affiliations.Any()) { salesOrder.AffiliationLoyaltyTierLines.Clear(); salesOrder.AffiliationLoyaltyTierLines.AddRange(orderInfo.Affiliations.Select(line => new SalesAffiliationLoyaltyTier { AffiliationId = line.AffiliationRecordId, LoyaltyTierId = line.LoyaltyTierRecordId, AffiliationType = line.AffiliationType })); } // Prepayment/Deposit override info if (orderInfo.PrepaymentAmountOverridden) { salesOrder.OverriddenDepositAmount = prepaymentAmountPaid; } salesOrder.PrepaymentAmountPaid = prepaymentAmountPaid; // Portion of the prepayment that has been applied to invoices // (total amount invoiced less payments, difference is the deposit applied) salesOrder.PrepaymentAmountInvoiced = orderInfo.PreviouslyInvoicedAmount - nonPrepayments; // if the prepayment invoiced is greater than the total paid as deposit, there is no credit left salesOrder.AvailableDepositAmount = Math.Max(decimal.Zero, salesOrder.PrepaymentAmountPaid - salesOrder.PrepaymentAmountInvoiced); salesOrder.HasLoyaltyPayment = orderInfo.HasLoyaltyPayment; salesOrder.CurrencyCode = orderInfo.CurrencyCode; return(salesOrder); }
/// <summary> /// Calculates the required reason codes on tender line. /// </summary> /// <param name="requestContext">The request context.</param> /// <param name="transaction">Current transaction.</param> /// <param name="tenderLine">The tender line.</param> /// <param name="sourceType">Type of the source.</param> public static void CalculateRequiredReasonCodesOnTenderLine(RequestContext requestContext, SalesTransaction transaction, TenderLine tenderLine, ReasonCodeSourceType sourceType) { ThrowIf.Null(requestContext, "requestContext"); ThrowIf.Null(tenderLine, "tenderLine"); var serviceRequest = new CalculateRequiredReasonCodesServiceRequest(transaction, sourceType, new[] { tenderLine }); CalculateRequiredReasonCodesHelper(requestContext, serviceRequest); }
public Task ValidateTenderLineForAdd(string id, TenderLine tenderLine) { return(Task.Run(() => OrderManager.Create(CommerceRuntimeManager.Runtime).ValidateTenderLineForAdd(id, tenderLine))); }