비동기 순서 업데이트

고객이 음식 주문을 제출하면 주문 엔드 투 엔드 서비스에 주문 업데이트 메시지를 전송하여 변경사항을 Google에 알릴 수 있습니다.

주문 업데이트를 전송하는 일반적인 이유는 다음과 같습니다.

  • 주문이 가능해지거나 변경될 것으로 예상되는 주문 처리 시간입니다.
  • 주문 상태가 변경됩니다.
  • 주문을 더 이상 처리할 수 없습니다.
  • 주문에 포함된 메뉴 항목의 가격이 변경됨
  • 고객은 고객 지원팀 또는 음식점 전화번호와 같은 새로운 방식으로 주문을 관리할 수 있습니다.
  • 주문 영수증이 제공됩니다.

다음 섹션에서는 주문 업데이트를 사용하여 이러한 다양한 시나리오를 해결하는 방법을 자세히 설명합니다.

주문 상태 전환

주문에는 6가지 상태가 있을 수 있습니다. 이러한 상태와 가능한 전환은 다음 다이어그램에 요약되어 있습니다.

상태 전환 순서

고객이 처음 주문을 제출하면 주문이 CREATED, CONFIRMED 또는 REJECTED 상태로 시작됩니다. 상태 전환이 유효하면 주문 업데이트 메시지를 전송하여 주문 상태를 업데이트할 수 있습니다. CREATED 상태는 파트너의 플랫폼이 주문을 즉시 확인하거나 거부할 수 없는 경우에 사용됩니다. 사용 사례 예시는 고객이 배송 애그리게이터를 통해 주문하는 경우입니다. 배달 애그리게이터가 Google로부터 배달 음식을 수신하고 이 정보를 식당으로 보냅니다. 음식점에서 주문 가능 여부를 수신하고 확인한 후에는 상태가 CONFIRMED일 수 있고, 그렇지 않은 경우에는 REJECTED일 수 있습니다.

CONFIRMED 상태의 주문은 다음으로 IN_PREPARATION 상태로 전환됩니다. 주문 상품이 수령인지 배달인지에 따라 다음으로 READY_FOR_PICKUP 또는 IN_TRANSIT 상태를 사용합니다. 음식이 배달 또는 수령되면 주문이 FULFILLED 상태로 설정됩니다.

고객이 주문을 취소할 수 있도록 허용하는 경우 CANCELLED 상태를 사용할 수 있습니다. CREATED, CONFIRMED, IN_PREPARATION, READY_FOR_PICKUP 또는 IN_TRANSIT 상태에서 주문을 취소할 수 있습니다. 주문 엔드 투 엔드 서비스는 취소 정책 및 취소 시점의 결제 상태에 따라 환불을 처리해야 합니다.

주문 엔드 투 엔드 서비스가 사용 가능한 모든 상태 및 전환을 지원할 필요는 없습니다. 하지만 주문의 최종 상태는 FULFILLED, REJECTED 또는 CANCELLED여야 합니다.

예상 처리 시간 제공

주문 상품을 수령 (또는 배달)할 수 있는 예상 기간을 사용자에게 제공할 수 있습니다. FoodOrderUpdateExtensionestimatedFulfillmentTimeIso8601 필드를 사용하여 고객의 주문 상품을 수령하거나 배달할 수 있는 예상 시간 범위를 제공합니다.

다음 경우에 estimatedFulfillmentTimeIso8601를 전송합니다.

  • 예상 시간을 사용할 수 있게 되면 CREATED 또는 CONFIRMED 상태인 것이 이상적입니다.
  • 예상 시간이 변경되는 경우(예: 주문이 IN_TRANSIT일 때 예상 시간을 더 정확하게 업데이트)

사용자 기대치를 효과적으로 관리하려면 추정치에 보수적인 값을 제시하고 고정된 날짜와 시간이 아닌 날짜 및 시간 범위를 제공하세요. 가능하면 교통 상황과 같은 변화를 고려해야 합니다. 예를 들어 예상 배송 시간이 오후 1시인 주문의 경우 오후 12시 45분 (하한값)에서 오후 1시 15분 (상한값)까지의 예상 시간을 전송할 수 있습니다.

주문 관리 작업 제공

주문 업데이트를 전송할 때 OrderManagementAction 형식으로 주문을 관리하는 데 도움이 되는 리소스를 고객에게 제공할 수 있습니다. 고객은 주문 후 진행 상황을 추적하거나, 변경하거나, 주문을 취소하기 위해 판매자 또는 주문을 처리하는 음식점에 문의해야 할 수 있습니다.

OrderManagementAction를 사용하면 고객이 기기에서 직접 이메일을 보내거나 전화를 걸거나 URL에 연결할 수 있습니다. 사용자에게 전송하는 이메일 주문 확인에 있는 것과 동일한 정보를 OrderManagementAction에 사용합니다.

