Join the Actions on Google Developer Challenge to win a trip to Google I/O 2018 and more than 20 other prizes.

Adding Transactions to your App

This document describes how to use the transactions features of Actions on Google, which lets you build orders and charge them to payment accounts linked with a user's Google account or with your service's account.

When building transactions into your app, you can request the following intents to be fulfilled throughout your conversation to help you complete the transaction:

  • actions.intent.TRANSACTION_REQUIREMENTS_CHECK - Ensures that a user can actually participate in transactions. This is useful for checking that a user doesn't go through creating an order and later finding out they can't transact. This is a silent intent, meaning it doesn't have a conversation with the user when triggered.
  • actions.intent.DELIVERY_ADDRESS - Obtains the user's delivery address. This is useful for things like presenting nearby locations at the beginning of a conversation or for obtaining an address towards the end of the transaction process.
  • actions.intent.TRANSACTION_DECISION - Confirms the transaction with the user. This intent silently triggers actions.intent.TRANSACTION_REQUIREMENTS_CHECK to ensure the user can participate in transactions. Requesting this intent is required to complete a transaction.
  • actions.intent.SIGN_IN - Asks the user to sign into their account with your service and link it to their Google account. Users can even sign up for a new account with your service and link it to their existing account with this flow. This is useful for asking users to sign up for an account on your service after a succesful transaction or linking their existing account before a transaction occurs to obtain payment methods.

The following sections show how to use these intents with the Node.js client library (highly recommended) or by constructing JSON payloads for the conversation webhook.

Checking for transaction requirements

To ensure that a user meets transaction requirements, request fulfillment of the actions.intent.TRANSACTION_REQUIREMENTS_CHECK intent with a TransactionRequirementsCheckSpec data object, which defines the following properties:

  • deliveryAddressRequired - True if a delivery address is required for this transaction. If the user doesn't have a delivery address set, the Google Assistant obtains one for you.
  • type - For non-Google payment instruments. Specify one of the types in action.Transactions.PaymentType
  • displayName - For non-Google payment instruments. This name is used for the account displayed on receipt. For example, for card payment, it could be VISA-1234.
  • tokenizationParameters - For Google-provided payment instruments.
  • cardNetworks - For Google-provided payment instruments. A set of accepted card networks. Must be any number of networks from action.Transactions.CardNetwork
  • prepaidCardDisallowed - For Google-provided payment instruments. True if prepaid cards are not allowed for this transaction

Checking requirements for a Google-linked payment option

You can check to see if a user satifies transactions requirements for a payment account link with a Google account in the following way.

API.AI Node.js
function checkforTransactionRequirements(app) {
  app.checkForTransactionRequirements({
    type: action.Transactions.PaymentType.PAYMENT_CARD,
    prepaidCardDisallowed: 'true',
    supportedCardNetworks: 'VISA'
  });
}
    
API.AI JSON
{
  "google": {
    "system_intent": {
      "intent": "actions.intent.TRANSACTION_REQUIREMENTS_CHECK",
      "data": {
        "@type": "type.googleapis.com/google.actions.v2.TransactionRequirementsCheckSpec",
        "paymentOptions": {
          "googleProvidedOptions": {
            "prepaidCardDisallowed": true,
            "supportedCardNetworks": [
              "VISA"
            ]
          }
        },
        "orderOptions": {
          "requestDeliveryAddress": true
        }
      }
    }
  }
}

Checking requirements for a developer-provided payment option

You can check to see if a user satifies transactions requirements for a payment account linked with your service's account in the following way.

API.AI Node.js
function checkForTransactionRequirements(app) {
  app.checkForTransactionRequirements({
    type: app.Transactions.PaymentType.PAYMENT_CARD,
    displayName: 'VISA-1234',
    deliveryAddressRequired: false
  });
}
    
API.AI JSON
{
  "google": {
    "system_intent": {
      "intent": "actions.intent.TRANSACTION_REQUIREMENTS_CHECK",
      "data": {
        "@type": "type.googleapis.com/google.actions.v2.TransactionRequirementsCheckSpec",
        "payment_options": {
          "action_provided_options": {
            "payment_type": "PAYMENT_CARD",
            "display_name": "VISA-9876"
          }
        },
        "order_options": {
          "request_delivery_address": true
        }
      }
    }
  }
}

