Advanced fulfillment

Mapping menu feeds and fulfillment cart items

When customers add items from your Menu feed into their cart and check out, Google sends those items to your fulfillment endpoint to verify their price and availability. Once pricing and availability are validated, the customer can place the order. This section illustrates how to map menu feed items to fulfillment cart items.

Samples in this section are stripped-down versions of the Menu feed and the Cart schema. Only fields relevant to illustrate the mapping between the Menu feed and the Cart object are shown. For full schemas, see Menu and Cart.

Items in the Menu feed that are added to a cart are sent in the Cart object for both checkout and order submission.

  • A simple MenuItem is represented as a LineItem in the lineItems array with the offerId being the selected menu item's offer.id in the Menu feed.
  • A MenuItem with a required MenuItemOption is represented as a LineItem in the lineItems array with the offerId being the selected menu item option's offer.id from the Menu feed.
  • An AddOnMenuItem of a LineItem is represented as a FoodItemOption in the options array of the FoodItemExtension. Each option has an offerId that corresponds to the selected add-on menu item's offer.id from the Menu feed. Note that an AddOnMenuItem can also have nested AddOnMenuItem(s) that are represented as subOptions inside each option.

The following examples map menu items between the menu feed and fulfillment cart.

JSON

This example contains a list of simple menu items.

Menu items in a Menu feed:

{
  "@type": "Menu",
  "@id": "menu_id",
  "hasMenuItem": [
    {
      "@type": "MenuItem",
      "@id": "menuitem_id_1",
      "offers": [
        {
          "@type": "Offer",
          "@id": "menuitem_offer_id_1",
          "price": "p_1",
          "priceCurrency": "USD"
        }
      ]
    },
    {
      "@type": "MenuItem",
      "@id": "menuitem_id_2",
      "offers": [
        {
          "@type": "Offer",
          "@id": "menuitem_offer_id_2",
          "price": "p_2",
          "priceCurrency": "USD"
        }
      ]
    }
  ]
}
    

Menu items mapped to a fulfillment cart:

{
  "@type": "Menu",
  "@id": "menu_id",
  "hasMenuItem": [
    {
      "@type": "MenuItem",
      "@id": "menuitem_id_1",
      "offers": [
        {
          "@type": "Offer",
          "@id": "menuitem_offer_id_1",
          "price": "p_1",
          "priceCurrency": "USD"
        }
      ]
    },
    {
      "@type": "MenuItem",
      "@id": "menuitem_id_2",
      "offers": [
        {
          "@type": "Offer",
          "@id": "menuitem_offer_id_2",
          "price": "p_2",
          "priceCurrency": "USD"
        }
      ]
    }
  ]
}
    

JSON

This example contains a menu item with one or more AddOnMenuItems.

Menu items in a Menu feed:

{
  "@type": "Menu",
  "@id": "menu_id",
  "hasMenuItem": [
    {
      "@type": "MenuItem",
      "@id": "menuitem_id_1",
      "offers": [
        {
          "@type": "Offer",
          "@id": "menuitem_offer_id_1",
          "price": "p_1",
          "priceCurrency": "USD"
        }
      ],
      "menuAddOn": [
        {
          "@type": "MenuAddOnSection",
          "@id": "menuaddon_section_id_1",
          "hasMenuItem": [
            {
              "@type": "AddOnMenuItem",
              "@id": "menuitem_addon_id_1",
              "offers": [
                {
                  "@type": "Offer",
                  "@id": "menuitem_addon_offer_id_1",
                  "price": "addon_p_1",
                  "priceCurrency": "USD"
                }
              ]
            },
            {
              "@type": "AddOnMenuItem",
              "@id": "menuitem_addon_id_2",
              "offers": [
                {
                  "@type": "Offer",
                  "@id": "menuitem_addon_offer_id_2",
                  "price": "addon_p_2",
                  "priceCurrency": "USD"
                }
              ]
            }
          ]
        }
      ]
    },
    {
      "@type": "MenuItem",
      "@id": "menuitem_id_2",
      "offers": [
        {
          "@type": "Offer",
          "@id": "menuitem_offer_id_2",
          "price": "p_2",
          "priceCurrency": "USD"
        }
      ]
    }
  ]
}
    

Menu items mapped to a fulfillment cart:

{
  "@type": "Cart",
  "lineItems": [
    {
      "offerId": "menuitem_offer_id_1",
      "price": {
        "amount": {
          "currencyCode": "USD",
          "units": "dollar(q_1*(p_1 + addon_q_1*addon_p_1 + addon_q_2*addon_p_2))",
          "nanos": "cent(q_1*(p_1 + addon_q_1*addon_p_1 + addon_q_2*addon_p_2))"
        }
      },
      "quantity": "q_1",
      "extension": {
        "@type": "FoodItemExtension",
        "options": [
          {
            "offerId": "menuitem_addon_offer_id_1",
            "price": {
                "currencyCode": "USD",
                "units": "dollar(addon_q_1*addon_p_1)",
                "nanos": "cent(addon_q_1*addon_p_1)"
            },
            "quantity": "addon_q_1"
          },
          {
            "offerId": "menuitem_addon_offer_id_2",
            "price": {
                "currencyCode": "USD",
                "units": "dollar(addon_q_2*addon_p_2)",
                "nanos": "cent(addon_q_2*addon_p_2)"
            },
            "quantity": "addon_q_2"
          }
        ]
      }
    },
    {
      "offerId": "menuitem_offer_id_2",
      "price": {
        "amount": {
          "currencyCode": "USD",
          "units": "dollar(q_2*p_2)",
          "nanos": "cent(q_2*p_2)"
        }
      },
      "quantity": "q_2"
    }
  ]
}
    

JSON

This example contains a menu item with menu item options, AddOnMenuItems, and nested AddOnMenuItems

Menu items in a Menu feed:

{
  "@type": "MenuItem",
  "@id": "menuitem_id_1",
  "hasMenuItemOptions": [
    {
      "@type": "MenuItemOption",
      "value": {
        "@type": "PropertyValue",
        "name": "OPTION",
        "value": "Large",
        "offers": [
          {
            "@type": "Offer",
            "@id": "menuitem_option_offer_id_1",
            "price": "p_1",
            "priceCurrency": "USD"
          }
        ],
        "menuAddOn": [
          {
            "@type": "AddOnMenuSection",
            "@id": "menuitem_option_addon_section_id_1",
            "hasMenuItem": [
              {
                "@type": "AddOnMenuItem",
                "@id": "menuitem_option_addon_id_1",
                "offers": [
                  {
                    "@type": "Offer",
                    "@id": "menuitem_option_addon_offer_id_1",
                    "price": "addon_p_1",
                    "priceCurrency": "USD"
                  }
                ]
              },
              {
                "@type": "AddOnMenuItem",
                "@id": "menuitem_option_addon_id_2",
                "offers": [
                  {
                    "@type": "Offer",
                    "@id": "menuitem_option_addon_offer_id_2",
                    "price": "addon_p_2",
                    "priceCurrency": "USD"
                  }
                ],
                "menuAddOn": [
                  {
                    "@type": "AddOnMenuSection",
                    "@id": "menuitem_option_subaddon_section_id_1",
                    "hasMenuItem": [
                      {
                        "@type": "AddOnMenuItem",
                        "@id": "menuitem_option_subaddon_id_1",
                        "offers": [
                          {
                            "@type": "Offer",
                            "@id": "menuitem_option_subaddon_offer_id_1",
                            "price": "subaddon_p_1",
                            "priceCurrency": "USD"
                          }
                        ]
                      }
                    ]
                  }
                ]
              }
            ]
          }
        ]
      }
    }
  ]
}
    

Menu items mapped to a fulfillment cart:

{
  "@type": "Cart",
  "lineItems": [
    {
      "offerId": "menuitem_option_offer_id_1",
      "price": {
        "amount": {
          "currencyCode": "USD",
          "units": "dollar(q_1*(p_1 + addon_q_1*addon_p_1 + addon_q_2*(addon_p_2 + subaddon_q_1*subaddon_p_1)))",
          "nanos": "cent(q_1*(p_1 + addon_q_1*addon_p_1 + addon_q_2*(addon_p_2 + subaddon_q_1*subaddon_p_1)))"
        }
      },
      "quantity": "q_1",
      "extension": {
        "@type": "FoodItemExtension",
        "options": [
          {
            "offerId": "menuitem_option_addon_offer_id_1",
            "price": {
              "currencyCode": "USD",
              "units": "dollar(addon_q_1*addon_p_1)",
              "nanos": "cent(addon_q_1*addon_p_1)"
            },
            "quantity": "addon_q_1"
          },
          {
            "offerId": "menuitem_option_addon_offer_id_2",
            "price": {
              "currencyCode": "USD",
              "units": "dollar(addon_q_2*(addon_p_2 + subaddon_q_1*subaddon_p_1))",
              "nanos": "cent(addon_q_2*(addon_p_2 + subaddon_q_1*subaddon_p_1))"
            },
            "quantity": "addon_q_2",
            "subOptions": [
              {
                "offerId": "menuitem_option_subaddon_offer_id_1",
                "price": {
                  "currencyCode": "USD",
                  "units": "dollar(subaddon_q_1*subaddon_p_1)",
                  "nanos": "cent(subaddon_q_1*subaddon_p_1)"
                },
                "quantity": "subaddon_q_1"
              }
            ]
          }
        ]
      }
    }
  ]
}
    

Handling errors

If you experience issues while processing a CheckoutRequestMessage, you can respond with a CheckoutResponseMessage that contains a FoodErrorExtension rather than a CheckoutResponse. You can use this response to identify one or more errors that occurred during processing.

There are 2 ways to handle the errors:

  • Recoverable errors: The user is not required to edit their cart to submit the order. For example, if you determine that an item in the Cart has a price change, you can respond with a FoodOrderError of error type PRICE_CHANGED, along with the correctedProposedOrder and paymentOptions. Google informs the user of the change but lets the user submit with the correctedProposedOrder. The user can also go back and edit their cart if desired. You will either receive a new CheckoutRequestMessage or a SubmitOrderRequestMessage.
  • Unrecoverable errors: The user is required to edit their cart before submitting the order. For example, if you determine that the restaurant is closed, you can respond with a FoodOrderError of error type CLOSED. Google informs the user and manages the interaction to update to a new restaurant. You will receive a new CheckoutRequestMessage for a new cart.

In general, make cart-level errors unrecoverable and item-level errors recoverable. For a complete list of error types and their meanings, see FoodOrderError.

Handling price changes

Price changes during checkout

If you encounter a price issue while processing a customer's checkout request, do the following:

  1. Respond to the CheckoutRequestMessage with a CheckoutResponseMessage that contains a FoodErrorExtension, as described in Handling errors.
  2. In the error response, use correctedProposedOrder.cart to update the price to the correct value. Google receives the corrected order and may issue a new CheckoutRequestMessage.

After checkout, Google displays an order confirmation page to the end user, regardless of whether the ProposedOrder was changed or not.

If the ProposedOrder was corrected, Google may show additional warnings to inform the user of the changes. If the user agrees to place the order, there will be no more checkout requests. The flow continues to order submission, with the corrected ProposedOrder.

However, the user can always change their mind and edit their cart again. When their cart updates in this way, Google sends a new CheckoutRequestMessage.

Price changes during submit order

If you encounter a price issue while processing order submission (the actions.intent.TRANSACTION_DECISION intent was triggered), don't respond with an error or update the price in your response. If the prices, quantities, or other details in the SubmitOrderRequestMessage do not correspond with your data, respond with the orderState set to REJECTED to indicate that the order cannot be placed as requested.

Then, if the order and payment details are valid, set orderState to CREATED or CONFIRMED. Also, include an actionOrderId to represent the order's ID in your system. This ID must be used when sending subsequent updates.

If you are unable to process the payment and have already sent the SubmitOrderRequestMessage, you can send an AsyncOrderUpdateRequestMessage with orderState set to REJECTED to let the user know that the order will not go through.

Price changes after submit order