주문 관리 작업에는 다음 유형이 포함됩니다.

  • CUSTOMER_SERVICE: 고객에게 고객 서비스에 연락할 수 있는 작업을 제공합니다. 이 관리 작업 유형은 주문 업데이트에 필수입니다.
  • EMAIL: 고객에게 제공된 이메일 주소로 이메일을 보내는 작업을 제공합니다.
  • CALL: 고객에게 제공된 전화번호로 전화를 걸 수 있는 액션을 제공합니다.
  • VIEW_DETAIL: 고객에게 주문 세부정보를 볼 수 있는 작업을 제공합니다.

각 주문 업데이트에는 하나 이상의 주문 관리 작업이 포함되어야 합니다. 하지만 제공된 주문 관리 작업은 주문 상태에 따라 다를 수 있습니다. 예를 들어 주문이 CONFIRMED 상태인 경우 CUSTOMER_SERVICE 작업이 고객 서비스 전화번호를 가리킬 수 있습니다. 주문 상태가 IN_TRANSIT로 업데이트되면 CUSTOMER_SERVICE 작업이 처리 레스토랑의 전화번호를 가리킬 수 있습니다.

주문 업데이트 전송

AsyncOrderUpdateRequestMessage 메시지 유형을 사용하여 주문 엔드 투 엔드 서비스에 주문 업데이트를 보냅니다. Google은 AsyncOrderUpdateResponseMessage로 응답합니다. 예를 들어 고객에게 주문이 유효하고 수락되었음을 알리려면 AsyncOrderUpdateRequestMessage를 전송하여 Accepted by restaurant 라벨을 사용하여 주문 상태를 CONFIRMED로 변경하면 됩니다.

주문 업데이트 다이어그램

주문 업데이트 메시지 설정

Google에 AsyncOrderUpdateRequestMessage를 전송할 때 OrderUpdate 필드를 사용하여 주문 상태에 대한 정보를 포함해야 합니다.

다음 예는 각 주문 상태의 AsyncOrderUpdateRequestMessage 샘플을 보여줍니다.

확정됨

이 예시는 영수증과 예상 배송 기간으로 주문이 확인되었음을 사용자에게 알리는 샘플 주문 업데이트 요청을 보여줍니다.

{
  "isInSandbox": true,
  "customPushMessage": {
    "orderUpdate": {
      "actionOrderId": "sample_action_order_id",
      "orderState": {
        "state": "CONFIRMED",
        "label": "Provider confirmed"
      },
      "receipt": {
        "userVisibleOrderId": "userVisibleId1234"
      },
      "updateTime": "2017-07-17T12:00:00Z",
      "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_RESTAURANT",
          "button": {
            "title": "Call restaurant",
            "openUrlAction": {
              "url": "tel:+16505554679"
            }
          }
        },
        {
          "type": "CALL_DRIVER",
          "button": {
            "title": "Call driver",
            "openUrlAction": {
              "url": "tel:+16505554681"
            }
          }
        }
      ],
      "infoExtension": {
        "@type": "type.googleapis.com/google.actions.v2.orders.FoodOrderUpdateExtension",
        "estimatedFulfillmentTimeIso8601": "2017-07-17T13:00:00Z/2017-07-17T13:30:00Z"
      }
    }
  }
}
    

거부됨

이 예시에서는 사용자에게 거부 사유와 함께 주문이 거부되었음을 알리는 주문 업데이트 요청 샘플을 보여줍니다.

{
  "isInSandbox": true,
  "customPushMessage": {
    "orderUpdate": {
      "actionOrderId": "sample_action_order_id",
      "orderState": {
        "state": "REJECTED",
        "label": "Order rejected"
      },
      "updateTime": "2017-05-10T02:30:00.000Z",
      "rejectionInfo": {
        "type": "UNKNOWN",
        "reason": "Sorry, the restaurant cannot take your order right now."
      },
      "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_RESTAURANT",
          "button": {
            "title": "Call restaurant",
            "openUrlAction": {
              "url": "tel:+16505554679"
            }
          }
        },
        {
          "type": "CALL_DRIVER",
          "button": {
            "title": "Call driver",
            "openUrlAction": {
              "url": "tel:+16505554681"
            }
          }
        }
      ],
      "infoExtension": {
      "@type": "type.googleapis.com/google.actions.v2.orders.FoodOrderUpdateExtension",
      "foodOrderErrors": [
        {
        "error": "NO_CAPACITY",
        "description": "Sorry, the restaurant cannot take your order right now."
        }
      ]
      }
    }
  }
}
    

CANCELLED

