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