Compare commits

..

80 Commits

Author SHA1 Message Date
b6a490c979 added AGO status to order response 2026-09-09 10:55:14 +02:00
Max Martens
1ac734de96 Add productId in issuedvoucher response (nb: its not added in purchasedproducts response as this was not implemented and not needed) 2026-09-09 09:33:11 +02:00
Max Martens
c692cd45ea Make voucherStatus response model consistent across array + object; add time offset everywhere 2026-08-31 12:57:13 +02:00
Max Martens
0f2eaed9dd Fixed touchPointId casing; added SE query param "includeStatusHistory", remove text explaining mandatory query params for SE issuedvoucher, add explanation for automatic touchpointId filtering 2026-08-27 16:15:29 +02:00
11ea8f5fea typo 2026-08-24 11:54:47 +02:00
e52fde1bb3 added delete endpoint to token vault API's 2026-08-21 13:08:50 +02:00
1761c662e7 Merge pull request 'features/OVPAY2475-Token-API' (#67) from features/OVPAY2475-Token-API into develop
Reviewed-on: #67
2026-08-21 11:02:29 +00:00
Max Martens
6a0e5a5fba Added metadata in VoucherSupplier GET/POST, re-added POST purchasedproduct CRUD 2026-08-14 08:55:40 +02:00
Max Martens
7953e035bb Add touchpointId/metadata1tm5/statusChangedAfter in CRUD and TP spec
POST purchasedProduct is missing in CRUD? Try to find it later (and add new changes to request body there as well)
2026-08-13 17:13:52 +02:00
Max Martens
3c4ca6e00c Fix ordervouchers delete URI 2026-08-10 16:56:00 +02:00
Max Martens
f87baaca06 Add voucherstatus reason in POST and GET 2026-08-10 15:04:31 +02:00
257f8f939b added query param for orderVoucher 2026-08-10 14:32:01 +02:00
c1bb52277e Merge pull request 'features/OVPAY2790-journalentryFiko' (#65) from features/OVPAY2790-journalentryFiko into develop
Reviewed-on: #65
2026-08-10 12:10:50 +00:00
e79ac51966 Merge pull request 'features/OVPAY2475-AGO' (#66) from features/OVPAY2475-AGO into develop
Reviewed-on: #66
2026-08-10 12:09:05 +00:00
c6b395a05b fix xtat -> xTat 2026-08-10 14:06:51 +02:00
1d4fbf5004 Links in se endpoints for TAT 2026-08-10 14:06:51 +02:00
Max Martens
8eed2a9dd6 Fix null paymentMomentId 2026-08-10 14:06:51 +02:00
Max Martens
5e255b7bac Added contractVersions in SE contracts list response, fixed some example formatting 2026-08-10 14:06:51 +02:00
Max Martens
e2f70a78af Updated SE-vouchers-supplier spec after review 2026-08-10 14:06:51 +02:00
Max Martens
ee0147b963 Fix JSON schema errors 2026-08-10 14:06:50 +02:00
0a8a706675 removed not available endpoint 2026-07-31 14:25:32 +02:00
d42236dafc meervoud 2026-07-31 10:07:00 +02:00
1aae9729b2 meervoud 2026-07-31 10:05:36 +02:00
f7975ed5a1 updates to processingFailure and transactionAuditTrail POST calls and GETS, consistency 2026-07-31 09:48:25 +02:00
f0c216be9b added FK to other tables 2026-07-30 15:58:35 +02:00
3db0d113a0 fixed example 2026-07-30 15:44:01 +02:00
f4e7bd9385 added new API's 2026-07-30 12:01:30 +02:00
c2a6b1c9c2 fixed description response 2026-07-29 09:14:09 +02:00
Max Martens
17f52cd914 Fix null paymentMomentId 2026-07-28 20:58:22 +02:00
a493694021 rebased changes 2026-07-28 15:38:12 +02:00
96fd6341b0 removed required for xTat in response 2026-07-28 14:35:10 +02:00
f1022c0d8d added href links 2026-07-28 14:29:14 +02:00
5b3264d9cd added authorize endpoint and get securitystatus endpoint 2026-07-28 14:15:18 +02:00
Max Martens
3d387e451b Added contractVersions in SE contracts list response, fixed some example formatting 2026-07-15 14:37:20 +02:00
Max Martens
851f60d665 Updated SE-vouchers-supplier spec after review 2026-07-14 17:31:52 +02:00
Max Martens
0987cedb4f Fix JSON schema errors 2026-07-14 15:50:57 +02:00
23dd83fac9 added schema for the post Order endpoint 2026-07-10 13:28:02 +02:00
a637cfb4f2 Removed voucher query param on the CRUD's en updated SE-order POST/ PATCH orderline in regards to vouchers 2026-07-09 17:50:14 +02:00
42ff41baea fixed indentation and added customerProfileId in POST body 2026-06-25 10:54:24 +02:00
699c633a4e changed name to securityTokens 2026-06-24 14:07:21 +02:00
95bc19d7ff semantics 2026-06-24 12:08:29 +02:00
8e77863480 simplified endpoint 2026-06-24 11:40:19 +02:00
07e1dc91e3 added security to the container specs 2026-06-24 11:30:18 +02:00
75531b4fdc fixed indentation 2026-06-24 11:12:19 +02:00
00a98a6178 things 2026-06-24 11:07:34 +02:00
937a0323ed added endpoints for the token vault 2026-06-23 14:47:22 +02:00
ebee1178ef added container endpoint for creating token in vault 2026-06-23 14:32:51 +02:00
6e37b7c4fe removed unused tags 2026-06-23 14:08:07 +02:00
a0ebde6ae2 Added swagger for container notifications 2026-06-23 13:47:00 +02:00
1f8e8f1381 Merge pull request 'feature/OVPAY-2485' (#63) from feature/OVPAY-2485 into develop
Reviewed-on: #63
2026-06-22 14:45:48 +00:00
1f0481119c OVPAY-2485 - Typo in url. 2026-06-18 16:04:41 +02:00
3d2885f2b8 OVPAY-2485 - Added support for anonymous TAT security. 2026-06-18 14:43:08 +02:00
280d3b1fcd OVPAY-2485 - Extended token details response bodies with security. 2026-06-18 11:58:34 +02:00
f28a797b6b OVPAY-2485 - Improved TP requests. 2026-06-18 11:17:04 +02:00
87d8f7df58 OVPAY-2485 - Updated OTP response body for TP. 2026-06-18 10:25:06 +02:00
30e8860a39 OVPAY-2485 - Added schemas. 2026-06-17 16:26:39 +02:00
d18afcae8c OVPAY-2485 - Minor additions. 2026-06-17 16:23:11 +02:00
eb03aa04e6 OVPAY-2485 - Added SE endpoint for AGO OTP verification. 2026-06-17 16:22:38 +02:00
aca160f65c OVPAY-2485 - Added SE endpoint for AGO activation. 2026-06-17 16:08:06 +02:00
3f090c76b6 Merge pull request 'added additional Query params for issuedVoucher CRUD' (#62) from OVPAY2738-adjustment-CRUD-PP into develop
Reviewed-on: #62
2026-06-16 10:38:31 +00:00
25788e0976 added additional Query params for issuedVoucher CRUD 2026-06-16 12:01:37 +02:00
Max Martens
90bb7c11b5 Change mandatoryItem 6 (phone) to voucher 2026-06-11 12:19:15 +02:00
Max Martens
0c23571085 Remove whitespace 2026-06-11 12:12:11 +02:00
Max Martens
f1a0850871 Now also for TP/SE endpoints 2026-06-11 12:11:05 +02:00
Max Martens
cc8881b9d5 Add onlyVouchers queryParam and needsVoucher product attribute 2026-06-11 12:06:47 +02:00
Max Martens
d484acbc69 Add required attributes that should always be present in every (valid) voucher definition 2026-06-11 11:14:10 +02:00
e5a0b52d1b Merge pull request 'Updated OpenAPI spec for voucher supplier, with examples more relevant to the first usecase (ooievaarspas AOW) - this YAML is also shared with PASS devs' (#60) from feature/OVPAY-2545-vouchers into develop
Reviewed-on: #60
2026-06-08 08:45:46 +00:00
89833680db Merge branch 'develop' into feature/OVPAY-2545-vouchers 2026-06-08 08:44:17 +00:00
Max Martens
ff225ea00c Merge branch 'feature/OVPAY-2545-vouchers' of https://git.integratielaag.nl/HTM/ovpay into feature/OVPAY-2545-vouchers 2026-06-03 19:06:25 +02:00
Max Martens
bd870d0fa8 Added JSON schema and updated examples and error specs for supplier - tweaked OAS for TP 2026-06-03 19:06:17 +02:00
531eaa2ff6 Update src/openapi/products/SE-vouchers-TP.yaml
Fix some typos

Signed-off-by: Max Martens <m.martens@htm.nl>
2026-06-02 15:21:03 +00:00
Max Martens
7173bd3727 Finished JSON schema and all 3 relevant examples for TP vouchers OpenAPI spec (will also update vouchers supplier OpenAPI spec soon) 2026-06-01 11:17:22 +02:00
Max Martens
a09ad1ad08 Process PR feedback, first version of issuedVoucher JSON schema 2026-06-01 10:40:28 +02:00
Max Martens
f198c1633b Default false for isEmailVerified 2026-05-19 17:29:40 +02:00
5f7893b057 Merge pull request 'Reverted some links to the old/"standard" href structure, becasuse the links are buggy and not worth the effort to fix. Exception is GET /devices, that one is working' (#61) from feature/DHIL-5732-links into develop
Reviewed-on: #61
2026-05-12 14:56:25 +00:00
Max Martens
383d43122b Reverted some links to the old/"standard" href structure, becasuse the links are buggy and not worth the effort to fix. Exception is GET /devices, that one is working 2026-05-12 16:29:51 +02:00
Max Martens
f8010560cc Tweak query param 2026-05-07 09:52:29 +02:00
Max Martens
69908f58b0 Finished updating, will be shared with pass devs in this state - I tweaked some stuff, needs to be discussed if this is final desired spec, but good enough to give pass devs a general idea 2026-05-06 22:44:55 +02:00
Max Martens
9fb20a491f WIP update spec with examples for Ooievaarspas-AOW (still need to finish POST/PATCH) 2026-05-06 08:28:21 +02:00
Max Martens
2fca37628e Only return deviceId in PATCH response to prevent extra internal GET call in PATCH logic 2026-05-05 15:12:20 +02:00
46 changed files with 6214 additions and 1995 deletions

View File

View File

@ -215,8 +215,13 @@ public class ABTProductsPUTGenerator {
}
case "paymentMoment" -> {
LOGGER.info("Rewriting paymentMoment to paymentMomentId...");
Long paymentMomentId = jsonField.getValue().get("paymentMomentId").asLong();
((ObjectNode)newJsonNode).put("paymentMomentId", paymentMomentId);
if (jsonField.getValue().get("paymentMomentId").isNull()) {
LOGGER.info("Rewriting null paymentMomentId...");
((ObjectNode)newJsonNode).putRawValue("paymentMomentId", null);
} else {
Long paymentMomentId = jsonField.getValue().get("paymentMomentId").asLong();
((ObjectNode)newJsonNode).put("paymentMomentId", paymentMomentId);
}
}
case "requiredCustomerLevel" -> {
LOGGER.info("Rewriting requiredCustomerLevel to requiredCustomerLevelId...");

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,417 @@
openapi: 3.0.3
info:
title: Integration layer APIs for token storage for ABT
description: >-
APIs that give business processes access to the security and authorization state of a token within the scope of HTM. In the backend this is connected to a container which handles the accessToken en refreshToken management for an OVpas as given by GBO.
version: "1.0"
servers:
- url: https://services.acc.api.htm.nl/abt/containertokens/1.0
security:
- oAuthSample: [profile]
tags:
- name: securitytokens
paths:
/securitytokens:
post:
tags:
- securitytokens
summary: Create a new security token entry in the vault
description: Create a new security token entry in the vault, if the customerProfileId has been supplied create in the customerToken DB otherwise in the AnonymousToken DB
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/CreateSecurityTokenBody"
examples:
token details:
value:
{
"xTat": "cf694905-7f76-4799-b0bc-490716921323",
"otpKey": "123456789123456789123456789abcde",
"customerProfileId": 12314
}
responses:
"201":
description: Created
"400":
description: Bad Request
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
examples:
Missing Parameters:
value:
status: 400
title: Missing Mandatory Parameter
detail: Required parameter {0} is missing.
Invalid Parameters:
value:
status: 400
title: Invalid Parameter
detail: Required parameter {0} is invalid.
"401":
description: Unauthorized
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
examples:
Unauthorized:
value:
status: 401
title: Unauthorized
detail: Invalid Access Token
"404":
description: Not found
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
examples:
TAT not found:
value:
status: 404
title: Not Found
detail: TAT Account Not Found
"409":
description: Conflict
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
examples:
TAT already exists:
value:
status: 409
title: Conflict
detail: Security Token already exists
"500":
description: Internal server error
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
examples:
Internal Server Error:
value:
status: 500
title: Internal Server Error
detail: An unknown error has occurred
/securitytokens/{xTat}/status:
parameters:
- name: xTat
in: path
schema:
type: string
example: 85dbeacc-b9aa-4d5b-bdec-9319010c597f
required: true
description: The xTat of the OVpastoken for which the status needs to be checked
- name: customerProfileId
in: query
schema:
type: integer
example: 323
required: false
description: The id of the customer Profile for which the request is being done, if customer has identified themselves
get:
tags:
- securitytokens
summary: Request the security and authorization status of an OV pas token
description: Request the security and authorization status of an OV pas token, if the customerProfileId has been supplied look in the customerToken DB otherwise check the AnonymousToken DB.
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/SecurityTokenStatusResponse"
examples:
Not secured and not authorized :
summary: Not secured, not authorized
value:
{
"isAuthorized": false,
"isSecured": false
}
Secured and not authorized :
summary: Secured, not authorized
value:
{
"isAuthorized": false,
"isSecured": true
}
Secured and authorized :
summary: Secured and authorized
value:
{
"isAuthorized": true,
"isSecured": true
}
"400":
description: Bad Request
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
examples:
Missing Parameters:
value:
status: 400
title: Missing Mandatory Parameter
detail: Required parameter {0} is missing.
Invalid Parameters:
value:
status: 400
title: Invalid Parameter
detail: Required parameter {0} is invalid.
"401":
description: Unauthorized
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
examples:
Unauthorized:
value:
status: 401
title: Unauthorized
detail: Invalid Access Token
"404":
description: Not found
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
examples:
TAT not found:
value:
status: 404
title: Not Found
detail: TAT Account Not Found
"500":
description: Internal server error
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
examples:
Internal Server Error:
value:
status: 500
title: Internal Server Error
detail: An unknown error has occurred
/securitytokens/{xTat}:
parameters:
- name: xTat
in: path
schema:
type: string
example: uuid
required: true
description: The xTat of the ovPastoken for which the status needs to be checked
- name: customerProfileId
in: query
schema:
type: integer
example: 323
required: false
description: The id of the customer Profile for which the request is being done, if customer has identified themselves
patch:
tags:
- securitytokens
summary: Validate the supplied OTP and secure and/or authorize the token
description: Validate the supplied OTP and secure and/or authorize the token if the customerProfileId has been supplied look in the customerToken DB otherwise check the AnonymousToken DB.
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/SecurityValidateTokenBody"
examples:
token:
value:
{
"otp": "123456"
}
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/SecurityTokenStatusResponse"
examples:
Not secured and not authorized :
summary: Not secured, not authorized
value:
{
"isAuthorized": false,
"isSecured": false
}
Secured and not authorized :
summary: Secured, not authorized
value:
{
"isAuthorized": false,
"isSecured": true
}
Secured and authorized :
summary: Secured and authorized
value:
{
"isAuthorized": true,
"isSecured": true
}
"400":
description: Bad Request
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
examples:
Missing Parameters:
value:
status: 400
title: Missing Mandatory Parameter
detail: Required parameter {0} is missing.
Invalid Parameters:
value:
status: 400
title: Invalid Parameter
detail: Required parameter {0} is invalid.
"422":
description: Unprocessable Entity
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
examples:
Validation failed:
value:
status: 422
title: OTP validation failed
detail: The supplied OTP could not be validated
"401":
description: Unauthorized
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
examples:
Unauthorized:
value:
status: 401
title: Unauthorized
detail: Invalid Access Token
"404":
description: Not found
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
examples:
TAT not found:
value:
status: 404
title: Not Found
detail: TAT Account Not Found
"500":
description: Internal server error
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
examples:
Internal Server Error:
value:
status: 500
title: Internal Server Error
detail: An unknown error has occurred
delete:
tags:
- securitytokens
summary: Delete the authorization for HTM for an ovpaytoken
description: Delete the authorization for HTM for an ovpaytoken if the customerProfileId has been supplied look in the customerToken DB otherwise check the AnonymousToken DB.
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/SecurityTokenStatusResponse"
example:
{}
components:
securitySchemes:
oAuthSample:
type: oauth2
description: This API uses OAuth 2 with the clientCredentials flow. [More info](https://api.example.com/docs/auth)
flows:
clientCredentials: # <---- OAuth flow( clientCredentials)
tokenUrl: https://identity.{env}.api.htm.nl/oauth2/token
scopes:
profile: "Access user profile"
schemas:
ErrorResponse:
description: Default response when an invalid request has been sent
type: object
properties:
status:
type: integer
description: >-
A status IS An integer that represents the HTTP status code of the
response.
example: 400
title:
type: string
description: A title IS A string that provides a brief summary of the error.
detail:
type: string
description: A detail IS A string that provides more details about the error.
CreateSecurityTokenBody:
description: Body for when the a new security token entry is created
type: object
required:
- xTat
- otpKey
properties:
xTat:
type : string
description: >-
Unique identifier for the GBO transit account
example: 03071794-482a-4168-b60f-84669a9e6a76
otpKey:
type : string
description: >-
Key used to validate the OTP at GBO
example: 123456789123456789123456789abcde
customerProfileId:
type : integer
description: >-
CustomerProfileId of the account requesting security or giving authorization
example: 123456
SecurityTokenStatusResponse:
description: Response for when the status of a token is requested
type: object
properties:
isSecured:
type: boolean
description: >-
Is the OVpas secured according to the GBO
example: false
isAuthorized:
type: boolean
description: >-
Does HTM have the authorization to access the secured the data of the OVpas
example: false
SecurityValidateTokenBody:
description: Body for when the a token entry is validated
type: object
required:
- otp
properties:
otp:
type : string
description: >-
OTP to be used to secure the token and retrieve the JWT at GBO
example: 123456

View File

@ -45,12 +45,36 @@ paths:
"contractStatus":
{ "contractStatusId": 2, "name": "active" },
"productId": 1,
"productName": "HTM Maand 20% korting",
"termDuration": "P0Y1M0D",
"productName": "HTM Maand 20% korting doorlopend",
"termDuration": "P1M",
"billingDay": 15,
"highestInvoiceTerm": 1,
"created": "2024-08-01 15:01:00.000",
"created": "2024-08-01T15:01:00.000Z",
"ovPayTokenId": 1337,
"contractVersions": [
{
"contractVersionId": 1,
"termsAndConditions": "https://www.htm.nl/reisproducten/productvoorwaarden/htm-maandkorting/",
"productId": 1,
"productName": "HTM Maand 20% korting doorlopend",
"taxCode": "V9",
"taxPercentage": 9,
"termAmountInclTax": 400,
"start": "2024-08-01T15:01:00.000Z",
"end": "2025-01-01T03:00:00.000Z"
},
{
"contractVersionId": 2,
"termsAndConditions": "https://www.htm.nl/reisproducten/productvoorwaarden/htm-maandkorting/",
"productId": 1,
"productName": "HTM Maand 20% korting doorlopend",
"taxCode": "V9",
"taxPercentage": 9,
"termAmountInclTax": 500,
"start": "2025-01-01T03:00:00.000Z",
"end": null
}
],
"_links":
{
"get_token":
@ -68,14 +92,38 @@ paths:
"orderLineId": "42f68042-908f-41f4-9d9b-4cab843ff0e8",
"touchpointId": 2,
"contractStatus":
{ "contractStatusId": 1, "name": "new" },
{ "contractStatusId": 6, "name": "pending cancellation" },
"productId": 1,
"productName": "HTM Maand 20% korting",
"termDuration": "P0Y1M0D",
"productName": "HTM 20% Korting doorlopend",
"termDuration": "P1M",
"billingDay": 15,
"highestInvoiceTerm": 1,
"created": "2024-08-01 15:01:00.000",
"created": "2024-08-01T15:01:00.000Z",
"ovPayTokenId": 1338,
"contractVersions": [
{
"contractVersionId": 1,
"termsAndConditions": "https://www.htm.nl/reisproducten/productvoorwaarden/htm-maandkorting/",
"productId": 1,
"productName": "HTM 20% Korting doorlopend",
"taxCode": "V9",
"taxPercentage": 9,
"termAmountInclTax": 400,
"start": "2024-08-01T15:01:00.000Z",
"end": "2025-01-01T03:00:00.000Z"
},
{
"contractVersionId": 2,
"termsAndConditions": "https://www.htm.nl/reisproducten/productvoorwaarden/htm-maandkorting/",
"productId": 1,
"productName": "HTM 20% Korting doorlopend",
"taxCode": "V9",
"taxPercentage": 9,
"termAmountInclTax": 500,
"start": "2025-01-01T03:00:00.000Z",
"end": "2025-02-01T03:00:00.000Z"
}
],
"_links":
{
"get_token":
@ -154,8 +202,8 @@ paths:
"contractStatus":
{ "contractStatusId": 2, "name": "active" },
"productId": 1,
"productName": "HTM Maand 20% korting",
"termDuration": "P0Y1M0D",
"productName": "HTM 20% Korting doorlopend",
"termDuration": "P1M",
"billingDay": 15,
"highestInvoiceTerm": 1,
"ovPayTokenId": 1337,
@ -165,22 +213,22 @@ paths:
"contractVersionId": 1,
"termsAndConditions": "https://www.htm.nl/reisproducten/productvoorwaarden/htm-maandkorting/",
"productId": 1,
"productName": "HTM Maand 20% korting",
"productName": "HTM 20% Korting doorlopend",
"taxCode": "V9",
"taxPercentage": 9.0,
"termAmountInclTax": 400,
"start": "2024-07-04 15:01:00.000",
"end": "2024-12-31 15:01:00.000",
"start": "2024-08-01T15:01:00.000Z",
"end": "2025-01-01T03:00:00.000Z",
},
{
"contractVersionId": 2,
"termsAndConditions": "https://www.htm.nl/reisproducten/productvoorwaarden/htm-maandkorting/",
"productId": 1,
"productName": "HTM Maand 20% korting",
"productName": "HTM 20% Korting doorlopend",
"taxCode": "V9",
"taxPercentage": 9.0,
"termAmountInclTax": 400,
"start": "2025-01-01 15:01:00.000",
"start": "2025-01-01T03:00:00.000Z",
},
],
"contractActions":
@ -190,7 +238,7 @@ paths:
"actionType":
{ "actionTypeId": 1, "name": "create" },
"user": "subid123456",
"timestamp": "2024-07-02 15:01:00.000",
"timestamp": "2024-07-02T15:01:00.000Z",
"details": "Contract created",
"correlationId": "976e7a4c-bf24-43d2-b444-55817556e7ee",
},
@ -199,7 +247,7 @@ paths:
"actionType":
{ "actionTypeId": 2, "name": "change" },
"user": "subid123456",
"timestamp": "2024-07-03 15:01:00.000",
"timestamp": "2024-07-03T15:01:00.000Z",
"details": "Contract changed",
"correlationId": "e2462347-6749-4841-b42a-cf8de19ec727",
},
@ -211,8 +259,8 @@ paths:
"externalReference": "F2024-0001",
"term": 1,
"invoiceDate": "2024-07-02",
"created": "2024-07-02 15:01:00.000",
"updated": "2024-07-02 15:01:00.000",
"created": "2024-07-02T15:01:00.000Z",
"updated": "2024-07-02T15:01:00.000Z",
"state": "invoice_created",
"data": "{json}",
"isCredit": false,
@ -270,8 +318,8 @@ paths:
"externalReference": "F2024-0001",
"term": 1,
"invoiceDate": "2024-07-02",
"created": "2024-07-02 15:01:34.000",
"updated": "2024-07-04 00:04:56.000",
"created": "2024-07-02T15:01:34.000Z",
"updated": "2024-07-04T00:04:56.000Z",
"state": "invoice_created",
"public_link": "http://mijnfactuurinzien.nl/F2024-0001",
"isCredit": false,
@ -282,8 +330,8 @@ paths:
"externalReference": "F2024-0002",
"term": 2,
"invoiceDate": "2024-08-02",
"created": "2024-08-02 15:01:34.000",
"updated": "2024-08-04 00:04:56.000",
"created": "2024-08-02T15:01:34.000Z",
"updated": "2024-08-04T00:04:56.000Z",
"state": "invoice_created",
"public_link": "http://mijnfactuurinzien.nl/F2024-0002",
"isCredit": false,
@ -328,8 +376,8 @@ paths:
"cancellationMoment": "termBound",
"termDuration": "P1M",
"billingDay": 18,
"cancellationFrom": "2024-08-10T00:00:00",
"cancellationUntil": "2026-08-10T00:00:00",
"cancellationFrom": "2024-08-10T00:00:00Z",
"cancellationUntil": "2026-08-10T00:00:00Z",
}
/contracts/{contractId}/cancellationvalidation:
parameters:
@ -382,7 +430,7 @@ paths:
{
"validationResult": true,
"validationMessage": "",
"end": "2024-08-10T03:59:59",
"end": "2024-08-10T03:59:59Z",
"refundAmount": 2489,
"refundMethods": ["creditInvoice", "iDeal"],
}
@ -451,7 +499,7 @@ paths:
the refund amount and refund method.
value:
{
"end": "2024-08-10T03:59:59",
"end": "2024-08-10T03:59:59Z",
"refundAmount": 2489,
"refundMethod": "creditInvoice",
}
@ -516,8 +564,8 @@ paths:
"taxCode": "V9",
"taxPercentage": 9.0,
"termAmountInclTax": 400,
"start": "2024-07-04 15:01:00.000",
"end": "2024-12-31 15:01:00.000",
"start": "2024-07-04T15:01:00.000Z",
"end": "2024-12-31T15:01:00.000Z",
},
{
"contractVersionId": 2,
@ -527,7 +575,8 @@ paths:
"taxCode": "V9",
"taxPercentage": 9.0,
"termAmountInclTax": 400,
"start": "2025-01-01 15:01:00.000",
"start": "2025-01-01T15:01:00.000Z",
"end": null
},
],
"contractActions":
@ -537,7 +586,7 @@ paths:
"actionType":
{ "actionTypeId": 1, "name": "create" },
"user": "subid123456",
"timestamp": "2024-07-02 15:01:00.000",
"timestamp": "2024-07-02T15:01:00.000Z",
"details": "Contract created",
"correlationId": "976e7a4c-bf24-43d2-b444-55817556e7ee",
},
@ -546,7 +595,7 @@ paths:
"actionType":
{ "actionTypeId": 2, "name": "change" },
"user": "subid123456",
"timestamp": "2024-07-03 15:01:00.000",
"timestamp": "2024-07-03T15:01:00.000Z",
"details": "Contract changed",
"correlationId": "e2462347-6749-4841-b42a-cf8de19ec727",
},
@ -558,8 +607,8 @@ paths:
"externalReference": "F2024-0001",
"term": 1,
"invoiceDate": "2024-07-02",
"created": "2024-07-02 15:01:00.000",
"updated": "2024-07-02 15:01:00.000",
"created": "2024-07-02T15:01:00.000Z",
"updated": "2024-07-02T15:01:00.000Z",
"state": "invoice_created",
"data": "{json}",
"isCredit": false,
@ -682,7 +731,7 @@ paths:
"termDuration": "P0Y1M0D",
"billingDay": 15,
"highestInvoiceTerm": 1,
"created": "2024-08-01 15:01:00.000",
"created": "2024-08-01T15:01:00.000Z",
"ovPayTokenId": 1337,
"contractVersions":
[
@ -781,7 +830,7 @@ paths:
"termDuration": "P0Y1M0D",
"billingDay": 15,
"highestInvoiceTerm": 1,
"created": "2024-08-01 15:01:00.000",
"created": "2024-08-01T15:01:00.000Z",
"ovPayTokenId": 1337,
"contractVersions":
[

View File

@ -380,7 +380,7 @@ paths:
type: string
example: Customer
required: false
description: The role of the HTM employee in the case of the SMP
description: The role of the HTM employee in the case of the SMP
requestBody:
content:
application/json:
@ -415,7 +415,7 @@ paths:
type: string
example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
required: false
description: The JWT of a customer in case of touchpoint were customer logs in themselves
description: The JWT of a customer in case of touchpoint were customer logs in themselves
- name: X-HTM-CUSTOMER-PROFILE-ID-HEADER
in: header
schema:
@ -429,7 +429,7 @@ paths:
type: string
example: Customer
required: false
description: The role of the HTM employee in the case of the SMP
description: The role of the HTM employee in the case of the SMP
requestBody:
content:
application/json:
@ -464,7 +464,7 @@ paths:
type: string
example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
required: false
description: The JWT of a customer in case of touchpoint were customer logs in themselves
description: The JWT of a customer in case of touchpoint were customer logs in themselves
- name: X-HTM-CUSTOMER-PROFILE-ID-HEADER
in: header
schema:
@ -478,7 +478,7 @@ paths:
type: string
example: Customer
required: false
description: The role of the HTM employee in the case of the SMP
description: The role of the HTM employee in the case of the SMP
- name: customerPreferenceId
in: path
required: true
@ -519,7 +519,7 @@ paths:
type: string
example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
required: false
description: The JWT of a customer in case of touchpoint were customer logs in themselves
description: The JWT of a customer in case of touchpoint were customer logs in themselves
- name: X-HTM-CUSTOMER-PROFILE-ID-HEADER
in: header
schema:
@ -533,8 +533,8 @@ paths:
type: string
example: Customer
required: false
description: The role of the HTM employee in the case of the SMP
description: The role of the HTM employee in the case of the SMP
requestBody:
content:
application/json:
@ -575,7 +575,7 @@ paths:
type: string
example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
required: false
description: The JWT of a customer in case of touchpoint were customer logs in themselves
description: The JWT of a customer in case of touchpoint were customer logs in themselves
- name: X-HTM-CUSTOMER-PROFILE-ID-HEADER
in: header
schema:
@ -589,7 +589,7 @@ paths:
type: string
example: Customer
required: false
description: The role of the HTM employee in the case of the SMP
description: The role of the HTM employee in the case of the SMP
- name: addressId
in: path
required: true
@ -634,7 +634,7 @@ paths:
type: string
example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
required: false
description: The JWT of a customer in case of touchpoint were customer logs in themselves
description: The JWT of a customer in case of touchpoint were customer logs in themselves
- name: X-HTM-CUSTOMER-PROFILE-ID-HEADER
in: header
schema:
@ -648,7 +648,7 @@ paths:
type: string
example: Customer
required: false
description: The role of the HTM employee in the case of the SMP
description: The role of the HTM employee in the case of the SMP
- name: addressId
in: path
required: true
@ -669,14 +669,14 @@ paths:
summary: Add a customer phone
description: >-
parameters:
parameters:
- name: X-HTM-JWT-AUTH-HEADER
in: header
schema:
type: string
example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
required: false
description: The JWT of a customer in case of touchpoint were customer logs in themselves
description: The JWT of a customer in case of touchpoint were customer logs in themselves
- name: X-HTM-CUSTOMER-PROFILE-ID-HEADER
in: header
schema:
@ -690,7 +690,7 @@ paths:
type: string
example: Customer
required: false
description: The role of the HTM employee in the case of the SMP
description: The role of the HTM employee in the case of the SMP
requestBody:
content:
application/json:
@ -728,7 +728,7 @@ paths:
type: string
example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
required: false
description: The JWT of a customer in case of touchpoint were customer logs in themselves
description: The JWT of a customer in case of touchpoint were customer logs in themselves
- name: X-HTM-CUSTOMER-PROFILE-ID-HEADER
in: header
schema:
@ -742,7 +742,7 @@ paths:
type: string
example: Customer
required: false
description: The role of the HTM employee in the case of the SMP
description: The role of the HTM employee in the case of the SMP
- name: phoneId
in: path
required: true
@ -754,7 +754,7 @@ paths:
application/json:
schema:
$ref: "#/components/schemas/phoneEntity"
examples:
examples:
updateFullPhoneEntity:
value:
phone:
@ -789,7 +789,7 @@ paths:
type: string
example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
required: false
description: The JWT of a customer in case of touchpoint were customer logs in themselves
description: The JWT of a customer in case of touchpoint were customer logs in themselves
- name: X-HTM-CUSTOMER-PROFILE-ID-HEADER
in: header
schema:
@ -803,7 +803,7 @@ paths:
type: string
example: Customer
required: false
description: The role of the HTM employee in the case of the SMP
description: The role of the HTM employee in the case of the SMP
- name: phoneId
in: path
required: true
@ -816,7 +816,7 @@ paths:
content:
application/json: {}
x-auth-type: Application & Application User
x-throttling-tier: Unlimited
x-throttling-tier: Unlimited
/customers/billinginformations:
get:
tags:
@ -831,7 +831,7 @@ paths:
type: string
example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
required: false
description: The JWT of a customer in case of touchpoint were customer logs in themselves
description: The JWT of a customer in case of touchpoint were customer logs in themselves
- name: X-HTM-CUSTOMER-PROFILE-ID-HEADER
in: header
schema:
@ -845,7 +845,7 @@ paths:
type: string
example: Customer
required: false
description: The role of the HTM employee in the case of the SMP
description: The role of the HTM employee in the case of the SMP
responses:
200:
description: OK
@ -866,7 +866,7 @@ paths:
"billingInformationId": 1,
"iban": "NL••••••••••••1234",
"ascription": "J. Doe",
"alias": null,
"alias": null,
"created": "2024-10-20T17:05:52.000",
},
],
@ -875,7 +875,7 @@ paths:
get:
tags:
- Customers
summary: Find direct debit mandates for a customer.
summary: Find direct debit mandates for a customer.
description: Find direct debit mandates for a customer.
parameters:
- name: X-HTM-JWT-AUTH-HEADER
@ -884,7 +884,7 @@ paths:
type: string
example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
required: false
description: The JWT of a customer in case of touchpoint were customer logs in themselves
description: The JWT of a customer in case of touchpoint were customer logs in themselves
- name: X-HTM-CUSTOMER-PROFILE-ID-HEADER
in: header
schema:
@ -898,7 +898,7 @@ paths:
type: string
example: Customer
required: false
description: The role of the HTM employee in the case of the SMP
description: The role of the HTM employee in the case of the SMP
responses:
200:
description: OK
@ -914,12 +914,12 @@ paths:
[
{
"directDebitMandateId": 71,
"billingInformationId":
"billingInformationId":
{
"billingInformationId": 15,
"iban": "NL••••••••••••1234",
"ascription": "J. Doe",
"alias": null,
"alias": null,
"created": "2024-10-20T17:05:52.000",
},
"directDebitMandateType":
@ -1081,6 +1081,10 @@ paths:
"photo": null,
},
"gboAgeProfile": null,
"tatSecurity": {
"isSecured": true,
"isAuthorized": true,
},
"_links":
{
"self":
@ -1130,9 +1134,9 @@ paths:
},
},
}
OV pas without PAD:
summary: OV pas without PAD
description: OV pas without PAD
OV pas without PAD, without AGO:
summary: OV pas without PAD, without AGO
description: OV pas without PAD, without AGO
value:
{
"ovPayTokens":
@ -1142,7 +1146,7 @@ paths:
"ovPayTokenId": 1,
"xTat": "32089cc8-d187-47ff-a3a9-5c2558def811",
"tokenType":
{ "tokenTypeId": 2, "name": "OV-pas physical" },
{ "tokenTypeId": 2, "name": "OV-pas physical" },
"ovpasNumber": "OV34567",
"alias": "MyToken",
"tokenStatus":
@ -1164,6 +1168,10 @@ paths:
"photo": null,
},
"gboAgeProfile": null,
"tatSecurity": {
"isSecured": false,
"isAuthorized": false,
},
"_links":
{
"self":
@ -1201,6 +1209,10 @@ paths:
"href": "https://api.integratielaag.nl/abt/touchpoint/1.0/customers/tokens/51compare",
"method": "POST",
},
"secure": {
"href": "/tokens/securetoken",
"method": "POST"
}
},
},
],
@ -1213,9 +1225,9 @@ paths:
},
},
}
OV pas with PAD and autoReload:
summary: OV pas with PAD and autoReload
description: OV pas with PAD and autoReload
OV pas with PAD, with autoReload, with AGO:
summary: OV pas with PAD, with autoReload, with AGO
description: OV pas with PAD, with autoReload, with AGO
value:
{
"ovPayTokens":
@ -1225,7 +1237,7 @@ paths:
"ovPayTokenId": 1,
"xTat": "32089cc8-d187-47ff-a3a9-5c2558def811",
"tokenType":
{ "tokenTypeId": 2, "name": "OV-pas physical" },
{ "tokenTypeId": 2, "name": "OV-pas physical" },
"ovpasNumber": "OV34567",
"alias": "MyToken",
"tokenStatus":
@ -1287,6 +1299,10 @@ paths:
"ageFromInclusive": 4,
"ageToInclusive": 11,
},
"tatSecurity": {
"isSecured": true,
"isAuthorized": true,
},
"_links":
{
"self":
@ -1348,8 +1364,8 @@ paths:
"ovPayTokenId": 1,
"xTat": "32089cc8-d187-47ff-a3a9-5c2558def811",
"tokenType":
{ "tokenTypeId": 2, "name": "OV-pas physical" },
"ovpasNumber": "OV34567",
{ "tokenTypeId": 2, "name": "OV-pas physical" },
"ovpasNumber": "OV34567",
"alias": "MyToken",
"tokenStatus":
{ "tokenStatusId": 2, "name": "Active" },
@ -1385,6 +1401,10 @@ paths:
"ageFromInclusive": 4,
"ageToInclusive": 11,
},
"tatSecurity": {
"isSecured": true,
"isAuthorized": true,
},
"_links":
{
"self":
@ -1446,7 +1466,7 @@ paths:
"ovPayTokenId": 1,
"xTat": "32089cc8-d187-47ff-a3a9-5c2558def811",
"tokenType":
{ "tokenTypeId": 2, "name": "OV-pas physical" },
{ "tokenTypeId": 2, "name": "OV-pas physical" },
"ovpasNumber": "OV34567",
"alias": "MyToken",
"tokenStatus":
@ -1501,6 +1521,10 @@ paths:
"ageFromInclusive": 4,
"ageToInclusive": 11,
},
"tatSecurity": {
"isSecured": true,
"isAuthorized": true,
},
"_links":
{
"self":
@ -1562,7 +1586,7 @@ paths:
"ovPayTokenId": 1,
"xTat": "e7fa3392-646b-40e2-95a6-c417dc0b0969",
"tokenType":
{ "tokenTypeId": 2, "name": "OV-pas physical" },
{ "tokenTypeId": 2, "name": "OV-pas physical" },
"ovpasNumber": "OV34567",
"alias": "MyToken",
"tokenStatus":
@ -1577,13 +1601,29 @@ paths:
"birthdate": null,
"photo": null,
},
"tatSecurity": {
"isSecured": true,
"isAuthorized": false,
},
"_links":{
"self":
{
"href": "https://api.integratielaag.nl/abt/touchpoint/1.0/customers/tokens?ovPaytokenId=1",
"method": "GET",
},
"authorize":
{
"href": "/tokens/authorizetoken",
"method": "POST"
}
}
},
{
"customerProfileId": 1,
"ovPayTokenId": 6,
"xTat": "e7fa3392-646b-40e2-95a6-c417dc0b0969",
"tokenType":
{ "tokenTypeId": 2, "name": "OV-pas physical" },
{ "tokenTypeId": 2, "name": "OV-pas physical" },
"ovpasNumber": "OV54567",
"alias": "MyToken",
"tokenStatus":
@ -1599,13 +1639,24 @@ paths:
"photo": null,
},
"gboAgeProfile": null,
"tatSecurity": {
"isSecured": true,
"isAuthorized": true,
},
"_links":{
"self":
{
"href": "https://api.integratielaag.nl/abt/touchpoint/1.0/customers/tokens?ovPaytokenId=1",
"method": "GET",
},
}
},
{
"customerProfileId": 18,
"ovPayTokenId": 13,
"xTat": "e7fa3392-646b-40e2-95a6-c417dc0b0969",
"tokenType":
{ "tokenTypeId": 2, "name": "OV-pas physical" },
{ "tokenTypeId": 2, "name": "OV-pas physical" },
"ovpasNumber": "OV34547",
"alias": "MyToken",
"tokenStatus":
@ -1621,13 +1672,24 @@ paths:
"photo": null,
},
"gboAgeProfile": null,
"tatSecurity": {
"isSecured": true,
"isAuthorized": true,
},
"_links":{
"self":
{
"href": "https://api.integratielaag.nl/abt/touchpoint/1.0/customers/tokens?ovPaytokenId=1",
"method": "GET",
},
}
},
{
"customerProfileId": 132,
"ovPayTokenId": 21,
"xTat": "e7fa3392-646b-40e2-95a6-c417dc0b0969",
"tokenType":
{ "tokenTypeId": 2, "name": "OV-pas physical" },
{ "tokenTypeId": 2, "name": "OV-pas physical" },
"ovpasNumber": "OV34831",
"alias": "Mijn OV Pas",
"tokenStatus":
@ -1643,13 +1705,24 @@ paths:
"photo": null,
},
"gboAgeProfile": null,
"tatSecurity": {
"isSecured": true,
"isAuthorized": true,
},
"_links":{
"self":
{
"href": "https://api.integratielaag.nl/abt/touchpoint/1.0/customers/tokens?ovPaytokenId=1",
"method": "GET",
},
}
},
{
"customerProfileId": 166,
"ovPayTokenId": 28,
"xTat": "e7fa3392-646b-40e2-95a6-c417dc0b0969",
"tokenType":
{ "tokenTypeId": 2, "name": "OV-pas physical" },
{ "tokenTypeId": 2, "name": "OV-pas physical" },
"ovpasNumber": "OV34984",
"alias": "Mijn OV Pas",
"tokenStatus":
@ -1668,13 +1741,29 @@ paths:
"photo": null,
},
"gboAgeProfile": null,
"tatSecurity": {
"isSecured": false,
"isAuthorized": false,
},
"_links":{
"self":
{
"href": "https://api.integratielaag.nl/abt/touchpoint/1.0/customers/tokens?ovPaytokenId=1",
"method": "GET",
},
"secure":
{
"href": "/tokens/securetoken",
"method": "POST"
}
}
},
{
"customerProfileId": 166,
"ovPayTokenId": 115,
"xTat": "e7fa3392-646b-40e2-95a6-c417dc0b0969",
"tokenType":
{ "tokenTypeId": 2, "name": "OV-pas physical" },
{ "tokenTypeId": 2, "name": "OV-pas physical" },
"ovpasNumber": "OV54368",
"alias": "My retired token",
"tokenStatus":
@ -1690,13 +1779,24 @@ paths:
"photo": null,
},
"gboAgeProfile": null,
"tatSecurity": {
"isSecured": true,
"isAuthorized": true,
},
"_links":{
"self":
{
"href": "https://api.integratielaag.nl/abt/touchpoint/1.0/customers/tokens?ovPaytokenId=1",
"method": "GET",
},
}
},
{
"customerProfileId": 1,
"ovPayTokenId": 118,
"xTat": "e7fa3392-646b-40e2-95a6-c417dc0b0969",
"tokenType":
{ "tokenTypeId": 2, "name": "OV-pas physical" },
{ "tokenTypeId": 2, "name": "OV-pas physical" },
"ovpasNumber": "OV98263",
"alias": "My found token",
"tokenStatus":
@ -1712,6 +1812,17 @@ paths:
"photo": null,
},
"gboAgeProfile": null,
"tatSecurity": {
"isSecured": true,
"isAuthorized": true,
},
"_links":{
"self":
{
"href": "https://api.integratielaag.nl/abt/touchpoint/1.0/customers/tokens?ovPaytokenId=1",
"method": "GET",
},
}
},
],
_links:
@ -1812,6 +1923,10 @@ paths:
"personalAccountData":
{ "name": null, "birthdate": null, "photo": null },
"gboAgeProfile": null,
"tatSecurity": {
"isSecured": true,
"isAuthorized": true,
},
"_links":
{
"self":
@ -1890,6 +2005,10 @@ paths:
"personalAccountData":
{ "name": null, "birthdate": null, "photo": null },
"gboAgeProfile": null,
"tatSecurity": {
"isSecured": true,
"isAuthorized": false,
},
"_links":
{
"self":
@ -1927,6 +2046,12 @@ paths:
"href": "https://api.integratielaag.nl/abt/touchpoint/1.0/customers/tokens/1/compare",
"method": "POST",
},
"authorize":{
{
"href": "/tokens/authorizetoken",
"method": "POST"
}
}
},
},
"_links":
@ -2086,6 +2211,10 @@ paths:
"personalAccountData":
{ "name": null, "birthdate": null, "photo": null },
"gboAgeProfile": null,
"tatSecurity": {
"isSecured": true,
"isAuthorized": true,
},
"_links":
{
"self":
@ -2149,6 +2278,10 @@ paths:
"personalAccountData":
{ "name": null, "birthdate": null, "photo": null },
"gboAgeProfile": null,
"tatSecurity": {
"isSecured": true,
"isAuthorized": true,
},
"_links":
{
"self":
@ -2905,9 +3038,9 @@ paths:
summary: "**INTEGRATIELAAG** Transfer old OVpay token to new OVpay token."
description: |-
**Note that this directly calls the integratielaag and not the Service Engine!**
First transfers all personal account data (if present), then all products, of an existing OVpay token
First transfers all personal account data (if present), then all products, of an existing OVpay token
to a new OVpay token. The new token will also be persisted in the profile as a replacement
of the old token. This call is asynchronous, and progress can be monitored using the
of the old token. This call is asynchronous, and progress can be monitored using the
`/customers/tokens/transfer/responsestatus/*` endpoint.
tags:
- Token Replace v2
@ -2920,9 +3053,9 @@ paths:
Transfer from a token without PAD to a new token:
summary: Transfer from a token without PAD to a new token
description: |
Transfer of a token without PAD to a new OVpay token. Note the new token in the request is always
identified by xTAT, regardless if the new token already exists in the customer profile. If the new
token does not exist in the customer profile, the user needs to provide an alias for the new token,
Transfer of a token without PAD to a new OVpay token. Note the new token in the request is always
identified by xTAT, regardless if the new token already exists in the customer profile. If the new
token does not exist in the customer profile, the user needs to provide an alias for the new token,
and it will be created in the customer profile.
value:
{
@ -2936,11 +3069,11 @@ paths:
Transfer from a token with PAD to a new token without existing PAD:
summary: Transfer from a token with PAD to a new token without existing PAD
description: |
Transfer of a token with PAD to a new token by xTAT without existing PAD. Note how this request is
different, since it has an e-mail address on which the new PAD should be created. This request may
Transfer of a token with PAD to a new token by xTAT without existing PAD. Note how this request is
different, since it has an e-mail address on which the new PAD should be created. This request may
optionally provide an OTP for verification of th provided email address.
Also note the new token in the request is always identified by xTAT, regardless if the new token
already exists in the customer profile. If the new token does not exist in the customer profile, the
Also note the new token in the request is always identified by xTAT, regardless if the new token
already exists in the customer profile. If the new token does not exist in the customer profile, the
user needs to provide an alias for the new token, and it will be created in the customer profile.
value:
{
@ -2957,8 +3090,8 @@ paths:
summary: Transfer from a token with PAD to a new token with existing PAD
description: |
Transfer of a token with PAD to a new token by xTAT with existing PAD.
Note that the new token in the request is always identified by xTAT, regardless if the new token
already exists in the customer profile. If the new token does not exist in the customer profile, the
Note that the new token in the request is always identified by xTAT, regardless if the new token
already exists in the customer profile. If the new token does not exist in the customer profile, the
user needs to provide an alias for the new token, and it will be created in the customer profile.
value:
{
@ -2996,7 +3129,7 @@ paths:
Transfer of token started:
description: |
The transfer of the token was started successfully. The data of the token will be transferred
asynchronously. In the response body the consumer will find information on how to retrieve the
asynchronously. In the response body the consumer will find information on how to retrieve the
processing status.
value:
{
@ -3038,7 +3171,7 @@ paths:
- Token Replace v2
summary: "**INTEGRATIELAAG** Get the status of the initiated token transfer process."
description: |
**Note that this directly calls the integratielaag and not the Service Engine!** Get the status of the
**Note that this directly calls the integratielaag and not the Service Engine!** Get the status of the
asynchronous token transfer processing.
responses:
"200":
@ -3166,7 +3299,7 @@ paths:
examples:
Token transfer in progress:
description: |
The transfer of the token is still in progress. The response body shows the details of the
The transfer of the token is still in progress. The response body shows the details of the
processing status.
value:
{
@ -3347,7 +3480,7 @@ paths:
in: query
description: External id of the device you want to get the purchased HTM products for.
schema:
type: string
type: string
- name: ovpayTokenId
in: query
description: Id of the OVpay-token you want to get the purchased HTM products for.
@ -3380,9 +3513,9 @@ paths:
}
getTwoBarcodePurchasedProductsForDevice:
summary: Two BarcodeTicket purchased products, one PendingActivation and one Active
description: |-
The first ticket (PendingActivation) shows the 30-day range in which the ticket can be activated
(using the PATCH endpoint) in the validityStart and validityEnd fields. The second ticket (Active)
description: |-
The first ticket (PendingActivation) shows the 30-day range in which the ticket can be activated
(using the PATCH endpoint) in the validityStart and validityEnd fields. The second ticket (Active)
shows the actual validity period of the ticket after activation.
value:
{
@ -3401,14 +3534,14 @@ paths:
"name": "Barcode",
},
"orderId": "501B17EF-36C4-4039-B92C-6517969B464E",
"orderLineId": "38B17EF-36C4-4039-B92C-4817969B464E",
"orderLineId": "38B17EF-36C4-4039-B92C-4817969B464E",
"ticketReference": "KJj43nejhbTxhrfef287",
"serviceId": "HTM-4321-7654-7659",
"issuedAt": "2026-03-21T09:01:35+01:00",
"activatedAt": null,
"blocked": false,
"cancelledAt": null,
"activateBefore": "2026-03-30",
"cancelledAt": null,
"activateBefore": "2026-03-30",
"validityStart": null,
"validityEnd": null,
"barcode": null,
@ -3436,14 +3569,14 @@ paths:
"name": "Barcode",
},
"orderId": "501B17EF-36C4-4039-B92C-6517969B464E",
"orderLineId": "38B17EF-36C4-4039-B92C-4817969B464E",
"orderLineId": "38B17EF-36C4-4039-B92C-4817969B464E",
"ticketReference": "KJj43nejhbTxhr897287",
"serviceId": "HTM-1234-7654-8945",
"issuedAt": "2026-03-21T10:01:12+01:00",
"activatedAt": "2026-03-21T12:45:01+01:00",
"blocked": false,
"cancelledAt": null,
"activateBefore": null,
"cancelledAt": null,
"activateBefore": null,
"validityStart": "2026-03-21T12:45:01+01:00",
"validityEnd": "2026-03-21T14:45:01+01:00",
"barcode": "iVBORw0KGgoAAAANSUhEUgAAAVwAAAFcAQAAAACsbTuBAAAFDElEQVR4Xu2bQa6rOhBEGzHwMEtgKewMyM5YCkvIkAGif50yyc/Tl770piZW9HSNywzqVlW3nXcjc83cusxnvsbMjBieuelf/bzGYz1jWCKmeGS+4g5grSwlz/IaI+JgEfzcvzQZo2yhxQgW7wDutLJrvVLzEInbVHLpM/tK5T7kHmNdbxssgqSipQj/0KLA24yqNvEWWRclpJKpefNgE7YZr/lj1eZ55wMYIWkUbZb7Xj/wH+BezvPkFJhQetmD+7AI2VuSEQMevANYESzqZh49VlEXIKOT7SDtseqnpz0Iyc2DJZezkEB5aaVKSvuF5FMwaIdB7wCGOgb5fFgus2wlKhPDpUiTlogr1tsHG7lfzcqYLlVnDHq6ylYn1OE54qd5cNC6aQxL0UQRy2YJJxCS8N4m273Gm4BJFAhzlytb5T4A0QrCYmUTrxd1bYNVgGqqPndH7vHwYqHLHf9cpO1pHJxU3Nq6vUbAVhXaocnTKO5cpK+4AViLitRwf8IQdYFwBkkKW7FzZxNh/AN/g5NHqXyed5MOz45kjkvKofpLYNwBrNBR/Dz9SKpTDkGd8CApZYonT24B7un56XKnoMeXkOBNHqTDk/NOmrrwyfIWYHe51O4kn4FBnfYbnHjqKuw3AGMreKOvC07DCAebHS5lAXWq5eKVA0HjYA2xM0MYCSMnqTqpx48ZVSmPTd15tT03AF+LVBuOgJoMqc2HL6Gy8nr1/K2DU+YpmKerd45fQ2ypzSFyq8coUM2D1yBksNWqOTWnjqvLpVYPvrt17f6BP+AaQk85zR4MarfHbAlSu/XhgGCJNg6mQNmASXnGhpZY+tKp93lywaY+SjcPhroCWx1BHFAnVemR81mdjEikWlllzYNrCM27bKVt7nInLplEnef6SfPFhb19MDmCbewcjtL/d7FwAzCqmShYzMd/qaPnp0AxV+SOpq5xMCnLZJOhZCNCZuIR4BWwL6un4luF5sE9N7CKXIJ2Zecncnt3vZ0it+hN7wNC6+DU/Ene1gj+lOOr4BDGilyT3D74NZ4OlYm2Xmx9t2rJN+kb7P3A/wUzV6lSQaonIoR3DUdy7vR1WQt782CrzrbbvTOdxwu2ZKy4MZXXflPzYJoVLg6GpbIVtLSb2JxduzEg+6Mm+R3AXECaHYRjG4nH2UIak7jSm9zXNQ9OLha2yhbUCT/vOA1VHUAGXpVuhpsHE7H7O0d6d2+i7rxeI2EVVMTm4wZg5vZUV2/nBUmoG5befZ0XERKkNg9O8zR9Rexls+WgldPRmTYHp90BrAIkqqYYnkmNGfvEY1E2Ijeu/3SQLKar1Q/8AX88eDqCRe3wZKJ89sVCRdaTZfvg/u8uFhoH14J0apHvil2rFwrUNtuDcXLOruNdvRoGB9ezgJXP2OrwN+mz8jl9lPab3Ob0NwDbVteFpEuX+7ylbHGdo5Oeny8y4vJgy2DniHhzQQp3c4yCkLwZvPbXk+INwKeqj6grVB/ha+dC88LVfec3zR/q2gYnlyO5GD+Kvfp03unruKCUygJb1e8HmwdbPp2pg8fgr3XYnE6YQGUWUp20Dz6hSgReznmXY3hLJ5A2ozJT9wN/gfVIEbzBsxvdYSneL571OV279S+/jubBiuCzAL5qt5xHPNHMcHgciSuD15rXbYMtJFbE1piEkEi0fPp6AoLX5Lvjt+paBvOIRQjSEEHaCX5BSP4DBSalHh4bB/8DVeIFt2RfPhQAAAAASUVORK5CYII=",
@ -3499,7 +3632,7 @@ paths:
"barcodeTickets":[],
"vouchers":[]
}
}
}
getSingleVoucherPurchasedProductForCustomer:
summary: One voucher purchased product
value:
@ -3523,7 +3656,7 @@ paths:
"voucherStatus": {
"voucherStatusId": 2,
"name": "issued"
},
},
"mandatoryCustomerDataItems":
[
{
@ -3538,7 +3671,7 @@ paths:
}
]
}
}
}
/purchasedproducts/{purchasedProductId}:
parameters:
- name: X-HTM-JWT-AUTH-HEADER
@ -3586,7 +3719,7 @@ paths:
value:
{
"status": "Active"
}
}
responses:
"200":
description: OK
@ -3636,7 +3769,6 @@ paths:
"vouchers": []
}
}
/customers/devices:
post:
summary: Add a new device to a customer profile.
@ -3813,7 +3945,7 @@ components:
customerProfileId:
type: integer
example: 1
customerPreference:
customerPreference:
$ref: "#/components/schemas/getCustomerPreference"
customerNumber:
type: integer
@ -4014,12 +4146,12 @@ components:
customerPreferenceId:
type: integer
languageId:
type: integer
type: integer
customerStatusEntity:
type: object
properties:
customerStatusId:
type: integer
type: integer
OvPayTokensResponse:
type: object
required:
@ -4298,7 +4430,7 @@ components:
items:
type: object
required:
- purchasedProductId
- purchasedProductId
- productId
- name
- status
@ -4421,13 +4553,13 @@ components:
type: string
example: HTM 2 uurskaart
description: |-
The name of the originating HTM product definition.
The name of the originating HTM product definition.
Will be returned in the language as specified in the Accept-Language header or the customer profile. Otherwise defaults to NL.
description:
type: string
example: "Met deze kaart reis je 2 uur lang onbeperkt met de bussen en trams van HTM. Overstappen is natuurlijk toegestaan!"
description: |-
The description of the originating HTM product definition.
The description of the originating HTM product definition.
Will be returned in the language as specified in the Accept-Language header or the customer profile. Otherwise defaults to NL.
status:
type: string
@ -4486,15 +4618,15 @@ components:
example: "2024-11-30"
description: Only present for barcode tickets that are not yet activated. Tickets can only be activated BEFORE this date.
validityStart:
description: |-
Only present for barcode tickets that have been activated. The date-time at which the ticket will become valid for traveling.
description: |-
Only present for barcode tickets that have been activated. The date-time at which the ticket will become valid for traveling.
The ticket will not be valid before this date/time.
type: string
format: date-time-offset
example: "2024-11-25T13:25:00+01:00"
validityEnd:
description: |-
Only present for barcode tickets that have been activated. The date-time at which the ticket will become invalid for traveling.
description: |-
Only present for barcode tickets that have been activated. The date-time at which the ticket will become invalid for traveling.
The ticket will not be valid after this date/time.
type: string
format: date-time-offset
@ -4603,4 +4735,3 @@ components:
customerDataItem:
type: string
example: emailAddress