If you determine that a price has changed from the price used when a customer submitted their order, you can issue an AsyncOrderUpdateRequestMessage, as described in ImplementingAsyncOrderUpdates, with the new price.

To update prices using async order updates:

  1. Change the price in lineItemUpdates[x].price. This value reflects the total cost of the item, including add-ons and multiplied by the quantity. (For more information, see the description of the price field of `LineItem`.)
  2. Enter an explanation in lineItemUpdates[x].reason.
  3. Set lineItemUpdates[x].orderState to CONFIRMED.

You can attempt to charge the payment instrument before or after sending the AsyncOrderUpdateRequestMessage, at your discretion. If the transaction fails (possibly because the price delta is too high), send an AsyncOrderUpdateRequestMessage with the following settings in the OrderUpdate to inform Google of the failure:

  • Set orderState to REJECTED.
  • Describe the failure in the label field.

Checkout validation

As discussed in Step 4: Implement Checkout, your fulfillment endpoint should perform validation on every incoming CheckoutRequestMessage, and respond with a CheckoutResponseMessage.

Here's an example of a CheckoutResponseMessage for a successful validation:

Use case How to implement
Use case 1: Validation is successful Return CheckoutResponse. It must have ProposedOrder and PaymentOptions. ProposedOrder includes tax, fees, and the total price of the cart.

JSON

{
  "expectUserResponse": false,
  // [START final_response]
  "finalResponse": {
    // [START rich_response]
    "richResponse": {
      // [START item]
      "items": [
        {
          // [START structured_response]
          "structuredResponse": {
            // [START checkout_response]
            "checkoutResponse": {
              // [START proposed_order]
              "proposedOrder": {
                "id": "sample_proposed_order_id_1",
                // [START line_item]
                "otherItems": [
                  {
                    "name":"New customer discount",
                    "price": {
                      "type":"ESTIMATE",
                      "amount": {
                        "currencyCode":"USD",
                        "units":"-5",
                        "nanos": -500000000
                      }
                    },
                    "type": "DISCOUNT"
                  },
                  {
                    "name": "Delivery fee",
                    // [START price]
                    "price": {
                      "type": "ESTIMATE",
                      "amount": {
                        "currencyCode": "USD",
                        "units": "3",
                        "nanos": 500000000
                      }
                    },
                    // [END price]
                    "type": "DELIVERY"
                  },
                  {
                    "name": "Tax",
                    "price": {
                      "type": "ESTIMATE",
                      "amount": {
                        "currencyCode": "USD",
                        "units": "1",
                        "nanos": 500000000
                      }
                    },
                    "type": "TAX"
                  }
                ],
                // [END line_item]
                // [START cart]
                "cart": {
                  // [START merchant]
                  "merchant": {
                    "id": "https://www.exampleprovider.com/merchant/id1",
                    "name": "Falafel Bite"
                  },
                  // [END merchant]
                  // [START line_item_2]
                  "lineItems": [
                    {
                      "name": "Pita Chips",
                      "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": "2",
                          "nanos": 750000000
                        }
                      },
                      "subLines": [
                        {
                          "note": "Notes for this item."
                        }
                      ],
                      "extension": {
                        "@type": "type.googleapis.com/google.actions.v2.orders.FoodItemExtension",
                        "options": [
                          {
                            "id": "sample_addon_offer_id_1",
                            "offerId": "https://www.exampleprovider.com/menu/item/addon/offer/id1",
                            "name": "Honey Mustard",
                            "price": {
                              "currencyCode": "USD"
                            },
                            "quantity": 1
                          },
                          {
                            "id": "sample_addon_offer_id_2",
                            "offerId": "https://www.exampleprovider.com/menu/item/addon/offer/id2",
                            "name": "BBQ Sauce",
                            "price": {
                              "currencyCode": "USD",
                              "nanos": 500000000
                            },
                            "quantity": 1
                          }
                        ]
                      }
                    },
                    {
                      "name": "Chicken Shwarma Wrap",
                      "type": "REGULAR",
                      "id": "sample_item_offer_id_2",
                      "offerId": "https://www.exampleprovider.com/menu/item/offer/id2",
                      "quantity": 1,
                      "price": {
                        "type": "ESTIMATE",
                        "amount": {
                          "currencyCode": "USD",
                          "units": "8"
                        }
                      },
                      "extension": {
                        "@type": "type.googleapis.com/google.actions.v2.orders.FoodItemExtension"
                      }
                    },
                    {
                      "name": "Greek Salad",
                      "type": "REGULAR",
                      "id": "sample_item_offer_id_3",
                      "offerId": "https://www.exampleprovider.com/menu/item/offer/id3",
                      "quantity": 1,
                      "price": {
                        "type": "ESTIMATE",
                        "amount": {
                          "currencyCode": "USD",
                          "units": "9",
                          "nanos": 990000000
                        }
                      },
                      "extension": {
                        "@type": "type.googleapis.com/google.actions.v2.orders.FoodItemExtension"
                      }
                    },
                    {
                      "name": "Prawns Biryani",
                      "type": "REGULAR",
                      "id": "sample_item_offer_id_4",
                      "offerId": "https://www.exampleprovider.com/menu/item/offer/id4",
                      "quantity": 1,
                      "price": {
                        "type": "ESTIMATE",
                        "amount": {
                          "currencyCode": "USD",
                          "units": "15",
                          "nanos": 990000000
                        }
                      },
                      // [START food_item_extension]
                      "extension": {
                        "@type": "type.googleapis.com/google.actions.v2.orders.FoodItemExtension"
                      }
                      // [END food_item_extension]
                    }
                  ],
                  // [END line_item_2]
                  // [START food_cart_extension]
                  "extension": {
                    "@type": "type.googleapis.com/google.actions.v2.orders.FoodCartExtension",
                    "fulfillmentPreference": {
                      "fulfillmentInfo": {
                        // [START delivery_info]
                        "delivery": {
                          "deliveryTimeIso8601": "P90M"
                        }
                        // [END delivery_info]
                      }
                    },
                    "location": {
                      "coordinates": {
                        "latitude": 37.788783,
                        "longitude": -122.41384
                      },
                      "formattedAddress": "1350 CHARLESTON ROAD, MOUNTAIN VIEW, CA, United States",
                      "zipCode": "94043",
                      "city": "Mountain View",
                      "postalAddress": {
                        "regionCode": "US",
                        "postalCode": "94043",
                        "administrativeArea": "CA",
                        "locality": "Mountain View",
                        "addressLines": [
                          "1350 Charleston Road"
                        ]
                      },
                      "notes": "Gate code is #111"
                     }
                   }
                   // [END food_cart_extension]
                },
                // [END cart]
                "totalPrice": {
                  "type": "ESTIMATE",
                  // [START money]
                  "amount": {
                    // Represents $36.73
                    "currencyCode": "USD",
                    "units": "36",
                    "nanos": 730000000
                  }
                  // [END money]
                },
                // [START food_order_extension]
                "extension": {
                  "@type": "type.googleapis.com/google.actions.v2.orders.FoodOrderExtension",
                  "availableFulfillmentOptions": [
                    {
                      "fulfillmentInfo": {
                        "delivery": {
                          "deliveryTimeIso8601": "P90M"
                        }
                      },
                      "expiresAt": "2017-07-17T12:30:00Z"
                    }
                  ]
                }
                // [END food_order_extension]
              },
              // [END proposed_order]
              // [START payment_options]
              "paymentOptions": {
                // [START google_provided_options]
                "googleProvidedOptions": {
                  "tokenizationParameters": {
                    "tokenizationType": "PAYMENT_GATEWAY",
                    "parameters": {
                      "gateway": "stripe",
                      "stripe:publishableKey": "pk_live_stripe_client_key",
                      "stripe:version": "2017-04-06"
                    }
                  },
                  "supportedCardNetworks": [
                    "AMEX",
                    "DISCOVER",
                    "MASTERCARD",
                    "JCB",
                    "VISA"
                  ],
                  "prepaidCardDisallowed": true
                }
                // [END google_provided_options]
              }
              // [END payment_options]
            }
            // [END checkout_response]
          }
          // [END structured_response]
        }
      ]
      // [END item]
    }
    // [END rich_response]
  }
  // [END final_response]
}
    

