Compare commits

...

81 Commits

Author SHA1 Message Date
cf0d24632f Merge pull request 'feature/OVPAY-2394' (#49) from feature/OVPAY-2394 into develop
Reviewed-on: #49
2026-01-14 12:06:39 +00:00
aa4721c651 OVPAY-2394 - Processed review remarks. 2026-01-14 13:05:32 +01:00
46bf7874d9 OVPAY-2394 - Added POST, PATCH and DELETE operations for NotificationPreferences. 2026-01-13 16:08:39 +01:00
1a14918f4a OVPAY-2394 - Added PATCH /notificationsubscriptions/{uuid}. 2026-01-13 15:45:52 +01:00
7147318900 OVPAY-2394 - Added resourceNameId to Channel. 2026-01-13 15:38:48 +01:00
e0f78d2e38 OVPAY-2394 - Updated GET /notificationsubscriptions examples. 2026-01-13 15:19:49 +01:00
ab57e27c1d OVPAY-2394 - Added OriginSubscription. 2026-01-12 15:08:32 +01:00
68c7ccc279 OVPAY-2394 - Added groupName to NotificationCategory. Added query params. 2026-01-12 13:41:20 +01:00
8946026860 Added fromInclusive and untilInclusive. 2026-01-05 13:34:12 +01:00
fb8723124a Merge pull request 'OVPAY-2378 - Updated spec for /contractpayments.' (#46) from feature/OVPAY-2378 into develop
Reviewed-on: #46
Reviewed-by: Max Martens <m.martens@htm.nl>
2025-12-29 12:31:57 +00:00
6d7c260e9e OVPAY-2378 - PaymentMethod in NL. 2025-12-29 12:32:59 +01:00
292588b7ba OVPAY-2378 - Updated spec for /contractpayments. 2025-12-29 12:01:12 +01:00
4bd216bd37 Merge pull request 'features/OVPAY2294-vouchers-in-orders' (#45) from features/OVPAY2294-vouchers-in-orders into develop
Reviewed-on: #45
Reviewed-by: Bas Boterman <b.boterman@htm.nl>
2025-12-16 13:57:40 +00:00
bfaa1ddcb1 fixed comments 2025-12-16 14:52:23 +01:00
55bac27561 simplified order_orderVoucherId 2025-12-15 15:36:51 +01:00
Max Martens
715a9668fc Clear up name and description of token transfer V2 endpoints 2025-12-15 14:27:48 +01:00
b4f20a9758 moved changes to TP orders to another branch and made equal to develop in this branch 2025-12-15 11:50:39 +01:00
22cb3ddd92 added query param deviceId 2025-12-15 11:43:03 +01:00
1f3e2289dc finished CRUD endpoints 2025-12-15 11:40:42 +01:00
3ff55e5888 Merge branch 'develop' of https://git.integratielaag.nl/HTM/ovpay into features/OVPAY2294-vouchers-in-orders 2025-12-15 10:44:01 +01:00
987f64a081 dubbele alias 2025-12-15 10:40:40 +01:00
00448486f3 added ovpasnumber to get token response 2025-12-15 10:39:33 +01:00
ed61633579 Merge pull request 'TapConnect V5 ticket events export schema reverse-engineered from real data' (#43) from feature/OVPAY-2345-TapConnect-Elastic into develop
Reviewed-on: #43
2025-12-15 09:01:20 +00:00
d34ba20d19 Merge branch 'develop' of https://git.integratielaag.nl/HTM/ovpay into features/OVPAY2294-vouchers-in-orders 2025-12-12 11:09:30 +01:00
3e1f23afe3 Merge pull request 'features/Edit-TP-notification' (#44) from features/Edit-TP-notification into develop
Reviewed-on: #44
2025-12-11 13:51:28 +00:00
Max Martens
6260cc5d63 Now supports handling events for TRIPS as well as ALERTS. Need to manually add xBOTs to queue using API 9853 2025-12-10 16:49:15 +01:00
d9157d04ef removed account or private from desc patch 2025-12-10 12:40:48 +01:00
f57f17e645 removed email from notification patch 2025-12-10 12:37:12 +01:00
3ae52206fc OVPAY-2370 - Status codes async jobs. 2025-12-10 12:26:09 +01:00
a52dfcab93 OVPAY-1946 - Fixed typo. 2025-12-09 16:57:10 +01:00
80b0de274e OVPAY-1946 - Updated examples. 2025-12-09 16:52:42 +01:00
38cfec81c8 removed is active from post body 2025-12-08 14:35:33 +01:00
Max Martens
3ffc525d48 Fix number typo 2025-12-08 13:30:22 +01:00
Max Martens
105a18c0ac TapConnect V5 ticket events export schema reverse-engineered from real data 2025-12-08 12:06:10 +01:00
7732ae13a6 Merge pull request 'added bulk endpoints to purchased product CRUD' (#42) from features/OVPAY2294-vouchers into develop
Reviewed-on: #42
2025-12-08 09:35:27 +00:00
4084a574c9 fixed the semantic error 2025-12-08 10:32:35 +01:00
3ebdf0090f added bulk status endpoints 2025-12-05 12:00:36 +01:00
b898288632 added bulk post to purchased product CRUD 2025-12-05 09:58:03 +01:00
dce4d5ea17 Update src/openapi/customers/SE-notifications.yaml
Fixed error in query param type.
2025-12-04 11:15:46 +00:00
e4b3ab4ccf added post voucher in SE orders 2025-12-01 16:54:10 +01:00
01aa8a9f1a Added get vouchers and voucher in response body's 2025-12-01 14:48:17 +01:00
bb917c3635 updated CRUD order swaggers for vouchers 2025-12-01 13:35:41 +01:00
e0e70af138 OVPAY-1859 - Updated id's to reflect reality. 2025-12-01 13:34:58 +01:00
ed40c1d546 Merge pull request 'Updated .NET PADP reference implementation - added ability to explicitly provide userAccessToken to requests; add V3 OTP endpoints (while also keeping V2 endpoints)' (#25) from feature/PADP-reference-POC into develop
Reviewed-on: #25
Reviewed-by: Bas Boterman <b.boterman@htm.nl>
2025-12-01 09:44:13 +00:00
f990a9844e OVPAY-1826 - Added YAML for one time tokens for app sales (commit from Mirjam). 2025-12-01 10:06:39 +01:00
235ec95edb Merge pull request 'fixes-customer-endpoints' (#37) from fixes-customer-endpoints into develop
Reviewed-on: #37
Reviewed-by: Bas Boterman <b.boterman@htm.nl>
2025-12-01 08:50:00 +00:00
76f6ed9c9f Merge pull request 'feature/OVPAY-2294' (#40) from feature/OVPAY-2294 into develop
Reviewed-on: #40
2025-11-28 14:33:25 +00:00
e6b34176c2 Merge pull request 'feature/OVPAY-2103' (#39) from feature/OVPAY-2103 into develop
Reviewed-on: #39
2025-11-28 14:33:00 +00:00
959ed1cd96 OVPAY-2294 - Introduced voucherStatusInstance. 2025-11-28 13:47:58 +01:00
d7f4cefca8 OVPAY-2103 - Updated customerTokens. 2025-11-28 12:39:19 +01:00
270217238f OVPAY-2103 - Typo OrderCustomers. 2025-11-28 11:51:05 +01:00
04be5e5fbd OVPAY-2103 - Processed review remarks. 2025-11-28 11:41:04 +01:00
cd9a2f1683 OVPAY-2103 - Typos. 2025-11-27 15:35:22 +01:00
8db879c589 OVPAY-2103 - Bugs after merge conflict, part deux. 2025-11-27 15:10:07 +01:00
8c3ad923f7 OVPAY-2103 - Bugs after merge conflict. 2025-11-27 15:05:58 +01:00
b8ad7c4ebf Merge branch 'develop' into feature/OVPAY-2103 2025-11-27 14:57:03 +01:00
de884764bd Merge pull request 'features/OVPAY1987-SE-orders-update' (#27) from features/OVPAY1987-SE-orders-update into develop
Reviewed-on: #27
2025-11-27 13:23:12 +00:00
Max Martens
403b336522 Rename HATEOAS for transfer to "transfer_token" to be in line with actual SE implementation 2025-11-27 13:22:09 +01:00
8f93fb03fd OVPAY-2103 - Updated examples existing endpoints. 2025-11-27 12:23:09 +01:00
6f95cee75e OVPAY-2103 - Added endpoints for TapConnectTicket. 2025-11-27 11:35:13 +01:00
b0360c22be OVPAY-2103 - Added endpoints for IssuedVoucher. 2025-11-27 11:01:09 +01:00
bfa400820f OVPAY-2103 - Added batch endpoints for bulk patch processing failures. 2025-11-27 09:42:35 +01:00
62ab18b8e8 OVPAY-2103 - Added emty attributes under customerTokens. 2025-11-26 16:11:16 +01:00
361d194bc4 OVPAY-1946 - Updated bulk responsestatus endpoints. 2025-11-26 15:23:18 +01:00
207c6961a8 OVPAY-1946 - Updated bulk endpoints. 2025-11-26 15:17:58 +01:00
70842ca9dc OVPAY-2103 - Created two versions of get concept order for known and anon token. 2025-11-21 17:09:33 +01:00
b51ce9cee1 OVPAY-2103 - Updated relevant examples of concept orders. 2025-11-21 17:02:08 +01:00
0b91eae4fa OVPAY-2103 - Updated examples for POST order. 2025-11-21 15:00:00 +01:00
482911d717 OVPAY-2103 - Added example for get single order. 2025-11-21 14:54:05 +01:00
a4f33c5439 OVPAY-2103 - Added ovPayToken info to get orders. 2025-11-21 14:36:29 +01:00
3230465cbb OVPAY-2103 - Added epurse and pad attributes to get orders. 2025-11-19 16:53:11 +01:00
6e3aa3a04b OVPAY-2103 - Removed unused endpoints. 2025-11-19 15:52:46 +01:00
b1bbe6b0cb OVPAY-2103 - Removed v1 examples from OAS. 2025-11-19 15:33:36 +01:00
e47464f4a6 removed customerSTatusInstanceId 2025-11-18 13:49:23 +01:00
6de1d6f22b semantic error 2025-11-17 15:19:45 +01:00
61332e38c6 small fixes preference and status Id's 2025-11-17 15:16:26 +01:00
bdfff9377b fixed merge conflicts 2025-09-13 21:34:06 +02:00
Max Martens
1eec7eab28 Add ability to explicitly provide userAccessToken to requests; add V3 OTP endpoints (while also keeping V2 endpoints) 2025-08-28 10:44:22 +02:00
ba8a31c94f derp 2025-08-07 17:34:45 +02:00
5e79d30c7c derp 2025-08-07 17:32:44 +02:00
343b74c038 updated specs 2025-08-07 16:20:15 +02:00
36 changed files with 7334 additions and 3563 deletions

View File

@ -0,0 +1,614 @@
using System.Text;
using System.Security.Cryptography.X509Certificates;
using System.Text.Json;
using Microsoft.AspNetCore.Http.HttpResults;
class ApimHelper
{
private readonly string _apimBaseUri;
private readonly string _b2bApiUri;
private readonly string _padp1201Uri;
private readonly string _padp1202Uri;
private readonly string _padp1204Uri;
private readonly string _padp1205Uri;
private readonly string _padp1206V2Uri;
private readonly string _padp1207V2Uri;
private readonly string _padp1206V3Uri;
private readonly string _padp1207V3Uri;
private readonly string _padp1210Uri;
private readonly string _padp1211Uri;
private readonly string _b2bApiKey;
private readonly string _padApiKey;
private readonly string _clientId;
private readonly string _clientSecret;
private CryptoHelper cryptoHelper;
private readonly HttpClient _httpClient;
private Dictionary<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

@ -0,0 +1,124 @@
using System.Security.Cryptography;
using System.Text;
public class CryptoHelper
{
private RSA rsa;
private string _publicKeyRsa;
public CryptoHelper(UserProperties userProperties)
{
string publicKeyPath = Environment.CurrentDirectory + userProperties.Rsa.PublicKeyFile;
string privateKeyPath = Environment.CurrentDirectory + userProperties.Rsa.PrivateKeyFile;
Console.WriteLine("Loading public key from: {0}", publicKeyPath);
Console.WriteLine("Loading private key from: {0}", privateKeyPath);
rsa = RSA.Create();
rsa.ImportFromPem(File.ReadAllText(publicKeyPath));
rsa.ImportFromPem(File.ReadAllText(privateKeyPath));
_publicKeyRsa = Convert.ToBase64String(rsa.ExportSubjectPublicKeyInfo());
}
public string GetPublicKeyRsa() => _publicKeyRsa;
public byte[] DecryptEphemeralKey(string encryptedEphemeralKey)
{
return rsa.Decrypt(Convert.FromBase64String(encryptedEphemeralKey), RSAEncryptionPadding.OaepSHA512);
}
public async Task<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

@ -0,0 +1,15 @@
<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

@ -0,0 +1,250 @@
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
using Swashbuckle.AspNetCore.Annotations;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(setupAction =>
{
setupAction.EnableAnnotations();
setupAction.SchemaFilter<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

@ -0,0 +1,38 @@
// Rename this file to appsettings.json and provide correct credentials and locations for the cryptographic files
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"UserProperties": {
"uris": {
"apimBaseUri": "https://api-ovpay-acc.translink.nl",
"b2bApiUri": "/b2b-client-authentication/v1/token",
"padp1201Uri": "/pad-management/v2/personal-data/{xtat}",
"padp1202Uri": "/pad-management/v2/personal-data/{xtat}",
"padp1204Uri": "/pad-management/v2/personal-data/{xtat}",
"padp1205Uri": "/pad-management/v2/personal-data/{xtat}",
"padp1206V2Uri": "/pad-management/v2/personal-data/{xtat}/generate-otp",
"padp1207V2Uri": "/pad-management/v2/personal-data/{xtat}/validate-otp",
"padp1206V3Uri": "/pad-management/v3/personal-data/generate-otp",
"padp1207V3Uri": "/pad-management/v3/personal-data/validate-otp",
"padp1210Uri": "/pad-management/v2/personal-data/encrypted-update-init",
"padp1211Uri": "/pad-management/v2/personal-data/{xtat}/administrative-data"
},
"rsa": {
"publicKeyFile": "\\Properties\\{public key in PEM format}.pem",
"privateKeyFile": "\\Properties\\{private key in PEM format}.pem"
},
"credentials": {
"clientCertFile": "\\Properties\\{APIM client certificate}.pfx",
"clientCertPassword": "{pfxPassword}",
"clientId": "HTM_Retailer",
"clientSecret": "{clientSecret}",
"b2bApiKey": "{b2bApiKey}",
"padApiKey": "{padApiKey}"
}
}
}

View File

@ -0,0 +1,44 @@
using System.Text.Json.Serialization;
public class AdministrativeData
{
public AdministrativeData(AdministrativeDataElement name, AdministrativeDataElement photo, AdministrativeDataElement birthdate)
{
this.name = name;
this.photo = photo;
this.birthdate = birthdate;
}
public AdministrativeDataElement name { get; set; }
public AdministrativeDataElement photo { get; set; }
public AdministrativeDataElement birthdate { get; set; }
public class AdministrativeDataElement
{
public AdministrativeDataElement(bool inaccuracyFlag, string inaccuracyFlagReason, int inaccuracyFlagCounter, int changeCounter, int maxUpdatesVerificationCount, DateTime lastChangeDate, bool isValidated)
{
this.inaccuracyFlag = inaccuracyFlag;
this.inaccuracyFlagReason = inaccuracyFlagReason;
this.inaccuracyFlagCounter = inaccuracyFlagCounter;
this.changeCounter = changeCounter;
this.maxUpdatesVerificationCount = maxUpdatesVerificationCount;
this.lastChangeDate = lastChangeDate;
this.isValidated = isValidated;
}
[JsonPropertyName("inaccuracyFlag")]
public bool inaccuracyFlag { get; set; }
[JsonPropertyName("inaccuracyFlagReason")]
public string inaccuracyFlagReason { get; set; }
[JsonPropertyName("inaccuracyFlagCounter")]
public int inaccuracyFlagCounter { get; set; }
[JsonPropertyName("changeCounter")]
public int changeCounter { get; set; }
[JsonPropertyName("maxUpdatesVerificationCount")]
public int maxUpdatesVerificationCount { get; set; }
[JsonPropertyName("lastChangeDate")]
public DateTime lastChangeDate { get; set; }
[JsonPropertyName("isValidated")]
public bool isValidated { get; set; }
}
}

View File

@ -0,0 +1,33 @@
using System.Text.Json.Serialization;
public class B2bAccessToken
{
public B2bAccessToken(string accessToken, int expiresIn, int refreshExpiresIn, string refreshToken, string tokenType, int notBeforePolicy, string scope, string beId)
{
this.accessToken = accessToken;
this.expiresIn = expiresIn;
this.refreshExpiresIn = refreshExpiresIn;
this.refreshToken = refreshToken;
this.tokenType = tokenType;
this.notBeforePolicy = notBeforePolicy;
this.scope = scope;
this.beId = beId;
}
[JsonPropertyName("access_token")]
public string accessToken { get; set; }
[JsonPropertyName("expires_in")]
public int expiresIn { get; set; }
[JsonPropertyName("refresh_expires_in")]
public int refreshExpiresIn { get; set; }
[JsonPropertyName("refresh_token")]
public string refreshToken { get; set; }
[JsonPropertyName("token_type")]
public string tokenType { get; set; }
[JsonPropertyName("not-before-policy")]
public int notBeforePolicy { get; set; }
[JsonPropertyName("scope")]
public string scope { get; set; }
[JsonPropertyName("BE_ID")]
public string beId { get; set; }
}

View File

@ -0,0 +1,29 @@
using System.Text.Json.Serialization;
public class CreatePersonalDataRequest
{
[JsonPropertyName("metadata")]
public Metadata metadata { get; set; } = new Metadata();
[JsonPropertyName("data")]
public Data data { get; set; } = new Data();
public class Metadata
{
public string? email { get; set; }
public string? ephemeralKeyAlias { get; set; }
}
public class Data
{
public PersonalAccountData personalAccountData { get; set; } = new PersonalAccountData();
}
public class PersonalAccountData
{
public string? name { get; set; }
public string? birthdate { get; set; }
public string? photo { get; set; }
}
}

View File

@ -0,0 +1,23 @@
using System.Text.Json.Serialization;
using Swashbuckle.AspNetCore.Annotations;
public class DecryptedPersonalData
{
public DecryptedPersonalData(DecryptedData decryptedData, PersonalData encryptedData)
{
this.decryptedData = decryptedData;
this.encryptedData = encryptedData;
}
public DecryptedData decryptedData { get; set; }
public PersonalData encryptedData { get; set; }
public class DecryptedData
{
[JsonPropertyName("decryptedName")]
public string? decryptedName { get; set; }
[JsonPropertyName("decryptedBirthdate")]
public string? decryptedBirthdate { get; set; }
[SwaggerSchema(Format = "byte", Description = "Base64 encoded photo")]
public string? decryptedPhoto { get; set; }
}
}

View File

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

View File

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

View File

@ -0,0 +1,38 @@
using System.Text.Json.Serialization;
public class ErrorResponse
{
[JsonConstructor]
public ErrorResponse(Error[] errors, string exceptionClassName, string exceptionStackTrace)
{
this.errors = errors;
this.exceptionClassName = exceptionClassName;
this.exceptionStackTrace = exceptionStackTrace;
}
public ErrorResponse(string errorMessage, int statusCode)
{
this.errors = new Error[] { new Error(statusCode.ToString(), new string[] { }, errorMessage) };
this.exceptionClassName = null;
this.exceptionStackTrace = null;
}
public Error[] errors { get; set; }
public string? exceptionClassName { get; set; }
public string? exceptionStackTrace { get; set; }
public class Error
{
public Error(string code, string[] data, string message)
{
this.code = code;
this.data = data;
this.message = message;
}
public string code { get; set; }
public string[] data { get; set; }
public string message { get; set; }
}
}

View File

@ -0,0 +1,13 @@
using System.Text.Json.Serialization;
public class GenerateOtpRequestV3
{
[JsonPropertyName("source")]
public string source { get; set; }
[JsonPropertyName("recipient")]
public string recipient { get; set; }
[JsonPropertyName("channel")]
public string channel { get; set; }
}

View File

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

View File

@ -0,0 +1,42 @@
using System.Reflection.Metadata.Ecma335;
using System.Text.Json.Serialization;
public class PersonalData
{
public PersonalData(Metadata metadata, Data data)
{
this.metadata = metadata;
this.data = data;
}
public Metadata metadata { get; set; }
public Data data { get; set; }
public class Metadata
{
public Metadata(string encryptedEphemeralKey)
{
this.encryptedEphemeralKey = encryptedEphemeralKey;
}
[JsonPropertyName("encryptedEphemeralKey")]
public string encryptedEphemeralKey { get; set; }
}
public class Data
{
public Data(string name, string birthdate, string photo)
{
this.name = name;
this.birthdate = birthdate;
this.photo = photo;
}
[JsonPropertyName("name")]
public string name { get; set; }
[JsonPropertyName("birthdate")]
public string birthdate { get; set; }
[JsonPropertyName("photo")]
public string photo { get; set; }
}
}

View File

@ -0,0 +1,42 @@
using System.Reflection;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
[AttributeUsage(
AttributeTargets.Class |
AttributeTargets.Struct |
AttributeTargets.Parameter |
AttributeTargets.Property |
AttributeTargets.Enum,
AllowMultiple = false)]
public class SwaggerSchemaExampleAttribute : Attribute
{
public SwaggerSchemaExampleAttribute(string example)
{
Example = example;
}
public string Example { get; set; }
}
public class SwaggerSchemaExampleFilter : ISchemaFilter
{
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
if (context.MemberInfo != null)
{
var schemaAttribute = context.MemberInfo.GetCustomAttributes<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

@ -0,0 +1,29 @@
using System.Text.Json.Serialization;
public class UpdatePersonalDataRequest
{
[JsonPropertyName("metadata")]
public Metadata metadata { get; set; } = new Metadata();
[JsonPropertyName("data")]
public Data data { get; set; } = new Data();
public class Metadata
{
public bool skipUpdateCounter { get; set; }
public string? ephemeralKeyAlias { get; set; }
}
public class Data
{
public PersonalAccountData personalAccountData { get; set; } = new PersonalAccountData();
}
public class PersonalAccountData
{
public string? name { get; set; }
public string? birthdate { get; set; }
public string? photo { get; set; }
}
}

View File

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

View File

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

View File

@ -0,0 +1,58 @@
public class UserProperties
{
public UserProperties(UrisSettings uris, RsaSettings rsa, CredentialsSettings credentials)
{
(Uris, Rsa, Credentials) = (uris, rsa, credentials);
}
public UrisSettings Uris { get; set; }
public RsaSettings Rsa { get; set; }
public CredentialsSettings Credentials { get; set; }
public class UrisSettings
{
public UrisSettings(string apimBaseUri, string b2bApiUri, string padp1201Uri, string padp1202Uri, string padp1204Uri, string padp1205Uri, string padp1206V2Uri, string padp1207V2Uri, string padp1206V3Uri, string padp1207V3Uri, string padp1210Uri, string padp1211Uri)
{
(ApimBaseUri, B2bApiUri, Padp1201Uri, Padp1202Uri, Padp1204Uri, Padp1205Uri, Padp1206V2Uri, Padp1207V2Uri, Padp1206V3Uri, Padp1207V3Uri, Padp1210Uri, Padp1211Uri) = (apimBaseUri, b2bApiUri, padp1201Uri, padp1202Uri, padp1204Uri, padp1205Uri, padp1206V2Uri, padp1207V2Uri, padp1206V3Uri, padp1207V3Uri, padp1210Uri, padp1211Uri);
}
public string ApimBaseUri { get; set; }
public string B2bApiUri { get; set; }
public string Padp1201Uri { get; set; }
public string Padp1202Uri { get; set; }
public string Padp1204Uri { get; set; }
public string Padp1205Uri { get; set; }
public string Padp1206V2Uri { get; set; }
public string Padp1207V2Uri { get; set; }
public string Padp1206V3Uri { get; set; }
public string Padp1207V3Uri { get; set; }
public string Padp1210Uri { get; set; }
public string Padp1211Uri { get; set; }
}
public class RsaSettings
{
public RsaSettings(string publicKeyFile, string privateKeyFile)
{
(PublicKeyFile, PrivateKeyFile) = (publicKeyFile, privateKeyFile);
}
public string PublicKeyFile { get; set; }
public string PrivateKeyFile { get; set; }
}
public class CredentialsSettings
{
public CredentialsSettings(string clientCertFile, string clientCertPassword, string clientId, string clientSecret, string b2bApiKey, string padApiKey)
{
(ClientCertFile, ClientCertPassword, ClientId, ClientSecret, B2bApiKey, PadApiKey) = (clientCertFile, clientCertPassword, clientId, clientSecret, b2bApiKey, padApiKey);
}
public string ClientCertFile { get; set; }
public string ClientCertPassword { get; set; }
public string ClientId { get; set; }
public string ClientSecret { get; set; }
public string B2bApiKey { get; set; }
public string PadApiKey { get; set; }
}
}

View File

@ -0,0 +1,13 @@
using System.Text.Json.Serialization;
public class ValidateOtpRequestV3
{
[JsonPropertyName("otp")]
public string otp { get; set; }
[JsonPropertyName("source")]
public string source { get; set; }
[JsonPropertyName("recipient")]
public string recipient { get; set; }
}

View File

@ -28,10 +28,33 @@ public final class Helpers {
return new JSONObject(string).get("alertId").toString(); 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 { public static String getXbot(String string) throws IOException {
return new JSONObject(string).get("xbot").toString(); 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 { public static void getAlertDetails(String alertId, String xBot, String gboBearerToken) throws Exception {
SSLContext sc = SSLContext.getInstance("SSL"); SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, DummyX509TrustManager.getDummyArray(), new java.security.SecureRandom()); sc.init(null, DummyX509TrustManager.getDummyArray(), new java.security.SecureRandom());
@ -48,7 +71,7 @@ public final class Helpers {
try(InputStream is = http.getInputStream()) { try(InputStream is = http.getInputStream()) {
String response = new String(is.readAllBytes(), StandardCharsets.UTF_8); String response = new String(is.readAllBytes(), StandardCharsets.UTF_8);
LOGGER.info("GBO API 8851 alert details response for xBOT " + xBot + ": \n" + new JSONObject(response).toString(2)); LOGGER.info("GBO API 8851 alert details response for xBOT " + xBot + " and alertId " + alertId + ": \n" + new JSONObject(response).toString(2));
} }
} }

View File

@ -16,14 +16,28 @@ public class RabbitConnector {
private static final Logger LOGGER = LoggerFactory.getLogger(RabbitConnector.class); 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 { public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory(); ConnectionFactory factory = new ConnectionFactory();
factory.setVirtualHost("/"); factory.setVirtualHost("/");
factory.setAutomaticRecoveryEnabled(true); factory.setAutomaticRecoveryEnabled(true);
factory.setPort(443); factory.setPort(443);
factory.setHost("not.sbx.idbt.translink.nl"); factory.setHost("not.sbx.idbt.translink.nl");
factory.setUsername("BEID_3_ALERTS_nZs3"); factory.setUsername(USER_NAME);
factory.setPassword("VyubhPnczKgTB2zJ"); factory.setPassword(PASSWORD);
factory.useSslProtocol("TLSv1.2"); factory.useSslProtocol("TLSv1.2");
factory.setExceptionHandler(new ForgivingExceptionHandler()); factory.setExceptionHandler(new ForgivingExceptionHandler());
Map<String, Object> configs = factory.getClientProperties(); Map<String, Object> configs = factory.getClientProperties();
@ -33,7 +47,7 @@ public class RabbitConnector {
Channel channel = connection.createChannel(); Channel channel = connection.createChannel();
DeliverCallback deliverCallback = initDeliverCallback(channel); DeliverCallback deliverCallback = initDeliverCallback(channel);
AMQP.Queue.DeclareOk queue = channel.queueDeclarePassive("BEID_3.ALERTS"); AMQP.Queue.DeclareOk queue = channel.queueDeclarePassive(QUEUE_NAME);
LOGGER.info( LOGGER.info(
"Declared queue: " + queue.getQueue() + ", consumer count: " + queue.getConsumerCount() + ", message count: " + "Declared queue: " + queue.getQueue() + ", consumer count: " + queue.getConsumerCount() + ", message count: " +
queue.getMessageCount()); queue.getMessageCount());
@ -52,17 +66,39 @@ public class RabbitConnector {
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false); channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
LOGGER.info("Successfully acknowledged message with delivery tag: " + delivery.getEnvelope().getDeliveryTag()); LOGGER.info("Successfully acknowledged message with delivery tag: " + delivery.getEnvelope().getDeliveryTag());
LOGGER.info("Getting alert details via GBO API 8851..."); if (QUEUE_NAME.equals("BEID_3.TRIPS")) {
try { getTripDetails(message);
String alertId = Helpers.getAlertId(message); } else if (QUEUE_NAME.equals("BEID_3.ALERTS")) {
String xBot = Helpers.getXbot(message); getAlertDetails(message);
String gboBearerToken = Helpers.getGboBearerToken();
Helpers.getAlertDetails(alertId, xBot, gboBearerToken);
} catch (Exception e) {
throw new RuntimeException(e);
} }
}; };
} }
private static void getAlertDetails(String message) {
try {
String alertId = Helpers.getAlertId(message);
String xBot = Helpers.getXbot(message);
String gboBearerToken = Helpers.getGboBearerToken();
LOGGER.info("Getting alert details for xBOT {} and alertId {} via GBO API 8851...", xBot, alertId);
Helpers.getAlertDetails(alertId, xBot, gboBearerToken);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static void getTripDetails(String message) {
try {
String tripId = Helpers.getTripId(message);
String xBot = Helpers.getXbot(message);
String gboBearerToken = Helpers.getGboBearerToken();
LOGGER.info("Getting trip details for xBOT {} and tripId {} via GBO API 8659...", xBot, tripId);
Helpers.getTripDetails(tripId, xBot, gboBearerToken);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
} }

View File

@ -597,9 +597,13 @@ paths:
schema: schema:
$ref: "#/components/schemas/unavailable" $ref: "#/components/schemas/unavailable"
examples: examples:
List all contract payments for a single debtor: Empty list:
summary: List all contract payments for a single debtor summary: Empty list
description: List all contract payments for single debtor with debtor number 'D123456'. 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.
value: value:
{ {
"contractPayments": "contractPayments":
@ -607,8 +611,133 @@ paths:
{ {
"paymentId": "151845776", "paymentId": "151845776",
"totalAmount": "26.62", "totalAmount": "26.62",
"paymentMethod": "Twikey", "paymentMethod": "Automatische incasso",
"paymentDate": "2024-09-12", "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",
},
},
},
],
}
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",
"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",
},
},
},
],
}
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": "invoice":
{ {
"invoiceId": "147722263", "invoiceId": "147722263",
@ -626,22 +755,67 @@ paths:
}, },
}, },
{ {
"paymentId": "151845851", "paymentId": "151845776",
"totalAmount": "45.21", "totalAmount": "-26.62",
"paymentMethod": "Twikey", "paymentMethod": "Stornering",
"paymentDate": "2024-09-12", "paymentDate": "2024-09-12",
"iban": "NL25INGB******1337",
"invoice": "invoice":
{ {
"invoiceId": "147722266", "invoiceId": "147722263",
"invoiceNumber": "F2024-0002", "invoiceNumber": "F2024-0001",
"description": "HTM Maandkorting 20%", "description": "HTM Maandkorting 20%",
"publicLink": "https://factuurinzien.nl/d/ddb245d6df67999eca48c4a71b5661b93038e20a/i/dp5h1i5cuu94nopiolkdst3u17vkmzo", "publicLink": "https://factuurinzien.nl/d/b0aac3f42f325f5dd6abc172f723caab5956524d/i/ddb245d6df67999eca48c4a71b5661b93038e20a",
}, },
"_links": "_links":
{ {
"get_contractdetails": "get_contractdetails":
{ {
"href": "https://api.integratielaag.nl/abt/touchpoint/1.0/customers/contracts/7b2f8c1a-3d9d-4c2d-960e-4471e8e28b6a", "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",
"method": "GET", "method": "GET",
}, },
}, },

File diff suppressed because it is too large Load Diff

View File

@ -21,315 +21,221 @@ paths:
get: get:
tags: tags:
- Notification categories - Notification categories
summary: Get notification categories and optins references for that category that a touchpoint can show. summary: Get notification categories and optins references for that category that a touchpoint can show.
description: | description: |
Get notification categories that a touchpoint can show and optins ( eventTypes) and channels (eventType_channels) for the optin for that category Get notification categories that a touchpoint can show and optins ( eventTypes) and channels (eventType_channels) for the optin for that category
parameters: parameters:
- name: expand - name: expand
in: query in: query
schema: schema:
type: string type: string
enum: [none, eventType, eventTypeChannel] enum: [none, eventType, eventTypeChannel]
default: none default: none
responses: responses:
"200": "200":
description: OK description: OK
content: content:
application/json: application/json:
examples: examples:
getNotifactionCategories?expand=eventTypeChannel:
summary: Return all the notification categories with their nested attributes
value:
{
"notificationCategories":[
{
"notificationCategoryId": 1,
"name": "Nieuwsbrief",
"eventTypes": [
{
"eventTypeId": 1,
"eventOrigin": {
"eventOriginId": 6,
"name": "Maileon"
},
"name": "HTM nieuwsbrief",
"subName": "",
"prettyName": "HTM nieuwsbrief",
"optinRequired": False,
"eventTypeChannels":[
{
"eventTypeChannelId": "447a1116-6cd7-4645-8c3d-43237b6186cd",
"channel":{
"channelId": 2,
"name": "email"
},
"isDefault": True,
"isMandatory": False
}
]
}
]
},
{
"notificationCategoryId": 2,
"name": "Mijn Reizen",
"eventTypes": [
{
"eventTypeId": 2,
"eventOrigin": {
"eventOriginId": 1,
"name": "GBO"
},
"name": "ALERTS, TRAVEL_SCHEME",
"subName": "CI",
"prettyName": "Check In",
"optinRequired": False,
"eventTypeChannels":[
{
"eventTypeChannelId": "ccc8c025-06b5-4928-a632-23e1c55cd173",
"channel":{
"channelId": 1,
"name": "push"
},
"isDefault": True,
"isMandatory": False
},
{
"eventTypeChannelId": "da2deb4c-ce77-4b5f-aecc-ddebfd14349d",
"channel":{
"channelId": 2,
"name": "email"
},
"isDefault": False,
"isMandatory": False
}
]
},
{
"eventTypeId": 3,
"eventOrigin": {
"eventOriginId": 1,
"name": "GBO"
},
"name": "ALERTS, PAD",
"subName": null,
"prettyName": "Profielgegevens op de pas",
"optinRequired": False,
"eventTypeChannels":[
{
"eventTypeChannelId": "8e7df8f1-7e50-482f-8301-d399e75fd432",
"channel":{
"channelId": 1,
"name": "push"
},
"isDefault": True,
"isMandatory": False
},
{
"eventTypeChannelId": "72960a92-1855-469f-9cfd-5d72f57106f2",
"channel":{
"channelId": 2,
"name": "email"
},
"isDefault": False,
"isMandatory": False
}
]
}
]
},
{
"notificationCategoryId": 3,
"name": "Mijn Passen",
"eventTypes": [
{
"eventTypeId": 4,
"eventOrigin": {
"eventOriginId": 1,
"name": "GBO"
},
"name": "ALERTS, CARD",
"subName": null,
"prettyName": "Mijn passen",
"optinRequired": False,
"eventTypeChannels":[
{
"eventTypeChannelId": "be07c7bb-714b-4637-acf5-a67025ad8e60",
"channel":{
"channelId": 1,
"name": "push"
},
"isDefault": True,
"isMandatory": False
},
{
"eventTypeChannelId": "0c797b5a-ed34-494b-8c64-0a832830d392",
"channel":{
"channelId": 2,
"name": "email"
},
"isDefault": False,
"isMandatory": False
}
]
},
{
"eventTypeId": 5,
"eventOrigin": {
"eventOriginId": 1,
"name": "GBO"
},
"name": "ALERTS, PAD",
"subName": null,
"prettyName": "Profielgegevens op de pas",
"optinRequired": False,
"eventTypeChannels":[
{
"eventTypeChannelId": "b910368f-c045-4e8e-b01d-bcbc78708bac",
"channel":{
"channelId": 1,
"name": "push"
},
"isDefault": True,
"isMandatory": False
},
{
"eventTypeChannelId": "93e773da-ba3b-48da-9a0e-ee478eaa752f",
"channel":{
"channelId": 2,
"name": "email"
},
"isDefault": False,
"isMandatory": False
}
]
}
]
}
]
}
getNotifactionCategories?expand=eventType:
summary: Return all the notification categories with nested eventTypes
value:
{
"notificationCategories":[
{
"notificationCategoryId": 1,
"name": "Nieuwsbrief",
"eventTypes": [
{
"eventTypeId": 1,
"eventOrigin": {
"eventOriginId": 6,
"name": "Maileon"
},
"name": "HTM nieuwsbrief",
"subName": "",
"prettyName": "HTM nieuwsbrief",
"optinRequired": False
}
]
},
{
"notificationCategoryId": 2,
"name": "Mijn Reizen",
"eventTypes": [
{
"eventTypeId": 2,
"eventOrigin": {
"eventOriginId": 1,
"name": "GBO"
},
"name": "ALERTS, TRAVEL_SCHEME",
"subName": "CI",
"prettyName": "Check In",
"optinRequired": False
},
{
"eventTypeId": 3,
"eventOrigin": {
"eventOriginId": 1,
"name": "GBO"
},
"name": "ALERTS, PAD",
"subName": null,
"prettyName": "Profielgegevens op de pas",
"optinRequired": False
}
]
},
{
"notificationCategoryId": 3,
"name": "Mijn Passen",
"eventTypes": [
{
"eventTypeId": 4,
"eventOrigin": {
"eventOriginId": 1,
"name": "GBO"
},
"name": "ALERTS, CARD",
"subName": null,
"prettyName": "Mijn passen",
"optinRequired": False
},
{
"eventTypeId": 5,
"eventOrigin": {
"eventOriginId": 1,
"name": "GBO"
},
"name": "ALERTS, PAD",
"subName": null,
"prettyName": "Profielgegevens op de pas",
"optinRequired": False
}
]
}
]
}
getNotifactionCategories?expand=none: getNotifactionCategories?expand=none:
summary: Return all the notification categories summary: Return all the notification categories without nested attributes (expand=none)
value: value:
{ notificationCategories:
"notificationCategories":[ - notificationCategoryId: 1
{ name: Mijn Reizen
"notificationCategoryId": 1, - notificationCategoryId: 2
"name": "Nieuwsbrief" name: Nieuwsbrief aanmelding
}, - notificationCategoryId: 3
{ name: Mijn Passen
"notificationCategoryId": 2, getNotifactionCategories?expand=eventType:
"name": "Mijn Reizen" summary: Return all the notification categories with nested event types (expand=eventType)
}, value:
{ notificationCategories:
"notificationCategoryId": 3, - notificationCategoryId: 1
"name": "Mijn Passen" name: Mijn Reizen
} eventTypes:
] - eventTypeId: 2
} eventOrigin:
eventOriginId: 1
name: GBO
name: ALERTS, TRAVEL_SCHEME
subName: CI
prettyName: Check In
optinRequired: false
- eventTypeId: 3
eventOrigin:
eventOriginId: 1
name: GBO
name: ALERTS, PAD
subName: null
prettyName: Profielgegevens op de pas
optinRequired: false
- notificationCategoryId: 2
name: Nieuwsbrief aanmelding
eventTypes:
- eventTypeId: 1
eventOrigin:
eventOriginId: 6
name: Maileon
name: newsletter
subName: null
prettyName: HTM nieuwsbrief
optinRequired: false
- notificationCategoryId: 3
name: Mijn Passen
eventTypes:
- eventTypeId: 4
eventOrigin:
eventOriginId: 1
name: GBO
name: ALERTS, CARD
subName: null
prettyName: Mijn passen
optinRequired: false
- eventTypeId: 5
eventOrigin:
eventOriginId: 1
name: GBO
name: ALERTS, PAD
subName: null
prettyName: Profielgegevens op de pas
optinRequired: false
getNotifactionCategories?expand=eventTypeChannel:
summary: Return all the notification categories with all nested attributes (expand=eventTypeChannel)
value:
notificationCategories:
- notificationCategoryId: 1
name: Mijn Reizen
eventTypes:
- eventTypeId: 2
eventOrigin:
eventOriginId: 1
name: GBO
name: ALERTS, TRAVEL_SCHEME
subName: CI
prettyName: Check In
optinRequired: false
eventTypeChannels:
- eventTypeChannelId: ccc8c025-06b5-4928-a632-23e1c55cd173
channel:
channelId: 1
name: push
isDefault: true
isMandatory: false
- eventTypeChannelId: da2deb4c-ce77-4b5f-aecc-ddebfd14349d
channel:
channelId: 2
name: email
isDefault: false
isMandatory: false
- eventTypeId: 3
eventOrigin:
eventOriginId: 1
name: GBO
name: ALERTS, PAD
subName: null
prettyName: Profielgegevens op de pas
optinRequired: false
eventTypeChannels:
- eventTypeChannelId: 8e7df8f1-7e50-482f-8301-d399e75fd432
channel:
channelId: 1
name: push
isDefault: true
isMandatory: false
- eventTypeChannelId: 72960a92-1855-469f-9cfd-5d72f57106f2
channel:
channelId: 2
name: email
isDefault: false
isMandatory: false
- notificationCategoryId: 2
name: Nieuwsbrief aanmelding
eventTypes:
- eventTypeId: 1
eventOrigin:
eventOriginId: 6
name: Maileon
name: newsletter
subName: null
prettyName: HTM nieuwsbrief
optinRequired: false
eventTypeChannels:
- eventTypeChannelId: 447a1116-6cd7-4645-8c3d-43237b6186cd
channel:
channelId: 2
name: email
isDefault: true
isMandatory: false
- notificationCategoryId: 3
name: Mijn Passen
eventTypes:
- eventTypeId: 4
eventOrigin:
eventOriginId: 1
name: GBO
name: ALERTS, CARD
subName: null
prettyName: Mijn passen
optinRequired: false
eventTypeChannels:
- eventTypeChannelId: be07c7bb-714b-4637-acf5-a67025ad8e60
channel:
channelId: 1
name: push
isDefault: true
isMandatory: false
- eventTypeChannelId: 0c797b5a-ed34-494b-8c64-0a832830d392
channel:
channelId: 2
name: email
isDefault: false
isMandatory: false
- eventTypeId: 5
eventOrigin:
eventOriginId: 1
name: GBO
name: ALERTS, PAD
subName: null
prettyName: Profielgegevens op de pas
optinRequired: false
eventTypeChannels:
- eventTypeChannelId: b910368f-c045-4e8e-b01d-bcbc78708bac
channel:
channelId: 1
name: push
isDefault: true
isMandatory: false
- eventTypeChannelId: 93e773da-ba3b-48da-9a0e-ee478eaa752f
channel:
channelId: 2
name: email
isDefault: false
isMandatory: false
"404": "404":
description: No notification category found description: No notification category found
content: content:
application/json: application/json:
example: example:
{ {
"type": "https://api.integratielaag.nl/abt/touchpoint/2.0/notifications", "type": "https://api.integratielaag.nl/abt/touchpoint/2.0/notifications",
"title": "Niet gevonden", "title": "Niet gevonden",
"detail": "Notificatiecategorie niet gevonden", "detail": "Notificatiecategorie niet gevonden",
"instance": "555d00b5-bc3f-4591-949b-479e76d49ea7", "instance": "555d00b5-bc3f-4591-949b-479e76d49ea7",
"errors": [ "errors":
{ [
"code": "404", {
"detail": null, "code": "404",
"path": null, "detail": null,
"parameter": null "path": null,
} "parameter": null,
], },
],
} }
/notificationsubscriptions: /notificationsubscriptions:
get: get:
tags: tags:
- Notification subscriptions - Notification subscriptions
summary: Get all possible notificationSubscriptions for a customer (account or private). summary: Get all possible notificationSubscriptions for a customer (account or private).
description: | description: |
Get all possible notificationSubscriptions for a customer (account or private), including if they have opted-in for it. Get all possible notificationSubscriptions for a customer (account or private), including if they have opted-in for it.
@ -358,7 +264,8 @@ paths:
- name: emailAddress - name: emailAddress
in: query in: query
schema: schema:
type: integer type: string
format: email
example: john.doe@mymailprovider.com example: john.doe@mymailprovider.com
required: false required: false
description: The emailadress of the customer in the case of anonymous opt-ins description: The emailadress of the customer in the case of anonymous opt-ins
@ -372,55 +279,62 @@ paths:
summary: Return all the notification subscriptions where for each category the client has actively opted in or out - All summary: Return all the notification subscriptions where for each category the client has actively opted in or out - All
value: value:
{ {
"notificationSubscriptions":[ "notificationSubscriptions":
{ [
"notificationSubscriptionId": "64047471-e0c3-4abc-b4eb-83a12a6de903", {
"notificationCategory": { "notificationSubscriptionId": "64047471-e0c3-4abc-b4eb-83a12a6de903",
"notificationCategoryId": 1, "notificationCategory":
"name": "HTM nieuwbrief" {
"notificationCategoryId": 1,
"name": "Mijn reizen",
},
"isActive": true,
}, },
"isActive": true {
}, "notificationSubscriptionId": "571388cd-8903-40d5-89e6-9191cb8d656e",
{ "notificationCategory":
"notificationSubscriptionId": "571388cd-8903-40d5-89e6-9191cb8d656e", {
"notificationCategory": { "notificationCategoryId": 2,
"notificationCategoryId": 2, "name": "Nieuwsbrief aanmelding",
"name": "Mijn reizen" },
"isActive": true,
}, },
"isActive": true {
}, "notificationSubscriptionId": "cf736ff2-2f8f-434e-a3c7-a14064b73c9b",
{ "notificationCategory":
"notificationSubscriptionId": "cf736ff2-2f8f-434e-a3c7-a14064b73c9b", {
"notificationCategory": { "notificationCategoryId": 3,
"notificationCategoryId": 3, "name": "Mijn contracten",
"name": "Mijn contracten" },
"isActive": false,
}, },
"isActive": false ],
}
]
} }
getNotifactionSubscriptionsSome: getNotifactionSubscriptionsSome:
summary: Return all the notification subscriptions where for each category the client has actively opted in or out - Some summary: Return all the notification subscriptions where for each category the client has actively opted in or out - Some
value: value:
{ {
"notificationSubscriptions": [ "notificationSubscriptions":
{ [
"notificationSubscriptionId": "64047471-e0c3-4abc-b4eb-83a12a6de903", {
"notificationCategory": { "notificationSubscriptionId": "64047471-e0c3-4abc-b4eb-83a12a6de903",
"notificationCategoryId": 1, "notificationCategory":
"name": "HTM nieuwbrief" {
"notificationCategoryId": 1,
"name": "Mijn reizen",
},
"isActive": true,
}, },
"isActive": true {
}, "notificationSubscriptionId": "cf736ff2-2f8f-434e-a3c7-a14064b73c9b",
{ "notificationCategory":
"notificationSubscriptionId": "cf736ff2-2f8f-434e-a3c7-a14064b73c9b", {
"notificationCategory": { "notificationCategoryId": 2,
"notificationCategoryId": 3, "name": "Nieuwsbrief aanmelding",
"name": "Mijn contracten" },
"isActive": false,
}, },
"isActive": false ],
}
]
} }
"403": "403":
description: Forbidden // Als geverifieerd profiel gevonden wordt, maar niet op een geverifieerde manier benaderd wordt description: Forbidden // Als geverifieerd profiel gevonden wordt, maar niet op een geverifieerde manier benaderd wordt
@ -432,14 +346,15 @@ paths:
"title": "Verboden", "title": "Verboden",
"detail": "Niet toegestaan", "detail": "Niet toegestaan",
"instance": "555d00b5-bc3f-4591-949b-479e76d49ea7", "instance": "555d00b5-bc3f-4591-949b-479e76d49ea7",
"errors": [ "errors":
{ [
"code": "403", {
"detail": null, "code": "403",
"path": null, "detail": null,
"parameter": null "path": null,
} "parameter": null,
], },
],
} }
"404": "404":
description: No notification subscriptions found description: No notification subscriptions found
@ -451,18 +366,19 @@ paths:
"title": "Niet gevonden", "title": "Niet gevonden",
"detail": "Notificatie niet gevonden", "detail": "Notificatie niet gevonden",
"instance": "555d00b5-bc3f-4591-949b-479e76d49ea7", "instance": "555d00b5-bc3f-4591-949b-479e76d49ea7",
"errors": [ "errors":
{ [
"code": "404", {
"detail": null, "code": "404",
"path": null, "detail": null,
"parameter": null "path": null,
} "parameter": null,
], },
],
} }
post: post:
tags: tags:
- Notification subscriptions - Notification subscriptions
summary: Create a new notificationSubscription for a customer (account or private). summary: Create a new notificationSubscription for a customer (account or private).
description: | description: |
Create a notificationSubscriptions for a customer (account or private), including if they have opted-in for it. Create a notificationSubscriptions for a customer (account or private), including if they have opted-in for it.
@ -494,81 +410,40 @@ paths:
schema: schema:
$ref: "#/components/schemas/unavailable" $ref: "#/components/schemas/unavailable"
examples: examples:
Create notificationSubscription anonymous active: Create notificationSubscription anonymous:
value: value:
{ {
"emailAddress": "anonymous@mymailprovider.com", "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: Create notificationSubscription account active:
value: value: { "notificationCategoryId": 2 }
{
"notificationCategoryId": 2,
"isActive": True
}
Create notificationSubscription account inactive:
value:
{
"notificationCategoryId": 2,
"isActive": False
}
responses: responses:
"201": "201":
description: Created description: Created
content: content:
application/json: application/json:
examples: examples:
Create notificationSubscription anonymous active: Create notificationSubscription anonymous:
summary: Return the created notification for an anonymous user active summary: Return the created notification for an anonymous user inactive by default.
value: value:
{ {
"notificationSubscriptionId": "e112f26e-37fa-4bde-8def-9977cd1d50ae", "notificationSubscriptionId": "e112f26e-37fa-4bde-8def-9977cd1d50ae",
"notificationCategory": { "notificationCategory":
"notificationCategoryId": 1, {
"name": "HTM nieuwbrief" "notificationCategoryId": 2,
}, "name": "Nieuwsbrief aanmelding",
"isActive": True },
} "isActive": False,
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: Create notificationSubscription account active:
summary: Return the created notification for an anonymous user active summary: Return the created notification for an anonymous user active
value: value:
{ {
"notificationSubscriptionId": "6b88eba1-af1f-42fc-82d3-d7202d5f1afe", "notificationSubscriptionId": "6b88eba1-af1f-42fc-82d3-d7202d5f1afe",
"notificationCategory": { "notificationCategory":
"notificationCategoryId": 2, { "notificationCategoryId": 2, "name": "Nieuwsbrief aanmelding" },
"name": "Mijn reizen" "isActive": True,
},
"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": "405":
description: Method not allowed, ook als een notificatie aangemaakt wordt voor een account maar op een anonieme manier benadert wordt. description: Method not allowed, ook als een notificatie aangemaakt wordt voor een account maar op een anonieme manier benadert wordt.
@ -580,22 +455,23 @@ paths:
"title": "Methode niet toegestaan", "title": "Methode niet toegestaan",
"detail": "", "detail": "",
"instance": "555d00b5-bc3f-4591-949b-479e76d49ea7", "instance": "555d00b5-bc3f-4591-949b-479e76d49ea7",
"errors": [ "errors":
{ [
"code": "405", {
"detail": null, "code": "405",
"path": null, "detail": null,
"parameter": null "path": null,
} "parameter": null,
], },
],
} }
/notificationsubscriptions/{notificationSubscriptionId}: /notificationsubscriptions/{notificationSubscriptionId}:
patch: patch:
tags: tags:
- Notification subscriptions - Notification subscriptions
summary: Update a notificationSubscription for a customer (account or private). summary: Update a notificationSubscription for a customer.
description: | description: |
Update a notificationSubscription for a customer (account or private). Update a notificationSubscription for a customer.
parameters: parameters:
- name: X-HTM-JWT-AUTH-HEADER - name: X-HTM-JWT-AUTH-HEADER
in: header in: header
@ -626,14 +502,6 @@ paths:
example: e112f26e-37fa-4bde-8def-9977cd1d50ae example: e112f26e-37fa-4bde-8def-9977cd1d50ae
required: true required: true
description: The id of the notificationSubscription you want to update 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: requestBody:
content: content:
application/json: application/json:
@ -641,15 +509,9 @@ paths:
$ref: "#/components/schemas/unavailable" $ref: "#/components/schemas/unavailable"
examples: examples:
Update a notificationSubscription to inactive: Update a notificationSubscription to inactive:
value: value: { "isActive": False }
{
"isActive": False
}
Update a notificationSubscription to active: Update a notificationSubscription to active:
value: value: { "isActive": True }
{
"isActive": True
}
responses: responses:
"200": "200":
description: OK description: OK
@ -661,23 +523,45 @@ paths:
value: value:
{ {
"notificationSubscriptionId": "e112f26e-37fa-4bde-8def-9977cd1d50ae", "notificationSubscriptionId": "e112f26e-37fa-4bde-8def-9977cd1d50ae",
"notificationCategory": { "notificationCategory":
"notificationCategoryId": 1, {
"name": "HTM nieuwbrief" "notificationCategoryId": 2,
}, "name": "Nieuwsbrief aanmelding",
"isActive": False },
"isActive": False,
} }
Update a notificationSubscription to active: Update a notificationSubscription to active:
summary: Return the updated active notification summary: Return the updated active notification
value: value:
{ {
"notificationSubscriptionId": "e112f26e-37fa-4bde-8def-9977cd1d50ae", "notificationSubscriptionId": "e112f26e-37fa-4bde-8def-9977cd1d50ae",
"notificationCategory": { "notificationCategory":
"notificationCategoryId": 1, {
"name": "HTM nieuwbrief" "notificationCategoryId": 2,
}, "name": "Nieuwsbrief aanmelding",
"isActive": True },
"isActive": True,
} }
"405":
description: Method not allowed,wanneer het account emailadres niet geverifieerd is.
content:
application/json:
example:
{
"type": "https://api.integratielaag.nl/abt/touchpoint/2.0/notifications",
"title": "Methode niet toegestaan",
"detail": "",
"instance": "555d00b5-bc3f-4591-949b-479e76d49ea7",
"errors":
[
{
"code": "405",
"detail": null,
"path": null,
"parameter": null,
},
],
}
components: components:
schemas: schemas:
unavailable: unavailable:

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -0,0 +1,110 @@
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

@ -1,180 +0,0 @@
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,6 +5,12 @@ info:
description: CRUD APIs for ABT Orders database. These are NOT the functional APIs from Service Engine. description: CRUD APIs for ABT Orders database. These are NOT the functional APIs from Service Engine.
servers: servers:
- url: https://services.acc.api.htm.nl/abt/abtorder/1.0 - 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: paths:
/orders: /orders:
get: get:
@ -50,6 +56,14 @@ paths:
example: 1 example: 1
required: false required: false
description: The id of the touch point where the order was initiated. 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 - in: query
name: languageId name: languageId
schema: schema:
@ -98,6 +112,14 @@ paths:
explode: false explode: false
required: false required: false
description: Filter on most recent order status. 1 = concept, 2 = awaitingPayment, 3 = pendingPayment, 4 = paid, 5 = delivered, 6 = cancelled. 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: responses:
"200": "200":
description: OK description: OK
@ -119,6 +141,7 @@ paths:
"touchPointId": 1, "touchPointId": 1,
"name": "Perplex" "name": "Perplex"
}, },
"deviceId": "42e77532-d831-41da-b07a-7edb9bb7f004",
"language": "language":
{ {
"languageId": 1, "languageId": 1,
@ -148,6 +171,30 @@ paths:
"description": "Betaling in behandeling", "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": "orderLines":
[ [
{ {
@ -327,19 +374,22 @@ paths:
"customerProfileId": 1337, "customerProfileId": 1337,
"totalAmount": 121, "totalAmount": 121,
"touchPointId": 1, "touchPointId": 1,
"deviceId": "b8ca9fdf-0bb9-4e49-b48d-41e395563377",
"languageId": 1, "languageId": 1,
"createdOn": "2024-03-22T09:00:00", "createdOn": "2024-03-22T09:00:00",
"order_OrderStatus": "order_OrderStatus":
[ [
{ {
"orderStatusId": 4, "orderStatusId": 1,
"createdOn": "2024-03-22T09:00:00",
"description": "Order succesvol betaald",
},
{
"orderStatusId": 3,
"createdOn": "2024-03-22T08:55:00", "createdOn": "2024-03-22T08:55:00",
"description": "Betaling in behandeling", "description": "Concept order",
},
],
"orderVouchers":
[
{
"issuedVoucherId": "e81b2197-a6c2-45b6-9560-8ce8442e8604",
"orderLineId": "97824d2e-5189-456d-b6da-4cca511a7685"
}, },
], ],
"orderLines": "orderLines":
@ -390,57 +440,6 @@ 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: responses:
"201": "201":
@ -487,6 +486,7 @@ paths:
"touchPointId": 1, "touchPointId": 1,
"name": "Perplex" "name": "Perplex"
}, },
"deviceId": null,
"language": "language":
{ {
"languageId": 1, "languageId": 1,
@ -512,6 +512,7 @@ paths:
"description": "Betaling in behandeling", "description": "Betaling in behandeling",
}, },
], ],
"orderVouchers": null,
"orderLines": "orderLines":
[ [
{ {
@ -658,6 +659,7 @@ paths:
example: example:
{ {
"customerProfileId": 1337, "customerProfileId": 1337,
"deviceId": "fe68e624-b75f-48ca-a179-d5f86a8ab7d5",
"totalAmount": 121, "totalAmount": 121,
"languageId": 1, "languageId": 1,
"lastUpdatedOn": "2024-03-22T09:00:00", "lastUpdatedOn": "2024-03-22T09:00:00",
@ -678,6 +680,7 @@ paths:
responses: responses:
"200": "200":
description: OK description: OK
/orders/{orderId}/statuses: /orders/{orderId}/statuses:
parameters: parameters:
- in: path - in: path
@ -715,6 +718,143 @@ paths:
{ {
"order_orderStatusId": "b9cf0096-4211-4be6-ac21-7bc34bc8e066", "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: /orders/{orderId}/orderlines:
parameters: parameters:
- in: path - in: path
@ -1844,6 +1984,7 @@ paths:
"touchPointId": 1, "touchPointId": 1,
"name": "Perplex" "name": "Perplex"
}, },
"deviceId": null,
"isRefund": false, "isRefund": false,
"htmPaymentReference": "HTM-1234", "htmPaymentReference": "HTM-1234",
"pspPaymentReference": "Buckaroo-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

@ -0,0 +1,477 @@
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