From e36bd1990bf6592dc06ed53d490c183b3fe5fda9 Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Tue, 28 May 2024 18:06:27 -0400 Subject: [PATCH 1/2] Split PayloadGenerator into a separate file for each class --- QRCoder/PayloadGenerator.cs | 3055 +---------------- QRCoder/PayloadGenerator/BezahlCode.cs | 573 ++++ QRCoder/PayloadGenerator/BitcoinAddress.cs | 15 + .../PayloadGenerator/BitcoinCashAddress.cs | 15 + .../BitcoinLikeCryptoCurrencyAddress.cs | 74 + QRCoder/PayloadGenerator/Bookmark.cs | 30 + QRCoder/PayloadGenerator/CalendarEvent.cs | 81 + QRCoder/PayloadGenerator/ContactData.cs | 240 ++ QRCoder/PayloadGenerator/Geolocation.cs | 47 + QRCoder/PayloadGenerator/Girocode.cs | 135 + QRCoder/PayloadGenerator/LitecoinAddress.cs | 15 + QRCoder/PayloadGenerator/MMS.cs | 68 + QRCoder/PayloadGenerator/Mail.cs | 65 + QRCoder/PayloadGenerator/MoneroTransaction.cs | 65 + QRCoder/PayloadGenerator/OneTimePassword.cs | 153 + QRCoder/PayloadGenerator/Payload.cs | 17 + QRCoder/PayloadGenerator/PhoneNumber.cs | 28 + .../PayloadGenerator/RussiaPaymentOrder.cs | 616 ++++ QRCoder/PayloadGenerator/SMS.cs | 72 + QRCoder/PayloadGenerator/ShadowSocksConfig.cs | 233 ++ QRCoder/PayloadGenerator/SkypeCall.cs | 28 + QRCoder/PayloadGenerator/SlovenianUpnQr.cs | 108 + QRCoder/PayloadGenerator/SwissQrCode.cs | 509 +++ QRCoder/PayloadGenerator/Url.cs | 29 + QRCoder/PayloadGenerator/WhatsAppMessage.cs | 46 + QRCoder/PayloadGenerator/WiFi.cs | 47 + .../AlignmentPattern.cs} | 0 .../CodewordBlock.cs} | 0 .../ECCInfo.cs} | 0 .../ECCLevel.cs} | 0 .../EciMode.cs} | 0 .../EncodingMode.cs} | 0 .../ModulePlacer.BlockedModules.cs} | 0 .../ModulePlacer.MaskPattern.cs} | 0 .../ModulePlacer.cs} | 0 .../Point.cs} | 0 .../Polynom.cs} | 0 .../PolynomItem.cs} | 0 .../Rectangle.cs} | 0 .../VersionInfo.cs} | 0 .../VersionInfoDetails.cs} | 0 41 files changed, 3310 insertions(+), 3054 deletions(-) create mode 100644 QRCoder/PayloadGenerator/BezahlCode.cs create mode 100644 QRCoder/PayloadGenerator/BitcoinAddress.cs create mode 100644 QRCoder/PayloadGenerator/BitcoinCashAddress.cs create mode 100644 QRCoder/PayloadGenerator/BitcoinLikeCryptoCurrencyAddress.cs create mode 100644 QRCoder/PayloadGenerator/Bookmark.cs create mode 100644 QRCoder/PayloadGenerator/CalendarEvent.cs create mode 100644 QRCoder/PayloadGenerator/ContactData.cs create mode 100644 QRCoder/PayloadGenerator/Geolocation.cs create mode 100644 QRCoder/PayloadGenerator/Girocode.cs create mode 100644 QRCoder/PayloadGenerator/LitecoinAddress.cs create mode 100644 QRCoder/PayloadGenerator/MMS.cs create mode 100644 QRCoder/PayloadGenerator/Mail.cs create mode 100644 QRCoder/PayloadGenerator/MoneroTransaction.cs create mode 100644 QRCoder/PayloadGenerator/OneTimePassword.cs create mode 100644 QRCoder/PayloadGenerator/Payload.cs create mode 100644 QRCoder/PayloadGenerator/PhoneNumber.cs create mode 100644 QRCoder/PayloadGenerator/RussiaPaymentOrder.cs create mode 100644 QRCoder/PayloadGenerator/SMS.cs create mode 100644 QRCoder/PayloadGenerator/ShadowSocksConfig.cs create mode 100644 QRCoder/PayloadGenerator/SkypeCall.cs create mode 100644 QRCoder/PayloadGenerator/SlovenianUpnQr.cs create mode 100644 QRCoder/PayloadGenerator/SwissQrCode.cs create mode 100644 QRCoder/PayloadGenerator/Url.cs create mode 100644 QRCoder/PayloadGenerator/WhatsAppMessage.cs create mode 100644 QRCoder/PayloadGenerator/WiFi.cs rename QRCoder/{QRCodeGenerator.AlignmentPattern.cs => QRCodeGenerator/AlignmentPattern.cs} (100%) rename QRCoder/{QRCodeGenerator.CodewordBlock.cs => QRCodeGenerator/CodewordBlock.cs} (100%) rename QRCoder/{QRCodeGenerator.ECCInfo.cs => QRCodeGenerator/ECCInfo.cs} (100%) rename QRCoder/{QRCodeGenerator.ECCLevel.cs => QRCodeGenerator/ECCLevel.cs} (100%) rename QRCoder/{QRCodeGenerator.EciMode.cs => QRCodeGenerator/EciMode.cs} (100%) rename QRCoder/{QRCodeGenerator.EncodingMode.cs => QRCodeGenerator/EncodingMode.cs} (100%) rename QRCoder/{QRCodeGenerator.ModulePlacer.BlockedModules.cs => QRCodeGenerator/ModulePlacer.BlockedModules.cs} (100%) rename QRCoder/{QRCodeGenerator.ModulePlacer.MaskPattern.cs => QRCodeGenerator/ModulePlacer.MaskPattern.cs} (100%) rename QRCoder/{QRCodeGenerator.ModulePlacer.cs => QRCodeGenerator/ModulePlacer.cs} (100%) rename QRCoder/{QRCodeGenerator.Point.cs => QRCodeGenerator/Point.cs} (100%) rename QRCoder/{QRCodeGenerator.Polynom.cs => QRCodeGenerator/Polynom.cs} (100%) rename QRCoder/{QRCodeGenerator.PolynomItem.cs => QRCodeGenerator/PolynomItem.cs} (100%) rename QRCoder/{QRCodeGenerator.Rectangle.cs => QRCodeGenerator/Rectangle.cs} (100%) rename QRCoder/{QRCodeGenerator.VersionInfo.cs => QRCodeGenerator/VersionInfo.cs} (100%) rename QRCoder/{QRCodeGenerator.VersionInfoDetails.cs => QRCodeGenerator/VersionInfoDetails.cs} (100%) diff --git a/QRCoder/PayloadGenerator.cs b/QRCoder/PayloadGenerator.cs index 9fcbeec1..5a253daf 100644 --- a/QRCoder/PayloadGenerator.cs +++ b/QRCoder/PayloadGenerator.cs @@ -11,3061 +11,8 @@ namespace QRCoder { - public static class PayloadGenerator + public static partial class PayloadGenerator { - public abstract class Payload - { - public virtual int Version { get { return -1; } } - public virtual QRCodeGenerator.ECCLevel EccLevel { get { return QRCodeGenerator.ECCLevel.Default; } } - public virtual QRCodeGenerator.EciMode EciMode { get { return QRCodeGenerator.EciMode.Default; } } - public abstract override string ToString(); - } - - public class WiFi : Payload - { - private readonly string ssid, password, authenticationMode; - private readonly bool isHiddenSsid; - - /// - /// Generates a WiFi payload. Scanned by a QR Code scanner app, the device will connect to the WiFi. - /// - /// SSID of the WiFi network - /// Password of the WiFi network - /// Authentification mode (WEP, WPA, WPA2) - /// Set flag, if the WiFi network hides its SSID - /// Set flag, if ssid/password is delivered as HEX string. Note: May not be supported on iOS devices. - public WiFi(string ssid, string password, Authentication authenticationMode, bool isHiddenSSID = false, bool escapeHexStrings = true) - { - this.ssid = EscapeInput(ssid); - this.ssid = escapeHexStrings && isHexStyle(this.ssid) ? "\"" + this.ssid + "\"" : this.ssid; - this.password = EscapeInput(password); - this.password = escapeHexStrings && isHexStyle(this.password) ? "\"" + this.password + "\"" : this.password; - this.authenticationMode = authenticationMode.ToString(); - this.isHiddenSsid = isHiddenSSID; - } - - public override string ToString() - { - return - $"WIFI:T:{this.authenticationMode};S:{this.ssid};P:{this.password};{(this.isHiddenSsid ? "H:true" : string.Empty)};"; - } - - public enum Authentication - { - WEP, - WPA, - nopass, - WPA2 - } - } - - public class Mail : Payload - { - private readonly string mailReceiver, subject, message; - private readonly MailEncoding encoding; - - - /// - /// Creates an email payload with subject and message/text - /// - /// Receiver's email address - /// Subject line of the email - /// Message content of the email - /// Payload encoding type. Choose dependent on your QR Code scanner app. - public Mail(string mailReceiver = null, string subject = null, string message = null, MailEncoding encoding = MailEncoding.MAILTO) - { - this.mailReceiver = mailReceiver; - this.subject = subject; - this.message = message; - this.encoding = encoding; - } - - public override string ToString() - { - var returnVal = string.Empty; - switch (this.encoding) - { - case MailEncoding.MAILTO: - var parts = new List(); - if (!string.IsNullOrEmpty(this.subject)) - parts.Add("subject=" + Uri.EscapeDataString(this.subject)); - if (!string.IsNullOrEmpty(this.message)) - parts.Add("body=" + Uri.EscapeDataString(this.message)); - var queryString = parts.Any() ? $"?{string.Join("&", parts.ToArray())}" : ""; - returnVal = $"mailto:{this.mailReceiver}{queryString}"; - break; - case MailEncoding.MATMSG: - returnVal = $"MATMSG:TO:{this.mailReceiver};SUB:{EscapeInput(this.subject)};BODY:{EscapeInput(this.message)};;"; - break; - case MailEncoding.SMTP: - returnVal = $"SMTP:{this.mailReceiver}:{EscapeInput(this.subject, true)}:{EscapeInput(this.message, true)}"; - break; - } - return returnVal; - } - - public enum MailEncoding - { - MAILTO, - MATMSG, - SMTP - } - } - - public class SMS : Payload - { - private readonly string number, subject; - private readonly SMSEncoding encoding; - - /// - /// Creates a SMS payload without text - /// - /// Receiver phone number - /// Encoding type - public SMS(string number, SMSEncoding encoding = SMSEncoding.SMS) - { - this.number = number; - this.subject = string.Empty; - this.encoding = encoding; - } - - /// - /// Creates a SMS payload with text (subject) - /// - /// Receiver phone number - /// Text of the SMS - /// Encoding type - public SMS(string number, string subject, SMSEncoding encoding = SMSEncoding.SMS) - { - this.number = number; - this.subject = subject; - this.encoding = encoding; - } - - public override string ToString() - { - var returnVal = string.Empty; - switch (this.encoding) - { - case SMSEncoding.SMS: - var queryString = string.Empty; - if (!string.IsNullOrEmpty(this.subject)) - queryString = $"?body={Uri.EscapeDataString(this.subject)}"; - returnVal = $"sms:{this.number}{queryString}"; - break; - case SMSEncoding.SMS_iOS: - var queryStringiOS = string.Empty; - if (!string.IsNullOrEmpty(this.subject)) - queryStringiOS = $";body={Uri.EscapeDataString(this.subject)}"; - returnVal = $"sms:{this.number}{queryStringiOS}"; - break; - case SMSEncoding.SMSTO: - returnVal = $"SMSTO:{this.number}:{this.subject}"; - break; - } - return returnVal; - } - - public enum SMSEncoding - { - SMS, - SMSTO, - SMS_iOS - } - } - - public class MMS : Payload - { - private readonly string number, subject; - private readonly MMSEncoding encoding; - - /// - /// Creates a MMS payload without text - /// - /// Receiver phone number - /// Encoding type - public MMS(string number, MMSEncoding encoding = MMSEncoding.MMS) - { - this.number = number; - this.subject = string.Empty; - this.encoding = encoding; - } - - /// - /// Creates a MMS payload with text (subject) - /// - /// Receiver phone number - /// Text of the MMS - /// Encoding type - public MMS(string number, string subject, MMSEncoding encoding = MMSEncoding.MMS) - { - this.number = number; - this.subject = subject; - this.encoding = encoding; - } - - public override string ToString() - { - var returnVal = string.Empty; - switch (this.encoding) - { - case MMSEncoding.MMSTO: - var queryStringMmsTo = string.Empty; - if (!string.IsNullOrEmpty(this.subject)) - queryStringMmsTo = $"?subject={Uri.EscapeDataString(this.subject)}"; - returnVal = $"mmsto:{this.number}{queryStringMmsTo}"; - break; - case MMSEncoding.MMS: - var queryStringMms = string.Empty; - if (!string.IsNullOrEmpty(this.subject)) - queryStringMms = $"?body={Uri.EscapeDataString(this.subject)}"; - returnVal = $"mms:{this.number}{queryStringMms}"; - break; - } - return returnVal; - } - - public enum MMSEncoding - { - MMS, - MMSTO - } - } - - public class Geolocation : Payload - { - private readonly string latitude, longitude; - private readonly GeolocationEncoding encoding; - - /// - /// Generates a geo location payload. Supports raw location (GEO encoding) or Google Maps link (GoogleMaps encoding) - /// - /// Latitude with . as splitter - /// Longitude with . as splitter - /// Encoding type - GEO or GoogleMaps - public Geolocation(string latitude, string longitude, GeolocationEncoding encoding = GeolocationEncoding.GEO) - { - this.latitude = latitude.Replace(",","."); - this.longitude = longitude.Replace(",", "."); - this.encoding = encoding; - } - - public override string ToString() - { - switch (this.encoding) - { - case GeolocationEncoding.GEO: - return $"geo:{this.latitude},{this.longitude}"; - case GeolocationEncoding.GoogleMaps: - return $"http://maps.google.com/maps?q={this.latitude},{this.longitude}"; - default: - return "geo:"; - } - } - - public enum GeolocationEncoding - { - GEO, - GoogleMaps - } - } - - public class PhoneNumber : Payload - { - private readonly string number; - - /// - /// Generates a phone call payload - /// - /// Phonenumber of the receiver - public PhoneNumber(string number) - { - this.number = number; - } - - public override string ToString() - { - return $"tel:{this.number}"; - } - } - - public class SkypeCall : Payload - { - private readonly string skypeUsername; - - /// - /// Generates a Skype call payload - /// - /// Skype username which will be called - public SkypeCall(string skypeUsername) - { - this.skypeUsername = skypeUsername; - } - - public override string ToString() - { - return $"skype:{this.skypeUsername}?call"; - } - } - - public class Url : Payload - { - private readonly string url; - - /// - /// Generates a link. If the protocol is not specified, the http protocol will be added. - /// - /// Link url target - public Url(string url) - { - this.url = url; - } - - public override string ToString() - { - return (!this.url.StartsWith("http", StringComparison.OrdinalIgnoreCase) ? "http://" + this.url : this.url); - } - } - - - public class WhatsAppMessage : Payload - { - private readonly string number, message; - - /// - /// Let's you compose a WhatApp message and send it the receiver number. - /// - /// Receiver phone number where the is a full phone number in international format. - /// Omit any zeroes, brackets, or dashes when adding the phone number in international format. - /// Use: 1XXXXXXXXXX | Don't use: +001-(XXX)XXXXXXX - /// - /// The message - public WhatsAppMessage(string number, string message) - { - this.number = number; - this.message = message; - } - - /// - /// Let's you compose a WhatApp message. When scanned the user is asked to choose a contact who will receive the message. - /// - /// The message - public WhatsAppMessage(string message) - { - this.number = string.Empty; - this.message = message; - } - - public override string ToString() - { - var cleanedPhone = Regex.Replace(this.number, @"^[0+]+|[ ()-]", string.Empty); - return ($"https://wa.me/{cleanedPhone}?text={Uri.EscapeDataString(message)}"); - } - } - - - public class Bookmark : Payload - { - private readonly string url, title; - - /// - /// Generates a bookmark payload. Scanned by an QR Code reader, this one creates a browser bookmark. - /// - /// Url of the bookmark - /// Title of the bookmark - public Bookmark(string url, string title) - { - this.url = EscapeInput(url); - this.title = EscapeInput(title); - } - - public override string ToString() - { - return $"MEBKM:TITLE:{this.title};URL:{this.url};;"; - } - } - - public class ContactData : Payload - { - private readonly string firstname; - private readonly string lastname; - private readonly string nickname; - private readonly string org; - private readonly string orgTitle; - private readonly string phone; - private readonly string mobilePhone; - private readonly string workPhone; - private readonly string email; - private readonly DateTime? birthday; - private readonly string website; - private readonly string street; - private readonly string houseNumber; - private readonly string city; - private readonly string zipCode; - private readonly string stateRegion; - private readonly string country; - private readonly string note; - private readonly ContactOutputType outputType; - private readonly AddressOrder addressOrder; - - - /// - /// Generates a vCard or meCard contact dataset - /// - /// Payload output type - /// The firstname - /// The lastname - /// The displayname - /// Normal phone number - /// Mobile phone - /// Office phone number - /// E-Mail address - /// Birthday - /// Website / Homepage - /// Street - /// Housenumber - /// City - /// State or Region - /// Zip code - /// Country - /// The address order format to use - /// Memo text / notes - /// Organisation/Company - /// Organisation/Company Title - public ContactData(ContactOutputType outputType, string firstname, string lastname, string nickname = null, string phone = null, string mobilePhone = null, string workPhone = null, string email = null, DateTime? birthday = null, string website = null, string street = null, string houseNumber = null, string city = null, string zipCode = null, string country = null, string note = null, string stateRegion = null, AddressOrder addressOrder = AddressOrder.Default, string org = null, string orgTitle = null) - { - this.firstname = firstname; - this.lastname = lastname; - this.nickname = nickname; - this.org = org; - this.orgTitle = orgTitle; - this.phone = phone; - this.mobilePhone = mobilePhone; - this.workPhone = workPhone; - this.email = email; - this.birthday = birthday; - this.website = website; - this.street = street; - this.houseNumber = houseNumber; - this.city = city; - this.stateRegion = stateRegion; - this.zipCode = zipCode; - this.country = country; - this.addressOrder = addressOrder; - this.note = note; - this.outputType = outputType; - } - - public override string ToString() - { - string payload = string.Empty; - if (outputType == ContactOutputType.MeCard) - { - payload += "MECARD+\r\n"; - if (!string.IsNullOrEmpty(firstname) && !string.IsNullOrEmpty(lastname)) - payload += $"N:{lastname}, {firstname}\r\n"; - else if (!string.IsNullOrEmpty(firstname) || !string.IsNullOrEmpty(lastname)) - payload += $"N:{firstname}{lastname}\r\n"; - if (!string.IsNullOrEmpty(org)) - payload += $"ORG:{org}\r\n"; - if (!string.IsNullOrEmpty(orgTitle)) - payload += $"TITLE:{orgTitle}\r\n"; - if (!string.IsNullOrEmpty(phone)) - payload += $"TEL:{phone}\r\n"; - if (!string.IsNullOrEmpty(mobilePhone)) - payload += $"TEL:{mobilePhone}\r\n"; - if (!string.IsNullOrEmpty(workPhone)) - payload += $"TEL:{workPhone}\r\n"; - if (!string.IsNullOrEmpty(email)) - payload += $"EMAIL:{email}\r\n"; - if (!string.IsNullOrEmpty(note)) - payload += $"NOTE:{note}\r\n"; - if (birthday != null) - payload += $"BDAY:{((DateTime)birthday).ToString("yyyyMMdd")}\r\n"; - string addressString = string.Empty; - if(addressOrder == AddressOrder.Default) - { - addressString = $"ADR:,,{(!string.IsNullOrEmpty(street) ? street + " " : "")}{(!string.IsNullOrEmpty(houseNumber) ? houseNumber : "")},{(!string.IsNullOrEmpty(zipCode) ? zipCode : "")},{(!string.IsNullOrEmpty(city) ? city : "")},{(!string.IsNullOrEmpty(stateRegion) ? stateRegion : "")},{(!string.IsNullOrEmpty(country) ? country : "")}\r\n"; - } - else - { - addressString = $"ADR:,,{(!string.IsNullOrEmpty(houseNumber) ? houseNumber + " " : "")}{(!string.IsNullOrEmpty(street) ? street : "")},{(!string.IsNullOrEmpty(city) ? city : "")},{(!string.IsNullOrEmpty(stateRegion) ? stateRegion : "")},{(!string.IsNullOrEmpty(zipCode) ? zipCode : "")},{(!string.IsNullOrEmpty(country) ? country : "")}\r\n"; - } - payload += addressString; - if (!string.IsNullOrEmpty(website)) - payload += $"URL:{website}\r\n"; - if (!string.IsNullOrEmpty(nickname)) - payload += $"NICKNAME:{nickname}\r\n"; - payload = payload.Trim(new char[] { '\r', '\n' }); - } - else - { - var version = outputType.ToString().Substring(5); - if (version.Length > 1) - version = version.Insert(1, "."); - else - version += ".0"; - - payload += "BEGIN:VCARD\r\n"; - payload += $"VERSION:{version}\r\n"; - - payload += $"N:{(!string.IsNullOrEmpty(lastname) ? lastname : "")};{(!string.IsNullOrEmpty(firstname) ? firstname : "")};;;\r\n"; - payload += $"FN:{(!string.IsNullOrEmpty(firstname) ? firstname + " " : "")}{(!string.IsNullOrEmpty(lastname) ? lastname : "")}\r\n"; - if (!string.IsNullOrEmpty(org)) - { - payload += $"ORG:" + org + "\r\n"; - } - if (!string.IsNullOrEmpty(orgTitle)) - { - payload += $"TITLE:" + orgTitle + "\r\n"; - } - if (!string.IsNullOrEmpty(phone)) - { - payload += $"TEL;"; - if (outputType == ContactOutputType.VCard21) - payload += $"HOME;VOICE:{phone}"; - else if (outputType == ContactOutputType.VCard3) - payload += $"TYPE=HOME,VOICE:{phone}"; - else - payload += $"TYPE=home,voice;VALUE=uri:tel:{phone}"; - payload += "\r\n"; - } - - if (!string.IsNullOrEmpty(mobilePhone)) - { - payload += $"TEL;"; - if (outputType == ContactOutputType.VCard21) - payload += $"HOME;CELL:{mobilePhone}"; - else if (outputType == ContactOutputType.VCard3) - payload += $"TYPE=HOME,CELL:{mobilePhone}"; - else - payload += $"TYPE=home,cell;VALUE=uri:tel:{mobilePhone}"; - payload += "\r\n"; - } - - if (!string.IsNullOrEmpty(workPhone)) - { - payload += $"TEL;"; - if (outputType == ContactOutputType.VCard21) - payload += $"WORK;VOICE:{workPhone}"; - else if (outputType == ContactOutputType.VCard3) - payload += $"TYPE=WORK,VOICE:{workPhone}"; - else - payload += $"TYPE=work,voice;VALUE=uri:tel:{workPhone}"; - payload += "\r\n"; - } - - - payload += "ADR;"; - if (outputType == ContactOutputType.VCard21) - payload += "HOME;PREF:"; - else if (outputType == ContactOutputType.VCard3) - payload += "TYPE=HOME,PREF:"; - else - payload += "TYPE=home,pref:"; - string addressString = string.Empty; - if(addressOrder == AddressOrder.Default) - { - addressString = $";;{(!string.IsNullOrEmpty(street) ? street + " " : "")}{(!string.IsNullOrEmpty(houseNumber) ? houseNumber : "")};{(!string.IsNullOrEmpty(zipCode) ? zipCode : "")};{(!string.IsNullOrEmpty(city) ? city : "")};{(!string.IsNullOrEmpty(stateRegion) ? stateRegion : "")};{(!string.IsNullOrEmpty(country) ? country : "")}\r\n"; - } - else - { - addressString = $";;{(!string.IsNullOrEmpty(houseNumber) ? houseNumber + " " : "")}{(!string.IsNullOrEmpty(street) ? street : "")};{(!string.IsNullOrEmpty(city) ? city : "")};{(!string.IsNullOrEmpty(stateRegion) ? stateRegion : "")};{(!string.IsNullOrEmpty(zipCode) ? zipCode : "")};{(!string.IsNullOrEmpty(country) ? country : "")}\r\n"; - } - payload += addressString; - - if (birthday != null) - payload += $"BDAY:{((DateTime)birthday).ToString("yyyyMMdd")}\r\n"; - if (!string.IsNullOrEmpty(website)) - payload += $"URL:{website}\r\n"; - if (!string.IsNullOrEmpty(email)) - payload += $"EMAIL:{email}\r\n"; - if (!string.IsNullOrEmpty(note)) - payload += $"NOTE:{note}\r\n"; - if (outputType != ContactOutputType.VCard21 && !string.IsNullOrEmpty(nickname)) - payload += $"NICKNAME:{nickname}\r\n"; - - payload += "END:VCARD"; - } - - return payload; - } - - /// - /// Possible output types. Either vCard 2.1, vCard 3.0, vCard 4.0 or MeCard. - /// - public enum ContactOutputType - { - MeCard, - VCard21, - VCard3, - VCard4 - } - - - /// - /// define the address format - /// Default: European format, ([Street] [House Number] and [Postal Code] [City] - /// Reversed: North American and others format ([House Number] [Street] and [City] [Postal Code]) - /// - public enum AddressOrder - { - Default, - Reversed - } - } - - public class BitcoinLikeCryptoCurrencyAddress : Payload - { - private readonly BitcoinLikeCryptoCurrencyType currencyType; - private readonly string address, label, message; - private readonly double? amount; - - /// - /// Generates a Bitcoin like cryptocurrency payment payload. QR Codes with this payload can open a payment app. - /// - /// Bitcoin like cryptocurrency address of the payment receiver - /// Bitcoin like cryptocurrency address of the payment receiver - /// Amount of coins to transfer - /// Reference label - /// Referece text aka message - public BitcoinLikeCryptoCurrencyAddress(BitcoinLikeCryptoCurrencyType currencyType, string address, double? amount, string label = null, string message = null) - { - this.currencyType = currencyType; - this.address = address; - - if (!string.IsNullOrEmpty(label)) - { - this.label = Uri.EscapeDataString(label); - } - - if (!string.IsNullOrEmpty(message)) - { - this.message = Uri.EscapeDataString(message); - } - - this.amount = amount; - } - - public override string ToString() - { - string query = null; - - var queryValues = new KeyValuePair[]{ - new KeyValuePair(nameof(label), label), - new KeyValuePair(nameof(message), message), - new KeyValuePair(nameof(amount), amount.HasValue ? amount.Value.ToString("#.########", CultureInfo.InvariantCulture) : null) - }; - - if (queryValues.Any(keyPair => !string.IsNullOrEmpty(keyPair.Value))) - { - query = "?" + string.Join("&", queryValues - .Where(keyPair => !string.IsNullOrEmpty(keyPair.Value)) - .Select(keyPair => $"{keyPair.Key}={keyPair.Value}") - .ToArray()); - } - - return $"{Enum.GetName(typeof(BitcoinLikeCryptoCurrencyType), currencyType).ToLower()}:{address}{query}"; - } - - public enum BitcoinLikeCryptoCurrencyType - { - Bitcoin, - BitcoinCash, - Litecoin - } - } - - public class BitcoinAddress : BitcoinLikeCryptoCurrencyAddress - { - public BitcoinAddress(string address, double? amount, string label = null, string message = null) - : base(BitcoinLikeCryptoCurrencyType.Bitcoin, address, amount, label, message) { } - } - - public class BitcoinCashAddress : BitcoinLikeCryptoCurrencyAddress - { - public BitcoinCashAddress(string address, double? amount, string label = null, string message = null) - : base(BitcoinLikeCryptoCurrencyType.BitcoinCash, address, amount, label, message) { } - } - - public class LitecoinAddress : BitcoinLikeCryptoCurrencyAddress - { - public LitecoinAddress(string address, double? amount, string label = null, string message = null) - : base(BitcoinLikeCryptoCurrencyType.Litecoin, address, amount, label, message) { } - } - - public class SwissQrCode : Payload - { - //Keep in mind, that the ECC level has to be set to "M" when generating a SwissQrCode! - //SwissQrCode specification: - // - (de) https://www.paymentstandards.ch/dam/downloads/ig-qr-bill-de.pdf - // - (en) https://www.paymentstandards.ch/dam/downloads/ig-qr-bill-en.pdf - //Changes between version 1.0 and 2.0: https://www.paymentstandards.ch/dam/downloads/change-documentation-qrr-de.pdf - - private readonly string br = "\r\n"; - private readonly string alternativeProcedure1, alternativeProcedure2; - private readonly Iban iban; - private readonly decimal? amount; - private readonly Contact creditor, ultimateCreditor, debitor; - private readonly Currency currency; - private readonly DateTime? requestedDateOfPayment; - private readonly Reference reference; - private readonly AdditionalInformation additionalInformation; - - public override QRCodeGenerator.ECCLevel EccLevel { get { return QRCodeGenerator.ECCLevel.M; } } - public override QRCodeGenerator.EciMode EciMode { get { return QRCodeGenerator.EciMode.Utf8; } } - - /// - /// Generates the payload for a SwissQrCode v2.0. (Don't forget to use ECC-Level=M, EncodingMode=UTF-8 and to set the Swiss flag icon to the final QR code.) - /// - /// IBAN object - /// Currency (either EUR or CHF) - /// Creditor (payee) information - /// Reference information - /// Debitor (payer) information - /// Amount - /// Requested date of debitor's payment - /// Ultimate creditor information (use only in consultation with your bank - for future use only!) - /// Optional command for alternative processing mode - line 1 - /// Optional command for alternative processing mode - line 2 - public SwissQrCode(Iban iban, Currency currency, Contact creditor, Reference reference, AdditionalInformation additionalInformation = null, Contact debitor = null, decimal? amount = null, DateTime? requestedDateOfPayment = null, Contact ultimateCreditor = null, string alternativeProcedure1 = null, string alternativeProcedure2 = null) - { - this.iban = iban; - - this.creditor = creditor; - this.ultimateCreditor = ultimateCreditor; - - this.additionalInformation = additionalInformation != null ? additionalInformation : new AdditionalInformation(); - - if (amount != null && amount.ToString().Length > 12) - throw new SwissQrCodeException("Amount (including decimals) must be shorter than 13 places."); - this.amount = amount; - - this.currency = currency; - this.requestedDateOfPayment = requestedDateOfPayment; - this.debitor = debitor; - - if (iban.IsQrIban && reference.RefType != Reference.ReferenceType.QRR) - throw new SwissQrCodeException("If QR-IBAN is used, you have to choose \"QRR\" as reference type!"); - if (!iban.IsQrIban && reference.RefType == Reference.ReferenceType.QRR) - throw new SwissQrCodeException("If non QR-IBAN is used, you have to choose either \"SCOR\" or \"NON\" as reference type!"); - this.reference = reference; - - if (alternativeProcedure1 != null && alternativeProcedure1.Length > 100) - throw new SwissQrCodeException("Alternative procedure information block 1 must be shorter than 101 chars."); - this.alternativeProcedure1 = alternativeProcedure1; - if (alternativeProcedure2 != null && alternativeProcedure2.Length > 100) - throw new SwissQrCodeException("Alternative procedure information block 2 must be shorter than 101 chars."); - this.alternativeProcedure2 = alternativeProcedure2; - } - - public class AdditionalInformation - { - private readonly string unstructuredMessage, billInformation, trailer; - - /// - /// Creates an additional information object. Both parameters are optional and must be shorter than 141 chars in combination. - /// - /// Unstructured text message - /// Bill information - public AdditionalInformation(string unstructuredMessage = null, string billInformation = null) - { - if (((unstructuredMessage != null ? unstructuredMessage.Length : 0) + (billInformation != null ? billInformation.Length : 0)) > 140) - throw new SwissQrCodeAdditionalInformationException("Unstructured message and bill information must be shorter than 141 chars in total/combined."); - this.unstructuredMessage = unstructuredMessage; - this.billInformation = billInformation; - this.trailer = "EPD"; - } - - public string UnstructureMessage - { - get { return !string.IsNullOrEmpty(unstructuredMessage) ? unstructuredMessage.Replace("\n", "") : null; } - } - - public string BillInformation - { - get { return !string.IsNullOrEmpty(billInformation) ? billInformation.Replace("\n", "") : null; } - } - - public string Trailer - { - get { return trailer; } - } - - - public class SwissQrCodeAdditionalInformationException : Exception - { - public SwissQrCodeAdditionalInformationException() - { - } - - public SwissQrCodeAdditionalInformationException(string message) - : base(message) - { - } - - public SwissQrCodeAdditionalInformationException(string message, Exception inner) - : base(message, inner) - { - } - } - } - - public class Reference - { - private readonly ReferenceType referenceType; - private readonly string reference; - private readonly ReferenceTextType? referenceTextType; - - /// - /// Creates a reference object which must be passed to the SwissQrCode instance - /// - /// Type of the reference (QRR, SCOR or NON) - /// Reference text - /// Type of the reference text (QR-reference or Creditor Reference) - public Reference(ReferenceType referenceType, string reference = null, ReferenceTextType? referenceTextType = null) - { - this.referenceType = referenceType; - this.referenceTextType = referenceTextType; - - if (referenceType == ReferenceType.NON && reference != null) - throw new SwissQrCodeReferenceException("Reference is only allowed when referenceType not equals \"NON\""); - if (referenceType != ReferenceType.NON && reference != null && referenceTextType == null) - throw new SwissQrCodeReferenceException("You have to set an ReferenceTextType when using the reference text."); - if (referenceTextType == ReferenceTextType.QrReference && reference != null && (reference.Length > 27)) - throw new SwissQrCodeReferenceException("QR-references have to be shorter than 28 chars."); - if (referenceTextType == ReferenceTextType.QrReference && reference != null && !Regex.IsMatch(reference, @"^[0-9]+$")) - throw new SwissQrCodeReferenceException("QR-reference must exist out of digits only."); - if (referenceTextType == ReferenceTextType.QrReference && reference != null && !ChecksumMod10(reference)) - throw new SwissQrCodeReferenceException("QR-references is invalid. Checksum error."); - if (referenceTextType == ReferenceTextType.CreditorReferenceIso11649 && reference != null && (reference.Length > 25)) - throw new SwissQrCodeReferenceException("Creditor references (ISO 11649) have to be shorter than 26 chars."); - - this.reference = reference; - } - - public ReferenceType RefType { - get { return referenceType; } - } - - public string ReferenceText - { - get { return !string.IsNullOrEmpty(reference) ? reference.Replace("\n", "") : null; } - } - - /// - /// Reference type. When using a QR-IBAN you have to use either "QRR" or "SCOR" - /// - public enum ReferenceType - { - QRR, - SCOR, - NON - } - - public enum ReferenceTextType - { - QrReference, - CreditorReferenceIso11649 - } - - public class SwissQrCodeReferenceException : Exception - { - public SwissQrCodeReferenceException() - { - } - - public SwissQrCodeReferenceException(string message) - : base(message) - { - } - - public SwissQrCodeReferenceException(string message, Exception inner) - : base(message, inner) - { - } - } - } - - public class Iban - { - private string iban; - private IbanType ibanType; - - /// - /// IBAN object with type information - /// - /// IBAN - /// Type of IBAN (normal or QR-IBAN) - public Iban(string iban, IbanType ibanType) - { - if (ibanType == IbanType.Iban && !IsValidIban(iban)) - throw new SwissQrCodeIbanException("The IBAN entered isn't valid."); - if (ibanType == IbanType.QrIban && !IsValidQRIban(iban)) - throw new SwissQrCodeIbanException("The QR-IBAN entered isn't valid."); - if (!iban.StartsWith("CH") && !iban.StartsWith("LI")) - throw new SwissQrCodeIbanException("The IBAN must start with \"CH\" or \"LI\"."); - this.iban = iban; - this.ibanType = ibanType; - } - - public bool IsQrIban - { - get { return ibanType == IbanType.QrIban; } - } - - public override string ToString() - { - return iban.Replace("-", "").Replace("\n", "").Replace(" ",""); - } - - public enum IbanType - { - Iban, - QrIban - } - - public class SwissQrCodeIbanException : Exception - { - public SwissQrCodeIbanException() - { - } - - public SwissQrCodeIbanException(string message) - : base(message) - { - } - - public SwissQrCodeIbanException(string message, Exception inner) - : base(message, inner) - { - } - } - } - - public class Contact - { - private static readonly HashSet twoLetterCodes = ValidTwoLetterCodes(); - private string br = "\r\n"; - private string name, streetOrAddressline1, houseNumberOrAddressline2, zipCode, city, country; - private AddressType adrType; - - /// - /// Contact type. Can be used for payee, ultimate payee, etc. with address in structured mode (S). - /// - /// Last name or company (optional first name) - /// Zip-/Postcode - /// City name - /// Two-letter country code as defined in ISO 3166-1 - /// Streetname without house number - /// House number - [Obsolete("This constructor is deprecated. Use WithStructuredAddress instead.")] - public Contact(string name, string zipCode, string city, string country, string street = null, string houseNumber = null) : this (name, zipCode, city, country, street, houseNumber, AddressType.StructuredAddress) - { - } - - - /// - /// Contact type. Can be used for payee, ultimate payee, etc. with address in combined mode (K). - /// - /// Last name or company (optional first name) - /// Two-letter country code as defined in ISO 3166-1 - /// Adress line 1 - /// Adress line 2 - [Obsolete("This constructor is deprecated. Use WithCombinedAddress instead.")] - public Contact(string name, string country, string addressLine1, string addressLine2) : this(name, null, null, country, addressLine1, addressLine2, AddressType.CombinedAddress) - { - } - - public static Contact WithStructuredAddress(string name, string zipCode, string city, string country, string street = null, string houseNumber = null) - { - return new Contact(name, zipCode, city, country, street, houseNumber, AddressType.StructuredAddress); - } - - public static Contact WithCombinedAddress(string name, string country, string addressLine1, string addressLine2) - { - return new Contact(name, null, null, country, addressLine1, addressLine2, AddressType.CombinedAddress); - } - - - private Contact(string name, string zipCode, string city, string country, string streetOrAddressline1, string houseNumberOrAddressline2, AddressType addressType) - { - //Pattern extracted from https://qr-validation.iso-payments.ch as explained in https://github.com/codebude/QRCoder/issues/97 - var charsetPattern = @"^([a-zA-Z0-9\.,;:'\ \+\-/\(\)?\*\[\]\{\}\\`´~ ^|]|[!""#%&<>÷=@_$£¡¢¤¥¦§¨©ª«¬®¯°±²³µ¶·¸¹º»¼½¾¿×Ø€]|[àáâäãåāăąçćĉċčďđèéêëēĕėęěĝğġģĥħìíîïĩīĭįıijķĸĺļľŀłñńņňʼnŋòóôöōŏőõŕŗřśŝşšșţťŧțùúûüũūŭůűųŵýÿŷźżžßÀÁÂÄÃÅĀĂĄÇĆĈĊČĎĐÈÉÊËĒĔĖĘĚĜĞĠĢĤĦÌÍÎÏĨĪĬĮİIJĴĵĶĹĻĽĿŁÑŃŅŇŊÒÓÔÖÕŌŎŐŔŖŘŚŜŞŠȘŢŤŦȚÙÚÛÜŨŪŬŮŰŲŴÝŶŸŹŻŽÆÐÞæðøþŒœſ])*$"; - - this.adrType = addressType; - - if (string.IsNullOrEmpty(name)) - throw new SwissQrCodeContactException("Name must not be empty."); - if (name.Length > 70) - throw new SwissQrCodeContactException("Name must be shorter than 71 chars."); - if (!Regex.IsMatch(name, charsetPattern)) - throw new SwissQrCodeContactException($"Name must match the following pattern as defined in pain.001: {charsetPattern}"); - this.name = name; - - if (AddressType.StructuredAddress == this.adrType) - { - if (!string.IsNullOrEmpty(streetOrAddressline1) && (streetOrAddressline1.Length > 70)) - throw new SwissQrCodeContactException("Street must be shorter than 71 chars."); - if (!string.IsNullOrEmpty(streetOrAddressline1) && !Regex.IsMatch(streetOrAddressline1, charsetPattern)) - throw new SwissQrCodeContactException($"Street must match the following pattern as defined in pain.001: {charsetPattern}"); - this.streetOrAddressline1 = streetOrAddressline1; - - if (!string.IsNullOrEmpty(houseNumberOrAddressline2) && houseNumberOrAddressline2.Length > 16) - throw new SwissQrCodeContactException("House number must be shorter than 17 chars."); - this.houseNumberOrAddressline2 = houseNumberOrAddressline2; - } - else - { - if (!string.IsNullOrEmpty(streetOrAddressline1) && (streetOrAddressline1.Length > 70)) - throw new SwissQrCodeContactException("Address line 1 must be shorter than 71 chars."); - if (!string.IsNullOrEmpty(streetOrAddressline1) && !Regex.IsMatch(streetOrAddressline1, charsetPattern)) - throw new SwissQrCodeContactException($"Address line 1 must match the following pattern as defined in pain.001: {charsetPattern}"); - this.streetOrAddressline1 = streetOrAddressline1; - - if (string.IsNullOrEmpty(houseNumberOrAddressline2)) - throw new SwissQrCodeContactException("Address line 2 must be provided for combined addresses (address line-based addresses)."); - if (!string.IsNullOrEmpty(houseNumberOrAddressline2) && (houseNumberOrAddressline2.Length > 70)) - throw new SwissQrCodeContactException("Address line 2 must be shorter than 71 chars."); - if (!string.IsNullOrEmpty(houseNumberOrAddressline2) && !Regex.IsMatch(houseNumberOrAddressline2, charsetPattern)) - throw new SwissQrCodeContactException($"Address line 2 must match the following pattern as defined in pain.001: {charsetPattern}"); - this.houseNumberOrAddressline2 = houseNumberOrAddressline2; - } - - if (AddressType.StructuredAddress == this.adrType) { - if (string.IsNullOrEmpty(zipCode)) - throw new SwissQrCodeContactException("Zip code must not be empty."); - if (zipCode.Length > 16) - throw new SwissQrCodeContactException("Zip code must be shorter than 17 chars."); - if (!Regex.IsMatch(zipCode, charsetPattern)) - throw new SwissQrCodeContactException($"Zip code must match the following pattern as defined in pain.001: {charsetPattern}"); - this.zipCode = zipCode; - - if (string.IsNullOrEmpty(city)) - throw new SwissQrCodeContactException("City must not be empty."); - if (city.Length > 35) - throw new SwissQrCodeContactException("City name must be shorter than 36 chars."); - if (!Regex.IsMatch(city, charsetPattern)) - throw new SwissQrCodeContactException($"City name must match the following pattern as defined in pain.001: {charsetPattern}"); - this.city = city; - } - else - { - this.zipCode = this.city = string.Empty; - } - - if (!IsValidTwoLetterCode(country)) - throw new SwissQrCodeContactException("Country must be a valid \"two letter\" country code as defined by ISO 3166-1, but it isn't."); - - this.country = country; - } - - private static bool IsValidTwoLetterCode(string code) => twoLetterCodes.Contains(code); - - private static HashSet ValidTwoLetterCodes() - { - string[] codes = new string[]{ "AF", "AL", "DZ", "AS", "AD", "AO", "AI", "AQ", "AG", "AR", "AM", "AW", "AU", "AT", "AZ", "BS", "BH", "BD", "BB", "BY", "BE", "BZ", "BJ", "BM", "BT", "BO", "BQ", "BA", "BW", "BV", "BR", "IO", "BN", "BG", "BF", "BI", "CV", "KH", "CM", "CA", "KY", "CF", "TD", "CL", "CN", "CX", "CC", "CO", "KM", "CG", "CD", "CK", "CR", "CI", "HR", "CU", "CW", "CY", "CZ", "DK", "DJ", "DM", "DO", "EC", "EG", "SV", "GQ", "ER", "EE", "SZ", "ET", "FK", "FO", "FJ", "FI", "FR", "GF", "PF", "TF", "GA", "GM", "GE", "DE", "GH", "GI", "GR", "GL", "GD", "GP", "GU", "GT", "GG", "GN", "GW", "GY", "HT", "HM", "VA", "HN", "HK", "HU", "IS", "IN", "ID", "IR", "IQ", "IE", "IM", "IL", "IT", "JM", "JP", "JE", "JO", "KZ", "KE", "KI", "KP", "KR", "KW", "KG", "LA", "LV", "LB", "LS", "LR", "LY", "LI", "LT", "LU", "MO", "MG", "MW", "MY", "MV", "ML", "MT", "MH", "MQ", "MR", "MU", "YT", "MX", "FM", "MD", "MC", "MN", "ME", "MS", "MA", "MZ", "MM", "NA", "NR", "NP", "NL", "NC", "NZ", "NI", "NE", "NG", "NU", "NF", "MP", "MK", "NO", "OM", "PK", "PW", "PS", "PA", "PG", "PY", "PE", "PH", "PN", "PL", "PT", "PR", "QA", "RE", "RO", "RU", "RW", "BL", "SH", "KN", "LC", "MF", "PM", "VC", "WS", "SM", "ST", "SA", "SN", "RS", "SC", "SL", "SG", "SX", "SK", "SI", "SB", "SO", "ZA", "GS", "SS", "ES", "LK", "SD", "SR", "SJ", "SE", "CH", "SY", "TW", "TJ", "TZ", "TH", "TL", "TG", "TK", "TO", "TT", "TN", "TR", "TM", "TC", "TV", "UG", "UA", "AE", "GB", "US", "UM", "UY", "UZ", "VU", "VE", "VN", "VG", "VI", "WF", "EH", "YE", "ZM", "ZW", "AX", "XK" }; - return new HashSet(codes, StringComparer.OrdinalIgnoreCase); - } - - public override string ToString() - { - string contactData = $"{(AddressType.StructuredAddress == adrType ? "S" : "K")}{br}"; //AdrTp - contactData += name.Replace("\n", "") + br; //Name - contactData += (!string.IsNullOrEmpty(streetOrAddressline1) ? streetOrAddressline1.Replace("\n","") : string.Empty) + br; //StrtNmOrAdrLine1 - contactData += (!string.IsNullOrEmpty(houseNumberOrAddressline2) ? houseNumberOrAddressline2.Replace("\n", "") : string.Empty) + br; //BldgNbOrAdrLine2 - contactData += zipCode.Replace("\n", "") + br; //PstCd - contactData += city.Replace("\n", "") + br; //TwnNm - contactData += country + br; //Ctry - return contactData; - } - - public enum AddressType - { - StructuredAddress, - CombinedAddress - } - - public class SwissQrCodeContactException : Exception - { - public SwissQrCodeContactException() - { - } - - public SwissQrCodeContactException(string message) - : base(message) - { - } - - public SwissQrCodeContactException(string message, Exception inner) - : base(message, inner) - { - } - } - } - - public override string ToString() - { - //Header "logical" element - var SwissQrCodePayload = "SPC" + br; //QRType - SwissQrCodePayload += "0200" + br; //Version - SwissQrCodePayload += "1" + br; //Coding - - //CdtrInf "logical" element - SwissQrCodePayload += iban.ToString() + br; //IBAN - - - //Cdtr "logical" element - SwissQrCodePayload += creditor.ToString(); - - //UltmtCdtr "logical" element - //Since version 2.0 ultimate creditor was marked as "for future use" and has to be delivered empty in any case! - SwissQrCodePayload += string.Concat(Enumerable.Repeat(br, 7).ToArray()); - - //CcyAmtDate "logical" element - //Amoutn has to use . as decimal seperator in any case. See https://www.paymentstandards.ch/dam/downloads/ig-qr-bill-en.pdf page 27. - SwissQrCodePayload += (amount != null ? $"{amount:0.00}".Replace(",", ".") : string.Empty) + br; //Amt - SwissQrCodePayload += currency + br; //Ccy - //Removed in S-QR version 2.0 - //SwissQrCodePayload += (requestedDateOfPayment != null ? ((DateTime)requestedDateOfPayment).ToString("yyyy-MM-dd") : string.Empty) + br; //ReqdExctnDt - - //UltmtDbtr "logical" element - if (debitor != null) - SwissQrCodePayload += debitor.ToString(); - else - SwissQrCodePayload += string.Concat(Enumerable.Repeat(br, 7).ToArray()); - - - //RmtInf "logical" element - SwissQrCodePayload += reference.RefType.ToString() + br; //Tp - SwissQrCodePayload += (!string.IsNullOrEmpty(reference.ReferenceText) ? reference.ReferenceText : string.Empty) + br; //Ref - - - //AddInf "logical" element - SwissQrCodePayload += (!string.IsNullOrEmpty(additionalInformation.UnstructureMessage) ? additionalInformation.UnstructureMessage : string.Empty) + br; //Ustrd - SwissQrCodePayload += additionalInformation.Trailer + br; //Trailer - // Bugfix PR #399 If BillInformation is empty, insert no linebreak - SwissQrCodePayload += (!string.IsNullOrEmpty(additionalInformation.BillInformation) ? additionalInformation.BillInformation + br : string.Empty); //StrdBkgInf - - //AltPmtInf "logical" element - if (!string.IsNullOrEmpty(alternativeProcedure1)) - SwissQrCodePayload += alternativeProcedure1.Replace("\n", "") + br; //AltPmt - if (!string.IsNullOrEmpty(alternativeProcedure2)) - SwissQrCodePayload += alternativeProcedure2.Replace("\n", "") + br; //AltPmt - - //S-QR specification 2.0, chapter 4.2.3 - if (SwissQrCodePayload.EndsWith(br)) - SwissQrCodePayload = SwissQrCodePayload.Remove(SwissQrCodePayload.Length - br.Length); - - return SwissQrCodePayload; - } - - - - - /// - /// ISO 4217 currency codes - /// - public enum Currency - { - CHF = 756, - EUR = 978 - } - - public class SwissQrCodeException : Exception - { - public SwissQrCodeException() - { - } - - public SwissQrCodeException(string message) - : base(message) - { - } - - public SwissQrCodeException(string message, Exception inner) - : base(message, inner) - { - } - } - } - - public class Girocode : Payload - { - //Keep in mind, that the ECC level has to be set to "M" when generating a Girocode! - //Girocode specification: http://www.europeanpaymentscouncil.eu/index.cfm/knowledge-bank/epc-documents/quick-response-code-guidelines-to-enable-data-capture-for-the-initiation-of-a-sepa-credit-transfer/epc069-12-quick-response-code-guidelines-to-enable-data-capture-for-the-initiation-of-a-sepa-credit-transfer1/ - - private string br = "\n"; - private readonly string iban, bic, name, purposeOfCreditTransfer, remittanceInformation, messageToGirocodeUser; - private readonly decimal amount; - private readonly GirocodeVersion version; - private readonly GirocodeEncoding encoding; - private readonly TypeOfRemittance typeOfRemittance; - - public override QRCodeGenerator.ECCLevel EccLevel { get { return QRCodeGenerator.ECCLevel.M; } } - - /// - /// Generates the payload for a Girocode (QR-Code with credit transfer information). - /// Attention: When using Girocode payload, QR code must be generated with ECC level M! - /// - /// Account number of the Beneficiary. Only IBAN is allowed. - /// BIC of the Beneficiary Bank. - /// Name of the Beneficiary. - /// Amount of the Credit Transfer in Euro. - /// (Amount must be more than 0.01 and less than 999999999.99) - /// Remittance Information (Purpose-/reference text). (optional) - /// Type of remittance information. Either structured (e.g. ISO 11649 RF Creditor Reference) and max. 35 chars or unstructured and max. 140 chars. - /// Purpose of the Credit Transfer (optional) - /// Beneficiary to originator information. (optional) - /// Girocode version. Either 001 or 002. Default: 001. - /// Encoding of the Girocode payload. Default: ISO-8859-1 - public Girocode(string iban, string bic, string name, decimal amount, string remittanceInformation = "", TypeOfRemittance typeOfRemittance = TypeOfRemittance.Unstructured, string purposeOfCreditTransfer = "", string messageToGirocodeUser = "", GirocodeVersion version = GirocodeVersion.Version1, GirocodeEncoding encoding = GirocodeEncoding.ISO_8859_1) - { - this.version = version; - this.encoding = encoding; - if (!IsValidIban(iban)) - throw new GirocodeException("The IBAN entered isn't valid."); - this.iban = iban.Replace(" ","").ToUpper(); - if (!IsValidBic(bic)) - throw new GirocodeException("The BIC entered isn't valid."); - this.bic = bic.Replace(" ", "").ToUpper(); - if (name.Length > 70) - throw new GirocodeException("(Payee-)Name must be shorter than 71 chars."); - this.name = name; - if (amount.ToString().Replace(",", ".").Contains(".") && amount.ToString().Replace(",",".").Split('.')[1].TrimEnd('0').Length > 2) - throw new GirocodeException("Amount must have less than 3 digits after decimal point."); - if (amount < 0.01m || amount > 999999999.99m) - throw new GirocodeException("Amount has to at least 0.01 and must be smaller or equal to 999999999.99."); - this.amount = amount; - if (purposeOfCreditTransfer.Length > 4) - throw new GirocodeException("Purpose of credit transfer can only have 4 chars at maximum."); - this.purposeOfCreditTransfer = purposeOfCreditTransfer; - if (typeOfRemittance == TypeOfRemittance.Unstructured && remittanceInformation.Length > 140) - throw new GirocodeException("Unstructured reference texts have to shorter than 141 chars."); - if (typeOfRemittance == TypeOfRemittance.Structured && remittanceInformation.Length > 35) - throw new GirocodeException("Structured reference texts have to shorter than 36 chars."); - this.typeOfRemittance = typeOfRemittance; - this.remittanceInformation = remittanceInformation; - if (messageToGirocodeUser.Length > 70) - throw new GirocodeException("Message to the Girocode-User reader texts have to shorter than 71 chars."); - this.messageToGirocodeUser = messageToGirocodeUser; - } - - public override string ToString() - { - var girocodePayload = "BCD" + br; - girocodePayload += ((version == GirocodeVersion.Version1) ? "001" : "002") + br; - girocodePayload += (int)encoding + 1 + br; - girocodePayload += "SCT" + br; - girocodePayload += bic + br; - girocodePayload += name + br; - girocodePayload += iban + br; - girocodePayload += $"EUR{amount:0.00}".Replace(",",".") + br; - girocodePayload += purposeOfCreditTransfer + br; - girocodePayload += ((typeOfRemittance == TypeOfRemittance.Structured) - ? remittanceInformation - : string.Empty) + br; - girocodePayload += ((typeOfRemittance == TypeOfRemittance.Unstructured) - ? remittanceInformation - : string.Empty) + br; - girocodePayload += messageToGirocodeUser; - - return ConvertStringToEncoding(girocodePayload, encoding.ToString().Replace("_","-")); - } - - public enum GirocodeVersion - { - Version1, - Version2 - } - - public enum TypeOfRemittance - { - Structured, - Unstructured - } - - public enum GirocodeEncoding - { - UTF_8, - ISO_8859_1, - ISO_8859_2, - ISO_8859_4, - ISO_8859_5, - ISO_8859_7, - ISO_8859_10, - ISO_8859_15 - } - - public class GirocodeException : Exception - { - public GirocodeException() - { - } - - public GirocodeException(string message) - : base(message) - { - } - - public GirocodeException(string message, Exception inner) - : base(message, inner) - { - } - } - } - - public class BezahlCode : Payload - { - //BezahlCode specification: http://www.bezahlcode.de/wp-content/uploads/BezahlCode_TechDok.pdf - - private readonly string name, iban, bic, account, bnc, sepaReference, reason, creditorId, mandateId, periodicTimeunit; - private readonly decimal amount; - private readonly int postingKey, periodicTimeunitRotation; - private readonly Currency currency; - private readonly AuthorityType authority; - private readonly DateTime executionDate, dateOfSignature, periodicFirstExecutionDate, periodicLastExecutionDate; - - - /// - /// Constructor for contact data - /// - /// Type of the bank transfer - /// Name of the receiver (Empfänger) - /// Bank account (Kontonummer) - /// Bank institute (Bankleitzahl) - /// IBAN - /// BIC - /// Reason (Verwendungszweck) - public BezahlCode(AuthorityType authority, string name, string account = "", string bnc = "", string iban = "", string bic = "", string reason = "") : this(authority, name, account, bnc, iban, bic, 0, string.Empty, 0, null, null, string.Empty, string.Empty, null, reason, 0, string.Empty, Currency.EUR, null, 1) - { - } - - - /// - /// Constructor for non-SEPA payments - /// - /// Type of the bank transfer - /// Name of the receiver (Empfänger) - /// Bank account (Kontonummer) - /// Bank institute (Bankleitzahl) - /// Amount (Betrag) - /// Unit of intervall for payment ('M' = monthly, 'W' = weekly) - /// Intervall for payment. This value is combined with 'periodicTimeunit' - /// Date of first periodic execution - /// Date of last periodic execution - /// Reason (Verwendungszweck) - /// Transfer Key (Textschlüssel, z.B. Spendenzahlung = 69) - /// Currency (Währung) - /// Execution date (Ausführungsdatum) - public BezahlCode(AuthorityType authority, string name, string account, string bnc, decimal amount, string periodicTimeunit = "", int periodicTimeunitRotation = 0, DateTime? periodicFirstExecutionDate = null, DateTime? periodicLastExecutionDate = null, string reason = "", int postingKey = 0, Currency currency = Currency.EUR, DateTime? executionDate = null) : this(authority, name, account, bnc, string.Empty, string.Empty, amount, periodicTimeunit, periodicTimeunitRotation, periodicFirstExecutionDate, periodicLastExecutionDate, string.Empty, string.Empty, null, reason, postingKey, string.Empty, currency, executionDate, 2) - { - } - - /// - /// Constructor for SEPA payments - /// - /// Type of the bank transfer - /// Name of the receiver (Empfänger) - /// IBAN - /// BIC - /// Amount (Betrag) - /// Unit of intervall for payment ('M' = monthly, 'W' = weekly) - /// Intervall for payment. This value is combined with 'periodicTimeunit' - /// Date of first periodic execution - /// Date of last periodic execution - /// Creditor id (Gläubiger ID) - /// Manadate id (Mandatsreferenz) - /// Signature date (Erteilungsdatum des Mandats) - /// Reason (Verwendungszweck) - /// Transfer Key (Textschlüssel, z.B. Spendenzahlung = 69) - /// SEPA reference (SEPA-Referenz) - /// Currency (Währung) - /// Execution date (Ausführungsdatum) - public BezahlCode(AuthorityType authority, string name, string iban, string bic, decimal amount, string periodicTimeunit = "", int periodicTimeunitRotation = 0, DateTime? periodicFirstExecutionDate = null, DateTime? periodicLastExecutionDate = null, string creditorId = "", string mandateId = "", DateTime? dateOfSignature = null, string reason = "", string sepaReference = "", Currency currency = Currency.EUR, DateTime? executionDate = null) : this(authority, name, string.Empty, string.Empty, iban, bic, amount, periodicTimeunit, periodicTimeunitRotation, periodicFirstExecutionDate, periodicLastExecutionDate, creditorId, mandateId, dateOfSignature, reason, 0, sepaReference, currency, executionDate, 3) - { - } - - - - - /// - /// Generic constructor. Please use specific (non-SEPA or SEPA) constructor - /// - /// Type of the bank transfer - /// Name of the receiver (Empfänger) - /// Bank account (Kontonummer) - /// Bank institute (Bankleitzahl) - /// IBAN - /// BIC - /// Amount (Betrag) - /// Unit of intervall for payment ('M' = monthly, 'W' = weekly) - /// Intervall for payment. This value is combined with 'periodicTimeunit' - /// Date of first periodic execution - /// Date of last periodic execution - /// Creditor id (Gläubiger ID) - /// Manadate id (Mandatsreferenz) - /// Signature date (Erteilungsdatum des Mandats) - /// Reason (Verwendungszweck) - /// Transfer Key (Textschlüssel, z.B. Spendenzahlung = 69) - /// SEPA reference (SEPA-Referenz) - /// Currency (Währung) - /// Execution date (Ausführungsdatum) - /// Only used for internal state handdling - public BezahlCode(AuthorityType authority, string name, string account, string bnc, string iban, string bic, decimal amount, string periodicTimeunit = "", int periodicTimeunitRotation = 0, DateTime? periodicFirstExecutionDate = null, DateTime? periodicLastExecutionDate = null, string creditorId = "", string mandateId = "", DateTime? dateOfSignature = null, string reason = "", int postingKey = 0, string sepaReference = "", Currency currency = Currency.EUR, DateTime? executionDate = null, int internalMode = 0) - { - //Loaded via "contact-constructor" - if (internalMode == 1) - { - if (authority != AuthorityType.contact && authority != AuthorityType.contact_v2) - throw new BezahlCodeException("The constructor without an amount may only ne used with authority types 'contact' and 'contact_v2'."); - if (authority == AuthorityType.contact && (string.IsNullOrEmpty(account) || string.IsNullOrEmpty(bnc))) - throw new BezahlCodeException("When using authority type 'contact' the parameters 'account' and 'bnc' must be set."); - - if (authority != AuthorityType.contact_v2) - { - var oldFilled = (!string.IsNullOrEmpty(account) && !string.IsNullOrEmpty(bnc)); - var newFilled = (!string.IsNullOrEmpty(iban) && !string.IsNullOrEmpty(bic)); - if ((!oldFilled && !newFilled) || (oldFilled && newFilled)) - throw new BezahlCodeException("When using authority type 'contact_v2' either the parameters 'account' and 'bnc' or the parameters 'iban' and 'bic' must be set. Leave the other parameter pair empty."); - } - } - else if (internalMode == 2) - { -#pragma warning disable CS0612 - if (authority != AuthorityType.periodicsinglepayment && authority != AuthorityType.singledirectdebit && authority != AuthorityType.singlepayment) - throw new BezahlCodeException("The constructor with 'account' and 'bnc' may only be used with 'non SEPA' authority types. Either choose another authority type or switch constructor."); - if (authority == AuthorityType.periodicsinglepayment && (string.IsNullOrEmpty(periodicTimeunit) || periodicTimeunitRotation == 0)) - throw new BezahlCodeException("When using 'periodicsinglepayment' as authority type, the parameters 'periodicTimeunit' and 'periodicTimeunitRotation' must be set."); -#pragma warning restore CS0612 - } - else if (internalMode == 3) - { - if (authority != AuthorityType.periodicsinglepaymentsepa && authority != AuthorityType.singledirectdebitsepa && authority != AuthorityType.singlepaymentsepa) - throw new BezahlCodeException("The constructor with 'iban' and 'bic' may only be used with 'SEPA' authority types. Either choose another authority type or switch constructor."); - if (authority == AuthorityType.periodicsinglepaymentsepa && (string.IsNullOrEmpty(periodicTimeunit) || periodicTimeunitRotation == 0)) - throw new BezahlCodeException("When using 'periodicsinglepaymentsepa' as authority type, the parameters 'periodicTimeunit' and 'periodicTimeunitRotation' must be set."); - } - - this.authority = authority; - - if (name.Length > 70) - throw new BezahlCodeException("(Payee-)Name must be shorter than 71 chars."); - this.name = name; - - if (reason.Length > 27) - throw new BezahlCodeException("Reasons texts have to be shorter than 28 chars."); - this.reason = reason; - - var oldWayFilled = (!string.IsNullOrEmpty(account) && !string.IsNullOrEmpty(bnc)); - var newWayFilled = (!string.IsNullOrEmpty(iban) && !string.IsNullOrEmpty(bic)); - - //Non-SEPA payment types -#pragma warning disable CS0612 - if (authority == AuthorityType.periodicsinglepayment || authority == AuthorityType.singledirectdebit || authority == AuthorityType.singlepayment || authority == AuthorityType.contact || (authority == AuthorityType.contact_v2 && oldWayFilled)) - { -#pragma warning restore CS0612 - if (!Regex.IsMatch(account.Replace(" ", ""), @"^[0-9]{1,9}$")) - throw new BezahlCodeException("The account entered isn't valid."); - this.account = account.Replace(" ", "").ToUpper(); - if(!Regex.IsMatch(bnc.Replace(" ", ""), @"^[0-9]{1,9}$")) - throw new BezahlCodeException("The bnc entered isn't valid."); - this.bnc = bnc.Replace(" ", "").ToUpper(); - - if (authority != AuthorityType.contact && authority != AuthorityType.contact_v2) - { - if (postingKey < 0 || postingKey >= 100) - throw new BezahlCodeException("PostingKey must be within 0 and 99."); - this.postingKey = postingKey; - } - } - - //SEPA payment types - if (authority == AuthorityType.periodicsinglepaymentsepa || authority == AuthorityType.singledirectdebitsepa || authority == AuthorityType.singlepaymentsepa || (authority == AuthorityType.contact_v2 && newWayFilled)) - { - if (!IsValidIban(iban)) - throw new BezahlCodeException("The IBAN entered isn't valid."); - this.iban = iban.Replace(" ", "").ToUpper(); - if (!IsValidBic(bic)) - throw new BezahlCodeException("The BIC entered isn't valid."); - this.bic = bic.Replace(" ", "").ToUpper(); - - if (authority != AuthorityType.contact_v2) - { - if (sepaReference.Length > 35) - throw new BezahlCodeException("SEPA reference texts have to be shorter than 36 chars."); - this.sepaReference = sepaReference; - - if (!string.IsNullOrEmpty(creditorId) && !Regex.IsMatch(creditorId.Replace(" ", ""), @"^[a-zA-Z]{2,2}[0-9]{2,2}([A-Za-z0-9]|[\+|\?|/|\-|:|\(|\)|\.|,|']){3,3}([A-Za-z0-9]|[\+|\?|/|\-|:|\(|\)|\.|,|']){1,28}$")) - throw new BezahlCodeException("The creditorId entered isn't valid."); - this.creditorId = creditorId; - if (!string.IsNullOrEmpty(mandateId) && !Regex.IsMatch(mandateId.Replace(" ", ""), @"^([A-Za-z0-9]|[\+|\?|/|\-|:|\(|\)|\.|,|']){1,35}$")) - throw new BezahlCodeException("The mandateId entered isn't valid."); - this.mandateId = mandateId; - if (dateOfSignature != null) - this.dateOfSignature = (DateTime)dateOfSignature; - } - } - - //Checks for all payment types - if (authority != AuthorityType.contact && authority != AuthorityType.contact_v2) - { - if (amount.ToString().Replace(",", ".").Contains(".") && amount.ToString().Replace(",", ".").Split('.')[1].TrimEnd('0').Length > 2) - throw new BezahlCodeException("Amount must have less than 3 digits after decimal point."); - if (amount < 0.01m || amount > 999999999.99m) - throw new BezahlCodeException("Amount has to at least 0.01 and must be smaller or equal to 999999999.99."); - this.amount = amount; - - this.currency = currency; - - if (executionDate == null) - this.executionDate = DateTime.Now; - else - { - if (DateTime.Today.Ticks > executionDate.Value.Ticks) - throw new BezahlCodeException("Execution date must be today or in future."); - this.executionDate = (DateTime)executionDate; - } -#pragma warning disable CS0612 - if (authority == AuthorityType.periodicsinglepayment || authority == AuthorityType.periodicsinglepaymentsepa) -#pragma warning restore CS0612 - { - if (periodicTimeunit.ToUpper() != "M" && periodicTimeunit.ToUpper() != "W") - throw new BezahlCodeException("The periodicTimeunit must be either 'M' (monthly) or 'W' (weekly)."); - this.periodicTimeunit = periodicTimeunit; - if (periodicTimeunitRotation < 1 || periodicTimeunitRotation > 52) - throw new BezahlCodeException("The periodicTimeunitRotation must be 1 or greater. (It means repeat the payment every 'periodicTimeunitRotation' weeks/months."); - this.periodicTimeunitRotation = periodicTimeunitRotation; - if (periodicFirstExecutionDate != null) - this.periodicFirstExecutionDate = (DateTime)periodicFirstExecutionDate; - if (periodicLastExecutionDate != null) - this.periodicLastExecutionDate = (DateTime)periodicLastExecutionDate; - } - - } - - - - } - - public override string ToString() - { - var bezahlCodePayload = $"bank://{authority}?"; - - bezahlCodePayload += $"name={Uri.EscapeDataString(name)}&"; - - if (authority != AuthorityType.contact && authority != AuthorityType.contact_v2) - { - //Handle what is same for all payments -#pragma warning disable CS0612 - if (authority == AuthorityType.periodicsinglepayment || authority == AuthorityType.singledirectdebit || authority == AuthorityType.singlepayment) -#pragma warning restore CS0612 - { - bezahlCodePayload += $"account={account}&"; - bezahlCodePayload += $"bnc={bnc}&"; - if (postingKey > 0) - bezahlCodePayload += $"postingkey={postingKey}&"; - } - else - { - bezahlCodePayload += $"iban={iban}&"; - bezahlCodePayload += $"bic={bic}&"; - - if (!string.IsNullOrEmpty(sepaReference)) - bezahlCodePayload += $"separeference={ Uri.EscapeDataString(sepaReference)}&"; - - if (authority == AuthorityType.singledirectdebitsepa) - { - if (!string.IsNullOrEmpty(creditorId)) - bezahlCodePayload += $"creditorid={ Uri.EscapeDataString(creditorId)}&"; - if (!string.IsNullOrEmpty(mandateId)) - bezahlCodePayload += $"mandateid={ Uri.EscapeDataString(mandateId)}&"; - if (dateOfSignature != DateTime.MinValue) - bezahlCodePayload += $"dateofsignature={dateOfSignature.ToString("ddMMyyyy")}&"; - } - } - bezahlCodePayload += $"amount={amount:0.00}&".Replace(".", ","); - - if (!string.IsNullOrEmpty(reason)) - bezahlCodePayload += $"reason={ Uri.EscapeDataString(reason)}&"; - bezahlCodePayload += $"currency={currency}&"; - bezahlCodePayload += $"executiondate={executionDate.ToString("ddMMyyyy")}&"; -#pragma warning disable CS0612 - if (authority == AuthorityType.periodicsinglepayment || authority == AuthorityType.periodicsinglepaymentsepa) - { - bezahlCodePayload += $"periodictimeunit={periodicTimeunit}&"; - bezahlCodePayload += $"periodictimeunitrotation={periodicTimeunitRotation}&"; - if (periodicFirstExecutionDate != DateTime.MinValue) - bezahlCodePayload += $"periodicfirstexecutiondate={periodicFirstExecutionDate.ToString("ddMMyyyy")}&"; - if (periodicLastExecutionDate != DateTime.MinValue) - bezahlCodePayload += $"periodiclastexecutiondate={periodicLastExecutionDate.ToString("ddMMyyyy")}&"; - } -#pragma warning restore CS0612 - } - else - { - //Handle what is same for all contacts - if (authority == AuthorityType.contact) - { - bezahlCodePayload += $"account={account}&"; - bezahlCodePayload += $"bnc={bnc}&"; - } - else if (authority == AuthorityType.contact_v2) - { - if (!string.IsNullOrEmpty(account) && !string.IsNullOrEmpty(bnc)) - { - bezahlCodePayload += $"account={account}&"; - bezahlCodePayload += $"bnc={bnc}&"; - } - else - { - bezahlCodePayload += $"iban={iban}&"; - bezahlCodePayload += $"bic={bic}&"; - } - } - - if (!string.IsNullOrEmpty(reason)) - bezahlCodePayload += $"reason={ Uri.EscapeDataString(reason)}&"; - } - - return bezahlCodePayload.Trim('&'); - } - - /// - /// ISO 4217 currency codes - /// - public enum Currency - { - AED = 784, - AFN = 971, - ALL = 008, - AMD = 051, - ANG = 532, - AOA = 973, - ARS = 032, - AUD = 036, - AWG = 533, - AZN = 944, - BAM = 977, - BBD = 052, - BDT = 050, - BGN = 975, - BHD = 048, - BIF = 108, - BMD = 060, - BND = 096, - BOB = 068, - BOV = 984, - BRL = 986, - BSD = 044, - BTN = 064, - BWP = 072, - BYR = 974, - BZD = 084, - CAD = 124, - CDF = 976, - CHE = 947, - CHF = 756, - CHW = 948, - CLF = 990, - CLP = 152, - CNY = 156, - COP = 170, - COU = 970, - CRC = 188, - CUC = 931, - CUP = 192, - CVE = 132, - CZK = 203, - DJF = 262, - DKK = 208, - DOP = 214, - DZD = 012, - EGP = 818, - ERN = 232, - ETB = 230, - EUR = 978, - FJD = 242, - FKP = 238, - GBP = 826, - GEL = 981, - GHS = 936, - GIP = 292, - GMD = 270, - GNF = 324, - GTQ = 320, - GYD = 328, - HKD = 344, - HNL = 340, - HRK = 191, - HTG = 332, - HUF = 348, - IDR = 360, - ILS = 376, - INR = 356, - IQD = 368, - IRR = 364, - ISK = 352, - JMD = 388, - JOD = 400, - JPY = 392, - KES = 404, - KGS = 417, - KHR = 116, - KMF = 174, - KPW = 408, - KRW = 410, - KWD = 414, - KYD = 136, - KZT = 398, - LAK = 418, - LBP = 422, - LKR = 144, - LRD = 430, - LSL = 426, - LYD = 434, - MAD = 504, - MDL = 498, - MGA = 969, - MKD = 807, - MMK = 104, - MNT = 496, - MOP = 446, - MRO = 478, - MUR = 480, - MVR = 462, - MWK = 454, - MXN = 484, - MXV = 979, - MYR = 458, - MZN = 943, - NAD = 516, - NGN = 566, - NIO = 558, - NOK = 578, - NPR = 524, - NZD = 554, - OMR = 512, - PAB = 590, - PEN = 604, - PGK = 598, - PHP = 608, - PKR = 586, - PLN = 985, - PYG = 600, - QAR = 634, - RON = 946, - RSD = 941, - RUB = 643, - RWF = 646, - SAR = 682, - SBD = 090, - SCR = 690, - SDG = 938, - SEK = 752, - SGD = 702, - SHP = 654, - SLL = 694, - SOS = 706, - SRD = 968, - SSP = 728, - STD = 678, - SVC = 222, - SYP = 760, - SZL = 748, - THB = 764, - TJS = 972, - TMT = 934, - TND = 788, - TOP = 776, - TRY = 949, - TTD = 780, - TWD = 901, - TZS = 834, - UAH = 980, - UGX = 800, - USD = 840, - USN = 997, - UYI = 940, - UYU = 858, - UZS = 860, - VEF = 937, - VND = 704, - VUV = 548, - WST = 882, - XAF = 950, - XAG = 961, - XAU = 959, - XBA = 955, - XBB = 956, - XBC = 957, - XBD = 958, - XCD = 951, - XDR = 960, - XOF = 952, - XPD = 964, - XPF = 953, - XPT = 962, - XSU = 994, - XTS = 963, - XUA = 965, - XXX = 999, - YER = 886, - ZAR = 710, - ZMW = 967, - ZWL = 932 - } - - - /// - /// Operation modes of the BezahlCode - /// - public enum AuthorityType - { - /// - /// Single payment (Überweisung) - /// - [Obsolete] - singlepayment, - /// - /// Single SEPA payment (SEPA-Überweisung) - /// - singlepaymentsepa, - /// - /// Single debit (Lastschrift) - /// - [Obsolete] - singledirectdebit, - /// - /// Single SEPA debit (SEPA-Lastschrift) - /// - singledirectdebitsepa, - /// - /// Periodic payment (Dauerauftrag) - /// - [Obsolete] - periodicsinglepayment, - /// - /// Periodic SEPA payment (SEPA-Dauerauftrag) - /// - periodicsinglepaymentsepa, - /// - /// Contact data - /// - contact, - /// - /// Contact data V2 - /// - contact_v2 - } - - public class BezahlCodeException : Exception - { - public BezahlCodeException() - { - } - - public BezahlCodeException(string message) - : base(message) - { - } - - public BezahlCodeException(string message, Exception inner) - : base(message, inner) - { - } - } - } - - public class CalendarEvent : Payload - { - private readonly string subject, description, location, start, end; - private readonly EventEncoding encoding; - - /// - /// Generates a calender entry/event payload. - /// - /// Subject/title of the calender event - /// Description of the event - /// Location (lat:long or address) of the event - /// Start time (incl. UTC offset) of the event - /// End time (incl. UTC offset) of the event - /// Is it a full day event? - /// Type of encoding (universal or iCal) - public CalendarEvent(string subject, string description, string location, DateTimeOffset start, DateTimeOffset end, bool allDayEvent, EventEncoding encoding = EventEncoding.Universal) : this(subject, description, location, start.UtcDateTime, end.UtcDateTime, allDayEvent, encoding) - { - } - - /// - /// Generates a calender entry/event payload. - /// - /// Subject/title of the calender event - /// Description of the event - /// Location (lat:long or address) of the event - /// Start time of the event - /// End time of the event - /// Is it a full day event? - /// Type of encoding (universal or iCal) - public CalendarEvent(string subject, string description, string location, DateTime start, DateTime end, bool allDayEvent, EventEncoding encoding = EventEncoding.Universal) - { - this.subject = subject; - this.description = description; - this.location = location; - this.encoding = encoding; - string dtFormatStart = "yyyyMMdd", dtFormatEnd = "yyyyMMdd"; - if (!allDayEvent) - { - dtFormatStart = dtFormatEnd = "yyyyMMddTHHmmss"; - if (start.Kind == DateTimeKind.Utc) - dtFormatStart = "yyyyMMddTHHmmssZ"; - if (end.Kind == DateTimeKind.Utc) - dtFormatEnd = "yyyyMMddTHHmmssZ"; - } - this.start = start.ToString(dtFormatStart); - this.end = end.ToString(dtFormatEnd); - } - - public override string ToString() - { - var vEvent = $"BEGIN:VEVENT{Environment.NewLine}"; - vEvent += $"SUMMARY:{this.subject}{Environment.NewLine}"; - vEvent += !string.IsNullOrEmpty(this.description) ? $"DESCRIPTION:{this.description}{Environment.NewLine}" : ""; - vEvent += !string.IsNullOrEmpty(this.location) ? $"LOCATION:{this.location}{Environment.NewLine}" : ""; - vEvent += $"DTSTART:{this.start}{Environment.NewLine}"; - vEvent += $"DTEND:{this.end}{Environment.NewLine}"; - vEvent += "END:VEVENT"; - - if (this.encoding == EventEncoding.iCalComplete) - vEvent = $@"BEGIN:VCALENDAR{Environment.NewLine}VERSION:2.0{Environment.NewLine}{vEvent}{Environment.NewLine}END:VCALENDAR"; - - return vEvent; - } - - public enum EventEncoding - { - iCalComplete, - Universal - } - } - - public class OneTimePassword : Payload - { - //https://github.com/google/google-authenticator/wiki/Key-Uri-Format - public OneTimePasswordAuthType Type { get; set; } = OneTimePasswordAuthType.TOTP; - public string Secret { get; set; } - - public OneTimePasswordAuthAlgorithm AuthAlgorithm { get; set; } = OneTimePasswordAuthAlgorithm.SHA1; - - [Obsolete("This property is obsolete, use " + nameof(AuthAlgorithm) + " instead", false)] - public OoneTimePasswordAuthAlgorithm Algorithm - { - get { return (OoneTimePasswordAuthAlgorithm)Enum.Parse(typeof(OoneTimePasswordAuthAlgorithm), AuthAlgorithm.ToString()); } - set { AuthAlgorithm = (OneTimePasswordAuthAlgorithm)Enum.Parse(typeof(OneTimePasswordAuthAlgorithm), value.ToString()); } - } - - public string Issuer { get; set; } - public string Label { get; set; } - public int Digits { get; set; } = 6; - public int? Counter { get; set; } = null; - public int? Period { get; set; } = 30; - - public enum OneTimePasswordAuthType - { - TOTP, - HOTP, - } - - public enum OneTimePasswordAuthAlgorithm - { - SHA1, - SHA256, - SHA512, - } - - [Obsolete("This enum is obsolete, use " + nameof(OneTimePasswordAuthAlgorithm) + " instead", false)] - public enum OoneTimePasswordAuthAlgorithm - { - SHA1, - SHA256, - SHA512, - } - - public override string ToString() - { - switch (Type) - { - case OneTimePasswordAuthType.TOTP: - return TimeToString(); - case OneTimePasswordAuthType.HOTP: - return HMACToString(); - default: - throw new ArgumentOutOfRangeException(); - } - } - - // Note: Issuer:Label must only contain 1 : if either of the Issuer or the Label has a : then it is invalid. - // Defaults are 6 digits and 30 for Period - private string HMACToString() - { - var sb = new StringBuilder("otpauth://hotp/"); - ProcessCommonFields(sb); - var actualCounter = Counter ?? 1; - sb.Append("&counter=" + actualCounter); - return sb.ToString(); - } - - private string TimeToString() - { - if (Period == null) - { - throw new Exception("Period must be set when using OneTimePasswordAuthType.TOTP"); - } - - var sb = new StringBuilder("otpauth://totp/"); - - ProcessCommonFields(sb); - - if (Period != 30) - { - sb.Append("&period=" + Period); - } - - return sb.ToString(); - } - - private void ProcessCommonFields(StringBuilder sb) - { - if (Secret.IsNullOrWhiteSpace()) - { - throw new Exception("Secret must be a filled out base32 encoded string"); - } - string strippedSecret = Secret.Replace(" ", ""); - string escapedIssuer = null; - string escapedLabel = null; - string label = null; - - if (!Issuer.IsNullOrWhiteSpace()) - { - if (Issuer.Contains(":")) - { - throw new Exception("Issuer must not have a ':'"); - } - escapedIssuer = Uri.EscapeDataString(Issuer); - } - - if (!Label.IsNullOrWhiteSpace()) - { - if (Label.Contains(":")) - { - throw new Exception("Label must not have a ':'"); - } - escapedLabel = Uri.EscapeDataString(Label); - } - - if (escapedLabel != null && escapedIssuer != null) - { - label = escapedIssuer + ":" + escapedLabel; - } - else if (escapedIssuer != null) - { - label = escapedIssuer; - } - - if (label != null) - { - sb.Append(label); - } - - sb.Append("?secret=" + strippedSecret); - - if (escapedIssuer != null) - { - sb.Append("&issuer=" + escapedIssuer); - } - - if (Digits != 6) - { - sb.Append("&digits=" + Digits); - } - } - } - - public class ShadowSocksConfig : Payload - { - private readonly string hostname, password, tag, methodStr, parameter; - private readonly Method method; - private readonly int port; - private Dictionary encryptionTexts = new Dictionary() { - { "Chacha20IetfPoly1305", "chacha20-ietf-poly1305" }, - { "Aes128Gcm", "aes-128-gcm" }, - { "Aes192Gcm", "aes-192-gcm" }, - { "Aes256Gcm", "aes-256-gcm" }, - - { "XChacha20IetfPoly1305", "xchacha20-ietf-poly1305" }, - - { "Aes128Cfb", "aes-128-cfb" }, - { "Aes192Cfb", "aes-192-cfb" }, - { "Aes256Cfb", "aes-256-cfb" }, - { "Aes128Ctr", "aes-128-ctr" }, - { "Aes192Ctr", "aes-192-ctr" }, - { "Aes256Ctr", "aes-256-ctr" }, - { "Camellia128Cfb", "camellia-128-cfb" }, - { "Camellia192Cfb", "camellia-192-cfb" }, - { "Camellia256Cfb", "camellia-256-cfb" }, - { "Chacha20Ietf", "chacha20-ietf" }, - - { "Aes256Cb", "aes-256-cfb" }, - - { "Aes128Ofb", "aes-128-ofb" }, - { "Aes192Ofb", "aes-192-ofb" }, - { "Aes256Ofb", "aes-256-ofb" }, - { "Aes128Cfb1", "aes-128-cfb1" }, - { "Aes192Cfb1", "aes-192-cfb1" }, - { "Aes256Cfb1", "aes-256-cfb1" }, - { "Aes128Cfb8", "aes-128-cfb8" }, - { "Aes192Cfb8", "aes-192-cfb8" }, - { "Aes256Cfb8", "aes-256-cfb8" }, - - { "Chacha20", "chacha20" }, - { "BfCfb", "bf-cfb" }, - { "Rc4Md5", "rc4-md5" }, - { "Salsa20", "salsa20" }, - - { "DesCfb", "des-cfb" }, - { "IdeaCfb", "idea-cfb" }, - { "Rc2Cfb", "rc2-cfb" }, - { "Cast5Cfb", "cast5-cfb" }, - { "Salsa20Ctr", "salsa20-ctr" }, - { "Rc4", "rc4" }, - { "SeedCfb", "seed-cfb" }, - { "Table", "table" } - }; - - /// - /// Generates a ShadowSocks proxy config payload. - /// - /// Hostname of the ShadowSocks proxy - /// Port of the ShadowSocks proxy - /// Password of the SS proxy - /// Encryption type - /// Optional tag line - public ShadowSocksConfig(string hostname, int port, string password, Method method, string tag = null) : - this(hostname, port, password, method, null, tag) - { } - - public ShadowSocksConfig(string hostname, int port, string password, Method method, string plugin, string pluginOption, string tag = null) : - this(hostname, port, password, method, new Dictionary - { - ["plugin"] = plugin + ( - string.IsNullOrEmpty(pluginOption) - ? "" - : $";{pluginOption}" - ) - }, tag) - { } - private Dictionary UrlEncodeTable = new Dictionary - { - [" "] = "+", - ["\0"] = "%00", - ["\t"] = "%09", - ["\n"] = "%0a", - ["\r"] = "%0d", - ["\""] = "%22", - ["#"] = "%23", - ["$"] = "%24", - ["%"] = "%25", - ["&"] = "%26", - ["'"] = "%27", - ["+"] = "%2b", - [","] = "%2c", - ["/"] = "%2f", - [":"] = "%3a", - [";"] = "%3b", - ["<"] = "%3c", - ["="] = "%3d", - [">"] = "%3e", - ["?"] = "%3f", - ["@"] = "%40", - ["["] = "%5b", - ["\\"] = "%5c", - ["]"] = "%5d", - ["^"] = "%5e", - ["`"] = "%60", - ["{"] = "%7b", - ["|"] = "%7c", - ["}"] = "%7d", - ["~"] = "%7e", - }; - - private string UrlEncode(string i) - { - string j = i; - foreach (var kv in UrlEncodeTable) - { - j = j.Replace(kv.Key, kv.Value); - } - return j; - } - - public ShadowSocksConfig(string hostname, int port, string password, Method method, Dictionary parameters, string tag = null) - { - this.hostname = Uri.CheckHostName(hostname) == UriHostNameType.IPv6 - ? $"[{hostname}]" - : hostname; - if (port < 1 || port > 65535) - throw new ShadowSocksConfigException("Value of 'port' must be within 0 and 65535."); - this.port = port; - this.password = password; - this.method = method; - this.methodStr = encryptionTexts[method.ToString()]; - this.tag = tag; - - if (parameters != null) - this.parameter = - string.Join("&", - parameters.Select( - kv => $"{UrlEncode(kv.Key)}={UrlEncode(kv.Value)}" - ).ToArray()); - } - - public override string ToString() - { - if (string.IsNullOrEmpty(parameter)) - { - var connectionString = $"{methodStr}:{password}@{hostname}:{port}"; - var connectionStringEncoded = Convert.ToBase64String(Encoding.UTF8.GetBytes(connectionString)); - return $"ss://{connectionStringEncoded}{(!string.IsNullOrEmpty(tag) ? $"#{tag}" : string.Empty)}"; - } - var authString = $"{methodStr}:{password}"; - var authStringEncoded = Convert.ToBase64String(Encoding.UTF8.GetBytes(authString)) - .Replace('+', '-') - .Replace('/', '_') - .TrimEnd('='); - return $"ss://{authStringEncoded}@{hostname}:{port}/?{parameter}{(!string.IsNullOrEmpty(tag) ? $"#{tag}" : string.Empty)}"; - } - - public enum Method - { - // AEAD - Chacha20IetfPoly1305, - Aes128Gcm, - Aes192Gcm, - Aes256Gcm, - // AEAD, not standard - XChacha20IetfPoly1305, - // Stream cipher - Aes128Cfb, - Aes192Cfb, - Aes256Cfb, - Aes128Ctr, - Aes192Ctr, - Aes256Ctr, - Camellia128Cfb, - Camellia192Cfb, - Camellia256Cfb, - Chacha20Ietf, - // alias of Aes256Cfb - Aes256Cb, - // Stream cipher, not standard - Aes128Ofb, - Aes192Ofb, - Aes256Ofb, - Aes128Cfb1, - Aes192Cfb1, - Aes256Cfb1, - Aes128Cfb8, - Aes192Cfb8, - Aes256Cfb8, - // Stream cipher, deprecated - Chacha20, - BfCfb, - Rc4Md5, - Salsa20, - // Not standard and not in acitve use - DesCfb, - IdeaCfb, - Rc2Cfb, - Cast5Cfb, - Salsa20Ctr, - Rc4, - SeedCfb, - Table - } - - public class ShadowSocksConfigException : Exception - { - public ShadowSocksConfigException() - { - } - - public ShadowSocksConfigException(string message) - : base(message) - { - } - - public ShadowSocksConfigException(string message, Exception inner) - : base(message, inner) - { - } - } - } - - public class MoneroTransaction : Payload - { - private readonly string address, txPaymentId, recipientName, txDescription; - private readonly float? txAmount; - - /// - /// Creates a monero transaction payload - /// - /// Receiver's monero address - /// Amount to transfer - /// Payment id - /// Receipient's name - /// Reference text / payment description - public MoneroTransaction(string address, float? txAmount = null, string txPaymentId = null, string recipientName = null, string txDescription = null) - { - if (string.IsNullOrEmpty(address)) - throw new MoneroTransactionException("The address is mandatory and has to be set."); - this.address = address; - if (txAmount != null && txAmount <= 0) - throw new MoneroTransactionException("Value of 'txAmount' must be greater than 0."); - this.txAmount = txAmount; - this.txPaymentId = txPaymentId; - this.recipientName = recipientName; - this.txDescription = txDescription; - } - - public override string ToString() - { - var moneroUri = $"monero://{address}{(!string.IsNullOrEmpty(txPaymentId) || !string.IsNullOrEmpty(recipientName) || !string.IsNullOrEmpty(txDescription) || txAmount != null ? "?" : string.Empty)}"; - moneroUri += (!string.IsNullOrEmpty(txPaymentId) ? $"tx_payment_id={Uri.EscapeDataString(txPaymentId)}&" : string.Empty); - moneroUri += (!string.IsNullOrEmpty(recipientName) ? $"recipient_name={Uri.EscapeDataString(recipientName)}&" : string.Empty); - moneroUri += (txAmount != null ? $"tx_amount={txAmount.ToString().Replace(",",".")}&" : string.Empty); - moneroUri += (!string.IsNullOrEmpty(txDescription) ? $"tx_description={Uri.EscapeDataString(txDescription)}" : string.Empty); - return moneroUri.TrimEnd('&'); - } - - - public class MoneroTransactionException : Exception - { - public MoneroTransactionException() - { - } - - public MoneroTransactionException(string message) - : base(message) - { - } - - public MoneroTransactionException(string message, Exception inner) - : base(message, inner) - { - } - } - } - - public class SlovenianUpnQr : Payload - { - //Keep in mind, that the ECC level has to be set to "M", version to 15 and ECI to EciMode.Iso8859_2 when generating a SlovenianUpnQr! - //SlovenianUpnQr specification: https://www.upn-qr.si/uploads/files/NavodilaZaProgramerjeUPNQR.pdf - - private string _payerName = ""; - private string _payerAddress = ""; - private string _payerPlace = ""; - private string _amount = ""; - private string _code = ""; - private string _purpose = ""; - private string _deadLine = ""; - private string _recipientIban = ""; - private string _recipientName = ""; - private string _recipientAddress = ""; - private string _recipientPlace = ""; - private string _recipientSiModel = ""; - private string _recipientSiReference = ""; - - public override int Version { get { return 15; } } - public override QRCodeGenerator.ECCLevel EccLevel { get { return QRCodeGenerator.ECCLevel.M; } } - public override QRCodeGenerator.EciMode EciMode { get { return QRCodeGenerator.EciMode.Iso8859_2; } } - - private string LimitLength(string value, int maxLength) - { - return (value.Length <= maxLength) ? value : value.Substring(0, maxLength); - } - - public SlovenianUpnQr(string payerName, string payerAddress, string payerPlace, string recipientName, string recipientAddress, string recipientPlace, string recipientIban, string description, double amount, string recipientSiModel = "SI00", string recipientSiReference = "", string code = "OTHR") : - this(payerName, payerAddress, payerPlace, recipientName, recipientAddress, recipientPlace, recipientIban, description, amount, null, recipientSiModel, recipientSiReference, code) - { } - - public SlovenianUpnQr(string payerName, string payerAddress, string payerPlace, string recipientName, string recipientAddress, string recipientPlace, string recipientIban, string description, double amount, DateTime? deadline, string recipientSiModel = "SI99", string recipientSiReference = "", string code = "OTHR") - { - _payerName = LimitLength(payerName.Trim(), 33); - _payerAddress = LimitLength(payerAddress.Trim(), 33); - _payerPlace = LimitLength(payerPlace.Trim(), 33); - _amount = FormatAmount(amount); - _code = LimitLength(code.Trim().ToUpper(), 4); - _purpose = LimitLength(description.Trim(), 42); - _deadLine = (deadline == null) ? "" : deadline?.ToString("dd.MM.yyyy"); - _recipientIban = LimitLength(recipientIban.Trim(), 34); - _recipientName = LimitLength(recipientName.Trim(), 33); - _recipientAddress = LimitLength(recipientAddress.Trim(), 33); - _recipientPlace = LimitLength(recipientPlace.Trim(), 33); - _recipientSiModel = LimitLength(recipientSiModel.Trim().ToUpper(), 4); - _recipientSiReference = LimitLength(recipientSiReference.Trim(), 22); - } - - - private string FormatAmount(double amount) - { - int _amt = (int)Math.Round(amount * 100.0); - return String.Format("{0:00000000000}", _amt); - } - - private int CalculateChecksum() - { - int _cs = 5 + _payerName.Length; //5 = UPNQR constant Length - _cs += _payerAddress.Length; - _cs += _payerPlace.Length; - _cs += _amount.Length; - _cs += _code.Length; - _cs += _purpose.Length; - _cs += _deadLine.Length; - _cs += _recipientIban.Length; - _cs += _recipientName.Length; - _cs += _recipientAddress.Length; - _cs += _recipientPlace.Length; - _cs += _recipientSiModel.Length; - _cs += _recipientSiReference.Length; - _cs += 19; - return _cs; - } - - public override string ToString() - { - var _sb = new StringBuilder(); - _sb.Append("UPNQR"); - _sb.Append('\n').Append('\n').Append('\n').Append('\n').Append('\n'); - _sb.Append(_payerName).Append('\n'); - _sb.Append(_payerAddress).Append('\n'); - _sb.Append(_payerPlace).Append('\n'); - _sb.Append(_amount).Append('\n').Append('\n').Append('\n'); - _sb.Append(_code.ToUpper()).Append('\n'); - _sb.Append(_purpose).Append('\n'); - _sb.Append(_deadLine).Append('\n'); - _sb.Append(_recipientIban.ToUpper()).Append('\n'); - _sb.Append(_recipientSiModel).Append(_recipientSiReference).Append('\n'); - _sb.Append(_recipientName).Append('\n'); - _sb.Append(_recipientAddress).Append('\n'); - _sb.Append(_recipientPlace).Append('\n'); - _sb.AppendFormat("{0:000}", CalculateChecksum()).Append('\n'); - return _sb.ToString(); - } - } - - - public class RussiaPaymentOrder : Payload - { - // Specification of RussianPaymentOrder - //https://docs.cntd.ru/document/1200110981 - //https://roskazna.gov.ru/upload/iblock/5fa/gost_r_56042_2014.pdf - //https://sbqr.ru/standard/files/standart.pdf - - // Specification of data types described in the above standard - // https://gitea.sergeybochkov.com/bochkov/emuik/src/commit/d18f3b550f6415ea4a4a5e6097eaab4661355c72/template/ed - - // Tool for QR validation - // https://www.sbqr.ru/validator/index.html - - //base - private CharacterSets characterSet; - private readonly MandatoryFields mFields = new MandatoryFields(); - private readonly OptionalFields oFields = new OptionalFields(); - private string separator = "|"; - - private RussiaPaymentOrder() - { - } - - /// - /// Generates a RussiaPaymentOrder payload - /// - /// Name of the payee (Наименование получателя платежа) - /// Beneficiary account number (Номер счета получателя платежа) - /// Name of the beneficiary's bank (Наименование банка получателя платежа) - /// BIC (БИК) - /// Box number / account payee's bank (Номер кор./сч. банка получателя платежа) - /// An (optional) object of additional fields - /// Type of encoding (default UTF-8) - public RussiaPaymentOrder(string name, string personalAcc, string bankName, string BIC, string correspAcc, OptionalFields optionalFields = null, CharacterSets characterSet = CharacterSets.utf_8) : this() - { - this.characterSet = characterSet; - mFields.Name = ValidateInput(name, "Name", @"^.{1,160}$"); - mFields.PersonalAcc = ValidateInput(personalAcc, "PersonalAcc", @"^[1-9]\d{4}[0-9ABCEHKMPTX]\d{14}$"); - mFields.BankName = ValidateInput(bankName, "BankName", @"^.{1,45}$"); - mFields.BIC = ValidateInput(BIC, "BIC", @"^\d{9}$"); - mFields.CorrespAcc = ValidateInput(correspAcc, "CorrespAcc", @"^[1-9]\d{4}[0-9ABCEHKMPTX]\d{14}$"); - - if (optionalFields != null) - oFields = optionalFields; - } - - /// - /// Returns payload as string. - /// - /// ⚠ Attention: If CharacterSets was set to windows-1251 or koi8-r you should use ToBytes() instead of ToString() and pass the bytes to CreateQrCode()! - /// - public override string ToString() - { - var cp = characterSet.ToString().Replace("_", "-"); - var bytes = ToBytes(); - -#if !NETFRAMEWORK - Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); -#endif - return Encoding.GetEncoding(cp).GetString(bytes, 0, bytes.Length); - } - - /// - /// Returns payload as byte[]. - /// - /// Should be used if CharacterSets equals windows-1251 or koi8-r - /// - - public byte[] ToBytes() - { - //Setup byte encoder - //Encode return string as byte[] with correct CharacterSet -#if !NETFRAMEWORK - Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); -#endif - var cp = this.characterSet.ToString().Replace("_", "-"); - - //Calculate the seperator - separator = DetermineSeparator(); - - //Create the payload string - string ret = $"ST0001" + ((int)characterSet).ToString() + //(separator != "|" ? separator : "") + - $"{separator}Name={mFields.Name}" + - $"{separator}PersonalAcc={mFields.PersonalAcc}" + - $"{separator}BankName={mFields.BankName}" + - $"{separator}BIC={mFields.BIC}" + - $"{separator}CorrespAcc={mFields.CorrespAcc}"; - - //Check length of mandatory field block (-8 => Removing service data block bytes from ret length) - int bytesMandatoryLen = Encoding.Convert(Encoding.UTF8, Encoding.GetEncoding(cp), Encoding.UTF8.GetBytes(ret)).Length - 8; - if (bytesMandatoryLen > 300) - throw new RussiaPaymentOrderException($"Data too long. Mandatory data must not exceed 300 bytes, but actually is {bytesMandatoryLen} bytes long. Remove additional data fields or shorten strings/values."); - - - //Add optional fields, if filled - var optionalFieldsList = GetOptionalFieldsAsList(); - if (optionalFieldsList.Count > 0) - ret += $"|{string.Join("|", optionalFieldsList.ToArray())}"; - ret += separator; - - return Encoding.Convert(Encoding.UTF8, Encoding.GetEncoding(cp), Encoding.UTF8.GetBytes(ret)); - } - - - /// - /// Determines a valid separator - /// - /// - private string DetermineSeparator() - { - // See chapter 5.2.1 of Standard (https://sbqr.ru/standard/files/standart.pdf) - - var mandatoryValues = GetMandatoryFieldsAsList(); - var optionalValues = GetOptionalFieldsAsList(); - - // Possible candidates for field separation - var separatorCandidates = new string[]{ "|", "#", ";", ":", "^", "_", "~", "{", "}", "!", "#", "$", "%", "&", "(", ")", "*", "+", ",", "/", "@" }; - foreach (var sepCandidate in separatorCandidates) - { - if (!mandatoryValues.Any(x => x.Contains(sepCandidate)) && !optionalValues.Any(x => x.Contains(sepCandidate))) - return sepCandidate; - } - throw new RussiaPaymentOrderException("No valid separator found."); - } - - /// - /// Takes all optional fields that are not null and returns their string represantion - /// - /// A List of strings - private List GetOptionalFieldsAsList() - { -#if NETSTANDARD1_3 - return typeof(OptionalFields).GetRuntimeProperties() - .Where(field => field.GetValue(oFields) != null) - .Select(field => { - var objValue = field.GetValue(oFields, null); - var value = field.PropertyType.Equals(typeof(DateTime?)) ? ((DateTime)objValue).ToString("dd.MM.yyyy") : objValue.ToString(); - return $"{field.Name}={value}"; - }) - .ToList(); -#else - return typeof(OptionalFields).GetProperties() - .Where(field => field.GetValue(oFields, null) != null) - .Select(field => { - var objValue = field.GetValue(oFields, null); - var value = field.PropertyType.Equals(typeof(DateTime?)) ? ((DateTime)objValue).ToString("dd.MM.yyyy") : objValue.ToString(); - return $"{field.Name}={value}"; - }) - .ToList(); -#endif - } - - - /// - /// Takes all mandatory fields that are not null and returns their string represantion - /// - /// A List of strings - private List GetMandatoryFieldsAsList() - { -#if NETSTANDARD1_3 - return typeof(MandatoryFields).GetRuntimeFields() - .Where(field => field.GetValue(mFields) != null) - .Select(field => { - var objValue = field.GetValue(mFields); - var value = field.FieldType.Equals(typeof(DateTime?)) ? ((DateTime)objValue).ToString("dd.MM.yyyy") : objValue.ToString(); - return $"{field.Name}={value}"; - }) - .ToList(); -#else - return typeof(MandatoryFields).GetFields() - .Where(field => field.GetValue(mFields) != null) - .Select(field => { - var objValue = field.GetValue(mFields); - var value = field.FieldType.Equals(typeof(DateTime?)) ? ((DateTime)objValue).ToString("dd.MM.yyyy") : objValue.ToString(); - return $"{field.Name}={value}"; - }) - .ToList(); -#endif - } - - /// - /// Validates a string against a given Regex pattern. Returns input if it matches the Regex expression (=valid) or throws Exception in case there's a mismatch - /// - /// String to be validated - /// Name/descriptor of the string to be validated - /// A regex pattern to be used for validation - /// An optional error text. If null, a standard error text is generated - /// Input value (in case it is valid) - private static string ValidateInput(string input, string fieldname, string pattern, string errorText = null) - { - return ValidateInput(input, fieldname, new string[] { pattern }, errorText); - } - - /// - /// Validates a string against one or more given Regex patterns. Returns input if it matches all regex expressions (=valid) or throws Exception in case there's a mismatch - /// - /// String to be validated - /// Name/descriptor of the string to be validated - /// An array of regex patterns to be used for validation - /// An optional error text. If null, a standard error text is generated - /// Input value (in case it is valid) - private static string ValidateInput(string input, string fieldname, string[] patterns, string errorText = null) - { - if (input == null) - throw new RussiaPaymentOrderException($"The input for '{fieldname}' must not be null."); - foreach (var pattern in patterns) - { - if (!Regex.IsMatch(input, pattern)) - throw new RussiaPaymentOrderException(errorText ?? $"The input for '{fieldname}' ({input}) doesn't match the pattern {pattern}"); - } - return input; - } - - private class MandatoryFields - { - public string Name; - public string PersonalAcc; - public string BankName; - public string BIC; - public string CorrespAcc; - } - - public class OptionalFields - { - private string _sum; - /// - /// Payment amount, in kopecks (FTI’s Amount.) - /// Сумма платежа, в копейках - /// - public string Sum - { - get { return _sum; } - set { _sum = ValidateInput(value, "Sum", @"^\d{1,18}$"); } - } - - private string _purpose; - /// - /// Payment name (purpose) - /// Наименование платежа (назначение) - /// - public string Purpose - { - get { return _purpose; } - set { _purpose = ValidateInput(value, "Purpose", @"^.{1,160}$"); } - } - - private string _payeeInn; - /// - /// Payee's INN (Resident Tax Identification Number; Text, up to 12 characters.) - /// ИНН получателя платежа - /// - public string PayeeINN - { - get { return _payeeInn; } - set { _payeeInn = ValidateInput(value, "PayeeINN", @"^.{1,12}$"); } - } - - private string _payerInn; - /// - /// Payer's INN (Resident Tax Identification Number; Text, up to 12 characters.) - /// ИНН плательщика - /// - public string PayerINN - { - get { return _payerInn; } - set { _payerInn = ValidateInput(value, "PayerINN", @"^.{1,12}$"); } - } - - private string _drawerStatus; - /// - /// Status compiler payment document - /// Статус составителя платежного документа - /// - public string DrawerStatus - { - get { return _drawerStatus; } - set { _drawerStatus = ValidateInput(value, "DrawerStatus", @"^.{1,2}$"); } - } - - private string _kpp; - /// - /// KPP of the payee (Tax Registration Code; Text, up to 9 characters.) - /// КПП получателя платежа - /// - public string KPP - { - get { return _kpp; } - set { _kpp = ValidateInput(value, "KPP", @"^.{1,9}$"); } - } - - private string _cbc; - /// - /// CBC - /// КБК - /// - public string CBC - { - get { return _cbc; } - set { _cbc = ValidateInput(value, "CBC", @"^.{1,20}$"); } - } - - private string _oktmo; - /// - /// All-Russian classifier territories of municipal formations - /// Общероссийский классификатор территорий муниципальных образований - /// - public string OKTMO - { - get { return _oktmo; } - set { _oktmo = ValidateInput(value, "OKTMO", @"^.{1,11}$"); } - } - - private string _paytReason; - /// - /// Basis of tax payment - /// Основание налогового платежа - /// - public string PaytReason - { - get { return _paytReason; } - set { _paytReason = ValidateInput(value, "PaytReason", @"^.{1,2}$"); } - } - - private string _taxPeriod; - /// - /// Taxable period - /// Налоговый период - /// - public string TaxPeriod - { - get { return _taxPeriod; } - set { _taxPeriod = ValidateInput(value, "ТaxPeriod", @"^.{1,10}$"); } - } - - private string _docNo; - /// - /// Document number - /// Номер документа - /// - public string DocNo - { - get { return _docNo; } - set { _docNo = ValidateInput(value, "DocNo", @"^.{1,15}$"); } - } - - /// - /// Document date - /// Дата документа - /// - public DateTime? DocDate { get; set; } - - private string _taxPaytKind; - /// - /// Payment type - /// Тип платежа - /// - public string TaxPaytKind - { - get { return _taxPaytKind; } - set { _taxPaytKind = ValidateInput(value, "TaxPaytKind", @"^.{1,2}$"); } - } - - /************************************************************************** - * The following fiels are no further specified in the standard - * document (https://sbqr.ru/standard/files/standart.pdf) thus there - * is no addition input validation implemented. - * **************************************************************************/ - - /// - /// Payer's surname - /// Фамилия плательщика - /// - public string LastName { get; set; } - - /// - /// Payer's name - /// Имя плательщика - /// - public string FirstName { get; set; } - - /// - /// Payer's patronymic - /// Отчество плательщика - /// - public string MiddleName { get; set; } - - /// - /// Payer's address - /// Адрес плательщика - /// - public string PayerAddress { get; set; } - - /// - /// Personal account of a budget recipient - /// Лицевой счет бюджетного получателя - /// - public string PersonalAccount { get; set; } - - /// - /// Payment document index - /// Индекс платежного документа - /// - public string DocIdx { get; set; } - - /// - /// Personal account number in the personalized accounting system in the Pension Fund of the Russian Federation - SNILS - /// № лицевого счета в системе персонифицированного учета в ПФР - СНИЛС - /// - public string PensAcc { get; set; } - - /// - /// Number of contract - /// Номер договора - /// - public string Contract { get; set; } - - /// - /// Personal account number of the payer in the organization (in the accounting system of the PU) - /// Номер лицевого счета плательщика в организации (в системе учета ПУ) - /// - public string PersAcc { get; set; } - - /// - /// Apartment number - /// Номер квартиры - /// - public string Flat { get; set; } - - /// - /// Phone number - /// Номер телефона - /// - public string Phone { get; set; } - - /// - /// DUL payer type - /// Вид ДУЛ плательщика - /// - public string PayerIdType { get; set; } - - /// - /// DUL number of the payer - /// Номер ДУЛ плательщика - /// - public string PayerIdNum { get; set; } - - /// - /// FULL NAME. child / student - /// Ф.И.О. ребенка/учащегося - /// - public string ChildFio { get; set; } - - /// - /// Date of birth - /// Дата рождения - /// - public DateTime? BirthDate { get; set; } - - /// - /// Due date / Invoice date - /// Срок платежа/дата выставления счета - /// - public string PaymTerm { get; set; } - - /// - /// Payment period - /// Период оплаты - /// - public string PaymPeriod { get; set; } - - /// - /// Payment type - /// Вид платежа - /// - public string Category { get; set; } - - /// - /// Service code / meter name - /// Код услуги/название прибора учета - /// - public string ServiceName { get; set; } - - /// - /// Metering device number - /// Номер прибора учета - /// - public string CounterId { get; set; } - - /// - /// Meter reading - /// Показание прибора учета - /// - public string CounterVal { get; set; } - - /// - /// Notification, accrual, account number - /// Номер извещения, начисления, счета - /// - public string QuittId { get; set; } - - /// - /// Date of notification / accrual / invoice / resolution (for traffic police) - /// Дата извещения/начисления/счета/постановления (для ГИБДД) - /// - public DateTime? QuittDate { get; set; } - - /// - /// Institution number (educational, medical) - /// Номер учреждения (образовательного, медицинского) - /// - public string InstNum { get; set; } - - /// - /// Kindergarten / school class number - /// Номер группы детсада/класса школы - /// - public string ClassNum { get; set; } - - /// - /// Full name of the teacher, specialist providing the service - /// ФИО преподавателя, специалиста, оказывающего услугу - /// - public string SpecFio { get; set; } - - /// - /// Insurance / additional service amount / Penalty amount (in kopecks) - /// Сумма страховки/дополнительной услуги/Сумма пени (в копейках) - /// - public string AddAmount { get; set; } - - /// - /// Resolution number (for traffic police) - /// Номер постановления (для ГИБДД) - /// - public string RuleId { get; set; } - - /// - /// Enforcement Proceedings Number - /// Номер исполнительного производства - /// - public string ExecId { get; set; } - - /// - /// Type of payment code (for example, for payments to Rosreestr) - /// Код вида платежа (например, для платежей в адрес Росреестра) - /// - public string RegType { get; set; } - - /// - /// Unique accrual identifier - /// Уникальный идентификатор начисления - /// - public string UIN { get; set; } - - /// - /// The technical code recommended by the service provider. Maybe used by the receiving organization to call the appropriate processing IT system. - /// Технический код, рекомендуемый для заполнения поставщиком услуг. Может использоваться принимающей организацией для вызова соответствующей обрабатывающей ИТ-системы. - /// - public TechCode? TechCode { get; set; } - } - - /// - /// (List of values of the technical code of the payment) - /// Перечень значений технического кода платежа - /// - public enum TechCode - { - Мобильная_связь_стационарный_телефон = 01, - Коммунальные_услуги_ЖКХAFN = 02, - ГИБДД_налоги_пошлины_бюджетные_платежи = 03, - Охранные_услуги = 04, - Услуги_оказываемые_УФМС = 05, - ПФР = 06, - Погашение_кредитов = 07, - Образовательные_учреждения = 08, - Интернет_и_ТВ = 09, - Электронные_деньги = 10, - Отдых_и_путешествия = 11, - Инвестиции_и_страхование = 12, - Спорт_и_здоровье = 13, - Благотворительные_и_общественные_организации = 14, - Прочие_услуги = 15 - } - - public enum CharacterSets - { - windows_1251 = 1, // Encoding.GetEncoding("windows-1251") - utf_8 = 2, // Encoding.UTF8 - koi8_r = 3 // Encoding.GetEncoding("koi8-r") - - } - - public class RussiaPaymentOrderException : Exception - { - public RussiaPaymentOrderException(string message) - : base(message) - { - } - } - - } - - private static bool IsValidIban(string iban) { //Clean IBAN diff --git a/QRCoder/PayloadGenerator/BezahlCode.cs b/QRCoder/PayloadGenerator/BezahlCode.cs new file mode 100644 index 00000000..7bb8c9c6 --- /dev/null +++ b/QRCoder/PayloadGenerator/BezahlCode.cs @@ -0,0 +1,573 @@ +using System; +using System.Text.RegularExpressions; +#if NETSTANDARD1_3 +using System.Reflection; +#endif + +namespace QRCoder +{ + public static partial class PayloadGenerator + { + public class BezahlCode : Payload + { + //BezahlCode specification: http://www.bezahlcode.de/wp-content/uploads/BezahlCode_TechDok.pdf + + private readonly string name, iban, bic, account, bnc, sepaReference, reason, creditorId, mandateId, periodicTimeunit; + private readonly decimal amount; + private readonly int postingKey, periodicTimeunitRotation; + private readonly Currency currency; + private readonly AuthorityType authority; + private readonly DateTime executionDate, dateOfSignature, periodicFirstExecutionDate, periodicLastExecutionDate; + + + /// + /// Constructor for contact data + /// + /// Type of the bank transfer + /// Name of the receiver (Empfänger) + /// Bank account (Kontonummer) + /// Bank institute (Bankleitzahl) + /// IBAN + /// BIC + /// Reason (Verwendungszweck) + public BezahlCode(AuthorityType authority, string name, string account = "", string bnc = "", string iban = "", string bic = "", string reason = "") : this(authority, name, account, bnc, iban, bic, 0, string.Empty, 0, null, null, string.Empty, string.Empty, null, reason, 0, string.Empty, Currency.EUR, null, 1) + { + } + + + /// + /// Constructor for non-SEPA payments + /// + /// Type of the bank transfer + /// Name of the receiver (Empfänger) + /// Bank account (Kontonummer) + /// Bank institute (Bankleitzahl) + /// Amount (Betrag) + /// Unit of intervall for payment ('M' = monthly, 'W' = weekly) + /// Intervall for payment. This value is combined with 'periodicTimeunit' + /// Date of first periodic execution + /// Date of last periodic execution + /// Reason (Verwendungszweck) + /// Transfer Key (Textschlüssel, z.B. Spendenzahlung = 69) + /// Currency (Währung) + /// Execution date (Ausführungsdatum) + public BezahlCode(AuthorityType authority, string name, string account, string bnc, decimal amount, string periodicTimeunit = "", int periodicTimeunitRotation = 0, DateTime? periodicFirstExecutionDate = null, DateTime? periodicLastExecutionDate = null, string reason = "", int postingKey = 0, Currency currency = Currency.EUR, DateTime? executionDate = null) : this(authority, name, account, bnc, string.Empty, string.Empty, amount, periodicTimeunit, periodicTimeunitRotation, periodicFirstExecutionDate, periodicLastExecutionDate, string.Empty, string.Empty, null, reason, postingKey, string.Empty, currency, executionDate, 2) + { + } + + /// + /// Constructor for SEPA payments + /// + /// Type of the bank transfer + /// Name of the receiver (Empfänger) + /// IBAN + /// BIC + /// Amount (Betrag) + /// Unit of intervall for payment ('M' = monthly, 'W' = weekly) + /// Intervall for payment. This value is combined with 'periodicTimeunit' + /// Date of first periodic execution + /// Date of last periodic execution + /// Creditor id (Gläubiger ID) + /// Manadate id (Mandatsreferenz) + /// Signature date (Erteilungsdatum des Mandats) + /// Reason (Verwendungszweck) + /// Transfer Key (Textschlüssel, z.B. Spendenzahlung = 69) + /// SEPA reference (SEPA-Referenz) + /// Currency (Währung) + /// Execution date (Ausführungsdatum) + public BezahlCode(AuthorityType authority, string name, string iban, string bic, decimal amount, string periodicTimeunit = "", int periodicTimeunitRotation = 0, DateTime? periodicFirstExecutionDate = null, DateTime? periodicLastExecutionDate = null, string creditorId = "", string mandateId = "", DateTime? dateOfSignature = null, string reason = "", string sepaReference = "", Currency currency = Currency.EUR, DateTime? executionDate = null) : this(authority, name, string.Empty, string.Empty, iban, bic, amount, periodicTimeunit, periodicTimeunitRotation, periodicFirstExecutionDate, periodicLastExecutionDate, creditorId, mandateId, dateOfSignature, reason, 0, sepaReference, currency, executionDate, 3) + { + } + + + + + /// + /// Generic constructor. Please use specific (non-SEPA or SEPA) constructor + /// + /// Type of the bank transfer + /// Name of the receiver (Empfänger) + /// Bank account (Kontonummer) + /// Bank institute (Bankleitzahl) + /// IBAN + /// BIC + /// Amount (Betrag) + /// Unit of intervall for payment ('M' = monthly, 'W' = weekly) + /// Intervall for payment. This value is combined with 'periodicTimeunit' + /// Date of first periodic execution + /// Date of last periodic execution + /// Creditor id (Gläubiger ID) + /// Manadate id (Mandatsreferenz) + /// Signature date (Erteilungsdatum des Mandats) + /// Reason (Verwendungszweck) + /// Transfer Key (Textschlüssel, z.B. Spendenzahlung = 69) + /// SEPA reference (SEPA-Referenz) + /// Currency (Währung) + /// Execution date (Ausführungsdatum) + /// Only used for internal state handdling + public BezahlCode(AuthorityType authority, string name, string account, string bnc, string iban, string bic, decimal amount, string periodicTimeunit = "", int periodicTimeunitRotation = 0, DateTime? periodicFirstExecutionDate = null, DateTime? periodicLastExecutionDate = null, string creditorId = "", string mandateId = "", DateTime? dateOfSignature = null, string reason = "", int postingKey = 0, string sepaReference = "", Currency currency = Currency.EUR, DateTime? executionDate = null, int internalMode = 0) + { + //Loaded via "contact-constructor" + if (internalMode == 1) + { + if (authority != AuthorityType.contact && authority != AuthorityType.contact_v2) + throw new BezahlCodeException("The constructor without an amount may only ne used with authority types 'contact' and 'contact_v2'."); + if (authority == AuthorityType.contact && (string.IsNullOrEmpty(account) || string.IsNullOrEmpty(bnc))) + throw new BezahlCodeException("When using authority type 'contact' the parameters 'account' and 'bnc' must be set."); + + if (authority != AuthorityType.contact_v2) + { + var oldFilled = (!string.IsNullOrEmpty(account) && !string.IsNullOrEmpty(bnc)); + var newFilled = (!string.IsNullOrEmpty(iban) && !string.IsNullOrEmpty(bic)); + if ((!oldFilled && !newFilled) || (oldFilled && newFilled)) + throw new BezahlCodeException("When using authority type 'contact_v2' either the parameters 'account' and 'bnc' or the parameters 'iban' and 'bic' must be set. Leave the other parameter pair empty."); + } + } + else if (internalMode == 2) + { +#pragma warning disable CS0612 + if (authority != AuthorityType.periodicsinglepayment && authority != AuthorityType.singledirectdebit && authority != AuthorityType.singlepayment) + throw new BezahlCodeException("The constructor with 'account' and 'bnc' may only be used with 'non SEPA' authority types. Either choose another authority type or switch constructor."); + if (authority == AuthorityType.periodicsinglepayment && (string.IsNullOrEmpty(periodicTimeunit) || periodicTimeunitRotation == 0)) + throw new BezahlCodeException("When using 'periodicsinglepayment' as authority type, the parameters 'periodicTimeunit' and 'periodicTimeunitRotation' must be set."); +#pragma warning restore CS0612 + } + else if (internalMode == 3) + { + if (authority != AuthorityType.periodicsinglepaymentsepa && authority != AuthorityType.singledirectdebitsepa && authority != AuthorityType.singlepaymentsepa) + throw new BezahlCodeException("The constructor with 'iban' and 'bic' may only be used with 'SEPA' authority types. Either choose another authority type or switch constructor."); + if (authority == AuthorityType.periodicsinglepaymentsepa && (string.IsNullOrEmpty(periodicTimeunit) || periodicTimeunitRotation == 0)) + throw new BezahlCodeException("When using 'periodicsinglepaymentsepa' as authority type, the parameters 'periodicTimeunit' and 'periodicTimeunitRotation' must be set."); + } + + this.authority = authority; + + if (name.Length > 70) + throw new BezahlCodeException("(Payee-)Name must be shorter than 71 chars."); + this.name = name; + + if (reason.Length > 27) + throw new BezahlCodeException("Reasons texts have to be shorter than 28 chars."); + this.reason = reason; + + var oldWayFilled = (!string.IsNullOrEmpty(account) && !string.IsNullOrEmpty(bnc)); + var newWayFilled = (!string.IsNullOrEmpty(iban) && !string.IsNullOrEmpty(bic)); + + //Non-SEPA payment types +#pragma warning disable CS0612 + if (authority == AuthorityType.periodicsinglepayment || authority == AuthorityType.singledirectdebit || authority == AuthorityType.singlepayment || authority == AuthorityType.contact || (authority == AuthorityType.contact_v2 && oldWayFilled)) + { +#pragma warning restore CS0612 + if (!Regex.IsMatch(account.Replace(" ", ""), @"^[0-9]{1,9}$")) + throw new BezahlCodeException("The account entered isn't valid."); + this.account = account.Replace(" ", "").ToUpper(); + if(!Regex.IsMatch(bnc.Replace(" ", ""), @"^[0-9]{1,9}$")) + throw new BezahlCodeException("The bnc entered isn't valid."); + this.bnc = bnc.Replace(" ", "").ToUpper(); + + if (authority != AuthorityType.contact && authority != AuthorityType.contact_v2) + { + if (postingKey < 0 || postingKey >= 100) + throw new BezahlCodeException("PostingKey must be within 0 and 99."); + this.postingKey = postingKey; + } + } + + //SEPA payment types + if (authority == AuthorityType.periodicsinglepaymentsepa || authority == AuthorityType.singledirectdebitsepa || authority == AuthorityType.singlepaymentsepa || (authority == AuthorityType.contact_v2 && newWayFilled)) + { + if (!IsValidIban(iban)) + throw new BezahlCodeException("The IBAN entered isn't valid."); + this.iban = iban.Replace(" ", "").ToUpper(); + if (!IsValidBic(bic)) + throw new BezahlCodeException("The BIC entered isn't valid."); + this.bic = bic.Replace(" ", "").ToUpper(); + + if (authority != AuthorityType.contact_v2) + { + if (sepaReference.Length > 35) + throw new BezahlCodeException("SEPA reference texts have to be shorter than 36 chars."); + this.sepaReference = sepaReference; + + if (!string.IsNullOrEmpty(creditorId) && !Regex.IsMatch(creditorId.Replace(" ", ""), @"^[a-zA-Z]{2,2}[0-9]{2,2}([A-Za-z0-9]|[\+|\?|/|\-|:|\(|\)|\.|,|']){3,3}([A-Za-z0-9]|[\+|\?|/|\-|:|\(|\)|\.|,|']){1,28}$")) + throw new BezahlCodeException("The creditorId entered isn't valid."); + this.creditorId = creditorId; + if (!string.IsNullOrEmpty(mandateId) && !Regex.IsMatch(mandateId.Replace(" ", ""), @"^([A-Za-z0-9]|[\+|\?|/|\-|:|\(|\)|\.|,|']){1,35}$")) + throw new BezahlCodeException("The mandateId entered isn't valid."); + this.mandateId = mandateId; + if (dateOfSignature != null) + this.dateOfSignature = (DateTime)dateOfSignature; + } + } + + //Checks for all payment types + if (authority != AuthorityType.contact && authority != AuthorityType.contact_v2) + { + if (amount.ToString().Replace(",", ".").Contains(".") && amount.ToString().Replace(",", ".").Split('.')[1].TrimEnd('0').Length > 2) + throw new BezahlCodeException("Amount must have less than 3 digits after decimal point."); + if (amount < 0.01m || amount > 999999999.99m) + throw new BezahlCodeException("Amount has to at least 0.01 and must be smaller or equal to 999999999.99."); + this.amount = amount; + + this.currency = currency; + + if (executionDate == null) + this.executionDate = DateTime.Now; + else + { + if (DateTime.Today.Ticks > executionDate.Value.Ticks) + throw new BezahlCodeException("Execution date must be today or in future."); + this.executionDate = (DateTime)executionDate; + } +#pragma warning disable CS0612 + if (authority == AuthorityType.periodicsinglepayment || authority == AuthorityType.periodicsinglepaymentsepa) +#pragma warning restore CS0612 + { + if (periodicTimeunit.ToUpper() != "M" && periodicTimeunit.ToUpper() != "W") + throw new BezahlCodeException("The periodicTimeunit must be either 'M' (monthly) or 'W' (weekly)."); + this.periodicTimeunit = periodicTimeunit; + if (periodicTimeunitRotation < 1 || periodicTimeunitRotation > 52) + throw new BezahlCodeException("The periodicTimeunitRotation must be 1 or greater. (It means repeat the payment every 'periodicTimeunitRotation' weeks/months."); + this.periodicTimeunitRotation = periodicTimeunitRotation; + if (periodicFirstExecutionDate != null) + this.periodicFirstExecutionDate = (DateTime)periodicFirstExecutionDate; + if (periodicLastExecutionDate != null) + this.periodicLastExecutionDate = (DateTime)periodicLastExecutionDate; + } + + } + + + + } + + public override string ToString() + { + var bezahlCodePayload = $"bank://{authority}?"; + + bezahlCodePayload += $"name={Uri.EscapeDataString(name)}&"; + + if (authority != AuthorityType.contact && authority != AuthorityType.contact_v2) + { + //Handle what is same for all payments +#pragma warning disable CS0612 + if (authority == AuthorityType.periodicsinglepayment || authority == AuthorityType.singledirectdebit || authority == AuthorityType.singlepayment) +#pragma warning restore CS0612 + { + bezahlCodePayload += $"account={account}&"; + bezahlCodePayload += $"bnc={bnc}&"; + if (postingKey > 0) + bezahlCodePayload += $"postingkey={postingKey}&"; + } + else + { + bezahlCodePayload += $"iban={iban}&"; + bezahlCodePayload += $"bic={bic}&"; + + if (!string.IsNullOrEmpty(sepaReference)) + bezahlCodePayload += $"separeference={ Uri.EscapeDataString(sepaReference)}&"; + + if (authority == AuthorityType.singledirectdebitsepa) + { + if (!string.IsNullOrEmpty(creditorId)) + bezahlCodePayload += $"creditorid={ Uri.EscapeDataString(creditorId)}&"; + if (!string.IsNullOrEmpty(mandateId)) + bezahlCodePayload += $"mandateid={ Uri.EscapeDataString(mandateId)}&"; + if (dateOfSignature != DateTime.MinValue) + bezahlCodePayload += $"dateofsignature={dateOfSignature.ToString("ddMMyyyy")}&"; + } + } + bezahlCodePayload += $"amount={amount:0.00}&".Replace(".", ","); + + if (!string.IsNullOrEmpty(reason)) + bezahlCodePayload += $"reason={ Uri.EscapeDataString(reason)}&"; + bezahlCodePayload += $"currency={currency}&"; + bezahlCodePayload += $"executiondate={executionDate.ToString("ddMMyyyy")}&"; +#pragma warning disable CS0612 + if (authority == AuthorityType.periodicsinglepayment || authority == AuthorityType.periodicsinglepaymentsepa) + { + bezahlCodePayload += $"periodictimeunit={periodicTimeunit}&"; + bezahlCodePayload += $"periodictimeunitrotation={periodicTimeunitRotation}&"; + if (periodicFirstExecutionDate != DateTime.MinValue) + bezahlCodePayload += $"periodicfirstexecutiondate={periodicFirstExecutionDate.ToString("ddMMyyyy")}&"; + if (periodicLastExecutionDate != DateTime.MinValue) + bezahlCodePayload += $"periodiclastexecutiondate={periodicLastExecutionDate.ToString("ddMMyyyy")}&"; + } +#pragma warning restore CS0612 + } + else + { + //Handle what is same for all contacts + if (authority == AuthorityType.contact) + { + bezahlCodePayload += $"account={account}&"; + bezahlCodePayload += $"bnc={bnc}&"; + } + else if (authority == AuthorityType.contact_v2) + { + if (!string.IsNullOrEmpty(account) && !string.IsNullOrEmpty(bnc)) + { + bezahlCodePayload += $"account={account}&"; + bezahlCodePayload += $"bnc={bnc}&"; + } + else + { + bezahlCodePayload += $"iban={iban}&"; + bezahlCodePayload += $"bic={bic}&"; + } + } + + if (!string.IsNullOrEmpty(reason)) + bezahlCodePayload += $"reason={ Uri.EscapeDataString(reason)}&"; + } + + return bezahlCodePayload.Trim('&'); + } + + /// + /// ISO 4217 currency codes + /// + public enum Currency + { + AED = 784, + AFN = 971, + ALL = 008, + AMD = 051, + ANG = 532, + AOA = 973, + ARS = 032, + AUD = 036, + AWG = 533, + AZN = 944, + BAM = 977, + BBD = 052, + BDT = 050, + BGN = 975, + BHD = 048, + BIF = 108, + BMD = 060, + BND = 096, + BOB = 068, + BOV = 984, + BRL = 986, + BSD = 044, + BTN = 064, + BWP = 072, + BYR = 974, + BZD = 084, + CAD = 124, + CDF = 976, + CHE = 947, + CHF = 756, + CHW = 948, + CLF = 990, + CLP = 152, + CNY = 156, + COP = 170, + COU = 970, + CRC = 188, + CUC = 931, + CUP = 192, + CVE = 132, + CZK = 203, + DJF = 262, + DKK = 208, + DOP = 214, + DZD = 012, + EGP = 818, + ERN = 232, + ETB = 230, + EUR = 978, + FJD = 242, + FKP = 238, + GBP = 826, + GEL = 981, + GHS = 936, + GIP = 292, + GMD = 270, + GNF = 324, + GTQ = 320, + GYD = 328, + HKD = 344, + HNL = 340, + HRK = 191, + HTG = 332, + HUF = 348, + IDR = 360, + ILS = 376, + INR = 356, + IQD = 368, + IRR = 364, + ISK = 352, + JMD = 388, + JOD = 400, + JPY = 392, + KES = 404, + KGS = 417, + KHR = 116, + KMF = 174, + KPW = 408, + KRW = 410, + KWD = 414, + KYD = 136, + KZT = 398, + LAK = 418, + LBP = 422, + LKR = 144, + LRD = 430, + LSL = 426, + LYD = 434, + MAD = 504, + MDL = 498, + MGA = 969, + MKD = 807, + MMK = 104, + MNT = 496, + MOP = 446, + MRO = 478, + MUR = 480, + MVR = 462, + MWK = 454, + MXN = 484, + MXV = 979, + MYR = 458, + MZN = 943, + NAD = 516, + NGN = 566, + NIO = 558, + NOK = 578, + NPR = 524, + NZD = 554, + OMR = 512, + PAB = 590, + PEN = 604, + PGK = 598, + PHP = 608, + PKR = 586, + PLN = 985, + PYG = 600, + QAR = 634, + RON = 946, + RSD = 941, + RUB = 643, + RWF = 646, + SAR = 682, + SBD = 090, + SCR = 690, + SDG = 938, + SEK = 752, + SGD = 702, + SHP = 654, + SLL = 694, + SOS = 706, + SRD = 968, + SSP = 728, + STD = 678, + SVC = 222, + SYP = 760, + SZL = 748, + THB = 764, + TJS = 972, + TMT = 934, + TND = 788, + TOP = 776, + TRY = 949, + TTD = 780, + TWD = 901, + TZS = 834, + UAH = 980, + UGX = 800, + USD = 840, + USN = 997, + UYI = 940, + UYU = 858, + UZS = 860, + VEF = 937, + VND = 704, + VUV = 548, + WST = 882, + XAF = 950, + XAG = 961, + XAU = 959, + XBA = 955, + XBB = 956, + XBC = 957, + XBD = 958, + XCD = 951, + XDR = 960, + XOF = 952, + XPD = 964, + XPF = 953, + XPT = 962, + XSU = 994, + XTS = 963, + XUA = 965, + XXX = 999, + YER = 886, + ZAR = 710, + ZMW = 967, + ZWL = 932 + } + + + /// + /// Operation modes of the BezahlCode + /// + public enum AuthorityType + { + /// + /// Single payment (Überweisung) + /// + [Obsolete] + singlepayment, + /// + /// Single SEPA payment (SEPA-Überweisung) + /// + singlepaymentsepa, + /// + /// Single debit (Lastschrift) + /// + [Obsolete] + singledirectdebit, + /// + /// Single SEPA debit (SEPA-Lastschrift) + /// + singledirectdebitsepa, + /// + /// Periodic payment (Dauerauftrag) + /// + [Obsolete] + periodicsinglepayment, + /// + /// Periodic SEPA payment (SEPA-Dauerauftrag) + /// + periodicsinglepaymentsepa, + /// + /// Contact data + /// + contact, + /// + /// Contact data V2 + /// + contact_v2 + } + + public class BezahlCodeException : Exception + { + public BezahlCodeException() + { + } + + public BezahlCodeException(string message) + : base(message) + { + } + + public BezahlCodeException(string message, Exception inner) + : base(message, inner) + { + } + } + } + } +} diff --git a/QRCoder/PayloadGenerator/BitcoinAddress.cs b/QRCoder/PayloadGenerator/BitcoinAddress.cs new file mode 100644 index 00000000..f1391d03 --- /dev/null +++ b/QRCoder/PayloadGenerator/BitcoinAddress.cs @@ -0,0 +1,15 @@ +#if NETSTANDARD1_3 +using System.Reflection; +#endif + +namespace QRCoder +{ + public static partial class PayloadGenerator + { + public class BitcoinAddress : BitcoinLikeCryptoCurrencyAddress + { + public BitcoinAddress(string address, double? amount, string label = null, string message = null) + : base(BitcoinLikeCryptoCurrencyType.Bitcoin, address, amount, label, message) { } + } + } +} diff --git a/QRCoder/PayloadGenerator/BitcoinCashAddress.cs b/QRCoder/PayloadGenerator/BitcoinCashAddress.cs new file mode 100644 index 00000000..4cc937d1 --- /dev/null +++ b/QRCoder/PayloadGenerator/BitcoinCashAddress.cs @@ -0,0 +1,15 @@ +#if NETSTANDARD1_3 +using System.Reflection; +#endif + +namespace QRCoder +{ + public static partial class PayloadGenerator + { + public class BitcoinCashAddress : BitcoinLikeCryptoCurrencyAddress + { + public BitcoinCashAddress(string address, double? amount, string label = null, string message = null) + : base(BitcoinLikeCryptoCurrencyType.BitcoinCash, address, amount, label, message) { } + } + } +} diff --git a/QRCoder/PayloadGenerator/BitcoinLikeCryptoCurrencyAddress.cs b/QRCoder/PayloadGenerator/BitcoinLikeCryptoCurrencyAddress.cs new file mode 100644 index 00000000..44021149 --- /dev/null +++ b/QRCoder/PayloadGenerator/BitcoinLikeCryptoCurrencyAddress.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Globalization; +#if NETSTANDARD1_3 +using System.Reflection; +#endif + +namespace QRCoder +{ + public static partial class PayloadGenerator + { + public class BitcoinLikeCryptoCurrencyAddress : Payload + { + private readonly BitcoinLikeCryptoCurrencyType currencyType; + private readonly string address, label, message; + private readonly double? amount; + + /// + /// Generates a Bitcoin like cryptocurrency payment payload. QR Codes with this payload can open a payment app. + /// + /// Bitcoin like cryptocurrency address of the payment receiver + /// Bitcoin like cryptocurrency address of the payment receiver + /// Amount of coins to transfer + /// Reference label + /// Referece text aka message + public BitcoinLikeCryptoCurrencyAddress(BitcoinLikeCryptoCurrencyType currencyType, string address, double? amount, string label = null, string message = null) + { + this.currencyType = currencyType; + this.address = address; + + if (!string.IsNullOrEmpty(label)) + { + this.label = Uri.EscapeDataString(label); + } + + if (!string.IsNullOrEmpty(message)) + { + this.message = Uri.EscapeDataString(message); + } + + this.amount = amount; + } + + public override string ToString() + { + string query = null; + + var queryValues = new KeyValuePair[]{ + new KeyValuePair(nameof(label), label), + new KeyValuePair(nameof(message), message), + new KeyValuePair(nameof(amount), amount.HasValue ? amount.Value.ToString("#.########", CultureInfo.InvariantCulture) : null) + }; + + if (queryValues.Any(keyPair => !string.IsNullOrEmpty(keyPair.Value))) + { + query = "?" + string.Join("&", queryValues + .Where(keyPair => !string.IsNullOrEmpty(keyPair.Value)) + .Select(keyPair => $"{keyPair.Key}={keyPair.Value}") + .ToArray()); + } + + return $"{Enum.GetName(typeof(BitcoinLikeCryptoCurrencyType), currencyType).ToLower()}:{address}{query}"; + } + + public enum BitcoinLikeCryptoCurrencyType + { + Bitcoin, + BitcoinCash, + Litecoin + } + } + } +} diff --git a/QRCoder/PayloadGenerator/Bookmark.cs b/QRCoder/PayloadGenerator/Bookmark.cs new file mode 100644 index 00000000..2072caf8 --- /dev/null +++ b/QRCoder/PayloadGenerator/Bookmark.cs @@ -0,0 +1,30 @@ +#if NETSTANDARD1_3 +using System.Reflection; +#endif + +namespace QRCoder +{ + public static partial class PayloadGenerator + { + public class Bookmark : Payload + { + private readonly string url, title; + + /// + /// Generates a bookmark payload. Scanned by an QR Code reader, this one creates a browser bookmark. + /// + /// Url of the bookmark + /// Title of the bookmark + public Bookmark(string url, string title) + { + this.url = EscapeInput(url); + this.title = EscapeInput(title); + } + + public override string ToString() + { + return $"MEBKM:TITLE:{this.title};URL:{this.url};;"; + } + } + } +} diff --git a/QRCoder/PayloadGenerator/CalendarEvent.cs b/QRCoder/PayloadGenerator/CalendarEvent.cs new file mode 100644 index 00000000..0f22e2f4 --- /dev/null +++ b/QRCoder/PayloadGenerator/CalendarEvent.cs @@ -0,0 +1,81 @@ +using System; +#if NETSTANDARD1_3 +using System.Reflection; +#endif + +namespace QRCoder +{ + public static partial class PayloadGenerator + { + public class CalendarEvent : Payload + { + private readonly string subject, description, location, start, end; + private readonly EventEncoding encoding; + + /// + /// Generates a calender entry/event payload. + /// + /// Subject/title of the calender event + /// Description of the event + /// Location (lat:long or address) of the event + /// Start time (incl. UTC offset) of the event + /// End time (incl. UTC offset) of the event + /// Is it a full day event? + /// Type of encoding (universal or iCal) + public CalendarEvent(string subject, string description, string location, DateTimeOffset start, DateTimeOffset end, bool allDayEvent, EventEncoding encoding = EventEncoding.Universal) : this(subject, description, location, start.UtcDateTime, end.UtcDateTime, allDayEvent, encoding) + { + } + + /// + /// Generates a calender entry/event payload. + /// + /// Subject/title of the calender event + /// Description of the event + /// Location (lat:long or address) of the event + /// Start time of the event + /// End time of the event + /// Is it a full day event? + /// Type of encoding (universal or iCal) + public CalendarEvent(string subject, string description, string location, DateTime start, DateTime end, bool allDayEvent, EventEncoding encoding = EventEncoding.Universal) + { + this.subject = subject; + this.description = description; + this.location = location; + this.encoding = encoding; + string dtFormatStart = "yyyyMMdd", dtFormatEnd = "yyyyMMdd"; + if (!allDayEvent) + { + dtFormatStart = dtFormatEnd = "yyyyMMddTHHmmss"; + if (start.Kind == DateTimeKind.Utc) + dtFormatStart = "yyyyMMddTHHmmssZ"; + if (end.Kind == DateTimeKind.Utc) + dtFormatEnd = "yyyyMMddTHHmmssZ"; + } + this.start = start.ToString(dtFormatStart); + this.end = end.ToString(dtFormatEnd); + } + + public override string ToString() + { + var vEvent = $"BEGIN:VEVENT{Environment.NewLine}"; + vEvent += $"SUMMARY:{this.subject}{Environment.NewLine}"; + vEvent += !string.IsNullOrEmpty(this.description) ? $"DESCRIPTION:{this.description}{Environment.NewLine}" : ""; + vEvent += !string.IsNullOrEmpty(this.location) ? $"LOCATION:{this.location}{Environment.NewLine}" : ""; + vEvent += $"DTSTART:{this.start}{Environment.NewLine}"; + vEvent += $"DTEND:{this.end}{Environment.NewLine}"; + vEvent += "END:VEVENT"; + + if (this.encoding == EventEncoding.iCalComplete) + vEvent = $@"BEGIN:VCALENDAR{Environment.NewLine}VERSION:2.0{Environment.NewLine}{vEvent}{Environment.NewLine}END:VCALENDAR"; + + return vEvent; + } + + public enum EventEncoding + { + iCalComplete, + Universal + } + } + } +} diff --git a/QRCoder/PayloadGenerator/ContactData.cs b/QRCoder/PayloadGenerator/ContactData.cs new file mode 100644 index 00000000..6c2db8ee --- /dev/null +++ b/QRCoder/PayloadGenerator/ContactData.cs @@ -0,0 +1,240 @@ +using System; +#if NETSTANDARD1_3 +using System.Reflection; +#endif + +namespace QRCoder +{ + public static partial class PayloadGenerator + { + public class ContactData : Payload + { + private readonly string firstname; + private readonly string lastname; + private readonly string nickname; + private readonly string org; + private readonly string orgTitle; + private readonly string phone; + private readonly string mobilePhone; + private readonly string workPhone; + private readonly string email; + private readonly DateTime? birthday; + private readonly string website; + private readonly string street; + private readonly string houseNumber; + private readonly string city; + private readonly string zipCode; + private readonly string stateRegion; + private readonly string country; + private readonly string note; + private readonly ContactOutputType outputType; + private readonly AddressOrder addressOrder; + + + /// + /// Generates a vCard or meCard contact dataset + /// + /// Payload output type + /// The firstname + /// The lastname + /// The displayname + /// Normal phone number + /// Mobile phone + /// Office phone number + /// E-Mail address + /// Birthday + /// Website / Homepage + /// Street + /// Housenumber + /// City + /// State or Region + /// Zip code + /// Country + /// The address order format to use + /// Memo text / notes + /// Organisation/Company + /// Organisation/Company Title + public ContactData(ContactOutputType outputType, string firstname, string lastname, string nickname = null, string phone = null, string mobilePhone = null, string workPhone = null, string email = null, DateTime? birthday = null, string website = null, string street = null, string houseNumber = null, string city = null, string zipCode = null, string country = null, string note = null, string stateRegion = null, AddressOrder addressOrder = AddressOrder.Default, string org = null, string orgTitle = null) + { + this.firstname = firstname; + this.lastname = lastname; + this.nickname = nickname; + this.org = org; + this.orgTitle = orgTitle; + this.phone = phone; + this.mobilePhone = mobilePhone; + this.workPhone = workPhone; + this.email = email; + this.birthday = birthday; + this.website = website; + this.street = street; + this.houseNumber = houseNumber; + this.city = city; + this.stateRegion = stateRegion; + this.zipCode = zipCode; + this.country = country; + this.addressOrder = addressOrder; + this.note = note; + this.outputType = outputType; + } + + public override string ToString() + { + string payload = string.Empty; + if (outputType == ContactOutputType.MeCard) + { + payload += "MECARD+\r\n"; + if (!string.IsNullOrEmpty(firstname) && !string.IsNullOrEmpty(lastname)) + payload += $"N:{lastname}, {firstname}\r\n"; + else if (!string.IsNullOrEmpty(firstname) || !string.IsNullOrEmpty(lastname)) + payload += $"N:{firstname}{lastname}\r\n"; + if (!string.IsNullOrEmpty(org)) + payload += $"ORG:{org}\r\n"; + if (!string.IsNullOrEmpty(orgTitle)) + payload += $"TITLE:{orgTitle}\r\n"; + if (!string.IsNullOrEmpty(phone)) + payload += $"TEL:{phone}\r\n"; + if (!string.IsNullOrEmpty(mobilePhone)) + payload += $"TEL:{mobilePhone}\r\n"; + if (!string.IsNullOrEmpty(workPhone)) + payload += $"TEL:{workPhone}\r\n"; + if (!string.IsNullOrEmpty(email)) + payload += $"EMAIL:{email}\r\n"; + if (!string.IsNullOrEmpty(note)) + payload += $"NOTE:{note}\r\n"; + if (birthday != null) + payload += $"BDAY:{((DateTime)birthday).ToString("yyyyMMdd")}\r\n"; + string addressString = string.Empty; + if(addressOrder == AddressOrder.Default) + { + addressString = $"ADR:,,{(!string.IsNullOrEmpty(street) ? street + " " : "")}{(!string.IsNullOrEmpty(houseNumber) ? houseNumber : "")},{(!string.IsNullOrEmpty(zipCode) ? zipCode : "")},{(!string.IsNullOrEmpty(city) ? city : "")},{(!string.IsNullOrEmpty(stateRegion) ? stateRegion : "")},{(!string.IsNullOrEmpty(country) ? country : "")}\r\n"; + } + else + { + addressString = $"ADR:,,{(!string.IsNullOrEmpty(houseNumber) ? houseNumber + " " : "")}{(!string.IsNullOrEmpty(street) ? street : "")},{(!string.IsNullOrEmpty(city) ? city : "")},{(!string.IsNullOrEmpty(stateRegion) ? stateRegion : "")},{(!string.IsNullOrEmpty(zipCode) ? zipCode : "")},{(!string.IsNullOrEmpty(country) ? country : "")}\r\n"; + } + payload += addressString; + if (!string.IsNullOrEmpty(website)) + payload += $"URL:{website}\r\n"; + if (!string.IsNullOrEmpty(nickname)) + payload += $"NICKNAME:{nickname}\r\n"; + payload = payload.Trim(new char[] { '\r', '\n' }); + } + else + { + var version = outputType.ToString().Substring(5); + if (version.Length > 1) + version = version.Insert(1, "."); + else + version += ".0"; + + payload += "BEGIN:VCARD\r\n"; + payload += $"VERSION:{version}\r\n"; + + payload += $"N:{(!string.IsNullOrEmpty(lastname) ? lastname : "")};{(!string.IsNullOrEmpty(firstname) ? firstname : "")};;;\r\n"; + payload += $"FN:{(!string.IsNullOrEmpty(firstname) ? firstname + " " : "")}{(!string.IsNullOrEmpty(lastname) ? lastname : "")}\r\n"; + if (!string.IsNullOrEmpty(org)) + { + payload += $"ORG:" + org + "\r\n"; + } + if (!string.IsNullOrEmpty(orgTitle)) + { + payload += $"TITLE:" + orgTitle + "\r\n"; + } + if (!string.IsNullOrEmpty(phone)) + { + payload += $"TEL;"; + if (outputType == ContactOutputType.VCard21) + payload += $"HOME;VOICE:{phone}"; + else if (outputType == ContactOutputType.VCard3) + payload += $"TYPE=HOME,VOICE:{phone}"; + else + payload += $"TYPE=home,voice;VALUE=uri:tel:{phone}"; + payload += "\r\n"; + } + + if (!string.IsNullOrEmpty(mobilePhone)) + { + payload += $"TEL;"; + if (outputType == ContactOutputType.VCard21) + payload += $"HOME;CELL:{mobilePhone}"; + else if (outputType == ContactOutputType.VCard3) + payload += $"TYPE=HOME,CELL:{mobilePhone}"; + else + payload += $"TYPE=home,cell;VALUE=uri:tel:{mobilePhone}"; + payload += "\r\n"; + } + + if (!string.IsNullOrEmpty(workPhone)) + { + payload += $"TEL;"; + if (outputType == ContactOutputType.VCard21) + payload += $"WORK;VOICE:{workPhone}"; + else if (outputType == ContactOutputType.VCard3) + payload += $"TYPE=WORK,VOICE:{workPhone}"; + else + payload += $"TYPE=work,voice;VALUE=uri:tel:{workPhone}"; + payload += "\r\n"; + } + + + payload += "ADR;"; + if (outputType == ContactOutputType.VCard21) + payload += "HOME;PREF:"; + else if (outputType == ContactOutputType.VCard3) + payload += "TYPE=HOME,PREF:"; + else + payload += "TYPE=home,pref:"; + string addressString = string.Empty; + if(addressOrder == AddressOrder.Default) + { + addressString = $";;{(!string.IsNullOrEmpty(street) ? street + " " : "")}{(!string.IsNullOrEmpty(houseNumber) ? houseNumber : "")};{(!string.IsNullOrEmpty(zipCode) ? zipCode : "")};{(!string.IsNullOrEmpty(city) ? city : "")};{(!string.IsNullOrEmpty(stateRegion) ? stateRegion : "")};{(!string.IsNullOrEmpty(country) ? country : "")}\r\n"; + } + else + { + addressString = $";;{(!string.IsNullOrEmpty(houseNumber) ? houseNumber + " " : "")}{(!string.IsNullOrEmpty(street) ? street : "")};{(!string.IsNullOrEmpty(city) ? city : "")};{(!string.IsNullOrEmpty(stateRegion) ? stateRegion : "")};{(!string.IsNullOrEmpty(zipCode) ? zipCode : "")};{(!string.IsNullOrEmpty(country) ? country : "")}\r\n"; + } + payload += addressString; + + if (birthday != null) + payload += $"BDAY:{((DateTime)birthday).ToString("yyyyMMdd")}\r\n"; + if (!string.IsNullOrEmpty(website)) + payload += $"URL:{website}\r\n"; + if (!string.IsNullOrEmpty(email)) + payload += $"EMAIL:{email}\r\n"; + if (!string.IsNullOrEmpty(note)) + payload += $"NOTE:{note}\r\n"; + if (outputType != ContactOutputType.VCard21 && !string.IsNullOrEmpty(nickname)) + payload += $"NICKNAME:{nickname}\r\n"; + + payload += "END:VCARD"; + } + + return payload; + } + + /// + /// Possible output types. Either vCard 2.1, vCard 3.0, vCard 4.0 or MeCard. + /// + public enum ContactOutputType + { + MeCard, + VCard21, + VCard3, + VCard4 + } + + + /// + /// define the address format + /// Default: European format, ([Street] [House Number] and [Postal Code] [City] + /// Reversed: North American and others format ([House Number] [Street] and [City] [Postal Code]) + /// + public enum AddressOrder + { + Default, + Reversed + } + } + } +} diff --git a/QRCoder/PayloadGenerator/Geolocation.cs b/QRCoder/PayloadGenerator/Geolocation.cs new file mode 100644 index 00000000..f6e351e1 --- /dev/null +++ b/QRCoder/PayloadGenerator/Geolocation.cs @@ -0,0 +1,47 @@ +#if NETSTANDARD1_3 +using System.Reflection; +#endif + +namespace QRCoder +{ + public static partial class PayloadGenerator + { + public class Geolocation : Payload + { + private readonly string latitude, longitude; + private readonly GeolocationEncoding encoding; + + /// + /// Generates a geo location payload. Supports raw location (GEO encoding) or Google Maps link (GoogleMaps encoding) + /// + /// Latitude with . as splitter + /// Longitude with . as splitter + /// Encoding type - GEO or GoogleMaps + public Geolocation(string latitude, string longitude, GeolocationEncoding encoding = GeolocationEncoding.GEO) + { + this.latitude = latitude.Replace(",","."); + this.longitude = longitude.Replace(",", "."); + this.encoding = encoding; + } + + public override string ToString() + { + switch (this.encoding) + { + case GeolocationEncoding.GEO: + return $"geo:{this.latitude},{this.longitude}"; + case GeolocationEncoding.GoogleMaps: + return $"http://maps.google.com/maps?q={this.latitude},{this.longitude}"; + default: + return "geo:"; + } + } + + public enum GeolocationEncoding + { + GEO, + GoogleMaps + } + } + } +} diff --git a/QRCoder/PayloadGenerator/Girocode.cs b/QRCoder/PayloadGenerator/Girocode.cs new file mode 100644 index 00000000..3083fcf9 --- /dev/null +++ b/QRCoder/PayloadGenerator/Girocode.cs @@ -0,0 +1,135 @@ +using System; +#if NETSTANDARD1_3 +using System.Reflection; +#endif + +namespace QRCoder +{ + public static partial class PayloadGenerator + { + public class Girocode : Payload + { + //Keep in mind, that the ECC level has to be set to "M" when generating a Girocode! + //Girocode specification: http://www.europeanpaymentscouncil.eu/index.cfm/knowledge-bank/epc-documents/quick-response-code-guidelines-to-enable-data-capture-for-the-initiation-of-a-sepa-credit-transfer/epc069-12-quick-response-code-guidelines-to-enable-data-capture-for-the-initiation-of-a-sepa-credit-transfer1/ + + private string br = "\n"; + private readonly string iban, bic, name, purposeOfCreditTransfer, remittanceInformation, messageToGirocodeUser; + private readonly decimal amount; + private readonly GirocodeVersion version; + private readonly GirocodeEncoding encoding; + private readonly TypeOfRemittance typeOfRemittance; + + public override QRCodeGenerator.ECCLevel EccLevel { get { return QRCodeGenerator.ECCLevel.M; } } + + /// + /// Generates the payload for a Girocode (QR-Code with credit transfer information). + /// Attention: When using Girocode payload, QR code must be generated with ECC level M! + /// + /// Account number of the Beneficiary. Only IBAN is allowed. + /// BIC of the Beneficiary Bank. + /// Name of the Beneficiary. + /// Amount of the Credit Transfer in Euro. + /// (Amount must be more than 0.01 and less than 999999999.99) + /// Remittance Information (Purpose-/reference text). (optional) + /// Type of remittance information. Either structured (e.g. ISO 11649 RF Creditor Reference) and max. 35 chars or unstructured and max. 140 chars. + /// Purpose of the Credit Transfer (optional) + /// Beneficiary to originator information. (optional) + /// Girocode version. Either 001 or 002. Default: 001. + /// Encoding of the Girocode payload. Default: ISO-8859-1 + public Girocode(string iban, string bic, string name, decimal amount, string remittanceInformation = "", TypeOfRemittance typeOfRemittance = TypeOfRemittance.Unstructured, string purposeOfCreditTransfer = "", string messageToGirocodeUser = "", GirocodeVersion version = GirocodeVersion.Version1, GirocodeEncoding encoding = GirocodeEncoding.ISO_8859_1) + { + this.version = version; + this.encoding = encoding; + if (!IsValidIban(iban)) + throw new GirocodeException("The IBAN entered isn't valid."); + this.iban = iban.Replace(" ","").ToUpper(); + if (!IsValidBic(bic)) + throw new GirocodeException("The BIC entered isn't valid."); + this.bic = bic.Replace(" ", "").ToUpper(); + if (name.Length > 70) + throw new GirocodeException("(Payee-)Name must be shorter than 71 chars."); + this.name = name; + if (amount.ToString().Replace(",", ".").Contains(".") && amount.ToString().Replace(",",".").Split('.')[1].TrimEnd('0').Length > 2) + throw new GirocodeException("Amount must have less than 3 digits after decimal point."); + if (amount < 0.01m || amount > 999999999.99m) + throw new GirocodeException("Amount has to at least 0.01 and must be smaller or equal to 999999999.99."); + this.amount = amount; + if (purposeOfCreditTransfer.Length > 4) + throw new GirocodeException("Purpose of credit transfer can only have 4 chars at maximum."); + this.purposeOfCreditTransfer = purposeOfCreditTransfer; + if (typeOfRemittance == TypeOfRemittance.Unstructured && remittanceInformation.Length > 140) + throw new GirocodeException("Unstructured reference texts have to shorter than 141 chars."); + if (typeOfRemittance == TypeOfRemittance.Structured && remittanceInformation.Length > 35) + throw new GirocodeException("Structured reference texts have to shorter than 36 chars."); + this.typeOfRemittance = typeOfRemittance; + this.remittanceInformation = remittanceInformation; + if (messageToGirocodeUser.Length > 70) + throw new GirocodeException("Message to the Girocode-User reader texts have to shorter than 71 chars."); + this.messageToGirocodeUser = messageToGirocodeUser; + } + + public override string ToString() + { + var girocodePayload = "BCD" + br; + girocodePayload += ((version == GirocodeVersion.Version1) ? "001" : "002") + br; + girocodePayload += (int)encoding + 1 + br; + girocodePayload += "SCT" + br; + girocodePayload += bic + br; + girocodePayload += name + br; + girocodePayload += iban + br; + girocodePayload += $"EUR{amount:0.00}".Replace(",",".") + br; + girocodePayload += purposeOfCreditTransfer + br; + girocodePayload += ((typeOfRemittance == TypeOfRemittance.Structured) + ? remittanceInformation + : string.Empty) + br; + girocodePayload += ((typeOfRemittance == TypeOfRemittance.Unstructured) + ? remittanceInformation + : string.Empty) + br; + girocodePayload += messageToGirocodeUser; + + return ConvertStringToEncoding(girocodePayload, encoding.ToString().Replace("_","-")); + } + + public enum GirocodeVersion + { + Version1, + Version2 + } + + public enum TypeOfRemittance + { + Structured, + Unstructured + } + + public enum GirocodeEncoding + { + UTF_8, + ISO_8859_1, + ISO_8859_2, + ISO_8859_4, + ISO_8859_5, + ISO_8859_7, + ISO_8859_10, + ISO_8859_15 + } + + public class GirocodeException : Exception + { + public GirocodeException() + { + } + + public GirocodeException(string message) + : base(message) + { + } + + public GirocodeException(string message, Exception inner) + : base(message, inner) + { + } + } + } + } +} diff --git a/QRCoder/PayloadGenerator/LitecoinAddress.cs b/QRCoder/PayloadGenerator/LitecoinAddress.cs new file mode 100644 index 00000000..dfd741f3 --- /dev/null +++ b/QRCoder/PayloadGenerator/LitecoinAddress.cs @@ -0,0 +1,15 @@ +#if NETSTANDARD1_3 +using System.Reflection; +#endif + +namespace QRCoder +{ + public static partial class PayloadGenerator + { + public class LitecoinAddress : BitcoinLikeCryptoCurrencyAddress + { + public LitecoinAddress(string address, double? amount, string label = null, string message = null) + : base(BitcoinLikeCryptoCurrencyType.Litecoin, address, amount, label, message) { } + } + } +} diff --git a/QRCoder/PayloadGenerator/MMS.cs b/QRCoder/PayloadGenerator/MMS.cs new file mode 100644 index 00000000..d1ac362d --- /dev/null +++ b/QRCoder/PayloadGenerator/MMS.cs @@ -0,0 +1,68 @@ +using System; +#if NETSTANDARD1_3 +using System.Reflection; +#endif + +namespace QRCoder +{ + public static partial class PayloadGenerator + { + public class MMS : Payload + { + private readonly string number, subject; + private readonly MMSEncoding encoding; + + /// + /// Creates a MMS payload without text + /// + /// Receiver phone number + /// Encoding type + public MMS(string number, MMSEncoding encoding = MMSEncoding.MMS) + { + this.number = number; + this.subject = string.Empty; + this.encoding = encoding; + } + + /// + /// Creates a MMS payload with text (subject) + /// + /// Receiver phone number + /// Text of the MMS + /// Encoding type + public MMS(string number, string subject, MMSEncoding encoding = MMSEncoding.MMS) + { + this.number = number; + this.subject = subject; + this.encoding = encoding; + } + + public override string ToString() + { + var returnVal = string.Empty; + switch (this.encoding) + { + case MMSEncoding.MMSTO: + var queryStringMmsTo = string.Empty; + if (!string.IsNullOrEmpty(this.subject)) + queryStringMmsTo = $"?subject={Uri.EscapeDataString(this.subject)}"; + returnVal = $"mmsto:{this.number}{queryStringMmsTo}"; + break; + case MMSEncoding.MMS: + var queryStringMms = string.Empty; + if (!string.IsNullOrEmpty(this.subject)) + queryStringMms = $"?body={Uri.EscapeDataString(this.subject)}"; + returnVal = $"mms:{this.number}{queryStringMms}"; + break; + } + return returnVal; + } + + public enum MMSEncoding + { + MMS, + MMSTO + } + } + } +} diff --git a/QRCoder/PayloadGenerator/Mail.cs b/QRCoder/PayloadGenerator/Mail.cs new file mode 100644 index 00000000..3643cac7 --- /dev/null +++ b/QRCoder/PayloadGenerator/Mail.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Linq; +#if NETSTANDARD1_3 +using System.Reflection; +#endif + +namespace QRCoder +{ + public static partial class PayloadGenerator + { + public class Mail : Payload + { + private readonly string mailReceiver, subject, message; + private readonly MailEncoding encoding; + + + /// + /// Creates an email payload with subject and message/text + /// + /// Receiver's email address + /// Subject line of the email + /// Message content of the email + /// Payload encoding type. Choose dependent on your QR Code scanner app. + public Mail(string mailReceiver = null, string subject = null, string message = null, MailEncoding encoding = MailEncoding.MAILTO) + { + this.mailReceiver = mailReceiver; + this.subject = subject; + this.message = message; + this.encoding = encoding; + } + + public override string ToString() + { + var returnVal = string.Empty; + switch (this.encoding) + { + case MailEncoding.MAILTO: + var parts = new List(); + if (!string.IsNullOrEmpty(this.subject)) + parts.Add("subject=" + Uri.EscapeDataString(this.subject)); + if (!string.IsNullOrEmpty(this.message)) + parts.Add("body=" + Uri.EscapeDataString(this.message)); + var queryString = parts.Any() ? $"?{string.Join("&", parts.ToArray())}" : ""; + returnVal = $"mailto:{this.mailReceiver}{queryString}"; + break; + case MailEncoding.MATMSG: + returnVal = $"MATMSG:TO:{this.mailReceiver};SUB:{EscapeInput(this.subject)};BODY:{EscapeInput(this.message)};;"; + break; + case MailEncoding.SMTP: + returnVal = $"SMTP:{this.mailReceiver}:{EscapeInput(this.subject, true)}:{EscapeInput(this.message, true)}"; + break; + } + return returnVal; + } + + public enum MailEncoding + { + MAILTO, + MATMSG, + SMTP + } + } + } +} diff --git a/QRCoder/PayloadGenerator/MoneroTransaction.cs b/QRCoder/PayloadGenerator/MoneroTransaction.cs new file mode 100644 index 00000000..355620ed --- /dev/null +++ b/QRCoder/PayloadGenerator/MoneroTransaction.cs @@ -0,0 +1,65 @@ +using System; +#if NETSTANDARD1_3 +using System.Reflection; +#endif + +namespace QRCoder +{ + public static partial class PayloadGenerator + { + public class MoneroTransaction : Payload + { + private readonly string address, txPaymentId, recipientName, txDescription; + private readonly float? txAmount; + + /// + /// Creates a monero transaction payload + /// + /// Receiver's monero address + /// Amount to transfer + /// Payment id + /// Receipient's name + /// Reference text / payment description + public MoneroTransaction(string address, float? txAmount = null, string txPaymentId = null, string recipientName = null, string txDescription = null) + { + if (string.IsNullOrEmpty(address)) + throw new MoneroTransactionException("The address is mandatory and has to be set."); + this.address = address; + if (txAmount != null && txAmount <= 0) + throw new MoneroTransactionException("Value of 'txAmount' must be greater than 0."); + this.txAmount = txAmount; + this.txPaymentId = txPaymentId; + this.recipientName = recipientName; + this.txDescription = txDescription; + } + + public override string ToString() + { + var moneroUri = $"monero://{address}{(!string.IsNullOrEmpty(txPaymentId) || !string.IsNullOrEmpty(recipientName) || !string.IsNullOrEmpty(txDescription) || txAmount != null ? "?" : string.Empty)}"; + moneroUri += (!string.IsNullOrEmpty(txPaymentId) ? $"tx_payment_id={Uri.EscapeDataString(txPaymentId)}&" : string.Empty); + moneroUri += (!string.IsNullOrEmpty(recipientName) ? $"recipient_name={Uri.EscapeDataString(recipientName)}&" : string.Empty); + moneroUri += (txAmount != null ? $"tx_amount={txAmount.ToString().Replace(",",".")}&" : string.Empty); + moneroUri += (!string.IsNullOrEmpty(txDescription) ? $"tx_description={Uri.EscapeDataString(txDescription)}" : string.Empty); + return moneroUri.TrimEnd('&'); + } + + + public class MoneroTransactionException : Exception + { + public MoneroTransactionException() + { + } + + public MoneroTransactionException(string message) + : base(message) + { + } + + public MoneroTransactionException(string message, Exception inner) + : base(message, inner) + { + } + } + } + } +} diff --git a/QRCoder/PayloadGenerator/OneTimePassword.cs b/QRCoder/PayloadGenerator/OneTimePassword.cs new file mode 100644 index 00000000..40c6f3fa --- /dev/null +++ b/QRCoder/PayloadGenerator/OneTimePassword.cs @@ -0,0 +1,153 @@ +using System; +using System.Text; +#if NETSTANDARD1_3 +using System.Reflection; +#endif + +namespace QRCoder +{ + public static partial class PayloadGenerator + { + public class OneTimePassword : Payload + { + //https://github.com/google/google-authenticator/wiki/Key-Uri-Format + public OneTimePasswordAuthType Type { get; set; } = OneTimePasswordAuthType.TOTP; + public string Secret { get; set; } + + public OneTimePasswordAuthAlgorithm AuthAlgorithm { get; set; } = OneTimePasswordAuthAlgorithm.SHA1; + + [Obsolete("This property is obsolete, use " + nameof(AuthAlgorithm) + " instead", false)] + public OoneTimePasswordAuthAlgorithm Algorithm + { + get { return (OoneTimePasswordAuthAlgorithm)Enum.Parse(typeof(OoneTimePasswordAuthAlgorithm), AuthAlgorithm.ToString()); } + set { AuthAlgorithm = (OneTimePasswordAuthAlgorithm)Enum.Parse(typeof(OneTimePasswordAuthAlgorithm), value.ToString()); } + } + + public string Issuer { get; set; } + public string Label { get; set; } + public int Digits { get; set; } = 6; + public int? Counter { get; set; } = null; + public int? Period { get; set; } = 30; + + public enum OneTimePasswordAuthType + { + TOTP, + HOTP, + } + + public enum OneTimePasswordAuthAlgorithm + { + SHA1, + SHA256, + SHA512, + } + + [Obsolete("This enum is obsolete, use " + nameof(OneTimePasswordAuthAlgorithm) + " instead", false)] + public enum OoneTimePasswordAuthAlgorithm + { + SHA1, + SHA256, + SHA512, + } + + public override string ToString() + { + switch (Type) + { + case OneTimePasswordAuthType.TOTP: + return TimeToString(); + case OneTimePasswordAuthType.HOTP: + return HMACToString(); + default: + throw new ArgumentOutOfRangeException(); + } + } + + // Note: Issuer:Label must only contain 1 : if either of the Issuer or the Label has a : then it is invalid. + // Defaults are 6 digits and 30 for Period + private string HMACToString() + { + var sb = new StringBuilder("otpauth://hotp/"); + ProcessCommonFields(sb); + var actualCounter = Counter ?? 1; + sb.Append("&counter=" + actualCounter); + return sb.ToString(); + } + + private string TimeToString() + { + if (Period == null) + { + throw new Exception("Period must be set when using OneTimePasswordAuthType.TOTP"); + } + + var sb = new StringBuilder("otpauth://totp/"); + + ProcessCommonFields(sb); + + if (Period != 30) + { + sb.Append("&period=" + Period); + } + + return sb.ToString(); + } + + private void ProcessCommonFields(StringBuilder sb) + { + if (Secret.IsNullOrWhiteSpace()) + { + throw new Exception("Secret must be a filled out base32 encoded string"); + } + string strippedSecret = Secret.Replace(" ", ""); + string escapedIssuer = null; + string escapedLabel = null; + string label = null; + + if (!Issuer.IsNullOrWhiteSpace()) + { + if (Issuer.Contains(":")) + { + throw new Exception("Issuer must not have a ':'"); + } + escapedIssuer = Uri.EscapeDataString(Issuer); + } + + if (!Label.IsNullOrWhiteSpace()) + { + if (Label.Contains(":")) + { + throw new Exception("Label must not have a ':'"); + } + escapedLabel = Uri.EscapeDataString(Label); + } + + if (escapedLabel != null && escapedIssuer != null) + { + label = escapedIssuer + ":" + escapedLabel; + } + else if (escapedIssuer != null) + { + label = escapedIssuer; + } + + if (label != null) + { + sb.Append(label); + } + + sb.Append("?secret=" + strippedSecret); + + if (escapedIssuer != null) + { + sb.Append("&issuer=" + escapedIssuer); + } + + if (Digits != 6) + { + sb.Append("&digits=" + Digits); + } + } + } + } +} diff --git a/QRCoder/PayloadGenerator/Payload.cs b/QRCoder/PayloadGenerator/Payload.cs new file mode 100644 index 00000000..7df489b8 --- /dev/null +++ b/QRCoder/PayloadGenerator/Payload.cs @@ -0,0 +1,17 @@ +#if NETSTANDARD1_3 +using System.Reflection; +#endif + +namespace QRCoder +{ + public static partial class PayloadGenerator + { + public abstract class Payload + { + public virtual int Version { get { return -1; } } + public virtual QRCodeGenerator.ECCLevel EccLevel { get { return QRCodeGenerator.ECCLevel.Default; } } + public virtual QRCodeGenerator.EciMode EciMode { get { return QRCodeGenerator.EciMode.Default; } } + public abstract override string ToString(); + } + } +} diff --git a/QRCoder/PayloadGenerator/PhoneNumber.cs b/QRCoder/PayloadGenerator/PhoneNumber.cs new file mode 100644 index 00000000..68550ffd --- /dev/null +++ b/QRCoder/PayloadGenerator/PhoneNumber.cs @@ -0,0 +1,28 @@ +#if NETSTANDARD1_3 +using System.Reflection; +#endif + +namespace QRCoder +{ + public static partial class PayloadGenerator + { + public class PhoneNumber : Payload + { + private readonly string number; + + /// + /// Generates a phone call payload + /// + /// Phonenumber of the receiver + public PhoneNumber(string number) + { + this.number = number; + } + + public override string ToString() + { + return $"tel:{this.number}"; + } + } + } +} diff --git a/QRCoder/PayloadGenerator/RussiaPaymentOrder.cs b/QRCoder/PayloadGenerator/RussiaPaymentOrder.cs new file mode 100644 index 00000000..8e41b332 --- /dev/null +++ b/QRCoder/PayloadGenerator/RussiaPaymentOrder.cs @@ -0,0 +1,616 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +#if NETSTANDARD1_3 +using System.Reflection; +#endif + +namespace QRCoder +{ + public static partial class PayloadGenerator + { + public class RussiaPaymentOrder : Payload + { + // Specification of RussianPaymentOrder + //https://docs.cntd.ru/document/1200110981 + //https://roskazna.gov.ru/upload/iblock/5fa/gost_r_56042_2014.pdf + //https://sbqr.ru/standard/files/standart.pdf + + // Specification of data types described in the above standard + // https://gitea.sergeybochkov.com/bochkov/emuik/src/commit/d18f3b550f6415ea4a4a5e6097eaab4661355c72/template/ed + + // Tool for QR validation + // https://www.sbqr.ru/validator/index.html + + //base + private CharacterSets characterSet; + private readonly MandatoryFields mFields = new MandatoryFields(); + private readonly OptionalFields oFields = new OptionalFields(); + private string separator = "|"; + + private RussiaPaymentOrder() + { + } + + /// + /// Generates a RussiaPaymentOrder payload + /// + /// Name of the payee (Наименование получателя платежа) + /// Beneficiary account number (Номер счета получателя платежа) + /// Name of the beneficiary's bank (Наименование банка получателя платежа) + /// BIC (БИК) + /// Box number / account payee's bank (Номер кор./сч. банка получателя платежа) + /// An (optional) object of additional fields + /// Type of encoding (default UTF-8) + public RussiaPaymentOrder(string name, string personalAcc, string bankName, string BIC, string correspAcc, OptionalFields optionalFields = null, CharacterSets characterSet = CharacterSets.utf_8) : this() + { + this.characterSet = characterSet; + mFields.Name = ValidateInput(name, "Name", @"^.{1,160}$"); + mFields.PersonalAcc = ValidateInput(personalAcc, "PersonalAcc", @"^[1-9]\d{4}[0-9ABCEHKMPTX]\d{14}$"); + mFields.BankName = ValidateInput(bankName, "BankName", @"^.{1,45}$"); + mFields.BIC = ValidateInput(BIC, "BIC", @"^\d{9}$"); + mFields.CorrespAcc = ValidateInput(correspAcc, "CorrespAcc", @"^[1-9]\d{4}[0-9ABCEHKMPTX]\d{14}$"); + + if (optionalFields != null) + oFields = optionalFields; + } + + /// + /// Returns payload as string. + /// + /// ⚠ Attention: If CharacterSets was set to windows-1251 or koi8-r you should use ToBytes() instead of ToString() and pass the bytes to CreateQrCode()! + /// + public override string ToString() + { + var cp = characterSet.ToString().Replace("_", "-"); + var bytes = ToBytes(); + +#if !NETFRAMEWORK + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); +#endif + return Encoding.GetEncoding(cp).GetString(bytes, 0, bytes.Length); + } + + /// + /// Returns payload as byte[]. + /// + /// Should be used if CharacterSets equals windows-1251 or koi8-r + /// + + public byte[] ToBytes() + { + //Setup byte encoder + //Encode return string as byte[] with correct CharacterSet +#if !NETFRAMEWORK + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); +#endif + var cp = this.characterSet.ToString().Replace("_", "-"); + + //Calculate the seperator + separator = DetermineSeparator(); + + //Create the payload string + string ret = $"ST0001" + ((int)characterSet).ToString() + //(separator != "|" ? separator : "") + + $"{separator}Name={mFields.Name}" + + $"{separator}PersonalAcc={mFields.PersonalAcc}" + + $"{separator}BankName={mFields.BankName}" + + $"{separator}BIC={mFields.BIC}" + + $"{separator}CorrespAcc={mFields.CorrespAcc}"; + + //Check length of mandatory field block (-8 => Removing service data block bytes from ret length) + int bytesMandatoryLen = Encoding.Convert(Encoding.UTF8, Encoding.GetEncoding(cp), Encoding.UTF8.GetBytes(ret)).Length - 8; + if (bytesMandatoryLen > 300) + throw new RussiaPaymentOrderException($"Data too long. Mandatory data must not exceed 300 bytes, but actually is {bytesMandatoryLen} bytes long. Remove additional data fields or shorten strings/values."); + + + //Add optional fields, if filled + var optionalFieldsList = GetOptionalFieldsAsList(); + if (optionalFieldsList.Count > 0) + ret += $"|{string.Join("|", optionalFieldsList.ToArray())}"; + ret += separator; + + return Encoding.Convert(Encoding.UTF8, Encoding.GetEncoding(cp), Encoding.UTF8.GetBytes(ret)); + } + + + /// + /// Determines a valid separator + /// + /// + private string DetermineSeparator() + { + // See chapter 5.2.1 of Standard (https://sbqr.ru/standard/files/standart.pdf) + + var mandatoryValues = GetMandatoryFieldsAsList(); + var optionalValues = GetOptionalFieldsAsList(); + + // Possible candidates for field separation + var separatorCandidates = new string[]{ "|", "#", ";", ":", "^", "_", "~", "{", "}", "!", "#", "$", "%", "&", "(", ")", "*", "+", ",", "/", "@" }; + foreach (var sepCandidate in separatorCandidates) + { + if (!mandatoryValues.Any(x => x.Contains(sepCandidate)) && !optionalValues.Any(x => x.Contains(sepCandidate))) + return sepCandidate; + } + throw new RussiaPaymentOrderException("No valid separator found."); + } + + /// + /// Takes all optional fields that are not null and returns their string represantion + /// + /// A List of strings + private List GetOptionalFieldsAsList() + { +#if NETSTANDARD1_3 + return typeof(OptionalFields).GetRuntimeProperties() + .Where(field => field.GetValue(oFields) != null) + .Select(field => { + var objValue = field.GetValue(oFields, null); + var value = field.PropertyType.Equals(typeof(DateTime?)) ? ((DateTime)objValue).ToString("dd.MM.yyyy") : objValue.ToString(); + return $"{field.Name}={value}"; + }) + .ToList(); +#else + return typeof(OptionalFields).GetProperties() + .Where(field => field.GetValue(oFields, null) != null) + .Select(field => { + var objValue = field.GetValue(oFields, null); + var value = field.PropertyType.Equals(typeof(DateTime?)) ? ((DateTime)objValue).ToString("dd.MM.yyyy") : objValue.ToString(); + return $"{field.Name}={value}"; + }) + .ToList(); +#endif + } + + + /// + /// Takes all mandatory fields that are not null and returns their string represantion + /// + /// A List of strings + private List GetMandatoryFieldsAsList() + { +#if NETSTANDARD1_3 + return typeof(MandatoryFields).GetRuntimeFields() + .Where(field => field.GetValue(mFields) != null) + .Select(field => { + var objValue = field.GetValue(mFields); + var value = field.FieldType.Equals(typeof(DateTime?)) ? ((DateTime)objValue).ToString("dd.MM.yyyy") : objValue.ToString(); + return $"{field.Name}={value}"; + }) + .ToList(); +#else + return typeof(MandatoryFields).GetFields() + .Where(field => field.GetValue(mFields) != null) + .Select(field => { + var objValue = field.GetValue(mFields); + var value = field.FieldType.Equals(typeof(DateTime?)) ? ((DateTime)objValue).ToString("dd.MM.yyyy") : objValue.ToString(); + return $"{field.Name}={value}"; + }) + .ToList(); +#endif + } + + /// + /// Validates a string against a given Regex pattern. Returns input if it matches the Regex expression (=valid) or throws Exception in case there's a mismatch + /// + /// String to be validated + /// Name/descriptor of the string to be validated + /// A regex pattern to be used for validation + /// An optional error text. If null, a standard error text is generated + /// Input value (in case it is valid) + private static string ValidateInput(string input, string fieldname, string pattern, string errorText = null) + { + return ValidateInput(input, fieldname, new string[] { pattern }, errorText); + } + + /// + /// Validates a string against one or more given Regex patterns. Returns input if it matches all regex expressions (=valid) or throws Exception in case there's a mismatch + /// + /// String to be validated + /// Name/descriptor of the string to be validated + /// An array of regex patterns to be used for validation + /// An optional error text. If null, a standard error text is generated + /// Input value (in case it is valid) + private static string ValidateInput(string input, string fieldname, string[] patterns, string errorText = null) + { + if (input == null) + throw new RussiaPaymentOrderException($"The input for '{fieldname}' must not be null."); + foreach (var pattern in patterns) + { + if (!Regex.IsMatch(input, pattern)) + throw new RussiaPaymentOrderException(errorText ?? $"The input for '{fieldname}' ({input}) doesn't match the pattern {pattern}"); + } + return input; + } + + private class MandatoryFields + { + public string Name; + public string PersonalAcc; + public string BankName; + public string BIC; + public string CorrespAcc; + } + + public class OptionalFields + { + private string _sum; + /// + /// Payment amount, in kopecks (FTI’s Amount.) + /// Сумма платежа, в копейках + /// + public string Sum + { + get { return _sum; } + set { _sum = ValidateInput(value, "Sum", @"^\d{1,18}$"); } + } + + private string _purpose; + /// + /// Payment name (purpose) + /// Наименование платежа (назначение) + /// + public string Purpose + { + get { return _purpose; } + set { _purpose = ValidateInput(value, "Purpose", @"^.{1,160}$"); } + } + + private string _payeeInn; + /// + /// Payee's INN (Resident Tax Identification Number; Text, up to 12 characters.) + /// ИНН получателя платежа + /// + public string PayeeINN + { + get { return _payeeInn; } + set { _payeeInn = ValidateInput(value, "PayeeINN", @"^.{1,12}$"); } + } + + private string _payerInn; + /// + /// Payer's INN (Resident Tax Identification Number; Text, up to 12 characters.) + /// ИНН плательщика + /// + public string PayerINN + { + get { return _payerInn; } + set { _payerInn = ValidateInput(value, "PayerINN", @"^.{1,12}$"); } + } + + private string _drawerStatus; + /// + /// Status compiler payment document + /// Статус составителя платежного документа + /// + public string DrawerStatus + { + get { return _drawerStatus; } + set { _drawerStatus = ValidateInput(value, "DrawerStatus", @"^.{1,2}$"); } + } + + private string _kpp; + /// + /// KPP of the payee (Tax Registration Code; Text, up to 9 characters.) + /// КПП получателя платежа + /// + public string KPP + { + get { return _kpp; } + set { _kpp = ValidateInput(value, "KPP", @"^.{1,9}$"); } + } + + private string _cbc; + /// + /// CBC + /// КБК + /// + public string CBC + { + get { return _cbc; } + set { _cbc = ValidateInput(value, "CBC", @"^.{1,20}$"); } + } + + private string _oktmo; + /// + /// All-Russian classifier territories of municipal formations + /// Общероссийский классификатор территорий муниципальных образований + /// + public string OKTMO + { + get { return _oktmo; } + set { _oktmo = ValidateInput(value, "OKTMO", @"^.{1,11}$"); } + } + + private string _paytReason; + /// + /// Basis of tax payment + /// Основание налогового платежа + /// + public string PaytReason + { + get { return _paytReason; } + set { _paytReason = ValidateInput(value, "PaytReason", @"^.{1,2}$"); } + } + + private string _taxPeriod; + /// + /// Taxable period + /// Налоговый период + /// + public string TaxPeriod + { + get { return _taxPeriod; } + set { _taxPeriod = ValidateInput(value, "ТaxPeriod", @"^.{1,10}$"); } + } + + private string _docNo; + /// + /// Document number + /// Номер документа + /// + public string DocNo + { + get { return _docNo; } + set { _docNo = ValidateInput(value, "DocNo", @"^.{1,15}$"); } + } + + /// + /// Document date + /// Дата документа + /// + public DateTime? DocDate { get; set; } + + private string _taxPaytKind; + /// + /// Payment type + /// Тип платежа + /// + public string TaxPaytKind + { + get { return _taxPaytKind; } + set { _taxPaytKind = ValidateInput(value, "TaxPaytKind", @"^.{1,2}$"); } + } + + /************************************************************************** + * The following fiels are no further specified in the standard + * document (https://sbqr.ru/standard/files/standart.pdf) thus there + * is no addition input validation implemented. + * **************************************************************************/ + + /// + /// Payer's surname + /// Фамилия плательщика + /// + public string LastName { get; set; } + + /// + /// Payer's name + /// Имя плательщика + /// + public string FirstName { get; set; } + + /// + /// Payer's patronymic + /// Отчество плательщика + /// + public string MiddleName { get; set; } + + /// + /// Payer's address + /// Адрес плательщика + /// + public string PayerAddress { get; set; } + + /// + /// Personal account of a budget recipient + /// Лицевой счет бюджетного получателя + /// + public string PersonalAccount { get; set; } + + /// + /// Payment document index + /// Индекс платежного документа + /// + public string DocIdx { get; set; } + + /// + /// Personal account number in the personalized accounting system in the Pension Fund of the Russian Federation - SNILS + /// № лицевого счета в системе персонифицированного учета в ПФР - СНИЛС + /// + public string PensAcc { get; set; } + + /// + /// Number of contract + /// Номер договора + /// + public string Contract { get; set; } + + /// + /// Personal account number of the payer in the organization (in the accounting system of the PU) + /// Номер лицевого счета плательщика в организации (в системе учета ПУ) + /// + public string PersAcc { get; set; } + + /// + /// Apartment number + /// Номер квартиры + /// + public string Flat { get; set; } + + /// + /// Phone number + /// Номер телефона + /// + public string Phone { get; set; } + + /// + /// DUL payer type + /// Вид ДУЛ плательщика + /// + public string PayerIdType { get; set; } + + /// + /// DUL number of the payer + /// Номер ДУЛ плательщика + /// + public string PayerIdNum { get; set; } + + /// + /// FULL NAME. child / student + /// Ф.И.О. ребенка/учащегося + /// + public string ChildFio { get; set; } + + /// + /// Date of birth + /// Дата рождения + /// + public DateTime? BirthDate { get; set; } + + /// + /// Due date / Invoice date + /// Срок платежа/дата выставления счета + /// + public string PaymTerm { get; set; } + + /// + /// Payment period + /// Период оплаты + /// + public string PaymPeriod { get; set; } + + /// + /// Payment type + /// Вид платежа + /// + public string Category { get; set; } + + /// + /// Service code / meter name + /// Код услуги/название прибора учета + /// + public string ServiceName { get; set; } + + /// + /// Metering device number + /// Номер прибора учета + /// + public string CounterId { get; set; } + + /// + /// Meter reading + /// Показание прибора учета + /// + public string CounterVal { get; set; } + + /// + /// Notification, accrual, account number + /// Номер извещения, начисления, счета + /// + public string QuittId { get; set; } + + /// + /// Date of notification / accrual / invoice / resolution (for traffic police) + /// Дата извещения/начисления/счета/постановления (для ГИБДД) + /// + public DateTime? QuittDate { get; set; } + + /// + /// Institution number (educational, medical) + /// Номер учреждения (образовательного, медицинского) + /// + public string InstNum { get; set; } + + /// + /// Kindergarten / school class number + /// Номер группы детсада/класса школы + /// + public string ClassNum { get; set; } + + /// + /// Full name of the teacher, specialist providing the service + /// ФИО преподавателя, специалиста, оказывающего услугу + /// + public string SpecFio { get; set; } + + /// + /// Insurance / additional service amount / Penalty amount (in kopecks) + /// Сумма страховки/дополнительной услуги/Сумма пени (в копейках) + /// + public string AddAmount { get; set; } + + /// + /// Resolution number (for traffic police) + /// Номер постановления (для ГИБДД) + /// + public string RuleId { get; set; } + + /// + /// Enforcement Proceedings Number + /// Номер исполнительного производства + /// + public string ExecId { get; set; } + + /// + /// Type of payment code (for example, for payments to Rosreestr) + /// Код вида платежа (например, для платежей в адрес Росреестра) + /// + public string RegType { get; set; } + + /// + /// Unique accrual identifier + /// Уникальный идентификатор начисления + /// + public string UIN { get; set; } + + /// + /// The technical code recommended by the service provider. Maybe used by the receiving organization to call the appropriate processing IT system. + /// Технический код, рекомендуемый для заполнения поставщиком услуг. Может использоваться принимающей организацией для вызова соответствующей обрабатывающей ИТ-системы. + /// + public TechCode? TechCode { get; set; } + } + + /// + /// (List of values of the technical code of the payment) + /// Перечень значений технического кода платежа + /// + public enum TechCode + { + Мобильная_связь_стационарный_телефон = 01, + Коммунальные_услуги_ЖКХAFN = 02, + ГИБДД_налоги_пошлины_бюджетные_платежи = 03, + Охранные_услуги = 04, + Услуги_оказываемые_УФМС = 05, + ПФР = 06, + Погашение_кредитов = 07, + Образовательные_учреждения = 08, + Интернет_и_ТВ = 09, + Электронные_деньги = 10, + Отдых_и_путешествия = 11, + Инвестиции_и_страхование = 12, + Спорт_и_здоровье = 13, + Благотворительные_и_общественные_организации = 14, + Прочие_услуги = 15 + } + + public enum CharacterSets + { + windows_1251 = 1, // Encoding.GetEncoding("windows-1251") + utf_8 = 2, // Encoding.UTF8 + koi8_r = 3 // Encoding.GetEncoding("koi8-r") + + } + + public class RussiaPaymentOrderException : Exception + { + public RussiaPaymentOrderException(string message) + : base(message) + { + } + } + + } + } +} diff --git a/QRCoder/PayloadGenerator/SMS.cs b/QRCoder/PayloadGenerator/SMS.cs new file mode 100644 index 00000000..180eb38b --- /dev/null +++ b/QRCoder/PayloadGenerator/SMS.cs @@ -0,0 +1,72 @@ +using System; +#if NETSTANDARD1_3 +using System.Reflection; +#endif + +namespace QRCoder +{ + public static partial class PayloadGenerator + { + public class SMS : Payload + { + private readonly string number, subject; + private readonly SMSEncoding encoding; + + /// + /// Creates a SMS payload without text + /// + /// Receiver phone number + /// Encoding type + public SMS(string number, SMSEncoding encoding = SMSEncoding.SMS) + { + this.number = number; + this.subject = string.Empty; + this.encoding = encoding; + } + + /// + /// Creates a SMS payload with text (subject) + /// + /// Receiver phone number + /// Text of the SMS + /// Encoding type + public SMS(string number, string subject, SMSEncoding encoding = SMSEncoding.SMS) + { + this.number = number; + this.subject = subject; + this.encoding = encoding; + } + + public override string ToString() + { + var returnVal = string.Empty; + switch (this.encoding) + { + case SMSEncoding.SMS: + var queryString = string.Empty; + if (!string.IsNullOrEmpty(this.subject)) + queryString = $"?body={Uri.EscapeDataString(this.subject)}"; + returnVal = $"sms:{this.number}{queryString}"; + break; + case SMSEncoding.SMS_iOS: + var queryStringiOS = string.Empty; + if (!string.IsNullOrEmpty(this.subject)) + queryStringiOS = $";body={Uri.EscapeDataString(this.subject)}"; + returnVal = $"sms:{this.number}{queryStringiOS}"; + break; + case SMSEncoding.SMSTO: + returnVal = $"SMSTO:{this.number}:{this.subject}"; + break; + } + return returnVal; + } + + public enum SMSEncoding + { + SMS, + SMSTO, + SMS_iOS + } + } + } +} diff --git a/QRCoder/PayloadGenerator/ShadowSocksConfig.cs b/QRCoder/PayloadGenerator/ShadowSocksConfig.cs new file mode 100644 index 00000000..5468826d --- /dev/null +++ b/QRCoder/PayloadGenerator/ShadowSocksConfig.cs @@ -0,0 +1,233 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +#if NETSTANDARD1_3 +using System.Reflection; +#endif + +namespace QRCoder +{ + public static partial class PayloadGenerator + { + public class ShadowSocksConfig : Payload + { + private readonly string hostname, password, tag, methodStr, parameter; + private readonly Method method; + private readonly int port; + private Dictionary encryptionTexts = new Dictionary() { + { "Chacha20IetfPoly1305", "chacha20-ietf-poly1305" }, + { "Aes128Gcm", "aes-128-gcm" }, + { "Aes192Gcm", "aes-192-gcm" }, + { "Aes256Gcm", "aes-256-gcm" }, + + { "XChacha20IetfPoly1305", "xchacha20-ietf-poly1305" }, + + { "Aes128Cfb", "aes-128-cfb" }, + { "Aes192Cfb", "aes-192-cfb" }, + { "Aes256Cfb", "aes-256-cfb" }, + { "Aes128Ctr", "aes-128-ctr" }, + { "Aes192Ctr", "aes-192-ctr" }, + { "Aes256Ctr", "aes-256-ctr" }, + { "Camellia128Cfb", "camellia-128-cfb" }, + { "Camellia192Cfb", "camellia-192-cfb" }, + { "Camellia256Cfb", "camellia-256-cfb" }, + { "Chacha20Ietf", "chacha20-ietf" }, + + { "Aes256Cb", "aes-256-cfb" }, + + { "Aes128Ofb", "aes-128-ofb" }, + { "Aes192Ofb", "aes-192-ofb" }, + { "Aes256Ofb", "aes-256-ofb" }, + { "Aes128Cfb1", "aes-128-cfb1" }, + { "Aes192Cfb1", "aes-192-cfb1" }, + { "Aes256Cfb1", "aes-256-cfb1" }, + { "Aes128Cfb8", "aes-128-cfb8" }, + { "Aes192Cfb8", "aes-192-cfb8" }, + { "Aes256Cfb8", "aes-256-cfb8" }, + + { "Chacha20", "chacha20" }, + { "BfCfb", "bf-cfb" }, + { "Rc4Md5", "rc4-md5" }, + { "Salsa20", "salsa20" }, + + { "DesCfb", "des-cfb" }, + { "IdeaCfb", "idea-cfb" }, + { "Rc2Cfb", "rc2-cfb" }, + { "Cast5Cfb", "cast5-cfb" }, + { "Salsa20Ctr", "salsa20-ctr" }, + { "Rc4", "rc4" }, + { "SeedCfb", "seed-cfb" }, + { "Table", "table" } + }; + + /// + /// Generates a ShadowSocks proxy config payload. + /// + /// Hostname of the ShadowSocks proxy + /// Port of the ShadowSocks proxy + /// Password of the SS proxy + /// Encryption type + /// Optional tag line + public ShadowSocksConfig(string hostname, int port, string password, Method method, string tag = null) : + this(hostname, port, password, method, null, tag) + { } + + public ShadowSocksConfig(string hostname, int port, string password, Method method, string plugin, string pluginOption, string tag = null) : + this(hostname, port, password, method, new Dictionary + { + ["plugin"] = plugin + ( + string.IsNullOrEmpty(pluginOption) + ? "" + : $";{pluginOption}" + ) + }, tag) + { } + private Dictionary UrlEncodeTable = new Dictionary + { + [" "] = "+", + ["\0"] = "%00", + ["\t"] = "%09", + ["\n"] = "%0a", + ["\r"] = "%0d", + ["\""] = "%22", + ["#"] = "%23", + ["$"] = "%24", + ["%"] = "%25", + ["&"] = "%26", + ["'"] = "%27", + ["+"] = "%2b", + [","] = "%2c", + ["/"] = "%2f", + [":"] = "%3a", + [";"] = "%3b", + ["<"] = "%3c", + ["="] = "%3d", + [">"] = "%3e", + ["?"] = "%3f", + ["@"] = "%40", + ["["] = "%5b", + ["\\"] = "%5c", + ["]"] = "%5d", + ["^"] = "%5e", + ["`"] = "%60", + ["{"] = "%7b", + ["|"] = "%7c", + ["}"] = "%7d", + ["~"] = "%7e", + }; + + private string UrlEncode(string i) + { + string j = i; + foreach (var kv in UrlEncodeTable) + { + j = j.Replace(kv.Key, kv.Value); + } + return j; + } + + public ShadowSocksConfig(string hostname, int port, string password, Method method, Dictionary parameters, string tag = null) + { + this.hostname = Uri.CheckHostName(hostname) == UriHostNameType.IPv6 + ? $"[{hostname}]" + : hostname; + if (port < 1 || port > 65535) + throw new ShadowSocksConfigException("Value of 'port' must be within 0 and 65535."); + this.port = port; + this.password = password; + this.method = method; + this.methodStr = encryptionTexts[method.ToString()]; + this.tag = tag; + + if (parameters != null) + this.parameter = + string.Join("&", + parameters.Select( + kv => $"{UrlEncode(kv.Key)}={UrlEncode(kv.Value)}" + ).ToArray()); + } + + public override string ToString() + { + if (string.IsNullOrEmpty(parameter)) + { + var connectionString = $"{methodStr}:{password}@{hostname}:{port}"; + var connectionStringEncoded = Convert.ToBase64String(Encoding.UTF8.GetBytes(connectionString)); + return $"ss://{connectionStringEncoded}{(!string.IsNullOrEmpty(tag) ? $"#{tag}" : string.Empty)}"; + } + var authString = $"{methodStr}:{password}"; + var authStringEncoded = Convert.ToBase64String(Encoding.UTF8.GetBytes(authString)) + .Replace('+', '-') + .Replace('/', '_') + .TrimEnd('='); + return $"ss://{authStringEncoded}@{hostname}:{port}/?{parameter}{(!string.IsNullOrEmpty(tag) ? $"#{tag}" : string.Empty)}"; + } + + public enum Method + { + // AEAD + Chacha20IetfPoly1305, + Aes128Gcm, + Aes192Gcm, + Aes256Gcm, + // AEAD, not standard + XChacha20IetfPoly1305, + // Stream cipher + Aes128Cfb, + Aes192Cfb, + Aes256Cfb, + Aes128Ctr, + Aes192Ctr, + Aes256Ctr, + Camellia128Cfb, + Camellia192Cfb, + Camellia256Cfb, + Chacha20Ietf, + // alias of Aes256Cfb + Aes256Cb, + // Stream cipher, not standard + Aes128Ofb, + Aes192Ofb, + Aes256Ofb, + Aes128Cfb1, + Aes192Cfb1, + Aes256Cfb1, + Aes128Cfb8, + Aes192Cfb8, + Aes256Cfb8, + // Stream cipher, deprecated + Chacha20, + BfCfb, + Rc4Md5, + Salsa20, + // Not standard and not in acitve use + DesCfb, + IdeaCfb, + Rc2Cfb, + Cast5Cfb, + Salsa20Ctr, + Rc4, + SeedCfb, + Table + } + + public class ShadowSocksConfigException : Exception + { + public ShadowSocksConfigException() + { + } + + public ShadowSocksConfigException(string message) + : base(message) + { + } + + public ShadowSocksConfigException(string message, Exception inner) + : base(message, inner) + { + } + } + } + } +} diff --git a/QRCoder/PayloadGenerator/SkypeCall.cs b/QRCoder/PayloadGenerator/SkypeCall.cs new file mode 100644 index 00000000..0aad10ed --- /dev/null +++ b/QRCoder/PayloadGenerator/SkypeCall.cs @@ -0,0 +1,28 @@ +#if NETSTANDARD1_3 +using System.Reflection; +#endif + +namespace QRCoder +{ + public static partial class PayloadGenerator + { + public class SkypeCall : Payload + { + private readonly string skypeUsername; + + /// + /// Generates a Skype call payload + /// + /// Skype username which will be called + public SkypeCall(string skypeUsername) + { + this.skypeUsername = skypeUsername; + } + + public override string ToString() + { + return $"skype:{this.skypeUsername}?call"; + } + } + } +} diff --git a/QRCoder/PayloadGenerator/SlovenianUpnQr.cs b/QRCoder/PayloadGenerator/SlovenianUpnQr.cs new file mode 100644 index 00000000..30ce31b5 --- /dev/null +++ b/QRCoder/PayloadGenerator/SlovenianUpnQr.cs @@ -0,0 +1,108 @@ +using System; +using System.Text; +#if NETSTANDARD1_3 +using System.Reflection; +#endif + +namespace QRCoder +{ + public static partial class PayloadGenerator + { + public class SlovenianUpnQr : Payload + { + //Keep in mind, that the ECC level has to be set to "M", version to 15 and ECI to EciMode.Iso8859_2 when generating a SlovenianUpnQr! + //SlovenianUpnQr specification: https://www.upn-qr.si/uploads/files/NavodilaZaProgramerjeUPNQR.pdf + + private string _payerName = ""; + private string _payerAddress = ""; + private string _payerPlace = ""; + private string _amount = ""; + private string _code = ""; + private string _purpose = ""; + private string _deadLine = ""; + private string _recipientIban = ""; + private string _recipientName = ""; + private string _recipientAddress = ""; + private string _recipientPlace = ""; + private string _recipientSiModel = ""; + private string _recipientSiReference = ""; + + public override int Version { get { return 15; } } + public override QRCodeGenerator.ECCLevel EccLevel { get { return QRCodeGenerator.ECCLevel.M; } } + public override QRCodeGenerator.EciMode EciMode { get { return QRCodeGenerator.EciMode.Iso8859_2; } } + + private string LimitLength(string value, int maxLength) + { + return (value.Length <= maxLength) ? value : value.Substring(0, maxLength); + } + + public SlovenianUpnQr(string payerName, string payerAddress, string payerPlace, string recipientName, string recipientAddress, string recipientPlace, string recipientIban, string description, double amount, string recipientSiModel = "SI00", string recipientSiReference = "", string code = "OTHR") : + this(payerName, payerAddress, payerPlace, recipientName, recipientAddress, recipientPlace, recipientIban, description, amount, null, recipientSiModel, recipientSiReference, code) + { } + + public SlovenianUpnQr(string payerName, string payerAddress, string payerPlace, string recipientName, string recipientAddress, string recipientPlace, string recipientIban, string description, double amount, DateTime? deadline, string recipientSiModel = "SI99", string recipientSiReference = "", string code = "OTHR") + { + _payerName = LimitLength(payerName.Trim(), 33); + _payerAddress = LimitLength(payerAddress.Trim(), 33); + _payerPlace = LimitLength(payerPlace.Trim(), 33); + _amount = FormatAmount(amount); + _code = LimitLength(code.Trim().ToUpper(), 4); + _purpose = LimitLength(description.Trim(), 42); + _deadLine = (deadline == null) ? "" : deadline?.ToString("dd.MM.yyyy"); + _recipientIban = LimitLength(recipientIban.Trim(), 34); + _recipientName = LimitLength(recipientName.Trim(), 33); + _recipientAddress = LimitLength(recipientAddress.Trim(), 33); + _recipientPlace = LimitLength(recipientPlace.Trim(), 33); + _recipientSiModel = LimitLength(recipientSiModel.Trim().ToUpper(), 4); + _recipientSiReference = LimitLength(recipientSiReference.Trim(), 22); + } + + + private string FormatAmount(double amount) + { + int _amt = (int)Math.Round(amount * 100.0); + return String.Format("{0:00000000000}", _amt); + } + + private int CalculateChecksum() + { + int _cs = 5 + _payerName.Length; //5 = UPNQR constant Length + _cs += _payerAddress.Length; + _cs += _payerPlace.Length; + _cs += _amount.Length; + _cs += _code.Length; + _cs += _purpose.Length; + _cs += _deadLine.Length; + _cs += _recipientIban.Length; + _cs += _recipientName.Length; + _cs += _recipientAddress.Length; + _cs += _recipientPlace.Length; + _cs += _recipientSiModel.Length; + _cs += _recipientSiReference.Length; + _cs += 19; + return _cs; + } + + public override string ToString() + { + var _sb = new StringBuilder(); + _sb.Append("UPNQR"); + _sb.Append('\n').Append('\n').Append('\n').Append('\n').Append('\n'); + _sb.Append(_payerName).Append('\n'); + _sb.Append(_payerAddress).Append('\n'); + _sb.Append(_payerPlace).Append('\n'); + _sb.Append(_amount).Append('\n').Append('\n').Append('\n'); + _sb.Append(_code.ToUpper()).Append('\n'); + _sb.Append(_purpose).Append('\n'); + _sb.Append(_deadLine).Append('\n'); + _sb.Append(_recipientIban.ToUpper()).Append('\n'); + _sb.Append(_recipientSiModel).Append(_recipientSiReference).Append('\n'); + _sb.Append(_recipientName).Append('\n'); + _sb.Append(_recipientAddress).Append('\n'); + _sb.Append(_recipientPlace).Append('\n'); + _sb.AppendFormat("{0:000}", CalculateChecksum()).Append('\n'); + return _sb.ToString(); + } + } + } +} diff --git a/QRCoder/PayloadGenerator/SwissQrCode.cs b/QRCoder/PayloadGenerator/SwissQrCode.cs new file mode 100644 index 00000000..992ec1db --- /dev/null +++ b/QRCoder/PayloadGenerator/SwissQrCode.cs @@ -0,0 +1,509 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +#if NETSTANDARD1_3 +using System.Reflection; +#endif + +namespace QRCoder +{ + public static partial class PayloadGenerator + { + public class SwissQrCode : Payload + { + //Keep in mind, that the ECC level has to be set to "M" when generating a SwissQrCode! + //SwissQrCode specification: + // - (de) https://www.paymentstandards.ch/dam/downloads/ig-qr-bill-de.pdf + // - (en) https://www.paymentstandards.ch/dam/downloads/ig-qr-bill-en.pdf + //Changes between version 1.0 and 2.0: https://www.paymentstandards.ch/dam/downloads/change-documentation-qrr-de.pdf + + private readonly string br = "\r\n"; + private readonly string alternativeProcedure1, alternativeProcedure2; + private readonly Iban iban; + private readonly decimal? amount; + private readonly Contact creditor, ultimateCreditor, debitor; + private readonly Currency currency; + private readonly DateTime? requestedDateOfPayment; + private readonly Reference reference; + private readonly AdditionalInformation additionalInformation; + + public override QRCodeGenerator.ECCLevel EccLevel { get { return QRCodeGenerator.ECCLevel.M; } } + public override QRCodeGenerator.EciMode EciMode { get { return QRCodeGenerator.EciMode.Utf8; } } + + /// + /// Generates the payload for a SwissQrCode v2.0. (Don't forget to use ECC-Level=M, EncodingMode=UTF-8 and to set the Swiss flag icon to the final QR code.) + /// + /// IBAN object + /// Currency (either EUR or CHF) + /// Creditor (payee) information + /// Reference information + /// Debitor (payer) information + /// Amount + /// Requested date of debitor's payment + /// Ultimate creditor information (use only in consultation with your bank - for future use only!) + /// Optional command for alternative processing mode - line 1 + /// Optional command for alternative processing mode - line 2 + public SwissQrCode(Iban iban, Currency currency, Contact creditor, Reference reference, AdditionalInformation additionalInformation = null, Contact debitor = null, decimal? amount = null, DateTime? requestedDateOfPayment = null, Contact ultimateCreditor = null, string alternativeProcedure1 = null, string alternativeProcedure2 = null) + { + this.iban = iban; + + this.creditor = creditor; + this.ultimateCreditor = ultimateCreditor; + + this.additionalInformation = additionalInformation != null ? additionalInformation : new AdditionalInformation(); + + if (amount != null && amount.ToString().Length > 12) + throw new SwissQrCodeException("Amount (including decimals) must be shorter than 13 places."); + this.amount = amount; + + this.currency = currency; + this.requestedDateOfPayment = requestedDateOfPayment; + this.debitor = debitor; + + if (iban.IsQrIban && reference.RefType != Reference.ReferenceType.QRR) + throw new SwissQrCodeException("If QR-IBAN is used, you have to choose \"QRR\" as reference type!"); + if (!iban.IsQrIban && reference.RefType == Reference.ReferenceType.QRR) + throw new SwissQrCodeException("If non QR-IBAN is used, you have to choose either \"SCOR\" or \"NON\" as reference type!"); + this.reference = reference; + + if (alternativeProcedure1 != null && alternativeProcedure1.Length > 100) + throw new SwissQrCodeException("Alternative procedure information block 1 must be shorter than 101 chars."); + this.alternativeProcedure1 = alternativeProcedure1; + if (alternativeProcedure2 != null && alternativeProcedure2.Length > 100) + throw new SwissQrCodeException("Alternative procedure information block 2 must be shorter than 101 chars."); + this.alternativeProcedure2 = alternativeProcedure2; + } + + public class AdditionalInformation + { + private readonly string unstructuredMessage, billInformation, trailer; + + /// + /// Creates an additional information object. Both parameters are optional and must be shorter than 141 chars in combination. + /// + /// Unstructured text message + /// Bill information + public AdditionalInformation(string unstructuredMessage = null, string billInformation = null) + { + if (((unstructuredMessage != null ? unstructuredMessage.Length : 0) + (billInformation != null ? billInformation.Length : 0)) > 140) + throw new SwissQrCodeAdditionalInformationException("Unstructured message and bill information must be shorter than 141 chars in total/combined."); + this.unstructuredMessage = unstructuredMessage; + this.billInformation = billInformation; + this.trailer = "EPD"; + } + + public string UnstructureMessage + { + get { return !string.IsNullOrEmpty(unstructuredMessage) ? unstructuredMessage.Replace("\n", "") : null; } + } + + public string BillInformation + { + get { return !string.IsNullOrEmpty(billInformation) ? billInformation.Replace("\n", "") : null; } + } + + public string Trailer + { + get { return trailer; } + } + + + public class SwissQrCodeAdditionalInformationException : Exception + { + public SwissQrCodeAdditionalInformationException() + { + } + + public SwissQrCodeAdditionalInformationException(string message) + : base(message) + { + } + + public SwissQrCodeAdditionalInformationException(string message, Exception inner) + : base(message, inner) + { + } + } + } + + public class Reference + { + private readonly ReferenceType referenceType; + private readonly string reference; + private readonly ReferenceTextType? referenceTextType; + + /// + /// Creates a reference object which must be passed to the SwissQrCode instance + /// + /// Type of the reference (QRR, SCOR or NON) + /// Reference text + /// Type of the reference text (QR-reference or Creditor Reference) + public Reference(ReferenceType referenceType, string reference = null, ReferenceTextType? referenceTextType = null) + { + this.referenceType = referenceType; + this.referenceTextType = referenceTextType; + + if (referenceType == ReferenceType.NON && reference != null) + throw new SwissQrCodeReferenceException("Reference is only allowed when referenceType not equals \"NON\""); + if (referenceType != ReferenceType.NON && reference != null && referenceTextType == null) + throw new SwissQrCodeReferenceException("You have to set an ReferenceTextType when using the reference text."); + if (referenceTextType == ReferenceTextType.QrReference && reference != null && (reference.Length > 27)) + throw new SwissQrCodeReferenceException("QR-references have to be shorter than 28 chars."); + if (referenceTextType == ReferenceTextType.QrReference && reference != null && !Regex.IsMatch(reference, @"^[0-9]+$")) + throw new SwissQrCodeReferenceException("QR-reference must exist out of digits only."); + if (referenceTextType == ReferenceTextType.QrReference && reference != null && !ChecksumMod10(reference)) + throw new SwissQrCodeReferenceException("QR-references is invalid. Checksum error."); + if (referenceTextType == ReferenceTextType.CreditorReferenceIso11649 && reference != null && (reference.Length > 25)) + throw new SwissQrCodeReferenceException("Creditor references (ISO 11649) have to be shorter than 26 chars."); + + this.reference = reference; + } + + public ReferenceType RefType { + get { return referenceType; } + } + + public string ReferenceText + { + get { return !string.IsNullOrEmpty(reference) ? reference.Replace("\n", "") : null; } + } + + /// + /// Reference type. When using a QR-IBAN you have to use either "QRR" or "SCOR" + /// + public enum ReferenceType + { + QRR, + SCOR, + NON + } + + public enum ReferenceTextType + { + QrReference, + CreditorReferenceIso11649 + } + + public class SwissQrCodeReferenceException : Exception + { + public SwissQrCodeReferenceException() + { + } + + public SwissQrCodeReferenceException(string message) + : base(message) + { + } + + public SwissQrCodeReferenceException(string message, Exception inner) + : base(message, inner) + { + } + } + } + + public class Iban + { + private string iban; + private IbanType ibanType; + + /// + /// IBAN object with type information + /// + /// IBAN + /// Type of IBAN (normal or QR-IBAN) + public Iban(string iban, IbanType ibanType) + { + if (ibanType == IbanType.Iban && !IsValidIban(iban)) + throw new SwissQrCodeIbanException("The IBAN entered isn't valid."); + if (ibanType == IbanType.QrIban && !IsValidQRIban(iban)) + throw new SwissQrCodeIbanException("The QR-IBAN entered isn't valid."); + if (!iban.StartsWith("CH") && !iban.StartsWith("LI")) + throw new SwissQrCodeIbanException("The IBAN must start with \"CH\" or \"LI\"."); + this.iban = iban; + this.ibanType = ibanType; + } + + public bool IsQrIban + { + get { return ibanType == IbanType.QrIban; } + } + + public override string ToString() + { + return iban.Replace("-", "").Replace("\n", "").Replace(" ",""); + } + + public enum IbanType + { + Iban, + QrIban + } + + public class SwissQrCodeIbanException : Exception + { + public SwissQrCodeIbanException() + { + } + + public SwissQrCodeIbanException(string message) + : base(message) + { + } + + public SwissQrCodeIbanException(string message, Exception inner) + : base(message, inner) + { + } + } + } + + public class Contact + { + private static readonly HashSet twoLetterCodes = ValidTwoLetterCodes(); + private string br = "\r\n"; + private string name, streetOrAddressline1, houseNumberOrAddressline2, zipCode, city, country; + private AddressType adrType; + + /// + /// Contact type. Can be used for payee, ultimate payee, etc. with address in structured mode (S). + /// + /// Last name or company (optional first name) + /// Zip-/Postcode + /// City name + /// Two-letter country code as defined in ISO 3166-1 + /// Streetname without house number + /// House number + [Obsolete("This constructor is deprecated. Use WithStructuredAddress instead.")] + public Contact(string name, string zipCode, string city, string country, string street = null, string houseNumber = null) : this (name, zipCode, city, country, street, houseNumber, AddressType.StructuredAddress) + { + } + + + /// + /// Contact type. Can be used for payee, ultimate payee, etc. with address in combined mode (K). + /// + /// Last name or company (optional first name) + /// Two-letter country code as defined in ISO 3166-1 + /// Adress line 1 + /// Adress line 2 + [Obsolete("This constructor is deprecated. Use WithCombinedAddress instead.")] + public Contact(string name, string country, string addressLine1, string addressLine2) : this(name, null, null, country, addressLine1, addressLine2, AddressType.CombinedAddress) + { + } + + public static Contact WithStructuredAddress(string name, string zipCode, string city, string country, string street = null, string houseNumber = null) + { + return new Contact(name, zipCode, city, country, street, houseNumber, AddressType.StructuredAddress); + } + + public static Contact WithCombinedAddress(string name, string country, string addressLine1, string addressLine2) + { + return new Contact(name, null, null, country, addressLine1, addressLine2, AddressType.CombinedAddress); + } + + + private Contact(string name, string zipCode, string city, string country, string streetOrAddressline1, string houseNumberOrAddressline2, AddressType addressType) + { + //Pattern extracted from https://qr-validation.iso-payments.ch as explained in https://github.com/codebude/QRCoder/issues/97 + var charsetPattern = @"^([a-zA-Z0-9\.,;:'\ \+\-/\(\)?\*\[\]\{\}\\`´~ ^|]|[!""#%&<>÷=@_$£¡¢¤¥¦§¨©ª«¬®¯°±²³µ¶·¸¹º»¼½¾¿×Ø€]|[àáâäãåāăąçćĉċčďđèéêëēĕėęěĝğġģĥħìíîïĩīĭįıijķĸĺļľŀłñńņňʼnŋòóôöōŏőõŕŗřśŝşšșţťŧțùúûüũūŭůűųŵýÿŷźżžßÀÁÂÄÃÅĀĂĄÇĆĈĊČĎĐÈÉÊËĒĔĖĘĚĜĞĠĢĤĦÌÍÎÏĨĪĬĮİIJĴĵĶĹĻĽĿŁÑŃŅŇŊÒÓÔÖÕŌŎŐŔŖŘŚŜŞŠȘŢŤŦȚÙÚÛÜŨŪŬŮŰŲŴÝŶŸŹŻŽÆÐÞæðøþŒœſ])*$"; + + this.adrType = addressType; + + if (string.IsNullOrEmpty(name)) + throw new SwissQrCodeContactException("Name must not be empty."); + if (name.Length > 70) + throw new SwissQrCodeContactException("Name must be shorter than 71 chars."); + if (!Regex.IsMatch(name, charsetPattern)) + throw new SwissQrCodeContactException($"Name must match the following pattern as defined in pain.001: {charsetPattern}"); + this.name = name; + + if (AddressType.StructuredAddress == this.adrType) + { + if (!string.IsNullOrEmpty(streetOrAddressline1) && (streetOrAddressline1.Length > 70)) + throw new SwissQrCodeContactException("Street must be shorter than 71 chars."); + if (!string.IsNullOrEmpty(streetOrAddressline1) && !Regex.IsMatch(streetOrAddressline1, charsetPattern)) + throw new SwissQrCodeContactException($"Street must match the following pattern as defined in pain.001: {charsetPattern}"); + this.streetOrAddressline1 = streetOrAddressline1; + + if (!string.IsNullOrEmpty(houseNumberOrAddressline2) && houseNumberOrAddressline2.Length > 16) + throw new SwissQrCodeContactException("House number must be shorter than 17 chars."); + this.houseNumberOrAddressline2 = houseNumberOrAddressline2; + } + else + { + if (!string.IsNullOrEmpty(streetOrAddressline1) && (streetOrAddressline1.Length > 70)) + throw new SwissQrCodeContactException("Address line 1 must be shorter than 71 chars."); + if (!string.IsNullOrEmpty(streetOrAddressline1) && !Regex.IsMatch(streetOrAddressline1, charsetPattern)) + throw new SwissQrCodeContactException($"Address line 1 must match the following pattern as defined in pain.001: {charsetPattern}"); + this.streetOrAddressline1 = streetOrAddressline1; + + if (string.IsNullOrEmpty(houseNumberOrAddressline2)) + throw new SwissQrCodeContactException("Address line 2 must be provided for combined addresses (address line-based addresses)."); + if (!string.IsNullOrEmpty(houseNumberOrAddressline2) && (houseNumberOrAddressline2.Length > 70)) + throw new SwissQrCodeContactException("Address line 2 must be shorter than 71 chars."); + if (!string.IsNullOrEmpty(houseNumberOrAddressline2) && !Regex.IsMatch(houseNumberOrAddressline2, charsetPattern)) + throw new SwissQrCodeContactException($"Address line 2 must match the following pattern as defined in pain.001: {charsetPattern}"); + this.houseNumberOrAddressline2 = houseNumberOrAddressline2; + } + + if (AddressType.StructuredAddress == this.adrType) { + if (string.IsNullOrEmpty(zipCode)) + throw new SwissQrCodeContactException("Zip code must not be empty."); + if (zipCode.Length > 16) + throw new SwissQrCodeContactException("Zip code must be shorter than 17 chars."); + if (!Regex.IsMatch(zipCode, charsetPattern)) + throw new SwissQrCodeContactException($"Zip code must match the following pattern as defined in pain.001: {charsetPattern}"); + this.zipCode = zipCode; + + if (string.IsNullOrEmpty(city)) + throw new SwissQrCodeContactException("City must not be empty."); + if (city.Length > 35) + throw new SwissQrCodeContactException("City name must be shorter than 36 chars."); + if (!Regex.IsMatch(city, charsetPattern)) + throw new SwissQrCodeContactException($"City name must match the following pattern as defined in pain.001: {charsetPattern}"); + this.city = city; + } + else + { + this.zipCode = this.city = string.Empty; + } + + if (!IsValidTwoLetterCode(country)) + throw new SwissQrCodeContactException("Country must be a valid \"two letter\" country code as defined by ISO 3166-1, but it isn't."); + + this.country = country; + } + + private static bool IsValidTwoLetterCode(string code) => twoLetterCodes.Contains(code); + + private static HashSet ValidTwoLetterCodes() + { + string[] codes = new string[]{ "AF", "AL", "DZ", "AS", "AD", "AO", "AI", "AQ", "AG", "AR", "AM", "AW", "AU", "AT", "AZ", "BS", "BH", "BD", "BB", "BY", "BE", "BZ", "BJ", "BM", "BT", "BO", "BQ", "BA", "BW", "BV", "BR", "IO", "BN", "BG", "BF", "BI", "CV", "KH", "CM", "CA", "KY", "CF", "TD", "CL", "CN", "CX", "CC", "CO", "KM", "CG", "CD", "CK", "CR", "CI", "HR", "CU", "CW", "CY", "CZ", "DK", "DJ", "DM", "DO", "EC", "EG", "SV", "GQ", "ER", "EE", "SZ", "ET", "FK", "FO", "FJ", "FI", "FR", "GF", "PF", "TF", "GA", "GM", "GE", "DE", "GH", "GI", "GR", "GL", "GD", "GP", "GU", "GT", "GG", "GN", "GW", "GY", "HT", "HM", "VA", "HN", "HK", "HU", "IS", "IN", "ID", "IR", "IQ", "IE", "IM", "IL", "IT", "JM", "JP", "JE", "JO", "KZ", "KE", "KI", "KP", "KR", "KW", "KG", "LA", "LV", "LB", "LS", "LR", "LY", "LI", "LT", "LU", "MO", "MG", "MW", "MY", "MV", "ML", "MT", "MH", "MQ", "MR", "MU", "YT", "MX", "FM", "MD", "MC", "MN", "ME", "MS", "MA", "MZ", "MM", "NA", "NR", "NP", "NL", "NC", "NZ", "NI", "NE", "NG", "NU", "NF", "MP", "MK", "NO", "OM", "PK", "PW", "PS", "PA", "PG", "PY", "PE", "PH", "PN", "PL", "PT", "PR", "QA", "RE", "RO", "RU", "RW", "BL", "SH", "KN", "LC", "MF", "PM", "VC", "WS", "SM", "ST", "SA", "SN", "RS", "SC", "SL", "SG", "SX", "SK", "SI", "SB", "SO", "ZA", "GS", "SS", "ES", "LK", "SD", "SR", "SJ", "SE", "CH", "SY", "TW", "TJ", "TZ", "TH", "TL", "TG", "TK", "TO", "TT", "TN", "TR", "TM", "TC", "TV", "UG", "UA", "AE", "GB", "US", "UM", "UY", "UZ", "VU", "VE", "VN", "VG", "VI", "WF", "EH", "YE", "ZM", "ZW", "AX", "XK" }; + return new HashSet(codes, StringComparer.OrdinalIgnoreCase); + } + + public override string ToString() + { + string contactData = $"{(AddressType.StructuredAddress == adrType ? "S" : "K")}{br}"; //AdrTp + contactData += name.Replace("\n", "") + br; //Name + contactData += (!string.IsNullOrEmpty(streetOrAddressline1) ? streetOrAddressline1.Replace("\n","") : string.Empty) + br; //StrtNmOrAdrLine1 + contactData += (!string.IsNullOrEmpty(houseNumberOrAddressline2) ? houseNumberOrAddressline2.Replace("\n", "") : string.Empty) + br; //BldgNbOrAdrLine2 + contactData += zipCode.Replace("\n", "") + br; //PstCd + contactData += city.Replace("\n", "") + br; //TwnNm + contactData += country + br; //Ctry + return contactData; + } + + public enum AddressType + { + StructuredAddress, + CombinedAddress + } + + public class SwissQrCodeContactException : Exception + { + public SwissQrCodeContactException() + { + } + + public SwissQrCodeContactException(string message) + : base(message) + { + } + + public SwissQrCodeContactException(string message, Exception inner) + : base(message, inner) + { + } + } + } + + public override string ToString() + { + //Header "logical" element + var SwissQrCodePayload = "SPC" + br; //QRType + SwissQrCodePayload += "0200" + br; //Version + SwissQrCodePayload += "1" + br; //Coding + + //CdtrInf "logical" element + SwissQrCodePayload += iban.ToString() + br; //IBAN + + + //Cdtr "logical" element + SwissQrCodePayload += creditor.ToString(); + + //UltmtCdtr "logical" element + //Since version 2.0 ultimate creditor was marked as "for future use" and has to be delivered empty in any case! + SwissQrCodePayload += string.Concat(Enumerable.Repeat(br, 7).ToArray()); + + //CcyAmtDate "logical" element + //Amoutn has to use . as decimal seperator in any case. See https://www.paymentstandards.ch/dam/downloads/ig-qr-bill-en.pdf page 27. + SwissQrCodePayload += (amount != null ? $"{amount:0.00}".Replace(",", ".") : string.Empty) + br; //Amt + SwissQrCodePayload += currency + br; //Ccy + //Removed in S-QR version 2.0 + //SwissQrCodePayload += (requestedDateOfPayment != null ? ((DateTime)requestedDateOfPayment).ToString("yyyy-MM-dd") : string.Empty) + br; //ReqdExctnDt + + //UltmtDbtr "logical" element + if (debitor != null) + SwissQrCodePayload += debitor.ToString(); + else + SwissQrCodePayload += string.Concat(Enumerable.Repeat(br, 7).ToArray()); + + + //RmtInf "logical" element + SwissQrCodePayload += reference.RefType.ToString() + br; //Tp + SwissQrCodePayload += (!string.IsNullOrEmpty(reference.ReferenceText) ? reference.ReferenceText : string.Empty) + br; //Ref + + + //AddInf "logical" element + SwissQrCodePayload += (!string.IsNullOrEmpty(additionalInformation.UnstructureMessage) ? additionalInformation.UnstructureMessage : string.Empty) + br; //Ustrd + SwissQrCodePayload += additionalInformation.Trailer + br; //Trailer + // Bugfix PR #399 If BillInformation is empty, insert no linebreak + SwissQrCodePayload += (!string.IsNullOrEmpty(additionalInformation.BillInformation) ? additionalInformation.BillInformation + br : string.Empty); //StrdBkgInf + + //AltPmtInf "logical" element + if (!string.IsNullOrEmpty(alternativeProcedure1)) + SwissQrCodePayload += alternativeProcedure1.Replace("\n", "") + br; //AltPmt + if (!string.IsNullOrEmpty(alternativeProcedure2)) + SwissQrCodePayload += alternativeProcedure2.Replace("\n", "") + br; //AltPmt + + //S-QR specification 2.0, chapter 4.2.3 + if (SwissQrCodePayload.EndsWith(br)) + SwissQrCodePayload = SwissQrCodePayload.Remove(SwissQrCodePayload.Length - br.Length); + + return SwissQrCodePayload; + } + + + + + /// + /// ISO 4217 currency codes + /// + public enum Currency + { + CHF = 756, + EUR = 978 + } + + public class SwissQrCodeException : Exception + { + public SwissQrCodeException() + { + } + + public SwissQrCodeException(string message) + : base(message) + { + } + + public SwissQrCodeException(string message, Exception inner) + : base(message, inner) + { + } + } + } + } +} diff --git a/QRCoder/PayloadGenerator/Url.cs b/QRCoder/PayloadGenerator/Url.cs new file mode 100644 index 00000000..0019327a --- /dev/null +++ b/QRCoder/PayloadGenerator/Url.cs @@ -0,0 +1,29 @@ +using System; +#if NETSTANDARD1_3 +using System.Reflection; +#endif + +namespace QRCoder +{ + public static partial class PayloadGenerator + { + public class Url : Payload + { + private readonly string url; + + /// + /// Generates a link. If the protocol is not specified, the http protocol will be added. + /// + /// Link url target + public Url(string url) + { + this.url = url; + } + + public override string ToString() + { + return (!this.url.StartsWith("http", StringComparison.OrdinalIgnoreCase) ? "http://" + this.url : this.url); + } + } + } +} diff --git a/QRCoder/PayloadGenerator/WhatsAppMessage.cs b/QRCoder/PayloadGenerator/WhatsAppMessage.cs new file mode 100644 index 00000000..0672596c --- /dev/null +++ b/QRCoder/PayloadGenerator/WhatsAppMessage.cs @@ -0,0 +1,46 @@ +using System; +using System.Text.RegularExpressions; +#if NETSTANDARD1_3 +using System.Reflection; +#endif + +namespace QRCoder +{ + public static partial class PayloadGenerator + { + public class WhatsAppMessage : Payload + { + private readonly string number, message; + + /// + /// Let's you compose a WhatApp message and send it the receiver number. + /// + /// Receiver phone number where the is a full phone number in international format. + /// Omit any zeroes, brackets, or dashes when adding the phone number in international format. + /// Use: 1XXXXXXXXXX | Don't use: +001-(XXX)XXXXXXX + /// + /// The message + public WhatsAppMessage(string number, string message) + { + this.number = number; + this.message = message; + } + + /// + /// Let's you compose a WhatApp message. When scanned the user is asked to choose a contact who will receive the message. + /// + /// The message + public WhatsAppMessage(string message) + { + this.number = string.Empty; + this.message = message; + } + + public override string ToString() + { + var cleanedPhone = Regex.Replace(this.number, @"^[0+]+|[ ()-]", string.Empty); + return ($"https://wa.me/{cleanedPhone}?text={Uri.EscapeDataString(message)}"); + } + } + } +} diff --git a/QRCoder/PayloadGenerator/WiFi.cs b/QRCoder/PayloadGenerator/WiFi.cs new file mode 100644 index 00000000..7963c09d --- /dev/null +++ b/QRCoder/PayloadGenerator/WiFi.cs @@ -0,0 +1,47 @@ +#if NETSTANDARD1_3 +using System.Reflection; +#endif + +namespace QRCoder +{ + public static partial class PayloadGenerator + { + public class WiFi : Payload + { + private readonly string ssid, password, authenticationMode; + private readonly bool isHiddenSsid; + + /// + /// Generates a WiFi payload. Scanned by a QR Code scanner app, the device will connect to the WiFi. + /// + /// SSID of the WiFi network + /// Password of the WiFi network + /// Authentification mode (WEP, WPA, WPA2) + /// Set flag, if the WiFi network hides its SSID + /// Set flag, if ssid/password is delivered as HEX string. Note: May not be supported on iOS devices. + public WiFi(string ssid, string password, Authentication authenticationMode, bool isHiddenSSID = false, bool escapeHexStrings = true) + { + this.ssid = EscapeInput(ssid); + this.ssid = escapeHexStrings && isHexStyle(this.ssid) ? "\"" + this.ssid + "\"" : this.ssid; + this.password = EscapeInput(password); + this.password = escapeHexStrings && isHexStyle(this.password) ? "\"" + this.password + "\"" : this.password; + this.authenticationMode = authenticationMode.ToString(); + this.isHiddenSsid = isHiddenSSID; + } + + public override string ToString() + { + return + $"WIFI:T:{this.authenticationMode};S:{this.ssid};P:{this.password};{(this.isHiddenSsid ? "H:true" : string.Empty)};"; + } + + public enum Authentication + { + WEP, + WPA, + nopass, + WPA2 + } + } + } +} diff --git a/QRCoder/QRCodeGenerator.AlignmentPattern.cs b/QRCoder/QRCodeGenerator/AlignmentPattern.cs similarity index 100% rename from QRCoder/QRCodeGenerator.AlignmentPattern.cs rename to QRCoder/QRCodeGenerator/AlignmentPattern.cs diff --git a/QRCoder/QRCodeGenerator.CodewordBlock.cs b/QRCoder/QRCodeGenerator/CodewordBlock.cs similarity index 100% rename from QRCoder/QRCodeGenerator.CodewordBlock.cs rename to QRCoder/QRCodeGenerator/CodewordBlock.cs diff --git a/QRCoder/QRCodeGenerator.ECCInfo.cs b/QRCoder/QRCodeGenerator/ECCInfo.cs similarity index 100% rename from QRCoder/QRCodeGenerator.ECCInfo.cs rename to QRCoder/QRCodeGenerator/ECCInfo.cs diff --git a/QRCoder/QRCodeGenerator.ECCLevel.cs b/QRCoder/QRCodeGenerator/ECCLevel.cs similarity index 100% rename from QRCoder/QRCodeGenerator.ECCLevel.cs rename to QRCoder/QRCodeGenerator/ECCLevel.cs diff --git a/QRCoder/QRCodeGenerator.EciMode.cs b/QRCoder/QRCodeGenerator/EciMode.cs similarity index 100% rename from QRCoder/QRCodeGenerator.EciMode.cs rename to QRCoder/QRCodeGenerator/EciMode.cs diff --git a/QRCoder/QRCodeGenerator.EncodingMode.cs b/QRCoder/QRCodeGenerator/EncodingMode.cs similarity index 100% rename from QRCoder/QRCodeGenerator.EncodingMode.cs rename to QRCoder/QRCodeGenerator/EncodingMode.cs diff --git a/QRCoder/QRCodeGenerator.ModulePlacer.BlockedModules.cs b/QRCoder/QRCodeGenerator/ModulePlacer.BlockedModules.cs similarity index 100% rename from QRCoder/QRCodeGenerator.ModulePlacer.BlockedModules.cs rename to QRCoder/QRCodeGenerator/ModulePlacer.BlockedModules.cs diff --git a/QRCoder/QRCodeGenerator.ModulePlacer.MaskPattern.cs b/QRCoder/QRCodeGenerator/ModulePlacer.MaskPattern.cs similarity index 100% rename from QRCoder/QRCodeGenerator.ModulePlacer.MaskPattern.cs rename to QRCoder/QRCodeGenerator/ModulePlacer.MaskPattern.cs diff --git a/QRCoder/QRCodeGenerator.ModulePlacer.cs b/QRCoder/QRCodeGenerator/ModulePlacer.cs similarity index 100% rename from QRCoder/QRCodeGenerator.ModulePlacer.cs rename to QRCoder/QRCodeGenerator/ModulePlacer.cs diff --git a/QRCoder/QRCodeGenerator.Point.cs b/QRCoder/QRCodeGenerator/Point.cs similarity index 100% rename from QRCoder/QRCodeGenerator.Point.cs rename to QRCoder/QRCodeGenerator/Point.cs diff --git a/QRCoder/QRCodeGenerator.Polynom.cs b/QRCoder/QRCodeGenerator/Polynom.cs similarity index 100% rename from QRCoder/QRCodeGenerator.Polynom.cs rename to QRCoder/QRCodeGenerator/Polynom.cs diff --git a/QRCoder/QRCodeGenerator.PolynomItem.cs b/QRCoder/QRCodeGenerator/PolynomItem.cs similarity index 100% rename from QRCoder/QRCodeGenerator.PolynomItem.cs rename to QRCoder/QRCodeGenerator/PolynomItem.cs diff --git a/QRCoder/QRCodeGenerator.Rectangle.cs b/QRCoder/QRCodeGenerator/Rectangle.cs similarity index 100% rename from QRCoder/QRCodeGenerator.Rectangle.cs rename to QRCoder/QRCodeGenerator/Rectangle.cs diff --git a/QRCoder/QRCodeGenerator.VersionInfo.cs b/QRCoder/QRCodeGenerator/VersionInfo.cs similarity index 100% rename from QRCoder/QRCodeGenerator.VersionInfo.cs rename to QRCoder/QRCodeGenerator/VersionInfo.cs diff --git a/QRCoder/QRCodeGenerator.VersionInfoDetails.cs b/QRCoder/QRCodeGenerator/VersionInfoDetails.cs similarity index 100% rename from QRCoder/QRCodeGenerator.VersionInfoDetails.cs rename to QRCoder/QRCodeGenerator/VersionInfoDetails.cs From f06eae2a055545bd37800466897532a237433090 Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Tue, 28 May 2024 18:11:00 -0400 Subject: [PATCH 2/2] Clean up using statements --- QRCoder/PayloadGenerator/BezahlCode.cs | 3 --- QRCoder/PayloadGenerator/BitcoinAddress.cs | 6 +----- QRCoder/PayloadGenerator/BitcoinCashAddress.cs | 6 +----- .../PayloadGenerator/BitcoinLikeCryptoCurrencyAddress.cs | 3 --- QRCoder/PayloadGenerator/Bookmark.cs | 6 +----- QRCoder/PayloadGenerator/CalendarEvent.cs | 3 --- QRCoder/PayloadGenerator/ContactData.cs | 3 --- QRCoder/PayloadGenerator/Geolocation.cs | 6 +----- QRCoder/PayloadGenerator/Girocode.cs | 3 --- QRCoder/PayloadGenerator/LitecoinAddress.cs | 6 +----- QRCoder/PayloadGenerator/MMS.cs | 3 --- QRCoder/PayloadGenerator/Mail.cs | 3 --- QRCoder/PayloadGenerator/MoneroTransaction.cs | 3 --- QRCoder/PayloadGenerator/OneTimePassword.cs | 3 --- QRCoder/PayloadGenerator/Payload.cs | 6 +----- QRCoder/PayloadGenerator/PhoneNumber.cs | 6 +----- QRCoder/PayloadGenerator/SMS.cs | 3 --- QRCoder/PayloadGenerator/ShadowSocksConfig.cs | 3 --- QRCoder/PayloadGenerator/SkypeCall.cs | 6 +----- QRCoder/PayloadGenerator/SlovenianUpnQr.cs | 3 --- QRCoder/PayloadGenerator/SwissQrCode.cs | 3 --- QRCoder/PayloadGenerator/Url.cs | 3 --- QRCoder/PayloadGenerator/WhatsAppMessage.cs | 3 --- QRCoder/PayloadGenerator/WiFi.cs | 6 +----- 24 files changed, 9 insertions(+), 90 deletions(-) diff --git a/QRCoder/PayloadGenerator/BezahlCode.cs b/QRCoder/PayloadGenerator/BezahlCode.cs index 7bb8c9c6..24f9dd18 100644 --- a/QRCoder/PayloadGenerator/BezahlCode.cs +++ b/QRCoder/PayloadGenerator/BezahlCode.cs @@ -1,8 +1,5 @@ using System; using System.Text.RegularExpressions; -#if NETSTANDARD1_3 -using System.Reflection; -#endif namespace QRCoder { diff --git a/QRCoder/PayloadGenerator/BitcoinAddress.cs b/QRCoder/PayloadGenerator/BitcoinAddress.cs index f1391d03..b76776c5 100644 --- a/QRCoder/PayloadGenerator/BitcoinAddress.cs +++ b/QRCoder/PayloadGenerator/BitcoinAddress.cs @@ -1,8 +1,4 @@ -#if NETSTANDARD1_3 -using System.Reflection; -#endif - -namespace QRCoder +namespace QRCoder { public static partial class PayloadGenerator { diff --git a/QRCoder/PayloadGenerator/BitcoinCashAddress.cs b/QRCoder/PayloadGenerator/BitcoinCashAddress.cs index 4cc937d1..fa14d7a6 100644 --- a/QRCoder/PayloadGenerator/BitcoinCashAddress.cs +++ b/QRCoder/PayloadGenerator/BitcoinCashAddress.cs @@ -1,8 +1,4 @@ -#if NETSTANDARD1_3 -using System.Reflection; -#endif - -namespace QRCoder +namespace QRCoder { public static partial class PayloadGenerator { diff --git a/QRCoder/PayloadGenerator/BitcoinLikeCryptoCurrencyAddress.cs b/QRCoder/PayloadGenerator/BitcoinLikeCryptoCurrencyAddress.cs index 44021149..bda54eae 100644 --- a/QRCoder/PayloadGenerator/BitcoinLikeCryptoCurrencyAddress.cs +++ b/QRCoder/PayloadGenerator/BitcoinLikeCryptoCurrencyAddress.cs @@ -2,9 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Globalization; -#if NETSTANDARD1_3 -using System.Reflection; -#endif namespace QRCoder { diff --git a/QRCoder/PayloadGenerator/Bookmark.cs b/QRCoder/PayloadGenerator/Bookmark.cs index 2072caf8..720866d1 100644 --- a/QRCoder/PayloadGenerator/Bookmark.cs +++ b/QRCoder/PayloadGenerator/Bookmark.cs @@ -1,8 +1,4 @@ -#if NETSTANDARD1_3 -using System.Reflection; -#endif - -namespace QRCoder +namespace QRCoder { public static partial class PayloadGenerator { diff --git a/QRCoder/PayloadGenerator/CalendarEvent.cs b/QRCoder/PayloadGenerator/CalendarEvent.cs index 0f22e2f4..4b75a3ee 100644 --- a/QRCoder/PayloadGenerator/CalendarEvent.cs +++ b/QRCoder/PayloadGenerator/CalendarEvent.cs @@ -1,7 +1,4 @@ using System; -#if NETSTANDARD1_3 -using System.Reflection; -#endif namespace QRCoder { diff --git a/QRCoder/PayloadGenerator/ContactData.cs b/QRCoder/PayloadGenerator/ContactData.cs index 6c2db8ee..28fba67f 100644 --- a/QRCoder/PayloadGenerator/ContactData.cs +++ b/QRCoder/PayloadGenerator/ContactData.cs @@ -1,7 +1,4 @@ using System; -#if NETSTANDARD1_3 -using System.Reflection; -#endif namespace QRCoder { diff --git a/QRCoder/PayloadGenerator/Geolocation.cs b/QRCoder/PayloadGenerator/Geolocation.cs index f6e351e1..65b546d0 100644 --- a/QRCoder/PayloadGenerator/Geolocation.cs +++ b/QRCoder/PayloadGenerator/Geolocation.cs @@ -1,8 +1,4 @@ -#if NETSTANDARD1_3 -using System.Reflection; -#endif - -namespace QRCoder +namespace QRCoder { public static partial class PayloadGenerator { diff --git a/QRCoder/PayloadGenerator/Girocode.cs b/QRCoder/PayloadGenerator/Girocode.cs index 3083fcf9..8fa99952 100644 --- a/QRCoder/PayloadGenerator/Girocode.cs +++ b/QRCoder/PayloadGenerator/Girocode.cs @@ -1,7 +1,4 @@ using System; -#if NETSTANDARD1_3 -using System.Reflection; -#endif namespace QRCoder { diff --git a/QRCoder/PayloadGenerator/LitecoinAddress.cs b/QRCoder/PayloadGenerator/LitecoinAddress.cs index dfd741f3..b2e5e76f 100644 --- a/QRCoder/PayloadGenerator/LitecoinAddress.cs +++ b/QRCoder/PayloadGenerator/LitecoinAddress.cs @@ -1,8 +1,4 @@ -#if NETSTANDARD1_3 -using System.Reflection; -#endif - -namespace QRCoder +namespace QRCoder { public static partial class PayloadGenerator { diff --git a/QRCoder/PayloadGenerator/MMS.cs b/QRCoder/PayloadGenerator/MMS.cs index d1ac362d..bb342800 100644 --- a/QRCoder/PayloadGenerator/MMS.cs +++ b/QRCoder/PayloadGenerator/MMS.cs @@ -1,7 +1,4 @@ using System; -#if NETSTANDARD1_3 -using System.Reflection; -#endif namespace QRCoder { diff --git a/QRCoder/PayloadGenerator/Mail.cs b/QRCoder/PayloadGenerator/Mail.cs index 3643cac7..217733f9 100644 --- a/QRCoder/PayloadGenerator/Mail.cs +++ b/QRCoder/PayloadGenerator/Mail.cs @@ -1,9 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -#if NETSTANDARD1_3 -using System.Reflection; -#endif namespace QRCoder { diff --git a/QRCoder/PayloadGenerator/MoneroTransaction.cs b/QRCoder/PayloadGenerator/MoneroTransaction.cs index 355620ed..8deb0ddf 100644 --- a/QRCoder/PayloadGenerator/MoneroTransaction.cs +++ b/QRCoder/PayloadGenerator/MoneroTransaction.cs @@ -1,7 +1,4 @@ using System; -#if NETSTANDARD1_3 -using System.Reflection; -#endif namespace QRCoder { diff --git a/QRCoder/PayloadGenerator/OneTimePassword.cs b/QRCoder/PayloadGenerator/OneTimePassword.cs index 40c6f3fa..b1fb0682 100644 --- a/QRCoder/PayloadGenerator/OneTimePassword.cs +++ b/QRCoder/PayloadGenerator/OneTimePassword.cs @@ -1,8 +1,5 @@ using System; using System.Text; -#if NETSTANDARD1_3 -using System.Reflection; -#endif namespace QRCoder { diff --git a/QRCoder/PayloadGenerator/Payload.cs b/QRCoder/PayloadGenerator/Payload.cs index 7df489b8..af6ee9d9 100644 --- a/QRCoder/PayloadGenerator/Payload.cs +++ b/QRCoder/PayloadGenerator/Payload.cs @@ -1,8 +1,4 @@ -#if NETSTANDARD1_3 -using System.Reflection; -#endif - -namespace QRCoder +namespace QRCoder { public static partial class PayloadGenerator { diff --git a/QRCoder/PayloadGenerator/PhoneNumber.cs b/QRCoder/PayloadGenerator/PhoneNumber.cs index 68550ffd..7d30cac9 100644 --- a/QRCoder/PayloadGenerator/PhoneNumber.cs +++ b/QRCoder/PayloadGenerator/PhoneNumber.cs @@ -1,8 +1,4 @@ -#if NETSTANDARD1_3 -using System.Reflection; -#endif - -namespace QRCoder +namespace QRCoder { public static partial class PayloadGenerator { diff --git a/QRCoder/PayloadGenerator/SMS.cs b/QRCoder/PayloadGenerator/SMS.cs index 180eb38b..c0cee669 100644 --- a/QRCoder/PayloadGenerator/SMS.cs +++ b/QRCoder/PayloadGenerator/SMS.cs @@ -1,7 +1,4 @@ using System; -#if NETSTANDARD1_3 -using System.Reflection; -#endif namespace QRCoder { diff --git a/QRCoder/PayloadGenerator/ShadowSocksConfig.cs b/QRCoder/PayloadGenerator/ShadowSocksConfig.cs index 5468826d..61a6098d 100644 --- a/QRCoder/PayloadGenerator/ShadowSocksConfig.cs +++ b/QRCoder/PayloadGenerator/ShadowSocksConfig.cs @@ -2,9 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Text; -#if NETSTANDARD1_3 -using System.Reflection; -#endif namespace QRCoder { diff --git a/QRCoder/PayloadGenerator/SkypeCall.cs b/QRCoder/PayloadGenerator/SkypeCall.cs index 0aad10ed..c57d04ab 100644 --- a/QRCoder/PayloadGenerator/SkypeCall.cs +++ b/QRCoder/PayloadGenerator/SkypeCall.cs @@ -1,8 +1,4 @@ -#if NETSTANDARD1_3 -using System.Reflection; -#endif - -namespace QRCoder +namespace QRCoder { public static partial class PayloadGenerator { diff --git a/QRCoder/PayloadGenerator/SlovenianUpnQr.cs b/QRCoder/PayloadGenerator/SlovenianUpnQr.cs index 30ce31b5..9d423946 100644 --- a/QRCoder/PayloadGenerator/SlovenianUpnQr.cs +++ b/QRCoder/PayloadGenerator/SlovenianUpnQr.cs @@ -1,8 +1,5 @@ using System; using System.Text; -#if NETSTANDARD1_3 -using System.Reflection; -#endif namespace QRCoder { diff --git a/QRCoder/PayloadGenerator/SwissQrCode.cs b/QRCoder/PayloadGenerator/SwissQrCode.cs index 992ec1db..06fcbbac 100644 --- a/QRCoder/PayloadGenerator/SwissQrCode.cs +++ b/QRCoder/PayloadGenerator/SwissQrCode.cs @@ -2,9 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; -#if NETSTANDARD1_3 -using System.Reflection; -#endif namespace QRCoder { diff --git a/QRCoder/PayloadGenerator/Url.cs b/QRCoder/PayloadGenerator/Url.cs index 0019327a..8b6f09d0 100644 --- a/QRCoder/PayloadGenerator/Url.cs +++ b/QRCoder/PayloadGenerator/Url.cs @@ -1,7 +1,4 @@ using System; -#if NETSTANDARD1_3 -using System.Reflection; -#endif namespace QRCoder { diff --git a/QRCoder/PayloadGenerator/WhatsAppMessage.cs b/QRCoder/PayloadGenerator/WhatsAppMessage.cs index 0672596c..59379a90 100644 --- a/QRCoder/PayloadGenerator/WhatsAppMessage.cs +++ b/QRCoder/PayloadGenerator/WhatsAppMessage.cs @@ -1,8 +1,5 @@ using System; using System.Text.RegularExpressions; -#if NETSTANDARD1_3 -using System.Reflection; -#endif namespace QRCoder { diff --git a/QRCoder/PayloadGenerator/WiFi.cs b/QRCoder/PayloadGenerator/WiFi.cs index 7963c09d..70efe2a7 100644 --- a/QRCoder/PayloadGenerator/WiFi.cs +++ b/QRCoder/PayloadGenerator/WiFi.cs @@ -1,8 +1,4 @@ -#if NETSTANDARD1_3 -using System.Reflection; -#endif - -namespace QRCoder +namespace QRCoder { public static partial class PayloadGenerator {