This guide walks you through the process of developing an Actions project
that incorporates transactions for physical goods using payment methods managed
by your site.

## Transaction flow

When your Actions project handles physical transactions using merchant-managed payments, it
uses the following flow:

1. **Link user's account** - In order for the user to use a payment method they've saved with your service, use Account Linking to associate their Google account with their account on your service.
2. **Gather information (optional)** - Depending on the nature of your transaction, you may want to gather the following information from the user early in the conversation:
   1. **Validate transaction requirements** - At the start of the transaction section of the conversation, validate that the user meets the requirements for making a transaction, such as having payment information properly configured and available before building their cart.
   2. **Request a delivery address** - If the transaction requires a delivery address, gather one from the user.
3. **Build the order** - Walk the user through a "cart assembly" where they pick which items they want to purchase.
4. **Propose the order** - Once the cart is complete, propose the order to the user so they can confirm it's correct. If the order is confirmed, you'll receive a response with order details and a payment token.
5. **Finalize the order and send a receipt** - With the order confirmed, update your inventory tracking or other fulfillment services and then send a receipt to the user.
6. **Send order updates** - Over the course of the order fulfillment's lifespan, give the user order updates by sending PATCH requests to the Orders API.

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

## Restrictions and review guidelines

Keep in mind that additional policies apply to Actions with transactions. 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#transactions)
before submitting your Action for review.

