Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
EDalerComponent.cs
1using Paiwise;
2using System;
3using System.Collections.Generic;
4using System.Reflection;
5using System.Text;
6using System.Threading.Tasks;
7using Waher.Content;
9using Waher.Events;
17using Waher.Security;
27
29{
34 {
38 public const string NamespaceEDaler = "http://waher.se/Schema/eDaler.xsd";
39
40 private readonly Dictionary<string, Wallet> wallets = new Dictionary<string, Wallet>();
41 private readonly Transactions<ITransaction> transactions = new Transactions<ITransaction>(TimeSpan.FromMinutes(1));
42 private readonly LegalComponent legal;
43 private string defaultCurrency = null;
44
53 : base(Server, Subdomain, Name)
54 {
55 this.legal = Legal;
56
57 this.RegisterIqGetHandler("balance", NamespaceEDaler, this.GetWalletBalanceHandler, true);
58 this.RegisterIqSetHandler("uri", NamespaceEDaler, this.SetUriHandler, false);
59 this.RegisterIqGetHandler("events", NamespaceEDaler, this.GetAccountEventsHandler, false);
60 this.RegisterIqSetHandler("execute", NamespaceEDaler, this.ExecuteHandler, false);
61 this.RegisterIqSetHandler("commit", NamespaceEDaler, this.CommitHandler, false);
62 this.RegisterIqSetHandler("rollback", NamespaceEDaler, this.RollbackHandler, false);
63 this.RegisterIqGetHandler("buyEDalerProviders", NamespaceEDaler, this.GetBuyEDalerProvidersHandler, false);
64 this.RegisterIqSetHandler("initiateGetOptionsBuyEDaler", NamespaceEDaler, this.InitiateGetOptionsBuyEDalerHandler, false);
65 this.RegisterIqSetHandler("initiateBuyEDaler", NamespaceEDaler, this.InitiateBuyEDalerHandler, false);
66 this.RegisterIqGetHandler("sellEDalerProviders", NamespaceEDaler, this.GetSellEDalerProvidersHandler, false);
67 this.RegisterIqSetHandler("initiateGetOptionsSellEDaler", NamespaceEDaler, this.InitiateGetOptionsSellEDalerHandler, false);
68 this.RegisterIqSetHandler("initiateSellEDaler", NamespaceEDaler, this.InitiateSellEDalerHandler, false);
69
70 NeuroFeaturesProcessor.RegisterHandlers(this);
71 StateMachineProcessor.RegisterHandlers(this);
72 }
73
77 public override void Dispose()
78 {
79 this.transactions.Dispose();
80
81 this.UnregisterIqGetHandler("balance", NamespaceEDaler, this.GetWalletBalanceHandler, true);
82 this.UnregisterIqSetHandler("uri", NamespaceEDaler, this.SetUriHandler, false);
83 this.UnregisterIqGetHandler("events", NamespaceEDaler, this.GetAccountEventsHandler, false);
84 this.UnregisterIqSetHandler("execute", NamespaceEDaler, this.ExecuteHandler, false);
85 this.UnregisterIqSetHandler("commit", NamespaceEDaler, this.CommitHandler, false);
86 this.UnregisterIqSetHandler("rollback", NamespaceEDaler, this.RollbackHandler, false);
87 this.UnregisterIqGetHandler("buyEDalerProviders", NamespaceEDaler, this.GetBuyEDalerProvidersHandler, false);
88 this.UnregisterIqSetHandler("initiateGetOptionsBuyEDaler", NamespaceEDaler, this.InitiateGetOptionsBuyEDalerHandler, false);
89 this.UnregisterIqSetHandler("initiateBuyEDaler", NamespaceEDaler, this.InitiateBuyEDalerHandler, false);
90 this.UnregisterIqGetHandler("sellEDalerProviders", NamespaceEDaler, this.GetSellEDalerProvidersHandler, false);
91 this.UnregisterIqSetHandler("initiateGetOptionsSellEDaler", NamespaceEDaler, this.InitiateGetOptionsSellEDalerHandler, false);
92 this.UnregisterIqSetHandler("initiateSellEDaler", NamespaceEDaler, this.InitiateSellEDalerHandler, false);
93
94 NeuroFeaturesProcessor.UnregisterHandlers(this);
95 StateMachineProcessor.UnregisterHandlers(this);
96 }
97
102 public override bool SupportsAccounts => false;
103
107 public LegalComponent Legal => this.legal;
108
113 public async Task<string> GetDefaultCurrency()
114 {
115 if (this.defaultCurrency is null)
116 this.defaultCurrency = await RuntimeSettings.GetAsync("DefaultCurrency", "EUR");
117
118 return this.defaultCurrency;
119 }
120
121 internal async Task<Wallet> GetWallet(CaseInsensitiveString AccountName, CaseInsensitiveString Domain)
122 {
123 string Key = AccountName.LowerCase + "@" + Domain.LowerCase;
125
126 lock (this.wallets)
127 {
128 if (this.wallets.TryGetValue(Key, out Wallet))
129 return Wallet;
130 }
131
132 bool Created = false;
133
134 Wallet = await Database.FindFirstDeleteRest<Wallet>(
135 new FilterAnd(
136 new FilterFieldEqualTo("Account", AccountName),
137 new FilterCustom<Wallet>((Obj)=>
138 {
139 return Obj.Domain == Domain || CaseInsensitiveString.IsNullOrEmpty(Obj.Domain);
140 })), "Created");
141
142 if (Wallet is null)
143 {
144 Wallet = new Wallet()
145 {
146 Account = AccountName,
147 Domain = Domain,
148 Currency = await this.GetDefaultCurrency(),
149 Created = DateTime.Now
150 };
151
152 Created = true;
153 }
154 else if (string.IsNullOrEmpty(Wallet.Currency))
155 {
156 Wallet.Currency = await this.GetDefaultCurrency();
157 await Database.Update(Wallet);
158 }
159
160 lock (this.wallets)
161 {
162 if (this.wallets.TryGetValue(Key, out Wallet Wallet2))
163 return Wallet2;
164 else
165 this.wallets[Key] = Wallet;
166 }
167
168 if (Created)
169 await Database.Insert(Wallet);
170
171 return Wallet;
172 }
173
174 private async Task GetWalletBalanceHandler(object Sender, IqEventArgs e)
175 {
176 if (!this.Server.IsServerDomain(e.From.Domain, true) || !e.From.HasAccount)
177 {
178 await e.IqErrorForbidden(e.To, "Access to wallet only granted to accounts on broker.", "en");
179 return;
180 }
181
182 CaseInsensitiveString AccountName = e.From.Account;
183 IAccount Account = await XmppServerModule.GetAccountAsync(AccountName);
184 if (Account is null)
185 {
186 await e.IqErrorForbidden(e.To, "Access to wallet only granted to accounts on broker.", "en");
187 return;
188 }
189
190 if (!Account.Enabled)
191 {
192 await e.IqErrorForbidden(e.To, "Account has been disabled.", "en");
193 return;
194 }
195
196 await e.IqResult(await this.GetWalletBalanceXml(AccountName, e.From.Domain, null), e.To);
197 }
198
199 internal async Task<string> GetWalletBalanceXml(CaseInsensitiveString AccountName, CaseInsensitiveString Domain, AccountEvent Event)
200 {
201 Wallet Wallet = await this.GetWallet(AccountName, Domain);
202 StringBuilder Xml = new StringBuilder();
203
204 Xml.Append("<balance xmlns='");
205 Xml.Append(NamespaceEDaler);
206 Xml.Append("' amount='");
207 Xml.Append(CommonTypes.Encode(Wallet.Balance));
208
209 if (Wallet.Reserved != 0)
210 {
211 Xml.Append("' reserved='");
212 Xml.Append(CommonTypes.Encode(Wallet.Reserved));
213 }
214
215 Xml.Append("' currency='");
216 Xml.Append(XML.Encode(Wallet.Currency));
217 Xml.Append("' timestamp='");
218 Xml.Append(XML.Encode(Wallet.BalanceTimestamp));
219
220 if (Event is null)
221 Xml.Append("'/>");
222 else
223 {
224 Xml.Append("'>");
225 Event.ToXml(Xml);
226 Xml.Append("</balance>");
227 }
228
229 return Xml.ToString();
230 }
231
232 public static bool IsValidCurrencySymbol(string Currency)
233 {
234 if (Currency.Length > 5)
235 return false;
236
237 Currency = Currency.ToUpper();
238
239 foreach (char ch in Currency)
240 {
241 if (ch < 'A' || ch > 'Z')
242 return false;
243 }
244
245 return true;
246 }
247
248 public static Task<string> GenerateIssueUrl(decimal Amount, string Currency, int ExpiresDays, string FreeText,
249 string ManagerPassword, HttpRequest Request, IUser User)
250 {
251 return GenerateIssueUrl(string.Empty, Amount, Currency, ExpiresDays, FreeText, ManagerPassword, Request, User);
252 }
253
254 public static async Task<string> GenerateIssueUrl(string To, decimal Amount, string Currency, int ExpiresDays, string FreeText,
255 string ManagerPassword, HttpRequest Request, IUser User)
256 {
257 if (User is null || !(User?.HasPrivilege("Admin.eDaler.Generate") ?? false))
258 throw new ForbiddenException("Insufficient privileges.");
259
260 string Uri = await GenerateUrl(To, Amount, Currency, ExpiresDays, FreeText, ManagerPassword, Request, User, "is", true);
261 KeyValuePair<string, object>[] Tags = await GetTags(Amount, Currency, ExpiresDays, FreeText, Request);
262
263 Log.Notice("URI created for eDaler creation.", XmppServerModule.Server.Domain, User.UserName,
264 "eDalerIssueUri", EventLevel.Major, Tags);
265
266 return Uri;
267 }
268
269 public static async Task<string> GenerateDestroyUrl(decimal Amount, string Currency, int ExpiresDays, string FreeText,
270 string ManagerPassword, HttpRequest Request, IUser User)
271 {
272 if (User is null || !(User?.HasPrivilege("Admin.eDaler.Destroy") ?? false))
273 throw new ForbiddenException("Insufficient privileges.");
274
275 string Uri = await GenerateUrl(string.Empty, Amount, Currency, ExpiresDays, FreeText, ManagerPassword, Request, User, "xx", false);
276 KeyValuePair<string, object>[] Tags = await GetTags(Amount, Currency, ExpiresDays, FreeText, Request);
277
278 Log.Notice("URI created for eDaler destruction.", XmppServerModule.Server.Domain, User.UserName,
279 "eDalerDestroyUri", EventLevel.Major, Tags);
280
281 return Uri;
282 }
283
284 private async static Task<KeyValuePair<string, object>[]> GetTags(decimal Amount, string Currency, int ExpiresDays, string FreeText, HttpRequest Request)
285 {
286 return await LoginAuditor.Annotate(Request.RemoteEndPoint,
287 new KeyValuePair<string, object>("Amount", Amount),
288 new KeyValuePair<string, object>("Currency", Currency),
289 new KeyValuePair<string, object>("ExpiresDays", ExpiresDays),
290 new KeyValuePair<string, object>("FreeText", FreeText),
291 new KeyValuePair<string, object>("RemoteEndpoint", Request.RemoteEndPoint));
292 }
293
294 private static async Task<string> GenerateUrl(string To, decimal Amount, string Currency, int ExpiresDays, string FreeText, string ManagerPassword,
295 HttpRequest Request, IUser User, string Command, bool Sign)
296 {
297 if (Amount <= 0)
298 throw new ArgumentException("Amount must be positive.", nameof(Amount));
299
300 if (string.IsNullOrEmpty(Currency))
301 throw new ArgumentException("No currency specified.");
302
303 if (!IsValidCurrencySymbol(Currency))
304 throw new ArgumentException("Invalid currency symbol.");
305
306 if (ExpiresDays <= 0)
307 throw new ArgumentException("Expiry interval must be positive.", nameof(Amount));
308
309 if (string.IsNullOrEmpty(FreeText))
310 throw new ArgumentException("No free text specified.");
311
312 if (User is null || string.IsNullOrEmpty(User.UserName))
313 throw new ForbiddenException("Invalid user.");
314
315 // Double check manager password
316
317 LoginResult Result = await Users.Login(User.UserName, ManagerPassword, Request.RemoteEndPoint, "Web");
318
319 switch (Result.Type)
320 {
321 case LoginResultType.PermanentlyBlocked:
322 StringBuilder sb = new StringBuilder();
323
324 sb.Append("This endpoint (");
325 sb.Append(Request.RemoteEndPoint);
326 sb.Append(") has been blocked from the system.");
327
328 throw new ForbiddenException(sb.ToString());
329
330 case LoginResultType.TemporarilyBlocked:
331 sb = new StringBuilder();
332 DateTime TP = Result.Next.Value;
333 DateTime Today = DateTime.Today;
334
335 sb.Append("Too many failed login attempts in a row registered. Try again after ");
336 sb.Append(TP.ToLongTimeString());
337
338 if (TP.Date != Today)
339 {
340 if (TP.Date == Today.AddDays(1))
341 sb.Append(" tomorrow");
342 else
343 {
344 sb.Append(", ");
345 sb.Append(TP.ToShortDateString());
346 }
347 }
348
349 sb.Append(". Remote Endpoint: ");
350 sb.Append(Request.RemoteEndPoint);
351
352 throw new ForbiddenException(sb.ToString());
353
354 case LoginResultType.NoPassword:
355 throw new ForbiddenException("No password provided.");
356
357 case LoginResultType.InvalidCredentials:
358 default:
359 throw new ForbiddenException("Invalid login credentials provided.");
360
361 case LoginResultType.Success:
362 break;
363 }
364
365 // Generate URI
366
367 StringBuilder Uri = new StringBuilder();
368 Guid Id = Guid.NewGuid();
369 DateTime Created = DateTime.UtcNow;
370
371 Uri.Append("edaler:");
372 Uri.Append(Command);
373 Uri.Append('=');
374 Uri.Append(XmppServerModule.Server.Domain);
375 Uri.Append(";id=");
376 Uri.Append(Id.ToString());
377
378 if (!string.IsNullOrEmpty(To))
379 {
380 int i = To.IndexOf('@');
381 if (i > 0 && Guid.TryParse(To.Substring(0, i), out _))
382 Uri.Append("ti=");
383 else
384 Uri.Append("t=");
385
386 Uri.Append(To);
387 }
388
389 Uri.Append(";cr=");
390 Uri.Append(XML.Encode(Created, false));
391 Uri.Append(";am=");
392 Uri.Append(CommonTypes.Encode(Amount));
393 Uri.Append(";cu=");
394 Uri.Append(Currency);
395 Uri.Append(";ex=");
396 Uri.Append(XML.Encode(Created.Date.AddDays(ExpiresDays), true));
397 Uri.Append(";m=");
398 Uri.Append(Convert.ToBase64String(Encoding.UTF8.GetBytes(FreeText)));
399
400 if (Sign)
401 {
402 byte[] PreSign = Encoding.UTF8.GetBytes(Uri.ToString());
403 byte[] S = XmppServerModule.Legal.Sign(PreSign);
404
405 Uri.Append(";s=");
406 Uri.Append(Convert.ToBase64String(S));
407 }
408
409 return Uri.ToString();
410 }
411
412 private async Task SetUriHandler(object Sender, IqEventArgs e)
413 {
415 {
416 await e.IqErrorServiceUnavailable(e.To, "Transaction module not running.", "en");
417 return;
418 }
419
420 if (e.From.HasAccount && !this.Server.IsServerDomain(e.From.Domain, true))
421 {
422 await e.IqErrorForbidden(e.To, "Access to wallet only granted to accounts on broker.", "en");
423 return;
424 }
425
426 string UriString = e.Query.InnerText;
427 EDalerUriState State = new ExternalRequest(e);
428 EDalerUri Uri = await EDalerUri.Parse(UriString, State, this);
429 if (Uri is null)
430 return;
431
432 if (await this.ProcessUri(Uri, e.From))
433 await e.IqResult(string.Empty, e.To);
434 }
435
442 public async Task<string> ProcessUri(string Uri, string From)
443 {
444 InternalProcessing State = new InternalProcessing(Uri);
445
446 EDalerUri ParsedUri = await EDalerUri.Parse(Uri, State, this);
447 if (ParsedUri is null)
448 return string.IsNullOrEmpty(State.ErrorMessage) ? "Unable to parse eDaler URI." : State.ErrorMessage;
449
450 XmppAddress ParsedFrom = new XmppAddress(From);
451
452 if (!await this.ProcessUri(ParsedUri, ParsedFrom))
453 return string.IsNullOrEmpty(State.ErrorMessage) ? "Unable to process eDaler URI." : State.ErrorMessage;
454
455 return null;
456 }
457
458 internal async Task<bool> ProcessUri(EDalerUri Uri, XmppAddress From)
459 {
460 ITransaction UriTransaction;
461 bool OnPrincipal = Uri.PrincipalDomain == this.Server.Domain;
462 bool FromPrincipal = From.Address == Uri.PrincipalDomain;
463 bool RemotelyControlled = false;
464 bool ContractualPayment = Uri is EDalerContractualPaymentUri;
465
466 if (OnPrincipal || FromPrincipal || ContractualPayment)
467 {
468 RemotelyControlled = (!OnPrincipal && FromPrincipal) || ContractualPayment;
469
470 List<ITransaction> Parts = new List<ITransaction>();
471 Uri.AddTransactionParts(Parts, RemotelyControlled, this.legal);
472
473 if (Parts.Count == 1)
474 UriTransaction = Parts[0];
475 else
476 UriTransaction = new CompositeTransaction(Uri.Id, true, Parts.ToArray());
477 }
478 else
479 UriTransaction = new RelayToPrimary(Uri);
480
481 try
482 {
483 if (!await UriTransaction.Prepare())
484 {
485 Uri.State.Error(EDalerUriErrorType.BadRequest, "Unable to prepare URI for processing.", false);
486 return false;
487 }
488 }
489 catch (Exception ex)
490 {
491 Uri.State.Error(ex);
492 return false;
493 }
494
495 if (RemotelyControlled)
496 {
497 UriTransaction.Tag = new RemoteControlState()
498 {
499 From = From.BareJid,
500 Uri = Uri
501 };
502
503 this.transactions.Register(UriTransaction);
504
505 return true;
506 }
507 else
508 {
509 try
510 {
511 if (await UriTransaction.Execute())
512 {
513 if (await UriTransaction.Commit())
514 return false;
515 else
516 await UriTransaction.Rollback();
517 }
518 else
519 await UriTransaction.Rollback();
520
521 Uri.State.Error(EDalerUriErrorType.ResourceConstraint, "Unable to process transaction.", false);
522 }
523 catch (Exception ex)
524 {
525 await UriTransaction.Abort();
526
527 Uri.State.Error(ex);
528 }
529 }
530
531 return false;
532 }
533
534 private class RemoteControlState
535 {
536 public CaseInsensitiveString From;
537 public EDalerUri Uri;
538 }
539
540 private async Task ExecuteHandler(object Sender, IqEventArgs e)
541 {
542 ITransaction Transaction = await this.PrepareTransaction(e);
543 if (Transaction is null)
544 return;
545
546 try
547 {
548 if (!await Transaction.Execute())
549 {
550 if (Transaction.Tag is RemoteControlState State)
551 await e.IqErrorConflict(e.To, "Unable to execute transaction: " + State.Uri.State.ErrorMessage, "en");
552 else
553 await e.IqErrorConflict(e.To, "Unable to execute transaction.", "en");
554
555 return;
556 }
557 }
558 catch (Exception ex)
559 {
560 await e.IqError(ex, e.To);
561 return;
562 }
563
564 await e.IqResult(string.Empty, e.To);
565 }
566
567 private async Task<ITransaction> PrepareTransaction(IqEventArgs e)
568 {
569 string IdStr = XML.Attribute(e.Query, "id");
570
571 if (!Guid.TryParse(IdStr, out Guid Id))
572 {
573 await e.IqErrorBadRequest(e.To, "Invalid transaction ID.", "en");
574 return null;
575 }
576
577 if (!this.transactions.TryGetTransaction(Id, out ITransaction Transaction))
578 {
579 await e.IqErrorItemNotFound(e.To, "An active transaction with the given ID was not found.", "en");
580 return null;
581 }
582
583 if (!(Transaction.Tag is RemoteControlState State) || State.From != e.From.BareJid)
584 {
585 await e.IqErrorForbidden(e.To, "You are not authorized to control this transaction.", "en");
586 return null;
587 }
588
589 State.Uri.State = new InternalProcessing(State.Uri.UriString);
590
591 return Transaction;
592 }
593
594 private async Task CommitHandler(object Sender, IqEventArgs e)
595 {
596 ITransaction Transaction = await this.PrepareTransaction(e);
597 if (Transaction is null)
598 return;
599
600 try
601 {
602 if (!await Transaction.Commit())
603 {
604 if (Transaction.Tag is RemoteControlState State)
605 await e.IqErrorConflict(e.To, "Unable to commit transaction: " + State.Uri.State.ErrorMessage, "en");
606 else
607 await e.IqErrorConflict(e.To, "Unable to commit transaction.", "en");
608
609 return;
610 }
611
612 this.transactions.Unregister(Transaction);
613 }
614 catch (Exception ex)
615 {
616 await e.IqError(ex, e.To);
617 return;
618 }
619
620 await e.IqResult(string.Empty, e.To);
621 }
622
623 private async Task RollbackHandler(object Sender, IqEventArgs e)
624 {
625 ITransaction Transaction = await this.PrepareTransaction(e);
626 if (Transaction is null)
627 return;
628
629 try
630 {
631 if (!await Transaction.Rollback())
632 {
633 if (Transaction.Tag is RemoteControlState State)
634 await e.IqErrorConflict(e.To, "Unable to roll transaction back: " + State.Uri.State.ErrorMessage, "en");
635 else
636 await e.IqErrorConflict(e.To, "Unable to roll transaction back.", "en");
637
638 return;
639 }
640
641 this.transactions.Unregister(Transaction);
642 }
643 catch (Exception ex)
644 {
645 await e.IqError(ex, e.To);
646 return;
647 }
648
649 await e.IqResult(string.Empty, e.To);
650 }
651
652 private async Task GetAccountEventsHandler(object Sender, IqEventArgs e)
653 {
654 if (!this.Server.IsServerDomain(e.From.Domain, true) || !e.From.HasAccount)
655 {
656 await e.IqErrorForbidden(e.To, "Access to wallet only granted to accounts on broker.", "en");
657 return;
658 }
659
660 CaseInsensitiveString AccountName = e.From.Account;
661 IAccount Account = await XmppServerModule.GetAccountAsync(AccountName);
662 if (Account is null)
663 {
664 await e.IqErrorForbidden(e.To, "Access to wallet only granted to accounts on broker.", "en");
665 return;
666 }
667
668 if (!Account.Enabled)
669 {
670 await e.IqErrorForbidden(e.To, "Account has been disabled.", "en");
671 return;
672 }
673
674 IEnumerable<AccountEvent> Events;
675 int MaxCount = XML.Attribute(e.Query, "maxCount", 20);
676 if (MaxCount > 100)
677 MaxCount = 100;
678
679 if (!e.Query.HasAttribute("from") ||
680 !XML.TryParse(e.Query.GetAttribute("from"), out DateTime From))
681 {
682 Events = await Database.Find<AccountEvent>(0, MaxCount + 1,
683 new FilterFieldEqualTo("Account", Account.UserName), "-Timestamp");
684 }
685 else
686 {
687 Events = await Database.Find<AccountEvent>(0, MaxCount + 1, new FilterAnd(
688 new FilterFieldEqualTo("Account", Account.UserName),
689 new FilterFieldLesserThan("Timestamp", From)), "-Timestamp");
690 }
691
692 StringBuilder Xml = new StringBuilder();
693
694 Xml.Append("<events xmlns=\"");
695 Xml.Append(NamespaceEDaler);
696 Xml.Append("\">");
697
698 foreach (AccountEvent Event in Events)
699 {
700 if (MaxCount-- <= 0)
701 {
702 Xml.Append("<more/>");
703 break;
704 }
705
706 Event.ToXml(Xml);
707 }
708
709 Xml.Append("</events>");
710
711 await e.IqResult(Xml.ToString(), e.To);
712 }
713
714 private async Task GetBuyEDalerProvidersHandler(object Sender, IqEventArgs e)
715 {
716 if (!this.Server.IsServerDomain(e.From.Domain, true) || !e.From.HasAccount)
717 {
718 await e.IqErrorForbidden(e.To, "Access to service providers only granted to accounts on broker.", "en");
719 return;
720 }
721
722 CaseInsensitiveString AccountName = e.From.Account;
723 IAccount Account = await XmppServerModule.GetAccountAsync(AccountName);
724 if (Account is null)
725 {
726 await e.IqErrorForbidden(e.To, "Access to service providers only granted to accounts on broker.", "en");
727 return;
728 }
729
730 if (!Account.Enabled)
731 {
732 await e.IqErrorForbidden(e.To, "Account has been disabled.", "en");
733 return;
734 }
735
736 LegalIdentity Identity = await this.legal.GetCurrentApprovedLegalIdentityAsync(AccountName);
737 if (Identity is null)
738 {
739 await e.IqErrorForbidden(e.To, "Account has no approved legal identity.", "en");
740 return;
741 }
742
743 string Country = Identity["COUNTRY"];
744 if (string.IsNullOrEmpty(Country))
745 {
746 await e.IqErrorForbidden(e.To, "Approved legal identity lacks country specified.", "en");
747 return;
748 }
749
750 Wallet Wallet = await this.GetWallet(AccountName, e.From.Domain);
751 StringBuilder Xml = new StringBuilder();
752
753 Xml.Append("<providers xmlns='");
754 Xml.Append(NamespaceEDaler);
755 Xml.Append("'>");
756
758
759 foreach (Type T in ServiceTypes)
760 {
761 ConstructorInfo CI = Types.GetDefaultConstructor(T);
762 if (CI is null)
763 continue;
764
766 IBuyEDalerService[] Services = await Provider.GetServicesForBuyingEDaler(Wallet.Currency, Country);
767
768 foreach (IBuyEDalerService Service in Services)
769 {
770 if (!await Service.CanBuyEDaler(AccountName))
771 continue;
772
773 Xml.Append("<provider id='");
774 Xml.Append(XML.Encode(Service.Id));
775 Xml.Append("' type='");
776 Xml.Append(XML.Encode(T.FullName));
777 Xml.Append("' name='");
778 Xml.Append(XML.Encode(Service.Name));
779
780 if (!string.IsNullOrEmpty(Service.IconUrl))
781 {
782 Xml.Append("' iconUrl='");
783 Xml.Append(XML.Encode(Service.IconUrl));
784 Xml.Append("' iconWidth='");
785 Xml.Append(Service.IconWidth.ToString());
786 Xml.Append("' iconHeight='");
787 Xml.Append(Service.IconHeight.ToString());
788 }
789
790 if (!string.IsNullOrEmpty(Service.BuyEDalerTemplateContractId))
791 {
792 Xml.Append("' templateId='");
793 Xml.Append(XML.Encode(Service.BuyEDalerTemplateContractId));
794 }
795
796 Xml.Append("'/>");
797 }
798 }
799
800 Xml.Append("</providers>");
801
802 await e.IqResult(Xml.ToString(), e.To);
803 }
804
805 private async Task InitiateGetOptionsBuyEDalerHandler(object Sender, IqEventArgs e)
806 {
807 if (!this.Server.IsServerDomain(e.From.Domain, true) || !e.From.HasAccount)
808 {
809 await e.IqErrorForbidden(e.To, "Access to service providers only granted to accounts on broker.", "en");
810 return;
811 }
812
813 CaseInsensitiveString AccountName = e.From.Account;
814 IAccount Account = await XmppServerModule.GetAccountAsync(AccountName);
815 if (Account is null)
816 {
817 await e.IqErrorForbidden(e.To, "Access to service providers only granted to accounts on broker.", "en");
818 return;
819 }
820
821 if (!Account.Enabled)
822 {
823 await e.IqErrorForbidden(e.To, "Account has been disabled.", "en");
824 return;
825 }
826
827 LegalIdentity Identity = await this.legal.GetCurrentApprovedLegalIdentityAsync(AccountName);
828 if (Identity is null)
829 {
830 await e.IqErrorForbidden(e.To, "Account has no approved legal identity.", "en");
831 return;
832 }
833
834 string Country = Identity["COUNTRY"];
835 if (string.IsNullOrEmpty(Country))
836 {
837 await e.IqErrorForbidden(e.To, "Approved legal identity lacks country specified.", "en");
838 return;
839 }
840
841 string ServiceId = XML.Attribute(e.Query, "serviceId");
842 string ServiceProvider = XML.Attribute(e.Query, "serviceProvider");
843 string SuccessUrl = XML.Attribute(e.Query, "successUrl");
844 string FailureUrl = XML.Attribute(e.Query, "failureUrl");
845 string CancelUrl = XML.Attribute(e.Query, "cancelUrl");
846 string TransactionId = e.Query.HasAttribute("tid") ? XML.Attribute(e.Query, "tid") : Guid.NewGuid().ToString();
847
848 if (string.IsNullOrEmpty(ServiceId))
849 {
850 await e.IqErrorBadRequest(e.To, "Service ID not defined.", "en");
851 return;
852 }
853
854 if (string.IsNullOrEmpty(ServiceProvider))
855 {
856 await e.IqErrorBadRequest(e.To, "Service Provider not defined.", "en");
857 return;
858 }
859
860 Type T = Types.GetType(ServiceProvider);
861 if (T is null)
862 {
863 await e.IqErrorItemNotFound(e.To, "Service Provider " + ServiceProvider + " not found or installed.", "en");
864 return;
865 }
866
867 if (!typeof(IBuyEDalerServiceProvider).IsAssignableFrom(T) ||
869 {
870 await e.IqErrorBadRequest(e.To, "Service Provider does not support buying of eDaler.", "en");
871 return;
872 }
873
874 Wallet Wallet = await this.GetWallet(AccountName, e.From.Domain);
875 IBuyEDalerService Service = await BuyEDalerServiceProvider.GetServiceForBuyingEDaler(ServiceId, Wallet.Currency, Country);
876
877 if (Service is null)
878 {
879 await e.IqErrorItemNotFound(e.To, "Payment Service ID not found.", "en");
880 return;
881 }
882
883 if (!await Service.CanBuyEDaler(AccountName))
884 {
885 await e.IqErrorNotAllowed(e.To, "Selected service provider cannot perform action.", "en");
886 return;
887 }
888
889 StringBuilder Xml = new StringBuilder();
890
891 Xml.Append("<transaction xmlns='");
892 Xml.Append(NamespaceEDaler);
893 Xml.Append("' tid='");
894 Xml.Append(XML.Encode(TransactionId));
895 Xml.Append("'/>");
896
897 await e.IqResult(Xml.ToString(), e.To);
898
899 Task _ = Task.Run(async () =>
900 {
901 try
902 {
903 Dictionary<CaseInsensitiveString, CaseInsensitiveString> BuyerIdParameters = new Dictionary<CaseInsensitiveString, CaseInsensitiveString>();
904
905 foreach (Property P in Identity.Properties)
906 BuyerIdParameters[P.Name] = P.Value;
907
908 IDictionary<CaseInsensitiveString, object>[] Options;
909
910 try
911 {
912 Options = await Service.GetPaymentOptionsForBuyingEDaler(BuyerIdParameters,
913 SuccessUrl, FailureUrl, CancelUrl, async (sender, e2) =>
914 {
915 Xml.Clear();
916
917 Xml.Append("<buyEDalerOptionsClientUrl xmlns='");
918 Xml.Append(NamespaceEDaler);
919 Xml.Append("' tid='");
920 Xml.Append(XML.Encode(TransactionId));
921 Xml.Append("' url='");
922 Xml.Append(XML.Encode(e2.Url));
923 Xml.Append("'/>");
924
925 await this.Server.SendMessage(string.Empty, string.Empty, e.To, e.From.ToBareJID(), string.Empty, Xml.ToString());
926 // Note: Client may have a new XMPP connection at this point.
927 }, null);
928
929 Xml.Clear();
930
931 Xml.Append("<buyEDalerOptionsCompleted xmlns='");
932 Xml.Append(NamespaceEDaler);
933 Xml.Append("' tid='");
934 Xml.Append(XML.Encode(TransactionId));
935 Xml.Append("'>");
936
937 if (!(Options is null))
938 {
939 foreach (IDictionary<CaseInsensitiveString, object> Option in Options)
940 {
941 Xml.Append("<option>");
942
943 foreach (KeyValuePair<CaseInsensitiveString, object> P in Option)
944 StateMachineProcessor.AppendVariable(Xml, P.Key.Value, P.Value);
945
946 Xml.Append("</option>");
947 }
948 }
949
950 Xml.Append("</buyEDalerOptionsCompleted>");
951
952 await this.Server.SendMessage(string.Empty, string.Empty, e.To, e.From.ToBareJID(), string.Empty, Xml.ToString());
953 // Note: Client may have displayed web URL and have a new XMPP connection at this point.
954 }
955 catch (Exception ex)
956 {
957 Xml.Clear();
958
959 Xml.Append("<buyEDalerOptionsError xmlns='");
960 Xml.Append(NamespaceEDaler);
961 Xml.Append("' tid='");
962 Xml.Append(XML.Encode(TransactionId));
963 Xml.Append("'>");
964 Xml.Append(XML.Encode(ex.Message));
965 Xml.Append("</buyEDalerOptionsError>");
966
967 await this.Server.SendMessage(string.Empty, string.Empty, e.To, e.From.ToBareJID(), string.Empty, Xml.ToString());
968 // Note: Client may have displayed web URL and have a new XMPP connection at this point.
969 }
970 }
971 catch (Exception ex)
972 {
973 Log.Exception(ex);
974 }
975 });
976 }
977
978 private async Task InitiateBuyEDalerHandler(object Sender, IqEventArgs e)
979 {
980 if (!this.Server.IsServerDomain(e.From.Domain, true) || !e.From.HasAccount)
981 {
982 await e.IqErrorForbidden(e.To, "Access to service providers only granted to accounts on broker.", "en");
983 return;
984 }
985
986 CaseInsensitiveString AccountName = e.From.Account;
987 IAccount Account = await XmppServerModule.GetAccountAsync(AccountName);
988 if (Account is null)
989 {
990 await e.IqErrorForbidden(e.To, "Access to service providers only granted to accounts on broker.", "en");
991 return;
992 }
993
994 if (!Account.Enabled)
995 {
996 await e.IqErrorForbidden(e.To, "Account has been disabled.", "en");
997 return;
998 }
999
1000 LegalIdentity Identity = await this.legal.GetCurrentApprovedLegalIdentityAsync(AccountName);
1001 if (Identity is null)
1002 {
1003 await e.IqErrorForbidden(e.To, "Account has no approved legal identity.", "en");
1004 return;
1005 }
1006
1007 string Country = Identity["COUNTRY"];
1008 if (string.IsNullOrEmpty(Country))
1009 {
1010 await e.IqErrorForbidden(e.To, "Approved legal identity lacks country specified.", "en");
1011 return;
1012 }
1013
1014 string ServiceId = XML.Attribute(e.Query, "serviceId");
1015 string ServiceProvider = XML.Attribute(e.Query, "serviceProvider");
1016 decimal Amount = XML.Attribute(e.Query, "amount", 0M);
1017 CaseInsensitiveString Currency = XML.Attribute(e.Query, "currency");
1018 string SuccessUrl = XML.Attribute(e.Query, "successUrl");
1019 string FailureUrl = XML.Attribute(e.Query, "failureUrl");
1020 string CancelUrl = XML.Attribute(e.Query, "cancelUrl");
1021 string TransactionId = e.Query.HasAttribute("tid") ? XML.Attribute(e.Query, "tid") : Guid.NewGuid().ToString();
1022
1023 if (Amount <= 0)
1024 {
1025 await e.IqErrorBadRequest(e.To, "Invalid amount.", "en");
1026 return;
1027 }
1028
1030 {
1031 await e.IqErrorBadRequest(e.To, "Invalid currency.", "en");
1032 return;
1033 }
1034
1035 if (string.IsNullOrEmpty(ServiceId))
1036 {
1037 await e.IqErrorBadRequest(e.To, "Service ID not defined.", "en");
1038 return;
1039 }
1040
1041 if (string.IsNullOrEmpty(ServiceProvider))
1042 {
1043 await e.IqErrorBadRequest(e.To, "Service Provider not defined.", "en");
1044 return;
1045 }
1046
1047 Type T = Types.GetType(ServiceProvider);
1048 if (T is null)
1049 {
1050 await e.IqErrorItemNotFound(e.To, "Service Provider " + ServiceProvider + " not found or installed.", "en");
1051 return;
1052 }
1053
1054 if (!typeof(IBuyEDalerServiceProvider).IsAssignableFrom(T) ||
1056 {
1057 await e.IqErrorBadRequest(e.To, "Service Provider does not support buying of eDaler.", "en");
1058 return;
1059 }
1060
1061 IBuyEDalerService Service = await BuyEDalerServiceProvider.GetServiceForBuyingEDaler(ServiceId, Currency, Country);
1062
1063 if (Service is null)
1064 {
1065 await e.IqErrorItemNotFound(e.To, "Payment Service ID not found.", "en");
1066 return;
1067 }
1068
1069 if (!await Service.CanBuyEDaler(AccountName))
1070 {
1071 await e.IqErrorNotAllowed(e.To, "Selected service provider cannot perform action.", "en");
1072 return;
1073 }
1074
1075 if (!CaseInsensitiveString.IsNullOrEmpty(Currency) && Service.Supports(Currency) == Grade.NotAtAll)
1076 {
1077 await e.IqErrorNotAllowed(e.To, "Selected service provider does not support selected currency (" + Currency + ").", "en");
1078 return;
1079 }
1080
1081 if (!string.IsNullOrEmpty(Service.BuyEDalerTemplateContractId))
1082 {
1083 await e.IqErrorForbidden(e.To, "Service provider requires a signed contract to perform payment. See associated Contract Template ID.", "en");
1084 return;
1085 }
1086
1087 Dictionary<CaseInsensitiveString, object> ContractParameters = new Dictionary<CaseInsensitiveString, object>()
1088 {
1089 { "Amount", Amount },
1090 { "Currency", Currency.Value.ToUpper() }
1091 };
1092 Dictionary<CaseInsensitiveString, CaseInsensitiveString> BuyerIdParameters = new Dictionary<CaseInsensitiveString, CaseInsensitiveString>();
1093
1094 foreach (Property P in Identity.Properties)
1095 BuyerIdParameters[P.Name] = P.Value;
1096
1097 StringBuilder Xml = new StringBuilder();
1098
1099 Xml.Append("<transaction xmlns='");
1100 Xml.Append(NamespaceEDaler);
1101 Xml.Append("' tid='");
1102 Xml.Append(XML.Encode(TransactionId));
1103 Xml.Append("'/>");
1104
1105 await e.IqResult(Xml.ToString(), e.To);
1106
1107 Task _ = Task.Run(async () =>
1108 {
1109 try
1110 {
1111 PaymentResult PaymentResult = await Paiwise.PaiwiseProcessor.BuyEDaler(Amount, Currency, SuccessUrl,
1112 FailureUrl, CancelUrl, Service, this, Identity.Id, null, ContractParameters, BuyerIdParameters, TransactionId,
1113 async (sender, e2) =>
1114 {
1115 Xml.Clear();
1116
1117 Xml.Append("<buyEDalerClientUrl xmlns='");
1118 Xml.Append(NamespaceEDaler);
1119 Xml.Append("' tid='");
1120 Xml.Append(XML.Encode(TransactionId));
1121 Xml.Append("' url='");
1122 Xml.Append(XML.Encode(e2.Url));
1123 Xml.Append("'/>");
1124
1125 await this.Server.SendMessage(string.Empty, string.Empty, e.To, e.From.ToBareJID(), string.Empty, Xml.ToString());
1126 // Note: Client may have a new XMPP connection at this point.
1127 }, null);
1128
1129 if (PaymentResult.Ok)
1130 {
1131 Xml.Clear();
1132
1133 Xml.Append("<buyEDalerCompleted xmlns='");
1134 Xml.Append(NamespaceEDaler);
1135 Xml.Append("' tid='");
1136 Xml.Append(XML.Encode(TransactionId));
1137 Xml.Append("' amount='");
1138 Xml.Append(CommonTypes.Encode(PaymentResult.Amount));
1139 Xml.Append("' currency='");
1140 Xml.Append(XML.Encode(PaymentResult.Currency));
1141 Xml.Append("'/>");
1142
1143 await this.Server.SendMessage(string.Empty, string.Empty, e.To, e.From.ToBareJID(), string.Empty, Xml.ToString());
1144 // Note: Client may have displayed web URL and have a new XMPP connection at this point.
1145 }
1146 else
1147 {
1148 Xml.Clear();
1149
1150 Xml.Append("<buyEDalerError xmlns='");
1151 Xml.Append(NamespaceEDaler);
1152 Xml.Append("' tid='");
1153 Xml.Append(XML.Encode(TransactionId));
1154 Xml.Append("'>");
1155 Xml.Append(XML.Encode(PaymentResult.Error));
1156 Xml.Append("</buyEDalerError>");
1157
1158 await this.Server.SendMessage(string.Empty, string.Empty, e.To, e.From.ToBareJID(), string.Empty, Xml.ToString());
1159 // Note: Client may have displayed web URL and have a new XMPP connection at this point.
1160 }
1161 }
1162 catch (Exception ex)
1163 {
1164 Log.Exception(ex);
1165 }
1166 });
1167 }
1168
1169 private async Task GetSellEDalerProvidersHandler(object Sender, IqEventArgs e)
1170 {
1171 if (!this.Server.IsServerDomain(e.From.Domain, true) || !e.From.HasAccount)
1172 {
1173 await e.IqErrorForbidden(e.To, "Access to service providers only granted to accounts on broker.", "en");
1174 return;
1175 }
1176
1177 CaseInsensitiveString AccountName = e.From.Account;
1178 IAccount Account = await XmppServerModule.GetAccountAsync(AccountName);
1179 if (Account is null)
1180 {
1181 await e.IqErrorForbidden(e.To, "Access to service providers only granted to accounts on broker.", "en");
1182 return;
1183 }
1184
1185 if (!Account.Enabled)
1186 {
1187 await e.IqErrorForbidden(e.To, "Account has been disabled.", "en");
1188 return;
1189 }
1190
1191 LegalIdentity Identity = await this.legal.GetCurrentApprovedLegalIdentityAsync(AccountName);
1192 if (Identity is null)
1193 {
1194 await e.IqErrorForbidden(e.To, "Account has no approved legal identity.", "en");
1195 return;
1196 }
1197
1198 string Country = Identity["COUNTRY"];
1199 if (string.IsNullOrEmpty(Country))
1200 {
1201 await e.IqErrorForbidden(e.To, "Approved legal identity lacks country specified.", "en");
1202 return;
1203 }
1204
1205 Wallet Wallet = await this.GetWallet(AccountName, e.From.Domain);
1206 StringBuilder Xml = new StringBuilder();
1207
1208 Xml.Append("<providers xmlns='");
1209 Xml.Append(NamespaceEDaler);
1210 Xml.Append("'>");
1211
1212 Type[] ServiceTypes = Types.GetTypesImplementingInterface(typeof(ISellEDalerServiceProvider));
1213
1214 foreach (Type T in ServiceTypes)
1215 {
1216 ConstructorInfo CI = Types.GetDefaultConstructor(T);
1217 if (CI is null)
1218 continue;
1219
1221 ISellEDalerService[] Services = await Provider.GetServicesForSellingEDaler(Wallet.Currency, Country);
1222
1223 foreach (ISellEDalerService Service in Services)
1224 {
1225 if (!await Service.CanSellEDaler(AccountName))
1226 continue;
1227
1228 Xml.Append("<provider id='");
1229 Xml.Append(XML.Encode(Service.Id));
1230 Xml.Append("' type='");
1231 Xml.Append(XML.Encode(T.FullName));
1232 Xml.Append("' name='");
1233 Xml.Append(XML.Encode(Service.Name));
1234
1235 if (!string.IsNullOrEmpty(Service.IconUrl))
1236 {
1237 Xml.Append("' iconUrl='");
1238 Xml.Append(XML.Encode(Service.IconUrl));
1239 Xml.Append("' iconWidth='");
1240 Xml.Append(Service.IconWidth.ToString());
1241 Xml.Append("' iconHeight='");
1242 Xml.Append(Service.IconHeight.ToString());
1243 }
1244
1245 if (!string.IsNullOrEmpty(Service.SellEDalerTemplateContractId))
1246 {
1247 Xml.Append("' templateId='");
1248 Xml.Append(XML.Encode(Service.SellEDalerTemplateContractId));
1249 }
1250
1251 Xml.Append("'/>");
1252 }
1253 }
1254
1255 Xml.Append("</providers>");
1256
1257 await e.IqResult(Xml.ToString(), e.To);
1258 }
1259
1260 private async Task InitiateGetOptionsSellEDalerHandler(object Sender, IqEventArgs e)
1261 {
1262 if (!this.Server.IsServerDomain(e.From.Domain, true) || !e.From.HasAccount)
1263 {
1264 await e.IqErrorForbidden(e.To, "Access to service providers only granted to accounts on broker.", "en");
1265 return;
1266 }
1267
1268 CaseInsensitiveString AccountName = e.From.Account;
1269 IAccount Account = await XmppServerModule.GetAccountAsync(AccountName);
1270 if (Account is null)
1271 {
1272 await e.IqErrorForbidden(e.To, "Access to service providers only granted to accounts on broker.", "en");
1273 return;
1274 }
1275
1276 if (!Account.Enabled)
1277 {
1278 await e.IqErrorForbidden(e.To, "Account has been disabled.", "en");
1279 return;
1280 }
1281
1282 LegalIdentity Identity = await this.legal.GetCurrentApprovedLegalIdentityAsync(AccountName);
1283 if (Identity is null)
1284 {
1285 await e.IqErrorForbidden(e.To, "Account has no approved legal identity.", "en");
1286 return;
1287 }
1288
1289 string Country = Identity["COUNTRY"];
1290 if (string.IsNullOrEmpty(Country))
1291 {
1292 await e.IqErrorForbidden(e.To, "Approved legal identity lacks country specified.", "en");
1293 return;
1294 }
1295
1296 string ServiceId = XML.Attribute(e.Query, "serviceId");
1297 string ServiceProvider = XML.Attribute(e.Query, "serviceProvider");
1298 string SuccessUrl = XML.Attribute(e.Query, "successUrl");
1299 string FailureUrl = XML.Attribute(e.Query, "failureUrl");
1300 string CancelUrl = XML.Attribute(e.Query, "cancelUrl");
1301 string TransactionId = e.Query.HasAttribute("tid") ? XML.Attribute(e.Query, "tid") : Guid.NewGuid().ToString();
1302
1303 if (string.IsNullOrEmpty(ServiceId))
1304 {
1305 await e.IqErrorBadRequest(e.To, "Service ID not defined.", "en");
1306 return;
1307 }
1308
1309 if (string.IsNullOrEmpty(ServiceProvider))
1310 {
1311 await e.IqErrorBadRequest(e.To, "Service Provider not defined.", "en");
1312 return;
1313 }
1314
1315 Type T = Types.GetType(ServiceProvider);
1316 if (T is null)
1317 {
1318 await e.IqErrorItemNotFound(e.To, "Service Provider " + ServiceProvider + " not found or installed.", "en");
1319 return;
1320 }
1321
1322 if (!typeof(ISellEDalerServiceProvider).IsAssignableFrom(T) ||
1324 {
1325 await e.IqErrorBadRequest(e.To, "Service Provider does not support selling of eDaler.", "en");
1326 return;
1327 }
1328
1329 Wallet Wallet = await this.GetWallet(AccountName, e.From.Domain);
1330 ISellEDalerService Service = await SellEDalerServiceProvider.GetServiceForSellingEDaler(ServiceId, Wallet.Currency, Country);
1331
1332 if (Service is null)
1333 {
1334 await e.IqErrorItemNotFound(e.To, "Payment Service ID not found.", "en");
1335 return;
1336 }
1337
1338 if (!await Service.CanSellEDaler(AccountName))
1339 {
1340 await e.IqErrorNotAllowed(e.To, "Selected service provider cannot perform action.", "en");
1341 return;
1342 }
1343
1344 StringBuilder Xml = new StringBuilder();
1345
1346 Xml.Append("<transaction xmlns='");
1347 Xml.Append(NamespaceEDaler);
1348 Xml.Append("' tid='");
1349 Xml.Append(XML.Encode(TransactionId));
1350 Xml.Append("'/>");
1351
1352 await e.IqResult(Xml.ToString(), e.To);
1353
1354 Task _ = Task.Run(async () =>
1355 {
1356 try
1357 {
1358 Dictionary<CaseInsensitiveString, CaseInsensitiveString> SellerIdParameters = new Dictionary<CaseInsensitiveString, CaseInsensitiveString>();
1359
1360 foreach (Property P in Identity.Properties)
1361 SellerIdParameters[P.Name] = P.Value;
1362
1363 IDictionary<CaseInsensitiveString, object>[] Options;
1364
1365 try
1366 {
1367 Options = await Service.GetPaymentOptionsForSellingEDaler(SellerIdParameters,
1368 SuccessUrl, FailureUrl, CancelUrl, async (sender, e2) =>
1369 {
1370 Xml.Clear();
1371
1372 Xml.Append("<sellEDalerOptionsClientUrl xmlns='");
1373 Xml.Append(NamespaceEDaler);
1374 Xml.Append("' tid='");
1375 Xml.Append(XML.Encode(TransactionId));
1376 Xml.Append("' url='");
1377 Xml.Append(XML.Encode(e2.Url));
1378 Xml.Append("'/>");
1379
1380 await this.Server.SendMessage(string.Empty, string.Empty, e.To, e.From.ToBareJID(), string.Empty, Xml.ToString());
1381 // Note: Client may have a new XMPP connection at this point.
1382 }, null);
1383
1384 Xml.Clear();
1385
1386 Xml.Append("<sellEDalerOptionsCompleted xmlns='");
1387 Xml.Append(NamespaceEDaler);
1388 Xml.Append("' tid='");
1389 Xml.Append(XML.Encode(TransactionId));
1390 Xml.Append("'>");
1391
1392 if (!(Options is null))
1393 {
1394 foreach (IDictionary<CaseInsensitiveString, object> Option in Options)
1395 {
1396 Xml.Append("<option>");
1397
1398 foreach (KeyValuePair<CaseInsensitiveString, object> P in Option)
1399 StateMachineProcessor.AppendVariable(Xml, P.Key.Value, P.Value);
1400
1401 Xml.Append("</option>");
1402 }
1403 }
1404
1405 Xml.Append("</sellEDalerOptionsCompleted>");
1406
1407 await this.Server.SendMessage(string.Empty, string.Empty, e.To, e.From.ToBareJID(), string.Empty, Xml.ToString());
1408 // Note: Client may have displayed web URL and have a new XMPP connection at this point.
1409 }
1410 catch (Exception ex)
1411 {
1412 Xml.Clear();
1413
1414 Xml.Append("<sellEDalerOptionsError xmlns='");
1415 Xml.Append(NamespaceEDaler);
1416 Xml.Append("' tid='");
1417 Xml.Append(XML.Encode(TransactionId));
1418 Xml.Append("'>");
1419 Xml.Append(XML.Encode(ex.Message));
1420 Xml.Append("</sellEDalerOptionsError>");
1421
1422 await this.Server.SendMessage(string.Empty, string.Empty, e.To, e.From.ToBareJID(), string.Empty, Xml.ToString());
1423 // Note: Client may have displayed web URL and have a new XMPP connection at this point.
1424 }
1425 }
1426 catch (Exception ex)
1427 {
1428 Log.Exception(ex);
1429 }
1430 });
1431 }
1432
1433 private async Task InitiateSellEDalerHandler(object Sender, IqEventArgs e)
1434 {
1435 if (!this.Server.IsServerDomain(e.From.Domain, true) || !e.From.HasAccount)
1436 {
1437 await e.IqErrorForbidden(e.To, "Access to service providers only granted to accounts on broker.", "en");
1438 return;
1439 }
1440
1441 CaseInsensitiveString AccountName = e.From.Account;
1442 IAccount Account = await XmppServerModule.GetAccountAsync(AccountName);
1443 if (Account is null)
1444 {
1445 await e.IqErrorForbidden(e.To, "Access to service providers only granted to accounts on broker.", "en");
1446 return;
1447 }
1448
1449 if (!Account.Enabled)
1450 {
1451 await e.IqErrorForbidden(e.To, "Account has been disabled.", "en");
1452 return;
1453 }
1454
1455 LegalIdentity Identity = await this.legal.GetCurrentApprovedLegalIdentityAsync(AccountName);
1456 if (Identity is null)
1457 {
1458 await e.IqErrorForbidden(e.To, "Account has no approved legal identity.", "en");
1459 return;
1460 }
1461
1462 string Country = Identity["COUNTRY"];
1463 if (string.IsNullOrEmpty(Country))
1464 {
1465 await e.IqErrorForbidden(e.To, "Approved legal identity lacks country specified.", "en");
1466 return;
1467 }
1468
1469 string ServiceId = XML.Attribute(e.Query, "serviceId");
1470 string ServiceProvider = XML.Attribute(e.Query, "serviceProvider");
1471 decimal Amount = XML.Attribute(e.Query, "amount", 0M);
1472 CaseInsensitiveString Currency = XML.Attribute(e.Query, "currency");
1473 string SuccessUrl = XML.Attribute(e.Query, "successUrl");
1474 string FailureUrl = XML.Attribute(e.Query, "failureUrl");
1475 string CancelUrl = XML.Attribute(e.Query, "cancelUrl");
1476 string TransactionId = e.Query.HasAttribute("tid") ? XML.Attribute(e.Query, "tid") : Guid.NewGuid().ToString();
1477
1478 if (Amount <= 0)
1479 {
1480 await e.IqErrorBadRequest(e.To, "Invalid amount.", "en");
1481 return;
1482 }
1483
1485 {
1486 await e.IqErrorBadRequest(e.To, "Invalid currency.", "en");
1487 return;
1488 }
1489
1490 if (string.IsNullOrEmpty(ServiceId))
1491 {
1492 await e.IqErrorBadRequest(e.To, "Service ID not defined.", "en");
1493 return;
1494 }
1495
1496 if (string.IsNullOrEmpty(ServiceProvider))
1497 {
1498 await e.IqErrorBadRequest(e.To, "Service Provider not defined.", "en");
1499 return;
1500 }
1501
1502 Type T = Types.GetType(ServiceProvider);
1503 if (T is null)
1504 {
1505 await e.IqErrorItemNotFound(e.To, "Service Provider " + ServiceProvider + " not found or installed.", "en");
1506 return;
1507 }
1508
1509 if (!typeof(ISellEDalerServiceProvider).IsAssignableFrom(T) ||
1511 {
1512 await e.IqErrorBadRequest(e.To, "Service Provider does not support selling of eDaler.", "en");
1513 return;
1514 }
1515
1516 ISellEDalerService Service = await SellEDalerServiceProvider.GetServiceForSellingEDaler(ServiceId, Currency, Country);
1517
1518 if (Service is null)
1519 {
1520 await e.IqErrorItemNotFound(e.To, "Payment Service ID not found.", "en");
1521 return;
1522 }
1523
1524 if (!await Service.CanSellEDaler(AccountName))
1525 {
1526 await e.IqErrorNotAllowed(e.To, "Selected service provider cannot perform action.", "en");
1527 return;
1528 }
1529
1530 if (!CaseInsensitiveString.IsNullOrEmpty(Currency) && Service.Supports(Currency) == Grade.NotAtAll)
1531 {
1532 await e.IqErrorNotAllowed(e.To, "Selected service provider does not support selected currency (" + Currency + ").", "en");
1533 return;
1534 }
1535
1536 if (!string.IsNullOrEmpty(Service.SellEDalerTemplateContractId))
1537 {
1538 await e.IqErrorForbidden(e.To, "Service provider requires a signed contract to perform payment. See associated Contract Template ID.", "en");
1539 return;
1540 }
1541
1542 Dictionary<CaseInsensitiveString, object> ContractParameters = new Dictionary<CaseInsensitiveString, object>()
1543 {
1544 { "Amount", Amount },
1545 { "Currency", Currency.Value.ToUpper() }
1546 };
1547 Dictionary<CaseInsensitiveString, CaseInsensitiveString> SellerIdParameters = new Dictionary<CaseInsensitiveString, CaseInsensitiveString>();
1548
1549 foreach (Property P in Identity.Properties)
1550 SellerIdParameters[P.Name] = P.Value;
1551
1552 StringBuilder Xml = new StringBuilder();
1553
1554 Xml.Append("<transaction xmlns='");
1555 Xml.Append(NamespaceEDaler);
1556 Xml.Append("' tid='");
1557 Xml.Append(XML.Encode(TransactionId));
1558 Xml.Append("'/>");
1559
1560 await e.IqResult(Xml.ToString(), e.To);
1561
1562 Task _ = Task.Run(async () =>
1563 {
1564 try
1565 {
1566 PaymentResult PaymentResult = await Paiwise.PaiwiseProcessor.SellEDaler(Amount, Currency, SuccessUrl,
1567 FailureUrl, CancelUrl, Service, this, Identity.Id, null, ContractParameters, SellerIdParameters, TransactionId,
1568 async (sender, e2) =>
1569 {
1570 Xml.Clear();
1571
1572 Xml.Append("<sellEDalerClientUrl xmlns='");
1573 Xml.Append(NamespaceEDaler);
1574 Xml.Append("' tid='");
1575 Xml.Append(XML.Encode(TransactionId));
1576 Xml.Append("' url='");
1577 Xml.Append(XML.Encode(e2.Url));
1578 Xml.Append("'/>");
1579
1580 await this.Server.SendMessage(string.Empty, string.Empty, e.To, e.From.ToBareJID(), string.Empty, Xml.ToString());
1581 // Note: Client may have a new XMPP connection at this point.
1582 }, null);
1583
1584 if (PaymentResult.Ok)
1585 {
1586 Xml.Clear();
1587
1588 Xml.Append("<sellEDalerCompleted xmlns='");
1589 Xml.Append(NamespaceEDaler);
1590 Xml.Append("' tid='");
1591 Xml.Append(XML.Encode(TransactionId));
1592 Xml.Append("' amount='");
1593 Xml.Append(CommonTypes.Encode(PaymentResult.Amount));
1594 Xml.Append("' currency='");
1595 Xml.Append(XML.Encode(PaymentResult.Currency));
1596 Xml.Append("'/>");
1597
1598 await this.Server.SendMessage(string.Empty, string.Empty, e.To, e.From.ToBareJID(), string.Empty, Xml.ToString());
1599 // Note: Client may have displayed web URL and have a new XMPP connection at this point.
1600 }
1601 else
1602 {
1603 Xml.Clear();
1604
1605 Xml.Append("<sellEDalerError xmlns='");
1606 Xml.Append(NamespaceEDaler);
1607 Xml.Append("' tid='");
1608 Xml.Append(XML.Encode(TransactionId));
1609 Xml.Append("'>");
1610 Xml.Append(XML.Encode(PaymentResult.Error));
1611 Xml.Append("</sellEDalerError>");
1612
1613 await this.Server.SendMessage(string.Empty, string.Empty, e.To, e.From.ToBareJID(), string.Empty, Xml.ToString());
1614 // Note: Client may have displayed web URL and have a new XMPP connection at this point.
1615 }
1616 }
1617 catch (Exception ex)
1618 {
1619 Log.Exception(ex);
1620 }
1621 });
1622 }
1623
1624 /* TODO:
1625 *
1626 * federated issue: Kolla edaler-komponent
1627 * Överföring: Krypterat meddelande
1628 * transactions over Neuro-Ledger
1629 *
1630 * getTrustChain
1631 * getTransactions
1632 *
1633 * Only accept eDaler from trusted domains
1634 * Only accept eDaler from endpoints with legal identities and proper sender signatures.
1635 * Only approved brokers can issue eDaler
1636 * Inform parent about created eDaler
1637 *
1638 * Accounts & privileges
1639 * Godkända utgivare
1640 * Currency conversion
1641 *
1642 * Manager settings
1643 * max limit generate eDaler
1644 *
1645 * Require user to sign agreement before being able to use wallet.
1646 */
1647 }
1648}
Contains information about a service provider that users can use to buy eDaler.
Result of request payment.
Definition: PaymentResult.cs:7
bool Ok
If payment was successful or not.
Contains information about a service provider that users can use to sell eDaler.
Contains information about a service provider.
Helps with parsing of commong data types.
Definition: CommonTypes.cs:13
static string Encode(bool x)
Encodes a Boolean for use in XML and other formats.
Definition: CommonTypes.cs:594
Helps with common XML-related tasks.
Definition: XML.cs:19
static string Attribute(XmlElement E, string Name)
Gets the value of an XML attribute.
Definition: XML.cs:914
static string Encode(string s)
Encodes a string for use in XML.
Definition: XML.cs:27
static bool TryParse(string s, out DateTime Value)
Tries to decode a string encoded DateTime.
Definition: XML.cs:744
Class representing an event.
Definition: Event.cs:10
override string ToString()
Definition: Event.cs:169
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:13
static void Exception(Exception Exception, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, params KeyValuePair< string, object >[] Tags)
Logs an exception. Event type will be determined by the severity of the exception.
Definition: Log.cs:1647
static void Notice(string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Logs a notice event.
Definition: Log.cs:450
The server understood the request, but is refusing to fulfill it. Authorization will not help and the...
Represents an HTTP request.
Definition: HttpRequest.cs:18
string RemoteEndPoint
Remote end-point.
Definition: HttpRequest.cs:195
Base class for components.
Definition: Component.cs:16
void RegisterIqSetHandler(string LocalName, string Namespace, EventHandlerAsync< IqEventArgs > Handler, bool PublishNamespaceAsFeature)
Registers an IQ-Set handler.
Definition: Component.cs:161
CaseInsensitiveString Subdomain
Subdomain name.
Definition: Component.cs:76
void RegisterIqGetHandler(string LocalName, string Namespace, EventHandlerAsync< IqEventArgs > Handler, bool PublishNamespaceAsFeature)
Registers an IQ-Get handler.
Definition: Component.cs:149
XmppServer Server
XMPP Server.
Definition: Component.cs:96
bool UnregisterIqGetHandler(string LocalName, string Namespace, EventHandlerAsync< IqEventArgs > Handler, bool RemoveNamespaceAsFeature)
Unregisters an IQ-Get handler.
Definition: Component.cs:249
bool UnregisterIqSetHandler(string LocalName, string Namespace, EventHandlerAsync< IqEventArgs > Handler, bool RemoveNamespaceAsFeature)
Unregisters an IQ-Set handler.
Definition: Component.cs:262
Event arguments for IQ queries.
Definition: IqEventArgs.cs:12
XmppAddress From
From address attribute
Definition: IqEventArgs.cs:93
Task IqResult(string Xml, string From)
Returns a response to the current request.
Definition: IqEventArgs.cs:113
Task IqErrorItemNotFound(XmppAddress From, string ErrorText, string Language)
Returns a item-not-found error.
Definition: IqEventArgs.cs:201
XmlElement Query
Query element, if found, null otherwise.
Definition: IqEventArgs.cs:70
Task IqErrorNotAllowed(XmppAddress From, string ErrorText, string Language)
Returns a not-allowed error.
Definition: IqEventArgs.cs:187
XmppAddress To
To address attribute
Definition: IqEventArgs.cs:88
async Task IqError(string ErrorType, string Xml, XmppAddress From, string ErrorText, string Language)
Returns an error response to the current request.
Definition: IqEventArgs.cs:137
Task IqErrorServiceUnavailable(XmppAddress From, string ErrorText, string Language)
Returns a service-unavailable error.
Definition: IqEventArgs.cs:215
Task IqErrorConflict(XmppAddress From, string ErrorText, string Language)
Returns a conflict error.
Definition: IqEventArgs.cs:257
Task IqErrorBadRequest(XmppAddress From, string ErrorText, string Language)
Returns a bad-request error.
Definition: IqEventArgs.cs:159
Task IqErrorForbidden(XmppAddress From, string ErrorText, string Language)
Returns a forbidden error.
Definition: IqEventArgs.cs:229
Contains information about one XMPP address.
Definition: XmppAddress.cs:9
override string ToString()
object.ToString()
Definition: XmppAddress.cs:190
bool HasAccount
If the address has an account part.
Definition: XmppAddress.cs:167
CaseInsensitiveString Domain
Domain
Definition: XmppAddress.cs:97
XmppAddress ToBareJID()
Returns the Bare JID as an XmppAddress object.
Definition: XmppAddress.cs:215
CaseInsensitiveString BareJid
Bare JID
Definition: XmppAddress.cs:45
static readonly XmppAddress Empty
Empty address.
Definition: XmppAddress.cs:31
CaseInsensitiveString Account
Account
Definition: XmppAddress.cs:124
Task< bool > SendMessage(string Type, string Id, string From, string To, string Language, string ContentXml)
Sends a Message stanza to a recipient.
Definition: XmppServer.cs:3412
bool IsServerDomain(CaseInsensitiveString Domain, bool IncludeAlternativeDomains)
Checks if a domain is the server domain, or optionally, an alternative domain.
Definition: XmppServer.cs:861
CaseInsensitiveString Domain
Domain name.
Definition: XmppServer.cs:882
Represents a case-insensitive string.
string Value
String-representation of the case-insensitive string. (Representation is case sensitive....
string LowerCase
Lower-case representation of the case-insensitive string.
static bool IsNullOrEmpty(CaseInsensitiveString value)
Indicates whether the specified string is null or an CaseInsensitiveString.Empty string.
Static interface for database persistence. In order to work, a database provider has to be assigned t...
Definition: Database.cs:19
static async Task Update(object Object)
Updates an object in the database.
Definition: Database.cs:626
static Task< IEnumerable< object > > Find(string Collection, params string[] SortOrder)
Finds objects in a given collection.
Definition: Database.cs:247
static async Task Insert(object Object)
Inserts an object into the default collection of the database.
Definition: Database.cs:95
This filter selects objects that conform to all child-filters provided.
Definition: FilterAnd.cs:10
Custom filter used to filter objects using an external expression.
Definition: FilterCustom.cs:10
This filter selects objects that have a named field equal to a given value.
This filter selects objects that have a named field lesser than a given value.
Static class that dynamically manages types and interfaces available in the runtime environment.
Definition: Types.cs:14
static Type GetType(string FullName)
Gets a type, given its full name.
Definition: Types.cs:41
static object[] NoParameters
Contains an empty array of parameter values.
Definition: Types.cs:548
static object Instantiate(Type Type, params object[] Arguments)
Returns an instance of the type Type . If one needs to be created, it is. If the constructor requires...
Definition: Types.cs:1353
static Type[] GetTypesImplementingInterface(string InterfaceFullName)
Gets all types implementing a given interface.
Definition: Types.cs:84
static ConstructorInfo GetDefaultConstructor(Type Type)
Gets the default constructor of a type, if one exists.
Definition: Types.cs:1630
Static class managing persistent settings.
static async Task< string > GetAsync(string Key, string DefaultValue)
Gets a string-valued setting.
A transaction built up of a set of sub-transactions.
Abstract base class for transactions.
Definition: Transaction.cs:17
async Task< bool > Commit()
Commits any changes made during the execution phase.
Definition: Transaction.cs:250
object Tag
Caller can use this property to tag the transaction with information.
Definition: Transaction.cs:55
async Task< bool > Execute()
Executes the transaction.
Definition: Transaction.cs:188
async Task< bool > Rollback()
Rolls back any changes made during the execution phase.
Definition: Transaction.cs:313
Module making sure no unfinished transactions are left when system ends.
static bool Running
If the transaction module is running.
Maintains a collection of active transactions.
Definition: Transactions.cs:15
Class that monitors login events, and help applications determine malicious intent....
Definition: LoginAuditor.cs:26
static async Task< KeyValuePair< string, object >[]> Annotate(string RemoteEndpoint, params KeyValuePair< string, object >[] Tags)
Annotates a remote endpoint.
Contains information about a login attempt.
Definition: LoginResult.cs:40
DateTime? Next
Time when a new login can be attempted.
Definition: LoginResult.cs:85
LoginResultType Type
Type of login result.
Definition: LoginResult.cs:90
Corresponds to a user in the system.
Definition: User.cs:21
string UserName
User Name
Definition: User.cs:53
Maintains the collection of all users in the system.
Definition: Users.cs:24
static async Task< LoginResult > Login(string UserName, string Password, string RemoteEndPoint, string Protocol)
Attempts to login in the system.
Definition: Users.cs:174
Manages eDaler on accounts connected to the broker.
override bool SupportsAccounts
If the component supports accounts (true), or if the subdomain name is the only valid address.
override void Dispose()
IDisposable.Dispose
async Task< string > ProcessUri(string Uri, string From)
Processes an eDaler URI.
const string NamespaceEDaler
Namespace of eDaler component.
EDalerComponent(XmppServer Server, CaseInsensitiveString Subdomain, string Name, LegalComponent Legal)
Manages eDaler on accounts connected to the broker.
async Task< string > GetDefaultCurrency()
Gets the default currency
Relays processing of the URI to the principal domain.
eDaler URI representing a contractual payment of eDaler from a sender to a receiver.
Abstract base class for eDaler URIs
Definition: EDalerUri.cs:20
abstract void AddTransactionParts(List< ITransaction > Subtransactions, bool LocalOnly, LegalComponent Legal)
Adds subtransaction objects necessary to process the URI.
EDalerUriState State
URI State object.
Definition: EDalerUri.cs:173
CaseInsensitiveString PrincipalDomain
Principal domain (i.e domain controlling the execution of the transaction.)
Definition: EDalerUri.cs:157
static async Task< EDalerUri > Parse(string Uri, EDalerUriState State, EDalerComponent EDaler)
Parses an eDaler URI
Definition: EDalerUri.cs:260
virtual void Error(EDalerUriErrorType ErrorType, string ErrorMessage, bool LogAsNotice)
Reports an error with the URI
string ErrorMessage
Error message, or null if no error.
Current state of URI from external source
Retains the current balance of an account.
Definition: Wallet.cs:16
Marketplace processor, brokering sales of items via tenders and offers defined in smart contracts.
Service Module hosting the XMPP broker and its components.
Interface for information about a service provider that users can use to buy eDaler.
string BuyEDalerTemplateContractId
Contract ID of Template, for buying e-Daler
Task< IDictionary< CaseInsensitiveString, object >[]> GetPaymentOptionsForBuyingEDaler(IDictionary< CaseInsensitiveString, CaseInsensitiveString > IdentityProperties, string SuccessUrl, string FailureUrl, string CancelUrl, ClientUrlEventHandler ClientUrlCallback, object State)
Gets available payment options for buying eDaler.
Task< bool > CanBuyEDaler(CaseInsensitiveString AccountName)
If the service provider can be used to process a request to buy eDaler of a certain amount,...
Interface for information about a service provider that users can use to buy eDaler.
Task< IBuyEDalerService[]> GetServicesForBuyingEDaler(CaseInsensitiveString Currency, CaseInsensitiveString Country)
Gets available payment services.
Interface for information about a service provider that users can use to sell eDaler.
Task< bool > CanSellEDaler(CaseInsensitiveString AccountName)
If the service provider can be used to process a request to sell eDaler of a certain amount,...
string SellEDalerTemplateContractId
Contract ID of Template, for selling e-Daler
Task< IDictionary< CaseInsensitiveString, object >[]> GetPaymentOptionsForSellingEDaler(IDictionary< CaseInsensitiveString, CaseInsensitiveString > IdentityProperties, string SuccessUrl, string FailureUrl, string CancelUrl, ClientUrlEventHandler ClientUrlCallback, object State)
Gets available payment options for selling eDaler.
Interface for information about a service provider that users can use to sell eDaler.
Task< ISellEDalerService[]> GetServicesForSellingEDaler(CaseInsensitiveString Currency, CaseInsensitiveString Country)
Gets available payment services.
string Id
ID of service provider.
int IconWidth
Width of icon, if available.
string IconUrl
Optional URL to icon of service provider.
int IconHeight
Height of icon, if available.
string Name
Displayable name of service provider.
CaseInsensitiveString UserName
User Name
Definition: IAccount.cs:24
bool Enabled
If the account is enabled.
Definition: IAccount.cs:40
Interface for XMPP user accounts.
Definition: IAccount.cs:9
Grade Supports(T Object)
If the interface understands objects such as Object .
Interface for transactions
Definition: ITransaction.cs:10
Task< bool > Execute()
Executes the transaction.
Task< bool > Prepare()
Prepares the transaction for execution. This step can be used for validation and authorization of the...
Task< bool > Rollback()
Rolls back any changes made during the execution phase.
Task Abort()
Aborts the transaction.
Task< bool > Commit()
Commits any changes made during the execution phase.
Basic interface for a user.
Definition: IUser.cs:7
EventLevel
Event level.
Definition: EventLevel.cs:7
Grade
Grade enumeration
Definition: Grade.cs:7
LoginResultType
Result of login attempt
Definition: LoginResult.cs:9