Apertura del portafoglio

Come seguire questo capitolo

Dati richiesti: userId, token CLIENT, ID delle risposte al questionario, proposta scelta e dati del cliente.

Passaggi e risultato atteso: Salva il questionario e i dati mancanti prima di inviare il contratto PDF. Conserva orderId e portfolioId. Il successo della richiesta registra un ordine, ma non significa che il portafoglio sia già investito.

Alternative e operazioni successive: Questo percorso richiede un contratto; il servizio di generazione PDF è opzionale. Puoi produrre il contratto con la tua soluzione.

Codice sorgente dei DTO: AccountHoldingInstitutionOutputModel, CivilStatusOutputModel, CountryOutputModel, GenderOutputModel, LanguageOutputModel, NationalityOutputModel, PensionSituationOutputModel, PortfolioOrderOutputModel, TaxLiabilityOutputModel

Le variabili assegnate in un blocco precedente vengono riutilizzate nei successivi. I dati inseriti dal cliente provengono dalla tua interfaccia. Conserva identificativi e valori di riferimento nel contesto del cliente; non ricreare il contesto a ogni passaggio.

Gli esempi utilizzano classi helper condivise. Consulta Classi helper condivise: codice C# e Java completo prima di copiare un esempio.
Prima di iniziare: userId, accessToken, risposte e proposta dei capitoli precedenti. L’autoregistrazione usa il token CLIENT ricevuto; l’integrazione amministrativa conserva il proprio token ADMIN.
Da conservare per il passaggio successivo: orderId e portfolioId restituiti dall’API. Una richiesta HTTP accettata non significa che la banca depositaria abbia accettato o investito il portafoglio.
Prima di inviare l’ordine, registra la propensione al rischio del cliente e completa le altre informazioni di profilo descritte di seguito.

Salvataggio del profilo di rischio

Dati richiesti: userId, token CLIENT e responseIds dal capitolo Scelta della proposta.
Operazione: Salva le risposte al questionario.
Da conservare / risultato atteso: Prosegui dopo una risposta positiva, mantenendo lo stesso contesto del cliente e della proposta.
Una volta che il cliente ha accettato il profilo calcolato, salva le risposte al questionario. Questo salvataggio archivia anche il suo profilo di rischio:
								
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var payload = JsonConvert.SerializeObject(new
{
    BusinessLineId = businessLineVorsorge.Id,
    UserId = userId,
    Responses = responseIds.Select(id => new { ResponseId = id }).ToArray()
});
(await httpClient.PostAsync("api/v1/user-risk-categorizations", new StringContent(payload, Encoding.UTF8, "application/json"))).EnsureSuccessStatusCode();

								
JsonObject payload = new JsonObject();
payload.addProperty("BusinessLineId", businessLineId);
payload.addProperty("UserId", userId);
payload.add("Responses", new Gson().toJsonTree(responseIds.stream().map(id -> Map.of("ResponseId", id)).toList()));
HttpRequest request = HttpRequest.newBuilder()
    .uri(Settings.wmsUri("user-risk-categorizations"))
    .timeout(Duration.ofMinutes(1))
    .header("Authorization", "Bearer " + accessToken)
    .header("Content-Type", "application/json")
    .POST(HttpRequest.BodyPublishers.ofString(payload.toString())).build();
var response = httpClient.send(request, BodyHandlers.ofString());
Api.requireSuccess(response);

Completamento dei dati del cliente

