v1 미리 주문 기능

사용자가 음식 주문 수령 및 배달을 미리 예약할 수 있도록 처리에 지원을 추가할 수 있습니다. 처리에서 이 지원을 구현하기 전에 인벤토리 피드 스키마(AdvanceServiceDeliveryHoursSpecification)에 설명된 대로 사용자가 고급 주문을 할 수 있는 시간을 지정하는 서비스 인벤토리 피드를 만듭니다.

사전 주문 슬롯

Google은 식당 또는 서비스의 처리 시간을 기준으로 (AdvanceServiceDeliveryHoursSpecification에 정의됨) 최대 7일 전에 15분 단위로 사전 주문 슬롯을 제안합니다.

제안된 사전 주문 슬롯을 가져오려면 결제 시 FoodCartExtension 객체의 fulfillmentPreference 필드에서 다음 값을 사용합니다.

  • PickupInfo.pickupTimeIso8601
  • DeliveryInfo.deliveryTimeIso8601

결제 시 고급 주문 구현

아래 표에는 사용자가 주문하려고 할 때 결제 시 처리 응답을 구현할 수 있는 방법이 나열되어 있습니다.

시나리오 처리 동작
요청된 시간대에 대해 사전 주문이 처리될 수 있습니다. 동일한 슬롯으로 ProposedOrder를 만들어 P0M ('최대한 빨리') 또는 FUTURE_SLOT 장바구니를 수락합니다. 슬롯을 허용하는 결제 응답의 예는 이 코드 스니펫을 참조하세요.
요청된 시간대에 대해 사전 주문을 처리할 수 없습니다. 처리는 다음을 실행해야 합니다.
  1. 요청된 P0M 또는 FUTURE_SLOT 장바구니를 거부하고 FoodErrorExtension 객체에서 주문을 처리할 수 없는 이유를 표시합니다.
    • 용량으로 인해 주문을 처리할 수 없는 경우 오류 유형 NO_CAPACITYFoodOrderError을 지정합니다.
    • 음식점이 문을 닫아 주문을 처리할 수 없다면 오류 유형 CLOSEDFoodOrderError를 지정합니다.
    • 기타 이유로 주문을 처리할 수 없는 경우 오류 유형이 UNAVAILABLE_SLOTFoodOrderError을 지정합니다.
  2. 가능하면 correctedProposedOrder에 대체 P0M 또는 FUTURE_SLOT 값을 제공하세요. 이 값은 현재 시간부터 향후 7일 동안 유효한 모든 처리 슬롯이어야 합니다. 적용 가능한 경우 항상 P0M 슬롯을 포함합니다.

대체 슬롯을 제안하는 결제 응답의 예는 이 코드 스니펫을 참조하세요.

주문 처리를 위한 대체 슬롯

결제 시 Google에서 제안한 미리 주문 시간대가 적합하지 않은 경우 처리에서 CheckoutResponseMessage 객체를 사용하여 대안을 제안할 수 있습니다.

대체 사전 주문 슬롯을 지정하려면 결제 요청에 FoodErrorExtension로 응답하고 다음 값을 설정합니다.

  1. foodOrderErrors 매개변수에서 오류 유형 (예: UNAVAILABLE_SLOT, NO_CAPACITY, CLOSED)을 지정합니다.
  2. correctedProposedOrder 매개변수에서 availableFulfillmentOptions를 통해 대체 P0M 또는 FUTURE_SLOT 값을 제공합니다.

대체 슬롯은 주문일로부터 7일 이내여야 하며 사용자가 요청한 장바구니를 처리할 수 있는 모든 슬롯을 포함해야 합니다.

예를 들어 점심 특판이 월요일부터 금요일, 오전 11시부터 오후 1시까지만 제공된다고 가정해 보겠습니다. 그런 다음 사용자는 점심 스페셜을 장바구니에 추가하려고 하지만 선택한 시간대를 사용할 수 없습니다. 이 경우 처리는 장바구니에 점심 특별 메뉴를 유지하고 다음 7일 동안 오전 11시~오후 1시 시간대만 반환해야 합니다.

