Tools

As you build out your Food Ordering experience, you may run into issues or need to test different parts of your Food Ordering service implementation. This page includes an assortment of features to help you build and test your integration throughout development.

Generate client libraries

A machine-readable version of the Food Ordering data feed and fulfillment API definitions are made available to generate client codes and validate the structure of JSON data. This allows you to spend more time in developing application capabilities and business logic required for the integration.

In this example, we use quicktype CLI to generate an easy-to-use client library.

Download the JSON Schemas

These machine-readable versions of data feeds and APIs are needed for code generation and validation.

Generate codes

Quicktype can be used to regenerate codes when APIs are changed so you can simply update the affected application code. QuickType supports C++, Java, JavaScript, Python and other programming languages.

You can also use other available code generator tools that support JSON Schema definitions to generate the client libraries.

Use Node package manager (npm) to install quicktype in the project directory for your Food Ordering integration.

npm install quicktype

TypeScript

  1. Generate client codes for Food Ordering data feeds.
    quicktype --lang typescript --src-lang schema inventory-v2-json-schema.json#top_level_definitions/ -o ./ts/fo-inventory.ts
            
  2. Generate client codes for Fulfillment Actions.
    quicktype --lang typescript --src-lang schema fulfillment-actions-json-schema.json#top_level_definitions/ -o ./ts/fo-fulfillment.ts
            
  3. Copy the generated files to your workspace and implement your business logic.

Usage and validation

Example of creating entities and converting them to JSON:

import { Convert, Fee, OperationHours, Restaurant, Service, ServiceArea, ServiceHours, Menu, MenuSection, Availability, MenuItem, MenuItemOption, MenuItemOffer, FeeType, FeeTypeEnum, RestaurantType } from "./fo-inventory";
import * as inventorySchema from '../inventory-v2-json.schema.json'

const ajv = new Ajv()

var restaurant: Restaurant = {
    "@id": "McDonalds",
    "@type": RestaurantType.Restaurant,
    "addressCountry": "US",
    "addressLocality": "123 Local",
    "addressRegion": "Region",
    "name": "MacDonald's",
    "postalCode": "1234",
    "streetAddress": "123",
    "telephone": "+15552999983",
    "url": "https://example.com"
}

var fee: Fee = {
    "@id": "123",
    "@type": FeeTypeEnum.Fee,
    "priceCurrency": "US",
    "serviceId": "123",
    "feeType": FeeType.Delivery
}

console.log(Convert.restaurantToJson(restaurant));
    

Java

  1. Generate client codes for Food Ordering data feeds.
    quicktype --lang java --src-lang schema inventory-v2-json-schema.json#top_level_definitions/ -o ./java/ --package com.example
            
  2. Generate client codes for Fulfillment Actions.
    quicktype --lang java --src-lang schema fulfillment-actions-json-schema.json#top_level_definitions/ -o ./java/ --package com.example
            
  3. Copy the generated files to your workspace and implement your business logic.

Usage and validation

Example of creating entities and converting them to JSON:

package com.example;

import com.example.Converter;
import com.example.Fee;
import com.example.FeeType;
import com.example.Restaurant;
import com.example.RestaurantType;

public class FoodOrderingResponse {
    public static void main(String[] args) {
        Restaurant restaurant = new Restaurant();
        restaurant.setId("MacDonalds");
        restaurant.setType(RestaurantType.RESTAURANT);
        restaurant.setAddressCountry("US");
        restaurant.setAddressLocality("123 Local");
        restaurant.setAddressRegion("Region");
        restaurant.setName("MacDonald's");
        restaurant.setPostalCode("1234");
        restaurant.setStreetAddress("123");
        restaurant.setTelephone("+15552999983");
        restaurant.setUrl("https://example.com");

        Fee fee = new Fee();
        fee.setId("123");
        fee.setType(FeeTypeEnum.FEE);
        fee.setPriceCurrency("US");
        fee.setServiceId("123");
        fee.setFeeType(FeeType.DELIVERY);

        String restaurantJson = Converter.RestaurantToJsonString(restaurant);
        String feeJson = Converter.FeeToJsonString(fee);
    }
}
    

JavaScript

  1. Generate client codes for Food Ordering data feeds.
    quicktype --lang javascript --src-lang schema inventory-v2-json-schema.json#top_level_definitions/ -o inventory.js
            
  2. Generate client codes for Fulfillment Actions.
    quicktype --lang javascript --src-lang schema fulfillment-actions-json-schema.json#top_level_definitions/ -o fulfillment.js
            
  3. Copy the generated files to your workspace and implement your business logic.

Usage and validation

Example of creating entities and converting them to JSON:

// Converts JSON strings to/from your types
// and asserts the results of JSON.parse at runtime
function toRestaurant(json) {
    return cast(JSON.parse(json), r("Restaurant"));
}

function restaurantToJson(value) {
    return JSON.stringify(uncast(value, r("Restaurant")), null, 2);
}

function invalidValue(typ, val, key = '') {
    if (key) {
        throw Error(`Invalid value for key "${key}". Expected type ${JSON.stringify(typ)} but got ${JSON.stringify(val)}`);
    }
    throw Error(`Invalid value ${JSON.stringify(val)} for type ${JSON.stringify(typ)}`, );
}

