Compare commits

..

3 Commits

36 changed files with 6023 additions and 7523 deletions

View File

@ -1,614 +0,0 @@
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<string, UserAuthInfo> userAuthInfoMap = new Dictionary<string, UserAuthInfo>();
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<Results<Ok<B2bAccessToken>, JsonHttpResult<ErrorResponse>>> 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<B2bAccessToken>());
} else {
return TypedResults.Json(await response.Content.ReadFromJsonAsync<ErrorResponse>(), JsonSerializerOptions.Default, "application/json", (int)response.StatusCode);
}
}
private string GetB2bAccessTokenAsString()
{
var b2bAccessToken = GetB2bAccessToken();
return ((Ok<B2bAccessToken>)b2bAccessToken.Result.Result).Value.accessToken;
}
public IResult GetImageFromBase64(string base64Image)
{
byte[] imageBytes = Convert.FromBase64String(base64Image);
return TypedResults.File(imageBytes, "image/jpeg");
}
public async Task<Results<Created, JsonHttpResult<ErrorResponse>>> 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>)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<ErrorResponse>(), JsonSerializerOptions.Default, "application/json", (int)response.StatusCode);
}
}
public async Task<Results<Created, JsonHttpResult<ErrorResponse>>> 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<ErrorResponse>(), JsonSerializerOptions.Default, "application/json", (int)response.StatusCode);
}
}
public async Task<Results<Ok<PersonalData>, JsonHttpResult<ErrorResponse>>> 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<PersonalData>());
}
else
{
return TypedResults.Json(await response.Content.ReadFromJsonAsync<ErrorResponse>(), JsonSerializerOptions.Default, "application/json", (int)response.StatusCode);
}
}
public async Task<Results<Ok<DecryptedPersonalData>, JsonHttpResult<ErrorResponse>>> GetDecryptedPersonalData(string xTat, string? userAccessToken)
{
var personalData = await GetPersonalData(xTat, userAccessToken);
if (personalData.Result.GetType() == typeof(JsonHttpResult<ErrorResponse>)) {
return (JsonHttpResult<ErrorResponse>)personalData.Result;
} else {
var decryptedPersonalData = await DecryptPersonalData(((Ok<PersonalData>)personalData.Result).Value);
return TypedResults.Ok(decryptedPersonalData);
}
}
public async Task<Results<Ok<DeletePersonalDataResponse>, JsonHttpResult<ErrorResponse>>> 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<DeletePersonalDataResponse>());
} else {
return TypedResults.Json(await response.Content.ReadFromJsonAsync<ErrorResponse>(), JsonSerializerOptions.Default, "application/json", (int)response.StatusCode);
}
}
public async Task<Results<Ok, JsonHttpResult<ErrorResponse>>> 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<ErrorResponse>(), JsonSerializerOptions.Default, "application/json", (int)response.StatusCode);
}
}
public async Task<Results<JsonHttpResult<OtpResponse>, JsonHttpResult<ErrorResponse>>> 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<OtpResponse>(), JsonSerializerOptions.Default, "application/json", (int)response.StatusCode);
} else {
return TypedResults.Json(await response.Content.ReadFromJsonAsync<ErrorResponse>(), JsonSerializerOptions.Default, "application/json", (int)response.StatusCode);
}
}
public async Task<Results<Ok<UserAccessToken>, JsonHttpResult<ErrorResponse>>> 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<UserAccessToken>();
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<ErrorResponse>(), JsonSerializerOptions.Default, "application/json", (int)response.StatusCode);
}
}
public async Task<Results<JsonHttpResult<OtpResponse>, JsonHttpResult<ErrorResponse>>> 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<OtpResponse>(), JsonSerializerOptions.Default, "application/json", (int)response.StatusCode);
} else {
return TypedResults.Json(await response.Content.ReadFromJsonAsync<ErrorResponse>(), JsonSerializerOptions.Default, "application/json", (int)response.StatusCode);
}
}
public async Task<Results<Ok<UserAccessToken>, JsonHttpResult<ErrorResponse>>> 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<UserAccessToken>();
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<ErrorResponse>(), JsonSerializerOptions.Default, "application/json", (int)response.StatusCode);
}
}
public async Task<Results<Ok<AdministrativeData>, JsonHttpResult<ErrorResponse>>> 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<AdministrativeData>());
}
else
{
return TypedResults.Json(await response.Content.ReadFromJsonAsync<ErrorResponse>(), 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<Results<Ok<EphemeralKey>, JsonHttpResult<ErrorResponse>>> 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<EphemeralKey>();
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<ErrorResponse>(), JsonSerializerOptions.Default, "application/json", (int)response.StatusCode);
}
}
private async Task<DecryptedPersonalData> 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);
}
}

View File

@ -1,124 +0,0 @@
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<string> DecryptTextAsync(byte[] decryptedEphemeralKey, string encryptedText)
{
var decryptedBytes = await DecryptContentAsync(decryptedEphemeralKey, encryptedText);
return Encoding.UTF8.GetString(decryptedBytes);
}
public async Task<string> 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<byte[]> 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;
}
}

View File

@ -1,15 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.14" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="8.0.0" />
</ItemGroup>
</Project>

View File

@ -1,250 +0,0 @@
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<SwaggerSchemaExampleFilter>();
});
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<UserProperties>();
ApimHelper apimHelper = new ApimHelper(userProperties);
group.MapGet("/idp/b2b-access-token", () =>
{
return apimHelper.GetB2bAccessToken();
})
.Produces<B2bAccessToken>(200)
.Produces<ErrorResponse>(400)
.Produces<ErrorResponse>(404)
.Produces<ErrorResponse>(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<FileContentHttpResult>(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<ErrorResponse>(400)
.Produces<ErrorResponse>(404)
.Produces<ErrorResponse>(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<ErrorResponse>(400)
.Produces<ErrorResponse>(404)
.Produces<ErrorResponse>(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<PersonalData>(200)
.Produces<ErrorResponse>(400)
.Produces<ErrorResponse>(404)
.Produces<ErrorResponse>(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<DecryptedPersonalData>(200)
.Produces<ErrorResponse>(400)
.Produces<ErrorResponse>(404)
.Produces<ErrorResponse>(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<DeletePersonalDataResponse>(200)
.Produces<ErrorResponse>(400)
.Produces<ErrorResponse>(404)
.Produces<ErrorResponse>(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<OtpResponse>(200)
.Produces<ErrorResponse>(400)
.Produces<ErrorResponse>(404)
.Produces<ErrorResponse>(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<UserAccessToken>(200)
.Produces<ErrorResponse>(400)
.Produces<ErrorResponse>(404)
.Produces<ErrorResponse>(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<OtpResponse>(200)
.Produces<ErrorResponse>(400)
.Produces<ErrorResponse>(404)
.Produces<ErrorResponse>(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<UserAccessToken>(200)
.Produces<ErrorResponse>(400)
.Produces<ErrorResponse>(404)
.Produces<ErrorResponse>(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<EphemeralKey>(200)
.Produces<ErrorResponse>(400)
.Produces<ErrorResponse>(404)
.Produces<ErrorResponse>(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<AdministrativeData>(200)
.Produces<ErrorResponse>(400)
.Produces<ErrorResponse>(404)
.Produces<ErrorResponse>(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();

View File

@ -1,38 +0,0 @@
// 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}"
}
}
}

View File

@ -1,44 +0,0 @@
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; }
}
}

View File

@ -1,33 +0,0 @@
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; }
}

View File

@ -1,29 +0,0 @@
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; }
}
}

View File

@ -1,23 +0,0 @@
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; }
}
}

View File

@ -1,12 +0,0 @@
using System.Text.Json.Serialization;
public class DeletePersonalDataResponse
{
public DeletePersonalDataResponse(string[] deletedAttributes)
{
this.deletedAttributes = deletedAttributes;
}
[JsonPropertyName("deletedAttributes")]
public string[] deletedAttributes { get; set; }
}

View File

@ -1,16 +0,0 @@
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; }
}

View File

@ -1,38 +0,0 @@
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; }
}
}

View File

@ -1,13 +0,0 @@
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; }
}

View File

@ -1,12 +0,0 @@
using System.Text.Json.Serialization;
public class OtpResponse
{
public OtpResponse(string maskedEmailAddress)
{
this.maskedEmailAddress = maskedEmailAddress;
}
[JsonPropertyName("maskedEmailAddress")]
public string maskedEmailAddress { get; set; }
}

View File

@ -1,42 +0,0 @@
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; }
}
}

View File

@ -1,42 +0,0 @@
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<SwaggerSchemaExampleAttribute>()
.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);
}
}
}

View File

@ -1,29 +0,0 @@
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; }
}
}

View File

@ -1,12 +0,0 @@
using System.Text.Json.Serialization;
public class UserAccessToken
{
public UserAccessToken(string accessToken)
{
this.accessToken = accessToken;
}
[JsonPropertyName("accessToken")]
public string accessToken { get; set; }
}

View File

@ -1,7 +0,0 @@
public class UserAuthInfo
{
public string? UserAccessToken { get; set; }
public string? ephemeralKeyAlias { get; set; }
public string? encryptedEphemeralKey { get; set; }
}

View File

@ -1,58 +0,0 @@
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; }
}
}

View File

@ -1,13 +0,0 @@
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; }
}

View File

@ -28,33 +28,10 @@ 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());
@ -71,7 +48,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 + " and alertId " + alertId + ": \n" + new JSONObject(response).toString(2));
LOGGER.info("GBO API 8851 alert details response for xBOT " + xBot + ": \n" + new JSONObject(response).toString(2));
}
}

View File

@ -16,28 +16,14 @@ 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(USER_NAME);
factory.setPassword(PASSWORD);
factory.setUsername("BEID_3_ALERTS_nZs3");
factory.setPassword("VyubhPnczKgTB2zJ");
factory.useSslProtocol("TLSv1.2");
factory.setExceptionHandler(new ForgivingExceptionHandler());
Map<String, Object> configs = factory.getClientProperties();
@ -47,7 +33,7 @@ public class RabbitConnector {
Channel channel = connection.createChannel();
DeliverCallback deliverCallback = initDeliverCallback(channel);
AMQP.Queue.DeclareOk queue = channel.queueDeclarePassive(QUEUE_NAME);
AMQP.Queue.DeclareOk queue = channel.queueDeclarePassive("BEID_3.ALERTS");
LOGGER.info(
"Declared queue: " + queue.getQueue() + ", consumer count: " + queue.getConsumerCount() + ", message count: " +
queue.getMessageCount());
@ -66,39 +52,17 @@ public class RabbitConnector {
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
LOGGER.info("Successfully acknowledged message with delivery tag: " + delivery.getEnvelope().getDeliveryTag());
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) {
LOGGER.info("Getting alert details via GBO API 8851...");
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);
}
};
}
}

View File