응답에서 correctedProposedOrder.Cart.fulfillmentPreference 객체를 생략해야 합니다.

예약 가능한 시간대가 없거나 음식점이나 서비스에서 사전 주문을 지원하지 않는 경우 correctedProposedOrder를 제공할 필요가 없습니다.

레스토랑이나 서비스에서 선주문을 받을 수 있는 경우 사전 주문의 결제 요청 및 응답 흐름에서 처리와 Google 간의 JSON 메시지는 아래 예를 참고하세요.

예: 배송 슬롯이 있는 CheckoutRequest

아래 스니펫은 사전 주문 배송 슬롯이 있는 결제 요청의 예를 보여줍니다.

{
  "inputs": [
    {
      "intent": "actions.foodordering.intent.CHECKOUT",
      "arguments": [
        {
          "extension": {
            "@type": "type.googleapis.com/google.actions.v2.orders.Cart",
            "merchant": {
              "id": "https://www.exampleprovider.com/merchant/id1",
              "name": "Cucina Venti"
            },
            "lineItems": [
              {
                "name": "Sizzling Prawns Dinner",
                "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": "16",
                    "nanos": 750000000
                  }
                },
              }
            ],
            "extension": {
              "@type": "type.googleapis.com/google.actions.v2.orders.FoodCartExtension",
              "fulfillmentPreference": {
                "fulfillmentInfo": {
                  "delivery": {
                    // Deliver at 6:30PM.
                    "deliveryTimeIso8601": "2017-12-14T18:30:00-07:00"
                  }
                }
              },
              "location": {
                ...
              }
            }
          }
        }
      ]
    }
  ]
}

예: 슬롯을 허용하는 CheckoutResponse

아래 스니펫은 처리가 제안된 사전 주문 슬롯을 수락하는 결제 응답의 예를 보여줍니다.

{
  "expectUserResponse": false,
  "finalResponse": {
    "richResponse": {
      "items": [
        {
          "structuredResponse": {
            "checkoutResponse": {
              "proposedOrder": {
                "id": "sample_proposed_order_id_1",
                "cart": {
                  "merchant": {
                    "id": "https://www.exampleprovider.com/merchant/id1",
                    "name": "Falafel Bite"
                  },
                  "lineItems": [
                    {
                      "name": "Sizzling Prawns Dinner",
                      "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": "16",
                          "nanos": 750000000
                        }
                      },
                    }
                  ],
                  "extension": {
                    "@type": "type.googleapis.com/google.actions.v2.orders.FoodCartExtension",
                    "fulfillmentPreference": {
                      "fulfillmentInfo": {
                        "delivery": {
                          // Same as the time in the request.
                          "deliveryTimeIso8601": "2017-12-14T18:30:00-07:00"
                        }
                      }
                    },
                    "location": {
                      ...
                     }
                   }
                },
                "totalPrice": {
                  "type": "ESTIMATE",
                  "amount": {
                    // Represents $16.75
                    "currencyCode": "USD",
                    "units": "16",
                    "nanos": 750000000
                  }
                },
                "extension": {
                  "@type": "type.googleapis.com/google.actions.v2.orders.FoodOrderExtension",
                  // Send whole proposed order back.
                  "availableFulfillmentOptions": [
                    "fulfillmentInfo": {
                      "delivery": {
                        // Same as the time in the request.
                        "deliveryTimeIso8601": "2017-12-14T18:30:00-07:00"
                      }
                    }
                  ]
                }
              },
              "paymentOptions": {
                ...
              }
            }
          }
        }
      ]
    }
  }
}

예: 대체 슬롯이 있는 CheckoutResponse

아래 스니펫은 처리에서 대체 사전 주문 슬롯을 제안하는 결제 응답의 예를 보여줍니다. correctedProposedOrder.Cart.fulfillmentPreference 객체는 응답에서 생략되어야 합니다.

