Build physical transactions with Google Pay

This guide walks you through the process of developing an Actions project that incorporates transactions for physical goods and uses Google Pay for payment.

Transaction flow

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

  1. Gather information (optional) - Depending on the nature of your transaction, you may want to gather the following information from the user at the start of the conversation:
    1. Validate transaction requirements - At the start 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.
  2. Build the order - Walk the user through a "cart assembly" where they pick which items they want to purchase.
  3. 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.
  4. 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.
  5. 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.

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 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
Russia
Singapore
Thailand
Turkey
United Kingdom
United States

Build your project

For extensive examples of transactional conversations, view the Node.js transactions sample.

Setup

When creating your Action, you must specify that you want to perform transactions in the Actions console.

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

1. Gather information (optional)

1a. 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.

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.

  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.

Request a delivery address

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.

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.

  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.

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 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 Design guidelines.

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.

Refer to the 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 will include tokenization parameters that change depending on which Google Pay processor you plan on using (such as Stripe, Braintree, ACI, etc).

const paymentParamenters = {
      'googlePaymentOption': {
        // facilitationSpec is expected to be a serialized JSON string
        'facilitationSpec': JSON.stringify({
          'apiVersion': 2,
          'apiVersionMinor': 0,
          'merchantInfo': {
            'merchantName': 'Example Merchant',
          },
          'allowedPaymentMethods': [
            {
              'type': 'CARD',
              'parameters': {
                'allowedAuthMethods': ['PAN_ONLY', 'CRYPTOGRAM_3DS'],
                'allowedCardNetworks': [
                  'AMEX', 'DISCOVER', 'JCB', 'MASTERCARD', 'VISA'],
              },
              'tokenizationSpecification': {
                'type': 'PAYMENT_GATEWAY',
                'parameters': {
                  'gateway': 'example',
                  'gatewayMerchantId': 'exampleGatewayMerchantId',
                },
              },
            },
          ],
          'transactionInfo': {
            'totalPriceStatus': 'FINAL',
            'totalPrice': '15.77',
            'currencyCode': 'USD',
          },
        }),
      },
    };

The contents of the tokenizationSpecification object will be different for each payment gateway. The following table shows the parameters used by each gateway:

