using Dynamicweb.Ecommerce; using Dynamicweb.Ecommerce.Prices; using Dynamicweb.Ecommerce.Orders; using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.IO; using System.Net; using System.Text; using Dynamicweb.Ecommerce.CheckoutHandlers.DibsEasyCheckout; using KBW.CustomModules.Helpers; namespace KBW.CustomModules.NotificationSubscribers.Payments { public class SubscriptionService { private readonly string[] NO_RETRY_CODES = { "04", "14", "15", "41", "43", "46", "54", "57" }; private readonly Lazy VariantOptionService = new Lazy(() => new VariantOptionService()); public void AutoCancel(string shopId, string stateId) { var today = DateTime.Today; var end = KBWOrders.PaymentLimit(); var subscriptionStart = KBWOrders.SubscriptionStart(); if (today <= end && today >= KBWOrders.SubscriptionLimit()) { return; } var orders = Services.Orders.GetAll() .Where(i => !i.Deleted && !i.IsRecurringOrderTemplate && i.ShopId == shopId && i.Date >= subscriptionStart && !string.IsNullOrEmpty(i.TransactionToken) && !string.IsNullOrEmpty(i.OrderFieldValues.GetOrderFieldValue("KBWNetsBulkId").Value?.ToString()) && i.CaptureInfo.State != OrderCaptureInfo.OrderCaptureState.Success); foreach (var o in orders) { o.StateId = stateId; Services.Orders.Complete(o); Services.Orders.Save(o); RecurringOrder.EndRecurring(o.RecurringOrderId); } } public void AutoCharge(string shopId, string paymentId, string stateId) { var today = DateTime.Today; var start = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1); var end = KBWOrders.PaymentLimit(); var exit = today <= start && today > end; if (exit) { return; } // get a list of orders to be charged var subscriptionStart = KBWOrders.SubscriptionStart(); var orders = Services.Orders.GetAll() .Where(i => !i.Deleted && !i.IsRecurringOrderTemplate && i.ShopId == shopId && !string.IsNullOrEmpty(i.TransactionToken) && !NO_RETRY_CODES.Contains(i.TransactionPayGatewayCode) && i.Date >= subscriptionStart && i.CaptureInfo.State != OrderCaptureInfo.OrderCaptureState.Success); List sentOrders = new List(); List subscriptions = new List(); var tokens = orders.Select(i => i.TransactionToken).Distinct(); // the API key var paymentGatewayObject = Services.Payments.GetPayment(paymentId).CaptureObject as NetsEasySubscriptionCheckout; var key = paymentGatewayObject.TestMode ? paymentGatewayObject.TestSecretKey : paymentGatewayObject.LiveSecretKey; var baseUrl = paymentGatewayObject.BaseApiUrl; foreach (var token in tokens) { // get the first order that has a token var tokenOrder = orders.FirstOrDefault(i => i.TransactionToken == token); // if none has a token, go to the next one if (tokenOrder == null) { continue; } var o = ConvertOrder(tokenOrder); sentOrders.Add(tokenOrder); subscriptions.Add(new { subscriptionId = token, externalReference = tokenOrder.Id, order = o }); } if (subscriptions.Any()) { var payload = new { // externalBulkChargeId = DateTime.Today.ToFileTimeUtc(), externalBulkChargeId = Guid.NewGuid().ToString(), subscriptions = subscriptions }; // send one request with all the orders var response = ExecutePostRequest(baseUrl, payload, key); // update the orders with the BulkId and with the right status foreach (var o in sentOrders) { o.OrderFieldValues.GetOrderFieldValue("KBWNetsBulkId").Value = response["bulkId"]; o.GatewayPaymentStatus = $"Charge initiated NETS BatchId: {response["bulkId"]}"; o.CaptureInfo.State = OrderCaptureInfo.OrderCaptureState.NotCaptured; o.CaptureInfo.Message = $"Charge request sent: { response["bulkId"]}"; o.TransactionStatus = $"Charge initiated NETS BatchId: {response["bulkId"]}"; o.StateId = stateId; Services.Orders.Complete(o); Services.Orders.Save(o); } } } public void UpdateChargedOrders(string shopId, string paymentId, string stateId, string retryStateId) { var subscriptionStart = KBWOrders.SubscriptionStart() ; // get a list with all the orders that are waiting for an answer // get a list of orders to be charged var orders = Services.Orders.GetAll() .Where(i => !i.Deleted && !i.IsRecurringOrderTemplate && i.ShopId == shopId && i.Date >= subscriptionStart && !string.IsNullOrEmpty(i.TransactionToken) && !string.IsNullOrEmpty(i.OrderFieldValues.GetOrderFieldValue("KBWNetsBulkId").Value?.ToString()) && i.CaptureInfo.State != OrderCaptureInfo.OrderCaptureState.Success); // extract the bulkIds list var bulkIds = orders.Select(o => o.OrderFieldValues.GetOrderFieldValue("KBWNetsBulkId").Value.ToString()) .Distinct() .Where(i => !string.IsNullOrEmpty(i)); // the API key var paymentGatewayObject = Services.Payments.GetPayment(paymentId).CaptureObject as NetsEasySubscriptionCheckout; var key = paymentGatewayObject.TestMode ? paymentGatewayObject.TestSecretKey : paymentGatewayObject.LiveSecretKey; var baseUrl = paymentGatewayObject.BaseApiUrl; // send the bulkId verifications requests foreach (string b in bulkIds) { bool more = true; while (more) { var response = ExecuteGetRequest(baseUrl, b, key); more = (bool)response["more"]; var subscriptions = (dynamic)response["page"]; foreach (var s in subscriptions) { Order o = Services.Orders.GetById(s["externalReference"].ToString()); var captureState = s["status"] == "Succeeded" ? OrderCaptureInfo.OrderCaptureState.Success : OrderCaptureInfo.OrderCaptureState.Failed; o.CaptureInfo.State = captureState; o.GatewayPaymentStatus = $"Charge status: {s["status"]}"; o.CaptureInfo.Message = s["message"]?.ToString(); o.TransactionPayGatewayCode = s["code"]?.ToString(); var tranStatus = s["message"]?.ToString(); if (!string.IsNullOrEmpty(tranStatus)) { if (tranStatus.IndexOf("ErrorM") > 0) { tranStatus = tranStatus.Substring(tranStatus.IndexOf("ErrorM")); } if (tranStatus.Length >= 100) { tranStatus = tranStatus.Substring(0, 100); } } o.TransactionStatus = tranStatus; if (captureState != OrderCaptureInfo.OrderCaptureState.Success) { if (o.Errors.Count == 0) { o.StateId = retryStateId; } else { o.StateId = stateId; } o.Errors.Add("Payment failed"); } Services.Orders.Save(o); } } } } #region NETS Communication private object ConvertOrder(Order order) { long linesTotalPIP = 0; var lineItems = new List(order.OrderLines.Select(x => ConvertOrderLine(x, ref linesTotalPIP))); if (order.ShippingFee.Price >= 0.01) { lineItems.Add(GetLineItem(order.ShippingMethodId, "ShippingFee", 1.0, order.ShippingFee, order.ShippingFee, "unit", ref linesTotalPIP)); } if (order.PaymentFee.Price >= 0.01) { lineItems.Add(GetLineItem(order.PaymentMethodId, "PaymentFee", 1.0, order.PaymentFee, order.PaymentFee, "unit", ref linesTotalPIP)); } var orderPriceWithoutVatPIP = PriceHelper.ConvertToPIP(order.Currency, order.Price.PriceWithoutVAT); if (linesTotalPIP != orderPriceWithoutVatPIP) { var roundingError = orderPriceWithoutVatPIP - linesTotalPIP; lineItems.Add(new { reference = "RoundingError", name = "RoundingError", quantity = 1.0, unitPrice = roundingError, unit = "unit", taxRate = 0, taxAmount = 0, grossTotalAmount = roundingError, netTotalAmount = roundingError }); } var orderRaw = new { items = lineItems, amount = order.Price.PricePIP, currency = order.CurrencyCode, reference = order.Id }; return orderRaw; } private object ConvertOrderLine(OrderLine orderline, ref long linesTotalPIP) { var unitName = "pcs"; if (!string.IsNullOrWhiteSpace(orderline.UnitId)) { var unit = orderline.Product?.GetUnitList(orderline.Order.LanguageId).FirstOrDefault(u => u.Id == orderline.UnitId); if (unit != null) { unitName = VariantOptionService.Value.GetVariantOption(unit.Id, orderline.Order.LanguageId)?.Name; } } return GetLineItem(orderline.Id, orderline.ProductName, orderline.Quantity, orderline.Price, orderline.UnitPrice, unitName, ref linesTotalPIP); } private object GetLineItem(string reference, string name, double quantity, PriceInfo linePrice, PriceInfo unitPrice, string unitName, ref long linesTotalPIP) { linesTotalPIP += PriceHelper.ConvertToPIP(linePrice.Currency, linePrice.PriceWithoutVAT); return new { reference = reference, name = EncodeAndTruncateText(name), quantity = quantity, unitPrice = PriceHelper.ConvertToPIP(unitPrice.Currency, unitPrice.PriceWithoutVAT), unit = EncodeAndTruncateText(unitName), taxRate = PriceHelper.ConvertToPIP(linePrice.Currency, linePrice.VATPercent), taxAmount = PriceHelper.ConvertToPIP(linePrice.Currency, linePrice.VAT), grossTotalAmount = PriceHelper.ConvertToPIP(linePrice.Currency, linePrice.PriceWithVAT), netTotalAmount = PriceHelper.ConvertToPIP(linePrice.Currency, linePrice.PriceWithoutVAT) }; } private string EncodeAndTruncateText(string name) { //https://tech.dibspayment.com/easy/api/paymentapi "About the parameters" name = HttpUtility.HtmlEncode(name); if (name.Length > 128) { name = name.Substring(0, 128); } return name; } private string ExecuteRequest(WebRequest request) { string result = null; try { using (var response = request.GetResponse()) { using (var streamReader = new StreamReader(response.GetResponseStream(), Encoding.UTF8)) { result = streamReader.ReadToEnd(); } } } catch (WebException wexc) { if (wexc.Response != null) { string response; using (StreamReader sr = new StreamReader(wexc.Response.GetResponseStream(), Encoding.UTF8)) { response = sr.ReadToEnd(); } var json_error = Dynamicweb.Core.Converter.DeserializeCompact>(response); object errors = null; var errorMessage = json_error.TryGetValue("errors", out errors) ? errors.ToString() : $"{wexc.Message} {response}"; errorMessage = $"Payment request failed with following errors: {errorMessage}"; throw new Exception(errorMessage); } throw; } return result; } private Dictionary ExecutePostRequest(string baseUrl, object body, string key, string method = "POST") { HttpWebRequest request = WebRequest.CreateHttp($"{baseUrl}/v1/subscriptions/charges"); request.Method = method; request.Timeout = 90 * 1000; request.ContentType = "application/json"; // "application/json;charset=UTF-8"; // request.Accept = "application/json, text/plain, */*"; request.Headers.Set(HttpRequestHeader.Authorization, key); if (body != null) { var strBody = Dynamicweb.Core.Converter.SerializeCompact(body); byte[] bytes = Encoding.UTF8.GetBytes(strBody); request.ContentLength = bytes.Length; using (Stream st = request.GetRequestStream()) { st.Write(bytes, 0, bytes.Length); } } string result = ExecuteRequest(request); return Dynamicweb.Core.Converter.DeserializeCompact>(result); } private Dictionary ExecuteGetRequest(string baseUrl, string query, string key, string method = "GET") { HttpWebRequest request = WebRequest.CreateHttp($"{baseUrl}/v1/subscriptions/charges/{query}"); request.Method = method; request.Timeout = 90 * 1000; request.Accept = "application/json, text/plain, */*"; request.Headers.Set(HttpRequestHeader.Authorization, key); string result = ExecuteRequest(request); return Dynamicweb.Core.Converter.DeserializeCompact>(result); ; } #endregion } }