{
  "expectUserResponse": false,
  "finalResponse": {
    "richResponse": {
      "items": [
        {
          "structuredResponse": {
            "error": {
              "@type": "type.googleapis.com/google.actions.v2.orders.FoodErrorExtension",
              "foodOrderErrors": [
                {
                  "error": "UNAVAILABLE_SLOT", // Cart level error
                  "description": "The restaurant is closed."
                }
              ],
              "correctedProposedOrder": {
                // Send whole original cart back,
                // without the fulfillmentPreference.
                "cart": {
                  ...
                },
                "otherItems": {
                  ...
                },
                "totalPrice": {
                  ...
                },
                "extension": {
                  "@type": "type.googleapis.com/google.actions.v2.orders.FoodOrderExtension",
                  "availableFulfillmentOptions": [
                    "fulfillmentInfo": {
                      "delivery": {
                        "deliveryTimeIso8601": "2017-12-14T19:00:00-07:00"
                      }
                    },
                    "fulfillmentInfo": {
                      "delivery": {
                        "deliveryTimeIso8601": "2017-12-14T19:30:00-07:00"
                      }
                    },
                    "fulfillmentInfo": {
                      "delivery": {
                        "deliveryTimeIso8601": "2017-12-14T20:00:00-07:00"
                      }
                    }
                  ]
                }
              },
              "paymentOptions": {
                ...
              }
            }
          }
        }
      ]
    }
  }
}

주문 제출 시 고급 주문 구현

주문 제출 시 고급 주문 슬롯에 문제가 있는 경우 SubmitOrderResponseMessage에서 RejectionInfo 객체에 이유 (UNAVAILABLE_SLOT 또는 UNKNOWN)를 포함해야 합니다.

제공업체에서 주문을 수락하면 OrderState 객체에서 주문 상태를 CREATED에서 CONFIRMED로 업데이트합니다. 사용자에게 보내는 확인 이메일에 선택한 시간대를 포함합니다.

처리에서 나중에 음식점에 주문을 전송하면 비동기 주문 업데이트 작업을 사용하여 Google에 업데이트를 전송합니다.

처리 제출 주문 응답 또는 후속 비동기 주문 업데이트의 OrderUpdate 객체에 다음과 같이 설정된 값이 있는 estimatedFulfillmentTimeIso8601를 포함합니다.

  • 주문 상태가 CREATED 또는 CONFIRMED이면 사용자가 고급 주문에 대해 예약한 배달 또는 수령 시간으로 값을 설정합니다.
  • 식당이나 서비스의 예상 배달 시간이 더 정확한 경우 값을 배달 또는 수령 예상 시간으로 설정합니다.

예: 전송 슬롯이 있는 SubmitOrderRequest

아래 스니펫은 사용자가 선택한 사전 주문 슬롯을 나타내는 주문 제출 요청의 예를 보여줍니다.

{
  "inputs": [
    {
      "intent": "actions.intent.TRANSACTION_DECISION",
      "arguments": [
        {
          "transactionDecisionValue": {
            "order": {
              "finalOrder": {
                "cart": {
                  "notes": "Guest prefers their food to be hot when it is delivered.",
                  "merchant": {
                    "id": "https://www.exampleprovider.com/merchant/id1",
                    "name": "Cucina Venti"
                  },
                  "lineItems": [
                    {
                      "name": "Sizzling Prawns Dinner",
                      "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": "16",
                          "nanos": 750000000
                        }
                      }
                    }
                  ],
                  "extension": {
                    "@type": "type.googleapis.com/google.actions.v2.orders.FoodCartExtension",
                    "fulfillmentPreference": {
                      "fulfillmentInfo": {
                        "delivery": {
                          "deliveryTimeIso8601": "2017-12-14T18:30:00-07:00"
                        }
                      }
                    }
                    "contact": {
                      ...
                    }
                  }
                },
                "totalPrice": {
                  "type": "ESTIMATE",
                  "amount": {
                    "currencyCode": "USD",
                    "units": "16",
                    "nanos": 750000000
                  }
                },
                "id": "sample_final_order_id",
                "extension": {
                  // Send whole proposed order back.
                  "availableFulfillmentOptions": [
                    "fulfillmentInfo": {
                      "delivery": {
                        "deliveryTimeIso8601": "2017-12-14T18:30:00-07:00"
                      }
                   ]
                }
              },
              "googleOrderId": "sample_google_order_id",
              "orderDate": "2017-07-17T12:00:00Z",
              "paymentInfo": {
                ...
              }
            }
          }
        }
      ]
    }
  ]
}