Delivery address validation

Your fulfillment endpoint should validate the delivery address contained in each CheckoutRequestMessage.

If there's an issue with the delivery address, such as it being out of range of the delivery service, the CheckoutResponseMessage returned by your fulfillment should contain a FoodOrderError of the appropriate type.

Use case How to implement
Use case 1: Validation failed because delivery address is out of range or there's an issue with the delivery address Return FoodErrorExtension with FoodOrderError of error type OUT_OF_SERVICE_AREA.

JSON

{
  "expectUserResponse": false,
  // [START final_response]
  "finalResponse": {
    // [START rich_response]
    "richResponse": {
      // [START item]
      "items": [
        {
          // [START structured_response]
          "structuredResponse": {
            // [START food_error_extension]
            "error": {
              "@type": "type.googleapis.com/google.actions.v2.orders.FoodErrorExtension",
              "foodOrderErrors": [
                // [START food_order_error]
                {
                  "error": "OUT_OF_SERVICE_AREA",
                  "description": "Sorry, the restaurant cannot deliver to your address."
                }
                // [END food_order_error]
              ]
            }
            // [END food_error_extension]
          }
          // [END structured_response]
        }
      ]
      // [END item]
    }
    // [END rich_response]
  }
  // [END final_response]
}
    

Minimum order value validation

Your fulfillment endpoint should validate the minimum order value of each CheckoutRequestMessage.

If the minimum order value is not met, the CheckoutResponseMessage returned by your fulfillment should contain a FoodOrderError of error type REQUIREMENTS_NOT_MET.

Use case How to implement
Use case 1: Validation failed because the minimum order value isn't met Return FoodErrorExtension with FoodOrderError of error type REQUIREMENTS_NOT_MET.

JSON

{
  "expectUserResponse": false,
  // [START final_response]
  "finalResponse": {
    // [START rich_response]
    "richResponse": {
      // [START item]
      "items": [
        {
          // [START structured_response]
          "structuredResponse": {
            // [START food_error_extension]
            "error": {
              "@type": "type.googleapis.com/google.actions.v2.orders.FoodErrorExtension",
              "foodOrderErrors": [
                // [START food_order_error]
                {
                  "error": "REQUIREMENTS_NOT_MET",
                  "description": "The cart subtotal must be over $20."
                }
                // [END food_order_error]
              ]
            }
            // [END food_error_extension]
          }
          // [END structured_response]
        }
      ]
      // [END item]
    }
    // [END rich_response]
  }
  // [END final_response]
}
    

Ordering window validation

Your fulfillment endpoint should validate any factors that may affect the ordering window of each CheckoutRequestMessage.

For example, if the restaurant is closed or no longer taking orders at the moment, the CheckoutResponseMessage returned by your fulfillment should contain a FoodOrderError of error type CLOSED or NO_CAPACITY, respectively.

Use case How to implement
Use case 1: Validation failed because the restaurant is closed or no longer supported Return FoodErrorExtension with FoodOrderError of error type CLOSED.
Use case 2: Validation failed because the restaurant is busy and not taking orders at the moment Return FoodErrorExtension with FoodOrderError of error type NO_CAPACITY.

JSON

{
  "expectUserResponse": false,
  // [START final_response]
  "finalResponse": {
    // [START rich_response]
    "richResponse": {
      // [START item]
      "items": [
        {
          // [START structured_response]
          "structuredResponse": {
            // [START food_error_extension]
            "error": {
              "@type": "type.googleapis.com/google.actions.v2.orders.FoodErrorExtension",
              "foodOrderErrors": [
                // [START food_order_error]
                {
                  "error": "CLOSED",
                  "description": "The restaurant is closed."
                }
                // [END food_order_error]
              ]
            }
            // [END food_error_extension]
          }
          // [END structured_response]
        }
      ]
      // [END item]
    }
    // [END rich_response]
  }
  // [END final_response]
}
    

JSON