Receiving 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 an API.AI intent that's triggered by the actions_intent_TRANSACTIONS_REQUIREMENTS_CHECK event. When triggered, handle it in your fulfillment in the following way.

API.AI Node.js
function transactionCheckComplete (action) {
  if (action.userCanTransact()) {
    // continue conversation
  } else {
    action.tell('Transaction failed.');
  }
}
    
API.AI JSON
    what to check in JSON?
    

Obtaining a delivery address

If your transaction requires a user's delivery address, you can request fulfillment of the actions.intent.DELIVERY_ADDRESS intent. This might be useful for delivering nearby locations during your conversation or for obtaining the user's delivery address towards the end of your transaction flow.

When requesting this intent to be fulfilled, you pass in a reason option that allows you preface the Assistant's request to obtain an address with a string. For example, if you specify "to know where to send the order", the Assistant might ask the user:

"To know where to send the order, I'll need to get your delivery address"

Requesting the delivery address

API.AI Node.js
function requirementsCheckComplete (action) {
  if (action.userCanTransact()) {
    action.askForDeliveryAddress('To proceed with your order');
  } else {
    action.tell('Transaction failed.');
  }
}
API.AI JSON
{
  "google": {
    "system_intent": {
      "intent": "actions.intent.DELIVERY_ADDRESS",
      "data": {
        "@type": "type.googleapis.com/google.actions.v2.DeliveryAddressValueSpec",
        "addressOptions": {
          "reason": "To proceed with your order"
        }
      }
    }
  }
}
    

Receiving the delivery address

After the Assistant fulfills the intent, it sends your fulfillment a request with the actions.intent.DELIVERY_ADDRESS intent.

To properly handle this request, declare an API.AI intent that's triggered by the actions_intent_TRANSACTIONS_DELIVERY_ADDRESS event. When triggered, handle it in your fulfillment in the following way.

API.AI Node.js
if (app.getOrderDeliveryAddress()) {
      console.log('DELIVERY ADDRESS: '
        + app.getOrderDeliveryAddress().postalAddress.addressLines[0]);
}
API.AI JSON
   what to check?
    

Build an order

Throughout your conversation, you'll need to gather the items that a user wants to add to their order and then construct an order that consists of:

  • merchant - identifies you by ID and name.
  • lineItems - collection of items in the order cart
  • totalPrice - the price of all items in the cart
  • otherItems - tax and subtotal that can be displayed on the user's receipt when the order is completed.

See the [conversation webhook documentation] for more information.

Building an order

API.AI Node.js
app.buildOrder('1234')
    .setCart(action.buildCart().setMerchant('test_merchant', 'Test Merchant')
      .addItems(action.buildItem('ABC Book', 'book_abc').setPrice('USD', 3))
      .setNotes('Buying a book'))
    .setTotalPrice(action.Transactions.PriceType.ESTIMATE, 'USD', 3)
  
API.AI JSON
"proposedOrder": {
  "cart": {
    "merchant": {
      "id": "TXN_TESTER",
      "name": "Panera"
    },
    "lineItems": [
      {
        "id": "chipotle-chicken-avocado-melt",
        "name": "Chipotle Chicken Avocado Melt",
        "type": "REGULAR",
        "price": {
          "type": "ESTIMATE",
          "amount": {
            "currencyCode": "USD",
            "units": 8,
            "nanos": 990000000
          }
        },
        "quantity": 1
      },
      {
        "id": "iced-caffe-mocha-regular",
        "name": "Iced Caffe Mocha (regular)",
        "type": "REGULAR",
        "price": {
          "type": "ESTIMATE",
          "amount": {
            "currencyCode": "USD",
            "units": 4,
            "nanos": 290000000
          }
        },
        "quantity": 1,
        "subLines": [
          {
            "lineItem": {
              "id": "espresso-shot",
              "name": "Espresso shot",
              "type": "REGULAR",
              "price": {
                "type": "ESTIMATE",
                "amount": {
                  "currencyCode": "USD",
                  "units": 0,
                  "nanos": 600000000
                }
              },
              "quantity": 1
            }
          }
        ]
      }
    ]
  },
  "totalPrice": {
    "type": "ESTIMATE",
    "amount": {
      "currencyCode": "USD",
      "units": 15
    }
  },
  "other_items": [
    {
      "id": "subtotal",
      "name": "Subtotal",
      "type": "SUBTOTAL",
      "quantity": 1,
      "price": {
        "type": "ESTIMATE",
        "amount": {
          "currencyCode": "USD",
          "units": 13,
          "nanos": 880000000
        }
      }
    },
    {
      "id": "tax",
      "name": "Tax",
      "type": "TAX",
      "quantity": 1,
      "price": {
        "type": "ESTIMATE",
        "amount": {
          "currencyCode": "USD",
          "units": 1,
          "nanos": 120000000
        }
      }
    }
  ]
}

