Portfolio eröffnen

So folgen Sie diesem Kapitel

Eingaben: userId, CLIENT-Token, Fragebogen-Antwort-IDs, gewählter Anlagevorschlag und Kundendaten.

Schritte und erwartetes Ergebnis: Speichern Sie den Fragebogen und die fehlenden Daten, bevor Sie den PDF-Vertrag senden. Bewahren Sie orderId und portfolioId auf. Eine erfolgreiche Anfrage erfasst einen Auftrag, bedeutet aber noch nicht, dass das Portfolio investiert ist.

Alternativen und spätere Aktionen: Für diesen Ablauf ist ein Vertrag erforderlich; der separate PDF-Erzeugungsdienst ist optional. Sie können den Vertrag auch mit Ihrer eigenen Lösung erstellen.

DTO-Quellcode: AccountHoldingInstitutionOutputModel, CivilStatusOutputModel, CountryOutputModel, GenderOutputModel, LanguageOutputModel, NationalityOutputModel, PensionSituationOutputModel, PortfolioOrderOutputModel, TaxLiabilityOutputModel

In früheren Codeblöcken zugewiesene Variablen werden später wiederverwendet. Kundeneingaben stammen aus Ihrer Oberfläche. Bewahren Sie IDs und Referenzwerte im Kontext des jeweiligen Kunden auf; legen Sie nicht für jeden Schritt einen neuen Kontext an.

Die Beispiele verwenden gemeinsame Hilfsklassen. Lesen Sie Gemeinsame Hilfsklassen: vollständiger C#- und Java-Quellcode bevor Sie ein Beispiel übernehmen.
Voraussetzungen: userId, accessToken, Fragebogenantworten und gewählter Vorschlag aus den vorherigen Kapiteln. Bei Selbstregistrierung verwenden Sie den erhaltenen CLIENT-Token; eine administrative Integration behält ihren ADMIN-Token.
Für den nächsten Schritt aufbewahren: Die von der API zurückgegebenen Werte orderId und portfolioId. Eine erfolgreiche HTTP-Anfrage bedeutet noch nicht, dass die Depotbank das Portfolio angenommen oder investiert hat.
Vor dem Senden des Auftrags speichern Sie die Risikobereitschaft des Kunden und ergänzen weitere Profildaten, wie nachfolgend beschrieben.

Risikoprofil speichern

Eingaben: userId, CLIENT-Token und responseIds aus dem Kapitel Anlagevorschlag wählen.
Aktion: Speichern Sie die Fragebogenantworten.
Aufbewahren / erwartetes Ergebnis: Fahren Sie nach einer erfolgreichen Antwort fort und behalten Sie denselben Kunden- und Vorschlagskontext bei.
Nachdem der Kunde das ermittelte Risikoprofil akzeptiert hat, speichern Sie die Fragebogenantworten. Damit wird auch sein Risikoprofil archiviert:
								
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);

Fehlende Kundendaten ergänzen

Eingaben: Die vom Kunden eingegebenen Daten und die nachfolgend gezeigten Referenzwerte.
Aktion: Ergänzen Sie die vor der Portfolioeröffnung erforderlichen Informationen.
Aufbewahren / erwartetes Ergebnis: Fahren Sie nach erfolgreicher Aktualisierung fort und behalten Sie userId und den gewählten Vorschlag bei.
Erfassen und speichern Sie die für dieses Produkt noch fehlenden Daten. Adressen und Referenzcodes im Beispiel sind illustrativ. Verwenden Sie tatsächliche Kundendaten und aktivierte Referenzwerte. Vorhandene Werte werden durch diese Anfragen aktualisiert, wenn neue Werte nicht null sind. Zivilstandsdaten gelten nur für entsprechende Zivilstände; clientBirthDate muss das tatsächliche Geburtsdatum des Kunden sein.
								
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);

Vertrag vorbereiten

Der nachfolgende PDF-Erzeugungsendpunkt ist optional. Wenn ein vollständiger Vertrag bereits vorliegt, verwenden Sie diesen und fahren Sie mit Auftrag senden fort.