function jsonToJSProps(typ) {
    if (typ.jsonToJS === undefined) {
        const map = {};
        typ.props.forEach((p) => map[p.json] = { key: p.js, typ: p.typ });
        typ.jsonToJS = map;
    }
    return typ.jsonToJS;
}

function jsToJSONProps(typ) {
    if (typ.jsToJSON === undefined) {
        const map = {};
        typ.props.forEach((p) => map[p.js] = { key: p.json, typ: p.typ });
        typ.jsToJSON = map;
    }
    return typ.jsToJSON;
}

function transform(val, typ, getProps, key = '') {
    function transformPrimitive(typ, val) {
        if (typeof typ === typeof val) return val;
        return invalidValue(typ, val, key);
    }

    function transformUnion(typs, val) {
        // val must validate against one typ in typs
        const l = typs.length;
        for (let i = 0; i < l; i++) {
            const typ = typs[i];
            try {
                return transform(val, typ, getProps);
            } catch (_) {}
        }
        return invalidValue(typs, val);
    }

    function transformEnum(cases, val) {
        if (cases.indexOf(val) !== -1) return val;
        return invalidValue(cases, val);
    }

    function transformArray(typ, val) {
        // val must be an array with no invalid elements
        if (!Array.isArray(val)) return invalidValue("array", val);
        return val.map(el => transform(el, typ, getProps));
    }

    function transformDate(val) {
        if (val === null) {
            return null;
        }
        const d = new Date(val);
        if (isNaN(d.valueOf())) {
            return invalidValue("Date", val);
        }
        return d;
    }

    function transformObject(props, additional, val) {
        if (val === null || typeof val !== "object" || Array.isArray(val)) {
            return invalidValue("object", val);
        }
        const result = {};
        Object.getOwnPropertyNames(props).forEach(key => {
            const prop = props[key];
            const v = Object.prototype.hasOwnProperty.call(val, key) ? val[key] : undefined;
            result[prop.key] = transform(v, prop.typ, getProps, prop.key);
        });
        Object.getOwnPropertyNames(val).forEach(key => {
            if (!Object.prototype.hasOwnProperty.call(props, key)) {
                result[key] = transform(val[key], additional, getProps, key);
            }
        });
        return result;
    }

    if (typ === "any") return val;
    if (typ === null) {
        if (val === null) return val;
        return invalidValue(typ, val);
    }
    if (typ === false) return invalidValue(typ, val);
    while (typeof typ === "object" && typ.ref !== undefined) {
        typ = typeMap[typ.ref];
    }
    if (Array.isArray(typ)) return transformEnum(typ, val);
    if (typeof typ === "object") {
        return typ.hasOwnProperty("unionMembers") ? transformUnion(typ.unionMembers, val)
            : typ.hasOwnProperty("arrayItems")    ? transformArray(typ.arrayItems, val)
            : typ.hasOwnProperty("props")         ? transformObject(getProps(typ), typ.additional, val)
            : invalidValue(typ, val);
    }
    // Numbers can be parsed by Date but shouldn't be.
    if (typ === Date && typeof val !== "number") return transformDate(val);
    return transformPrimitive(typ, val);
}

function cast(val, typ) {
    return transform(val, typ, jsonToJSProps);
}

function uncast(val, typ) {
    return transform(val, typ, jsToJSONProps);
}

function a(typ) {
    return { arrayItems: typ };
}

function u(...typs) {
    return { unionMembers: typs };
}

function o(props, additional) {
    return { props, additional };
}

function m(additional) {
    return { props: [], additional };
}

function r(name) {
    return { ref: name };
}

const typeMap = {
    "Restaurant": o([
        { json: "@id", js: "@id", typ: "" },
        { json: "@type", js: "@type", typ: r("Type") },
        { json: "addressCountry", js: "addressCountry", typ: "" },
        { json: "addressLocality", js: "addressLocality", typ: "" },
        { json: "addressRegion", js: "addressRegion", typ: "" },
        { json: "dateModified", js: "dateModified", typ: u(undefined, Date) },
        { json: "description", js: "description", typ: u(undefined, "") },
        { json: "latitude", js: "latitude", typ: u(undefined, 3.14) },
        { json: "longitude", js: "longitude", typ: u(undefined, 3.14) },
        { json: "name", js: "name", typ: "" },
        { json: "postalCode", js: "postalCode", typ: "" },
        { json: "sameAs", js: "sameAs", typ: u(undefined, "") },
        { json: "streetAddress", js: "streetAddress", typ: "" },
        { json: "telephone", js: "telephone", typ: u(undefined, "") },
        { json: "url", js: "url", typ: u(undefined, "") },
    ], false),
    "Type": [
        "Restaurant",
    ],
};

module.exports = {
    "restaurantToJson": restaurantToJson,
    "toRestaurant": toRestaurant,
};
    

