v1 order ahead feature

You can add support in your fulfillment for users to schedule pickup and delivery food orders in advance. Before implementing this support in fulfillment, create a service inventory feed that specifies the hours for users to place advance orders, as described in the inventory feed schema (AdvanceServiceDeliveryHoursSpecification).

Advance order slots

Google proposes advance order slots in 15 minute increments, for up to 7 days ahead, based on the fulfillment times for a restaurant or service (as defined in AdvanceServiceDeliveryHoursSpecification).

To retrieve the proposed order ahead slots, use the following values from the fulfillmentPreference field of the FoodCartExtension object at checkout:

  • PickupInfo.pickupTimeIso8601
  • DeliveryInfo.deliveryTimeIso8601

Implement advance orders at checkout

The table below lists the possible ways you can implement your fulfillment's response at checkout time when users try to place orders.

Scenario Fulfillment behavior
Advance order can be fulfilled for the requested slot. Accept the P0M ("as soon as possible") or FUTURE_SLOT cart by creating a ProposedOrder with the same slot. For an example of a checkout response accepting a slot, see this code snippet.
Advance order cannot be fulfilled for the requested slot. Your fulfillment should do the following:
  1. Reject the requested P0M or FUTURE_SLOT cart, and indicate the reason why the order cannot be fulfilled in the FoodErrorExtension object.
    • If the order cannot be fulfilled due to capacity, specify a FoodOrderError of error type NO_CAPACITY.
    • If the order cannot be fulfilled because the restaurant is closed, specify a FoodOrderError of error type CLOSED.
    • If the order cannot be fulfilled because of some other reason, specify a FoodOrderError of error type UNAVAILABLE_SLOT.
  2. If possible, provide alternative P0M or FUTURE_SLOT values in correctedProposedOrder. These values should be all the valid fulfillment slots for the next 7 days starting from the current time. Include the P0M slot whenever applicable.

For an example of a checkout response proposing alternative slots, see this code snippet.

Alternative slots for order fulfillment

At checkout, if the proposed order ahead slots by Google are not suitable, your fulfillment can suggest alternatives using the CheckoutResponseMessage object.

To specify alternative advance order slots, respond to the checkout request with a FoodErrorExtension and set the following values:

  1. In the foodOrderErrors parameter, specify the error type (like UNAVAILABLE_SLOT, NO_CAPACITY, or CLOSED).
  2. In the correctedProposedOrder parameter, provide alternative P0M or FUTURE_SLOT values via availableFulfillmentOptions.

The alternative slots should be for the next 7 days from the time of order placement, and include all slots in which the user's requested cart can be fulfilled.

For example, say that lunch specials are available only from Monday to Friday from 11 AM to 1 PM. The user then tries adding lunch specials to their cart but their selected slot is not available. In this case, your fulfillment should retain the lunch specials in the cart, and return only the 11 AM to 1 PM slots for the next 7 days

You should omit the correctedProposedOrder.Cart.fulfillmentPreference object in your response.

If there are no available slots, or if the restaurant or service does not support advance orders, then you do not need to provide a correctedProposedOrder.

See the examples below for the JSON messages between your fulfillment and Google during the checkout request and response flow for an advance order, when the restaurant or service is available for taking pre-orders.

Example: CheckoutRequest with delivery slot

The snippet below shows an example of a checkout request with an advance order delivery slot.

{
  "inputs": [
    {
      "intent": "actions.foodordering.intent.CHECKOUT",
      "arguments": [
        {
          "extension": {
            "@type": "type.googleapis.com/google.actions.v2.orders.Cart",
            "merchant": {
              "id": "https://www.exampleprovider.com/merchant/id1",
              "name": "Cucina Venti"
            },
            "lineItems": [
              {
                "name": "Sizzling Prawns Dinner",
                "type": "REGULAR",
                "id": "sample_item_offer_id_1",
                "offerId": "https://www.exampleprovider.com/menu/item/offer/id1",
                "quantity": 1,
                "price": {
                  "type": "ESTIMATE",
                  "amount": {
                    "currencyCode": "USD",
                    "units": "16",
                    "nanos": 750000000
                  }
                },
              }
            ],
            "extension": {
              "@type": "type.googleapis.com/google.actions.v2.orders.FoodCartExtension",
              "fulfillmentPreference": {
                "fulfillmentInfo": {
                  "delivery": {
                    // Deliver at 6:30PM.
                    "deliveryTimeIso8601": "2017-12-14T18:30:00-07:00"
                  }
                }
              },
              "location": {
                ...
              }
            }
          }
        }
      ]
    }
  ]
}

