This guide will walk you through the process of developing an Actions project
that uses the Orders API to place reservations.

## Transaction flow

When your Actions project handles reservations, it
uses the following flow:

1. **Validate transaction requirements (optional)** - Use the transactions requirements helper at the start of the conversation to make sure the user is capable of carrying out a transaction.
2. **Build the order** - Walk the user through a "cart assembly" where they build the details of their reservation.
3. **Propose the order** - Once the "cart" is complete, propose the reservation "order" to the user, so they can confirm it's correct. If the reservation is confirmed, you'll receive a response with reservation details.
4. **Finalize the order and send a receipt** - With the order confirmed, update your reservation system and send a receipt to the user.
5. **Send order updates** - Over the course of the reservation's lifespan, give the user reservation status updates by sending PATCH requests to the Orders API.

## Restrictions and review guidelines

Keep in mind that additional policies apply to Actions that use the transactions and Orders API. It can take us up to six weeks to review Actions with transactions, so factor that time in when planning your release schedule. To ease the review process, make sure you comply with the [policies and guidelines for transactions](https://developers.google.com/assistant/console/policies/general-policies.md.txt#transactions) before submitting your Action for review.

You can only deploy Actions that use the Orders API in the following countries:

|---|---|---|
| Australia Brazil Canada Indonesia | Japan Mexico Qatar Russia | Singapore Switzerland Thailand Turkey United Kingdom United States |

> [!NOTE]
> **Note:** During the review process, you may see transactions performed by unknown users. These users are associated with Google as part of the transactions Action review process. Your Action won't be available to the general public until it's fully reviewed and published.

## Build your project

For extensive examples of transactional conversations, view our transactions samples in [Node.js](https://github.com/actions-on-google/dialogflow-transactions-nodejs) and [Java](https://github.com/actions-on-google/dialogflow-transactions-java).

### Project setup

When creating your Action, you must specify that you want to perform transactions
in the [Actions console](https://console.actions.google.com). Also, if you're
using the Node.JS client library, set up your fulfillment to use the latest
version of the Orders API.

> [!IMPORTANT]
> **Key Term:** The **Orders API** defines a suite of object types for processing transactions (like payment information and cart contents) and a dedicated API endpoint for sending order updates to a customer.

To set up your project and fulfillment, do the following:

1. Create a new project or import an existing project.
2. Navigate to **Deploy \> Directory information**.
3. Under **Additional information \> Transactions** \> check the box that says "Do your Actions
   use the Transactions API to perform transactions of physical goods?".

   > [!NOTE]
   > **Note:** We recommend using Dialogflow to make it much easier to build a high-quality conversational experience.

4. If you're using the Node.JS client library to build your Action's fulfillment,
   open your fulfillment code and update your app delcaration to set the
   `ordersv3` flag to `true`. The following code snippet shows an example app
   declaration for Orders version 3.

### Node.js

```javascript
const {dialogflow} = require('actions-on-google');
let app = dialogflow({
  clientId, // If using account linking
  debug: true,
  ordersv3: true,
});
```

### Node.js

```javascript
const {actionssdk} = require('actions-on-google');
let app = actionssdk({
  clientId, // If using account linking
  debug: true,
  ordersv3: true,
});
```

### 1. Validate transaction requirements *(optional)*

#### User experience

As soon as the user has indicated they wish to set up a reservation, we recommend triggering the
`actions.intent.TRANSACTION_REQUIREMENTS_CHECK` intent to ensure they can
request a reservation. For example, when invoked, your Action might ask,
"would you like to reserve a seat?" If the user says
"yes", you should request this intent right away. This will ensure
that they can proceed and give them an opportunity to fix any settings
preventing them from continuing with the transaction.

Requesting the transactions
requirements check intent results in one of the following outcomes:

- If the requirements are met, your fulfillment receives an intent with a success condition and you can proceed with building the user's order.
- If one or more of the requirements aren't, your fulfillment receives
  the intent with a failure condition. In this case, end the conversation or
  pivot away from the reservation flow.

  If the user can fix the error, they're automatically prompted to resolve those issues
  on their device. If the conversation is taking place on a voice-only surface
  like a smart speaker, it's handed off to the user's phone.

#### Fulfillment

To ensure that a user meets
transaction requirements, request fulfillment of the
`actions.intent.TRANSACTION_REQUIREMENTS_CHECK` intent with a
[TransactionRequirementsCheckSpec](https://developers.google.com/assistant/conversational/webhook/reference/rest/Shared.Types/TransactionRequirementsCheckSpec) object.

> [!NOTE]
> **Note:** This intent's fulfillment is also silently requested during fulfillment of `actions.intent.TRANSACTION_DECISION`.

##### Check requirements

Check to see if a user satisfies reservation requirements with the client library:
Node.js

```
conv.ask(new TransactionRequirements());
```
Java

```
return getResponseBuilder(request)
    .add("Placeholder for transaction requirements text")
    .add(new TransactionRequirements())
    .build();
```
Dialogflow JSON

Note that the JSON below describes a webhook response.

```
{
  "payload": {
    "google": {
      "expectUserResponse": true,
      "systemIntent": {
        "intent": "actions.intent.TRANSACTION_REQUIREMENTS_CHECK",
        "data": {
          "@type": "type.googleapis.com/google.actions.transactions.v3.TransactionRequirementsCheckSpec"
        }
      }
    }
  }
}
```
Actions SDK JSON

Note that the JSON below describes a webhook response.

```
{
  "expectUserResponse": true,
  "expectedInputs": [
    {
      "possibleIntents": [
        {
          "intent": "actions.intent.TRANSACTION_REQUIREMENTS_CHECK",
          "inputValueData": {
            "@type": "type.googleapis.com/google.actions.transactions.v3.TransactionRequirementsCheckSpec"
          }
        }
      ]
    }
  ]
}
```

###### Receive the result of a requirements check

After the Assistant fulfills the intent, it sends your fulfillment a request
with the `actions.intent.TRANSACTION_REQUIREMENTS_CHECK` intent with the result
of the check.

To properly handle this request, declare a Dialogflow intent that's triggered by
the `actions_intent_TRANSACTION_REQUIREMENTS_CHECK` event. When triggered,
handle this intent in your fulfillment:
Node.js

```
const arg = conv.arguments.get('TRANSACTION_REQUIREMENTS_CHECK_RESULT');
if (arg && arg.resultType === 'CAN_TRANSACT') {
  // Normally take the user through cart building flow
  conv.ask(`Looks like you're good to go!`);
} else {
  conv.close('Transaction failed.');
}
```
Java

```
Argument transactionCheckResult = request
    .getArgument("TRANSACTION_REQUIREMENTS_CHECK_RESULT");
boolean result = false;
if (transactionCheckResult != null) {
  Map<String, Object> map = transactionCheckResult.getExtension();
  if (map != null) {
    String resultType = (String) map.get("resultType");
    result = resultType != null && resultType.equals("CAN_TRANSACT");
  }
}
ResponseBuilder responseBuilder = getResponseBuilder(request);
if (result) {
  responseBuilder.add("Looks like you're good to go! Now say 'confirm transaction'");
} else {
  responseBuilder.add("Transaction failed");
}
return responseBuilder.build();
```
Dialogflow JSON

Note that the JSON below describes a webhook request.

```
{
  "responseId": "",
  "queryResult": {
    "queryText": "",
    "action": "",
    "parameters": {},
    "allRequiredParamsPresent": true,
    "fulfillmentText": "",
    "fulfillmentMessages": [],
    "outputContexts": [],
    "intent": {
      "name": "reservation_transaction_check_complete_df",
      "displayName": "reservation_transaction_check_complete_df"
    },
    "intentDetectionConfidence": 1,
    "diagnosticInfo": {},
    "languageCode": ""
  },
  "originalDetectIntentRequest": {
    "source": "google",
    "version": "2",
    "payload": {
      "isInSandbox": true,
      "surface": {
        "capabilities": [
          {
            "name": "actions.capability.SCREEN_OUTPUT"
          },
          {
            "name": "actions.capability.AUDIO_OUTPUT"
          },
          {
            "name": "actions.capability.MEDIA_RESPONSE_AUDIO"
          },
          {
            "name": "actions.capability.WEB_BROWSER"
          }
        ]
      },
      "inputs": [
        {
          "rawInputs": [],
          "intent": "",
          "arguments": [
            {
              "extension": {
                "@type": "type.googleapis.com/google.transactions.v3.TransactionRequirementsCheckResult",
                "resultType": "CAN_TRANSACT"
              },
              "name": "TRANSACTION_REQUIREMENTS_CHECK_RESULT"
            }
          ]
        }
      ],
      "user": {},
      "conversation": {},
      "availableSurfaces": [
        {
          "capabilities": [
            {
              "name": "actions.capability.SCREEN_OUTPUT"
            },
            {
              "name": "actions.capability.AUDIO_OUTPUT"
            },
            {
              "name": "actions.capability.MEDIA_RESPONSE_AUDIO"
            },
            {
              "name": "actions.capability.WEB_BROWSER"
            }
          ]
        }
      ]
    }
  },
  "session": ""
}
```
Actions SDK JSON

Note that the JSON below describes a webhook request.

```
{
  "user": {},
  "device": {},
  "surface": {
    "capabilities": [
      {
        "name": "actions.capability.SCREEN_OUTPUT"
      },
      {
        "name": "actions.capability.AUDIO_OUTPUT"
      },
      {
        "name": "actions.capability.MEDIA_RESPONSE_AUDIO"
      },
      {
        "name": "actions.capability.WEB_BROWSER"
      }
    ]
  },
  "conversation": {},
  "inputs": [
    {
      "rawInputs": [],
      "intent": "reservation_transaction_check_complete_asdk",
      "arguments": [
        {
          "extension": {
            "@type": "type.googleapis.com/google.transactions.v3.TransactionRequirementsCheckResult",
            "resultType": "CAN_TRANSACT"
          },
          "name": "TRANSACTION_REQUIREMENTS_CHECK_RESULT"
        }
      ]
    }
  ],
  "availableSurfaces": [
    {
      "capabilities": [
        {
          "name": "actions.capability.SCREEN_OUTPUT"
        },
        {
          "name": "actions.capability.AUDIO_OUTPUT"
        },
        {
          "name": "actions.capability.MEDIA_RESPONSE_AUDIO"
        },
        {
          "name": "actions.capability.WEB_BROWSER"
        }
      ]
    }
  ]
}
```

### 2. Build the order

#### User experience

Once you have the user information you need, build a "cart
assembly" experience that guides the user to build their reservation. Every
Action will have a slightly different cart assembly flow as appropriate for their
service.

In a basic cart assembly experience, a user selects options from a list to add
to their reservation, though you can design the conversation to simplify the
user experience. For example, build a cart assembly experience that enables the
user to schedule a monthly reservation with a simple yes or no question.
You can also present the user a carousel or list card of "recommended"
reservations.

We recommend using [rich
responses](https://developers.google.com/assistant/df-asdk/rich-responses) to present the user's options
visually, but also design the conversation so the user can build their
cart using only their voice. For some best practices and examples of
cart assembly experiences, see the [Transactions Design Guidelines](https://developers.google.com/assistant/df-asdk/transactions/physical/design.md.txt).

#### Fulfillment

Throughout your conversation, gather the reservation details that a user wants
to purchase and then construct an [`Order`](https://developers.google.com/assistant/df-asdk/transactions/reference/physical/rest/v3/Order.md.txt) object.

Your `Order` must at least contain the following:

- `buyerInfo` - Information about the user scheduling the reservation.
- `transactionMerchant` - Information about the merchant that's facilitating the reservation.
- `contents` - The actual details of the reservation listed as `lineItems`.

> [!NOTE]
> **Note:** Additional information is automatically added to the `Order` object throughout the transaction. For more information about the possible contents of an order, refer to the [reference documentation](https://developers.google.com/assistant/df-asdk/transactions/reference/physical.md.txt).

Refer to the [`Order`](https://developers.google.com/assistant/df-asdk/transactions/reference/physical/rest/v3/Order.md.txt) response documentation
to construct your cart. Note that you may need to include different fields
depending on the reservation.

The sample code below shows a complete reservation order, including optional fields:
Node.js

```
app.intent('build_reservation_df', (conv) => {
  const now = new Date().toISOString();
  const order = {
    createTime: now,
    lastUpdateTime: now,
    merchantOrderId: 'UNIQUE_ORDER_ID',
    userVisibleOrderId: 'USER_VISIBLE_ORDER_ID',
    transactionMerchant: {
      id: 'https://www.example.com',
      name: 'Example Merchant',
    },
    contents: {
      lineItems: [
        {
          id: 'LINE_ITEM_ID',
          name: 'Dinner reservation',
          description: 'A world of flavors all in one destination.',
          reservation: {
            status: 'PENDING',
            userVisibleStatusLabel: 'Reservation is pending.',
            type: 'RESTAURANT',
            reservationTime: {
              timeIso8601: '2020-01-16T01:30:15.01Z',
            },
            userAcceptableTimeRange: {
              timeIso8601: '2020-01-15/2020-01-17',
            },
            partySize: 6,
            staffFacilitators: [
              {
                name: 'John Smith',
              },
            ],
            location: {
              zipCode: '94086',
              city: 'Sunnyvale',
              postalAddress: {
                regionCode: 'US',
                postalCode: '94086',
                administrativeArea: 'CA',
                locality: 'Sunnyvale',
                addressLines: [
                  '222, Some other Street',
                ],
              },
            },
          },
        },
      ],
    },
    buyerInfo: {
      email: 'janedoe@gmail.com',
      firstName: 'Jane',
      lastName: 'Doe',
      displayName: 'Jane Doe',
    },
    followUpActions: [
      {
        type: 'VIEW_DETAILS',
        title: 'View details',
        openUrlAction: {
          url: 'https://example.com',
        },
      },
      {
        type: 'CALL',
        title: 'Call us',
        openUrlAction: {
          url: 'tel:+16501112222',
        },
      },
      {
        type: 'EMAIL',
        title: 'Email us',
        openUrlAction: {
          url: 'mailto:person@example.com',
        },
      },
    ],
    termsOfServiceUrl: 'https://www.example.com',
  };
```
Java

```
private static OrderV3 createOrder() {
  // Transaction Merchant
  MerchantV3 transactionMerchant = new MerchantV3()
      .setId("http://www.example.com")
      .setName("Example Merchant");

  // Line Item

  // Reservation Item Extension
  ReservationItemExtension reservationItemExtension = new ReservationItemExtension()
      .setStatus("PENDING")
      .setUserVisibleStatusLabel("Reservation pending.")
      .setType("RESTAURANT")
      .setReservationTime(new TimeV3()
          .setTimeIso8601("2020-01-16T01:30:15.01Z"))
      .setUserAcceptableTimeRange(new TimeV3()
          .setTimeIso8601("2020-01-15/2020-01-17"))
      .setPartySize(6)
      .setStaffFacilitators(Collections.singletonList(new StaffFacilitator()
          .setName("John Smith")))
      .setLocation(new Location()
          .setZipCode("94086")
          .setCity("Sunnyvale")
          .setPostalAddress(new PostalAddress()
              .setRegionCode("US")
              .setPostalCode("94086")
              .setAdministrativeArea("CA")
              .setLocality("Sunnyvale")
              .setAddressLines(
                  Collections.singletonList("222, Some other Street"))));

  LineItemV3 lineItem = new LineItemV3()
      .setId("LINE_ITEM_ID")
      .setName("Dinner reservation")
      .setDescription("A world of flavors all in one destination.")
      .setReservation(reservationItemExtension);

  // Order Contents
  OrderContents contents = new OrderContents()
      .setLineItems(Collections.singletonList(lineItem));

  // User Info
  UserInfo buyerInfo = new UserInfo()
      .setEmail("janedoe@gmail.com")
      .setFirstName("Jane")
      .setLastName("Doe")
      .setDisplayName("Jane Doe");

  // Follow up actions
  Action viewDetails = new Action()
      .setType("VIEW_DETAILS")
      .setTitle("View details")
      .setOpenUrlAction(new OpenUrlAction()
          .setUrl("https://example.com"));

  Action call = new Action()
      .setType("CALL")
      .setTitle("Call us")
      .setOpenUrlAction(new OpenUrlAction()
          .setUrl("tel:+16501112222"));

  Action email = new Action()
      .setType("EMAIL")
      .setTitle("Email us")
      .setOpenUrlAction(new OpenUrlAction()
          .setUrl("mailto:person@example.com"));

  // Terms of service and order note
  String termsOfServiceUrl = "https://example.com";

  String now = Instant.now().toString();

  OrderV3 order = new OrderV3()
      .setCreateTime(now)
      .setLastUpdateTime(now)
      .setMerchantOrderId("UNIQUE_ORDER_ID")
      .setUserVisibleOrderId("UNIQUE_USER_VISIBLE_ORDER_ID")
      .setTransactionMerchant(transactionMerchant)
      .setContents(contents)
      .setBuyerInfo(buyerInfo)
      .setFollowUpActions(Arrays.asList(
          viewDetails,
          call,
          email
      ))
      .setTermsOfServiceUrl(termsOfServiceUrl);

  return order;
}
```
JSON

Note that the JSON below describes a webhook response.

```
{
  "payload": {
    "google": {
      "expectUserResponse": true,
      "systemIntent": {
        "intent": "actions.intent.TRANSACTION_DECISION",
        "data": {
          "@type": "type.googleapis.com/google.actions.transactions.v3.TransactionDecisionValueSpec",
          "order": {
            "createTime": "2019-07-17T18:25:30.182Z",
            "lastUpdateTime": "2019-07-17T18:25:30.182Z",
            "merchantOrderId": "UNIQUE_ORDER_ID",
            "userVisibleOrderId": "USER_VISIBLE_ORDER_ID",
            "transactionMerchant": {
              "id": "https://www.example.com",
              "name": "Example Merchant"
            },
            "contents": {
              "lineItems": [
                {
                  "id": "LINE_ITEM_ID",
                  "name": "Dinner reservation",
                  "description": "A world of flavors all in one destination.",
                  "reservation": {
                    "status": "PENDING",
                    "userVisibleStatusLabel": "Reservation is pending.",
                    "type": "RESTAURANT",
                    "reservationTime": {
                      "timeIso8601": "2020-01-16T01:30:15.01Z"
                    },
                    "userAcceptableTimeRange": {
                      "timeIso8601": "2020-01-15/2020-01-17"
                    },
                    "partySize": 6,
                    "staffFacilitators": [
                      {
                        "name": "John Smith"
                      }
                    ],
                    "location": {
                      "zipCode": "94086",
                      "city": "Sunnyvale",
                      "postalAddress": {
                        "regionCode": "US",
                        "postalCode": "94086",
                        "administrativeArea": "CA",
                        "locality": "Sunnyvale",
                        "addressLines": [
                          "222, Some other Street"
                        ]
                      }
                    }
                  }
                }
              ]
            },
            "buyerInfo": {
              "email": "janedoe@gmail.com",
              "firstName": "Jane",
              "lastName": "Doe",
              "displayName": "Jane Doe"
            },
            "followUpActions": [
              {
                "type": "VIEW_DETAILS",
                "title": "View details",
                "openUrlAction": {
                  "url": "https://example.com"
                }
              },
              {
                "type": "CALL",
                "title": "Call us",
                "openUrlAction": {
                  "url": "tel:+16501112222"
                }
              },
              {
                "type": "EMAIL",
                "title": "Email us",
                "openUrlAction": {
                  "url": "mailto:person@example.com"
                }
              }
            ],
            "termsOfServiceUrl": "https://www.example.com"
          },
          "orderOptions": {
            "requestDeliveryAddress": false,
            "userInfoOptions": {
              "userInfoProperties": [
                "EMAIL"
              ]
            }
          },
          "presentationOptions": {
            "actionDisplayName": "RESERVE"
          }
        }
      }
    }
  }
}
```

### 3. Propose the order

Present your reservation order to the user for them to confirm or
reject. Request the `actions.intent.TRANSACTION_DECISION`
intent and provide the `Order` that you built.

#### User Experience

When you request the `actions.intent.TRANSACTION_DECISION` intent, the Assistant
initiates a built-in experience in which the `Order` is
rendered directly onto a "cart preview card". The user can say "schedule reservation",
decline the transaction, or request to change the reservation details.

The user may also request changes to the order at this point. In this case,
you should make sure your fulfillment can handle order change requests after
finishing the cart assembly experience.

> [!NOTE]
> **Note:** The data presented in the "cart preview card" is rendered as-is according to your `Order`. Our policy requires that the data presented is itemized, accurate, and consistent with what the user requested during their dialog. Please note that Google does not calculate fields like taxes, fees, and others, but rather takes them directly from what you pass into the `actions.intent.TRANSACTION_DECISION` intent.

#### Fulfillment

When you request the
`actions.intent.TRANSACTION_DECISION` intent, create a
`TransactionDecision` that contains the `Order`
and `orderOptions`

> [!NOTE]
> **Note:** While most reservations don't require payment, you can include `paymentParameters` if a user needs to pay a fee to schedule their reservation. Refer to the [Google Pay](https://developers.google.com/assistant/df-asdk/transactions/physical/dev-guide-physical-gpay.md.txt) and [merchant-managed payment](https://developers.google.com/assistant/df-asdk/transactions/physical/dev-guide-physical-custom.md.txt) guides for guidance on including `paymentParameters` to request payment.

The following code shows an example `TransactionsDecision` for an order:
Node.js

```
conv.ask(new TransactionDecision({
  orderOptions: {
    requestDeliveryAddress: 'false',
  },
  presentationOptions: {
    actionDisplayName: 'RESERVE',
  },
  order: order,
}));
```
Java

```
// Create order options
OrderOptionsV3 orderOptions = new OrderOptionsV3()
    .setRequestDeliveryAddress(false)
    .setUserInfoOptions(new UserInfoOptions()
        .setUserInfoProperties(Collections.singletonList("EMAIL")));

// Create presentation options
PresentationOptionsV3 presentationOptions = new PresentationOptionsV3()
    .setActionDisplayName("RESERVE");

// Ask for transaction decision
return getResponseBuilder(request)
    .add("Placeholder for transaction decision text")
    .add(new TransactionDecision()
        .setOrder(order)
        .setOrderOptions(orderOptions)
        .setPresentationOptions(presentationOptions)
    )
    .build();
```
Dialogflow JSON

Note that the JSON below describes a webhook response.

```
{
  "payload": {
    "google": {
      "expectUserResponse": true,
      "systemIntent": {
        "intent": "actions.intent.TRANSACTION_DECISION",
        "data": {
          "@type": "type.googleapis.com/google.actions.transactions.v3.TransactionDecisionValueSpec",
          "orderOptions": {
            "requestDeliveryAddress": "false"
          },
          "presentationOptions": {
            "actionDisplayName": "RESERVE"
          },
          "order": {
            "createTime": "2019-07-17T18:25:30.184Z",
            "lastUpdateTime": "2019-07-17T18:25:30.184Z",
            "merchantOrderId": "UNIQUE_ORDER_ID",
            "userVisibleOrderId": "USER_VISIBLE_ORDER_ID",
            "transactionMerchant": {
              "id": "https://www.example.com",
              "name": "Example Merchant"
            },
            "contents": {
              "lineItems": [
                {
                  "id": "LINE_ITEM_ID",
                  "name": "Dinner reservation",
                  "description": "A world of flavors all in one destination.",
                  "reservation": {
                    "status": "PENDING",
                    "userVisibleStatusLabel": "Reservation is pending.",
                    "type": "RESTAURANT",
                    "reservationTime": {
                      "timeIso8601": "2020-01-16T01:30:15.01Z"
                    },
                    "userAcceptableTimeRange": {
                      "timeIso8601": "2020-01-15/2020-01-17"
                    },
                    "partySize": 6,
                    "staffFacilitators": [
                      {
                        "name": "John Smith"
                      }
                    ],
                    "location": {
                      "zipCode": "94086",
                      "city": "Sunnyvale",
                      "postalAddress": {
                        "regionCode": "US",
                        "postalCode": "94086",
                        "administrativeArea": "CA",
                        "locality": "Sunnyvale",
                        "addressLines": [
                          "222, Some other Street"
                        ]
                      }
                    }
                  }
                }
              ]
            },
            "buyerInfo": {
              "email": "janedoe@gmail.com",
              "firstName": "Jane",
              "lastName": "Doe",
              "displayName": "Jane Doe"
            },
            "followUpActions": [
              {
                "type": "VIEW_DETAILS",
                "title": "View details",
                "openUrlAction": {
                  "url": "https://example.com"
                }
              },
              {
                "type": "CALL",
                "title": "Call us",
                "openUrlAction": {
                  "url": "tel:+16501112222"
                }
              },
              {
                "type": "EMAIL",
                "title": "Email us",
                "openUrlAction": {
                  "url": "mailto:person@example.com"
                }
              }
            ],
            "termsOfServiceUrl": "https://www.example.com"
          }
        }
      }
    }
  }
}
```
Actions SDK JSON

Note that the JSON below describes a webhook response.

```
{
  "expectUserResponse": true,
  "expectedInputs": [
    {
      "possibleIntents": [
        {
          "intent": "actions.intent.TRANSACTION_DECISION",
          "inputValueData": {
            "@type": "type.googleapis.com/google.actions.transactions.v3.TransactionDecisionValueSpec",
            "orderOptions": {
              "requestDeliveryAddress": "false"
            },
            "presentationOptions": {
              "actionDisplayName": "RESERVE"
            },
            "order": {
              "createTime": "2019-07-17T18:25:30.057Z",
              "lastUpdateTime": "2019-07-17T18:25:30.057Z",
              "merchantOrderId": "UNIQUE_ORDER_ID",
              "userVisibleOrderId": "USER_VISIBLE_ORDER_ID",
              "transactionMerchant": {
                "id": "https://www.example.com",
                "name": "Example Merchant"
              },
              "contents": {
                "lineItems": [
                  {
                    "id": "LINE_ITEM_ID",
                    "name": "Dinner reservation",
                    "description": "A world of flavors all in one destination.",
                    "reservation": {
                      "status": "PENDING",
                      "userVisibleStatusLabel": "Reservation is pending.",
                      "type": "RESTAURANT",
                      "reservationTime": {
                        "timeIso8601": "2020-01-16T01:30:15.01Z"
                      },
                      "userAcceptableTimeRange": {
                        "timeIso8601": "2020-01-15/2020-01-17"
                      },
                      "partySize": 6,
                      "staffFacilitators": [
                        {
                          "name": "John Smith"
                        }
                      ],
                      "location": {
                        "zipCode": "94086",
                        "city": "Sunnyvale",
                        "postalAddress": {
                          "regionCode": "US",
                          "postalCode": "94086",
                          "administrativeArea": "CA",
                          "locality": "Sunnyvale",
                          "addressLines": [
                            "222, Some other Street"
                          ]
                        }
                      }
                    }
                  }
                ]
              },
              "buyerInfo": {
                "email": "janedoe@gmail.com",
                "firstName": "Jane",
                "lastName": "Doe",
                "displayName": "Jane Doe"
              },
              "followUpActions": [
                {
                  "type": "VIEW_DETAILS",
                  "title": "View details",
                  "openUrlAction": {
                    "url": "https://example.com"
                  }
                },
                {
                  "type": "CALL",
                  "title": "Call us",
                  "openUrlAction": {
                    "url": "tel:+16501112222"
                  }
                },
                {
                  "type": "EMAIL",
                  "title": "Email us",
                  "openUrlAction": {
                    "url": "mailto:person@example.com"
                  }
                }
              ],
              "termsOfServiceUrl": "https://www.example.com"
            }
          }
        }
      ]
    }
  ]
}
```

##### **Handle the user's decision**

After the user responds to the proposed order, your fulfillment receives the
`actions_intent_TRANSACTION_DECISION` intent with an argument containing a
`TransactionDecisionValue`. This value will contain the following:

- `transactionDecision` - The user's decision regarding the proposed order. The possible values are `ORDER_ACCEPTED`, `ORDER_REJECTED`, `CART_CHANGE_REQUESTED`, and `USER_CANNOT_TRANSACT`.

To handle this request, declare a Dialogflow intent that's triggered by
the `actions_intent_TRANSACTION_DECISION` event. Handle this intent in
your fulfillment:
Node.js

```
const arg = conv.arguments.get('TRANSACTION_DECISION_VALUE');
if (arg && arg.transactionDecision === 'ORDER_ACCEPTED') {
  console.log('order accepted');
  const order = arg.order;
}
```
Java

```
Argument transactionDecisionValue = request
    .getArgument("TRANSACTION_DECISION_VALUE");
Map<String, Object> extension = null;
if (transactionDecisionValue != null) {
  extension = transactionDecisionValue.getExtension();
}

String transactionDecision = null;
if (extension != null) {
  transactionDecision = (String) extension.get("transactionDecision");
}
if ((transactionDecision != null && transactionDecision.equals("ORDER_ACCEPTED"))) {
  OrderV3 order = ((OrderV3) extension.get("order"));
}
```
Dialogflow JSON

Note that the JSON below describes a webhook request.

```
{
  "responseId": "",
  "queryResult": {
    "queryText": "",
    "action": "",
    "parameters": {},
    "allRequiredParamsPresent": true,
    "fulfillmentText": "",
    "fulfillmentMessages": [],
    "outputContexts": [],
    "intent": {
      "name": "reservation_get_transaction_decision_df",
      "displayName": "reservation_get_transaction_decision_df"
    },
    "intentDetectionConfidence": 1,
    "diagnosticInfo": {},
    "languageCode": ""
  },
  "originalDetectIntentRequest": {
    "source": "google",
    "version": "2",
    "payload": {
      "isInSandbox": true,
      "surface": {
        "capabilities": [
          {
            "name": "actions.capability.SCREEN_OUTPUT"
          },
          {
            "name": "actions.capability.AUDIO_OUTPUT"
          },
          {
            "name": "actions.capability.MEDIA_RESPONSE_AUDIO"
          },
          {
            "name": "actions.capability.WEB_BROWSER"
          }
        ]
      },
      "inputs": [
        {
          "rawInputs": [],
          "intent": "",
          "arguments": []
        }
      ],
      "user": {},
      "conversation": {},
      "availableSurfaces": [
        {
          "capabilities": [
            {
              "name": "actions.capability.SCREEN_OUTPUT"
            },
            {
              "name": "actions.capability.AUDIO_OUTPUT"
            },
            {
              "name": "actions.capability.MEDIA_RESPONSE_AUDIO"
            },
            {
              "name": "actions.capability.WEB_BROWSER"
            }
          ]
        }
      ]
    }
  },
  "session": ""
}
```
Actions SDK JSON

Note that the JSON below describes a webhook request.

```
{
  "user": {},
  "device": {},
  "surface": {
    "capabilities": [
      {
        "name": "actions.capability.SCREEN_OUTPUT"
      },
      {
        "name": "actions.capability.AUDIO_OUTPUT"
      },
      {
        "name": "actions.capability.MEDIA_RESPONSE_AUDIO"
      },
      {
        "name": "actions.capability.WEB_BROWSER"
      }
    ]
  },
  "conversation": {},
  "inputs": [
    {
      "rawInputs": [],
      "intent": "reservation_get_transaction_decision_asdk",
      "arguments": []
    }
  ],
  "availableSurfaces": [
    {
      "capabilities": [
        {
          "name": "actions.capability.SCREEN_OUTPUT"
        },
        {
          "name": "actions.capability.AUDIO_OUTPUT"
        },
        {
          "name": "actions.capability.MEDIA_RESPONSE_AUDIO"
        },
        {
          "name": "actions.capability.WEB_BROWSER"
        }
      ]
    }
  ]
}
```

### 4. Finalize the reservation and send a receipt

When the `actions.intent.TRANSACTION_DECISION` intent returns with a
`transactionDecision`
of `ORDER_ACCEPTED`, perform whatever
processing is required to schedule the reservation (like persisting it in
your own database).

Send a [simple response](https://developers.google.com/assistant/df-asdk/simple-responses.md.txt)
to keep the conversation moving. The user receives a "collapsed receipt card"
along with your response.

#### Fulfillment

Node.js

```
// Set lastUpdateTime and update status of reservation
order.lastUpdateTime = new Date().toISOString();
order.reservation.status = 'CONFIRMED';
order.reservation.userVisibleStatusLabel = 'Reservation confirmed';
order.reservation.confirmationCode = '123ABCDEFGXYZ';

// Send synchronous order update
conv.ask(`Transaction completed! You're all set!`);
conv.ask(new OrderUpdate({
  type: 'SNAPSHOT',
  reason: 'Reason string',
  order: order,
}));
```
Java

```
ResponseBuilder responseBuilder = getResponseBuilder(request);
order.setLastUpdateTime(Instant.now().toString());

// Set reservation status to confirmed and provide confirmation code
LineItemV3 lineItem = order.getContents().getLineItems().get(0);
ReservationItemExtension reservationItemExtension = lineItem.getReservation();
reservationItemExtension.setStatus("CONFIRMED");
reservationItemExtension.setUserVisibleStatusLabel("Reservation confirmed.");
reservationItemExtension.setConfirmationCode("123ABCDEFGXYZ");
lineItem.setReservation(reservationItemExtension);
order.getContents().getLineItems().set(0, lineItem);

// Order update
OrderUpdateV3 orderUpdate = new OrderUpdateV3()
    .setType("SNAPSHOT")
    .setReason("Reason string")
    .setOrder(order);

responseBuilder
    .add("Transaction completed! You're all set! Would you like to do anything else?")
    .add(new StructuredResponse().setOrderUpdateV3(orderUpdate));
return responseBuilder.build();
```
Dialogflow JSON

Note that the JSON below describes a webhook response.

```
{
  "payload": {
    "google": {
      "expectUserResponse": true,
      "richResponse": {
        "items": [
          {
            "simpleResponse": {
              "textToSpeech": "Transaction completed! You're all set!"
            }
          },
          {
            "structuredResponse": {
              "orderUpdateV3": {
                "type": "SNAPSHOT",
                "reason": "Reason string",
                "order": {
                  "merchantOrderId": "UNIQUE_ORDER_ID",
                  "reservation": {
                    "status": "CONFIRMED",
                    "userVisibleStatusLabel": "Reservation confirmed",
                    "confirmationCode": "123ABCDEFGXYZ"
                  },
                  "lastUpdateTime": "2019-07-17T18:25:30.187Z"
                }
              }
            }
          }
        ]
      }
    }
  }
}
```
Actions SDK JSON

Note that the JSON below describes a webhook response.

```
{
  "expectUserResponse": true,
  "expectedInputs": [
    {
      "possibleIntents": [
        {
          "intent": "actions.intent.TEXT"
        }
      ],
      "inputPrompt": {
        "richInitialPrompt": {
          "items": [
            {
              "simpleResponse": {
                "textToSpeech": "Transaction completed! You're all set!"
              }
            },
            {
              "structuredResponse": {
                "orderUpdateV3": {
                  "type": "SNAPSHOT",
                  "reason": "Reason string",
                  "order": {
                    "merchantOrderId": "UNIQUE_ORDER_ID",
                    "reservation": {
                      "status": "CONFIRMED",
                      "userVisibleStatusLabel": "Reservation confirmed",
                      "confirmationCode": "123ABCDEFGXYZ"
                    },
                    "lastUpdateTime": "2019-07-17T18:25:30.059Z"
                  }
                }
              }
            }
          ]
        }
      }
    }
  ]
}
```

### 5. Send order updates

The reservation status changes over the course of
its lifetime. Send the user reservation order updates with HTTP
PATCH requests to the Orders API, containing the order status and details.

#### Set up asynchronous requests to the Orders API

Order update requests to the Orders API are authorized by an access
token. To PATCH an order update to the Orders API, download a JSON
service account key associated with your Actions Console project, then exchange
the service account key for a bearer token that can be passed into the
`Authorization` header of the HTTP request.

To retrieve your service account key, perform the following steps:

1. In the [Google Cloud console](https://console.cloud.google.com/), go to **Menu ☰ \> APIs \& Services \> Credentials \> Create credentials \> Service account key**.
2. Under **Service Account** , select **New Service Account**.
3. Set the service account to `service-account`.
4. Set **Role** to **Project \> Owner**.
5. Set key type to **JSON**.
6. Select **Create**.
7. A private JSON service account key will be downloaded to your local machine.

In your order updates code, exchange your service key for a bearer token
using the Google APIs client library and the *"https://www.googleapis.com/auth/actions.order.developer"* scope. You can find installation steps and
examples on the API client library [GitHub page](https://github.com/google/google-api-nodejs-client#using-jwt-service-tokens).

Reference `order-update.js` in our [Node.js](https://github.com/actions-on-google/dialogflow-transactions-nodejs) and [Java](https://github.com/actions-on-google/dialogflow-transactions-java) samples for
an example key exchange.

#### Send order updates

Once you've exchanged your service account key for an OAuth bearer token, send
order updates as authorized PATCH requests to the Orders API.

**Orders API URL:**
`
PATCH https://actions.googleapis.com/v3/orders/${orderId}
`

Provide the following headers in your request:

- `"Authorization: Bearer token"` with the OAuth bearer token you exchanged your service account key for.
- `"Content-Type: application/json"`.

The PATCH request should take a JSON body of the following format:

```
{ "orderUpdate": OrderUpdate }
```

The [`OrderUpdate`](https://developers.google.com/assistant/df-asdk/transactions/reference/physical/rest/v3/orders/patch.md.txt#orderupdate)
object consists of the following top-level fields:

- `updateMask` - The fields of the order that you're updating. To update the reservation status, set the value to `reservation.status, reservation.userVisibleStatusLabel`.
- `order` - The contents of the update. If you're updating the
  contents of the reservation, set the value to the updated `Order` object.
  If you're just updating the status of the reservation (for example, from
  `"PENDING"` to `"FULFILLED"`), the object contains the
  following fields:

  - `merchantOrderId` - The same ID you set in your `Order` object.
  - `lastUpdateTime` - The timestamp of this update.
  - `purchase` - An object containing the following:
    - `status` - The status of the order as a [`ReservationStatus`](https://developers.google.com/assistant/df-asdk/transactions/reference/physical/rest/v3/Order.md.txt#reservationstatus), such as "`CONFIRMED`" or "`CANCELLED`".
    - `userVisibleStatusLabel` - A user-facing label providing details on the order status, such as "Your reservation is confirmed".
- `userNotification` *(optional)* - A [`userNotification`](https://developers.google.com/assistant/transactions/reference/physical/rest/v3/orders/patch.md.txt#usernotification)
  object that can display on the user's device when this update is sent. Note
  that including this object doesn't guarantee that a notification appears on
  the user's device.

The following sample code shows an example `OrderUpdate` that updates the
status of the reservation order to `FULFILLED`:
Node.js

```
// Import the 'googleapis' module for authorizing the request.
const {google} = require('googleapis');
// Import the 'request' module for sending an HTTP POST request.
const request = require('request');
// Import the OrderUpdate class from the Actions on Google client library.
const {OrderUpdate} = require('actions-on-google');
// Import the service account key used to authorize the request. Replace the string path with a path to your service account key.
const key = require('./service-account.json');
// Create a new JWT client for the Actions API using credentials from the service account key.
let jwtClient = new google.auth.JWT(
    key.client_email,
    null,
    key.private_key,
    ['https://www.googleapis.com/auth/actions.order.developer'],
    null
);
// Authorize the client asynchronously, passing in a callback to run upon authorization.
jwtClient.authorize((err, tokens) => {
    if (err) {
        console.log(err);
        return;
    }
    // Declare the ID of the order to update.
    const orderId = '<UNIQUE_MERCHANT_ORDER_ID>';

    const orderUpdateJson = new OrderUpdate({
        updateMask: [
          'lastUpdateTime',
          'contents.lineItems.reservation.status',
          'contents.lineItems.reservation.userVisibleStatusLabel',
      ].join(','),
        order: {
          merchantOrderId: orderId,
          lastUpdateTime: new Date().toISOString(),
          contents: {
            lineItems: [
              {
                reservation: {
                  status: 'FULFILLED',
                  userVisibleStatusLabel: 'Reservation fulfilled',
                },
              }
            ]
          }
        },
        reason: 'Reservation status was updated to fulfilled.',
    });

    // Set up the PATCH request header and body, including the authorized token
    // and order update.
    const bearer = 'Bearer ' + tokens.access_token;
    const options = {
        method: 'PATCH',
        url: `https://actions.googleapis.com/v3/orders/${orderId}`,
        headers: {
          'Authorization': bearer,
        },
        body: {
          header: {
            'isInSandbox': true,
          },
          orderUpdate: orderUpdateJson,
        },
        json: true,
      };
    // Send the PATCH request to the Orders API.
    request.patch(options, (err, httpResponse, body) => {
        if (err) {
            console.log('There was an error...');
            console.log(err);
            return;
        }
    });
});
```
Java

```
// Create order update
FieldMask fieldMask = FieldMask.newBuilder().addAllPaths(Arrays.asList(
    "lastUpdateTime",
    "contents.lineItems.reservation.status",
    "contents.lineItems.reservation.userVisibleStatusLabel"))
    .build();

OrderUpdateV3 orderUpdate = new OrderUpdateV3()
    .setOrder(new OrderV3()
        .setMerchantOrderId(orderId)
        .setLastUpdateTime(Instant.now().toString())
        .setContents(new OrderContents()
        .setLineItems(Collections.singletonList(new LineItemV3()
            .setReservation(new ReservationItemExtension()
                .setStatus("FULFILLED")
                .setUserVisibleStatusLabel("Reservation fulfilled."))))))
    .setUpdateMask(FieldMaskUtil.toString(fieldMask))
    .setReason("Reservation status was updated to fulfilled.");

// Setup JSON body containing order update
JsonParser parser = new JsonParser();
JsonObject orderUpdateJson =
    parser.parse(new Gson().toJson(orderUpdate)).getAsJsonObject();
JsonObject body = new JsonObject();
body.add("orderUpdate", orderUpdateJson);
JsonObject header = new JsonObject();
header.addProperty("isInSandbox", true);
body.add("header", header);
StringEntity entity = new StringEntity(body.toString());
entity.setContentType(ContentType.APPLICATION_JSON.getMimeType());
request.setEntity(entity);

// Make request
HttpClient httpClient = HttpClientBuilder.create().build();
HttpResponse response = httpClient.execute(request);
```

##### Set the reservation status

An order update's `ReservationStatus`
must be descriptive of the current state of the order. In your update's `order.ReservationStatus`
field, use one of the following values:

- `PENDING` - The reservation has been "created" by your Action but requires additional processing on your back-end.
- `CONFIRMED` - The reservation is confirmed in your scheduling back-end.
- `CANCELLED` - The user cancelled their reservation.
- `FULFILLED` - The user's reservation was fulfilled by the service.
- `CHANGE_REQUESTED` - The user requested a change to the reservation, and the change is being processed.
- `REJECTED` - If you were unable to process or otherwise confirm the reservation.

Send order updates for each status that is relevant to your
reservation. For example, if your reservation requires manual processing to
confirm the reservation after it's requested, send a `PENDING` order update until
that additional processing is done. Not every reservation requires every status value.

> [!NOTE]
> **Note:** At minimum, every reservation requires a `CONFIRMED` order update.

## Troubleshooting

If you run into any issues during testing, read our [troubleshooting steps](https://developers.google.com/assistant/df-asdk/transactions/physical/troubleshooting.md.txt)
for transactions.

> [!WARNING]
> **Warning:** Don't use the Actions Console simulator to test an Action with transactions. Use an Android or iOS device with the Assistant to test your Action.