Python

  1. Generate client codes for Food Ordering data feeds.
    quicktype --lang python --src-lang schema inventory-v2-json-schema.json#top_level_definitions/ -o inventory.py
            
  2. Generate client codes for Fulfillment Actions.
    quicktype --lang python --src-lang schema fulfillment-actions-json-schema.json#top_level_definitions/ -o fulfillment.py
            
  3. Copy the generated files to your workspace and implement your business logic.

Usage

Example of creating entities and converting them to JSON:

class RestaurantType(Enum):
    RESTAURANT = "Restaurant"

class Restaurant:
    """A required entity to implement. Describes a restaurant."""
    """A unique identifier of the restaurant or delivery provider."""
    id: str
    type: RestaurantType
    """Two-letter ISO 3166-1 alpha-2 country code."""
    address_country: str
    """The locality or city."""
    address_locality: str
    """The region or state."""
    address_region: str
    """The last modified date and time of the Restaurant entity feed in ISO timestamp format but
    with type String.
    """
    date_modified: Optional[datetime]
    """A description of the restaurant."""
    description: Optional[str]
    """Latitude in degrees. Values are restricted to the range [[-90, 90]].

    The precision should be at least 5 decimal places.
    """
    latitude: Optional[float]
    """Longitude in degrees. Values are restricted to the range [[-180, 180]].

    The precision should be at least 5 decimal places.
    """
    longitude: Optional[float]
    """Name of the restaurant."""
    name: str
    """The postal code."""
    postal_code: str
    """The official website for the restaurant."""
    same_as: Optional[str]
    """The street address of the restaurant."""
    street_address: str
    """Telephone number of the restaurant."""
    telephone: Optional[str]
    """The URL that represents the restaurant. The restaurant domain is preferred over the
    aggregator domain.
    """
    url: Optional[str]

    def __init__(self, id: str, type: RestaurantType, address_country: str, address_locality: str, address_region: str, date_modified: Optional[datetime], description: Optional[str], latitude: Optional[float], longitude: Optional[float], name: str, postal_code: str, same_as: Optional[str], street_address: str, telephone: Optional[str], url: Optional[str]) -> None:
        self.id = id
        self.type = type
        self.address_country = address_country
        self.address_locality = address_locality
        self.address_region = address_region
        self.date_modified = date_modified
        self.description = description
        self.latitude = latitude
        self.longitude = longitude
        self.name = name
        self.postal_code = postal_code
        self.same_as = same_as
        self.street_address = street_address
        self.telephone = telephone
        self.url = url

    @staticmethod
    def from_dict(obj: Any) -> 'Restaurant':
        assert isinstance(obj, dict)
        id = from_str(obj.get("@id"))
        type = RestaurantType(obj.get("@type"))
        address_country = from_str(obj.get("addressCountry"))
        address_locality = from_str(obj.get("addressLocality"))
        address_region = from_str(obj.get("addressRegion"))
        date_modified = from_union([from_datetime, from_none], obj.get("dateModified"))
        description = from_union([from_str, from_none], obj.get("description"))
        latitude = from_union([from_float, from_none], obj.get("latitude"))
        longitude = from_union([from_float, from_none], obj.get("longitude"))
        name = from_str(obj.get("name"))
        postal_code = from_str(obj.get("postalCode"))
        same_as = from_union([from_str, from_none], obj.get("sameAs"))
        street_address = from_str(obj.get("streetAddress"))
        telephone = from_union([from_str, from_none], obj.get("telephone"))
        url = from_union([from_str, from_none], obj.get("url"))
        return Restaurant(id, type, address_country, address_locality, address_region, date_modified, description, latitude, longitude, name, postal_code, same_as, street_address, telephone, url)

    def to_dict(self) -> dict:
        result: dict = {}
        result["@id"] = from_str(self.id)
        result["@type"] = to_enum(RestaurantType, self.type)
        result["addressCountry"] = from_str(self.address_country)
        result["addressLocality"] = from_str(self.address_locality)
        result["addressRegion"] = from_str(self.address_region)
        result["dateModified"] = from_union([lambda x: x.isoformat(), from_none], self.date_modified)
        result["description"] = from_union([from_str, from_none], self.description)
        result["latitude"] = from_union([to_float, from_none], self.latitude)
        result["longitude"] = from_union([to_float, from_none], self.longitude)
        result["name"] = from_str(self.name)
        result["postalCode"] = from_str(self.postal_code)
        result["sameAs"] = from_union([from_str, from_none], self.same_as)
        result["streetAddress"] = from_str(self.street_address)
        result["telephone"] = from_union([from_str, from_none], self.telephone)
        result["url"] = from_union([from_str, from_none], self.url)
        return result
    

Data feed debugging tools

This section describes the Actions console features that can help you debug your service data feeds.

Data feed ingestion statistics

In the Actions console, you can view feed ingestion data for your service data feed. Logs include ingestion statistics with detailed information about top-level entities you provide in your data feeds.

To access the ingestion statistics, follow these steps:

  1. Go to the Actions console and open your project.
  2. From the main navigation, go to Develop > Data feeds.
  3. In the table of data feeds for the environment you're interested in, select a data feed to view its ingestion statistics:

On the ingestion statistics page, the Actions console presents statistics for the latest ingestion, as well as for previously ingested data feeds. Logs are recorded for the 50 most recent versions of both types of feed.

On this page, you can see the following data about ingested data feeds:

  • Time: Time at which Google fetched and ingested inventory feeds from your server.
  • Status: Status of the ingestion process once the inventory is fetched. The status of ingestion can be one of the following: "In Progress", "Completed", or "Failure".
  • Files found: Number of files identified in the ingested feed so far.
  • Files with errors: Number of files containing errors.
  • Entities found: Number of top-level entities identified in the ingested feed so far.
  • Entities with errors: Number of top-level entities that have errors.
  • Entities with warnings: Number of top-level entities that have warnings. These entities are not included in Entities without errors.
  • Stale entities: Number of stale top-level entities. Stale entities have a version older than was previously detected.

Entity and file detail views

The ingestion statistics page displays errors and warnings in the latest processed feeds, but you can also search for specific entities by ID and explore ingestions by when they were processed. For each ingestion, the console displays errors and warnings by file and entity.

When you select a data feed, you can expand errors and warnings to view more details. For entities, you can click the entity ID to view all issues for that entity as well:

To look up a specific entity, enter its entity ID specified at ingestion time into the Entity ID field and click Lookup:

The page that appears after you select an entity provides information in multiple tabs:

  • General info: Provides high-level information about the entity, like its ID and when it was ingested.
  • Issues: Lists all errors or warnings identified for the entity.
  • Raw source: Displays the raw JSON object as provided by your feeds.

Ordering testing tools

Use tools that can test and measure your integration's ability to handle orders, and help you troubleshoot failure states.

Google Pay payment processing test

Test payments with your Google Pay payment gateway in a sandbox environment using JSFiddle.

