diff --git a/src/dotnet/padp-reference-web/PadpReferenceApi/ApimHelper.cs b/src/dotnet/padp-reference-web/PadpReferenceApi/ApimHelper.cs new file mode 100644 index 0000000..dfb2003 --- /dev/null +++ b/src/dotnet/padp-reference-web/PadpReferenceApi/ApimHelper.cs @@ -0,0 +1,614 @@ +using System.Text; +using System.Security.Cryptography.X509Certificates; +using System.Text.Json; +using Microsoft.AspNetCore.Http.HttpResults; + +class ApimHelper +{ + private readonly string _apimBaseUri; + private readonly string _b2bApiUri; + private readonly string _padp1201Uri; + private readonly string _padp1202Uri; + private readonly string _padp1204Uri; + private readonly string _padp1205Uri; + private readonly string _padp1206V2Uri; + private readonly string _padp1207V2Uri; + private readonly string _padp1206V3Uri; + private readonly string _padp1207V3Uri; + + private readonly string _padp1210Uri; + private readonly string _padp1211Uri; + + private readonly string _b2bApiKey; + private readonly string _padApiKey; + private readonly string _clientId; + private readonly string _clientSecret; + private CryptoHelper cryptoHelper; + private readonly HttpClient _httpClient; + + private Dictionary userAuthInfoMap = new Dictionary(); + + public ApimHelper(UserProperties userProperties) + { + _apimBaseUri = userProperties.Uris.ApimBaseUri; + _b2bApiUri = userProperties.Uris.B2bApiUri; + _padp1201Uri = userProperties.Uris.Padp1201Uri; + _padp1202Uri = userProperties.Uris.Padp1202Uri; + _padp1204Uri = userProperties.Uris.Padp1204Uri; + _padp1205Uri = userProperties.Uris.Padp1205Uri; + _padp1206V2Uri = userProperties.Uris.Padp1206V2Uri; + _padp1207V2Uri = userProperties.Uris.Padp1207V2Uri; + _padp1206V3Uri = userProperties.Uris.Padp1206V3Uri; + _padp1207V3Uri = userProperties.Uris.Padp1207V3Uri; + _padp1210Uri = userProperties.Uris.Padp1210Uri; + _padp1211Uri = userProperties.Uris.Padp1211Uri; + + _b2bApiKey = userProperties.Credentials.B2bApiKey; + _padApiKey = userProperties.Credentials.PadApiKey; + _clientId = userProperties.Credentials.ClientId; + _clientSecret = userProperties.Credentials.ClientSecret; + + cryptoHelper = new CryptoHelper(userProperties); + + HttpClientHandler handler = new HttpClientHandler(); + string clientCertPath = Environment.CurrentDirectory + userProperties.Credentials.ClientCertFile; + Console.WriteLine("Loading client cert from: {0}", clientCertPath); + handler.ClientCertificates.Add(new X509Certificate2(clientCertPath, userProperties.Credentials.ClientCertPassword)); + + _httpClient = new HttpClient(handler); + _httpClient.BaseAddress = new Uri(_apimBaseUri); + } + + public async Task, JsonHttpResult>> GetB2bAccessToken() + { + var request = new HttpRequestMessage(HttpMethod.Post, _b2bApiUri); + request.Headers.Add("APIKey", _b2bApiKey); + request.Headers.Add("Accept", "application/json"); + request.Content = new StringContent($"grant_type=client_credentials&client_id={_clientId}&client_secret={_clientSecret}", Encoding.UTF8, "application/x-www-form-urlencoded"); + + Console.WriteLine("Sending request to {0}...", _b2bApiUri); + + var response = await _httpClient.SendAsync(request); + + if (response.StatusCode == System.Net.HttpStatusCode.OK) { + return TypedResults.Ok(await response.Content.ReadFromJsonAsync()); + } else { + return TypedResults.Json(await response.Content.ReadFromJsonAsync(), JsonSerializerOptions.Default, "application/json", (int)response.StatusCode); + } + } + + private string GetB2bAccessTokenAsString() + { + var b2bAccessToken = GetB2bAccessToken(); + return ((Ok)b2bAccessToken.Result.Result).Value.accessToken; + } + + public IResult GetImageFromBase64(string base64Image) + { + byte[] imageBytes = Convert.FromBase64String(base64Image); + return TypedResults.File(imageBytes, "image/jpeg"); + } + + public async Task>> CreatePersonalData(string xTat, string email, string? name, string? birthDate, IFormFile? photo) + { + UserAuthInfo userAuthInfo; + if (userAuthInfoMap.TryGetValue(xTat, out UserAuthInfo? authInfo) && authInfo.encryptedEphemeralKey != null) { + userAuthInfo = authInfo; + } else { + var errorResponse = new ErrorResponse("No ephemeral key found for xTAT " + xTat + " - generate and validate OTP first using API 1210!", 400); + return TypedResults.Json(errorResponse, JsonSerializerOptions.Default, "application/json", 400); + } + + var b2bAccessToken = await GetB2bAccessToken(); + + var requestUri = _padp1201Uri.Replace("{xtat}", xTat); + var request = new HttpRequestMessage(HttpMethod.Post, requestUri); + request.Headers.Add("Authorization", "Bearer " + ((Ok)b2bAccessToken.Result).Value.accessToken); + request.Headers.Add("APIKey", _padApiKey); + request.Headers.Add("requestId", Guid.NewGuid().ToString()); + request.Headers.Add("Accept", "application/json"); + + var createPersonalDataRequest = new CreatePersonalDataRequest(); + createPersonalDataRequest.metadata.email = email; + createPersonalDataRequest.metadata.ephemeralKeyAlias = userAuthInfo.ephemeralKeyAlias; + + var decryptedEphemeralKey = cryptoHelper.DecryptEphemeralKey(userAuthInfo.encryptedEphemeralKey); + + if (name != null) { + createPersonalDataRequest.data.personalAccountData.name = cryptoHelper.EncryptText(decryptedEphemeralKey, name); + } + if (birthDate != null) { + createPersonalDataRequest.data.personalAccountData.birthdate = cryptoHelper.EncryptText(decryptedEphemeralKey, birthDate);; + } + if (photo != null) { + using (MemoryStream memoryStream = new MemoryStream()) + { + photo.CopyTo(memoryStream); + byte[] photoBytes = memoryStream.ToArray(); + createPersonalDataRequest.data.personalAccountData.photo = cryptoHelper.EncryptPhoto(decryptedEphemeralKey, photoBytes); + } + } + request.Content = JsonContent.Create(createPersonalDataRequest); + + Console.WriteLine("Sending request to {0} with request body {1}...", requestUri, request.Content.ReadAsStringAsync().Result); + + var response = await _httpClient.SendAsync(request); + + if (response.StatusCode == System.Net.HttpStatusCode.Created) { + return TypedResults.Created(); + } else { + return TypedResults.Json(await response.Content.ReadFromJsonAsync(), JsonSerializerOptions.Default, "application/json", (int)response.StatusCode); + } + } + + public async Task>> CreatePersonalDataWithEmailVerification(string xTat, string email, string? userAccessToken, string? name, string? birthDate, IFormFile? photo) + { + UserAuthInfo userAuthInfo; + if (userAuthInfoMap.TryGetValue(xTat, out UserAuthInfo? authInfo)) { + if (authInfo.encryptedEphemeralKey == null) { + var errorResponse = new ErrorResponse("No ephemeral key found for xTAT " + xTat + " - generate an ephemeral key first, using API 1210!", 400); + return TypedResults.Json(errorResponse, JsonSerializerOptions.Default, "application/json", 400); + } else if (authInfo.UserAccessToken == null && userAccessToken == null) { + var errorResponse = new ErrorResponse("No User Access Token found for xTAT " + xTat + " - provide one in the request, or generate and validate OTP using API 1206 and 1207!", 400); + return TypedResults.Json(errorResponse, JsonSerializerOptions.Default, "application/json", 400); + } + userAuthInfo = authInfo; + } else { + var errorResponse = new ErrorResponse("No ephemeral key found for xTAT " + xTat + " - generate an ephemeral key first, using API 1210!", 400); + return TypedResults.Json(errorResponse, JsonSerializerOptions.Default, "application/json", 400); + } + + var requestUri = _padp1201Uri.Replace("{xtat}", xTat); + var request = new HttpRequestMessage(HttpMethod.Post, requestUri); + + if (userAccessToken != null) { + Console.WriteLine("Using provided User Access Token {0}...", userAccessToken); + request.Headers.Add("Authorization", "Bearer " + userAccessToken); + } else { + Console.WriteLine("Using stored User Access Token {0}...", userAuthInfo.UserAccessToken); + request.Headers.Add("Authorization", "Bearer " + userAuthInfo.UserAccessToken); + } + request.Headers.Add("APIKey", _padApiKey); + request.Headers.Add("requestId", Guid.NewGuid().ToString()); + request.Headers.Add("Accept", "application/json"); + + var createPersonalDataRequest = new CreatePersonalDataRequest(); + createPersonalDataRequest.metadata.email = email; + createPersonalDataRequest.metadata.ephemeralKeyAlias = userAuthInfo.ephemeralKeyAlias; + + var decryptedEphemeralKey = cryptoHelper.DecryptEphemeralKey(userAuthInfo.encryptedEphemeralKey); + + if (name != null) { + createPersonalDataRequest.data.personalAccountData.name = cryptoHelper.EncryptText(decryptedEphemeralKey, name); + } + if (birthDate != null) { + createPersonalDataRequest.data.personalAccountData.birthdate = cryptoHelper.EncryptText(decryptedEphemeralKey, birthDate);; + } + if (photo != null) { + using (MemoryStream memoryStream = new MemoryStream()) + { + photo.CopyTo(memoryStream); + byte[] photoBytes = memoryStream.ToArray(); + createPersonalDataRequest.data.personalAccountData.photo = cryptoHelper.EncryptPhoto(decryptedEphemeralKey, photoBytes); + } + } + request.Content = JsonContent.Create(createPersonalDataRequest); + + Console.WriteLine("Sending request to {0} with request body {1}...", requestUri, request.Content.ReadAsStringAsync().Result); + + var response = await _httpClient.SendAsync(request); + + if (response.StatusCode == System.Net.HttpStatusCode.Created) { + return TypedResults.Created(); + } else { + return TypedResults.Json(await response.Content.ReadFromJsonAsync(), JsonSerializerOptions.Default, "application/json", (int)response.StatusCode); + } + } + + public async Task, JsonHttpResult>> GetPersonalData(string xTat, string? userAccessToken) + { + UserAuthInfo? userAuthInfo = null; + if (userAuthInfoMap.TryGetValue(xTat, out UserAuthInfo? authInfo) && authInfo.UserAccessToken != null) + { + userAuthInfo = authInfo; + } else if (userAccessToken == null) { + var errorResponse = new ErrorResponse("No User Access Token found for xTAT " + xTat + " - provide one in the request, or generate and validate OTP using API 1206 and 1207!", 400); + return TypedResults.Json(errorResponse, JsonSerializerOptions.Default, "application/json", 400); + } + + var requestUri = _padp1202Uri.Replace("{xtat}", xTat) + "?pemRsaPublicKey=" + cryptoHelper.GetPublicKeyRsa(); + var request = new HttpRequestMessage(HttpMethod.Get, requestUri); + + if (userAccessToken != null) + { + Console.WriteLine("Using provided User Access Token {0}...", userAccessToken); + request.Headers.Add("Authorization", "Bearer " + userAccessToken); + } else { + Console.WriteLine("Using stored User Access Token {0}...", userAuthInfo.UserAccessToken); + request.Headers.Add("Authorization", "Bearer " + userAuthInfo.UserAccessToken); + } + request.Headers.Add("APIKey", _padApiKey); + request.Headers.Add("requestId", Guid.NewGuid().ToString()); + request.Headers.Add("Accept", "application/json"); + + Console.WriteLine("Sending request to {0}...", requestUri); + + var response = await _httpClient.SendAsync(request); + + if (response.StatusCode == System.Net.HttpStatusCode.OK) + { + return TypedResults.Ok(await response.Content.ReadFromJsonAsync()); + } + else + { + return TypedResults.Json(await response.Content.ReadFromJsonAsync(), JsonSerializerOptions.Default, "application/json", (int)response.StatusCode); + } + } + + public async Task, JsonHttpResult>> GetDecryptedPersonalData(string xTat, string? userAccessToken) + { + var personalData = await GetPersonalData(xTat, userAccessToken); + if (personalData.Result.GetType() == typeof(JsonHttpResult)) { + return (JsonHttpResult)personalData.Result; + } else { + var decryptedPersonalData = await DecryptPersonalData(((Ok)personalData.Result).Value); + return TypedResults.Ok(decryptedPersonalData); + } + } + + public async Task, JsonHttpResult>> DeletePersonalData(string xTat, string? userAccessToken) + { + UserAuthInfo? userAuthInfo = null; + if (userAuthInfoMap.TryGetValue(xTat, out UserAuthInfo? authInfo) && authInfo.UserAccessToken != null) + { + userAuthInfo = authInfo; + } else if (userAccessToken == null) { + var errorResponse = new ErrorResponse("No User Access Token found for xTAT " + xTat + " - provide one in the request, or generate and validate OTP using API 1206 and 1207!", 400); + return TypedResults.Json(errorResponse, JsonSerializerOptions.Default, "application/json", 400); + } + + var requestUri = _padp1204Uri.Replace("{xtat}", xTat); + var request = new HttpRequestMessage(HttpMethod.Delete, requestUri); + + if (userAccessToken != null) { + Console.WriteLine("Using provided User Access Token {0}...", userAccessToken); + request.Headers.Add("Authorization", "Bearer " + userAccessToken); + } else { + Console.WriteLine("Using stored User Access Token {0}...", userAuthInfo.UserAccessToken); + request.Headers.Add("Authorization", "Bearer " + userAuthInfo.UserAccessToken); + } + request.Headers.Add("APIKey", _padApiKey); + request.Headers.Add("requestId", Guid.NewGuid().ToString()); + request.Headers.Add("Accept", "application/json"); + + Console.WriteLine("Sending request to {0}...", requestUri); + + var response = await _httpClient.SendAsync(request); + + if (response.StatusCode == System.Net.HttpStatusCode.OK) { + return TypedResults.Ok(await response.Content.ReadFromJsonAsync()); + } else { + return TypedResults.Json(await response.Content.ReadFromJsonAsync(), JsonSerializerOptions.Default, "application/json", (int)response.StatusCode); + } + } + + public async Task>> UpdatePersonalData(string xTat, string? userAccessToken, bool skipUpdateCounter, string? name, string? birthDate, IFormFile? photo) + { + UserAuthInfo userAuthInfo; + if (userAuthInfoMap.TryGetValue(xTat, out UserAuthInfo? authInfo)) { + if (authInfo.encryptedEphemeralKey == null) { + var errorResponse = new ErrorResponse("No ephemeral key found for xTAT " + xTat + " - generate an ephemeral key first, using API 1210!", 400); + return TypedResults.Json(errorResponse, JsonSerializerOptions.Default, "application/json", 400); + } else if (authInfo.UserAccessToken == null && userAccessToken == null) { + var errorResponse = new ErrorResponse("No User Access Token found for xTAT " + xTat + " - provide one in the request, or generate and validate OTP using API 1206 and 1207!", 400); + return TypedResults.Json(errorResponse, JsonSerializerOptions.Default, "application/json", 400); + } + userAuthInfo = authInfo; + } else { + var errorResponse = new ErrorResponse("No User Access Token found for xTAT " + xTat + " - provide one in the request, or generate and validate OTP using API 1206 and 1207!", 400); + return TypedResults.Json(errorResponse, JsonSerializerOptions.Default, "application/json", 400); + } + + var requestUri = _padp1205Uri.Replace("{xtat}", xTat); + var request = new HttpRequestMessage(HttpMethod.Put, requestUri); + + if (userAccessToken != null) { + Console.WriteLine("Using provided User Access Token {0}...", userAccessToken); + request.Headers.Add("Authorization", "Bearer " + userAccessToken); + } else { + Console.WriteLine("Using stored User Access Token {0}...", userAuthInfo.UserAccessToken); + request.Headers.Add("Authorization", "Bearer " + userAuthInfo.UserAccessToken); + } + request.Headers.Add("APIKey", _padApiKey); + request.Headers.Add("requestId", Guid.NewGuid().ToString()); + request.Headers.Add("Accept", "application/json"); + + var updatePersonalDataRequest = new UpdatePersonalDataRequest(); + updatePersonalDataRequest.metadata.skipUpdateCounter = skipUpdateCounter; + updatePersonalDataRequest.metadata.ephemeralKeyAlias = userAuthInfo.ephemeralKeyAlias; + + var decryptedEphemeralKey = cryptoHelper.DecryptEphemeralKey(userAuthInfo.encryptedEphemeralKey); + + if (name != null) { + updatePersonalDataRequest.data.personalAccountData.name = cryptoHelper.EncryptText(decryptedEphemeralKey, name); + } + if (birthDate != null) { + updatePersonalDataRequest.data.personalAccountData.birthdate = cryptoHelper.EncryptText(decryptedEphemeralKey, birthDate);; + } + if (photo != null) { + using (MemoryStream memoryStream = new MemoryStream()) + { + photo.CopyTo(memoryStream); + byte[] photoBytes = memoryStream.ToArray(); + updatePersonalDataRequest.data.personalAccountData.photo = cryptoHelper.EncryptPhoto(decryptedEphemeralKey, photoBytes); + } + } + request.Content = JsonContent.Create(updatePersonalDataRequest); + + Console.WriteLine("Sending request to {0} with request body {1}...", requestUri, request.Content.ReadAsStringAsync().Result); + + var response = await _httpClient.SendAsync(request); + + if (response.StatusCode == System.Net.HttpStatusCode.OK) { + return TypedResults.Ok(); + } else { + return TypedResults.Json(await response.Content.ReadFromJsonAsync(), JsonSerializerOptions.Default, "application/json", (int)response.StatusCode); + } + } + + public async Task, JsonHttpResult>> GenerateOtpV2(string xTat) + { + var requestUri = _padp1206V2Uri.Replace("{xtat}", xTat); + var request = new HttpRequestMessage(HttpMethod.Post, requestUri); + request.Headers.Add("Authorization", "Bearer " + GetB2bAccessTokenAsString()); + request.Headers.Add("APIKey", _padApiKey); + request.Headers.Add("requestId", Guid.NewGuid().ToString()); + request.Headers.Add("Accept", "application/json"); + request.Content = new StringContent("{\"channel\": \"EMAIL\"}", Encoding.UTF8, "application/json"); + + Console.WriteLine("Sending request to {0}...", requestUri); + + var response = await _httpClient.SendAsync(request); + + if (response.StatusCode == System.Net.HttpStatusCode.Accepted) { + return TypedResults.Json(await response.Content.ReadFromJsonAsync(), JsonSerializerOptions.Default, "application/json", (int)response.StatusCode); + } else { + return TypedResults.Json(await response.Content.ReadFromJsonAsync(), JsonSerializerOptions.Default, "application/json", (int)response.StatusCode); + } + } + + public async Task, JsonHttpResult>> ValidateOtpV2(string xTat, string otp) + { + var requestUri = _padp1207V2Uri.Replace("{xtat}", xTat); + var request = new HttpRequestMessage(HttpMethod.Post, requestUri); + request.Headers.Add("Authorization", "Bearer " + GetB2bAccessTokenAsString()); + request.Headers.Add("APIKey", _padApiKey); + request.Headers.Add("requestId", Guid.NewGuid().ToString()); + request.Headers.Add("Accept", "application/json"); + request.Content = new StringContent("{\"otp\": \"" + otp + "\"}", Encoding.UTF8, "application/json"); + + Console.WriteLine("Sending request to {0}...", requestUri); + + var response = await _httpClient.SendAsync(request); + + if (response.StatusCode == System.Net.HttpStatusCode.OK) { + var content = await response.Content.ReadFromJsonAsync(); + Console.WriteLine("Successfully retrieved User Access Token for xTAT {0} - storing for use in following calls...", xTat); + + if (!userAuthInfoMap.ContainsKey(xTat)) { + userAuthInfoMap.Add(xTat, new UserAuthInfo()); + } + UserAuthInfo userAuthInfo = userAuthInfoMap[xTat]; + userAuthInfo.UserAccessToken = content.accessToken; + + return TypedResults.Ok(content); + } else { + return TypedResults.Json(await response.Content.ReadFromJsonAsync(), JsonSerializerOptions.Default, "application/json", (int)response.StatusCode); + } + } + + public async Task, JsonHttpResult>> GenerateOtpV3(string? email, string? xTat) + { + var requestUri = _padp1206V3Uri; + var request = new HttpRequestMessage(HttpMethod.Post, requestUri); + request.Headers.Add("Authorization", "Bearer " + GetB2bAccessTokenAsString()); + request.Headers.Add("APIKey", _padApiKey); + request.Headers.Add("requestId", Guid.NewGuid().ToString()); + request.Headers.Add("Accept", "application/json"); + + var generateOtpRequest = new GenerateOtpRequestV3(); + + generateOtpRequest.channel = "EMAIL"; + if (email != null) { + generateOtpRequest.source = "EMAIL"; + generateOtpRequest.recipient = email; + } else if (xTat != null) { + generateOtpRequest.source = "XTAT"; + generateOtpRequest.recipient = xTat; + } else if (email != null && xTat != null){ + var errorResponse = new ErrorResponse("Both e-mail and xTAT filled, only one should be filled in the request!", 400); + return TypedResults.Json(errorResponse, JsonSerializerOptions.Default, "application/json", 400); + } + + request.Content = JsonContent.Create(generateOtpRequest); + + Console.WriteLine("Sending request to {0}...", requestUri); + + var response = await _httpClient.SendAsync(request); + + if (response.StatusCode == System.Net.HttpStatusCode.Accepted) { + return TypedResults.Json(await response.Content.ReadFromJsonAsync(), JsonSerializerOptions.Default, "application/json", (int)response.StatusCode); + } else { + return TypedResults.Json(await response.Content.ReadFromJsonAsync(), JsonSerializerOptions.Default, "application/json", (int)response.StatusCode); + } + } + + public async Task, JsonHttpResult>> ValidateOtpV3(string? email, string? xTat, string otp) + { + var requestUri = _padp1207V3Uri; + var request = new HttpRequestMessage(HttpMethod.Post, requestUri); + request.Headers.Add("Authorization", "Bearer " + GetB2bAccessTokenAsString()); + request.Headers.Add("APIKey", _padApiKey); + request.Headers.Add("requestId", Guid.NewGuid().ToString()); + request.Headers.Add("Accept", "application/json"); + + var validateOtpRequest = new ValidateOtpRequestV3(); + + validateOtpRequest.otp = otp; + if (email != null) + { + validateOtpRequest.source = "EMAIL"; + validateOtpRequest.recipient = email; + } + else if (xTat != null) + { + validateOtpRequest.source = "XTAT"; + validateOtpRequest.recipient = xTat; + } + else if (email != null && xTat != null) + { + var errorResponse = new ErrorResponse("Both e-mail and xTAT filled, only one should be filled in the request!", 400); + return TypedResults.Json(errorResponse, JsonSerializerOptions.Default, "application/json", 400); + } + + request.Content = JsonContent.Create(validateOtpRequest); + + Console.WriteLine("Sending request to {0}...", requestUri); + + var response = await _httpClient.SendAsync(request); + + if (response.StatusCode == System.Net.HttpStatusCode.OK) { + var content = await response.Content.ReadFromJsonAsync(); + + if (xTat != null) { + Console.WriteLine("Successfully retrieved User Access Token for xTAT {0} - storing for use in following calls...", xTat); + + if (!userAuthInfoMap.ContainsKey(xTat)) + { + userAuthInfoMap.Add(xTat, new UserAuthInfo()); + } + UserAuthInfo userAuthInfo = userAuthInfoMap[xTat]; + userAuthInfo.UserAccessToken = content.accessToken; + } + else { + Console.WriteLine("Successfully retrieved User Access Token for e-mail {0} - we don't know the xTAT (yet), so we don't cannot the user access token for later use..."); + } + + + return TypedResults.Ok(content); + } else { + return TypedResults.Json(await response.Content.ReadFromJsonAsync(), JsonSerializerOptions.Default, "application/json", (int)response.StatusCode); + } + } + + public async Task, JsonHttpResult>> GetAdministrativeData(string xTat) + { + var requestUri = _padp1211Uri.Replace("{xtat}", xTat); + var request = new HttpRequestMessage(HttpMethod.Get, requestUri); + request.Headers.Add("Authorization", "Bearer " + GetB2bAccessTokenAsString()); + request.Headers.Add("APIKey", _padApiKey); + request.Headers.Add("requestId", Guid.NewGuid().ToString()); + request.Headers.Add("Accept", "application/json"); + + Console.WriteLine("Sending request to {0}...", requestUri); + + var response = await _httpClient.SendAsync(request); + + if (response.StatusCode == System.Net.HttpStatusCode.OK) + { + return TypedResults.Ok(await response.Content.ReadFromJsonAsync()); + } + else + { + return TypedResults.Json(await response.Content.ReadFromJsonAsync(), JsonSerializerOptions.Default, "application/json", (int)response.StatusCode); + } + } + + public string EncryptDecryptPoc(string textToEncrypt, string encryptedEphemeralKey) + { + Console.WriteLine("Encrypting, then decrypting text {0} using encrypted ephemeral key {1}...", textToEncrypt, encryptedEphemeralKey); + var decryptedEphemeralKey = cryptoHelper.DecryptEphemeralKey(encryptedEphemeralKey); + var encryptedText = cryptoHelper.EncryptText(decryptedEphemeralKey, textToEncrypt); + Console.WriteLine($"Encrypted text: {encryptedText}"); + + Console.WriteLine("Decrypting text {0}...", encryptedText); + var decryptedText = cryptoHelper.DecryptTextAsync(decryptedEphemeralKey, encryptedText); + Console.WriteLine($"Decrypted text: {decryptedText.Result}"); + + return "Encrypted text: " + encryptedText + "\nDecrypted text: " + decryptedText.Result; + } + + public string EncryptPoc(string textToEncrypt, string encryptedEphemeralKey) + { + Console.WriteLine("Encrypting text {0} using encrypted ephemeral key {1}...", textToEncrypt, encryptedEphemeralKey); + var decryptedEphemeralKey = cryptoHelper.DecryptEphemeralKey(encryptedEphemeralKey); + var encryptedText = cryptoHelper.EncryptText(decryptedEphemeralKey, textToEncrypt); + Console.WriteLine($"Encrypted text: {encryptedText}"); + + return "Encrypted text: " + encryptedText; + } + + public string DecryptPoc(string textToDecrypt, string encryptedEphemeralKey) + { + Console.WriteLine("Decrypting text {0} using ephemeral key {1}...", textToDecrypt, encryptedEphemeralKey); + var decryptedEphemeralKey = cryptoHelper.DecryptEphemeralKey(encryptedEphemeralKey); + var decryptedText = cryptoHelper.DecryptTextAsync(decryptedEphemeralKey, textToDecrypt); + Console.WriteLine($"Decrypted text: {decryptedText.Result}"); + + return "Decrypted text: " + decryptedText.Result; + } + + public async Task, JsonHttpResult>> CreateEphemeralKey(string xTat) + { + var requestUri = _padp1210Uri + "?pemRsaPublicKey=" + cryptoHelper.GetPublicKeyRsa(); + var request = new HttpRequestMessage(HttpMethod.Post, requestUri); + request.Headers.Add("Authorization", "Bearer " + GetB2bAccessTokenAsString()); + request.Headers.Add("APIKey", _padApiKey); + request.Headers.Add("requestId", Guid.NewGuid().ToString()); + request.Headers.Add("Accept", "application/json"); + + Console.WriteLine("Sending request to {0}...", requestUri); + + var response = await _httpClient.SendAsync(request); + + if (response.StatusCode == System.Net.HttpStatusCode.Created) { + var content = await response.Content.ReadFromJsonAsync(); + Console.WriteLine("Successfully created Ephemeral Key for xTAT {0} - storing for use in following calls...", xTat); + if (!userAuthInfoMap.ContainsKey(xTat)) { + userAuthInfoMap.Add(xTat, new UserAuthInfo()); + } + UserAuthInfo userAuthInfo = userAuthInfoMap[xTat]; + userAuthInfo.ephemeralKeyAlias = content.ephemeralKeyAlias; + userAuthInfo.encryptedEphemeralKey = content.encryptedEphemeralKey; + + return TypedResults.Ok(content); + } else { + return TypedResults.Json(await response.Content.ReadFromJsonAsync(), JsonSerializerOptions.Default, "application/json", (int)response.StatusCode); + } + } + + private async Task DecryptPersonalData(PersonalData personalData) + { + var decryptedEphemeralKey = cryptoHelper.DecryptEphemeralKey(personalData.metadata.encryptedEphemeralKey); + Console.WriteLine("Decrypted encryptedEphemeralKey: {0}", Convert.ToBase64String(decryptedEphemeralKey)); + + var decryptedData = new DecryptedPersonalData.DecryptedData(); + + if (personalData.data.name != null) { + decryptedData.decryptedName = await cryptoHelper.DecryptTextAsync(decryptedEphemeralKey, personalData.data.name); + Console.WriteLine($"Decrypted name: {decryptedData.decryptedName}"); + } + + if (personalData.data.birthdate != null) { + decryptedData.decryptedBirthdate = await cryptoHelper.DecryptTextAsync(decryptedEphemeralKey, personalData.data.birthdate); + Console.WriteLine($"Decrypted birthDate: {decryptedData.decryptedBirthdate}"); + } + + if (personalData.data.photo != null) { + decryptedData.decryptedPhoto = await cryptoHelper.DecryptPhotoAsync(decryptedEphemeralKey, personalData.data.photo); + Console.WriteLine($"Decrypted photo: {decryptedData.decryptedPhoto}"); + } + + return new DecryptedPersonalData(decryptedData, personalData); + } +} \ No newline at end of file diff --git a/src/dotnet/padp-reference-web/PadpReferenceApi/CryptoHelper.cs b/src/dotnet/padp-reference-web/PadpReferenceApi/CryptoHelper.cs new file mode 100644 index 0000000..0e9799b --- /dev/null +++ b/src/dotnet/padp-reference-web/PadpReferenceApi/CryptoHelper.cs @@ -0,0 +1,124 @@ +using System.Security.Cryptography; +using System.Text; + +public class CryptoHelper +{ + private RSA rsa; + private string _publicKeyRsa; + + public CryptoHelper(UserProperties userProperties) + { + string publicKeyPath = Environment.CurrentDirectory + userProperties.Rsa.PublicKeyFile; + string privateKeyPath = Environment.CurrentDirectory + userProperties.Rsa.PrivateKeyFile; + + Console.WriteLine("Loading public key from: {0}", publicKeyPath); + Console.WriteLine("Loading private key from: {0}", privateKeyPath); + + rsa = RSA.Create(); + rsa.ImportFromPem(File.ReadAllText(publicKeyPath)); + rsa.ImportFromPem(File.ReadAllText(privateKeyPath)); + + _publicKeyRsa = Convert.ToBase64String(rsa.ExportSubjectPublicKeyInfo()); + } + + public string GetPublicKeyRsa() => _publicKeyRsa; + + public byte[] DecryptEphemeralKey(string encryptedEphemeralKey) + { + return rsa.Decrypt(Convert.FromBase64String(encryptedEphemeralKey), RSAEncryptionPadding.OaepSHA512); + } + + public async Task DecryptTextAsync(byte[] decryptedEphemeralKey, string encryptedText) + { + var decryptedBytes = await DecryptContentAsync(decryptedEphemeralKey, encryptedText); + return Encoding.UTF8.GetString(decryptedBytes); + } + + public async Task DecryptPhotoAsync(byte[] decryptedEphemeralKey, string encryptedPhoto) + { + var decryptedBytes = await DecryptContentAsync(decryptedEphemeralKey, encryptedPhoto); + return Convert.ToBase64String(decryptedBytes); + } + + public string EncryptText(byte[] decryptedEphemeralKey, string textValue) + { + return EncryptContent(decryptedEphemeralKey, Encoding.UTF8.GetBytes(textValue)); + } + + public string EncryptPhoto(byte[] decryptedEphemeralKey, byte[] photoBytes) + { + return EncryptContent(decryptedEphemeralKey, photoBytes); + } + + private async Task DecryptContentAsync(byte[] decryptedEphemeralKey, string encryptedData) + { + byte[] encryptedDataByteArray = Convert.FromBase64String(encryptedData); + using MemoryStream memoryStream = new(encryptedDataByteArray); + using Aes aes = Aes.Create(); + aes.Mode = CipherMode.CBC; + aes.Padding = PaddingMode.PKCS7; + + byte[] iv = new byte[aes.IV.Length]; + int numBytesToRead = aes.IV.Length; + int numBytesRead = 0; + while (numBytesToRead > 0) + { + int n = memoryStream.Read(iv, numBytesRead, numBytesToRead); + if (n == 0) + { + break; + } + numBytesRead += n; + numBytesToRead -= n; + } + + byte[] key = decryptedEphemeralKey; + + await using CryptoStream cryptoStream = new(memoryStream, + aes.CreateDecryptor(key, iv), + CryptoStreamMode.Read); + + using MemoryStream ms = new(); + await cryptoStream.CopyToAsync(ms); + var bytes = ms.ToArray(); + return bytes; + } + + private string EncryptContent(byte[] decryptedEphemeralKey, byte[] content) + { + byte[] encrypted; + byte[] iv; + + using (var aesAlg = Aes.Create()) + { + aesAlg.Key = decryptedEphemeralKey; + + aesAlg.GenerateIV(); + iv = aesAlg.IV; + + aesAlg.Mode = CipherMode.CBC; + aesAlg.Padding = PaddingMode.PKCS7; + + // Create an encryptor to perform the stream transform. + var encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV); + + // Create the streams used for encryption. + using (MemoryStream msEncrypt = new()) + { + using (CryptoStream csEncrypt = new(msEncrypt, encryptor, CryptoStreamMode.Write)) + { + + //Write all data to the stream. + csEncrypt.Write(content, 0, content.Length); + + } + encrypted = msEncrypt.ToArray(); + } + } + var combinedIvCt = new byte[iv.Length + encrypted.Length]; + Array.Copy(iv, 0, combinedIvCt, 0, iv.Length); + Array.Copy(encrypted, 0, combinedIvCt, iv.Length, encrypted.Length); + var encryptedData = Convert.ToBase64String(combinedIvCt); + return encryptedData; + } +} \ No newline at end of file diff --git a/src/dotnet/padp-reference-web/PadpReferenceApi/PadpReferenceApi.csproj b/src/dotnet/padp-reference-web/PadpReferenceApi/PadpReferenceApi.csproj new file mode 100644 index 0000000..0ed99c3 --- /dev/null +++ b/src/dotnet/padp-reference-web/PadpReferenceApi/PadpReferenceApi.csproj @@ -0,0 +1,15 @@ + + + + net8.0 + enable + enable + + + + + + + + + diff --git a/src/dotnet/padp-reference-web/PadpReferenceApi/Program.cs b/src/dotnet/padp-reference-web/PadpReferenceApi/Program.cs new file mode 100644 index 0000000..f352e32 --- /dev/null +++ b/src/dotnet/padp-reference-web/PadpReferenceApi/Program.cs @@ -0,0 +1,250 @@ +using Microsoft.AspNetCore.Http.HttpResults; +using Microsoft.AspNetCore.Mvc; +using Swashbuckle.AspNetCore.Annotations; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(setupAction => +{ + setupAction.EnableAnnotations(); + setupAction.SchemaFilter(); +}); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +var group = app.MapGroup("/").DisableAntiforgery().WithTags("PADP Reference API"); +var pocGroup = app.MapGroup("/poc").DisableAntiforgery().WithTags("Encrypt/decrypt POC"); + +UserProperties userProperties = app.Configuration.GetSection("UserProperties").Get(); + +ApimHelper apimHelper = new ApimHelper(userProperties); + +group.MapGet("/idp/b2b-access-token", () => +{ + return apimHelper.GetB2bAccessToken(); +}) +.Produces(200) +.Produces(400) +.Produces(404) +.Produces(500) +.WithName("GetB2bAccessToken") +.WithSummary("API 1020 - Get B2B Access Token") +.WithDescription("Returns a client access token, needed for most other PADP APIs.") +.WithOpenApi(); + +group.MapGet("/get-image-from-base64", ([FromHeader]string base64String) => +{ + return apimHelper.GetImageFromBase64(base64String); +}) +.Produces(200, "image/jpeg") +.WithName("GetImageFromBase64") +.WithSummary("Get rendered image from Base64 encoded String") +.WithOpenApi(); + +group.MapPost("/personal-data/{xtat}", (string xTat, [SwaggerParameter("Email address to be used for OTP challenges")] string email, [SwaggerParameter("Should be at least two words (first name and last name)")] string? name, [SwaggerParameter("Should be a date between 1900-01-01 and now, in the format YYYY-MM-DD")] string? birthDate, [SwaggerSchema("Should be a JPG image, of max. 512KB and resolution between 520x520 and 720x720")] IFormFile? photo) => +{ + Console.WriteLine("Creating personal data for xTAT: " + xTat); + return apimHelper.CreatePersonalData(xTat, email, name, birthDate, photo); +}) +.Produces(201) +.Produces(400) +.Produces(404) +.Produces(500) +.WithName("CreatePersonalData") +.WithSummary("API 1201 - Create Personal Data - First create an ephemeral key using API 1210!") +.WithDescription("First create an ephemeral key using API 1210!") +.WithOpenApi(); + +group.MapPost("/personal-data/{xtat}/with-email-verification", (string xTat, [SwaggerParameter("Email address to be used for OTP challenges")]string email, [SwaggerParameter("This user access token will be used for e-mail verification if provided - otherwise, the internally stored one (based on previous V3 1206+1207 calls) will be used")]string? userAccessToken, [SwaggerParameter("Should be at least two words (first name and last name)")]string? name, [SwaggerParameter("Should be a date between 1900-01-01 and now, in the format YYYY-MM-DD")]string? birthDate, [SwaggerSchema("Should be a JPG image, of max. 512KB and resolution between 520x520 and 720x720")]IFormFile? photo) => +{ + Console.WriteLine("Creating personal data with e-mail verification for xTAT: " + xTat); + return apimHelper.CreatePersonalDataWithEmailVerification(xTat, email, userAccessToken,name, birthDate, photo); +}) +.Produces(201) +.Produces(400) +.Produces(404) +.Produces(500) +.WithName("CreatePersonalDataWithEmailVerification") +.WithSummary("API 1201 - Create Personal Data with e-mail verification - First create an ephemeral key using API 1210!") +.WithDescription("Performs extra e-mail verification using OTP flow; using internally stored (after 1206+1207 call) or explicitly provided user access token. First create an ephemeral key using API 1210!") +.WithOpenApi(); + +group.MapGet("/personal-data/{xtat}", (string xTat, [SwaggerParameter("If provided, this user access token will be used over the internally stored one (that was restrieved via earlier 1206+1207 calls)")]string? userAccessToken) => +{ + Console.WriteLine("Retrieving personal data for xTAT: " + xTat); + return apimHelper.GetPersonalData(xTat, userAccessToken); +}) +.Produces(200) +.Produces(400) +.Produces(404) +.Produces(500) +.WithName("GetPersonalData") +.WithSummary("API 1202 - Get Personal Data - First perform an OTP challenge using API 1206 and 1207 or explicitly provide user access token!") +.WithDescription("First perform an OTP challenge using API 1206 and 1207 or explicitly provide user access token!") +.WithOpenApi(); + +group.MapGet("/personal-data/{xtat}/decrypted", (string xTat, [SwaggerParameter("If provided, this user access token will be used over the internally stored one (that was restrieved via earlier 1206+1207 calls)")]string? userAccessToken) => +{ + Console.WriteLine("Retrieving decrypted personal data for xTAT: " + xTat); + return apimHelper.GetDecryptedPersonalData(xTat, userAccessToken); +}) +.Produces(200) +.Produces(400) +.Produces(404) +.Produces(500) +.WithName("GetDecryptedPersonalData") +.WithSummary("API 1202 - Get Personal Data AND decrypt response - First perform an OTP challenge using API 1206 and 1207 or explicitly provide user access token!") +.WithDescription("First perform an OTP challenge using API 1206 and 1207 or explicitly provide user access token!") +.WithOpenApi(); + +group.MapDelete("/personal-data/{xtat}", (string xTat, [SwaggerParameter("If provided, this user access token will be used over the internally stored one (that was restrieved via earlier 1206+1207 calls)")]string? userAccessToken) => +{ + Console.WriteLine("Deleting personal data for xTAT: " + xTat); + return apimHelper.DeletePersonalData(xTat, userAccessToken); +}) +.Produces(200) +.Produces(400) +.Produces(404) +.Produces(500) +.WithName("DeletePersonalData") +.WithSummary("API 1204 - Delete Personal Data - First perform an OTP challenge using API 1206 and 1207 or explicitly provide user access token!") +.WithDescription("First perform an OTP challenge using API 1206 and 1207 or explicitly provide user access token!") +.WithOpenApi(); + +group.MapPut("/personal-data/{xtat}", (string xTat, [SwaggerParameter("If provided, this user access token will be used over the internally stored one (that was restrieved via earlier 1206+1207 calls)")]string? userAccessToken, bool skipUpdateCounter, [SwaggerParameter("Should be at least two words (first name and last name)")]string? name, [SwaggerParameter("Should be a date between 1900-01-01 and now, in the format YYYY-MM-DD")]string? birthDate, [SwaggerSchema("Should be a JPG image, of max. 512KB and resolution between 520x520 and 720x720")]IFormFile? photo) => +{ + Console.WriteLine("Replacing personal data for xTAT: " + xTat); + return apimHelper.UpdatePersonalData(xTat, userAccessToken, skipUpdateCounter, name, birthDate, photo); +}) +.WithName("UpdatePersonalData") +.WithSummary("API 1205 - Update Personal Data - First perform an OTP challenge using API 1206 and 1207 or explicitly provide user access token!") +.WithDescription("Performs a complete replacement; empty request parameters will result in the corresponding PADP attribute being deleted.") +.WithOpenApi(); + +group.MapGet("/v2/personal-data/{xtat}/generate-otp", (string xTat) => +{ + Console.WriteLine("Generating OTP for xTAT: " + xTat); + return apimHelper.GenerateOtpV2(xTat); +}) +.Produces(200) +.Produces(400) +.Produces(404) +.Produces(500) +.WithName("GenerateOtpV2") +.WithSummary("API 1206 V2 - Generate OTP") +.WithOpenApi(); + +group.MapGet("/v2/personal-data/{xtat}/validate-otp", (string xTat, string otp) => +{ + Console.WriteLine("Validating OTP {0} for xTAT: {1}", otp, xTat); + return apimHelper.ValidateOtpV2(xTat, otp); +}) +.Produces(200) +.Produces(400) +.Produces(404) +.Produces(500) +.WithName("ValidateOtpV2") +.WithSummary("API 1207 V2 - Validate OTP") +.WithOpenApi(); + +group.MapGet("/v3/personal-data/generate-otp", (string? email, string? xTat) => +{ + if (email != null){ + Console.WriteLine("Generating OTP for e-mail: " + email); + } else if (xTat != null){ + Console.WriteLine("Generating OTP for xTAT: " + xTat); + } + return apimHelper.GenerateOtpV3(email, xTat); +}) +.Produces(200) +.Produces(400) +.Produces(404) +.Produces(500) +.WithName("GenerateOtpV3") +.WithSummary("API 1206 V3 - Generate OTP") +.WithDescription("If e-mail is provided, the OTP is generated for the e-mail address. If xTAT is provided, the OTP is generated for the xTAT.") +.WithOpenApi(); + +group.MapGet("/v3/personal-data/validate-otp", (string? email, string? xTat, string otp) => +{ + if (email != null){ + Console.WriteLine("Validating OTP {0} for e-mail: {1}", otp, email); + } else if (xTat != null){ + Console.WriteLine("Validating OTP {0} for xTAT: {1}", otp, xTat); + } + return apimHelper.ValidateOtpV3(email, xTat, otp); +}) +.Produces(200) +.Produces(400) +.Produces(404) +.Produces(500) +.WithName("ValidateOtpV3") +.WithSummary("API 1207 V3 - Validate OTP") +.WithDescription("If e-mail is provided, the OTP is validated for the e-mail address. If xTAT is provided, the OTP is validated for the xTAT.") +.WithOpenApi(); + +group.MapGet("/personal-data/{xtat}/encrypted-update-init", (string xTat) => +{ + return apimHelper.CreateEphemeralKey(xTat); +}) +.Produces(200) +.Produces(400) +.Produces(404) +.Produces(500) +.WithName("CreateEphemeralKey") +.WithSummary("API 1210 - Create Ephemeral Key") +.WithOpenApi(); + +group.MapGet("/personal-data/{xtat}/administrative-data", (string xTat) => +{ + Console.WriteLine("Retrieving administrative data for xTAT: {0}", xTat); + return apimHelper.GetAdministrativeData(xTat); +}) +.Produces(200) +.Produces(400) +.Produces(404) +.Produces(500) +.WithName("GetAdministrativeData") +.WithSummary("API 1211 - Get Administrative Data") +.WithOpenApi(); + +pocGroup.MapGet("/encrypt-decrypt-poc", ([FromHeader]string textToEncrypt, string encryptedEphemeralKey) => +{ + Console.WriteLine("Text to encrypt: {0}", textToEncrypt); + return apimHelper.EncryptDecryptPoc(textToEncrypt, encryptedEphemeralKey); +}) +.WithName("EncryptDecryptPoc") +.WithSummary("Encrypt/Decrypt POC") +.WithOpenApi(); + +pocGroup.MapGet("/encrypt-poc", ([FromHeader]string textToEncrypt, string encryptedEphemeralKey) => +{ + Console.WriteLine("Text to encrypt: {0}", textToEncrypt); + return apimHelper.EncryptPoc(textToEncrypt, encryptedEphemeralKey); +}) +.WithName("EncryptPoc") +.WithSummary("Encrypt POC") +.WithOpenApi(); + +pocGroup.MapGet("/decrypt-poc", ([FromHeader]string textToDecrypt, string encryptedEphemeralKey) => +{ + Console.WriteLine("Text to decrypt: {0}", textToDecrypt); + return apimHelper.DecryptPoc(textToDecrypt, encryptedEphemeralKey); +}) +.WithName("DecryptPoc") +.WithSummary("Decrypt POC") +.WithOpenApi(); + +app.Run(); diff --git a/src/dotnet/padp-reference-web/PadpReferenceApi/appsettings template.json b/src/dotnet/padp-reference-web/PadpReferenceApi/appsettings template.json new file mode 100644 index 0000000..29ee000 --- /dev/null +++ b/src/dotnet/padp-reference-web/PadpReferenceApi/appsettings template.json @@ -0,0 +1,38 @@ +// Rename this file to appsettings.json and provide correct credentials and locations for the cryptographic files +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "UserProperties": { + "uris": { + "apimBaseUri": "https://api-ovpay-acc.translink.nl", + "b2bApiUri": "/b2b-client-authentication/v1/token", + "padp1201Uri": "/pad-management/v2/personal-data/{xtat}", + "padp1202Uri": "/pad-management/v2/personal-data/{xtat}", + "padp1204Uri": "/pad-management/v2/personal-data/{xtat}", + "padp1205Uri": "/pad-management/v2/personal-data/{xtat}", + "padp1206V2Uri": "/pad-management/v2/personal-data/{xtat}/generate-otp", + "padp1207V2Uri": "/pad-management/v2/personal-data/{xtat}/validate-otp", + "padp1206V3Uri": "/pad-management/v3/personal-data/generate-otp", + "padp1207V3Uri": "/pad-management/v3/personal-data/validate-otp", + "padp1210Uri": "/pad-management/v2/personal-data/encrypted-update-init", + "padp1211Uri": "/pad-management/v2/personal-data/{xtat}/administrative-data" + }, + "rsa": { + "publicKeyFile": "\\Properties\\{public key in PEM format}.pem", + "privateKeyFile": "\\Properties\\{private key in PEM format}.pem" + }, + "credentials": { + "clientCertFile": "\\Properties\\{APIM client certificate}.pfx", + "clientCertPassword": "{pfxPassword}", + "clientId": "HTM_Retailer", + "clientSecret": "{clientSecret}", + "b2bApiKey": "{b2bApiKey}", + "padApiKey": "{padApiKey}" + } + } +} diff --git a/src/dotnet/padp-reference-web/PadpReferenceApi/model/AdministrativeData.cs b/src/dotnet/padp-reference-web/PadpReferenceApi/model/AdministrativeData.cs new file mode 100644 index 0000000..e061744 --- /dev/null +++ b/src/dotnet/padp-reference-web/PadpReferenceApi/model/AdministrativeData.cs @@ -0,0 +1,44 @@ +using System.Text.Json.Serialization; + +public class AdministrativeData +{ + public AdministrativeData(AdministrativeDataElement name, AdministrativeDataElement photo, AdministrativeDataElement birthdate) + { + this.name = name; + this.photo = photo; + this.birthdate = birthdate; + } + + public AdministrativeDataElement name { get; set; } + public AdministrativeDataElement photo { get; set; } + public AdministrativeDataElement birthdate { get; set; } + + public class AdministrativeDataElement + { + public AdministrativeDataElement(bool inaccuracyFlag, string inaccuracyFlagReason, int inaccuracyFlagCounter, int changeCounter, int maxUpdatesVerificationCount, DateTime lastChangeDate, bool isValidated) + { + this.inaccuracyFlag = inaccuracyFlag; + this.inaccuracyFlagReason = inaccuracyFlagReason; + this.inaccuracyFlagCounter = inaccuracyFlagCounter; + this.changeCounter = changeCounter; + this.maxUpdatesVerificationCount = maxUpdatesVerificationCount; + this.lastChangeDate = lastChangeDate; + this.isValidated = isValidated; + } + + [JsonPropertyName("inaccuracyFlag")] + public bool inaccuracyFlag { get; set; } + [JsonPropertyName("inaccuracyFlagReason")] + public string inaccuracyFlagReason { get; set; } + [JsonPropertyName("inaccuracyFlagCounter")] + public int inaccuracyFlagCounter { get; set; } + [JsonPropertyName("changeCounter")] + public int changeCounter { get; set; } + [JsonPropertyName("maxUpdatesVerificationCount")] + public int maxUpdatesVerificationCount { get; set; } + [JsonPropertyName("lastChangeDate")] + public DateTime lastChangeDate { get; set; } + [JsonPropertyName("isValidated")] + public bool isValidated { get; set; } + } +} \ No newline at end of file diff --git a/src/dotnet/padp-reference-web/PadpReferenceApi/model/B2bAccessToken.cs b/src/dotnet/padp-reference-web/PadpReferenceApi/model/B2bAccessToken.cs new file mode 100644 index 0000000..c5de68f --- /dev/null +++ b/src/dotnet/padp-reference-web/PadpReferenceApi/model/B2bAccessToken.cs @@ -0,0 +1,33 @@ +using System.Text.Json.Serialization; + +public class B2bAccessToken +{ + public B2bAccessToken(string accessToken, int expiresIn, int refreshExpiresIn, string refreshToken, string tokenType, int notBeforePolicy, string scope, string beId) + { + this.accessToken = accessToken; + this.expiresIn = expiresIn; + this.refreshExpiresIn = refreshExpiresIn; + this.refreshToken = refreshToken; + this.tokenType = tokenType; + this.notBeforePolicy = notBeforePolicy; + this.scope = scope; + this.beId = beId; + } + + [JsonPropertyName("access_token")] + public string accessToken { get; set; } + [JsonPropertyName("expires_in")] + public int expiresIn { get; set; } + [JsonPropertyName("refresh_expires_in")] + public int refreshExpiresIn { get; set; } + [JsonPropertyName("refresh_token")] + public string refreshToken { get; set; } + [JsonPropertyName("token_type")] + public string tokenType { get; set; } + [JsonPropertyName("not-before-policy")] + public int notBeforePolicy { get; set; } + [JsonPropertyName("scope")] + public string scope { get; set; } + [JsonPropertyName("BE_ID")] + public string beId { get; set; } +} \ No newline at end of file diff --git a/src/dotnet/padp-reference-web/PadpReferenceApi/model/CreatePersonalDataRequest.cs b/src/dotnet/padp-reference-web/PadpReferenceApi/model/CreatePersonalDataRequest.cs new file mode 100644 index 0000000..b01bec7 --- /dev/null +++ b/src/dotnet/padp-reference-web/PadpReferenceApi/model/CreatePersonalDataRequest.cs @@ -0,0 +1,29 @@ +using System.Text.Json.Serialization; + +public class CreatePersonalDataRequest +{ + + [JsonPropertyName("metadata")] + public Metadata metadata { get; set; } = new Metadata(); + + [JsonPropertyName("data")] + public Data data { get; set; } = new Data(); + + public class Metadata + { + public string? email { get; set; } + public string? ephemeralKeyAlias { get; set; } + } + + public class Data + { + public PersonalAccountData personalAccountData { get; set; } = new PersonalAccountData(); + } + + public class PersonalAccountData + { + public string? name { get; set; } + public string? birthdate { get; set; } + public string? photo { get; set; } + } +} \ No newline at end of file diff --git a/src/dotnet/padp-reference-web/PadpReferenceApi/model/DecryptedPersonalData.cs b/src/dotnet/padp-reference-web/PadpReferenceApi/model/DecryptedPersonalData.cs new file mode 100644 index 0000000..379e85d --- /dev/null +++ b/src/dotnet/padp-reference-web/PadpReferenceApi/model/DecryptedPersonalData.cs @@ -0,0 +1,23 @@ +using System.Text.Json.Serialization; +using Swashbuckle.AspNetCore.Annotations; + +public class DecryptedPersonalData +{ + public DecryptedPersonalData(DecryptedData decryptedData, PersonalData encryptedData) + { + this.decryptedData = decryptedData; + this.encryptedData = encryptedData; + } + + public DecryptedData decryptedData { get; set; } + public PersonalData encryptedData { get; set; } + public class DecryptedData + { + [JsonPropertyName("decryptedName")] + public string? decryptedName { get; set; } + [JsonPropertyName("decryptedBirthdate")] + public string? decryptedBirthdate { get; set; } + [SwaggerSchema(Format = "byte", Description = "Base64 encoded photo")] + public string? decryptedPhoto { get; set; } + } +} \ No newline at end of file diff --git a/src/dotnet/padp-reference-web/PadpReferenceApi/model/DeletePersonalDataResponse.cs b/src/dotnet/padp-reference-web/PadpReferenceApi/model/DeletePersonalDataResponse.cs new file mode 100644 index 0000000..e003672 --- /dev/null +++ b/src/dotnet/padp-reference-web/PadpReferenceApi/model/DeletePersonalDataResponse.cs @@ -0,0 +1,12 @@ +using System.Text.Json.Serialization; + +public class DeletePersonalDataResponse +{ + public DeletePersonalDataResponse(string[] deletedAttributes) + { + this.deletedAttributes = deletedAttributes; + } + + [JsonPropertyName("deletedAttributes")] + public string[] deletedAttributes { get; set; } +} \ No newline at end of file diff --git a/src/dotnet/padp-reference-web/PadpReferenceApi/model/EphemeralKey.cs b/src/dotnet/padp-reference-web/PadpReferenceApi/model/EphemeralKey.cs new file mode 100644 index 0000000..71c8806 --- /dev/null +++ b/src/dotnet/padp-reference-web/PadpReferenceApi/model/EphemeralKey.cs @@ -0,0 +1,16 @@ +using System.Text.Json.Serialization; + +public class EphemeralKey +{ + public EphemeralKey(string ephemeralKeyAlias, string encryptedEphemeralKey) + { + this.ephemeralKeyAlias = ephemeralKeyAlias; + this.encryptedEphemeralKey = encryptedEphemeralKey; + } + + [JsonPropertyName("ephemeralKeyAlias")] + public string ephemeralKeyAlias { get; set; } + + [JsonPropertyName("encryptedEphemeralKey")] + public string encryptedEphemeralKey { get; set; } +} \ No newline at end of file diff --git a/src/dotnet/padp-reference-web/PadpReferenceApi/model/ErrorResponse.cs b/src/dotnet/padp-reference-web/PadpReferenceApi/model/ErrorResponse.cs new file mode 100644 index 0000000..3c09561 --- /dev/null +++ b/src/dotnet/padp-reference-web/PadpReferenceApi/model/ErrorResponse.cs @@ -0,0 +1,38 @@ +using System.Text.Json.Serialization; + +public class ErrorResponse +{ + [JsonConstructor] + public ErrorResponse(Error[] errors, string exceptionClassName, string exceptionStackTrace) + { + this.errors = errors; + this.exceptionClassName = exceptionClassName; + this.exceptionStackTrace = exceptionStackTrace; + } + + public ErrorResponse(string errorMessage, int statusCode) + { + this.errors = new Error[] { new Error(statusCode.ToString(), new string[] { }, errorMessage) }; + this.exceptionClassName = null; + this.exceptionStackTrace = null; + } + + public Error[] errors { get; set; } + public string? exceptionClassName { get; set; } + public string? exceptionStackTrace { get; set; } + + public class Error + { + public Error(string code, string[] data, string message) + { + this.code = code; + this.data = data; + this.message = message; + } + + public string code { get; set; } + public string[] data { get; set; } + public string message { get; set; } + } + +} \ No newline at end of file diff --git a/src/dotnet/padp-reference-web/PadpReferenceApi/model/GenerateOtpRequestV3.cs b/src/dotnet/padp-reference-web/PadpReferenceApi/model/GenerateOtpRequestV3.cs new file mode 100644 index 0000000..b158287 --- /dev/null +++ b/src/dotnet/padp-reference-web/PadpReferenceApi/model/GenerateOtpRequestV3.cs @@ -0,0 +1,13 @@ +using System.Text.Json.Serialization; + +public class GenerateOtpRequestV3 +{ + [JsonPropertyName("source")] + public string source { get; set; } + + [JsonPropertyName("recipient")] + public string recipient { get; set; } + + [JsonPropertyName("channel")] + public string channel { get; set; } +} \ No newline at end of file diff --git a/src/dotnet/padp-reference-web/PadpReferenceApi/model/OtpResponse.cs b/src/dotnet/padp-reference-web/PadpReferenceApi/model/OtpResponse.cs new file mode 100644 index 0000000..3293a74 --- /dev/null +++ b/src/dotnet/padp-reference-web/PadpReferenceApi/model/OtpResponse.cs @@ -0,0 +1,12 @@ +using System.Text.Json.Serialization; + +public class OtpResponse +{ + public OtpResponse(string maskedEmailAddress) + { + this.maskedEmailAddress = maskedEmailAddress; + } + + [JsonPropertyName("maskedEmailAddress")] + public string maskedEmailAddress { get; set; } +} \ No newline at end of file diff --git a/src/dotnet/padp-reference-web/PadpReferenceApi/model/PersonalData.cs b/src/dotnet/padp-reference-web/PadpReferenceApi/model/PersonalData.cs new file mode 100644 index 0000000..ffb7acc --- /dev/null +++ b/src/dotnet/padp-reference-web/PadpReferenceApi/model/PersonalData.cs @@ -0,0 +1,42 @@ +using System.Reflection.Metadata.Ecma335; +using System.Text.Json.Serialization; + +public class PersonalData +{ + public PersonalData(Metadata metadata, Data data) + { + this.metadata = metadata; + this.data = data; + } + + public Metadata metadata { get; set; } + public Data data { get; set; } + + public class Metadata + { + public Metadata(string encryptedEphemeralKey) + { + this.encryptedEphemeralKey = encryptedEphemeralKey; + } + + [JsonPropertyName("encryptedEphemeralKey")] + public string encryptedEphemeralKey { get; set; } + } + + public class Data + { + public Data(string name, string birthdate, string photo) + { + this.name = name; + this.birthdate = birthdate; + this.photo = photo; + } + + [JsonPropertyName("name")] + public string name { get; set; } + [JsonPropertyName("birthdate")] + public string birthdate { get; set; } + [JsonPropertyName("photo")] + public string photo { get; set; } + } +} \ No newline at end of file diff --git a/src/dotnet/padp-reference-web/PadpReferenceApi/model/SwaggerSchemaExampleAttribute.cs b/src/dotnet/padp-reference-web/PadpReferenceApi/model/SwaggerSchemaExampleAttribute.cs new file mode 100644 index 0000000..ca2cd05 --- /dev/null +++ b/src/dotnet/padp-reference-web/PadpReferenceApi/model/SwaggerSchemaExampleAttribute.cs @@ -0,0 +1,42 @@ +using System.Reflection; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; + +[AttributeUsage( + AttributeTargets.Class | + AttributeTargets.Struct | + AttributeTargets.Parameter | + AttributeTargets.Property | + AttributeTargets.Enum, + AllowMultiple = false)] +public class SwaggerSchemaExampleAttribute : Attribute +{ + public SwaggerSchemaExampleAttribute(string example) + { + Example = example; + } + + public string Example { get; set; } +} + +public class SwaggerSchemaExampleFilter : ISchemaFilter +{ + public void Apply(OpenApiSchema schema, SchemaFilterContext context) + { + if (context.MemberInfo != null) + { + var schemaAttribute = context.MemberInfo.GetCustomAttributes() + .FirstOrDefault(); + if (schemaAttribute != null) + ApplySchemaAttribute(schema, schemaAttribute); + } + } + + private void ApplySchemaAttribute(OpenApiSchema schema, SwaggerSchemaExampleAttribute schemaAttribute) + { + if (schemaAttribute.Example != null) + { + schema.Example = new Microsoft.OpenApi.Any.OpenApiString(schemaAttribute.Example); + } + } +} \ No newline at end of file diff --git a/src/dotnet/padp-reference-web/PadpReferenceApi/model/UpdatePersonalDataRequest.cs b/src/dotnet/padp-reference-web/PadpReferenceApi/model/UpdatePersonalDataRequest.cs new file mode 100644 index 0000000..69b2e7b --- /dev/null +++ b/src/dotnet/padp-reference-web/PadpReferenceApi/model/UpdatePersonalDataRequest.cs @@ -0,0 +1,29 @@ +using System.Text.Json.Serialization; + +public class UpdatePersonalDataRequest +{ + + [JsonPropertyName("metadata")] + public Metadata metadata { get; set; } = new Metadata(); + + [JsonPropertyName("data")] + public Data data { get; set; } = new Data(); + + public class Metadata + { + public bool skipUpdateCounter { get; set; } + public string? ephemeralKeyAlias { get; set; } + } + + public class Data + { + public PersonalAccountData personalAccountData { get; set; } = new PersonalAccountData(); + } + + public class PersonalAccountData + { + public string? name { get; set; } + public string? birthdate { get; set; } + public string? photo { get; set; } + } +} \ No newline at end of file diff --git a/src/dotnet/padp-reference-web/PadpReferenceApi/model/UserAccessToken.cs b/src/dotnet/padp-reference-web/PadpReferenceApi/model/UserAccessToken.cs new file mode 100644 index 0000000..cfaa62f --- /dev/null +++ b/src/dotnet/padp-reference-web/PadpReferenceApi/model/UserAccessToken.cs @@ -0,0 +1,12 @@ +using System.Text.Json.Serialization; + +public class UserAccessToken +{ + public UserAccessToken(string accessToken) + { + this.accessToken = accessToken; + } + + [JsonPropertyName("accessToken")] + public string accessToken { get; set; } +} \ No newline at end of file diff --git a/src/dotnet/padp-reference-web/PadpReferenceApi/model/UserAuthInfo.cs b/src/dotnet/padp-reference-web/PadpReferenceApi/model/UserAuthInfo.cs new file mode 100644 index 0000000..3ee6c5a --- /dev/null +++ b/src/dotnet/padp-reference-web/PadpReferenceApi/model/UserAuthInfo.cs @@ -0,0 +1,7 @@ +public class UserAuthInfo +{ + public string? UserAccessToken { get; set; } + public string? ephemeralKeyAlias { get; set; } + public string? encryptedEphemeralKey { get; set; } + +} \ No newline at end of file diff --git a/src/dotnet/padp-reference-web/PadpReferenceApi/model/UserProperties.cs b/src/dotnet/padp-reference-web/PadpReferenceApi/model/UserProperties.cs new file mode 100644 index 0000000..4ee9540 --- /dev/null +++ b/src/dotnet/padp-reference-web/PadpReferenceApi/model/UserProperties.cs @@ -0,0 +1,58 @@ +public class UserProperties +{ + public UserProperties(UrisSettings uris, RsaSettings rsa, CredentialsSettings credentials) + { + (Uris, Rsa, Credentials) = (uris, rsa, credentials); + } + + public UrisSettings Uris { get; set; } + public RsaSettings Rsa { get; set; } + public CredentialsSettings Credentials { get; set; } + + public class UrisSettings + { + public UrisSettings(string apimBaseUri, string b2bApiUri, string padp1201Uri, string padp1202Uri, string padp1204Uri, string padp1205Uri, string padp1206V2Uri, string padp1207V2Uri, string padp1206V3Uri, string padp1207V3Uri, string padp1210Uri, string padp1211Uri) + { + (ApimBaseUri, B2bApiUri, Padp1201Uri, Padp1202Uri, Padp1204Uri, Padp1205Uri, Padp1206V2Uri, Padp1207V2Uri, Padp1206V3Uri, Padp1207V3Uri, Padp1210Uri, Padp1211Uri) = (apimBaseUri, b2bApiUri, padp1201Uri, padp1202Uri, padp1204Uri, padp1205Uri, padp1206V2Uri, padp1207V2Uri, padp1206V3Uri, padp1207V3Uri, padp1210Uri, padp1211Uri); + } + + public string ApimBaseUri { get; set; } + public string B2bApiUri { get; set; } + public string Padp1201Uri { get; set; } + public string Padp1202Uri { get; set; } + public string Padp1204Uri { get; set; } + public string Padp1205Uri { get; set; } + public string Padp1206V2Uri { get; set; } + public string Padp1207V2Uri { get; set; } + public string Padp1206V3Uri { get; set; } + public string Padp1207V3Uri { get; set; } + public string Padp1210Uri { get; set; } + public string Padp1211Uri { get; set; } + } + + public class RsaSettings + { + public RsaSettings(string publicKeyFile, string privateKeyFile) + { + (PublicKeyFile, PrivateKeyFile) = (publicKeyFile, privateKeyFile); + } + + public string PublicKeyFile { get; set; } + public string PrivateKeyFile { get; set; } + } + + public class CredentialsSettings + { + public CredentialsSettings(string clientCertFile, string clientCertPassword, string clientId, string clientSecret, string b2bApiKey, string padApiKey) + { + (ClientCertFile, ClientCertPassword, ClientId, ClientSecret, B2bApiKey, PadApiKey) = (clientCertFile, clientCertPassword, clientId, clientSecret, b2bApiKey, padApiKey); + } + + public string ClientCertFile { get; set; } + public string ClientCertPassword { get; set; } + public string ClientId { get; set; } + public string ClientSecret { get; set; } + public string B2bApiKey { get; set; } + public string PadApiKey { get; set; } + } +} \ No newline at end of file diff --git a/src/dotnet/padp-reference-web/PadpReferenceApi/model/ValidateOtpRequestV3.cs b/src/dotnet/padp-reference-web/PadpReferenceApi/model/ValidateOtpRequestV3.cs new file mode 100644 index 0000000..9f7e0a9 --- /dev/null +++ b/src/dotnet/padp-reference-web/PadpReferenceApi/model/ValidateOtpRequestV3.cs @@ -0,0 +1,13 @@ +using System.Text.Json.Serialization; + +public class ValidateOtpRequestV3 +{ + [JsonPropertyName("otp")] + public string otp { get; set; } + + [JsonPropertyName("source")] + public string source { get; set; } + + [JsonPropertyName("recipient")] + public string recipient { get; set; } +} \ No newline at end of file diff --git a/src/java/rabbitmq-poc/src/main/java/nl/ovpay/queue/Helpers.java b/src/java/rabbitmq-poc/src/main/java/nl/ovpay/queue/Helpers.java index fce38e7..e436d25 100644 --- a/src/java/rabbitmq-poc/src/main/java/nl/ovpay/queue/Helpers.java +++ b/src/java/rabbitmq-poc/src/main/java/nl/ovpay/queue/Helpers.java @@ -28,10 +28,33 @@ public final class Helpers { return new JSONObject(string).get("alertId").toString(); } + public static String getTripId(String string) throws IOException { + return new JSONObject(string).get("tripId").toString(); + } + public static String getXbot(String string) throws IOException { return new JSONObject(string).get("xbot").toString(); } + public static void getTripDetails(String tripId, String xbot, String gboBearerToken) throws Exception { + SSLContext sc = SSLContext.getInstance("SSL"); + sc.init(null, DummyX509TrustManager.getDummyArray(), new java.security.SecureRandom()); + HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); + + URL url = new URL("https://api.sbx.idbt.translink.nl/api/v3/id-media/tokens/xbot/" + xbot + "/trips/details/" + tripId); + URLConnection con = url.openConnection(); + HttpURLConnection http = (HttpURLConnection)con; + http.setRequestMethod("GET"); + http.setDoOutput(true); + http.setRequestProperty("Authorization", "Bearer " + gboBearerToken); + http.connect(); + + try(InputStream is = http.getInputStream()) { + String response = new String(is.readAllBytes(), StandardCharsets.UTF_8); + LOGGER.info("GBO API 8659 trip details response for xBOT " + xbot + " and tripId " + tripId + ": \n" + new JSONObject(response).toString(2)); + } + } + public static void getAlertDetails(String alertId, String xBot, String gboBearerToken) throws Exception { SSLContext sc = SSLContext.getInstance("SSL"); sc.init(null, DummyX509TrustManager.getDummyArray(), new java.security.SecureRandom()); @@ -48,7 +71,7 @@ public final class Helpers { try(InputStream is = http.getInputStream()) { String response = new String(is.readAllBytes(), StandardCharsets.UTF_8); - LOGGER.info("GBO API 8851 alert details response for xBOT " + xBot + ": \n" + new JSONObject(response).toString(2)); + LOGGER.info("GBO API 8851 alert details response for xBOT " + xBot + " and alertId " + alertId + ": \n" + new JSONObject(response).toString(2)); } } diff --git a/src/java/rabbitmq-poc/src/main/java/nl/ovpay/queue/RabbitConnector.java b/src/java/rabbitmq-poc/src/main/java/nl/ovpay/queue/RabbitConnector.java index a408dc0..ced65ec 100644 --- a/src/java/rabbitmq-poc/src/main/java/nl/ovpay/queue/RabbitConnector.java +++ b/src/java/rabbitmq-poc/src/main/java/nl/ovpay/queue/RabbitConnector.java @@ -16,14 +16,28 @@ public class RabbitConnector { private static final Logger LOGGER = LoggerFactory.getLogger(RabbitConnector.class); + // TRIPS + // SubscriptionId = 3e246de5-d3ad-468f-834b-1aaebf52244c + // Use API 9853 to manually add xBOT to queue + private static final String QUEUE_NAME = "BEID_3.TRIPS"; + private static final String USER_NAME = "BEID_3_TRIPS_HlTT"; + private static final String PASSWORD = "xJR4C8hIqhHQw0sn"; + + // ALERTS + // SubscriptionId = 17c8100b-88a2-4cef-b40d-8dca4f93d311 + // Use API 9853 to manually add xBOT to queue + // private static final String QUEUE_NAME = "BEID_3.ALERTS"; + // private static final String USER_NAME = "BEID_3_ALERTS_nZs3"; + // private static final String PASSWORD = "VyubhPnczKgTB2zJ"; + public static void main(String[] args) throws Exception { ConnectionFactory factory = new ConnectionFactory(); factory.setVirtualHost("/"); factory.setAutomaticRecoveryEnabled(true); factory.setPort(443); factory.setHost("not.sbx.idbt.translink.nl"); - factory.setUsername("BEID_3_ALERTS_nZs3"); - factory.setPassword("VyubhPnczKgTB2zJ"); + factory.setUsername(USER_NAME); + factory.setPassword(PASSWORD); factory.useSslProtocol("TLSv1.2"); factory.setExceptionHandler(new ForgivingExceptionHandler()); Map configs = factory.getClientProperties(); @@ -33,7 +47,7 @@ public class RabbitConnector { Channel channel = connection.createChannel(); DeliverCallback deliverCallback = initDeliverCallback(channel); - AMQP.Queue.DeclareOk queue = channel.queueDeclarePassive("BEID_3.ALERTS"); + AMQP.Queue.DeclareOk queue = channel.queueDeclarePassive(QUEUE_NAME); LOGGER.info( "Declared queue: " + queue.getQueue() + ", consumer count: " + queue.getConsumerCount() + ", message count: " + queue.getMessageCount()); @@ -52,17 +66,39 @@ public class RabbitConnector { channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false); LOGGER.info("Successfully acknowledged message with delivery tag: " + delivery.getEnvelope().getDeliveryTag()); - LOGGER.info("Getting alert details via GBO API 8851..."); - try { - String alertId = Helpers.getAlertId(message); - String xBot = Helpers.getXbot(message); - String gboBearerToken = Helpers.getGboBearerToken(); - - Helpers.getAlertDetails(alertId, xBot, gboBearerToken); - - } catch (Exception e) { - throw new RuntimeException(e); + if (QUEUE_NAME.equals("BEID_3.TRIPS")) { + getTripDetails(message); + } else if (QUEUE_NAME.equals("BEID_3.ALERTS")) { + getAlertDetails(message); } }; } + + private static void getAlertDetails(String message) { + try { + String alertId = Helpers.getAlertId(message); + String xBot = Helpers.getXbot(message); + String gboBearerToken = Helpers.getGboBearerToken(); + + LOGGER.info("Getting alert details for xBOT {} and alertId {} via GBO API 8851...", xBot, alertId); + Helpers.getAlertDetails(alertId, xBot, gboBearerToken); + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static void getTripDetails(String message) { + try { + String tripId = Helpers.getTripId(message); + String xBot = Helpers.getXbot(message); + String gboBearerToken = Helpers.getGboBearerToken(); + + LOGGER.info("Getting trip details for xBOT {} and tripId {} via GBO API 8659...", xBot, tripId); + Helpers.getTripDetails(tripId, xBot, gboBearerToken); + + } catch (Exception e) { + throw new RuntimeException(e); + } + } } diff --git a/src/openapi/customers/SE-customers.yaml b/src/openapi/customers/SE-customers.yaml index 0f75322..c982add 100644 --- a/src/openapi/customers/SE-customers.yaml +++ b/src/openapi/customers/SE-customers.yaml @@ -166,10 +166,10 @@ paths: "detail": "Meer dan 1 klantprofiel gevonden. Verfijn je zoekcriteria.", "instance": "555d00b5-bc3f-4591-949b-479e76d49ea7", } - post: + post: tags: - Customers - parameters: + parameters: - name: X-HTM-ROLE-HEADER in: header schema: @@ -178,7 +178,7 @@ paths: required: false description: The role of the HTM employee in the case of the SMP, in this case the call can only be done through the SMP summary: Create a customer profile. - description: Create a customer profile in the ABT database with status ACTIVE and debtorStatus Inactive with a start date based on the timestamp of this post call. + description: Create a customer profile in the ABT database with status ACTIVE and debtorStatus Inactive with a start date based on the timestamp of this post call. requestBody: content: application/json: @@ -187,73 +187,71 @@ paths: examples: minimalCustomerProfile: value: - { - "person": { - "emailAddress": "j.jansen@hatseflats.nl" - } - } + { "person": { "emailAddress": "j.jansen@hatseflats.nl" } } fullCustomerProfile: value: { - "customerPreference": { - "languageId": 1 - }, - "person": { - "birthname": "Jan", - "surname": "Jansen", - "prefix": "dhr", - "suffix": "jr", - "dateOfBirth": "1970-01-01", - "emailAddress": "j.jansen@hatseflats.nl", - "addresses": [ - { - "street": "Laan van Meerdervoort", - "houseNumber": 5, - "houseNumberSuffix": "B", - "postalCode": "2500AA", - "city": "Den Haag", - "country": "NL", - "isPreferred": true, - "addressTypeId": 1 - }, - { - "street": "Beeklaan", - "houseNumber": 30, - "houseNumberSuffix": "B", - "postalCode": "2500AA", - "city": "Den Haag", - "country": "NL", - "isPreferred": false, - "addressTypeId": 2 - } - ], - "phones": [ - { - "number": "6123456789", - "countryCode": "0031", - "phoneTypeId": 1, - "isPreferred": true - }, - { - "number": "7012345678", - "countryCode": "0031", - "phoneTypeId": 2, - "isPreferred": false - } - ], - "devices": [ - { - "externalDeviceId": "123e4567-e89b-12d3-a456-426614174000", - "alias": "My iPhone", - }, - { - "externalDeviceId": "987e6543-e21b-12d3-a456-426614174999", - "alias": "My iPad", - } - ] - } + "customerPreference": { "languageId": 1 }, + "person": + { + "birthname": "Jan", + "surname": "Jansen", + "prefix": "dhr", + "suffix": "jr", + "dateOfBirth": "1970-01-01", + "emailAddress": "j.jansen@hatseflats.nl", + "addresses": + [ + { + "street": "Laan van Meerdervoort", + "houseNumber": 5, + "houseNumberSuffix": "B", + "postalCode": "2500AA", + "city": "Den Haag", + "country": "NL", + "isPreferred": true, + "addressTypeId": 1, + }, + { + "street": "Beeklaan", + "houseNumber": 30, + "houseNumberSuffix": "B", + "postalCode": "2500AA", + "city": "Den Haag", + "country": "NL", + "isPreferred": false, + "addressTypeId": 2, + }, + ], + "phones": + [ + { + "number": "6123456789", + "countryCode": "0031", + "phoneTypeId": 1, + "isPreferred": true, + }, + { + "number": "7012345678", + "countryCode": "0031", + "phoneTypeId": 2, + "isPreferred": false, + }, + ], + "devices": + [ + { + "externalDeviceId": "123e4567-e89b-12d3-a456-426614174000", + "alias": "My iPhone", + }, + { + "externalDeviceId": "987e6543-e21b-12d3-a456-426614174999", + "alias": "My iPad", + }, + ], + }, } - responses: + responses: "201": description: Created content: @@ -287,7 +285,7 @@ paths: description: The role of the HTM employee in the case of the SMP summary: Update a customer profile description: Update a customer profile based on the sub from the JWT, or customerProfileId in case of SMP - requestBody: + requestBody: content: application/json: schema: @@ -296,70 +294,74 @@ paths: patchCustomer: value: { - "person": { - "birthname": "Jan", - "surname": "Jansen", - "prefix": "dhr", - "suffix": "jr", - "dateOfBirth": "1970-01-01", - "addresses": [ - { - "addressId": 2, - "street": "Laan van Meerdervoort", - "houseNumber": 5, - "postalCode": "2500AA", - "city": "Den Haag", - "country": "NL" - }, - { - "addressId": 1, - "street": "Beeklaan", - "houseNumber": 30, - "houseNumberSuffix": "B", - "postalCode": "2500AA", - "city": "Den Haag", - "country": "NL", - "addressTypeId": 2 - } - ], - "phones": [ - { - "phoneId": 1, - "number": "6123456789", - "countryCode": "0031", - "phoneTypeId": 1, - "isPreferred": true - }, - { - "phoneId": 2, - "number": "7012345678", - "countryCode": "0031", - "phoneTypeId": 2, - "isPreferred": false - } - ], - "devices": [ - { - "deviceId": "813afdd8-bf8c-4e26-bfda-4da79552bd38", - "externalDeviceId": "123e4567-e89b-12d3-a456-426614174000", - "alias": "My iPhone", - }, - { - "deviceId": "4f4249a2-ac6c-44f9-b740-66e66b6f3c28", - "externalDeviceId": "987e6543-e21b-12d3-a456-426614174999", - "alias": "My iPad", - } - ] - } - } - responses: + "person": + { + "birthname": "Jan", + "surname": "Jansen", + "prefix": "dhr", + "suffix": "jr", + "dateOfBirth": "1970-01-01", + "addresses": + [ + { + "addressId": 2, + "street": "Laan van Meerdervoort", + "houseNumber": 5, + "postalCode": "2500AA", + "city": "Den Haag", + "country": "NL", + }, + { + "addressId": 1, + "street": "Beeklaan", + "houseNumber": 30, + "houseNumberSuffix": "B", + "postalCode": "2500AA", + "city": "Den Haag", + "country": "NL", + "addressTypeId": 2, + }, + ], + "phones": + [ + { + "phoneId": 1, + "number": "6123456789", + "countryCode": "0031", + "phoneTypeId": 1, + "isPreferred": true, + }, + { + "phoneId": 2, + "number": "7012345678", + "countryCode": "0031", + "phoneTypeId": 2, + "isPreferred": false, + }, + ], + "devices": + [ + { + "deviceId": "813afdd8-bf8c-4e26-bfda-4da79552bd38", + "externalDeviceId": "123e4567-e89b-12d3-a456-426614174000", + "alias": "My iPhone", + }, + { + "deviceId": "4f4249a2-ac6c-44f9-b740-66e66b6f3c28", + "externalDeviceId": "987e6543-e21b-12d3-a456-426614174999", + "alias": "My iPad", + }, + ], + }, + } + responses: "200": description: OK content: application/json: schema: $ref: "#/components/schemas/CustomersResponse" - + /customers/tokens: get: tags: @@ -585,7 +587,7 @@ paths: "name": null, "birthdate": null, "photo": null, - }, + }, "gboAgeProfile": null, "_links": { @@ -707,7 +709,7 @@ paths: "gboAgeProfileId": 1, "name": "Kind (4 t/m 11 jaar)", "ageFromInclusive": 4, - "ageToInclusive": 11 + "ageToInclusive": 11, }, "_links": { @@ -804,7 +806,7 @@ paths: "gboAgeProfileId": 1, "name": "Kind (4 t/m 11 jaar)", "ageFromInclusive": 4, - "ageToInclusive": 11 + "ageToInclusive": 11, }, "_links": { @@ -914,12 +916,12 @@ paths: "lastChangeDate": "2024-08-24T14:15:22Z", }, }, - "gboAgeProfile": + "gboAgeProfile": { "gboAgeProfileId": 1, "name": "Kind (4 t/m 11 jaar)", "ageFromInclusive": 4, - "ageToInclusive": 11 + "ageToInclusive": 11, }, "_links": { @@ -1037,7 +1039,7 @@ paths: "birthdate": null, "photo": null, }, - "gboAgeProfile": null + "gboAgeProfile": null, }, { "customerProfileId": 132, @@ -1058,7 +1060,7 @@ paths: "birthdate": null, "photo": null, }, - "gboAgeProfile": null + "gboAgeProfile": null, }, { "customerProfileId": 166, @@ -1082,7 +1084,7 @@ paths: "birthdate": null, "photo": null, }, - "gboAgeProfile": null + "gboAgeProfile": null, }, { "customerProfileId": 166, @@ -1103,7 +1105,7 @@ paths: "birthdate": null, "photo": null, }, - "gboAgeProfile": null + "gboAgeProfile": null, }, { "customerProfileId": 1, @@ -1124,7 +1126,7 @@ paths: "birthdate": null, "photo": null, }, - "gboAgeProfile": null + "gboAgeProfile": null, }, ], _links: @@ -2369,7 +2371,7 @@ paths: "ePurse": null, "personalAccountData": { "name": null, "birthdate": null, "photo": null }, - "gboAgeProfile": null + "gboAgeProfile": null, }, "newOvPayToken": { @@ -2386,7 +2388,7 @@ paths: "ePurse": null, "personalAccountData": { "name": null, "birthdate": null, "photo": null }, - "gboAgeProfile": null + "gboAgeProfile": null, }, "isTransferable": true, "transferableObjects": @@ -2399,7 +2401,7 @@ paths: "ePurse": true, "personalAccountData": { "name": true, "birthdate": true, "photo": true }, - "gboAgeProfile": true + "gboAgeProfile": true, }, "_links": { @@ -2423,50 +2425,57 @@ paths: "tokenStatus": { "tokenStatusId": 2, "name": "Active" }, "expirationDate": "2028-02-01", - "productInstances": [ - { - "productId": 1, - "name": "HTM 90% Korting EMV", - "status": "Active", - "isRenewable": true, - "productCategory": { - "productCategoryId": 1, - "name": "Kortingsabonnement" + "productInstances": + [ + { + "productId": 1, + "name": "HTM 90% Korting EMV", + "status": "Active", + "isRenewable": true, + "productCategory": + { + "productCategoryId": 1, + "name": "Kortingsabonnement", + }, + "fromInclusive": "2025-11-25T13:25:00+01:00", + "untilInclusive": "2025-12-25T03:59:59+01:00", + "orderId": "501B17EF-36C4-4039-B92C-6517969B464E", + "orderLineId": "38B17EF-36C4-4039-B92C-4817969B464E", + "contractId": "56B17EF-C436-9043-B76C-481797WEB464F", + "_links": + { + "self": + { + "href": "https://api.integratielaag.nl/abt/touchpoint/1.0/customers/tokens/1/productinstances/1", + "method": "GET", + }, + "get_order": + { + "href": "https://api.integratielaag.nl/abt/touchpoint/1.0/orders/501B17EF-36C4-4039-B92C-6517969B464E", + "method": "GET", + }, + "get_contract": + { + "href": "https://api.integratielaag.nl/abt/touchpoint/1.0/customers/contracts/56B17EF-C436-9043-B76C-481797WEB464F", + "method": "GET", + }, + }, }, - "fromInclusive": "2025-11-25T13:25:00+01:00", - "untilInclusive": "2025-12-25T03:59:59+01:00", - "orderId": "501B17EF-36C4-4039-B92C-6517969B464E", - "orderLineId": "38B17EF-36C4-4039-B92C-4817969B464E", - "contractId": "56B17EF-C436-9043-B76C-481797WEB464F", - "_links": { - "self": { - "href": "https://api.integratielaag.nl/abt/touchpoint/1.0/customers/tokens/1/productinstances/1", - "method": "GET" - }, - "get_order": { - "href": "https://api.integratielaag.nl/abt/touchpoint/1.0/orders/501B17EF-36C4-4039-B92C-6517969B464E", - "method": "GET" - }, - "get_contract": { - "href": "https://api.integratielaag.nl/abt/touchpoint/1.0/customers/contracts/56B17EF-C436-9043-B76C-481797WEB464F", - "method": "GET" - } - } - } - ], + ], "replacedByTokenId": null, "autoReloadRegistration": null, "ePurse": null, "personalAccountData": { "name": null, "birthdate": null, "photo": null }, - "gboAgeProfile": null + "gboAgeProfile": null, }, "newOvPayToken": { "customerProfileId": null, "ovPayTokenId": null, "xTat": "32089cc8-d187-47ff-a3a9-5c2558def811", - "tokenType": { "tokenTypeId": 2, "name": "OV-pas physical" }, + "tokenType": + { "tokenTypeId": 2, "name": "OV-pas physical" }, "alias": null, "tokenStatus": null, "expirationDate": "2028-02-01", @@ -2476,7 +2485,7 @@ paths: "ePurse": null, "personalAccountData": { "name": null, "birthdate": null, "photo": null }, - "gboAgeProfile": null + "gboAgeProfile": null, }, "isTransferable": false, "transferableObjects": @@ -2513,7 +2522,7 @@ paths: "ePurse": null, "personalAccountData": { "name": null, "birthdate": null, "photo": null }, - "gboAgeProfile": null + "gboAgeProfile": null, }, "newOvPayToken": { @@ -2530,7 +2539,7 @@ paths: "ePurse": null, "personalAccountData": { "name": null, "birthdate": null, "photo": null }, - "gboAgeProfile": null + "gboAgeProfile": null, }, "isTransferable": false, "transferableObjects": @@ -2602,8 +2611,8 @@ paths: "gboAgeProfileId": 1, "name": "Kind (4 t/m 11 jaar)", "ageFromInclusive": 4, - "ageToInclusive": 11 - } + "ageToInclusive": 11, + }, }, "newOvPayToken": { @@ -2620,7 +2629,7 @@ paths: "ePurse": null, "personalAccountData": { "name": null, "birthdate": null, "photo": null }, - "gboAgeProfile": null + "gboAgeProfile": null, }, "isTransferable": false, "transferableObjects": @@ -2674,12 +2683,12 @@ paths: "photo": null, }, "gboAgeProfile": - { - "gboAgeProfileId": 1, - "name": "Kind (4 t/m 11 jaar)", - "ageFromInclusive": 4, - "ageToInclusive": 11 - } + { + "gboAgeProfileId": 1, + "name": "Kind (4 t/m 11 jaar)", + "ageFromInclusive": 4, + "ageToInclusive": 11, + }, }, "newOvPayToken": { @@ -2709,13 +2718,13 @@ paths: }, "photo": null, }, - "gboAgeProfile": - { - "gboAgeProfileId": 4, - "name": "Kind (19 t/m 65 jaar)", - "ageFromInclusive": 19, - "ageToInclusive": 65 - } + "gboAgeProfile": + { + "gboAgeProfileId": 4, + "name": "Kind (19 t/m 65 jaar)", + "ageFromInclusive": 19, + "ageToInclusive": 65, + }, }, "isTransferable": false, "transferableObjects": @@ -3075,10 +3084,10 @@ paths: }, "gboAgeProfile": { - "gboAgeProfileId": 1, - "name": "Kind (4 t/m 11 jaar)", - "ageFromInclusive": 4, - "ageToInclusive": 11 + "gboAgeProfileId": 1, + "name": "Kind (4 t/m 11 jaar)", + "ageFromInclusive": 4, + "ageToInclusive": 11, }, "_links": { @@ -3119,60 +3128,22 @@ paths: }, }, } + "202": + description: Accepted + content: + application/json: + schema: + $ref: "#/components/schemas/unavailable" + examples: Token transfer in progress: description: | The transfer of the token is still in progress. The response body shows the details of the processing status. value: { - "properties": - { - "waitEndTime": "2025-06-04T09:29:21.9641991Z", - "startTime": "2025-06-04T09:29:21.9641991Z", - "status": "Running", - "correlation": - { - "clientTrackingId": "08584525775244808022011782750CU00", - }, - "workflow": - { - "id": "/workflows/9cd96b77c0b94b31832778569f8ef2f9/versions/08584532480062676349", - "name": "08584532480062676349", - "type": "workflows/versions", - }, - "trigger": - { - "name": "token_transfer", - "inputsLink": - { - "uri": "https://htm-abt-logicapp-acc.azurewebsites.net:443/runtime/webhooks/workflow/scaleUnits/prod-00/workflows/9cd96b77c0b94b31832778569f8ef2f9/runs/08584525775235278538475939776CU00/contents/TriggerInputs?api-version=2022-05-01&code=C6PDQGl3MGwt8KyA9BjWDdQbzBwm-01gEmZaTp-hPJ5UAzFuPU-thg%3d%3d&se=2025-06-04T13%3A00%3A00.0000000Z&sp=%2Fruns%2F08584525775235278538475939776CU00%2Fcontents%2FTriggerInputs%2Fread&sv=1.0&sig=6Uxs33K7cQ7jONWzhv9XFPzx4RRHZ6smzfM6wNPk5Mc", - "contentSize": 298, - }, - "outputsLink": - { - "uri": "https://htm-abt-logicapp-acc.azurewebsites.net:443/runtime/webhooks/workflow/scaleUnits/prod-00/workflows/9cd96b77c0b94b31832778569f8ef2f9/runs/08584525775235278538475939776CU00/contents/TriggerOutputs?api-version=2022-05-01&code=C6PDQGl3MGwt8KyA9BjWDdQbzBwm-01gEmZaTp-hPJ5UAzFuPU-thg%3d%3d&se=2025-06-04T13%3A00%3A00.0000000Z&sp=%2Fruns%2F08584525775235278538475939776CU00%2Fcontents%2FTriggerOutputs%2Fread&sv=1.0&sig=vJ6pmCsmz2aP7f73MVOmCTes3YvC1e2w0ZLqdypLXrM", - "contentSize": 6110, - }, - "startTime": "2025-06-04T09:29:21.9497457Z", - "endTime": "2025-06-04T09:29:21.9497457Z", - "originHistoryName": "08584525775235278538475939776CU00", - "correlation": - { - "clientTrackingId": "08584525775244808022011782750CU00", - }, - "status": "Succeeded", - }, - "outputs": {}, - "response": - { - "startTime": "2025-06-04T09:29:21.9642901Z", - "correlation": {}, - "status": "Waiting", - }, - }, - "id": "/workflows/9cd96b77c0b94b31832778569f8ef2f9/runs/08584525775235278538475939776CU00", - "name": "08584525775235278538475939776CU00", - "type": "workflows/runs", + "startTime": "2025-02-14T05:32:47.067Z", + "status": "Running", + "clientTrackingId": "08584620957189579629541919368CU00", } "404": description: Not found @@ -3371,10 +3342,7 @@ paths: $ref: "#/components/schemas/unavailable" examples: Update alias of a device: - value: - { - "alias": "My old iPhone 13", - } + value: { "alias": "My old iPhone 13" } responses: "200": description: OK @@ -3484,209 +3452,209 @@ components: CustomersResponseList: type: object properties: - customers: + customers: type: array items: - $ref: "#/components/schemas/CustomersResponse" - CustomersResponse: + $ref: "#/components/schemas/CustomersResponse" + CustomersResponse: type: object properties: - customerProfileId: - type: integer - example: 1 - customerNumber: - type: integer - example: 1000001 - customerStatus: - type: object - properties: - customerStatusId: - type: integer - example: 1 - name: - type: string - example: Active - debtorNumber: - type: string - example: DB100001 - debtorStatus: - type: object - properties: - debtorStatusId: - type: integer - example: 1 - name: - type: string - example: Active - person: - type: object - properties: - prefix: - type: string - example: Mr - birthname: - type: string - example: John - surname: - type: string - example: Doe - suffix: - type: string - example: Jr. - dateOfBirth: - type: string - format: date - example: "2023-02-01" - emailAddress: - type: string - format: email - example: 4j2dD@example.com - addresses: - type: array - items: - type: object - properties: - addressId: - type: integer - example: 1 - addressType: - type: object - properties: - addressTypeId: - type: integer - example: 1 - name: - type: string - example: Shipping - street: - type: string - example: Appelstraat - houseNumber: - type: integer - example: 1 - houseNumberSuffix: - type: string - example: BS - postalCode: - type: string - example: 1234AB - city: - type: string - example: Den Haag - country: - type: string - example: NL - phones: - type: array - items: - type: object - properties: - phoneId: - type: integer - example: 1 - isPreferred: - type: boolean - example: true - phoneType: - type: object - properties: - phoneTypeId: - type: integer - example: 1 - name: - type: string - example: Home - number: - type: string - example: "0123456789" - countryCode: - type: string - example: "0031" - devices: - type: array - items: - type: object - properties: - deviceId: - type: string - format: uuid - example: 1 - externalDeviceId: - type: string - format: uuid - example: dee7d80e-9288-4508-a3ed-c067e619179f - alias: - type: string - example: My iPhone 13 - _links: - type: object - properties: - add_device: - type: object - properties: - href: - type: string - example: https://api.integratielaag.nl/abt/touchpoint/1.0/customers/devices - method: - type: string - example: POST - edit_device: - type: object - properties: - href: - type: string - example: https://api.integratielaag.nl/abt/touchpoint/1.0/customers/devices/1 - method: - type: string - example: PATCH - delete_device: - type: object - properties: - href: - type: string - example: https://api.integratielaag.nl/abt/touchpoint/1.0/customers/devices/1 - method: - type: string - example: DELETE - _links: - type: object - properties: - self: + customerProfileId: + type: integer + example: 1 + customerNumber: + type: integer + example: 1000001 + customerStatus: + type: object + properties: + customerStatusId: + type: integer + example: 1 + name: + type: string + example: Active + debtorNumber: + type: string + example: DB100001 + debtorStatus: + type: object + properties: + debtorStatusId: + type: integer + example: 1 + name: + type: string + example: Active + person: + type: object + properties: + prefix: + type: string + example: Mr + birthname: + type: string + example: John + surname: + type: string + example: Doe + suffix: + type: string + example: Jr. + dateOfBirth: + type: string + format: date + example: "2023-02-01" + emailAddress: + type: string + format: email + example: 4j2dD@example.com + addresses: + type: array + items: type: object properties: - href: + addressId: + type: integer + example: 1 + addressType: + type: object + properties: + addressTypeId: + type: integer + example: 1 + name: + type: string + example: Shipping + street: type: string - example: https://api.integratielaag.nl/abt/touchpoint/1.0/customers - method: + example: Appelstraat + houseNumber: + type: integer + example: 1 + houseNumberSuffix: type: string - example: GET - partial_edit: + example: BS + postalCode: + type: string + example: 1234AB + city: + type: string + example: Den Haag + country: + type: string + example: NL + phones: + type: array + items: type: object properties: - href: + phoneId: + type: integer + example: 1 + isPreferred: + type: boolean + example: true + phoneType: + type: object + properties: + phoneTypeId: + type: integer + example: 1 + name: + type: string + example: Home + number: type: string - example: https://api.integratielaag.nl/abt/touchpoint/1.0/customers - method: + example: "0123456789" + countryCode: type: string - example: PATCH - get_tokens: + example: "0031" + devices: + type: array + items: type: object properties: - href: + deviceId: type: string - example: https://api.integratielaag.nl/abt/touchpoint/1.0/customers/tokens - method: + format: uuid + example: 1 + externalDeviceId: type: string - example: GET - create_token: - type: object - properties: - href: + format: uuid + example: dee7d80e-9288-4508-a3ed-c067e619179f + alias: type: string - example: https://api.integratielaag.nl/abt/touchpoint/1.0/customers/tokens - method: - type: string - example: POST + example: My iPhone 13 + _links: + type: object + properties: + add_device: + type: object + properties: + href: + type: string + example: https://api.integratielaag.nl/abt/touchpoint/1.0/customers/devices + method: + type: string + example: POST + edit_device: + type: object + properties: + href: + type: string + example: https://api.integratielaag.nl/abt/touchpoint/1.0/customers/devices/1 + method: + type: string + example: PATCH + delete_device: + type: object + properties: + href: + type: string + example: https://api.integratielaag.nl/abt/touchpoint/1.0/customers/devices/1 + method: + type: string + example: DELETE + _links: + type: object + properties: + self: + type: object + properties: + href: + type: string + example: https://api.integratielaag.nl/abt/touchpoint/1.0/customers + method: + type: string + example: GET + partial_edit: + type: object + properties: + href: + type: string + example: https://api.integratielaag.nl/abt/touchpoint/1.0/customers + method: + type: string + example: PATCH + get_tokens: + type: object + properties: + href: + type: string + example: https://api.integratielaag.nl/abt/touchpoint/1.0/customers/tokens + method: + type: string + example: GET + create_token: + type: object + properties: + href: + type: string + example: https://api.integratielaag.nl/abt/touchpoint/1.0/customers/tokens + method: + type: string + example: POST OvPayTokensResponse: type: object required: diff --git a/src/openapi/customers/SE-notifications.yaml b/src/openapi/customers/SE-notifications.yaml index 1358b4b..72f3f4f 100644 --- a/src/openapi/customers/SE-notifications.yaml +++ b/src/openapi/customers/SE-notifications.yaml @@ -9,10 +9,10 @@ servers: tags: - name: Notification categories description: >- - Service Engine APIs for all HTM Notifications metadata. To build content only. + Service Engine APIs for all HTM Notifications metadata. To build content only. - name: Notification subscriptions description: >- - Service Engine APIs for the HTM Notification subscriptions for a user. Contains the subscriptions on category level + Service Engine APIs for the HTM Notification subscriptions for a user. Contains the subscriptions on category level - name: Notification preferences description: >- //future Service Engine APIs for the HTM Notification preferences for a category for a user. Contains the discrepancies from the default. or @@ -20,319 +20,225 @@ paths: /notificationcategories: get: tags: - - Notification categories - summary: Get notification categories and optins references for that category that a touchpoint can show. + - Notification categories + summary: Get notification categories and optins references for that category that a touchpoint can show. description: | Get notification categories that a touchpoint can show and optins ( eventTypes) and channels (eventType_channels) for the optin for that category - parameters: + parameters: - name: expand in: query - schema: - type: string - enum: [none, eventType, eventTypeChannel] - default: none + schema: + type: string + enum: [none, eventType, eventTypeChannel] + default: none responses: "200": description: OK content: application/json: examples: - getNotifactionCategories?expand=eventTypeChannel: - summary: Return all the notification categories with their nested attributes - value: - { - "notificationCategories":[ - { - "notificationCategoryId": 1, - "name": "Nieuwsbrief", - "eventTypes": [ - { - "eventTypeId": 1, - "eventOrigin": { - "eventOriginId": 6, - "name": "Maileon" - }, - "name": "HTM nieuwsbrief", - "subName": "", - "prettyName": "HTM nieuwsbrief", - "optinRequired": False, - "eventTypeChannels":[ - { - "eventTypeChannelId": "447a1116-6cd7-4645-8c3d-43237b6186cd", - "channel":{ - "channelId": 2, - "name": "email" - }, - "isDefault": True, - "isMandatory": False - } - ] - } - ] - }, - { - "notificationCategoryId": 2, - "name": "Mijn Reizen", - "eventTypes": [ - { - "eventTypeId": 2, - "eventOrigin": { - "eventOriginId": 1, - "name": "GBO" - }, - "name": "ALERTS, TRAVEL_SCHEME", - "subName": "CI", - "prettyName": "Check In", - "optinRequired": False, - "eventTypeChannels":[ - { - "eventTypeChannelId": "ccc8c025-06b5-4928-a632-23e1c55cd173", - "channel":{ - "channelId": 1, - "name": "push" - }, - "isDefault": True, - "isMandatory": False - }, - { - "eventTypeChannelId": "da2deb4c-ce77-4b5f-aecc-ddebfd14349d", - "channel":{ - "channelId": 2, - "name": "email" - }, - "isDefault": False, - "isMandatory": False - } - ] - }, - { - "eventTypeId": 3, - "eventOrigin": { - "eventOriginId": 1, - "name": "GBO" - }, - "name": "ALERTS, PAD", - "subName": null, - "prettyName": "Profielgegevens op de pas", - "optinRequired": False, - "eventTypeChannels":[ - { - "eventTypeChannelId": "8e7df8f1-7e50-482f-8301-d399e75fd432", - "channel":{ - "channelId": 1, - "name": "push" - }, - "isDefault": True, - "isMandatory": False - }, - { - "eventTypeChannelId": "72960a92-1855-469f-9cfd-5d72f57106f2", - "channel":{ - "channelId": 2, - "name": "email" - }, - "isDefault": False, - "isMandatory": False - } - ] - } - ] - }, - { - "notificationCategoryId": 3, - "name": "Mijn Passen", - "eventTypes": [ - { - "eventTypeId": 4, - "eventOrigin": { - "eventOriginId": 1, - "name": "GBO" - }, - "name": "ALERTS, CARD", - "subName": null, - "prettyName": "Mijn passen", - "optinRequired": False, - "eventTypeChannels":[ - { - "eventTypeChannelId": "be07c7bb-714b-4637-acf5-a67025ad8e60", - "channel":{ - "channelId": 1, - "name": "push" - }, - "isDefault": True, - "isMandatory": False - }, - { - "eventTypeChannelId": "0c797b5a-ed34-494b-8c64-0a832830d392", - "channel":{ - "channelId": 2, - "name": "email" - }, - "isDefault": False, - "isMandatory": False - } - ] - }, - { - "eventTypeId": 5, - "eventOrigin": { - "eventOriginId": 1, - "name": "GBO" - }, - "name": "ALERTS, PAD", - "subName": null, - "prettyName": "Profielgegevens op de pas", - "optinRequired": False, - "eventTypeChannels":[ - { - "eventTypeChannelId": "b910368f-c045-4e8e-b01d-bcbc78708bac", - "channel":{ - "channelId": 1, - "name": "push" - }, - "isDefault": True, - "isMandatory": False - }, - { - "eventTypeChannelId": "93e773da-ba3b-48da-9a0e-ee478eaa752f", - "channel":{ - "channelId": 2, - "name": "email" - }, - "isDefault": False, - "isMandatory": False - } - ] - } - ] - } - ] - } - getNotifactionCategories?expand=eventType: - summary: Return all the notification categories with nested eventTypes - value: - { - "notificationCategories":[ - { - "notificationCategoryId": 1, - "name": "Nieuwsbrief", - "eventTypes": [ - { - "eventTypeId": 1, - "eventOrigin": { - "eventOriginId": 6, - "name": "Maileon" - }, - "name": "HTM nieuwsbrief", - "subName": "", - "prettyName": "HTM nieuwsbrief", - "optinRequired": False - } - ] - }, - { - "notificationCategoryId": 2, - "name": "Mijn Reizen", - "eventTypes": [ - { - "eventTypeId": 2, - "eventOrigin": { - "eventOriginId": 1, - "name": "GBO" - }, - "name": "ALERTS, TRAVEL_SCHEME", - "subName": "CI", - "prettyName": "Check In", - "optinRequired": False - }, - { - "eventTypeId": 3, - "eventOrigin": { - "eventOriginId": 1, - "name": "GBO" - }, - "name": "ALERTS, PAD", - "subName": null, - "prettyName": "Profielgegevens op de pas", - "optinRequired": False - } - ] - }, - { - "notificationCategoryId": 3, - "name": "Mijn Passen", - "eventTypes": [ - { - "eventTypeId": 4, - "eventOrigin": { - "eventOriginId": 1, - "name": "GBO" - }, - "name": "ALERTS, CARD", - "subName": null, - "prettyName": "Mijn passen", - "optinRequired": False - }, - { - "eventTypeId": 5, - "eventOrigin": { - "eventOriginId": 1, - "name": "GBO" - }, - "name": "ALERTS, PAD", - "subName": null, - "prettyName": "Profielgegevens op de pas", - "optinRequired": False - } - ] - } - ] - } getNotifactionCategories?expand=none: - summary: Return all the notification categories + summary: Return all the notification categories without nested attributes (expand=none) value: - { - "notificationCategories":[ - { - "notificationCategoryId": 1, - "name": "Nieuwsbrief" - }, - { - "notificationCategoryId": 2, - "name": "Mijn Reizen" - }, - { - "notificationCategoryId": 3, - "name": "Mijn Passen" - } - ] - } + notificationCategories: + - notificationCategoryId: 1 + name: Mijn Reizen + - notificationCategoryId: 2 + name: Nieuwsbrief aanmelding + - notificationCategoryId: 3 + name: Mijn Passen + getNotifactionCategories?expand=eventType: + summary: Return all the notification categories with nested event types (expand=eventType) + value: + notificationCategories: + - notificationCategoryId: 1 + name: Mijn Reizen + eventTypes: + - eventTypeId: 2 + eventOrigin: + eventOriginId: 1 + name: GBO + name: ALERTS, TRAVEL_SCHEME + subName: CI + prettyName: Check In + optinRequired: false + - eventTypeId: 3 + eventOrigin: + eventOriginId: 1 + name: GBO + name: ALERTS, PAD + subName: null + prettyName: Profielgegevens op de pas + optinRequired: false + - notificationCategoryId: 2 + name: Nieuwsbrief aanmelding + eventTypes: + - eventTypeId: 1 + eventOrigin: + eventOriginId: 6 + name: Maileon + name: newsletter + subName: null + prettyName: HTM nieuwsbrief + optinRequired: false + - notificationCategoryId: 3 + name: Mijn Passen + eventTypes: + - eventTypeId: 4 + eventOrigin: + eventOriginId: 1 + name: GBO + name: ALERTS, CARD + subName: null + prettyName: Mijn passen + optinRequired: false + - eventTypeId: 5 + eventOrigin: + eventOriginId: 1 + name: GBO + name: ALERTS, PAD + subName: null + prettyName: Profielgegevens op de pas + optinRequired: false + getNotifactionCategories?expand=eventTypeChannel: + summary: Return all the notification categories with all nested attributes (expand=eventTypeChannel) + value: + notificationCategories: + - notificationCategoryId: 1 + name: Mijn Reizen + eventTypes: + - eventTypeId: 2 + eventOrigin: + eventOriginId: 1 + name: GBO + name: ALERTS, TRAVEL_SCHEME + subName: CI + prettyName: Check In + optinRequired: false + eventTypeChannels: + - eventTypeChannelId: ccc8c025-06b5-4928-a632-23e1c55cd173 + channel: + channelId: 1 + name: push + isDefault: true + isMandatory: false + - eventTypeChannelId: da2deb4c-ce77-4b5f-aecc-ddebfd14349d + channel: + channelId: 2 + name: email + isDefault: false + isMandatory: false + - eventTypeId: 3 + eventOrigin: + eventOriginId: 1 + name: GBO + name: ALERTS, PAD + subName: null + prettyName: Profielgegevens op de pas + optinRequired: false + eventTypeChannels: + - eventTypeChannelId: 8e7df8f1-7e50-482f-8301-d399e75fd432 + channel: + channelId: 1 + name: push + isDefault: true + isMandatory: false + - eventTypeChannelId: 72960a92-1855-469f-9cfd-5d72f57106f2 + channel: + channelId: 2 + name: email + isDefault: false + isMandatory: false + - notificationCategoryId: 2 + name: Nieuwsbrief aanmelding + eventTypes: + - eventTypeId: 1 + eventOrigin: + eventOriginId: 6 + name: Maileon + name: newsletter + subName: null + prettyName: HTM nieuwsbrief + optinRequired: false + eventTypeChannels: + - eventTypeChannelId: 447a1116-6cd7-4645-8c3d-43237b6186cd + channel: + channelId: 2 + name: email + isDefault: true + isMandatory: false + - notificationCategoryId: 3 + name: Mijn Passen + eventTypes: + - eventTypeId: 4 + eventOrigin: + eventOriginId: 1 + name: GBO + name: ALERTS, CARD + subName: null + prettyName: Mijn passen + optinRequired: false + eventTypeChannels: + - eventTypeChannelId: be07c7bb-714b-4637-acf5-a67025ad8e60 + channel: + channelId: 1 + name: push + isDefault: true + isMandatory: false + - eventTypeChannelId: 0c797b5a-ed34-494b-8c64-0a832830d392 + channel: + channelId: 2 + name: email + isDefault: false + isMandatory: false + - eventTypeId: 5 + eventOrigin: + eventOriginId: 1 + name: GBO + name: ALERTS, PAD + subName: null + prettyName: Profielgegevens op de pas + optinRequired: false + eventTypeChannels: + - eventTypeChannelId: b910368f-c045-4e8e-b01d-bcbc78708bac + channel: + channelId: 1 + name: push + isDefault: true + isMandatory: false + - eventTypeChannelId: 93e773da-ba3b-48da-9a0e-ee478eaa752f + channel: + channelId: 2 + name: email + isDefault: false + isMandatory: false "404": description: No notification category found content: application/json: example: - { - "type": "https://api.integratielaag.nl/abt/touchpoint/2.0/notifications", + { + "type": "https://api.integratielaag.nl/abt/touchpoint/2.0/notifications", "title": "Niet gevonden", "detail": "Notificatiecategorie niet gevonden", "instance": "555d00b5-bc3f-4591-949b-479e76d49ea7", - "errors": [ - { - "code": "404", - "detail": null, - "path": null, - "parameter": null - } - ], + "errors": + [ + { + "code": "404", + "detail": null, + "path": null, + "parameter": null, + }, + ], } /notificationsubscriptions: get: tags: - - Notification subscriptions + - Notification subscriptions summary: Get all possible notificationSubscriptions for a customer (account or private). description: | - Get all possible notificationSubscriptions for a customer (account or private), including if they have opted-in for it. + Get all possible notificationSubscriptions for a customer (account or private), including if they have opted-in for it. parameters: - name: X-HTM-JWT-AUTH-HEADER in: header @@ -358,69 +264,77 @@ paths: - name: emailAddress in: query schema: - type: integer - example: john.doe@mymailprovider.com + type: string + format: email + example: john.doe@mymailprovider.com required: false - description: The emailadress of the customer in the case of anonymous opt-ins + description: The emailadress of the customer in the case of anonymous opt-ins responses: "200": description: OK content: application/json: - examples: + examples: getNotifactionSubscriptionsAll: summary: Return all the notification subscriptions where for each category the client has actively opted in or out - All value: { - "notificationSubscriptions":[ - { - "notificationSubscriptionId": "64047471-e0c3-4abc-b4eb-83a12a6de903", - "notificationCategory": { - "notificationCategoryId": 1, - "name": "HTM nieuwbrief" + "notificationSubscriptions": + [ + { + "notificationSubscriptionId": "64047471-e0c3-4abc-b4eb-83a12a6de903", + "notificationCategory": + { + "notificationCategoryId": 1, + "name": "Mijn reizen", + }, + "isActive": true, }, - "isActive": true - }, - { - "notificationSubscriptionId": "571388cd-8903-40d5-89e6-9191cb8d656e", - "notificationCategory": { - "notificationCategoryId": 2, - "name": "Mijn reizen" + { + "notificationSubscriptionId": "571388cd-8903-40d5-89e6-9191cb8d656e", + "notificationCategory": + { + "notificationCategoryId": 2, + "name": "Nieuwsbrief aanmelding", + }, + "isActive": true, }, - "isActive": true - }, - { - "notificationSubscriptionId": "cf736ff2-2f8f-434e-a3c7-a14064b73c9b", - "notificationCategory": { - "notificationCategoryId": 3, - "name": "Mijn contracten" + { + "notificationSubscriptionId": "cf736ff2-2f8f-434e-a3c7-a14064b73c9b", + "notificationCategory": + { + "notificationCategoryId": 3, + "name": "Mijn contracten", + }, + "isActive": false, }, - "isActive": false - } - ] + ], } getNotifactionSubscriptionsSome: summary: Return all the notification subscriptions where for each category the client has actively opted in or out - Some value: { - "notificationSubscriptions": [ - { - "notificationSubscriptionId": "64047471-e0c3-4abc-b4eb-83a12a6de903", - "notificationCategory": { - "notificationCategoryId": 1, - "name": "HTM nieuwbrief" + "notificationSubscriptions": + [ + { + "notificationSubscriptionId": "64047471-e0c3-4abc-b4eb-83a12a6de903", + "notificationCategory": + { + "notificationCategoryId": 1, + "name": "Mijn reizen", + }, + "isActive": true, }, - "isActive": true - }, - { - "notificationSubscriptionId": "cf736ff2-2f8f-434e-a3c7-a14064b73c9b", - "notificationCategory": { - "notificationCategoryId": 3, - "name": "Mijn contracten" + { + "notificationSubscriptionId": "cf736ff2-2f8f-434e-a3c7-a14064b73c9b", + "notificationCategory": + { + "notificationCategoryId": 2, + "name": "Nieuwsbrief aanmelding", + }, + "isActive": false, }, - "isActive": false - } - ] + ], } "403": description: Forbidden // Als geverifieerd profiel gevonden wordt, maar niet op een geverifieerde manier benaderd wordt @@ -428,18 +342,19 @@ paths: application/json: example: { - "type": "https://api.integratielaag.nl/abt/touchpoint/2.0/notifications", + "type": "https://api.integratielaag.nl/abt/touchpoint/2.0/notifications", "title": "Verboden", "detail": "Niet toegestaan", "instance": "555d00b5-bc3f-4591-949b-479e76d49ea7", - "errors": [ - { - "code": "403", - "detail": null, - "path": null, - "parameter": null - } - ], + "errors": + [ + { + "code": "403", + "detail": null, + "path": null, + "parameter": null, + }, + ], } "404": description: No notification subscriptions found @@ -447,25 +362,26 @@ paths: application/json: example: { - "type": "https://api.integratielaag.nl/abt/touchpoint/2.0/notifications", + "type": "https://api.integratielaag.nl/abt/touchpoint/2.0/notifications", "title": "Niet gevonden", "detail": "Notificatie niet gevonden", "instance": "555d00b5-bc3f-4591-949b-479e76d49ea7", - "errors": [ - { - "code": "404", - "detail": null, - "path": null, - "parameter": null - } - ], + "errors": + [ + { + "code": "404", + "detail": null, + "path": null, + "parameter": null, + }, + ], } post: tags: - - Notification subscriptions + - Notification subscriptions summary: Create a new notificationSubscription for a customer (account or private). description: | - Create a notificationSubscriptions for a customer (account or private), including if they have opted-in for it. + Create a notificationSubscriptions for a customer (account or private), including if they have opted-in for it. parameters: - name: X-HTM-JWT-AUTH-HEADER in: header @@ -494,108 +410,68 @@ paths: schema: $ref: "#/components/schemas/unavailable" examples: - Create notificationSubscription anonymous active: + Create notificationSubscription anonymous: value: { "emailAddress": "anonymous@mymailprovider.com", - "notificationCategoryId": 1, - "isActive": True - } - Create notificationSubscription anonymous inactive: - value: - { - "emailAddress": "anonymous@mymailprovider.com", - "notificationCategoryId": 1, - "isActive": False + "notificationCategoryId": 1 } Create notificationSubscription account active: - value: - { - "notificationCategoryId": 2, - "isActive": True - } - Create notificationSubscription account inactive: - value: - { - "notificationCategoryId": 2, - "isActive": False - } + value: { "notificationCategoryId": 2 } responses: "201": description: Created content: application/json: - examples: - Create notificationSubscription anonymous active: - summary: Return the created notification for an anonymous user active + examples: + Create notificationSubscription anonymous: + summary: Return the created notification for an anonymous user inactive by default. value: { "notificationSubscriptionId": "e112f26e-37fa-4bde-8def-9977cd1d50ae", - "notificationCategory": { - "notificationCategoryId": 1, - "name": "HTM nieuwbrief" - }, - "isActive": True - } - Create notificationSubscription anonymous inactive: - summary: Return the created notification for an anonymous user inactive - value: - { - "notificationSubscriptionId": "d51081fd-c48d-4111-8de4-ac5db7d47ecb", - "notificationCategory": { - "notificationCategoryId": 1, - "name": "HTM nieuwbrief" - }, - "isActive": False - } + "notificationCategory": + { + "notificationCategoryId": 2, + "name": "Nieuwsbrief aanmelding", + }, + "isActive": False, + } Create notificationSubscription account active: summary: Return the created notification for an anonymous user active value: { "notificationSubscriptionId": "6b88eba1-af1f-42fc-82d3-d7202d5f1afe", - "notificationCategory": { - "notificationCategoryId": 2, - "name": "Mijn reizen" - }, - "isActive": True - } - Create notificationSubscription account inactive: - summary: Return the created notification for an anonymous user inactive - value: - { - "notificationSubscriptionId": "0dfc0ac9-c221-4493-8828-9dfa79ad9061", - "notificationCategory": { - "notificationCategoryId": 2, - "name": "Mijn reizen" - }, - "isActive": False + "notificationCategory": + { "notificationCategoryId": 2, "name": "Nieuwsbrief aanmelding" }, + "isActive": True, } "405": description: Method not allowed, ook als een notificatie aangemaakt wordt voor een account maar op een anonieme manier benadert wordt. - content: + content: application/json: - example: + example: { - "type": "https://api.integratielaag.nl/abt/touchpoint/2.0/notifications", + "type": "https://api.integratielaag.nl/abt/touchpoint/2.0/notifications", "title": "Methode niet toegestaan", "detail": "", "instance": "555d00b5-bc3f-4591-949b-479e76d49ea7", - "errors": [ - { - "code": "405", - "detail": null, - "path": null, - "parameter": null - } - ], + "errors": + [ + { + "code": "405", + "detail": null, + "path": null, + "parameter": null, + }, + ], } /notificationsubscriptions/{notificationSubscriptionId}: patch: - tags: + tags: - Notification subscriptions - summary: Update a notificationSubscription for a customer (account or private). + summary: Update a notificationSubscription for a customer. description: | - Update a notificationSubscription for a customer (account or private). + Update a notificationSubscription for a customer. parameters: - name: X-HTM-JWT-AUTH-HEADER in: header @@ -626,14 +502,6 @@ paths: example: e112f26e-37fa-4bde-8def-9977cd1d50ae required: true description: The id of the notificationSubscription you want to update - - name: emailAddress - in: query - schema: - type: string - format: email - example: john.doe@mymailprovider.com - required: false - description: The emailadress of the customer in the case of anonymous opt-ins requestBody: content: application/json: @@ -641,43 +509,59 @@ paths: $ref: "#/components/schemas/unavailable" examples: Update a notificationSubscription to inactive: - value: - { - "isActive": False - } + value: { "isActive": False } Update a notificationSubscription to active: - value: - { - "isActive": True - } + value: { "isActive": True } responses: "200": description: OK content: application/json: - examples: + examples: Update a notificationSubscription to inactive: summary: Return the updated inactive notification value: { "notificationSubscriptionId": "e112f26e-37fa-4bde-8def-9977cd1d50ae", - "notificationCategory": { - "notificationCategoryId": 1, - "name": "HTM nieuwbrief" - }, - "isActive": False - } + "notificationCategory": + { + "notificationCategoryId": 2, + "name": "Nieuwsbrief aanmelding", + }, + "isActive": False, + } Update a notificationSubscription to active: summary: Return the updated active notification value: { "notificationSubscriptionId": "e112f26e-37fa-4bde-8def-9977cd1d50ae", - "notificationCategory": { - "notificationCategoryId": 1, - "name": "HTM nieuwbrief" - }, - "isActive": True - } + "notificationCategory": + { + "notificationCategoryId": 2, + "name": "Nieuwsbrief aanmelding", + }, + "isActive": True, + } + "405": + description: Method not allowed,wanneer het account emailadres niet geverifieerd is. + content: + application/json: + example: + { + "type": "https://api.integratielaag.nl/abt/touchpoint/2.0/notifications", + "title": "Methode niet toegestaan", + "detail": "", + "instance": "555d00b5-bc3f-4591-949b-479e76d49ea7", + "errors": + [ + { + "code": "405", + "detail": null, + "path": null, + "parameter": null, + }, + ], + } components: schemas: unavailable: diff --git a/src/openapi/customers/notifications-crud.yaml b/src/openapi/customers/notifications-crud.yaml index ecf0d31..9e06e5e 100644 --- a/src/openapi/customers/notifications-crud.yaml +++ b/src/openapi/customers/notifications-crud.yaml @@ -76,7 +76,7 @@ paths: - notificationSubscriptionId: 39e8d8e6-5c85-49b6-ba4b-62e47fa4f7fd notificationCategory: notificationCategoryId: 2 - name: Mijn Passen + name: Nieuwsbrief aanmelding customerProfileId: 1338 subscriptionActivities: - subscriptionActivityId: 7fae0d2c-1e20-4f3e-b25d-fd8505a381c4 @@ -308,9 +308,9 @@ paths: value: notificationCategories: - notificationCategoryId: 1 - name: Nieuwsbrief - - notificationCategoryId: 2 name: Mijn Reizen + - notificationCategoryId: 2 + name: Nieuwsbrief aanmelding - notificationCategoryId: 3 name: Mijn Passen getNotifactionCategories?expand=eventType: @@ -318,17 +318,6 @@ paths: value: notificationCategories: - notificationCategoryId: 1 - name: Nieuwsbrief - eventTypes: - - eventTypeId: 1 - eventOrigin: - eventOriginId: 6 - name: Maileon - name: HTM nieuwsbrief - subName: "" - prettyName: HTM nieuwsbrief - optinRequired: false - - notificationCategoryId: 2 name: Mijn Reizen eventTypes: - eventTypeId: 2 @@ -347,6 +336,17 @@ paths: subName: null prettyName: Profielgegevens op de pas optinRequired: false + - notificationCategoryId: 2 + name: Nieuwsbrief aanmelding + eventTypes: + - eventTypeId: 1 + eventOrigin: + eventOriginId: 6 + name: Maileon + name: newsletter + subName: null + prettyName: HTM nieuwsbrief + optinRequired: false - notificationCategoryId: 3 name: Mijn Passen eventTypes: @@ -371,24 +371,6 @@ paths: value: notificationCategories: - notificationCategoryId: 1 - name: Nieuwsbrief - eventTypes: - - eventTypeId: 1 - eventOrigin: - eventOriginId: 6 - name: Maileon - name: HTM nieuwsbrief - subName: "" - prettyName: HTM nieuwsbrief - optinRequired: false - eventTypeChannels: - - eventTypeChannelId: 447a1116-6cd7-4645-8c3d-43237b6186cd - channel: - channelId: 2 - name: email - isDefault: true - isMandatory: false - - notificationCategoryId: 2 name: Mijn Reizen eventTypes: - eventTypeId: 2 @@ -433,6 +415,24 @@ paths: name: email isDefault: false isMandatory: false + - notificationCategoryId: 2 + name: Nieuwsbrief aanmelding + eventTypes: + - eventTypeId: 1 + eventOrigin: + eventOriginId: 6 + name: Maileon + name: newsletter + subName: null + prettyName: HTM nieuwsbrief + optinRequired: false + eventTypeChannels: + - eventTypeChannelId: 447a1116-6cd7-4645-8c3d-43237b6186cd + channel: + channelId: 2 + name: email + isDefault: true + isMandatory: false - notificationCategoryId: 3 name: Mijn Passen eventTypes: @@ -532,19 +532,16 @@ paths: eventOrigins: - eventOriginId: 1 name: GBO - description: Events originated at GBO - eventOriginId: 2 name: Website - description: Events originated at the website - eventOriginId: 3 name: Payt - description: Events originated at Payt - eventOriginId: 4 name: Twikey - description: Events originated at Twikey - eventOriginId: 5 name: TapConnect - description: Events originated at TapConnect + - eventOriginId: 6 + name: Maileon "400": description: Bad request content: @@ -657,11 +654,18 @@ paths: eventTypesResponse: value: eventTypes: + - eventTypeId: 1 + eventOrigin: + eventOriginId: 6 + name: Maileon + name: newsletter + subname: null + prettyName: HTM nieuwsbrief + optInRequired: false - eventTypeId: 15 eventOrigin: eventOriginId: 1 name: GBO - description: Events originated at GBO name: Travel subname: Missing CKO prettyName: Checkout gemist @@ -670,7 +674,6 @@ paths: eventOrigin: eventOriginId: 1 name: GBO - description: Events originated at GBO name: Travel subname: Missing CKI prettyName: Checkin gemist @@ -679,7 +682,6 @@ paths: eventOrigin: eventOriginId: 3 name: Payt - description: Events originated at Payt name: Payment subname: Failed Payment prettyName: Betaling mislukt @@ -741,7 +743,6 @@ paths: eventOrigin: eventOriginId: 1 name: GBO - description: Events originated at GBO name: Travel subname: Missing CKO prettyName: Checkout gemist @@ -754,7 +755,6 @@ paths: eventOrigin: eventOriginId: 1 name: GBO - description: Events originated at GBO name: Travel subname: Missing CKI prettyName: Checkin gemist @@ -820,7 +820,6 @@ paths: eventOrigin: eventOriginId: 1 name: GBO - description: Events originated at GBO name: Travel subname: Missing CKO prettyName: Checkout gemist @@ -836,7 +835,6 @@ paths: eventOrigin: eventOriginId: 1 name: GBO - description: Events originated at GBO name: Travel subname: Missing CKI prettyName: Checkin gemist @@ -1097,9 +1095,6 @@ components: name: type: string example: GBO - description: - type: string - example: Events originated at GBO required: - eventOriginId - name diff --git a/src/openapi/fiko/fiko-crud.yaml b/src/openapi/fiko/fiko-crud.yaml index 1500486..2434861 100644 --- a/src/openapi/fiko/fiko-crud.yaml +++ b/src/openapi/fiko/fiko-crud.yaml @@ -2999,8 +2999,12 @@ paths: transactionItems: - transactionItemId: d8ee7035-fa3d-400e-9ad5-4fe8c4c73eb7 status: returned to src + aggregationReference: null + accountingSystemReference: null - transactionItemId: 88910e83-4b1e-4fde-ab13-bd8bb60cbcd3 status: returned to src + aggregationReference: null + accountingSystemReference: null List of transactions items to return: summary: List of transaction items to return to transaction database description: List of transaction items to return to transaction database in bulk. @@ -3008,8 +3012,12 @@ paths: transactionItems: - transactionItemId: eacb9bdc-c6b5-4277-942b-cebb102944f5 status: returned to trx-db + aggregationReference: null + accountingSystemReference: null - transactionItemId: 2f361bfb-9df0-4e0f-af7c-7b9be3e7bc61 status: returned to trx-db + aggregationReference: null + accountingSystemReference: null responses: "202": description: Accepted @@ -3061,10 +3069,32 @@ paths: Body of a batch of transaction items that was successfully patched. A number of transaction items were patched. value: + startTime: 2025-02-14T05:32:47.067Z + status: Finished + clientTrackingId: 08584620957189579629541919368CU00 summary: created: 0 updated: 15 total: 15 + "202": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/GetResponseStatus" + examples: + Batch is still being processed: + summary: Batch is still being processed + description: | + Batch is still being processed + value: + startTime: 2025-02-14T05:32:47.067Z + status: Running + clientTrackingId: 08584620957189579629541919368CU00 + summary: + created: 0 + updated: 0 + total: 0 security: - default: [] x-auth-type: Application & Application User @@ -3089,8 +3119,10 @@ paths: processingFailures: - processingFailureId: d8ee7035-fa3d-400e-9ad5-4fe8c4c73eb7 resolved: true + change: Configuratie aangepast voor artikelnummer 1337. - processingFailureId: 88910e83-4b1e-4fde-ab13-bd8bb60cbcd3 resolved: true + change: Configuratie aangepast voor artikelnummer 1337. responses: "202": description: Accepted @@ -3142,10 +3174,32 @@ paths: Body of a batch of processing failures that was successfully patched. A number of processing failures were patched. value: + startTime: 2025-02-14T05:32:47.067Z + status: Finished + clientTrackingId: 08584620957189579629541919368CU00 summary: created: 0 updated: 15 total: 15 + "202": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/GetResponseStatus" + examples: + Batch is still being processed: + summary: Batch is still being processed + description: | + Batch is still being processed + value: + startTime: 2025-02-14T05:32:47.067Z + status: Running + clientTrackingId: 08584620957189579629541919368CU00 + summary: + created: 0 + updated: 0 + total: 0 security: - default: [] x-auth-type: Application & Application User diff --git a/src/openapi/products/purchased_products-crud.yaml b/src/openapi/products/purchased_products-crud.yaml index 991e1f7..8dafca3 100644 --- a/src/openapi/products/purchased_products-crud.yaml +++ b/src/openapi/products/purchased_products-crud.yaml @@ -243,143 +243,6 @@ paths: ], "href": null, } - post: - tags: - - Purchased Product - summary: Create a new purchased product. - description: Create a new purchased product. - requestBody: - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/unavailable" - examples: - Create Purchased GBO Product: - value: - { - "productId": 11, - "createdOn": "2024-10-04T12:34:56.000", - "lastUpdatedOn": "2024-10-04T12:34:56.000", - "purchasedProductResources": - [ - { - "resourceNameId": 1, - "resourceIdentifier": "408eefa9-b393-4bb3-8439-b2e51833abc7", - }, - { - "resourceNameId": 2, - "resourceIdentifier": "f809a6e1-1c8d-4f8e-8a6e-0d0b1e1e1e1e", - }, - ], - "purchasedGboProducts": - [ - { - "salesTimestamp": "2024-10-04T12:34:56.000", - "refundTimestamp": "2024-10-04T12:34:56.000", - "fromInclusive": "2024-10-04T12:34:56.000", - "untilInclusive": "2024-10-04T12:34:56.000", - "packageTemplateId": "30003", - "xBot": "f15efe6f-7353-4968-b134-60ba6fc2da8b", - "xTat": "42efebf7-132e-4ee0-9cbb-4037a9a54ad8", - "xSpit": "d67b2f72-918a-4e6c-957d-a39ed9c9e16b", - "customerTokenId": "b6492322-c458-4857-9ac3-a109c1887b9f", - "ovPayTokenId": 13, - "createdOn": "2024-10-04T12:34:56.000", - "lastUpdatedOn": "2024-10-04T12:34:56.000", - "createdBy": "someuser", - "lastUpdatedBy": null, - }, - ], - "purchasedTapconnectTickets": [], - "issuedVouchers": [], - } - Create Purchased TapConnet Ticket: - value: - { - "productId": 11, - "createdOn": "2024-10-04T12:34:56.000", - "lastUpdatedOn": "2024-10-04T12:34:56.000", - "purchasedProductResources": - [ - { - "resourceNameId": 1, - "resourceIdentifier": "408eefa9-b393-4bb3-8439-b2e51833abc7", - }, - { - "resourceNameId": 2, - "resourceIdentifier": "f809a6e1-1c8d-4f8e-8a6e-0d0b1e1e1e1e", - }, - ], - "purchasedGboProducts": [], - "purchasedTapconnectTickets": - [ - { - "issuedAt": "2024-10-04T12:34:56.000", - "activatedAt": "2024-10-04T12:34:56.000", - "cancelledAt": null, - "ticketReference": "KJj43nejhbTxhr897287", - "createdOn": "2024-10-04T12:34:56.000", - "lastUpdatedOn": "2024-10-04T12:34:56.000", - "createdBy": "user", - "lastUpdatedBy": "user", - }, - ], - "issuedVouchers": [], - } - Create Issued Voucher: - value: - { - "productId": 11, - "createdOn": "2024-10-04T12:34:56.000", - "lastUpdatedOn": "2024-10-04T12:34:56.000", - "purchasedProductResources": - [ - { - "resourceNameId": 1, - "resourceIdentifier": "408eefa9-b393-4bb3-8439-b2e51833abc7", - }, - { - "resourceNameId": 2, - "resourceIdentifier": "f809a6e1-1c8d-4f8e-8a6e-0d0b1e1e1e1e", - }, - ], - "purchasedGboProducts": [], - "purchasedTapconnectTickets": [], - "issuedVouchers": - [ - { - "voucherCode": "VOUCHER123", - "voucherStatusInstances": - [ - { - "voucherStatusId": 1, - "createdOn": "2024-10-04T12:34:56.000", - }, - ], - "voucherClaims": - [ - { - "mandatoryCustomerDataItemId": 8, - "value": "1999-12-31", - }, - { - "mandatoryCustomerDataItemId": 4, - "value": "vlad.harkonnen@househarkonnen.net", - }, - ], - }, - ], - } - responses: - "201": - description: Created - content: - application/json: - schema: - $ref: "#/components/schemas/unavailable" - example: - { "purchasedProductId": "a9b3dea5-fb8a-4b1e-9fe6-90cad31c0cfd" } /purchasedproducts/{purchasedProductId}: parameters: - in: path @@ -1302,6 +1165,439 @@ paths: { "voucherStatusId": 5, "name": "Expired" }, ], } + /purchasedproducts/bulk: + post: + tags: + - Bulk processing + summary: Create one or more purchased product(s) in bulk. + description: Create a new purchased product. + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/unavailable" + examples: + Create Single Purchased GBO Product: + value: + { + "purchasedProducts":[ + { + "productId": 11, + "createdOn": "2024-10-04T12:34:56.000", + "lastUpdatedOn": "2024-10-04T12:34:56.000", + "purchasedProductResources": + [ + { + "resourceNameId": 1, + "resourceIdentifier": "408eefa9-b393-4bb3-8439-b2e51833abc7", + }, + { + "resourceNameId": 2, + "resourceIdentifier": "f809a6e1-1c8d-4f8e-8a6e-0d0b1e1e1e1e", + }, + ], + "purchasedGboProducts": + [ + { + "salesTimestamp": "2024-10-04T12:34:56.000", + "refundTimestamp": "2024-10-04T12:34:56.000", + "fromInclusive": "2024-10-04T12:34:56.000", + "untilInclusive": "2024-10-04T12:34:56.000", + "packageTemplateId": "30003", + "xBot": "f15efe6f-7353-4968-b134-60ba6fc2da8b", + "xTat": "42efebf7-132e-4ee0-9cbb-4037a9a54ad8", + "xSpit": "d67b2f72-918a-4e6c-957d-a39ed9c9e16b", + "customerTokenId": "b6492322-c458-4857-9ac3-a109c1887b9f", + "ovPayTokenId": 13, + "createdOn": "2024-10-04T12:34:56.000", + "lastUpdatedOn": "2024-10-04T12:34:56.000", + "createdBy": "someuser", + "lastUpdatedBy": null, + }, + ], + "purchasedTapconnectTickets": [], + "issuedVouchers": [], + } + ] + } + Create Single Purchased TapConnet Ticket: + value: + { + "purchasedProducts":[ + { + "productId": 11, + "createdOn": "2024-10-04T12:34:56.000", + "lastUpdatedOn": "2024-10-04T12:34:56.000", + "purchasedProductResources": + [ + { + "resourceNameId": 1, + "resourceIdentifier": "408eefa9-b393-4bb3-8439-b2e51833abc7", + }, + { + "resourceNameId": 2, + "resourceIdentifier": "f809a6e1-1c8d-4f8e-8a6e-0d0b1e1e1e1e", + }, + ], + "purchasedGboProducts": [], + "purchasedTapconnectTickets": + [ + { + "issuedAt": "2024-10-04T12:34:56.000", + "activatedAt": "2024-10-04T12:34:56.000", + "cancelledAt": null, + "ticketReference": "KJj43nejhbTxhr897287", + "createdOn": "2024-10-04T12:34:56.000", + "lastUpdatedOn": "2024-10-04T12:34:56.000", + "createdBy": "user", + "lastUpdatedBy": "user", + }, + ], + "issuedVouchers": [], + } + ] + } + Create Single Issued Voucher: + value: + { + "purchasedProducts":[ + { + "productId": 11, + "createdOn": "2024-10-04T12:34:56.000", + "lastUpdatedOn": "2024-10-04T12:34:56.000", + "purchasedProductResources": + [ + { + "resourceNameId": 1, + "resourceIdentifier": "408eefa9-b393-4bb3-8439-b2e51833abc7", + }, + { + "resourceNameId": 2, + "resourceIdentifier": "f809a6e1-1c8d-4f8e-8a6e-0d0b1e1e1e1e", + }, + ], + "purchasedGboProducts": [], + "purchasedTapconnectTickets": [], + "issuedVouchers": + [ + { + "voucherCode": "VOUCHER123", + "voucherStatusInstances": + [ + { + "voucherStatusId": 1, + "createdOn": "2024-10-04T12:34:56.000", + }, + ], + "voucherClaims": + [ + { + "mandatoryCustomerDataItemId": 8, + "value": "1999-12-31", + }, + { + "mandatoryCustomerDataItemId": 4, + "value": "vlad.harkonnen@househarkonnen.net", + }, + ], + }, + ], + } + ] + } + Create Multiple Issued Vouchers: + value: + { + "purchasedProducts":[ + { + "productId": 11, + "createdOn": "2024-10-04T12:34:56.000", + "lastUpdatedOn": "2024-10-04T12:34:56.000", + "purchasedProductResources": + [ + { + "resourceNameId": 1, + "resourceIdentifier": "408eefa9-b393-4bb3-8439-b2e51833abc7", + }, + { + "resourceNameId": 2, + "resourceIdentifier": "f809a6e1-1c8d-4f8e-8a6e-0d0b1e1e1e1e", + }, + ], + "purchasedGboProducts": [], + "purchasedTapconnectTickets": [], + "issuedVouchers": + [ + { + "voucherCode": "VOUCHER123", + "voucherStatusInstances": + [ + { + "voucherStatusId": 1, + "createdOn": "2024-10-04T12:34:56.000", + }, + ], + "voucherClaims": + [ + { + "mandatoryCustomerDataItemId": 8, + "value": "1999-12-31", + }, + { + "mandatoryCustomerDataItemId": 4, + "value": "vlad.harkonnen@househarkonnen.net", + }, + ], + }, + ], + }, + { + "productId": 11, + "createdOn": "2024-10-04T12:34:56.000", + "lastUpdatedOn": "2024-10-04T12:34:56.000", + "purchasedProductResources": + [ + { + "resourceNameId": 1, + "resourceIdentifier": "7ce32f9b-52f0-4e80-a527-0c6184b57f52", + }, + { + "resourceNameId": 2, + "resourceIdentifier": "02047745-f03e-4c00-8e1b-8dc5c86a786e", + }, + ], + "purchasedGboProducts": [], + "purchasedTapconnectTickets": [], + "issuedVouchers": + [ + { + "voucherCode": "VOUCHER123", + "voucherStatusInstances": + [ + { + "voucherStatusId": 1, + "createdOn": "2024-10-04T12:34:56.000", + }, + ], + "voucherClaims": + [ + { + "mandatoryCustomerDataItemId": 8, + "value": "1940-01-18", + }, + { + "mandatoryCustomerDataItemId": 4, + "value": "valdemar.hoskanner@househarkonnen.net", + }, + ], + }, + ], + }, + { + "productId": 11, + "createdOn": "2024-10-04T12:34:56.000", + "lastUpdatedOn": "2024-10-04T12:34:56.000", + "purchasedProductResources": + [ + { + "resourceNameId": 1, + "resourceIdentifier": "7c71ec8a-3326-451f-9464-3e36d10260e3", + }, + { + "resourceNameId": 2, + "resourceIdentifier": "73c7a805-2edf-4616-a04c-267e88e0931c", + }, + ], + "purchasedGboProducts": [], + "purchasedTapconnectTickets": [], + "issuedVouchers": + [ + { + "voucherCode": "VOUCHER123", + "voucherStatusInstances": + [ + { + "voucherStatusId": 1, + "createdOn": "2024-10-04T12:34:56.000", + }, + ], + "voucherClaims": + [ + { + "mandatoryCustomerDataItemId": 8, + "value": "2016-06-08", + }, + { + "mandatoryCustomerDataItemId": 4, + "value": "alia.artreides@housearteides.net", + }, + ], + }, + ], + } + ] + } + responses: + "202": + description: Accepted + content: + application/json: + schema: + $ref: "#/components/schemas/BulkResponseBody" + examples: + Array of purchased products accepted: + summary: Array of purchased products accepted + description: | + The array of purchased products was accepted successfully. + The purchased products will be processed asynchronously. + In the response body the consumer will find information on how to retrieve the processing status. + value: + startTime: 2025-02-14T05:32:47.0672237Z + status: Running + clientTrackingId: 08584620957189579629541919368CU00 + callbackurl: https://api.integratielaag.nl/purchasedproducts/responsestatus/runtime/webhooks/workflow/scaleUnits/prod-00/workflows/6fd466916c + retryAfter: 10 + summary: null + /purchasedproducts/bulk/{clientTrackingId}: + get: + tags: + - Bulk processing + summary: Get the status of the purchased products bulk post. + description: Get the status of the asynchronous purchased products bulk post. + parameters: + - in: path + name: clientTrackingId + schema: + type: string + required: true + description: The clientTrackingId of the purchased products bulk post. + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/BulkResponseBody" + examples: + Batch successfully processed: + summary: Batch successfully processed + description: | + Body of a batch of purchased products that was successfully created. + A number of purchased products were created. + value: + startTime: "2025-02-14T05:32:47.067Z" + status: "Finished" + clientTrackingId: "08584620957189579629541919368CU00" + callbackurl: https://api.integratielaag.nl/purchasedproducts/bulk/responsestatus/webhooks/workflow/scaleUnits/prod-00/workflows/6fd466916c + retryAfter: 0 + summary: + created: 13 + updated: 0 + total: 13 + /voucherstatusinstances/bulk: + post: + summary: Post voucher status instances in bulk. + description: Post voucher status instances in bulk. + tags: + - Bulk processing + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/unavailable" + examples: + List of issued vouchers to set status to revoked: + summary: List of issued vouchers to set status to revoked + description: List of issued vouchers to set status to revoked + value: + { + "voucherStatusInstances":[ + { + "issuedVoucherId": "8a63552f-faf5-43f3-b22d-bebc976a8a5e", + "voucherStatusId": 4, + "createdOn": "2024-10-04T12:34:56.000" + }, + { + "issuedVoucherId": "a9ff40ec-2940-413a-9957-dfd471c4caf3", + "voucherStatusId": 4, + "createdOn": "2024-10-04T12:34:56.000" + }, + { + "issuedVoucherId": "9e7363e6-beaa-4c38-9ed6-c8afed459bd5", + "voucherStatusId": 4, + "createdOn": "2024-10-04T12:34:56.000" + }, + { + "issuedVoucherId": "9d7332d6-1949-4c20-aa99-d87096b035fa", + "voucherStatusId": 4, + "createdOn": "2024-10-04T12:34:56.000" + }, + { + "issuedVoucherId": "43ca757b-8370-4cb0-92b9-717948383d5e", + "voucherStatusId": 4, + "createdOn": "2024-10-04T12:34:56.000" + }, + ] + } + responses: + "202": + description: Accepted + content: + application/json: + schema: + $ref: "#/components/schemas/BulkResponseBody" + examples: + Array of issued vouchers accepted: + summary: Array of issued vouchers status instances accepted + description: | + The array of issued vouchers status instances was accepted successfully. + The issued vouchers status instances will be processed asynchronously. + In the response body the consumer will find information on how to retrieve the processing status. + value: + startTime: 2025-02-14T05:32:47.0672237Z + status: Running + clientTrackingId: 08584620957189579629541919368CU00 + callbackurl: https://api.integratielaag.nl/voucherstatusinstances/bulk/responsestatus/webhooks/workflow/scaleUnits/prod-00/workflows/6fd466916c + retryAfter: 10 + summary: null + /voucherstatusinstances/bulk/responsestatus/{clientTrackingId}: + get: + tags: + - Bulk processing + summary: Get the status of the voucher status instances bulk post. + description: Get the status of the asynchronous voucher status instances bulk post. + parameters: + - in: path + name: clientTrackingId + schema: + type: string + required: true + description: The clientTrackingId of the voucher status instances bulk post. + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/BulkResponseBody" + examples: + Batch successfully processed: + summary: Batch successfully processed + description: | + Body of a batch of voucher status instances that was successfully created. + A number of voucher status instances were created. + value: + startTime: "2025-02-14T05:32:47.067Z" + status: "Finished" + clientTrackingId: "08584620957189579629541919368CU00" + callbackurl: https://api.integratielaag.nl/voucherstatusinstances/bulk/responsestatus/webhooks/workflow/scaleUnits/prod-00/workflows/6fd466916c + retryAfter: 0 + summary: + created: 5 + updated: 0 + total: 5 components: securitySchemes: bearerToken: @@ -1311,6 +1607,56 @@ components: schemas: unavailable: type: object + BulkResponseBody: + type: object + properties: + startTime: + type: string + format: date-time + example: 2025-02-14T05:32:47.0672237Z + status: + type: string + example: Running + clientTrackingId: + type: string + example: 08584620957189579629541919368CU00 + callbackurl: + type: string + format: uri + example: https://services.api.htm.nl/purchasedproducts/responsestatus/runtime/webhooks/workflow/scaleUnits/prod-00/workflows/6fd466916c + retryAfter: + type: integer + example: 10 + summary: + $ref: "#/components/schemas/summaryBody" + required: + - startTime + - status + - clientTrackingId + - callbackurl + - retryAfter + - summary + summaryBody: + type: object + properties: + summary: + type: object + properties: + created: + type: integer + example: 15 + updated: + type: integer + example: 2 + total: + type: integer + example: 17 + required: + - created + - total + - updated + required: + - summary rfc9457: type: object properties: