En esta guía, se explica el proceso de desarrollo de un proyecto de Acciones que use la API de Orders para realizar reservas.
Flujo de transacciones
Cuando tu proyecto de Acciones controla las reservas, usa el siguiente flujo:
- Valida los requisitos de las transacciones (opcional): Usa el asistente de requisitos de transacciones al comienzo de la conversación para asegurarte de que el usuario pueda realizar una transacción.
- Crea el pedido: Guía al usuario a través de un "ensamblaje del carrito" en el que compila los detalles de su reserva.
- Proponer el pedido: Una vez que el "carrito" esté completo, propón el "pedido" de la reserva al usuario para que pueda confirmar que es correcto. Si se confirma la reserva, recibirás una respuesta con los detalles de la reserva.
- Finaliza el pedido y envía un recibo: Una vez confirmado el pedido, actualiza tu sistema de reservas y envía un recibo al usuario.
- Enviar actualizaciones de pedidos: En el transcurso de la vida útil de la reserva, envía solicitudes PATCH a la API de pedidos para proporcionar al usuario actualizaciones del estado de la reserva.
Restricciones y lineamientos para la revisión
Ten en cuenta que se aplican políticas adicionales a las Acciones que usan las transacciones y la API de Orders. Podemos demorar hasta seis semanas en revisar las Acciones con transacciones, así que ten en cuenta ese tiempo cuando planifiques tu programa de lanzamientos. Para facilitar el proceso de revisión, asegúrate de cumplir con las políticas y los lineamientos para las transacciones antes de enviar tu Acción a revisión.
Solo puedes implementar Acciones que usen la API de Orders en los siguientes países:
Australia Brasil Canadá Indonesia |
Japón México Catar Rusia |
Singapur Suiza Tailandia Turquía Reino Unido Estados Unidos |
Cómo compilar un proyecto
Si deseas ver una amplia variedad de ejemplos de conversaciones transaccionales, consulta nuestros ejemplos de transacciones en Node.js y Java.
Configuración del proyecto
Cuando creas tu acción, debes especificar que deseas realizar transacciones en la Consola de Actions. Además, si usas la biblioteca cliente de Node.JS, configura tu entrega para que use la versión más reciente de la API de Orders.
Para configurar tu proyecto y la entrega, haz lo siguiente:
- Crea un proyecto nuevo o importa uno existente.
- Ve a Implementar > Información del directorio.
En Información adicional > Transacciones > marca la casilla que dice “¿Tus acciones usan la API de transacciones para realizar transacciones de bienes físicos?”.
Si usas la biblioteca cliente de Node.JS para compilar la entrega de tu acción, abre el código de entrega y actualiza la delegación de la app a fin de establecer la marca
ordersv3
entrue
. En el siguiente fragmento de código, se muestra un ejemplo de declaración de app para la versión 3 de Orders.
Node.js
const {dialogflow} = require('actions-on-google'); let app = dialogflow({ clientId, // If using account linking debug: true, ordersv3: true, });
Node.js
const {actionssdk} = require('actions-on-google'); let app = actionssdk({ clientId, // If using account linking debug: true, ordersv3: true, });
1. Valida los requisitos de las transacciones (opcional)
Experiencia del usuario
En cuanto el usuario indique que desea configurar una reserva, recomendamos que actives el intent actions.intent.TRANSACTION_REQUIREMENTS_CHECK
para asegurarte de que pueda solicitar una reserva. Por ejemplo, cuando se invoca, tu Acción podría preguntar: "¿Te gustaría reservar un lugar?". Si el usuario dice "sí", debes solicitar este intent de inmediato. Esto garantizará
que puedan continuar y les dará la oportunidad de corregir cualquier configuración
que les impida continuar con la transacción.
Solicitar el intent de verificación de requisitos de las transacciones da como resultado uno de los siguientes resultados:
- Si se cumplen los requisitos, tu entrega recibe un intent con una condición de éxito y puedes continuar con la creación del pedido del usuario.
De lo contrario, tu entrega recibe el intent con una condición de falla. En este caso, finaliza la conversación o aléjate del flujo de reserva.
Si el usuario puede corregir el error, se le solicitará automáticamente que resuelva esos problemas en su dispositivo. Si la conversación se lleva a cabo en una superficie solo de voz, como una bocina inteligente, se transfiere al teléfono del usuario.
Entrega
Para asegurarte de que un usuario cumpla con los requisitos de la transacción, solicita la entrega del intent actions.intent.TRANSACTION_REQUIREMENTS_CHECK
con un objeto TransactionRequirementsCheckSpec.
Cómo consultar los requisitos
Verifica si un usuario cumple con los requisitos de reserva con la biblioteca cliente:
conv.ask(new TransactionRequirements());
return getResponseBuilder(request) .add("Placeholder for transaction requirements text") .add(new TransactionRequirements()) .build();
Ten en cuenta que el siguiente JSON describe una respuesta de webhook.
{ "payload": { "google": { "expectUserResponse": true, "systemIntent": { "intent": "actions.intent.TRANSACTION_REQUIREMENTS_CHECK", "data": { "@type": "type.googleapis.com/google.actions.transactions.v3.TransactionRequirementsCheckSpec" } } } } }
Ten en cuenta que el siguiente JSON describe una respuesta de webhook.
{ "expectUserResponse": true, "expectedInputs": [ { "possibleIntents": [ { "intent": "actions.intent.TRANSACTION_REQUIREMENTS_CHECK", "inputValueData": { "@type": "type.googleapis.com/google.actions.transactions.v3.TransactionRequirementsCheckSpec" } } ] } ] }
Cómo recibir el resultado de una verificación de requisitos
Una vez que Asistente entrega el intent, le envía a tu entrega una solicitud con el intent actions.intent.TRANSACTION_REQUIREMENTS_CHECK
con el resultado de la verificación.
Para manejar de forma correcta esta solicitud, declara un intent de Dialogflow que se active con el evento actions_intent_TRANSACTION_REQUIREMENTS_CHECK
. Cuando se active, controla este intent en tu entrega:
const arg = conv.arguments.get('TRANSACTION_REQUIREMENTS_CHECK_RESULT'); if (arg && arg.resultType === 'CAN_TRANSACT') { // Normally take the user through cart building flow conv.ask(`Looks like you're good to go!`); } else { conv.close('Transaction failed.'); }
Argument transactionCheckResult = request .getArgument("TRANSACTION_REQUIREMENTS_CHECK_RESULT"); boolean result = false; if (transactionCheckResult != null) { Map<String, Object> map = transactionCheckResult.getExtension(); if (map != null) { String resultType = (String) map.get("resultType"); result = resultType != null && resultType.equals("CAN_TRANSACT"); } } ResponseBuilder responseBuilder = getResponseBuilder(request); if (result) { responseBuilder.add("Looks like you're good to go! Now say 'confirm transaction'"); } else { responseBuilder.add("Transaction failed"); } return responseBuilder.build();
Ten en cuenta que el siguiente JSON describe una solicitud de webhook.
{ "responseId": "", "queryResult": { "queryText": "", "action": "", "parameters": {}, "allRequiredParamsPresent": true, "fulfillmentText": "", "fulfillmentMessages": [], "outputContexts": [], "intent": { "name": "reservation_transaction_check_complete_df", "displayName": "reservation_transaction_check_complete_df" }, "intentDetectionConfidence": 1, "diagnosticInfo": {}, "languageCode": "" }, "originalDetectIntentRequest": { "source": "google", "version": "2", "payload": { "isInSandbox": true, "surface": { "capabilities": [ { "name": "actions.capability.SCREEN_OUTPUT" }, { "name": "actions.capability.AUDIO_OUTPUT" }, { "name": "actions.capability.MEDIA_RESPONSE_AUDIO" }, { "name": "actions.capability.WEB_BROWSER" } ] }, "inputs": [ { "rawInputs": [], "intent": "", "arguments": [ { "extension": { "@type": "type.googleapis.com/google.transactions.v3.TransactionRequirementsCheckResult", "resultType": "CAN_TRANSACT" }, "name": "TRANSACTION_REQUIREMENTS_CHECK_RESULT" } ] } ], "user": {}, "conversation": {}, "availableSurfaces": [ { "capabilities": [ { "name": "actions.capability.SCREEN_OUTPUT" }, { "name": "actions.capability.AUDIO_OUTPUT" }, { "name": "actions.capability.MEDIA_RESPONSE_AUDIO" }, { "name": "actions.capability.WEB_BROWSER" } ] } ] } }, "session": "" }
Ten en cuenta que el siguiente JSON describe una solicitud de webhook.
{ "user": {}, "device": {}, "surface": { "capabilities": [ { "name": "actions.capability.SCREEN_OUTPUT" }, { "name": "actions.capability.AUDIO_OUTPUT" }, { "name": "actions.capability.MEDIA_RESPONSE_AUDIO" }, { "name": "actions.capability.WEB_BROWSER" } ] }, "conversation": {}, "inputs": [ { "rawInputs": [], "intent": "reservation_transaction_check_complete_asdk", "arguments": [ { "extension": { "@type": "type.googleapis.com/google.transactions.v3.TransactionRequirementsCheckResult", "resultType": "CAN_TRANSACT" }, "name": "TRANSACTION_REQUIREMENTS_CHECK_RESULT" } ] } ], "availableSurfaces": [ { "capabilities": [ { "name": "actions.capability.SCREEN_OUTPUT" }, { "name": "actions.capability.AUDIO_OUTPUT" }, { "name": "actions.capability.MEDIA_RESPONSE_AUDIO" }, { "name": "actions.capability.WEB_BROWSER" } ] } ] }
2. Arma el pedido
Experiencia del usuario
Una vez que tengas la información del usuario que necesitas, crea una experiencia de "ensamblaje del carrito" que guíe al usuario a crear su reserva. Cada acción tendrá un flujo de ensamblado del carrito ligeramente diferente según corresponda para su servicio.
En una experiencia básica de armado de carrito, un usuario selecciona opciones de una lista para agregar a su reserva, aunque puedes diseñar la conversación para simplificar la experiencia del usuario. Por ejemplo, crea una experiencia de ensamblaje de carritos que le permita al usuario programar una reserva mensual con una pregunta simple de sí o no. También puedes presentar al usuario un carrusel o una tarjeta de lista de reservas "recomendadas".
Recomendamos usar respuestas enriquecidas para presentar las opciones del usuario de forma visual, pero también diseñar la conversación para que el usuario pueda crear su carrito solo con su voz. Para conocer algunas prácticas recomendadas y ejemplos de experiencias de ensamblaje de carritos, consulta los Lineamientos de diseño de transacciones.
Entrega
A lo largo de la conversación, recopila los detalles de la reserva que un usuario desea comprar y, luego, crea un objeto Order
.
Tu Order
debe contener al menos lo siguiente:
buyerInfo
: Es información sobre el usuario que programa la reserva.transactionMerchant
: Es información sobre el comercio que facilita la reserva.contents
: Son los detalles reales de la reserva que aparecen comolineItems
.
Consulta la documentación sobre la respuesta de Order
para crear tu carrito. Ten en cuenta que es posible que debas incluir diferentes campos según la reserva.
El siguiente código de muestra indica un pedido de reserva completo, que incluye campos opcionales:
app.intent('build_reservation_df', (conv) => { const now = new Date().toISOString(); const order = { createTime: now, lastUpdateTime: now, merchantOrderId: 'UNIQUE_ORDER_ID', userVisibleOrderId: 'USER_VISIBLE_ORDER_ID', transactionMerchant: { id: 'https://www.example.com', name: 'Example Merchant', }, contents: { lineItems: [ { id: 'LINE_ITEM_ID', name: 'Dinner reservation', description: 'A world of flavors all in one destination.', reservation: { status: 'PENDING', userVisibleStatusLabel: 'Reservation is pending.', type: 'RESTAURANT', reservationTime: { timeIso8601: '2020-01-16T01:30:15.01Z', }, userAcceptableTimeRange: { timeIso8601: '2020-01-15/2020-01-17', }, partySize: 6, staffFacilitators: [ { name: 'John Smith', }, ], location: { zipCode: '94086', city: 'Sunnyvale', postalAddress: { regionCode: 'US', postalCode: '94086', administrativeArea: 'CA', locality: 'Sunnyvale', addressLines: [ '222, Some other Street', ], }, }, }, }, ], }, buyerInfo: { email: 'janedoe@gmail.com', firstName: 'Jane', lastName: 'Doe', displayName: 'Jane Doe', }, followUpActions: [ { type: 'VIEW_DETAILS', title: 'View details', openUrlAction: { url: 'https://example.com', }, }, { type: 'CALL', title: 'Call us', openUrlAction: { url: 'tel:+16501112222', }, }, { type: 'EMAIL', title: 'Email us', openUrlAction: { url: 'mailto:person@example.com', }, }, ], termsOfServiceUrl: 'https://www.example.com', };
private static OrderV3 createOrder() { // Transaction Merchant MerchantV3 transactionMerchant = new MerchantV3() .setId("http://www.example.com") .setName("Example Merchant"); // Line Item // Reservation Item Extension ReservationItemExtension reservationItemExtension = new ReservationItemExtension() .setStatus("PENDING") .setUserVisibleStatusLabel("Reservation pending.") .setType("RESTAURANT") .setReservationTime(new TimeV3() .setTimeIso8601("2020-01-16T01:30:15.01Z")) .setUserAcceptableTimeRange(new TimeV3() .setTimeIso8601("2020-01-15/2020-01-17")) .setPartySize(6) .setStaffFacilitators(Collections.singletonList(new StaffFacilitator() .setName("John Smith"))) .setLocation(new Location() .setZipCode("94086") .setCity("Sunnyvale") .setPostalAddress(new PostalAddress() .setRegionCode("US") .setPostalCode("94086") .setAdministrativeArea("CA") .setLocality("Sunnyvale") .setAddressLines( Collections.singletonList("222, Some other Street")))); LineItemV3 lineItem = new LineItemV3() .setId("LINE_ITEM_ID") .setName("Dinner reservation") .setDescription("A world of flavors all in one destination.") .setReservation(reservationItemExtension); // Order Contents OrderContents contents = new OrderContents() .setLineItems(Collections.singletonList(lineItem)); // User Info UserInfo buyerInfo = new UserInfo() .setEmail("janedoe@gmail.com") .setFirstName("Jane") .setLastName("Doe") .setDisplayName("Jane Doe"); // Follow up actions Action viewDetails = new Action() .setType("VIEW_DETAILS") .setTitle("View details") .setOpenUrlAction(new OpenUrlAction() .setUrl("https://example.com")); Action call = new Action() .setType("CALL") .setTitle("Call us") .setOpenUrlAction(new OpenUrlAction() .setUrl("tel:+16501112222")); Action email = new Action() .setType("EMAIL") .setTitle("Email us") .setOpenUrlAction(new OpenUrlAction() .setUrl("mailto:person@example.com")); // Terms of service and order note String termsOfServiceUrl = "https://example.com"; String now = Instant.now().toString(); OrderV3 order = new OrderV3() .setCreateTime(now) .setLastUpdateTime(now) .setMerchantOrderId("UNIQUE_ORDER_ID") .setUserVisibleOrderId("UNIQUE_USER_VISIBLE_ORDER_ID") .setTransactionMerchant(transactionMerchant) .setContents(contents) .setBuyerInfo(buyerInfo) .setFollowUpActions(Arrays.asList( viewDetails, call, email )) .setTermsOfServiceUrl(termsOfServiceUrl); return order; }
Ten en cuenta que el siguiente JSON describe una respuesta de webhook.
{ "payload": { "google": { "expectUserResponse": true, "systemIntent": { "intent": "actions.intent.TRANSACTION_DECISION", "data": { "@type": "type.googleapis.com/google.actions.transactions.v3.TransactionDecisionValueSpec", "order": { "createTime": "2019-07-17T18:25:30.182Z", "lastUpdateTime": "2019-07-17T18:25:30.182Z", "merchantOrderId": "UNIQUE_ORDER_ID", "userVisibleOrderId": "USER_VISIBLE_ORDER_ID", "transactionMerchant": { "id": "https://www.example.com", "name": "Example Merchant" }, "contents": { "lineItems": [ { "id": "LINE_ITEM_ID", "name": "Dinner reservation", "description": "A world of flavors all in one destination.", "reservation": { "status": "PENDING", "userVisibleStatusLabel": "Reservation is pending.", "type": "RESTAURANT", "reservationTime": { "timeIso8601": "2020-01-16T01:30:15.01Z" }, "userAcceptableTimeRange": { "timeIso8601": "2020-01-15/2020-01-17" }, "partySize": 6, "staffFacilitators": [ { "name": "John Smith" } ], "location": { "zipCode": "94086", "city": "Sunnyvale", "postalAddress": { "regionCode": "US", "postalCode": "94086", "administrativeArea": "CA", "locality": "Sunnyvale", "addressLines": [ "222, Some other Street" ] } } } } ] }, "buyerInfo": { "email": "janedoe@gmail.com", "firstName": "Jane", "lastName": "Doe", "displayName": "Jane Doe" }, "followUpActions": [ { "type": "VIEW_DETAILS", "title": "View details", "openUrlAction": { "url": "https://example.com" } }, { "type": "CALL", "title": "Call us", "openUrlAction": { "url": "tel:+16501112222" } }, { "type": "EMAIL", "title": "Email us", "openUrlAction": { "url": "mailto:person@example.com" } } ], "termsOfServiceUrl": "https://www.example.com" }, "orderOptions": { "requestDeliveryAddress": false, "userInfoOptions": { "userInfoProperties": [ "EMAIL" ] } }, "presentationOptions": { "actionDisplayName": "RESERVE" } } } } } }
3. Proponer el orden
Presenta tu pedido de reserva al usuario para que lo confirme o rechace. Solicita el intent actions.intent.TRANSACTION_DECISION
y proporciona el Order
que compilaste.
Experiencia del usuario
Cuando solicitas el intent actions.intent.TRANSACTION_DECISION
, Asistente inicia una experiencia integrada en la que el Order
se renderiza directamente en una "tarjeta de vista previa del carrito". El usuario puede decir “programar reserva”, rechazar la transacción o solicitar cambiar los detalles de la reserva.
En este momento, el usuario también puede solicitar cambios en el pedido. En este caso, debes asegurarte de que tu entrega pueda controlar las solicitudes de cambio de pedido después de finalizar la experiencia de ensamblado del carrito.
Entrega
Cuando solicites el intent actions.intent.TRANSACTION_DECISION
, crea un TransactionDecision
que contenga el Order
y el orderOptions
.
En el siguiente código, se muestra un TransactionsDecision
de ejemplo para un pedido:
conv.ask(new TransactionDecision({ orderOptions: { requestDeliveryAddress: 'false', }, presentationOptions: { actionDisplayName: 'RESERVE', }, order: order, }));
// Create order options OrderOptionsV3 orderOptions = new OrderOptionsV3() .setRequestDeliveryAddress(false) .setUserInfoOptions(new UserInfoOptions() .setUserInfoProperties(Collections.singletonList("EMAIL"))); // Create presentation options PresentationOptionsV3 presentationOptions = new PresentationOptionsV3() .setActionDisplayName("RESERVE"); // Ask for transaction decision return getResponseBuilder(request) .add("Placeholder for transaction decision text") .add(new TransactionDecision() .setOrder(order) .setOrderOptions(orderOptions) .setPresentationOptions(presentationOptions) ) .build();
Ten en cuenta que el siguiente JSON describe una respuesta de webhook.
{ "payload": { "google": { "expectUserResponse": true, "systemIntent": { "intent": "actions.intent.TRANSACTION_DECISION", "data": { "@type": "type.googleapis.com/google.actions.transactions.v3.TransactionDecisionValueSpec", "orderOptions": { "requestDeliveryAddress": "false" }, "presentationOptions": { "actionDisplayName": "RESERVE" }, "order": { "createTime": "2019-07-17T18:25:30.184Z", "lastUpdateTime": "2019-07-17T18:25:30.184Z", "merchantOrderId": "UNIQUE_ORDER_ID", "userVisibleOrderId": "USER_VISIBLE_ORDER_ID", "transactionMerchant": { "id": "https://www.example.com", "name": "Example Merchant" }, "contents": { "lineItems": [ { "id": "LINE_ITEM_ID", "name": "Dinner reservation", "description": "A world of flavors all in one destination.", "reservation": { "status": "PENDING", "userVisibleStatusLabel": "Reservation is pending.", "type": "RESTAURANT", "reservationTime": { "timeIso8601": "2020-01-16T01:30:15.01Z" }, "userAcceptableTimeRange": { "timeIso8601": "2020-01-15/2020-01-17" }, "partySize": 6, "staffFacilitators": [ { "name": "John Smith" } ], "location": { "zipCode": "94086", "city": "Sunnyvale", "postalAddress": { "regionCode": "US", "postalCode": "94086", "administrativeArea": "CA", "locality": "Sunnyvale", "addressLines": [ "222, Some other Street" ] } } } } ] }, "buyerInfo": { "email": "janedoe@gmail.com", "firstName": "Jane", "lastName": "Doe", "displayName": "Jane Doe" }, "followUpActions": [ { "type": "VIEW_DETAILS", "title": "View details", "openUrlAction": { "url": "https://example.com" } }, { "type": "CALL", "title": "Call us", "openUrlAction": { "url": "tel:+16501112222" } }, { "type": "EMAIL", "title": "Email us", "openUrlAction": { "url": "mailto:person@example.com" } } ], "termsOfServiceUrl": "https://www.example.com" } } } } } }
Ten en cuenta que el siguiente JSON describe una respuesta de webhook.
{ "expectUserResponse": true, "expectedInputs": [ { "possibleIntents": [ { "intent": "actions.intent.TRANSACTION_DECISION", "inputValueData": { "@type": "type.googleapis.com/google.actions.transactions.v3.TransactionDecisionValueSpec", "orderOptions": { "requestDeliveryAddress": "false" }, "presentationOptions": { "actionDisplayName": "RESERVE" }, "order": { "createTime": "2019-07-17T18:25:30.057Z", "lastUpdateTime": "2019-07-17T18:25:30.057Z", "merchantOrderId": "UNIQUE_ORDER_ID", "userVisibleOrderId": "USER_VISIBLE_ORDER_ID", "transactionMerchant": { "id": "https://www.example.com", "name": "Example Merchant" }, "contents": { "lineItems": [ { "id": "LINE_ITEM_ID", "name": "Dinner reservation", "description": "A world of flavors all in one destination.", "reservation": { "status": "PENDING", "userVisibleStatusLabel": "Reservation is pending.", "type": "RESTAURANT", "reservationTime": { "timeIso8601": "2020-01-16T01:30:15.01Z" }, "userAcceptableTimeRange": { "timeIso8601": "2020-01-15/2020-01-17" }, "partySize": 6, "staffFacilitators": [ { "name": "John Smith" } ], "location": { "zipCode": "94086", "city": "Sunnyvale", "postalAddress": { "regionCode": "US", "postalCode": "94086", "administrativeArea": "CA", "locality": "Sunnyvale", "addressLines": [ "222, Some other Street" ] } } } } ] }, "buyerInfo": { "email": "janedoe@gmail.com", "firstName": "Jane", "lastName": "Doe", "displayName": "Jane Doe" }, "followUpActions": [ { "type": "VIEW_DETAILS", "title": "View details", "openUrlAction": { "url": "https://example.com" } }, { "type": "CALL", "title": "Call us", "openUrlAction": { "url": "tel:+16501112222" } }, { "type": "EMAIL", "title": "Email us", "openUrlAction": { "url": "mailto:person@example.com" } } ], "termsOfServiceUrl": "https://www.example.com" } } } ] } ] }
Cómo controlar la decisión del usuario
Una vez que el usuario responde al pedido propuesto, tu entrega recibe el intent actions_intent_TRANSACTION_DECISION
con un argumento que contiene un TransactionDecisionValue
. Este valor contendrá lo siguiente:
transactionDecision
: Es la decisión del usuario con respecto al pedido propuesto. Los valores posibles sonORDER_ACCEPTED
,ORDER_REJECTED
,CART_CHANGE_REQUESTED
yUSER_CANNOT_TRANSACT
.
Para controlar esta solicitud, declara un intent de Dialogflow que se active con el evento actions_intent_TRANSACTION_DECISION
. Controla este intent en tu entrega:
const arg = conv.arguments.get('TRANSACTION_DECISION_VALUE'); if (arg && arg.transactionDecision === 'ORDER_ACCEPTED') { console.log('order accepted'); const order = arg.order; }
Argument transactionDecisionValue = request .getArgument("TRANSACTION_DECISION_VALUE"); Map<String, Object> extension = null; if (transactionDecisionValue != null) { extension = transactionDecisionValue.getExtension(); } String transactionDecision = null; if (extension != null) { transactionDecision = (String) extension.get("transactionDecision"); } if ((transactionDecision != null && transactionDecision.equals("ORDER_ACCEPTED"))) { OrderV3 order = ((OrderV3) extension.get("order")); }
Ten en cuenta que el siguiente JSON describe una solicitud de webhook.
{ "responseId": "", "queryResult": { "queryText": "", "action": "", "parameters": {}, "allRequiredParamsPresent": true, "fulfillmentText": "", "fulfillmentMessages": [], "outputContexts": [], "intent": { "name": "reservation_get_transaction_decision_df", "displayName": "reservation_get_transaction_decision_df" }, "intentDetectionConfidence": 1, "diagnosticInfo": {}, "languageCode": "" }, "originalDetectIntentRequest": { "source": "google", "version": "2", "payload": { "isInSandbox": true, "surface": { "capabilities": [ { "name": "actions.capability.SCREEN_OUTPUT" }, { "name": "actions.capability.AUDIO_OUTPUT" }, { "name": "actions.capability.MEDIA_RESPONSE_AUDIO" }, { "name": "actions.capability.WEB_BROWSER" } ] }, "inputs": [ { "rawInputs": [], "intent": "", "arguments": [] } ], "user": {}, "conversation": {}, "availableSurfaces": [ { "capabilities": [ { "name": "actions.capability.SCREEN_OUTPUT" }, { "name": "actions.capability.AUDIO_OUTPUT" }, { "name": "actions.capability.MEDIA_RESPONSE_AUDIO" }, { "name": "actions.capability.WEB_BROWSER" } ] } ] } }, "session": "" }
Ten en cuenta que el siguiente JSON describe una solicitud de webhook.
{ "user": {}, "device": {}, "surface": { "capabilities": [ { "name": "actions.capability.SCREEN_OUTPUT" }, { "name": "actions.capability.AUDIO_OUTPUT" }, { "name": "actions.capability.MEDIA_RESPONSE_AUDIO" }, { "name": "actions.capability.WEB_BROWSER" } ] }, "conversation": {}, "inputs": [ { "rawInputs": [], "intent": "reservation_get_transaction_decision_asdk", "arguments": [] } ], "availableSurfaces": [ { "capabilities": [ { "name": "actions.capability.SCREEN_OUTPUT" }, { "name": "actions.capability.AUDIO_OUTPUT" }, { "name": "actions.capability.MEDIA_RESPONSE_AUDIO" }, { "name": "actions.capability.WEB_BROWSER" } ] } ] }
4. Finaliza la reserva y envía un recibo
Cuando el intent actions.intent.TRANSACTION_DECISION
se muestre con un transactionDecision
de ORDER_ACCEPTED
, realiza el procesamiento necesario para programar la reserva (como conservarla en tu propia base de datos).
Enviar una respuesta simple para mantener la conversación en movimiento. El usuario recibirá una "tarjeta de recibo contraída" junto con tu respuesta.
Entrega
// Set lastUpdateTime and update status of reservation order.lastUpdateTime = new Date().toISOString(); order.reservation.status = 'CONFIRMED'; order.reservation.userVisibleStatusLabel = 'Reservation confirmed'; order.reservation.confirmationCode = '123ABCDEFGXYZ'; // Send synchronous order update conv.ask(`Transaction completed! You're all set!`); conv.ask(new OrderUpdate({ type: 'SNAPSHOT', reason: 'Reason string', order: order, }));
ResponseBuilder responseBuilder = getResponseBuilder(request); order.setLastUpdateTime(Instant.now().toString()); // Set reservation status to confirmed and provide confirmation code LineItemV3 lineItem = order.getContents().getLineItems().get(0); ReservationItemExtension reservationItemExtension = lineItem.getReservation(); reservationItemExtension.setStatus("CONFIRMED"); reservationItemExtension.setUserVisibleStatusLabel("Reservation confirmed."); reservationItemExtension.setConfirmationCode("123ABCDEFGXYZ"); lineItem.setReservation(reservationItemExtension); order.getContents().getLineItems().set(0, lineItem); // Order update OrderUpdateV3 orderUpdate = new OrderUpdateV3() .setType("SNAPSHOT") .setReason("Reason string") .setOrder(order); responseBuilder .add("Transaction completed! You're all set! Would you like to do anything else?") .add(new StructuredResponse().setOrderUpdateV3(orderUpdate)); return responseBuilder.build();
Ten en cuenta que el siguiente JSON describe una respuesta de webhook.
{ "payload": { "google": { "expectUserResponse": true, "richResponse": { "items": [ { "simpleResponse": { "textToSpeech": "Transaction completed! You're all set!" } }, { "structuredResponse": { "orderUpdateV3": { "type": "SNAPSHOT", "reason": "Reason string", "order": { "merchantOrderId": "UNIQUE_ORDER_ID", "reservation": { "status": "CONFIRMED", "userVisibleStatusLabel": "Reservation confirmed", "confirmationCode": "123ABCDEFGXYZ" }, "lastUpdateTime": "2019-07-17T18:25:30.187Z" } } } } ] } } } }
Ten en cuenta que el siguiente JSON describe una respuesta de webhook.
{ "expectUserResponse": true, "expectedInputs": [ { "possibleIntents": [ { "intent": "actions.intent.TEXT" } ], "inputPrompt": { "richInitialPrompt": { "items": [ { "simpleResponse": { "textToSpeech": "Transaction completed! You're all set!" } }, { "structuredResponse": { "orderUpdateV3": { "type": "SNAPSHOT", "reason": "Reason string", "order": { "merchantOrderId": "UNIQUE_ORDER_ID", "reservation": { "status": "CONFIRMED", "userVisibleStatusLabel": "Reservation confirmed", "confirmationCode": "123ABCDEFGXYZ" }, "lastUpdateTime": "2019-07-17T18:25:30.059Z" } } } } ] } } } ] }
5. Enviar actualizaciones del pedido
El estado de la reserva cambia con el paso de su vida útil. Envía las actualizaciones del pedido de reserva del usuario con solicitudes HTTP PATCH a la API de Orders, que contiene el estado y los detalles del pedido.
Configura solicitudes asíncronas en la API de Orders
Las solicitudes de actualización de pedidos a la API de Orders están autorizadas por un token de acceso. Para empaquetar una actualización de pedido en la API de Orders, descarga una clave de cuenta de servicio JSON asociada con tu proyecto de Actions Console y, luego, intercambia la clave de la cuenta de servicio por un token del portador que se pueda pasar al encabezado Authorization
de la solicitud HTTP.
Para recuperar la clave de tu cuenta de servicio, sigue estos pasos:
- En la consola de Google Cloud, ve a Menú \r > APIs y servicios > Credenciales > Crear credenciales > Clave de cuenta de servicio.
- En Cuenta de servicio, selecciona Nueva cuenta de servicio.
- Establece la cuenta de servicio en
service-account
. - Configura el Rol como Proyecto > Propietario.
- Establece el tipo de clave en JSON.
- Selecciona Crear.
- Se descargará una clave de cuenta de servicio JSON privada en tu máquina local.
En tu código de actualizaciones de pedido, intercambia tu clave de servicio por un token del portador mediante la biblioteca cliente de las APIs de Google y el alcance "https://www.googleapis.com/auth/actions.order.developer". Puedes encontrar pasos de instalación y ejemplos en la página de GitHub de la biblioteca cliente de la API.
Haz referencia a order-update.js
en nuestras muestras de Node.js y Java para un intercambio de claves de ejemplo.
Enviar actualizaciones del pedido
Una vez que hayas intercambiado la clave de tu cuenta de servicio por un token del portador de OAuth, envía actualizaciones de pedidos como solicitudes PATCH autorizadas a la API de pedidos.
URL de la API de Orders:
PATCH https://actions.googleapis.com/v3/orders/${orderId}
Proporciona los siguientes encabezados en tu solicitud:
"Authorization: Bearer token"
por el token del portador de OAuth por el que intercambiaste la clave de tu cuenta de servicio."Content-Type: application/json"
.
La solicitud PATCH debe tener un cuerpo JSON con el siguiente formato:
{ "orderUpdate": OrderUpdate }
El objeto OrderUpdate
consta de los siguientes campos de nivel superior:
updateMask
: Son los campos del pedido que estás actualizando. Para actualizar el estado de la reserva, establece el valor enreservation.status, reservation.userVisibleStatusLabel
.order
: Es el contenido de la actualización. Si actualizas el contenido de la reserva, establece el valor en el objetoOrder
actualizado. Si solo actualizas el estado de la reserva (por ejemplo, de"PENDING"
a"FULFILLED"
), el objeto contendrá los siguientes campos:merchantOrderId
: Es el mismo ID que configuraste en tu objetoOrder
.lastUpdateTime
: Es la marca de tiempo de esta actualización.purchase
: Es un objeto que contiene lo siguiente:status
: Es el estado del pedido comoReservationStatus
, como "CONFIRMED
" o "CANCELLED
".userVisibleStatusLabel
: Es una etiqueta visible para el usuario que proporciona detalles sobre el estado del pedido, como “Se confirmó tu reserva”.
userNotification
que se puede mostrar en el dispositivo del usuario cuando se envía esta actualización. Ten en cuenta que incluir este objeto no garantiza que aparezca una notificación en el dispositivo del usuario.
En el siguiente código de muestra, se presenta un OrderUpdate
de ejemplo que actualiza el estado del pedido de reserva a FULFILLED
:
// Import the 'googleapis' module for authorizing the request. const {google} = require('googleapis'); // Import the 'request' module for sending an HTTP POST request. const request = require('request'); // Import the OrderUpdate class from the Actions on Google client library. const {OrderUpdate} = require('actions-on-google'); // Import the service account key used to authorize the request. Replace the string path with a path to your service account key. const key = require('./service-account.json'); // Create a new JWT client for the Actions API using credentials from the service account key. let jwtClient = new google.auth.JWT( key.client_email, null, key.private_key, ['https://www.googleapis.com/auth/actions.order.developer'], null ); // Authorize the client asynchronously, passing in a callback to run upon authorization. jwtClient.authorize((err, tokens) => { if (err) { console.log(err); return; } // Declare the ID of the order to update. const orderId = '<UNIQUE_MERCHANT_ORDER_ID>'; const orderUpdateJson = new OrderUpdate({ updateMask: [ 'lastUpdateTime', 'contents.lineItems.reservation.status', 'contents.lineItems.reservation.userVisibleStatusLabel', ].join(','), order: { merchantOrderId: orderId, lastUpdateTime: new Date().toISOString(), contents: { lineItems: [ { reservation: { status: 'FULFILLED', userVisibleStatusLabel: 'Reservation fulfilled', }, } ] } }, reason: 'Reservation status was updated to fulfilled.', }); // Set up the PATCH request header and body, including the authorized token // and order update. const bearer = 'Bearer ' + tokens.access_token; const options = { method: 'PATCH', url: `https://actions.googleapis.com/v3/orders/${orderId}`, headers: { 'Authorization': bearer, }, body: { header: { 'isInSandbox': true, }, orderUpdate: orderUpdateJson, }, json: true, }; // Send the PATCH request to the Orders API. request.patch(options, (err, httpResponse, body) => { if (err) { console.log('There was an error...'); console.log(err); return; } }); });
// Create order update FieldMask fieldMask = FieldMask.newBuilder().addAllPaths(Arrays.asList( "lastUpdateTime", "contents.lineItems.reservation.status", "contents.lineItems.reservation.userVisibleStatusLabel")) .build(); OrderUpdateV3 orderUpdate = new OrderUpdateV3() .setOrder(new OrderV3() .setMerchantOrderId(orderId) .setLastUpdateTime(Instant.now().toString()) .setContents(new OrderContents() .setLineItems(Collections.singletonList(new LineItemV3() .setReservation(new ReservationItemExtension() .setStatus("FULFILLED") .setUserVisibleStatusLabel("Reservation fulfilled.")))))) .setUpdateMask(FieldMaskUtil.toString(fieldMask)) .setReason("Reservation status was updated to fulfilled."); // Setup JSON body containing order update JsonParser parser = new JsonParser(); JsonObject orderUpdateJson = parser.parse(new Gson().toJson(orderUpdate)).getAsJsonObject(); JsonObject body = new JsonObject(); body.add("orderUpdate", orderUpdateJson); JsonObject header = new JsonObject(); header.addProperty("isInSandbox", true); body.add("header", header); StringEntity entity = new StringEntity(body.toString()); entity.setContentType(ContentType.APPLICATION_JSON.getMimeType()); request.setEntity(entity); // Make request HttpClient httpClient = HttpClientBuilder.create().build(); HttpResponse response = httpClient.execute(request);
Establece el estado de la reserva
El elemento ReservationStatus
de una actualización de pedido debe describir el estado actual del pedido. En el campo order.ReservationStatus
de tu actualización, usa uno de los siguientes valores:
PENDING
: Tu acción "creó" la reserva, pero requiere procesamiento adicional en el backend.CONFIRMED
: La reserva se confirma en el backend de programación.CANCELLED
: El usuario canceló su reserva.FULFILLED
: El servicio completó la reserva del usuario.CHANGE_REQUESTED
: El usuario solicitó un cambio en la reserva y se está procesando el cambio.REJECTED
: si no pudiste procesar o confirmar la reserva.
Envía actualizaciones de los pedidos por cada estado que sea relevante para tu reserva. Por ejemplo, si tu reserva requiere procesamiento manual para confirmarla después de que se solicita, envía una actualización de pedido de PENDING
hasta que se complete el procesamiento adicional. No todas las reservas requieren todos los valores de estado.
Solución de problemas
Si tienes algún problema durante la prueba, lee nuestros pasos para solucionar problemas de transacciones.