Eingaben: Kundendaten und gewählter Anlagevorschlag.
Aktion: Erstellen Sie einen PDF-Vertrag mit Ihrer eigenen Lösung oder dem optionalen PDF-Dienst.
Aufbewahren / erwartetes Ergebnis: C#: pdfContractAsByteArray. Java: contractPath verweist auf das erzeugte PDF.
Zum Senden des Eröffnungsauftrags wird ein PDF-Vertrag mit den Kundendaten benötigt. Die Vorlage wird vom Vermögensverwalter vorgegeben. Die Beispiele füllen eine freigegebene Vorlage mit Kunden- und Portfoliodaten. Ein optionaler PDF-Microservice kann die Vorlagenfelder ausfüllen; er gehört nicht zur Mindestkonfiguration. Verwenden Sie ihn, wenn er für Ihre Integration eingerichtet ist, oder erstellen Sie den Vertrag selbst. Nutzen Sie die freigegebene Vorlage und tatsächliche Kundendaten. Konfigurieren Sie pdfServiceBaseUrl, pdfServiceToken und contractPath separat von WMS und befüllen Sie formFields gemäss der Vorlage. Der PDF-Dienst ersetzt keine erforderliche Unterschrift oder Zustimmung. Zu Beginn des Beispiels bezeichnet contractPath die Eingabevorlage. Java setzt contractPath anschliessend auf eine separate Datei mit dem ausgefüllten PDF; der nächste Schritt muss diese Datei lesen. Ohne PDF-Dienst laden Sie in C# das vollständige PDF in pdfContractAsByteArray und setzen in Java contractPath auf die vollständige PDF-Datei. Bewahren Sie diese bis zum Senden auf und entfernen Sie temporäre Kopien, sobald Ihre Anwendung sie nicht mehr benötigt. Alternativ verwenden Sie den von Ihrer eigenen Lösung erstellten Vertrag:
Leere PDF-Vertragsvorlage
PDF-Vertrag mit ausgefüllten Kundendaten
								
// 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;

Auftrag senden

Eingaben: userId, gewählter Vorschlag, Anlagekategorie, Abweichungsgründe und PDF-Vertrag.
Aktion: Senden Sie den Eröffnungsauftrag.
Aufbewahren / erwartetes Ergebnis: Bewahren Sie orderId und portfolioId auf und prüfen Sie anschliessend den Auftragsstatus.
Wählen Sie die für das Produkt und die Umgebung unterstützte Depotbank. LIENHARDT dient hier nur als Beispiel und ist unabhängig vom Vermögensverwalter des Kunden. Jedes Portfolio hat eine eigene Depotbank. Zusätzlich senden Sie einen PDF-Vertrag mit Kundendaten und gewähltem Anlagevorschlag:
								
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;

Die API hat den Eröffnungsauftrag erfasst. Die Verarbeitung durch die Depotbank läuft asynchron weiter.
HTTP 201 liefert ein PortfolioOrderOutputModel. Id bezeichnet den Auftrag, PortfolioId das Portfolio. Location verweist auf den Auftrag. Bewahren Sie beide IDs auf und prüfen Sie den Auftragsstatus, bevor Sie von Annahme oder Investition ausgehen.
Wenn der Kunde eine andere als die vorgeschlagene Anlagestrategie wählt, muss der Parameter ReasonToChangeProposalResponsesIds mit den Antwort-IDs für die Abweichungsgründe befüllt werden.

Auftragsstatus verfolgen

Eingaben: Die beim Senden zurückgegebene orderId.
Aktion: Lesen Sie den Auftrag aus und lösen Sie OrderStatusId über die Statusreferenzliste auf.
Aufbewahren / erwartetes Ergebnis: Verwenden Sie den Status, um zu entscheiden, ob Sie warten oder ein Ergebnis anzeigen. Numerische Status-IDs haben in dieser Anleitung keine feste allgemeine Bedeutung.
Rufen Sie mit einem gültigen Bearer-Token GET api/v1/portfolio-order-statuses auf. Die Referenzliste enthält Id, Code und Description. Ordnen Sie OrderStatusId des aktuellen Auftrags einer Id zu; zeigen Sie Description an und verwenden Sie Code zur Interpretation des Verarbeitungsschritts. Setzen Sie keine feste numerische Zuordnung voraus und leiten Sie den Abschluss nicht allein aus HTTP-Erfolg ab. Senden Sie einen Auftrag nach einem Timeout nicht automatisch erneut: Prüfen Sie zuerst, ob er erfasst wurde. Über PortfolioId kann der Kunde das Portfolio abrufen; verfügbare Daten hängen von dessen Lebenszyklus ab.

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

Ergebnis erkennen

Illustrativer Ausschnitt einer Auftragsantwort. Die IDs sind Beispiele; lösen Sie den tatsächlichen Status über die Referenzliste auf. HTTP-Erfolg bestätigt den Eingang, spätere Statuswerte zeigen die Verarbeitung.

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

Weitere Antwortfelder finden Sie im DTO-Katalog. Behandeln Sie fehlgeschlagene Anfragen, bevor Sie fortfahren; siehe Fehlerbehandlung.