@ -597,13 +597,9 @@ paths:
schema:
$ref: "#/components/schemas/unavailable"
examples:
Empty list:
summary: Empty list
description: List all contract payments for a debtor with no payments.
value: { "contractPayments": [] }
Successful direct debit:
summary: Successful direct debit
description: One payment for a debtor with a successful direct debit.
List all contract payments for a single debtor:
summary: List all contract payments for a single debtor
description: List all contract payments for single debtor with debtor number 'D123456'.
value:
{
"contractPayments":
@ -611,9 +607,8 @@ paths:
{
"paymentId": "151845776",
"totalAmount": "26.62",
"paymentMethod": "Automatische incasso",
"paymentMethod": "Twikey",
"paymentDate": "2024-09-12",
"iban": "NL25INGB******1337",
"invoice":
{
"invoiceId": "147722263",
@ -630,192 +625,23 @@ paths:
},
},
},
],
}
Direct debit reversal:
summary: Direct debit reversal
description: One payment for a debtor with a reversed direct debit.
value:
{
"contractPayments":
[
{
"paymentId": "151845776",
"totalAmount": "-26.62",
"paymentMethod": "Stornering",
"paymentId": "151845851",
"totalAmount": "45.21",
"paymentMethod": "Twikey",
"paymentDate": "2024-09-12",
"iban": "NL25INGB******1337",
"invoice":
{
"invoiceId": "147722263",
"invoiceNumber": "F2024-0001",
"invoiceId": "147722266",
"invoiceNumber": "F2024-0002",
"description": "HTM Maandkorting 20%",
"publicLink": "https://factuurinzien.nl/d/b0aac3f42f325f5dd6abc172f723caab5956524d/i/ddb245d6df67999eca48c4a71b5661b93038e20a",
"publicLink": "https://factuurinzien.nl/d/ddb245d6df67999eca48c4a71b5661b93038e20a/i/dp5h1i5cuu94nopiolkdst3u17vkmzo",
},
"_links":
{
"get_contractdetails":
{
"href": "https://api.integratielaag.nl/abt/touchpoint/1.0/customers/contracts/5ca46c1a-cb9d-4c2d-960e-4471e8e28b6a",
"method": "GET",
},
},
},
],
}
iDEAL payment:
summary: iDEAL payment
description: One payment for a debtor with an iDEAL payment.
value:
{
"contractPayments":
[
{
"paymentId": "151845776",
"totalAmount": "26.62",
"paymentMethod": "iDEAL",
"paymentDate": "2024-09-12",
"iban": "NL25INGB******1337",
"invoice":
{
"invoiceId": "147722263",
"invoiceNumber": "F2024-0001",
"description": "HTM Maandkorting 20%",
"publicLink": "https://factuurinzien.nl/d/b0aac3f42f325f5dd6abc172f723caab5956524d/i/ddb245d6df67999eca48c4a71b5661b93038e20a",
},
"_links":
{
"get_contractdetails":
{
"href": "https://api.integratielaag.nl/abt/touchpoint/1.0/customers/contracts/5ca46c1a-cb9d-4c2d-960e-4471e8e28b6a",
"method": "GET",
},
},
},
],
}
Bank transfer:
summary: Bank transfer
description: One payment for a debtor with a bank transfer.
value:
{
"contractPayments":
[
{
"paymentId": "151845776",
"totalAmount": "26.62",
"paymentMethod": "Overboeking",
"paymentDate": "2024-09-12",
"iban": "NL25INGB******1337",
"invoice":
{
"invoiceId": "147722263",
"invoiceNumber": "F2024-0001",
"description": "HTM Maandkorting 20%",
"publicLink": "https://factuurinzien.nl/d/b0aac3f42f325f5dd6abc172f723caab5956524d/i/ddb245d6df67999eca48c4a71b5661b93038e20a",
},
"_links":
{
"get_contractdetails":
{
"href": "https://api.integratielaag.nl/abt/touchpoint/1.0/customers/contracts/5ca46c1a-cb9d-4c2d-960e-4471e8e28b6a",
"method": "GET",
},
},
},
],
}
List of four payments for one invoice:
summary: List of four payments for one invoice
description: Four payments for a debtor for one invoice; a direct debit, a direct debit reversal, a bank transfer and an iDEAL payment.
value:
{
"contractPayments":
[
{
"paymentId": "151845776",
"totalAmount": "26.62",
"paymentMethod": "Automatische incasso",
"paymentDate": "2024-09-12",
"iban": "NL25INGB******1337",
"invoice":
{
"invoiceId": "147722263",
"invoiceNumber": "F2024-0001",
"description": "HTM Maandkorting 20%",
"publicLink": "https://factuurinzien.nl/d/b0aac3f42f325f5dd6abc172f723caab5956524d/i/ddb245d6df67999eca48c4a71b5661b93038e20a",
},
"_links":
{
"get_contractdetails":
{
"href": "https://api.integratielaag.nl/abt/touchpoint/1.0/customers/contracts/5ca46c1a-cb9d-4c2d-960e-4471e8e28b6a",
"method": "GET",
},
},
},
{
"paymentId": "151845776",
"totalAmount": "-26.62",
"paymentMethod": "Stornering",
"paymentDate": "2024-09-12",
"iban": "NL25INGB******1337",
"invoice":
{
"invoiceId": "147722263",
"invoiceNumber": "F2024-0001",
"description": "HTM Maandkorting 20%",
"publicLink": "https://factuurinzien.nl/d/b0aac3f42f325f5dd6abc172f723caab5956524d/i/ddb245d6df67999eca48c4a71b5661b93038e20a",
},
"_links":
{
"get_contractdetails":
{
"href": "https://api.integratielaag.nl/abt/touchpoint/1.0/customers/contracts/5ca46c1a-cb9d-4c2d-960e-4471e8e28b6a",
"method": "GET",
},
},
},
{
"paymentId": "151845777",
"totalAmount": "10.00",
"paymentMethod": "Overboeking",
"paymentDate": "2024-09-13",
"iban": "NL25INGB******1337",
"invoice":
{
"invoiceId": "147722263",
"invoiceNumber": "F2024-0001",
"description": "HTM Maandkorting 20%",
"publicLink": "https://factuurinzien.nl/d/b0aac3f42f325f5dd6abc172f723caab5956524d/i/ddb245d6df67999eca48c4a71b5661b93038e20a",
},
"_links":
{
"get_contractdetails":
{
"href": "https://api.integratielaag.nl/abt/touchpoint/1.0/customers/contracts/5ca46c1a-cb9d-4c2d-960e-4471e8e28b6a",
"method": "GET",
},
},
},
{
"paymentId": "151845778",
"totalAmount": "16.62",
"paymentMethod": "iDEAL",
"paymentDate": "2024-09-14",
"iban": "NL25INGB******1337",
"invoice":
{
"invoiceId": "147722263",
"invoiceNumber": "F2024-0001",
"description": "HTM Maandkorting 20%",
"publicLink": "https://factuurinzien.nl/d/b0aac3f42f325f5dd6abc172f723caab5956524d/i/ddb245d6df67999eca48c4a71b5661b93038e20a",
},
"_links":
{
"get_contractdetails":
{
"href": "https://api.integratielaag.nl/abt/touchpoint/1.0/customers/contracts/5ca46c1a-cb9d-4c2d-960e-4471e8e28b6a",
"href": "https://api.integratielaag.nl/abt/touchpoint/1.0/customers/contracts/7b2f8c1a-3d9d-4c2d-960e-4471e8e28b6a",
"method": "GET",
},
},

View File

@ -187,21 +187,25 @@ paths:
examples:
minimalCustomerProfile:
value:
{ "person": { "emailAddress": "j.jansen@hatseflats.nl" } }
{
"person": {
"emailAddress": "j.jansen@hatseflats.nl"
}
}
fullCustomerProfile:
value:
{
"customerPreference": { "languageId": 1 },
"person":
{
"customerPreference": {
"languageId": 1
},
"person": {
"birthname": "Jan",
"surname": "Jansen",
"prefix": "dhr",
"suffix": "jr",
"dateOfBirth": "1970-01-01",
"emailAddress": "j.jansen@hatseflats.nl",
"addresses":
[
"addresses": [
{
"street": "Laan van Meerdervoort",
"houseNumber": 5,
@ -210,7 +214,7 @@ paths:
"city": "Den Haag",
"country": "NL",
"isPreferred": true,
"addressTypeId": 1,
"addressTypeId": 1
},
{
"street": "Beeklaan",
@ -220,26 +224,24 @@ paths:
"city": "Den Haag",
"country": "NL",
"isPreferred": false,
"addressTypeId": 2,
},
"addressTypeId": 2
}
],
"phones":
[
"phones": [
{
"number": "6123456789",
"countryCode": "0031",
"phoneTypeId": 1,
"isPreferred": true,
"isPreferred": true
},
{
"number": "7012345678",
"countryCode": "0031",
"phoneTypeId": 2,
"isPreferred": false,
},
"isPreferred": false
}
],
"devices":
[
"devices": [
{
"externalDeviceId": "123e4567-e89b-12d3-a456-426614174000",
"alias": "My iPhone",
@ -247,9 +249,9 @@ paths:
{
"externalDeviceId": "987e6543-e21b-12d3-a456-426614174999",
"alias": "My iPad",
},
],
},
}
]
}
}
responses:
"201":
@ -294,22 +296,20 @@ paths:
patchCustomer:
value:
{
"person":
{
"person": {
"birthname": "Jan",
"surname": "Jansen",
"prefix": "dhr",
"suffix": "jr",
"dateOfBirth": "1970-01-01",
"addresses":
[
"addresses": [
{
"addressId": 2,
"street": "Laan van Meerdervoort",
"houseNumber": 5,
"postalCode": "2500AA",
"city": "Den Haag",
"country": "NL",
"country": "NL"
},
{
"addressId": 1,
@ -319,28 +319,26 @@ paths:
"postalCode": "2500AA",
"city": "Den Haag",
"country": "NL",
"addressTypeId": 2,
},
"addressTypeId": 2
}
],
"phones":
[
"phones": [
{
"phoneId": 1,
"number": "6123456789",
"countryCode": "0031",
"phoneTypeId": 1,
"isPreferred": true,
"isPreferred": true
},
{
"phoneId": 2,
"number": "7012345678",
"countryCode": "0031",
"phoneTypeId": 2,
"isPreferred": false,
},
"isPreferred": false
}
],
"devices":
[
"devices": [
{
"deviceId": "813afdd8-bf8c-4e26-bfda-4da79552bd38",
"externalDeviceId": "123e4567-e89b-12d3-a456-426614174000",
@ -350,9 +348,9 @@ paths:
"deviceId": "4f4249a2-ac6c-44f9-b740-66e66b6f3c28",
"externalDeviceId": "987e6543-e21b-12d3-a456-426614174999",
"alias": "My iPad",
},
],
},
}
]
}
}
responses:
"200":
@ -361,7 +359,6 @@ paths:
application/json:
schema:
$ref: "#/components/schemas/CustomersResponse"
/customers/tokens:
get:
tags:
@ -492,9 +489,8 @@ paths:
"customerProfileId": 1,
"ovPayTokenId": 1,
"xTat": "32089cc8-d187-47ff-a3a9-5c2558def811",
"ovpasNumber": "",
"alias": "Mijn EMV pas",
"tokenType": { "tokenTypeId": 1, "name": "EMV" },
"alias": "MyToken",
"tokenStatus":
{ "tokenStatusId": 2, "name": "Active" },
"expirationDate": "2028-02-01",
@ -570,7 +566,6 @@ paths:
"xTat": "32089cc8-d187-47ff-a3a9-5c2558def811",
"tokenType":
{ "tokenTypeId": 2, "name": "OV-pas physical" },
"ovpasNumber": "OV34567",
"alias": "MyToken",
"tokenStatus":
{ "tokenStatusId": 2, "name": "Active" },
@ -653,7 +648,6 @@ paths:
"xTat": "32089cc8-d187-47ff-a3a9-5c2558def811",
"tokenType":
{ "tokenTypeId": 2, "name": "OV-pas physical" },
"ovpasNumber": "OV34567",
"alias": "MyToken",
"tokenStatus":
{ "tokenStatusId": 2, "name": "Active" },
@ -712,7 +706,7 @@ paths:
"gboAgeProfileId": 1,
"name": "Kind (4 t/m 11 jaar)",
"ageFromInclusive": 4,
"ageToInclusive": 11,
"ageToInclusive": 11
},
"_links":
{
@ -776,7 +770,6 @@ paths:
"xTat": "32089cc8-d187-47ff-a3a9-5c2558def811",
"tokenType":
{ "tokenTypeId": 2, "name": "OV-pas physical" },
"ovpasNumber": "OV34567",
"alias": "MyToken",
"tokenStatus":
{ "tokenStatusId": 2, "name": "Active" },
@ -810,7 +803,7 @@ paths:
"gboAgeProfileId": 1,
"name": "Kind (4 t/m 11 jaar)",
"ageFromInclusive": 4,
"ageToInclusive": 11,
"ageToInclusive": 11
},
"_links":
{
@ -874,7 +867,6 @@ paths:
"xTat": "32089cc8-d187-47ff-a3a9-5c2558def811",
"tokenType":
{ "tokenTypeId": 2, "name": "OV-pas physical" },
"ovpasNumber": "OV34567",
"alias": "MyToken",
"tokenStatus":
{ "tokenStatusId": 2, "name": "Active" },
@ -926,7 +918,7 @@ paths:
"gboAgeProfileId": 1,
"name": "Kind (4 t/m 11 jaar)",
"ageFromInclusive": 4,
"ageToInclusive": 11,
"ageToInclusive": 11
},
"_links":
{
@ -990,7 +982,6 @@ paths:
"xTat": "e7fa3392-646b-40e2-95a6-c417dc0b0969",
"tokenType":
{ "tokenTypeId": 2, "name": "OV-pas physical" },
"ovpasNumber": "OV34567",
"alias": "MyToken",
"tokenStatus":
{ "tokenStatusId": 2, "name": "Active" },
@ -1011,7 +1002,6 @@ paths:
"xTat": "e7fa3392-646b-40e2-95a6-c417dc0b0969",
"tokenType":
{ "tokenTypeId": 2, "name": "OV-pas physical" },
"ovpasNumber": "OV54567",
"alias": "MyToken",
"tokenStatus":
{ "tokenStatusId": 3, "name": "Replaced (*)" },
@ -1033,7 +1023,6 @@ paths:
"xTat": "e7fa3392-646b-40e2-95a6-c417dc0b0969",
"tokenType":
{ "tokenTypeId": 2, "name": "OV-pas physical" },
"ovpasNumber": "OV34547",
"alias": "MyToken",
"tokenStatus":
{ "tokenStatusId": 4, "name": "On stock" },
@ -1047,7 +1036,7 @@ paths:
"birthdate": null,
"photo": null,
},
"gboAgeProfile": null,
"gboAgeProfile": null
},
{
"customerProfileId": 132,
@ -1055,7 +1044,6 @@ paths:
"xTat": "e7fa3392-646b-40e2-95a6-c417dc0b0969",
"tokenType":
{ "tokenTypeId": 2, "name": "OV-pas physical" },
"ovpasNumber": "OV34831",
"alias": "Mijn OV Pas",
"tokenStatus":
{ "tokenStatusId": 5, "name": "Suspended" },
@ -1069,7 +1057,7 @@ paths:
"birthdate": null,
"photo": null,
},
"gboAgeProfile": null,
"gboAgeProfile": null
},
{
"customerProfileId": 166,
@ -1077,7 +1065,6 @@ paths:
"xTat": "e7fa3392-646b-40e2-95a6-c417dc0b0969",
"tokenType":
{ "tokenTypeId": 2, "name": "OV-pas physical" },
"ovpasNumber": "OV34984",
"alias": "Mijn OV Pas",
"tokenStatus":
{
@ -1094,7 +1081,7 @@ paths:
"birthdate": null,
"photo": null,
},
"gboAgeProfile": null,
"gboAgeProfile": null
},
{
"customerProfileId": 166,
@ -1102,7 +1089,6 @@ paths:
"xTat": "e7fa3392-646b-40e2-95a6-c417dc0b0969",
"tokenType":
{ "tokenTypeId": 2, "name": "OV-pas physical" },
"ovpasNumber": "OV54368",
"alias": "My retired token",
"tokenStatus":
{ "tokenStatusId": 1, "name": "Retired" },
@ -1116,7 +1102,7 @@ paths:
"birthdate": null,
"photo": null,
},
"gboAgeProfile": null,
"gboAgeProfile": null
},
{
"customerProfileId": 1,
@ -1124,7 +1110,6 @@ paths:
"xTat": "e7fa3392-646b-40e2-95a6-c417dc0b0969",
"tokenType":
{ "tokenTypeId": 2, "name": "OV-pas physical" },
"ovpasNumber": "OV98263",
"alias": "My found token",
"tokenStatus":
{ "tokenStatusId": 7, "name": "Renewed Active" },
@ -1138,7 +1123,7 @@ paths:
"birthdate": null,
"photo": null,
},
"gboAgeProfile": null,
"gboAgeProfile": null
},
],
_links:
@ -1228,7 +1213,7 @@ paths:
"xTat": "32089cc8-d187-47ff-a3a9-5c2558def811",
"tokenType": { "tokenTypeId": 1, "name": "EMV" },
"lastDigits": null,
"ovpasNumber": null,
"ovPasNumber": null,
"alias": "Mijn token",
"tokenStatus":
{ "tokenStatusId": 2, "name": "Active" },
@ -1964,12 +1949,11 @@ paths:
type: integer
example: 1
required: true
summary: |-
NOT TO BE CALLED BY TOUCHPOINTS - Replace an OVpay token with another (+ transfer products) - V2
description: |-
**NOTE: This endpoint is for usage by integratielaag only. Touchpoints should use
`/customers/tokens/{ovpayTokenId}/transfer` instead.**\
summary: Replace an OVpay token with another (+ transfer products) - V2 (for Integratielaag)
description: |
Transfer products from one OVpay token to another, and replace the tokens in the database.
This endpoint is for usage by integratielaag only. Touch points should use
`/customers/tokens/{ovpayTokenId}/transfer` instead.
requestBody:
content:
application/json:
@ -2225,7 +2209,7 @@ paths:
content:
application/json:
schema:
$ref: "#/components/schemas/OvPayTokenProductInstancesResponse"
$ref: "#/components/schemas/ProductInstancesResponse"
examples:
getEmptyProductInstances:
summary: No product-instances found on token
@ -2238,8 +2222,10 @@ paths:
"productInstances":
[
{
"productInstanceId": "26d41861-f77e-4666-9cde-2c5c66ace0a2",
"productId": 1,
"name": "HTM 90% Korting",
"purchasedProductType": "GBO",
"status": "Active",
"isRenewable": true,
"productCategory":
@ -2252,13 +2238,9 @@ paths:
"orderId": "501B17EF-36C4-4039-B92C-6517969B464E",
"orderLineId": "38B17EF-36C4-4039-B92C-4817969B464E",
"contractId": "56B17EF-C436-9043-B76C-481797WEB464F",
"content": null,
"_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",
@ -2384,7 +2366,7 @@ paths:
"ePurse": null,
"personalAccountData":
{ "name": null, "birthdate": null, "photo": null },
"gboAgeProfile": null,
"gboAgeProfile": null
},
"newOvPayToken":
{
@ -2401,7 +2383,7 @@ paths:
"ePurse": null,
"personalAccountData":
{ "name": null, "birthdate": null, "photo": null },
"gboAgeProfile": null,
"gboAgeProfile": null
},
"isTransferable": true,
"transferableObjects":
@ -2414,11 +2396,11 @@ paths:
"ePurse": true,
"personalAccountData":
{ "name": true, "birthdate": true, "photo": true },
"gboAgeProfile": true,
"gboAgeProfile": true
},
"_links":
{
"transfer_token":
"transfer":
{
"href": "https://services.dev.api.htm.nl/abt/touchpoint/1.0/customers/tokens/1/transfer",
"method": "POST",
@ -2438,57 +2420,50 @@ paths:
"tokenStatus":
{ "tokenStatusId": 2, "name": "Active" },
"expirationDate": "2028-02-01",
"productInstances":
[
"productInstances": [
{
"productId": 1,
"name": "HTM 90% Korting EMV",
"status": "Active",
"isRenewable": true,
"productCategory":
{
"productCategory": {
"productCategoryId": 1,
"name": "Kortingsabonnement",
"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":
{
"_links": {
"self": {
"href": "https://api.integratielaag.nl/abt/touchpoint/1.0/customers/tokens/1/productinstances/1",
"method": "GET",
"method": "GET"
},
"get_order":
{
"get_order": {
"href": "https://api.integratielaag.nl/abt/touchpoint/1.0/orders/501B17EF-36C4-4039-B92C-6517969B464E",
"method": "GET",
"method": "GET"
},
"get_contract":
{
"get_contract": {
"href": "https://api.integratielaag.nl/abt/touchpoint/1.0/customers/contracts/56B17EF-C436-9043-B76C-481797WEB464F",
"method": "GET",
},
},
},
"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",
@ -2498,7 +2473,7 @@ paths:
"ePurse": null,
"personalAccountData":
{ "name": null, "birthdate": null, "photo": null },
"gboAgeProfile": null,
"gboAgeProfile": null
},
"isTransferable": false,
"transferableObjects":
@ -2535,7 +2510,7 @@ paths:
"ePurse": null,
"personalAccountData":
{ "name": null, "birthdate": null, "photo": null },
"gboAgeProfile": null,
"gboAgeProfile": null
},
"newOvPayToken":
{
@ -2552,7 +2527,7 @@ paths:
"ePurse": null,
"personalAccountData":
{ "name": null, "birthdate": null, "photo": null },
"gboAgeProfile": null,
"gboAgeProfile": null
},
"isTransferable": false,
"transferableObjects":
@ -2624,8 +2599,8 @@ paths:
"gboAgeProfileId": 1,
"name": "Kind (4 t/m 11 jaar)",
"ageFromInclusive": 4,
"ageToInclusive": 11,
},
"ageToInclusive": 11
}
},
"newOvPayToken":
{
@ -2642,7 +2617,7 @@ paths:
"ePurse": null,
"personalAccountData":
{ "name": null, "birthdate": null, "photo": null },
"gboAgeProfile": null,
"gboAgeProfile": null
},
"isTransferable": false,
"transferableObjects":
@ -2700,8 +2675,8 @@ paths:
"gboAgeProfileId": 1,
"name": "Kind (4 t/m 11 jaar)",
"ageFromInclusive": 4,
"ageToInclusive": 11,
},
"ageToInclusive": 11
}
},
"newOvPayToken":
{
@ -2736,8 +2711,8 @@ paths:
"gboAgeProfileId": 4,
"name": "Kind (19 t/m 65 jaar)",
"ageFromInclusive": 19,
"ageToInclusive": 65,
},
"ageToInclusive": 65
}
},
"isTransferable": false,
"transferableObjects":
@ -2879,9 +2854,9 @@ paths:
type: integer
example: 1
post:
summary: "Transfer old OVpay token to new OVpay token."
summary: "**INTEGRATIELAAG** Transfer old OVpay token to new OVpay token."
description: |-
**Note that this directly calls the integratielaag and not the Service Engine!**
**Note that this is an integratielaag endpoint, not a Service Engine endpoint!**
First transfers all personal account data (if present), then all products, of an existing OVpay token
to a new OVpay token. The new token will also be persisted in the profile as a replacement
of the old token. This call is asynchronous, and progress can be monitored using the
@ -3020,9 +2995,9 @@ paths:
get:
tags:
- Token Replace v2
summary: "Get the status of the initiated token transfer process (POST)."
summary: "**INTEGRATIELAAG** Get the status of the initiated token transfer process (POST)."
description: |
**Note that this directly calls the integratielaag and not the Service Engine!** Get the status of the
**Note that this is an integratielaag endpoint, not a Service Engine endpoint!** Get the status of the
asynchronous token transfer processing.
responses:
"200":
@ -3100,7 +3075,7 @@ paths:
"gboAgeProfileId": 1,
"name": "Kind (4 t/m 11 jaar)",
"ageFromInclusive": 4,
"ageToInclusive": 11,
"ageToInclusive": 11
},
"_links":
{
@ -3141,22 +3116,60 @@ 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:
{
"startTime": "2025-02-14T05:32:47.067Z",
"properties":
{
"waitEndTime": "2025-06-04T09:29:21.9641991Z",
"startTime": "2025-06-04T09:29:21.9641991Z",
"status": "Running",
"clientTrackingId": "08584620957189579629541919368CU00",
"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",
}
"404":
description: Not found
@ -3355,7 +3368,10 @@ 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
@ -3379,6 +3395,209 @@ paths:
responses:
"200":
description: OK
/customers/devices/{deviceId}/productinstances:
parameters:
- name: X-HTM-JWT-AUTH-HEADER
in: header
schema:
type: string
example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
required: false
description: The JWT of a customer in case of touchpoint were customer logs in themselves
- name: X-HTM-CUSTOMER-PROFILE-ID-HEADER
in: header
schema:
type: string
example: feaaef83-a551-4283-8419-340b1ada3b55
required: false
description: The customerProfileId of a customer in the case of the SMP
- name: X-HTM-ROLE-HEADER
in: header
schema:
type: string
example: Customer
required: false
- name: deviceId
in: path
required: true
style: simple
description: Id of the device you want to get the instantiated HTM products for.
schema:
type: string
format: uuid
example: 0f0981bf-6d60-4b06-bc55-de1ba325f366
get:
summary: Get a list of all HTM products instantiated on the given device
description: |-
Get a list of all HTM products instantiated on the given device.
Only HTM products are returned; GBO does not allow HTM to get information on non-HTM product-instances.
Where relevant, operations to be performed are returned as HATEOAS links per product-instance.
tags:
- Devices
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/ProductInstancesResponse"
examples:
getEmptyProductInstances:
summary: No product-instances found for devi
value:
productInstances: []
getSingleProductInstance:
summary: One TapConnect product-instance
value:
{
"productInstances":
[
{
"productInstanceId": "0f0981bf-6d60-4b06-bc55-de1ba325f366",
"productId": 13,
"name": "HTM dagkaart",
"purchasedProductType": "TapConnect",
"status": "Created",
"isRenewable": false,
"productCategory":
{
"productCategoryId": 2,
"name": "Afgekocht reisrecht",
},
"fromInclusive": "2024-11-25T13:25:00+01:00",
"untilInclusive": null,
"orderId": "501B17EF-36C4-4039-B92C-6517969B464E",
"orderLineId": "38B17EF-36C4-4039-B92C-4817969B464E",
"contractId": null,
"content": {
"ticketReference": "KJj43nejhbTxhr897287",
"issuedAt": "2020-03-21T00:00:00",
"activatedAt": null,
"blocked": false,
"cancelledAt": null,
"fraudDetected": false,
"barcode": "barcodeBytes"
},
"_links":
{
"get_order":
{
"href": "https://api.integratielaag.nl/abt/touchpoint/1.0/orders/501B17EF-36C4-4039-B92C-6517969B464E",
"method": "GET",
},
"patch_productinstance":
{
"href": "https://api.integratielaag.nl/abt/touchpoint/1.0/customers/productinstances/0f0981bf-6d60-4b06-bc55-de1ba325f366",
"method": "PATCH",
},
},
},
],
}
/customers/productinstances/{productInstanceId}:
parameters:
- name: X-HTM-JWT-AUTH-HEADER
in: header
schema:
type: string
example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
required: false
description: The JWT of a customer in case of touchpoint were customer logs in themselves
- name: X-HTM-CUSTOMER-PROFILE-ID-HEADER
in: header
schema:
type: string
example: feaaef83-a551-4283-8419-340b1ada3b55
required: false
description: The customerProfileId of a customer in the case of the SMP
- name: X-HTM-ROLE-HEADER
in: header
schema:
type: string
example: Customer
required: false
- name: productInstanceId
in: path
required: true
style: simple
description: Id of the product instance you want to change
schema:
type: string
format: uuid
example: 0f0981bf-6d60-4b06-bc55-de1ba325f366
patch:
summary: Update a productInstance
description: |-
Update the status of the give productInstance.
tags:
- Product instances
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/unavailable"
examples:
Update a productInstance status to active:
value:
{
"status": "Active"
}
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/unavailable"
examples:
Update a productInstance status to active:
summary: Update a productInstance status to active
value:
{
"productInstances":
[
{
"productInstanceId": "0f0981bf-6d60-4b06-bc55-de1ba325f366",
"productId": 13,
"name": "HTM dagkaart",
"purchasedProductType": "TapConnect",
"status": "Active",
"isRenewable": false,
"productCategory":
{
"productCategoryId": 2,
"name": "Afgekocht reisrecht",
},
"fromInclusive": "2024-11-25T13:25:00+01:00",
"untilInclusive": null,
"orderId": "501B17EF-36C4-4039-B92C-6517969B464E",
"orderLineId": "38B17EF-36C4-4039-B92C-4817969B464E",
"contractId": null,
"content": {
"ticketReference": "KJj43nejhbTxhr897287",
"issuedAt": "2020-03-21T00:00:00",
"activatedAt": "2020-03-21T00:00:00",
"blocked": false,
"cancelledAt": null,
"fraudDetected": false,
"barcode": "barcodeBytes"
},
"_links":
{
"get_order":
{
"href": "https://api.integratielaag.nl/abt/touchpoint/1.0/orders/501B17EF-36C4-4039-B92C-6517969B464E",
"method": "GET",
},
"patch_productinstance":
{
"href": "https://api.integratielaag.nl/abt/touchpoint/1.0/customers/productinstances/0f0981bf-6d60-4b06-bc55-de1ba325f366",
"method": "PATCH",
},
},
},
],
}
components:
schemas:
unavailable:
@ -3767,20 +3986,39 @@ components:
method:
type: string
example: GET
OvPayTokenProductInstancesResponse:
ProductInstancesResponse:
type: object
required:
- productInstances
properties:
productInstances:
type: array
items:
type: object
required:
- productInstanceId
- productId
- name
- status
- purchasedProductType
- isRenewable
- productCategory
- _links
properties:
productInstanceId:
type: string
format: uuid
example: 0f0981bf-6d60-4b06-bc55-de1ba325f366
productId:
type: integer
example: 1
name:
type: string
example: HTM 90% Korting
purchasedProductType:
type: string
description: The type of product instance (e.g. GBO, TapConnect, physical, etc.)
example: GBO
status:
type: string
enum: ["Active", "Ended", "Refunded"]
@ -3821,6 +4059,10 @@ components:
format: uuid
example: 56B17EF-C436-9043-B76C-481797WEB464F
description: Only present for subscriptions/contracts
content:
type: object
description: Custom data for the product-instance, depending on the purchasedProductType
example: null
_links:
type: object
properties:
@ -3835,21 +4077,31 @@ components:
example: GET
get_order:
type: object
description: Always present for any HTM product-instance
properties:
href:
type: string
description: Always present for any HTM product-instance
example: https://api.integratielaag.nl/abt/touchpoint/1.0/orders/501B17EF-36C4-4039-B92C-6517969B464E
method:
type: string
example: GET
get_contract:
type: object
description: Only present for subscriptions/contracts
properties:
href:
type: string
description: Only present for subscriptions/contracts
example: https://api.integratielaag.nl/abt/touchpoint/1.0/customers/contracts/56B17EF-C436-9043-B76C-481797WEB464F
method:
type: string
example: GET
patch_productinstance:
type: object
description: Only present for TapConnect product-instances that need to be activated
properties:
href:
type: string
example: https://api.integratielaag.nl/abt/touchpoint/1.0/customers/productinstances/0f0981bf-6d60-4b06-bc55-de1ba325f366
method:
type: string
example: PATCH

View File

@ -37,181 +37,276 @@ paths:
content:
application/json:
examples:
getNotifactionCategories?expand=none:
summary: Return all the notification categories without nested attributes (expand=none)
value:
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)
summary: Return all the notification categories with their nested attributes
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
{
"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
value:
{
"notificationCategories":[
{
"notificationCategoryId": 1,
"name": "Nieuwsbrief"
},
{
"notificationCategoryId": 2,
"name": "Mijn Reizen"
},
{
"notificationCategoryId": 3,
"name": "Mijn Passen"
}
]
}
"404":
description: No notification category found
content:
@ -222,14 +317,13 @@ paths:
"title": "Niet gevonden",
"detail": "Notificatiecategorie niet gevonden",
"instance": "555d00b5-bc3f-4591-949b-479e76d49ea7",
"errors":
[
"errors": [
{
"code": "404",
"detail": null,
"path": null,
"parameter": null,
},
"parameter": null
}
],
}
/notificationsubscriptions:
@ -264,8 +358,7 @@ paths:
- name: emailAddress
in: query
schema:
type: string
format: email
type: integer
example: john.doe@mymailprovider.com
required: false
description: The emailadress of the customer in the case of anonymous opt-ins
@ -279,62 +372,55 @@ paths:
summary: Return all the notification subscriptions where for each category the client has actively opted in or out - All
value:
{
"notificationSubscriptions":
[
"notificationSubscriptions":[
{
"notificationSubscriptionId": "64047471-e0c3-4abc-b4eb-83a12a6de903",
"notificationCategory":
{
"notificationCategory": {
"notificationCategoryId": 1,
"name": "Mijn reizen",
"name": "HTM nieuwbrief"
},
"isActive": true,
"isActive": true
},
{
"notificationSubscriptionId": "571388cd-8903-40d5-89e6-9191cb8d656e",
"notificationCategory":
{
"notificationCategory": {
"notificationCategoryId": 2,
"name": "Nieuwsbrief aanmelding",
"name": "Mijn reizen"
},
"isActive": true,
"isActive": true
},
{
"notificationSubscriptionId": "cf736ff2-2f8f-434e-a3c7-a14064b73c9b",
"notificationCategory":
{
"notificationCategory": {
"notificationCategoryId": 3,
"name": "Mijn contracten",
"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":
[
"notificationSubscriptions": [
{
"notificationSubscriptionId": "64047471-e0c3-4abc-b4eb-83a12a6de903",
"notificationCategory":
{
"notificationCategory": {
"notificationCategoryId": 1,
"name": "Mijn reizen",
"name": "HTM nieuwbrief"
},
"isActive": true,
"isActive": true
},
{
"notificationSubscriptionId": "cf736ff2-2f8f-434e-a3c7-a14064b73c9b",
"notificationCategory":
{
"notificationCategoryId": 2,
"name": "Nieuwsbrief aanmelding",
"notificationCategory": {
"notificationCategoryId": 3,
"name": "Mijn contracten"
},
"isActive": false,
},
],
"isActive": false
}
]
}
"403":
description: Forbidden // Als geverifieerd profiel gevonden wordt, maar niet op een geverifieerde manier benaderd wordt
@ -346,14 +432,13 @@ paths:
"title": "Verboden",
"detail": "Niet toegestaan",
"instance": "555d00b5-bc3f-4591-949b-479e76d49ea7",
"errors":
[
"errors": [
{
"code": "403",
"detail": null,
"path": null,
"parameter": null,
},
"parameter": null
}
],
}
"404":
@ -366,14 +451,13 @@ paths:
"title": "Niet gevonden",
"detail": "Notificatie niet gevonden",
"instance": "555d00b5-bc3f-4591-949b-479e76d49ea7",
"errors":
[
"errors": [
{
"code": "404",
"detail": null,
"path": null,
"parameter": null,
},
"parameter": null
}
],
}
post:
@ -410,40 +494,81 @@ paths:
schema:
$ref: "#/components/schemas/unavailable"
examples:
Create notificationSubscription anonymous:
Create notificationSubscription anonymous active:
value:
{
"emailAddress": "anonymous@mymailprovider.com",
"notificationCategoryId": 1
"notificationCategoryId": 1,
"isActive": True
}
Create notificationSubscription anonymous inactive:
value:
{
"emailAddress": "anonymous@mymailprovider.com",
"notificationCategoryId": 1,
"isActive": False
}
Create notificationSubscription account active:
value: { "notificationCategoryId": 2 }
value:
{
"notificationCategoryId": 2,
"isActive": True
}
Create notificationSubscription account inactive:
value:
{
"notificationCategoryId": 2,
"isActive": False
}
responses:
"201":
description: Created
content:
application/json:
examples:
Create notificationSubscription anonymous:
summary: Return the created notification for an anonymous user inactive by default.
Create notificationSubscription anonymous active:
summary: Return the created notification for an anonymous user active
value:
{
"notificationSubscriptionId": "e112f26e-37fa-4bde-8def-9977cd1d50ae",
"notificationCategory":
{
"notificationCategoryId": 2,
"name": "Nieuwsbrief aanmelding",
"notificationCategory": {
"notificationCategoryId": 1,
"name": "HTM nieuwbrief"
},
"isActive": False,
"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
}
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": "Nieuwsbrief aanmelding" },
"isActive": True,
"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
}
"405":
description: Method not allowed, ook als een notificatie aangemaakt wordt voor een account maar op een anonieme manier benadert wordt.
@ -455,23 +580,22 @@ paths:
"title": "Methode niet toegestaan",
"detail": "",
"instance": "555d00b5-bc3f-4591-949b-479e76d49ea7",
"errors":
[
"errors": [
{
"code": "405",
"detail": null,
"path": null,
"parameter": null,
},
"parameter": null
}
],
}
/notificationsubscriptions/{notificationSubscriptionId}:
patch:
tags:
- Notification subscriptions
summary: Update a notificationSubscription for a customer.
summary: Update a notificationSubscription for a customer (account or private).
description: |
Update a notificationSubscription for a customer.
Update a notificationSubscription for a customer (account or private).
parameters:
- name: X-HTM-JWT-AUTH-HEADER
in: header
@ -502,6 +626,14 @@ 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:
@ -509,9 +641,15 @@ 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
@ -523,44 +661,22 @@ paths:
value:
{
"notificationSubscriptionId": "e112f26e-37fa-4bde-8def-9977cd1d50ae",
"notificationCategory":
{
"notificationCategoryId": 2,
"name": "Nieuwsbrief aanmelding",
"notificationCategory": {
"notificationCategoryId": 1,
"name": "HTM nieuwbrief"
},
"isActive": False,
"isActive": False
}
Update a notificationSubscription to active:
summary: Return the updated active notification
value:
{
"notificationSubscriptionId": "e112f26e-37fa-4bde-8def-9977cd1d50ae",
"notificationCategory":
{
"notificationCategoryId": 2,
"name": "Nieuwsbrief aanmelding",
"notificationCategory": {
"notificationCategoryId": 1,
"name": "HTM nieuwbrief"
},
"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,
},
],
"isActive": True
}
components:
schemas:

View File

@ -224,7 +224,6 @@ paths:
name: Inactive
vasCustomerNumber: CST005
customerPreference:
customerPreferenceId: 1
language:
languageId: 1
name: Dutch
@ -348,7 +347,6 @@ paths:
name: Inactive
vasCustomerNumber: CST005
customerPreference:
customerPreferenceId: 2
language:
languageId: 1
name: Dutch
@ -507,7 +505,6 @@ paths:
name: Inactive
vasCustomerNumber: CST005
customerPreference:
customerPreferenceId: 1
language:
languageId: 1
name: Dutch
@ -645,7 +642,6 @@ paths:
name: Active
vasCustomerNumber: CST005
customerPreference:
customerPreferenceId: 5
language:
languageId: 1
name: Dutch
@ -2713,6 +2709,10 @@ components:
example: 1
postCustomerStatusResponse:
type: object
properties:
customerPreferenceId:
type: integer
example: 1
postCustomerPreferencesResponse:
type: object
properties:

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,468 @@
openapi: 3.0.1
info:
title: Service Engine APIs for maileon
description: >-
Service Engine APIs for maileon. These are NOT the CRUD APIs to access raw data in the database.
version: "1.0"
servers:
- url: https://services.acc.api.htm.nl/abt/maileon/1.0
tags:
- name: Customers
description: >-
Service Engine API's for maileon to update the customer profile. Only field isEmailVerified.
- name: Notification categories
description: >-
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.
paths:
/customers:
patch:
tags:
- Customers
parameters:
- name: customerProfileId
in: header
schema:
type: string
example: 1
required: true
description: The customerProfileId of a customer
- name: emailAddress
in: header
schema:
type: string
format: email
example: john.doe@mymailprovider.com
required: true
description: The emailadress of the customer
summary: Update a customer profile
description: Update a customer profile based on the customerProfileId and emailAddress
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/unavailable"
examples:
patchCustomer:
value:
{
"person": {
"isEmailVerified": True
}
}
responses:
"202":
description: OK
/notificationcategories:
get:
tags:
- Notification categories
summary: Get notification categories and optins 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:
- name: expand
in: query
schema:
type: string
enum: [none, eventType, eventTypeChannel]
default: none
responses:
"200":
description: OK
content:
application/json:
examples:
getNotifactionCategories?expand=evenTypeChannel:
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
value:
{
"notificationCategories":[
{
"notificationCategoryId": 1,
"name": "Nieuwsbrief"
},
{
"notificationCategoryId": 2,
"name": "Mijn Reizen"
},
{
"notificationCategoryId": 3,
"name": "Mijn Passen"
}
]
}
"404":
description: No notification category found
content:
application/json:
example:
{
"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
}
],
}
/notificationsubscriptions:
get:
tags:
- 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.
parameters:
- name: customerProfileId
in: header
schema:
type: integer
example: 12361
required: true
description: The customerProfileId of a customer
responses:
"200":
description: OK
content:
application/json:
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",
"notificationCategoryId": 1,
"isActive": true
},
{
"notificationSubscriptionId": "571388cd-8903-40d5-89e6-9191cb8d656e",
"notificationCategoryId": 2,
"isActive": true
},
{
"notificationSubscriptionId": "cf736ff2-2f8f-434e-a3c7-a14064b73c9b",
"notificationCategoryId": 3,
"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",
"notificationCategoryId": 1,
"isActive": true
},
{
"notificationSubscriptionId": "cf736ff2-2f8f-434e-a3c7-a14064b73c9b",
"notificationCategoryId": 3,
"isActive": false
}
]
}
"403":
description: Forbidden // Als geverifieerd profiel gevonden wordt, maar niet op een geverifieerde manier benaderd wordt
content:
application/json:
example:
{
"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
}
],
}
"404":
description: No notification subscriptions found
content:
application/json:
example:
{
"type": "https://api.integratielaag.nl/abt/touchpoint/2.0/notifications",
"title": "Niet gevonden",
"detail": "Notificaties niet gevonden",
"instance": "555d00b5-bc3f-4591-949b-479e76d49ea7",
"errors": [
{
"code": "404",
"detail": null,
"path": null,
"parameter": null
}
],
}
components:
schemas:
unavailable:
type: object

