Ouvrir un portefeuille

Comment suivre ce chapitre

Entrées : userId, token CLIENT, identifiants des réponses au questionnaire, proposition choisie et données du client.

Étapes et résultat attendu : Enregistrez le questionnaire et les données manquantes avant d’envoyer le contrat PDF. Conservez orderId et portfolioId. Le succès de la demande enregistre un ordre, sans signifier que le portefeuille est déjà investi.

Alternatives et actions ultérieures : Ce parcours exige un contrat ; le service de génération PDF est facultatif. Vous pouvez produire le contrat avec votre propre solution.

Code source des DTO : AccountHoldingInstitutionOutputModel, CivilStatusOutputModel, CountryOutputModel, GenderOutputModel, LanguageOutputModel, NationalityOutputModel, PensionSituationOutputModel, PortfolioOrderOutputModel, TaxLiabilityOutputModel

Les variables affectées dans un bloc précédent sont réutilisées dans les suivants. Les saisies du client proviennent de votre interface. Conservez les identifiants et les valeurs de référence dans le contexte du client ; ne recréez pas ce contexte à chaque étape.

Les exemples utilisent des classes utilitaires communes. Consultez Classes utilitaires communes : code source C# et Java complet avant de copier un exemple.
Prérequis : userId, accessToken, réponses et proposition issus des chapitres précédents. L’auto-inscription utilise le token CLIENT reçu ; l’intégration administrative conserve son token ADMIN.
À conserver pour la suite : orderId et portfolioId retournés par l’API. Une demande HTTP acceptée ne signifie pas que la banque dépositaire a accepté ou investi le portefeuille.
Avant d’envoyer l’ordre, enregistrez l’appétence au risque du client et complétez les autres informations de profil décrites ci-dessous.

Enregistrer le profil de risque

Entrées : userId, token CLIENT et responseIds issus de Sélectionner une proposition.
Action : Enregistrez les réponses au questionnaire.
À conserver / résultat attendu : Continuez après une réponse réussie en conservant le même contexte de client et de proposition.
Une fois le profil calculé accepté par le client, enregistrez les réponses au questionnaire. Cette sauvegarde archive aussi son profil de risque :
								
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);

Compléter les données du client

Entrées : Les données saisies par le client et les références présentées ci-dessous.
Action : Complétez les informations requises avant d’ouvrir le portefeuille.
À conserver / résultat attendu : Poursuivez après une mise à jour réussie ; conservez userId et la proposition sélectionnée.
Recueillez et enregistrez les données encore manquantes pour ce produit. L’adresse et les codes sont illustratifs : utilisez les valeurs réelles du client et les références activées. Les valeurs existantes sont modifiées par ces demandes lorsqu’une nouvelle valeur non null est fournie. Les dates d’état civil ne concernent que les états qui les exigent ; clientBirthDate doit être la date de naissance réelle.
								
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);

Préparer le contrat

Le point de terminaison PDF présenté est facultatif. Si vous avez déjà un contrat complet, utilisez-le et passez à Envoyer l’ordre.

Entrées : Données du client et proposition choisie.
Action : Préparez un contrat PDF avec votre solution ou le service PDF facultatif.
À conserver / résultat attendu : C# : pdfContractAsByteArray. Java : contractPath pointe vers le PDF généré.
L’ordre d’ouverture exige un contrat PDF contenant les données du client. Le modèle dépend du gestionnaire de fortune. Les exemples remplissent un modèle approuvé avec les données du client et du portefeuille. Un microservice PDF facultatif peut remplir les champs ; il ne fait pas partie de la configuration minimale. Utilisez-le si votre intégration le prévoit, ou produisez le contrat vous-même. Utilisez le modèle approuvé et les données réelles. Configurez pdfServiceBaseUrl, pdfServiceToken et contractPath séparément de WMS et préparez formFields selon le modèle. Ce service ne remplace aucune signature ou acceptation requise. Au début de l’exemple, contractPath désigne le modèle d’entrée. Java affecte ensuite à contractPath un fichier distinct contenant le PDF rempli ; l’envoi doit lire ce fichier. Sans le service PDF, initialisez pdfContractAsByteArray avec votre PDF complet en C# et faites pointer contractPath vers ce PDF en Java. Conservez-le jusqu’à l’envoi et supprimez les copies temporaires quand elles ne sont plus nécessaires. Vous pouvez aussi fournir le contrat produit par votre solution :
Modèle de contrat PDF vide
Contrat PDF rempli avec les données du client
								
// 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;

Envoyer l’ordre

Entrées : userId, proposition choisie, catégorie d’investissement, motifs d’écart et contrat PDF.
Action : Envoyez l’ordre d’ouverture.
À conserver / résultat attendu : Conservez orderId et portfolioId, puis vérifiez l’état de l’ordre.
Choisissez la banque dépositaire prise en charge pour le produit et l’environnement. LIENHARDT n’est ici qu’un exemple, indépendant du gestionnaire de fortune du client. Chaque portefeuille a sa propre banque dépositaire. Fournissez aussi un contrat PDF avec les données du client et la proposition choisie :
								
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 a enregistré l’ordre d’ouverture. Le traitement par la banque dépositaire continue de manière asynchrone.
HTTP 201 retourne un PortfolioOrderOutputModel. Id identifie l’ordre et PortfolioId le portefeuille. Location pointe vers l’ordre. Conservez les deux identifiants et vérifiez l’état avant de conclure à l’acceptation ou à l’investissement.
Si le client choisit une stratégie différente de celle proposée, le paramètre ReasonToChangeProposalResponsesIds doit contenir les identifiants de réponse des motifs d’écart.

Suivre l’état de l’ordre

Entrées : L’orderId retourné lors de l’envoi.
Action : Lisez l’ordre enregistré et résolvez OrderStatusId à l’aide de la liste des états.
À conserver / résultat attendu : Utilisez l’état retourné pour attendre ou afficher un résultat. Les identifiants numériques n’ont pas de signification universelle dans ce guide.
Récupérez la liste avec GET api/v1/portfolio-order-statuses et un token Bearer valide. Elle contient Id, Code et Description. Associez OrderStatusId de l’ordre à une Id ; affichez Description et interprétez l’étape à partir de Code. Ne supposez pas de correspondance numérique fixe ni d’achèvement sur la seule base du succès HTTP. Après un timeout, ne renvoyez pas automatiquement l’ordre : vérifiez d’abord s’il a été enregistré. Le client peut consulter le portefeuille avec PortfolioId, mais les données disponibles dépendent de son cycle de vie.

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);

Reconnaître le résultat

Extrait illustratif d’une réponse d’ordre. Les identifiants sont des exemples ; résolvez l’état réel avec la liste de référence. Le succès HTTP confirme la réception, les états ultérieurs indiquent le traitement.

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

Le catalogue des DTO contient les autres propriétés de la réponse. Traitez une demande échouée avant de poursuivre ; consultez Gestion des erreurs.