You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2234 lines
101 KiB

  1. #if NET40 || NET461
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Globalization;
  5. #if !NET20
  6. using System.Linq;
  7. #endif
  8. using System.Text;
  9. using System.Text.RegularExpressions;
  10. namespace Apewer.Internals.QrCode
  11. {
  12. internal static class PayloadGenerator
  13. {
  14. public abstract class Payload
  15. {
  16. public virtual int Version { get { return -1; } }
  17. public virtual QRCodeGenerator.ECCLevel EccLevel { get { return QRCodeGenerator.ECCLevel.M; } }
  18. public virtual QRCodeGenerator.EciMode EciMode { get { return QRCodeGenerator.EciMode.Default; } }
  19. public abstract override string ToString();
  20. }
  21. public class WiFi : Payload
  22. {
  23. private readonly string ssid, password, authenticationMode;
  24. private readonly bool isHiddenSsid;
  25. /// <summary>
  26. /// Generates a WiFi payload. Scanned by a QR Code scanner app, the device will connect to the WiFi.
  27. /// </summary>
  28. /// <param name="ssid">SSID of the WiFi network</param>
  29. /// <param name="password">Password of the WiFi network</param>
  30. /// <param name="authenticationMode">Authentification mode (WEP, WPA, WPA2)</param>
  31. /// <param name="isHiddenSSID">Set flag, if the WiFi network hides its SSID</param>
  32. public WiFi(string ssid, string password, Authentication authenticationMode, bool isHiddenSSID = false)
  33. {
  34. this.ssid = EscapeInput(ssid);
  35. this.ssid = isHexStyle(this.ssid) ? "\"" + this.ssid + "\"" : this.ssid;
  36. this.password = EscapeInput(password);
  37. this.password = isHexStyle(this.password) ? "\"" + this.password + "\"" : this.password;
  38. this.authenticationMode = authenticationMode.ToString();
  39. this.isHiddenSsid = isHiddenSSID;
  40. }
  41. public override string ToString()
  42. {
  43. return
  44. $"WIFI:T:{this.authenticationMode};S:{this.ssid};P:{this.password};{(this.isHiddenSsid ? "H:true" : string.Empty)};";
  45. }
  46. public enum Authentication
  47. {
  48. WEP,
  49. WPA,
  50. nopass
  51. }
  52. }
  53. public class Mail : Payload
  54. {
  55. private readonly string mailReceiver, subject, message;
  56. private readonly MailEncoding encoding;
  57. /// <summary>
  58. /// Creates an empty email payload
  59. /// </summary>
  60. /// <param name="mailReceiver">Receiver's email address</param>
  61. /// <param name="encoding">Payload encoding type. Choose dependent on your QR Code scanner app.</param>
  62. public Mail(string mailReceiver, MailEncoding encoding = MailEncoding.MAILTO)
  63. {
  64. this.mailReceiver = mailReceiver;
  65. this.subject = this.message = string.Empty;
  66. this.encoding = encoding;
  67. }
  68. /// <summary>
  69. /// Creates an email payload with subject
  70. /// </summary>
  71. /// <param name="mailReceiver">Receiver's email address</param>
  72. /// <param name="subject">Subject line of the email</param>
  73. /// <param name="encoding">Payload encoding type. Choose dependent on your QR Code scanner app.</param>
  74. public Mail(string mailReceiver, string subject, MailEncoding encoding = MailEncoding.MAILTO)
  75. {
  76. this.mailReceiver = mailReceiver;
  77. this.subject = subject;
  78. this.message = string.Empty;
  79. this.encoding = encoding;
  80. }
  81. /// <summary>
  82. /// Creates an email payload with subject and message/text
  83. /// </summary>
  84. /// <param name="mailReceiver">Receiver's email address</param>
  85. /// <param name="subject">Subject line of the email</param>
  86. /// <param name="message">Message content of the email</param>
  87. /// <param name="encoding">Payload encoding type. Choose dependent on your QR Code scanner app.</param>
  88. public Mail(string mailReceiver, string subject, string message, MailEncoding encoding = MailEncoding.MAILTO)
  89. {
  90. this.mailReceiver = mailReceiver;
  91. this.subject = subject;
  92. this.message = message;
  93. this.encoding = encoding;
  94. }
  95. public override string ToString()
  96. {
  97. switch (this.encoding)
  98. {
  99. case MailEncoding.MAILTO:
  100. return
  101. $"mailto:{this.mailReceiver}?subject={System.Uri.EscapeDataString(this.subject)}&body={System.Uri.EscapeDataString(this.message)}";
  102. case MailEncoding.MATMSG:
  103. return
  104. $"MATMSG:TO:{this.mailReceiver};SUB:{EscapeInput(this.subject)};BODY:{EscapeInput(this.message)};;";
  105. case MailEncoding.SMTP:
  106. return
  107. $"SMTP:{this.mailReceiver}:{EscapeInput(this.subject, true)}:{EscapeInput(this.message, true)}";
  108. default:
  109. return this.mailReceiver;
  110. }
  111. }
  112. public enum MailEncoding
  113. {
  114. MAILTO,
  115. MATMSG,
  116. SMTP
  117. }
  118. }
  119. public class SMS : Payload
  120. {
  121. private readonly string number, subject;
  122. private readonly SMSEncoding encoding;
  123. /// <summary>
  124. /// Creates a SMS payload without text
  125. /// </summary>
  126. /// <param name="number">Receiver phone number</param>
  127. /// <param name="encoding">Encoding type</param>
  128. public SMS(string number, SMSEncoding encoding = SMSEncoding.SMS)
  129. {
  130. this.number = number;
  131. this.subject = string.Empty;
  132. this.encoding = encoding;
  133. }
  134. /// <summary>
  135. /// Creates a SMS payload with text (subject)
  136. /// </summary>
  137. /// <param name="number">Receiver phone number</param>
  138. /// <param name="subject">Text of the SMS</param>
  139. /// <param name="encoding">Encoding type</param>
  140. public SMS(string number, string subject, SMSEncoding encoding = SMSEncoding.SMS)
  141. {
  142. this.number = number;
  143. this.subject = subject;
  144. this.encoding = encoding;
  145. }
  146. public override string ToString()
  147. {
  148. switch (this.encoding)
  149. {
  150. case SMSEncoding.SMS:
  151. return $"sms:{this.number}?body={System.Uri.EscapeDataString(this.subject)}";
  152. case SMSEncoding.SMS_iOS:
  153. return $"sms:{this.number};body={System.Uri.EscapeDataString(this.subject)}";
  154. case SMSEncoding.SMSTO:
  155. return $"SMSTO:{this.number}:{this.subject}";
  156. default:
  157. return "sms:";
  158. }
  159. }
  160. public enum SMSEncoding
  161. {
  162. SMS,
  163. SMSTO,
  164. SMS_iOS
  165. }
  166. }
  167. public class MMS : Payload
  168. {
  169. private readonly string number, subject;
  170. private readonly MMSEncoding encoding;
  171. /// <summary>
  172. /// Creates a MMS payload without text
  173. /// </summary>
  174. /// <param name="number">Receiver phone number</param>
  175. /// <param name="encoding">Encoding type</param>
  176. public MMS(string number, MMSEncoding encoding = MMSEncoding.MMS)
  177. {
  178. this.number = number;
  179. this.subject = string.Empty;
  180. this.encoding = encoding;
  181. }
  182. /// <summary>
  183. /// Creates a MMS payload with text (subject)
  184. /// </summary>
  185. /// <param name="number">Receiver phone number</param>
  186. /// <param name="subject">Text of the MMS</param>
  187. /// <param name="encoding">Encoding type</param>
  188. public MMS(string number, string subject, MMSEncoding encoding = MMSEncoding.MMS)
  189. {
  190. this.number = number;
  191. this.subject = subject;
  192. this.encoding = encoding;
  193. }
  194. public override string ToString()
  195. {
  196. switch (this.encoding)
  197. {
  198. case MMSEncoding.MMSTO:
  199. return $"mmsto:{this.number}?subject={System.Uri.EscapeDataString(this.subject)}";
  200. case MMSEncoding.MMS:
  201. return $"mms:{this.number}?body={System.Uri.EscapeDataString(this.subject)}";
  202. default:
  203. return "mms:";
  204. }
  205. }
  206. public enum MMSEncoding
  207. {
  208. MMS,
  209. MMSTO
  210. }
  211. }
  212. public class Geolocation : Payload
  213. {
  214. private readonly string latitude, longitude;
  215. private readonly GeolocationEncoding encoding;
  216. /// <summary>
  217. /// Generates a geo location payload. Supports raw location (GEO encoding) or Google Maps link (GoogleMaps encoding)
  218. /// </summary>
  219. /// <param name="latitude">Latitude with . as splitter</param>
  220. /// <param name="longitude">Longitude with . as splitter</param>
  221. /// <param name="encoding">Encoding type - GEO or GoogleMaps</param>
  222. public Geolocation(string latitude, string longitude, GeolocationEncoding encoding = GeolocationEncoding.GEO)
  223. {
  224. this.latitude = latitude.Replace(",", ".");
  225. this.longitude = longitude.Replace(",", ".");
  226. this.encoding = encoding;
  227. }
  228. public override string ToString()
  229. {
  230. switch (this.encoding)
  231. {
  232. case GeolocationEncoding.GEO:
  233. return $"geo:{this.latitude},{this.longitude}";
  234. case GeolocationEncoding.GoogleMaps:
  235. return $"http://maps.google.com/maps?q={this.latitude},{this.longitude}";
  236. default:
  237. return "geo:";
  238. }
  239. }
  240. public enum GeolocationEncoding
  241. {
  242. GEO,
  243. GoogleMaps
  244. }
  245. }
  246. public class PhoneNumber : Payload
  247. {
  248. private readonly string number;
  249. /// <summary>
  250. /// Generates a phone call payload
  251. /// </summary>
  252. /// <param name="number">Phonenumber of the receiver</param>
  253. public PhoneNumber(string number)
  254. {
  255. this.number = number;
  256. }
  257. public override string ToString()
  258. {
  259. return $"tel:{this.number}";
  260. }
  261. }
  262. public class SkypeCall : Payload
  263. {
  264. private readonly string skypeUsername;
  265. /// <summary>
  266. /// Generates a Skype call payload
  267. /// </summary>
  268. /// <param name="skypeUsername">Skype username which will be called</param>
  269. public SkypeCall(string skypeUsername)
  270. {
  271. this.skypeUsername = skypeUsername;
  272. }
  273. public override string ToString()
  274. {
  275. return $"skype:{this.skypeUsername}?call";
  276. }
  277. }
  278. public class Url : Payload
  279. {
  280. private readonly string url;
  281. /// <summary>
  282. /// Generates a link. If not given, http/https protocol will be added.
  283. /// </summary>
  284. /// <param name="url">Link url target</param>
  285. public Url(string url)
  286. {
  287. this.url = url;
  288. }
  289. public override string ToString()
  290. {
  291. return (!this.url.StartsWith("http") ? "http://" + this.url : this.url);
  292. }
  293. }
  294. public class WhatsAppMessage : Payload
  295. {
  296. private readonly string message;
  297. /// <summary>
  298. /// Let's you compose a WhatApp message. When scanned the user is asked to choose a contact who will receive the message.
  299. /// </summary>
  300. /// <param name="message">The message</param>
  301. public WhatsAppMessage(string message)
  302. {
  303. this.message = message;
  304. }
  305. public override string ToString()
  306. {
  307. return ($"whatsapp://send?text={Uri.EscapeDataString(message)}");
  308. }
  309. }
  310. public class Bookmark : Payload
  311. {
  312. private readonly string url, title;
  313. /// <summary>
  314. /// Generates a bookmark payload. Scanned by an QR Code reader, this one creates a browser bookmark.
  315. /// </summary>
  316. /// <param name="url">Url of the bookmark</param>
  317. /// <param name="title">Title of the bookmark</param>
  318. public Bookmark(string url, string title)
  319. {
  320. this.url = EscapeInput(url);
  321. this.title = EscapeInput(title);
  322. }
  323. public override string ToString()
  324. {
  325. return $"MEBKM:TITLE:{this.title};URL:{this.url};;";
  326. }
  327. }
  328. public class ContactData : Payload
  329. {
  330. private readonly string firstname;
  331. private readonly string lastname;
  332. private readonly string nickname;
  333. private readonly string phone;
  334. private readonly string mobilePhone;
  335. private readonly string workPhone;
  336. private readonly string email;
  337. private readonly DateTime? birthday;
  338. private readonly string website;
  339. private readonly string street;
  340. private readonly string houseNumber;
  341. private readonly string city;
  342. private readonly string zipCode;
  343. private readonly string stateRegion;
  344. private readonly string country;
  345. private readonly string note;
  346. private readonly ContactOutputType outputType;
  347. private readonly AddressOrder addressOrder;
  348. /// <summary>
  349. /// Generates a vCard or meCard contact dataset
  350. /// </summary>
  351. /// <param name="outputType">Payload output type</param>
  352. /// <param name="firstname">The firstname</param>
  353. /// <param name="lastname">The lastname</param>
  354. /// <param name="nickname">The displayname</param>
  355. /// <param name="phone">Normal phone number</param>
  356. /// <param name="mobilePhone">Mobile phone</param>
  357. /// <param name="workPhone">Office phone number</param>
  358. /// <param name="email">E-Mail address</param>
  359. /// <param name="birthday">Birthday</param>
  360. /// <param name="website">Website / Homepage</param>
  361. /// <param name="street">Street</param>
  362. /// <param name="houseNumber">Housenumber</param>
  363. /// <param name="city">City</param>
  364. /// <param name="stateRegion">State or Region</param>
  365. /// <param name="zipCode">Zip code</param>
  366. /// <param name="country">Country</param>
  367. /// <param name="addressOrder">The address order format to use</param>
  368. /// <param name="note">Memo text / notes</param>
  369. 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)
  370. {
  371. this.firstname = firstname;
  372. this.lastname = lastname;
  373. this.nickname = nickname;
  374. this.phone = phone;
  375. this.mobilePhone = mobilePhone;
  376. this.workPhone = workPhone;
  377. this.email = email;
  378. this.birthday = birthday;
  379. this.website = website;
  380. this.street = street;
  381. this.houseNumber = houseNumber;
  382. this.city = city;
  383. this.stateRegion = stateRegion;
  384. this.zipCode = zipCode;
  385. this.country = country;
  386. this.addressOrder = addressOrder;
  387. this.note = note;
  388. this.outputType = outputType;
  389. }
  390. public override string ToString()
  391. {
  392. string payload = string.Empty;
  393. if (outputType.Equals(ContactOutputType.MeCard))
  394. {
  395. payload += "MECARD+\r\n";
  396. if (!string.IsNullOrEmpty(firstname) && !string.IsNullOrEmpty(lastname))
  397. payload += $"N:{lastname}, {firstname}\r\n";
  398. else if (!string.IsNullOrEmpty(firstname) || !string.IsNullOrEmpty(lastname))
  399. payload += $"N:{firstname}{lastname}\r\n";
  400. if (!string.IsNullOrEmpty(phone))
  401. payload += $"TEL:{phone}\r\n";
  402. if (!string.IsNullOrEmpty(mobilePhone))
  403. payload += $"TEL:{mobilePhone}\r\n";
  404. if (!string.IsNullOrEmpty(workPhone))
  405. payload += $"TEL:{workPhone}\r\n";
  406. if (!string.IsNullOrEmpty(email))
  407. payload += $"EMAIL:{email}\r\n";
  408. if (!string.IsNullOrEmpty(note))
  409. payload += $"NOTE:{note}\r\n";
  410. if (birthday != null)
  411. payload += $"BDAY:{((DateTime)birthday).ToString("yyyyMMdd")}\r\n";
  412. string addressString = string.Empty;
  413. if (addressOrder == AddressOrder.Default)
  414. {
  415. 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";
  416. }
  417. else
  418. {
  419. 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";
  420. }
  421. payload += addressString;
  422. if (!string.IsNullOrEmpty(website))
  423. payload += $"URL:{website}\r\n";
  424. if (!string.IsNullOrEmpty(nickname))
  425. payload += $"NICKNAME:{nickname}\r\n";
  426. payload = payload.Trim(new char[] { '\r', '\n' });
  427. }
  428. else
  429. {
  430. var version = outputType.ToString().Substring(5);
  431. if (version.Length > 1)
  432. version = version.Insert(1, ".");
  433. else
  434. version += ".0";
  435. payload += "BEGIN:VCARD\r\n";
  436. payload += $"VERSION:{version}\r\n";
  437. payload += $"N:{(!string.IsNullOrEmpty(lastname) ? lastname : "")};{(!string.IsNullOrEmpty(firstname) ? firstname : "")};;;\r\n";
  438. payload += $"FN:{(!string.IsNullOrEmpty(firstname) ? firstname + " " : "")}{(!string.IsNullOrEmpty(lastname) ? lastname : "")}\r\n";
  439. if (!string.IsNullOrEmpty(phone))
  440. {
  441. payload += $"TEL;";
  442. if (outputType.Equals(ContactOutputType.VCard21))
  443. payload += $"HOME;VOICE:{phone}";
  444. else if (outputType.Equals(ContactOutputType.VCard3))
  445. payload += $"TYPE=HOME,VOICE:{phone}";
  446. else
  447. payload += $"TYPE=home,voice;VALUE=uri:tel:{phone}";
  448. payload += "\r\n";
  449. }
  450. if (!string.IsNullOrEmpty(mobilePhone))
  451. {
  452. payload += $"TEL;";
  453. if (outputType.Equals(ContactOutputType.VCard21))
  454. payload += $"HOME;CELL:{mobilePhone}";
  455. else if (outputType.Equals(ContactOutputType.VCard3))
  456. payload += $"TYPE=HOME,CELL:{mobilePhone}";
  457. else
  458. payload += $"TYPE=home,cell;VALUE=uri:tel:{mobilePhone}";
  459. payload += "\r\n";
  460. }
  461. if (!string.IsNullOrEmpty(workPhone))
  462. {
  463. payload += $"TEL;";
  464. if (outputType.Equals(ContactOutputType.VCard21))
  465. payload += $"WORK;VOICE:{workPhone}";
  466. else if (outputType.Equals(ContactOutputType.VCard3))
  467. payload += $"TYPE=WORK,VOICE:{workPhone}";
  468. else
  469. payload += $"TYPE=work,voice;VALUE=uri:tel:{workPhone}";
  470. payload += "\r\n";
  471. }
  472. payload += "ADR;";
  473. if (outputType.Equals(ContactOutputType.VCard21))
  474. payload += "HOME;PREF:";
  475. else if (outputType.Equals(ContactOutputType.VCard3))
  476. payload += "TYPE=HOME,PREF:";
  477. else
  478. payload += "TYPE=home,pref:";
  479. string addressString = string.Empty;
  480. if (addressOrder == AddressOrder.Default)
  481. {
  482. 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";
  483. }
  484. else
  485. {
  486. 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";
  487. }
  488. payload += addressString;
  489. if (birthday != null)
  490. payload += $"BDAY:{((DateTime)birthday).ToString("yyyyMMdd")}\r\n";
  491. if (!string.IsNullOrEmpty(phone))
  492. payload += $"URL:{website}\r\n";
  493. if (!string.IsNullOrEmpty(email))
  494. payload += $"EMAIL:{email}\r\n";
  495. if (!string.IsNullOrEmpty(note))
  496. payload += $"NOTE:{note}\r\n";
  497. if (!outputType.Equals(ContactOutputType.VCard21) && !string.IsNullOrEmpty(nickname))
  498. payload += $"NICKNAME:{nickname}\r\n";
  499. payload += "END:VCARD";
  500. }
  501. return payload;
  502. }
  503. /// <summary>
  504. /// Possible output types. Either vCard 2.1, vCard 3.0, vCard 4.0 or MeCard.
  505. /// </summary>
  506. public enum ContactOutputType
  507. {
  508. MeCard,
  509. VCard21,
  510. VCard3,
  511. VCard4
  512. }
  513. /// <summary>
  514. /// define the address format
  515. /// Default: European format, ([Street] [House Number] and [Postal Code] [City]
  516. /// Reversed: North American and others format ([House Number] [Street] and [City] [Postal Code])
  517. /// </summary>
  518. public enum AddressOrder
  519. {
  520. Default,
  521. Reversed
  522. }
  523. }
  524. public class BitcoinAddress : Payload
  525. {
  526. private readonly string address, label, message;
  527. private readonly double? amount;
  528. /// <summary>
  529. /// Generates a Bitcoin payment payload. QR Codes with this payload can open a Bitcoin payment app.
  530. /// </summary>
  531. /// <param name="address">Bitcoin address of the payment receiver</param>
  532. /// <param name="amount">Amount of Bitcoins to transfer</param>
  533. /// <param name="label">Reference label</param>
  534. /// <param name="message">Referece text aka message</param>
  535. public BitcoinAddress(string address, double? amount, string label = null, string message = null)
  536. {
  537. this.address = address;
  538. if (!string.IsNullOrEmpty(label))
  539. {
  540. this.label = Uri.EscapeUriString(label);
  541. }
  542. if (!string.IsNullOrEmpty(message))
  543. {
  544. this.message = Uri.EscapeUriString(message);
  545. }
  546. this.amount = amount;
  547. }
  548. public override string ToString()
  549. {
  550. string query = null;
  551. var queryValues = new List<KeyValuePair<string, string>>{
  552. new KeyValuePair<string, string>(nameof(label), label),
  553. new KeyValuePair<string, string>(nameof(message), message),
  554. new KeyValuePair<string, string>(nameof(amount), amount.HasValue ? amount.Value.ToString("#.########", CultureInfo.InvariantCulture) : null)
  555. };
  556. var predicated = false;
  557. foreach (var keyPair in queryValues)
  558. {
  559. if (!string.IsNullOrEmpty(keyPair.Value))
  560. {
  561. predicated = true;
  562. break;
  563. }
  564. }
  565. if (predicated)
  566. {
  567. var values2 = new List<KeyValuePair<string, string>>();
  568. foreach (var keyPair in queryValues)
  569. {
  570. if (!string.IsNullOrEmpty(keyPair.Value)) values2.Add(keyPair);
  571. }
  572. var pairs = new List<string>();
  573. foreach (var keyPair in values2)
  574. {
  575. pairs.Add($"{keyPair.Key}={keyPair.Value}");
  576. }
  577. query = string.Join("&", pairs.ToArray());
  578. }
  579. //if (queryValues.Any(keyPair => !string.IsNullOrEmpty(keyPair.Value)))
  580. //{
  581. // query = "?" + string.Join("&", queryValues
  582. // .Where(keyPair => !string.IsNullOrEmpty(keyPair.Value))
  583. // .Select(keyPair => $"{keyPair.Key}={keyPair.Value}")
  584. // .ToArray());
  585. //}
  586. return $"bitcoin:{address}{query}";
  587. }
  588. }
  589. public class SwissQrCode : Payload
  590. {
  591. //Keep in mind, that the ECC level has to be set to "M" when generating a SwissQrCode!
  592. //SwissQrCode specification: https://www.paymentstandards.ch/dam/downloads/ig-qr-bill-de.pdf
  593. private readonly string br = "\r\n";
  594. private readonly string alternativeProcedure1, alternativeProcedure2;
  595. private readonly Iban iban;
  596. private readonly decimal? amount;
  597. private readonly Contact creditor, ultimateCreditor, debitor;
  598. private readonly Currency currency;
  599. private readonly DateTime? requestedDateOfPayment;
  600. private readonly Reference reference;
  601. /// <summary>
  602. /// Generates the payload for a SwissQrCode. (Don't forget to use ECC-Level M and set the Swiss flag icon to the final QR code.)
  603. /// </summary>
  604. /// <param name="iban">IBAN object</param>
  605. /// <param name="currency">Currency (either EUR or CHF)</param>
  606. /// <param name="creditor">Creditor (payee) information</param>
  607. /// <param name="reference">Reference information</param>
  608. /// <param name="debitor">Debitor (payer) information</param>
  609. /// <param name="amount">Amount</param>
  610. /// <param name="requestedDateOfPayment">Requested date of debitor's payment</param>
  611. /// <param name="ultimateCreditor">Ultimate creditor information (use only in consultation with your bank!)</param>
  612. /// <param name="alternativeProcedure1">Optional command for alternative processing mode - line 1</param>
  613. /// <param name="alternativeProcedure2">Optional command for alternative processing mode - line 2</param>
  614. public SwissQrCode(Iban iban, Currency currency, Contact creditor, Reference reference, Contact debitor = null, decimal? amount = null, DateTime? requestedDateOfPayment = null, Contact ultimateCreditor = null, string alternativeProcedure1 = null, string alternativeProcedure2 = null)
  615. {
  616. this.iban = iban;
  617. this.creditor = creditor;
  618. this.ultimateCreditor = ultimateCreditor;
  619. if (amount != null && amount.ToString().Length > 12)
  620. throw new SwissQrCodeException("Amount (including decimals) must be shorter than 13 places.");
  621. this.amount = amount;
  622. this.currency = currency;
  623. this.requestedDateOfPayment = requestedDateOfPayment;
  624. this.debitor = debitor;
  625. if (iban.IsQrIban && reference.RefType.Equals(Reference.ReferenceType.NON))
  626. throw new SwissQrCodeException("If QR-IBAN is used, you have to choose \"QRR\" or \"SCOR\" as reference type!");
  627. this.reference = reference;
  628. if (alternativeProcedure1 != null && alternativeProcedure1.Length > 100)
  629. throw new SwissQrCodeException("Alternative procedure information block 1 must be shorter than 101 chars.");
  630. this.alternativeProcedure1 = alternativeProcedure1;
  631. if (alternativeProcedure2 != null && alternativeProcedure2.Length > 100)
  632. throw new SwissQrCodeException("Alternative procedure information block 2 must be shorter than 101 chars.");
  633. this.alternativeProcedure2 = alternativeProcedure2;
  634. }
  635. public class Reference
  636. {
  637. private readonly ReferenceType referenceType;
  638. private readonly string reference, unstructuredMessage;
  639. private readonly ReferenceTextType? referenceTextType;
  640. /// <summary>
  641. /// Creates a reference object which must be passed to the SwissQrCode instance
  642. /// </summary>
  643. /// <param name="referenceType">Type of the reference (QRR, SCOR or NON)</param>
  644. /// <param name="reference">Reference text</param>
  645. /// <param name="referenceTextType">Type of the reference text (QR-reference or Creditor Reference)</param>
  646. /// <param name="unstructuredMessage">Unstructured message</param>
  647. public Reference(ReferenceType referenceType, string reference = null, ReferenceTextType? referenceTextType = null, string unstructuredMessage = null)
  648. {
  649. this.referenceType = referenceType;
  650. this.referenceTextType = referenceTextType;
  651. if (referenceType.Equals(ReferenceType.NON) && reference != null)
  652. throw new SwissQrCodeReferenceException("Reference is only allowed when referenceType not equals \"NON\"");
  653. if (!referenceType.Equals(ReferenceType.NON) && reference != null && referenceTextType == null)
  654. throw new SwissQrCodeReferenceException("You have to set an ReferenceTextType when using the reference text.");
  655. if (referenceTextType.Equals(ReferenceTextType.QrReference) && reference != null && (reference.Length > 27))
  656. throw new SwissQrCodeReferenceException("QR-references have to be shorter than 28 chars.");
  657. if (referenceTextType.Equals(ReferenceTextType.QrReference) && reference != null && !Regex.IsMatch(reference, @"^[0-9]+$"))
  658. throw new SwissQrCodeReferenceException("QR-reference must exist out of digits only.");
  659. if (referenceTextType.Equals(ReferenceTextType.QrReference) && reference != null && !ChecksumMod10(reference))
  660. throw new SwissQrCodeReferenceException("QR-references is invalid. Checksum error.");
  661. if (referenceTextType.Equals(ReferenceTextType.CreditorReferenceIso11649) && reference != null && (reference.Length > 25))
  662. throw new SwissQrCodeReferenceException("Creditor references (ISO 11649) have to be shorter than 26 chars.");
  663. this.reference = reference;
  664. if (unstructuredMessage != null && (unstructuredMessage.Length > 140))
  665. throw new SwissQrCodeReferenceException("The unstructured message must be shorter than 141 chars.");
  666. this.unstructuredMessage = unstructuredMessage;
  667. }
  668. public ReferenceType RefType
  669. {
  670. get { return referenceType; }
  671. }
  672. public string ReferenceText
  673. {
  674. get { return !string.IsNullOrEmpty(reference) ? reference.Replace("\n", "") : null; }
  675. }
  676. public string UnstructureMessage
  677. {
  678. get { return !string.IsNullOrEmpty(unstructuredMessage) ? unstructuredMessage.Replace("\n", "") : null; }
  679. }
  680. /// <summary>
  681. /// Reference type. When using a QR-IBAN you have to use either "QRR" or "SCOR"
  682. /// </summary>
  683. public enum ReferenceType
  684. {
  685. QRR,
  686. SCOR,
  687. NON
  688. }
  689. public enum ReferenceTextType
  690. {
  691. QrReference,
  692. CreditorReferenceIso11649
  693. }
  694. public class SwissQrCodeReferenceException : Exception
  695. {
  696. public SwissQrCodeReferenceException()
  697. {
  698. }
  699. public SwissQrCodeReferenceException(string message)
  700. : base(message)
  701. {
  702. }
  703. public SwissQrCodeReferenceException(string message, Exception inner)
  704. : base(message, inner)
  705. {
  706. }
  707. }
  708. }
  709. public class Iban
  710. {
  711. private string iban;
  712. private IbanType ibanType;
  713. /// <summary>
  714. /// IBAN object with type information
  715. /// </summary>
  716. /// <param name="iban">IBAN</param>
  717. /// <param name="ibanType">Type of IBAN (normal or QR-IBAN)</param>
  718. public Iban(string iban, IbanType ibanType)
  719. {
  720. if (!IsValidIban(iban))
  721. throw new SwissQrCodeIbanException("The IBAN entered isn't valid.");
  722. if (!iban.StartsWith("CH") && !iban.StartsWith("LI"))
  723. throw new SwissQrCodeIbanException("The IBAN must start with \"CH\" or \"LI\".");
  724. this.iban = iban;
  725. this.ibanType = ibanType;
  726. }
  727. public bool IsQrIban
  728. {
  729. get { return ibanType.Equals(IbanType.QrIban); }
  730. }
  731. public override string ToString()
  732. {
  733. return iban.Replace("\n", "").Replace(" ", "");
  734. }
  735. public enum IbanType
  736. {
  737. Iban,
  738. QrIban
  739. }
  740. public class SwissQrCodeIbanException : Exception
  741. {
  742. public SwissQrCodeIbanException()
  743. {
  744. }
  745. public SwissQrCodeIbanException(string message)
  746. : base(message)
  747. {
  748. }
  749. public SwissQrCodeIbanException(string message, Exception inner)
  750. : base(message, inner)
  751. {
  752. }
  753. }
  754. }
  755. public class Contact
  756. {
  757. private string br = "\r\n";
  758. private string name, street, houseNumber, zipCode, city, country;
  759. /// <summary>
  760. /// Contact type. Can be used for payee, ultimate payee, etc.
  761. /// </summary>
  762. /// <param name="name">Last name or company (optional first name)</param>
  763. /// <param name="zipCode">Zip-/Postcode</param>
  764. /// <param name="city">City name</param>
  765. /// <param name="country">Two-letter country code as defined in ISO 3166-1</param>
  766. /// <param name="street">Streetname without house number</param>
  767. /// <param name="houseNumber">House number</param>
  768. public Contact(string name, string zipCode, string city, string country, string street = null, string houseNumber = null)
  769. {
  770. //Pattern extracted from https://qr-validation.iso-payments.ch as explained in https://github.com/codebude/QRCoder/issues/97
  771. var charsetPattern = @"^([a-zA-Z0-9\.,;:'\ \-/\(\)?\*\[\]\{\}\\`´~ ]|[!""#%&<>÷=@_$£]|[àáâäçèéêëìíîïñòóôöùúûüýßÀÁÂÄÇÈÉÊËÌÍÎÏÒÓÔÖÙÚÛÜÑ])*$";
  772. if (string.IsNullOrEmpty(name))
  773. throw new SwissQrCodeContactException("Name must not be empty.");
  774. if (name.Length > 70)
  775. throw new SwissQrCodeContactException("Name must be shorter than 71 chars.");
  776. if (!Regex.IsMatch(name, charsetPattern))
  777. throw new SwissQrCodeContactException($"Name must match the following pattern as defined in pain.001: {charsetPattern}");
  778. this.name = name;
  779. if (!string.IsNullOrEmpty(street) && (street.Length > 70))
  780. throw new SwissQrCodeContactException("Street must be shorter than 71 chars.");
  781. if (!string.IsNullOrEmpty(street) && !Regex.IsMatch(street, charsetPattern))
  782. throw new SwissQrCodeContactException($"Street must match the following pattern as defined in pain.001: {charsetPattern}");
  783. this.street = street;
  784. if (!string.IsNullOrEmpty(houseNumber) && houseNumber.Length > 16)
  785. throw new SwissQrCodeContactException("House number must be shorter than 17 chars.");
  786. this.houseNumber = houseNumber;
  787. if (string.IsNullOrEmpty(zipCode))
  788. throw new SwissQrCodeContactException("Zip code must not be empty.");
  789. if (zipCode.Length > 16)
  790. throw new SwissQrCodeContactException("Zip code must be shorter than 17 chars.");
  791. if (!Regex.IsMatch(zipCode, charsetPattern))
  792. throw new SwissQrCodeContactException($"Zip code must match the following pattern as defined in pain.001: {charsetPattern}");
  793. this.zipCode = zipCode;
  794. if (string.IsNullOrEmpty(city))
  795. throw new SwissQrCodeContactException("City must not be empty.");
  796. if (city.Length > 35)
  797. throw new SwissQrCodeContactException("City name must be shorter than 36 chars.");
  798. if (!Regex.IsMatch(city, charsetPattern))
  799. throw new SwissQrCodeContactException($"City name must match the following pattern as defined in pain.001: {charsetPattern}");
  800. this.city = city;
  801. #if NET40 || NET461
  802. if (!CultureInfo.GetCultures(CultureTypes.SpecificCultures).Where(x => new RegionInfo(x.LCID).TwoLetterISORegionName.ToUpper() == country.ToUpper()).Any())
  803. throw new SwissQrCodeContactException("Country must be a valid \"two letter\" country code as defined by ISO 3166-1, but it isn't.");
  804. #else
  805. try { var cultureCheck = new CultureInfo(country.ToUpper()); }
  806. catch { throw new SwissQrCodeContactException("Country must be a valid \"two letter\" country code as defined by ISO 3166-1, but it isn't."); }
  807. #endif
  808. this.country = country;
  809. }
  810. public override string ToString()
  811. {
  812. string contactData = name.Replace("\n", "") + br; //Name
  813. contactData += (!string.IsNullOrEmpty(street) ? street.Replace("\n", "") : string.Empty) + br; //StrtNm
  814. contactData += (!string.IsNullOrEmpty(houseNumber) ? houseNumber.Replace("\n", "") : string.Empty) + br; //BldgNb
  815. contactData += zipCode.Replace("\n", "") + br; //PstCd
  816. contactData += city.Replace("\n", "") + br; //TwnNm
  817. contactData += country + br; //Ctry
  818. return contactData;
  819. }
  820. public class SwissQrCodeContactException : Exception
  821. {
  822. public SwissQrCodeContactException()
  823. {
  824. }
  825. public SwissQrCodeContactException(string message)
  826. : base(message)
  827. {
  828. }
  829. public SwissQrCodeContactException(string message, Exception inner)
  830. : base(message, inner)
  831. {
  832. }
  833. }
  834. }
  835. private static T[] Repeat<T>(T element, int count)
  836. {
  837. var array = new T[count];
  838. for (int i = 0; i < count; i++) array[i] = element;
  839. return array;
  840. }
  841. public override string ToString()
  842. {
  843. //Header "logical" element
  844. var SwissQrCodePayload = "SPC" + br; //QRType
  845. SwissQrCodePayload += "0100" + br; //Version
  846. SwissQrCodePayload += "1" + br; //Coding
  847. //CdtrInf "logical" element
  848. SwissQrCodePayload += iban.ToString() + br; //IBAN
  849. //Cdtr "logical" element
  850. SwissQrCodePayload += creditor.ToString();
  851. //UltmtCdtr "logical" element
  852. if (ultimateCreditor != null)
  853. SwissQrCodePayload += ultimateCreditor.ToString();
  854. else
  855. SwissQrCodePayload += string.Concat(Repeat(br, 6));
  856. //CcyAmtDate "logical" element
  857. SwissQrCodePayload += (amount != null ? $"{amount:0.00}" : string.Empty) + br; //Amt
  858. SwissQrCodePayload += currency + br; //Ccy
  859. SwissQrCodePayload += (requestedDateOfPayment != null ? ((DateTime)requestedDateOfPayment).ToString("yyyy-MM-dd") : string.Empty) + br; //ReqdExctnDt
  860. //UltmtDbtr "logical" element
  861. if (debitor != null)
  862. SwissQrCodePayload += debitor.ToString();
  863. else
  864. SwissQrCodePayload += string.Concat(Repeat(br, 6));
  865. //RmtInf "logical" element
  866. SwissQrCodePayload += reference.RefType.ToString() + br; //Tp
  867. SwissQrCodePayload += (!string.IsNullOrEmpty(reference.ReferenceText) ? reference.ReferenceText : string.Empty) + br; //Ref
  868. SwissQrCodePayload += (!string.IsNullOrEmpty(reference.UnstructureMessage) ? reference.UnstructureMessage : string.Empty) + br; //Ustrd
  869. //AltPmtInf "logical" element
  870. if (!string.IsNullOrEmpty(alternativeProcedure1))
  871. SwissQrCodePayload += alternativeProcedure1.Replace("\n", "") + br; //AltPmt
  872. if (!string.IsNullOrEmpty(alternativeProcedure2))
  873. SwissQrCodePayload += alternativeProcedure2.Replace("\n", "") + br; //AltPmt
  874. return SwissQrCodePayload;
  875. }
  876. /// <summary>
  877. /// ISO 4217 currency codes
  878. /// </summary>
  879. public enum Currency
  880. {
  881. CHF = 756,
  882. EUR = 978
  883. }
  884. public class SwissQrCodeException : Exception
  885. {
  886. public SwissQrCodeException()
  887. {
  888. }
  889. public SwissQrCodeException(string message)
  890. : base(message)
  891. {
  892. }
  893. public SwissQrCodeException(string message, Exception inner)
  894. : base(message, inner)
  895. {
  896. }
  897. }
  898. }
  899. public class Girocode : Payload
  900. {
  901. //Keep in mind, that the ECC level has to be set to "M" when generating a Girocode!
  902. //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/
  903. private string br = "\n";
  904. private readonly string iban, bic, name, purposeOfCreditTransfer, remittanceInformation, messageToGirocodeUser;
  905. private readonly decimal amount;
  906. private readonly GirocodeVersion version;
  907. private readonly GirocodeEncoding encoding;
  908. private readonly TypeOfRemittance typeOfRemittance;
  909. /// <summary>
  910. /// Generates the payload for a Girocode (QR-Code with credit transfer information).
  911. /// Attention: When using Girocode payload, QR code must be generated with ECC level M!
  912. /// </summary>
  913. /// <param name="iban">Account number of the Beneficiary. Only IBAN is allowed.</param>
  914. /// <param name="bic">BIC of the Beneficiary Bank.</param>
  915. /// <param name="name">Name of the Beneficiary.</param>
  916. /// <param name="amount">Amount of the Credit Transfer in Euro.
  917. /// (Amount must be more than 0.01 and less than 999999999.99)</param>
  918. /// <param name="remittanceInformation">Remittance Information (Purpose-/reference text). (optional)</param>
  919. /// <param name="typeOfRemittance">Type of remittance information. Either structured (e.g. ISO 11649 RF Creditor Reference) and max. 35 chars or unstructured and max. 140 chars.</param>
  920. /// <param name="purposeOfCreditTransfer">Purpose of the Credit Transfer (optional)</param>
  921. /// <param name="messageToGirocodeUser">Beneficiary to originator information. (optional)</param>
  922. /// <param name="version">Girocode version. Either 001 or 002. Default: 001.</param>
  923. /// <param name="encoding">Encoding of the Girocode payload. Default: ISO-8859-1</param>
  924. 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)
  925. {
  926. this.version = version;
  927. this.encoding = encoding;
  928. if (!IsValidIban(iban))
  929. throw new GirocodeException("The IBAN entered isn't valid.");
  930. this.iban = iban.Replace(" ", "").ToUpper();
  931. if (!IsValidBic(bic))
  932. throw new GirocodeException("The BIC entered isn't valid.");
  933. this.bic = bic.Replace(" ", "").ToUpper();
  934. if (name.Length > 70)
  935. throw new GirocodeException("(Payee-)Name must be shorter than 71 chars.");
  936. this.name = name;
  937. if (amount.ToString().Replace(",", ".").Contains(".") && amount.ToString().Replace(",", ".").Split('.')[1].TrimEnd('0').Length > 2)
  938. throw new GirocodeException("Amount must have less than 3 digits after decimal point.");
  939. if (amount < 0.01m || amount > 999999999.99m)
  940. throw new GirocodeException("Amount has to at least 0.01 and must be smaller or equal to 999999999.99.");
  941. this.amount = amount;
  942. if (purposeOfCreditTransfer.Length > 4)
  943. throw new GirocodeException("Purpose of credit transfer can only have 4 chars at maximum.");
  944. this.purposeOfCreditTransfer = purposeOfCreditTransfer;
  945. if (typeOfRemittance.Equals(TypeOfRemittance.Unstructured) && remittanceInformation.Length > 140)
  946. throw new GirocodeException("Unstructured reference texts have to shorter than 141 chars.");
  947. if (typeOfRemittance.Equals(TypeOfRemittance.Structured) && remittanceInformation.Length > 35)
  948. throw new GirocodeException("Structured reference texts have to shorter than 36 chars.");
  949. this.typeOfRemittance = typeOfRemittance;
  950. this.remittanceInformation = remittanceInformation;
  951. if (messageToGirocodeUser.Length > 70)
  952. throw new GirocodeException("Message to the Girocode-User reader texts have to shorter than 71 chars.");
  953. this.messageToGirocodeUser = messageToGirocodeUser;
  954. }
  955. public override string ToString()
  956. {
  957. var girocodePayload = "BCD" + br;
  958. girocodePayload += (version.Equals(GirocodeVersion.Version1) ? "001" : "002") + br;
  959. girocodePayload += (int)encoding + 1 + br;
  960. girocodePayload += "SCT" + br;
  961. girocodePayload += bic + br;
  962. girocodePayload += name + br;
  963. girocodePayload += iban + br;
  964. girocodePayload += $"EUR{amount:0.00}".Replace(",", ".") + br;
  965. girocodePayload += purposeOfCreditTransfer + br;
  966. girocodePayload += (typeOfRemittance.Equals(TypeOfRemittance.Structured)
  967. ? remittanceInformation
  968. : string.Empty) + br;
  969. girocodePayload += (typeOfRemittance.Equals(TypeOfRemittance.Unstructured)
  970. ? remittanceInformation
  971. : string.Empty) + br;
  972. girocodePayload += messageToGirocodeUser;
  973. return ConvertStringToEncoding(girocodePayload, encoding.ToString().Replace("_", "-"));
  974. }
  975. public enum GirocodeVersion
  976. {
  977. Version1,
  978. Version2
  979. }
  980. public enum TypeOfRemittance
  981. {
  982. Structured,
  983. Unstructured
  984. }
  985. public enum GirocodeEncoding
  986. {
  987. UTF_8,
  988. ISO_8859_1,
  989. ISO_8859_2,
  990. ISO_8859_4,
  991. ISO_8859_5,
  992. ISO_8859_7,
  993. ISO_8859_10,
  994. ISO_8859_15
  995. }
  996. public class GirocodeException : Exception
  997. {
  998. public GirocodeException()
  999. {
  1000. }
  1001. public GirocodeException(string message)
  1002. : base(message)
  1003. {
  1004. }
  1005. public GirocodeException(string message, Exception inner)
  1006. : base(message, inner)
  1007. {
  1008. }
  1009. }
  1010. }
  1011. public class BezahlCode : Payload
  1012. {
  1013. //BezahlCode specification: http://www.bezahlcode.de/wp-content/uploads/BezahlCode_TechDok.pdf
  1014. private readonly string name, iban, bic, account, bnc, sepaReference, reason, creditorId, mandateId, periodicTimeunit;
  1015. private readonly decimal amount;
  1016. private readonly int postingKey, periodicTimeunitRotation;
  1017. private readonly Currency currency;
  1018. private readonly AuthorityType authority;
  1019. private readonly DateTime executionDate, dateOfSignature, periodicFirstExecutionDate, periodicLastExecutionDate;
  1020. /// <summary>
  1021. /// Constructor for contact data
  1022. /// </summary>
  1023. /// <param name="authority">Type of the bank transfer</param>
  1024. /// <param name="name">Name of the receiver (Empfänger)</param>
  1025. /// <param name="account">Bank account (Kontonummer)</param>
  1026. /// <param name="bnc">Bank institute (Bankleitzahl)</param>
  1027. /// <param name="iban">IBAN</param>
  1028. /// <param name="bic">BIC</param>
  1029. /// <param name="reason">Reason (Verwendungszweck)</param>
  1030. 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)
  1031. {
  1032. }
  1033. /// <summary>
  1034. /// Constructor for non-SEPA payments
  1035. /// </summary>
  1036. /// <param name="authority">Type of the bank transfer</param>
  1037. /// <param name="name">Name of the receiver (Empfänger)</param>
  1038. /// <param name="account">Bank account (Kontonummer)</param>
  1039. /// <param name="bnc">Bank institute (Bankleitzahl)</param>
  1040. /// <param name="amount">Amount (Betrag)</param>
  1041. /// <param name="periodicTimeunit">Unit of intervall for payment ('M' = monthly, 'W' = weekly)</param>
  1042. /// <param name="periodicTimeunitRotation">Intervall for payment. This value is combined with 'periodicTimeunit'</param>
  1043. /// <param name="periodicFirstExecutionDate">Date of first periodic execution</param>
  1044. /// <param name="periodicLastExecutionDate">Date of last periodic execution</param>
  1045. /// <param name="reason">Reason (Verwendungszweck)</param>
  1046. /// <param name="postingKey">Transfer Key (Textschlüssel, z.B. Spendenzahlung = 69)</param>
  1047. /// <param name="currency">Currency (Währung)</param>
  1048. /// <param name="executionDate">Execution date (Ausführungsdatum)</param>
  1049. 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)
  1050. {
  1051. }
  1052. /// <summary>
  1053. /// Constructor for SEPA payments
  1054. /// </summary>
  1055. /// <param name="authority">Type of the bank transfer</param>
  1056. /// <param name="name">Name of the receiver (Empfänger)</param>
  1057. /// <param name="iban">IBAN</param>
  1058. /// <param name="bic">BIC</param>
  1059. /// <param name="amount">Amount (Betrag)</param>
  1060. /// <param name="periodicTimeunit">Unit of intervall for payment ('M' = monthly, 'W' = weekly)</param>
  1061. /// <param name="periodicTimeunitRotation">Intervall for payment. This value is combined with 'periodicTimeunit'</param>
  1062. /// <param name="periodicFirstExecutionDate">Date of first periodic execution</param>
  1063. /// <param name="periodicLastExecutionDate">Date of last periodic execution</param>
  1064. /// <param name="creditorId">Creditor id (Gläubiger ID)</param>
  1065. /// <param name="mandateId">Manadate id (Mandatsreferenz)</param>
  1066. /// <param name="dateOfSignature">Signature date (Erteilungsdatum des Mandats)</param>
  1067. /// <param name="reason">Reason (Verwendungszweck)</param>
  1068. /// <param name="sepaReference">SEPA reference (SEPA-Referenz)</param>
  1069. /// <param name="currency">Currency (Währung)</param>
  1070. /// <param name="executionDate">Execution date (Ausführungsdatum)</param>
  1071. 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)
  1072. {
  1073. }
  1074. /// <summary>
  1075. /// Generic constructor. Please use specific (non-SEPA or SEPA) constructor
  1076. /// </summary>
  1077. /// <param name="authority">Type of the bank transfer</param>
  1078. /// <param name="name">Name of the receiver (Empfänger)</param>
  1079. /// <param name="account">Bank account (Kontonummer)</param>
  1080. /// <param name="bnc">Bank institute (Bankleitzahl)</param>
  1081. /// <param name="iban">IBAN</param>
  1082. /// <param name="bic">BIC</param>
  1083. /// <param name="amount">Amount (Betrag)</param>
  1084. /// <param name="periodicTimeunit">Unit of intervall for payment ('M' = monthly, 'W' = weekly)</param>
  1085. /// <param name="periodicTimeunitRotation">Intervall for payment. This value is combined with 'periodicTimeunit'</param>
  1086. /// <param name="periodicFirstExecutionDate">Date of first periodic execution</param>
  1087. /// <param name="periodicLastExecutionDate">Date of last periodic execution</param>
  1088. /// <param name="creditorId">Creditor id (Gläubiger ID)</param>
  1089. /// <param name="mandateId">Manadate id (Mandatsreferenz)</param>
  1090. /// <param name="dateOfSignature">Signature date (Erteilungsdatum des Mandats)</param>
  1091. /// <param name="reason">Reason (Verwendungszweck)</param>
  1092. /// <param name="postingKey">Transfer Key (Textschlüssel, z.B. Spendenzahlung = 69)</param>
  1093. /// <param name="sepaReference">SEPA reference (SEPA-Referenz)</param>
  1094. /// <param name="currency">Currency (Währung)</param>
  1095. /// <param name="executionDate">Execution date (Ausführungsdatum)</param>
  1096. /// <param name="internalMode">Only used for internal state handdling</param>
  1097. 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)
  1098. {
  1099. //Loaded via "contact-constructor"
  1100. if (internalMode == 1)
  1101. {
  1102. if (!authority.Equals(AuthorityType.contact) && !authority.Equals(AuthorityType.contact_v2))
  1103. throw new BezahlCodeException("The constructor without an amount may only ne used with authority types 'contact' and 'contact_v2'.");
  1104. if (authority.Equals(AuthorityType.contact) && (string.IsNullOrEmpty(account) || string.IsNullOrEmpty(bnc)))
  1105. throw new BezahlCodeException("When using authority type 'contact' the parameters 'account' and 'bnc' must be set.");
  1106. if (!authority.Equals(AuthorityType.contact_v2))
  1107. {
  1108. var oldFilled = (!string.IsNullOrEmpty(account) && !string.IsNullOrEmpty(bnc));
  1109. var newFilled = (!string.IsNullOrEmpty(iban) && !string.IsNullOrEmpty(bic));
  1110. if ((!oldFilled && !newFilled) || (oldFilled && newFilled))
  1111. 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.");
  1112. }
  1113. }
  1114. else if (internalMode == 2)
  1115. {
  1116. if (!authority.Equals(AuthorityType.periodicsinglepayment) && !authority.Equals(AuthorityType.singledirectdebit) && !authority.Equals(AuthorityType.singlepayment))
  1117. 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.");
  1118. if (authority.Equals(AuthorityType.periodicsinglepayment) && (string.IsNullOrEmpty(periodicTimeunit) || periodicTimeunitRotation == 0))
  1119. throw new BezahlCodeException("When using 'periodicsinglepayment' as authority type, the parameters 'periodicTimeunit' and 'periodicTimeunitRotation' must be set.");
  1120. }
  1121. else if (internalMode == 3)
  1122. {
  1123. if (!authority.Equals(AuthorityType.periodicsinglepaymentsepa) && !authority.Equals(AuthorityType.singledirectdebitsepa) && !authority.Equals(AuthorityType.singlepaymentsepa))
  1124. 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.");
  1125. if (authority.Equals(AuthorityType.periodicsinglepaymentsepa) && (string.IsNullOrEmpty(periodicTimeunit) || periodicTimeunitRotation == 0))
  1126. throw new BezahlCodeException("When using 'periodicsinglepaymentsepa' as authority type, the parameters 'periodicTimeunit' and 'periodicTimeunitRotation' must be set.");
  1127. }
  1128. this.authority = authority;
  1129. if (name.Length > 70)
  1130. throw new BezahlCodeException("(Payee-)Name must be shorter than 71 chars.");
  1131. this.name = name;
  1132. if (reason.Length > 27)
  1133. throw new BezahlCodeException("Reasons texts have to be shorter than 28 chars.");
  1134. this.reason = reason;
  1135. var oldWayFilled = (!string.IsNullOrEmpty(account) && !string.IsNullOrEmpty(bnc));
  1136. var newWayFilled = (!string.IsNullOrEmpty(iban) && !string.IsNullOrEmpty(bic));
  1137. //Non-SEPA payment types
  1138. if (authority.Equals(AuthorityType.periodicsinglepayment) || authority.Equals(AuthorityType.singledirectdebit) || authority.Equals(AuthorityType.singlepayment) || authority.Equals(AuthorityType.contact) || (authority.Equals(AuthorityType.contact_v2) && oldWayFilled))
  1139. {
  1140. if (!Regex.IsMatch(account.Replace(" ", ""), @"^[0-9]{1,9}$"))
  1141. throw new BezahlCodeException("The account entered isn't valid.");
  1142. this.account = account.Replace(" ", "").ToUpper();
  1143. if (!Regex.IsMatch(bnc.Replace(" ", ""), @"^[0-9]{1,9}$"))
  1144. throw new BezahlCodeException("The bnc entered isn't valid.");
  1145. this.bnc = bnc.Replace(" ", "").ToUpper();
  1146. if (!authority.Equals(AuthorityType.contact) && !authority.Equals(AuthorityType.contact_v2))
  1147. {
  1148. if (postingKey < 0 || postingKey >= 100)
  1149. throw new BezahlCodeException("PostingKey must be within 0 and 99.");
  1150. this.postingKey = postingKey;
  1151. }
  1152. }
  1153. //SEPA payment types
  1154. if (authority.Equals(AuthorityType.periodicsinglepaymentsepa) || authority.Equals(AuthorityType.singledirectdebitsepa) || authority.Equals(AuthorityType.singlepaymentsepa) || (authority.Equals(AuthorityType.contact_v2) && newWayFilled))
  1155. {
  1156. if (!IsValidIban(iban))
  1157. throw new BezahlCodeException("The IBAN entered isn't valid.");
  1158. this.iban = iban.Replace(" ", "").ToUpper();
  1159. if (!IsValidBic(bic))
  1160. throw new BezahlCodeException("The BIC entered isn't valid.");
  1161. this.bic = bic.Replace(" ", "").ToUpper();
  1162. if (!authority.Equals(AuthorityType.contact_v2))
  1163. {
  1164. if (sepaReference.Length > 35)
  1165. throw new BezahlCodeException("SEPA reference texts have to be shorter than 36 chars.");
  1166. this.sepaReference = sepaReference;
  1167. 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}$"))
  1168. throw new BezahlCodeException("The creditorId entered isn't valid.");
  1169. this.creditorId = creditorId;
  1170. if (!string.IsNullOrEmpty(mandateId) && !Regex.IsMatch(mandateId.Replace(" ", ""), @"^([A-Za-z0-9]|[\+|\?|/|\-|:|\(|\)|\.|,|']){1,35}$"))
  1171. throw new BezahlCodeException("The mandateId entered isn't valid.");
  1172. this.mandateId = mandateId;
  1173. if (dateOfSignature != null)
  1174. this.dateOfSignature = (DateTime)dateOfSignature;
  1175. }
  1176. }
  1177. //Checks for all payment types
  1178. if (!authority.Equals(AuthorityType.contact) && !authority.Equals(AuthorityType.contact_v2))
  1179. {
  1180. if (amount.ToString().Replace(",", ".").Contains(".") && amount.ToString().Replace(",", ".").Split('.')[1].TrimEnd('0').Length > 2)
  1181. throw new BezahlCodeException("Amount must have less than 3 digits after decimal point.");
  1182. if (amount < 0.01m || amount > 999999999.99m)
  1183. throw new BezahlCodeException("Amount has to at least 0.01 and must be smaller or equal to 999999999.99.");
  1184. this.amount = amount;
  1185. this.currency = currency;
  1186. if (executionDate == null)
  1187. this.executionDate = DateTime.Now;
  1188. else
  1189. {
  1190. if (DateTime.Today.Ticks > executionDate.Value.Ticks)
  1191. throw new BezahlCodeException("Execution date must be today or in future.");
  1192. this.executionDate = (DateTime)executionDate;
  1193. }
  1194. if (authority.Equals(AuthorityType.periodicsinglepayment) || authority.Equals(AuthorityType.periodicsinglepaymentsepa))
  1195. {
  1196. if (periodicTimeunit.ToUpper() != "M" && periodicTimeunit.ToUpper() != "W")
  1197. throw new BezahlCodeException("The periodicTimeunit must be either 'M' (monthly) or 'W' (weekly).");
  1198. this.periodicTimeunit = periodicTimeunit;
  1199. if (periodicTimeunitRotation < 1 || periodicTimeunitRotation > 52)
  1200. throw new BezahlCodeException("The periodicTimeunitRotation must be 1 or greater. (It means repeat the payment every 'periodicTimeunitRotation' weeks/months.");
  1201. this.periodicTimeunitRotation = periodicTimeunitRotation;
  1202. if (periodicFirstExecutionDate != null)
  1203. this.periodicFirstExecutionDate = (DateTime)periodicFirstExecutionDate;
  1204. if (periodicLastExecutionDate != null)
  1205. this.periodicLastExecutionDate = (DateTime)periodicLastExecutionDate;
  1206. }
  1207. }
  1208. }
  1209. public override string ToString()
  1210. {
  1211. var bezahlCodePayload = $"bank://{authority}?";
  1212. bezahlCodePayload += $"name={Uri.EscapeDataString(name)}&";
  1213. if (!authority.Equals(AuthorityType.contact) && !authority.Equals(AuthorityType.contact_v2))
  1214. {
  1215. //Handle what is same for all payments
  1216. if (authority.Equals(AuthorityType.periodicsinglepayment) || authority.Equals(AuthorityType.singledirectdebit) || authority.Equals(AuthorityType.singlepayment))
  1217. {
  1218. bezahlCodePayload += $"account={account}&";
  1219. bezahlCodePayload += $"bnc={bnc}&";
  1220. if (postingKey > 0)
  1221. bezahlCodePayload += $"postingkey={postingKey}&";
  1222. }
  1223. else
  1224. {
  1225. bezahlCodePayload += $"iban={iban}&";
  1226. bezahlCodePayload += $"bic={bic}&";
  1227. if (!string.IsNullOrEmpty(sepaReference))
  1228. bezahlCodePayload += $"separeference={ Uri.EscapeDataString(sepaReference)}&";
  1229. if (authority.Equals(AuthorityType.singledirectdebitsepa))
  1230. {
  1231. if (!string.IsNullOrEmpty(creditorId))
  1232. bezahlCodePayload += $"creditorid={ Uri.EscapeDataString(creditorId)}&";
  1233. if (!string.IsNullOrEmpty(mandateId))
  1234. bezahlCodePayload += $"mandateid={ Uri.EscapeDataString(mandateId)}&";
  1235. if (dateOfSignature != null)
  1236. bezahlCodePayload += $"dateofsignature={dateOfSignature.ToString("ddMMyyyy")}&";
  1237. }
  1238. }
  1239. bezahlCodePayload += $"amount={amount:0.00}&".Replace(".", ",");
  1240. if (!string.IsNullOrEmpty(reason))
  1241. bezahlCodePayload += $"reason={ Uri.EscapeDataString(reason)}&";
  1242. bezahlCodePayload += $"currency={currency}&";
  1243. bezahlCodePayload += $"executiondate={executionDate.ToString("ddMMyyyy")}&";
  1244. if (authority.Equals(AuthorityType.periodicsinglepayment) || authority.Equals(AuthorityType.periodicsinglepaymentsepa))
  1245. {
  1246. bezahlCodePayload += $"periodictimeunit={periodicTimeunit}&";
  1247. bezahlCodePayload += $"periodictimeunitrotation={periodicTimeunitRotation}&";
  1248. if (periodicFirstExecutionDate != null)
  1249. bezahlCodePayload += $"periodicfirstexecutiondate={periodicFirstExecutionDate.ToString("ddMMyyyy")}&";
  1250. if (periodicLastExecutionDate != null)
  1251. bezahlCodePayload += $"periodiclastexecutiondate={periodicLastExecutionDate.ToString("ddMMyyyy")}&";
  1252. }
  1253. }
  1254. else
  1255. {
  1256. //Handle what is same for all contacts
  1257. if (authority.Equals(AuthorityType.contact))
  1258. {
  1259. bezahlCodePayload += $"account={account}&";
  1260. bezahlCodePayload += $"bnc={bnc}&";
  1261. }
  1262. else if (authority.Equals(AuthorityType.contact_v2))
  1263. {
  1264. if (!string.IsNullOrEmpty(account) && !string.IsNullOrEmpty(bnc))
  1265. {
  1266. bezahlCodePayload += $"account={account}&";
  1267. bezahlCodePayload += $"bnc={bnc}&";
  1268. }
  1269. else
  1270. {
  1271. bezahlCodePayload += $"iban={iban}&";
  1272. bezahlCodePayload += $"bic={bic}&";
  1273. }
  1274. }
  1275. if (!string.IsNullOrEmpty(reason))
  1276. bezahlCodePayload += $"reason={ Uri.EscapeDataString(reason)}&";
  1277. }
  1278. return bezahlCodePayload.Trim('&');
  1279. }
  1280. /// <summary>
  1281. /// ISO 4217 currency codes
  1282. /// </summary>
  1283. public enum Currency
  1284. {
  1285. AED = 784,
  1286. AFN = 971,
  1287. ALL = 008,
  1288. AMD = 051,
  1289. ANG = 532,
  1290. AOA = 973,
  1291. ARS = 032,
  1292. AUD = 036,
  1293. AWG = 533,
  1294. AZN = 944,
  1295. BAM = 977,
  1296. BBD = 052,
  1297. BDT = 050,
  1298. BGN = 975,
  1299. BHD = 048,
  1300. BIF = 108,
  1301. BMD = 060,
  1302. BND = 096,
  1303. BOB = 068,
  1304. BOV = 984,
  1305. BRL = 986,
  1306. BSD = 044,
  1307. BTN = 064,
  1308. BWP = 072,
  1309. BYR = 974,
  1310. BZD = 084,
  1311. CAD = 124,
  1312. CDF = 976,
  1313. CHE = 947,
  1314. CHF = 756,
  1315. CHW = 948,
  1316. CLF = 990,
  1317. CLP = 152,
  1318. CNY = 156,
  1319. COP = 170,
  1320. COU = 970,
  1321. CRC = 188,
  1322. CUC = 931,
  1323. CUP = 192,
  1324. CVE = 132,
  1325. CZK = 203,
  1326. DJF = 262,
  1327. DKK = 208,
  1328. DOP = 214,
  1329. DZD = 012,
  1330. EGP = 818,
  1331. ERN = 232,
  1332. ETB = 230,
  1333. EUR = 978,
  1334. FJD = 242,
  1335. FKP = 238,
  1336. GBP = 826,
  1337. GEL = 981,
  1338. GHS = 936,
  1339. GIP = 292,
  1340. GMD = 270,
  1341. GNF = 324,
  1342. GTQ = 320,
  1343. GYD = 328,
  1344. HKD = 344,
  1345. HNL = 340,
  1346. HRK = 191,
  1347. HTG = 332,
  1348. HUF = 348,
  1349. IDR = 360,
  1350. ILS = 376,
  1351. INR = 356,
  1352. IQD = 368,
  1353. IRR = 364,
  1354. ISK = 352,
  1355. JMD = 388,
  1356. JOD = 400,
  1357. JPY = 392,
  1358. KES = 404,
  1359. KGS = 417,
  1360. KHR = 116,
  1361. KMF = 174,
  1362. KPW = 408,
  1363. KRW = 410,
  1364. KWD = 414,
  1365. KYD = 136,
  1366. KZT = 398,
  1367. LAK = 418,
  1368. LBP = 422,
  1369. LKR = 144,
  1370. LRD = 430,
  1371. LSL = 426,
  1372. LYD = 434,
  1373. MAD = 504,
  1374. MDL = 498,
  1375. MGA = 969,
  1376. MKD = 807,
  1377. MMK = 104,
  1378. MNT = 496,
  1379. MOP = 446,
  1380. MRO = 478,
  1381. MUR = 480,
  1382. MVR = 462,
  1383. MWK = 454,
  1384. MXN = 484,
  1385. MXV = 979,
  1386. MYR = 458,
  1387. MZN = 943,
  1388. NAD = 516,
  1389. NGN = 566,
  1390. NIO = 558,
  1391. NOK = 578,
  1392. NPR = 524,
  1393. NZD = 554,
  1394. OMR = 512,
  1395. PAB = 590,
  1396. PEN = 604,
  1397. PGK = 598,
  1398. PHP = 608,
  1399. PKR = 586,
  1400. PLN = 985,
  1401. PYG = 600,
  1402. QAR = 634,
  1403. RON = 946,
  1404. RSD = 941,
  1405. RUB = 643,
  1406. RWF = 646,
  1407. SAR = 682,
  1408. SBD = 090,
  1409. SCR = 690,
  1410. SDG = 938,
  1411. SEK = 752,
  1412. SGD = 702,
  1413. SHP = 654,
  1414. SLL = 694,
  1415. SOS = 706,
  1416. SRD = 968,
  1417. SSP = 728,
  1418. STD = 678,
  1419. SVC = 222,
  1420. SYP = 760,
  1421. SZL = 748,
  1422. THB = 764,
  1423. TJS = 972,
  1424. TMT = 934,
  1425. TND = 788,
  1426. TOP = 776,
  1427. TRY = 949,
  1428. TTD = 780,
  1429. TWD = 901,
  1430. TZS = 834,
  1431. UAH = 980,
  1432. UGX = 800,
  1433. USD = 840,
  1434. USN = 997,
  1435. UYI = 940,
  1436. UYU = 858,
  1437. UZS = 860,
  1438. VEF = 937,
  1439. VND = 704,
  1440. VUV = 548,
  1441. WST = 882,
  1442. XAF = 950,
  1443. XAG = 961,
  1444. XAU = 959,
  1445. XBA = 955,
  1446. XBB = 956,
  1447. XBC = 957,
  1448. XBD = 958,
  1449. XCD = 951,
  1450. XDR = 960,
  1451. XOF = 952,
  1452. XPD = 964,
  1453. XPF = 953,
  1454. XPT = 962,
  1455. XSU = 994,
  1456. XTS = 963,
  1457. XUA = 965,
  1458. XXX = 999,
  1459. YER = 886,
  1460. ZAR = 710,
  1461. ZMW = 967,
  1462. ZWL = 932
  1463. }
  1464. /// <summary>
  1465. /// Operation modes of the BezahlCode
  1466. /// </summary>
  1467. public enum AuthorityType
  1468. {
  1469. /// <summary>
  1470. /// Single payment (Überweisung)
  1471. /// </summary>
  1472. [Obsolete]
  1473. singlepayment,
  1474. /// <summary>
  1475. /// Single SEPA payment (SEPA-Überweisung)
  1476. /// </summary>
  1477. singlepaymentsepa,
  1478. /// <summary>
  1479. /// Single debit (Lastschrift)
  1480. /// </summary>
  1481. [Obsolete]
  1482. singledirectdebit,
  1483. /// <summary>
  1484. /// Single SEPA debit (SEPA-Lastschrift)
  1485. /// </summary>
  1486. singledirectdebitsepa,
  1487. /// <summary>
  1488. /// Periodic payment (Dauerauftrag)
  1489. /// </summary>
  1490. [Obsolete]
  1491. periodicsinglepayment,
  1492. /// <summary>
  1493. /// Periodic SEPA payment (SEPA-Dauerauftrag)
  1494. /// </summary>
  1495. periodicsinglepaymentsepa,
  1496. /// <summary>
  1497. /// Contact data
  1498. /// </summary>
  1499. contact,
  1500. /// <summary>
  1501. /// Contact data V2
  1502. /// </summary>
  1503. contact_v2
  1504. }
  1505. public class BezahlCodeException : Exception
  1506. {
  1507. public BezahlCodeException()
  1508. {
  1509. }
  1510. public BezahlCodeException(string message)
  1511. : base(message)
  1512. {
  1513. }
  1514. public BezahlCodeException(string message, Exception inner)
  1515. : base(message, inner)
  1516. {
  1517. }
  1518. }
  1519. }
  1520. public class CalendarEvent : Payload
  1521. {
  1522. private readonly string subject, description, location, start, end;
  1523. private readonly EventEncoding encoding;
  1524. /// <summary>
  1525. /// Generates a calender entry/event payload.
  1526. /// </summary>
  1527. /// <param name="subject">Subject/title of the calender event</param>
  1528. /// <param name="description">Description of the event</param>
  1529. /// <param name="location">Location (lat:long or address) of the event</param>
  1530. /// <param name="start">Start time of the event</param>
  1531. /// <param name="end">End time of the event</param>
  1532. /// <param name="allDayEvent">Is it a full day event?</param>
  1533. /// <param name="encoding">Type of encoding (universal or iCal)</param>
  1534. public CalendarEvent(string subject, string description, string location, DateTime start, DateTime end, bool allDayEvent, EventEncoding encoding = EventEncoding.Universal)
  1535. {
  1536. this.subject = subject;
  1537. this.description = description;
  1538. this.location = location;
  1539. this.encoding = encoding;
  1540. string dtFormat = allDayEvent ? "yyyyMMdd" : "yyyyMMddTHHmmss";
  1541. this.start = start.ToString(dtFormat);
  1542. this.end = end.ToString(dtFormat);
  1543. }
  1544. public override string ToString()
  1545. {
  1546. var vEvent = $"BEGIN:VEVENT{Environment.NewLine}";
  1547. vEvent += $"SUMMARY:{this.subject}{Environment.NewLine}";
  1548. vEvent += !string.IsNullOrEmpty(this.description) ? $"DESCRIPTION:{this.description}{Environment.NewLine}" : "";
  1549. vEvent += !string.IsNullOrEmpty(this.location) ? $"LOCATION:{this.location}{Environment.NewLine}" : "";
  1550. vEvent += $"DTSTART:{this.start}{Environment.NewLine}";
  1551. vEvent += $"DTEND:{this.end}{Environment.NewLine}";
  1552. vEvent += "END:VEVENT";
  1553. if (this.encoding.Equals(EventEncoding.iCalComplete))
  1554. vEvent = $@"BEGIN:VCALENDAR{Environment.NewLine}VERSION:2.0{Environment.NewLine}{vEvent}{Environment.NewLine}END:VCALENDAR";
  1555. return vEvent;
  1556. }
  1557. public enum EventEncoding
  1558. {
  1559. iCalComplete,
  1560. Universal
  1561. }
  1562. }
  1563. public class OneTimePassword : Payload
  1564. {
  1565. //https://github.com/google/google-authenticator/wiki/Key-Uri-Format
  1566. public OneTimePasswordAuthType Type { get; set; } = OneTimePasswordAuthType.TOTP;
  1567. public string Secret { get; set; }
  1568. public OneTimePasswordAuthAlgorithm AuthAlgorithm { get; set; } = OneTimePasswordAuthAlgorithm.SHA1;
  1569. [Obsolete("This property is obsolete, use " + nameof(AuthAlgorithm) + " instead", false)]
  1570. public OoneTimePasswordAuthAlgorithm Algorithm
  1571. {
  1572. get { return (OoneTimePasswordAuthAlgorithm)Enum.Parse(typeof(OoneTimePasswordAuthAlgorithm), AuthAlgorithm.ToString()); }
  1573. set { AuthAlgorithm = (OneTimePasswordAuthAlgorithm)Enum.Parse(typeof(OneTimePasswordAuthAlgorithm), value.ToString()); }
  1574. }
  1575. public string Issuer { get; set; }
  1576. public string Label { get; set; }
  1577. public int Digits { get; set; } = 6;
  1578. public int? Counter { get; set; } = null;
  1579. public int? Period { get; set; } = 30;
  1580. public enum OneTimePasswordAuthType
  1581. {
  1582. TOTP,
  1583. HOTP,
  1584. }
  1585. public enum OneTimePasswordAuthAlgorithm
  1586. {
  1587. SHA1,
  1588. SHA256,
  1589. SHA512,
  1590. }
  1591. [Obsolete("This enum is obsolete, use " + nameof(OneTimePasswordAuthAlgorithm) + " instead", false)]
  1592. public enum OoneTimePasswordAuthAlgorithm
  1593. {
  1594. SHA1,
  1595. SHA256,
  1596. SHA512,
  1597. }
  1598. public override string ToString()
  1599. {
  1600. switch (Type)
  1601. {
  1602. case OneTimePasswordAuthType.TOTP:
  1603. return TimeToString();
  1604. case OneTimePasswordAuthType.HOTP:
  1605. return HMACToString();
  1606. default:
  1607. throw new ArgumentOutOfRangeException();
  1608. }
  1609. }
  1610. // Note: Issuer:Label must only contain 1 : if either of the Issuer or the Label has a : then it is invalid.
  1611. // Defaults are 6 digits and 30 for Period
  1612. private string HMACToString()
  1613. {
  1614. var sb = new StringBuilder("otpauth://hotp/");
  1615. ProcessCommonFields(sb);
  1616. var actualCounter = Counter ?? 1;
  1617. sb.Append("&counter=" + actualCounter);
  1618. return sb.ToString();
  1619. }
  1620. private string TimeToString()
  1621. {
  1622. if (Period == null)
  1623. {
  1624. throw new Exception("Period must be set when using OneTimePasswordAuthType.TOTP");
  1625. }
  1626. var sb = new StringBuilder("otpauth://totp/");
  1627. ProcessCommonFields(sb);
  1628. if (Period != 30)
  1629. {
  1630. sb.Append("&period=" + Period);
  1631. }
  1632. return sb.ToString();
  1633. }
  1634. private void ProcessCommonFields(StringBuilder sb)
  1635. {
  1636. if (String40Methods.IsNullOrWhiteSpace(Secret))
  1637. {
  1638. throw new Exception("Secret must be a filled out base32 encoded string");
  1639. }
  1640. string strippedSecret = Secret.Replace(" ", "");
  1641. string escapedIssuer = null;
  1642. string escapedLabel = null;
  1643. if (!String40Methods.IsNullOrWhiteSpace(Issuer))
  1644. {
  1645. if (Issuer.Contains(":"))
  1646. {
  1647. throw new Exception("Issuer must not have a ':'");
  1648. }
  1649. escapedIssuer = Uri.EscapeUriString(Issuer);
  1650. }
  1651. if (!String40Methods.IsNullOrWhiteSpace(Label))
  1652. {
  1653. if (Label.Contains(":"))
  1654. {
  1655. throw new Exception("Label must not have a ':'");
  1656. }
  1657. escapedLabel = Uri.EscapeUriString(Label);
  1658. }
  1659. if (escapedLabel != null)
  1660. {
  1661. if (escapedIssuer != null)
  1662. {
  1663. escapedLabel = escapedIssuer + ":" + escapedLabel;
  1664. }
  1665. }
  1666. else if (escapedIssuer != null)
  1667. {
  1668. escapedLabel = escapedIssuer;
  1669. }
  1670. if (escapedLabel != null)
  1671. {
  1672. sb.Append(escapedLabel);
  1673. }
  1674. sb.Append("?secret=" + strippedSecret);
  1675. if (escapedIssuer != null)
  1676. {
  1677. sb.Append("&issuer=" + escapedIssuer);
  1678. }
  1679. if (Digits != 6)
  1680. {
  1681. sb.Append("&digits=" + Digits);
  1682. }
  1683. }
  1684. }
  1685. public class ShadowSocksConfig : Payload
  1686. {
  1687. private readonly string hostname, password, tag, methodStr;
  1688. private readonly Method method;
  1689. private readonly int port;
  1690. private Dictionary<string, string> encryptionTexts = new Dictionary<string, string>() {
  1691. { "Aes128Cfb", "aes-128-cfb" },
  1692. { "Aes128Cfb1", "aes-128-cfb1" },
  1693. { "Aes128Cfb8", "aes-128-cfb8" },
  1694. { "Aes128Ctr", "aes-128-ctr" },
  1695. { "Aes128Ofb", "aes-128-ofb" },
  1696. { "Aes192Cfb", "aes-192-cfb" },
  1697. { "Aes192Cfb1", "aes-192-cfb1" },
  1698. { "Aes192Cfb8", "aes-192-cfb8" },
  1699. { "Aes192Ctr", "aes-192-ctr" },
  1700. { "Aes192Ofb", "aes-192-ofb" },
  1701. { "Aes256Cb", "aes-256-cfb" },
  1702. { "Aes256Cfb1", "aes-256-cfb1" },
  1703. { "Aes256Cfb8", "aes-256-cfb8" },
  1704. { "Aes256Ctr", "aes-256-ctr" },
  1705. { "Aes256Ofb", "aes-256-ofb" },
  1706. { "BfCfb", "bf-cfb" },
  1707. { "Camellia128Cfb", "camellia-128-cfb" },
  1708. { "Camellia192Cfb", "camellia-192-cfb" },
  1709. { "Camellia256Cfb", "camellia-256-cfb" },
  1710. { "Cast5Cfb", "cast5-cfb" },
  1711. { "Chacha20", "chacha20" },
  1712. { "DesCfb", "des-cfb" },
  1713. { "IdeaCfb", "idea-cfb" },
  1714. { "Rc2Cfb", "rc2-cfb" },
  1715. { "Rc4", "rc4" },
  1716. { "Rc4Md5", "rc4-md5" },
  1717. { "Salsa20", "salsa20" },
  1718. { "Salsa20Ctr", "salsa20-ctr" },
  1719. { "SeedCfb", "seed-cfb" },
  1720. { "Table", "table" }
  1721. };
  1722. /// <summary>
  1723. /// Generates a ShadowSocks proxy config payload.
  1724. /// </summary>
  1725. /// <param name="hostname">Hostname of the ShadowSocks proxy</param>
  1726. /// <param name="port">Port of the ShadowSocks proxy</param>
  1727. /// <param name="password">Password of the SS proxy</param>
  1728. /// <param name="method">Encryption type</param>
  1729. /// <param name="tag">Optional tag line</param>
  1730. public ShadowSocksConfig(string hostname, int port, string password, Method method, string tag = null)
  1731. {
  1732. this.hostname = hostname;
  1733. if (port < 1 || port > 65535)
  1734. throw new ShadowSocksConfigException("Value of 'port' must be within 0 and 65535.");
  1735. this.port = port;
  1736. this.password = password;
  1737. this.method = method;
  1738. this.methodStr = encryptionTexts[method.ToString()];
  1739. this.tag = tag;
  1740. }
  1741. public override string ToString()
  1742. {
  1743. var connectionString = $"{methodStr}:{password}@{hostname}:{port}";
  1744. var connectionStringEncoded = Convert.ToBase64String(Encoding.UTF8.GetBytes(connectionString));
  1745. return $"ss://{connectionStringEncoded}{(!string.IsNullOrEmpty(tag) ? $"#{tag}" : string.Empty)}";
  1746. }
  1747. public enum Method
  1748. {
  1749. Aes128Cfb,
  1750. Aes128Cfb1,
  1751. Aes128Cfb8,
  1752. Aes128Ctr,
  1753. Aes128Ofb,
  1754. Aes192Cfb,
  1755. Aes192Cfb1,
  1756. Aes192Cfb8,
  1757. Aes192Ctr,
  1758. Aes192Ofb,
  1759. Aes256Cb,
  1760. Aes256Cfb1,
  1761. Aes256Cfb8,
  1762. Aes256Ctr,
  1763. Aes256Ofb,
  1764. BfCfb,
  1765. Camellia128Cfb,
  1766. Camellia192Cfb,
  1767. Camellia256Cfb,
  1768. Cast5Cfb,
  1769. Chacha20,
  1770. DesCfb,
  1771. IdeaCfb,
  1772. Rc2Cfb,
  1773. Rc4,
  1774. Rc4Md5,
  1775. Salsa20,
  1776. Salsa20Ctr,
  1777. SeedCfb,
  1778. Table
  1779. }
  1780. public class ShadowSocksConfigException : Exception
  1781. {
  1782. public ShadowSocksConfigException()
  1783. {
  1784. }
  1785. public ShadowSocksConfigException(string message)
  1786. : base(message)
  1787. {
  1788. }
  1789. public ShadowSocksConfigException(string message, Exception inner)
  1790. : base(message, inner)
  1791. {
  1792. }
  1793. }
  1794. }
  1795. public class MoneroTransaction : Payload
  1796. {
  1797. private readonly string address, txPaymentId, recipientName, txDescription;
  1798. private readonly float? txAmount;
  1799. /// <summary>
  1800. /// Creates a monero transaction payload
  1801. /// </summary>
  1802. /// <param name="address">Receiver's monero address</param>
  1803. /// <param name="txAmount">Amount to transfer</param>
  1804. /// <param name="txPaymentId">Payment id</param>
  1805. /// <param name="recipientName">Receipient's name</param>
  1806. /// <param name="txDescription">Reference text / payment description</param>
  1807. public MoneroTransaction(string address, float? txAmount = null, string txPaymentId = null, string recipientName = null, string txDescription = null)
  1808. {
  1809. if (string.IsNullOrEmpty(address))
  1810. throw new MoneroTransactionException("The address is mandatory and has to be set.");
  1811. this.address = address;
  1812. if (txAmount != null && txAmount <= 0)
  1813. throw new MoneroTransactionException("Value of 'txAmount' must be greater than 0.");
  1814. this.txAmount = txAmount;
  1815. this.txPaymentId = txPaymentId;
  1816. this.recipientName = recipientName;
  1817. this.txDescription = txDescription;
  1818. }
  1819. public override string ToString()
  1820. {
  1821. var moneroUri = $"monero://{address}{(!string.IsNullOrEmpty(txPaymentId) || !string.IsNullOrEmpty(recipientName) || !string.IsNullOrEmpty(txDescription) || txAmount != null ? "?" : string.Empty)}";
  1822. moneroUri += (!string.IsNullOrEmpty(txPaymentId) ? $"tx_payment_id={Uri.EscapeDataString(txPaymentId)}&" : string.Empty);
  1823. moneroUri += (!string.IsNullOrEmpty(recipientName) ? $"recipient_name={Uri.EscapeDataString(recipientName)}&" : string.Empty);
  1824. moneroUri += (txAmount != null ? $"tx_amount={txAmount.ToString().Replace(",", ".")}&" : string.Empty);
  1825. moneroUri += (!string.IsNullOrEmpty(txDescription) ? $"tx_description={Uri.EscapeDataString(txDescription)}" : string.Empty);
  1826. return moneroUri.TrimEnd('&');
  1827. }
  1828. public class MoneroTransactionException : Exception
  1829. {
  1830. public MoneroTransactionException()
  1831. {
  1832. }
  1833. public MoneroTransactionException(string message)
  1834. : base(message)
  1835. {
  1836. }
  1837. public MoneroTransactionException(string message, Exception inner)
  1838. : base(message, inner)
  1839. {
  1840. }
  1841. }
  1842. }
  1843. public class SlovenianUpnQr : Payload
  1844. {
  1845. //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!
  1846. //SlovenianUpnQr specification: https://www.upn-qr.si/uploads/files/NavodilaZaProgramerjeUPNQR.pdf
  1847. private string _payerName = "";
  1848. private string _payerAddress = "";
  1849. private string _payerPlace = "";
  1850. private string _amount = "";
  1851. private string _code = "";
  1852. private string _purpose = "";
  1853. private string _deadLine = "";
  1854. private string _recipientIban = "";
  1855. private string _recipientName = "";
  1856. private string _recipientAddress = "";
  1857. private string _recipientPlace = "";
  1858. private string _recipientSiModel = "";
  1859. private string _recipientSiReference = "";
  1860. public override int Version { get { return 15; } }
  1861. public override QRCodeGenerator.ECCLevel EccLevel { get { return QRCodeGenerator.ECCLevel.M; } }
  1862. public override QRCodeGenerator.EciMode EciMode { get { return QRCodeGenerator.EciMode.Iso8859_2; } }
  1863. private string LimitLength(string value, int maxLength)
  1864. {
  1865. return (value.Length <= maxLength) ? value : value.Substring(0, maxLength);
  1866. }
  1867. 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") :
  1868. this(payerName, payerAddress, payerPlace, recipientName, recipientAddress, recipientPlace, recipientIban, description, amount, null, recipientSiModel, recipientSiReference, code)
  1869. { }
  1870. 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")
  1871. {
  1872. _payerName = LimitLength(payerName.Trim(), 33);
  1873. _payerAddress = LimitLength(payerAddress.Trim(), 33);
  1874. _payerPlace = LimitLength(payerPlace.Trim(), 33);
  1875. _amount = FormatAmount(amount);
  1876. _code = LimitLength(code.Trim().ToUpper(), 4);
  1877. _purpose = LimitLength(description.Trim(), 42);
  1878. _deadLine = (deadline == null) ? "" : deadline?.ToString("dd.MM.yyyy");
  1879. _recipientIban = LimitLength(recipientIban.Trim(), 34);
  1880. _recipientName = LimitLength(recipientName.Trim(), 33);
  1881. _recipientAddress = LimitLength(recipientAddress.Trim(), 33);
  1882. _recipientPlace = LimitLength(recipientPlace.Trim(), 33);
  1883. _recipientSiModel = LimitLength(recipientSiModel.Trim().ToUpper(), 4);
  1884. _recipientSiReference = LimitLength(recipientSiReference.Trim(), 22);
  1885. }
  1886. private string FormatAmount(double amount)
  1887. {
  1888. int _amt = (int)Math.Round(amount * 100.0);
  1889. return String.Format("{0:00000000000}", _amt);
  1890. }
  1891. private int CalculateChecksum()
  1892. {
  1893. int _cs = 5 + _payerName.Length; //5 = UPNQR constant Length
  1894. _cs += _payerAddress.Length;
  1895. _cs += _payerPlace.Length;
  1896. _cs += _amount.Length;
  1897. _cs += _code.Length;
  1898. _cs += _purpose.Length;
  1899. _cs += _deadLine.Length;
  1900. _cs += _recipientIban.Length;
  1901. _cs += _recipientName.Length;
  1902. _cs += _recipientAddress.Length;
  1903. _cs += _recipientPlace.Length;
  1904. _cs += _recipientSiModel.Length;
  1905. _cs += _recipientSiReference.Length;
  1906. _cs += 19;
  1907. return _cs;
  1908. }
  1909. public override string ToString()
  1910. {
  1911. var _sb = new StringBuilder();
  1912. _sb.Append("UPNQR");
  1913. _sb.Append('\n').Append('\n').Append('\n').Append('\n').Append('\n');
  1914. _sb.Append(_payerName).Append('\n');
  1915. _sb.Append(_payerAddress).Append('\n');
  1916. _sb.Append(_payerPlace).Append('\n');
  1917. _sb.Append(_amount).Append('\n').Append('\n').Append('\n');
  1918. _sb.Append(_code.ToUpper()).Append('\n');
  1919. _sb.Append(_purpose).Append('\n');
  1920. _sb.Append(_deadLine).Append('\n');
  1921. _sb.Append(_recipientIban.ToUpper()).Append('\n');
  1922. _sb.Append(_recipientSiModel).Append(_recipientSiReference).Append('\n');
  1923. _sb.Append(_recipientName).Append('\n');
  1924. _sb.Append(_recipientAddress).Append('\n');
  1925. _sb.Append(_recipientPlace).Append('\n');
  1926. _sb.AppendFormat("{0:000}", CalculateChecksum()).Append('\n');
  1927. return _sb.ToString();
  1928. }
  1929. }
  1930. private static bool IsValidIban(string iban)
  1931. {
  1932. return Regex.IsMatch(iban.Replace(" ", ""), @"^[a-zA-Z]{2}[0-9]{2}[a-zA-Z0-9]{4}[0-9]{7}([a-zA-Z0-9]?){0,16}$");
  1933. }
  1934. private static bool IsValidBic(string bic)
  1935. {
  1936. return Regex.IsMatch(bic.Replace(" ", ""), @"^([a-zA-Z]{4}[a-zA-Z]{2}[a-zA-Z0-9]{2}([a-zA-Z0-9]{3})?)$");
  1937. }
  1938. private static string ConvertStringToEncoding(string message, string encoding)
  1939. {
  1940. Encoding iso = Encoding.GetEncoding(encoding);
  1941. Encoding utf8 = Encoding.UTF8;
  1942. byte[] utfBytes = utf8.GetBytes(message);
  1943. byte[] isoBytes = Encoding.Convert(utf8, iso, utfBytes);
  1944. #if NET40 || NET461
  1945. return iso.GetString(isoBytes);
  1946. #else
  1947. return iso.GetString(isoBytes,0, isoBytes.Length);
  1948. #endif
  1949. }
  1950. private static string EscapeInput(string inp, bool simple = false)
  1951. {
  1952. char[] forbiddenChars = { '\\', ';', ',', ':' };
  1953. if (simple)
  1954. {
  1955. forbiddenChars = new char[1] { ':' };
  1956. }
  1957. foreach (var c in forbiddenChars)
  1958. {
  1959. inp = inp.Replace(c.ToString(), "\\" + c);
  1960. }
  1961. return inp;
  1962. }
  1963. public static bool ChecksumMod10(string digits)
  1964. {
  1965. if (string.IsNullOrEmpty(digits) || digits.Length < 2)
  1966. return false;
  1967. int[] mods = new int[] { 0, 9, 4, 6, 8, 2, 7, 1, 3, 5 };
  1968. int remainder = 0;
  1969. for (int i = 0; i < digits.Length - 1; i++)
  1970. {
  1971. var num = Convert.ToInt32(digits[i]) - 48;
  1972. remainder = mods[(num + remainder) % 10];
  1973. }
  1974. var checksum = (10 - remainder) % 10;
  1975. return checksum == Convert.ToInt32(digits[digits.Length - 1]) - 48;
  1976. }
  1977. private static bool isHexStyle(string inp)
  1978. {
  1979. return (System.Text.RegularExpressions.Regex.IsMatch(inp, @"\A\b[0-9a-fA-F]+\b\Z") || System.Text.RegularExpressions.Regex.IsMatch(inp, @"\A\b(0[xX])?[0-9a-fA-F]+\b\Z"));
  1980. }
  1981. }
  1982. }
  1983. #endif