View File

@ -228,7 +228,7 @@ paths:
content:
application/json:
schema:
$ref: "#/components/schemas/TransactionItemsBulkRequestBody"
$ref: "#/components/schemas/TransactionItemsPostRequestBody"
examples:
Add single transaction item:
summary: Add single transaction item
@ -285,7 +285,7 @@ paths:
content:
application/json:
schema:
$ref: "#/components/schemas/BulkResponseBody"
$ref: "#/components/schemas/TransactionItemsPostResponseBody"
examples:
Array of transaction items accepted:
summary: Array of transaction items accepted
@ -381,7 +381,7 @@ paths:
content:
application/json:
schema:
$ref: "#/components/schemas/GetResponseStatus"
$ref: "#/components/schemas/TransactionItemsResponseStatusGetResponseBody"
examples:
Batch successfully processed:
summary: Batch successfully processed
@ -2979,10 +2979,10 @@ paths:
- default: []
x-auth-type: Application & Application User
x-throttling-tier: Unlimited
/transactionitems/bulk:
patch:
summary: Patch transaction items in bulk.
description: Patch transaction items in bulk.
/transactionitems/reject:
post:
summary: Reject transaction items to source in bulk.
description: Reject transaction items to source in bulk.
tags:
- Bulk processing v2.2
requestBody:
@ -2992,117 +2992,76 @@ paths:
schema:
$ref: "#/components/schemas/TransactionItemsBulkRequestBody"
examples:
List of transactions items to reject:
summary: List of transaction items to reject to source
description: List of transaction items to reject to source in bulk.
List of transaction item ids to reject:
summary: List of transaction item ids to reject to source
description: List of transaction item ids to reject to source.
value:
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.
value:
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
transactionItemIds:
- afce35b2-1dff-4ace-98d0-4b9ac405c87d
- b1c4f8e7-3f4e-4d2a-9c6e-2f5e6d7c8b9a
- c2d5e6f7-4g5h-5i6j-0k1l-3m4n5o6p7q8r
responses:
"202":
description: Accepted
content:
application/json:
schema:
$ref: "#/components/schemas/BulkResponseBody"
$ref: "#/components/schemas/TransactionItemsPostResponseBody"
examples:
Array of transaction items accepted:
summary: Array of transaction items accepted
summary: Array of transaction item ids accepted
description: |
The array of transaction items was accepted successfully.
The array of transaction item ids was accepted successfully.
The transaction items 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/transactionitems/bulk/responsestatus/webhooks/workflow/scaleUnits/prod-00/workflows/6fd466916c
callbackurl: https://api.integratielaag.nl/transactionitems/reject/responsestatus/runtime/webhooks/workflow/scaleUnits/prod-00/workflows/6fd466916c
retryAfter: 10
security:
- default: []
x-auth-type: Application & Application User
x-throttling-tier: Unlimited
/transactionitems/bulk/responsestatus/{clientTrackingId}:
/transactionitems/responsestatus/reject/{clientTrackingId}:
get:
tags:
- Bulk processing v2.2
summary: Get the status of the transaction item bulk patch.
description: Get the status of the asynchronous transaction item bulk patch.
summary: Get the status of the transaction item bulk reject.
description: Get the status of the asynchronous transaction item bulk reject.
parameters:
- in: path
name: clientTrackingId
schema:
type: string
required: true
description: The clientTrackingId of the transaction item bulk patch.
description: The clientTrackingId of the transaction item bulk reject.
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/GetResponseStatus"
$ref: "#/components/schemas/TransactionItemsRejectResponseStatusGetResponseBody"
examples:
Batch successfully processed:
summary: Batch successfully processed
description: |
Body of a batch of transaction items that was successfully patched.
A number of transaction items were patched.
Body of a batch of transaction items that was successfully rejected.
A number of transaction items were rejected.
value:
startTime: 2025-02-14T05:32:47.067Z
status: Finished
clientTrackingId: 08584620957189579629541919368CU00
summary:
created: 0
updated: 15
rejected: 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
x-throttling-tier: Unlimited
/processingfailures/bulk:
patch:
summary: Patch processing failures in bulk.
description: Patch processing failures in bulk.
/transactionitems/return:
post:
summary: Return transaction items to trx db in bulk.
description: Return transaction items to trx db in bulk.
tags:
- Bulk processing v2.2
requestBody:
@ -3110,96 +3069,70 @@ paths:
content:
application/json:
schema:
$ref: "#/components/schemas/ProcessingFailuresBulkRequestBody"
$ref: "#/components/schemas/TransactionItemsBulkRequestBody"
examples:
List of processing failures to resolve:
summary: List of processing failures to resolve
description: List of processing failures to resolve in bulk.
List of transaction item ids to reject:
summary: List of transaction item ids to return to trx db
description: List of transaction item ids to return to trx db.
value:
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.
transactionItemIds:
- afce35b2-1dff-4ace-98d0-4b9ac405c87d
- b1c4f8e7-3f4e-4d2a-9c6e-2f5e6d7c8b9a
- c2d5e6f7-4g5h-5i6j-0k1l-3m4n5o6p7q8r
responses:
"202":
description: Accepted
content:
application/json:
schema:
$ref: "#/components/schemas/BulkResponseBody"
$ref: "#/components/schemas/TransactionItemsPostResponseBody"
examples:
Array of processing failures accepted:
summary: Array of processing failures accepted
Array of transaction items accepted:
summary: Array of transaction item ids accepted
description: |
The array of processing failures was accepted successfully.
The processing failures will be processed asynchronously.
The array of transaction item ids was accepted successfully.
The transaction items 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/processingfailures/bulk/responsestatus/webhooks/workflow/scaleUnits/prod-00/workflows/6fd466916c
callbackurl: https://api.integratielaag.nl/transactionitems/return/responsestatus/runtime/webhooks/workflow/scaleUnits/prod-00/workflows/6fd466916c
retryAfter: 10
security:
- default: []
x-auth-type: Application & Application User
x-throttling-tier: Unlimited
/processingfailures/bulk/responsestatus/{clientTrackingId}:
/transactionitems/responsestatus/return/{clientTrackingId}:
get:
tags:
- Bulk processing v2.2
summary: Get the status of the processing failures bulk patch.
description: Get the status of the asynchronous processing failures bulk patch.
summary: Get the status of the transaction item bulk return.
description: Get the status of the asynchronous transaction item bulk return.
parameters:
- in: path
name: clientTrackingId
schema:
type: string
required: true
description: The clientTrackingId of the processing failures bulk patch.
description: The clientTrackingId of the transaction item bulk return.
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/GetResponseStatus"
$ref: "#/components/schemas/TransactionItemsReturnResponseStatusGetResponseBody"
examples:
Batch successfully processed:
summary: Batch successfully processed
description: |
Body of a batch of processing failures that was successfully patched.
A number of processing failures were patched.
Body of a batch of transaction items that was successfully returned to trx db.
A number of transaction items were returned to trx db.
value:
startTime: 2025-02-14T05:32:47.067Z
status: Finished
clientTrackingId: 08584620957189579629541919368CU00
summary:
created: 0
updated: 15
returned: 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
@ -3316,7 +3249,7 @@ components:
- occurredOn
- type
- status
TransactionItemsBulkRequestBody:
TransactionItemsPostRequestBody:
type: object
properties:
transactionItems:
@ -3324,10 +3257,6 @@ components:
items:
type: object
properties:
transactionItemId:
type: string
format: uuid
example: afce35b2-1dff-4ace-98d0-4b9ac405c87d
transactionType:
type: string
enum:
@ -3387,7 +3316,7 @@ components:
- amountTax
- occurredOn
- type
BulkResponseBody:
TransactionItemsPostResponseBody:
type: object
properties:
startTime:
@ -4973,7 +4902,7 @@ components:
required:
- concessionId
- name
GetResponseStatus:
TransactionItemsResponseStatusGetResponseBody:
type: object
properties:
summary:
@ -5030,24 +4959,51 @@ components:
transactionLineId:
type: string
example: fee907dd-e59d-44f5-a63b-bbdec38f79b7
ProcessingFailuresBulkRequestBody:
TransactionItemsBulkRequestBody:
type: object
properties:
processingFailures:
transactionItemIds:
type: array
items:
type: object
properties:
processingFailureId::
type: string
format: uuid
example: d8ee7035-fa3d-400e-9ad5-4fe8c4c73eb7
resolved:
type: boolean
example: true
example: 1ad109d3-fd7d-4b6f-872b-220d492f385f
required:
- transactionItemId
- resolved
- transactionItemIds
TransactionItemsRejectResponseStatusGetResponseBody:
type: object
properties:
summary:
type: object
properties:
rejected:
type: integer
example: 3
total:
type: integer
example: 3
required:
- rejected
- total
required:
- summary
TransactionItemsReturnResponseStatusGetResponseBody:
type: object
properties:
summary:
type: object
properties:
returned:
type: integer
example: 3
total:
type: integer
example: 3
required:
- returned
- total
required:
- summary
rfc9457:
type: object
properties:

View File

@ -1,110 +0,0 @@
openapi: 3.0.1
info:
title: Integratielaag APIs for one time tokens for a HTM customer
description: >-
Integratielaag APIs for one time tokens for a HTM customer. So that the a customer can be redirected between touchpoints, and the source of the request can be validated. NOTE : this is only to validate the redirect and the source of the redirect, touchpoint needs to make another call to take over the entra session.
version: "1.0"
servers:
- url: https://api.integratielaag.nl/v1/touchpoint
tags:
- name: OneTimeTokens
description: >-
Service Engine APIs for HTM Customers. These are NOT the CRUD APIs to access raw data in the database.
To be used by touchpoints to get information about HTM customers.
paths:
/one-time-tokens:
post:
tags:
- OneTimeTokens
summary: Create a one time token, to be included in the redirect
description: |
Create a one time token, to be included in the redirect
parameters:
- name: Authorization
in: header
required: true
style: simple
explode: false
schema:
type: string
example: Bearer {WSO2 Token}
description: Fill in a valid WSO2 Token for the touchpoint
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/unavailable'
example:
{
"nonce": "randomNonce123",
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik5HVEZ2ZEstZnl0aEV1Q..."
}
responses:
"201":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/oneTimeTokenResponse"
/one-time-tokens/validate:
post:
tags:
- OneTimeTokens
summary: Validate a received one time token
description: Validate a received one time token through the redirect, validates the source and validity of the token
parameters:
- name: Authorization
in: header
required: true
style: simple
explode: false
schema:
type: string
example: Bearer {WSO2 Token}
description: Fill in a valid WSO2 Token for the touchpoint
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/unavailable'
example:
{
"token": "handoff-xyz987"
}
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/unavailable"
examples:
logged in user:
summary: Logged in user
description: Logged in user
value:
{
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik5HVEZ2ZEstZnl0aEV1Q..."
}
user not logged in:
summary: User not logged in
description: User not logged in
value:
{}
"400":
description: Bad request
"401":
description: Not authorized
components:
schemas:
unavailable:
type: object
oneTimeTokenResponse:
type: object
properties:
token:
type: string
example: "handoff-xyz987"
expires_in:
type: integer
example: 120

