Neste guia, mostramos o processo de desenvolvimento de um projeto do Actions que usa a API Orders para fazer reservas.
Fluxo de transações
Quando o projeto do Actions processa reservas, ele usa o seguinte fluxo:
- Validar os requisitos de transação (opcional): use o assistente de requisitos de transações no início da conversa para garantir que o usuário seja capaz de realizar uma transação.
- Criar o pedido: oriente o usuário sobre uma "montagem de carrinho" em que ele cria os detalhes da reserva.
- Propor o pedido: depois que o "carrinho" for concluído, proponha a "ordem" da reserva ao usuário, para que ele possa confirmar que está correto. Se a reserva for confirmada, você vai receber uma resposta com os detalhes dela.
- Finalizar o pedido e enviar um comprovante: depois de confirmar o pedido, atualize o sistema de reservas e envie um comprovante ao usuário.
- Enviar atualizações de pedidos: ao longo do tempo de vida útil da reserva, forneça ao usuário atualizações do status da reserva enviando solicitações PATCH para a API Orders.
Restrições e diretrizes de revisão
Lembre-se de que políticas adicionais se aplicam a ações que usam as transações e a API Orders. Pode levar até seis semanas para analisar as Ações com transações. Portanto, considere esse tempo ao planejar a programação de lançamentos. Para facilitar o processo de revisão, verifique se você está em conformidade com as políticas e diretrizes para transações antes de enviar sua Ação para análise.
Só é possível implantar ações que usam a API Orders nos seguintes países:
Austrália Brasil Canadá Indonésia |
Japão México Catar Rússia |
Singapura Suíça Tailândia Turquia Reino Unido Estados Unidos |
Criar o projeto
Para conferir exemplos abrangentes de conversas transacionais, confira nossas amostras de transações em Node.js e Java.
Configuração do projeto
Ao criar sua ação, é necessário especificar que você quer realizar transações no Console do Actions. Além disso, se você estiver usando a biblioteca de cliente Node.JS, configure o fulfillment para usar a versão mais recente da API Orders.
Para configurar o projeto e o fulfillment, faça o seguinte:
- Crie um novo projeto ou importe um existente.
- Navegue até Implantar > Informações do diretório.
Em Informações adicionais > Transações > marque a caixa que diz "Suas ações usam a API Transações para realizar transações de produtos físicos?".
Se você estiver usando a biblioteca de cliente Node.JS para criar o fulfillment da ação, abra o código de fulfillment e atualize a declaração do app para definir a flag
ordersv3
comotrue
. O snippet de código abaixo mostra um exemplo de declaração de app para a versão 3 do Pedidos.
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. Validar os requisitos da transação (opcional)
Experiência do usuário
Assim que o usuário indicar que quer configurar uma reserva, recomendamos acionar a
intent actions.intent.TRANSACTION_REQUIREMENTS_CHECK
para garantir que ele possa
solicitar uma reserva. Por exemplo, quando invocada, a Ação pode perguntar:
"Você quer reservar um lugar?" Se o usuário disser
"sim", solicite essa intent imediatamente. Isso garante
que eles possam prosseguir e corrija as configurações
que os impedem de continuar com a transação.
Solicitar a intent de verificação de requisitos de transações resulta em um dos seguintes resultados:
- Se os requisitos forem atendidos, seu fulfillment receberá uma intent com uma condição de sucesso e você poderá prosseguir com a criação do pedido do usuário.
Se um ou mais dos requisitos não estiverem, o fulfillment receberá a intent com uma condição de falha. Nesse caso, encerre a conversa ou saia do fluxo de reserva.
Se o usuário puder corrigir o erro, ele receberá automaticamente uma solicitação para resolver esses problemas no dispositivo. Se a conversa ocorre em uma superfície apenas de voz, como um alto-falante inteligente, ela é entregue ao smartphone do usuário.
Fulfillment
Para garantir que um usuário atenda
aos requisitos de transação, solicite o fulfillment da
intent actions.intent.TRANSACTION_REQUIREMENTS_CHECK
com um objeto
TransactionRequirementsCheckSpec.
Verificar os requisitos
Verifique se um usuário atende aos requisitos de reserva com a biblioteca de cliente:
conv.ask(new TransactionRequirements());
return getResponseBuilder(request) .add("Placeholder for transaction requirements text") .add(new TransactionRequirements()) .build();
Observe que o JSON abaixo descreve uma resposta do webhook.
{ "payload": { "google": { "expectUserResponse": true, "systemIntent": { "intent": "actions.intent.TRANSACTION_REQUIREMENTS_CHECK", "data": { "@type": "type.googleapis.com/google.actions.transactions.v3.TransactionRequirementsCheckSpec" } } } } }
Observe que o JSON abaixo descreve uma resposta do webhook.
{ "expectUserResponse": true, "expectedInputs": [ { "possibleIntents": [ { "intent": "actions.intent.TRANSACTION_REQUIREMENTS_CHECK", "inputValueData": { "@type": "type.googleapis.com/google.actions.transactions.v3.TransactionRequirementsCheckSpec" } } ] } ] }
Receber o resultado de uma verificação de requisitos
Depois de concluir a intent, o Google Assistente envia uma solicitação de fulfillment
com a intent actions.intent.TRANSACTION_REQUIREMENTS_CHECK
com o resultado
da verificação.
Para processar corretamente essa solicitação, declare uma intent do Dialogflow acionada pelo evento actions_intent_TRANSACTION_REQUIREMENTS_CHECK
. Quando acionada,
processe essa intent no fulfillment:
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();
Observe que o JSON abaixo descreve uma solicitação 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": "" }
Observe que o JSON abaixo descreve uma solicitação 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. Criar o pedido
Experiência do usuário
Quando você tiver as informações necessárias do usuário, crie uma experiência de "montagem do carrinho" que oriente o usuário a criar a reserva. Cada ação terá um fluxo de montagem do carrinho um pouco diferente, conforme apropriado para o serviço.
Em uma experiência básica de montagem de carrinho, um usuário seleciona opções de uma lista para adicionar à reserva, embora você possa projetar a conversa para simplificar a experiência do usuário. Por exemplo, crie uma experiência de montagem de carrinho que permita ao usuário agendar uma reserva mensal com uma simples pergunta de sim ou não. Também é possível apresentar ao usuário um carrossel ou um card de lista de reservas "recomendadas".
Recomendamos o uso de respostas avançadas para apresentar visualmente as opções do usuário, mas também é recomendável projetar a conversa de modo que o usuário possa criar o carrinho usando apenas a voz. Para algumas práticas recomendadas e exemplos de experiências de montagem de carrinho, consulte as Diretrizes de design de transações.
Fulfillment
Ao longo da conversa, reúna os detalhes da reserva que um usuário quer
comprar e crie um objeto Order
.
Seu Order
precisa conter pelo menos o seguinte:
buyerInfo
: informações sobre o usuário que agendou a reserva.transactionMerchant
: informações sobre o comerciante que está facilitando a reserva.contents
: os detalhes reais da reserva listados comolineItems
.
Consulte a documentação de resposta Order
para criar seu carrinho. Talvez seja necessário incluir campos diferentes, dependendo da reserva.
O exemplo de código abaixo mostra um pedido de reserva completo, incluindo campos opcionais:
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; }
Observe que o JSON abaixo descreve uma resposta do 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. Propor o pedido
Apresente seu pedido de reserva ao usuário para que ele confirme ou
rejeite. Solicite a intent actions.intent.TRANSACTION_DECISION
e forneça o Order
que você criou.
Experiência do usuário
Quando você solicita a intent actions.intent.TRANSACTION_DECISION
, o Google Assistente
inicia uma experiência integrada em que o Order
é
renderizado diretamente em um "card de visualização de carrinho". O usuário pode dizer "agendar reserva", recusar a transação ou pedir a mudança dos detalhes da reserva.
O usuário também pode solicitar alterações no pedido nesse momento. Nesse caso, verifique se o fulfillment pode processar solicitações de mudança de pedido após a conclusão da experiência de montagem do carrinho.
Fulfillment
Ao solicitar a intent
actions.intent.TRANSACTION_DECISION
, crie uma
TransactionDecision
que contenha o Order
e a orderOptions
O código abaixo mostra um exemplo de TransactionsDecision
para um 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();
Observe que o JSON abaixo descreve uma resposta do 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" } } } } } }
Observe que o JSON abaixo descreve uma resposta do 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" } } } ] } ] }
Processar a decisão do usuário
Depois que o usuário responder ao pedido proposto, o fulfillment receberá a intent
actions_intent_TRANSACTION_DECISION
com um argumento contendo um
TransactionDecisionValue
. Esse valor contém o seguinte:
transactionDecision
: a decisão do usuário em relação ao pedido proposta. Os valores possíveis sãoORDER_ACCEPTED
,ORDER_REJECTED
,CART_CHANGE_REQUESTED
eUSER_CANNOT_TRANSACT
.
Para processar essa solicitação, declare uma intent do Dialogflow acionada pelo evento actions_intent_TRANSACTION_DECISION
. Processe essa intent no
fulfillment:
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")); }
Observe que o JSON abaixo descreve uma solicitação 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": "" }
Observe que o JSON abaixo descreve uma solicitação 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. Finalizar a reserva e enviar um comprovante
Quando a intent actions.intent.TRANSACTION_DECISION
retornar com um
transactionDecision
de ORDER_ACCEPTED
, execute o
processamento necessário para programar a reserva (como persistir no
seu próprio banco de dados).
Envie uma resposta simples para não perder o ritmo da conversa. O usuário recebe um "cartão de recibo recolhido" junto com sua resposta.
Fulfillment
// 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();
Observe que o JSON abaixo descreve uma resposta do 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" } } } } ] } } } }
Observe que o JSON abaixo descreve uma resposta do 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 atualizações do pedido
O status da reserva muda ao longo do ciclo de vida. Envie as atualizações dos pedidos de reserva do usuário com solicitações PATCH HTTP à API Orders com o status e os detalhes do pedido.
Configurar solicitações assíncronas para a API Orders
As solicitações de atualização do pedido para a API Orders são autorizadas por um token de acesso. Para fazer o PATCH de uma atualização de pedido para a API Orders, faça o download de uma chave
de conta de serviço JSON associada ao seu projeto do Console do Actions e troque
a chave da conta de serviço por um token do portador que pode ser transmitido para o
cabeçalho Authorization
da solicitação HTTP.
Para recuperar a chave da conta de serviço, siga estas etapas:
- No Console do Google Cloud, acesse Menu ☰ > APIs e serviços > Credenciais > Criar credenciais > Chave da conta de serviço.
- Em Conta de serviço, selecione Nova conta de serviço.
- Defina a conta de serviço como
service-account
. - Defina o Papel como Projeto > Proprietário.
- Defina o tipo de chave como JSON.
- Selecione Criar.
- Uma chave privada da conta de serviço JSON será transferida por download para a máquina local.
No seu pedido de atualização de código, troque a chave de serviço por um token do portador usando a biblioteca de cliente de APIs do Google e o escopo "https://www.googleapis.com/auth/actions.order.developer". Você pode encontrar exemplos e etapas de instalação na página do GitHub da biblioteca de cliente da API.
Consulte order-update.js
nas nossas amostras de Node.js e Java para um exemplo de troca de chaves.
Enviar atualizações do pedido
Depois de trocar a chave da sua conta de serviço por um token do portador OAuth, envie atualizações de pedidos como solicitações PATCH autorizadas para a API Orders.
URL da API Orders:
PATCH https://actions.googleapis.com/v3/orders/${orderId}
Forneça os seguintes cabeçalhos na sua solicitação:
"Authorization: Bearer token"
pelo token do portador OAuth pelo qual você trocou a chave da conta de serviço."Content-Type: application/json"
.
A solicitação PATCH precisa ter um corpo JSON no seguinte formato:
{ "orderUpdate": OrderUpdate }
O objeto OrderUpdate
consiste nos seguintes campos de nível superior:
updateMask
: os campos do pedido que você está atualizando. Para atualizar o status da reserva, defina o valor comoreservation.status, reservation.userVisibleStatusLabel
.order
: o conteúdo da atualização. Se você estiver atualizando o conteúdo da reserva, defina o valor como o objetoOrder
atualizado. Se você estiver apenas atualizando o status da reserva (por exemplo, de"PENDING"
para"FULFILLED"
), o objeto conterá os seguintes campos:merchantOrderId
: o mesmo ID que você definiu no objetoOrder
.lastUpdateTime
: o carimbo de data/hora dessa atualização.purchase
: um objeto que contém o seguinte:status
: o status do pedido como umReservationStatus
, como "CONFIRMED
" ou "CANCELLED
".userVisibleStatusLabel
: um rótulo voltado ao usuário que fornece detalhes sobre o status do pedido, como "Sua reserva foi confirmada".
userNotification
que pode ser exibido no dispositivo do usuário quando essa atualização é enviada. A inclusão desse objeto não garante que uma notificação seja exibida no dispositivo do usuário.
A amostra de código a seguir mostra um exemplo de OrderUpdate
que atualiza o status do pedido de reserva para 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);
Definir o status da reserva
O ReservationStatus
de uma atualização de pedido
precisa descrever o estado atual dele. No campo order.ReservationStatus
da atualização, use um dos seguintes valores:
PENDING
: a reserva foi "criada" pela sua ação, mas requer um processamento adicional no seu back-end.CONFIRMED
: a reserva é confirmada no back-end da programação.CANCELLED
: o usuário cancelou a reserva.FULFILLED
: a reserva do usuário foi atendida pelo serviço.CHANGE_REQUESTED
: o usuário solicitou uma alteração na reserva, e a alteração está sendo processada.REJECTED
: se não foi possível processar ou confirmar a reserva.
Envie atualizações de pedido para cada status relevante para sua reserva. Por exemplo, se a reserva exigir processamento manual para confirmar a reserva após ser solicitada, envie uma atualização do pedido PENDING
até que o processamento adicional seja concluído. Nem todas as reservas exigem todos os valores de status.
Solução de problemas
Se você tiver problemas durante o teste, leia nossas etapas de solução de problemas para transações.