2using System.Collections.Generic;
4using System.Text.RegularExpressions;
5using System.Threading.Tasks;
23 private readonly
string method;
24 private readonly
string url;
25 private readonly
string optionsUrl;
26 private readonly
string host;
44 int IconHeight,
string TemplateContractId,
string Method,
string Url,
string OptionsUrl,
50 this.optionsUrl = OptionsUrl;
54 this.SellEDalerTemplateContractId = TemplateContractId;
55 this.SellEDalerServiceProvider = Provider;
76 Currency.Length == 3 ? Grade.Ok :
Grade.NotAtAll;
87 if (
string.Compare(this.method,
"POST",
true) != 0)
92 return !
string.IsNullOrEmpty(Token);
95 private static readonly Regex PaiwiseProcessorRegex =
new Regex(
@"Waher\.Service\.IoTBroker\.Paiwise\.PaiwiseProcessor[.+]<(SellEDaler|SellEDaler)>\w*([.]\w*)?",
96 RegexOptions.Compiled | RegexOptions.Singleline);
98 private static readonly Regex UnitTestRegex =
new Regex(
@"Paiwise\.Internal\.Test\.PaiwiseTests[.+]<(Test_07_ProcessSellEDalerUsingContract|Test_08_ProcessSellEDalerUsingClientUrl)>\w*([.]\w*)?",
99 RegexOptions.Compiled | RegexOptions.Singleline);
101 private static readonly
object[] approvedSources =
new object[]
103 PaiwiseProcessorRegex,
123 public async Task<PaymentResult>
SellEDaler(IDictionary<CaseInsensitiveString, object> ContractParameters,
124 IDictionary<CaseInsensitiveString, CaseInsensitiveString> IdentityProperties,
125 decimal Amount,
string Currency,
string SuccessUrl,
string FailureUrl,
string CancelUrl,
ClientUrlEventHandler ClientUrlCallback,
object State)
132 if (
string.IsNullOrEmpty(Currency) ||
133 Currency.Length != 3 ||
134 Currency.ToUpper() != Currency)
140 Dictionary<string, object> Request =
new Dictionary<string, object>()
142 {
"webhook", string.Empty }
147 if (ContractParameters.TryGetValue(Field.
FieldId, out Obj))
150 Request[Field.
FieldId] = Obj2.Value;
156 Request[Field.
FieldId] = SuccessUrl;
159 case "returnerrorurl":
160 Request[Field.
FieldId] = FailureUrl;
164 Request[Field.
FieldId] = CancelUrl;
168 Request[Field.
FieldId] = Amount;
172 Request[Field.
FieldId] = Currency;
176 StringBuilder sb =
new StringBuilder();
179 Append(sb,
"FIRST", IdentityProperties, ref First);
180 Append(sb,
"MIDDLE", IdentityProperties, ref First);
181 Append(sb,
"LAST", IdentityProperties, ref First);
184 Append(sb,
"PNR", IdentityProperties, ref First);
186 Request[Field.
FieldId] = sb.ToString();
190 sb =
new StringBuilder();
193 Append(sb,
"ADDR", IdentityProperties, ref First);
195 Request[Field.
FieldId] = sb.ToString();
200 Request[Field.
FieldId] =
string.Empty;
209 if (
string.IsNullOrEmpty(Token))
217 new KeyValuePair<string, string>(
"Authorization",
"Bearer " + Token),
222 Log.
Error(
"Sending paiwise request to " + this.url +
", but an error was returned:" + ex.Message +
223 "\r\n\r\n" +
JSON.
Encode(Request,
true),
new KeyValuePair<string, object>(
"Token",
"Token"));
228 if (!(Result is Dictionary<string, object> Response) ||
229 !Response.TryGetValue(
"status", out Obj) || !(Obj is
string Status))
231 return new PaymentResult(
"Invalid response returned from Paiwise");
234 if (Status ==
"paid")
236 if (Response.TryGetValue(
"amount", out Obj) && IsDecimal(Obj, out decimal Amount2))
239 if (Response.TryGetValue(
"currency", out Obj) && Obj is
string Currency2)
240 Currency = Currency2;
245 if (
int.TryParse(Status, out _) &&
246 Response.TryGetValue(
"message", out Obj) && Obj is
string Message)
251 if (Status ==
"pending")
253 if (!Response.TryGetValue(
"id", out Obj) || !(Obj is
string TransactionId))
256 if (Response.TryGetValue(
"redirectUrl", out Obj) && Obj is
string RedirectUrl)
258 if (ClientUrlCallback is
null)
259 return new PaymentResult(
"No Client URL callback method defined.");
273 StringBuilder Url =
new StringBuilder();
275 Url.Append(
"https://");
276 Url.Append(this.host);
277 Url.Append(
"/payment/retrieve");
279 Request =
new Dictionary<string, object>()
281 {
"id", TransactionId }
284 DateTime Start = DateTime.Now;
286 while (Status ==
"pending" && DateTime.Now.Subtract(Start).TotalMinutes < TimeoutMinutes)
288 await Task.Delay(2000);
291 new KeyValuePair<string, string>(
"Authorization",
"Bearer " + Token),
294 if (!(Obj is Dictionary<string, object> Response2) ||
295 !Response2.TryGetValue(
"status", out Obj) || !(Obj is
string Status2))
297 return new PaymentResult(
"Invalid polling response returned from Paiwise");
300 if (
int.TryParse(Status2, out _) &&
301 Response2.TryGetValue(
"message", out Obj) && Obj is
string Message2)
308 if (Status ==
"paid" &&
309 Response2.TryGetValue(
"request", out Obj) &&
310 Obj is Dictionary<string, object> Request2)
312 if (Request2.TryGetValue(
"amount", out Obj) && IsDecimal(Obj, out decimal Amount2))
315 if (Request2.TryGetValue(
"currency", out Obj) && Obj is
string Currency2)
316 Currency = Currency2;
320 if (Status ==
"pending")
322 await Task.Delay(2000);
325 Url.Append(
"https://");
326 Url.Append(this.host);
327 Url.Append(
"/payment/cancel");
330 new KeyValuePair<string, string>(
"Authorization",
"Bearer " + Token),
333 if (!(Obj is Dictionary<string, object> Response2) ||
334 !Response2.TryGetValue(
"status", out Obj) || !(Obj is
string Status2))
336 return new PaymentResult(
"Invalid cancel response returned from Paiwise");
339 if (
int.TryParse(Status2, out _) &&
340 Response2.TryGetValue(
"message", out Obj) && Obj is
string Message2)
349 if (Status ==
"paid")
351 else if (Status ==
"cancelled")
363 IDictionary<CaseInsensitiveString, CaseInsensitiveString> IdentityProperties, ref
bool First)
376 private static bool IsDecimal(
object Obj, out decimal Result)
383 else if (Obj is decimal d)
388 else if (Obj is
double d2)
390 Result = (decimal)d2;
413 IDictionary<CaseInsensitiveString, CaseInsensitiveString> IdentityProperties,
414 string SuccessUrl,
string FailureUrl,
string CancelUrl,
419 if (
string.IsNullOrEmpty(this.optionsUrl))
420 return new IDictionary<CaseInsensitiveString, object>[0];
422 Dictionary<string, object> Request =
new Dictionary<string, object>()
424 {
"webhook", string.Empty }
430 Request[Field.
FieldId] = Obj2.Value;
436 Request[Field.
FieldId] = SuccessUrl ??
string.Empty;
439 case "returnerrorurl":
440 Request[Field.
FieldId] = FailureUrl ??
string.Empty;
444 Request[Field.
FieldId] = CancelUrl ??
string.Empty;
448 StringBuilder sb =
new StringBuilder();
451 Append(sb,
"FIRST", IdentityProperties, ref First);
452 Append(sb,
"MIDDLE", IdentityProperties, ref First);
453 Append(sb,
"LAST", IdentityProperties, ref First);
456 Append(sb,
"PNR", IdentityProperties, ref First);
458 Request[Field.
FieldId] = sb.ToString();
462 sb =
new StringBuilder();
465 Append(sb,
"ADDR", IdentityProperties, ref First);
467 Request[Field.
FieldId] = sb.ToString();
478 if (
string.IsNullOrEmpty(Token))
479 return new IDictionary<CaseInsensitiveString, object>[0];
486 new KeyValuePair<string, string>(
"Authorization",
"Bearer " + Token),
491 Log.
Error(
"Requesting payment options for paiwise request to " + this.optionsUrl +
", but an error was returned:" + ex.Message +
492 "\r\n\r\n" +
JSON.
Encode(Request,
true),
new KeyValuePair<string, object>(
"Token",
"Token"));
494 return new IDictionary<CaseInsensitiveString, object>[0];
497 if (!(Result is Array A))
498 throw new Exception(
"Unexpected response type returned: " + Result.GetType().FullName);
500 List<IDictionary<CaseInsensitiveString, object>> Options =
new List<IDictionary<CaseInsensitiveString, object>>();
502 foreach (
object Item
in A)
504 if (Item is Dictionary<string, object> Option)
506 Dictionary<CaseInsensitiveString, object> Properties =
new Dictionary<CaseInsensitiveString, object>();
508 foreach (KeyValuePair<string, object> P
in Option)
509 Properties[P.Key] = P.Value;
511 Options.Add(Properties);
515 return Options.ToArray();
521 new KeyValuePair<string, object>(
"ServiceId",
this.Id),
522 new KeyValuePair<string, object>(
"URL",
this.optionsUrl));
524 return new IDictionary<CaseInsensitiveString, object>[0];
Event arguments for callback methods with the aim of pushing a URL to a client.
Payment services made available by Paiwise.
const string TimeoutMinutesSetting
Settings key for Paiwise timeout, in minutes.
const string TokenIdSetting
Settings key for Paiwise token.
Reference to a Paiwise payment service.
SellEDalerPaymentService(string Id, string Name, string IconUrl, int IconWidth, int IconHeight, string TemplateContractId, string Method, string Url, string OptionsUrl, ServiceField[] Fields, string Host, ISellEDalerServiceProvider Provider)
Reference to a Paiwise payment service.
string SellEDalerTemplateContractId
Optional Contract ID of Template, for selling e-Daler
async Task< bool > CanSellEDaler(CaseInsensitiveString AccountName)
If the service provider can be used to process a request to sell eDaler of a certain amount,...
async Task< PaymentResult > SellEDaler(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 selling eDaler.
Grade Supports(CaseInsensitiveString Currency)
Checks if the service provider supports a given currency.
async 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.
Represents a field in a service.
bool Required
If the field is required or not.
CaseInsensitiveString FieldId
Field ID
Result of request payment.
Contains information about a service provider that users can use to sell eDaler.
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.
static string Encode(string s)
Encodes a string for inclusion in JSON.
const string DefaultContentType
application/json
Static class managing the application event log. Applications and services log events on this static ...
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.
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.
Static class managing the runtime environment of the IoT Gateway.
static X509Certificate2 Certificate
Domain certificate.
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 sell eDaler.
Interface for information about a service provider that users can use to sell eDaler.
delegate Task ClientUrlEventHandler(object Sender, ClientUrlEventArgs e)
Delegat for client URL callback methods.