Example: CheckoutResponse accepting the slot

The snippet below shows an example of a checkout response where your fulfillment accepts the proposed advance order slots.

{
  "expectUserResponse": false,
  "finalResponse": {
    "richResponse": {
      "items": [
        {
          "structuredResponse": {
            "checkoutResponse": {
              "proposedOrder": {
                "id": "sample_proposed_order_id_1",
                "cart": {
                  "merchant": {
                    "id": "https://www.exampleprovider.com/merchant/id1",
                    "name": "Falafel Bite"
                  },
                  "lineItems": [
                    {
                      "name": "Sizzling Prawns Dinner",
                      "type": "REGULAR",
                      "id": "sample_item_offer_id_1",
                      "offerId": "https://www.exampleprovider.com/menu/item/offer/id1",
                      "quantity": 1,
                      "price": {
                        "type": "ESTIMATE",
                        "amount": {
                          "currencyCode": "USD",
                          "units": "16",
                          "nanos": 750000000
                        }
                      },
                    }
                  ],
                  "extension": {
                    "@type": "type.googleapis.com/google.actions.v2.orders.FoodCartExtension",
                    "fulfillmentPreference": {
                      "fulfillmentInfo": {
                        "delivery": {
                          // Same as the time in the request.
                          "deliveryTimeIso8601": "2017-12-14T18:30:00-07:00"
                        }
                      }
                    },
                    "location": {
                      ...
                     }
                   }
                },
                "totalPrice": {
                  "type": "ESTIMATE",
                  "amount": {
                    // Represents $16.75
                    "currencyCode": "USD",
                    "units": "16",
                    "nanos": 750000000
                  }
                },
                "extension": {
                  "@type": "type.googleapis.com/google.actions.v2.orders.FoodOrderExtension",
                  // Send whole proposed order back.
                  "availableFulfillmentOptions": [
                    "fulfillmentInfo": {
                      "delivery": {
                        // Same as the time in the request.
                        "deliveryTimeIso8601": "2017-12-14T18:30:00-07:00"
                      }
                    }
                  ]
                }
              },
              "paymentOptions": {
                ...
              }
            }
          }
        }
      ]
    }
  }
}

Example: CheckoutResponse with alternate slots

The snippet below shows an example of a checkout response where your fulfillment proposes alternative advance order slots. Note that the correctedProposedOrder.Cart.fulfillmentPreference object should be omitted in your response.

{
  "expectUserResponse": false,
  "finalResponse": {
    "richResponse": {
      "items": [
        {
          "structuredResponse": {
            "error": {
              "@type": "type.googleapis.com/google.actions.v2.orders.FoodErrorExtension",
              "foodOrderErrors": [
                {
                  "error": "UNAVAILABLE_SLOT", // Cart level error
                  "description": "The restaurant is closed."
                }
              ],
              "correctedProposedOrder": {
                // Send whole original cart back,
                // without the fulfillmentPreference.
                "cart": {
                  ...
                },
                "otherItems": {
                  ...
                },
                "totalPrice": {
                  ...
                },
                "extension": {
                  "@type": "type.googleapis.com/google.actions.v2.orders.FoodOrderExtension",
                  "availableFulfillmentOptions": [
                    "fulfillmentInfo": {
                      "delivery": {
                        "deliveryTimeIso8601": "2017-12-14T19:00:00-07:00"
                      }
                    },
                    "fulfillmentInfo": {
                      "delivery": {
                        "deliveryTimeIso8601": "2017-12-14T19:30:00-07:00"
                      }
                    },
                    "fulfillmentInfo": {
                      "delivery": {
                        "deliveryTimeIso8601": "2017-12-14T20:00:00-07:00"
                      }
                    }
                  ]
                }
              },
              "paymentOptions": {
                ...
              }
            }
          }
        }
      ]
    }
  }
}

Implement advance orders at submit order

At order submission, if there is an issue with the advance order slots, your SubmitOrderResponseMessage should include the reason (like UNAVAILABLE_SLOT or UNKNOWN) in the RejectionInfo object.

Update the state of the order from CREATED to CONFIRMED in the OrderState object when the order is accepted by the provider. Include the selected time slot in your confirmation email to the user.

