public Uri Create()
        {
            var cart            = _klarnaCheckoutUtils.GetCart();
            var merchant        = _klarnaCheckoutUtils.GetMerchant();
            var supportedLocale = _klarnaCheckoutUtils.GetSupportedLocale();
            var gui             = _klarnaCheckoutUtils.GetGui();
            var options         = _klarnaCheckoutUtils.GetOptions();
            var shippingAddress = _klarnaCheckoutUtils.GetShippingAddress();

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

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

            order.Create(dictData);

            var location = order.Location;

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

            _klarnaRepository.Insert(kcoOrderRequest);

            return(location);
        }
        private void SyncKlarnaAndNopOrder(KlarnaCheckoutEntity klarnaRequest, Customer customer, out Order nopOrder, out KlarnaCheckoutOrder klarnaCheckoutOrder)
        {
            nopOrder = _orderService.GetOrderByGuid(klarnaRequest.OrderGuid);
            var resourceUri = new Uri(klarnaRequest.KlarnaResourceUri);
            var apiOrder    = _klarnaCheckoutPaymentService.Fetch(resourceUri);

            klarnaCheckoutOrder = KlarnaCheckoutOrder.FromApiOrder(apiOrder);

            // 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 && klarnaCheckoutOrder.Status == KlarnaCheckoutOrder.StatusCheckoutComplete)
            {
                if (klarnaCheckoutOrder.Status == KlarnaCheckoutOrder.StatusCheckoutComplete)
                {
                    klarnaRequest.Status = KlarnaCheckoutStatus.Complete;
                    _repository.Update(klarnaRequest);
                }

                _klarnaCheckoutPaymentService.SyncBillingAndShippingAddress(customer, klarnaCheckoutOrder);

                nopOrder = CreateOrderAndSyncWithKlarna(klarnaRequest, customer, klarnaCheckoutOrder, resourceUri);
            }
        }
        public void Acknowledge(global::Nop.Core.Domain.Orders.Order order)
        {
            try
            {
                var entity      = _klarnaRepository.Table.First(x => x.OrderGuid == order.OrderGuid);
                var resourceUri = new Uri(entity.KlarnaResourceUri);
                var apiOrder    = Fetch(resourceUri);
                var klarnaOrder = KlarnaCheckoutOrder.FromApiOrder(apiOrder);

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

                    var dictData = updateData.ToDictionary();

                    apiOrder.Update(dictData);

                    order.AuthorizationTransactionId = klarnaOrder.Reservation;
                    _orderService.UpdateOrder(order);
                }
            }
            catch (Exception ex)
            {
                throw new KlarnaCheckoutException("Error acknowledging Klarna order. Order Id: " + order.Id, ex);
            }
        }
        public ActionResult ConfirmationSnippet(int orderId)
        {
            var order = _orderService.GetOrderById(orderId);

            if (order == null || order.PaymentMethodSystemName != KlarnaCheckoutProcessor.PaymentMethodSystemName)
            {
                return(Content(string.Empty));
            }

            var klarnaRequest = _repository.Table.FirstOrDefault(x => x.OrderGuid == order.OrderGuid);

            if (klarnaRequest == null)
            {
                _logger.Warning(string.Format(CultureInfo.CurrentCulture,
                                              "KlarnaCheckout: Didn't find entity for order payed with Klarna. Order Id: {0}", orderId));
                return(Content(string.Empty));
            }

            try
            {
                var apiOrder    = _klarnaCheckoutPaymentService.Fetch(new Uri(klarnaRequest.KlarnaResourceUri));
                var klarnaOrder = KlarnaCheckoutOrder.FromApiOrder(apiOrder);

                if (klarnaOrder.Status != KlarnaCheckoutOrder.StatusCheckoutComplete && klarnaOrder.Status != KlarnaCheckoutOrder.StatusCreated)
                {
                    _logger.Warning(string.Format(CultureInfo.CurrentCulture,
                                                  "KlarnaCheckout: Cannot show confirmation snippet for Klarna order that is not marked as complete or created. Order Id: {0}; Status: {1}; Resource URI: {2}",
                                                  orderId, klarnaOrder.Status, klarnaRequest.KlarnaResourceUri));
                    return(Content(string.Empty));
                }

                return(Content(klarnaOrder.Gui.Snippet));
            }
            catch (KlarnaCheckoutException kce)
            {
                _logger.Error(string.Format(CultureInfo.CurrentCulture,
                                            "KlarnaCheckout: Error when fetching and getting Klarna confirmation snippet. Order Id: {0}; Resource URI: {1}",
                                            orderId, klarnaRequest.KlarnaResourceUri),
                              exception: kce, customer: order.Customer);
            }

            return(Content(string.Empty));
        }
        public bool Update(Uri resourceUri)
        {
            try
            {
                var klarnaOrderId   = _klarnaCheckoutUtils.GetOrderIdFromUri(resourceUri);
                var cart            = _klarnaCheckoutUtils.GetCart();
                var options         = _klarnaCheckoutUtils.GetOptions();
                var connector       = Connector.Create(_klarnaSettings.SharedSecret, BaseUri);
                var supportedLocale = _klarnaCheckoutUtils.GetSupportedLocale();

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

                var order    = new Order(connector, klarnaOrderId);
                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 void SyncCartWithKlarnaOrder(Customer customer, KlarnaCheckoutOrder klarnaCheckoutOrder)
        {
            var currentStoreId = _storeContext.CurrentStore.Id;

            var orderCurrency = _currencyService.GetCurrencyByCode(klarnaCheckoutOrder.PurchaseCurrency);

            if (orderCurrency != null)
            {
                _workContext.WorkingCurrency = orderCurrency;
            }

            ClearItemsInCart(customer, currentStoreId);
            ClearDiscountsAndShippingSelection(customer, currentStoreId);

            var physicalItems       = klarnaCheckoutOrder.Cart.Items.Where(x => x.Type == CartItem.TypePhysical);
            var appliedCoupons      = klarnaCheckoutOrder.Cart.Items.Where(x => x.Type == CartItem.TypeDiscount && x.HasOrderLevelDiscountCouponCode());
            var appliedGiftCards    = klarnaCheckoutOrder.Cart.Items.Where(x => x.Type == CartItem.TypeDiscount && x.IsDiscountGiftCardCartItem());
            var appliedRewardPoints = klarnaCheckoutOrder.Cart.Items.Where(x => x.Type == CartItem.TypeDiscount && x.IsRewardPointsCartItem());
            var checkoutAttributes  = klarnaCheckoutOrder.Cart.Items.Where(x => x.Type == CartItem.TypeDiscount && x.IsCheckoutAttribtue());
            var shippingItems       = klarnaCheckoutOrder.Cart.Items.Where(x => x.Type == CartItem.TypeShippingFee);

            foreach (var physicalItem in physicalItems)
            {
                AddPhysicalItemToCart(customer, physicalItem, currentStoreId);
                var couponCodes = physicalItem.GetCouponCodesFromPhysicalCartItem();
                foreach (var couponCode in couponCodes)
                {
                    UseCouponCode(customer, couponCode);
                }
            }

            foreach (var coupon in appliedCoupons)
            {
                var couponCode = coupon.GetCouponCodeFromDiscountCouponCartItem();
                UseCouponCode(customer, couponCode);
            }

            foreach (var giftCardCartItem in appliedGiftCards)
            {
                var giftCardId = giftCardCartItem.GetGiftCardIdFromDiscountGiftCardCartItem();
                var giftCard   = _giftCardService.GetGiftCardById(giftCardId);
                customer.ApplyGiftCardCouponCode(giftCard.GiftCardCouponCode);
            }

            if (appliedRewardPoints.Any())
            {
                _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.UseRewardPointsDuringCheckout, true, currentStoreId);
            }

            var checkoutAttributesXml = GetSelectedCheckoutAttributesThatHasNotBeenSentToKlarna(customer, currentStoreId);

            foreach (var item in checkoutAttributes)
            {
                var ca = _checkoutAttributeService.GetCheckoutAttributeById(item.GetCheckoutAttributeId());
                checkoutAttributesXml = _checkoutAttributeParser.AddCheckoutAttribute(checkoutAttributesXml, ca, item.GetCheckoutAttributeValue());
            }
            _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.CheckoutAttributes, checkoutAttributesXml, currentStoreId);

            foreach (var shippingItem in shippingItems)
            {
                AddShippingItemToCart(customer, shippingItem, currentStoreId);
                var couponCodes = shippingItem.GetCouponCodesFromShippingItem();
                foreach (var couponCode in couponCodes)
                {
                    UseCouponCode(customer, couponCode);
                }
            }
        }
        private Order CreateOrderAndSyncWithKlarna(KlarnaCheckoutEntity klarnaRequest, Customer customer, KlarnaCheckoutOrder klarnaCheckoutOrder, Uri resourceUri)
        {
            SyncCartWithKlarnaOrder(customer, klarnaCheckoutOrder);

            var processPaymentRequest = new ProcessPaymentRequest
            {
                OrderGuid  = klarnaRequest.OrderGuid,
                CustomerId = customer.Id,
                StoreId    = _storeContext.CurrentStore.Id,
                PaymentMethodSystemName = KlarnaCheckoutProcessor.PaymentMethodSystemName
            };

            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}",
                                            klarnaCheckoutOrder.Id, klarnaRequest.KlarnaResourceUri, errors), customer: customer);

                _klarnaCheckoutPaymentService.CancelPayment(klarnaCheckoutOrder.Reservation, customer);

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

            // Order was successfully created.
            var orderId       = placeOrderResult.PlacedOrder.Id;
            var nopOrder      = _orderService.GetOrderById(orderId);
            var klarnaPayment =
                _paymentService.LoadPaymentMethodBySystemName(KlarnaCheckoutProcessor.PaymentMethodSystemName);

            klarnaPayment.PostProcessPayment(new PostProcessPaymentRequest
            {
                Order = nopOrder
            });

            var orderTotalInCurrentCurrency = _currencyService.ConvertFromPrimaryStoreCurrency(nopOrder.OrderTotal, _workContext.WorkingCurrency);

            // 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 - (klarnaCheckoutOrder.Cart.TotalPriceIncludingTax.Value / 100m));

            if (diff >= allowedPriceDiff)
            {
                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, klarnaCheckoutOrder.Cart.TotalPriceIncludingTax, allowedPriceDiff, diff, resourceUri),
                    DisplayToCustomer = false,
                    CreatedOnUtc      = DateTime.UtcNow
                });
            }

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

            if (_orderProcessingService.CanMarkOrderAsAuthorized(nopOrder))
            {
                _orderProcessingService.MarkAsAuthorized(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 || nopOrder.ShippingStatus == ShippingStatus.ShippingNotRequired)
                {
                    nopOrder.OrderNotes.Add(new OrderNote
                    {
                        Note              = "KlarnaCheckout: Order complete after payment, will try to capture payment.",
                        CreatedOnUtc      = DateTime.UtcNow,
                        DisplayToCustomer = false
                    });
                    _orderService.UpdateOrder(nopOrder);

                    if (_orderProcessingService.CanCapture(nopOrder))
                    {
                        _orderProcessingService.Capture(nopOrder);
                    }
                }
            }

            return(nopOrder);
        }
        public ActionResult PaymentInfo()
        {
            Uri resourceUri;
            var customer = _workContext.CurrentCustomer;
            var storeId  = _storeContext.CurrentStore.Id;
            var payment  = _repository.Table
                           .OrderByDescending(x => x.CreatedOnUtc)
                           .FirstOrDefault(x => x.CustomerId == customer.Id && x.StoreId == storeId && x.Status == KlarnaCheckoutStatus.Pending);

            if (payment == null)
            {
                try
                {
                    resourceUri = _klarnaCheckoutPaymentService.Create();
                }
                catch (Exception ex)
                {
                    var exceptionJson = JsonConvert.SerializeObject(ex.Data);

                    _logger.Error(string.Format(CultureInfo.CurrentCulture, "KlarnaCheckout: Error creating Klarna order. Data: {0}", exceptionJson),
                                  exception: ex, customer: customer);
                    ViewBag.StatusCode = ex.Data["http_status_code"];

                    return(View("~/Plugins/Motillo.KlarnaCheckout/Views/KlarnaCheckout/PaymentInfoError.cshtml"));
                }
            }
            else
            {
                try
                {
                    resourceUri = new Uri(payment.KlarnaResourceUri);

                    // If update of old Klarna order failed, try creating a new one.
                    // Failure can occur for old orders or when toggling between live/test mode.
                    if (!_klarnaCheckoutPaymentService.Update(resourceUri))
                    {
                        payment.Status = KlarnaCheckoutStatus.Failed;
                        _repository.Update(payment);

                        resourceUri = _klarnaCheckoutPaymentService.Create();
                    }
                }
                catch (Exception ex)
                {
                    var exceptionJson = JsonConvert.SerializeObject(ex.Data);

                    _logger.Error(string.Format(CultureInfo.CurrentCulture, "KlarnaCheckout: Error updating Klarna order. ResourceURI: {0}, Data: {1}",
                                                payment.KlarnaResourceUri, exceptionJson), exception: ex, customer: customer);

                    ViewBag.StatusCode = ex.Data["http_status_code"];

                    return(View("~/Plugins/Motillo.KlarnaCheckout/Views/KlarnaCheckout/PaymentInfoError.cshtml"));
                }
            }

            var apiOrder    = _klarnaCheckoutPaymentService.Fetch(resourceUri);
            var klarnaOrder = KlarnaCheckoutOrder.FromApiOrder(apiOrder);

            var model = new PaymentInfoModel
            {
                SnippetHtml = klarnaOrder.Gui.Snippet
            };

            return(View("~/Plugins/Motillo.KlarnaCheckout/Views/KlarnaCheckout/PaymentInfo.cshtml", model));
        }
        public void SyncBillingAndShippingAddress(global::Nop.Core.Domain.Customers.Customer customer, KlarnaCheckoutOrder klarnaCheckoutOrder)
        {
            try
            {
                var billingAddress  = klarnaCheckoutOrder.BillingAddress;
                var shippingAddress = klarnaCheckoutOrder.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(klarnaCheckoutOrder.BillingAddress);
                var shipping = JsonConvert.SerializeObject(klarnaCheckoutOrder.ShippingAddress);
                throw new KlarnaCheckoutException(string.Format(CultureInfo.CurrentCulture, "Error syncing addresses. Billing: {0}, Shipping: {1}", billing, shipping), ex);
            }
        }