first commit
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
using System.Reflection;
|
||||
using System.ServiceModel;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
using CRIF.ACTion.Configuration;
|
||||
|
||||
namespace CRIF.ACTion.Services.Crif
|
||||
{
|
||||
public class CrifService : ICrifService
|
||||
{
|
||||
private ConnectorServiceClient serviceClient;
|
||||
private readonly IWcfInspectorBehavior WcfInspectorBehavior;
|
||||
private readonly CrifSettings _settings;
|
||||
|
||||
public CrifService(IWcfInspectorBehavior wcfInspectorBehavior,
|
||||
IOptions<CrifSettings> settings)
|
||||
{
|
||||
WcfInspectorBehavior = wcfInspectorBehavior;
|
||||
|
||||
_settings = settings.Value;
|
||||
IsAnyNullOrEmpty(_settings.ApiSettings);
|
||||
|
||||
CreateServiceClient();
|
||||
}
|
||||
|
||||
private void CreateServiceClient()
|
||||
{
|
||||
Log.Debug("Creating client binding and object");
|
||||
|
||||
var binding = new BasicHttpsBinding
|
||||
{
|
||||
MaxBufferSize = int.MaxValue,
|
||||
ReaderQuotas = System.Xml.XmlDictionaryReaderQuotas.Max,
|
||||
MaxReceivedMessageSize = int.MaxValue,
|
||||
AllowCookies = true,
|
||||
SendTimeout = CrifConstants.SubmissionTimeoutInMinutes
|
||||
};
|
||||
|
||||
var endpoint = new EndpointAddress(_settings.ApiSettings.EndpointUrl);
|
||||
serviceClient = new ConnectorServiceClient(binding, endpoint);
|
||||
|
||||
if (Log.IsEnabled(LogEventLevel.Debug))
|
||||
{
|
||||
serviceClient.Endpoint.EndpointBehaviors.Add(WcfInspectorBehavior);
|
||||
}
|
||||
}
|
||||
|
||||
public btMessage BuildMessage(XElement actionApi)
|
||||
{
|
||||
Log.Information("Building btMessage");
|
||||
|
||||
btMessage message = new btMessage
|
||||
{
|
||||
body = actionApi,
|
||||
routing = new Routing
|
||||
{
|
||||
org = new Org
|
||||
{
|
||||
orgCode = _settings.ApiSettings.OrgCode,
|
||||
orgID = _settings.ApiSettings.OrgId,
|
||||
orgIDSpecified = true,
|
||||
},
|
||||
user = new User
|
||||
{
|
||||
userName = _settings.ApiSettings.Username,
|
||||
},
|
||||
@interface = new Interface
|
||||
{
|
||||
interfaceID = _settings.ApiSettings.InterfaceId,
|
||||
interfaceIDSpecified = true,
|
||||
},
|
||||
action = new CRIF.ACTion.Action
|
||||
{
|
||||
transactionType = "API",
|
||||
},
|
||||
system = new ApproSystem(), // empty object required
|
||||
approResult = new ApproResult(), // empty object required
|
||||
}
|
||||
};
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
public async Task<XElement> SendMessage(XElement request)
|
||||
{
|
||||
var message = BuildMessage(request);
|
||||
XElement body;
|
||||
try
|
||||
{
|
||||
Log.Information("Sending message to CRIF");
|
||||
var response = await serviceClient.SubmitExAsync(message);
|
||||
|
||||
if (response.routing.approResult.exceptionCodeSpecified)
|
||||
{
|
||||
//Note that approResult.result will likely equal ApproResultEnum.Success
|
||||
//even with a failed submission
|
||||
|
||||
var errorResult = response.routing.approResult;
|
||||
var errorText = "CRIF responded with error " + errorResult.exceptionCode + " - " + errorResult.exceptionText;
|
||||
if (errorResult.exceptionCode == 13)
|
||||
{
|
||||
// Go ahead and catch the error 13 that CRIF won't resolve.
|
||||
throw new Error13Exception(errorText + "[KnownReoccuringError]");
|
||||
} else {
|
||||
throw new FormatException("CRIF responded with error " +
|
||||
errorResult.exceptionCode + " - " + errorResult.exceptionText);
|
||||
}
|
||||
}
|
||||
|
||||
body = response.body;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Debug("{exception}", e.Message);
|
||||
throw;
|
||||
}
|
||||
|
||||
return body;
|
||||
}
|
||||
|
||||
public static bool IsAnyNullOrEmpty(ApiConnectionSettings myObject)
|
||||
{
|
||||
foreach (PropertyInfo pi in myObject.GetType().GetProperties())
|
||||
{
|
||||
if (pi.PropertyType == typeof(string))
|
||||
{
|
||||
string value = (string)pi.GetValue(myObject);
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
throw new ArgumentNullException(pi.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
using Serilog;
|
||||
using CRIF.ACTion.Common.Parsers;
|
||||
using CRIF.ACTion.Configuration;
|
||||
using CRIF.ACTion.Decisioning;
|
||||
using CRIF.ACTion.Files;
|
||||
using CRIF.ACTion.Loan;
|
||||
using CRIF.ACTion.Loan.Individuals;
|
||||
using CRIF.ACTion.Services.Landmark.DistributionChannel;
|
||||
using Landmark.OLA.Models;
|
||||
using Landmark.OLA.Models.Decisioning;
|
||||
using Landmark.OLA.Models.Decisioning.Enums;
|
||||
using Landmark.OLA.Models.Individuals;
|
||||
using Landmark.OLA.Models.Loan;
|
||||
|
||||
namespace CRIF.ACTion.Services.Crif
|
||||
{
|
||||
public class CrifVendorClient : ICrifVendorClient
|
||||
{
|
||||
private readonly string _getCustomFieldsString = "getCustomFields";
|
||||
private readonly string _getProductDetailsString = "getProducts";
|
||||
private readonly int retryLimit = 6;
|
||||
private readonly ICrifService _crifService;
|
||||
private readonly IResponseParser<Individual> _individualResponseParser;
|
||||
private readonly IResponseParser<LoanApplication> _loanApplicationResponseParser;
|
||||
private readonly IResponseParser<DecisioningState> _decisioningStateResponseParser;
|
||||
private readonly IResponseParser<DecisioningDetails> _decisioningDetailsResponseParser;
|
||||
private readonly IResponseParser<SortedList<int, string>> _configurationResponseParser;
|
||||
private readonly IResponseParser<Dictionary<string, string>> _productDetailsResponseParser;
|
||||
private readonly IResponseParser<CustomFieldConfigs> _customFieldResponseParser;
|
||||
private readonly IResponseParser<DealershipAuthRequest> _dealershipResponseParser;
|
||||
private readonly CrifSettings _settings;
|
||||
|
||||
public CrifVendorClient(ICrifService crifService,
|
||||
IResponseParser<Individual> individualResponseParser,
|
||||
IResponseParser<LoanApplication> loanApplicationResponseParser,
|
||||
IResponseParser<DecisioningState> decisioningStateResponseParser,
|
||||
IResponseParser<DecisioningDetails> decisioningDetailsResponseParser,
|
||||
IResponseParser<SortedList<int, string>> crifConfigurationResponseParser,
|
||||
IResponseParser<Dictionary<string, string>> productDetailsResponseParser,
|
||||
IResponseParser<CustomFieldConfigs> customFieldResponseParser,
|
||||
IResponseParser<DealershipAuthRequest> dealershipResponseParser,
|
||||
IOptions<CrifSettings> settings)
|
||||
{
|
||||
_crifService = crifService;
|
||||
_individualResponseParser = individualResponseParser;
|
||||
_loanApplicationResponseParser = loanApplicationResponseParser;
|
||||
_decisioningStateResponseParser = decisioningStateResponseParser;
|
||||
_decisioningDetailsResponseParser = decisioningDetailsResponseParser;
|
||||
_configurationResponseParser = crifConfigurationResponseParser;
|
||||
_productDetailsResponseParser = productDetailsResponseParser;
|
||||
_customFieldResponseParser = customFieldResponseParser;
|
||||
_dealershipResponseParser = dealershipResponseParser;
|
||||
_settings = settings.Value;
|
||||
_settings.CustomFields = GetCustomFieldConfiguration().GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public async Task<DealershipAuthRequest> GetDealershipAuthDetails(string applicationId)
|
||||
{
|
||||
var getApp = new DealershipRequestBuilder(applicationId, _settings);
|
||||
|
||||
var xmlApp = getApp.Build();
|
||||
|
||||
try
|
||||
{
|
||||
var response = await _crifService.SendMessage(xmlApp);
|
||||
return _dealershipResponseParser.Parse(response);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Fatal("Error parsing response: " + e.Message);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<LoanApplication> CreateApplication(LoanApplication loanApplication, IDistributionChannelLookupService channelService = null)
|
||||
{
|
||||
Log.Debug("Building Loan Application");
|
||||
|
||||
ModelValidator.Validate(loanApplication);
|
||||
|
||||
var createApp = new LoanApplicationRequestBuilder(loanApplication, _settings, null, null, null, channelService);
|
||||
|
||||
var xmlApp = createApp.Build();
|
||||
|
||||
var response = await _crifService.SendMessage(xmlApp);
|
||||
|
||||
try
|
||||
{
|
||||
return _loanApplicationResponseParser.Parse(response);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Log.Fatal("Error parsing response.");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> CreateApplicationAndGetId(LoanApplication loanApplication, IDistributionChannelLookupService channelService = null)
|
||||
{
|
||||
var parsedLoanApplication = await CreateApplication(loanApplication, channelService);
|
||||
return parsedLoanApplication.Id;
|
||||
}
|
||||
|
||||
public async Task<bool> AddFile(string applicationId, LoanApplicationFile file, string fileBase64Value)
|
||||
{
|
||||
try
|
||||
{
|
||||
var addFile = new FileRequestBuilder(applicationId, file, fileBase64Value, _settings);
|
||||
var response = await _crifService.SendMessage(addFile.Build());
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Fatal("Error uploading file to loan application: " + applicationId);
|
||||
Log.Fatal(e.ToString());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<Individual> AddIndividual(string applicationId, Individual individual)
|
||||
{
|
||||
ModelValidator.Validate(individual);
|
||||
|
||||
var addIndividual = new AddIndividualRequestBuilder(applicationId, individual, _settings);
|
||||
|
||||
var response = await _crifService.SendMessage(addIndividual.Build());
|
||||
|
||||
try
|
||||
{
|
||||
return _individualResponseParser.Parse(response);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Log.Fatal("Error parsing response.");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> SendToDecisioning(string applicationId)
|
||||
{
|
||||
var sendToDecisioning = new SendToDecisioningRequestBuilder(applicationId, _settings);
|
||||
|
||||
var response = await _crifService.SendMessage(sendToDecisioning.Build());
|
||||
|
||||
//TODO: This cast is not particularly Dependency Injection-y
|
||||
//TODO: Requiring the Application Id is really just a double check, and may not matter all that much
|
||||
((DecisioningStateResponseParser)_decisioningStateResponseParser).SetLoanId(applicationId);
|
||||
|
||||
var result = _decisioningStateResponseParser.Parse(response);
|
||||
|
||||
if (result == DecisioningState.MissingRequiredInformation)
|
||||
{
|
||||
throw new InvalidOperationException("Loan application cannot be sent to decisioning, and is likely missing required information.");
|
||||
}
|
||||
|
||||
if (result == DecisioningState.Requested || result == DecisioningState.Ready)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<DecisioningDetails> GetDecisionStatus(string applicationId)
|
||||
{
|
||||
//TODO: This cast is not particularly Dependency Injection-y
|
||||
//TODO: Requiring the Application Id is really just a double check, and may not matter all that much
|
||||
((GetApplicationProductsResponseParser)_decisioningDetailsResponseParser).SetLoanId(applicationId);
|
||||
|
||||
var result = new DecisioningDetails()
|
||||
{
|
||||
DecisioningState = DecisioningState.Unknown
|
||||
};
|
||||
int retryAttempts = 0;
|
||||
Thread.Sleep(2500);
|
||||
|
||||
do
|
||||
{
|
||||
Thread.Sleep((retryAttempts * 200) + 1500);
|
||||
var getDecisionStatus = new GetApplicationProductsRequestBuilder(applicationId, _settings);
|
||||
var response = await _crifService.SendMessage(getDecisionStatus.Build());
|
||||
result = _decisioningDetailsResponseParser.Parse(response);
|
||||
retryAttempts++;
|
||||
} while (retryAttempts < retryLimit && result.DecisioningState == DecisioningState.Unknown);
|
||||
|
||||
if (retryAttempts >= retryLimit && result.DecisioningState == DecisioningState.Unknown)
|
||||
{
|
||||
result.DecisioningState = DecisioningState.Ready;
|
||||
result.DecisioningResult = DecisioningResult.DecisionStatus_Review;
|
||||
}
|
||||
|
||||
Log.Information($"Application ID: {applicationId}, Number of Attempts to Get DecisionStatus: {retryAttempts}");
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<SortedList<int, string>> GetBranchNameConfiguration(string ConfigurationSectionName)
|
||||
{
|
||||
var configurationRequest = new ConfigurationRequestBuilder(ConfigurationSectionName, _settings);
|
||||
|
||||
var response = await _crifService.SendMessage(configurationRequest.Build());
|
||||
|
||||
var result = _configurationResponseParser.Parse(response);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<CustomFieldConfigs> GetCustomFieldConfiguration()
|
||||
{
|
||||
CustomFieldConfigs customFieldConfig = new CustomFieldConfigs();
|
||||
string municipalityTypeId = null;
|
||||
string municipalityWhereKeptId = null;
|
||||
|
||||
try
|
||||
{
|
||||
var configurationRequest = new ConfigurationRequestBuilder(_getCustomFieldsString, _settings);
|
||||
|
||||
var response = await _crifService.SendMessage(configurationRequest.Build());
|
||||
|
||||
customFieldConfig = _customFieldResponseParser.Parse(response);
|
||||
|
||||
if (_settings?.CustomFieldMappings?.CustomFieldID != null)
|
||||
{
|
||||
_settings.CustomFieldMappings.CustomFieldID.TryGetValue("MunicipalityType", out municipalityTypeId);
|
||||
_settings.CustomFieldMappings.CustomFieldID.TryGetValue("MunicipalityWhereKept", out municipalityWhereKeptId);
|
||||
}
|
||||
|
||||
Log.Information(
|
||||
"CRIF custom field mappings loaded: MunicipalityType={MunicipalityTypeId}, MunicipalityWhereKept={MunicipalityWhereKeptId}",
|
||||
municipalityTypeId ?? "<missing>",
|
||||
municipalityWhereKeptId ?? "<missing>");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Fatal("Error parsing CRIF custom fields configuration response." + ex.StackTrace);
|
||||
}
|
||||
|
||||
customFieldConfig.ProductIdToCategoryId = await GetProductDetailsConfiguration();
|
||||
|
||||
return customFieldConfig;
|
||||
}
|
||||
|
||||
public async Task<Dictionary<string, string>> GetProductDetailsConfiguration()
|
||||
{
|
||||
Dictionary<string, string> mappingList = [];
|
||||
|
||||
try
|
||||
{
|
||||
var configurationRequest = new ConfigurationRequestBuilder(_getProductDetailsString, _settings);
|
||||
|
||||
var response = await _crifService.SendMessage(configurationRequest.Build());
|
||||
|
||||
mappingList = _productDetailsResponseParser.Parse(response);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Fatal("Error parsing CRIF custom fields configuration response." + ex.StackTrace);
|
||||
}
|
||||
|
||||
return mappingList;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
|
||||
namespace CRIF.ACTion.Services.Crif
|
||||
{
|
||||
public class Error13Exception : Exception
|
||||
{
|
||||
public Error13Exception(string message) : base(message)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace CRIF.ACTion.Services.Crif
|
||||
{
|
||||
public interface ICrifService
|
||||
{
|
||||
btMessage BuildMessage(XElement actionApi);
|
||||
|
||||
Task<XElement> SendMessage(XElement request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using CRIF.ACTion.Configuration;
|
||||
using CRIF.ACTion.Services.Landmark.DistributionChannel;
|
||||
using Landmark.OLA.Models.Decisioning;
|
||||
using Landmark.OLA.Models.Individuals;
|
||||
using Landmark.OLA.Models.Loan;
|
||||
|
||||
namespace CRIF.ACTion.Services.Crif
|
||||
{
|
||||
public interface ICrifVendorClient
|
||||
{
|
||||
Task<DealershipAuthRequest> GetDealershipAuthDetails(string applicationId);
|
||||
|
||||
Task<LoanApplication> CreateApplication(LoanApplication loanApplication, IDistributionChannelLookupService channelService);
|
||||
|
||||
Task<string> CreateApplicationAndGetId(LoanApplication loanApplication, IDistributionChannelLookupService channelService = null);
|
||||
|
||||
Task<bool> AddFile(string applicationId, LoanApplicationFile file, string fileBase64Value);
|
||||
|
||||
Task<Individual> AddIndividual(string applicationId, Individual individual);
|
||||
|
||||
Task<bool> SendToDecisioning(string applicationId);
|
||||
|
||||
Task<DecisioningDetails> GetDecisionStatus(string applicationId);
|
||||
|
||||
Task<SortedList<int, string>> GetBranchNameConfiguration(string ConfigurationSectionName);
|
||||
|
||||
Task<CustomFieldConfigs> GetCustomFieldConfiguration();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using System.ServiceModel.Description;
|
||||
using System.ServiceModel.Dispatcher;
|
||||
|
||||
namespace CRIF.ACTion.Services.Crif
|
||||
{
|
||||
public interface IWcfInspectorBehavior: IClientMessageInspector, IEndpointBehavior
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using System.ServiceModel;
|
||||
using System.ServiceModel.Channels;
|
||||
using System.ServiceModel.Description;
|
||||
using System.ServiceModel.Dispatcher;
|
||||
using Serilog;
|
||||
|
||||
namespace CRIF.ACTion.Services.Crif
|
||||
{
|
||||
public class WcfInspectorBehavior : IWcfInspectorBehavior
|
||||
{
|
||||
public WcfInspectorBehavior()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public void AfterReceiveReply(ref Message reply, object correlationState)
|
||||
{
|
||||
Log.Debug("Reply received: {reply}", reply);
|
||||
}
|
||||
|
||||
public object BeforeSendRequest(ref Message request, IClientChannel channel)
|
||||
{
|
||||
|
||||
Log.Debug("Sending request: {request}", request);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
|
||||
{
|
||||
}
|
||||
|
||||
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
|
||||
{
|
||||
clientRuntime.ClientMessageInspectors.Add(this);
|
||||
}
|
||||
|
||||
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
|
||||
{
|
||||
}
|
||||
|
||||
public void Validate(ServiceEndpoint endpoint)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user