From 7de54cbd86f52bb8fb0cfd64d9a4201cb868727a Mon Sep 17 00:00:00 2001 From: Max Martens Date: Sun, 17 Aug 2025 19:53:29 +0200 Subject: [PATCH] WIP, still need to fix some very legacy incorrect confusing stuff in this OAI-spec (thanks Greg for pointing that out!) --- src/openapi/products/products-crud.yaml | 319 ++++++++++++++++++++++-- 1 file changed, 301 insertions(+), 18 deletions(-) diff --git a/src/openapi/products/products-crud.yaml b/src/openapi/products/products-crud.yaml index f0a72ce..d86e7f6 100644 --- a/src/openapi/products/products-crud.yaml +++ b/src/openapi/products/products-crud.yaml @@ -778,6 +778,7 @@ paths: { "productId": 1, "parentProductId": null, + "layerInfo": null, "fikoArticleNumber": "1234", "isValid": true, "isArchived": false, @@ -839,7 +840,7 @@ paths: }, "requiredCustomerLevel": { "requiredCustomerLevelId": 1, - "name": "anonymous" + "name": "guest" }, "requiredProducts": null, "incompatibleProducts": [ @@ -952,6 +953,7 @@ paths: { "productId": 2, "parentProductId": null, + "layerInfo": null, "fikoArticleNumber": "1234", "isValid": true, "isArchived": false, @@ -1013,7 +1015,7 @@ paths: }, "requiredCustomerLevel": { "requiredCustomerLevelId": 1, - "name": "anonymous" + "name": "guest" }, "requiredProducts": null, "incompatibleProducts": [ @@ -1176,17 +1178,18 @@ paths: "timestamp": "2024-09-03T08:38:24.000+00:00" } ] - } + } getDetailsGboPadProduct: summary: GBO product (PAD required, renewable, serviceOptions, forbiddenPaymentMethods) value: { "productId": 3, "parentProductId": null, + "layerInfo": null, "fikoArticleNumber": "1234", "isValid": true, "isArchived": false, - "gboPackageTemplateId": "35301", + "gboPackageTemplateId": "33610", "tapConnectProductCode": null, "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.", @@ -1223,6 +1226,237 @@ paths: "name": "Ooievaarspas-gerechtigde" } ], + "allowedGboAgeProfiles": [ + { + "gboAgeProfileId": 2, + "name": "Kind (4 t/m 11 jaar)", + "ageFromInclusive": 4, + "ageToInclusive": 11 + }, + { + "gboAgeProfileId": 3, + "name": "Jongere (12 t/m 18 jaar)", + "ageFromInclusive": 12, + "ageToInclusive": 18 + }, + { + "gboAgeProfileId": 4, + "name": "Volwassene (19 t/m 64 jaar)", + "ageFromInclusive": 19, + "ageToInclusive": 64 + }, + { + "gboAgeProfileId": 5, + "name": "Oudere (65 jaar of ouder)", + "ageFromInclusive": 65, + "ageToInclusive": 999 + } + ], + "productCategory": { + "productCategoryId": 3, + "isTravelProduct": true, + "name": "Afgekocht reisrecht" + }, + "requiredCustomerLevel": { + "requiredCustomerLevelId": 3, + "name": "profile" + }, + "requiredProducts": null, + "incompatibleProducts": [ + { + "incompatibleProductId": 3, + "productName": "HTM Regio Vrij DH73", + "description": "Kan niet combineren met zichzelf" + } + ], + "mandatoryCustomerDataItems": [ + { + "mandatoryCustomerDataItemId": 4, + "customerDataItem": "emailAddress" + }, + { + "mandatoryCustomerDataItemId": 8, + "customerDataItem": "padBirthDate" + } + ], + "requiredGboPersonalAttributes": [ + { + "requiredGboPersonalAttributeId": 1, + "name": "NAME" + }, + { + "requiredGboPersonalAttributeId": 2, + "name": "BIRTHDATE" + }, + { + "requiredGboPersonalAttributeId": 3, + "name": "PHOTO" + } + ], + "tokenTypes": [ + { + "tokenTypeId": 1, + "name": "EMV" + }, + { + "tokenTypeId": 2, + "name": "OVpas physical" + }, + { + "tokenTypeId": 3, + "name": "OVpas digital" + } + ], + "paymentMoment": { + "paymentMomentId": 1, + "name": "prepaid" + }, + "serviceOptions": [ + { + "serviceOptionId": 1, + "action": "cancellableTermAhead", + "description": "Per termijn vooruit opzegbaar" + } + ], + "validityDuration": "P1M", + "maxStartInFutureDuration": "P6W", + "isRenewable": true, + "sendInvoice": true, + "imageReference": "https://www.htm.nl/media/leif2leu/htm-logo-mobile.svg", + "productPageUrl": "https://www.htm.nl/nog-onbekende-product-pagina", + "termsUrl": "https://www.htm.nl/nog-onbekende-productvoorwaarden-pagina", + "isSellableAtHtm": true, + "needsSolvencyCheckConsumer": false, + "needsSolvencyCheckBusiness": false, + "sellingPeriods": [ + { + "sellingPeriodId": 5, + "fromInclusive": "2024-09-01T00:00:00.000+00:00", + "toInclusive": "2024-12-31T23:59:59.999+00:00", + "salesTouchpoint": { + "salesTouchpointId": 3, + "name": "Website (Perplex)", + "isActive": true, + "retailer": { + "retailerId": 1001, + "name": "HTM externe touchpoints", + "street": "Koningin Julianaplein", + "number": "10", + "numberAddition": null, + "postalCode": "2595 AA", + "city": "Den Haag", + "country": "Nederland", + "emailAddress": "info@htm.nl", + "phoneNumber": "070 374 9002", + "taxId": "572309345923", + "imageReference": "https://www.htm.nl/media/leif2leu/htm-logo-mobile.svg" + } + }, + "forbiddenPaymentMethods": [ + { + "forbiddenPaymentMethodId": 1, + "name": "Credit Card", + "issuer": "American Express" + } + ], + "sellingPrices": [ + { + "sellingPriceId": 5, + "amountExclTax": 5413, + "amountInclTax": 5900, + "taxCode": "V09", + "taxPercentage": 9.0000, + "fromInclusive": "2024-09-01T00:00:00.000+00:00", + "toInclusive": "2024-12-31T23:59:59.999+00:00", + "internalPrice": 5413.0000 + } + ] + } + ], + "purchasePrices": [ + { + "purchasePriceId": 1, + "amountExclTax": 0, + "taxCode": "V09", + "taxPercentage": 9.0000, + "amountInclTax": 0, + "fromInclusive": "2024-09-01T00:00:00.000+00:00", + "toInclusive": "2024-12-31T23:59:59.999+00:00" + } + ], + "auditTrail": [ + { + "auditTrailId": 2, + "action": "update", + "user": "api", + "timestamp": "2024-09-03T08:39:38.000+00:00" + }, + { + "auditTrailId": 1, + "action": "insert", + "user": "api", + "timestamp": "2024-09-03T08:38:24.000+00:00" + } + ] + } + getDetailsProductVariantWithParent: + summary: GBO product-variant, meaning that parentProductId and layerInfo are present + description: >- + This product is not a top-level parent, but a product-variant that refers to another product (via `parentProductId`). + This means that the `layerInfo` should be present for this product-variant. The parent product references is `productId 3`, + so the product in this example is an extension of that product-definition. MOre specifically: in this product-variant, we + removed some `allowedGboAgeProfiles` from the definition - this means that the customer has to provide the birthdate of + the token-owner (padBirthdate) in order to be able to buy this product-variant. + value: + { + "productId": 4, + "parentProductId": 3, + "layerInfoId": { + "layerInfoId": 1, + "choiceKey": "allowedGboAgeProfiles", + "choiceLabel": "Wat is uw geboortedatum?", + "isCustomChoice": false + }, + "fikoArticleNumber": "1234", + "isValid": true, + "isArchived": false, + "gboPackageTemplateId": "33610", + "tapConnectProductCode": null, + "productName": "HTM Regio Vrij DH73 Reductietarief", + "productDescription": "Voor een vast bedrag onbeperkt reizen met EBS, HTM en RET in het gekozen gebied in de regio Rotterdam Den Haag.", + "validityPeriod": { + "validityPeriodId": 3, + "fromInclusive": "2024-09-01T00:00:00.000+00:00", + "toInclusive": "2024-12-31T23:59:59.999+00:00" + }, + "productTranslations": [ + { + "language": "en", + "name": "HTM Regio Free DH73", + "description": "For a fixed amount unlimited travel with EBS, HTM and RET in the chosen area in the Rotterdam The Hague region." + } + ], + "productOwner": { + "productOwnerId": 1, + "name": "Corneel Verstoep", + "organization": "HTM" + }, + "marketSegments": [ + { + "marketSegmentId": 1, + "name": "B2C" + } + ], + "customerSegments": [ + { + "customerSegmentId": 6, + "name": "Student" + }, + { + "customerSegmentId": 7, + "name": "Ooievaarspas-gerechtigde" + } + ], "allowedGboAgeProfiles": [ { "gboAgeProfileId": 2, @@ -1244,33 +1478,25 @@ paths: }, "requiredCustomerLevel": { "requiredCustomerLevelId": 3, - "name": "account" + "name": "profile" }, "requiredProducts": null, "incompatibleProducts": [ { - "incompatibleProductId": 3, - "productName": "HTM Regio Vrij DH73", + "incompatibleProductId": 4, + "productName": "HTM Regio Vrij DH73 Reductietarief", "description": "Kan niet combineren met zichzelf" } ], "mandatoryCustomerDataItems": [ - { - "mandatoryCustomerDataItemId": 3, - "customerDataItem": "dateOfBirth" - }, { "mandatoryCustomerDataItemId": 4, "customerDataItem": "emailAddress" }, { - "mandatoryCustomerDataItemId": 5, - "customerDataItem": "ovPayToken" + "mandatoryCustomerDataItemId": 8, + "customerDataItem": "padBirthDate" }, - { - "mandatoryCustomerDataItemId": 6, - "customerDataItem": "directDebitMandate" - } ], "requiredGboPersonalAttributes": [ { @@ -1981,6 +2207,53 @@ paths: $ref: '#/components/schemas/500Response' components: schemas: + LayerInfoResponse: + type: object + description: >- + Gives information on the choice that the customer has to make, to enable the touchpoint + to proceed further "down the product-tree" by selecting (PATCH-ing) the correct product-variant. + LayerInfo is not a mandatory product-attribute, but it should always be present on all product for which + `parentProductId != null`. (LayerInfo makes no sense for top-level parents as there is always a single starting point). \ + + **PMT should ensure that all products in the same "product-branch" (same `parentProductId`) have the same `layerInfoId` + referenced. If a product is found to be in violation of this rule, its attribtue `isValid` should be set to `false`.** + required: + - layerInfoId + - choiceKey + - choiceLabel + - isCustomChoice + properties: + layerInfoId: + example: 1 + type: integer + choiceKey: + example: isRenewable + type: string + description: >- + Contains the JSON Key of the product-attribute that the customer has to make some + choice on (determined by PMT), so that te correct product-variant can be selected by the touchpoint. For some + decisions (like region), there is no product attribute, and thus `isCustomChoice` will be set + to `true`, and `choiceKey` can then be set to any string on which touchpoints can also trigger + behaviour if desired (think "region picker tool"). Therefore, reuse of choiceKeys should be + the goal, so touchoints can keep their triggers simple and prevent duplication of + similar choiceKeys to trigger the same behaviour. + choiceLabel: + example: Kies voor een doorlopend abonnement of een enkele termijn + type: string + description: >- + Contains a human-readable label for the choice that the customer has to make - this label + should be something that is easy to understand for the customer. Only one label (and thus, one language) + is supported; label translations should be handled by the touchpoint. + isCustomChoice: + example: false + type: boolean + description: >- + Indicates if the choice is a custom choice. If `false`, the PMT should fill `choiceKey` with the + "differing attribute for this product-layer" and the user should not be able to override this. When + no single attribute can be pinpointed by PMT, the product will become invalid (`isValid == false`) until either + a situation with a single differing attribute is created, or if `isCustomChoice` is set to `true` - this would, + however, also mean that touchpoints should be notified of this, especially if the configured LayerInfo contains + a new, not previously used, `choiceKey`. GboAgeProfileResponse: type: object required: @@ -2246,6 +2519,8 @@ components: parentProductId: type: integer example: 1 + layerInfo: + $ref: '#/components/schemas/LayerInfoResponse' fikoArticleNumber: type: string description: The article number of the product in FIKO @@ -2367,7 +2642,7 @@ components: example: 1 name: type: string - example: anonymous + example: guest requiredProducts: type: array items: @@ -2599,6 +2874,10 @@ components: type: integer description: The ID of the parent product (if any) example: 1 + layerInfoId: + type: integer + description: Only relevant when `parentProductId != null`; the ID of the layerInfo reference, giving information on why this layer of product-variants exists (PMT should enforce that this is filled for all products for which `parentProductId != null`) + example: 1 fikoArticleNumber: type: string description: The article number of the product in FIKO @@ -2972,6 +3251,10 @@ components: type: integer description: The ID of the parent product (if any) example: 1 + layerInfoId: + type: integer + description: Only relevant when `parentProductId != null`; the ID of the layerInfo reference, giving information on why this layer of product-variants exists (PMT should enforce that this is filled for all products for which `parentProductId != null`) + example: 1 fikoArticleNumber: type: string description: The article number of the product in FIKO