using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Text; using System.Threading.Tasks; using System.Linq; namespace Knoks.Framework.Services { public class DocuSigneSignatureService : ISignatureService, IDisposable { public string ServiceProvider { get { return "DocuSign"; } } public string Version { get { return "1.0.0"; } } private readonly ILogger _logger; private readonly DocuSigneSignatureSettings _settings; private readonly HttpClient _httpClient; private string _baseUrl; public DocuSigneSignatureService(ILogger logger, DocuSigneSignatureSettings settings) { _logger = logger; _settings = settings; _httpClient = new HttpClient(); _httpClient.BaseAddress = new Uri(settings.EndpointUrl); _httpClient.DefaultRequestHeaders.Add("X-DocuSign-Authentication", JsonConvert.SerializeObject(new { Username = settings.Username, Password = settings.Password, IntegratorKey = settings.IntegratorKey })); } public async Task GetBaseUrl() { try { var res = await _httpClient.GetAsync("login_information"); var jsonResult = await res.Content.ReadAsStringAsync(); return JObject.Parse(jsonResult).SelectToken("loginAccounts[0].baseUrl").Value(); } catch (Exception ex) { _logger.LogError(ex.Message); throw; } } public async Task RequestSignature(SignatureEntityInput signatureEntityInput, object settings = null) { try { if (signatureEntityInput.ServiceProvider != ServiceProvider) throw new InvalidOperationException($"Invalid {nameof(signatureEntityInput.ServiceProvider)} value '{signatureEntityInput.ServiceProvider}'. an expected one is '{ServiceProvider}'"); if (signatureEntityInput.Version != Version) throw new InvalidOperationException($"Invalid {nameof(signatureEntityInput.Version)} value '{signatureEntityInput.Version}'. an expected one is '{Version}'"); var docuSignEnvelope = signatureEntityInput.Input as JObject; if (docuSignEnvelope == null) throw new InvalidOperationException($"Invalid {nameof(signatureEntityInput.Input)} type '{signatureEntityInput.Input.GetType().FullName}'. an expected one is '{typeof(JObject).FullName}'"); var properties = (JObject)settings; if (settings != null) foreach (var item in (JObject)properties["JsonPlacement"]) ((JProperty)docuSignEnvelope.SelectToken(item.Key).Parent).Value = item.Value; await SetEventNotificationUrl(docuSignEnvelope, properties["LoanRequestAndUserUid"]?.Value()); if (_baseUrl == null) _baseUrl = await GetBaseUrl(); return new SignatureEntityOutput { ServiceProvider = ServiceProvider, Version = Version, Output = (await SendPostRequest(_baseUrl + "/envelopes", docuSignEnvelope)).ContentObject }; } catch (Exception ex) { _logger.LogError(ex.Message); throw; } } //Install and run "ngrok agent" on your local machine for redirect webhoock to localhost. //Additional info on https://ngrok.com/ //Command example when application running on IIS or IIS Express: ngrok http 52281 -host-header="localhost:52281" //Cammand example when application running as self hosted hosted on 5000 port: ngrok http 5000 private async Task SetEventNotificationUrl(JObject jObj, string loanRequestAndUserUid) { if (jObj.SelectToken("eventNotification.url") == null) return; var uri = new Uri(_settings.EventNotificationUrl, UriKind.Absolute); if (uri.Host.Equals("ngrok", StringComparison.OrdinalIgnoreCase)) { using (var httpClient = new HttpClient()) { var res = JObject.Parse(await httpClient.GetStringAsync("http://127.0.0.1:4040/api/tunnels")); var ngrokUrl = res["tunnels"].Where(item => item["proto"].Value() == uri.Scheme).Single()["public_url"].Value(); uri = new Uri(ngrokUrl + uri.PathAndQuery, UriKind.Absolute); } } jObj["eventNotification"]["url"] = uri.ToString().Replace($"{{{nameof(loanRequestAndUserUid)}}}", loanRequestAndUserUid ?? string.Empty); } public async Task GetRecipientViewUrl(IDictionary properties) { return (await SendPostRequest(_baseUrl + $"/envelopes/{properties["envelopeId"]}/views/recipient", JsonConvert.SerializeObject(new { authenticationMethod = properties["authenticationMethod"], clientUserId = properties["clientUserId"], email = properties["email"], recipientId = properties["recipientId"], userName = properties["name"] }))).ContentObject; } private class SendPostResult { public HttpRequestMessage RequestMessage; public HttpResponseMessage ResponseMessage; public string ContentString; public JObject ContentObject; public HttpStatusCode StatusCode { get { return ResponseMessage.StatusCode; } } } private async Task SendPostRequest(string url, object body, HttpStatusCode expectedStatusCode = HttpStatusCode.Created, bool throwExceptionOnUnexpectedStatusCode = true) { //- request envelope -- var request = new HttpRequestMessage(HttpMethod.Post, url); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); request.Content = new StringContent(body.ToString(), Encoding.UTF8, "application/json"); //- response envelope -- var responce = await _httpClient.SendAsync(request); var contentString = await responce.Content.ReadAsStringAsync(); var contentObject = JObject.Parse(await responce.Content.ReadAsStringAsync()); if (throwExceptionOnUnexpectedStatusCode && responce.StatusCode != HttpStatusCode.Created) { throw new InvalidOperationException(new JObject { ["HttpStatusCode"] = (int)responce.StatusCode, ["Content"] = contentObject }.ToString()); } return new SendPostResult { RequestMessage = request, ResponseMessage = responce, ContentString = contentString, ContentObject = contentObject }; } public void Dispose() { if (_httpClient != null) _httpClient.Dispose(); } } }