View File

@ -0,0 +1,617 @@
openapi: 3.0.1
info:
title: Service Engine APIs for TAT security
description: >-
Service Engine APIs for TAT security. These are NOT the raw GBO APIs to access TAT security at GBO directly.
To be used by touch points to get secure a TAT.
version: "2.0"
servers:
- url: https://services.acc.api.htm.nl/abt/touchpoint/2.0
tags:
- name: TAT Security
paths:
/tokens/status:
parameters:
- name: X-HTM-JWT-AUTH-HEADER
in: header
schema:
type: string
example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
required: false
description: The JWT of a customer in case of touchpoint were customer logs in themselves
- name: X-HTM-CUSTOMER-PROFILE-ID-HEADER
in: header
schema:
type: integer
example: 323
required: false
description: The id of the customer Profile
- name: X-HTM-ROLE-HEADER
in: header
schema:
type: string
example: Customer
required: false
description: The role of the HTM employee in the case of the SMP
- name: xTat
in: query
schema:
type: string
format : uuid
example: 5c4cba83-1ecd-46a4-b712-a8de0dcb7bbf
required: false
description: The xtat of the customerToken
- name: ovPayTokenId
in: query
schema:
type: integer
example: 1
required: false
description: The ovpay token id of a specific customer token
- name: serviceReferenceId
in: query
schema:
type: string
example: NLOV1234567ABCDEFG
required: false
description: The serviceReferencId found on the bankstatement for a given trip with a bankcard, required for an unknown EMV card
- name: amount
in: query
schema:
type: integer
example: 0
required: false
description: The amount of the value of the bankstatement in cents, required for an unknown EMV card
- name: ovpasNumber
in: query
schema:
type: string
example: OV34567
required: false
description: The number of the ovpas, required for an unknown Ovpas card
- name: verificationCode
in: query
schema:
type: string
example: HL78
required: false
description: The 4 digit verificationCode found on the backside of the Ovpas, required for an unknown Ovpas card
get:
tags:
- TAT Security
summary: Check the status of the security and authorization status of the given token
description: Check the status of the security and authorization status of the given token, check anonymous tokenDB if no customer headers are supplied otherwise check customer tokenDB
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/TokenStatusResponse"
examples:
isSecuredandnotAuthorized:
summary: Token is secured but HTM is not authorized
description: Token is secured but HTM is not authorized
value:
isSecured: true
isAuthorized: false
xTat: 12da8f2a-6390-408a-94eb-a6701b5daf27
_links: {
"authorize": {
"href": "/tokens/authorizetoken",
"method": "POST"
}
}
notSecuredandnotAuthorized:
summary: Token is not secured but HTM is not authorized
description: Token is not secured but HTM is not authorized
value:
isSecured: false
isAuthorized: false
xTat: 12da8f2a-6390-408a-94eb-a6701b5daf27
_links: {
"secure": {
"href": "/tokens/securetoken",
"method": "POST"
}
}
"400":
description: Bad Request
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
examples:
Missing Parameters:
value:
status: 400
title: Missing Mandatory Parameter
detail: Required parameter {0} is missing.
Invalid Parameters:
value:
status: 400
title: Invalid Parameter
detail: Required parameter {0} is invalid.
"404":
description: Not found
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
examples:
TAT not found:
value:
status: 404
title: Not Found
detail: XTAT Account Not Found
"500":
description: Internal server error
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
example:
error: An unknown error has occurred
/tokens/securetoken:
parameters:
- name: X-HTM-JWT-AUTH-HEADER
in: header
schema:
type: string
example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
required: false
description: The JWT of a customer in case of touchpoint were customer logs in themselves
- name: X-HTM-CUSTOMER-PROFILE-ID-HEADER
in: header
schema:
type: integer
example: 323
required: false
description: The id of the customer Profile
- name: X-HTM-ROLE-HEADER
in: header
schema:
type: string
example: Customer
required: false
description: The role of the HTM employee in the case of the SMP
post:
tags:
- TAT Security
summary: Request additional OV-pas security for a token either in profile or anonymous.
description: Request additional OV-pas security for a token either in profile or anonymous
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/SecureTokenRequest"
examples:
With customer account:
value:
ovPayTokenId: 42
emailAddress: stasjo@htm.nl
Without customer account:
value:
xTat: 62914b49-2c7f-437f-b4b0-2ad61a9f902d
emailAddress: stasjo@htm.nl
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/EmailNotPreApprovedResponse"
example:
recipient: xxxxxx.user@gmail.com
description: OTP Sent
_links: {
"verifyotp": {
"href": "/tokens/verifyotp",
"method": "POST"
}
}
"400":
description: Bad Request
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
examples:
Missing Parameters:
value:
status: 400
title: Missing Mandatory Parameter
detail: Required parameter {0} is missing.
Invalid Parameters:
value:
status: 400
title: Invalid Parameter
detail: Required parameter {0} is invalid.
"401":
description: Unauthorized
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
examples:
Unauthorized:
value:
status: 401
title: Unauthorized
detail: Invalid Access Token
"404":
description: Not found
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
examples:
TAT not found:
value:
status: 404
title: Not Found
detail: TAT Account Not Found
"409":
description: Conflict
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
examples:
TAT Already Secured:
value:
status: 409
title: Conflict
detail: TAT Already Secured
"500":
description: Internal server error
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
example:
error: An unknown error has occurred
/tokens/authorizetoken:
parameters:
- name: X-HTM-JWT-AUTH-HEADER
in: header
schema:
type: string
example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
required: false
description: The JWT of a customer in case of touchpoint were customer logs in themselves
- name: X-HTM-CUSTOMER-PROFILE-ID-HEADER
in: header
schema:
type: integer
example: 323
required: false
description: The id of the customer Profile
- name: X-HTM-ROLE-HEADER
in: header
schema:
type: string
example: Customer
required: false
description: The role of the HTM employee in the case of the SMP
post:
tags:
- TAT Authorization
summary: Request authorization for HTM to access the data secured by additional OV-pas security for a token either in profile or anonymous.
description: Request authorization for HTM to access the data secured by additional OV-pas security for a token either in profile or anonymous.
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/AuthorizeTokenRequest"
examples:
With customer account:
value:
ovPayTokenId: 42
Without customer account:
value:
xTat: 62914b49-2c7f-437f-b4b0-2ad61a9f902d
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/EmailNotPreApprovedResponse"
example:
recipient: xxxxxx.user@gmail.com
description: OTP Sent
_links: {
"verifyotp": {
"href": "/tokens/verifyotp",
"method": "POST"
}
}
"400":
description: Bad Request
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
examples:
Missing Parameters:
value:
status: 400
title: Missing Mandatory Parameter
detail: Required parameter {0} is missing.
Invalid Parameters:
value:
status: 400
title: Invalid Parameter
detail: Required parameter {0} is invalid.
"401":
description: Unauthorized
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
examples:
Unauthorized:
value:
status: 401
title: Unauthorized
detail: Invalid Access Token
"404":
description: Not found
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
examples:
TAT not found:
value:
status: 404
title: Not Found
detail: TAT Account Not Found
"500":
description: Internal server error
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
example:
error: An unknown error has occurred
/tokens/verifyotp:
parameters:
- name: X-HTM-JWT-AUTH-HEADER
in: header
schema:
type: string
example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
required: false
description: The JWT of a customer in case of touchpoint were customer logs in themselves
- name: X-HTM-CUSTOMER-PROFILE-ID-HEADER
in: header
schema:
type: integer
example: 323
required: false
description: The id of the customer Profile
- name: X-HTM-ROLE-HEADER
in: header
schema:
type: string
example: Customer
required: false
description: The role of the HTM employee in the case of the SMP
post:
tags:
- TAT Verification
summary: Submit an OTP for a triggered OTP flow.
description: |
Submit an OTP for a triggered OTP flow. This can either be result of an AGO activation, or
result of an AGO authorization flow. Since the backoffice behaves slightly different depending
on which use case is executed, the calling TP needs to provide an `action` in the request body.
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/VerifyOtpRequest"
examples:
OTP verification for TAT security activation for token in customer account:
value:
ovPayTokenId: 42
otp: 123456
action: secure
OTP verification for TAT security authorization for anonymous token:
value:
xTat: 0f0defe8-828c-48e5-97e5-26d1d0179ef0
otp: 123456
action: authorize
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/VerifyOtpResponse'
examples:
Tat Secured:
value:
status: Success
description: TAT Secured
Tat Unsecured:
value:
status: Success
description: TAT Unsecured
Tat Authorized:
value:
status: Success
description: TAT Authorized
"400":
description: Bad request
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
examples:
Missing Parameters:
value:
status: 400
title: Missing Mandatory Parameter
detail: Required parameter {0} is missing.
Invalid Parameters:
value:
status: 400
title: Invalid Parameter
detail: Required parameter {0} is invalid.
"401":
description: Unauthorized
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
examples:
Unauthorized:
value:
status: 401
title: Unauthorized
detail: Invalid Access Token
"500":
description: Internal server error
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
example:
error: An unknown error has occurred
components:
schemas:
unavailable:
type: object
GenerateTatOutput:
type: object
properties:
uid:
type: string
description: >-
An uid IS A unique identifier THAT is associated with the
user.
recipient:
type: string
description: >-
A recipient IS A unique identifier THAT is associated with the
TAT owner.
key:
type: string
description: >-
A key IS a 32 character string THAT uniquely identifies the
OTP session.
EmailNotPreApprovedResponse:
type: object
description: >-
EmailNotPreApprovedResponse IS AN object THAT represents the response of
email pre-approval check.
properties:
recipient:
type: string
description: >-
A recipient IS A unique identifier THAT is associated with the TAT
owner.
example: xxxxxx.user@gmail.com
description:
type: string
description: >-
A description IS A string THAT describes the reason why the email is
not pre-approved.
example: OTP Sent
SecureTokenRequest:
type: object
properties:
ovPayTokenId:
type: integer
example: 42
xTat:
type: string
format: uuid
example: 6134db53-9ae5-41d1-a343-36656b60b510
emailAddress:
type: string
format: email
example: stasjo@htm.nl
required:
- emailAddress
AuthorizeTokenRequest:
type: object
properties:
ovPayTokenId:
type: integer
example: 42
xTat:
type: string
format: uuid
example: 6134db53-9ae5-41d1-a343-36656b60b510
VerifyOtpRequest:
type: object
properties:
ovPayTokenId:
type: integer
example: 42
xTat:
type: string
format: uuid
example: f3474452-e1d4-428c-b366-e5ad5965eb8c
otp:
type: string
example: 123456
action:
type: string
example: secure
required:
- otp
- action
VerifyOtpResponse:
type: object
properties:
status:
type: string
example: Success
description:
type: string
example: TAT Secured
ErrorResponse:
description: Default response when an invalid request has been sent
type: object
properties:
status:
type: integer
description: >-
A status IS An integer that represents the HTTP status code of the
response.
example: 400
title:
type: string
description: A title IS A string that provides a brief summary of the error.
detail:
type: string
description: A detail IS A string that provides more details about the error.
TokenStatusResponse:
type: object
description: >-
TokenStatusResponse IS AN object THAT represents the current state of the token in relation to the AGO security and authorization status of HTM.
required:
- isSecured
- isAuthorized
properties:
isSecured:
type: boolean
description: >-
Is the token secured in the national database (GBO)
example: false
isAuthorized:
type: boolean
description: >-
Does HTM have the authorization to access the secured data for this token, only applicable when isSecured equals True.
example: false
xTat:
type: string
format: uuid
description: >-
The xtat of the customerToken given when token is unknown for the customer
example: 883674e5-54c7-47d6-a3e3-8781f0d3ffea

