v1 事前注文機能

ユーザーが事前に受け取りと宅配の料理の注文をスケジュールするためのサポートをフルフィルメントに追加できます。フルフィルメントにこのサポートを実装する前に、在庫フィード スキーマ(AdvanceServiceDeliveryHoursSpecification)で説明されているように、事前注文を行う時間帯を指定するサービス在庫フィードを作成します。

事前注文スロット

Google では、レストランやサービス(AdvanceServiceDeliveryHoursSpecification で定義)のフルフィルメント時間に基づいて、15 分単位で 7 日前まで事前注文スロットを提案します。

事前スロットの提案を取得するには、ご購入手続き時に FoodCartExtension オブジェクトの fulfillmentPreference フィールドにある次の値を使用します。

  • PickupInfo.pickupTimeIso8601
  • DeliveryInfo.deliveryTimeIso8601

ご購入手続き時に事前予約を実装する

以下の表に、ユーザーが購入手続き時に注文をしようとしたときに返されるフルフィルメントのレスポンスを実装する方法を示します。

シナリオ フルフィルメントの動作
リクエストされたスロットについて事前注文を処理できます。 同じスロットで ProposedOrder を作成して、P0M(できるだけ早く)または FUTURE_SLOT カートを受け入れます。スロットを受け入れる購入手続きの応答例については、こちらのコード スニペットをご覧ください。
リクエストされたスロットでは事前注文を処理できません。 フルフィルメントでは、次の処理を行う必要があります。
  1. リクエストされた P0M または FUTURE_SLOT カートを拒否し、注文を遂行できない理由を FoodErrorExtension オブジェクトで指定します。
    • 容量が原因で注文を処理できない場合は、エラータイプ NO_CAPACITYFoodOrderError を指定します。
    • レストランが閉まっているために注文を完了できない場合は、エラータイプ CLOSEDFoodOrderError を指定します。
    • なんらかの理由で注文を処理できない場合は、エラータイプ UNAVAILABLE_SLOTFoodOrderError を指定します。
  2. 可能であれば、correctedProposedOrderP0M または FUTURE_SLOT の代替値を指定します。これらの値は、現在の時刻から今後 7 日間有効なすべてのフルフィルメント スロットです。該当する場合は、P0M スロットを含めます。

代替スロットを提案する購入手続きの応答例については、こちらのコード スニペットをご覧ください。

注文フルフィルメントの代替スロット

購入手続きの際に、Google が提案した事前注文スロットが不適切な場合、フルフィルメントは CheckoutResponseMessage オブジェクトを使用して代替注文を提案できます。

代替の事前予約スロットを指定するには、FoodErrorExtension を使用して購入手続きリクエストに応答し、次の値を設定します。

  1. foodOrderErrors パラメータで、エラーのタイプ(UNAVAILABLE_SLOTNO_CAPACITYCLOSED など)を指定します。
  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": {
                ...
              }
            }
          }
        }
      ]
    }
  }
}

注文の送信時に事前予約を実装する

注文の送信時に、事前注文スロットに問題がある場合は、SubmitOrderResponseMessageRejectionInfo オブジェクトに理由(UNAVAILABLE_SLOTUNKNOWN など)を含める必要があります。

注文がプロバイダに受け入れられたら、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 タイプを使用して、ユーザーが事前に注文をスケジュールできる配送時間または受け取り時間を指定できます。

注: サービス フルフィルメントには、ユーザーが注文できるタイミングを指定する注文ウィンドウと、注文が実行されるタイミングを指定するフルフィルメント ウィンドウの 2 つの時間枠を指定する必要があります。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 月 25 日に事前注文を行って、12 月 27 日に配達を予定しています。
  • お客様は 12 月 22 日に事前注文をして、12 月 25 日に配送予定の場合、注文することはできません
{
  "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 月 25 日に事前注文を行って、12 月 27 日に配達を予定しています。
  • お客様は 12 月 22 日に事前注文をして、12 月 25 日に配送予定の場合、注文することはできません
{
  "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"
    }
  ]
}

次のサンプル Service は、24 時間 365 日注文を受け付け、平日の午前 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"
    }
  }
}
...

次のサンプル Service は、毎日午前 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 時間 365 日対応しておりません。

...
{
  // 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"
      }
    }
  ]
}
...