Open nathansalter opened 3 years ago
This is really great! A couple of modelling notes:
beta:addOns
does not currently specify a type for its valuebeta:addOns
should be singular (1, 2)beta:addOns
should perhaps separate the thing being purchased from the Offer
being purchased, to allow for different price levels (in line with the rest of the Booking spec), for example if members get to hire rackets for free? This also would make it easy to add new OrderItem
s to the Order
for each "add-on", though some thought is required about how this is structured in C1, C2, B, etc.PriceSpecification
eligibleQuantity
relates to the eligibleQuantity
of the PriceSpecification
(e.g. the price relevant to the first 4 rackets purchased), rather than the Add On (if the example implies that a maximum of 4 rackets can be purchased?). Perhaps this data should be moved to the Add On level?@id
is the Url identifier for all JSON-LD models, rather than identifier
So for example, something like this perhaps:
"beta:addOn": [
{
"@type": "Product",
"@id": "https://padel4all.com/openactive/add-ons/de505ce7-351f-4f8f-90c4-29887947a603",
"name": "Adult Racket Hire",
"beta:availableQuantity": {
"@type": "QuantitiveValue",
"minValue": 0,
"maxValue": 4
},
"ageRange": {
"@type": "QuantitativeValue",
"minValue": 16
},
"offers": [
{
"@id": "https://padel4all.com/openactive/add-ons/de505ce7-351f-4f8f-90c4-29887947a603#/offers/1",
"@type": "Offer",
"price": 1.5,
"priceCurrency": "GBP"
}
]
}
]
Great suggestions, all makes a lot of sense! I thought we should get this sorted out in preparation before tackling the slightly more complicated booking system. I also think we should specify what to do in the case of free addons, for example if Junior racket hire was free but adult was not. Would it be reasonable just to have the offers.price
set to 0
do you think?
Great plan - and yes offers.price
set to 0
is exactly how the booking spec handles free opportunities currently, so that seems consistent 👍
Might be worth thinking about booking at the same time in case it affects the model? If this was to be added to beta, would be good to ensure that it had that broader compatibility?
I'm thinking for adding to the booking OrderItem in this format:
"orderedItem": [
{
"@type": "OrderItem",
"orderedItem": "https://example.com/api/openactive/slots/12345",
"acceptedOffer": "https://example.com/api/openactive/offers/12345",
"beta:addOn": [{
"@id": "https://example.com/api/openactive/add-on/4",
"quantity": 3,
"acceptedOffer": "https://example.com/api/openactive/add-on/4#/offers/1"
},{
"@id": "https://example.com/api/openactive/add-on/5",
"quantity": 0,
"acceptedOffer": "https://example.com/api/openactive/add-on/5#/offers/1"
}]
"position": 0
},
{
"@type": "OrderItem",
"orderedItem": "https://example.com/api/openactive/slots/23456",
"acceptedOffer": "https://example.com/api/openactive/offers/23456",
"beta:addOn": [{
"@id": "https://example.com/api/openactive/add-on/4",
"quantity": 1,
"acceptedOffer": "https://example.com/api/openactive/add-on/4#/offers/1"
},{
"@id": "https://example.com/api/openactive/add-on/5",
"quantity": 1,
"acceptedOffer": "https://example.com/api/openactive/add-on/5#/offers/2"
}],
"position": 1
}
],
So they're dependents of the OrderItem?
If so I wonder if it might be worth thinking about the expected behaviour for e.g. cancellation and replacement - do the addOns get immediately cancelled along with their parent OrderItem?
Perhaps there's a more generic structure that can be used here so that interaction with OrderItems can be homogeneous, so something roughly like this:
"orderedItem": [
{
"@type": "OrderItem",
"orderedItem": "https://example.com/api/openactive/slots/23456",
"acceptedOffer": "https://example.com/api/openactive/offers/23456",
"position": 0
},
{
"@type": "OrderItem",
"orderedItem": "https://example.com/api/openactive/add-on/4",
"acceptedOffer": "https://example.com/api/openactive/add-on/4#/offers/1",
"beta:addOnForPosition": 0,
"position": 1
},
{
"@type": "OrderItem",
"orderedItem": "https://example.com/api/openactive/add-on/4",
"acceptedOffer": "https://example.com/api/openactive/add-on/4#/offers/1",
"beta:addOnForPosition": 0,
"position": 2
},
{
"@type": "OrderItem",
"orderedItem": "https://example.com/api/openactive/add-on/5",
"acceptedOffer": "https://example.com/api/openactive/add-on/5#/offers/2",
"beta:addOnForPosition": 0,
"position": 3
}
]
I think it makes sense for them to be dependents of the OrderItem. For two main reasons, firstly if you're hiring equipment it makes sense for that to be at a specific time and duration. Secondly, if you're purchasing equipment it's important for the Venue to know when that particular item has to be in stock, and when you're expecting it. Consider the example of racket hire, if you're cancelling that opportunity it no longer makes sense to hire the rackets.
After further discussion it's agreed that they should be flat, as in Nick's example above. This is so that we can still handle the OrderItem cancellation/modification flows such as for standard OrderItems.
Also do we also need a beta:addOnForId
to sit alongside the beta:addOnForPosition
for the response from P and B (position
s don't exist after P in the approval flow, as they exist only within the lifetime of the request)?
In fact, looking at the modelling again, perhaps we should model this as a more generic beta:parentOrderItem
with range OrderItem
?
C1, C2, B request
{
"@type": "OrderItem",
"orderedItem": "https://example.com/api/openactive/add-on/4",
"acceptedOffer": "https://example.com/api/openactive/add-on/4#/offers/1",
"beta:parentOrderItem": {
"@type": "OrderItem",
"position": 1
},
"position": 4
},
C1, C2 response:
{
"@type": "OrderItem",
"orderedItem": {...},
"acceptedOffer": {...},
"beta:parentOrderItem": {
"@type": "OrderItem",
"position": 1
},
"position": 4
},
P response:
{
"@type": "OrderItem",
"orderedItem": {...},
"acceptedOffer": {...},
"beta:parentOrderItem": {
"@type": "OrderItem",
"@id": "https://example.com/api/openbooking/orders/63cc36f8-e755-4445-99b6-739ff03f3c77#/orderedItems/1994",
"position": 1
},
"position": 4
},
At B response:
{
"@type": "OrderItem",
"orderedItem": {...},
"acceptedOffer": {...},
"beta:parentOrderItem": {
"@type": "OrderItem",
"@id": "https://example.com/api/openbooking/orders/63cc36f8-e755-4445-99b6-739ff03f3c77#/orderedItems/1994"
}
},
Yes I think that's a sensible suggestion, and nicely abstracts it for future additions
@nathansalter areas to address from our call just now:
Note when referencing items across Orders we'd probably need a different request and potentially more detail in the Orders feed to allow the Broker to dereference the Order (as the URI scheme of the OrderItem is known only to the booking system) e.g. as below:
Request:
{
"@type": "OrderItem",
"orderedItem": "https://example.com/api/openactive/add-on/4",
"acceptedOffer": "https://example.com/api/openactive/add-on/4#/offers/1",
"beta:parentOrderItem": {
"@type": "OrderItem",
"@id": "https://example.com/api/openbooking/orders/63cc36f8-e755-4445-99b6-739ff03f3c77#/orderedItems/1994"
},
"position": 4
},
Response / Orders Feed contents
{
"@type": "OrderItem",
"orderedItem": "https://example.com/api/openactive/add-on/4",
"acceptedOffer": "https://example.com/api/openactive/add-on/4#/offers/1",
"beta:parentOrderItem": {
"@type": "OrderItem",
"@id": "https://example.com/api/openbooking/orders/63cc36f8-e755-4445-99b6-739ff03f3c77#/orderedItems/1994"
"beta:order": {
"@type": "Order",
"@id": "https://example.com/api/openbooking/orders/63cc36f8-e755-4445-99b6-739ff03f3c77"
"identifier": "63cc36f8-e755-4445-99b6-739ff03f3c77"
}
},
"position": 4
},
I've also transferred this to Open Booking API, as the functionality around the modelling may be as important to reach consensus on as the modelling itself? It appears that the modelling exists here for the purposes of facilitating booking functionality rather than purely to publish open data?
Collating this all into a single issue, we have stage 1 with adding this into the IndividualFacilityUse feeds
{
"next": "https://example.com/api/open-active/4813dbc0-41df-42c3-aa85-cff6f89531dd/individual-facility-uses?afterTimestamp=1643121185935915&afterId=2d21b9fb-ebb1-49b1-a07c-fdb81dba5c23",
"items": [
{
"state": "updated",
"kind": "IndividualFacilityUse",
"id": "d7fb4341-756a-41a8-ad17-4b8f6b574bb6",
"modified": 1592994256385869,
"data": {
"@id": "https://example.com/api/open-active/fe30c0e1-b4f9-4226-9fd8-3ddb230edd9f/individual-facility-uses/d7fb4341-756a-41a8-ad17-4b8f6b574bb6",
"@type": "IndividualFacilityUse",
"@context": [
"https://openactive.io/",
"https://openactive.io/ns-beta"
],
"identifier": "d7fb4341-756a-41a8-ad17-4b8f6b574bb6",
"name": "Football 11-a-side",
"url": "https://example.com/api/open-active/fe30c0e1-b4f9-4226-9fd8-3ddb230edd9f/individual-facility-uses/d7fb4341-756a-41a8-ad17-4b8f6b574bb6",
"activity": [
{
"@type": "Concept",
"@id": "https://openactive.io/activity-list#117e7f70-6c42-4b1f-a3bb-620b63ea263l",
"inScheme": "https://openactive.io/activity-list",
"prefLabel": "11-a-side"
}
],
"beta:addOn": [{
"@id": "https://example.com/openactive/add-ons/de505ce7-351f-4f8f-90c4-29887947a603",
"@type": "AddOn",
"name": "Adult Racket Hire",
"priceSpecification": {
"eligibleQuantity": {
"@type": "QuantitiveValue",
"minValue": 0,
"maxValue": 4
}
},
"offers": [
{
"@type": "Offer",
"@id": "https://example.com/openactive/add-ons/de505ce7-351f-4f8f-90c4-29887947a603#offer",
"priceCurrency": "GBP",
"price": 1.5
}
]
}, {
"@id": "https://example.com/openactive/add-ons/b9372482-8d6e-4d89-9160-58fa52d86944",
"@type": "AddOn",
"name": "Junior Racket Hire",
"priceSpecification": {
"eligibleQuantity": {
"@type": "QuantitiveValue",
"minValue": 0,
"maxValue": 4
}
},
"offers": [
{
"@type": "Offer",
"@id": "https://example.com/openactive/add-ons/b9372482-8d6e-4d89-9160-58fa52d86944#offer",
"priceCurrency": "GBP",
"price": 0
}
]
}],
"hoursAvailable": [
...
],
"location": {
...
},
"provider": {
"@type": "Organization",
"@id": "https://example.com/api/venues/fe30c0e1-b4f9-4226-9fd8-3ddb230edd9f",
"name": "Sundridge Park Lawn Tennis & Squash Rackets Club"
}
}
}
],
"license": "https://creativecommons.org/licenses/by/4.0/"
}
Objects in the beta:addOn
property indicate that the facility can provide AddOns with the provided offers. Brokers SHOULD offer these at checkout for the customer to optionally select. The minimum eligibleQuantity
MAY be above zero to indicate that this is a required AddOn. It is RECOMMENDED that when this is the case, brokers include this extra cost (if applicable) into the cost shown to the customer before slot selection takes place. The Broker MUST adhere to the minimum/maximum quantity values shown here when submitting Order requests at C1, C2, B etc.
And Stage 2 adding this into the booking APIs:
In the image above, it indicates how the structure of the order items is assumed to be. Although it's possible that you can have a deeper structure than a single AddOn, currently brokers SHOULD only use a single level of nesting to help implementation from the booking system side.
The standard booking api can be updated to the following:
PUT /api/order-quote-templates/e11429ea-467f-4270-ab62-e47368996fe8 HTTP/1.1
Host: example.com
Date: Mon, 8 Oct 2018 20:52:35 GMT
Accept: application/vnd.openactive.booking+json; version=1
Content-Type: application/vnd.openactive.booking+json; version=1
{
"@context": "https://openactive.io/",
"@type": "OrderQuote",
"brokerRole": "https://openactive.io/AgentBroker",
"broker": {
"@type": "Organization",
"name": "MyFitnessApp",
"url": "https://myfitnessapp.example.com",
"description": "A fitness app for all the community",
"logo": {
"@type": "ImageObject",
"url": "http://data.myfitnessapp.org.uk/images/logo.png"
},
"address": {
"@type": "PostalAddress",
"streetAddress": "Alan Peacock Way",
"addressLocality": "Village East",
"addressRegion": "Middlesbrough",
"postalCode": "TS4 3AE",
"addressCountry": "GB"
}
},
"seller": {
"@type": "Organization",
"@id": "https://example.com/api/organisations/123"
},
"orderedItem": [
{
"@type": "OrderItem",
"position": 0,
"acceptedOffer": "https://example.com/api/open-active/offers/1643725800-9c44e3f8-029c-4b47-845c-52198c29ff53",
"orderedItem": "https://example.com/api/open-active/4813dbc0-41df-42c3-aa85-cff6f89531dd/slots/f5b4d223-1c77-5e68-acbf-8624239fef13",
},{
"@type": "OrderItem",
"position": 1,
"quantity": 2,
"acceptedOffer": "https://example.com/openactive/add-ons/de505ce7-351f-4f8f-90c4-29887947a603#offer",
"orderedItem": "https://example.com/openactive/add-ons/de505ce7-351f-4f8f-90c4-29887947a603",
"beta:parentOrderItem": {
"@type": "OrderItem",
"position": 0
}
},{
"@type": "OrderItem",
"position": 2,
"quantity": 3,
"acceptedOffer": "https://example.com/openactive/add-ons/b9372482-8d6e-4d89-9160-58fa52d86944#offer",
"orderedItem": "https://example.com/openactive/add-ons/b9372482-8d6e-4d89-9160-58fa52d86944",
"beta:parentOrderItem": {
"@type": "OrderItem",
"position": 0
}
}
]
}
HTTP/1.1 200 OK
Host: example.com
Date: Mon, 8 Oct 2018 20:52:35 GMT
Accept: application/vnd.openactive.booking+json; version=1
Content-Type: application/vnd.openactive.booking+json; version=1
{
"@id": "https://example.com/api/order-quotes/e11429ea-467f-4270-ab62-e47368996fe8",
"@context": "https://openactive.io/",
"@type": "OrderQuote",
"brokerRole": "https://openactive.io/AgentBroker",
"broker": {
"@type": "Organization",
...
},
"seller": {
"@type": "Organization",
"@id": "https://example.com/api/organisations/123"
},
"orderedItem": [
{
"@id": "https://example.com/api/open-active/order-items/c67de59f-3f32-4751-ac4d-59431a13be0d",
"@type": "OrderItem",
"acceptedOffer": "https://example.com/api/open-active/offers/1643725800-9c44e3f8-029c-4b47-845c-52198c29ff53",
"orderedItem": "https://example.com/api/open-active/4813dbc0-41df-42c3-aa85-cff6f89531dd/slots/f5b4d223-1c77-5e68-acbf-8624239fef13",
},{
"@id": "https://example.com/api/open-active/order-items/08d7e6a6-757f-4dd5-af0e-d1692bbd21c7",
"@type": "OrderItem",
"quantity": 2,
"acceptedOffer": "https://example.com/openactive/add-ons/de505ce7-351f-4f8f-90c4-29887947a603#offer",
"orderedItem": "https://example.com/openactive/add-ons/de505ce7-351f-4f8f-90c4-29887947a603",
"beta:parentOrderItem": {
"@id": "https://example.com/api/open-active/order-items/c67de59f-3f32-4751-ac4d-59431a13be0d",
"@type": "OrderItem"
}
},{
"@id": "https://example.com/api/open-active/order-items/5ec216fd-1680-48f5-ba86-33d80b2c60f4",
"@type": "OrderItem",
"acceptedOffer": "https://example.com/openactive/add-ons/b9372482-8d6e-4d89-9160-58fa52d86944#offer",
"orderedItem": {
"@type": "AddOn",
"@id": "https://example.com/openactive/add-ons/b9372482-8d6e-4d89-9160-58fa52d86944",
},
"beta:parentOrderItem": {
"@id": "https://example.com/api/open-active/order-items/c67de59f-3f32-4751-ac4d-59431a13be0d",
"@type": "OrderItem"
}
}
]
}
Note how the position
property is dropped from the response. In future requests to C2
the broker MUST supply both position
and @id
in the beta:parentOrderItem
so that the booking system can easily make modifications if required.
PUT /api/order-quote-templates/e11429ea-467f-4270-ab62-e47368996fe8 HTTP/1.1
Host: example.com
Date: Mon, 8 Oct 2018 20:52:35 GMT
Accept: application/vnd.openactive.booking+json; version=1
Content-Type: application/vnd.openactive.booking+json; version=1
{
"@context": "https://openactive.io/",
"@type": "OrderQuote",
"brokerRole": "https://openactive.io/AgentBroker",
"broker": {
"@type": "Organization",
"name": "MyFitnessApp",
"url": "https://myfitnessapp.example.com",
"description": "A fitness app for all the community",
"logo": {
"@type": "ImageObject",
"url": "http://data.myfitnessapp.org.uk/images/logo.png"
},
"address": {
"@type": "PostalAddress",
"streetAddress": "Alan Peacock Way",
"addressLocality": "Village East",
"addressRegion": "Middlesbrough",
"postalCode": "TS4 3AE",
"addressCountry": "GB"
}
},
"seller": {
"@type": "Organization",
"@id": "https://example.com/api/organisations/123"
},
"orderedItem": [
{
"@id": "https://example.com/api/open-active/order-items/c67de59f-3f32-4751-ac4d-59431a13be0d",
"@type": "OrderItem",
"position": 0,
"acceptedOffer": "https://example.com/api/open-active/offers/1643725800-9c44e3f8-029c-4b47-845c-52198c29ff53",
"orderedItem": "https://example.com/api/open-active/4813dbc0-41df-42c3-aa85-cff6f89531dd/slots/f5b4d223-1c77-5e68-acbf-8624239fef13",
},{
"@id": "https://example.com/api/open-active/order-items/08d7e6a6-757f-4dd5-af0e-d1692bbd21c7",
"@type": "OrderItem",
"position": 1,
"quantity": 2,
"acceptedOffer": "https://example.com/openactive/add-ons/de505ce7-351f-4f8f-90c4-29887947a603#offer",
"orderedItem": "https://example.com/openactive/add-ons/de505ce7-351f-4f8f-90c4-29887947a603",
"beta:parentOrderItem": {
"@id": "https://example.com/api/open-active/order-items/c67de59f-3f32-4751-ac4d-59431a13be0d",
"@type": "OrderItem",
"position": 0
}
},{
"@type": "OrderItem",
"position": 2,
"quantity": 3,
"acceptedOffer": "https://example.com/openactive/add-ons/b9372482-8d6e-4d89-9160-58fa52d86944#offer",
"orderedItem": "https://example.com/openactive/add-ons/b9372482-8d6e-4d89-9160-58fa52d86944",
"beta:parentOrderItem": {
"@id": "https://example.com/api/open-active/order-items/c67de59f-3f32-4751-ac4d-59431a13be0d",
"@type": "OrderItem",
"position": 0
}
}
]
}
In this request we have transparently removed the original third order item and replaced it with an identical replacement. This is to show how the booking system must take the given OrderItems and remove existing ones from its original booking model where they are replaced. This is indicated by any order item with a position
property but not an @id
property.
Requests at P and B will be identical when referring to AddOns.
Cancelling an AddOn. As described in Open Booking API 8.2, simply removing the AddOn from the next request will remove it from the Order:
PUT /api/order-quotes/e11429ea-467f-4270-ab62-e47368996fe8 HTTP/1.1
Host: example.com
Date: Mon, 8 Oct 2018 20:52:35 GMT
Accept: application/vnd.openactive.booking+json; version=1
Content-Type: application/vnd.openactive.booking+json; version=1
{
"@context": "https://openactive.io/",
"@type": "OrderQuote",
"brokerRole": "https://openactive.io/AgentBroker",
"broker": {
"@type": "Organization",
"name": "MyFitnessApp",
"url": "https://myfitnessapp.example.com",
"description": "A fitness app for all the community",
"logo": {
"@type": "ImageObject",
"url": "http://data.myfitnessapp.org.uk/images/logo.png"
},
"address": {
"@type": "PostalAddress",
"streetAddress": "Alan Peacock Way",
"addressLocality": "Village East",
"addressRegion": "Middlesbrough",
"postalCode": "TS4 3AE",
"addressCountry": "GB"
}
},
"seller": {
"@type": "Organization",
"@id": "https://example.com/api/organisations/123"
},
"orderedItem": [
{
"@id": "https://example.com/api/open-active/order-items/c67de59f-3f32-4751-ac4d-59431a13be0d",
"@type": "OrderItem",
"position": 0,
"acceptedOffer": "https://example.com/api/open-active/offers/1643725800-9c44e3f8-029c-4b47-845c-52198c29ff53",
"orderedItem": "https://example.com/api/open-active/4813dbc0-41df-42c3-aa85-cff6f89531dd/slots/f5b4d223-1c77-5e68-acbf-8624239fef13",
},{
"@id": "https://example.com/api/open-active/order-items/5ec216fd-1680-48f5-ba86-33d80b2c60f4",
"@type": "OrderItem",
"position": 1,
"quantity": 3,
"acceptedOffer": "https://example.com/openactive/add-ons/b9372482-8d6e-4d89-9160-58fa52d86944#offer",
"orderedItem": "https://example.com/openactive/add-ons/b9372482-8d6e-4d89-9160-58fa52d86944",
"beta:parentOrderItem": {
"@id": "https://example.com/api/open-active/order-items/c67de59f-3f32-4751-ac4d-59431a13be0d",
"@type": "OrderItem",
"position": 0
}
}
]
}
To cancel an AddOn after B, simply use the same request as defined in the booking spec
PATCH /api/orders/e11429ea-467f-4270-ab62-e47368996fe8 HTTP/1.1
Host: example.com
Date: Mon, 8 Oct 2018 20:52:35 GMT
Accept: application/vnd.openactive.booking+json; version=1
Content-Type: application/vnd.openactive.booking+json; version=1
{
"@context": "https://openactive.io/",
"@type": "OrderQuote",
"brokerRole": "https://openactive.io/AgentBroker",
"broker": {
...
},
"seller": {
"@type": "Organization",
"@id": "https://example.com/api/organisations/123"
},
"orderedItem": [
{
"@id": "https://example.com/api/open-active/order-items/08d7e6a6-757f-4dd5-af0e-d1692bbd21c7",
"@type": "OrderItem",
"orderItemStatus": "https://openactive.io/CustomerCancelled"
}
]
}
However, if you try to cancel an OrderItem which has children, Brokers MUST cancel all Order Items:
PATCH /api/orders/e11429ea-467f-4270-ab62-e47368996fe8 HTTP/1.1
Host: example.com
Date: Mon, 8 Oct 2018 20:52:35 GMT
Accept: application/vnd.openactive.booking+json; version=1
Content-Type: application/vnd.openactive.booking+json; version=1
{
"@context": "https://openactive.io/",
"@type": "OrderQuote",
"brokerRole": "https://openactive.io/AgentBroker",
"broker": {
...
},
"seller": {
"@type": "Organization",
"@id": "https://example.com/api/organisations/123"
},
"orderedItem": [
{
"@id": "https://example.com/api/open-active/order-items/c67de59f-3f32-4751-ac4d-59431a13be0d",
"@type": "OrderItem",
"orderItemStatus": "https://openactive.io/CustomerCancelled"
},{
"@id": "https://example.com/api/open-active/order-items/08d7e6a6-757f-4dd5-af0e-d1692bbd21c7",
"@type": "OrderItem",
"orderItemStatus": "https://openactive.io/CustomerCancelled"
},{
"@id": "https://example.com/api/open-active/order-items/5ec216fd-1680-48f5-ba86-33d80b2c60f4",
"@type": "OrderItem",
"orderItemStatus": "https://openactive.io/CustomerCancelled"
}
]
}
As opposed to:
PATCH /api/orders/e11429ea-467f-4270-ab62-e47368996fe8 HTTP/1.1
Host: example.com
Date: Mon, 8 Oct 2018 20:52:35 GMT
Accept: application/vnd.openactive.booking+json; version=1
Content-Type: application/vnd.openactive.booking+json; version=1
{
"@context": "https://openactive.io/",
"@type": "OrderQuote",
"brokerRole": "https://openactive.io/AgentBroker",
"broker": {
...
},
"seller": {
"@type": "Organization",
"@id": "https://example.com/api/organisations/123"
},
"orderedItem": [
{
"@id": "https://example.com/api/open-active/order-items/c67de59f-3f32-4751-ac4d-59431a13be0d",
"@type": "OrderItem",
"orderItemStatus": "https://openactive.io/CustomerCancelled"
}
]
}
Which the Booking System MUST return this error:
HTTP/1.1 400 Bad Request
Date: Mon, 8 Oct 2018 20:52:36 GMT
Content-Type: application/vnd.openactive.booking+json; version=1
{
"@context": "https://openactive.io/",
"@type": "OrphansNotPermittedError",
"description": "Cannot orphan AddOns without also removing those AddOns"
}
AddOns MAY be modified before B using replacement as defined above. However after B, AddOns MUST NOT increase in quantity for that Order. If more AddOns are required, the Broker will need to create a new Order. AddOns however can reduce in quantity, as this can fit nicely with the cancellation flow:
PATCH /api/orders/e11429ea-467f-4270-ab62-e47368996fe8 HTTP/1.1
Host: example.com
Date: Mon, 8 Oct 2018 20:52:35 GMT
Accept: application/vnd.openactive.booking+json; version=1
Content-Type: application/vnd.openactive.booking+json; version=1
{
"@context": "https://openactive.io/",
"@type": "OrderQuote",
"brokerRole": "https://openactive.io/AgentBroker",
"broker": {
...
},
"seller": {
"@type": "Organization",
"@id": "https://example.com/api/organisations/123"
},
"orderedItem": [
{
"@id": "https://example.com/api/open-active/order-items/08d7e6a6-757f-4dd5-af0e-d1692bbd21c7",
"@type": "OrderItem",
"quantity": 1,
"orderItemStatus": "https://openactive.io/AddOnQuantityReduction",
"acceptedOffer": "https://example.com/openactive/add-ons/de505ce7-351f-4f8f-90c4-29887947a603#offer",
"orderedItem": "https://example.com/openactive/add-ons/de505ce7-351f-4f8f-90c4-29887947a603",
"beta:parentOrderItem": {
"@id": "https://example.com/api/open-active/order-items/c67de59f-3f32-4751-ac4d-59431a13be0d",
"@type": "OrderItem"
}
}
]
}
If helpful, some of my notes from the W3C call 27/04/2022 (there were other actions recorded too not captured here):
beta:parentOrderItem
should be a conformance criteriaPriceSpecification
eligibleQuantity
relates to the eligibleQuantity
of the PriceSpecification
(e.g. the price relevant to the first 4 rackets purchased), rather than the Add On (if the example implies that a maximum of 4 rackets can be purchased?). This data should be moved to the Add On level.SessionSeries
openBookingPrepayment
and openBookingInAdvance
quantity
around tax, cancellation, and anything else where there might be implicationsIt might be useful to also - in future - consider deposits for rackets etc (e.g. via partial refunds)
It would also be helpful to have a data dump from Playfinder for the shape of Add-ons
Finally gone through this again, and revised with the comments from the discussions made. We're going to be continuing on with implementing this, so I'll update here with any issues we face during execution.
{
"next": "https://example.com/api/openactive/individual-facility-uses?afterTimestamp=1643121185935915&afterId=2d21b9fb-ebb1-49b1-a07c-fdb81dba5c23",
"items": [
{
"state": "updated",
"kind": "IndividualFacilityUse",
"id": "d7fb4341-756a-41a8-ad17-4b8f6b574bb6",
"modified": 1592994256385869,
"data": {
"@id": "https://example.com/api/openactive/fe30c0e1-b4f9-4226-9fd8-3ddb230edd9f/individual-facility-uses/d7fb4341-756a-41a8-ad17-4b8f6b574bb6",
"@type": "IndividualFacilityUse",
"@context": [
"https://openactive.io/",
"https://openactive.io/ns-beta"
],
"identifier": "d7fb4341-756a-41a8-ad17-4b8f6b574bb6",
"name": "Football 11-a-side",
"url": "https://example.com/api/openactive/fe30c0e1-b4f9-4226-9fd8-3ddb230edd9f/individual-facility-uses/d7fb4341-756a-41a8-ad17-4b8f6b574bb6",
"activity": [
{
"@type": "Concept",
"@id": "https://openactive.io/activity-list#117e7f70-6c42-4b1f-a3bb-620b63ea263l",
"inScheme": "https://openactive.io/activity-list",
"prefLabel": "11-a-side"
}
],
"beta:addOn": [{
"@id": "https://example.com/openactive/add-ons/de505ce7-351f-4f8f-90c4-29887947a603",
"@type": "AddOn",
"name": "Adult Racket Hire",
"priceSpecification": {
"eligibleQuantity": {
"@type": "QuantitiveValue",
"minValue": 0,
"maxValue": 4
}
},
"offers": [
{
"@type": "Offer",
"@id": "https://example.com/openactive/add-ons/de505ce7-351f-4f8f-90c4-29887947a603#offer",
"priceCurrency": "GBP",
"price": 1.5,
"availableChannel": "https://openactive.io/OpenBookingPrepayment"
}
]
}, {
"@id": "https://example.com/openactive/add-ons/b9372482-8d6e-4d89-9160-58fa52d86944",
"@type": "AddOn",
"name": "Junior Racket Hire",
"priceSpecification": {
"eligibleQuantity": {
"@type": "QuantitiveValue",
"minValue": 0,
"maxValue": 4
}
},
"offers": [
{
"@type": "Offer",
"@id": "https://example.com/openactive/add-ons/b9372482-8d6e-4d89-9160-58fa52d86944#offer",
"priceCurrency": "GBP",
"price": 0,
"availableChannel": "https://openactive.io/OpenBookingPrepayment"
}
]
}],
"hoursAvailable": [
...
],
"location": {
...
},
"provider": {
"@type": "Organization",
"@id": "https://example.com/api/venues/fe30c0e1-b4f9-4226-9fd8-3ddb230edd9f",
"name": "Sundridge Park Lawn Tennis & Squash Rackets Club"
}
}
}
],
"license": "https://creativecommons.org/licenses/by/4.0/"
}
Objects in the beta:addOn
property indicate that the facility can
provide AddOns with the provided offers.
You MAY also specify advanceBooking
as https://openactive.io/Unavailable
to signify that the AddOn is available but not bookable e.g. if the
facility provides coin-operated lockers, but you can't pay for them in
advance.
Brokers SHOULD offer these at checkout for the customer to optionally
select. The minimum eligibleQuantity
MAY be above zero to indicate
that this is a required AddOn. It is RECOMMENDED that when this is the
case, brokers include this extra cost (if applicable) into the cost
shown to the customer before slot selection takes place. The Broker MUST
adhere to the minimum/maximum quantity values shown here when submitting
Order requests at C1, C2, B etc.
{
"next": "https://example.com/api/openactive/schedules?afterTimestamp=1643121185935915&afterId=2d21b9fb-ebb1-49b1-a07c-fdb81dba5c23",
"items": [
{
"state": "updated",
"kind": "SessionSeries",
"id": "756f1b7a-df67-4d4d-8d81-fcc2b169f212",
"modified": 1676375547094113,
"data": {
"@type": "SessionSeries",
"@context": [
"https://openactive.io/",
"https://openactive.io/ns-beta"
],
"@id": "https://example.com/api/openactive/42b3928b-4613-4e9d-a086-54d8c76cf8ae/session-series/756f1b7a-df67-4d4d-8d81-fcc2b169f212",
"identifier": "756f1b7a-df67-4d4d-8d81-fcc2b169f212",
"name": "Organiser Email check",
"description": "Testing",
"url": "https://example.com",
"offers": [
{
"@type": "Offer",
"@id": "https://example.com/api/openactive/offers/756f1b7a-df67-4d4d-8d81-fcc2b169f212-0",
"identifier": "756f1b7a-df67-4d4d-8d81-fcc2b169f212-0",
"name": "Organiser Email check (0)",
"url": "https://example.com/api/openactive/order-quote-templates/56bc1392-ea1a-4df1-b2dc-ffdf18c9e40b",
"priceCurrency": "GBP",
"price": 5
}
],
"location": {
"@type": "Place",
"@id": "https://example.com/api/openactive/place/42b3928b-4613-4e9d-a086-54d8c76cf8ae",
"name": "AJ Sports Complex",
"address": {
"@type": "PostalAddress",
"addressCountry": "GB",
"addressRegion": "London",
"addressLocality": "Greater London",
"postalCode": "E1W 3DP",
"streetAddress": "445 Cable Street"
},
"geo": {
"@type": "GeoCoordinates",
"latitude": 51.5114909,
"longitude": -0.0477878
}
},
"organizer": {
"@type": "Organization",
"@id": "https://example.com/api/venues/42b3928b-4613-4e9d-a086-54d8c76cf8ae",
"name": "PF",
"email": "email.check@example.com",
"isOpenBookingAllowed": true,
"taxMode": "https://openactive.io/TaxGross"
},
"eventSchedule": [
{
"@type": "PartialSchedule",
"byDay": [
"https://schema.org/Tuesday"
],
"repeatCount": 5,
"repeatFrequency": "P7D",
"scheduleTimezone": "Europe/London",
"startDate": "2023-02-20"
}
],
"activity": [
{
"@type": "Concept",
"@id": "https://openactive.io/activity-list#c4661096-04c3-41de-b8d2-00788dd53023",
"inScheme": "https://openactive.io/activity-list",
"prefLabel": "Billiards"
}
],
"ageRange": {
"@type": "QuantitativeValue",
"maxValue": 90,
"minValue": 18
},
"category": [
"drop-in"
],
"genderRestriction": "https://openactive.io/MaleOnly",
"level": [
"intermediate"
],
"beta:offerValidityPeriod": "P1D",
"beta:addOn": [{
"@id": "https://example.com/openactive/add-ons/de505ce7-351f-4f8f-90c4-29887947a603",
"@type": "AddOn",
"name": "Adult Racket Hire",
"priceSpecification": {
"eligibleQuantity": {
"@type": "QuantitiveValue",
"minValue": 0,
"maxValue": 4
}
},
"offers": [
{
"@type": "Offer",
"@id": "https://example.com/openactive/add-ons/de505ce7-351f-4f8f-90c4-29887947a603#offer",
"priceCurrency": "GBP",
"price": 1.5
}
]
}]
}
}
],
"license": "https://creativecommons.org/licenses/by/4.0/"
}
And Stage 2 adding this into the booking APIs:
In the image above, it indicates how the structure of the order items is assumed to be. Brokers MUST only use a single level of nesting.
The standard booking api can be updated to the following:
PUT /api/order-quote-templates/e11429ea-467f-4270-ab62-e47368996fe8 HTTP/2
Host: example.com
Date: Mon, 8 Oct 2018 20:52:35 GMT
Accept: application/vnd.openactive.booking+json; version=1
Content-Type: application/vnd.openactive.booking+json; version=1
{
"@context": "https://openactive.io/",
"@type": "OrderQuote",
"brokerRole": "https://openactive.io/AgentBroker",
"broker": {
"@type": "Organization",
"name": "MyFitnessApp",
"url": "https://myfitnessapp.example.com",
"description": "A fitness app for all the community",
"logo": {
"@type": "ImageObject",
"url": "http://data.myfitnessapp.org.uk/images/logo.png"
},
"address": {
"@type": "PostalAddress",
"streetAddress": "Alan Peacock Way",
"addressLocality": "Village East",
"addressRegion": "Middlesbrough",
"postalCode": "TS4 3AE",
"addressCountry": "GB"
}
},
"seller": "https://example.com/api/organisations/123",
"orderedItem": [
{
"@type": "OrderItem",
"position": 0,
"acceptedOffer": "https://example.com/events/452#/offers/878",
"orderedItem": "https://example.com/events/452/subEvents/132"
},
{
"@type": "OrderItem",
"position": 1,
"quantity": 2,
"acceptedOffer": "https://example.com/openactive/add-ons/de505ce7-351f-4f8f-90c4-29887947a603#offer",
"orderedItem": "https://example.com/openactive/add-ons/de505ce7-351f-4f8f-90c4-29887947a603",
"beta:parentOrderItem": {
"@type": "OrderItem",
"position": 0
}
},
{
"@type": "OrderItem",
"position": 2,
"quantity": 3,
"acceptedOffer": "https://example.com/openactive/add-ons/b9372482-8d6e-4d89-9160-58fa52d86944#offer",
"orderedItem": "https://example.com/openactive/add-ons/b9372482-8d6e-4d89-9160-58fa52d86944",
"beta:parentOrderItem": {
"@type": "OrderItem",
"position": 0
}
}
]
}
HTTP/2 200 OK
Host: example.com
Date: Mon, 8 Oct 2018 20:52:35 GMT
Accept: application/vnd.openactive.booking+json; version=1
Content-Type: application/vnd.openactive.booking+json; version=1
{
"@context": "https://openactive.io/",
"@type": "OrderQuote",
"@id": "https://example.com/api/order-quotes/e11429ea-467f-4270-ab62-e47368996fe8",
"orderRequiresApproval": false,
"brokerRole": "https://openactive.io/AgentBroker",
"broker": {
...
},
"seller": {
..
},
"bookingService": {
"@type": "BookingService",
"name": "Playwaze",
"url": "http://www.playwaze.com",
"termsOfService": [
{
"@type": "Terms",
"name": "Terms of Service",
"url": "https://brokerexample.com/terms.html",
"requiresExplicitConsent": false
}
]
},
"lease": {
"@type": "Lease",
"leaseExpires": "2018-10-01T11:00:00Z"
},
"orderedItem": [
{
"@type": "OrderItem",
"position": 0,
"unitTaxSpecification": [
{
"@type": "TaxChargeSpecification",
"name": "VAT at 20%",
"price": 1,
"priceCurrency": "GBP",
"rate": 0.2
}
],
"acceptedOffer": {
"@type": "Offer",
"@id": "https://example.com/events/452#/offers/878",
"description": "Winger space for Speedball.",
"name": "Speedball winger position",
"price": 10,
"priceCurrency": "GBP",
"validFromBeforeStartDate": "P6D",
"allowCustomerCancellationFullRefund": true,
"latestCancellationBeforeStartDate": "P1D"
},
"orderedItem": {
"@type": "ScheduledSession",
"@id": "https://example.com/events/452/subEvents/132",
"identifier": 123,
"eventStatus": "https://schema.org/EventScheduled",
"maximumAttendeeCapacity": 30,
"remainingAttendeeCapacity": 20,
...
}
},
{
"@type": "OrderItem",
"position": 1,
"quantity": 2,
"acceptedOffer": {
"@type": "Offer",
"@id": "https://example.com/openactive/add-ons/de505ce7-351f-4f8f-90c4-29887947a603#offer",
"priceCurrency": "GBP",
"price": 1.5
},
"orderedItem": {
"@id": "https://example.com/openactive/add-ons/de505ce7-351f-4f8f-90c4-29887947a603",
"@type": "AddOn",
"name": "Adult Racket Hire"
},
"beta:parentOrderItem": {
"@type": "OrderItem",
"position": 0
}
},
{
"@type": "OrderItem",
"position": 2,
"quantity": 3,
"acceptedOffer": {
"@type": "Offer",
"@id": "https://example.com/openactive/add-ons/b9372482-8d6e-4d89-9160-58fa52d86944#offer",
"priceCurrency": "GBP",
"price": 1.5
},
"orderedItem": {
"@id": "https://example.com/openactive/add-ons/b9372482-8d6e-4d89-9160-58fa52d86944",
"@type": "AddOn",
"name": "Junior Racket Hire"
},
"beta:parentOrderItem": {
"@type": "OrderItem",
"position": 0
}
}
],
"totalPaymentDue": {
"@type": "PriceSpecification",
"price": 5,
"priceCurrency": "GBP"
},
"totalPaymentTax": [
{
"@type": "TaxChargeSpecification",
"name": "VAT at 20%",
"price": 1,
"priceCurrency": "GBP",
"rate": 0.2
}
]
}
PUT /api/order-quote-templates/e11429ea-467f-4270-ab62-e47368996fe8 HTTP/2
Host: example.com
Date: Mon, 8 Oct 2018 20:52:35 GMT
Accept: application/vnd.openactive.booking+json; version=1
Content-Type: application/vnd.openactive.booking+json; version=1
{
"@context": "https://openactive.io/",
"@type": "OrderQuote",
"brokerRole": "https://openactive.io/AgentBroker",
"broker": {
"@type": "Organization",
"name": "MyFitnessApp",
"url": "https://myfitnessapp.example.com",
"description": "A fitness app for all the community",
"logo": {
"@type": "ImageObject",
"url": "http://data.myfitnessapp.org.uk/images/logo.png"
},
"address": {
"@type": "PostalAddress",
"streetAddress": "Alan Peacock Way",
"addressLocality": "Village East",
"addressRegion": "Middlesbrough",
"postalCode": "TS4 3AE",
"addressCountry": "GB"
}
},
"seller": {
"@type": "Organization",
"@id": "https://example.com/api/organisations/123"
},
"orderedItem": [
{
"@id": "https://example.com/api/openactive/order-items/c67de59f-3f32-4751-ac4d-59431a13be0d",
"@type": "OrderItem",
"position": 0,
"acceptedOffer": "https://example.com/api/openactive/offers/1643725800-9c44e3f8-029c-4b47-845c-52198c29ff53",
"orderedItem": "https://example.com/api/openactive/4813dbc0-41df-42c3-aa85-cff6f89531dd/slots/f5b4d223-1c77-5e68-acbf-8624239fef13",
},{
"@id": "https://example.com/api/openactive/order-items/08d7e6a6-757f-4dd5-af0e-d1692bbd21c7",
"@type": "OrderItem",
"position": 1,
"quantity": 2,
"acceptedOffer": "https://example.com/openactive/add-ons/de505ce7-351f-4f8f-90c4-29887947a603#offer",
"orderedItem": "https://example.com/openactive/add-ons/de505ce7-351f-4f8f-90c4-29887947a603",
"beta:parentOrderItem": {
"@id": "https://example.com/api/openactive/order-items/c67de59f-3f32-4751-ac4d-59431a13be0d",
"@type": "OrderItem",
"position": 0
}
},{
"@type": "OrderItem",
"position": 2,
"quantity": 3,
"acceptedOffer": "https://example.com/openactive/add-ons/b9372482-8d6e-4d89-9160-58fa52d86944#offer",
"orderedItem": "https://example.com/openactive/add-ons/b9372482-8d6e-4d89-9160-58fa52d86944",
"beta:parentOrderItem": {
"@id": "https://example.com/api/openactive/order-items/c67de59f-3f32-4751-ac4d-59431a13be0d",
"@type": "OrderItem",
"position": 0
}
}
]
}
In this request we have transparently removed the original third order item and replaced it with an identical replacement. This is to show how the booking system must take the given OrderItems and remove existing ones from its original booking model where they are replaced. This is indicated by any order item with a position
property but not an @id
property.
Requests at P and B will be identical when referring to AddOns.
Cancelling an AddOn. As described in Open Booking API 8.2, simply removing the AddOn from the next request will remove it from the Order:
PUT /api/order-quotes/e11429ea-467f-4270-ab62-e47368996fe8 HTTP/2
Host: example.com
Date: Mon, 8 Oct 2018 20:52:35 GMT
Accept: application/vnd.openactive.booking+json; version=1
Content-Type: application/vnd.openactive.booking+json; version=1
{
"@context": "https://openactive.io/",
"@type": "OrderQuote",
"brokerRole": "https://openactive.io/AgentBroker",
"broker": {
"@type": "Organization",
"name": "MyFitnessApp",
"url": "https://myfitnessapp.example.com",
"description": "A fitness app for all the community",
"logo": {
"@type": "ImageObject",
"url": "http://data.myfitnessapp.org.uk/images/logo.png"
},
"address": {
"@type": "PostalAddress",
"streetAddress": "Alan Peacock Way",
"addressLocality": "Village East",
"addressRegion": "Middlesbrough",
"postalCode": "TS4 3AE",
"addressCountry": "GB"
}
},
"seller": {
"@type": "Organization",
"@id": "https://example.com/api/organisations/123"
},
"orderedItem": [
{
"@id": "https://example.com/api/openactive/order-items/c67de59f-3f32-4751-ac4d-59431a13be0d",
"@type": "OrderItem",
"position": 0,
"acceptedOffer": "https://example.com/api/openactive/offers/1643725800-9c44e3f8-029c-4b47-845c-52198c29ff53",
"orderedItem": "https://example.com/api/openactive/4813dbc0-41df-42c3-aa85-cff6f89531dd/slots/f5b4d223-1c77-5e68-acbf-8624239fef13",
},{
"@id": "https://example.com/api/openactive/order-items/5ec216fd-1680-48f5-ba86-33d80b2c60f4",
"@type": "OrderItem",
"position": 1,
"quantity": 3,
"acceptedOffer": "https://example.com/openactive/add-ons/b9372482-8d6e-4d89-9160-58fa52d86944#offer",
"orderedItem": "https://example.com/openactive/add-ons/b9372482-8d6e-4d89-9160-58fa52d86944",
"beta:parentOrderItem": {
"@id": "https://example.com/api/openactive/order-items/c67de59f-3f32-4751-ac4d-59431a13be0d",
"@type": "OrderItem",
"position": 0
}
}
]
}
To cancel an AddOn after B, simply use the same request as defined in the booking spec
PATCH /api/orders/e11429ea-467f-4270-ab62-e47368996fe8 HTTP/2
Host: example.com
Date: Mon, 8 Oct 2018 20:52:35 GMT
Accept: application/vnd.openactive.booking+json; version=1
Content-Type: application/vnd.openactive.booking+json; version=1
{
"@context": "https://openactive.io/",
"@type": "Order",
"orderedItem": [
{
"@id": "https://example.com/api/openactive/order-items/08d7e6a6-757f-4dd5-af0e-d1692bbd21c7",
"@type": "OrderItem",
"orderItemStatus": "https://openactive.io/CustomerCancelled"
}
]
}
However, if you try to cancel an OrderItem which has children, Brokers MUST cancel all Order Items:
PATCH /api/orders/e11429ea-467f-4270-ab62-e47368996fe8 HTTP/2
Host: example.com
Date: Mon, 8 Oct 2018 20:52:35 GMT
Accept: application/vnd.openactive.booking+json; version=1
Content-Type: application/vnd.openactive.booking+json; version=1
{
"@context": "https://openactive.io/",
"@type": "Order",
"orderedItem": [
{
"@id": "https://example.com/api/openactive/order-items/c67de59f-3f32-4751-ac4d-59431a13be0d",
"@type": "OrderItem",
"orderItemStatus": "https://openactive.io/CustomerCancelled"
},{
"@id": "https://example.com/api/openactive/order-items/08d7e6a6-757f-4dd5-af0e-d1692bbd21c7",
"@type": "OrderItem",
"orderItemStatus": "https://openactive.io/CustomerCancelled"
},{
"@id": "https://example.com/api/openactive/order-items/5ec216fd-1680-48f5-ba86-33d80b2c60f4",
"@type": "OrderItem",
"orderItemStatus": "https://openactive.io/CustomerCancelled"
}
]
}
As opposed to:
PATCH /api/orders/e11429ea-467f-4270-ab62-e47368996fe8 HTTP/2
Host: example.com
Date: Mon, 8 Oct 2018 20:52:35 GMT
Accept: application/vnd.openactive.booking+json; version=1
Content-Type: application/vnd.openactive.booking+json; version=1
{
"@context": "https://openactive.io/",
"@type": "Order",
"orderedItem": [
{
"@id": "https://example.com/api/openactive/order-items/c67de59f-3f32-4751-ac4d-59431a13be0d",
"@type": "OrderItem",
"orderItemStatus": "https://openactive.io/CustomerCancelled"
}
]
}
To which the Booking System MUST return this error:
HTTP/2 400 Bad Request
Date: Mon, 8 Oct 2018 20:52:36 GMT
Content-Type: application/vnd.openactive.booking+json; version=1
{
"@context": "https://openactive.io/",
"@type": "OrphansNotPermittedError",
"description": "Cannot orphan OrderItems without also removing those OrderItems"
}
Order Items cannot be created without a parent:
PUT /api/order-quotes/e11429ea-467f-4270-ab62-e47368996fe8 HTTP/2
Host: example.com
Date: Mon, 8 Oct 2018 20:52:35 GMT
Accept: application/vnd.openactive.booking+json; version=1
Content-Type: application/vnd.openactive.booking+json; version=1
{
"@context": "https://openactive.io/",
"@type": "OrderQuote",
...
"orderedItem": [
{
"@type": "OrderItem",
"position": 1,
"quantity": 1,
"acceptedOffer": "https://example.com/openactive/add-ons/de505ce7-351f-4f8f-90c4-29887947a603#offer",
"orderedItem": "https://example.com/openactive/add-ons/de505ce7-351f-4f8f-90c4-29887947a603",
"beta:parentOrderItem": {
"@type": "OrderItem",
"position": 10000
}
}
]
}
To which the Booking System MUST return this error:
HTTP/2 400 Bad Request
Date: Mon, 8 Oct 2018 20:52:36 GMT
Content-Type: application/vnd.openactive.booking+json; version=1
{
"@context": "https://openactive.io/",
"@type": "OrphansNotPermittedError",
"description": "Cannot create OrderItems with a parent that does not exist"
}
AddOns MAY be modified before B using replacement as defined above. However after B, AddOns MUST NOT increase in quantity for that Order. If more AddOns are required, the Broker will need to create a new Order. AddOns however can reduce in quantity, as this can fit nicely with the cancellation flow:
PATCH /api/orders/e11429ea-467f-4270-ab62-e47368996fe8 HTTP/2
Host: example.com
Date: Mon, 8 Oct 2018 20:52:35 GMT
Accept: application/vnd.openactive.booking+json; version=1
Content-Type: application/vnd.openactive.booking+json; version=1
{
"@context": "https://openactive.io/",
"@type": "OrderQuote",
"brokerRole": "https://openactive.io/AgentBroker",
"broker": {
...
},
"seller": {
"@type": "Organization",
"@id": "https://example.com/api/organisations/123"
},
"orderedItem": [
{
"@id": "https://example.com/api/openactive/order-items/08d7e6a6-757f-4dd5-af0e-d1692bbd21c7",
"@type": "OrderItem",
"quantity": 1,
"acceptedOffer": "https://example.com/openactive/add-ons/de505ce7-351f-4f8f-90c4-29887947a603#offer",
"orderedItem": "https://example.com/openactive/add-ons/de505ce7-351f-4f8f-90c4-29887947a603",
"beta:parentOrderItem": {
"@id": "https://example.com/api/openactive/order-items/c67de59f-3f32-4751-ac4d-59431a13be0d",
"@type": "OrderItem"
}
}
]
}
Order Items cannot be forced to create a recursive tree:
PUT /api/order-quotes/e11429ea-467f-4270-ab62-e47368996fe8 HTTP/2
Host: example.com
Date: Mon, 8 Oct 2018 20:52:35 GMT
Accept: application/vnd.openactive.booking+json; version=1
Content-Type: application/vnd.openactive.booking+json; version=1
{
"@context": "https://openactive.io/",
"@type": "OrderQuote",
...
"orderedItem": [
{
"@type": "OrderItem",
"position": 1,
"quantity": 1,
"acceptedOffer": "https://example.com/openactive/add-ons/de505ce7-351f-4f8f-90c4-29887947a603#offer",
"orderedItem": "https://example.com/openactive/add-ons/de505ce7-351f-4f8f-90c4-29887947a603",
"beta:parentOrderItem": {
"@type": "OrderItem",
"position": 1
}
}
]
}
To which the Booking System MUST return this error:
HTTP/2 400 Bad Request
Date: Mon, 8 Oct 2018 20:52:36 GMT
Content-Type: application/vnd.openactive.booking+json; version=1
{
"@context": "https://openactive.io/",
"@type": "RecursiveOrderItemError",
"description": "Cannot create an OrderItem which references itself"
}
Order Items cannot be created at multiple levels of inheritance. This is partly for simplicity, especially with the above error if A is a child of B which is in turn a Child of A, the logic to detect this becomes needlessly complex for a use-case which we have been unable to come up with.
PUT /api/order-quotes/e11429ea-467f-4270-ab62-e47368996fe8 HTTP/2
Host: example.com
Date: Mon, 8 Oct 2018 20:52:35 GMT
Accept: application/vnd.openactive.booking+json; version=1
Content-Type: application/vnd.openactive.booking+json; version=1
{
"@context": "https://openactive.io/",
"@type": "OrderQuote",
...
"orderedItem": [
{
"@type": "OrderItem",
"position": 0,
"quantity": 1,
"acceptedOffer": "https://example.com/openactive/add-ons/de505ce7-351f-4f8f-90c4-29887947a603#offer",
"orderedItem": "https://example.com/openactive/add-ons/de505ce7-351f-4f8f-90c4-29887947a603",
},
{
"@type": "OrderItem",
"position": 1,
"quantity": 1,
"acceptedOffer": "https://example.com/openactive/add-ons/de505ce7-351f-4f8f-90c4-29887947a603#offer",
"orderedItem": "https://example.com/openactive/add-ons/de505ce7-351f-4f8f-90c4-29887947a603",
"beta:parentOrderItem": {
"@type": "OrderItem",
"position": 0
}
},
{
"@type": "OrderItem",
"position": 2,
"quantity": 1,
"acceptedOffer": "https://example.com/openactive/add-ons/de505ce7-351f-4f8f-90c4-29887947a603#offer",
"orderedItem": "https://example.com/openactive/add-ons/de505ce7-351f-4f8f-90c4-29887947a603",
"beta:parentOrderItem": {
"@type": "OrderItem",
"position": 1
}
}
]
}
To which the Booking System MUST return this error:
HTTP/2 400 Bad Request
Date: Mon, 8 Oct 2018 20:52:36 GMT
Content-Type: application/vnd.openactive.booking+json; version=1
{
"@context": "https://openactive.io/",
"@type": "UnexpectedOrderItemGrandparentError",
"description": "Cannot create an OrderItem which references a child of another OrderItem"
}
If the AddOn has a stock level which can run out, when running C1 or
C2 then the response MUST respond with an OpportunityHasInsufficientCapacityError
.
HTTP/2 409 Conflict
Date: Mon, 8 Oct 2018 20:52:36 GMT
Content-Type: application/vnd.openactive.booking+json; version=1
{
"@context": "https://openactive.io/",
"@type": "OrderQuote",
"@id": "https://example.com/api/order-quotes/e11429ea-467f-4270-ab62-e47368996fe8",
...
"orderedItem": [
{
..
"error": [
{
"@type": "OpportunityHasInsufficientCapacityError",
"name": "There is an insufficient quantity of this item available to book",
"statusCode": 409
}
]
}
],
}
Proposer
Playfinder/Bookteq
Use Case
When pulling a list of FacilityUses, I want to be able to view what additional paid facilities this has. Examples include racket hire, floodlight hire, tennis balls and other sports equipment.
Why is this not covered by existing properties?
An
Offer
is described as:This is currently too restrictive to allow other types of Offers to be purchased by the Customer.
Please provide a link to example data
The Bookteq widget on Padel4all (https://play.padel4all.com/dashboard/book user account required) has options for hiring equipment.
Proposal
The
FacilityUse
andIndividualFacilityUse
models should have an extrabeta:addOns
property which contains an array of Offer modelsExample