--- sidebar_label: 'Fulfilling a deal' sidebar_position: 3 --- # Fulfilling a deal ## Process to add discounts to a deal When a deal has been reserved for an order, the Future Ordering backend will send events to Loyalty Integration for every change made to the order, informing that a change has occurred. The sequence diagram for this process is shown below. ```mermaid sequenceDiagram actor user as User participant fo as Future Ordering participant loyint as Loyalty Integration participant loy as Loyalty participant posint as POS user ->> fo: Add item/remove item/change order fo ->> loyint: (Event) order.deal.fulfillment_required loyint->>fo: Get order with order-id loyint ->> loy: Are there discounts for this order? loy-->>loyint: Discounts loyint ->> fo: Updated discount information with POS Discount IDs fo ->> user: Display provided discounts user ->> fo: Pay and Place order fo ->> posint: Place Order, including
mapping and discount information note over posint: Places order ``` ## Fulfillment required event Example for a fulfillment required event: ```json { "specversion": "1.0", "data": { "dealId": "a0e34dd7-1052-4918-9896-2930d4c121e1", "tenantId": "my_tenant", "key": "key-for-my-own-integration", "orderVersionId": "3", "orderId": "ebuvUDP0i8i" }, "dataContentType": "application/json", "id": "ac76bae2-5b0c-48ae-a308-c04932a06742", "source": "https://api.futureordering.com", "time": "2024-05-21T19:41:37.3430427+00:00", "type": "com.futureordering.order.deal.fulfillment_required" } ``` If the value of the `orderVersionId` property of the deal is lower than the current version of the order, the `fulfillment_required` event is sent to Loyalty Integration. This event is triggered every time a customer adds a product to their basket or makes a significant change to their order, such as changing the order type, collection type, desired ready time, etc. To respond to this `fulfillment_required` event, the deal must be updated using one of the PATCH endpoints. The current `versionId` of the order must be included in the PATCH request together with `isFulfilled=true`. When a `fulfillment_required` event is received, this is the opportunity to add discounts to the order. If an integration fails to provide updates for the current `orderVersionId` within a configured timespan, the order will either continue without discount updates or fail in the Future Ordering backend. This timespan must be selected carefully, as it affects the user experience. A user cannot pay for the order until the Loyalty Integration has provided discounts to Future Ordering. If the Loyalty Integration is temporarily down or experiencing issues, the user may be kept waiting for a long time before being able to proceed to payment if the configured timespan is too long. This is dependent on configuration, and these settings must be provided to Future Ordering when enabling the loyalty integration. ## Update deal APIs Below are a couple of examples of how to update a deal with discounts for an order after a `fulfillment_required` event has been received. Example: Given that the user has bought 2 Taco Sauces and 1 Future Burger and the loyalty system has a discount that gives 50% off if the user buys one Future Burger and one Taco Sauce. Initially when a fulfillment_required event is received, the order must be fetched by using: - GET `/orders/{order-id}/` Below is the most central part of the order response containing `versionId` and `items[]`. ```json { "versionId" : "3", "items": [ { "subItemType": "none", "isContainer": false, "type": "menuItem", "menuItemId": "10", "title": "Taco Sauce", "quantity": 2, "configItems": [], "price": { "amount": 500, "isVatIncluded": true, "vat": [ { "type": "12%", "percent": 12 } ] }, "id": "c0c88723-3a99-404b-b4e2-27815fe0b335", "unitIds": { "c0c88723-3a99-404b-b4e2-27815fe0b335": [ "c0c88723-3a99-404b-b4e2-27815fe0b335.0", "c0c88723-3a99-404b-b4e2-27815fe0b335.1" ] } }, { "subItemType": "none", "isContainer": false, "type": "menuItem", "menuItemId": "20", "title": "Future Burger", "quantity": 1, "configItems": [], "price": { "amount": 1000, "isVatIncluded": true, "vat": [ { "type": "12%", "percent": 12 } ] }, "id": "b1c88723-3a99-404b-b4e2-27815fe0b443", "unitIds": { "b1c88723-3a99-404b-b4e2-27815fe0b443": [ "b1c88723-3a99-404b-b4e2-27815fe0b443.0" ] } } ] } ``` Using one of the PATCH endpoints, discounts can be applied. This example uses the following PATCH request, where several deals can be updated in one request. Note that `order-id` in the PATCH path is the `order-id` from the event. - PATCH `/orders/{order-id}/deals` Depending on how the discounts are being added to the deal - the discounts will be shown in different ways in UI. In this first example the discount will be shown with the Future Burger in the UI. ![Discount One Item](../images/discount-one-item.png) The following PATCH request should be sent to achieve the example above. ```json { "a0e34dd7-1052-4918-9896-2930d4c121e1": { "isFulfilled": true, "visibility": "notShown", "title": { "translations": { "en-GB": "Changed title", "default": "Summer deal" } }, "name": "internal name", "orderVersionId": "3", "discounts": [ { "title": { "translations": { "en-GB": "British English translation", "default": "Buy Burger + Sauce and get 50% off" } }, "relatedItemIds": [ "c0c88723-3a99-404b-b4e2-27815fe0b335", "b1c88723-3a99-404b-b4e2-27815fe0b443" ], "relatedItemUnitIds": [ "c0c88723-3a99-404b-b4e2-27815fe0b335.0", "b1c88723-3a99-404b-b4e2-27815fe0b443.0" ], "amounts": [ { "discountedItemId": "b1c88723-3a99-404b-b4e2-27815fe0b443", "discountedItemUnitId": "b1c88723-3a99-404b-b4e2-27815fe0b443.0", "amount": 750, "vat": [ { "type": "12.5", "percent": 12.5 } ] } ], "mappingDetails": { "externalDiscountId": "101" } } ] } } ``` The second example shows how the discounts can be divided to be shown both with the Taco Sauce and with the Future Burger in the UI. The discount is split between Burger and Taco Sauce. ![Discount Per Item](../images/discount-per-item.png) The following PATCH request should be sent to achieve the example above. ```json { "a0e34dd7-1052-4918-9896-2930d4c121e1": { "isFulfilled": true, "title": { "translations": { "en-Gb": "Changed title", "default": "Buy Burger + Sauce and get 50% off" } }, "name": "internal name", "orderVersionId": "3", "discounts": [ { "title": { "translations": { "en-Gb": "British English translation", "default": "Buy Burger + Sauce and get 50% off" } }, "relatedItemIds": [ "c0c88723-3a99-404b-b4e2-27815fe0b335", "b1c88723-3a99-404b-b4e2-27815fe0b443" ], "relatedItemUnitIds": [ "c0c88723-3a99-404b-b4e2-27815fe0b335.0", "b1c88723-3a99-404b-b4e2-27815fe0b443.0" ], "amounts": [ { "discountedItemId": "c0c88723-3a99-404b-b4e2-27815fe0b335", "discountedItemUnitId": "c0c88723-3a99-404b-b4e2-27815fe0b335.0", "amount": 250, "vat": [ { "type": "12.5", "percent": 12.5 } ] }, { "discountedItemId": "b1c88723-3a99-404b-b4e2-27815fe0b443", "discountedItemUnitId": "b1c88723-3a99-404b-b4e2-27815fe0b443.0", "amount": 500, "vat": [ { "type": "12.5", "percent": 12.5 } ] } ], "mappingDetails": { "externalDiscountId": "101" } } ] } } ``` `unitIds` represent specific quantity units of an item. If an item with ID `c0c88723-3a99-404b-b4e2-27815fe0b335` has quantity `3`, that item has `3` units and `3` unitIds. - `relatedItemIds` Can be used to ensure that the same item isn't part of several discounts. - `relatedItemUnitIds` Can be used to ensure that the same item unit isn't part of several discounts. RelatedItemIds and relatedItemUnitIds are required to be populated with itemIds that exist in the order. - `amounts[].discountedItemId` This item will receive this amount of discount - `amounts[].discountedItemUnitId` This specific unit will receive this amount of discount DiscountedItemIds and DiscountedItemUnitIds are required to be populated with itemIds that exist in the order. If a discount should apply to both Taco Sauces in the example - each unit must have one `amount` for the discount. This is needed to get correct VAT calculations. `mappingDetails` on the `discount` object can consist of any generic property that may be needed by the loyalty integration itself. But there is one property that is **_required_** to be set by the loyalty integration _if_ Future Ordering Stsgen2 integration will send the order to the POS and this property is the `externalDiscountId`. - `mappingDetails.externalDiscountId` represents a preconfigured discountId in Simphony that Future Ordering Stsgen2 Integration must use when sending an order with discounts to Stsgen2. Here is an example when 2 Taco sauces get 50% off for each unit. ![Discount Two Units](../images/discount-two-units.png) ```json { "a0e34dd7-1052-4918-9896-2930d4c121e1": { "isFulfilled": true, "title": { "translations": { "en-Gb": "Changed title", "default": "Buy 2 Taco Sauce and get 50% off" } }, "name": "internal name", "orderVersionId": "3", "discounts": [ { "title": { "translations": { "en-Gb": "British English translation", "default": "Buy 2 Taco Sauce and get 50% off" } }, "relatedItemIds": [ "c0c88723-3a99-404b-b4e2-27815fe0b335" ], "relatedItemUnitIds": [ "c0c88723-3a99-404b-b4e2-27815fe0b335.0", "c0c88723-3a99-404b-b4e2-27815fe0b335.1" ], "amounts": [ { "discountedItemId": "c0c88723-3a99-404b-b4e2-27815fe0b335", "discountedItemUnitId": "c0c88723-3a99-404b-b4e2-27815fe0b335.0", "amount": 250, "vat": [ { "type": "12.5", "percent": 12.5 } ] }, { "discountedItemId": "c0c88723-3a99-404b-b4e2-27815fe0b335", "discountedItemUnitId": "c0c88723-3a99-404b-b4e2-27815fe0b335.1", "amount": 250, "vat": [ { "type": "12.5", "percent": 12.5 } ] } ], "mappingDetails": { "externalDiscountId": "101" } } ] } } ``` The parameter `isFulfilled` represents an action that the API should try to complete. If all deals have this parameter set to true the API will try to fulfill and validate the deals if that passes the API will attempt to apply discounts from each of the deals. Note that all deals have to pass validation for discounts to be applied. If the parameter `isFulfilled` is true, then `orderVersionId` must match the current version of the order. The current `orderVersionId` can be retrieved from the Orders API: `GET /orders/{order-id}`. The response includes `versionId`, which can be used as `orderVersionId`. ```json { "versionId": "6" } ``` If `orderVersionId` is lower than the current order version, the Deals API responds with the following payload: ```json { "innerCode": "order.deal.version-mismatch", "outerCode": 400, "data": null, "code": "deal.generic.bad-request", "message": "OrderVersionId is not matching the current version of the order fulfillment is not possible", "innerError": null } ``` If the PATCH request is successful and with the property `isFulfilled: true` the order should have discounts applied. Example response: ```json { "statusCode": "successful", "message": "Patched", "innerCode": null, "successful": true, "outerCode": 200 } ``` `innerCode` values that can occur for the PATCH request when `outerCode` is `400 Bad Request`: - `order.deal.version-mismatch`: Order version ID does not match the current order version ID. A lower order version ID was provided. - `order.generic.not.found`: Order is not found. - `order.deal.property-missing`: Fulfillment of deal is not possible without order version id. - `order.deal.annulled`: Deal is annulled and does not accept changes. - `order.deal.invalid-order-version`: An invalid format for order version id is given, or the order version is higher than the current order version. - `order.deal.set-discount-unavailable`: Order does not accept discounts and order cannot be updated for current order version. :::note There are two options when building functionality for `fulfillment_required` events. 1. Always use the information in each event when executing a PATCH request. If a PATCH request fails, the integration waits for the next event before calculating discounts for the current order version. 2. When a `fulfillment_required` event is sent to the loyalty integration, the latest order can be fetched to retrieve the latest `versionId`. This enables discount calculations to always be based on the most recent order version. For instance, if the latest order version is `4`, a subsequent event with `orderVersionId` `3` can be safely disregarded, as discounts are intended to be computed for order version `4`. To avoid concurrent processing of multiple events for the same order, implementing an order-locking mechanism is beneficial. :::