예: SubmitOrderResponse에서 주문 수락

아래 스니펫은 처리에서 사용자의 사전 주문을 수락했음을 확인하는 주문 제출 응답의 예를 보여줍니다.

{
  "expectUserResponse": false,
  "finalResponse": {
    "richResponse": {
      "items": [
        {
          "structuredResponse": {
            "orderUpdate": {
              "actionOrderId": "sample_action_order_id",
              "orderState": {
                "state": "CREATED",
                "label": "Order placed"
              },
              "receipt": {
                "userVisibleOrderId": "userVisibleId1234"
              },
              "updateTime": "2017-07-17T12:00:00Z",
              "orderManagementActions": [
                ...
              ],
              "infoExtension": {
                 "@type": "type.googleapis.com/google.actions.v2.orders.FoodOrderUpdateExtension",
                 // Same as the user selected time.
                 "estimatedFulfillmentTimeIso8601": "2017-12-14T18:30:00-07:00"
              }
            }
          }
        }
      ]
    }
  }
}

예: SubmitOrderResponse에서 시간대를 사용할 수 없어 주문 거부

아래 스니펫은 처리할 수 없는 시간대로 인해 사용자의 사전 주문을 거부하는 주문 제출 응답의 예를 보여줍니다.

{
  "expectUserResponse": false,
  "finalResponse": {
    "richResponse": {
      "items": [
        {
          "structuredResponse": {
            "orderUpdate": {
              "actionOrderId": "sample_action_order_id",
              "orderState": {
                "state": "REJECTED",
                "label": "Unavailable slot"
              },
              "rejectionInfo": {
                // Note that this UNAVAILABLE_SLOT is different from the enum
                // with the same name proposed for FoodOrderError.
                "state": "UNAVAILABLE_SLOT",
                "label": "Unavailable slot"
              },
              "updateTime": "2017-07-17T12:00:00Z",
              "orderManagementActions": [
                ...
              ]
            }
          }
        }
      ]
    }
  }
}

사전 주문의 예

AdvanceServiceDeliveryHoursSpecification 유형을 사용하여 사용자가 미리 주문을 예약할 수 있도록 배달 또는 수령 시간을 지정할 수 있습니다.

참고: 서비스 처리를 위해 지정해야 하는 두 가지 개별적인 기간이 있습니다. 하나는 사용자가 주문할 수 있는 시기를 지정하는 주문 기간이고 다른 하나는 주문이 처리될 시기를 지정하는 처리 기간입니다. OpeningHoursSpecification 객체는 사용자가 주문할 수 있는 시기를 정의합니다. 하위 처리 시간 (ServiceDeliveryHoursSpecification 또는 AdvanceServiceDeliveryHoursSpecification)은 주문을 처리할 수 있는 시기를 정의합니다.

다음 예에서는 서비스의 사전 주문을 수락하는 시간을 15분의 서비스 간격으로 정의합니다.

{
  "hoursAvailable": [
    {
      "@type": "OpeningHoursSpecification",
      "opens": "T00:00:00", // Ordering available 24 hours
      "closes": "T23:59:59",
      "deliveryHours": [
        {
          "@type": "ServiceDeliveryHoursSpecification",
          "opens": "T09:00:00", // ASAP orders b/w 9am and 8:59:59pm
          "closes": "T21:00:00",
          "deliveryLeadTime": {
            "value": "60",
            "unitCode": "MIN"
          }
        },
        {
          "@type": "AdvanceServiceDeliveryHoursSpecification",
          "opens": "T10:00:00",  // Delivery between 10AM and 7:59:59PM
          "closes": "T20:00:00",
          "serviceTimeInterval": "PT15M", // in slots spaced 15 minutes apart (ISO8601)
          "advanceBookingRequirement": {
            "minValue": 60,   // The slot should be at least 60 mins away
            "maxValue": 8640, // but not more than 6 days away
            "unitCode": "MIN"
          }
        }
      ]
    }
  ]
}