View File

@ -0,0 +1,180 @@
openapi: 3.0.1
info:
title: ABT Service Engine Order APIs
version: "1.0"
description: Order APIs available in the Service Engine for order validation and fulfillment.
servers:
- url: https://services.acc.api.htm.nl/abt/touchpoint/2.0
paths:
/orders/validation/gboAgeProfile:
get:
tags:
- ServiceEngine Order validation
summary: Get GBO age profile that is valid for the given input
description: |-
Only one age profile can be valid at any one time, so only one profile is returned.
parameters:
- name: productStartDate
in: query
required: true
description: Start date of product validity
schema:
type: string
format: date
example: 2025-03-20
- name: birthDate
in: query
required: false
description: Birthdate of the OVpay-token holder
schema:
type: string
format: date
example: 2000-01-01
- name: ovPayTokenId
in: query
required: false
description: ovPayTokenId of the customer's selected token
schema:
type: integer
example: 1
- name: customerTokenId
in: query
required: false
description: customerTokenId of the customer's selected token
schema:
type: string
format: uuid
example: "4a2d2c9c-1e5d-4d8a-9c0a-6c0a6c0a6c0a"
- name: serviceReferenceId
in: query
required: false
description: serviceReferenceId of the customer's selected token
schema:
type: string
example: "NLOV1234567ABCDEFG"
- name: amount
in: query
required: false
description: amount belonging to the serviceReferenceId
schema:
type: integer
example: 100
- name: ovpasNumber
in: query
required: false
description: OVpas number of the customer's selected token
schema:
type: string
example: "63AW974"
- name: verificationCode
in: query
required: false
description: verification code belonging to the OVpas number
schema:
type: string
example: 1A3C7D
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/GboAgeProfileResponse"
"400":
description: Bad request
content:
application/json:
schema:
$ref: "#/components/schemas/rfc9457Response"
examples:
Missing parameter(s):
summary: Missing parameter(s)
value:
{
"type": "https://www.htm.nl/api/v1/400Error",
"title": "Missing parameter(s)",
"detail": "At least one of the following parameters must be present: birthDate, ovPayTokenId, customerTokenId, (serviceReferenceId and amount), (ovpasNumber and verificationCode)",
"instance": "urn:uuid:13c8416f-7632-4c8b-8a16-2132197be60c",
"apiErrorCode": "htm.api.err.400.2",
}
Invalid date format:
summary: Invalid date format
value:
{
"type": "https://www.htm.nl/api/v1/400Error",
"title": "Invalid input",
"detail": "The given birthDate is not a valid Date format",
"instance": "urn:uuid:13c8416f-7632-4c8b-8a16-2132197be60c",
"apiErrorCode": "htm.api.err.400.3",
}
"404":
description: Not found
content:
application/json:
schema:
$ref: "#/components/schemas/rfc9457Response"
examples:
No birthdate present in PAD of OVpay token:
summary: No birthdate present in PAD of OVpay token
value:
{
"type": "https://www.htm.nl/api/v1/404Error",
"title": "Missing birthdate in PAD",
"detail": "There is no birthdate present in the PAD of the OVpay token, or there is no PAD present at all",
"instance": "urn:uuid:13c8416f-7632-4c8b-8a16-2132197be60c",
"apiErrorCode": "htm.api.err.404.1",
}
components:
schemas:
GboAgeProfileResponse:
type: object
required:
- gboAgeProfileId
- name
- ageFromInclusive
- ageToInclusive
properties:
gboAgeProfileId:
type: integer
example: 1
name:
type: string
example: Kind (4 t/m 11 jaar)
ageFromInclusive:
type: integer
example: 4
ageToInclusive:
type: integer
example: 11
rfc9457Response:
type: object
properties:
type:
type: string
format: url
example: "https://www.htm.nl/api/v1/rfc9457Error"
title:
type: string
description: "Short summary of the error."
example: "The request is not valid."
detail:
type: string
description: "More detailed descriptionof the error."
example: "Some required parameters are missing."
instance:
type: string
description: "Unique identifier to correlate this specific error with logging in other applications."
example: "urn:uuid:13c8416f-7632-4c8b-8a16-2132197be60c"
additionalProperty1:
type: string
description: "Example of an additional property value to be used for error reporting."
example: "additionalValue1"
additionalProperty2:
type: array
description: "Example of an additional property array to be used for error reporting."
items:
type: string
example:
- "additionalValue2-1"
- "additionalValue2-2"