<html>
  <head>
    <style>
    body {  background-color: #a3d5d3;}.googlepay {  background-color: #000;  background-image: url('data:image/svg+xml,%3Csvg%20width%3D%2241%22%20height%3D%2217%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22M19.526%202.635v4.083h2.518c.6%200%201.096-.202%201.488-.605.403-.402.605-.882.605-1.437%200-.544-.202-1.018-.605-1.422-.392-.413-.888-.62-1.488-.62h-2.518zm0%205.52v4.736h-1.504V1.198h3.99c1.013%200%201.873.337%202.582%201.012.72.675%201.08%201.497%201.08%202.466%200%20.991-.36%201.819-1.08%202.482-.697.665-1.559.996-2.583.996h-2.485v.001zm7.668%202.287c0%20.392.166.718.499.98.332.26.722.391%201.168.391.633%200%201.196-.234%201.692-.701.497-.469.744-1.019.744-1.65-.469-.37-1.123-.555-1.962-.555-.61%200-1.12.148-1.528.442-.409.294-.613.657-.613%201.093m1.946-5.815c1.112%200%201.989.297%202.633.89.642.594.964%201.408.964%202.442v4.932h-1.439v-1.11h-.065c-.622.914-1.45%201.372-2.486%201.372-.882%200-1.621-.262-2.215-.784-.594-.523-.891-1.176-.891-1.96%200-.828.313-1.486.94-1.976s1.463-.735%202.51-.735c.892%200%201.629.163%202.206.49v-.344c0-.522-.207-.966-.621-1.33a2.132%202.132%200%200%200-1.455-.547c-.84%200-1.504.353-1.995%201.062l-1.324-.834c.73-1.045%201.81-1.568%203.238-1.568m11.853.262l-5.02%2011.53H34.42l1.864-4.034-3.302-7.496h1.635l2.387%205.749h.032l2.322-5.75z%22%20fill%3D%22%23FFF%22%2F%3E%3Cpath%20d%3D%22M13.448%207.134c0-.473-.04-.93-.116-1.366H6.988v2.588h3.634a3.11%203.11%200%200%201-1.344%202.042v1.68h2.169c1.27-1.17%202.001-2.9%202.001-4.944%22%20fill%3D%22%234285F4%22%2F%3E%3Cpath%20d%3D%22M6.988%2013.7c1.816%200%203.344-.595%204.459-1.621l-2.169-1.681c-.603.406-1.38.643-2.29.643-1.754%200-3.244-1.182-3.776-2.774H.978v1.731a6.728%206.728%200%200%200%206.01%203.703%22%20fill%3D%22%2334A853%22%2F%3E%3Cpath%20d%3D%22M3.212%208.267a4.034%204.034%200%200%201%200-2.572V3.964H.978A6.678%206.678%200%200%200%20.261%206.98c0%201.085.26%202.11.717%203.017l2.234-1.731z%22%20fill%3D%22%23FABB05%22%2F%3E%3Cpath%20d%3D%22M6.988%202.921c.992%200%201.88.34%202.58%201.008v.001l1.92-1.918C10.324.928%208.804.262%206.989.262a6.728%206.728%200%200%200-6.01%203.702l2.234%201.731c.532-1.592%202.022-2.774%203.776-2.774%22%20fill%3D%22%23E94235%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E');  background-origin: content-box;  background-position: center;  background-repeat: no-repeat;  background-size: contain;  border: 0;  border-radius: 4px;  box-shadow: 0 1px 1px 0 rgba(60, 64, 67, 0.30), 0 1px 3px 1px rgba(60, 64, 67, 0.15);  outline: 0;  padding: 11px 24px;  width: 160px;  min-width: 90px;  height: 40px;  min-height: 40px;}.googlepay.long {  background-image: url('data:image/svg+xml,%3Csvg%20width%3D%22103%22%20height%3D%2217%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22M.148%202.976h3.766c.532%200%201.024.117%201.477.35.453.233.814.555%201.085.966.27.41.406.863.406%201.358%200%20.495-.124.924-.371%201.288s-.572.64-.973.826v.084c.504.177.912.471%201.225.882.313.41.469.891.469%201.442a2.6%202.6%200%200%201-.427%201.47c-.285.43-.667.763-1.148%201.001A3.5%203.5%200%200%201%204.082%2013H.148V2.976zm3.696%204.2c.448%200%20.81-.14%201.085-.42.275-.28.413-.602.413-.966s-.133-.684-.399-.959c-.266-.275-.614-.413-1.043-.413H1.716v2.758h2.128zm.238%204.368c.476%200%20.856-.15%201.141-.448.285-.299.427-.644.427-1.036%200-.401-.147-.749-.441-1.043-.294-.294-.688-.441-1.183-.441h-2.31v2.968h2.366zm5.379.903c-.453-.518-.679-1.239-.679-2.163V5.86h1.54v4.214c0%20.579.138%201.013.413%201.302.275.29.637.434%201.085.434.364%200%20.686-.096.966-.287.28-.191.495-.446.644-.763a2.37%202.37%200%200%200%20.224-1.022V5.86h1.54V13h-1.456v-.924h-.084c-.196.336-.5.611-.91.826-.41.215-.845.322-1.302.322-.868%200-1.528-.259-1.981-.777zm9.859.161L16.352%205.86h1.722l2.016%204.858h.056l1.96-4.858H23.8l-4.41%2010.164h-1.624l1.554-3.416zm8.266-6.748h1.666l1.442%205.11h.056l1.61-5.11h1.582l1.596%205.11h.056l1.442-5.11h1.638L36.392%2013h-1.624L33.13%207.876h-.042L31.464%2013h-1.596l-2.282-7.14zm12.379-1.337a1%201%200%200%201-.301-.735%201%201%200%200%201%20.301-.735%201%201%200%200%201%20.735-.301%201%201%200%200%201%20.735.301%201%201%200%200%201%20.301.735%201%201%200%200%201-.301.735%201%201%200%200%201-.735.301%201%201%200%200%201-.735-.301zM39.93%205.86h1.54V13h-1.54V5.86zm5.568%207.098a1.967%201.967%200%200%201-.686-.406c-.401-.401-.602-.947-.602-1.638V7.218h-1.246V5.86h1.246V3.844h1.54V5.86h1.736v1.358H45.75v3.36c0%20.383.075.653.224.812.14.187.383.28.728.28.159%200%20.299-.021.42-.063.121-.042.252-.11.392-.203v1.498c-.308.14-.681.21-1.12.21-.317%200-.616-.051-.896-.154zm3.678-9.982h1.54v2.73l-.07%201.092h.07c.205-.336.511-.614.917-.833.406-.22.842-.329%201.309-.329.868%200%201.53.254%201.988.763.457.509.686%201.202.686%202.079V13h-1.54V8.688c0-.541-.142-.947-.427-1.218-.285-.27-.656-.406-1.113-.406-.345%200-.656.098-.931.294a2.042%202.042%200%200%200-.651.777%202.297%202.297%200%200%200-.238%201.029V13h-1.54V2.976zm32.35-.341v4.083h2.518c.6%200%201.096-.202%201.488-.605.403-.402.605-.882.605-1.437%200-.544-.202-1.018-.605-1.422-.392-.413-.888-.62-1.488-.62h-2.518zm0%205.52v4.736h-1.504V1.198h3.99c1.013%200%201.873.337%202.582%201.012.72.675%201.08%201.497%201.08%202.466%200%20.991-.36%201.819-1.08%202.482-.697.665-1.559.996-2.583.996h-2.485v.001zm7.668%202.287c0%20.392.166.718.499.98.332.26.722.391%201.168.391.633%200%201.196-.234%201.692-.701.497-.469.744-1.019.744-1.65-.469-.37-1.123-.555-1.962-.555-.61%200-1.12.148-1.528.442-.409.294-.613.657-.613%201.093m1.946-5.815c1.112%200%201.989.297%202.633.89.642.594.964%201.408.964%202.442v4.932h-1.439v-1.11h-.065c-.622.914-1.45%201.372-2.486%201.372-.882%200-1.621-.262-2.215-.784-.594-.523-.891-1.176-.891-1.96%200-.828.313-1.486.94-1.976s1.463-.735%202.51-.735c.892%200%201.629.163%202.206.49v-.344c0-.522-.207-.966-.621-1.33a2.132%202.132%200%200%200-1.455-.547c-.84%200-1.504.353-1.995%201.062l-1.324-.834c.73-1.045%201.81-1.568%203.238-1.568m11.853.262l-5.02%2011.53H96.42l1.864-4.034-3.302-7.496h1.635l2.387%205.749h.032l2.322-5.75z%22%20fill%3D%22%23FFF%22%2F%3E%3Cpath%20d%3D%22M75.448%207.134c0-.473-.04-.93-.116-1.366h-6.344v2.588h3.634a3.11%203.11%200%200%201-1.344%202.042v1.68h2.169c1.27-1.17%202.001-2.9%202.001-4.944%22%20fill%3D%22%234285F4%22%2F%3E%3Cpath%20d%3D%22M68.988%2013.7c1.816%200%203.344-.595%204.459-1.621l-2.169-1.681c-.603.406-1.38.643-2.29.643-1.754%200-3.244-1.182-3.776-2.774h-2.234v1.731a6.728%206.728%200%200%200%206.01%203.703%22%20fill%3D%22%2334A853%22%2F%3E%3Cpath%20d%3D%22M65.212%208.267a4.034%204.034%200%200%201%200-2.572V3.964h-2.234a6.678%206.678%200%200%200-.717%203.017c0%201.085.26%202.11.717%203.017l2.234-1.731z%22%20fill%3D%22%23FABB05%22%2F%3E%3Cpath%20d%3D%22M68.988%202.921c.992%200%201.88.34%202.58%201.008v.001l1.92-1.918c-1.165-1.084-2.685-1.75-4.5-1.75a6.728%206.728%200%200%200-6.01%203.702l2.234%201.731c.532-1.592%202.022-2.774%203.776-2.774%22%20fill%3D%22%23E94235%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E');  width: 240px;  min-width: 151px;}.googlepay:hover {  background-color: #3c4043;}.googlepay:focus {  box-shadow: #202124;}.googlepay:active {  background-color: #5f6368;}// extra padding for testingsection {  padding: 1em 0;}div {  margin: 0.5em 0;}button {  margin-top: 0.5em;  margin-right: 0.5em;}table,th,td {  border: 1px solid black;  font-family: Arial, Helvetica, sans-serif;}pre {  white-space: pre-wrap;  /* Since CSS 2.1 */  white-space: -moz-pre-wrap;  /* Mozilla, since 1999 */  white-space: -pre-wrap;  /* Opera 4-6 */  white-space: -o-pre-wrap;  /* Opera 7 */  word-wrap: break-word;  /* Internet Explorer 5.5+ */  font-family: Arial, Helvetica, sans-serif;}
    </style>
  </head>
  <body>
    
    <section>
      <div>
        <button class="googlepay long" title="Buy with Google Pay" onclick="onGooglePaymentButtonClicked()"></button>
      </div>
    </section>
  <div>
    <table style="width:100%">
      <tr>
        <th>Payment Gateway Response</th>
      </tr>
      <tr>
        <td>
          <pre id="gatewayResponse"></pre>
        </td>
      </tr>
    </table>
  </div>
    
    <script>
    /**
 * Identify your gateway and your site's gateway merchant identifier
 *
 * The Google Pay API response will return an encrypted payment method capable
 * of being charged by a supported gateway after payer authorization
 *
 * @see {@link https://developers.google.com/pay/api/web/reference/request-objects#gateway|PaymentMethodTokenizationSpecification}
 */
const tokenizationSpecification = {
type: 'PAYMENT_GATEWAY',
parameters: {
"gateway": "example",
"gatewayMerchantId": "exampleGatewayMerchantId",
}
};

/**
 * Card networks supported by your site and your gateway
 *
 * @see {@link https://developers.google.com/pay/api/web/reference/request-objects#CardParameters|CardParameters}
 */
const allowedCardNetworks = ["AMEX", "DISCOVER", "INTERAC", "JCB", "MASTERCARD", "VISA"];

/**
 * Define the version of the Google Pay API referenced when creating your
 * configuration
 *
 * @see {@link https://developers.google.com/pay/api/web/reference/request-objects#PaymentDataRequest|apiVersion in PaymentDataRequest}
 */
const baseRequest = {
  apiVersion: 2,
  apiVersionMinor: 0
};

/**
 * Card authentication methods supported by your site and your gateway
 *
 * @see {@link https://developers.google.com/pay/api/web/reference/request-objects#CardParameters|CardParameters}
 */
const allowedCardAuthMethods = ["PAN_ONLY", "CRYPTOGRAM_3DS"];

/**
 * Describe your site's support for the CARD payment method and its required
 * fields
 *
 * @see {@link https://developers.google.com/pay/api/web/reference/request-objects#CardParameters|CardParameters}
 */
const baseCardPaymentMethod = {
  type: 'CARD',
  parameters: {
    allowedAuthMethods: allowedCardAuthMethods,
    allowedCardNetworks: allowedCardNetworks
  }
};

/**
 * Describe your site's support for the CARD payment method including optional
 * fields
 *
 * @see {@link https://developers.google.com/pay/api/web/reference/request-objects#CardParameters|CardParameters}
 */
const cardPaymentMethod = Object.assign({},
  baseCardPaymentMethod, {
    tokenizationSpecification: tokenizationSpecification
  }
);

function displayResult(result) {
    document.getElementById("gatewayResponse").innerHTML = JSON.stringify(JSON.parse(result.paymentMethodData.tokenizationData.token), null, 2);
}

/**
 * An initialized google.payments.api.PaymentsClient object or null if not yet set
 *
 * @see {@link getGooglePaymentsClient}
 */
let paymentsClient = null;

/**
 * Configure your site's support for payment methods supported by the Google Pay
 * API.
 *
 * Each member of allowedPaymentMethods should contain only the required fields,
 * allowing reuse of this base request when determining a viewer's ability
 * to pay and later requesting a supported payment method
 *
 * @return {object} Google Pay API version, payment methods supported by the site
 */
function getGoogleIsReadyToPayRequest() {
    return Object.assign({},
        baseRequest, {
            allowedPaymentMethods: [baseCardPaymentMethod]
        }
    );
}

/**
 * Configure support for the Google Pay API
 *
 * @see {@link https://developers.google.com/pay/api/web/reference/request-objects#PaymentDataRequest|PaymentDataRequest}
 * @return {object} PaymentDataRequest fields
 */
function getGooglePaymentDataRequest() {
    const paymentDataRequest = Object.assign({}, baseRequest);
    paymentDataRequest.allowedPaymentMethods = [cardPaymentMethod];
    paymentDataRequest.transactionInfo = getGoogleTransactionInfo();
    paymentDataRequest.merchantInfo = {
        // See {@link https://developers.google.com/pay/api/web/guides/test-and-deploy/integration-checklist|Integration checklist}
        // merchantId: '01234567890123456789',
        merchantName: 'Example Merchant'
    };
    return paymentDataRequest;
}

/**
 * Return an active PaymentsClient or initialize
 *
 * @see {@link https://developers.google.com/pay/api/web/reference/client#PaymentsClient|PaymentsClient constructor}
 * @return {google.payments.api.PaymentsClient} Google Pay API client
 */
function getGooglePaymentsClient() {
    if (paymentsClient === null) {
        paymentsClient = new google.payments.api.PaymentsClient({
            environment: 'TEST'
        });
    }
    return paymentsClient;
}

/**
 * Initialize Google PaymentsClient after Google-hosted JavaScript has loaded
 *
 * Display a Google Pay payment button after confirmation of the viewer's
 * ability to pay.
 */
function onGooglePayLoaded() {
    const paymentsClient = getGooglePaymentsClient();
    paymentsClient.isReadyToPay(getGoogleIsReadyToPayRequest())
        .then(function (response) {
            if (response.result) {
                // prefetchGooglePaymentData();
            }
        })
        .catch(function (err) {
            // show error in developer console for debugging
            console.error(err);
        });
}

/**
 * Provide Google Pay API with a payment amount, currency, and amount status
 *
 * @see {@link https://developers.google.com/pay/api/web/reference/request-objects#TransactionInfo|TransactionInfo}
 * @return {object} transaction info, suitable for use as transactionInfo property of PaymentDataRequest
 */
function getGoogleTransactionInfo() {
    return {
        countryCode: 'US',
        currencyCode: 'USD',
        totalPriceStatus: 'FINAL',
        // set to cart total
        totalPrice: '1.00'
    };
}

/**
 * Prefetch payment data to improve performance
 *
 * @see {@link https://developers.google.com/pay/api/web/reference/client#prefetchPaymentData|prefetchPaymentData()}
 */
function prefetchGooglePaymentData() {
    const paymentDataRequest = getGooglePaymentDataRequest();
    // transactionInfo must be set but does not affect cache
    paymentDataRequest.transactionInfo = {
        totalPriceStatus: 'NOT_CURRENTLY_KNOWN',
        currencyCode: 'USD'
    };
    const paymentsClient = getGooglePaymentsClient();
    paymentsClient.prefetchPaymentData(paymentDataRequest);
}

/**
 * Show Google Pay payment sheet when Google Pay payment button is clicked
 */
function onGooglePaymentButtonClicked() {
    // Alert if sample is using example values
    if (tokenizationSpecification.parameters["gateway"].startsWith('example')) {
        alert('In the JavaScript box, replace the tokenizationSpecification parameters with your gateway\'s sandbox parameters.');
        return;
    }
    const paymentDataRequest = getGooglePaymentDataRequest();
    paymentDataRequest.transactionInfo = getGoogleTransactionInfo();

    const paymentsClient = getGooglePaymentsClient();
    paymentsClient.loadPaymentData(paymentDataRequest)
        .then(function (paymentData) {
            // handle the response
            processPayment(paymentData);
            displayResult(paymentData);
        })
        .catch(function (err) {
            // show error in developer console for debugging
            console.error(err);
        });
}

/**
 * Process payment data returned by the Google Pay API
 *
 * @param {object} paymentData response from Google Pay API after user approves payment
 * @see {@link https://developers.google.com/pay/api/web/reference/response-objects#PaymentData|PaymentData object reference}
 */
function processPayment(paymentData) {
    // show returned data in developer console for debugging
    console.log(paymentData);
    paymentToken = paymentData.paymentMethodData.tokenizationData.token;
}

    </script>
    
<script async
      src="https://pay.google.com/gp/p/js/pay.js"></script>
    
</body> </html>

To test your payment parameters, do the following:

  1. Click the Open in JSFiddle (< >) button to open the above sample code in JSFiddle.
  2. Find the JavaScript + No-Library pane.
  3. In tokenizationSpecification (begins on line #9), replace the parameters with your own values. Refer to the Google Pay API gateway documentation for details on your payment gateway's parameters.

  4. On the JSFiddle page, click Run in the main navigation.
  5. Click the Buy with GPay button.
  6. Select your Google Account and payment method.
  7. Click Continue to complete the payment.

When the test payment is successful, the Payment Gateway Response section contains a message with your payment token and generic payment details:

A test payment token from Stripe

Automated integration test

Start an integration test to run a series of test cases against your data feeds and fulfillment endpoint. When the test is complete, you can review the success rate and specific test cases to troubleshoot issues.

Each test case can handle up to 250 entities, and you may perform one integration test run at a time. Before starting another test, wait until the previous one completes.

To run an integration test in the Actions console, do the following:

  1. From the main navigation, go to Test > Integrations.
  2. Click the New test button.
  3. In the Data feed drop-down list, select the feed you want to test. A feed must be uploaded in order to run a test on it.
  4. In the Fulfillment endpoint field, enter the URL for your fulfillment endpoint.
  5. Click Run to start the test.

This test may take up to 20 minutes to complete depending on the number of entities. To see the progress of the test, refresh the page.

When a test run finishes, you can view test results for each test case. Click on a test case for details on how each entity performed for that case, with error messages describing entities that failed. Note that some entities skip certain test cases because they don't meet the criteria for that case.

Integration test cases

In each test case, the console reads your data feeds to generate Checkout and SubmitOrder requests to your endpoint that check your cart validation, order creation, and error handling implementations.

The integration test runs the following test scenarios for Checkout:

  • Eligible cart - items without options: The test checks out an eligible cart with simple menu items.
  • Eligible cart - items with options: The test checks out an eligible cart with complex menu item customizations.
  • Delivery address is out of range: The test attempts to check out a cart with a delivery address outside of the service area.
  • Minimum order value is not met: The test attempts to check out a cart that is below the minimum order value.
  • Restaurant is closed: The test attempts to check out a cart while the restaurant is closed.
  • Unavailable items: The test attempts to check out a cart with unavailable items like lunch specials.
  • Unavailable items - min order value not met: The test attempts to check out a cart with unavailable items like lunch specials, and the other valid items in the cart don't meet the minimum order value.
  • Outdated item prices: The test attempts to check out a cart with artificially modified item prices.
  • Outdated item prices - min order value not met: The test attempts to check out a cart with artificially modified item prices, and the actual value of the cart does not meet the minimum order value.
  • Outdated option prices: The test attempts to check out a cart with artificially modified option prices.
  • Outdated option prices - min order value not met: The test attempts to check out a cart with artificially modified option prices, and the actual value of the cart does not meet the minimum order value.
  • Invalid promo code sent in checkout: The test attempts to check out with an invalid promo code.

The integration test runs the following test scenarios for SubmitOrder:

  • Eligible cart: The test submits an order with an eligible cart.
  • Payment declined: The test attempts to submit an order with an invalid payment token.
  • Ineligible user: The test attempts to submit an order with incomplete user information.

Chrome DevTools Console

When testing the Food Ordering UI, you can view the Checkout Action and SubmitOrder Action logs in the Chrome DevTools Console. In the logs, you can examine data such as the request and response to your fulfillment endpoints, and the commands (for example, the Retry command).

Stackdriver logs

Stackdriver lets you view message logs for all invocations of your project's fulfillment actions (Checkout, Submit order, and Order update). Stackdriver logs all requests, responses, errors, and warnings, which can be useful for monitoring and debugging your Food Ordering service.

To view the log history, follow these steps:

  1. In the Google Cloud Platform Console, go to Stackdriver > Logging > Logs.
  2. Select your existing project at the top of the page.
  3. Using the drop-down lists, select Google Assistant Action as the resource to see Food Ordering logs. You can filter the logs by timestamp or search by keyword.

You can learn more about how to navigate your project's logs at Viewing logs in the Stackdriver documentation.

Filter logs for Food Ordering requests

Note that multiple checkout and submit order requests are linked by a Stackdriver trace value if they belong to the same session (that is, from the same user making multiple requests).

To filter log results by trace, follow these steps:

  1. In the Stackdriver Logs page, click the downward triangle icon at the end of the Filter by label or text search field.
  2. Click Convert to advanced filter.
  3. In the text field, set resource.type=assistant_actions and set the trace property to the trace ID (or trace>=projects to view all traces).
  4. Click Submit Filter to view the results.

Here's an example of the Stackdriver logs results filtered by trace:

You can search for order update requests using the action_order_id field.