Confirming the transaction with user

Once you've built an order, you can present it to the user to confirm or reject. You do this by requesting fulfillment of the actions.intent.TRANSACTION_DECISION intent and providing the order thar you built.

Confirming a transaction for a Google payment account

API.AI Node.js
    function transactionDecision (action) {
      action.askForTransactionDecision(action.buildOrder('1234')
          .setCart(action.buildCart().setMerchant('test_merchant', 'Test Merchant')
            .addItems(action.buildItem('ABC Book', 'book_abc').setPrice('USD', 3))
            .setNotes('Buying a book'))
          .setTotalPrice(action.Transactions.PriceType.ESTIMATE, 'USD', 3),
        {
          type: action.Transactions.DelegatedPaymentType.PAYMENT_CARD,
          displayName: 'VISA-1234',
          shippingAddressRequired: false
        });
    }
  
API.AI JSON
{
  "google": {
    "system_intent": {
      "intent": "actions.intent.TRANSACTION_DECISION",
      "data": {
        "@type": "type.googleapis.com/google.actions.v2.TransactionDecisionValueSpec",
        "proposedOrder": {
          "cart": {
            "merchant": {
              "id": "TXN_TESTER",
              "name": "Panera"
            },
            "lineItems": [
              {
                "id": "iced-latte-large",
                "name": "Iced latte, Large",
                "type": "REGULAR",
                "price": {
                  "type": "ESTIMATE",
                  "amount": {
                    "currencyCode": "USD",
                    "units": "3",
                    "nanos": "590000000"
                  }
                },
                "quantity": "1",
                "subLines": [
                  {
                    "note": "Caramel swirl"
                  }
                ]
              },
              {
                "id": "boston-creme-donut",
                "name": "Boston creme donut",
                "type": "REGULAR",
                "price": {
                  "type": "ESTIMATE",
                  "amount": {
                    "currencyCode": "USD",
                    "units": "1",
                    "nanos": "200000000"
                  }
                },
                "quantity": "1"
              }
            ]
          },
          "totalPrice": {
            "type": "ESTIMATE",
            "amount": {
              "currencyCode": "USD",
              "units": "5",
              "nanos": "170000000"
            }
          },
          "otherItems": [
            {
              "id": "subtotal",
              "name": "Subtotal",
              "type": "SUBTOTAL",
              "quantity": "1",
              "price": {
                "type": "ESTIMATE",
                "amount": {
                  "currencyCode": "USD",
                  "units": "4",
                  "nanos": "790000000"
                }
              }
            },
            {
              "id": "tax",
              "name": "Tax",
              "type": "TAX",
              "quantity": "1",
              "price": {
                "type": "ESTIMATE",
                "amount": {
                  "currencyCode": "USD",
                  "units": "0",
                  "nanos": "380000000"
                }
              }
            }
          ],
          "extension": {
            "@type": "type.googleapis.com/google.actions.v2.orders.GenericExtension",
            "locations": [
              {
                "type": "DELIVERY",
                "location": {
                  "postalAddress": {
                    "addressLines": [
                      "205 Charleston Road"
                    ],
                    "recipients": [
                      "Adam Coimbra"
                    ]
                  },
                  "phoneNumber": "610 717 2651"
                }
              }
            ]
          }
        },
        "paymentOptions": {
          "googleProvidedOptions": {
            "tokenizationParameters": {
              "tokenizationType": "PAYMENT_GATEWAY",
              "parameters": {
                "gateway": "stripe",
                "stripe:publishableKey": "pk_test_Sxk0ECylJy82lPcVT8YT4SmQ",
                "stripe:version": "2017-04-06"
              }
            }
          }
        },
        "orderOptions": {
          "customerInfoOptions": {
            "customerInfoProperties": [
              "EMAIL"
            ]
          }
        }
      }
    }
  }
}

Confirm a developer-provided payment option