Dati richiesti: I dati inseriti dal cliente e i riferimenti mostrati di seguito.
Operazione: Completa le informazioni richieste prima dell’apertura del portafoglio.
Da conservare / risultato atteso: Prosegui dopo l’aggiornamento riuscito; conserva userId e la proposta selezionata.
Raccogli e salva i dati ancora mancanti per il prodotto. Indirizzo e codici sono illustrativi: usa i valori reali del cliente e i riferimenti attivati. Queste richieste aggiornano i valori esistenti quando fornisci un nuovo valore non null. Le date dello stato civile riguardano solo gli stati che le richiedono; clientBirthDate deve essere la data di nascita effettiva.
								
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");
// clientBirthDate and civilStatusDate are collected from the client.
payload = JsonConvert.SerializeObject(new
{
    Name = clientName,
    Surname = clientSurname,
    Email = clientEmail,
    PhoneNumberPrefix = phoneNumberPrefix,
    PhoneNumberNumber = phoneNumberNumber,
    BirthDate = clientBirthDate,
    CivilStatusId = civilStatus.Id,
    CivilStatusDate = civilStatusDate,
    GenderId = gender.Id,
    LanguageId = language.Id,
    Nationality1Id = nationality.Id,
    DeliverTaxStatement = true,
    TaxLiabilityId = taxLiability.Id,
    PensionSituationId = pensionSituation.Id
});
using var clientUpdateContent = new MultipartFormDataContent();
clientUpdateContent.Add(new StringContent(payload, Encoding.UTF8, "application/json"), "jsonPayload");
(await httpClient.PutAsync($"api/v1/users/user-id/{userId}", clientUpdateContent)).EnsureSuccessStatusCode();
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/{userId}", new StringContent(payload, Encoding.UTF8, "application/json"))).EnsureSuccessStatusCode();

								
// Saving missing client data
HttpRequest languageRequest = HttpRequest.newBuilder()
    .timeout(Duration.ofMinutes(1))
    .uri(Settings.wmsUri("languages/code/DE"))
    .header("Accept-Language", "de-DE")
    .header("Authorization", "Bearer " + Settings.GUEST_TOKEN)
    .header("Content-Type", "application/json")
    .build();

response = httpClient.send(languageRequest, BodyHandlers.ofString());
Api.requireSuccess(response);
LanguageOutputModel languageObj = new Gson().fromJson(response.body(), LanguageOutputModel.class);
var languageId = languageObj.Id;

