390 lines
16 KiB
C#

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
{
/// <summary>
/// Service der alle benötigten Klarna-Funktionen zur Verfügung stellt
/// </summary>
public class KlarnaService : IKlarnaService
{
private HttpClient _httpClient;
private readonly IOrderService _orderService;
private readonly ICustomerService _customerService;
private readonly IShopSettingsService _shopSettingsService;
private readonly IOptions<ShopOptions> _shopOptions;
/// <summary>
/// Erstellt eine Instanz
/// </summary>
/// <param name="orderService">Instanz eines IOrderService</param>
/// <param name="customerService">Instanz eines ICustomerService</param>
/// <param name="shopSettingsService">Instanz eines IShopSettingsService</param>
/// <param name="shopOptions">Instanz von ShopOptions</param>
public KlarnaService(IOrderService orderService, ICustomerService customerService, IShopSettingsService shopSettingsService, IOptions<ShopOptions> shopOptions)
{
_orderService = orderService;
_customerService = customerService;
_shopSettingsService = shopSettingsService;
_shopOptions = shopOptions;
}
/// <summary>
/// Gibt einen HTTP-Client für die Kommunikation mit Klarna zurück
/// </summary>
/// <returns>HttpClient</returns>
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;
}
/// <summary>
/// Prüft ob die Klarna Credentials korrekt sind und eine Verbindung zur Api aufgenommen werden kann
/// </summary>
/// <param name="username">Klarna Benutzername</param>
/// <param name="password">Klarna Passwort</param>
/// <returns>true wenn erfolgreich, false sonst</returns>
public async Task<bool> 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;
}
/// <summary>
/// Erstellen einer Klarna-Order
/// </summary>
/// <param name="orderId">Id der simple print Bestellung</param>
/// <param name="username">Klarna Benutzername</param>
/// <param name="password">Klarna Passwort</param>
/// <param name="termsUrl">Redirect-Url für AGB</param>
/// <param name="checkoutUrl">Url für Checkout</param>
/// <param name="confirmationUrl">Bestätigungs-URL</param>
/// <param name="pushUrl">URL für Push-Notifications</param>
/// <returns>Klarna Order oder null, wenn ein Fehler aufgetreten ist</returns>
public async Task<Order> 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<OrderLine>()
};
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<ShippingOption>();
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<Order>(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;
}
/// <summary>
/// Abfragen einer Klarna-Order
/// </summary>
/// <param name="klarnaOrderId">Klarna Order ID</param>
/// <param name="username">Klarna Benutzername</param>
/// <param name="password">Klarna Passwort</param>
/// <returns>Klarna Order oder null, wenn nicht gefunden</returns>
public async Task<Order> 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<Order>(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;
}
/// <summary>
/// Abfragen einer Klarna-Order - OrderManagement API
/// </summary>
/// <param name="klarnaOrderId">Klarna Order ID</param>
/// <param name="username">Klarna Benutzername</param>
/// <param name="password">Klarna Passwort</param>
/// <returns>Klarna Order oder null, wenn nicht gefunden</returns>
public async Task<Klarna.OrderManagement.Order> 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<Klarna.OrderManagement.Order>(responseJson);
return response;
}
else
{
var response = await result.Content.ReadAsStringAsync();
}
return null;
}
/// <summary>
/// Bestätigen einer Klarna-Order - OrderManagement API
/// </summary>
/// <param name="klarnaOrderId">Klarna Order ID</param>
/// <param name="username">Klarna Benutzername</param>
/// <param name="password">Klarna Passwort</param>
/// <returns>Klarna Order oder null, wenn nicht gefunden</returns>
public async Task<bool> 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;
}
/// <summary>
/// Bestätigen des Versands einer Bestellung - OrderManagement API
/// </summary>
/// <param name="orderId">Order ID</param>
/// <param name="username">Klarna Benutzername</param>
/// <param name="password">Klarna Passwort</param>
/// <returns>true wenn erfolgreich, false sonst</returns>
public async Task<CaptureOrderResult> 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;
}
}
}