이 예에서는 사용자에게 취소 사유와 함께 주문이 취소되었음을 알리는 샘플 주문 업데이트 요청을 보여줍니다.

{
  "isInSandbox": true,
  "customPushMessage": {
    "orderUpdate": {
      "actionOrderId": "sample_action_order_id",
      "orderState": {
        "state": "CANCELLED",
        "label": "Order cancelled"
      },
      "updateTime": "2017-05-10T02:30:00.000Z",
      "cancellationInfo": {
        "reason": "Customer requested"
      },
      "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_RESTAURANT",
          "button": {
            "title": "Call restaurant",
            "openUrlAction": {
              "url": "tel:+16505554679"
            }
          }
        },
        {
          "type": "CALL_DRIVER",
          "button": {
            "title": "Call driver",
            "openUrlAction": {
              "url": "tel:+16505554681"
            }
          }
        }
      ]
    }
  }
}
    

IN_PREPARATION

이 예는 사용자에게 음식이 현재 준비 중임을 알리는 샘플 주문 업데이트 요청을 보여줍니다.

{
  "isInSandbox":true,
  "customPushMessage":{
    "orderUpdate":{
      "actionOrderId":"sample_action_order_id",
      "orderState":{
        "state":"IN_PREPARATION",
        "label":"Order is being prepared"
      },
      "receipt": {
        "userVisibleOrderId": "userVisibleId1234"
      },
      "updateTime":"2018-04-15T11:30:00Z",
      "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_RESTAURANT",
          "button": {
            "title": "Call restaurant",
            "openUrlAction": {
              "url": "tel:+16505554679"
            }
          }
        },
        {
          "type": "CALL_DRIVER",
          "button": {
            "title": "Call driver",
            "openUrlAction": {
              "url": "tel:+16505554681"
            }
          }
        }
      ],
      "infoExtension":{
        "@type":"type.googleapis.com/google.actions.v2.orders.FoodOrderUpdateExtension",
        "estimatedFulfillmentTimeIso8601":"PT20M"
      }
    }
  }
}
    

READY_FOR_PICKUP

이 예는 사용자에게 음식 수령이 준비되었음을 알리는 샘플 주문 업데이트 요청을 보여줍니다.

{
  "isInSandbox": true,
  "customPushMessage": {
    "orderUpdate": {
      "actionOrderId": "sample_action_order_id",
      "orderState": {
        "state": "READY_FOR_PICKUP",
        "label": "Order is ready for pickup"
      },
      "receipt": {
        "userVisibleOrderId": "userVisibleId1234"
      },
      "updateTime": "2018-04-15T12:00:00Z",
      "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_RESTAURANT",
          "button": {
            "title": "Call restaurant",
            "openUrlAction": {
              "url": "tel:+16505554679"
            }
          }
        },
        {
          "type": "CALL_DRIVER",
          "button": {
            "title": "Call driver",
            "openUrlAction": {
              "url": "tel:+16505554681"
            }
          }
        }
      ],
      "infoExtension": {
        "@type": "type.googleapis.com/google.actions.v2.orders.FoodOrderUpdateExtension",
        "estimatedFulfillmentTimeIso8601": "PT20M"
      }
    }
  }
}
    

IN_TRANSIT

이 예에서는 사용자에게 주문 상품이 예상 배송 시간과 함께 배송 중임을 알리는 샘플 주문 업데이트 요청을 보여줍니다.

{
  "isInSandbox": true,
  "customPushMessage": {
    "orderUpdate": {
      "actionOrderId": "sample_action_order_id",
      "orderState": {
        "state": "IN_TRANSIT",
        "label": "Order is on the way"
      },
      "inTransitInfo": {
        "updatedTime": "2017-07-17T12:00:00Z"
      },
      "updateTime": "2017-07-17T12:00:00Z",
      "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_RESTAURANT",
          "button": {
            "title": "Call restaurant",
            "openUrlAction": {
              "url": "tel:+16505554679"
            }
          }
        },
        {
          "type": "CALL_DRIVER",
          "button": {
            "title": "Call driver",
            "openUrlAction": {
              "url": "tel:+16505554681"
            }
          }
        }
      ],
      "infoExtension": {
        "@type": "type.googleapis.com/google.actions.v2.orders.FoodOrderUpdateExtension",
        "estimatedFulfillmentTimeIso8601": "PT20M"
      }
    }
  }
}
  

처리됨

이 예시는 사용자에게 주문 상품이 수령 또는 배송되었음을 알리는 샘플 주문 업데이트 요청을 보여줍니다.

