using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Text; using System.Threading.Tasks; using gehGassi.Core.Interfaces; using gehGassi.Domain.Common; using gehGassi.Klarna.Checkout; using gehGassi.Klarna.OrderManagement; using Microsoft.Extensions.Options; using Newtonsoft.Json; using Address = gehGassi.Klarna.Checkout.Address; using Order = gehGassi.Klarna.Checkout.Order; using OrderLine = gehGassi.Klarna.Checkout.OrderLine; using ShippingOption = gehGassi.Klarna.Checkout.ShippingOption; namespace gehGassi.External.Services { /// /// Service der alle benötigten Klarna-Funktionen zur Verfügung stellt /// public class KlarnaService : IKlarnaService { private HttpClient _httpClient; private readonly IOrderService _orderService; private readonly ICustomerService _customerService; private readonly IShopSettingsService _shopSettingsService; private readonly IOptions _shopOptions; /// /// Erstellt eine Instanz /// /// Instanz eines IOrderService /// Instanz eines ICustomerService /// Instanz eines IShopSettingsService /// Instanz von ShopOptions public KlarnaService(IOrderService orderService, ICustomerService customerService, IShopSettingsService shopSettingsService, IOptions shopOptions) { _orderService = orderService; _customerService = customerService; _shopSettingsService = shopSettingsService; _shopOptions = shopOptions; } /// /// Gibt einen HTTP-Client für die Kommunikation mit Klarna zurück /// /// HttpClient internal HttpClient GetHttpClient(string username, string password) { if (_httpClient == null) { var baseUri = _shopOptions.Value.KlarnaBaseUrl; if (_shopOptions.Value.KlarnaUsePlayground) baseUri = _shopOptions.Value.KlarnaPlaygroundUrl; _httpClient = new HttpClient() { BaseAddress = new Uri(baseUri) }; var authString = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{username}:{password}")); _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", authString); } return _httpClient; } /// /// Prüft ob die Klarna Credentials korrekt sind und eine Verbindung zur Api aufgenommen werden kann /// /// Klarna Benutzername /// Klarna Passwort /// true wenn erfolgreich, false sonst public async Task TestCredentialsAsync(string username, string password) { var client = GetHttpClient(username, password); var result = await client.PostAsync("checkout/v3/orders", new StringContent("")); return result.StatusCode != HttpStatusCode.Unauthorized; } /// /// Erstellen einer Klarna-Order /// /// Id der simple print Bestellung /// Klarna Benutzername /// Klarna Passwort /// Redirect-Url für AGB /// Url für Checkout /// Bestätigungs-URL /// URL für Push-Notifications /// Klarna Order oder null, wenn ein Fehler aufgetreten ist public async Task CreateOrderAsync(long orderId, string username, string password, string termsUrl, string checkoutUrl, string confirmationUrl, string pushUrl) { var client = GetHttpClient(username, password); var order = await _orderService.GetAsync(orderId); if (order == null) return null; var orderItems = await _orderService.GetItemsAsync(orderId); if (!orderItems.Any()) return null; var customer = await _customerService.GetAsync(order.CustomerId.Value); if (customer == null) return null; var shopSettings = await _shopSettingsService.GetAsync(); //TODO: Locale noch behandeln var klarnaOrder = new Order { PurchaseCountry = _shopOptions.Value.SourceCountry.ToUpperInvariant(), PurchaseCurrency = "EUR", Locale = "de-AT", OrderAmount = (int)((order.TotalGross - order.ShipmentGross) * 100), OrderTaxAmount = (int)(((order.TotalGross - order.ShipmentGross) - (order.Total - order.Shipment)) * 100), OrderLines = new List() }; var shippingTaxRate = (int)(((Math.Round(order.ShipmentGross / (order.Shipment > 0 ? order.Shipment : 1), 2, MidpointRounding.AwayFromZero) - 1) * 100) * 100); if (shippingTaxRate < 0) shippingTaxRate = 0; var shippingOption = new ShippingOption() { Id = "postal", Name = "POST", Price = (int)((order.ShipmentGross) * 100), Preselected = true, TaxAmount = (int)(((order.ShipmentGross - order.Shipment)) * 100), TaxRate = shippingTaxRate }; klarnaOrder.ShippingOptions = new List(); klarnaOrder.ShippingOptions.Add(shippingOption); klarnaOrder.SelectedShippingOption = shippingOption; klarnaOrder.BillingAddress = new Address() { GivenName = order.BillingAddress.FirstName, FamilyName = order.BillingAddress.LastName, OrganizationName = order.BillingAddress.Company, StreetAddress = order.BillingAddress.AddressLine1, StreetAddress2 = order.BillingAddress.AddressLine2, PostalCode = order.BillingAddress.Zip, City = order.BillingAddress.City, Region = order.BillingAddress.State, Country = order.BillingAddress.CountryCode }; klarnaOrder.ShippingAddress = new Address() { GivenName = order.DeliveryAddress.FirstName, FamilyName = order.DeliveryAddress.LastName, OrganizationName = order.DeliveryAddress.Company, StreetAddress = order.DeliveryAddress.AddressLine1, StreetAddress2 = order.DeliveryAddress.AddressLine2, PostalCode = order.DeliveryAddress.Zip, City = order.DeliveryAddress.City, Region = order.DeliveryAddress.State, Country = order.DeliveryAddress.CountryCode }; foreach (var orderItem in orderItems) { var taxRate = (int)(((Math.Round(orderItem.PriceGross / (orderItem.Price > 0 ? orderItem.Price : 1), 2, MidpointRounding.AwayFromZero) - 1) * 100) * 100); if (taxRate < 0) taxRate = 0; var klarnaOrderLine = new OrderLine { Type = "physical", Reference = orderItem.Sku, Name = orderItem.Name, Quantity = orderItem.Quantity, UnitPrice = (int)(orderItem.PriceGross * 100), TaxRate = taxRate, TotalAmount = (int)(orderItem.TotalGross * 100), TotalTaxAmount = (int)((orderItem.TotalGross - orderItem.Total) * 100) }; klarnaOrderLine.Type = orderItem.ProductType switch { ProductType.Physical => "physical", ProductType.Download => "digital", ProductType.Listing => "digital", ProductType.Advertisement => "digital", ProductType.Pin => "digital", ProductType.Banner => "digital", _ => throw new ArgumentOutOfRangeException() }; klarnaOrder.OrderLines.Add(klarnaOrderLine); } klarnaOrder.MerchantUrls = new MerchantUrls() { Terms = termsUrl, Checkout = checkoutUrl, Confirmation = confirmationUrl, Push = pushUrl }; var jsonOrder = JsonConvert.SerializeObject(klarnaOrder); var result = await client.PostAsync("checkout/v3/orders", new StringContent(jsonOrder, Encoding.UTF8, "application/json")); if (result.IsSuccessStatusCode) { var responseJson = await result.Content.ReadAsStringAsync(); var response = JsonConvert.DeserializeObject(responseJson); order.KlarnaOrderId = response.OrderId; order.PaymentStatus = PaymentStatus.Pending; order.PaymentInfo = $"{DateTime.UtcNow}: Klarna order created. OrderId: {response.OrderId}" + System.Environment.NewLine + order.PaymentInfo; await _orderService.CommitAsync("klarna"); return response; } else { var response = await result.Content.ReadAsStringAsync(); } return null; } /// /// Abfragen einer Klarna-Order /// /// Klarna Order ID /// Klarna Benutzername /// Klarna Passwort /// Klarna Order oder null, wenn nicht gefunden public async Task GetOrderAsync(string klarnaOrderId, string username, string password) { var client = GetHttpClient(username, password); var result = await client.GetAsync($"checkout/v3/orders/{klarnaOrderId}"); if (result.IsSuccessStatusCode) { var responseJson = await result.Content.ReadAsStringAsync(); var response = JsonConvert.DeserializeObject(responseJson); //order.KlarnaOrderId = response.OrderId; //order.PaymentStatus = PaymentStatus.Pending; //await _orderService.CommitAsync("klarna"); if (response.Status == "checkout_complete") { return response; } } else { var response = await result.Content.ReadAsStringAsync(); } return null; } /// /// Abfragen einer Klarna-Order - OrderManagement API /// /// Klarna Order ID /// Klarna Benutzername /// Klarna Passwort /// Klarna Order oder null, wenn nicht gefunden public async Task GetManagementOrderAsync(string klarnaOrderId, string username, string password) { var client = GetHttpClient(username, password); var result = await client.GetAsync($"ordermanagement/v1/orders/{klarnaOrderId}"); if (result.IsSuccessStatusCode) { var responseJson = await result.Content.ReadAsStringAsync(); var response = JsonConvert.DeserializeObject(responseJson); return response; } else { var response = await result.Content.ReadAsStringAsync(); } return null; } /// /// Bestätigen einer Klarna-Order - OrderManagement API /// /// Klarna Order ID /// Klarna Benutzername /// Klarna Passwort /// Klarna Order oder null, wenn nicht gefunden public async Task AcknowledgeOrderAsync(string klarnaOrderId, string username, string password) { var client = GetHttpClient(username, password); var result = await client.PostAsync($"ordermanagement/v1/orders/{klarnaOrderId}/acknowledge", new StringContent("")); if (result.IsSuccessStatusCode) { return true; } else { var response = await result.Content.ReadAsStringAsync(); } return false; } /// /// Bestätigen des Versands einer Bestellung - OrderManagement API /// /// Order ID /// Klarna Benutzername /// Klarna Passwort /// true wenn erfolgreich, false sonst public async Task CaptureOrderAsync(long orderId, string username, string password) { var result = CaptureOrderResult.Success; var client = GetHttpClient(username, password); var order = await _orderService.GetAsync(orderId); if (order != null) { var orderItems = await _orderService.GetItemsAsync(orderId); if (orderItems.Any()) { try { var klarnaOrder = await GetManagementOrderAsync(order.KlarnaOrderId, username, password); if (klarnaOrder != null) { if (klarnaOrder.RemainingAuthorizedAmount is > 0) { var capture = new Capture() { CapturedAmount = klarnaOrder.RemainingAuthorizedAmount }; var jsonOrder = JsonConvert.SerializeObject(capture); try { var httpResult = await client.PostAsync($"ordermanagement/v1/orders/{order.KlarnaOrderId}/captures", new StringContent(jsonOrder, Encoding.UTF8, "application/json")); if (httpResult.IsSuccessStatusCode) { result = CaptureOrderResult.Success; } else { if (httpResult.StatusCode == HttpStatusCode.Forbidden) result = CaptureOrderResult.CaptureNotAllowed; if (httpResult.StatusCode == HttpStatusCode.NotFound) result = CaptureOrderResult.OrderNotFound; else result = CaptureOrderResult.AuthorizationFailed; } } catch { result = CaptureOrderResult.Timeout; } } else { result = CaptureOrderResult.RemainingAuthorizedAmount; } } else result = CaptureOrderResult.OrderNotFound; } catch { result = CaptureOrderResult.Timeout; } } else { result = CaptureOrderResult.OrderNotFound; } } else { result = CaptureOrderResult.OrderNotFound; } return result; } } }