다음 예시는 서비스가 크리스마스 당일 주문에는 영업을 시작하고 당일에 예약된 고급 주문의 경우 종료하도록 지정할 수 있는 방법을 보여줍니다. 이 예에서는 다음 시나리오를 지원합니다.

  • 사용자는 12월 25일에 주문을 하여 당일 배송을 이용할 수 있습니다.
  • 사용자는 12월 27일에 배송되도록 12월 25일에 사전 주문을 할 수 있습니다.
  • 사용자는 12월 22일에 배송되도록 12월 22일에 사전 주문을 할 수 없습니다.
{
  "specialOpeningHoursSpecification": {
    "@type": "AdvanceServiceDeliveryHoursSpecification",
    "validFrom": "2018-12-25T00:00:00-07:00",
    "validThrough": "2018-12-26T00:00:00-07:00",
    "opens": "T00:00:00", // No advance ordering
    "closes": "T00:00:00"
  }
}

다음 예시는 서비스가 당일 주문이나 크리스마스에 예약된 사전 주문의 경우 종료하지만 이후 날로 예약된 고급 주문에는 서비스를 실행하도록 지정할 수 있는 방법을 보여줍니다. 이 예에서는 다음 시나리오를 지원합니다.

  • 사용자는 12월 25일에 당일 배송 주문을 할 수 없습니다.
  • 사용자는 12월 27일에 배송되도록 12월 25일에 사전 주문을 할 수 있습니다.
  • 사용자는 12월 22일에 배송되도록 12월 22일에 사전 주문을 할 수 없습니다.
{
  "specialOpeningHoursSpecification": [
    {
      "@type": "ServiceDeliveryHoursSpecification",
      "validFrom": "2018-12-25T00:00:00-07:00",
      "validThrough": "2018-12-26T00:00:00-07:00",
      "opens": "T00:00:00", // No ASAP ordering on Christmas
      "closes": "T00:00:00"
    },
    {
      "@type": "AdvanceServiceDeliveryHoursSpecification",
      "validFrom": "2018-12-25T00:00:00-07:00",
      "validThrough": "2018-12-26T00:00:00-07:00",
      "opens": "T00:00:00", // Orders cannot be scheduled for Christmas
      "closes": "T00:00:00"
    }
  ]
}

다음 샘플 서비스는 연중무휴 24시간 주문을 받고 평일 오전 10시부터 오후 2시 59분 59초까지 배송됩니다.

...
{
  "@type": "OpeningHoursSpecification",
  "opens": "T00:00:00",
  "closes": "T23:59:59",
  "deliveryHours": {
    "@type": "AdvanceServiceDeliveryHoursSpecification",
    "opens": "T10:00:00", // Delivery starts at 10:00AM
    "closes": "T15:00:00", // Delivery ends at 3:00PM. Delivery from 10AM-2:59:59PM.
    "dayOfWeek": [
      "Monday",
      "Tuesday",
      "Wednesday",
      "Thursday",
      "Friday"
    ],
    "serviceTimeInterval": "PT15M", // in slots spaced 15 minutes apart
    "advanceBookingRequirement": {
      "minValue": 60,   // The slot should be at least 60 mins away
      "maxValue": 8640, // but not more than 6 days away
      "unitCode": "MIN"
    }
  }
}
...

다음 샘플 서비스는 매일 오전 8시~오후 4시 59분 59초의 주문을 받습니다. 고객은 1시간 이내에 배송하도록 선택하거나 시간대 중 하나를 선택할 수 있습니다.

