public void Acknowledge(Uri resourceUri, global::Nop.Core.Domain.Orders.Order order)
        {
            try
            {
                var connector = Connector.Create(_klarnaSettings.SharedSecret);
                var klarnaOrder = new Klarna.Checkout.Order(connector, resourceUri)
                {
                    ContentType = ContentType
                };

                klarnaOrder.Fetch();
                var fetchedData = klarnaOrder.Marshal();
                var typedData = KlarnaOrder.FromDictionary(fetchedData);

                if (typedData.Status == KlarnaOrder.StatusCheckoutComplete)
                {
                    var updateData = new KlarnaOrder
                    {
                        Status = KlarnaOrder.StatusCreated,
                        MerchantReference = new MerchantReference
                        {
                            OrderId1 = order.Id.ToString(CultureInfo.InvariantCulture),
                            OrderId2 = order.OrderGuid.ToString()
                        }
                    };

                    var dictData = updateData.ToDictionary();

                    klarnaOrder.Update(dictData);
                }
            }
            catch (Exception ex)
            {
                throw new NopException("Error Acknowledging Klarna Order", ex);
            }
        }
        public Uri Create()
        {
            var cart = _klarnaCheckoutUtils.GetCart();
            var merchant = _klarnaCheckoutUtils.GetMerchant();
            var supportedLocale = _klarnaCheckoutUtils.GetSupportedLocale();
            var gui = _klarnaCheckoutUtils.GetGui();
            var options = _klarnaCheckoutUtils.GetOptions();

            var klarnaOrder = new KlarnaOrder
            {
                Cart = cart,
                Merchant = merchant,
                Gui = gui,
                Options = options,
                Locale = supportedLocale.Locale,
                PurchaseCountry = supportedLocale.PurchaseCountry,
                PurchaseCurrency = supportedLocale.PurchaseCurrency
            };

            var dictData = klarnaOrder.ToDictionary();
            var connector = Connector.Create(_klarnaSettings.SharedSecret);
            var order = new Klarna.Checkout.Order(connector)
            {
                BaseUri = new Uri(BaseUri),
                ContentType = ContentType
            };

            order.Create(dictData);

            var location = order.Location;

            var kcoOrderRequest = GetKcoOrderRequest(_workContext.CurrentCustomer, location);
            _klarnaRepository.Insert(kcoOrderRequest);

            return location;
        }
        public void SyncBillingAndShippingAddress(global::Nop.Core.Domain.Customers.Customer customer, KlarnaOrder klarnaOrder)
        {
            try
            {
                var billingAddress = klarnaOrder.BillingAddress;
                var shippingAddress = klarnaOrder.ShippingAddress;

                var nopBillingAddress = customer.Addresses.FirstOrDefault(billingAddress.RepresentsAddress);
                if (nopBillingAddress == null)
                {
                    nopBillingAddress = new global::Nop.Core.Domain.Common.Address { CreatedOnUtc = DateTime.UtcNow };
                    customer.Addresses.Add(nopBillingAddress);
                }

                customer.BillingAddress = nopBillingAddress;
                billingAddress.CopyTo(nopBillingAddress);

                var nopShippingAddress = customer.Addresses.FirstOrDefault(shippingAddress.RepresentsAddress);
                if (nopShippingAddress == null)
                {
                    nopShippingAddress = new global::Nop.Core.Domain.Common.Address { CreatedOnUtc = DateTime.UtcNow };
                    customer.Addresses.Add(nopShippingAddress);
                }

                customer.ShippingAddress = nopShippingAddress;
                shippingAddress.CopyTo(nopShippingAddress);

                _customerService.UpdateCustomer(customer);
            }
            catch (Exception ex)
            {
                var billing = JsonConvert.SerializeObject(klarnaOrder.BillingAddress);
                var shipping = JsonConvert.SerializeObject(klarnaOrder.ShippingAddress);
                throw new KlarnaCheckoutException(string.Format(CultureInfo.CurrentCulture, "Error syncing addresses. Billing: {0}, Shipping: {1}", billing, shipping), ex);
            }
        }
        public bool Update(Uri resourceUri)
        {
            try
            {
                var cart = _klarnaCheckoutUtils.GetCart();
                var options = _klarnaCheckoutUtils.GetOptions();
                var connector = Connector.Create(_klarnaSettings.SharedSecret);
                var supportedLocale = _klarnaCheckoutUtils.GetSupportedLocale();

                var klarnaOrder = new KlarnaOrder
                {
                    Cart = cart,
                    Options = options,
                    Locale = supportedLocale.Locale,
                    PurchaseCountry = supportedLocale.PurchaseCountry,
                    PurchaseCurrency = supportedLocale.PurchaseCurrency
                };

                var order = new Order(connector, resourceUri)
                {
                    ContentType = ContentType
                };
                var dictData = klarnaOrder.ToDictionary();

                order.Update(dictData);

                return true;
            }
            catch (Exception ex)
            {
                var exceptionJson = JsonConvert.SerializeObject(ex.Data);

                _logger.Warning(string.Format(CultureInfo.CurrentCulture, "KlarnaCheckout: Error updating Klarna order. Will try to create a new one. ResourceURI: {0}, Data: {1}",
                    resourceUri, exceptionJson), exception: ex);
            }

            return false;
        }
        private Order CreateOrderAndSyncWithKlarna(KlarnaCheckoutEntity klarnaRequest, Customer customer, KlarnaOrder klarnaOrder, Uri resourceUri)
        {
            var processPaymentRequest = new ProcessPaymentRequest
            {
                OrderGuid = klarnaRequest.OrderGuid,
                CustomerId = customer.Id,
                StoreId = _storeContext.CurrentStore.Id,
                PaymentMethodSystemName = "Motillo.KlarnaCheckout"
            };

            var placeOrderResult = _orderProcessingService.PlaceOrder(processPaymentRequest);

            // If you tamper with the cart after the klarna widget is rendered nop fails to create the order.
            if (!placeOrderResult.Success)
            {
                var errors = string.Join("; ", placeOrderResult.Errors);
                _logger.Error(string.Format(CultureInfo.CurrentCulture, "KlarnaCheckout: Klarna has been processed but order could not be created in Nop! Klarna ID={0}, ResourceURI={1}. Errors: {2}",
                    klarnaOrder.Id, klarnaRequest.KlarnaResourceUri, errors), customer: customer);

                if (!_klarnaCheckoutPaymentService.CancelPayment(klarnaOrder.Reservation, customer))
                {
                    _logger.Error("KlarnaCheckout: Error canceling reservation: " + klarnaOrder.Reservation, customer: customer);
                }

                throw new KlarnaCheckoutException("Error creating order: " + errors);
            }

            // Order was successfully created.
            var orderId = placeOrderResult.PlacedOrder.Id;
            var nopOrder = _orderService.GetOrderById(orderId);

            _klarnaCheckoutPaymentService.Acknowledge(resourceUri, nopOrder);

            nopOrder.AuthorizationTransactionId = klarnaOrder.Reservation;
            _orderService.UpdateOrder(nopOrder);

            var orderTotalInCurrentCurrency = _currencyService.ConvertFromPrimaryStoreCurrency(nopOrder.OrderTotal, _workContext.WorkingCurrency);
            
            // We need to ensure the shopping cart wasn't tampered with before the user confirmed the Klarna order.
            // If so an order may have been created which doesn't represent the paid items.

            // Due to rounding when using prices contains more than 2 decimals (e.g. currency conversion), we allow
            // a slight diff in paid price and nop's reported price.
            // For example nop rounds the prices after _all_ cart item prices have been summed but when sending
            // items to Klarna, each price needs to be rounded separately (Klarna uses 2 decimals).
            
            // Assume a cart with two items.
            // 1.114 + 2.114 = 3.228 which nop rounds to 3.23.
            // 1.11 + 2.11 is sent to Klarna, which totals 3.22.

            var allowedPriceDiff = orderTotalInCurrentCurrency*0.01m;
            var diff = Math.Abs(orderTotalInCurrentCurrency - (klarnaOrder.Cart.TotalPriceIncludingTax.Value/100m));

            if (diff < allowedPriceDiff)
            {
                nopOrder.OrderNotes.Add(new OrderNote
                {
                    Note = "KlarnaCheckout: Order acknowledged. Uri: " + resourceUri,
                    DisplayToCustomer = false,
                    CreatedOnUtc = DateTime.UtcNow
                });
                _orderService.UpdateOrder(nopOrder);

                if (_orderProcessingService.CanMarkOrderAsPaid(nopOrder))
                {
                    _orderProcessingService.MarkOrderAsPaid(nopOrder);

                    // Sometimes shipping isn't required, e.g. if only ordering virtual gift cards.
                    // In those cases, make sure the Klarna order is activated.
                    if (nopOrder.OrderStatus == OrderStatus.Complete)
                    {
                        var activationResult = _klarnaCheckoutPaymentService.Activate(klarnaOrder.Reservation, customer);

                        if (activationResult != null)
                        {
                            nopOrder.OrderNotes.Add(new OrderNote
                            {
                                Note = string.Format(CultureInfo.CurrentCulture, "KlarnaCheckout: Order complete after payment, activating Klarna order. Reservation: {0}, RiskStatus: {1}, InvoiceNumber: {2}",
                                    klarnaOrder.Reservation, activationResult.RiskStatus, activationResult.InvoiceNumber),
                                CreatedOnUtc = DateTime.UtcNow,
                                DisplayToCustomer = false
                            });
                            _orderService.UpdateOrder(nopOrder);

                            klarnaRequest.Status = KlarnaCheckoutStatus.Activated;
                            _repository.Update(klarnaRequest);

                            _logger.Information(string.Format(CultureInfo.CurrentCulture, "KlarnaCheckout: Order complete after payment, activating Klarna order. OrderId: {0}, OrderGuid: {1}",
                                nopOrder.Id, nopOrder.OrderGuid));
                        }
                    }
                }
            }
            else
            {
                var orderTotalInCents = _klarnaCheckoutHelper.ConvertToCents(orderTotalInCurrentCurrency);

                nopOrder.OrderNotes.Add(new OrderNote
                {
                    Note = string.Format(CultureInfo.CurrentCulture, "KlarnaCheckout: Order total differs from Klarna order. OrderTotal: {0}, OrderTotalInCents: {1}, KlarnaTotal: {2}, AllowedDiff: {3}, Diff: {4}, Uri: {5}",
                        orderTotalInCurrentCurrency, orderTotalInCents, klarnaOrder.Cart.TotalPriceIncludingTax, allowedPriceDiff, diff, resourceUri),
                    DisplayToCustomer = false,
                    CreatedOnUtc = DateTime.UtcNow
                });
                nopOrder.OrderNotes.Add(new OrderNote
                {
                    Note = "KlarnaCheckout: Order total differs from paid amount to Klarna. Order has not been marked as paid. Please contact an administrator.",
                    DisplayToCustomer = true,
                    CreatedOnUtc = DateTime.UtcNow
                });
                _orderService.UpdateOrder(nopOrder);

                _logger.Warning(string.Format(CultureInfo.CurrentCulture, "KlarnaCheckout: Order total differs. Please verify the sum and mark the order as paid in the administration. See order notes for more details. OrderId: {0}, OrderGuid: {1}",
                    nopOrder.Id, nopOrder.OrderGuid), customer: customer);
            }

            return nopOrder;
        }
        private void SyncKlarnaAndNopOrder(KlarnaCheckoutEntity klarnaRequest, Customer customer, out Order nopOrder, out KlarnaOrder klarnaOrder)
        {
            nopOrder = _orderService.GetOrderByGuid(klarnaRequest.OrderGuid);
            var resourceUri = new Uri(klarnaRequest.KlarnaResourceUri);
            var order = _klarnaCheckoutPaymentService.Fetch(resourceUri);
            var data = order.Marshal();
            var jsonData = JsonConvert.SerializeObject(data);
            
            klarnaOrder = JsonConvert.DeserializeObject<Models.KlarnaOrder>(jsonData);

            // Create the order if it doesn't exist in nop. According to the Klarna Checkout
            // developer guidelines, one should only create the order if the status is
            // 'checkout_complete'.
            // https://developers.klarna.com/en/klarna-checkout/acknowledge-an-order
            if (nopOrder == null && klarnaOrder.Status == KlarnaOrder.StatusCheckoutComplete)
            {
                if (klarnaOrder.Status == KlarnaOrder.StatusCheckoutComplete)
                {
                    klarnaRequest.Status = KlarnaCheckoutStatus.Complete;
                    _repository.Update(klarnaRequest);
                }

                _klarnaCheckoutPaymentService.SyncBillingAndShippingAddress(customer, klarnaOrder);

                nopOrder = CreateOrderAndSyncWithKlarna(klarnaRequest, customer, klarnaOrder, resourceUri);
            }
        }