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); } }