{
  "isInSandbox": true,
  "customPushMessage": {
    "orderUpdate": {
      "actionOrderId": "sample_action_order_id",
      "orderState": {
      "state": "FULFILLED",
      "label": "Order delivered"
      },
      "updateTime": "2017-05-10T02:30:00.000Z",
      "fulfillmentInfo": {
        "deliveryTime": "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_RESTAURANT",
          "button": {
            "title": "Call restaurant",
            "openUrlAction": {
              "url": "tel:+16505554679"
            }
          }
        },
        {
          "type": "CALL_DRIVER",
          "button": {
            "title": "Call driver",
            "openUrlAction": {
              "url": "tel:+16505554681"
            }
          }
        }
      ]
    }
  }
}
    

다양한 사용 사례의 주문 업데이트 요청에 관한 자세한 예는 고급 주문 업데이트 구현을 참고하세요.

승인 토큰 생성 및 메시지 전송

주문 업데이트에는 주문 엔드 투 엔드 서비스에서 메시지가 주문 엔드 투 엔드 웹 서비스에서 전송된 메시지임을 확인할 수 있도록 승인 토큰이 필요합니다.

프로젝트의 주문 업데이트를 구현하려면 다음 단계를 따르세요.

  1. 다음 단계에 따라 승인 토큰을 생성합니다.
    1. Google 인증 라이브러리를 사용하여 서비스 계정 파일에서 사용자 인증 정보를 읽습니다.
    2. 다음 API 범위를 사용하는 요청 토큰: https://www.googleapis.com/auth/actions.fulfillment.conversation
  2. 이 토큰을 사용하여 인증된 HTTP POST 요청을 https://actions.googleapis.com/v2/conversations:send 엔드포인트로 보냅니다.
  3. 요청의 일부로 Content-Type 헤더를 application/json로 설정합니다.

다음 예는 주문 업데이트를 구현하는 방법을 보여줍니다.

Node.js

이 코드는 Node.js용 Google 인증 라이브러리를 사용합니다.

const {auth} = require('google-auth-library')
const request = require('request');
// The service account client secret file downloaded from the Google Cloud Console
const serviceAccountJson = require('./service-account.json')
// order-update.json is a file that contains the payload
const jsonBody = require('./order-update.json')

/**
 * Get the authorization token using a service account.
 */
async function getAuthToken() {
  let client = auth.fromJSON(serviceAccountJson)
  client.scopes = ['https://www.googleapis.com/auth/actions.fulfillment.conversation']
  const tokens = await client.authorize()
  return tokens.access_token;
}

/**
 * Send an order update request
 */
async function sendOrderUpdate() {
  const token = await getAuthToken()
  request.post({
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${token}`
    },
    url: 'https://actions.googleapis.com/v2/conversations:send',
    body: jsonBody,
    json: true
  },
  (err, res, body) => {
    if (err) { return console.log(err); }
    console.log(`Response: ${JSON.stringify(res)}`)
  })
}
    

Python

이 코드는 Python용 Google 인증 라이브러리를 사용합니다.

from google.oauth2 import service_account
from google.auth.transport.requests import AuthorizedSession
import json

# service-account.json is the service account client secret file downloaded from the
# Google Cloud Console
credentials = service_account.Credentials.from_service_account_file(
    'service-account.json')

scoped_credentials = credentials.with_scopes(
    ['https://www.googleapis.com/auth/actions.fulfillment.conversation'])

authed_session = AuthorizedSession(scoped_credentials)

# order-update.json is a file that contains the payload
json_payload=json.load(open('order-update.json'))

response = authed_session.post(
    'https://actions.googleapis.com/v2/conversations:send',
    json=json_payload)
    

Java

이 코드는 Java용 Google 인증 라이브러리를 사용합니다.

/**
 * Get the authorization token using a service account.
 */
private static String getAuthToken() {
  InputStream serviceAccountFile = Example.class.getClassLoader().getResourceAsStream("service-account.json");
  ServiceAccountCredentials.Builder credentialsSimpleBuilder =
      ServiceAccountCredentials.fromStream(serviceAccountFile).toBuilder();
  credentialsSimpleBuilder.setScopes(ImmutableList.of("https://www.googleapis.com/auth/actions.fulfillment.conversation"));
  AccessToken accessToken = credentialsSimpleBuilder.build().refreshAccessToken();
  return accessToken.getTokenValue();
}

/**
 * Send an order update request
 */
public void sendOrderUpdate() {
  String authToken = getAuthToken();
  // Execute POST request
  executePostRequest("https://actions.googleapis.com/v2/conversations:send",
      authToken, "update_order_example.json",);
}
    

오류 없이 주문 업데이트가 완료되면 Google은 페이로드가 비어 있는 HTTP 200 응답을 반환합니다. 업데이트 형식이 잘못된 경우와 같은 문제가 있으면 Google에서 오류를 반환합니다.