4using System.Text.RegularExpressions;
5using System.Threading.Tasks;
22 private readonly
string method;
23 private readonly
string url;
24 private readonly
string optionsUrl;
25 private readonly
string host;
43 int IconHeight,
string TemplateContractId,
string Method,
string Url,
string OptionsUrl,
49 this.optionsUrl = OptionsUrl;
53 this.SellEDalerTemplateContractId = TemplateContractId;
54 this.SellEDalerServiceProvider = Provider;
75 Currency.Length == 3 ? Grade.Ok :
Grade.NotAtAll;
86 if (
string.Compare(this.method,
"POST",
true) != 0)
91 return !
string.IsNullOrEmpty(Token);
94 private static readonly Regex PaiwiseProcessorRegex =
new Regex(
@"Waher\.Service\.IoTBroker\.Paiwise\.PaiwiseProcessor[.+]<?(SellEDaler|SellEDaler)>?\w*([.]\w*)?",
95 RegexOptions.Compiled | RegexOptions.Singleline);
97 private static readonly Regex UnitTestRegex =
new Regex(
@"Paiwise\.Internal\.Test\.PaiwiseTests[.+]<?(Test_07_ProcessSellEDalerUsingContract|Test_08_ProcessSellEDalerUsingClientUrl)>?\w*([.]\w*)?",
98 RegexOptions.Compiled | RegexOptions.Singleline);
100 private static readonly
object[] approvedSources =
new object[]
102 PaiwiseProcessorRegex,
122 public async Task<PaymentResult>
SellEDaler(IDictionary<CaseInsensitiveString, object> ContractParameters,
123 IDictionary<CaseInsensitiveString, CaseInsensitiveString> IdentityProperties,
124 decimal Amount,
string Currency,
string SuccessUrl,
string FailureUrl,
string CancelUrl,
125 EventHandlerAsync<ClientUrlEventArgs> 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();
186 Request[Field.
FieldId] = sb.ToString();
190 sb =
new StringBuilder();
195 Request[Field.
FieldId] = sb.ToString();
200 Request[Field.
FieldId] =
string.Empty;
209 if (
string.IsNullOrEmpty(Token))
213 new KeyValuePair<string, string>(
"Authorization",
"Bearer " + Token),
218 Log.
Error(
"Sending paiwise request to " + this.url +
", but an error was returned:" + Result.
Error.Message);
222 if (!(Result.
Decoded is Dictionary<string, object> Response) ||
223 !Response.TryGetValue(
"status", out Obj) || !(Obj is
string Status))
225 return new PaymentResult(
"Invalid response returned from Paiwise");
228 if (Status ==
"paid")
230 if (Response.TryGetValue(
"amount", out Obj) && IsDecimal(Obj, out decimal Amount2))
233 if (Response.TryGetValue(
"currency", out Obj) && Obj is
string Currency2)
234 Currency = Currency2;
239 if (
int.TryParse(Status, out
_) &&
240 Response.TryGetValue(
"message", out Obj) && Obj is
string Message)
245 if (Status ==
"pending")
247 if (!Response.TryGetValue(
"id", out Obj) || !(Obj is
string TransactionId))
250 if (Response.TryGetValue(
"redirectUrl", out Obj) && Obj is
string RedirectUrl)
252 if (ClientUrlCallback is
null)
253 return new PaymentResult(
"No Client URL callback method defined.");
260 StringBuilder Url =
new StringBuilder();
262 Url.Append(
"https://");
263 Url.Append(this.host);
264 Url.Append(
"/payment/retrieve");
266 Request =
new Dictionary<string, object>()
268 {
"id", TransactionId }
271 DateTime Start = DateTime.Now;
273 while (Status ==
"pending" && DateTime.Now.Subtract(Start).TotalMinutes < TimeoutMinutes)
275 await Task.Delay(2000);
278 new KeyValuePair<string, string>(
"Authorization",
"Bearer " + Token),
284 if (!(Result.
Decoded is Dictionary<string, object> Response2) ||
285 !Response2.TryGetValue(
"status", out Obj) || !(Obj is
string Status2))
287 return new PaymentResult(
"Invalid polling response returned from Paiwise");
290 if (
int.TryParse(Status2, out
_) &&
291 Response2.TryGetValue(
"message", out Obj) && Obj is
string Message2)
298 if (Status ==
"paid" &&
299 Response2.TryGetValue(
"request", out Obj) &&
300 Obj is Dictionary<string, object> Request2)
302 if (Request2.TryGetValue(
"amount", out Obj) && IsDecimal(Obj, out decimal Amount2))
305 if (Request2.TryGetValue(
"currency", out Obj) && Obj is
string Currency2)
306 Currency = Currency2;
310 if (Status ==
"pending")
312 await Task.Delay(2000);
315 Url.Append(
"https://");
316 Url.Append(this.host);
317 Url.Append(
"/payment/cancel");
320 new KeyValuePair<string, string>(
"Authorization",
"Bearer " + Token),
326 if (!(Result.
Decoded is Dictionary<string, object> Response2) ||
327 !Response2.TryGetValue(
"status", out Obj) || !(Obj is
string Status2))
329 return new PaymentResult(
"Invalid cancel response returned from Paiwise");
332 if (
int.TryParse(Status2, out
_) &&
333 Response2.TryGetValue(
"message", out Obj) && Obj is
string Message2)
342 if (Status ==
"paid")
344 else if (Status ==
"cancelled")
356 IDictionary<CaseInsensitiveString, CaseInsensitiveString> IdentityProperties, ref
bool First)
369 private static bool IsDecimal(
object Obj, out decimal Result)
376 else if (Obj is decimal d)
381 else if (Obj is
double d2)
383 Result = (decimal)d2;
406 IDictionary<CaseInsensitiveString, CaseInsensitiveString> IdentityProperties,
407 string SuccessUrl,
string FailureUrl,
string CancelUrl,
408 EventHandlerAsync<ClientUrlEventArgs> ClientUrlCallback,
object State)
412 if (
string.IsNullOrEmpty(this.optionsUrl))
413 return Array.Empty<IDictionary<CaseInsensitiveString, object>>();
415 Dictionary<string, object> Request =
new Dictionary<string, object>()
417 {
"webhook", string.Empty }
423 Request[Field.
FieldId] = Obj2.Value;
429 Request[Field.
FieldId] = SuccessUrl ??
string.Empty;
432 case "returnerrorurl":
433 Request[Field.
FieldId] = FailureUrl ??
string.Empty;
437 Request[Field.
FieldId] = CancelUrl ??
string.Empty;
441 StringBuilder sb =
new StringBuilder();
451 Request[Field.
FieldId] = sb.ToString();
455 sb =
new StringBuilder();
460 Request[Field.
FieldId] = sb.ToString();
471 if (
string.IsNullOrEmpty(Token))
472 return Array.Empty<IDictionary<CaseInsensitiveString, object>>();
475 new KeyValuePair<string, string>(
"Authorization",
"Bearer " + Token),
480 Log.
Error(
"Requesting payment options for paiwise request to " + this.optionsUrl +
", but an error was returned:" + Result.Error.Message);
481 return Array.Empty<IDictionary<CaseInsensitiveString, object>>();
484 if (!(Result.Decoded is Array A))
485 throw new Exception(
"Unexpected response type returned: " + Result.GetType().FullName);
487 List<IDictionary<CaseInsensitiveString, object>> Options =
new List<IDictionary<CaseInsensitiveString, object>>();
489 foreach (
object Item
in A)
491 if (Item is Dictionary<string, object> Option)
493 Dictionary<CaseInsensitiveString, object> Properties =
new Dictionary<CaseInsensitiveString, object>();
495 foreach (KeyValuePair<string, object> P
in Option)
496 Properties[P.Key] = P.Value;
498 Options.Add(Properties);
502 return Options.ToArray();
508 new KeyValuePair<string, object>(
"ServiceId",
this.Id),
509 new KeyValuePair<string, object>(
"URL",
this.optionsUrl));
511 return Array.Empty<IDictionary<CaseInsensitiveString, object>>();
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, EventHandlerAsync< ClientUrlEventArgs > ClientUrlCallback, object State)
Processes payment for selling eDaler.
async 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.
Grade Supports(CaseInsensitiveString Currency)
Checks if the service provider supports a given currency.
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.
Contains information about a response to a content request.
bool HasError
If an error occurred.
object Decoded
Decoded object.
Exception Error
Error response.
Static class managing encoding and decoding of internet content.
static Task< ContentResponse > PostAsync(Uri Uri, object Data, params KeyValuePair< string, string >[] Headers)
Posts to a resource, using a Uniform Resource Identifier (or Locator).
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.