{
  "expectUserResponse": false,
  // [START final_response]
  "finalResponse": {
    // [START rich_response]
    "richResponse": {
      // [START item]
      "items": [
        {
          // [START structured_response]
          "structuredResponse": {
            // [START food_error_extension]
            "error": {
              "@type": "type.googleapis.com/google.actions.v2.orders.FoodErrorExtension",
              "foodOrderErrors": [
                // [START food_order_error]
                {
                  "error": "CLOSED",
                  "description": "The restaurant is closed."
                }
                // [END food_order_error]
              ]
            }
            // [END food_error_extension]
          }
          // [END structured_response]
        }
      ]
      // [END item]
    }
    // [END rich_response]
  }
  // [END final_response]
}
    

Cart items validation

Your fulfillment endpoint should validate the pricing and availability of each cart item contained in a CheckoutRequestMessage.

If the availability or pricing has changed, the CheckoutResponseMessage returned by your fulfillment should contain a FoodOrderError of error type AVAILABILITY_CHANGED or PRICE_CHANGED, respectively.

Use case How to implement
Use case 1: Validation failed because some menu items and/or their customizations are not valid or are out of stock Return FoodErrorExtension with correctedProposedOrder, PaymentOptions, and FoodOrderError of error type AVAILABILITY_CHANGED. Invalid items must be removed from CorrectedProposedOrder.
Use case 2: Validation failed because some menu items and/or their customizations are not valid or are out of stock. The corrected cart no longer meets the minimum order value requirement. Return FoodErrorExtension with FoodOrderError of error types AVAILABILITY_CHANGED and REQUIREMENTS_NOT_MET.
Use case 3: Validation failed because some menu item and/or customization prices have changed Return FoodErrorExtension with correctedProposedOrder, PaymentOptions, and FoodOrderError of error type PRICE_CHANGED. Outdated prices must be updated in CorrectedProposedOrder.
Use case 4: Validation failed because some menu item and/or customization prices have changed. The corrected cart no longer meets the minimum order value requirement Return FoodErrorExtension with FoodOrderError of error types PRICE_CHANGED and REQUIREMENTS_NOT_MET.

JSON

{
  "expectUserResponse": false,
  "finalResponse": {
    "richResponse": {
      "items": [
        {
          "structuredResponse": {
            // [START food_error_extension]
            "error": {
              "@type": "type.googleapis.com/google.actions.v2.orders.FoodErrorExtension",
              // [START food_order_error]
              "foodOrderErrors": [
                {
                  "error": "AVAILABILITY_CHANGED",
                  "id": "sample_item_offer_id_1",
                  "description": "The item is no longer available."
                },
                {
                  "error": "AVAILABILITY_CHANGED",
                  "id": "sample_item_offer_id_2",
                  "description": "The item is no longer available."
                }
              ],
              // [END food_order_error]
              "correctedProposedOrder": {
                "id": "sample_corrected_proposed_order_id_1",
                "otherItems": [
                  {
                    "name":"New customer discount",
                    "price": {
                      "type":"ESTIMATE",
                      "amount": {
                        "currencyCode":"USD",
                        "units":"-5",
                        "nanos": -500000000
                      }
                    },
                    "type": "DISCOUNT"
                  },
                  {
                    "name": "Delivery fee",
                    "price": {
                      "type": "ESTIMATE",
                      "amount": {
                        "currencyCode": "USD",
                        "units": "3",
                        "nanos": 500000000
                      }
                    },
                    "type": "DELIVERY"
                  },
                  {
                    "name": "Tax",
                    "price": {
                      "type": "ESTIMATE",
                      "amount": {
                        "currencyCode": "USD",
                        "units": "1",
                        "nanos": 500000000
                      }
                    },
                    "type": "TAX"
                  }
                ],
                "cart": {
                  "merchant": {
                    "id": "https://www.exampleprovider.com/merchant/id1",
                    "name": "Falafel Bite"
                  },
                  "lineItems": [
                    {
                      "name": "Greek Salad",
                      "type": "REGULAR",
                      "id": "sample_item_offer_id_3",
                      "offerId": "https://www.exampleprovider.com/menu/item/offer/id3",
                      "quantity": 1,
                      "price": {
                        "type": "ESTIMATE",
                        "amount": {
                          "currencyCode": "USD",
                          "units": "9",
                          "nanos": 990000000
                        }
                      },
                      "extension": {
                        "@type": "type.googleapis.com/google.actions.v2.orders.FoodItemExtension"
                      }
                    },
                    {
                      "name": "Prawns Biryani",
                      "type": "REGULAR",
                      "id": "sample_item_offer_id_4",
                      "offerId": "https://www.exampleprovider.com/menu/item/offer/id4",
                      "quantity": 1,
                      "price": {
                        "type": "ESTIMATE",
                        "amount": {
                          "currencyCode": "USD",
                          "units": "15",
                          "nanos": 990000000
                        }
                      },
                      "extension": {
                        "@type": "type.googleapis.com/google.actions.v2.orders.FoodItemExtension"
                      }
                    }
                  ],
                  "extension": {
                    "@type": "type.googleapis.com/google.actions.v2.orders.FoodCartExtension",
                    "fulfillmentPreference": {
                      "fulfillmentInfo": {
                        "delivery": {
                          "deliveryTimeIso8601": "P90M"
                        }
                      }
                    },
                    "location": {
                      "coordinates": {
                        "latitude": 37.788783,
                        "longitude": -122.41384
                      },
                      "formattedAddress": "1350 CHARLESTON ROAD, MOUNTAIN VIEW, CA, United States",
                      "zipCode": "94043",
                      "city": "Mountain View",
                      "postalAddress": {
                        "regionCode": "US",
                        "postalCode": "94043",
                        "administrativeArea": "CA",
                        "locality": "Mountain View",
                        "addressLines": [
                          "1350 Charleston Road"
                        ]
                      },
                      "notes": "Gate code is #111"
                     }
                   }
                },
                "totalPrice": {
                  "type": "ESTIMATE",
                  "amount": {
                    "currencyCode": "USD",
                    "units": "36",
                    "nanos": 730000000
                  }
                },
                "extension": {
                  "@type": "type.googleapis.com/google.actions.v2.orders.FoodOrderExtension",
                  "availableFulfillmentOptions": [
                    {
                      "fulfillmentInfo": {
                        "delivery": {
                          "deliveryTimeIso8601": "P90M"
                        }
                      },
                      "expiresAt": "2017-07-17T12:30:00Z"
                    }
                  ]
                }
              },
              "paymentOptions": {
                "googleProvidedOptions": {
                  "tokenizationParameters": {
                    "tokenizationType": "PAYMENT_GATEWAY",
                    "parameters": {
                      "gateway": "stripe",
                      "stripe:publishableKey": "pk_live_stripe_client_key",
                      "stripe:version": "2017-04-06"
                    }
                  },
                  "supportedCardNetworks": [
                    "AMEX",
                    "DISCOVER",
                    "MASTERCARD",
                    "JCB",
                    "VISA"
                  ],
                  "prepaidCardDisallowed": true
                }
              }
            }
            // [END food_error_extension]
          }
        }
      ]
    }
  }
}
    

