Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
BuyEDalerPaymentService.cs
1using System;
2using System.Collections.Generic;
3using System.Text;
4using System.Threading.Tasks;
5using Waher.Content;
7using Waher.Events;
12
14{
19 {
20 private readonly ServiceField[] fields;
21 private readonly string method;
22 private readonly string url;
23 private readonly string optionsUrl;
24 private readonly string host;
25
41 public BuyEDalerPaymentService(string Id, string Name, string IconUrl, int IconWidth,
42 int IconHeight, string TemplateContractId, string Method, string Url, string OptionsUrl,
43 ServiceField[] Fields, string Host, PaiwisePaymentServices Provider)
45 {
46 this.method = Method;
47 this.url = Url;
48 this.optionsUrl = OptionsUrl;
49 this.fields = Fields;
50 this.host = Host;
51
52 this.BuyEDalerTemplateContractId = TemplateContractId;
53 this.BuyEDalerServiceProvider = Provider;
54 this.PaymentServiceProvider = Provider;
55 }
56
60 public string BuyEDalerTemplateContractId { get; }
61
66
73 {
74 return !CaseInsensitiveString.IsNullOrEmpty(Currency) &&
75 Currency.Length == 3 ? Grade.Ok : Grade.NotAtAll;
76 }
77
84 public async Task<bool> CanBuyEDaler(CaseInsensitiveString AccountName)
85 {
86 if (string.Compare(this.method, "POST", true) != 0)
87 return false;
88
89 string Token = await RuntimeSettings.GetAsync(PaiwisePaymentServices.TokenIdSetting, string.Empty);
90
91 return !string.IsNullOrEmpty(Token);
92 }
93
110 public async Task<PaymentResult> BuyEDaler(IDictionary<CaseInsensitiveString, object> ContractParameters,
111 IDictionary<CaseInsensitiveString, CaseInsensitiveString> IdentityProperties,
112 decimal Amount, string Currency, string SuccessUrl, string FailureUrl, string CancelUrl, ClientUrlEventHandler ClientUrlCallback, object State)
113 {
114 try
115 {
116 if (Amount <= 0)
117 return new PaymentResult("Amount must be positive.");
118
119 if (string.IsNullOrEmpty(Currency) ||
120 Currency.Length != 3 ||
121 Currency.ToUpper() != Currency)
122 {
123 return new PaymentResult("Invalid currency.");
124 }
125
126 Dictionary<string, object> Request = new Dictionary<string, object>()
127 {
128 { "webhook", string.Empty }
129 };
130
131 foreach (ServiceField Field in this.fields)
132 {
133 if (ContractParameters.TryGetValue(Field.FieldId, out object Obj))
134 Request[Field.FieldId] = Obj;
135 else if (IdentityProperties.TryGetValue(Field.FieldId, out CaseInsensitiveString Obj2))
136 Request[Field.FieldId] = Obj2.Value;
137 else
138 {
139 switch (Field.FieldId.LowerCase)
140 {
141 case "returnurl":
142 Request[Field.FieldId] = SuccessUrl ?? string.Empty;
143 break;
144
145 case "returnerrorurl":
146 Request[Field.FieldId] = FailureUrl ?? string.Empty;
147 break;
148
149 case "cancelurl":
150 Request[Field.FieldId] = CancelUrl ?? string.Empty;
151 break;
152
153 case "amount":
154 Request[Field.FieldId] = Amount;
155 break;
156
157 case "currency":
158 Request[Field.FieldId] = Currency;
159 break;
160
161 default:
162 if (Field.Required)
163 return new PaymentResult("Missing Contract or ID property: " + Field.FieldId);
164 break;
165 }
166 }
167 }
168
169 return await this.ProcessRequest(Request, Amount, Currency, ClientUrlCallback, State);
170 }
171 catch (Exception ex)
172 {
173 return new PaymentResult(ex.Message);
174 }
175 }
176
177 private async Task<PaymentResult> ProcessRequest(Dictionary<string, object> Request, decimal Amount, string Currency,
178 ClientUrlEventHandler ClientUrlCallback, object State)
179 {
180 // TODO: Proper callback
181
182 string Token = await RuntimeSettings.GetAsync(PaiwisePaymentServices.TokenIdSetting, string.Empty);
183 if (string.IsNullOrEmpty(Token))
184 return new PaymentResult("Paiwise token not configured");
185
186 object Result;
187
188 try
189 {
190 Result = await InternetContent.PostAsync(new Uri(this.url), Request, Gateway.Certificate,
191 new KeyValuePair<string, string>("Authorization", "Bearer " + Token),
192 new KeyValuePair<string, string>("Accept", JsonCodec.DefaultContentType));
193 }
194 catch (Exception ex)
195 {
196 Log.Error("Sending paiwise request to " + this.url + ", but an error was returned:" + ex.Message +
197 "\r\n\r\n" + JSON.Encode(Request, true), new KeyValuePair<string, object>("Token", "Token")); // TODO: Remove
198
199 return new PaymentResult(ex.Message);
200 }
201
202 if (!(Result is Dictionary<string, object> Response) ||
203 !Response.TryGetValue("status", out object Obj) || !(Obj is string Status))
204 {
205 return new PaymentResult("Invalid response returned from Paiwise");
206 }
207
208 if (Status == "paid")
209 {
210 if (Response.TryGetValue("amount", out Obj) && IsDecimal(Obj, out decimal Amount2))
211 Amount = Amount2;
212
213 if (Response.TryGetValue("currency", out Obj) && Obj is string Currency2)
214 Currency = Currency2;
215
216 return new PaymentResult(Amount, Currency);
217 }
218
219 if (int.TryParse(Status, out _) &&
220 Response.TryGetValue("message", out Obj) && Obj is string Message)
221 {
222 return new PaymentResult(Message);
223 }
224
225 if (Status == "pending") // TODO: Callback, if domain available.
226 {
227 if (!Response.TryGetValue("id", out Obj) || !(Obj is string TransactionId))
228 return new PaymentResult("No transaction ID returned.");
229
230 if (Response.TryGetValue("redirectUrl", out Obj) && Obj is string RedirectUrl)
231 {
232 if (ClientUrlCallback is null)
233 return new PaymentResult("No Client URL callback method defined.");
234
235 try
236 {
237 await ClientUrlCallback(this, new ClientUrlEventArgs(RedirectUrl, State));
238 }
239 catch (Exception ex)
240 {
241 return new PaymentResult(ex.Message);
242 }
243 }
244
245 double TimeoutMinutes = await RuntimeSettings.GetAsync(PaiwisePaymentServices.TimeoutMinutesSetting, 5.0);
246
247 StringBuilder Url = new StringBuilder();
248
249 Url.Append("https://");
250 Url.Append(this.host);
251 Url.Append("/payment/retrieve");
252
253 Request = new Dictionary<string, object>()
254 {
255 { "id", TransactionId }
256 };
257
258 DateTime Start = DateTime.Now;
259
260 while (Status == "pending" && DateTime.Now.Subtract(Start).TotalMinutes < TimeoutMinutes)
261 {
262 await Task.Delay(2000);
263
264 Obj = await InternetContent.PostAsync(new Uri(Url.ToString()), Request, Gateway.Certificate,
265 new KeyValuePair<string, string>("Authorization", "Bearer " + Token),
266 new KeyValuePair<string, string>("Accept", JsonCodec.DefaultContentType));
267
268 if (!(Obj is Dictionary<string, object> Response2) ||
269 !Response2.TryGetValue("status", out Obj) || !(Obj is string Status2))
270 {
271 return new PaymentResult("Invalid polling response returned from Paiwise");
272 }
273
274 if (int.TryParse(Status2, out _) &&
275 Response2.TryGetValue("message", out Obj) && Obj is string Message2)
276 {
277 return new PaymentResult(Message2);
278 }
279
280 Status = Status2;
281
282 if (Status == "paid" &&
283 Response2.TryGetValue("request", out Obj) &&
284 Obj is Dictionary<string, object> Request2)
285 {
286 if (Request2.TryGetValue("amount", out Obj) && IsDecimal(Obj, out decimal Amount2))
287 Amount = Amount2;
288
289 if (Request2.TryGetValue("currency", out Obj) && Obj is string Currency2)
290 Currency = Currency2;
291 }
292 }
293
294 if (Status == "pending")
295 {
296 await Task.Delay(2000);
297
298 Url.Clear();
299 Url.Append("https://");
300 Url.Append(this.host);
301 Url.Append("/payment/cancel");
302
303 Obj = await InternetContent.PostAsync(new Uri(Url.ToString()), Request, Gateway.Certificate,
304 new KeyValuePair<string, string>("Authorization", "Bearer " + Token),
305 new KeyValuePair<string, string>("Accept", JsonCodec.DefaultContentType));
306
307 if (!(Obj is Dictionary<string, object> Response2) ||
308 !Response2.TryGetValue("status", out Obj) || !(Obj is string Status2))
309 {
310 return new PaymentResult("Invalid cancel response returned from Paiwise");
311 }
312
313 if (int.TryParse(Status2, out _) &&
314 Response2.TryGetValue("message", out Obj) && Obj is string Message2)
315 {
316 return new PaymentResult(Message2);
317 }
318
319 Status = Status2;
320 }
321 }
322
323 if (Status == "paid")
324 return new PaymentResult(Amount, Currency);
325 else if (Status == "cancelled")
326 return new PaymentResult("Payment has been cancelled.");
327 else
328 return new PaymentResult(Status);
329 }
330
331 private static bool IsDecimal(object Obj, out decimal Result)
332 {
333 if (Obj is int i)
334 {
335 Result = i;
336 return true;
337 }
338 else if (Obj is decimal d)
339 {
340 Result = d;
341 return true;
342 }
343 else if (Obj is double d2)
344 {
345 Result = (decimal)d2;
346 return true;
347 }
348 else
349 {
350 Result = 0;
351 return false;
352 }
353 }
354
367 public async Task<IDictionary<CaseInsensitiveString, object>[]> GetPaymentOptionsForBuyingEDaler(
368 IDictionary<CaseInsensitiveString, CaseInsensitiveString> IdentityProperties,
369 string SuccessUrl, string FailureUrl, string CancelUrl,
370 ClientUrlEventHandler ClientUrlCallback, object State)
371 {
372 try
373 {
374 if (string.IsNullOrEmpty(this.optionsUrl))
375 return new IDictionary<CaseInsensitiveString, object>[0];
376
377 Dictionary<string, object> Request = new Dictionary<string, object>()
378 {
379 { "webhook", string.Empty }
380 };
381
382 foreach (ServiceField Field in this.fields)
383 {
384 if (IdentityProperties.TryGetValue(Field.FieldId, out CaseInsensitiveString Obj2))
385 Request[Field.FieldId] = Obj2.Value;
386 else
387 {
388 switch (Field.FieldId.LowerCase)
389 {
390 case "returnurl":
391 Request[Field.FieldId] = SuccessUrl ?? string.Empty;
392 break;
393
394 case "returnerrorurl":
395 Request[Field.FieldId] = FailureUrl ?? string.Empty;
396 break;
397
398 case "cancelurl":
399 Request[Field.FieldId] = CancelUrl ?? string.Empty;
400 break;
401
402 default:
403 Request[Field.FieldId] = null;
404 break;
405 }
406 }
407 }
408
409 string Token = await RuntimeSettings.GetAsync(PaiwisePaymentServices.TokenIdSetting, string.Empty);
410 if (string.IsNullOrEmpty(Token))
411 return new IDictionary<CaseInsensitiveString, object>[0];
412
413 object Result;
414
415 try
416 {
417 Result = await InternetContent.PostAsync(new Uri(this.optionsUrl), Request, Gateway.Certificate,
418 new KeyValuePair<string, string>("Authorization", "Bearer " + Token),
419 new KeyValuePair<string, string>("Accept", JsonCodec.DefaultContentType));
420 }
421 catch (Exception ex)
422 {
423 Log.Error("Requesting payment options for paiwise request to " + this.optionsUrl + ", but an error was returned:" + ex.Message +
424 "\r\n\r\n" + JSON.Encode(Request, true), new KeyValuePair<string, object>("Token", "Token")); // TODO: Remove
425
426 return new IDictionary<CaseInsensitiveString, object>[0];
427 }
428
429 if (!(Result is Array A))
430 throw new Exception("Unexpected response type returned: " + Result.GetType().FullName);
431
432 List<IDictionary<CaseInsensitiveString, object>> Options = new List<IDictionary<CaseInsensitiveString, object>>();
433
434 foreach (object Item in A)
435 {
436 if (Item is Dictionary<string, object> Option)
437 {
438 Dictionary<CaseInsensitiveString, object> Properties = new Dictionary<CaseInsensitiveString, object>();
439
440 foreach (KeyValuePair<string, object> P in Option)
441 Properties[P.Key] = P.Value;
442
443 Options.Add(Properties);
444 }
445 }
446
447 return Options.ToArray();
448 }
449 catch (Exception ex)
450 {
451 Log.Exception(ex,
452 new KeyValuePair<string, object>("ServiceProvider", this.BuyEDalerServiceProvider.GetType().FullName),
453 new KeyValuePair<string, object>("ServiceId", this.Id),
454 new KeyValuePair<string, object>("URL", this.optionsUrl));
455
456 return new IDictionary<CaseInsensitiveString, object>[0];
457 }
458 }
459
464 {
465 get
466 {
467 if (!string.IsNullOrEmpty(this.BuyEDalerTemplateContractId))
468 return false;
469
470 foreach (ServiceField Field in this.fields)
471 {
472 switch (Field.FieldId.LowerCase)
473 {
474 case "returnurl":
475 case "returnerrorurl":
476 case "cancelurl":
477 case "amount":
478 case "currency":
479 break;
480
481 default:
482 if (Field.Required)
483 return false;
484 break;
485 }
486 }
487
488 return true;
489 }
490 }
491
496
509 public async Task<PaymentResult> Pay(decimal Amount, string Currency, string Description, string SuccessUrl, string FailureUrl, string CancelUrl,
510 ClientUrlEventHandler ClientUrlCallback, object State)
511 {
512 try
513 {
514 if (Amount <= 0)
515 return new PaymentResult("Amount must be positive.");
516
517 if (string.IsNullOrEmpty(Currency) ||
518 Currency.Length != 3 ||
519 Currency.ToUpper() != Currency)
520 {
521 return new PaymentResult("Invalid currency.");
522 }
523
524 Dictionary<string, object> Request = new Dictionary<string, object>()
525 {
526 { "webhook", string.Empty }
527 };
528
529 foreach (ServiceField Field in this.fields)
530 {
531 switch (Field.FieldId.LowerCase)
532 {
533 case "returnurl":
534 Request[Field.FieldId] = SuccessUrl ?? string.Empty;
535 break;
536
537 case "returnerrorurl":
538 Request[Field.FieldId] = FailureUrl ?? string.Empty;
539 break;
540
541 case "cancelurl":
542 Request[Field.FieldId] = CancelUrl ?? string.Empty;
543 break;
544
545 case "amount":
546 Request[Field.FieldId] = Amount;
547 break;
548
549 case "currency":
550 Request[Field.FieldId] = Currency;
551 break;
552
553 default:
554 if (Field.Required)
555 return new PaymentResult("Missing Contract or ID property: " + Field.FieldId);
556 break;
557 }
558 }
559
560 return await this.ProcessRequest(Request, Amount, Currency, ClientUrlCallback, State);
561 }
562 catch (Exception ex)
563 {
564 return new PaymentResult(ex.Message);
565 }
566 }
567 }
568}
Contains information about a service provider that users can use to buy eDaler.
Reference to a Paiwise payment service.
string BuyEDalerTemplateContractId
Optional Contract ID of Template, for buying e-Daler
Grade Supports(CaseInsensitiveString Currency)
Checks if the service provider supports a given currency.
async 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.
IPaymentServiceProvider PaymentServiceProvider
Reference to service provider.
async Task< bool > CanBuyEDaler(CaseInsensitiveString AccountName)
If the service provider can be used to process a request to buy eDaler of a certain amount,...
async Task< PaymentResult > Pay(decimal Amount, string Currency, string Description, string SuccessUrl, string FailureUrl, string CancelUrl, ClientUrlEventHandler ClientUrlCallback, object State)
Processes a payment.
bool CanBeUsedForPayment
If service can be used for payments.
BuyEDalerPaymentService(string Id, string Name, string IconUrl, int IconWidth, int IconHeight, string TemplateContractId, string Method, string Url, string OptionsUrl, ServiceField[] Fields, string Host, PaiwisePaymentServices Provider)
Reference to a Paiwise payment service.
async Task< PaymentResult > BuyEDaler(IDictionary< CaseInsensitiveString, object > ContractParameters, IDictionary< CaseInsensitiveString, CaseInsensitiveString > IdentityProperties, decimal Amount, string Currency, string SuccessUrl, string FailureUrl, string CancelUrl, ClientUrlEventHandler ClientUrlCallback, object State)
Processes payment for buying eDaler.
Payment services made available by Paiwise.
const string TokenIdSetting
Settings key for Paiwise token.
Represents a field in a service.
Definition: ServiceField.cs:9
bool Required
If the field is required or not.
Definition: ServiceField.cs:36
CaseInsensitiveString FieldId
Field ID
Definition: ServiceField.cs:26
Result of request payment.
Definition: PaymentResult.cs:7
Contains information about a service provider.
string IconUrl
Optional URL to icon of service provider.
string Id
ID of service provider.
int IconHeight
Height of icon, if available.
int IconWidth
Width of icon, if available.
string Name
Displayable name of service provider.
Static class managing encoding and decoding of internet content.
static Task< object > PostAsync(Uri Uri, object Data, params KeyValuePair< string, string >[] Headers)
Posts to a resource, using a Uniform Resource Identifier (or Locator).
Helps with common JSON-related tasks.
Definition: JSON.cs:14
static string Encode(string s)
Encodes a string for inclusion in JSON.
Definition: JSON.cs:507
const string DefaultContentType
application/json
Definition: JsonCodec.cs:19
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 Error(string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Logs an error event.
Definition: Log.cs:682
Static class managing the runtime environment of the IoT Gateway.
Definition: Gateway.cs:126
static X509Certificate2 Certificate
Domain certificate.
Definition: Gateway.cs:2349
Represents a case-insensitive string.
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 class managing persistent settings.
static async Task< string > GetAsync(string Key, string DefaultValue)
Gets a string-valued setting.
Interface for information about a service provider that users can use to buy eDaler.
Interface for information about a service provider that users can use to buy eDaler.
Interface for information about a service provider that users can use to pay for services.
Interface for information about a service provider that users can use to pay for services.
delegate Task ClientUrlEventHandler(object Sender, ClientUrlEventArgs e)
Delegat for client URL callback methods.
Grade
Grade enumeration
Definition: Grade.cs:7