public AmazonPayApiData ParseNotification(HttpRequestBase request)
        {
            string json = null;

            using (var reader = new StreamReader(request.InputStream))
            {
                json = reader.ReadToEnd();
            }

            var parser = new OffAmazonPaymentsNotifications.NotificationsParser();
            var message = parser.ParseRawMessage(request.Headers, json);

            var data = new AmazonPayApiData()
            {
                MessageType = message.NotificationType.ToString(),
                MessageId = ((OffAmazonPaymentsNotifications.IpnNotificationMetadata)message.NotificationMetadata).NotificationReferenceId
            };

            if (message.NotificationType == OffAmazonPaymentsNotifications.NotificationType.AuthorizationNotification)
            {
                var details = ((OffAmazonPaymentsNotifications.AuthorizationNotification)message).AuthorizationDetails;

                data.AuthorizationId = details.AmazonAuthorizationId;
                data.CaptureId = details.IdList.SafeGet(0);
                data.ReferenceId = details.AuthorizationReferenceId;
                data.CaptureNow = details.CaptureNow;
                data.Creation = details.CreationTimestamp;

                if (details.AuthorizationFee != null)
                    data.Fee = new AmazonPayApiPrice(details.AuthorizationFee.Amount, details.AuthorizationFee.CurrencyCode);

                if (details.AuthorizationAmount != null)
                    data.AuthorizedAmount = new AmazonPayApiPrice(details.AuthorizationAmount.Amount, details.AuthorizationAmount.CurrencyCode);

                if (details.CapturedAmount != null)
                    data.CapturedAmount = new AmazonPayApiPrice(details.CapturedAmount.Amount, details.CapturedAmount.CurrencyCode);

                if (details.ExpirationTimestampSpecified)
                    data.Expiration = details.ExpirationTimestamp;

                if (details.AuthorizationStatus != null)
                {
                    data.ReasonCode = details.AuthorizationStatus.ReasonCode;
                    data.ReasonDescription = details.AuthorizationStatus.ReasonDescription;
                    data.State = details.AuthorizationStatus.State;
                    data.StateLastUpdate = details.AuthorizationStatus.LastUpdateTimestamp;
                }
            }
            else if (message.NotificationType == OffAmazonPaymentsNotifications.NotificationType.CaptureNotification)
            {
                var details = ((OffAmazonPaymentsNotifications.CaptureNotification)message).CaptureDetails;

                data.CaptureId = details.AmazonCaptureId;
                data.ReferenceId = details.CaptureReferenceId;
                data.Creation = details.CreationTimestamp;

                if (details.CaptureFee != null)
                    data.Fee = new AmazonPayApiPrice(details.CaptureFee.Amount, details.CaptureFee.CurrencyCode);

                if (details.CaptureAmount != null)
                    data.CapturedAmount = new AmazonPayApiPrice(details.CaptureAmount.Amount, details.CaptureAmount.CurrencyCode);

                if (details.RefundedAmount != null)
                    data.RefundedAmount = new AmazonPayApiPrice(details.RefundedAmount.Amount, details.RefundedAmount.CurrencyCode);

                if (details.CaptureStatus != null)
                {
                    data.ReasonCode = details.CaptureStatus.ReasonCode;
                    data.ReasonDescription = details.CaptureStatus.ReasonDescription;
                    data.State = details.CaptureStatus.State;
                    data.StateLastUpdate = details.CaptureStatus.LastUpdateTimestamp;
                }
            }
            else if (message.NotificationType == OffAmazonPaymentsNotifications.NotificationType.RefundNotification)
            {
                var details = ((OffAmazonPaymentsNotifications.RefundNotification)message).RefundDetails;

                data.RefundId = details.AmazonRefundId;
                data.ReferenceId = details.RefundReferenceId;
                data.Creation = details.CreationTimestamp;

                if (details.FeeRefunded != null)
                    data.Fee = new AmazonPayApiPrice(details.FeeRefunded.Amount, details.FeeRefunded.CurrencyCode);

                if (details.RefundAmount != null)
                    data.RefundedAmount = new AmazonPayApiPrice(details.RefundAmount.Amount, details.RefundAmount.CurrencyCode);

                if (details.RefundStatus != null)
                {
                    data.ReasonCode = details.RefundStatus.ReasonCode;
                    data.ReasonDescription = details.RefundStatus.ReasonDescription;
                    data.State = details.RefundStatus.State;
                    data.StateLastUpdate = details.RefundStatus.LastUpdateTimestamp;
                }
            }

            return data;
        }
        public string ToInfoString(AmazonPayApiData data)
        {
            var sb = new StringBuilder();

            try
            {
                string[] strings = _localizationService.GetResource("Plugins.Payments.AmazonPay.MessageStrings").SplitSafe(";");

                string state = data.State.Grow(data.ReasonCode, " ");
                if (data.ReasonDescription.HasValue())
                    state = "{0} ({1})".FormatWith(state, data.ReasonDescription);

                sb.AppendLine("{0}: {1}".FormatWith(strings.SafeGet((int)AmazonPayMessage.MessageTyp), data.MessageType.NaIfEmpty()));

                sb.AppendLine("{0}: {1}".FormatWith(strings.SafeGet((int)AmazonPayMessage.State), state));

                var stateDate = _dateTimeHelper.ConvertToUserTime(data.StateLastUpdate, DateTimeKind.Utc);
                sb.AppendLine("{0}: {1}".FormatWith(strings.SafeGet((int)AmazonPayMessage.StateUpdate), stateDate.ToString()));

                sb.AppendLine("{0}: {1}".FormatWith(strings.SafeGet((int)AmazonPayMessage.MessageId), data.MessageId.NaIfEmpty()));

                if (data.AuthorizationId.HasValue())
                    sb.AppendLine("{0}: {1}".FormatWith(strings.SafeGet((int)AmazonPayMessage.AuthorizationID), data.AuthorizationId));

                if (data.CaptureId.HasValue())
                    sb.AppendLine("{0}: {1}".FormatWith(strings.SafeGet((int)AmazonPayMessage.CaptureID), data.CaptureId));

                if (data.RefundId.HasValue())
                    sb.AppendLine("{0}: {1}".FormatWith(strings.SafeGet((int)AmazonPayMessage.RefundID), data.RefundId));

                sb.AppendLine("{0}: {1}".FormatWith(strings.SafeGet((int)AmazonPayMessage.ReferenceID), data.ReferenceId.NaIfEmpty()));

                if (data.Fee != null && data.Fee.Amount != 0.0)
                {
                    bool isSigned = (data.MessageType.IsCaseInsensitiveEqual("RefundNotification") || data.MessageType.IsCaseInsensitiveEqual("GetRefundDetails"));
                    sb.AppendLine("{0}: {1}".FormatWith(strings.SafeGet((int)AmazonPayMessage.Fee), (isSigned ? "-" : "") + data.Fee.ToString()));
                }

                if (data.AuthorizedAmount != null && data.AuthorizedAmount.Amount != 0.0)
                    sb.AppendLine("{0}: {1}".FormatWith(strings.SafeGet((int)AmazonPayMessage.AuthorizedAmount), data.AuthorizedAmount.ToString()));

                if (data.CapturedAmount != null && data.CapturedAmount.Amount != 0.0)
                    sb.AppendLine("{0}: {1}".FormatWith(strings.SafeGet((int)AmazonPayMessage.CapturedAmount), data.CapturedAmount.ToString()));

                if (data.RefundedAmount != null && data.RefundedAmount.Amount != 0.0)
                    sb.AppendLine("{0}: {1}".FormatWith(strings.SafeGet((int)AmazonPayMessage.RefundedAmount), data.RefundedAmount.ToString()));

                if (data.CaptureNow.HasValue)
                    sb.AppendLine("{0}: {1}".FormatWith(strings.SafeGet((int)AmazonPayMessage.CaptureNow), data.CaptureNow.Value.ToString()));

                var creationDate = _dateTimeHelper.ConvertToUserTime(data.Creation, DateTimeKind.Utc);
                sb.AppendLine("{0}: {1}".FormatWith(strings.SafeGet((int)AmazonPayMessage.Creation), creationDate.ToString()));

                if (data.Expiration.HasValue)
                {
                    var expirationDate = _dateTimeHelper.ConvertToUserTime(data.Expiration.Value, DateTimeKind.Utc);
                    sb.AppendLine("{0}: {1}".FormatWith(strings.SafeGet((int)AmazonPayMessage.Expiration), expirationDate.ToString()));
                }
            }
            catch (Exception exc)
            {
                exc.Dump();
            }
            return sb.ToString();
        }
        public AuthorizationDetails GetAuthorizationDetails(AmazonPayClient client, string authorizationId, out AmazonPayApiData data)
        {
            data = new AmazonPayApiData();

            AuthorizationDetails details = null;
            var request = new GetAuthorizationDetailsRequest();
            request.SellerId = client.Settings.SellerId;
            request.AmazonAuthorizationId = authorizationId;

            var response = client.Service.GetAuthorizationDetails(request);

            if (response.IsSetGetAuthorizationDetailsResult())
            {
                var result = response.GetAuthorizationDetailsResult;

                if (result != null && result.IsSetAuthorizationDetails())
                    details = result.AuthorizationDetails;
            }

            try
            {
                data.MessageType = "GetAuthorizationDetails";

                if (response.IsSetResponseMetadata() && response.ResponseMetadata.IsSetRequestId())
                    data.MessageId = response.ResponseMetadata.RequestId;

                if (details != null)
                {
                    if (details.IsSetAmazonAuthorizationId())
                        data.AuthorizationId = details.AmazonAuthorizationId;

                    if (details.IsSetAuthorizationReferenceId())
                        data.ReferenceId = details.AuthorizationReferenceId;

                    if (details.IsSetIdList() && details.IdList.IsSetmember())
                        data.CaptureId = (details.IdList.member != null && details.IdList.member.Count > 0 ? details.IdList.member[0] : null);

                    if (details.IsSetAuthorizationFee())
                        data.Fee = new AmazonPayApiPrice(details.AuthorizationFee.Amount, details.AuthorizationFee.CurrencyCode);

                    if (details.IsSetAuthorizationAmount())
                        data.AuthorizedAmount = new AmazonPayApiPrice(details.AuthorizationAmount.Amount, details.AuthorizationAmount.CurrencyCode);

                    if (details.IsSetCapturedAmount())
                        data.CapturedAmount = new AmazonPayApiPrice(details.CapturedAmount.Amount, details.CapturedAmount.CurrencyCode);

                    if (details.IsSetCaptureNow())
                        data.CaptureNow = details.CaptureNow;

                    if (details.IsSetCreationTimestamp())
                        data.Creation = details.CreationTimestamp;

                    if (details.IsSetExpirationTimestamp())
                        data.Expiration = details.ExpirationTimestamp;

                    if (details.IsSetAuthorizationStatus())
                    {
                        data.ReasonCode = details.AuthorizationStatus.ReasonCode;
                        data.ReasonDescription = details.AuthorizationStatus.ReasonDescription;
                        data.State = details.AuthorizationStatus.State.ToString();
                        data.StateLastUpdate = details.AuthorizationStatus.LastUpdateTimestamp;
                    }
                }
            }
            catch (Exception exc)
            {
                exc.Dump();
            }
            return details;
        }
        public RefundDetails GetRefundDetails(AmazonPayClient client, string refundId, out AmazonPayApiData data)
        {
            data = new AmazonPayApiData();

            RefundDetails details = null;
            var request = new GetRefundDetailsRequest();
            request.SellerId = client.Settings.SellerId;
            request.AmazonRefundId = refundId;

            var response = client.Service.GetRefundDetails(request);

            if (response != null && response.IsSetGetRefundDetailsResult())
            {
                var result = response.GetRefundDetailsResult;
                if (result != null && result.IsSetRefundDetails())
                    details = result.RefundDetails;
            }

            try
            {
                data.MessageType = "GetRefundDetails";

                if (response.IsSetResponseMetadata() && response.ResponseMetadata.IsSetRequestId())
                    data.MessageId = response.ResponseMetadata.RequestId;

                if (details != null)
                {
                    if (details.IsSetAmazonRefundId())
                        data.RefundId = details.AmazonRefundId;

                    if (details.IsSetRefundReferenceId())
                        data.ReferenceId = details.RefundReferenceId;

                    if (details.IsSetFeeRefunded())
                        data.Fee = new AmazonPayApiPrice(details.FeeRefunded.Amount, details.FeeRefunded.CurrencyCode);

                    if (details.IsSetRefundAmount())
                        data.RefundedAmount = new AmazonPayApiPrice(details.RefundAmount.Amount, details.RefundAmount.CurrencyCode);

                    if (details.IsSetCreationTimestamp())
                        data.Creation = details.CreationTimestamp;

                    if (details.IsSetRefundStatus())
                    {
                        data.ReasonCode = details.RefundStatus.ReasonCode;
                        data.ReasonDescription = details.RefundStatus.ReasonDescription;
                        data.State = details.RefundStatus.State.ToString();
                        data.StateLastUpdate = details.RefundStatus.LastUpdateTimestamp;
                    }
                }
            }
            catch (Exception exc)
            {
                exc.Dump();
            }
            return details;
        }
        public void DataPollingTaskProcess()
        {
            try
            {
                // ignore cancelled and completed (paid and shipped) orders. ignore old orders too.

                var data = new AmazonPayApiData();
                int pollingMaxOrderCreationDays = _services.Settings.GetSettingByKey<int>("AmazonPaySettings.PollingMaxOrderCreationDays", 31);
                var isTooOld = DateTime.UtcNow.AddDays(-(pollingMaxOrderCreationDays));

                var query =
                    from x in _orderRepository.Table
                    where x.PaymentMethodSystemName == AmazonPayCore.SystemName && x.CreatedOnUtc > isTooOld &&
                        !x.Deleted && x.OrderStatusId < (int)OrderStatus.Complete && x.PaymentStatusId != (int)PaymentStatus.Voided
                    orderby x.Id descending
                    select x;

                var orders = query.ToList();

                //"- start polling {0} orders".FormatWith(orders.Count).Dump();

                foreach (var order in orders)
                {
                    try
                    {
                        var client = new AmazonPayClient(_services.Settings.LoadSetting<AmazonPaySettings>(order.StoreId));

                        if (client.Settings.DataFetching == AmazonPayDataFetchingType.Polling)
                        {
                            if (order.AuthorizationTransactionId.HasValue())
                            {
                                var details = _api.GetAuthorizationDetails(client, order.AuthorizationTransactionId, out data);

                                ProcessAuthorizationResult(client, order, data, details);
                            }

                            if (order.CaptureTransactionId.HasValue())
                            {
                                if (_orderProcessingService.CanMarkOrderAsPaid(order) || _orderProcessingService.CanVoidOffline(order) ||
                                    _orderProcessingService.CanRefundOffline(order) || _orderProcessingService.CanPartiallyRefundOffline(order, 0.01M))
                                {
                                    var details = _api.GetCaptureDetails(client, order.CaptureTransactionId, out data);

                                    ProcessCaptureResult(client, order, data);

                                    if (_orderProcessingService.CanRefundOffline(order) || _orderProcessingService.CanPartiallyRefundOffline(order, 0.01M))
                                    {
                                        // note status polling: we cannot use GetRefundDetails to reflect refund(s) made at Amazon seller central cause we
                                        // do not have any refund-id and there is no api endpoint that serves them. so we only can process CaptureDetails.RefundedAmount.

                                        ProcessRefundResult(client, order, data);
                                    }
                                }
                            }
                        }
                    }
                    catch (OffAmazonPaymentsServiceException exc)
                    {
                        LogAmazonError(exc);
                    }
                    catch (Exception exc)
                    {
                        LogError(exc);
                    }
                }
            }
            catch (OffAmazonPaymentsServiceException exc)
            {
                LogAmazonError(exc);
            }
            catch (Exception exc)
            {
                LogError(exc);
            }
        }
        private void ProcessRefundResult(AmazonPayClient client, Order order, AmazonPayApiData data)
        {
            if (data.State.IsCaseInsensitiveEqual("Pending"))
                return;

            if (data.RefundedAmount != null && data.RefundedAmount.Amount != 0.0)	// totally refunded amount
            {
                // we could only process it once cause otherwise order.RefundedAmount would getting wrong.
                if (order.RefundedAmount == decimal.Zero)
                {
                    decimal refundAmount = Convert.ToDecimal(data.RefundedAmount.Amount);
                    decimal receivable = order.OrderTotal - refundAmount;

                    if (receivable <= decimal.Zero)
                    {
                        if (_orderProcessingService.CanRefundOffline(order))
                        {
                            _orderProcessingService.RefundOffline(order);

                            if (client.Settings.DataFetching == AmazonPayDataFetchingType.Polling)
                                AddOrderNote(client.Settings, order, AmazonPayOrderNote.AmazonMessageProcessed, _api.ToInfoString(data));
                        }
                    }
                    else
                    {
                        if (_orderProcessingService.CanPartiallyRefundOffline(order, refundAmount))
                        {
                            _orderProcessingService.PartiallyRefundOffline(order, refundAmount);

                            if (client.Settings.DataFetching == AmazonPayDataFetchingType.Polling)
                                AddOrderNote(client.Settings, order, AmazonPayOrderNote.AmazonMessageProcessed, _api.ToInfoString(data));
                        }
                    }
                }
            }

            if (client.Settings.DataFetching == AmazonPayDataFetchingType.Ipn)
                AddOrderNote(client.Settings, order, AmazonPayOrderNote.AmazonMessageProcessed, _api.ToInfoString(data));
        }
        private void ProcessCaptureResult(AmazonPayClient client, Order order, AmazonPayApiData data)
        {
            if (data.State.IsCaseInsensitiveEqual("Pending"))
                return;

            string newResult = data.State.Grow(data.ReasonCode, " ");

            if (data.State.IsCaseInsensitiveEqual("Completed") && _orderProcessingService.CanMarkOrderAsPaid(order))
            {
                _orderProcessingService.MarkOrderAsPaid(order);

                CloseOrderReference(client.Settings, order);
            }
            else if (data.State.IsCaseInsensitiveEqual("Declined") && _orderProcessingService.CanVoidOffline(order))
            {
                if (!GetAuthorizationState(client, order.AuthorizationTransactionId).IsCaseInsensitiveEqual("Open"))
                {
                    _orderProcessingService.VoidOffline(order);
                }
            }

            if (!newResult.IsCaseInsensitiveEqual(order.CaptureTransactionResult))
            {
                order.CaptureTransactionResult = newResult;
                _orderService.UpdateOrder(order);

                AddOrderNote(client.Settings, order, AmazonPayOrderNote.AmazonMessageProcessed, _api.ToInfoString(data));
            }
        }
        private void ProcessAuthorizationResult(AmazonPayClient client, Order order, AmazonPayApiData data, OffAmazonPaymentsService.Model.AuthorizationDetails details)
        {
            string formattedAddress;
            var orderAttribute = DeserializeOrderAttribute(order);

            if (!orderAttribute.IsBillingAddressApplied)
            {
                if (_api.FulfillBillingAddress(client.Settings, order, details, out formattedAddress))
                {
                    AddOrderNote(client.Settings, order, AmazonPayOrderNote.BillingAddressApplied, formattedAddress);

                    orderAttribute.IsBillingAddressApplied = true;
                    SerializeOrderAttribute(orderAttribute, order);
                }
                else if (formattedAddress.HasValue())
                {
                    AddOrderNote(client.Settings, order, AmazonPayOrderNote.BillingAddressCountryNotAllowed, formattedAddress);

                    orderAttribute.IsBillingAddressApplied = true;
                    SerializeOrderAttribute(orderAttribute, order);
                }
            }

            if (data.State.IsCaseInsensitiveEqual("Pending"))
                return;

            string newResult = data.State.Grow(data.ReasonCode, " ");

            if (_orderProcessingService.CanMarkOrderAsAuthorized(order))
            {
                _orderProcessingService.MarkAsAuthorized(order);
            }

            if (data.State.IsCaseInsensitiveEqual("Closed") && data.ReasonCode.IsCaseInsensitiveEqual("OrderReferenceCanceled") && _orderProcessingService.CanVoidOffline(order))
            {
                _orderProcessingService.VoidOffline(order);		// cancelation at amazon seller central
            }

            if (!newResult.IsCaseInsensitiveEqual(order.AuthorizationTransactionResult))
            {
                order.AuthorizationTransactionResult = newResult;

                if (order.CaptureTransactionId.IsNullOrEmpty() && data.CaptureId.HasValue())
                    order.CaptureTransactionId = data.CaptureId;	// captured at amazon seller central

                _orderService.UpdateOrder(order);

                AddOrderNote(client.Settings, order, AmazonPayOrderNote.AmazonMessageProcessed, _api.ToInfoString(data));
            }
        }
        private Order FindOrder(AmazonPayApiData data)
        {
            Order order = null;
            string errorId = null;

            if (data.MessageType.IsCaseInsensitiveEqual("AuthorizationNotification"))
            {
                if ((order = _orderService.GetOrderByPaymentAuthorization(AmazonPayCore.SystemName, data.AuthorizationId)) == null)
                    errorId = "AuthorizationId {0}".FormatWith(data.AuthorizationId);
            }
            else if (data.MessageType.IsCaseInsensitiveEqual("CaptureNotification"))
            {
                if ((order = _orderService.GetOrderByPaymentCapture(AmazonPayCore.SystemName, data.CaptureId)) == null)
                    order = _orderRepository.GetOrderByAmazonId(data.AnyAmazonId);

                if (order == null)
                    errorId = "CaptureId {0}".FormatWith(data.CaptureId);
            }
            else if (data.MessageType.IsCaseInsensitiveEqual("RefundNotification"))
            {
                var attribute = _genericAttributeService.GetAttributes(AmazonPayCore.AmazonPayRefundIdKey, "Order")
                    .Where(x => x.Value == data.RefundId)
                    .FirstOrDefault();

                if (attribute == null || (order = _orderService.GetOrderById(attribute.EntityId)) == null)
                    order = _orderRepository.GetOrderByAmazonId(data.AnyAmazonId);

                if (order == null)
                    errorId = "RefundId {0}".FormatWith(data.RefundId);
            }

            if (errorId.HasValue())
                Logger.InsertLog(LogLevel.Warning, T("Plugins.Payments.AmazonPay.OrderNotFound", errorId), "");

            return order;
        }