JSON

{
  "expectUserResponse": false,
  // [START final_response]
  "finalResponse": {
    // [START rich_response]
    "richResponse": {
      // [START item]
      "items": [
        {
          // [START structured_response]
          "structuredResponse": {
            // [START food_error_extension]
            "error": {
              "@type": "type.googleapis.com/google.actions.v2.orders.FoodErrorExtension",
              "foodOrderErrors": [
                // [START food_order_error]
                {
                  "error": "REQUIREMENTS_NOT_MET",
                  "description": "The cart subtotal must be over $20."
                },
                {
                  "error": "AVAILABILITY_CHANGED",
                  "id": "cart_lineitem_id"
                  "description": "cart_lineitem_id is no longer available."
                }
                // [END food_order_error]
              ]
            }
            // [END food_error_extension]
          }
          // [END structured_response]
        }
      ]
      // [END item]
    }
    // [END rich_response]
  }
  // [END final_response]
}
    

JSON

{
  "expectUserResponse": false,
  "finalResponse": {
    "richResponse": {
      "items": [
        {
          "structuredResponse": {
            // [START food_error_extension]
            "error": {
              "@type": "type.googleapis.com/google.actions.v2.orders.FoodErrorExtension",
              // [START food_order_error]
              "foodOrderErrors": [
                {
                  "error": "PRICE_CHANGED",
                  "id": "sample_item_offer_id_1",
                  "description": "The price has changed.",
                  "updatedPrice": {
                    "currencyCode": "USD",
                    "units": "2",
                    "nanos": 750000000
                  }
                },
                {
                  "error": "PRICE_CHANGED",
                  "id": "sample_item_offer_id_2",
                  "description": "The price has changed.",
                  "updatedPrice": {
                    "currencyCode": "USD",
                    "units": "8"
                  }
                }
              ],
              // [END food_order_error]
              "correctedProposedOrder": {
                "id": "sample_corrected_proposed_order_id_1",
                "otherItems": [
                  {
                    "name":"New customer discount",
                    "price": {
                      "type":"ESTIMATE",
                      "amount": {
                        "currencyCode":"USD",
                        "units":"-5",
                        "nanos": -500000000
                      }
                    },
                    "type": "DISCOUNT"
                  },
                  {
                    "name": "Delivery fee",
                    "price": {
                      "type": "ESTIMATE",
                      "amount": {
                        "currencyCode": "USD",
                        "units": "3",
                        "nanos": 500000000
                      }
                    },
                    "type": "DELIVERY"
                  },
                  {
                    "name": "Tax",
                    "price": {
                      "type": "ESTIMATE",
                      "amount": {
                        "currencyCode": "USD",
                        "units": "1",
                        "nanos": 500000000
                      }
                    },
                    "type": "TAX"
                  }
                ],
                "cart": {
                  "merchant": {
                    "id": "https://www.exampleprovider.com/merchant/id1",
                    "name": "Falafel Bite"
                  },
                  "lineItems": [
                    {
                      "name": "Pita Chips",
                      "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": "2",
                          "nanos": 750000000
                        }
                      },
                      "subLines": [
                        {
                          "note": "Notes for this item."
                        }
                      ],
                      "extension": {
                        "@type": "type.googleapis.com/google.actions.v2.orders.FoodItemExtension",
                        "options": [
                          {
                            "id": "sample_addon_offer_id_1",
                            "offerId": "https://www.exampleprovider.com/menu/item/addon/offer/id1",
                            "name": "Honey Mustard",
                            "price": {
                              "currencyCode": "USD"
                            },
                            "quantity": 1
                          },
                          {
                            "id": "sample_addon_offer_id_2",
                            "offerId": "https://www.exampleprovider.com/menu/item/addon/offer/id2",
                            "name": "BBQ Sauce",
                            "price": {
                              "currencyCode": "USD",
                              "nanos": 500000000
                            },
                            "quantity": 1
                          }
                        ]
                      }
                    },
                    {
                      "name": "Chicken Shwarma Wrap",
                      "type": "REGULAR",
                      "id": "sample_item_offer_id_2",
                      "offerId": "https://www.exampleprovider.com/menu/item/offer/id2",
                      "quantity": 1,
                      "price": {
                        "type": "ESTIMATE",
                        "amount": {
                          "currencyCode": "USD",
                          "units": "8"
                        }
                      },
                      "extension": {
                        "@type": "type.googleapis.com/google.actions.v2.orders.FoodItemExtension"
                      }
                    },
                    {
                      "name": "Greek Salad",
                      "type": "REGULAR",
                      "id": "sample_item_offer_id_3",
                      "offerId": "https://www.exampleprovider.com/menu/item/offer/id3",
                      "quantity": 1,
                      "price": {
                        "type": "ESTIMATE",
                        "amount": {
                          "currencyCode": "USD",
                          "units": "9",
                          "nanos": 990000000
                        }
                      },
                      "extension": {
                        "@type": "type.googleapis.com/google.actions.v2.orders.FoodItemExtension"
                      }
                    },
                    {
                      "name": "Prawns Biryani",
                      "type": "REGULAR",
                      "id": "sample_item_offer_id_4",
                      "offerId": "https://www.exampleprovider.com/menu/item/offer/id4",
                      "quantity": 1,
                      "price": {
                        "type": "ESTIMATE",
                        "amount": {
                          "currencyCode": "USD",
                          "units": "15",
                          "nanos": 990000000
                        }
                      },
                      "extension": {
                        "@type": "type.googleapis.com/google.actions.v2.orders.FoodItemExtension"
                      }
                    }
                  ],
                  "extension": {
                    "@type": "type.googleapis.com/google.actions.v2.orders.FoodCartExtension",
                    "fulfillmentPreference": {
                      "fulfillmentInfo": {
                        "delivery": {
                          "deliveryTimeIso8601": "P90M"
                        }
                      }
                    },
                    "location": {
                      "coordinates": {
                        "latitude": 37.788783,
                        "longitude": -122.41384
                      },
                      "formattedAddress": "1350 CHARLESTON ROAD, MOUNTAIN VIEW, CA, United States",
                      "zipCode": "94043",
                      "city": "Mountain View",
                      "postalAddress": {
                        "regionCode": "US",
                        "postalCode": "94043",
                        "administrativeArea": "CA",
                        "locality": "Mountain View",
                        "addressLines": [
                          "1350 Charleston Road"
                        ]
                      },
                      "notes": "Gate code is #111"
                     }
                   }
                },
                "totalPrice": {
                  "type": "ESTIMATE",
                  "amount": {
                    "currencyCode": "USD",
                    "units": "36",
                    "nanos": 730000000
                  }
                },
                "extension": {
                  "@type": "type.googleapis.com/google.actions.v2.orders.FoodOrderExtension",
                  "availableFulfillmentOptions": [
                    {
                      "fulfillmentInfo": {
                        "delivery": {
                          "deliveryTimeIso8601": "P90M"
                        }
                      },
                      "expiresAt": "2017-07-17T12:30:00Z"
                    }
                  ]
                }
              },
              "paymentOptions": {
                "googleProvidedOptions": {
                  "tokenizationParameters": {
                    "tokenizationType": "PAYMENT_GATEWAY",
                    "parameters": {
                      "gateway": "stripe",
                      "stripe:publishableKey": "pk_live_stripe_client_key",
                      "stripe:version": "2017-04-06"
                    }
                  },
                  "supportedCardNetworks": [
                    "AMEX",
                    "DISCOVER",
                    "MASTERCARD",
                    "JCB",
                    "VISA"
                  ],
                  "prepaidCardDisallowed": true
                }
              }
            }
            // [END food_error_extension]
          }
        }
      ]
    }
  }
}
    