...
{
  "@type": "OpeningHoursSpecification",
  "opens": "T08:00:00",  // Ordering opens at 8:00AM
  "closes": "T17:00:00",  // Ordering closes at 5:00PM, last order at 4:59:59PM
  "deliveryHours": [
    {
      "@type": "ServiceDeliveryHoursSpecification",
      "opens": "T08:00:00",
      "closes": "T17:00:00",
      "deliveryLeadTime": {
        "@type": "QuantitativeValue",
        "value": "60", // If no exact deliveryLeadTime, put a maximum time
        "unitCode": "MIN"
      }
    },
    {
      "@type": "AdvanceServiceDeliveryHoursSpecification",
      "opens": "T08:00:00",
      "closes": "T17:00:00",
      "serviceTimeInterval": "PT15M", // in slots spaced 15 minutes apart
      "advanceBookingRequirement": {
        "minValue": 90,   // The slot should be at least 90 mins away
        "maxValue": 8640, // but not more than 6 days away
        "unitCode": "MIN"
      }
    }
  ]
}
...

다음 샘플은 매장이 평일 오전 8시~오후 4시 59분 59초이지만 주말에는 오전 8시~오후 6시 59분에 문을 여는 경우를 보여줍니다. 연중무휴 24시간 주문은 불가합니다.

...
{
  // On weekdays, ordering open from 8AM-4:59:59PM.
  "@type": "OpeningHoursSpecification",
  "opens": "T08:00:00",
  "closes": "T17:00:00",
  "dayOfWeek": [
    "Monday",
    "Tuesday",
    "Wednesday",
    "Thursday",
    "Friday"
  ],
  "deliveryHours": [
    {
      // Fulfillment between 8AM-4:59:59PM on weekdays.
      "@type": "AdvanceServiceDeliveryHoursSpecification",
      "opens": "T08:00:00",
      "closes": "T17:00:00",
      "dayOfWeek": [
        "Monday",
        "Tuesday",
        "Wednesday",
        "Thursday",
        "Friday"
      ],
      "serviceTimeInterval": "PT15M",
      "advanceBookingRequirement": {
        "minValue": 60,
        "maxValue": 8640,
        "unitCode": "MIN"
      }
    },
    {
      // Fulfillment between 8AM-6:59:59PM on weekends (even for orders placed on a
      // weekday).
      "@type": "AdvanceServiceDeliveryHoursSpecification",
      "opens": "T08:00:00",
      "closes": "T19:00:00",
      "dayOfWeek": [
        "Saturday",
        "Sunday"
      ],
      "serviceTimeInterval": "PT15M",
      "advanceBookingRequirement": {
        "minValue": 60,
        "maxValue": 8640,
        "unitCode": "MIN"
      }
    }
  ]
},
{
  // On weekends, one can place orders upto 6:59:59PM.
  "@type": "OpeningHoursSpecification",
  "opens": "T08:00:00",
  "closes": "T19:00:00",
  "dayOfWeek": [
    "Saturday",
    "Sunday"
  ],
  "deliveryHours": [
    {
      // But fulfillment on weekdays is only till 4:59:59PM.
      "@type": "AdvanceServiceDeliveryHoursSpecification",
      "opens": "T08:00:00",
      "closes": "T17:00:00",
      "dayOfWeek": [
        "Monday",
        "Tuesday",
        "Wednesday",
        "Thursday",
        "Friday"
      ],
      "serviceTimeInterval": "PT15M",
      "advanceBookingRequirement": {
        "minValue": 60,
        "maxValue": 8640,
        "unitCode": "MIN"
      }
    },
    {
      // Fulfillment on weekends is till 6:59:59PM.
      "@type": "AdvanceServiceDeliveryHoursSpecification",
      "opens": "T08:00:00",
      "closes": "T19:00:00",
      "dayOfWeek": [
        "Saturday",
        "Sunday"
      ],
      "serviceTimeInterval": "PT15M",
      "advanceBookingRequirement": {
        "minValue": 60,
        "maxValue": 8640,
        "unitCode": "MIN"
      }
    }
  ]
}
...