API.AI Node.js
function transactionDecision (action) {
  action.askForTransactionDecision(action.buildOrder('1234')
      .setCart(action.buildCart().setMerchant('test_merchant', 'Test Merchant')
        .addItems(action.buildItem('ABC Book', 'book_abc').setPrice('USD', 3))
        .setNotes('Buying a book'))
      .setTotalPrice(action.Transactions.PriceType.ESTIMATE, 'USD', 3),
    {
      type: action.Transactions.DelegatedPaymentType.PAYMENT_CARD,
      displayName: 'VISA-1234',
      shippingAddressRequired: false
    });
}
    
API.AI JSON
{
  "google": {
    "system_intent": {
      "intent": "actions.intent.TRANSACTION_DECISION",
      "data": {
        "@type": "type.googleapis.com/google.actions.v2.TransactionDecisionValueSpec",
        "proposedOrder": {
          "cart": {
            "merchant": {
              "id": "TXN_TESTER",
              "name": "Panera"
            },
            "lineItems": [
              {
                "id": "chipotle-chicken-avocado-melt",
                "name": "Chipotle Chicken Avocado Melt",
                "type": "REGULAR",
                "price": {
                  "type": "ESTIMATE",
                  "amount": {
                    "currencyCode": "USD",
                    "units": 8,
                    "nanos": 990000000
                  }
                },
                "quantity": 1
              },
              {
                "id": "iced-caffe-mocha-regular",
                "name": "Iced Caffe Mocha (regular)",
                "type": "REGULAR",
                "price": {
                  "type": "ESTIMATE",
                  "amount": {
                    "currencyCode": "USD",
                    "units": 4,
                    "nanos": 290000000
                  }
                },
                "quantity": 1,
                "subLines": [
                  {
                    "lineItem": {
                      "id": "espresso-shot",
                      "name": "Espresso shot",
                      "type": "REGULAR",
                      "price": {
                        "type": "ESTIMATE",
                        "amount": {
                          "currencyCode": "USD",
                          "units": 0,
                          "nanos": 600000000
                        }
                      },
                      "quantity": 1
                    }
                  }
                ]
              }
            ]
          },
          "totalPrice": {
            "type": "ESTIMATE",
            "amount": {
              "currencyCode": "USD",
              "units": 15
            }
          },
          "other_items": [
            {
              "id": "subtotal",
              "name": "Subtotal",
              "type": "SUBTOTAL",
              "quantity": 1,
              "price": {
                "type": "ESTIMATE",
                "amount": {
                  "currencyCode": "USD",
                  "units": 13,
                  "nanos": 880000000
                }
              }
            },
            {
              "id": "tax",
              "name": "Tax",
              "type": "TAX",
              "quantity": 1,
              "price": {
                "type": "ESTIMATE",
                "amount": {
                  "currencyCode": "USD",
                  "units": 1,
                  "nanos": 120000000
                }
              }
            }
          ]
        },
        "paymentOptions": {
          "actionProvidedOptions": {
            "paymentType": "PAYMENT_CARD",
            "displayName": "VISA-9876"
          }
        }
      }
    }
  }
}

Get the user's order confirmation

After the Assistant fulfills the intent, it sends your fulfillment a request with the actions_intent_TRANSACTION_DECISION intent with the user's answer to the transaction decision.

To properly handle this request, declare an API.AI intent that's triggered by the actions_intent_TRANSACTIONS_DECISION event. When triggered, handle it in your fulfillment in the following way.

API.AI Node.js
function transactionDecisionComplete (action) {
if (action.getTransactionDecision()
&& action.getTransactionDecision().userDecision ===
  action.Transactions.ConfirmationDecision.ACCEPTED) {
}
    
API.AI JSON

Complete the order and send status

complete order on your backend

Sandbox

Tell devs not to persist order as real if "is_in_sandbox" is true, but they do need to persist it so they have an order ID to send updates

Action order ID

General API explanations

Order management actions

Signin

Runtime signin

You can let users create new or signin to their existing accounts on your service Sign in Adding GDI or non-GDI in console (link out to other docs) Calling askForSignIn

Asynchronous signin flow

Setup flow (service account key) Mention google oauth client libraries but can use oauth2l in the guidance Show CURL command P2: client library General API explanations

idk about this right now

Handling rejections and bad user input

I assume this is similar across all askFor's and we don't need a section for each one.