Plan Creation
Before submitting an order, we need to record the client's risk appetite and collect other profile information, as described below.
Saving the client's risk profile
Once the client has accepted the calculated risk profile, we need to save the answers from the questionnaire. By saving the answers, the client's risk profile will also be archived:
payload = JsonConvert.SerializeObject(new
{
BusinessLineId = businessLineVorsorge.Id,
UserId = userId,
Responses = new[]
{
new { ResponseId = response1 },
new { ResponseId = response2 },
new { ResponseId = response3 },
new { ResponseId = response4 }
}
});
_ = await httpClient.PostAsync("api/v1/user-risk-categorizations", new StringContent(payload, Encoding.UTF8, "application/json"));
// Saving the client's risk profile
StringBuilder registrationPayload = new StringBuilder("{");
registrationPayload.append("'BusinessLineId': " + businessLineId + ",");
registrationPayload.append("'UserId': " + userId + ",");
registrationPayload.append("'Responses': [ { ResponseId:" + proposalSelectionResult.ResponseIds.get(0) + "}, { ResponseId:" + proposalSelectionResult.ResponseIds.get(1) + "}, { ResponseId:" + proposalSelectionResult.ResponseIds.get(2) + "}, { ResponseId:" + proposalSelectionResult.ResponseIds.get(3) + " } ]");
registrationPayload.append("}");
HttpRequest createRequest = HttpRequest.newBuilder()
.timeout(Duration.ofMinutes(1))
.uri(URI.create(Program.BASE_URL + "user-risk-categorizations"))
.header("Accept-Language", "de-DE")
.header("Authorization", "Bearer " + Program.ADMIN_TOKEN)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(registrationPayload.toString()))
.build();
var response = httpClient.send(createRequest, BodyHandlers.ofString());
Saving missing client data
For the next step, it is necessary to save the missing client's data and address (if missing).
var nationality = await httpClient.GetFromJsonAsync<NationalityOutputModel>("api/v1/nationalities/code/CH");
var country = await httpClient.GetFromJsonAsync<CountryOutputModel>("api/v1/countries/code/CH");
var pensionSituation = await httpClient.GetFromJsonAsync<PensionSituationOutputModel>("api/v1/pension-situations/code/PENSION-FUND");
var taxLiability = await httpClient.GetFromJsonAsync<TaxLiabilityOutputModel>("api/v1/tax-liabilities/code/CH");
var civilStatus = await httpClient.GetFromJsonAsync<CivilStatusOutputModel>("api/v1/civil-statuses/code/MARRIAGE");
var day = new Random().Next(1, 28);
var month = new Random().Next(1, 12);
var year = new Random().Next(1965, 2005);
var clientBirthDate = new DateTime(year, month, day);
payload = JsonConvert.SerializeObject(new
{
Name = clientName,
Surname = clientSurname,
Email = clientEmail,
PhoneNumberPrefix = "0041",
PhoneNumberNumber = "767676000",
BirthDate = clientBirthDate,
CivilStatusId = civilStatus.Id,
CivilStatusDate = DateTime.Now.AddYears(-5),
GenderId = gender.Id,
LanguageId = language.Id,
Nationality1Id = nationality.Id,
DeliverTaxStatement = true,
TaxLiabilityId = taxLiability.Id,
PensionSituationId = pensionSituation.Id
});
_ = await httpClient.PutAsync($"api/v1/users/user-id/{thirdFactorRegistrationResult.UserId}", new StringContent(payload, Encoding.UTF8, "application/json"));
payload = JsonConvert.SerializeObject(new
{
Street = "Neugasse",
StreetNr = "1",
City = "Baar",
CountryId = country.Id,
Zip = "6340"
});
_ = await httpClient.PutAsync($"api/v1/users/update-address/user-id/{thirdFactorRegistrationResult.UserId}", new StringContent(payload, Encoding.UTF8, "application/json"));
// Saving missing client data
HttpRequest languageRequest = HttpRequest.newBuilder()
.timeout(Duration.ofMinutes(1))
.uri(URI.create(Settings.BASE_URL + "languages/code/FR"))
.header("Accept-Language", "de-DE")
.header("Authorization", "Bearer " + Settings.GUEST_TOKEN)
.header("Content-Type", "application/json")
.build();
response = httpClient.send(languageRequest, BodyHandlers.ofString());
LanguageOutputModel languageObj = new Gson().fromJson(response.body().toString(), LanguageOutputModel.class);
var languageId = languageObj.Id;
HttpRequest nationalityRequest = HttpRequest.newBuilder()
.timeout(Duration.ofMinutes(1))
.uri(URI.create(Settings.BASE_URL + "nationalities/code/CH"))
.header("Accept-Language", "de-DE")
.header("Authorization", "Bearer " + Settings.GUEST_TOKEN)
.header("Content-Type", "application/json")
.build();
response = httpClient.send(nationalityRequest, BodyHandlers.ofString());
NationalityOutputModel nationalityObj = new Gson().fromJson(response.body().toString(), NationalityOutputModel.class);
var nationalityId = nationalityObj.Id;
HttpRequest countryRequest = HttpRequest.newBuilder()
.timeout(Duration.ofMinutes(1))
.uri(URI.create(Settings.BASE_URL + "countries/code/CH"))
.header("Accept-Language", "de-DE")
.header("Authorization", "Bearer " + Settings.GUEST_TOKEN)
.header("Content-Type", "application/json")
.build();
response = httpClient.send(countryRequest, BodyHandlers.ofString());
CountryOutputModel countryObj = new Gson().fromJson(response.body().toString(), CountryOutputModel.class);
var countryId = countryObj.Id;
HttpRequest pensionRequest = HttpRequest.newBuilder()
.timeout(Duration.ofMinutes(1))
.uri(URI.create(Settings.BASE_URL + "pension-situations/code/PENSION-FUND"))
.header("Accept-Language", "de-DE")
.header("Authorization", "Bearer " + Settings.GUEST_TOKEN)
.header("Content-Type", "application/json")
.build();
response = httpClient.send(pensionRequest, BodyHandlers.ofString());
PensionSituationOutputModel pensionObj = new Gson().fromJson(response.body().toString(), PensionSituationOutputModel.class);
var pensionId = pensionObj.Id;
HttpRequest taxLiabilityRequest = HttpRequest.newBuilder()
.timeout(Duration.ofMinutes(1))
.uri(URI.create(Settings.BASE_URL + "tax-liabilities/code/CH"))
.header("Accept-Language", "de-DE")
.header("Authorization", "Bearer " + Settings.GUEST_TOKEN)
.header("Content-Type", "application/json")
.build();
response = httpClient.send(taxLiabilityRequest, BodyHandlers.ofString());
TaxLiabilityOutputModel taxLiabilityObj = new Gson().fromJson(response.body().toString(), TaxLiabilityOutputModel.class);
var taxLiabilityId = taxLiabilityObj.Id;
HttpRequest civilStatusRequest = HttpRequest.newBuilder()
.timeout(Duration.ofMinutes(1))
.uri(URI.create(Settings.BASE_URL + "civil-statuses/code/SINGLE"))
.header("Accept-Language", "de-DE")
.header("Authorization", "Bearer " + Settings.GUEST_TOKEN)
.header("Content-Type", "application/json")
.build();
response = httpClient.send(civilStatusRequest, BodyHandlers.ofString());
CivilStatusOutputModel civilStatusObj = new Gson().fromJson(response.body().toString(), CivilStatusOutputModel.class);
var civilStatusId = civilStatusObj.Id;
HttpRequest genderRequest = HttpRequest.newBuilder()
.timeout(Duration.ofMinutes(1))
.uri(URI.create(Settings.BASE_URL + "genders/code/MALE"))
.header("Accept-Language", "de-DE")
.header("Authorization", "Bearer " + Settings.GUEST_TOKEN)
.header("Content-Type", "application/json")
.build();
response = httpClient.send(genderRequest, BodyHandlers.ofString());
GenderOutputModel genderObj = new Gson().fromJson(response.body().toString(), GenderOutputModel.class);
var genderId = genderObj.Id;
StringBuilder updatePayload = new StringBuilder("{");
updatePayload.append("'CivilStatusId': " + civilStatusId + ",");
updatePayload.append("'CivilStatusDate': '1989-01-01',");
updatePayload.append("'GenderId': " + genderId + ",");
updatePayload.append("'LanguageId': " + languageId + ",");
updatePayload.append("'Nationality1Id': " + nationalityId + ",");
updatePayload.append("'DeliverTaxStatement': true,");
updatePayload.append("'TaxLiabilityId': " + taxLiabilityId + ",");
updatePayload.append("'PensionSituationId': " + pensionId);
updatePayload.append("}");
MultiPartBodyPublisher multipartBody = new MultiPartBodyPublisher()
.addPart("jsonPayload", updatePayload.toString());
HttpRequest updateRequest = HttpRequest.newBuilder()
.timeout(Duration.ofMinutes(1))
.uri(URI.create(Settings.BASE_URL + "users/user-id/" + userId))
.header("Accept-Language", "de-DE")
.header("Authorization", "Bearer " + Settings.ADMIN_TOKEN)
.header("Content-Type", "multipart/form-data; boundary=" + multipartBody.getBoundary())
.PUT(multipartBody.build())
.build();
response = httpClient.send(updateRequest, BodyHandlers.ofString());
StringBuilder updateAddressPayload = new StringBuilder("{");
updateAddressPayload.append("'Street': 'Neugasse',");
updateAddressPayload.append("'StreetNr': '1',");
updateAddressPayload.append("'City': 'Baar',");
updateAddressPayload.append("'CountryId':" + countryId + ",");
updateAddressPayload.append("'Zip': '6340',");
updateAddressPayload.append("}");
HttpRequest updateAddressRequest = HttpRequest.newBuilder()
.timeout(Duration.ofMinutes(1))
.uri(URI.create(Settings.BASE_URL + "users/update-address/user-id/" + userId))
.header("Accept-Language", "de-DE")
.header("Authorization", "Bearer " + Settings.ADMIN_TOKEN)
.header("Content-Type", "application/json")
.PUT(HttpRequest.BodyPublishers.ofString(updateAddressPayload.toString()))
.build();
response = httpClient.send(updateAddressRequest, BodyHandlers.ofString());
Preparing the contract
Submission of the plan order requires a PDF contract with the client information. The PDF template depends on the asset manager. In the following example, we simulate the preparation of a PDF using Descartes Finance's PDF template. The WMS solution uses a microservice to complete the PDF documents. This microservice is not part of the minimum configuration. In case you wish to use it, you must request its installation. The PDF compiler takes an empty template and sets the respective fields with the values provided:
private async Task<byte[]> GetPdf(IEnumerable<PdfPlaceHolder> placeHolderValues)
{
var payload = new PdfDataInputModel
{
PlaceHolderValues = placeHolderValues.Select(x => new FormField(x.Name, x.Value)).ToList()
};
var byteArray = FileContentHelper.GetFileContent(this.GetType(), "Contract-3A.pdf");
using var httpClient = new HttpClient { BaseAddress = new Uri(_configuration["PdfFillerBaseUrl"]) }; // <== DIFFERENT URL
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json", 1));
httpClient.DefaultRequestHeaders.AcceptLanguage.Add(new StringWithQualityHeaderValue("de-DE", 1));
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", this.HttpContext.Session.Get("Token"));
var httpContent = HttpHelper.CreateMultipartFormDataHttpContent(payload, byteArray);
var httpResponseMessage = await httpClient.PostAsync("api/v1/pdf-service/fill-pdf-with-data", httpContent);
if (httpResponseMessage.IsSuccessStatusCode)
{
return await httpResponseMessage.Content.ReadAsByteArrayAsync();
}
return default;
}
....
....
var client = await httpClient.GetFromJsonAsync<ClientOutputModel>($"api/v1/users/{this.HttpContext.Session.Get("ClientId")}");
// Collect some (hardcoded) fields...
var fields = new List<PdfPlaceHolder>();
fields.Add(new PdfPlaceHolder("Name", client.Name));
fields.Add(new PdfPlaceHolder("Surname", client.Surname));
fields.Add(new PdfPlaceHolder("Language", "DE"));
fields.Add(new PdfPlaceHolder("Gender", "MALE"));
fields.Add(new PdfPlaceHolder("Street", "Nowhere 0"));
fields.Add(new PdfPlaceHolder("ZipAndCity", "6000 Elsewhere"));
fields.Add(new PdfPlaceHolder("Country", "No man's land"));
fields.Add(new PdfPlaceHolder("Nationality", "Mars"));
fields.Add(new PdfPlaceHolder("Birthdate", "01.01.1990"));
fields.Add(new PdfPlaceHolder("Email", client.Email));
fields.Add(new PdfPlaceHolder("Phone", client.PhoneNumber));
var pdfContractAsByteArray = await this.GetPdf(fields);
private static byte[] GetPdf(HttpClient httpClient, Map placeHolderValues) throws Exception {
var payload = new PdfDataInputModel();
payload.PlaceHolderValues = new ArrayList();
for (Map.Entry<String, String> entry : placeHolderValues.entrySet()) {
payload.PlaceHolderValues.add(new FormField(entry.getKey(), entry.getValue()));
}
var placeHolderValuesJson = new Gson().toJson(payload);
// Retrieve the PDF template
var inputStream = Files.newInputStream(Paths.get("c:\\Temp\\Contract-DIR-3A.pdf"));
MultiPartBodyPublisher multipartBody = new MultiPartBodyPublisher()
.addPart("jsonPayload", placeHolderValuesJson)
.addPart("attachments", () -> inputStream, "Contract.pdf", "application/pdf");
// ***************************************************************************************
// Please note: we are calling a different WEB API service, so it is a different URL!
// ***************************************************************************************
HttpRequest fillPdfRequest = HttpRequest.newBuilder()
.timeout(Duration.ofMinutes(1))
.uri(URI.create("http://localhost:5300/api/v1/pdf-service/fill-pdf-with-data"))
.header("Accept-Language", "de-DE")
.header("Authorization", "Bearer " + Settings.ADMIN_TOKEN)
.header("Content-Type", "multipart/form-data; boundary=" + multipartBody.getBoundary())
.POST(multipartBody.build())
.build();
HttpResponse<byte[]> response = httpClient.send(fillPdfRequest, BodyHandlers.ofByteArray());
if (response.statusCode() != 200 && response.statusCode() != 201) {
System.out.println(response.body());
throw new Exception("pdf-service/fill-pdf-with-data ==> response.statusCode() = " + response.statusCode());
}
return response.body();
}
...
...
// Get client data
HttpRequest updateClientInfoRequest = HttpRequest.newBuilder()
.timeout(Duration.ofMinutes(1))
.uri(URI.create(Settings.BASE_URL + "users/" + userId))
.header("Accept-Language", "de-DE")
.header("Authorization", "Bearer " + Settings.ADMIN_TOKEN)
.header("Content-Type", "application/json")
.build();
response = httpClient.send(updateClientInfoRequest, BodyHandlers.ofString());
ClientOutputModel clientObj = new Gson().fromJson(response.body().toString(), ClientOutputModel.class);
// Collect some (hard coded) client's fields...
Map<String, String> placeHolderValues = new HashMap();
placeHolderValues.put("Name", clientObj.Name);
placeHolderValues.put("Surname", clientObj.Surname);
placeHolderValues.put("Language", "DE");
placeHolderValues.put("Gender", "MALE");
placeHolderValues.put("Street", "Nowhere 0");
placeHolderValues.put("ZipAndCity", "6000 Elsewhere");
placeHolderValues.put("Country", "No man's land");
placeHolderValues.put("Nationality", "Mars");
placeHolderValues.put("Birthdate", "01-01-1990");
placeHolderValues.put("Email", clientObj.Email);
placeHolderValues.put("Phone", clientObj.PhoneNumber);
// Fill contract with client's data
var pdfAsByteArray = GetPdf(httpClient, placeHolderValues);
Submitting the order
In the following example, we enter the account-holding institution where the custody account is to be opened (in this example Lienhardt Privatbank). In addition, we need to provide a PDF contract containing client details with the selected investment proposal:
var accountHoldingInstitution = await httpClient.GetFromJsonAsync<AccountHoldingInstitutionOutputModel>("api/v1/account-holding-institutions/code/LIENHARDT");
var orderPortfolioCreationInputModel = new
{
AccountHoldingInstitutionCode = accountHoldingInstitution.Code,
UserId = thirdFactorRegistrationResult.UserId,
InvestmentCategoryId = investmentCategory3A.Id,
ProposalId = proposalSelectedByUser.Id,
Name = "I will be rich soon",
ReasonToChangeProposalResponsesIds = default(List<long>) // In case user agreed with suggested proposal, this list can be left empty
};
var httpContent = HttpHelper.CreateMultipartFormDataHttpContent(orderPortfolioCreationInputModel, pdfContractAsByteArray);
_ = await httpClient.PostAsync("api/v1/user-portfolio-orders/creation", httpContent);
// Submitting the order
HttpRequest accountHoldingInstitutionRequest = HttpRequest.newBuilder()
.timeout(Duration.ofMinutes(1))
.uri(URI.create(Settings.BASE_URL + "account-holding-institutions/code/LIENHARDT"))
.header("Accept-Language", "de-DE")
.header("Authorization", "Bearer " + Settings.GUEST_TOKEN)
.header("Content-Type", "application/json")
.build();
response = httpClient.send(accountHoldingInstitutionRequest, BodyHandlers.ofString());
AccountHoldingInstitutionOutputModel accountHoldingInstitutionObj = new Gson().fromJson(response.body().toString(), AccountHoldingInstitutionOutputModel.class);
StringBuilder orderPayload = new StringBuilder("{");
orderPayload.append("'AccountHoldingInstitutionCode': '" + accountHoldingInstitutionObj.Code + "',");
orderPayload.append("'UserId': " + userId + ",");
orderPayload.append("'InvestmentCategoryId': " + investmentCategoryId + ",");
orderPayload.append("'ProposalId': " + proposalSelectionResult.ProposalId + ",");
orderPayload.append("'ReasonToChangeProposalResponsesIds': []");
orderPayload.append("}");
InputStream contractStream = new ByteArrayInputStream(pdfAsByteArray);
MultiPartBodyPublisher multipartOrderBody = new MultiPartBodyPublisher()
.addPart("jsonPayload", orderPayload.toString())
.addPart("userContracts", () -> contractStream, "Contract.pdf", "application/pdf");
HttpRequest createPortfolioRequest = HttpRequest.newBuilder()
.timeout(Duration.ofMinutes(1))
.uri(URI.create(Settings.BASE_URL + "user-portfolio-orders/creation"))
.header("Accept-Language", "de-DE")
.header("Authorization", "Bearer " + Settings.ADMIN_TOKEN)
.header("Content-Type", "multipart/form-data; boundary=" + multipartOrderBody.getBoundary())
.POST(multipartOrderBody.build())
.build();
response = httpClient.send(createPortfolioRequest, BodyHandlers.ofString());
PortfolioOrderOutputModel orderObj = new Gson().fromJson(response.body().toString(), PortfolioOrderOutputModel.class);
Done. The first client portfolio has been ordered.
The POST request returns, in the HTTP location header, the URL of the newly created portfolio.
As mentioned above, in case the client chooses a different investment strategy than the one proposed, the ReasonToChangeProposalResponsesIds parameter must be filled in with the "reason to change proposal" response ID(s).