You can only deploy Actions that sell physical goods 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 an example of a transactional conversation, view the Node.js [transactions
sample](https://github.com/actions-on-google/actions-builder-transactions-nodejs).

### Project setup

When creating your Action, you must specify that you want to perform transactions
in the [Actions console](https://console.actions.google.com).

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?".

   ![](https://developers.google.com/static/assistant/transactions/images/transactions-checkbox.png)

## Link user's account *(optional)*

When using your own payment method to charge the user, we recommend linking
their Google account with an account they have with your own service to retrieve,
present, and charge payment methods stored there.

### Design the voice user interface for the authentication flow

> [!WARNING]
> **Warning:** Don't give users a required account linking prompt at the start of the conversation with your Action. Instead, give unauthenticated users a guest flow to show how your Action works, and then only ask for account linking if it's necessary to proceed. If users are leaving the Action consistently because of account linking issues, Google will not actively promote the Action, which may decrease the Action's user traffic.
>
>
> [!NOTE]
> **Note:** You must use [Actions Builder](https://developers.google.com/assistant/conversational/build#actions_builder) to build an Action that implements account linking.

#### Check if the user is verified and start the account linking flow

1. Open your Actions Builder project in the [Actions Console](https://console.actions.google.com).
2. Create a new scene to start account linking in your Action:
   1. Click **Scenes**.
   2. Click the **add** (+) icon to add a new scene.
3. In the newly created scene, click the add icon for **Conditions**.
4. Add a condition that checks if the user associated with the conversation is a a verified user. If the check fails, your Action can't perform account linking during the conversation, and should fall back to providing access to functionality that doesn't require account linking.
   1. In the `Enter new expression` field under **Condition** , enter the following logic: `user.verificationStatus != "VERIFIED"`
   2. Under **Transition**, select a scene that doesn't require account linking or a scene that is the entry point to guest-only functionality.

![](https://developers.google.com/static/assistant/identity/images/ab_vui_verified_condition.png)

1. Click the add icon for **Conditions**.
2. Add a condition to trigger an account linking flow if the user doesn't have an associated identity.
   1. In the `Enter new expression` field under **Condition** , enter the following logic:: `user.verificationStatus == "VERIFIED"`
   2. Under **Transition** , select the **Account Linking** system scene.
   3. Click **Save**.

![](https://developers.google.com/static/assistant/identity/images/ab_vui_al_transition.png)

After saving, a new account linking system scene called `<SceneName>_AccountLinking`
is added to your project.

#### Customize the account linking scene

1. Under **Scenes**, select the account linking system scene.
2. Click **Send prompt** and add a short sentence to describe to the user why the Action needs to access their identity (for example "To save your preferences").
3. Click **Save**.

![](https://developers.google.com/static/assistant/identity/images/ab_vui_context.png)

1. Under **Conditions** , click **If user successfully completes account linking**.
2. Configure how the flow should proceed if the user agrees to link their account. For example, call the webhook to process any custom business logic required and transition back to the originating scene.
3. Click **Save**.

![](https://developers.google.com/static/assistant/identity/images/ab_vui_user_yes.png)

1. Under **Conditions** , click **If user cancels or dismisses account linking**.
2. Configure how the flow should proceed if the user doesn't agree to link their account. For example, send an acknowledging message and redirect to scenes that provide functionality that doesn't require account linking.
3. Click **Save**.

![](https://developers.google.com/static/assistant/identity/images/ab_vui_user_no.png)

1. Under **Conditions** , click **If system or network error occurs**.
2. Configure how the flow should proceed if the account linking flow can't be completed because of system or network errors. For example, send an acknowledging message and redirect to scenes that provide functionality that doesn't require account linking.
3. Click **Save**.

### Gather information *(optional)*

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

As soon as the user has indicated they wish to make a purchase,
you should check to ensure that they will be able to perform a transaction.
For example, when invoked,your Action might ask, *"Would you like to order shoes,
or check your account balance?"* If the user says "order shoes", you should ensure that they
can proceed and give them an opportunity to fix any settings preventing them
from continuing with the transaction. To do so, you should transition to a
scene that performs a transaction requirements check.

##### Create Transaction Requirements Check scene

1. From the Scenes tab, add a new Scene with the name `TransactionRequirementsCheck`.
2. Under **Slot filling** , click **+** to add a new slot.
3. Under **Select type** , select `actions.type.TransactionRequirementsCheckResult` as the slot type.
4. In the slot name field, give the slot the name `TransactionRequirementsCheck`.
5. Enable the **Customize slot value writeback** checkbox (enabled by default).
6. Click **Save**.

   ![](https://developers.google.com/static/assistant/transactions/images/transaction-requirements-check-1.png)

A transaction requirements check will result in one of the following outcomes:

- If the requirements are met, the session parameter is set with a success condition and you can proceed with building the user's order.
- If one or more of the requirements cannot be met, the session parameter is set with a failure condition. In this case, you should pivot the conversation away from the transactional experience, or end the conversation.
  - If any errors resulting in the failure state can be fixed by the user, they will be prompted to resolve those issues on their device. If the conversation is taking place on a voice-only surface, a handoff will be initiated to the user's phone.

#### Handle Transaction Requirements Check result

1. From the **Scenes** tab, select your newly created `TransactionRequirementsCheck` scene.
2. Under **Condition** , click **+** to add a new condition.
3. In the text field, enter the following condition syntax to check for the
   success condition:

       scene.slots.status == "FINAL" && session.params.TransactionRequirementsCheck.resultType == "CAN_TRANSACT"

4. Hover your cursor over the condition you just added and click the up arrow
   to place it before `if scene.slots.status == "FINAL"`.

5. Enable **Send prompts** and provide a simple prompt letting the user know
   they are ready to make a transaction:

       candidates:
         - first_simple:
             variants:
               - speech: >-
                   You are ready to purchase physical goods.

6. Under **Transition** select another scene, allowing the user to continue the
   conversation and proceed with making a transaction.
   ![](https://developers.google.com/static/assistant/transactions/images/transaction-requirement-check-2.png)

7. Select the condition **`else if scene.slots.status == "FINAL"`**.

8. Enable **Send prompts** and provide a simple prompt letting the user know
   they are unable to make a transaction:

       candidates:
         - first_simple:
             variants:
               - speech: Transaction requirements check failed.

9. Under **Transition** , select **End conversation** to end the conversation if
   a user is unable to make transactions.

   ![](https://developers.google.com/static/assistant/transactions/images/transaction-requirement-check-3.png)

### Request a delivery address *(optional)*

If your transaction requires a user's delivery address, you should request it
from the user. This might be useful for determining the total price,
delivery/pickup location, or for ensuring the user is within your service region.
To do so, you should transition to a scene that prompts the user for their
delivery address.

### Create Delivery Address Scene

1. From the **Scenes** tab, add a new scene with the name `DeliveryAddress`.
2. Under **Slot filling** , click **+** to add a new slot.
3. Under **Select type** , select `actions.type.DeliveryAddressValue` as the slot type.
4. In the slot name field, give the slot the name `TransactionDeliveryAddress`.
5. Enable the **Customize slot value writeback** checkbox (enabled by default).
6. Click **Save** . ![](https://developers.google.com/static/assistant/transactions/images/delivery-address-1.png)

When configuring the slot, you can provide a `reason` that allows you to
preface the Assistant's request to obtain an address with a string.The default
reason string is "to know where to send the order". Therefore, the Assistant
might ask the user: *"To know where to send the order, I'll need to get your delivery address".*

- On surfaces with a screen, the user will choose which address they want to use for the transaction. If they haven't previously given an address, they'll be able to enter a new address.
- On voice-only surfaces, the Assistant will ask the user for permission to share their default address for the transaction. If they haven't previously given an address, the conversation will be handed off to a phone for entry.

To handle the Delivery Address result, follow these steps:

1. From the **Scenes** tab, select your newly created `DeliveryAddress` scene.
2. Under **Condition** , click **+** to add a new condition.
3. In the text field, enter the following condition syntax to check for the
   success condition:

       scene.slots.status == "FINAL" && session.params.TransactionDeliveryAddress.userDecision == "ACCEPTED"

4. Hover your cursor over the condition you just added and click the up arrow
   to place it before `if scene.slots.status == "FINAL"`.

5. Enable **Send prompts** and provide a simple prompt letting the user
   know you've received their address:

       candidates:
         - first_simple:
             variants:
               - speech: >-
                   Great! Your order will be delivered to
                   $session.params.TransactionDeliveryAddress.location.postalAddress.locality
                   $session.params.TransactionDeliveryAddress.location.postalAddress.administrativeArea
                   $session.params.TransactionDeliveryAddress.location.postalAddress.regionCode
                   $session.params.TransactionDeliveryAddress.location.postalAddress.postalCode

6. Under **Transition** , select another scene, allowing the user to continue
   the conversation.
   ![](https://developers.google.com/static/assistant/transactions/images/delivery-address-2.png)

7. Select the condition `else if scene.slots.status == "FINAL"`.

8. Enable **Send prompts** and provide a simple prompt letting the user know
   they are unable to make a transaction:

       candidates:
         - first_simple:
             variants:
               - speech: I failed to get your delivery address.

9. Under **Transition** , select **End conversation** to end the conversation
   if a user is unable to make transactions.

   ![](https://developers.google.com/static/assistant/transactions/images/delivery-address-3.png)

## Build the order

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

The most basic cart assembly experience has a user pick items from a list to add
to their order, though you can design the conversation to simplify the
user experience. You could build a cart assembly experience that enables the
user to re-order their most recent purchase via a simple yes or no question.
You could also present the user a carousel or list card of the top "featured" or
"recommended" items.

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

### Create an order

Throughout your conversation, you'll need to gather the items that a user wants
to purchase and then construct an `Order` object.

At minimum, your `Order` must contain the following:

- `buyerInfo` - Information about the user making the purchase.
- `transactionMerchant` - Information about the merchant that facilitated the order.
- `contents` - The actual contents of the order listed as `lineItems`.
- `priceAttributes` - Pricing details about the order, including the total cost of the order with discounts and taxes.

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

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

The sample code below shows a complete order, including optional fields:

    const order = {
      createTime: '2019-09-24T18:00:00.877Z',
      lastUpdateTime: '2019-09-24T18:00:00.877Z',
      merchantOrderId: orderId, // A unique ID String for the order
      userVisibleOrderId: orderId,
      transactionMerchant: {
        id: 'http://www.example.com',
        name: 'Example Merchant',
      },
      contents: {
        lineItems: [
          {
            id: 'LINE_ITEM_ID',
            name: 'Pizza',
            description: 'A four cheese pizza.',
            priceAttributes: [
              {
                type: 'REGULAR',
                name: 'Item Price',
                state: 'ACTUAL',
                amount: {
                  currencyCode: 'USD',
                  amountInMicros: 8990000,
                },
                taxIncluded: true,
              },
              {
                type: 'TOTAL',
                name: 'Total Price',
                state: 'ACTUAL',
                amount: {
                  currencyCode: 'USD',
                  amountInMicros: 9990000,
                },
                taxIncluded: true,
              },
            ],
            notes: [
              'Extra cheese.',
            ],
            purchase: {
              quantity: 1,
              unitMeasure: {
                measure: 1,
                unit: 'POUND',
              },
              itemOptions: [
                {
                  id: 'ITEM_OPTION_ID',
                  name: 'Pepperoni',
                  prices: [
                    {
                      type: 'REGULAR',
                      state: 'ACTUAL',
                      name: 'Item Price',
                      amount: {
                        currencyCode: 'USD',
                        amountInMicros: 1000000,
                      },
                      taxIncluded: true,
                    },
                    {
                      type: 'TOTAL',
                      name: 'Total Price',
                      state: 'ACTUAL',
                      amount: {
                        currencyCode: 'USD',
                        amountInMicros: 1000000,
                      },
                      taxIncluded: true,
                    },
                  ],
                  note: 'Extra pepperoni',
                  quantity: 1,
                  subOptions: [],
                },
              ],
            },
          },
        ],
      },
      buyerInfo: {
        email: 'janedoe@gmail.com',
        firstName: 'Jane',
        lastName: 'Doe',
        displayName: 'Jane Doe',
      },
      priceAttributes: [
        {
          type: 'SUBTOTAL',
          name: 'Subtotal',
          state: 'ESTIMATE',
          amount: {
            currencyCode: 'USD',
            amountInMicros: 9990000,
          },
          taxIncluded: true,
        },
        {
          type: 'DELIVERY',
          name: 'Delivery',
          state: 'ACTUAL',
          amount: {
            currencyCode: 'USD',
            amountInMicros: 2000000,
          },
          taxIncluded: true,
        },
        {
          type: 'TAX',
          name: 'Tax',
          state: 'ESTIMATE',
          amount: {
            currencyCode: 'USD',
            amountInMicros: 3780000,
          },
          taxIncluded: true,
        },
        {
          type: 'TOTAL',
          name: 'Total Price',
          state: 'ESTIMATE',
          amount: {
            currencyCode: 'USD',
            amountInMicros: 15770000,
          },
          taxIncluded: true,
        },
      ],
      followUpActions: [
        {
          type: 'VIEW_DETAILS',
          title: 'View details',
          openUrlAction: {
            url: 'http://example.com',
          },
        },
        {
          type: 'CALL',
          title: 'Call us',
          openUrlAction: {
            url: 'tel:+16501112222',
          },
        },
        {
          type: 'EMAIL',
          title: 'Email us',
          openUrlAction: {
            url: 'mailto:person@example.com',
          },
        },
      ],
      termsOfServiceUrl: 'http://www.example.com',
      note: 'Sale event',
      promotions: [
        {
          coupon: 'COUPON_CODE',
        },
      ],
      purchase: {
        status: 'CREATED',
        userVisibleStatusLabel: 'CREATED',
        type: 'FOOD',
        returnsInfo: {
          isReturnable: false,
          daysToReturn: 1,
          policyUrl: 'http://www.example.com',
        },
        fulfillmentInfo: {
          id: 'FULFILLMENT_SERVICE_ID',
          fulfillmentType: 'DELIVERY',
          expectedFulfillmentTime: {
            timeIso8601: '2019-09-25T18:00:00.877Z',
          },
          location: location,
          price: {
            type: 'REGULAR',
            name: 'Delivery Price',
            state: 'ACTUAL',
            amount: {
              currencyCode: 'USD',
              amountInMicros: 2000000,
            },
            taxIncluded: true,
          },
          fulfillmentContact: {
            email: 'johnjohnson@gmail.com',
            firstName: 'John',
            lastName: 'Johnson',
            displayName: 'John Johnson',
          },
        },
        purchaseLocationType: 'ONLINE_PURCHASE',
      },
    };

### Create order and presentation options

Before the user confirms their order, they will be presented with a proposed
order card. You can customize the way this card is presented to the user by
setting various order and presentation options.

Below are order and presentation options for placing an order that requires a
delivery address, including the email of the user in the order confirmation card:

    const orderOptions = {
          'requestDeliveryAddress': true,
          'userInfoOptions': {
            'userInfoProperties': ['EMAIL']
          }
        };

    const presentationOptions = {
          'actionDisplayName': 'PLACE_ORDER'
        };

### Create payment parameters

Your `paymentParameters` object includes a `merchantPaymentOption` with fields
that describe the user's payment method for their order. Below is an example
for payment parameters, using a Visa credit card:

    const paymentParamenters = {
          'merchantPaymentOption': {
            'defaultMerchantPaymentMethodId': '12345678',
            'managePaymentMethodUrl': 'https://example.com/managePayment',
            'merchantPaymentMethod': [{
              'paymentMethodDisplayInfo': {
                'paymentMethodDisplayName': 'VISA **** 1234',
                'paymentType': 'PAYMENT_CARD'
              },
              'paymentMethodGroup': 'Payment method group',
              'paymentMethodId': '12345678',
              'paymentMethodStatus': {
                'status': 'STATUS_OK',
                'statusMessage': 'Status message'
              }
            }]
          }
        };

### Save order data in session parameter

From your fulfillment, save the order data to a `session` parameter.
The order object will be used across scenes for the same session.

    conv.session.params.order = {
        '@type': 'type.googleapis.com/google.actions.transactions.v3.TransactionDecisionValueSpec',
        order: order,
        orderOptions: orderOptions,
        presentationOptions: presentationOptions,
        paymentParameters: paymentParameters
    };

> [!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 provide in the order.

## Propose the order

Once you've built an order, you must present it to the user to confirm or reject.
To do so, you should transition to a scene that performs a transaction decision.

### Create Transaction Decision scene

1. From the **Scenes** tab, add a new scene with the name `TransactionDecision`.
2. Under **Slot filling** , click **+** to add a new slot.
3. Under **Select type** , select `actions.type.TransactionDecisionValue` as the slot type.
4. In the slot name field, give the slot the name `TransactionDecision`.
5. Enable the **Customize slot value writeback** checkbox (enabled by default).
6. Under **Configure slot** , select **Use session parameter** from the dropdown.
7. Under **Configure slot,** enter the name of the session parameter used to store the order into the text field (i.e. `$session.params.order`).
8. Click **Save** . ![](https://developers.google.com/static/assistant/transactions/images/propose-order-1.png)

In an attempt to fill a `TransactionDecisionValue` slot, the Assistant initiates
a built-in experience in which the `Order` you passed is rendered directly onto
a "cart preview card". The user can say "place order", decline the transaction,
change a payment option like the credit card or address, or request to change
the order's contents.

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.

#### Handle Transaction Decision result

When a `TransactionDecisionValue` slot is filled, the user's answer to the
transaction decision will be stored in a session parameter. This value contains
the following:

- `ORDER_ACCEPTED`,
- `ORDER_REJECTED`,
- `DELIVERY_ADDRESS_UPDATED`,
- `CART_CHANGE_REQUESTED`
- `USER_CANNOT_TRANSACT`.

To handle a transaction decision result:

1. From the **Scenes** tab, select your newly created `TransactionDecision` scene.
2. Under **Condition** , click **+** to add a new condition.
3. In the text field, enter the following condition syntax to check for the success condition:

       scene.slots.status == "FINAL" && session.params.TransactionDecision.transactionDecision == "ORDER_ACCEPTED"

4. Hover your cursor over the condition you just added and click the up arrow
   to place it before `if scene.slots.status == "FINAL"`.

5. Enable **Send prompts** and provide a simple prompt letting the user know
   their order is completed:

       candidates:
         - first_simple:
             variants:
               - speech: >-
                   Transaction completed! Your order
                   $session.params.TransactionDecision.order.merchantOrderId is all
                   set!

6. Under **Transition** , select **End conversation** to end the conversation.

   ![](https://developers.google.com/static/assistant/transactions/images/propose-order-2.png)
7. Under **Condition** , click **+** to add a new condition.

8. In the text field, enter the following condition syntax to check for the
   failure conditions:

         scene.slots.status == "FINAL" && session.params.TransactionDecision.transactionDecision == "ORDER_REJECTED"

9. Hover your cursor over the condition you just added and click the up arrow
   to place it before `if scene.slots.status == "FINAL"`.

10. Enable **Send prompts** and provide a simple prompt letting the user know
    the order has been rejected:

        candidates:
          - first_simple:
              variants:
                - speech: Look like you don't want to order anything. Goodbye.

11. Under **Transition** select **End conversation** to end the conversation.

    ![](https://developers.google.com/static/assistant/transactions/images/propose-order-3.png)
12. Select the condition `else if scene.slots.status == "FINAL"`.

13. Enable **Send prompts** and provide a simple prompt letting the user
    know they are unable to make a transaction:

        candidates:
          - first_simple:
              variants:
                - speech: >-
                    Transaction failed with status
                    $session.params.TransactionDecision.transactionDecision

14. Under **Transition** , select **End conversation** to end the conversation
    if a user is unable to make transactions.
    ![](https://developers.google.com/static/assistant/transactions/images/propose-order-4.png)

#### Finalize the order and send a receipt

When the `TransactionDecisionValue` slot returns a result of `ORDER_ACCEPTED`,
you must immediately perform whatever processing is required to "confirm" the
order (like persisting it in your own database and charging the user).

You can end the conversation with this response, but you must include a simple
response to keep the conversation moving. When you provide this initial
`orderUpdate`, the user will see a "collapsed receipt card" along with the rest
of your response. This card will mirror the receipt that the user finds in their
Order History.

During order confirmation, your order object can include a `userVisibleOrderId`,
which is the ID that the user sees for the order. You can reuse your
`merchantOrderId` for this field.

Part of the `OrderUpdate` object will need to contain a follow-up action object,
which manifest as URL buttons at the bottom of the order details that the user
can find in their Assistant Order History.

> [!NOTE]
> **Note:** Your Action's order updates must meet the following minimum requirements:

- You must provide, at a minimum, a [`VIEW_DETAILS`](https://developers.google.com/assistant/transactions/reference/physical/migrationv1v2/rest/old-types/ActionType) follow-up Action with each order. This should contain a deep-link to the representation of the order on your mobile app or website.
- You must also send a formal receipt over email that meets all legal requirements for conducting a transaction, in addition to the receipt card in your Action's conversation.

To send an initial order update:

1. From the **Scenes** tab, select your `TransactionDecision` scene.
2. Under **Condition** , select the condition that checks for the success result,
   `ORDER_ACCEPTED`:

         scene.slots.status == "FINAL" && session.params.TransactionDecision.transactionDecision == "ORDER_ACCEPTED"

3. For this condition, enable **Call your webhook,** and provide a intent
   handler name, such as `update_order`.

   ![](https://developers.google.com/static/assistant/transactions/images/propose-order-5.png)
4. In your webhook code, add an intent handler for sending an initial order
   update:

       app.handle('update_order', conv => {
         const currentTime = new Date().toISOString();
         let order = conv.session.params.TransactionDecision.order;
         conv.add(new OrderUpdate({
           'updateMask': {
             'paths': [
               'purchase.status',
               'purchase.user_visible_status_label'
             ]
           },
           'order': {
             'merchantOrderId': order.merchantOrderId,
             'lastUpdateTime': currentTime,
             'purchase': {
               'status': 'CONFIRMED',
               'userVisibleStatusLabel': 'Order confirmed'
             },
           },
           'reason': 'Reason string
         }));
       });

## Send order updates

You'll need to keep the user informed about the order's status over the course of
its lifetime. Send the user order updates by sending HTTP
PATCH requests to the Orders API with 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, you can 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).

You can also reference `order-update.js` in our
[Node.js sample](https://github.com/actions-on-google/actions-builder-transactions-nodejs)
for an example key exchange.

#### Send order updates

Once you've exchanged your service account key for an OAuth bearer token, you
can 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/transactions/reference/physical/rest/v3/orders/patch#orderupdate)
object consists of the following top-level fields:

- `updateMask` - The fields of the order that you're updating. To update the order status, set the value to `purchase.status, purchase.userVisibleStatusLabel`.
- `order` - The contents of the update. If you're updating the
  contents of the order, set the value to the updated `Order` object.
  If you're updating the status of the order (for example, from
  `"CONFIRMED"` to `"SHIPPED"`), 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 [`PurchaseStatus`](https://developers.google.com/assistant/transactions/reference/physical/rest/v3/Order#purchasestatus), such as "`SHIPPED`" or "`DELIVERED`".
    - `userVisibleStatusLabel` - A user-facing label providing details on the order status, such as "Your order has been shipped and is on the way".
- `userNotification` *(optional)* - A [`userNotification`](https://developers.google.com/assistant/transactions/reference/physical/rest/v3/orders/patch#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 order to `DELIVERED`:

    // Import the 'googleapis' module for authorizing the request.
    const {google} = require('googleapis');
    // Import the 'request-promise' module for sending an HTTP POST request.
    const request = require('request-promise');
    // Import the OrderUpdate class from the client library.
    const {OrderUpdate} = require('@assistant/conversation');

    // Import the service account key used to authorize the request.
    // Replacing the string path with a path to your service account key.
    // i.e. const serviceAccountKey = 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(
        serviceAccountKey.client_email,
        null,
        serviceAccountKey.private_key,
        ['https://www.googleapis.com/auth/actions.order.developer'],
        null,
    );

    // Authorize the client
    let tokens = await jwtClient.authorize();

    // Declare order update
    const orderUpdate = new OrderUpdate({
        updateMask: {
          paths: [
            'purchase.status',
            'purchase.user_visible_status_label'
          ]
        },
        order: {
          merchantOrderId: orderId, // Specify the ID of the order to update
          lastUpdateTime: new Date().toISOString(),
          purchase: {
            status: 'DELIVERED',
            userVisibleStatusLabel: 'Order delivered',
          },
        },
        reason: 'Order status updated to delivered.',
    });

    // Set up the PATCH request header and body,
    // including the authorized token and order update.
    let options = {
      method: 'PATCH',
      uri: `https://actions.googleapis.com/v3/orders/${orderId}`,
      auth: {
        bearer: tokens.access_token,
      },
      body: {
        header: {
          isInSandbox: true,
        },
        orderUpdate,
      },
      json: true,
    };

    // Send the PATCH request to the Orders API.
    try {
      await request(options);
    } catch (e) {
      console.log(`Error: ${e}`);
    }

##### Set the purchase status

An order update's [`status`](https://developers.google.com/assistant/transactions/reference/physical/rest/v3/Order#purchasestatus)
must be descriptive of the current state of the order. In your update's `order.purchase.status`
field, use one of the following values:

- `CREATED` - Order is accepted by the user and "created" from the perspective of your Action but requires manual processing on your back-end.
- `CONFIRMED` - Order is active and being processed for fulfillment.
- `IN_PREPARATION` - Order is being prepared for shipment/delivery, such as food being cooked or an item being packaged.
- `READY_FOR_PICKUP` - Order is available for pick-up by the recipient.
- `DELIVERED` - Order has been delivered to the recipient
- `OUT_OF_STOCK` - One or more items in the order is out-of-stock.
- `CHANGE_REQUESTED` - User requested a change to the order, and the change is being processed.
- `RETURNED` - Order has been returned by the user after delivery.
- `REJECTED` - If you were unable to process, charge, or otherwise "activate" the order.
- `CANCELLED` - Order was cancelled by the user.

You should send order updates for each status that is relevant to your
transaction. For example, if your transaction requires manual processing to
log the order after it's placed, send a `CREATED` order update until
that additional processing is done. Not every order requires every status value.

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

## Test your project

When testing your project, you can enable the sandbox mode in the Actions console
to test your Action without charging a payment method.
To enable the sandbox mode, follow these steps:

1. In the Actions console, click **Test** in the navigation.
2. Click **Settings**.
3. Enable the **Development Sandbox** option.

For physical transactions, you can also set the field `isInSandbox` to `true` in
your sample. This action is equivalent to enabling the sandbox mode setting in
the Actions console. To see a code snippet that uses `isInSandbox`, see the
[Send order updates](https://developers.google.com/assistant/transactions/physical/dev-guide-physical-custom#send_order_updates) section.

## Troubleshooting

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

> [!WARNING]
> **Warning:** The Actions Web Simulator should not be used to test an Action with transactions. Please use an Assistant-enabled Android or iOS device to test your Action.