View File

@ -5,12 +5,6 @@ info:
description: CRUD APIs for ABT Orders database. These are NOT the functional APIs from Service Engine.
servers:
- url: https://services.acc.api.htm.nl/abt/abtorder/1.0
tags:
- name: Order
- name: Order Line
- name: Payment
- name: Customer
- name: Order Voucher
paths:
/orders:
get:
@ -42,6 +36,13 @@ paths:
example: 42
required: false
description: The technical id of the customer related to the order.
- in: query
name: deviceId
schema:
type: string
example: 181004c2-817a-4479-8aae-7a3faf02e239
required: false
description: The technical id of the device related to the order.
- in: query
name: totalAmount
schema:
@ -56,14 +57,6 @@ paths:
example: 1
required: false
description: The id of the touch point where the order was initiated.
- in: query
name: deviceId
schema:
type: string
format: uuid
example: "7a28bd54-7ca9-499a-a722-d15ab858ab99"
required: false
description: The id of the device used to place the order.
- in: query
name: languageId
schema:
@ -112,14 +105,6 @@ paths:
explode: false
required: false
description: Filter on most recent order status. 1 = concept, 2 = awaitingPayment, 3 = pendingPayment, 4 = paid, 5 = delivered, 6 = cancelled.
- in: query
name: issuedVoucherId
schema:
type: string
format: uuid
example: "b0a9f3c9-9b92-4f8c-b78d-6129be7218a6"
required: false
description: Filter on applied issuedVoucherId for the order.
responses:
"200":
description: OK
@ -137,11 +122,14 @@ paths:
"orderNumber": "123456",
"customerProfileId": 1337,
"totalAmount": 121,
"device":{
"deviceId": "8a3a1f90-2a50-4eb6-bc68-26cf0fac3015",
"alias": "Mijn mobiel"
},
"touchPoint":{
"touchPointId": 1,
"name": "Perplex"
},
"deviceId": "42e77532-d831-41da-b07a-7edb9bb7f004",
"language":
{
"languageId": 1,
@ -171,30 +159,6 @@ paths:
"description": "Betaling in behandeling",
},
],
"orderVouchers": [
{
"orderVoucherId": "399bd3b3-9721-4f09-a936-d64637de1621",
"issuedVoucher":{
"issuedVoucherId": "a0996218-bc5e-4826-9020-cda98a32838d",
"voucherCode": "Voucher1234",
"purchasedProductId": 31,
"fromInclusive": "2025-03-22T08:55:00",
"untillInclusive": "2026-03-22T08:55:00"
},
"orderLineId": null
},
{
"orderVoucherId": "f6c7ac42-1811-4e4d-82af-53e18fe16110",
"issuedVoucher":{
"issuedVoucherId": "54668baf-4905-4e9a-af02-09c170f295ed",
"voucherCode": "Voucher124",
"purchasedProductId": 35,
"fromInclusive": "2025-03-22T08:55:00",
"untillInclusive": "2026-03-22T08:55:00"
},
"orderLineId": "7a7a9d1a-3fc8-4058-a28b-082860aaa311"
}
],
"orderLines":
[
{
@ -374,22 +338,20 @@ paths:
"customerProfileId": 1337,
"totalAmount": 121,
"touchPointId": 1,
"deviceId": "b8ca9fdf-0bb9-4e49-b48d-41e395563377",
"languageId": 1,
"deviceId": "40132fc5-2371-4974-bdfc-8af16aa8bbd0",
"createdOn": "2024-03-22T09:00:00",
"order_OrderStatus":
[
{
"orderStatusId": 1,
"createdOn": "2024-03-22T08:55:00",
"description": "Concept order",
"orderStatusId": 4,
"createdOn": "2024-03-22T09:00:00",
"description": "Order succesvol betaald",
},
],
"orderVouchers":
[
{
"issuedVoucherId": "e81b2197-a6c2-45b6-9560-8ce8442e8604",
"orderLineId": "97824d2e-5189-456d-b6da-4cca511a7685"
"orderStatusId": 3,
"createdOn": "2024-03-22T08:55:00",
"description": "Betaling in behandeling",
},
],
"orderLines":
@ -440,6 +402,57 @@ paths:
],
},
],
"payments":
[
{
"createdOn": "2024-03-22T09:00:00",
"amountDebit": 121,
"paymentMethodId": 1,
"touchPointId": 1,
"isRefund": false,
"htmPaymentReference": "HTM-1234",
"pspPaymentReference": "Buckaroo-1234",
"paymentStatuses":
[
{
"createdOn": "2024-03-22T09:00:00",
"statusCode": "190",
"statusDescription": "Success",
"statusSubCode": "S001",
"statusSubDescription": "PaymentSuccessFul",
},
],
"mandateInput":
{
"directDebitMandateTypeId": 1,
"createdOn": "2024-03-22T09:00:00",
"bic": "RABONL2U",
"iban": "NL44RABO0123456789",
"ascription": "J. de Vries",
"place": "Den Haag",
},
},
],
"orderCustomer":
{
"birthname": "Jan",
"surname": "Vries",
"prefix": "de",
"emailAddress": "jandevries@outlook.com",
"dateOfBirth": "1970-01-01",
"orderCustomerAddresses":
[
{
"addressTypeId": 1,
"street": "Kon. Julianaplein",
"houseNumber": 10,
"houseNumberSuffix": "a",
"postalCode": "2595 AA",
"city": "Den Haag",
"country": "NL",
},
],
},
}
responses:
"201":
@ -482,11 +495,11 @@ paths:
"orderNumber": "123456",
"customerProfileId": 1337,
"totalAmount": 121,
"device": null,
"touchPoint":{
"touchPointId": 1,
"name": "Perplex"
},
"deviceId": null,
"language":
{
"languageId": 1,
@ -512,7 +525,6 @@ paths:
"description": "Betaling in behandeling",
},
],
"orderVouchers": null,
"orderLines":
[
{
@ -659,10 +671,10 @@ paths:
example:
{
"customerProfileId": 1337,
"deviceId": "fe68e624-b75f-48ca-a179-d5f86a8ab7d5",
"totalAmount": 121,
"languageId": 1,
"lastUpdatedOn": "2024-03-22T09:00:00",
"deviceId": "c4cbc26f-99c3-4068-90e6-29a890d9aee0"
}
responses:
"200":
@ -680,7 +692,6 @@ paths:
responses:
"200":
description: OK
/orders/{orderId}/statuses:
parameters:
- in: path
@ -718,143 +729,6 @@ paths:
{
"order_orderStatusId": "b9cf0096-4211-4be6-ac21-7bc34bc8e066",
}
/orders/{orderId}/ordervouchers:
parameters:
- in: path
name: orderId
schema:
type: string
format: uuid
example: d1dd439b-6072-4b97-89c9-724268865b93
required: true
description: The id of the order to process.
post:
summary: Add an order voucher.
description: Add an order voucher.
tags:
- Order Voucher
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/unavailable"
example:
{
"issuedVoucherId": "eec6af41-1a60-49f6-a92e-440054a92f13",
"orderLineId": "7a9d6e15-7c5c-421d-9ea9-00b9bb6dbe67"
}
responses:
"201":
description: Created
content:
application/json:
schema:
$ref: "#/components/schemas/unavailable"
example:
{
"orderVoucherId": "b9cf0096-4211-4be6-ac21-7bc34bc8e066",
}
/ordervouchers:
parameters:
- in: query
name: orderVoucherId
schema:
type: string
format: uuid
example: d1dd439b-6072-4b97-89c9-724268865b93
required: false
description: The id of the orderVoucher you are looking for.
- in: query
name: orderId
schema:
type: string
example: 90c926b9-3178-4757-acca-34cff66b980c
required: false
description: The id of the order
- in: query
name: orderLineId
schema:
type: string
example: 9e3363c8-e776-4675-b108-99b8c2e38eb6
required: false
description: The id of the orderLine
get:
summary: Find vouchers on the order
description: Find vouchers on the order
tags:
- Order Voucher
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/unavailable"
example:
[
{
"orderVoucherId": "19ef6882-8eda-43bf-b48e-9b4ff8745a50",
"issuedVoucher":{
"issuedVoucherId": "54668baf-4905-4e9a-af02-09c170f295ed",
"voucherCode": "Voucher124",
"purchasedProductId": 35,
"fromInclusive": "2025-03-22T08:55:00",
"untillInclusive": "2026-03-22T08:55:00"
},
"orderId": "f59e4769-53a0-4156-8991-6f9119ba629f",
"orderLineId": "eeb86071-4f59-405d-b2be-7d7a77044bfa"
}
]
/ordervouchers/{ordervoucherId}:
parameters:
- in: path
name: ordervoucherId
schema:
type: string
format: uuid
example: d1dd439b-6072-4b97-89c9-724268865b93
required: true
description: The id of the order to process.
patch:
summary: Update an order voucher.
description: Update an order voucher.
tags:
- Order Voucher
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/unavailable"
example:
{
"issuedVoucherId": "eec6af41-1a60-49f6-a92e-440054a92f13",
"orderLineId": "7a9d6e15-7c5c-421d-9ea9-00b9bb6dbe67"
}
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/unavailable"
example:
{
"orderVoucherId": "b9cf0096-4211-4be6-ac21-7bc34bc8e066",
}
delete:
summary: Delete an order voucher.
description: Delete an order voucher.
tags:
- Order Voucher
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/unavailable"
example:
{}
/orders/{orderId}/orderlines:
parameters:
- in: path
@ -1984,7 +1858,6 @@ paths:
"touchPointId": 1,
"name": "Perplex"
},
"deviceId": null,
"isRefund": false,
"htmPaymentReference": "HTM-1234",
"pspPaymentReference": "Buckaroo-1234",

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,477 +0,0 @@
openapi: 3.0.1
info:
title: TapConnect
description: >-
Welcome to the TapConnect Issuing API documentation. These pages describe the endpoints available within TapConnect. Please note that for historical reasons, Date/times are always in the "Europe/Amsterdam" timezone. The endpoints in this document are grouped as follows:
- **Export endpoints**: Export related endpoints return information about events related to tickets that have been issued. This can be used for Business Intelligence purposes or for financial record keeping.
- **Product endpoints**: Product related endpoints return information about products that are available to you as a sales partner and the details of each of these products.
- **Ticket endpoints**: Ticket related endpoints allow you to manage tickets throughout their lifecycle. This includes ticket creation, retrieving ticket information, or retrieving the barcode for a ticket.
- **Journey endpoints**: Used to calculate the price of a journey and to issue a ticket for that journey using the external fare calculation engine.
For more information on TapConnect please visit [https://tapconnect.io](https://tapconnect.io) or [https://documentation.tapconnect.io](https://documentation.tapconnect.io).
version: '1.0'
servers:
- url: https://services.acc.api.htm.nl/tapconnect/1.0
tags:
- name: Export
description: >-
Export data that can be used to generate reports about issued tickets
and related information
paths:
/v5/ticket-events-export:
get:
summary: Elastic Search ticket events export
description: "Exports ticket events data from Elastic Search.\n\nBoth parameters\
\ are a string, and they have to represent a date. Consider the list of valid\
\ formats below:\n1. 2021 - searches for all events in a year\n2. 2021-02\
\ - searches for all events in a month\n3. 2021-02-02 - searches for all events\
\ in a day\n4. 2021-02-02T12 - searches for all events in a specific hour\n\
5. 2021-02-02T12:00 - searches for all events in a specific minute\n6. 2021-02-02T12:00:00\
\ - searches for all events in a specific second\n\nIf an error occur, the\
\ last element returned will be a message with \"An error occurred on Elasticsearch\"\
\ and it \nmeans that not all the results are returned.\n_Keep in mind that\
\ the above timestamps would also be accepted as Zulu: 2021-02-02T11:00:00Z\
\ (winter time)_\n"
parameters:
- explode: true
in: query
name: start
required: true
description: The date to be considered as the interval starting date
schema:
example: 2021-02-01T00:00:00
type: string
style: form
- explode: true
in: query
name: end
required: true
description: The date to be considered as the interval ending date
schema:
example: 2021-02-02T00:00:00
type: string
style: form
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/v5_ticket_events_export_response'
description: Returns a JSON chunked array of ticket events exported from elastic search.
"400":
description: |
The request could not be validated. The request body or parameters contain incomplete or incorrect parameters. The body of the response will contain information about the problem.
"401":
description: |
Unauthorized call, you are not authorized to call this endpoint with the api key provided in the Authorization header. Please verify that your api key is correct and/or if you are authorized to call this endpoint.
"403":
description: |
Unauthorized call, you are not authorized to call this endpoint with the api key provided in the Authorization header. Please verify that your api key is correct and/or if you are authorized to call this endpoint.
"404":
description: |
The requested URL does not exist, or the requested object was not found.
"406":
description: |
The request was not accepted by the server. The body of the response will contain information about the problem.
tags:
- Export
components:
schemas:
v5_ticket_events_export_response:
type: array
items:
anyOf:
- $ref: '#/components/schemas/ActivateTicketEvent'
- $ref: '#/components/schemas/CreateBarcodeEvent'
- $ref: '#/components/schemas/CreateTicketEvent'
- $ref: '#/components/schemas/TapEvent'
- $ref: '#/components/schemas/InspectTicketEvent'
- $ref: '#/components/schemas/NotifyEvent'
ActivateTicketEvent:
properties:
eventId:
example: 1
type: number
eventType:
example: ACTIVATE_TICKET
type: string
occurredAt:
example: 2021-06-07T08:42:00.791992000Z
type: string
receivedAt:
example: 2021-06-07T08:42:00.791992000Z
type: string
timeToDie:
example: 2445836980
type: number
operators:
items:
example: HTM
type: string
type: array
createdBy:
example: HTM
type: string
definedBy:
example: HTM
type: string
ticketId:
example: hkbu3415fbidswd803nfdg7
type: string
validityStart:
example: 2021-06-06T22:00:00.000000000Z
type: string
validityEnd:
example: 2021-06-07T23:40:00.000000000Z
type: string
validityType:
example: FIXED
type: string
CreateBarcodeEvent:
properties:
eventId:
example: 2
type: number
eventType:
example: CREATE_BARCODE
type: string
occurredAt:
example: 2021-06-07T08:42:01.629279000Z
type: string
receivedAt:
example: 2021-06-07T08:42:01.629279000Z
type: string
timeToDie:
example: 2445836980
type: number
operators:
items:
example: HTM
type: string
type: array
createdBy:
example: HTM
type: string
definedBy:
example: HTM
type: string
ticketId:
example: 2huCpR99LHjGfiq8ZJoF
type: string
barcodeSignatureKeyId:
example: TCT07
type: string
barcodeValidityStart:
example: 2021-06-06T22:00:00.000000000Z
type: string
barcodeValidityEnd:
example: 2021-06-07T23:40:00.000000000Z
type: string
CreateTicketEvent:
properties:
eventId:
example: 0
type: number
eventType:
example: CREATE_TICKET
type: string
occurredAt:
example: 2021-06-07T08:42:00.790992000Z
type: string
receivedAt:
example: 2021-06-07T08:42:00.790992000Z
type: string
reportedAt:
example: 2021-06-07T08:42:00.790992000Z
type: string
timeToDie:
example: 2445836980
type: number
operators:
items:
example: HTM
type: string
type: array
createdBy:
example: HTM
type: string
definedBy:
example: HTM
type: string
ticketId:
example: 344BEuHcFkEChOPm06sY
type: string
serviceId:
example: HTM-0987-7477-0993
type: string
productName:
example: HTM Kinder Dagkaart
type: string
productCode:
example: "303"
type: string
productValidityPeriodUnit:
example: "DAYS"
type: string
productValidityPeriod:
example: 1
type: number
lifespanStart:
example: 2021-06-07
type: string
lifespanEnd:
example: 2021-06-08
type: string
language:
example: NL
type: string
salesChannelId:
example: "9999"
type: string
salesChannelName:
example: HTM App
type: string
startStation:
example: Haarlem
type: string
endStation:
example: Leiden Centraal
type: string
barcodeType:
example: UIC
type: string
validityType:
example: FIXED
type: string
refundable:
example: true
type: boolean
priceInCents:
example: 150,
type: number
numberOfAdults:
example: 1
type: number
numberOfChildren:
example: 0
type: number
roundToBusinessDay:
example: true
type: boolean
modalities:
example: ["BUS", "TRAM"]
type: array
items:
example: BUS
type: string
TapEvent:
properties:
eventId:
example: 5
type: number
eventType:
example: TAP
type: string
occurredAt:
example: 2021-06-07T08:42:00.790992000Z
type: string
receivedAt:
example: 2021-06-07T08:42:00.790992000Z
type: string
timeToDie:
example: 2445836980
type: number
validationAction:
example: CHECK_OUT
type: string
validationResult:
example: Approved
type: string
tapId:
example: a9aea0ae-52de-42cd-a2f1-93b80d9af389
type: string
operators:
items:
example: HTM
type: string
type: array
createdBy:
example: HTM
type: string
definedBy:
example: HTM
type: string
ticketId:
example: hkbu3415fbidswd803nfdg7
type: string
modality:
example: BUS
type: string
line:
example: "25"
type: string
trip:
example: "240"
type: string
vehicle:
example: "1512"
type: string
deviceId:
example: "13513A"
type: string
deviceType:
example: VBS
type: string
lastStopId:
example: "3409"
type: string
lastStopName:
example: Gramsbergenlaan
type: string
nextStopId:
example: "3409"
type: string
nextStopName:
example: Gramsbergenlaan
type: string
location:
type: object
properties:
lat:
example: 52.00089453333333
type: number
lon:
example: 4.004570666666667
type: number
InspectTicketEvent:
properties:
eventId:
example: 3
type: number
eventType:
example: INSPECT_TICKET
type: string
occurredAt:
example: 2021-06-07T08:42:00.790992000Z
type: string
receivedAt:
example: 2021-06-07T08:42:00.790992000Z
type: string
timeToDie:
example: 2445836980
type: number
operators:
items:
example: HTM
type: string
type: array
createdBy:
example: HTM
type: string
definedBy:
example: HTM
type: string
ticketId:
example: uv1hzvrRd7Xd1Fs9vTxi
type: string
modality:
example: BUS
type: string
deviceId:
example: 6959bd00eaec8e68
type: string
deviceType:
example: IBS
type: string
validationResult:
example: Approved
type: string
NotifyEvent:
properties:
eventId:
example: 1185
type: number
eventType:
example: NOTIFY
type: string
occurredAt:
example: 2021-06-07T08:42:00.790992000Z
type: string
receivedAt:
example: 2021-06-07T08:42:00.790992000Z
type: string
timeToDie:
example: 2445836980
type: number
tapId:
example: a9aea0ae-52de-42cd-a2f1-93b80d9af389
type: string
sequence:
example: 2
type: number
tapResponseTimeMillis:
example: 402
type: number
validationAction:
example: CHECK_OUT
type: string
validationMethod:
example: ONLINE
type: string
validationResult:
example: Approved
type: string
operators:
items:
example: HTM
type: string
type: array
createdBy:
example: HTM
type: string
definedBy:
example: HTM
type: string
modality:
example: TRAM
type: string
line:
example: "3"
type: string
trip:
example: "692"
type: string
vehicle:
example: "4058"
type: string
deviceId:
example: "13A886"
type: string
deviceType:
example: VBS
type: string
lastStopId:
example: "2005"
type: string
lastStopName:
example: Fahrenheitstraat
type: string
nextStopId:
example: "2011"
type: string
nextStopName:
example: Valkenbosplein
type: string
ticketId:
example: hkbu3415fbidswd803nfdg7
type: string
location:
type: object
properties:
lat:
example: 52.001300283333336
type: number
lon:
example: 4.004586633333333
type: number