EXAMPLE
"parameters": {
  "gateway": "example",
  "gatewayMerchantId": "exampleGatewayMerchantId"
}
ACI
"parameters": {
  "gateway": "aciworldwide",
  "gatewayMerchantId": "YOUR_ENTITY_ID"
}
ADYEN
"parameters": {
  "gateway": "adyen",
  "gatewayMerchantId": "YOUR_MERCHANT_ACCOUNT_NAME"
}
ALFA-BANK
"parameters": {
  "gateway": "alfabank",
  "gatewayMerchantId": "YOUR_GATEWAY_MERCHANT_ID"
}
BLUE_MEDIA
"parameters": {
  "gateway": "bluemedia",
  "gatewayMerchantId": "YOUR_GATEWAY_MERCHANT_ID"
}
BLUESNAP
"parameters": {
  "gateway": "bluesnap",
  "gatewayMerchantId": "YOUR_GATEWAY_MERCHANT_ID"
}
BRAINTREE
"parameters": {
  "gateway": "braintree",
  "braintree:apiVersion": "v1",
  "braintree:sdkVersion": braintree.client.VERSION,
  "braintree:merchantId": "YOUR_BRAINTREE_MERCHANT_ID",
  "braintree:clientKey": "YOUR_BRAINTREE_TOKENIZATION_KEY"
}
CHASE_PAYMENTECH
"parameters": {
  "gateway": "chase",
  "gatewayMerchantId": "YOUR_GATEWAY_MERCHANT_ACCOUNT_NUMBER"
}
CHECKOUT
"parameters": {
  "gateway": "checkoutltd",
  "gatewayMerchantId": "YOUR_PUBLIC_KEY"
}
CLOUDPAYMENTS
"parameters": {
  "gateway": "cloudpayments",
  "gatewayMerchantId": "YOUR_GATEWAY_MERCHANT_ID"
}
CYBERSOURCE
"parameters": {
  "gateway": "cybersource",
  "gatewayMerchantId": "YOUR_MERCHANT_ID"
}
DATATRANS
"parameters": {
  "gateway": "datatrans",
  "gatewayMerchantId": "YOUR_MERCHANT_ID"
}
EBANX
"parameters": {
  "gateway": "ebanx",
  "gatewayMerchantId": "YOUR_PUBLIC_INTEGRATION_KEY"
}
FIRST_DATA
"parameters": {
  "gateway": "firstdata",
  "gatewayMerchantId": "YOUR_MERCHANT_ID"
}
GLOBAL_PAYMENTS
"parameters": {
  "gateway": "globalpayments",
  "gatewayMerchantId": "YOUR_MERCHANT_ID"
}
GOPAY
"parameters": {
  "gateway": "gopay",
  "gatewayMerchantId": "YOUR_GATEWAY_MERCHANT_ID"
}
HITRUST
"parameters": {
  "gateway": "hitrustpay",
  "gatewayMerchantId": "YOUR_GATEWAY_MERCHANT_ID"
}
IMSOLUTIONS
"parameters": {
  "gateway": "imsolutions",
  "gatewayMerchantId": "YOUR_MERCHANT_ID"
}
LYRA
"parameters": {
  "gateway": "lyra",
  "gatewayMerchantId": "YOUR_GATEWAY_MERCHANT_ID"
}
MPGS
"parameters": {
  "gateway": "mpgs",
  "gatewayMerchantId": "YOUR_GATEWAY_MERCHANT_ID"
}
MONEY_MAIL_RU
"parameters": {
  "gateway": "moneymailru",
  "gatewayMerchantId": "YOUR_GATEWAY_MERCHANT_ID"
}
NEWEBPAY
"parameters": {
  "gateway": "newebpay",
  "gatewayMerchantId": "YOUR_GATEWAY_MERCHANT_ID"
}
NEXI
"parameters": {
  "gateway": "nexi",
  "gatewayMerchantId": "YOUR_GATEWAY_MERCHANT_ID"
}
NMI
"parameters": {
  "gateway": "creditcall",
  "gatewayMerchantId": "YOUR_GATEWAY_MERCHANT_ID"
}
PAYSAFE
"parameters": {
  "gateway": "paysafe",
  "gatewayMerchantId": "YOUR_GATEWAY_MERCHANT_ID"
}
PAYTURE
"parameters": {
  "gateway": "payture",
  "gatewayMerchantId": "YOUR_GATEWAY_MERCHANT_ID"
}
PAYU
"parameters": {
  "gateway": "payu",
  "gatewayMerchantId": "YOUR_GATEWAY_MERCHANT_ID"
}
PRZELEWY24
"parameters": {
  "gateway": "przelewy24",
  "gatewayMerchantId": "YOUR_MERCHANT_ID"
}
RBKMONEY
"parameters": {
  "gateway": "rbkmoney",
  "gatewayMerchantId": "YOUR_MERCHANT_ID"
}
SBERBANK
"parameters": {
  "gateway": "sberbank",
  "gatewayMerchantId": "YOUR_ORGANIZATION_NAME"
}
SQUARE
"parameters": {
  "gateway": "square",
  "gatewayMerchantId": "YOUR_GATEWAY_MERCHANT_ID"
}
STRIPE
"parameters": {
  "gateway": "stripe",
  "stripe:version": "2018-10-31",
  "stripe:publishableKey": "YOUR_PUBLIC_STRIPE_KEY"
}
TAPPAY
"parameters": {
  "gateway": "tappay",
  "gatewayMerchantId": "YOUR_GATEWAY_MERCHANT_ID"
}
TINKOFF
"parameters": {
  "gateway": "tinkoff",
  "gatewayMerchantId": "YOUR_GATEWAY_MERCHANT_ID"
}
UNITELLER
"parameters": {
  "gateway": "uniteller",
  "gatewayMerchantId": "YOUR_GATEWAY_MERCHANT_ID"
}
VANTIV
"parameters": {
  "gateway": "vantiv",
  "vantiv:merchantPayPageId": "YOUR_PAY_PAGE_ID",
  "vantiv:merchantOrderId": "YOUR_ORDER_ID",
  "vantiv:merchantTransactionId": "YOUR_TRANSACTION_ID",
  "vantiv:merchantReportGroup": "*web"
}
WORLDPAY
"parameters": {
  "gateway": "worldpay",
  "gatewayMerchantId": "YOUR_WORLDPAY_MERCHANT_ID"
}
YANDEX
"parameters": {
  "gateway": "yandexcheckout",
  "gatewayMerchantId": "YOUR_SHOP_ID"
}

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
};

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.

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.

  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.

  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.

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.

  • You must provide, at a minimum, a VIEW_DETAILS 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.

  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, 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.

You can also reference order-update.js in our Node.js sample 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 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, 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 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 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.

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 section.

Troubleshooting

If you run into any issues during testing, you should read our troubleshooting steps for transactions.