JSON

{
  "expectUserResponse": false,
  // [START final_response]
  "finalResponse": {
    // [START rich_response]
    "richResponse": {
      // [START item]
      "items": [
        {
          // [START structured_response]
          "structuredResponse": {
            // [START food_error_extension]
            "error": {
              "@type": "type.googleapis.com/google.actions.v2.orders.FoodErrorExtension",
              "foodOrderErrors": [
                // [START food_order_error]
                {
                  "error": "REQUIREMENTS_NOT_MET",
                  "description": "The cart subtotal must be over $20."
                },
                {
                  "error": "PRICE_CHANGED",
                  "id": "cart_lineitem_id"
                  "description": "cart_lineitem_id price has been updated."
                  "updatedPrice": {
                    "currencyCode": "USD",
                    "units": "2",
                    "nanos": 750000000
                  }
                }
                // [END food_order_error]
              ]
            }
            // [END food_error_extension]
          }
          // [END structured_response]
        }
      ]
      // [END item]
    }
    // [END rich_response]
  }
  // [END final_response]
}
    

Submit order validation

As discussed in Step 7: Implement Submit Order, your fulfillment endpoint should perform validation on every incoming SubmitOrderRequestMessage, and respond with a SubmitOrderResponseMessage.

Here's an example of a SubmitOrderResponseMessage for a successful validation:

Use case How to implement
Use case 1: Order is successfully created A `SubmitOrderResponseMessage` with CREATED order state. It must have actionOrderId, userVisibleId, orderManagementActions, and estimatedFulfillmentTime.
Use case 2: Order is rejected because of payment issues A `SubmitOrderResponseMessage` with REJECTED order state. It must have actionOrderId, userVisibleId, orderManagementActions, and rejectionInfo of type PAYMENT_DECLINED.
Use case 3: Order is rejected for user is flagged as banned A `SubmitOrderResponseMessage` with REJECTED order status. It must have actionOrderId, userVisibleId, orderManagementActions, and rejectionInfo of type INELIGIBLE.
Use case 4: Order is rejected because user information is incomplete or invalid A `SubmitOrderResponseMessage` with REJECTED order state. It must have actionOrderId, userVisibleId, orderManagementActions, and rejectionInfo of type INELIGIBLE.
Use case 5: Order is rejected for unknown reason A `SubmitOrderResponseMessage` with REJECTED order state. It must have actionOrderId, userVisibleId, orderManagementActions, and rejectionInfo of type UNKNOWN.

JSON

{
  "expectUserResponse": false,
  "finalResponse": {
    "richResponse": {
      "items": [
        {
          "structuredResponse": {
            "orderUpdate": {
              "actionOrderId": "sample_action_order_id",
              "orderState": {
                "state": "CREATED",
                "label": "Order received"
              },
              "updateTime": "2017-05-10T02:30:00.000Z",
              "orderManagementActions": [
                {
                  "type": "CUSTOMER_SERVICE",
                  "button": {
                    "title": "Contact customer service",
                    "openUrlAction": {
                      "url": "mailto:support@example.com"
                    }
                  }
                },
                {
                  "type": "EMAIL",
                  "button": {
                    "title": "Email restaurant",
                    "openUrlAction": {
                      "url": "mailto:person@example.com"
                    }
                  }
                },
                {
                  "type": "CALL",
                  "button": {
                    "title": "Call restaurant",
                    "openUrlAction": {
                      "url": "tel:+16505554679"
                    }
                  }
                },
                {
                  "type": "VIEW_DETAILS",
                  "button": {
                    "title": "View order",
                    "openUrlAction": {
                      "url": "https://orderview.partner.com?orderid=sample_action_order_id"
                    }
                  }
                }
              ]
            }
          }
        }
      ]
    }
  }
}
    

