using System; using System.Collections.Generic; using System.Net.Http; using System.Threading.Tasks; using System.Text.Json; using System.Text.Json.Serialization; using System.Xml.Linq; using System.Linq; using System.Text; namespace Nop.Plugin.Misc.FruitBankPlugin.Services { /// /// Service for interacting with InnVoice Invoice API /// API Documentation: https://help.innvoice.hu/hc/hu/articles/360003142839 /// public class InnVoiceApiService { private readonly HttpClient _httpClient; private readonly string _companyName; private readonly string _username; private readonly string _password; private readonly string _baseUrl; public InnVoiceApiService(string companyName = "apiteszt", string username = "apiteszt", string password = "dsjfluio4324hjhjfdhkjskjh213kjgsd", string baseUrl = "https://api.innvoice.hu") { _companyName = companyName ?? throw new ArgumentNullException(nameof(companyName)); _username = username ?? throw new ArgumentNullException(nameof(username)); _password = password ?? throw new ArgumentNullException(nameof(password)); _baseUrl = baseUrl.TrimEnd('/'); _httpClient = new HttpClient(); SetupAuthentication(); } private void SetupAuthentication() { var authToken = Convert.ToBase64String( System.Text.Encoding.ASCII.GetBytes($"{_username}:{_password}") ); _httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", authToken); } /// /// Get all invoices /// Rate limit: 20 times per hour without ID parameter /// public async Task> GetAllInvoicesAsync() { var url = $"{_baseUrl}/{_companyName}/invoice"; return await GetInvoicesFromUrlAsync(url); } /// /// Get invoice by internal table ID /// public async Task GetInvoiceByIdAsync(int tableId) { var url = $"{_baseUrl}/{_companyName}/invoice/id/{tableId}"; var invoices = await GetInvoicesFromUrlAsync(url); return invoices.Count > 0 ? invoices[0] : null; } /// /// Get invoice by invoice number /// public async Task> GetInvoiceByNumberAsync(string invoiceNumber) { var url = $"{_baseUrl}/{_companyName}/invoice/invoicenumber/{Uri.EscapeDataString(invoiceNumber)}"; return await GetInvoicesFromUrlAsync(url); } /// /// Get invoice by invoice number /// public async Task> GetInvoiceByTechIdAsync(string techId) { //api.innvoice.hu/%regnev%/invoice/techid/%TECHID% var url = $"{_baseUrl}/{_companyName}/invoice/techid/{Uri.EscapeDataString(techId)}"; return await GetInvoicesFromUrlAsync(url); } /// /// Get invoices by creation date /// Format: YYYYMMDD /// public async Task> GetInvoicesByCreationDateAsync(DateTime date) { var dateStr = date.ToString("yyyyMMdd"); var url = $"{_baseUrl}/{_companyName}/invoice/created/{dateStr}"; return await GetInvoicesFromUrlAsync(url); } /// /// Get invoices by fulfillment date /// Format: YYYYMMDD /// public async Task> GetInvoicesByFulfillmentDateAsync(DateTime date) { var dateStr = date.ToString("yyyyMMdd"); var url = $"{_baseUrl}/{_companyName}/invoice/fulfillment/{dateStr}"; return await GetInvoicesFromUrlAsync(url); } /// /// Get invoices by due date /// Format: YYYYMMDD /// public async Task> GetInvoicesByDueDateAsync(DateTime date) { var dateStr = date.ToString("yyyyMMdd"); var url = $"{_baseUrl}/{_companyName}/invoice/duedate/{dateStr}"; return await GetInvoicesFromUrlAsync(url); } /// /// Get invoices by payment date /// Format: YYYYMMDD /// public async Task> GetInvoicesByPaymentDateAsync(DateTime date) { var dateStr = date.ToString("yyyyMMdd"); var url = $"{_baseUrl}/{_companyName}/invoice/paymentdate/{dateStr}"; return await GetInvoicesFromUrlAsync(url); } /// /// Get invoices by customer tax number /// public async Task> GetInvoicesByCustomerTaxNumberAsync(string taxNumber) { var url = $"{_baseUrl}/{_companyName}/invoice/taxnumber/{Uri.EscapeDataString(taxNumber)}"; return await GetInvoicesFromUrlAsync(url); } /// /// Get invoices modified since a specific timestamp /// Format: YYYYMMDDHHmmss (year, month, day, hour, minute, second) /// Recommended for tracking changes every 10 minutes /// Rate limit: Full queries or queries older than current month limited to 10 times per 30 days /// Recommended: Only use current month dates /// public async Task> GetInvoicesByUpdateTimeAsync(DateTime updateTime) { var timeStr = updateTime.ToString("yyyyMMddHHmmss"); var url = $"{_baseUrl}/{_companyName}/invoice/updatedtime/{timeStr}"; return await GetInvoicesFromUrlAsync(url); } private async Task> GetInvoicesFromUrlAsync(string url) { try { var response = await _httpClient.GetAsync(url); response.EnsureSuccessStatusCode(); var content = await response.Content.ReadAsStringAsync(); // Parse XML response return ParseInvoicesFromXml(content); } catch (HttpRequestException ex) { throw new InnVoiceApiException($"Error calling InnVoice API: {ex.Message}", ex); } catch (Exception ex) { throw new InnVoiceApiException($"Error parsing API response: {ex.Message}", ex); } } private List ParseInvoicesFromXml(string xml) { var invoices = new List(); try { var doc = XDocument.Parse(xml); var invoiceElements = doc.Descendants("invoice"); foreach (var invoiceElement in invoiceElements) { var invoice = new Invoice { TableId = GetIntValue(invoiceElement, "TABLE_ID"), VevoID = GetIntValue(invoiceElement, "VevoID"), CustomerName = GetStringValue(invoiceElement, "VevoNev"), CustomerZipCode = GetStringValue(invoiceElement, "VevoIrsz"), CustomerCity = GetStringValue(invoiceElement, "VevoTelep"), CustomerAddress = GetStringValue(invoiceElement, "VevoUtcaHsz"), CustomerCountry = GetStringValue(invoiceElement, "VevoOrszag"), CustomerTaxNumber = GetStringValue(invoiceElement, "VevoAdoszam"), CustomerGroupTaxNumber = GetStringValue(invoiceElement, "VevoCsAdoszam"), PaymentMethod = GetStringValue(invoiceElement, "FizetesiMod"), FulfillmentDate = GetStringValue(invoiceElement, "TeljesitesKelte"), InvoiceDate = GetStringValue(invoiceElement, "SzamlaKelte"), DueDate = GetStringValue(invoiceElement, "Hatarido"), Notes = GetStringValue(invoiceElement, "Megjegyzes"), Currency = GetStringValue(invoiceElement, "Devizanem"), IsDraft = GetIntValue(invoiceElement, "Felretett") == 1, IsCancelled = GetIntValue(invoiceElement, "Storno") == 1, IsAdvance = GetIntValue(invoiceElement, "Eloleg") == 1, IsProforma = GetIntValue(invoiceElement, "Proforma") == 1, TotalNet = GetDecimalValue(invoiceElement, "NettoErtek"), TotalVAT = GetDecimalValue(invoiceElement, "AFAErtek"), TotalGross = GetDecimalValue(invoiceElement, "BruttoErtek"), OutstandingAmount = GetDecimalValue(invoiceElement, "Hatralek"), OverdueAmount = GetDecimalValue(invoiceElement, "LejartHatralek"), PaidAmount = GetDecimalValue(invoiceElement, "Befizetes"), InvoiceNumber = GetStringValue(invoiceElement, "Sorszam"), InvoiceNumberFormatted = GetStringValue(invoiceElement, "SorszamFormatted"), InvoiceId = GetIntValue(invoiceElement, "SzamlaID"), UniqueId = GetStringValue(invoiceElement, "_UniqueID"), ExchangeRate = GetDecimalValue(invoiceElement, "Arfolyam"), InvoiceBookId = GetIntValue(invoiceElement, "SzamlatombID"), InvoiceBookName = GetStringValue(invoiceElement, "SzamlatombNev"), IsElectronic = GetIntValue(invoiceElement, "Eszamla") == 1, Phone = GetStringValue(invoiceElement, "Telefon"), Email = GetStringValue(invoiceElement, "Email"), UpdateTime = GetStringValue(invoiceElement, "_UpdateTime"), QueryTime = GetStringValue(invoiceElement, "QueryTime"), OrderNumber = GetStringValue(invoiceElement, "MegrendelesSzamStr"), OrderDate = GetStringValue(invoiceElement, "MegrendelesIdopontStr"), ShippingName = GetStringValue(invoiceElement, "SzallNev"), ShippingZipCode = GetStringValue(invoiceElement, "SzallIrsz"), ShippingCity = GetStringValue(invoiceElement, "SzallTelep"), ShippingAddress = GetStringValue(invoiceElement, "SzallUtcaHsz"), ShippingCountry = GetStringValue(invoiceElement, "SzallOrszag"), PrintUrl = GetStringValue(invoiceElement, "PrintLink"), InvoiceLink = GetStringValue(invoiceElement, "SzamlaLink"), HealthFundName = GetStringValue(invoiceElement, "VevoEPNev"), HealthFundCode = GetStringValue(invoiceElement, "VevoEPKod") }; // Parse line items var itemElements = invoiceElement.Descendants("tetel"); foreach (var itemElement in itemElements) { invoice.Items.Add(new InvoiceLineItem { TableId = GetIntValue(itemElement, "TABLE_ID"), ItemName = GetStringValue(itemElement, "TetelNev"), ArticleNumber = GetStringValue(itemElement, "CikkSzam"), VTSZSZJ = GetStringValue(itemElement, "VTSZSZJ"), VATText = GetStringValue(itemElement, "AfaSzoveg"), VATRate = GetDecimalValue(itemElement, "AfaKulcs"), IsGross = GetIntValue(itemElement, "Brutto") == 1, UnitPrice = GetDecimalValue(itemElement, "EgysegAr"), Quantity = GetDecimalValue(itemElement, "Mennyiseg"), Unit = GetStringValue(itemElement, "MennyisegEgyseg"), ProductId = GetIntValue(itemElement, "TermekID"), DiscountAmount = GetDecimalValue(itemElement, "KedvezmenyOsszeg") }); } invoices.Add(invoice); } } catch (Exception ex) { throw new InnVoiceApiException($"Error parsing XML: {ex.Message}", ex); } return invoices; } private string GetStringValue(XElement element, string name) { return element.Element(name)?.Value?.Trim() ?? string.Empty; } private int GetIntValue(XElement element, string name) { var value = GetStringValue(element, name); return int.TryParse(value, out var result) ? result : 0; } private decimal GetDecimalValue(XElement element, string name) { var value = GetStringValue(element, name); return decimal.TryParse(value, out var result) ? result : 0m; } /// /// Create a new invoice /// public async Task CreateInvoiceAsync(InvoiceCreateRequest request) { var url = $"{_baseUrl}/{_companyName}/invoice"; var xml = request.ToXml(); var content = new FormUrlEncodedContent(new[] { new KeyValuePair("data", xml) }); try { var response = await _httpClient.PostAsync(url, content); response.EnsureSuccessStatusCode(); var responseContent = await response.Content.ReadAsStringAsync(); return InvoiceCreateResponse.FromXml(responseContent); } catch (HttpRequestException ex) { throw new InnVoiceApiException($"Error creating invoice: {ex.Message}", ex); } } /// /// Update an existing invoice /// public async Task UpdateInvoiceAsync(int tableId, InvoiceCreateRequest request) { // Set the VevoID if updating customer information var url = $"{_baseUrl}/{_companyName}/invoice"; var xml = request.ToXml(); var content = new FormUrlEncodedContent(new[] { new KeyValuePair("data", xml), new KeyValuePair("id", tableId.ToString()) }); try { var response = await _httpClient.PostAsync(url, content); response.EnsureSuccessStatusCode(); var responseContent = await response.Content.ReadAsStringAsync(); return InvoiceCreateResponse.FromXml(responseContent); } catch (HttpRequestException ex) { throw new InnVoiceApiException($"Error updating invoice: {ex.Message}", ex); } } } // Models public class Invoice { public int TableId { get; set; } public int VevoID { get; set; } public string CustomerName { get; set; } public string CustomerZipCode { get; set; } public string CustomerCity { get; set; } public string CustomerAddress { get; set; } public string CustomerCountry { get; set; } public string CustomerTaxNumber { get; set; } public string CustomerGroupTaxNumber { get; set; } public string PaymentMethod { get; set; } public string FulfillmentDate { get; set; } public string InvoiceDate { get; set; } public string DueDate { get; set; } public string Notes { get; set; } public string Currency { get; set; } public bool IsDraft { get; set; } public bool IsCancelled { get; set; } public bool IsAdvance { get; set; } public bool IsProforma { get; set; } public decimal TotalNet { get; set; } public decimal TotalVAT { get; set; } public decimal TotalGross { get; set; } public decimal OutstandingAmount { get; set; } public decimal OverdueAmount { get; set; } public decimal PaidAmount { get; set; } public string InvoiceNumber { get; set; } public string InvoiceNumberFormatted { get; set; } public int InvoiceId { get; set; } public string UniqueId { get; set; } public decimal ExchangeRate { get; set; } public int InvoiceBookId { get; set; } public string InvoiceBookName { get; set; } public bool IsElectronic { get; set; } public string Phone { get; set; } public string Email { get; set; } public string UpdateTime { get; set; } public string QueryTime { get; set; } public string OrderNumber { get; set; } public string OrderDate { get; set; } public string ShippingName { get; set; } public string ShippingZipCode { get; set; } public string ShippingCity { get; set; } public string ShippingAddress { get; set; } public string ShippingCountry { get; set; } public string PrintUrl { get; set; } public string InvoiceLink { get; set; } public string HealthFundName { get; set; } public string HealthFundCode { get; set; } public List Items { get; set; } = new List(); } public class InvoiceLineItem { public int TableId { get; set; } public string ItemName { get; set; } public string ArticleNumber { get; set; } public string VTSZSZJ { get; set; } public string VATText { get; set; } public decimal VATRate { get; set; } public bool IsGross { get; set; } public decimal UnitPrice { get; set; } public decimal Quantity { get; set; } public string Unit { get; set; } public int ProductId { get; set; } public decimal DiscountAmount { get; set; } } // Invoice Creation Models public class InvoiceCreateRequest { public int VevoID { get; set; } = 0; public string VevoNev { get; set; } public string VevoIrsz { get; set; } public string VevoOrszag { get; set; } public string VevoTelep { get; set; } public string VevoUtcaHsz { get; set; } public string VevoEPNev { get; set; } public string VevoEPKod { get; set; } public string SzallNev { get; set; } public string SzallIrsz { get; set; } public string SzallTelep { get; set; } public string SzallUtcaHsz { get; set; } public string SzallOrszag { get; set; } public int SzamlatombID { get; set; } public DateTime SzamlaKelte { get; set; } public DateTime TeljesitesKelte { get; set; } public DateTime Hatarido { get; set; } public string Devizanem { get; set; } public string FizetesiMod { get; set; } public string Megjegyzes { get; set; } public string Nyelv1 { get; set; } public string Nyelv2 { get; set; } public decimal? Arfolyam { get; set; } public string ArfolyamDeviza { get; set; } public bool Fizetve { get; set; } public bool Eszamla { get; set; } public string VevoAdoszam { get; set; } public string VevoCsAdoszam { get; set; } public string Telefon { get; set; } public string Email { get; set; } public string MegrendelesSzamStr { get; set; } public string MegrendelesIdopontStr { get; set; } public bool Felretett { get; set; } public bool Proforma { get; set; } public bool AutomatikusAr { get; set; } public bool Eloleg { get; set; } public bool Sendmail { get; set; } public string MailSubject { get; set; } public string MailBody { get; set; } public string Eredetiszamla { get; set; } public List Items { get; set; } = new List(); public void AddItem(InvoiceItem item) { Items.Add(item); } public string ToXml() { var invoices = new XElement("invoices"); var invoice = new XElement("invoice"); if (VevoID > 0) invoice.Add(new XElement("VevoID", new XCData(VevoID.ToString()))); invoice.Add(new XElement("VevoNev", new XCData(VevoNev ?? ""))); invoice.Add(new XElement("VevoIrsz", new XCData(VevoIrsz ?? ""))); invoice.Add(new XElement("VevoTelep", new XCData(VevoTelep ?? ""))); invoice.Add(new XElement("VevoOrszag", new XCData(VevoOrszag ?? ""))); invoice.Add(new XElement("VevoUtcaHsz", new XCData(VevoUtcaHsz ?? ""))); if (!string.IsNullOrEmpty(VevoEPNev)) invoice.Add(new XElement("VevoEPNev", new XCData(VevoEPNev))); if (!string.IsNullOrEmpty(VevoEPKod)) invoice.Add(new XElement("VevoEPKod", new XCData(VevoEPKod))); if (!string.IsNullOrEmpty(SzallNev)) invoice.Add(new XElement("SzallNev", new XCData(SzallNev))); if (!string.IsNullOrEmpty(SzallIrsz)) invoice.Add(new XElement("SzallIrsz", new XCData(SzallIrsz))); if (!string.IsNullOrEmpty(SzallTelep)) invoice.Add(new XElement("SzallTelep", new XCData(SzallTelep))); if (!string.IsNullOrEmpty(SzallUtcaHsz)) invoice.Add(new XElement("SzallUtcaHsz", new XCData(SzallUtcaHsz))); if (!string.IsNullOrEmpty(SzallOrszag)) invoice.Add(new XElement("SzallOrszag", new XCData(SzallOrszag))); invoice.Add(new XElement("SzamlatombID", new XCData(SzamlatombID.ToString()))); invoice.Add(new XElement("SzamlaKelte", new XCData(SzamlaKelte.ToString("yyyy.MM.dd.")))); invoice.Add(new XElement("TeljesitesKelte", new XCData(TeljesitesKelte.ToString("yyyy.MM.dd.")))); invoice.Add(new XElement("Hatarido", new XCData(Hatarido.ToString("yyyy.MM.dd.")))); invoice.Add(new XElement("Devizanem", new XCData(Devizanem ?? ""))); invoice.Add(new XElement("FizetesiMod", new XCData(FizetesiMod ?? ""))); if (!string.IsNullOrEmpty(Megjegyzes)) invoice.Add(new XElement("Megjegyzes", new XCData(Megjegyzes))); if (!string.IsNullOrEmpty(Nyelv1)) invoice.Add(new XElement("Nyelv1", new XCData(Nyelv1))); if (!string.IsNullOrEmpty(Nyelv2)) invoice.Add(new XElement("Nyelv2", new XCData(Nyelv2))); if (Arfolyam.HasValue) invoice.Add(new XElement("Arfolyam", new XCData(Arfolyam.Value.ToString()))); if (!string.IsNullOrEmpty(ArfolyamDeviza)) invoice.Add(new XElement("ArfolyamDeviza", new XCData(ArfolyamDeviza))); invoice.Add(new XElement("Fizetve", Fizetve ? "1" : "0")); invoice.Add(new XElement("Eszamla", Eszamla ? "1" : "0")); if (!string.IsNullOrEmpty(VevoAdoszam)) invoice.Add(new XElement("VevoAdoszam", new XCData(VevoAdoszam))); if (!string.IsNullOrEmpty(VevoCsAdoszam)) invoice.Add(new XElement("VevoCsAdoszam", new XCData(VevoCsAdoszam))); if (!string.IsNullOrEmpty(Telefon)) invoice.Add(new XElement("Telefon", new XCData(Telefon))); if (!string.IsNullOrEmpty(Email)) invoice.Add(new XElement("Email", new XCData(Email))); if (!string.IsNullOrEmpty(MegrendelesSzamStr)) invoice.Add(new XElement("MegrendelesSzamStr", new XCData(MegrendelesSzamStr))); if (!string.IsNullOrEmpty(MegrendelesIdopontStr)) invoice.Add(new XElement("MegrendelesIdopontStr", new XCData(MegrendelesIdopontStr))); invoice.Add(new XElement("Felretett", Felretett ? "1" : "0")); invoice.Add(new XElement("Proforma", Proforma ? "1" : "0")); if (AutomatikusAr) invoice.Add(new XElement("AutomatikusAr", "1")); if (Eloleg) invoice.Add(new XElement("Eloleg", "1")); if (Sendmail) { invoice.Add(new XElement("Sendmail", "1")); if (!string.IsNullOrEmpty(MailSubject)) invoice.Add(new XElement("MailSubject", new XCData(MailSubject))); if (!string.IsNullOrEmpty(MailBody)) invoice.Add(new XElement("MailBody", new XCData(MailBody))); } if (!string.IsNullOrEmpty(Eredetiszamla)) invoice.Add(new XElement("Eredetiszamla", new XCData(Eredetiszamla))); // Add items foreach (var item in Items) { var tetel = new XElement("tetel"); tetel.Add(new XElement("TetelNev", new XCData(item.TetelNev ?? ""))); tetel.Add(new XElement("AfaSzoveg", item.AfaSzoveg ?? "")); tetel.Add(new XElement("Brutto", item.Brutto ? "1" : "0")); tetel.Add(new XElement("EgysegAr", item.EgysegAr.ToString())); tetel.Add(new XElement("Mennyiseg", item.Mennyiseg.ToString())); tetel.Add(new XElement("MennyisegEgyseg", new XCData(item.MennyisegEgyseg ?? ""))); if (item.KedvezmenyOsszeg.HasValue) tetel.Add(new XElement("KedvezmenyOsszeg", item.KedvezmenyOsszeg.Value.ToString())); if (item.TermekID.HasValue) tetel.Add(new XElement("TermekID", item.TermekID.Value.ToString())); if (!string.IsNullOrEmpty(item.Megjegyzes)) tetel.Add(new XElement("Megjegyzes", new XCData(item.Megjegyzes))); if (!string.IsNullOrEmpty(item.CikkSzam)) tetel.Add(new XElement("CikkSzam", new XCData(item.CikkSzam))); if (!string.IsNullOrEmpty(item.VTSZSZJ)) tetel.Add(new XElement("VTSZSZJ", new XCData(item.VTSZSZJ))); if (item.ElolegSzamlaTABLE_ID.HasValue) tetel.Add(new XElement("ElolegSzamlaTABLE_ID", item.ElolegSzamlaTABLE_ID.Value.ToString())); if (!string.IsNullOrEmpty(item.ElolegSzamlaSorszam)) tetel.Add(new XElement("ElolegSzamlaSorszam", new XCData(item.ElolegSzamlaSorszam))); invoice.Add(tetel); } invoices.Add(invoice); return new XDeclaration("1.0", "UTF-8", null).ToString() + "\n" + invoices.ToString(); } } public class InvoiceItem { public string TetelNev { get; set; } public string AfaSzoveg { get; set; } public bool Brutto { get; set; } public decimal EgysegAr { get; set; } public decimal Mennyiseg { get; set; } public string MennyisegEgyseg { get; set; } public decimal? KedvezmenyOsszeg { get; set; } public int? TermekID { get; set; } public string Megjegyzes { get; set; } public string CikkSzam { get; set; } public string VTSZSZJ { get; set; } public int? ElolegSzamlaTABLE_ID { get; set; } public string ElolegSzamlaSorszam { get; set; } } public class InvoiceCreateResponse { public string ErrorCode { get; set; } public string Message { get; set; } public int? TableId { get; set; } public int? VevoID { get; set; } public string TechId { get; set; } public string Sorszam { get; set; } public string PrintUrl { get; set; } public bool IsSuccess => ErrorCode == "200"; public static InvoiceCreateResponse FromXml(string xml) { var doc = XDocument.Parse(xml); var invoice = doc.Descendants("invoice").FirstOrDefault(); if (invoice == null) { throw new InnVoiceApiException("Invalid XML response format"); } return new InvoiceCreateResponse { ErrorCode = invoice.Element("error")?.Value, Message = invoice.Element("message")?.Value?.Trim(), TableId = int.TryParse(invoice.Element("TABLE_ID")?.Value?.Trim(), out var tid) ? tid : (int?)null, VevoID = int.TryParse(invoice.Element("VevoID")?.Value?.Trim(), out var vid) ? vid : (int?)null, TechId = invoice.Element("techid")?.Value?.Trim(), Sorszam = invoice.Element("Sorszam")?.Value?.Trim(), PrintUrl = invoice.Element("PrintUrl")?.Value?.Trim() }; } } }