View File

@ -243,7 +243,7 @@ paths:
suffix: PhD
dateOfBirth: "2002-10-29"
emailAddresses: TEST@HTM.NL
isEmailVerified: True
isEmailVerified: false
addresses:
- addressId: 1
street: mystreet
@ -332,14 +332,6 @@ paths:
user: user
timestamp: "2023-10-20T17:05:52.000+02:00"
correlationId: 01c6d8b4-1cd3-4f9b-85ea-c9adca53ce95
_links:
{
"self":
{
"href": "https://api.integratielaag.nl/abt/abtcustomers/2.0/customers?customerProfileId=1",
"method": "GET",
},
}
- customerProfileId: 12
customerNumber: 1000002
debtorNumber: DB100121
@ -377,7 +369,7 @@ paths:
suffix: PhD
dateOfBirth: "2002-10-29"
emailAddresses: TEST@HTM.NL
isEmailVerified: False
isEmailVerified: false
address:
- addressId: 1
street: mystreet
@ -467,32 +459,7 @@ paths:
user: user
timestamp: "2023-10-20T17:05:52.000+02:00"
correlationId: 01c6d8b4-1cd3-4f9b-85ea-c9adca53ce95
_links:
{
"self":
{
"href": "https://api.integratielaag.nl/abt/abtcustomers/2.0/customers?customerProfileId=12",
"method": "GET",
},
}
_links:
{
"self":
{
"href": "https://api.integratielaag.nl/abt/abtcustomers/2.0/customers?offset=20",
"method": "GET",
},
"prev":
{
"href": "https://api.integratielaag.nl/abt/abtcustomers/2.0/customers",
"method": "GET",
},
"next":
{
"href": "https://api.integratielaag.nl/abt/abtcustomers/2.0/customers?offset=40",
"method": "GET",
},
}
href: "https://api.integratielaag.nl/abt/abtcustomers/2.0/customers?offset=20&limit=20"
getCustomerActive:
summary: getCustomerActive
description: >-
@ -526,7 +493,7 @@ paths:
suffix: PhD
dateOfBirth: "2002-10-29"
emailAddresses: TEST@HTM.NL
isEmailVerified: False
isEmailVerified: false
addresses:
- addressId: 1
street: mystreet
@ -615,22 +582,7 @@ paths:
user: user
timestamp: "2023-10-20T17:05:52.000+02:00"
correlationId: 01c6d8b4-1cd3-4f9b-85ea-c9adca53ce95
_links:
{
"self":
{
"href": "https://api.integratielaag.nl/abt/abtcustomers/2.0/customers?customerProfileId=1",
"method": "GET",
},
}
_links:
{
"self":
{
"href": "https://api.integratielaag.nl/abt/abtcustomers/2.0/customers",
"method": "GET",
},
}
href: https://api.integratielaag.nl/abt/abtcustomers/2.0/customers?offset=20&limit=20
getCustomerBlocked:
summary: getCustomerBlocked
description: >-
@ -669,7 +621,7 @@ paths:
suffix: PhD
dateOfBirth: "2002-10-29"
emailAddresses: TEST@HTM.NL
isEmailVerified: True
isEmailVerified: false
addresses:
- addressId: 1
street: mystreet
@ -758,22 +710,7 @@ paths:
user: user
timestamp: "2023-10-20T17:05:52.000+02:00"
correlationId: 01c6d8b4-1cd3-4f9b-85ea-c9adca53ce95
_links:
{
"self":
{
"href": "https://api.integratielaag.nl/abt/abtcustomers/2.0/customers?customerProfileId=1",
"method": "GET",
},
}
_links:
{
"self":
{
"href": "https://api.integratielaag.nl/abt/abtcustomers/2.0/customers",
"method": "GET",
},
}
href: https://api.integratielaag.nl/abt/abtcustomers/2.0/customers?offset=20&limit=20
400:
description: Bad Request
content:
@ -835,7 +772,7 @@ paths:
suffix: jr
dateOfBirth: "1970-01-01"
emailAddress: j.jansen@hatseflats.nl
isEmailVerified: False
isEmailVerified: false
addresses:
- street: Laan van Meerdervoort
houseNumber: 5
@ -934,7 +871,7 @@ paths:
suffix: "jr",
dateOfBirth: "1970-01-01",
emailAddress: "TEST@TEST1.NL",
isEmailVerified: False
isEmailVerified: false
}
updateCompleteEntity:
value:
@ -946,7 +883,7 @@ paths:
suffix: "jr",
dateOfBirth: "1970-01-01",
emailAddress: "TEST@TEST1.NL",
isEmailVerified: True
isEmailVerified: false
}
required: true
responses:
@ -1438,13 +1375,7 @@ paths:
content:
application/json:
schema:
$ref: "#/components/schemas/getDevices"
examples:
updateDeviceResponse:
value:
deviceId: "5bedce29-af0c-4f3c-b182-2caa8a1f9377"
externalDeviceId: "7122a988-a00a-417d-a5b4-da2d91354976"
alias: "iPhone zakelijk"
$ref: "#/components/schemas/patchDeviceResponse"
security:
- default: []
@ -1607,24 +1538,7 @@ paths:
"replacedByTokenId": 12,
},
],
"_links":
{
"self":
{
"href": "https://api.integratielaag.nl/abt/abtcustomers/2.0/ovPayTokens?offset=20",
"method": "GET",
},
"prev":
{
"href": "https://api.integratielaag.nl/abt/abtcustomers/2.0/ovPayTokens",
"method": "GET",
},
"next":
{
"href": "https://api.integratielaag.nl/abt/abtcustomers/2.0/ovPayTokens?offset=40",
"method": "GET",
},
},
"href": "https://api.integratielaag.nl/abt/abtcustomers/2.0/ovPayTokens?offset=20&limit=20"
}
/customers/{customerProfileId}/ovpaytokens:
post:
@ -1943,9 +1857,10 @@ paths:
"created": "2024-10-20T17:05:52.000",
},
],
"href": "https://api.integratielaag.nl/abt/abtcustomers/2.0/billingInformations?offset=20&limit=20"
}
getBillingInformationSingleSpecificCustomer:
summary: Get singel billing information entity for a specific customers
summary: Get single billing information entity for a specific customers
description: >-
Found one billing information matching the search parameters
value:
@ -2135,59 +2050,7 @@ paths:
"updateTimestamp": "2024-03-22T08:55:00",
},
],
"_links":
{
"self":
{
"href": "https://api.integratielaag.nl/abt/abtcustomers/2.0/directdebitmandates",
"method": "GET",
},
"next":
{
"href": "https://api.integratielaag.nl/abt/abtcustomers/2.0/directdebitmandates?offset=20",
"method": "GET",
},
},
}
getDirectDebitMandateWithNextPageandPreviousPage:
value:
{
"directDebitMandates":
[
{
"directDebitMandateId": 71,
"customerProfileId": 12,
"billingInformationId": 51,
"directDebitMandateType":
{
"directDebitMandateTypeId": 1,
"name": "import",
"description": "import",
},
"created": "2024-03-22T08:55:00",
"mandateReference": "CORE01",
"mandateState": "SIGNED",
"updateTimestamp": "2024-03-22T08:55:00",
},
],
"_links":
{
"self":
{
"href": "https://api.integratielaag.nl/abt/abtcustomers/2.0/directdebitmandates?offset=20",
"method": "GET",
},
"prev":
{
"href": "https://api.integratielaag.nl/abt/abtcustomers/2.0/directdebitmandates",
"method": "GET",
},
"next":
{
"href": "https://api.integratielaag.nl/abt/abtcustomers/2.0/directdebitmandates?offset=40",
"method": "GET",
},
},
"href": "https://api.integratielaag.nl/abt/abtcustomers/2.0/directdebitmandates?offset=20&limit=20"
}
/billingInformation/{billingInformationId}/directdebitmandates:
post:
@ -2843,7 +2706,7 @@ components:
emailAddress:
type: string
isEmailVerified:
type: string
type: boolean
postOvPayTokenResponse:
type: object
properties:
@ -2857,6 +2720,14 @@ components:
type: array
items:
$ref: "#/components/schemas/getDeviceEntity"
patchDeviceResponse:
type: object
properties:
deviceId:
type: string
format: uuid
example: "b2c8a8c6-3d1c-4b6b-8f8d-3a6a1b6a1b6a"
getDeviceEntity:
type: object
properties:

View File

@ -49,6 +49,8 @@ paths:
- status
- aggregationReference
- accountingSystemReference
- createdOn
- lastUpdatedOn
- name: $select
in: query
description: Select properties to be returned
@ -79,6 +81,8 @@ paths:
- status
- aggregationReference
- accountingSystemReference
- createdOn
- lastUpdatedOn
responses:
"200":
description: OK
@ -117,7 +121,9 @@ paths:
articleNumber: "4031"
status: created
aggregationReference: null
accountingSystemReference: null
accountingSystemReference: null
createdOn: 2024-11-04T00:00:00Z
lastUpdatedOn: 2024-11-04T04:00:00Z
- "@odata.etag": e671c8a1-b918-4867-bf12-5289822479c3
transactionItemRowId: 3232
transactionItemId: 1c1fc1c8-57f4-4336-9b43-a974eae5afbf
@ -136,7 +142,9 @@ paths:
articleNumber: "4051"
status: failed
aggregationReference: null
accountingSystemReference: null
accountingSystemReference: null
createdOn: 2024-11-04T00:00:00Z
lastUpdatedOn: 2024-11-04T04:00:00Z
- "@odata.etag": be2715d4-4eff-4562-9bf3-5c43ad23e2c8
transactionItemRowId: 3233
transactionItemId: 3f58441e-dc8f-4956-9bc5-c952312476db
@ -155,7 +163,9 @@ paths:
articleNumber: "4051"
status: returned to src
aggregationReference: FIKO-171f40609e
accountingSystemReference: null
accountingSystemReference: null
createdOn: 2024-11-04T00:00:00Z
lastUpdatedOn: 2024-11-04T04:00:00Z
- "@odata.etag": 52130c4d-e944-4454-a73e-8d55449a4bd1
transactionItemRowId: 3234
transactionItemId: 4418825f-3f9b-45bc-b662-dc3cd4ce6599
@ -174,7 +184,9 @@ paths:
articleNumber: "4051"
status: returned to trx-db
aggregationReference: null
accountingSystemReference: null
accountingSystemReference: null
createdOn: 2024-11-04T00:00:00Z
lastUpdatedOn: 2024-11-04T04:00:00Z
- "@odata.etag": 7da305e1-85cd-4250-ad77-8b54b2649cfb
transactionItemRowId: 3235
transactionItemId: 2ddc1831-cf7b-4a77-8aa1-11aaf8e98d9f
@ -193,7 +205,9 @@ paths:
articleNumber: "4051"
status: re-entered
aggregationReference: null
accountingSystemReference: null
accountingSystemReference: null
createdOn: 2024-11-04T00:00:00Z
lastUpdatedOn: 2024-11-04T04:00:00Z
- "@odata.etag": c3b17d21-0506-474d-b3d4-7ff654f368b9
transactionItemRowId: 3236
transactionItemId: 5ab2513d-f334-4cf8-8895-4e7269374a4b
@ -212,7 +226,9 @@ paths:
articleNumber: "1737"
status: succeeded
aggregationReference: FIKO-6a4fca8cd6
accountingSystemReference: U4F-123456
accountingSystemReference: U4F-123456
createdOn: 2024-11-04T00:00:00Z
lastUpdatedOn: 2024-11-04T04:00:00Z
security:
- default: []
x-auth-type: Application & Application User
@ -434,7 +450,7 @@ paths:
summary: Add a processing failure.
description: Add a processing failure.
tags:
- Failures v2.2
- Failures v2.3
requestBody:
required: true
content:
@ -455,6 +471,7 @@ paths:
reasonCode: "012"
reasonDescription: Transaction date is in the future.
resolved: false
journalEntryId: 0e53b984-16b2-4ef7-b669-29bcdad2a3ce
responses:
"201":
description: Created
@ -472,67 +489,6 @@ paths:
- default: []
x-auth-type: Application & Application User
x-throttling-tier: Unlimited
/transactionitems/{transactionItemId}/transactionaudittrails:
parameters:
- in: path
name: transactionItemId
schema:
type: string
format: uuid
example: 9834b346-d68c-438d-9bd5-6716422c2657
required: true
description: The id of the transaction item to process.
post:
summary: Add an audit trail entry.
description: Add an audit trail entry.
tags:
- Audit Trail v2.2
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/TransactionAuditTrailsPostRequestBody"
examples:
Add new transaction audit trail for created transaction item:
summary: Add new transaction audit trail for created transaction item
description: Add a new audit trail record for a newly created transaction item.
value:
user: fiko
timestamp: 2022-01-01 12:34:00
action: create
status: created
description: Transaction item created.
correlationId: null
Add new transaction audit trail for updated transaction item:
summary: Add new transaction audit trail for updated transaction item
description: Add a new audit trail record for an updated transaction item.
value:
user: ad.vogelaar
timestamp: 2022-01-02 12:34:00
action: update
status: return to src
description: Transaction item returned to source.
correlationId: 0dc216ea-58e7-4a5e-aa18-449f0304209c
responses:
"201":
description: Created
content:
application/json:
schema:
$ref: "#/components/schemas/TransactionAuditTrailsPostResponseBody"
examples:
Transaction audit trail record created:
summary: Transaction audit trail record created
description: |
A new audit trail record for this transaction item was successfully created.
The transactionAuditTrailId is returned as a reference for the consumer.
value:
transactionAuditTrailId: 37a8095b-19db-498f-9349-5f37d21c5bdf
security:
- default: []
x-auth-type: Application & Application User
x-throttling-tier: Unlimited
/Odata/TransactionAuditTrail:
get:
summary: Find audit trail entries.
@ -564,6 +520,7 @@ paths:
- action
- status
- correlationId
- journalEntryId
- name: $select
in: query
description: Select properties to be returned
@ -584,6 +541,7 @@ paths:
- action
- status
- correlationId
- journalEntryId
- name: $expand
in: query
description: Expand related entities
@ -597,6 +555,7 @@ paths:
type: string
enum:
- TransactionItem
- JournalEntry
responses:
"200":
description: OK
@ -627,6 +586,7 @@ paths:
status: created
description: Transaction created.
correlationId: null
journalEntryId: null
- "@odata.etag": 1ce407d3-4725-46b8-92b2-47282ccd38f5
transactionAuditTrailRowId: 8219
transactionAuditTrailId: 1d234fc1-eb16-41d3-b2e0-e400e17b017f
@ -637,6 +597,7 @@ paths:
status: created
description: Transaction created.
correlationId: null
journalEntryId: null
- "@odata.etag": c8dec28b-bb85-4cb2-8a56-4a95fb2ed7bb
transactionAuditTrailRowId: 8220
transactionAuditTrailId: f4798a84-489d-4533-9dfe-42423c70f6b6
@ -647,6 +608,7 @@ paths:
status: return to src
description: Transaction retunred to source.
correlationId: a3891560-3084-42cb-867c-f289e7cda47d
journalEntryId: null
- "@odata.etag": 967ddfa0-835e-4954-a4af-2eb11de325dc
transactionAuditTrailRowId: 8221
transactionAuditTrailId: cc80d58d-6ee7-409a-8c14-b1f807e67c10
@ -657,6 +619,7 @@ paths:
status: failed
description: Validation XYZ failed.
correlationId: null
journalEntryId: a062f64f-f39e-48e7-9793-efbfa86d2827
List containing multiple transaction audit trails of one transaction item:
summary: List containing multiple audit trails of one transaction item
description: List containing multiple audit trail records of one transaction item (with id `d667d293-aa82-4c9e-9b10-77cffc9058a1`).
@ -673,6 +636,7 @@ paths:
status: re-entered
description: Transaction re-entered.
correlationId: null
journalEntryId: null
- "@odata.etag": 1ce407d3-4725-46b8-92b2-47282ccd38f5
transactionAuditTrailRowId: 8219
transactionAuditTrailId: b2192e6c-ab79-4000-a04d-d8f4d574a82f
@ -683,6 +647,7 @@ paths:
status: return to src
description: Transaction retunred to source.
correlationId: a3891560-3084-42cb-867c-f289e7cda47d
journalEntryId: null
- "@odata.etag": c8dec28b-bb85-4cb2-8a56-4a95fb2ed7bb
transactionAuditTrailRowId: 8220
transactionAuditTrailId: 1d759985-7f1a-48bd-8dd1-efa30ce26423
@ -693,6 +658,7 @@ paths:
status: failed
description: Validation XYZ failed.
correlationId: null
journalEntryId: b479507a-a65d-48b5-8476-87c88ba3c560
- "@odata.etag": 967ddfa0-835e-4954-a4af-2eb11de325dc
transactionAuditTrailRowId: 8221
transactionAuditTrailId: afce35b2-1dff-4ace-98d0-4b9ac405c87d
@ -703,6 +669,7 @@ paths:
status: created
description: Transaction created.
correlationId: null
journalEntryId: null
List containing multiple audit trails of different failed transactions:
summary: List containing multiple audit trails of different failed transactions
description: List containing multiple audit trails of different failed transactions (status = failed).
@ -719,6 +686,7 @@ paths:
status: failed
description: Validation XYZ failed.
correlationId: null
journalEntryId: null
- "@odata.etag": 1ce407d3-4725-46b8-92b2-47282ccd38f5
transactionAuditTrailRowId: 8219
transactionAuditTrailId: 1d234fc1-eb16-41d3-b2e0-e400e17b017f
@ -729,6 +697,7 @@ paths:
status: failed
description: Validation XYZ failed.
correlationId: null
journalEntryId: 43a98533-bb01-4aad-8d61-005051125969
- "@odata.etag": c8dec28b-bb85-4cb2-8a56-4a95fb2ed7bb
transactionAuditTrailRowId: 8220
transactionAuditTrailId: f4798a84-489d-4533-9dfe-42423c70f6b6
@ -739,6 +708,7 @@ paths:
status: failed
description: Validation XYZ failed.
correlationId: null
journalEntryId: null
- "@odata.etag": 967ddfa0-835e-4954-a4af-2eb11de325dc
transactionAuditTrailRowId: 8221
transactionAuditTrailId: cc80d58d-6ee7-409a-8c14-b1f807e67c10
@ -749,6 +719,7 @@ paths:
status: failed
description: Validation XYZ failed.
correlationId: null
journalEntryId: null
security:
- default: []
x-auth-type: Application & Application User
@ -786,6 +757,7 @@ paths:
- reasonDescription
- resolved
- change
- journalEntryId
- name: $select
in: query
description: Select properties to be returned
@ -808,6 +780,7 @@ paths:
- reasonDescription
- resolved
- change
- journalEntryId
- name: $expand
in: query
description: Expand related entities
@ -820,7 +793,8 @@ paths:
items:
type: string
enum:
- TransactionItem
- TransactionItem
- JournalEntry
responses:
"200":
description: OK
@ -856,6 +830,7 @@ paths:
reasonDescription: Cost type does not exist.
resolved: false
change: null
journalEntryId: null
- "@odata.etag": ff49829d-a5de-4c1a-b331-15fd9c71dfe9
processingFailureRowId: 10
processingFailureId: ccc06dc9-956d-47d8-bde7-9644ddde2193
@ -866,7 +841,8 @@ paths:
reasonCode: "002"
reasonDescription: Cost center does not exist.
resolved: true
change: Adapt config
change: Adapt config
journalEntryId: null
- "@odata.etag": 5d5d3351-d3f1-49b2-905f-4dbbe1cc30e3
processingFailureRowId: 11
processingFailureId: 96bf8d44-0030-4509-b24e-2d62299779c5
@ -877,7 +853,8 @@ paths:
reasonCode: "001"
reasonDescription: Department does not exist.
resolved: true
change: Adapt config
change: Adapt config
journalEntryId: null
List of unfiltered processing failures:
summary: List of unfiltered processing failures
description: |
@ -896,7 +873,8 @@ paths:
reasonCode: "009"
reasonDescription: Transaction date is in the future
resolved: false
change: null
change: null
journalEntryId: null
- "@odata.etag": 584d3a8a-48bc-45f9-99be-8dd70399e6e4
processingFailureRowId: 10
processingFailureId: 76aa82e1-a10f-4664-a84f-1f30d60884b3
@ -907,7 +885,8 @@ paths:
reasonCode: "002"
reasonDescription: Cost center does not exist.
resolved: false
change: null
change: null
journalEntryId: null
- "@odata.etag": df4ade9b-1bda-4d91-a5ba-4dbe067568a0
processingFailureRowId: 11
processingFailureId: 25d2dd6f-ae17-44a7-b7a5-43eaf6de78e1
@ -918,7 +897,8 @@ paths:
reasonCode: "001"
reasonDescription: Department does not exist.
resolved: false
change: null
change: null
journalEntryId: null
security:
- default: []
x-auth-type: Application & Application User
@ -969,6 +949,415 @@ paths:
- default: []
x-auth-type: Application & Application User
x-throttling-tier: Unlimited
/Odata/JournalEntries:
get:
summary: Find journalEntry.
description: Find journalEntry.
tags:
- Odata
parameters:
- $ref: "#/components/parameters/top"
- $ref: "#/components/parameters/skip"
- $ref: "#/components/parameters/filter"
- $ref: "#/components/parameters/count"
- name: $orderby
in: query
description: Order items by property values
required: false
style: form
explode: false
schema:
uniqueItems: true
type: array
items:
type: string
enum:
- journalEntryId
- aggregationReference
- accountingSystemReference
- name: $select
in: query
description: Select properties to be returned
required: false
style: form
explode: false
schema:
uniqueItems: true
type: array
items:
type: string
enum:
- journalEntryId
- aggregationReference
- accountingSystemReference
- data
- response
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/JournalEntryGetResponseBody"
examples:
List containing all journal entries:
summary: List containing all journal entries
description: Returns a list containing all journal entries (no filters applied).
value:
"@odata.context": "$metadata#JournalEntry"
value:
- "@odata.etag": 8f085711-eeae-44ed-bac1-06779bedb480
journalEntryId: e0af8c8f-d421-4567-a14e-48e2a1bb86fb
aggregationReference: FIKO-123456
accountingSystemReference: null
data: {
"transaction": {
"header": {
"key": {
"cmpCode": "1HTM",
"code": "OPBR-FIKO"
},
"timeStamp": 0,
"inputDate": "2026-07-20T00:00:00Z",
"period": "2026/6",
"curCode": "EUR",
"date": "2026-07-20T00:00:00Z",
"originalCompany": "1HTM",
"originalCode": "OPBR-FIKO",
"completionWorkflow": {
"completionStatus": "wfl_no_completion"
},
"salesInvoiceStatus": "salesinv_notyetinv"
},
"lines": {
"line": [
{
"number": 1,
"accountCode": "100.99999.D150000001",
"docValue": 100.00,
"lineType": "summary",
"lineSense": "debit",
"lineOrigin": "dl_orig_defined",
"description": "test",
"extRef1": "99999991",
"extRef2": "99999",
"sepaPaymentCategoryPurposeCode": "none",
"sepaPaymentPurposeCode": "none"
},
{
"number": 2,
"accountCode": "642.41236.609311",
"docValue": 100.00,
"userStatus": "",
"lineType": "analysis",
"lineSense": "credit",
"lineOrigin": "dl_orig_defined",
"sepaPaymentCategoryPurposeCode": "none",
"sepaPaymentPurposeCode": "none"
}
]
}
}
}
response: {
"status": "failed",
"message": [
{
"code": "352",
"reason": "Documentregel 1 kan niet geboekt worden."
},
{
"code": "159",
"reason": "Element 99999 (niveau 2) is niet toegestaan in linkerlijst van element D150000001 (niveau 3)."
}
],
"hints": [],
"paths": [
"/InputRequest/PostToBooks/Request/Transaction/Lines/Line[1]/AccountCode"
]
}
- "@odata.etag": 66c033af-fc67-4eb2-9d17-d74a0166470f
journalEntryId: 36a34bd8-2b84-4c89-87a4-22e809f6ed4a
aggregationReference: FIKO-123457
accountingSystemReference: "OPBR-FIKO10000090"
data: {
"transaction": {
"header": {
"key": {
"cmpCode": "1HTM",
"code": "OPBR-FIKO"
},
"timeStamp": 0,
"inputDate": "2026-07-20T00:00:00Z",
"period": "2026/6",
"curCode": "EUR",
"date": "2026-07-20T00:00:00Z",
"originalCompany": "1HTM",
"originalCode": "OPBR-FIKO",
"completionWorkflow": {
"completionStatus": "wfl_no_completion"
},
"salesInvoiceStatus": "salesinv_notyetinv"
},
"lines": {
"line": [
{
"number": 1,
"accountCode": "100.99999.D150000001",
"docValue": 100.00,
"lineType": "summary",
"lineSense": "debit",
"lineOrigin": "dl_orig_defined",
"description": "test",
"extRef1": "99999991",
"extRef2": "99999",
"sepaPaymentCategoryPurposeCode": "none",
"sepaPaymentPurposeCode": "none"
},
{
"number": 2,
"accountCode": "642.41236.609311",
"docValue": 100.00,
"userStatus": "",
"lineType": "analysis",
"lineSense": "credit",
"lineOrigin": "dl_orig_defined",
"sepaPaymentCategoryPurposeCode": "none",
"sepaPaymentPurposeCode": "none"
}
]
}
}
}
response: {
"key": {
"$type": "finance_transaction_txnKey",
"cmpCode": "1HTM",
"code": "OPBR-FIKO",
"number": " 10000090"
},
"suppliedNumber": ""
}
- "@odata.etag": 159f526c-3c88-48a1-93a0-e16430500bef
journalEntryId: 6097121f-f300-4c0b-ab8d-0a24685bda92
aggregationReference: FIKO-123458
accountingSystemReference: null
data: {
"transaction": {
"header": {
"key": {
"cmpCode": "1HTM",
"code": "OPBR-FIKO"
},
"timeStamp": 0,
"inputDate": "2026-07-20T00:00:00Z",
"period": "2026/6",
"curCode": "EUR",
"date": "2026-07-20T00:00:00Z",
"originalCompany": "1HTM",
"originalCode": "OPBR-FIKO",
"completionWorkflow": {
"completionStatus": "wfl_no_completion"
},
"salesInvoiceStatus": "salesinv_notyetinv"
},
"lines": {
"line": [
{
"number": 1,
"accountCode": "100.99999.D150000001",
"docValue": 100.00,
"lineType": "summary",
"lineSense": "debit",
"lineOrigin": "dl_orig_defined",
"description": "test",
"extRef1": "99999991",
"extRef2": "99999",
"sepaPaymentCategoryPurposeCode": "none",
"sepaPaymentPurposeCode": "none"
},
{
"number": 2,
"accountCode": "642.41236.609311",
"docValue": 100.00,
"userStatus": "",
"lineType": "analysis",
"lineSense": "credit",
"lineOrigin": "dl_orig_defined",
"sepaPaymentCategoryPurposeCode": "none",
"sepaPaymentPurposeCode": "none"
}
]
}
}
}
response: null
security:
- default: []
x-auth-type: Application & Application User
x-throttling-tier: Unlimited
/journalEntries:
post:
summary: Add a journalEntry.
description: Add a journalEntry.
tags:
- journalEntries v1.0
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/JournalEntryPostRequestBody"
examples:
Add a new journalEntry:
summary: Add a new journalEntry
description: In this example we add a new journalEntry.
value:
aggregationReference: FIKO-123456
data: {
"transaction": {
"header": {
"key": {
"cmpCode": "1HTM",
"code": "OPBR-FIKO"
},
"timeStamp": 0,
"inputDate": "2026-07-20T00:00:00Z",
"period": "2026/6",
"curCode": "EUR",
"date": "2026-07-20T00:00:00Z",
"originalCompany": "1HTM",
"originalCode": "OPBR-FIKO",
"completionWorkflow": {
"completionStatus": "wfl_no_completion"
},
"salesInvoiceStatus": "salesinv_notyetinv"
},
"lines": {
"line": [
{
"number": 1,
"accountCode": "100.99999.D150000001",
"docValue": 100.00,
"lineType": "summary",
"lineSense": "debit",
"lineOrigin": "dl_orig_defined",
"description": "test",
"extRef1": "99999991",
"extRef2": "99999",
"sepaPaymentCategoryPurposeCode": "none",
"sepaPaymentPurposeCode": "none"
},
{
"number": 2,
"accountCode": "642.41236.609311",
"docValue": 100.00,
"userStatus": "",
"lineType": "analysis",
"lineSense": "credit",
"lineOrigin": "dl_orig_defined",
"sepaPaymentCategoryPurposeCode": "none",
"sepaPaymentPurposeCode": "none"
}
]
}
}
}
responses:
"201":
description: Created
content:
application/json:
schema:
$ref: "#/components/schemas/JournalEntryPostResponseBody"
examples:
Journalentry created successfully:
summary: Journalentry created successfully
description: TheJournalentry was successfully added to the database.
value:
journalEntryId: 6ce10520-9a73-4cb4-b8dd-39ee46ceb617
security:
- default: []
x-auth-type: Application & Application User
x-throttling-tier: Unlimited
/journalEntries/{journalEntryId}:
parameters:
- in: path
name: journalEntryId
schema:
type: string
format: uuid
example: 37a8095b-19db-498f-9349-5f37d21c5bdf
required: true
description: The id of the journalEntry.
patch:
summary: Update journalEntry.
description: Update journalEntry.
tags:
- journalEntries v1.0
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/JournalEntryPatchRequestBody"
examples:
Update an journalEntry after error:
summary: Update an journalEntry after error
description: Update an journalEntry after error by logging the response
value:
response: {
"status": "failed",
"message": [
{
"code": "352",
"reason": "Documentregel 1 kan niet geboekt worden."
},
{
"code": "159",
"reason": "Element 99999 (niveau 2) is niet toegestaan in linkerlijst van element D150000001 (niveau 3)."
}
],
"hints": [],
"paths": [
"/InputRequest/PostToBooks/Request/Transaction/Lines/Line[1]/AccountCode"
]
}
Update an journalEntry after success:
summary: Update an journalEntry after succesfull call
description: Update an journalEntry after uccesfull call bij logging the response and the accountingSystemReference.
value:
accountingSystemReference: "OPBR-FIKO10000090"
response: {
"key": {
"$type": "finance_transaction_txnKey",
"cmpCode": "1HTM",
"code": "OPBR-FIKO",
"number": " 10000090"
},
"suppliedNumber": ""
}
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/JournalEntryPatchResponseBody"
examples:
JournalEntry updated successfully:
summary: JournalEntry updated successfully
description: The journalEntry was successfully updated in the database.
value:
journalEntryId: 5bbf5abc-4da5-4bb4-894e-a7f553789a20
security:
- default: []
x-auth-type: Application & Application User
x-throttling-tier: Unlimited
/Odata/BookingProcess:
get:
summary: Find booking processes.
@ -3316,6 +3705,16 @@ components:
- occurredOn
- type
- status
createdOn:
type: string
nullable: false
format: datetime
example: "2026-07-20T00:00:00Z"
updatedOn:
type: string
nullable: false
format: datetime
example: "2026-07-25T00:00:00Z"
TransactionItemsBulkRequestBody:
type: object
properties:
@ -3551,6 +3950,11 @@ components:
type: string
nullable: true
example: Adapt config
journalEntryId:
type: string
format: uuid
nullable: true
example: b51b377d-6c82-4c4f-9ed3-1068ab6e30c9
required:
- timestamp
- element
@ -3617,6 +4021,11 @@ components:
type: string
nullable: true
example: Adapt config
journalEntryId:
type: string
format: uuid
nullable: true
example: b51b377d-6c82-4c4f-9ed3-1068ab6e30c9
required:
- processingFailureId
- transactionItemId
@ -3681,6 +4090,11 @@ components:
format: uuid
nullable: true
example: 37a8095b-19db-498f-9349-5f37d21c5bdf
journalEntryId:
type: string
format: uuid
nullable: true
example: b51b377d-6c82-4c4f-9ed3-1068ab6e30c9
required:
- user
- timestamp
@ -3754,6 +4168,11 @@ components:
format: uuid
nullable: true
example: a3891560-3084-42cb-867c-f289e7cda47d
journalEntryId:
type: string
format: uuid
nullable: true
example: b51b377d-6c82-4c4f-9ed3-1068ab6e30c9
required:
- transactionAuditTrailId
- transactionItemId
@ -3761,6 +4180,89 @@ components:
- timestamp
- action
- status
JournalEntryGetResponseBody:
type: object
properties:
"@odata.context":
type: string
example: $metadata#JournalEntry
value:
type: array
items:
$ref: "#/components/schemas/JournalEntry"
JournalEntry:
type: object
properties:
"@odata.etag":
type: string
example: 333d6b7d-b709-3b89-89e0-bcb3d5fdddfc
journalEntryId:
type: string
format: uuid
example: 8871d779-512c-4d4d-9a8a-dee7c635179d
aggregationReference:
type: string
example: FIKO-123456
accountingSystemReference:
type: string
nullable: true
example: U4F-123456
data:
type: object
example: {}
responses:
type: object
nullable: true
example: {}
required:
- journalEntryId
- aggregationReference
- data
JournalEntryPostRequestBody:
type: object
properties:
aggregationReference:
type: string
example: FIKO-123456
accountingSystemReference:
type: string
nullable: true
example: U4F-123456
data:
type: object
example: {}
responses:
type: object
nullable: true
example: {}
required:
- aggregationReference
- data
JournalEntryPostResponseBody:
type: object
properties:
journalEntryId:
type: string
format: uuid
example: c97a8123-8d0e-41ea-9737-35843ebe2385
JournalEntryPatchRequestBody:
type: object
properties:
accountingSystemReference:
type: string
nullable: true
example: U4F-123456
responses:
type: object
nullable: true
example: {}
JournalEntryPatchResponseBody:
type: object
properties:
journalEntryId:
type: string
format: uuid
example: c97a8123-8d0e-41ea-9737-35843ebe2385
BookingProcessesGetResponseBody:
type: object
properties:

View File

@ -111,14 +111,6 @@ paths:
explode: false
required: false
description: Filter on most recent order status. 1 = concept, 2 = awaitingPayment, 3 = pendingPayment, 4 = paid, 5 = delivered, 6 = cancelled.
- in: query
name: issuedVoucherId
schema:
type: string
format: uuid
example: "b0a9f3c9-9b92-4f8c-b78d-6129be7218a6"
required: false
description: Filter on applied issuedVoucherId for the order.
responses:
"200":
description: OK
@ -777,6 +769,13 @@ paths:
example: 9e3363c8-e776-4675-b108-99b8c2e38eb6
required: false
description: The id of the orderLine
- in: query
name: issuedVoucherId
schema:
type: string
example: 9e3363c8-e776-4675-b108-99b8c2e38eb6
required: false
description: The id of the issuedVoucher
get:
summary: Find vouchers on the order
description: Find vouchers on the order

File diff suppressed because it is too large Load Diff

View File

@ -313,6 +313,7 @@ paths:
{
"productId": 24,
"parentProductId": null,
"needsVoucher": false,
"layerInfo": null,
"fikoArticleNumber": "1234",
"gboPackageTemplateId": "30901",
@ -429,6 +430,7 @@ paths:
{
"productId": 126,
"parentProductId": null,
"needsVoucher": false,
"layerInfo": {
"layerInfoId": 1,
"choiceKey": "isRenewable",
@ -537,6 +539,7 @@ paths:
{
"productId": 119,
"parentProductId": 126,
"needsVoucher": false,
"layerInfo": null,
"fikoArticleNumber": "1234",
"gboPackageTemplateId": "30001",
@ -652,6 +655,7 @@ paths:
{
"productId": 120,
"parentProductId": 126,
"needsVoucher": false,
"layerInfo": null,
"fikoArticleNumber": "1234",
"gboPackageTemplateId": "30001",
@ -779,6 +783,7 @@ paths:
{
"productId": 49,
"parentProductId": null,
"needsVoucher": false,
"layerInfo": {
"layerInfoId": 2,
"choiceKey": "regio",
@ -926,6 +931,7 @@ paths:
{
"productId": 109,
"parentProductId": 49,
"needsVoucher": false,
"layerInfo": {
"layerInfoId": 3,
"choiceKey": "allowedGboAgeProfiles",
@ -1076,6 +1082,7 @@ paths:
{
"productId": 114,
"parentProductId": 109,
"needsVoucher": false,
"layerInfo": {
"layerInfoId": 1,
"choiceKey": "isRenewable",
@ -1226,6 +1233,7 @@ paths:
{
"productId": 115,
"parentProductId": 109,
"needsVoucher": false,
"layerInfo": {
"layerInfoId": 1,
"choiceKey": "isRenewable",
@ -1377,6 +1385,7 @@ paths:
{
"productId": 116,
"parentProductId": 115,
"needsVoucher": false,
"layerInfo": null,
"fikoArticleNumber": "1234",
"gboPackageTemplateId": "33615",
@ -1526,6 +1535,7 @@ paths:
{
"productId": 117,
"parentProductId": 115,
"needsVoucher": false,
"layerInfo": null,
"fikoArticleNumber": "1234",
"gboPackageTemplateId": "33615",
@ -1679,6 +1689,7 @@ paths:
{
"productId": 112,
"parentProductId": 49,
"needsVoucher": false,
"layerInfo": {
"layerInfoId": 3,
"choiceKey": "allowedGboAgeProfiles",
@ -2150,6 +2161,10 @@ components:
parentProductId:
type: integer
example: 1
needsVoucher:
type: boolean
description: Indicates if the product needs a voucher (with this productId as requiredProductId) to be bought - if false, this is optional, but still allowed
example: false
layerInfo:
$ref: '#/components/schemas/LayerInfoResponse'
fikoArticleNumber:

View File

@ -313,6 +313,7 @@ paths:
{
"productId": 24,
"parentProductId": null,
"needsVoucher": false,
"layerInfo": null,
"fikoArticleNumber": "1234",
"gboPackageTemplateId": "30901",
@ -429,6 +430,7 @@ paths:
{
"productId": 126,
"parentProductId": null,
"needsVoucher": false,
"layerInfo": {
"layerInfoId": 1,
"choiceKey": "isRenewable",
@ -537,6 +539,7 @@ paths:
{
"productId": 119,
"parentProductId": 126,
"needsVoucher": false,
"layerInfo": null,
"fikoArticleNumber": "1234",
"gboPackageTemplateId": "30001",
@ -652,6 +655,7 @@ paths:
{
"productId": 120,
"parentProductId": 126,
"needsVoucher": false,
"layerInfo": null,
"fikoArticleNumber": "1234",
"gboPackageTemplateId": "30001",
@ -779,6 +783,7 @@ paths:
{
"productId": 49,
"parentProductId": null,
"needsVoucher": false,
"layerInfo": {
"layerInfoId": 2,
"choiceKey": "regio",
@ -926,6 +931,7 @@ paths:
{
"productId": 109,
"parentProductId": 49,
"needsVoucher": false,
"layerInfo": {
"layerInfoId": 3,
"choiceKey": "allowedGboAgeProfiles",
@ -1076,6 +1082,7 @@ paths:
{
"productId": 114,
"parentProductId": 109,
"needsVoucher": false,
"layerInfo": {
"layerInfoId": 1,
"choiceKey": "isRenewable",
@ -1226,6 +1233,7 @@ paths:
{
"productId": 115,
"parentProductId": 109,
"needsVoucher": false,
"layerInfo": {
"layerInfoId": 1,
"choiceKey": "isRenewable",
@ -1377,6 +1385,7 @@ paths:
{
"productId": 116,
"parentProductId": 115,
"needsVoucher": false,
"layerInfo": null,
"fikoArticleNumber": "1234",
"gboPackageTemplateId": "33615",
@ -1526,6 +1535,7 @@ paths:
{
"productId": 117,
"parentProductId": 115,
"needsVoucher": false,
"layerInfo": null,
"fikoArticleNumber": "1234",
"gboPackageTemplateId": "33615",
@ -1679,6 +1689,7 @@ paths:
{
"productId": 112,
"parentProductId": 49,
"needsVoucher": false,
"layerInfo": {
"layerInfoId": 3,
"choiceKey": "allowedGboAgeProfiles",
@ -2150,6 +2161,10 @@ components:
parentProductId:
type: integer
example: 1
needsVoucher:
type: boolean
description: Indicates if the product needs a voucher (with this productId as requiredProductId) to be bought - if false, this is optional, but still allowed
example: false
layerInfo:
$ref: '#/components/schemas/LayerInfoResponse'
fikoArticleNumber:

View File

@ -1,17 +1,17 @@
openapi: 3.0.1
info:
title: Service Engine APIs for HTM voucher for sales Touchpint
description: Service Engine APIs for HTM vouchers. These are NOT the CRUD APIs to the data hub. These ARE the api's for sales touchpoints.
title: Service Engine APIs for HTM voucher for sales Touchpoint
description: Service Engine APIs for HTM vouchers. These are NOT the CRUD APIs to the data hub. These ARE the APIs for sales touchpoints.
version: "1.0"
servers:
- url: https://services.acc.api.htm.nl/abt/abtvouchersTouchpoint/1.0
paths:
/issuedvouchers/{voucherCode}:
get:
summary: Get details of a voucher, that was issued for a specific touch point
summary: Get details of a voucher, that was issued for a specific touchpoint
description:
Get details of an issued voucher for a specific touch point. This means that only products that the calling touch point is allowed to see or sell
(i.e. has active sellingPeriods for touchPointId of the calling touch point) are returned.
Get details of an issued voucher for a specific touchpoint. This means that only products that the calling touchpoint is allowed to see or sell
(i.e. has active sellingPeriods for touchPointId of the calling touchpoint) are returned.
parameters:
- name: voucherCode
in: path
@ -28,98 +28,132 @@ paths:
content:
application/json:
schema:
$ref: "#/components/schemas/unavailable"
$ref: "#/components/schemas/salesTouchpointIssuedVoucherResponse"
examples:
Voucher for a product with required attributes:
summary: Voucher for a single product with required attributes
Voucher that grants a voucher-only product for free:
summary: Voucher that grants a voucher-only product for free
description: |-
Voucher that grants a product (that can only be acquired via a voucher) for free.
In this case, the voucher has an amountInclTax of 0. If the requiredProduct has a
non-zero sellingPrice (amountInclTax), the difference between sellingPrice and
voucher amountInclTax represents the granted discount. If the requiredProduct has
a sellingPrice of 0, no "discount" is shown; just the voucher's amountInclTax.
value:
{
"issuedVoucherId": "d4e5f6a7-b8c9-0d1e-2f3a-4b5c6d7e8f90",
"voucherCode": "VOUCHER123",
"voucherCode": "HTM-A7J-128-PYT",
"fromInclusive": "2024-10-04T00:00:00.000",
"untilInclusive": "2024-11-04T00:00:00.000",
"voucherStatus": { "voucherStatusId": 1, "name": "New" },
"voucherStatus": { "voucherStatusId": 2, "name": "issued" },
"product":
{
"productId": 263,
"productName": "HTM-80001",
"productDescription": "10 euro korting op Regiovrij maand.",
"productCategory":
{
"productCategoryId": 9,
"isTravelProduct": false,
"name": "Voucher",
},
"amountInclTax": -1000,
"productName": "Voucher Ooievaarspas-product AOW",
"productDescription": "Voucher voor AOW-ers in Den Haag met een Ooievaarspas, die ingewisseld kan worden voor het product \"Ooievaarspas voor AOW-ers in Den Haag\"",
"amountInclTax": 0,
"requiredProducts":
[
{
"productId": 126,
"productName": "HTM-30001",
"productDescription": "Regiovrij maand.",
"productId": 982,
"productName": "Ooievaarspas voor AOW-ers in Den Haag",
"productDescription": "Vrij reizen bij HTM voor Haagse AOW-gerechtigden met een Ooievaarspas.",
"productCategory":
{
"productCategoryId": 2,
"isTravelProduct": true,
"name": "Afgekocht reisrecht",
},
"amountInclTax": 0,
"_links":
{
"get_details":
{
"href": "https://api.integratielaag.nl/abt/touchpoint/1.0/products/126",
"href": "https://api.integratielaag.nl/abt/touchpoint/1.0/products/982",
"method": "GET",
},
},
},
],
"_links":
{
"get_details":
{
"href": "https://api.integratielaag.nl/abt/touchpoint/1.0/products/263",
"method": "GET",
},
},
]
},
"mandatoryCustomerDataItems":
[
{
"mandatoryCustomerDataItemId": 8,
"customerDataItem": "padBirthDate",
},
{
"mandatoryCustomerDataItemId": 4,
"customerDataItem": "emailAddress",
},
],
"customerDataItem": "padBirthDate"
}
]
}
Voucher for a whole order:
summary: Voucher for a whole order
Voucher that grants a hybrid product for a reduced price:
summary: Voucher that grants a hybrid product for a reduced price
description: |-
Voucher that grants a hybrid product (that can be acquired via voucher or via ordinary order flow)
for a reduced price. In this case, the voucher has an amountInclTax > 0, which dictates the (modified)
total amount that needs to be paid for said product. If the requiredProduct has a
non-zero sellingPrice (amountInclTax), the difference between sellingPrice and
voucher amountInclTax represents the granted discount. If the requiredProduct has
a sellingPrice of 0, no "discount" is shown; just the voucher's amountInclTax./
Specifically, in this example, the product "HTM 20% Korting" can be purchased for 1 euro instead of 5 euro.
value:
{
"issuedVoucherId": "d4e5f6a7-b8c9-0d1e-2f3a-4b5c6d7e8f90",
"voucherCode": "VOUCHER123",
"voucherCode": "HTM-A7J-128-PYT",
"fromInclusive": "2024-10-04T00:00:00.000",
"untilInclusive": "2024-11-04T00:00:00.000",
"voucherStatus": { "voucherStatusId": 1, "name": "New" },
"voucherStatus": { "voucherStatusId": 2, "name": "issued" },
"product":
{
"productId": 263,
"productName": "HTM-80002",
"productDescription": "10 euro korting op je gehele winkelmand.",
"productCategory":
{
"productCategoryId": 9,
"isTravelProduct": false,
"name": "Voucher",
},
"amountInclTax": -1000,
"requiredProducts": [],
"_links":
{
"get_details":
{
"href": "https://api.integratielaag.nl/abt/touchpoint/1.0/products/263",
"method": "GET",
},
},
"productId": 264,
"productName": "Kortingsvoucher HTM 20% Korting ",
"productDescription": "Voucher waarmee het product \"HTM 20% Korting\" voor een lagere prijs kan worden aangeschaft.",
"amountInclTax": 100,
"requiredProducts":
[
{
"productId": 984,
"productName": "HTM 20% Korting",
"productDescription": "Reis met 20% korting op je betaalpas bij HTM.",
"productCategory":
{
"productCategoryId": 1,
"isTravelProduct": true,
"name": "Kortingsabonnement",
},
"amountInclTax": 500,
"_links":
{
"get_details":
{
"href": "https://api.integratielaag.nl/abt/touchpoint/1.0/products/984",
"method": "GET",
},
},
},
]
},
"mandatoryCustomerDataItems": [],
"mandatoryCustomerDataItems": []
}
Voucher that grants a discount for a whole order:
summary: Voucher that grants a discount for a whole order
description: |-
Voucher that grants a discount for a whole order. In this case, no requiredProduct is specified, and
the voucher has an amountInclTax < 0. The (negative) amountInclTax dictates the value of the voucher,
that is subtracted from the total order value as a discount (with a minimum order total of 0).
value:
{
"issuedVoucherId": "d4e5f6a7-b8c9-0d1e-2f3a-4b5c6d7e8f90",
"voucherCode": "HTM-A7J-128-PYT",
"fromInclusive": "2024-10-04T00:00:00.000",
"untilInclusive": "2024-11-04T00:00:00.000",
"voucherStatus": { "voucherStatusId": 2, "name": "issued" },
"product":
{
"productId": 265,
"productName": "Voucher 10 euro korting",
"productDescription": "Voucher die 10 euro korting geeft op je gehele winkelmand.",
"amountInclTax": -1000,
"requiredProducts": []
},
"mandatoryCustomerDataItems": []
}
"403":
description: Forbidden
@ -137,20 +171,20 @@ paths:
"detail": "You do not have permission to access this resource.",
"instance": "/issuedvouchers",
}
"404":
description: Not found
"400":
description: Bad request
content:
application/problem+json:
schema:
$ref: "#/components/schemas/rfc9457"
examples:
Voucher not found:
summary: Voucher not found
Invalid voucher code:
summary: Invalid voucher code
value:
{
"type": "https://example.com/probs/not-found",
"title": "Voucher not found",
"detail": "The voucher with code VOUCHER123 does not exist.",
"type": "https://example.com/probs/bad-request",
"title": "Invalid voucher code",
"detail": "No valid voucher found for code VOUCHER123.",
"instance": "/issuedvouchers",
}
"500":
@ -176,6 +210,166 @@ components:
scheme: bearer
bearerFormat: JWT
schemas:
salesTouchpointIssuedVoucherResponse:
type: object
required:
- issuedVoucherId
- voucherCode
- fromInclusive
- untilInclusive
- voucherStatus
- product
- mandatoryCustomerDataItems
properties:
issuedVoucherId:
type: string
description: The unique (technical) identifier of the issued voucher instance.
example: "d4e5f6a7-b8c9-0d1e-2f3a-4b5c6d7e8f90"
voucherCode:
type: string
description: The voucher code that is shared with the customer and uniquely identifies this voucher.
example: "HTM-A7J-128-PYT"
fromInclusive:
type: string
format: date-time-offset
description: |-
The date and time the voucher becomes valid for redemption. This has nothing to do with
the allowed start date of a requiredProduct (for this, the existing order flow logic is applied).
example: "2024-10-04T00:00:00.000+02:00"
untilInclusive:
type: string
format: date-time-offset
description: |-
The date and time the voucher becomes invalid for redemption. This has nothing to do with
the maximum allowed start date of a requiredProduct (for this, the existing order flow logic is applied).
example: "2024-11-04T00:00:00.000+01:00"
voucherStatus:
type: object
required:
- voucherStatusId
- name
description: |-
The current status of the voucher. Can be any of the following:
- 1 = new
- 2 = issued
- 3 = redeemed
- 4 = revoked
- 5 = expired
- 6 = pendingRedemption
properties:
voucherStatusId:
type: integer
example: 2
name:
type: string
example: "issued"
product:
type: object
description: The "product" referencing the voucher definition that this voucher instance is based on
required:
- productId
- productName
- productDescription
- amountInclTax
properties:
productId:
type: integer
example: 263
productName:
type: string
example: Voucher Ooievaarspas-product AOW
productDescription:
type: string
example: |-
Voucher voor AOW-ers in Den Haag met een Ooievaarspas, die ingewisseld kan worden
voor het product "Ooievaarspas voor AOW-ers in Den Haag".
amountInclTax:
type: integer
description: |-
When combined with a requiredProduct, the (positive or zero) amountInclTax dictates
the (modified) total amount that needs to be paid for said product. When the requiredProduct
has a sellingPrice > 0, the difference between sellingPrice and voucher amountInclTax
represents the granted discount.
When not combined with a required product, the (negative) amountInclTax dictates
the value of the voucher, that is subtracted from the total order value as a discount
(with a minimum order total of 0).
example: 0
requiredProducts:
type: array
description: |-
Currently, only one requiredProduct is supported and thus, this array can contain at
most one element.
items:
type: object
required:
- productId
properties:
productId:
type: integer
example: 892
productName:
type: string
example: "Ooievaarspas voor AOW-ers in Den Haag"
productDescription:
type: string
example: "Vrij reizen bij HTM voor Haagse AOW-gerechtigden met een Ooievaarspas."
productCategory:
type: object
required:
- productCategoryId
- isTravelProduct
- name
properties:
productCategoryId:
type: integer
example: 2
isTravelProduct:
type: boolean
example: true
name:
type: string
example: "Afgekocht reisrecht"
amountInclTax:
type: integer
description: |-
Selling price of the product in cents (including tax, if applicable) that is
currently active for the calling touchpoint. When 0, the product is free of charge.
example: 0
imageReference:
type: string
description: Can be a URL or a base64 encoded image
example: https://www.htm.nl/nog-onbekende-productafbeelding
_links:
type: object
properties:
get_details:
type: object
description: Link to get more details for the product that this voucher instance applies to
properties:
href:
type: string
example: https://api.integratielaag.nl/abt/touchpoint/1.0/products/982
method:
type: string
example: GET
mandatoryCustomerDataItems:
type: array
description: |-
List of mandatory customer data items that are required to redeem this voucher.\
The values provided for these data items may be checked against allowed values as provided by the voucher issuer.
items:
type: object
required:
- mandatoryCustomerDataItemId
- customerDataItem
properties:
mandatoryCustomerDataItemId:
type: integer
example: 8
customerDataItem:
type: string
example: padBirthdate
unavailable:
type: object
rfc9457:

File diff suppressed because it is too large Load Diff

View File

@ -1553,6 +1553,9 @@ components:
- productCategoryId: 8
name: Saldo
isTravelProduct: true
- productCategoryId: 9
name: Voucher
isTravelProduct: false
type: array
items:
$ref: '#/components/schemas/productCategoryGetEntity'
@ -1811,7 +1814,7 @@ components:
- mandatoryCustomerDataItemId: 5
customerDataItem: address
- mandatoryCustomerDataItemId: 6
customerDataItem: phone
customerDataItem: voucher
- mandatoryCustomerDataItemId: 7
customerDataItem: ovPayToken
- mandatoryCustomerDataItemId: 8

View File

@ -46,6 +46,13 @@ paths:
when false, return only non-archived products (isArchived = false).
schema:
type: boolean
- name: onlyVouchers
in: query
required: false
description: |-
When omitted, defaults to false (i.e. return all normal products that are NOT voucher definitions); when true, return only voucher definitions
schema:
type: boolean
responses:
'200':
description: OK
@ -387,6 +394,7 @@ paths:
"fikoArticleNumber": "1234",
"isValid": true,
"isArchived": false,
"needsVoucher": false,
"gboPackageTemplateId": "30901",
"productName": "HTM pilot 90% korting",
"productDescription": "Reis met 90% korting gedurende de eerste F&F pilot!",
@ -474,6 +482,7 @@ paths:
"fikoArticleNumber": "1234",
"isValid": true,
"isArchived": false,
"needsVoucher": false,
"gboPackageTemplateId": "30901",
"productName": "HTM pilot 90% korting",
"productDescription": "Reis met 90% korting gedurende de eerste F&F pilot!",
@ -560,6 +569,7 @@ paths:
"fikoArticleNumber": "1234",
"isValid": true,
"isArchived": false,
"needsVoucher": false,
"gboPackageTemplateId": "30901",
"productName": "HTM pilot 90% korting",
"productDescription": "Reis met 90% korting gedurende de eerste F&F pilot!",
@ -680,6 +690,7 @@ paths:
"fikoArticleNumber": "1234",
"isValid": true,
"isArchived": false,
"needsVoucher": false,
"gboPackageTemplateId": "33610",
"productName": "HTM Regio Vrij DH73",
"productDescription": "Voor een vast bedrag onbeperkt reizen met EBS, HTM en RET in het gekozen gebied in de regio Rotterdam Den Haag.",
@ -866,6 +877,7 @@ paths:
"fikoArticleNumber": "1234",
"isValid": true,
"isArchived": false,
"needsVoucher": false,
"gboPackageTemplateId": "30901",
"tapConnectProductCode": null,
"productName": "HTM pilot 90% korting",
@ -1028,6 +1040,7 @@ paths:
"fikoArticleNumber": "1234",
"isValid": true,
"isArchived": false,
"needsVoucher": false,
"gboPackageTemplateId": "30901",
"tapConnectProductCode": null,
"productName": "HTM pilot 90% korting",
@ -1270,6 +1283,7 @@ paths:
"fikoArticleNumber": "1234",
"isValid": true,
"isArchived": false,
"needsVoucher": false,
"gboPackageTemplateId": "33610",
"tapConnectProductCode": null,
"productName": "HTM Regio Vrij DH73",
@ -1475,6 +1489,7 @@ paths:
"fikoArticleNumber": "1234",
"isValid": true,
"isArchived": false,
"needsVoucher": false,
"gboPackageTemplateId": null,
"tapConnectProductCode": null,
"productName": "IBAN wijzigen functioneel product",
@ -1672,6 +1687,7 @@ paths:
"fikoArticleNumber": "1234",
"isValid": true,
"isArchived": false,
"needsVoucher": false,
"gboPackageTemplateId": null,
"tapConnectProductCode": null,
"productName": "OV-pas saldo (1 eurocent)",
@ -2775,6 +2791,10 @@ components:
type: boolean
description: Indicates if the product is archived - if true, the Service Engine will prevent touchpoints from seeing this product
example: false
needsVoucher:
type: boolean
description: Indicates if the product needs a voucher (with this productId as requiredProductId) to be bought - if false, this is optional, but still allowed
example: false
gboPackageTemplateId:
type: string
example: '30901'
@ -3132,6 +3152,10 @@ components:
type: boolean
description: Indicates if the product is archived - if true, the Service Engine will prevent touchpoints from seeing this product
example: false
needsVoucher:
type: boolean
description: Indicates if the product needs a voucher (with this productId as requiredProductId) to be bought - if false, this is optional, but still allowed
example: false
gboPackageTemplateId:
type: string
description: >-
@ -3503,6 +3527,10 @@ components:
type: boolean
description: Indicates if the product is archived - if true, the Service Engine will prevent touchpoints from seeing this product
example: false
needsVoucher:
type: boolean
description: Indicates if the product needs a voucher (with this productId as requiredProductId) to be bought - if false, this is optional, but still allowed
example: false
gboPackageTemplateId:
type: string
description: >-

View File

@ -209,6 +209,12 @@ paths:
{
"issuedVoucherId": "e1f2g3h4-i5j6-k7l8-m9n0-o1p2q3r4s5t6",
"voucherCode": "VOUCHER12345",
"touchPointId": 23,
"metadata1": null,
"metadata2": null,
"metadata3": null,
"metadata4": null,
"metadata5": null,
"voucherStatusInstances":
[
{
@ -219,6 +225,7 @@ paths:
"name": "New",
},
"createdOn": "2024-10-04T12:34:56.000",
"reason": null,
},
{
"voucherStatusInstanceId": "b01412e6-70fb-4c05-9bef-e65bf2747ca1",
@ -228,6 +235,7 @@ paths:
"name": "Issued",
},
"createdOn": "2024-10-04T12:37:14.000",
"reason": "test reason"
},
],
"voucherClaims":
@ -248,6 +256,155 @@ paths:
],
"href": null,
}
post:
tags:
- Purchased Product
summary: Create a new purchased product.
description: Create a new purchased product. Only one single instance of `purchasedGboProducts`, `purchasedTapconnectTickets` or `issuedVouchers` can be specified at once.
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/unavailable"
examples:
Create Purchased GBO Product:
value:
{
"productId": 11,
"createdOn": "2024-10-04T12:34:56.000",
"lastUpdatedOn": "2024-10-04T12:34:56.000",
"purchasedProductResources":
[
{
"resourceNameId": 1,
"resourceIdentifier": "408eefa9-b393-4bb3-8439-b2e51833abc7",
},
{
"resourceNameId": 2,
"resourceIdentifier": "f809a6e1-1c8d-4f8e-8a6e-0d0b1e1e1e1e",
},
],
"purchasedGboProducts":
[
{
"salesTimestamp": "2024-10-04T12:34:56.000",
"refundTimestamp": "2024-10-04T12:34:56.000",
"fromInclusive": "2024-10-04T12:34:56.000",
"untilInclusive": "2024-10-04T12:34:56.000",
"packageTemplateId": "30003",
"xBot": "f15efe6f-7353-4968-b134-60ba6fc2da8b",
"xTat": "42efebf7-132e-4ee0-9cbb-4037a9a54ad8",
"xSpit": "d67b2f72-918a-4e6c-957d-a39ed9c9e16b",
"customerTokenId": "b6492322-c458-4857-9ac3-a109c1887b9f",
"ovPayTokenId": 13,
"createdOn": "2024-10-04T12:34:56.000",
"lastUpdatedOn": "2024-10-04T12:34:56.000",
"createdBy": "someuser",
"lastUpdatedBy": null
},
],
"purchasedTapconnectTickets": [],
"issuedVouchers": [],
}
Create Purchased TapConnect Ticket:
value:
{
"productId": 11,
"createdOn": "2024-10-04T12:34:56.000",
"lastUpdatedOn": "2024-10-04T12:34:56.000",
"purchasedProductResources":
[
{
"resourceNameId": 1,
"resourceIdentifier": "408eefa9-b393-4bb3-8439-b2e51833abc7",
},
{
"resourceNameId": 2,
"resourceIdentifier": "f809a6e1-1c8d-4f8e-8a6e-0d0b1e1e1e1e",
},
],
"purchasedGboProducts": [],
"purchasedTapconnectTickets":
[
{
"issuedAt": "2024-10-04T12:34:56.000",
"activatedAt": "2024-10-04T12:34:56.000",
"cancelledAt": "2024-10-04T12:34:56.000",
"ticketReference": "KJj43nejhbTxhr897287",
"createdOn": "2024-10-04T12:34:56.000",
"lastUpdatedOn": "2024-10-04T12:34:56.000",
"createdBy": "John Doe",
"lastUpdatedBy": "John Doe",
"externalDeviceId": "c5545584-04af-4c60-a955-d6a70baab848",
"serviceId": "HTM-1234-7654-8945",
"activateBefore": "2024-10-08",
"validityStart": "2024-10-05T12:34:56.000",
"validityEnd": "2024-10-06T12:34:56.000"
},
],
"issuedVouchers": [],
}
Create Issued Voucher:
value:
{
"productId": 11,
"createdOn": "2024-10-04T12:34:56.000",
"lastUpdatedOn": "2024-10-04T12:34:56.000",
"purchasedProductResources":
[
{
"resourceNameId": 1,
"resourceIdentifier": "408eefa9-b393-4bb3-8439-b2e51833abc7",
},
{
"resourceNameId": 2,
"resourceIdentifier": "f809a6e1-1c8d-4f8e-8a6e-0d0b1e1e1e1e",
},
],
"purchasedGboProducts": [],
"purchasedTapconnectTickets": [],
"issuedVouchers":
[
{
"voucherCode": "VOUCHER123",
"touchPointId": 23,
"metadata1": null,
"metadata2": null,
"metadata3": null,
"metadata4": null,
"metadata5": null,
"voucherStatusInstances": [
{
"voucherStatusId": 1,
"createdOn": "2024-10-04T12:34:56.000",
"reason": "test reason"
}
],
"voucherClaims": [
{
"mandatoryCustomerDataItemId": 8,
"value": "1999-12-31"
},
{
"mandatoryCustomerDataItemId": 4,
"value": "john.doe@example.com"
}
],
"fromInclusive": "2024-10-04T12:34:56.000",
"untilInclusive": "2025-10-04T12:34:56.000"
},
],
}
responses:
"201":
description: Created
content:
application/json:
schema:
$ref: "#/components/schemas/unavailable"
example:
{ "purchasedProductId": "a9b3dea5-fb8a-4b1e-9fe6-90cad31c0cfd" }
/purchasedproducts/{purchasedProductId}:
parameters:
- in: path
@ -382,6 +539,12 @@ paths:
{
"issuedVoucherId": "e1f2g3h4-i5j6-k7l8-m9n0-o1p2q3r4s5t6",
"voucherCode": "VOUCHER12345",
"touchPointId": 23,
"metadata1": null,
"metadata2": null,
"metadata3": null,
"metadata4": null,
"metadata5": null,
"voucherStatusInstances":
[
{
@ -389,12 +552,14 @@ paths:
"voucherStatus":
{ "voucherStatusId": 1, "name": "New" },
"createdOn": "2024-10-04T12:34:56.000",
"reason": null,
},
{
"voucherStatusInstanceId": "b01412e6-70fb-4c05-9bef-e65bf2747ca1",
"voucherStatus":
{ "voucherStatusId": 2, "name": "Issued" },
"createdOn": "2024-10-04T12:37:14.000",
"reason": "test reason"
},
],
"voucherClaims":
@ -1054,11 +1219,18 @@ paths:
example:
{
"voucherCode": "VOUCHER123",
"touchPointId": 23,
"metadata1": null,
"metadata2": null,
"metadata3": null,
"metadata4": null,
"metadata5": null,
"voucherStatusInstances":
[
{
"voucherStatusId": 1,
"createdOn": "2024-10-04T12:34:56.000",
"reason": "test reason"
},
],
"voucherClaims":
@ -1066,7 +1238,7 @@ paths:
{ "mandatoryCustomerDataItemId": 8, "value": "1999-12-31" },
{
"mandatoryCustomerDataItemId": 4,
"value": "vlad.harkonnen@househarkonnen.net",
"value": "john.doe@example.com",
},
],
"fromInclusive": "2024-10-04T12:34:56.000",
@ -1102,6 +1274,20 @@ paths:
format: uuid
example: 058a1af7-897f-45d5-b691-9cc9161e387f
description: The id of the purchased product.
- in: query
name: statusChangedAfter
required: false
description: Return only issued vouchers that have had their status changed after the given date and time.
schema:
type: string
format: date-time-offset
example: "2024-10-04T00:00:00.000Z"
- in: query
name: productId
schema:
type: integer
example: 1
description: The id of the product and the issuedVouchers related to it
- in: query
name: voucherCode
schema:
@ -1117,6 +1303,14 @@ paths:
explode: false
required: false
description: The moest recent status id of the voucher.
- in: query
name: orderBy
schema:
type: string
enum: [PurchasedProductCreatedOn, PurchasedProductLastUpdatedOn]
explode: false
required: false
description: The ordering of the issuedVouchers in the list
responses:
"200":
description: OK
@ -1132,6 +1326,13 @@ paths:
"issuedVoucherId": "d4e5f6a7-b8c9-0d1e-2f3a-4b5c6d7e8f90",
"purchasedProductId": "058a1af7-897f-45d5-b691-9cc9161e387f",
"voucherCode": "VOUCHER123",
"productId": 888,
"touchPointId": 23,
"metadata1": null,
"metadata2": null,
"metadata3": null,
"metadata4": null,
"metadata5": null,
"voucherStatusInstances":
[
{
@ -1139,12 +1340,14 @@ paths:
"voucherStatus":
{ "voucherStatusId": 1, "name": "New" },
"createdOn": "2024-10-04T12:34:56.000",
"reason": null,
},
{
"voucherStatusInstanceId": "b01412e6-70fb-4c05-9bef-e65bf2747ca1",
"voucherStatus":
{ "voucherStatusId": 2, "name": "Issued" },
"createdOn": "2024-10-04T12:37:14.000",
"reason": "test reason",
},
],
"voucherClaims":
@ -1163,7 +1366,7 @@ paths:
"mandatoryCustomerDataItemId": 4,
"customerDataItem": "emailAddress",
},
"value": "vlad.harkonnen@househarkonnen.net",
"value": "john.doe@example.com",
},
],
"fromInclusive": "2024-10-04T12:34:56.000",
@ -1191,9 +1394,16 @@ paths:
content:
application/json:
schema:
$ref: "#/components/schemas/unavailable"
example:
{ "voucherStatusId": 2, "createdOn": "2024-10-04T12:34:56.000" }
$ref: "#/components/schemas/PostIssuedVoucherStatusInstanceRequest"
examples:
newToIssued:
summary: Mark a voucher as issued
value:
{ "voucherStatusId": 2, "createdOn": "2024-10-04T12:34:56.000" }
issuedToRevokedWithReason:
summary: Mark a voucher as revoked with reason
value:
{ "voucherStatusId": 4, "createdOn": "2024-10-04T12:34:56.000", "reason": "Voucher revoked because it was redeemed by an unauthorized user." }
responses:
"200":
description: OK
@ -1227,6 +1437,7 @@ paths:
{ "voucherStatusId": 3, "name": "Redeemed" },
{ "voucherStatusId": 4, "name": "Revoked" },
{ "voucherStatusId": 5, "name": "Expired" },
{ "voucherStatusId": 6, "name": "PendingRedemption" },
],
}
components:
@ -1238,6 +1449,22 @@ components:
schemas:
unavailable:
type: object
PostIssuedVoucherStatusInstanceRequest:
type: object
required:
- voucherStatusId
- createdOn
properties:
voucherStatusId:
type: integer
example: 4
createdOn:
type: string
format: date-time
example: 2024-10-04T12:34:56.000
reason:
type: string
example: "Voucher revoked because it was redeemed by an unauthorized user."
PostPurchasedTapConnectTicketRequest:
type: object
required:
@ -1299,56 +1526,6 @@ components:
format: date-time
description: The date-time at which the ticket will become invalid for traveling. The ticket will not be valid after this date/time.
example: 2024-10-06T12:34:56.000
BulkResponseBody:
type: object
properties:
startTime:
type: string
format: date-time
example: 2025-02-14T05:32:47.0672237Z
status:
type: string
example: Running
clientTrackingId:
type: string
example: 08584620957189579629541919368CU00
callbackurl:
type: string
format: uri
example: https://services.api.htm.nl/purchasedproducts/responsestatus/runtime/webhooks/workflow/scaleUnits/prod-00/workflows/6fd466916c
retryAfter:
type: integer
example: 10
summary:
$ref: "#/components/schemas/summaryBody"
required:
- startTime
- status
- clientTrackingId
- callbackurl
- retryAfter
- summary
summaryBody:
type: object
properties:
summary:
type: object
properties:
created:
type: integer
example: 15
updated:
type: integer
example: 2
total:
type: integer
example: 17
required:
- created
- total
- updated
required:
- summary
rfc9457:
type: object
properties:

View File

@ -1,121 +0,0 @@
# Notification preferences transformation service - Python example
Reference implementation for the Service Engine `GET /customers/notificationpreferences` endpoint. It composes raw CRUD API data into the frontend-friendly response model from `SE-notifications.yaml`.
## Supplied contracts used
- `notifications-crud.yaml`
- `GET /notificationsubscriptions?customerProfileId={id}&expand=eventTypeChannel`
- `GET /notificationcategories?notificationCategoryId={id}&expand=eventTypeChannel`
- `customers-crud-v2.yaml`
- `GET /ovpaytokens?ovPayTokenId={id}`
- `GET /devices?customerProfileId={id}`
- `GET /customers?customerProfileId={id}` for person/email resource data
- `SE-notifications.yaml`
- `GET /customers/notificationpreferences`
## Runtime options
### Local FastAPI with real CRUD APIs
```powershell
.\.venv\Scripts\python.exe -m pip install -r requirements.txt
$env:NOTIFICATIONS_CRUD_BASE_URL="https://your-notifications-crud-api.example.nl"
$env:CUSTOMERS_CRUD_BASE_URL="https://your-customers-crud-api.example.nl"
.\.venv\Scripts\python.exe -m uvicorn app.entrypoints.fastapi_app:app --reload
```
Call:
```powershell
Invoke-RestMethod `
-Uri "http://127.0.0.1:8000/customers/notificationpreferences?deviceId=device-1" `
-Headers @{"X-HTM-CUSTOMER-PROFILE-ID-HEADER"="42"}
```
### Local FastAPI with mock data
Use mock mode when you want to demo or test the transformation without live CRUD APIs.
```powershell
$env:USE_MOCK_DATA="true"
$env:MOCK_SCENARIO="mixed"
.\.venv\Scripts\python.exe -m uvicorn app.entrypoints.fastapi_app:app --reload
```
Open:
```text
http://127.0.0.1:8000/docs
```
Health check:
```text
http://127.0.0.1:8000/health
```
Available mock scenarios:
| Scenario | Purpose |
|---|---|
| `mixed` | Default-on push, default-off email, plus customer deviations |
| `defaults_only` | No customer preferences, so only defaults are applied |
| `global_override` | Preference with `resourceIdentifier = null` applies to all resources |
| `inactive_subscription` | Subscription is returned but marked inactive |
### Azure Function
Use `app/entrypoints/azure_function.py` as the HTTP-trigger body. Keep the `app/services` and `app/domain` modules unchanged.
## Running automated tests
```powershell
.\.venv\Scripts\python.exe -m pytest
```
The tests cover:
- default-on channel without preference -> active
- default-on channel with preference -> inactive deviation
- default-off channel without preference -> inactive
- default-off channel with preference -> active deviation
- global preference where `resourceIdentifier = null`
- device filtering via `deviceId`
- inactive subscription still returned as inactive
## Design choice
The implementation deliberately separates:
- HTTP entrypoints: request/response only
- CRUD client: raw REST calls only
- Mock CRUD client: deterministic local/demo data
- Service layer: composition and transformation logic
- Domain models: frontend/SE response shape
This makes the same transformation reusable in Azure Functions, Container Apps, and tests.
## Business rule interpretation
For each customer subscription:
1. Load customer `NotificationSubscriptions` with `NotificationPreferences` expanded to `EventTypeChannel`.
2. Load the default category definition with all `EventTypes` and `EventTypeChannels`.
3. Load customer resources for each channel resource type:
- `resourceNameId = 8` -> `GET /devices?customerProfileId={id}`
- `resourceNameId = 4` -> `GET /customers?customerProfileId={id}` and use the embedded `person` email data
4. For every default `EventTypeChannel`, write out resources explicitly.
5. If the channel is default-on and no preference exists, resource is active.
6. If the channel is default-off and no preference exists, resource is inactive.
7. A `NotificationPreference` row is interpreted as a deviation:
- default-on + preference -> inactive unless explicit `isActive` says otherwise
- default-off + preference -> active unless explicit `isActive` says otherwise
8. A null `resourceIdentifier` applies to all resources for that EventTypeChannel.
## Production hardening points
- Add authentication propagation from the SE API to both CRUD APIs.
- Decide whether category defaults should be cached across requests.
- Decide whether missing category defaults should become `502 Bad Gateway`, partial data, or an empty category.
- Confirm whether the real `NotificationPreference` has explicit `isActive`; this example supports it, but also works when preference existence alone means deviation.

View File

@ -1,89 +0,0 @@
from __future__ import annotations
from typing import Any, Optional
import httpx
class CrudApiClient:
"""Thin async client around the ABT CRUD APIs.
Keep business/transformation logic out of this class. The client only knows how
to call the source endpoints and unwrap the collection names from the CRUD
contracts.
"""
def __init__(
self,
notifications_base_url: str,
customers_base_url: str,
timeout_seconds: float = 10.0,
):
self.notifications_base_url = notifications_base_url.rstrip("/")
self.customers_base_url = customers_base_url.rstrip("/")
self.timeout_seconds = timeout_seconds
async def _get(
self,
base_url: str,
path: str,
params: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
async with httpx.AsyncClient(base_url=base_url, timeout=self.timeout_seconds) as client:
response = await client.get(path, params=params)
response.raise_for_status()
return response.json()
# notifications-crud.yaml
async def get_notification_subscriptions(self, customer_profile_id: int) -> list[dict[str, Any]]:
payload = await self._get(
self.notifications_base_url,
"/notificationsubscriptions",
params={"customerProfileId": customer_profile_id, "expand": "eventTypeChannel"},
)
return payload.get("notificationSubscriptions", [])
async def get_notification_category_defaults(self, notification_category_id: int) -> Optional[dict[str, Any]]:
payload = await self._get(
self.notifications_base_url,
"/notificationcategories",
params={"notificationCategoryId": notification_category_id, "expand": "eventTypeChannel"},
)
categories = payload.get("notificationCategories", [])
return categories[0] if categories else None
# customers-crud-v2.yaml
async def get_ovpay_token(self, ovpay_token_id: Optional[int]) -> Optional[dict[str, Any]]:
if ovpay_token_id is None:
return None
payload = await self._get(
self.customers_base_url,
"/ovpaytokens",
params={"ovPayTokenId": ovpay_token_id},
)
tokens = payload.get("ovPayTokens", [])
return tokens[0] if tokens else None
async def get_devices(self, customer_profile_id: int) -> list[dict[str, Any]]:
payload = await self._get(
self.customers_base_url,
"/devices",
params={"customerProfileId": customer_profile_id},
)
return payload.get("devices", [])
async def get_persons(self, customer_profile_id: int) -> list[dict[str, Any]]:
"""Return customer/person resources used for email-like channels.
customers-crud-v2.yaml exposes person details through GET /customers,
filtered by customerProfileId. For the transformation service we normalize
the matching customer profile into one resource with the customer's email.
"""
payload = await self._get(
self.customers_base_url,
"/customers",
params={"customerProfileId": customer_profile_id},
)
return payload.get("customers", [])
# Backwards-compatible alias used by the service type hints.
NotificationsCrudClient = CrudApiClient

View File

@ -1,162 +0,0 @@
from __future__ import annotations
from copy import deepcopy
from typing import Any, Optional
class MockCrudClient:
"""Deterministic in-memory CRUD client for local demos and tests.
It implements the same methods as CrudApiClient, so the service layer does
not know whether data comes from real CRUD APIs or from mock fixtures.
"""
def __init__(self, scenario: str = "mixed"):
self.scenario = scenario
async def get_notification_subscriptions(self, customer_profile_id: int) -> list[dict[str, Any]]:
subscriptions = deepcopy(MOCK_SUBSCRIPTIONS.get(self.scenario, MOCK_SUBSCRIPTIONS["mixed"]))
for subscription in subscriptions:
subscription["customerProfileId"] = customer_profile_id
return subscriptions
async def get_notification_category_defaults(self, notification_category_id: int) -> Optional[dict[str, Any]]:
return deepcopy(MOCK_CATEGORIES.get(notification_category_id))
async def get_ovpay_token(self, ovpay_token_id: Optional[int]) -> Optional[dict[str, Any]]:
if ovpay_token_id is None:
return None
return deepcopy(MOCK_OVPAY_TOKENS.get(ovpay_token_id, {"ovPayTokenId": ovpay_token_id, "alias": None}))
async def get_devices(self, customer_profile_id: int) -> list[dict[str, Any]]:
return deepcopy(MOCK_DEVICES)
async def get_persons(self, customer_profile_id: int) -> list[dict[str, Any]]:
return deepcopy(MOCK_CUSTOMERS)
MOCK_CATEGORIES: dict[int, dict[str, Any]] = {
1: {
"notificationCategoryId": 1,
"name": "Reizen",
"groupName": "Mijn passen",
"eventTypes": [
{
"eventTypeId": 10,
"name": "OVPAY_CHECK_IN",
"subName": "Check-in",
"prettyName": "Check-in bevestiging",
"eventTypeChannels": [
{
"eventTypeChannelId": "etc-push-default-on",
"channel": {
"channelId": 1,
"name": "Push",
"resourceName": {"resourceNameId": 8, "name": "devices"},
},
"isDefault": True,
"isMandatory": False,
},
{
"eventTypeChannelId": "etc-email-default-off",
"channel": {
"channelId": 2,
"name": "E-mail",
"resourceName": {"resourceNameId": 4, "name": "customers"},
},
"isDefault": False,
"isMandatory": False,
},
],
},
{
"eventTypeId": 20,
"name": "SERVICE_MESSAGE",
"subName": "Service",
"prettyName": "Servicebericht",
"eventTypeChannels": [
{
"eventTypeChannelId": "etc-email-mandatory",
"channel": {
"channelId": 2,
"name": "E-mail",
"resourceName": {"resourceNameId": 4, "name": "customers"},
},
"isDefault": True,
"isMandatory": True,
}
],
},
],
}
}
MOCK_SUBSCRIPTIONS: dict[str, list[dict[str, Any]]] = {
# Mixed scenario demonstrates both default behavior and customer deviations.
"mixed": [
{
"notificationSubscriptionId": "sub-mixed-1",
"notificationCategoryId": 1,
"notificationCategory": {"notificationCategoryId": 1, "name": "Reizen", "groupName": "Mijn passen"},
"customerProfileId": 42,
"ovPayTokenId": 112,
"subscriptionActivities": [{"timestamp": "2026-04-01T10:00:00Z", "isActive": True}],
"notificationPreferences": [
# Default push=true: this row opts out only device-2.
{"notificationPreferenceId": "pref-push-device-2-off", "eventTypeChannelId": "etc-push-default-on", "resourceIdentifier": "device-2"},
# Default email=false: this row opts in the customer's email resource.
{"notificationPreferenceId": "pref-email-on", "eventTypeChannelId": "etc-email-default-off", "resourceIdentifier": "42"},
],
}
],
# No preferences: output should be entirely based on category defaults.
"defaults_only": [
{
"notificationSubscriptionId": "sub-defaults-1",
"notificationCategoryId": 1,
"notificationCategory": {"notificationCategoryId": 1, "name": "Reizen", "groupName": "Mijn passen"},
"customerProfileId": 42,
"ovPayTokenId": 112,
"subscriptionActivities": [{"timestamp": "2026-04-01T10:00:00Z", "isActive": True}],
"notificationPreferences": [],
}
],
# Global preference: null resourceIdentifier applies the deviation to all resources.
"global_override": [
{
"notificationSubscriptionId": "sub-global-1",
"notificationCategoryId": 1,
"notificationCategory": {"notificationCategoryId": 1, "name": "Reizen", "groupName": "Mijn passen"},
"customerProfileId": 42,
"ovPayTokenId": 112,
"subscriptionActivities": [{"timestamp": "2026-04-01T10:00:00Z", "isActive": True}],
"notificationPreferences": [
{"notificationPreferenceId": "pref-all-push-off", "eventTypeChannelId": "etc-push-default-on", "resourceIdentifier": None}
],
}
],
# Subscription inactive while preferences are still written out for the frontend.
"inactive_subscription": [
{
"notificationSubscriptionId": "sub-inactive-1",
"notificationCategoryId": 1,
"notificationCategory": {"notificationCategoryId": 1, "name": "Reizen", "groupName": "Mijn passen"},
"customerProfileId": 42,
"ovPayTokenId": 112,
"subscriptionActivities": [{"timestamp": "2026-04-01T10:00:00Z", "isActive": False}],
"notificationPreferences": [],
}
],
}
MOCK_OVPAY_TOKENS = {
112: {"ovPayTokenId": 112, "alias": "Mijn betaalpas"},
}
MOCK_DEVICES = [
{"deviceId": "device-1", "alias": "Mijn iPhone"},
{"deviceId": "device-2", "alias": "Werktelefoon"},
]
MOCK_CUSTOMERS = [
{"customerProfileId": 42, "person": {"emailAddress": "klant@example.nl"}},
]

View File

@ -1,60 +0,0 @@
from __future__ import annotations
from typing import Any, Optional
from pydantic import BaseModel, Field
class Resource(BaseModel):
resourceName: str
resourceIdentifier: str
alias: Optional[str] = None
isActive: bool
class EventTypeChannelPreference(BaseModel):
eventTypeChannelId: str
channelId: int
name: str
isMandatory: bool = False
resources: list[Resource] = Field(default_factory=list)
class EventTypePreference(BaseModel):
eventTypeId: int
name: str
subName: Optional[str] = None
prettyName: str
eventTypeChannels: list[EventTypeChannelPreference] = Field(default_factory=list)
class OvPayToken(BaseModel):
ovPayTokenId: int
alias: Optional[str] = None
class NotificationSubscriptionPreference(BaseModel):
notificationSubscriptionId: str
customerProfileId: int
ovPayToken: Optional[OvPayToken] = None
isActive: bool
eventTypes: list[EventTypePreference] = Field(default_factory=list)
class NotificationCategoryPreference(BaseModel):
notificationCategoryId: int
name: str
groupName: Optional[str] = None
notificationSubscriptions: list[NotificationSubscriptionPreference] = Field(default_factory=list)
class CustomerNotificationPreferencesResponse(BaseModel):
notificationCategories: list[NotificationCategoryPreference] = Field(default_factory=list)
def latest_activity_is_active(subscription: dict[str, Any]) -> bool:
"""CRUD returns subscriptionActivities; the most recent/current one determines active state."""
activities = subscription.get("subscriptionActivities") or []
if not activities:
return False
# CRUD activityLimit defaults to 1; when more are supplied, sort defensively on timestamp.
latest = sorted(activities, key=lambda a: a.get("timestamp", ""), reverse=True)[0]
return bool(latest.get("isActive"))

View File

@ -1,17 +0,0 @@
from __future__ import annotations
import azure.functions as func
from app.service_factory import create_notification_preferences_service
async def main(req: func.HttpRequest) -> func.HttpResponse:
customer_profile_id = req.headers.get("X-HTM-CUSTOMER-PROFILE-ID-HEADER")
if not customer_profile_id:
return func.HttpResponse("Missing X-HTM-CUSTOMER-PROFILE-ID-HEADER", status_code=400)
service = create_notification_preferences_service()
result = await service.get_customer_notification_preferences(
customer_profile_id=int(customer_profile_id),
device_id=req.params.get("deviceId"),
)
return func.HttpResponse(result.model_dump_json(exclude_none=True), mimetype="application/json", status_code=200)

View File

@ -1,25 +0,0 @@
from __future__ import annotations
from fastapi import FastAPI, Header, Query
from app.domain.models import CustomerNotificationPreferencesResponse
from app.service_factory import create_notification_preferences_service
from app.settings import settings
app = FastAPI(title="SE Notifications example")
@app.get("/health")
async def health():
return {"status": "ok", "useMockData": settings.use_mock_data, "mockScenario": settings.mock_scenario}
@app.get("/customers/notificationpreferences", response_model=CustomerNotificationPreferencesResponse)
async def get_customer_notification_preferences(
x_htm_customer_profile_id_header: int = Header(..., alias="X-HTM-CUSTOMER-PROFILE-ID-HEADER"),
device_id: str | None = Query(default=None, alias="deviceId"),
):
service = create_notification_preferences_service()
return await service.get_customer_notification_preferences(
customer_profile_id=x_htm_customer_profile_id_header,
device_id=device_id,
)

View File

@ -1,18 +0,0 @@
from __future__ import annotations
from app.clients.crud_client import CrudApiClient
from app.clients.mock_crud_client import MockCrudClient
from app.services.notification_preferences_service import NotificationPreferencesService
from app.settings import settings
def create_notification_preferences_service() -> NotificationPreferencesService:
if settings.use_mock_data:
return NotificationPreferencesService(MockCrudClient(settings.mock_scenario))
return NotificationPreferencesService(
CrudApiClient(
notifications_base_url=settings.notifications_crud_base_url,
customers_base_url=settings.customers_crud_base_url,
)
)

View File

@ -1,228 +0,0 @@
from __future__ import annotations
from collections import defaultdict
from typing import Any, Optional
from app.clients.crud_client import NotificationsCrudClient
from app.domain.models import (
CustomerNotificationPreferencesResponse,
EventTypeChannelPreference,
EventTypePreference,
NotificationCategoryPreference,
NotificationSubscriptionPreference,
OvPayToken,
Resource,
latest_activity_is_active,
)
RESOURCE_DEVICES_ID = 8
RESOURCE_PERSONS_ID = 4
class NotificationPreferencesService:
def __init__(self, crud_client: NotificationsCrudClient):
self.crud_client = crud_client
async def get_customer_notification_preferences(
self,
customer_profile_id: int,
device_id: Optional[str] = None,
) -> CustomerNotificationPreferencesResponse:
subscriptions = await self.crud_client.get_notification_subscriptions(customer_profile_id)
categories_by_id: dict[int, NotificationCategoryPreference] = {}
# Small in-request caches avoid repeated calls for the same category/resources.
default_category_cache: dict[int, dict[str, Any]] = {}
resource_cache: dict[int, list[dict[str, Any]]] = {}
for subscription in subscriptions:
category_id = _extract_category_id(subscription)
default_category = default_category_cache.get(category_id)
if default_category is None:
default_category = await self.crud_client.get_notification_category_defaults(category_id)
if default_category is None:
# Defensive choice: skip orphaned subscriptions rather than returning incomplete UI data.
continue
default_category_cache[category_id] = default_category
category_response = categories_by_id.setdefault(
category_id,
NotificationCategoryPreference(
notificationCategoryId=category_id,
name=default_category.get("name") or subscription.get("notificationCategory", {}).get("name"),
groupName=default_category.get("groupName") or subscription.get("notificationCategory", {}).get("groupName"),
),
)
token_payload = await self.crud_client.get_ovpay_token(subscription.get("ovPayTokenId"))
token = _to_ovpay_token(token_payload, subscription.get("ovPayTokenId"))
subscription_response = NotificationSubscriptionPreference(
notificationSubscriptionId=subscription["notificationSubscriptionId"],
customerProfileId=subscription["customerProfileId"],
ovPayToken=token,
isActive=latest_activity_is_active(subscription),
)
preferences_by_event_type_channel = _index_preferences(subscription)
for event_type in default_category.get("eventTypes", []):
event_type_response = EventTypePreference(
eventTypeId=event_type["eventTypeId"],
name=event_type["name"],
subName=event_type.get("subName"),
prettyName=event_type.get("prettyName") or event_type["name"],
)
for event_type_channel in event_type.get("eventTypeChannels", []):
channel = event_type_channel.get("channel", {})
resource_name = channel.get("resourceName", {})
resource_name_id = resource_name.get("resourceNameId")
resource_name_text = resource_name.get("name") or channel.get("name")
if resource_name_id not in resource_cache:
resource_cache[resource_name_id] = await self._load_resources(resource_name_id, customer_profile_id)
source_resources = resource_cache[resource_name_id]
if device_id and resource_name_id == RESOURCE_DEVICES_ID:
source_resources = [r for r in source_resources if str(_resource_identifier(r)) == str(device_id)]
if not source_resources:
# SE swagger states a deviceId filter returns only ETCs with resources for that device.
continue
matching_preferences = preferences_by_event_type_channel.get(
str(event_type_channel["eventTypeChannelId"]), []
)
resources = self._normalize_resources(
resource_name=resource_name_text,
event_type_channel=event_type_channel,
source_resources=source_resources,
preferences=matching_preferences,
)
event_type_response.eventTypeChannels.append(
EventTypeChannelPreference(
eventTypeChannelId=event_type_channel["eventTypeChannelId"],
channelId=channel["channelId"],
name=channel["name"],
isMandatory=bool(event_type_channel.get("isMandatory")),
resources=resources,
)
)
if event_type_response.eventTypeChannels:
subscription_response.eventTypes.append(event_type_response)
category_response.notificationSubscriptions.append(subscription_response)
return CustomerNotificationPreferencesResponse(notificationCategories=list(categories_by_id.values()))
async def _load_resources(self, resource_name_id: int, customer_profile_id: int) -> list[dict[str, Any]]:
if resource_name_id == RESOURCE_DEVICES_ID:
return await self.crud_client.get_devices(customer_profile_id)
if resource_name_id == RESOURCE_PERSONS_ID:
return await self.crud_client.get_persons(customer_profile_id)
return []
def _normalize_resources(
self,
resource_name: str,
event_type_channel: dict[str, Any],
source_resources: list[dict[str, Any]],
preferences: list[dict[str, Any]],
) -> list[Resource]:
default_active = bool(event_type_channel.get("isDefault"))
if not preferences:
return [
Resource(
resourceName=resource_name,
resourceIdentifier=str(_resource_identifier(resource)),
alias=_resource_alias(resource),
isActive=default_active,
)
for resource in source_resources
]
# Preference rows are exceptions/deviations. A null resourceIdentifier means the exception applies to all resources.
preference_by_resource: dict[str, dict[str, Any]] = {}
global_preference: Optional[dict[str, Any]] = None
for preference in preferences:
pref_resource_id = preference.get("resourceIdentifier")
if pref_resource_id is None:
global_preference = preference
else:
preference_by_resource[str(pref_resource_id)] = preference
normalized: list[Resource] = []
for resource in source_resources:
resource_id = str(_resource_identifier(resource))
preference = preference_by_resource.get(resource_id) or global_preference
if preference is None:
is_active = default_active
else:
# The CRUD example only shows the existence of a preference row, no explicit isActive.
# Therefore: for default-on channels a row means opted out; for default-off a row means opted in.
is_active = bool(preference.get("isActive", not default_active))
normalized.append(
Resource(
resourceName=resource_name,
resourceIdentifier=resource_id,
alias=_resource_alias(resource),
isActive=is_active,
)
)
return normalized
def _extract_category_id(subscription: dict[str, Any]) -> int:
if subscription.get("notificationCategoryId") is not None:
return int(subscription["notificationCategoryId"])
return int(subscription["notificationCategory"]["notificationCategoryId"])
def _index_preferences(subscription: dict[str, Any]) -> dict[str, list[dict[str, Any]]]:
result: dict[str, list[dict[str, Any]]] = defaultdict(list)
for preference in subscription.get("notificationPreferences", []) or []:
event_type_channel_id = preference.get("eventTypeChannelId")
if isinstance(event_type_channel_id, dict):
event_type_channel_id = event_type_channel_id.get("eventTypeChannelId")
if event_type_channel_id is None and isinstance(preference.get("eventTypeChannel"), dict):
event_type_channel_id = preference["eventTypeChannel"].get("eventTypeChannelId")
if event_type_channel_id is not None:
result[str(event_type_channel_id)].append(preference)
return result
def _to_ovpay_token(payload: Optional[dict[str, Any]], fallback_id: Optional[int]) -> Optional[OvPayToken]:
if fallback_id is None:
return None
# Support both {ovPayToken: {...}} and direct {...} shapes.
token = (payload or {}).get("ovPayToken", payload or {})
return OvPayToken(ovPayTokenId=token.get("ovPayTokenId", fallback_id), alias=token.get("alias"))
def _resource_identifier(resource: dict[str, Any]) -> Any:
# Device resources use deviceId. Customer/person email resources are normalized
# to customerProfileId because customers-crud-v2 exposes person under /customers.
return (
resource.get("resourceIdentifier")
or resource.get("deviceId")
or resource.get("personId")
or resource.get("customerProfileId")
or resource.get("id")
)
def _resource_alias(resource: dict[str, Any]) -> Optional[str]:
person = resource.get("person") or {}
return (
resource.get("alias")
or resource.get("emailAddress")
or resource.get("emailAddresses")
or resource.get("email")
or person.get("emailAddress")
or person.get("emailAddresses")
or resource.get("name")
)

View File

@ -1,13 +0,0 @@
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
notifications_crud_base_url: str = "http://localhost:8001"
customers_crud_base_url: str = "http://localhost:8002"
# local development aid: set USE_MOCK_DATA=true to run without CRUD APIs
use_mock_data: bool = False
mock_scenario: str = "mixed"
settings = Settings()

View File

@ -1,8 +0,0 @@
fastapi==0.115.6
uvicorn[standard]==0.34.0
httpx==0.28.1
pydantic>=2.12.0
pydantic-settings>=2.7.1
azure-functions==1.21.3
pytest==8.3.4
pytest-asyncio==0.25.2

View File

@ -1,69 +0,0 @@
import pytest
from app.clients.mock_crud_client import MockCrudClient
from app.services.notification_preferences_service import NotificationPreferencesService
def _channels(result):
subscription = result.notificationCategories[0].notificationSubscriptions[0]
return {
channel.eventTypeChannelId: channel
for event_type in subscription.eventTypes
for channel in event_type.eventTypeChannels
}
@pytest.mark.asyncio
async def test_mixed_scenario_applies_default_on_and_default_off_deviations():
service = NotificationPreferencesService(MockCrudClient("mixed"))
result = await service.get_customer_notification_preferences(42)
channels = _channels(result)
push_resources = channels["etc-push-default-on"].resources
assert [(r.resourceIdentifier, r.isActive) for r in push_resources] == [
("device-1", True),
("device-2", False),
]
email_resources = channels["etc-email-default-off"].resources
assert [(r.resourceIdentifier, r.isActive) for r in email_resources] == [("42", True)]
@pytest.mark.asyncio
async def test_defaults_only_scenario_writes_all_resources_from_defaults():
service = NotificationPreferencesService(MockCrudClient("defaults_only"))
result = await service.get_customer_notification_preferences(42)
channels = _channels(result)
assert [r.isActive for r in channels["etc-push-default-on"].resources] == [True, True]
assert [r.isActive for r in channels["etc-email-default-off"].resources] == [False]
assert channels["etc-email-mandatory"].isMandatory is True
assert [r.isActive for r in channels["etc-email-mandatory"].resources] == [True]
@pytest.mark.asyncio
async def test_global_override_applies_to_all_resources_when_resource_identifier_is_null():
service = NotificationPreferencesService(MockCrudClient("global_override"))
result = await service.get_customer_notification_preferences(42)
channels = _channels(result)
assert [r.isActive for r in channels["etc-push-default-on"].resources] == [False, False]
@pytest.mark.asyncio
async def test_device_filter_returns_only_requested_device_for_device_channels():
service = NotificationPreferencesService(MockCrudClient("mixed"))
result = await service.get_customer_notification_preferences(42, device_id="device-1")
channels = _channels(result)
assert [(r.resourceIdentifier, r.isActive) for r in channels["etc-push-default-on"].resources] == [("device-1", True)]
@pytest.mark.asyncio
async def test_inactive_subscription_is_visible_but_marked_inactive():
service = NotificationPreferencesService(MockCrudClient("inactive_subscription"))
result = await service.get_customer_notification_preferences(42)
subscription = result.notificationCategories[0].notificationSubscriptions[0]
assert subscription.isActive is False
assert subscription.eventTypes

View File

@ -1,83 +0,0 @@
import pytest
from app.services.notification_preferences_service import NotificationPreferencesService
class FakeCrudClient:
async def get_notification_subscriptions(self, customer_profile_id: int):
return [
{
"notificationSubscriptionId": "sub-1",
"notificationCategory": {"notificationCategoryId": 1, "name": "Reizen", "groupName": "Mijn passen"},
"customerProfileId": customer_profile_id,
"ovPayTokenId": 112,
"subscriptionActivities": [{"timestamp": "2026-01-01T00:00:00Z", "isActive": True}],
"notificationPreferences": [
# Default push=true, this row switches device-2 off only.
{"notificationPreferenceId": "pref-1", "eventTypeChannelId": "etc-push", "resourceIdentifier": "device-2"},
# Default email=false, this row switches customer email on.
{"notificationPreferenceId": "pref-2", "eventTypeChannelId": "etc-email", "resourceIdentifier": "42"},
],
}
]
async def get_notification_category_defaults(self, notification_category_id: int):
return {
"notificationCategoryId": 1,
"name": "Reizen",
"groupName": "Mijn passen",
"eventTypes": [
{
"eventTypeId": 2,
"name": "GBO",
"subName": "CI",
"prettyName": "Normal Check-in",
"eventTypeChannels": [
{
"eventTypeChannelId": "etc-push",
"channel": {"channelId": 1, "name": "push", "resourceName": {"resourceNameId": 8, "name": "devices"}},
"isDefault": True,
"isMandatory": False,
},
{
"eventTypeChannelId": "etc-email",
"channel": {"channelId": 2, "name": "email", "resourceName": {"resourceNameId": 4, "name": "customers"}},
"isDefault": False,
"isMandatory": False,
},
],
}
],
}
async def get_ovpay_token(self, ovpay_token_id):
return {"ovPayToken": {"ovPayTokenId": ovpay_token_id, "alias": "Mijn ING bankpas"}}
async def get_devices(self, customer_profile_id: int):
return [
{"deviceId": "device-1", "alias": "Mijn iPhone"},
{"deviceId": "device-2", "alias": "Mijn Pixel"},
]
async def get_persons(self, customer_profile_id: int):
return [{"customerProfileId": 42, "emailAddress": "customer@example.nl"}]
@pytest.mark.asyncio
async def test_normalizes_defaults_and_customer_exceptions():
result = await NotificationPreferencesService(FakeCrudClient()).get_customer_notification_preferences(42)
event_type = result.notificationCategories[0].notificationSubscriptions[0].eventTypes[0]
push_resources = event_type.eventTypeChannels[0].resources
assert [r.isActive for r in push_resources] == [True, False]
email_resources = event_type.eventTypeChannels[1].resources
assert email_resources[0].isActive is True
@pytest.mark.asyncio
async def test_device_filter_only_returns_that_device_resource():
result = await NotificationPreferencesService(FakeCrudClient()).get_customer_notification_preferences(42, device_id="device-1")
event_type = result.notificationCategories[0].notificationSubscriptions[0].eventTypes[0]
assert len(event_type.eventTypeChannels[0].resources) == 1
assert event_type.eventTypeChannels[0].resources[0].resourceIdentifier == "device-1"

0
src/trip.json Normal file
View File

151
trip2.json Normal file
View File

@ -0,0 +1,151 @@
{ "tripId": 796727186, "tripVersion": 1, "tripStatus": "COMPLETE", "organisationIcon": "/icons/organisations/3.svg", "organisationId": "3", "organisationName": "HTM", "supersededByTripIds": [], "supersedesTripIds": [], "products": [ {
"productTemplateId": "30901", "productCommercialName": { "items": [ {
"key": "nl-NL", "value": "HTM 90% Korting" },
{
"key": "en-US", "value": "HTM 90% Discount" }
]
},
"productIcon": "001.jpg", "purchaseDateTime": "2026-04-02T12:38:28+02:00", "organisationName": "ProductRetailer HTM", "appliedValue": -125, "organisationId": "3", "category": "SALES", "typesOfFareProduct": [ {
"privateCode": "discount" }
],
"xspit": "821ddb0d-4ea7-4a0d-84aa-bb7facce15d1" },
{
"productTemplateId": "N/A", "purchaseDateTime": "2025-07-09T12:39:27+02:00", "appliedValue": 139, "category": "SALES", "typesOfFareProduct": [ {
"privateCode": "payg" }
],
"xspit": "7618337f-c986-4bef-990d-f5a6b47292b8" },
{
"productTemplateId": "1:2000", "productCommercialName": { "items": [ {
"key": "nl-NL", "value": "Basis Bus Tram Metro" },
{
"key": "en-US", "value": "BTM full fare" }
]
},
"validFrom": "2000-01-01T01:00:00+01:00", "validUntil": "2100-01-01T00:59:59+01:00", "weight": 116, "appliedMetric": "FARE", "appliedValue": 116, "category": "INTERNAL", "typesOfFareProduct": [ {
"privateCode": "payg" }
],
"xspit": "7618337f-c986-4bef-990d-f5a6b47292b8", "xpit": "f4256df6-b767-460f-9880-f7597bd7f3f2" },
{
"productTemplateId": "2:2000", "productCommercialName": { "items": [ {
"key": "nl-NL", "value": "Basis Bus Tram Metro" },
{
"key": "en-US", "value": "BTM full fare" }
]
},
"validFrom": "2000-01-01T01:00:00+01:00", "validUntil": "2100-01-01T00:59:59+01:00", "weight": 23, "appliedMetric": "FARE", "appliedValue": 23, "category": "INTERNAL", "typesOfFareProduct": [ {
"privateCode": "payg" }
],
"xspit": "7618337f-c986-4bef-990d-f5a6b47292b8", "xpit": "f4256df6-b767-460f-9880-f7597bd7f3f2" },
{
"productTemplateId": "3:30901", "productCommercialName": { "items": [ {
"key": "nl-NL", "value": "HTM 90% Korting" },
{
"key": "en-US", "value": "HTM 90% Discount" },
{
"key": "nl-NL", "value": "Reis je regelmatig met HTM? Activeer dan HTM 90% Korting op je betaalpas of credit card en reis een week lang met korting!" },
{
"key": "en-US", "value": "Are you a regular traveler? Activate HTM 90% discount on your EMV card!" }
]
},
"validFrom": "2026-04-03T04:00:00+02:00", "validUntil": "2031-04-03T04:00:00+02:00", "organisationName": "Product Owner HTM", "weight": -125, "appliedMetric": "DISCOUNT", "appliedValue": -125, "organisationId": "3", "category": "INTERNAL", "typesOfFareProduct": [ {
"privateCode": "discount" }
],
"productCatalogVersionId": "PC-HTM:70", "xspit": "821ddb0d-4ea7-4a0d-84aa-bb7facce15d1", "xpit": "1848597c-5ba9-4dab-8d9f-3948948706a1" }
],
"checkinTransactionTimestamp": "2026-04-20T16:59:48+02:00", "checkoutTransactionTimestamp": "2026-04-20T17:04:50+02:00", "checkinTransactionReference": "00310F92;29023;emvClt", "checkoutTransactionReference": "0030DBC1;30528;emvClt", "checkinLocation": { "stopId": 2721, "stopName": { "items": [ {
"key": "nl-NL", "value": "Station Hollands Spoor" },
{
"key": "en-US", "value": "Station Hollands Spoor" }
]
},
"stopDesc": "Stop point2721", "stopUrl": "/icons/stops/stop.svg", "locationType": 0 },
"checkoutLocation": { "stopId": 2862, "stopName": { "items": [ {
"key": "nl-NL", "value": "Kalvermarkt-Stadhuis" },
{
"key": "en-US", "value": "Kalvermarkt-Stadhuis" }
]
},
"stopDesc": "Stop point2862", "stopUrl": "/icons/stops/stop.svg", "locationType": 0 },
"tokenType": "EMV-CLT", "transportType": "TRAM", "lineId": "17", "lineName": "Tram17", "networkId": "1", "networkName": "HTM netwerk", "currency": "EUR", "transportIcon": "/icons/transportTypes/tram.svg", "legs": [ {
"legId": 1614950977, "checkinTransactionTimestamp": "2026-04-20T16:59:48+02:00", "checkoutTransactionTimestamp": "2026-04-20T17:04:50+02:00", "checkinLocation": { "stopId": 2721, "stopName": { "items": [ {
"key": "nl-NL", "value": "Station Hollands Spoor" },
{
"key": "en-US", "value": "Station Hollands Spoor" }
]
},
"stopDesc": "Stop point2721", "stopUrl": "/icons/stops/stop.svg", "locationType": 0 },
"checkoutLocation": { "stopId": 2862, "stopName": { "items": [ {
"key": "nl-NL", "value": "Kalvermarkt-Stadhuis" },
{
"key": "en-US", "value": "Kalvermarkt-Stadhuis" }
]
},
"stopDesc": "Stop point2862", "stopUrl": "/icons/stops/stop.svg", "locationType": 0 },
"baseRate": 139, "lineId": "17", "lineName": "Tram17", "networkId": "1", "networkName": "HTM netwerk", "fare": 14, "fareState": "CALCULATED", "currency": "EUR", "products": [ {
"productTemplateId": "30901", "productCommercialName": { "items": [ {
"key": "nl-NL", "value": "HTM 90% Korting" },
{
"key": "en-US", "value": "HTM 90% Discount" }
]
},
"productIcon": "001.jpg", "purchaseDateTime": "2026-04-02T12:38:28+02:00", "organisationName": "ProductRetailer HTM", "appliedValue": -125, "organisationId": "3", "category": "SALES", "typesOfFareProduct": [ {
"privateCode": "discount" }
],
"xspit": "821ddb0d-4ea7-4a0d-84aa-bb7facce15d1" },
{
"productTemplateId": "N/A", "purchaseDateTime": "2025-07-09T12:39:27+02:00", "appliedValue": 139, "category": "SALES", "typesOfFareProduct": [ {
"privateCode": "payg" }
],
"xspit": "7618337f-c986-4bef-990d-f5a6b47292b8" },
{
"productTemplateId": "1:2000", "productCommercialName": { "items": [ {
"key": "nl-NL", "value": "Basis Bus Tram Metro" },
{
"key": "en-US", "value": "BTM full fare" }
]
},
"validFrom": "2000-01-01T01:00:00+01:00", "validUntil": "2100-01-01T00:59:59+01:00", "weight": 116, "appliedMetric": "FARE", "appliedValue": 116, "category": "INTERNAL", "typesOfFareProduct": [ {
"privateCode": "payg" }
],
"xspit": "7618337f-c986-4bef-990d-f5a6b47292b8", "xpit": "f4256df6-b767-460f-9880-f7597bd7f3f2" },
{
"productTemplateId": "2:2000", "productCommercialName": { "items": [ {
"key": "nl-NL", "value": "Basis Bus Tram Metro" },
{
"key": "en-US", "value": "BTM full fare" }
]
},
"validFrom": "2000-01-01T01:00:00+01:00", "validUntil": "2100-01-01T00:59:59+01:00", "weight": 23, "appliedMetric": "FARE", "appliedValue": 23, "category": "INTERNAL", "typesOfFareProduct": [ {
"privateCode": "payg" }
],
"xspit": "7618337f-c986-4bef-990d-f5a6b47292b8", "xpit": "f4256df6-b767-460f-9880-f7597bd7f3f2" },
{
"productTemplateId": "3:30901", "productCommercialName": { "items": [ {
"key": "nl-NL", "value": "HTM 90% Korting" },
{
"key": "en-US", "value": "HTM 90% Discount" },
{
"key": "nl-NL", "value": "Reis je regelmatig met HTM? Activeer dan HTM 90% Korting op je betaalpas of credit card en reis een week lang met korting!" },
{
"key": "en-US", "value": "Are you a regular traveler? Activate HTM 90% discount on your EMV card!" }
]
},
"validFrom": "2026-04-03T04:00:00+02:00", "validUntil": "2031-04-03T04:00:00+02:00", "organisationName": "Product Owner HTM", "weight": -125, "appliedMetric": "DISCOUNT", "appliedValue": -125, "organisationId": "3", "category": "INTERNAL", "typesOfFareProduct": [ {
"privateCode": "discount" }
],
"discountPercentage": 90, "productCatalogVersionId": "PC-HTM:70", "xspit": "821ddb0d-4ea7-4a0d-84aa-bb7facce15d1", "xpit": "1848597c-5ba9-4dab-8d9f-3948948706a1" }
],
"appliedTimePeriods": [], "tariffJourneyStart": true, "topoDistance": 1161, "concessionId": "1", "appliedZones": [] }
],
"lastUpdatedTripDateTime": "2026-04-20T17:04:50+02:00", "fare": 14, "loyaltyOrDiscount": true, "supportCode": "pWM8Km6J8aHg5n", "tripVersionReason": "STRUCTURE", "checkinTransactionId": "00310F92-29023-emvClt-1776697188000", "checkoutTransactionId": "0030DBC1-30528-emvClt-1776697490000", "tariffVersionId": "HTM-CF1:278", "correlationId": "4bc7a040-71bf-4084-a61e-b2dd72092079", "createdAt": "2026-04-20T17:05:01.473+02:00", "entryTariffLocation": { "stopId": 2721, "stopName": { "items": [ {
"key": "nl-NL", "value": "Station Hollands Spoor" },
{
"key": "en-US", "value": "Station Hollands Spoor" }
]
},
"stopDesc": "Stop point2721", "stopUrl": "/icons/stops/stop.svg", "locationType": 0 },
"traveledLines": [ {
"lineId": "17", "lineName": "Tram17" }
],
"tripFareNature": "FIXED", "paymentMethodId": "229a01ff-7bd9-4740-9b19-00637146a6ca", "paymentMethodMode": "PREPAID", "paymentMethodName": "EPURSE", "paymentProviderName": "TPURSE", "paymentProviderBusinessEntityId": "600027", "registryFragmentVersionId": "REG:27", "vehicleId": 5063, "xbot": "c85e34d6-f9c1-49c2-8574-59b37e0f3b8e" }