Como mapear feeds de cardápio e itens do carrinho de finalização de compra
Quando os clientes adicionam itens do seu feed de cardápio ao carrinho e finalizam a compra, o Google envia esses itens ao seu endpoint de fulfillment para verificar o preço e a disponibilidade. Depois que o preço e a disponibilidade forem validados, o cliente poderá fazer o pedido. Esta seção ilustra como mapear itens do feed de menu para itens do carrinho de fulfillment.
Os exemplos desta seção são versões simplificadas do feed de menu e do esquema de
carrinho. Somente os campos relevantes para ilustrar o mapeamento entre o feed de menu e o objeto do carrinho são mostrados. Para esquemas completos, consulte Menu
e Cart
.
Os itens no feed Menu
que são adicionados a um carrinho são enviados no objeto Cart
para a finalização da compra e o envio do pedido.
- Um
MenuItem
simples é representado como umLineItem
na matrizlineItems
, comofferId
sendo ooffer.id
do item de menu selecionado no feed do menu. - Um
MenuItem
com umMenuItemOption
obrigatório é representado como umLineItem
na matrizlineItems
, com oofferId
sendo ooffer.id
da opção de item de menu selecionada do feed de menu. - Um
AddOnMenuItem
de umLineItem
é representado como umFoodItemOption
na matrizoptions
doFoodItemExtension
. Cada opção tem umofferId
que corresponde aooffer.id
do item de menu do complemento selecionado no feed de menu. Um AddOnMenuItem também pode ter AddOnMenuItems aninhados representados comosubOptions
dentro de cada opção.
Os exemplos a seguir mapeiam itens de menu entre o feed de menu e o carrinho de envio.
Este exemplo contém uma lista de itens de menu simples.
Itens de menu em um feed de menu:
{ "@type": "Menu", "@id": "menu_id", "hasMenuItem": [ { "@type": "MenuItem", "@id": "menuitem_id_1", "offers": [ { "@type": "Offer", "@id": "menuitem_offer_id_1", "price": "p_1", "priceCurrency": "USD" } ] }, { "@type": "MenuItem", "@id": "menuitem_id_2", "offers": [ { "@type": "Offer", "@id": "menuitem_offer_id_2", "price": "p_2", "priceCurrency": "USD" } ] } ] }
Itens do cardápio mapeados para um carrinho de atendimento:
{ "@type": "Cart", "lineItems": [ { "offerId": "menuitem_offer_id_1", "price": { "amount": { "currencyCode": "USD", "units": "dollar(q_1*p_1)", "nanos": "cent(q_1*p_1)" } }, "quantity": "q_1" }, { "offerId": "menuitem_offer_id_2", "price": { "amount": { "currencyCode": "USD", "units": "dollar(q_2*p_2)", "nanos": "cent(q_2*p_2)" } }, "quantity": "q_2" } ] }
Este exemplo contém um item de menu com um ou mais AddOnMenuItems.
Itens de menu em um feed de menu:
{ "@type": "Menu", "@id": "menu_id", "hasMenuItem": [ { "@type": "MenuItem", "@id": "menuitem_id_1", "offers": [ { "@type": "Offer", "@id": "menuitem_offer_id_1", "price": "p_1", "priceCurrency": "USD" } ], "menuAddOn": [ { "@type": "MenuAddOnSection", "@id": "menuaddon_section_id_1", "hasMenuItem": [ { "@type": "AddOnMenuItem", "@id": "menuitem_addon_id_1", "offers": [ { "@type": "Offer", "@id": "menuitem_addon_offer_id_1", "price": "addon_p_1", "priceCurrency": "USD" } ] }, { "@type": "AddOnMenuItem", "@id": "menuitem_addon_id_2", "offers": [ { "@type": "Offer", "@id": "menuitem_addon_offer_id_2", "price": "addon_p_2", "priceCurrency": "USD" } ] } ] } ] }, { "@type": "MenuItem", "@id": "menuitem_id_2", "offers": [ { "@type": "Offer", "@id": "menuitem_offer_id_2", "price": "p_2", "priceCurrency": "USD" } ] } ] }
Itens do cardápio mapeados para um carrinho de atendimento:
{ "@type": "Cart", "lineItems": [ { "offerId": "menuitem_offer_id_1", "price": { "amount": { "currencyCode": "USD", "units": "dollar(q_1*(p_1 + addon_q_1*addon_p_1 + addon_q_2*addon_p_2))", "nanos": "cent(q_1*(p_1 + addon_q_1*addon_p_1 + addon_q_2*addon_p_2))" } }, "quantity": "q_1", "extension": { "@type": "FoodItemExtension", "options": [ { "offerId": "menuitem_addon_offer_id_1", "price": { "currencyCode": "USD", "units": "dollar(addon_q_1*addon_p_1)", "nanos": "cent(addon_q_1*addon_p_1)" }, "quantity": "addon_q_1" }, { "offerId": "menuitem_addon_offer_id_2", "price": { "currencyCode": "USD", "units": "dollar(addon_q_2*addon_p_2)", "nanos": "cent(addon_q_2*addon_p_2)" }, "quantity": "addon_q_2" } ] } }, { "offerId": "menuitem_offer_id_2", "price": { "amount": { "currencyCode": "USD", "units": "dollar(q_2*p_2)", "nanos": "cent(q_2*p_2)" } }, "quantity": "q_2" } ] }
Este exemplo contém um item de menu com opções de item de menu, AddOnMenuItems e AddOnMenuItems aninhados
Itens de menu em um feed de menu:
{ "@type": "MenuItem", "@id": "menuitem_id_1", "hasMenuItemOptions": [ { "@type": "MenuItemOption", "value": { "@type": "PropertyValue", "name": "OPTION", "value": "Large", "offers": [ { "@type": "Offer", "@id": "menuitem_option_offer_id_1", "price": "p_1", "priceCurrency": "USD" } ], "menuAddOn": [ { "@type": "AddOnMenuSection", "@id": "menuitem_option_addon_section_id_1", "hasMenuItem": [ { "@type": "AddOnMenuItem", "@id": "menuitem_option_addon_id_1", "offers": [ { "@type": "Offer", "@id": "menuitem_option_addon_offer_id_1", "price": "addon_p_1", "priceCurrency": "USD" } ] }, { "@type": "AddOnMenuItem", "@id": "menuitem_option_addon_id_2", "offers": [ { "@type": "Offer", "@id": "menuitem_option_addon_offer_id_2", "price": "addon_p_2", "priceCurrency": "USD" } ], "menuAddOn": [ { "@type": "AddOnMenuSection", "@id": "menuitem_option_subaddon_section_id_1", "hasMenuItem": [ { "@type": "AddOnMenuItem", "@id": "menuitem_option_subaddon_id_1", "offers": [ { "@type": "Offer", "@id": "menuitem_option_subaddon_offer_id_1", "price": "subaddon_p_1", "priceCurrency": "USD" } ] } ] } ] } ] } ] } } ] }
Itens do cardápio mapeados para um carrinho de atendimento:
{ "@type": "Cart", "lineItems": [ { "offerId": "menuitem_option_offer_id_1", "price": { "amount": { "currencyCode": "USD", "units": "dollar(q_1*(p_1 + addon_q_1*addon_p_1 + addon_q_2*(addon_p_2 + subaddon_q_1*subaddon_p_1)))", "nanos": "cent(q_1*(p_1 + addon_q_1*addon_p_1 + addon_q_2*(addon_p_2 + subaddon_q_1*subaddon_p_1)))" } }, "quantity": "q_1", "extension": { "@type": "FoodItemExtension", "options": [ { "offerId": "menuitem_option_addon_offer_id_1", "price": { "currencyCode": "USD", "units": "dollar(addon_q_1*addon_p_1)", "nanos": "cent(addon_q_1*addon_p_1)" }, "quantity": "addon_q_1" }, { "offerId": "menuitem_option_addon_offer_id_2", "price": { "currencyCode": "USD", "units": "dollar(addon_q_2*(addon_p_2 + subaddon_q_1*subaddon_p_1))", "nanos": "cent(addon_q_2*(addon_p_2 + subaddon_q_1*subaddon_p_1))" }, "quantity": "addon_q_2", "subOptions": [ { "offerId": "menuitem_option_subaddon_offer_id_1", "price": { "currencyCode": "USD", "units": "dollar(subaddon_q_1*subaddon_p_1)", "nanos": "cent(subaddon_q_1*subaddon_p_1)" }, "quantity": "subaddon_q_1" } ] } ] } } ] }
Como processar os erros
Se você tiver problemas ao processar um CheckoutRequestMessage
, responda com um CheckoutResponseMessage
que contenha um
FoodErrorExtension
em vez de uma CheckoutResponse. É possível usar essa
resposta para identificar um ou mais erros que ocorreram durante o processamento.
Há duas maneiras de processar os erros:
- Erros recuperáveis: o usuário não precisa editar o carrinho para enviar o
pedido. Por exemplo, se você determinar que um item no
Cart
tem uma mudança de preço, responda com umFoodOrderError
do tipo de erroPRICE_CHANGED
, junto comcorrectedProposedOrder
epaymentOptions
. O Google informa o usuário sobre a mudança, mas permite que ele envie com ocorrectedProposedOrder
. O usuário também pode voltar e editar o carrinho, se quiser. Você vai receber um novoCheckoutRequestMessage
ou umSubmitOrderRequestMessage
. - Erros irrecuperáveis: o usuário precisa editar o carrinho antes
de enviar o pedido. Por exemplo, se você determinar que o restaurante está
fechado, responda com um
FoodOrderError
do tipo de erroCLOSED
. O Google informa o usuário e gerencia a interação para atualizar para um novo restaurante. Você vai receber um novoCheckoutRequestMessage
para um novo carrinho.
Em geral, torne os erros no nível do carrinho irrecuperáveis e os erros no nível do item
recuperáveis. Para uma lista completa de tipos de erros e significados, consulte
FoodOrderError
.
Como lidar com mudanças de preço
Mudanças de preço durante a finalização da compra
Se você encontrar um problema de preço ao processar a solicitação de finalização de compra de um cliente, faça o seguinte:
- Responda ao
CheckoutRequestMessage
com umCheckoutResponseMessage
que contenha umFoodErrorExtension
, conforme descrito em Como lidar com erros. - Na resposta de erro, use
correctedProposedOrder.cart
para atualizar o preço para o valor correto. O Google recebe o pedido corrigido e pode emitir um novoCheckoutRequestMessage
.
Após a finalização da compra, o Google mostra uma página de confirmação do pedido para o usuário final,
independente de a ProposedOrder
ter sido alterada ou não.
Se a ordem proposta foi corrigida, o Google poderá mostrar outros avisos para
informar o usuário sobre as mudanças. Se o usuário concordar em fazer o pedido, não
haverá mais solicitações de finalização de compra. O fluxo continua para o envio do pedido, com
o ProposedOrder
corrigido.
No entanto, o usuário pode mudar de ideia e editar o carrinho novamente. Quando
o carrinho é atualizado dessa forma, o Google envia uma nova CheckoutRequestMessage
.
Mudanças de preço durante o envio do pedido
Se você encontrar um problema de preço durante o processamento do envio de pedidos
(a intent actions.intent.TRANSACTION_DECISION
foi acionada), não responda
com um erro nem atualize o preço na sua resposta. Se os preços, quantidades
ou outros detalhes no SubmitOrderRequestMessage
não corresponderem aos
seus dados, responda com o orderState
definido como REJECTED
para indicar que o
pedido não pode ser feito conforme solicitado.
Em seguida, se os detalhes do pedido e do pagamento forem válidos, defina orderState
como CREATED
ou CONFIRMED
. Além disso, inclua um actionOrderId
para representar o ID do pedido no
seu sistema. Esse ID precisa ser usado ao enviar atualizações subsequentes.
Se você não conseguir processar o pagamento e já tiver enviado o
SubmitOrderRequestMessage
, envie uma
AsyncOrderUpdateRequestMessage
com orderState
definido como REJECTED
para informar
ao usuário que o pedido não será concluído.
Mudanças de preço após o envio do pedido
Se você determinar que um preço foi alterado em relação ao preço usado quando um cliente
enviou o pedido, poderá emitir uma AsyncOrderUpdateRequestMessage
, conforme
descrito em Como implementar atualizações de pedidos assíncronos, com o novo preço.
Para atualizar os preços usando atualizações de pedidos assíncronas:
- Mude o preço em
lineItemUpdates[x].price
. Esse valor reflete o custo total do item, incluindo complementos e multiplicado pela quantidade. Para mais informações, consulte a descrição do campoprice
deLineItem
. - Insira uma explicação em
lineItemUpdates[x].reason
. - Defina
lineItemUpdates[x].orderState
comoCONFIRMED
.
Você pode tentar cobrar o instrumento de pagamento antes ou depois de enviar a
AsyncOrderUpdateRequestMessage, a seu critério. Se a transação falhar
(talvez porque o delta de preço seja muito alto), envie uma
AsyncOrderUpdateRequestMessage com as seguintes configurações no
OrderUpdate
para informar o Google sobre a falha:
- Defina
orderState
comoREJECTED
. - Descreva a falha no campo
label
.
Validação de finalização de compra
Conforme discutido na Etapa 4: implementar o checkout, o
endpoint de fulfillment precisa realizar a validação em cada CheckoutRequestMessage
de entrada e responder com um CheckoutResponseMessage
.
Confira um exemplo de CheckoutResponseMessage
para uma validação
bem-sucedida:
Caso de uso | Como fazer a implementação |
---|---|
Caso de uso 1: a validação foi concluída | Retorne o CheckoutResponse . Ela precisa ter
ProposedOrder e PaymentOptions .
ProposedOrder inclui tributos, taxas e o preço total do
carrinho. |
{ "expectUserResponse": false, "finalResponse": { "richResponse": { "items": [ { "structuredResponse": { "checkoutResponse": { "proposedOrder": { "id": "sample_proposed_order_id_1", "otherItems": [ { "name":"New customer discount", "price": { "type":"ESTIMATE", "amount": { "currencyCode":"USD", "units":"-5", "nanos": -500000000 } }, "type": "DISCOUNT" }, { "name": "Delivery fee", "price": { "type": "ESTIMATE", "amount": { "currencyCode": "USD", "units": "3", "nanos": 500000000 } }, "type": "DELIVERY" }, { "name": "Tax", "price": { "type": "ESTIMATE", "amount": { "currencyCode": "USD", "units": "1", "nanos": 500000000 } }, "type": "TAX" } ], "cart": { "merchant": { "id": "https://www.exampleprovider.com/merchant/id1", "name": "Falafel Bite" }, "lineItems": [ { "name": "Pita Chips", "type": "REGULAR", "id": "sample_item_offer_id_1", "offerId": "https://www.exampleprovider.com/menu/item/offer/id1", "quantity": 1, "price": { "type": "ESTIMATE", "amount": { "currencyCode": "USD", "units": "2", "nanos": 750000000 } }, "subLines": [ { "note": "Notes for this item." } ], "extension": { "@type": "type.googleapis.com/google.actions.v2.orders.FoodItemExtension", "options": [ { "id": "sample_addon_offer_id_1", "offerId": "https://www.exampleprovider.com/menu/item/addon/offer/id1", "name": "Honey Mustard", "price": { "currencyCode": "USD" }, "quantity": 1 }, { "id": "sample_addon_offer_id_2", "offerId": "https://www.exampleprovider.com/menu/item/addon/offer/id2", "name": "BBQ Sauce", "price": { "currencyCode": "USD", "nanos": 500000000 }, "quantity": 1 } ] } }, { "name": "Chicken Shwarma Wrap", "type": "REGULAR", "id": "sample_item_offer_id_2", "offerId": "https://www.exampleprovider.com/menu/item/offer/id2", "quantity": 1, "price": { "type": "ESTIMATE", "amount": { "currencyCode": "USD", "units": "8" } }, "extension": { "@type": "type.googleapis.com/google.actions.v2.orders.FoodItemExtension" } }, { "name": "Greek Salad", "type": "REGULAR", "id": "sample_item_offer_id_3", "offerId": "https://www.exampleprovider.com/menu/item/offer/id3", "quantity": 1, "price": { "type": "ESTIMATE", "amount": { "currencyCode": "USD", "units": "9", "nanos": 990000000 } }, "extension": { "@type": "type.googleapis.com/google.actions.v2.orders.FoodItemExtension" } }, { "name": "Prawns Biryani", "type": "REGULAR", "id": "sample_item_offer_id_4", "offerId": "https://www.exampleprovider.com/menu/item/offer/id4", "quantity": 1, "price": { "type": "ESTIMATE", "amount": { "currencyCode": "USD", "units": "15", "nanos": 990000000 } }, "extension": { "@type": "type.googleapis.com/google.actions.v2.orders.FoodItemExtension" } } ], "extension": { "@type": "type.googleapis.com/google.actions.v2.orders.FoodCartExtension", "fulfillmentPreference": { "fulfillmentInfo": { "delivery": { "deliveryTimeIso8601": "P90M" } } }, "location": { "coordinates": { "latitude": 37.788783, "longitude": -122.41384 }, "formattedAddress": "1350 CHARLESTON ROAD, MOUNTAIN VIEW, CA, United States", "zipCode": "94043", "city": "Mountain View", "postalAddress": { "regionCode": "US", "postalCode": "94043", "administrativeArea": "CA", "locality": "Mountain View", "addressLines": [ "1350 Charleston Road" ] }, "notes": "Gate code is #111" } } }, "totalPrice": { "type": "ESTIMATE", "amount": { // Represents $36.73 "currencyCode": "USD", "units": "36", "nanos": 730000000 } }, "extension": { "@type": "type.googleapis.com/google.actions.v2.orders.FoodOrderExtension", "availableFulfillmentOptions": [ { "fulfillmentInfo": { "delivery": { "deliveryTimeIso8601": "P90M" } }, "expiresAt": "2017-07-17T12:30:00Z" } ] } }, "paymentOptions": { "googleProvidedOptions": { "tokenizationParameters": { "tokenizationType": "PAYMENT_GATEWAY", "parameters": { "gateway": "stripe", "stripe:publishableKey": "pk_live_stripe_client_key", "stripe:version": "2017-04-06" } }, "supportedCardNetworks": [ "AMEX", "DISCOVER", "MASTERCARD", "JCB", "VISA" ], "prepaidCardDisallowed": true } } } } } ] } } }
Validação do endereço de entrega
O endpoint de fulfillment precisa validar o endereço de entrega contido em cada
CheckoutRequestMessage
.
Se houver um problema com o endereço de entrega, como estar fora do alcance do
serviço de entrega, o CheckoutResponseMessage
retornado pelo
atendimento precisa conter um FoodOrderError
do tipo correto.
Caso de uso | Como fazer a implementação |
---|---|
Caso de uso 1:a validação falhou porque o endereço de entrega está fora do intervalo ou há um problema com ele | Retorne FoodErrorExtension com FoodOrderError
do tipo de erro OUT_OF_SERVICE_AREA . |
{ "expectUserResponse": false, "finalResponse": { "richResponse": { "items": [ { "structuredResponse": { "error": { "@type": "type.googleapis.com/google.actions.v2.orders.FoodErrorExtension", "foodOrderErrors": [ { "error": "OUT_OF_SERVICE_AREA", "description": "Sorry, the restaurant cannot deliver to your address." } ] } } } ] } } }
Validação do valor mínimo do pedido
O endpoint de fulfillment precisa validar o valor mínimo do pedido de cada
CheckoutRequestMessage
.
Se o valor mínimo do pedido não for atendido, o CheckoutResponseMessage
retornado pelo fulfillment precisará conter um FoodOrderError
do tipo de erro
REQUIREMENTS_NOT_MET
.
Caso de uso | Como fazer a implementação |
---|---|
Caso de uso 1:a validação falhou porque o valor mínimo do pedido não foi atendido | Retorne FoodErrorExtension com FoodOrderError
do tipo de erro REQUIREMENTS_NOT_MET . |
{ "expectUserResponse": false, "finalResponse": { "richResponse": { "items": [ { "structuredResponse": { "error": { "@type": "type.googleapis.com/google.actions.v2.orders.FoodErrorExtension", "foodOrderErrors": [ { "error": "REQUIREMENTS_NOT_MET", "description": "The cart subtotal must be over $20." } ] } } } ] } } }
Validação da janela de ordenação
O endpoint de fulfillment precisa validar todos os fatores que possam afetar o
período de pedidos de cada CheckoutRequestMessage
.
Por exemplo, se o restaurante estiver fechado ou não estiver mais aceitando pedidos no
momento, o CheckoutResponseMessage
retornado pela entrega precisa
conter um FoodOrderError
do tipo de erro CLOSED
ou NO_CAPACITY
,
respectivamente.
Caso de uso | Como fazer a implementação |
---|---|
Caso de uso 1:a validação falhou porque o restaurante está fechado ou não tem mais suporte | Retorne FoodErrorExtension com FoodOrderError
do tipo de erro CLOSED . |
Caso de uso 2:a validação falhou porque o restaurante está ocupado e não está aceitando pedidos no momento | Retorne FoodErrorExtension com FoodOrderError
do tipo de erro NO_CAPACITY . |
{ "expectUserResponse": false, "finalResponse": { "richResponse": { "items": [ { "structuredResponse": { "error": { "@type": "type.googleapis.com/google.actions.v2.orders.FoodErrorExtension", "foodOrderErrors": [ { "error": "CLOSED", "description": "The restaurant is closed." } ] } } } ] } } }
{ "expectUserResponse": false, "finalResponse": { "richResponse": { "items": [ { "structuredResponse": { "error": { "@type": "type.googleapis.com/google.actions.v2.orders.FoodErrorExtension", "foodOrderErrors": [ { "error": "NO_CAPACITY", "description": "Sorry, the restaurant is busy at the moment." } ] } } } ] } } }
Validação dos itens do carrinho
Seu endpoint de fulfillment precisa validar o preço e a disponibilidade de cada
item do carrinho contido em um CheckoutRequestMessage
.
Se a disponibilidade ou o preço mudar, o CheckoutResponseMessage
retornado pelo fulfillment precisará conter um FoodOrderError
do tipo de erro
AVAILABILITY_CHANGED
ou PRICE_CHANGED
, respectivamente.
Caso de uso | Como fazer a implementação |
---|---|
Caso de uso 1:a validação falhou porque alguns itens do menu e/ou as personalizações deles não são válidos ou estão esgotados | Retorne FoodErrorExtension com correctedProposedOrder ,
PaymentOptions e FoodOrderError do tipo de erro
AVAILABILITY_CHANGED . Os itens inválidos precisam ser removidos de
CorrectedProposedOrder . |
Caso de uso 2:a validação falhou porque alguns itens do menu e/ou as personalizações deles não são válidos ou estão esgotados. O carrinho corrigido não atende mais ao requisito de valor mínimo do pedido. | Retorne FoodErrorExtension com FoodOrderError
dos tipos de erro AVAILABILITY_CHANGED e
REQUIREMENTS_NOT_MET . |
Caso de uso 3:a validação falhou porque alguns itens do menu e/ou preços de personalização foram alterados. | Retorne FoodErrorExtension com correctedProposedOrder ,
PaymentOptions e FoodOrderError do tipo de erro
PRICE_CHANGED . Os preços desatualizados precisam ser atualizados em
CorrectedProposedOrder . |
Caso de uso 4:a validação falhou porque alguns itens do menu e/ou preços de personalização foram alterados. O carrinho corrigido não atende mais ao requisito de valor mínimo do pedido | Retorne FoodErrorExtension com FoodOrderError
dos tipos de erro PRICE_CHANGED e
REQUIREMENTS_NOT_MET . |
{ "expectUserResponse": false, "finalResponse": { "richResponse": { "items": [ { "structuredResponse": { "error": { "@type": "type.googleapis.com/google.actions.v2.orders.FoodErrorExtension", "foodOrderErrors": [ { "error": "AVAILABILITY_CHANGED", "id": "sample_item_offer_id_1", "description": "The item is no longer available." }, { "error": "AVAILABILITY_CHANGED", "id": "sample_item_offer_id_2", "description": "The item is no longer available." } ], "correctedProposedOrder": { "id": "sample_corrected_proposed_order_id_1", "otherItems": [ { "name":"New customer discount", "price": { "type":"ESTIMATE", "amount": { "currencyCode":"USD", "units":"-5", "nanos": -500000000 } }, "type": "DISCOUNT" }, { "name": "Delivery fee", "price": { "type": "ESTIMATE", "amount": { "currencyCode": "USD", "units": "3", "nanos": 500000000 } }, "type": "DELIVERY" }, { "name": "Tax", "price": { "type": "ESTIMATE", "amount": { "currencyCode": "USD", "units": "1", "nanos": 500000000 } }, "type": "TAX" } ], "cart": { "merchant": { "id": "https://www.exampleprovider.com/merchant/id1", "name": "Falafel Bite" }, "lineItems": [ { "name": "Greek Salad", "type": "REGULAR", "id": "sample_item_offer_id_3", "offerId": "https://www.exampleprovider.com/menu/item/offer/id3", "quantity": 1, "price": { "type": "ESTIMATE", "amount": { "currencyCode": "USD", "units": "9", "nanos": 990000000 } }, "extension": { "@type": "type.googleapis.com/google.actions.v2.orders.FoodItemExtension" } }, { "name": "Prawns Biryani", "type": "REGULAR", "id": "sample_item_offer_id_4", "offerId": "https://www.exampleprovider.com/menu/item/offer/id4", "quantity": 1, "price": { "type": "ESTIMATE", "amount": { "currencyCode": "USD", "units": "15", "nanos": 990000000 } }, "extension": { "@type": "type.googleapis.com/google.actions.v2.orders.FoodItemExtension" } } ], "extension": { "@type": "type.googleapis.com/google.actions.v2.orders.FoodCartExtension", "fulfillmentPreference": { "fulfillmentInfo": { "delivery": { "deliveryTimeIso8601": "P90M" } } }, "location": { "coordinates": { "latitude": 37.788783, "longitude": -122.41384 }, "formattedAddress": "1350 CHARLESTON ROAD, MOUNTAIN VIEW, CA, United States", "zipCode": "94043", "city": "Mountain View", "postalAddress": { "regionCode": "US", "postalCode": "94043", "administrativeArea": "CA", "locality": "Mountain View", "addressLines": [ "1350 Charleston Road" ] }, "notes": "Gate code is #111" } } }, "totalPrice": { "type": "ESTIMATE", "amount": { "currencyCode": "USD", "units": "36", "nanos": 730000000 } }, "extension": { "@type": "type.googleapis.com/google.actions.v2.orders.FoodOrderExtension", "availableFulfillmentOptions": [ { "fulfillmentInfo": { "delivery": { "deliveryTimeIso8601": "P90M" } }, "expiresAt": "2017-07-17T12:30:00Z" } ] } }, "paymentOptions": { "googleProvidedOptions": { "tokenizationParameters": { "tokenizationType": "PAYMENT_GATEWAY", "parameters": { "gateway": "stripe", "stripe:publishableKey": "pk_live_stripe_client_key", "stripe:version": "2017-04-06" } }, "supportedCardNetworks": [ "AMEX", "DISCOVER", "MASTERCARD", "JCB", "VISA" ], "prepaidCardDisallowed": true } } } } } ] } } }
{ "expectUserResponse": false, "finalResponse": { "richResponse": { "items": [ { "structuredResponse": { "error": { "@type": "type.googleapis.com/google.actions.v2.orders.FoodErrorExtension", "foodOrderErrors": [ { "error": "REQUIREMENTS_NOT_MET", "description": "The cart subtotal must be over $20." }, { "error": "AVAILABILITY_CHANGED", "id": "cart_lineitem_id" "description": "cart_lineitem_id is no longer available." } ] } } } ] } } }
{ "expectUserResponse": false, "finalResponse": { "richResponse": { "items": [ { "structuredResponse": { "error": { "@type": "type.googleapis.com/google.actions.v2.orders.FoodErrorExtension", "foodOrderErrors": [ { "error": "PRICE_CHANGED", "id": "sample_item_offer_id_1", "description": "The price has changed.", "updatedPrice": { "currencyCode": "USD", "units": "2", "nanos": 750000000 } }, { "error": "PRICE_CHANGED", "id": "sample_item_offer_id_2", "description": "The price has changed.", "updatedPrice": { "currencyCode": "USD", "units": "8" } } ], "correctedProposedOrder": { "id": "sample_corrected_proposed_order_id_1", "otherItems": [ { "name":"New customer discount", "price": { "type":"ESTIMATE", "amount": { "currencyCode":"USD", "units":"-5", "nanos": -500000000 } }, "type": "DISCOUNT" }, { "name": "Delivery fee", "price": { "type": "ESTIMATE", "amount": { "currencyCode": "USD", "units": "3", "nanos": 500000000 } }, "type": "DELIVERY" }, { "name": "Tax", "price": { "type": "ESTIMATE", "amount": { "currencyCode": "USD", "units": "1", "nanos": 500000000 } }, "type": "TAX" } ], "cart": { "merchant": { "id": "https://www.exampleprovider.com/merchant/id1", "name": "Falafel Bite" }, "lineItems": [ { "name": "Pita Chips", "type": "REGULAR", "id": "sample_item_offer_id_1", "offerId": "https://www.exampleprovider.com/menu/item/offer/id1", "quantity": 1, "price": { "type": "ESTIMATE", "amount": { "currencyCode": "USD", "units": "2", "nanos": 750000000 } }, "subLines": [ { "note": "Notes for this item." } ], "extension": { "@type": "type.googleapis.com/google.actions.v2.orders.FoodItemExtension", "options": [ { "id": "sample_addon_offer_id_1", "offerId": "https://www.exampleprovider.com/menu/item/addon/offer/id1", "name": "Honey Mustard", "price": { "currencyCode": "USD" }, "quantity": 1 }, { "id": "sample_addon_offer_id_2", "offerId": "https://www.exampleprovider.com/menu/item/addon/offer/id2", "name": "BBQ Sauce", "price": { "currencyCode": "USD", "nanos": 500000000 }, "quantity": 1 } ] } }, { "name": "Chicken Shwarma Wrap", "type": "REGULAR", "id": "sample_item_offer_id_2", "offerId": "https://www.exampleprovider.com/menu/item/offer/id2", "quantity": 1, "price": { "type": "ESTIMATE", "amount": { "currencyCode": "USD", "units": "8" } }, "extension": { "@type": "type.googleapis.com/google.actions.v2.orders.FoodItemExtension" } }, { "name": "Greek Salad", "type": "REGULAR", "id": "sample_item_offer_id_3", "offerId": "https://www.exampleprovider.com/menu/item/offer/id3", "quantity": 1, "price": { "type": "ESTIMATE", "amount": { "currencyCode": "USD", "units": "9", "nanos": 990000000 } }, "extension": { "@type": "type.googleapis.com/google.actions.v2.orders.FoodItemExtension" } }, { "name": "Prawns Biryani", "type": "REGULAR", "id": "sample_item_offer_id_4", "offerId": "https://www.exampleprovider.com/menu/item/offer/id4", "quantity": 1, "price": { "type": "ESTIMATE", "amount": { "currencyCode": "USD", "units": "15", "nanos": 990000000 } }, "extension": { "@type": "type.googleapis.com/google.actions.v2.orders.FoodItemExtension" } } ], "extension": { "@type": "type.googleapis.com/google.actions.v2.orders.FoodCartExtension", "fulfillmentPreference": { "fulfillmentInfo": { "delivery": { "deliveryTimeIso8601": "P90M" } } }, "location": { "coordinates": { "latitude": 37.788783, "longitude": -122.41384 }, "formattedAddress": "1350 CHARLESTON ROAD, MOUNTAIN VIEW, CA, United States", "zipCode": "94043", "city": "Mountain View", "postalAddress": { "regionCode": "US", "postalCode": "94043", "administrativeArea": "CA", "locality": "Mountain View", "addressLines": [ "1350 Charleston Road" ] }, "notes": "Gate code is #111" } } }, "totalPrice": { "type": "ESTIMATE", "amount": { "currencyCode": "USD", "units": "36", "nanos": 730000000 } }, "extension": { "@type": "type.googleapis.com/google.actions.v2.orders.FoodOrderExtension", "availableFulfillmentOptions": [ { "fulfillmentInfo": { "delivery": { "deliveryTimeIso8601": "P90M" } }, "expiresAt": "2017-07-17T12:30:00Z" } ] } }, "paymentOptions": { "googleProvidedOptions": { "tokenizationParameters": { "tokenizationType": "PAYMENT_GATEWAY", "parameters": { "gateway": "stripe", "stripe:publishableKey": "pk_live_stripe_client_key", "stripe:version": "2017-04-06" } }, "supportedCardNetworks": [ "AMEX", "DISCOVER", "MASTERCARD", "JCB", "VISA" ], "prepaidCardDisallowed": true } } } } } ] } } }
{ "expectUserResponse": false, "finalResponse": { "richResponse": { "items": [ { "structuredResponse": { "error": { "@type": "type.googleapis.com/google.actions.v2.orders.FoodErrorExtension", "foodOrderErrors": [ { "error": "REQUIREMENTS_NOT_MET", "description": "The cart subtotal must be over $20." }, { "error": "PRICE_CHANGED", "id": "cart_lineitem_id" "description": "cart_lineitem_id price has been updated." "updatedPrice": { "currencyCode": "USD", "units": "2", "nanos": 750000000 } } ] } } } ] } } }
Enviar validação do pedido
Conforme discutido na Etapa 7: implementar o envio de pedidos, o
endpoint de fulfillment precisa realizar a validação em cada SubmitOrderRequestMessage
de entrada e responder com um
SubmitOrderResponseMessage
.
Confira um exemplo de SubmitOrderResponseMessage
para uma validação
bem-sucedida:
Caso de uso | Como fazer a implementação |
---|---|
Caso de uso 1:o pedido é criado | Um SubmitOrderResponseMessage com o estado de ordem
CREATED . Ele precisa ter actionOrderId ,
userVisibleId , orderManagementActions e
estimatedFulfillmentTime . |
Caso de uso 2:o pedido é rejeitado devido a problemas de pagamento | Um SubmitOrderResponseMessage com o estado de ordem
REJECTED . Ele precisa ter actionOrderId ,
userVisibleId , orderManagementActions e
rejectionInfo do tipo PAYMENT_DECLINED . |
Caso de uso 3:o pedido é rejeitado porque o usuário foi sinalizado como banido | Um SubmitOrderResponseMessage com o status de pedido
REJECTED . Ele precisa ter actionOrderId ,
userVisibleId , orderManagementActions e
rejectionInfo do tipo INELIGIBLE . |
Caso de uso 4:o pedido é rejeitado porque as informações do usuário estão incompletas ou inválidas | Um SubmitOrderResponseMessage com o estado de ordem
REJECTED . Ele precisa ter actionOrderId ,
userVisibleId , orderManagementActions e
rejectionInfo do tipo INELIGIBLE . |
Caso de uso 5:o pedido é rejeitado por um motivo desconhecido | Um SubmitOrderResponseMessage com o estado de ordem
REJECTED . Ele precisa ter actionOrderId ,
userVisibleId , orderManagementActions e
rejectionInfo do tipo UNKNOWN . |
{ "expectUserResponse": false, "finalResponse": { "richResponse": { "items": [ { "structuredResponse": { "orderUpdate": { "actionOrderId": "sample_action_order_id", "orderState": { "state": "CREATED", "label": "Order received" }, "updateTime": "2017-05-10T02:30:00.000Z", "orderManagementActions": [ { "type": "CUSTOMER_SERVICE", "button": { "title": "Contact customer service", "openUrlAction": { "url": "mailto:support@example.com" } } }, { "type": "EMAIL", "button": { "title": "Email restaurant", "openUrlAction": { "url": "mailto:person@example.com" } } }, { "type": "CALL", "button": { "title": "Call restaurant", "openUrlAction": { "url": "tel:+16505554679" } } }, { "type": "VIEW_DETAILS", "button": { "title": "View order", "openUrlAction": { "url": "https://orderview.partner.com?orderid=sample_action_order_id" } } } ] } } } ] } } }
{ "expectUserResponse": false, "finalResponse": { "richResponse": { "items": [ { "structuredResponse": { "orderUpdate": { "actionOrderId": "sample_action_order_id", "orderState": { "state": "REJECTED", "label": "Order rejected" }, "updateTime": "2017-05-10T02:30:00.000Z", "rejectionInfo": { "type": "PAYMENT_DECLINED", "reason": "Insufficient funds" }, "orderManagementActions": [ { "type": "CUSTOMER_SERVICE", "button": { "title": "Contact customer service", "openUrlAction": { "url": "mailto:support@example.com" } } }, { "type": "EMAIL", "button": { "title": "Email restaurant", "openUrlAction": { "url": "mailto:person@example.com" } } }, { "type": "CALL", "button": { "title": "Call restaurant", "openUrlAction": { "url": "tel:+16505554679" } } }, { "type": "VIEW_DETAILS", "button": { "title": "View order", "openUrlAction": { "url": "https://orderview.partner.com?orderid=sample_action_order_id" } } } ] } } } ] } } }
{ "expectUserResponse": false, "finalResponse": { "richResponse": { "items": [ { "structuredResponse": { "orderUpdate": { "actionOrderId": "sample_action_order_id", "orderState": { "state": "REJECTED", "label": "Order rejected" }, "updateTime": "2017-05-10T02:30:00.000Z", "rejectionInfo": { "type": "INELIGIBLE", "reason": "Sorry, we are not able to take orders from this user" }, "orderManagementActions": [ { "type": "CUSTOMER_SERVICE", "button": { "title": "Contact customer service", "openUrlAction": { "url": "mailto:support@example.com" } } }, { "type": "EMAIL", "button": { "title": "Email restaurant", "openUrlAction": { "url": "mailto:person@example.com" } } }, { "type": "CALL", "button": { "title": "Call restaurant", "openUrlAction": { "url": "tel:+16505554679" } } }, { "type": "VIEW_DETAILS", "button": { "title": "View order", "openUrlAction": { "url": "https://orderview.partner.com?orderid=sample_action_order_id" } } } ] } } } ] } } }
{ "expectUserResponse": false, "finalResponse": { "richResponse": { "items": [ { "structuredResponse": { "orderUpdate": { "actionOrderId": "sample_action_order_id", "orderState": { "state": "REJECTED", "label": "Order rejected" }, "updateTime": "2017-05-10T02:30:00.000Z", "rejectionInfo": { "type": "INELIGIBLE", "reason": "Sorry, the phone number must not be blank" }, "orderManagementActions": [ { "type": "CUSTOMER_SERVICE", "button": { "title": "Contact customer service", "openUrlAction": { "url": "mailto:support@example.com" } } }, { "type": "EMAIL", "button": { "title": "Email restaurant", "openUrlAction": { "url": "mailto:person@example.com" } } }, { "type": "CALL", "button": { "title": "Call restaurant", "openUrlAction": { "url": "tel:+16505554679" } } }, { "type": "VIEW_DETAILS", "button": { "title": "View order", "openUrlAction": { "url": "https://orderview.partner.com?orderid=sample_action_order_id" } } } ] } } } ] } } }
{ "expectUserResponse": false, "finalResponse": { "richResponse": { "items": [ { "structuredResponse": { "orderUpdate": { "actionOrderId": "sample_action_order_id", "orderState": { "state": "REJECTED", "label": "Order rejected" }, "updateTime": "2017-05-10T02:30:00.000Z", "rejectionInfo": { "type": "UNKNOWN", "reason": "Sorry, there is something wrong with this order." }, "orderManagementActions": [ { "type": "CUSTOMER_SERVICE", "button": { "title": "Contact customer service", "openUrlAction": { "url": "mailto:support@example.com" } } }, { "type": "EMAIL", "button": { "title": "Email restaurant", "openUrlAction": { "url": "mailto:person@example.com" } } }, { "type": "CALL", "button": { "title": "Call restaurant", "openUrlAction": { "url": "tel:+16505554679" } } }, { "type": "VIEW_DETAILS", "button": { "title": "View order", "openUrlAction": { "url": "https://orderview.partner.com?orderid=sample_action_order_id" } } } ] } } } ] } } }