JSON

{
  "expectUserResponse": false,
  "finalResponse": {
    "richResponse": {
      "items": [
        {
          "structuredResponse": {
            "orderUpdate": {
              "actionOrderId": "sample_action_order_id",
              "orderState": {
                "state": "REJECTED",
                "label": "Order rejected"
              },
              "updateTime": "2017-05-10T02:30:00.000Z",
              "rejectionInfo": {
                 "type": "PAYMENT_DECLINED",
                 "reason": "Insufficient funds"
              },
              "orderManagementActions": [
                {
                  "type": "CUSTOMER_SERVICE",
                  "button": {
                    "title": "Contact customer service",
                    "openUrlAction": {
                      "url": "mailto:support@example.com"
                    }
                  }
                },
                {
                  "type": "EMAIL",
                  "button": {
                    "title": "Email restaurant",
                    "openUrlAction": {
                      "url": "mailto:person@example.com"
                    }
                  }
                },
                {
                  "type": "CALL",
                  "button": {
                    "title": "Call restaurant",
                    "openUrlAction": {
                      "url": "tel:+16505554679"
                    }
                  }
                },
                {
                  "type": "VIEW_DETAILS",
                  "button": {
                    "title": "View order",
                    "openUrlAction": {
                      "url": "https://orderview.partner.com?orderid=sample_action_order_id"
                    }
                  }
                }
              ]
            }
          }
        }
      ]
    }
  }
}
    

JSON

{
  "expectUserResponse": false,
  "finalResponse": {
    "richResponse": {
      "items": [
        {
          "structuredResponse": {
            "orderUpdate": {
              "actionOrderId": "sample_action_order_id",
              "orderState": {
                "state": "REJECTED",
                "label": "Order rejected"
              },
              "updateTime": "2017-05-10T02:30:00.000Z",
              "rejectionInfo": {
                 "type": "INELIGIBLE",
                 "reason": "Sorry, we are not able to take orders from this user"
              },
              "orderManagementActions": [
                {
                  "type": "CUSTOMER_SERVICE",
                  "button": {
                    "title": "Contact customer service",
                    "openUrlAction": {
                      "url": "mailto:support@example.com"
                    }
                  }
                },
                {
                  "type": "EMAIL",
                  "button": {
                    "title": "Email restaurant",
                    "openUrlAction": {
                      "url": "mailto:person@example.com"
                    }
                  }
                },
                {
                  "type": "CALL",
                  "button": {
                    "title": "Call restaurant",
                    "openUrlAction": {
                      "url": "tel:+16505554679"
                    }
                  }
                },
                {
                  "type": "VIEW_DETAILS",
                  "button": {
                    "title": "View order",
                    "openUrlAction": {
                      "url": "https://orderview.partner.com?orderid=sample_action_order_id"
                    }
                  }
                }
              ]
            }
          }
        }
      ]
    }
  }
}
    

JSON

{
  "expectUserResponse": false,
  "finalResponse": {
    "richResponse": {
      "items": [
        {
          "structuredResponse": {
            "orderUpdate": {
              "actionOrderId": "sample_action_order_id",
              "orderState": {
                "state": "REJECTED",
                "label": "Order rejected"
              },
              "updateTime": "2017-05-10T02:30:00.000Z",
              "rejectionInfo": {
                 "type": "INELIGIBLE",
                 "reason": "Sorry, the phone number must not be blank"
              },
              "orderManagementActions": [
                {
                  "type": "CUSTOMER_SERVICE",
                  "button": {
                    "title": "Contact customer service",
                    "openUrlAction": {
                      "url": "mailto:support@example.com"
                    }
                  }
                },
                {
                  "type": "EMAIL",
                  "button": {
                    "title": "Email restaurant",
                    "openUrlAction": {
                      "url": "mailto:person@example.com"
                    }
                  }
                },
                {
                  "type": "CALL",
                  "button": {
                    "title": "Call restaurant",
                    "openUrlAction": {
                      "url": "tel:+16505554679"
                    }
                  }
                },
                {
                  "type": "VIEW_DETAILS",
                  "button": {
                    "title": "View order",
                    "openUrlAction": {
                      "url": "https://orderview.partner.com?orderid=sample_action_order_id"
                    }
                  }
                }
              ]
            }
          }
        }
      ]
    }
  }
}
    

JSON

{
  "expectUserResponse": false,
  "finalResponse": {
    "richResponse": {
      "items": [
        {
          "structuredResponse": {
            "orderUpdate": {
              "actionOrderId": "sample_action_order_id",
              "orderState": {
                "state": "REJECTED",
                "label": "Order rejected"
              },
              "updateTime": "2017-05-10T02:30:00.000Z",
              "rejectionInfo": {
                 "type": "UNKNOWN",
                 "reason": "Sorry, there is something wrong with this order."
              },
              "orderManagementActions": [
                {
                  "type": "CUSTOMER_SERVICE",
                  "button": {
                    "title": "Contact customer service",
                    "openUrlAction": {
                      "url": "mailto:support@example.com"
                    }
                  }
                },
                {
                  "type": "EMAIL",
                  "button": {
                    "title": "Email restaurant",
                    "openUrlAction": {
                      "url": "mailto:person@example.com"
                    }
                  }
                },
                {
                  "type": "CALL",
                  "button": {
                    "title": "Call restaurant",
                    "openUrlAction": {
                      "url": "tel:+16505554679"
                    }
                  }
                },
                {
                  "type": "VIEW_DETAILS",
                  "button": {
                    "title": "View order",
                    "openUrlAction": {
                      "url": "https://orderview.partner.com?orderid=sample_action_order_id"
                    }
                  }
                }
              ]
            }
          }
        }
      ]
    }
  }
}