If your fulfillment sends the order to the restaurant later, send Google an update using the Asynchronous Order Update Action.

In the OrderUpdate object of your fulfillment's submit order response or subsequent asynchronous order updates, include an estimatedFulfillmentTimeIso8601 with the value set as follows:

  • When the order status is CREATED or CONFIRMED, set the value to the delivery or pickup time that the user scheduled for their advance order.
  • When there is a more accurate estimated delivery time from the restaurant or service, set the value to the estimated time for delivery or pickup time.

Example: SubmitOrderRequest with delivery slot

The snippet below shows an example of a submit order request indicating the advance order slot that the user selected.

{
  "inputs": [
    {
      "intent": "actions.intent.TRANSACTION_DECISION",
      "arguments": [
        {
          "transactionDecisionValue": {
            "order": {
              "finalOrder": {
                "cart": {
                  "notes": "Guest prefers their food to be hot when it is delivered.",
                  "merchant": {
                    "id": "https://www.exampleprovider.com/merchant/id1",
                    "name": "Cucina Venti"
                  },
                  "lineItems": [
                    {
                      "name": "Sizzling Prawns Dinner",
                      "type": "REGULAR",
                      "id": "sample_item_offer_id_1",
                      "offerId": "https://www.exampleprovider.com/menu/item/offer/id1",
                      "quantity": 1,
                      "price": {
                        "type": "ESTIMATE",
                        "amount": {
                          "currencyCode": "USD",
                          "units": "16",
                          "nanos": 750000000
                        }
                      }
                    }
                  ],
                  "extension": {
                    "@type": "type.googleapis.com/google.actions.v2.orders.FoodCartExtension",
                    "fulfillmentPreference": {
                      "fulfillmentInfo": {
                        "delivery": {
                          "deliveryTimeIso8601": "2017-12-14T18:30:00-07:00"
                        }
                      }
                    }
                    "contact": {
                      ...
                    }
                  }
                },
                "totalPrice": {
                  "type": "ESTIMATE",
                  "amount": {
                    "currencyCode": "USD",
                    "units": "16",
                    "nanos": 750000000
                  }
                },
                "id": "sample_final_order_id",
                "extension": {
                  // Send whole proposed order back.
                  "availableFulfillmentOptions": [
                    "fulfillmentInfo": {
                      "delivery": {
                        "deliveryTimeIso8601": "2017-12-14T18:30:00-07:00"
                      }
                   ]
                }
              },
              "googleOrderId": "sample_google_order_id",
              "orderDate": "2017-07-17T12:00:00Z",
              "paymentInfo": {
                ...
              }
            }
          }
        }
      ]
    }
  ]
}

Example: SubmitOrderResponse accepting the order

The snippet below shows an example of a submit order response where your fulfillment confirms that it has accepted the user's advance order.

{
  "expectUserResponse": false,
  "finalResponse": {
    "richResponse": {
      "items": [
        {
          "structuredResponse": {
            "orderUpdate": {
              "actionOrderId": "sample_action_order_id",
              "orderState": {
                "state": "CREATED",
                "label": "Order placed"
              },
              "receipt": {
                "userVisibleOrderId": "userVisibleId1234"
              },
              "updateTime": "2017-07-17T12:00:00Z",
              "orderManagementActions": [
                ...
              ],
              "infoExtension": {
                 "@type": "type.googleapis.com/google.actions.v2.orders.FoodOrderUpdateExtension",
                 // Same as the user selected time.
                 "estimatedFulfillmentTimeIso8601": "2017-12-14T18:30:00-07:00"
              }
            }
          }
        }
      ]
    }
  }
}

Example: SubmitOrderResponse declining the order because of unavailability of slot

The snippet below shows an example of a submit order response where your fulfillment declines a user's advance order because of an unavailable slot.

{
  "expectUserResponse": false,
  "finalResponse": {
    "richResponse": {
      "items": [
        {
          "structuredResponse": {
            "orderUpdate": {
              "actionOrderId": "sample_action_order_id",
              "orderState": {
                "state": "REJECTED",
                "label": "Unavailable slot"
              },
              "rejectionInfo": {
                // Note that this UNAVAILABLE_SLOT is different from the enum
                // with the same name proposed for FoodOrderError.
                "state": "UNAVAILABLE_SLOT",
                "label": "Unavailable slot"
              },
              "updateTime": "2017-07-17T12:00:00Z",
              "orderManagementActions": [
                ...
              ]
            }
          }
        }
      ]
    }
  }
}

Advance orders examples

The AdvanceServiceDeliveryHoursSpecification type can be used to specify the delivery or pickup hours for users to schedule their order in advance.

Note: There are two separate time windows that you must specify for service fulfillment: the ordering window which specifies when users can place an order, and the fulfillment window which specifies when the order be fulfilled. The OpeningHoursSpecification object defines when the user can place the order. Its child fulfillment times (ServiceDeliveryHoursSpecification or AdvanceServiceDeliveryHoursSpecification) define when the order can be fulfilled.

The following example defines a service's hours for accepting advance orders, with 15 minute service intervals.

{
  "hoursAvailable": [
    {
      "@type": "OpeningHoursSpecification",
      "opens": "T00:00:00", // Ordering available 24 hours
      "closes": "T23:59:59",
      "deliveryHours": [
        {
          "@type": "ServiceDeliveryHoursSpecification",
          "opens": "T09:00:00", // ASAP orders b/w 9am and 8:59:59pm
          "closes": "T21:00:00",
          "deliveryLeadTime": {
            "value": "60",
            "unitCode": "MIN"
          }
        },
        {
          "@type": "AdvanceServiceDeliveryHoursSpecification",
          "opens": "T10:00:00",  // Delivery between 10AM and 7:59:59PM
          "closes": "T20:00:00",
          "serviceTimeInterval": "PT15M", // in slots spaced 15 minutes apart (ISO8601)
          "advanceBookingRequirement": {
            "minValue": 60,   // The slot should be at least 60 mins away
            "maxValue": 8640, // but not more than 6 days away
            "unitCode": "MIN"
          }
        }
      ]
    }
  ]
}

The following example shows how you might specify that the service is open for same-day orders on Christmas day but closed for advanced orders scheduled for that day. This example supports the following scenarios:

  • Users can make an order on December 25th for same day delivery.
  • Users can make an advance order on December 25th for delivery scheduled for December 27th.
  • Users cannot make an advance order on December 22nd for delivery scheduled on December 25th.
{
  "specialOpeningHoursSpecification": {
    "@type": "AdvanceServiceDeliveryHoursSpecification",
    "validFrom": "2018-12-25T00:00:00-07:00",
    "validThrough": "2018-12-26T00:00:00-07:00",
    "opens": "T00:00:00", // No advance ordering
    "closes": "T00:00:00"
  }
}

The following example shows how you might specify that the service is closed for same-day orders or advance orders scheduled for Christmas day, but open for advanced orders scheduled for a later day. This example supports the following scenarios:

  • Users cannot make an order on December 25th for same day delivery.
  • Users can make an advance order on December 25th for delivery scheduled for December 27th.
  • Users cannot make an advance order on December 22nd for delivery scheduled on December 25th.
{
  "specialOpeningHoursSpecification": [
    {
      "@type": "ServiceDeliveryHoursSpecification",
      "validFrom": "2018-12-25T00:00:00-07:00",
      "validThrough": "2018-12-26T00:00:00-07:00",
      "opens": "T00:00:00", // No ASAP ordering on Christmas
      "closes": "T00:00:00"
    },
    {
      "@type": "AdvanceServiceDeliveryHoursSpecification",
      "validFrom": "2018-12-25T00:00:00-07:00",
      "validThrough": "2018-12-26T00:00:00-07:00",
      "opens": "T00:00:00", // Orders cannot be scheduled for Christmas
      "closes": "T00:00:00"
    }
  ]
}

The following sample Service accepts orders 24x7 and delivers from 10 AM-2:59:59 PM on weekdays:

...
{
  "@type": "OpeningHoursSpecification",
  "opens": "T00:00:00",
  "closes": "T23:59:59",
  "deliveryHours": {
    "@type": "AdvanceServiceDeliveryHoursSpecification",
    "opens": "T10:00:00", // Delivery starts at 10:00AM
    "closes": "T15:00:00", // Delivery ends at 3:00PM. Delivery from 10AM-2:59:59PM.
    "dayOfWeek": [
      "Monday",
      "Tuesday",
      "Wednesday",
      "Thursday",
      "Friday"
    ],
    "serviceTimeInterval": "PT15M", // in slots spaced 15 minutes apart
    "advanceBookingRequirement": {
      "minValue": 60,   // The slot should be at least 60 mins away
      "maxValue": 8640, // but not more than 6 days away
      "unitCode": "MIN"
    }
  }
}
...

The following sample Service accepts orders every day from 8 AM-4:59:59PM, and customers can either opt for a delivery within an hour, or choose one of the slots:

...
{
  "@type": "OpeningHoursSpecification",
  "opens": "T08:00:00",  // Ordering opens at 8:00AM
  "closes": "T17:00:00",  // Ordering closes at 5:00PM, last order at 4:59:59PM
  "deliveryHours": [
    {
      "@type": "ServiceDeliveryHoursSpecification",
      "opens": "T08:00:00",
      "closes": "T17:00:00",
      "deliveryLeadTime": {
        "@type": "QuantitativeValue",
        "value": "60", // If no exact deliveryLeadTime, put a maximum time
        "unitCode": "MIN"
      }
    },
    {
      "@type": "AdvanceServiceDeliveryHoursSpecification",
      "opens": "T08:00:00",
      "closes": "T17:00:00",
      "serviceTimeInterval": "PT15M", // in slots spaced 15 minutes apart
      "advanceBookingRequirement": {
        "minValue": 90,   // The slot should be at least 90 mins away
        "maxValue": 8640, // but not more than 6 days away
        "unitCode": "MIN"
      }
    }
  ]
}
...

The following sample shows a case where the store opens 8 AM-4:59:59 PM on weekdays but 8 AM-6:59 PM on weekends. Orders are not accepted 24x7.

...
{
  // On weekdays, ordering open from 8AM-4:59:59PM.
  "@type": "OpeningHoursSpecification",
  "opens": "T08:00:00",
  "closes": "T17:00:00",
  "dayOfWeek": [
    "Monday",
    "Tuesday",
    "Wednesday",
    "Thursday",
    "Friday"
  ],
  "deliveryHours": [
    {
      // Fulfillment between 8AM-4:59:59PM on weekdays.
      "@type": "AdvanceServiceDeliveryHoursSpecification",
      "opens": "T08:00:00",
      "closes": "T17:00:00",
      "dayOfWeek": [
        "Monday",
        "Tuesday",
        "Wednesday",
        "Thursday",
        "Friday"
      ],
      "serviceTimeInterval": "PT15M",
      "advanceBookingRequirement": {
        "minValue": 60,
        "maxValue": 8640,
        "unitCode": "MIN"
      }
    },
    {
      // Fulfillment between 8AM-6:59:59PM on weekends (even for orders placed on a
      // weekday).
      "@type": "AdvanceServiceDeliveryHoursSpecification",
      "opens": "T08:00:00",
      "closes": "T19:00:00",
      "dayOfWeek": [
        "Saturday",
        "Sunday"
      ],
      "serviceTimeInterval": "PT15M",
      "advanceBookingRequirement": {
        "minValue": 60,
        "maxValue": 8640,
        "unitCode": "MIN"
      }
    }
  ]
},
{
  // On weekends, one can place orders upto 6:59:59PM.
  "@type": "OpeningHoursSpecification",
  "opens": "T08:00:00",
  "closes": "T19:00:00",
  "dayOfWeek": [
    "Saturday",
    "Sunday"
  ],
  "deliveryHours": [
    {
      // But fulfillment on weekdays is only till 4:59:59PM.
      "@type": "AdvanceServiceDeliveryHoursSpecification",
      "opens": "T08:00:00",
      "closes": "T17:00:00",
      "dayOfWeek": [
        "Monday",
        "Tuesday",
        "Wednesday",
        "Thursday",
        "Friday"
      ],
      "serviceTimeInterval": "PT15M",
      "advanceBookingRequirement": {
        "minValue": 60,
        "maxValue": 8640,
        "unitCode": "MIN"
      }
    },
    {
      // Fulfillment on weekends is till 6:59:59PM.
      "@type": "AdvanceServiceDeliveryHoursSpecification",
      "opens": "T08:00:00",
      "closes": "T19:00:00",
      "dayOfWeek": [
        "Saturday",
        "Sunday"
      ],
      "serviceTimeInterval": "PT15M",
      "advanceBookingRequirement": {
        "minValue": 60,
        "maxValue": 8640,
        "unitCode": "MIN"
      }
    }
  ]
}
...