HttpRequest nationalityRequest = HttpRequest.newBuilder()
    .timeout(Duration.ofMinutes(1))
    .uri(Settings.wmsUri("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());
Api.requireSuccess(response);
NationalityOutputModel nationalityObj = new Gson().fromJson(response.body(), NationalityOutputModel.class);
var nationalityId = nationalityObj.Id;

HttpRequest countryRequest = HttpRequest.newBuilder()
    .timeout(Duration.ofMinutes(1))
    .uri(Settings.wmsUri("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());
Api.requireSuccess(response);
CountryOutputModel countryObj = new Gson().fromJson(response.body(), CountryOutputModel.class);
var countryId = countryObj.Id;

HttpRequest pensionRequest = HttpRequest.newBuilder()
    .timeout(Duration.ofMinutes(1))
    .uri(Settings.wmsUri("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());
Api.requireSuccess(response);
PensionSituationOutputModel pensionObj = new Gson().fromJson(response.body(), PensionSituationOutputModel.class);
var pensionId = pensionObj.Id;

HttpRequest taxLiabilityRequest = HttpRequest.newBuilder()
    .timeout(Duration.ofMinutes(1))
    .uri(Settings.wmsUri("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());
Api.requireSuccess(response);
TaxLiabilityOutputModel taxLiabilityObj = new Gson().fromJson(response.body(), TaxLiabilityOutputModel.class);
var taxLiabilityId = taxLiabilityObj.Id;

HttpRequest civilStatusRequest = HttpRequest.newBuilder()
    .timeout(Duration.ofMinutes(1))
    .uri(Settings.wmsUri("civil-statuses/code/MARRIAGE"))
    .header("Accept-Language", "de-DE")
    .header("Authorization", "Bearer " + Settings.GUEST_TOKEN)
    .header("Content-Type", "application/json")
    .build();

response = httpClient.send(civilStatusRequest, BodyHandlers.ofString());
Api.requireSuccess(response);
CivilStatusOutputModel civilStatusObj = new Gson().fromJson(response.body(), CivilStatusOutputModel.class);
var civilStatusId = civilStatusObj.Id;

HttpRequest genderRequest = HttpRequest.newBuilder()
    .timeout(Duration.ofMinutes(1))
    .uri(Settings.wmsUri("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());
Api.requireSuccess(response);
GenderOutputModel genderObj = new Gson().fromJson(response.body(), GenderOutputModel.class);
var genderId = genderObj.Id;

JsonObject updatePayload = new JsonObject();
updatePayload.addProperty("CivilStatusId", civilStatusId);
updatePayload.addProperty("CivilStatusDate", civilStatusDate);
updatePayload.addProperty("BirthDate", clientBirthDate);
updatePayload.addProperty("GenderId", genderId);
updatePayload.addProperty("LanguageId", languageId);
updatePayload.addProperty("Nationality1Id", nationalityId);
updatePayload.addProperty("DeliverTaxStatement", true);
updatePayload.addProperty("TaxLiabilityId", taxLiabilityId);
updatePayload.addProperty("PensionSituationId", pensionId);

MultiPartBodyPublisher multipartBody = new MultiPartBodyPublisher()
    .addPart("jsonPayload", updatePayload.toString());

HttpRequest updateRequest = HttpRequest.newBuilder()
    .timeout(Duration.ofMinutes(1))
    .uri(Settings.wmsUri("users/user-id/" + userId))
    .header("Accept-Language", "de-DE")
    .header("Authorization", "Bearer " + accessToken)
    .header("Content-Type", "multipart/form-data; boundary=" + multipartBody.getBoundary())
    .PUT(multipartBody.build())
    .build();

response = httpClient.send(updateRequest, BodyHandlers.ofString());
Api.requireSuccess(response);

JsonObject updateAddressPayload = new JsonObject();
updateAddressPayload.addProperty("Street", "Neugasse");
updateAddressPayload.addProperty("StreetNr", "1");
updateAddressPayload.addProperty("City", "Baar");
updateAddressPayload.addProperty("CountryId", countryId);
updateAddressPayload.addProperty("Zip", "6340");

HttpRequest updateAddressRequest = HttpRequest.newBuilder()
    .timeout(Duration.ofMinutes(1))
    .uri(Settings.wmsUri("users/update-address/user-id/" + userId))
    .header("Accept-Language", "de-DE")
    .header("Authorization", "Bearer " + accessToken)
    .header("Content-Type", "application/json")
    .PUT(HttpRequest.BodyPublishers.ofString(updateAddressPayload.toString()))
    .build();

response = httpClient.send(updateAddressRequest, BodyHandlers.ofString());
Api.requireSuccess(response);

Preparazione del contratto

L’endpoint PDF mostrato è opzionale. Se hai già un contratto completo, usalo e passa a Invio dell’ordine.

Dati richiesti: Dati del cliente e proposta scelta.
Operazione: Prepara un contratto PDF con la tua soluzione o con il servizio PDF opzionale.
Da conservare / risultato atteso: C#: pdfContractAsByteArray. Java: contractPath punta al PDF generato.
L’ordine di apertura richiede un contratto PDF con i dati del cliente. Il modello dipende dal gestore patrimoniale. Gli esempi compilano un modello approvato con i dati del cliente e del portafoglio. Un microservizio PDF opzionale può compilare i campi; non fa parte della configurazione minima. Usalo se previsto dall’integrazione oppure produci il contratto autonomamente. Usa il modello approvato e i dati reali. Configura pdfServiceBaseUrl, pdfServiceToken e contractPath separatamente da WMS e prepara formFields secondo il modello. Il servizio non sostituisce eventuali firme o accettazioni richieste. All’inizio dell’esempio contractPath identifica il modello di ingresso. Java assegna poi a contractPath un file distinto con il PDF compilato; l’invio deve leggere questo file. Senza il servizio PDF, inizializza pdfContractAsByteArray con il PDF completo in C# e fai puntare contractPath al PDF completo in Java. Conservalo fino all’invio ed elimina le copie temporanee quando non servono più. Puoi anche fornire il contratto generato dalla tua soluzione:
Modello vuoto del contratto PDF
Contratto PDF compilato con i dati del cliente
								
// Optional PDF service: use its configured origin and authorized token.
// contractPath points to the current template approved for this product.
// formFields maps template field names to actual client/portfolio values.
using var pdfClient = new HttpClient
{
    BaseAddress = new Uri(pdfServiceBaseUrl.TrimEnd('/') + "/")
};
pdfClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", pdfServiceToken);
var payload = new
{
    PlaceHolderValues = formFields.Select(x => new { Name = x.Key, Value = x.Value }).ToList()
};
var template = await File.ReadAllBytesAsync(contractPath);
using var content = GuideHttp.CreateMultipartContent(payload, template, "attachments");
using var response = await pdfClient.PostAsync("api/v1/pdf-service/fill-pdf-with-data", content);
response.EnsureSuccessStatusCode();
var pdfContractAsByteArray = await response.Content.ReadAsByteArrayAsync();

								
// Optional PDF service: formFields contains actual values for its template fields.
JsonArray fields = new JsonArray();
for (Map.Entry<String, String> entry : formFields.entrySet()) {
    JsonObject field = new JsonObject();
    field.addProperty("Name", entry.getKey());
    field.addProperty("Value", entry.getValue());
    fields.add(field);
}
JsonObject payload = new JsonObject();
payload.add("PlaceHolderValues", fields);
byte[] template = Files.readAllBytes(contractPath);
MultiPartBodyPublisher body = new MultiPartBodyPublisher()
    .addPart("jsonPayload", payload.toString())
    .addPart("attachments", () -> new ByteArrayInputStream(template), "Contract.pdf", "application/pdf");
URI pdfUri = URI.create(pdfServiceBaseUrl.replaceAll("/+$", "") + "/api/v1/pdf-service/fill-pdf-with-data");
HttpRequest request = HttpRequest.newBuilder().uri(pdfUri)
    .timeout(Duration.ofMinutes(1))
    .header("Authorization", "Bearer " + pdfServiceToken)
    .header("Content-Type", "multipart/form-data; boundary=" + body.getBoundary())
    .POST(body.build()).build();
var response = httpClient.send(request, BodyHandlers.ofByteArray());
Api.requireSuccess(response);
var pdfAsByteArray = response.body();
// Preserve the filled PDF separately from the input template.
var completedContractPath = Files.createTempFile("wms-contract-", ".pdf");
Files.write(completedContractPath, pdfAsByteArray);
contractPath = completedContractPath;

Invio dell’ordine

Dati richiesti: userId, proposta scelta, categoria d’investimento, motivi di scostamento e contratto PDF.
Operazione: Invia l’ordine di apertura.
Da conservare / risultato atteso: Conserva orderId e portfolioId, quindi verifica lo stato dell’ordine.
Scegli la banca depositaria supportata per il prodotto e l’ambiente. LIENHARDT è soltanto l’esempio utilizzato ed è indipendente dal gestore patrimoniale del cliente. Ogni portafoglio ha la propria banca depositaria. Fornisci inoltre un contratto PDF con i dati del cliente e la proposta scelta:
								
var accountHoldingInstitution = await httpClient.GetFromJsonAsync<AccountHoldingInstitutionOutputModel>("api/v1/account-holding-institutions/code/LIENHARDT");
var orderPayload = new
{
    AccountHoldingInstitutionCode = accountHoldingInstitution.Code,
    UserId = userId,
    InvestmentCategoryId = investmentCategory3A.Id,
    ProposalId = proposalSelectedByUser.Id,
    ReasonToChangeProposalResponsesIds = selectedReasonIds
};
using var content = GuideHttp.CreateMultipartContent(orderPayload, pdfContractAsByteArray);
using var response = await httpClient.PostAsync("api/v1/user-portfolio-orders/creation", content);
response.EnsureSuccessStatusCode();
var order = JsonConvert.DeserializeObject<PortfolioOrderOutputModel>(await response.Content.ReadAsStringAsync());
var orderId = order.Id;
var portfolioId = order.PortfolioId;

								
// Submitting the order
HttpRequest accountHoldingInstitutionRequest = HttpRequest.newBuilder()
    .timeout(Duration.ofMinutes(1))
    .uri(Settings.wmsUri("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());
Api.requireSuccess(response);
AccountHoldingInstitutionOutputModel accountHoldingInstitutionObj = new Gson().fromJson(response.body(), AccountHoldingInstitutionOutputModel.class);

JsonObject orderPayload = new JsonObject();
orderPayload.addProperty("AccountHoldingInstitutionCode", accountHoldingInstitutionObj.Code);
orderPayload.addProperty("UserId", userId);
orderPayload.addProperty("InvestmentCategoryId", investmentCategoryId);
orderPayload.addProperty("ProposalId", proposalId);
orderPayload.add("ReasonToChangeProposalResponsesIds", new Gson().toJsonTree(selectedReasonIds));

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(Settings.wmsUri("user-portfolio-orders/creation"))
    .header("Accept-Language", "de-DE")
    .header("Authorization", "Bearer " + accessToken)
    .header("Content-Type", "multipart/form-data; boundary=" + multipartOrderBody.getBoundary())
    .POST(multipartOrderBody.build())
    .build();

response = httpClient.send(createPortfolioRequest, BodyHandlers.ofString());
Api.requireSuccess(response);

PortfolioOrderOutputModel orderObj = new Gson().fromJson(response.body(), PortfolioOrderOutputModel.class);
var orderId = orderObj.Id;
var portfolioId = orderObj.PortfolioId;

L’API ha registrato l’ordine di apertura. L’elaborazione della banca depositaria prosegue in modo asincrono.
HTTP 201 restituisce un PortfolioOrderOutputModel. Id identifica l’ordine e PortfolioId il portafoglio. Location punta all’ordine. Conserva entrambi gli ID e verifica lo stato prima di considerarlo accettato o investito.
Se il cliente sceglie una strategia diversa da quella proposta, il parametro ReasonToChangeProposalResponsesIds deve contenere gli ID delle risposte relative ai motivi di scostamento.

Monitoraggio dello stato dell’ordine

Dati richiesti: L’orderId restituito all’invio.
Operazione: Leggi l’ordine registrato e risolvi OrderStatusId tramite l’elenco degli stati.
Da conservare / risultato atteso: Usa lo stato restituito per attendere o mostrare un risultato. Gli ID numerici non hanno un significato universale in questa guida.
Recupera l’elenco con GET api/v1/portfolio-order-statuses e un token Bearer valido. Contiene Id, Code e Description. Associa OrderStatusId dell’ordine a una Id; mostra Description e interpreta la fase tramite Code. Non presumere una corrispondenza numerica fissa né il completamento sulla sola base del successo HTTP. Dopo un timeout, non reinviare automaticamente l’ordine: verifica prima se è stato registrato. Il cliente può consultare il portafoglio tramite PortfolioId, ma i dati disponibili dipendono dal ciclo di vita.

var currentOrder = await httpClient.GetFromJsonAsync<PortfolioOrderOutputModel>($"api/v1/user-portfolio-orders/order-id/{orderId}");

HttpRequest request = HttpRequest.newBuilder()
    .uri(Settings.wmsUri("user-portfolio-orders/order-id/" + orderId))
    .header("Authorization", "Bearer " + accessToken).GET().build();
var response = httpClient.send(request, BodyHandlers.ofString());
Api.requireSuccess(response);
PortfolioOrderOutputModel currentOrder = new Gson().fromJson(response.body(), PortfolioOrderOutputModel.class);

Riconoscere il risultato

Estratto illustrativo della risposta di un ordine. Gli ID sono esempi; risolvi lo stato reale tramite l’elenco di riferimento. Il successo HTTP conferma la ricezione, gli stati successivi indicano l’elaborazione.

{"id":456,"portfolioId":789,"orderStatusId":1}

Il catalogo DTO contiene le altre proprietà della risposta. Gestisci una richiesta non riuscita prima di proseguire; consulta Gestione degli errori.