빌드 예약 (Dialogflow)

이 가이드에서는 Orders API를 사용하여 예약을 하는 작업 프로젝트를 개발하는 프로세스를 설명합니다.

거래 흐름

작업 프로젝트가 예약을 처리할 때는 다음 흐름을 사용합니다.

  1. 트랜잭션 요구사항 검증 (선택사항) - 대화를 시작할 때 트랜잭션 요구사항 도우미를 사용하여 사용자가 트랜잭션을 수행할 수 있는지 확인합니다.
  2. 주문 작성 - 사용자에게 예약 세부정보를 작성하는 '장바구니' 과정을 안내합니다.
  3. 주문 제안 - '장바구니'가 완료되면 사용자에게 예약 '주문'을 제안하여 사용자가 올바른지 확인할 수 있도록 합니다. 예약이 확인되면 예약 세부정보가 포함된 응답을 받게 됩니다.
  4. 주문 완료 및 영수증 전송 - 주문이 확인되면 예약 시스템을 업데이트하고 사용자에게 영수증을 보냅니다.
  5. 주문 업데이트 전송 - 예약 수명 기간 동안 PATCH 요청을 Orders API에 전송하여 사용자에게 예약 상태 업데이트를 제공합니다.

제한사항 및 검토 가이드라인

거래 및 Orders API를 사용하는 작업에는 추가 정책이 적용된다는 점에 유의하세요. Google에서 거래가 있는 작업을 검토하는 데 최대 6주가 걸릴 수 있으므로 출시 일정을 계획할 때 이 시간을 고려하시기 바랍니다. 검토 절차를 간소화하려면 검토를 위해 작업을 제출하기 전에 거래 정책 및 가이드라인을 준수하는지 확인하세요.

다음 국가에서만 Orders API를 사용하는 작업을 배포할 수 있습니다.

오스트레일리아
브라질
캐나다
인도네시아
일본
멕시코
카타르
러시아
싱가포르
스위스
태국
터키
영국
미국

프로젝트 빌드

트랜잭션 대화의 광범위한 예는 Node.js자바의 트랜잭션 샘플을 참조하세요.

프로젝트 설정

작업을 만들 때 Actions 콘솔에서 트랜잭션을 수행하겠다고 지정해야 합니다. 또한 Node.JS 클라이언트 라이브러리를 사용하는 경우 최신 버전의 Orders API를 사용하도록 처리를 설정합니다.

프로젝트와 처리를 설정하려면 다음을 실행하세요.

  1. 새 프로젝트를 만들거나 기존 프로젝트를 가져옵니다.
  2. 배포 > 디렉터리 정보로 이동합니다.
  3. 추가 정보 > 거래에서 '작업에서 실제 상품 거래를 실행하기 위해 거래 API를 사용하나요?'라는 체크박스를 선택합니다.

  4. Node.JS 클라이언트 라이브러리를 사용하여 작업의 처리를 빌드하는 경우 처리 코드를 열고 앱 선언을 업데이트하여 ordersv3 플래그를 true로 설정합니다. 다음 코드 스니펫은 Orders 버전 3의 앱 선언 예를 보여줍니다.

Node.js

const {dialogflow} = require('actions-on-google');
let app = dialogflow({
  clientId, // If using account linking
  debug: true,
  ordersv3: true,
});

Node.js

const {actionssdk} = require('actions-on-google');
let app = actionssdk({
  clientId, // If using account linking
  debug: true,
  ordersv3: true,
});

1. 거래 요구사항 확인(선택사항)

사용자 환경

사용자가 예약을 설정하겠다고 표시하면 바로 actions.intent.TRANSACTION_REQUIREMENTS_CHECK 인텐트를 트리거하여 예약을 요청할 수 있도록 하는 것이 좋습니다. 예를 들어 호출되면 작업이 "좌석을 예약하시겠어요?"라고 물을 수 있습니다. 사용자가 '예'라고 답하면 이 인텐트를 즉시 요청해야 합니다. 이렇게 하면 계속 진행할 수 있고 트랜잭션을 계속하지 못하도록 하는 설정을 수정할 수 있는 기회를 갖게 됩니다.

트랜잭션 요구사항 확인 인텐트를 요청하면 다음 결과 중 하나가 발생합니다.

  • 요구사항이 충족되면 처리에서 성공 조건이 포함된 인텐트를 수신하며, 사용자 주문 작성을 진행할 수 있습니다.
  • 요구사항이 하나 이상 없으면 처리에서 실패 조건이 포함된 인텐트를 수신합니다. 이 경우 대화를 종료하거나 예약 흐름에서 피벗하세요.

    사용자가 오류를 수정할 수 있는 경우 기기에서 문제를 해결하라는 메시지가 자동으로 표시됩니다. 대화가 스마트 스피커와 같은 음성 전용 화면에서 진행되는 경우 사용자의 휴대전화로 전달됩니다.

주문 처리

사용자가 트랜잭션 요구사항을 충족하는지 확인하려면 TransactionRequirementsCheckSpec 객체를 사용하여 actions.intent.TRANSACTION_REQUIREMENTS_CHECK 인텐트의 처리를 요청합니다.

요구사항 확인

사용자가 클라이언트 라이브러리의 예약 요구사항을 충족하는지 확인합니다.

Node.js
conv.ask(new TransactionRequirements());
자바
return getResponseBuilder(request)
    .add("Placeholder for transaction requirements text")
    .add(new TransactionRequirements())
    .build();
Dialogflow JSON

아래의 JSON은 웹훅 응답을 설명합니다.

{
  "payload": {
    "google": {
      "expectUserResponse": true,
      "systemIntent": {
        "intent": "actions.intent.TRANSACTION_REQUIREMENTS_CHECK",
        "data": {
          "@type": "type.googleapis.com/google.actions.transactions.v3.TransactionRequirementsCheckSpec"
        }
      }
    }
  }
}
Actions SDK JSON

아래의 JSON은 웹훅 응답을 설명합니다.

{
  "expectUserResponse": true,
  "expectedInputs": [
    {
      "possibleIntents": [
        {
          "intent": "actions.intent.TRANSACTION_REQUIREMENTS_CHECK",
          "inputValueData": {
            "@type": "type.googleapis.com/google.actions.transactions.v3.TransactionRequirementsCheckSpec"
          }
        }
      ]
    }
  ]
}
요구사항 확인 결과 수신

어시스턴트는 인텐트를 처리한 후 확인 결과와 함께 actions.intent.TRANSACTION_REQUIREMENTS_CHECK 인텐트가 포함된 요청을 처리에 전송합니다.

이 요청을 올바르게 처리하려면 actions_intent_TRANSACTION_REQUIREMENTS_CHECK 이벤트에 의해 트리거되는 Dialogflow 인텐트를 선언합니다. 트리거되면 처리에서 이 인텐트를 처리합니다.

Node.js
const arg = conv.arguments.get('TRANSACTION_REQUIREMENTS_CHECK_RESULT');
if (arg && arg.resultType === 'CAN_TRANSACT') {
  // Normally take the user through cart building flow
  conv.ask(`Looks like you're good to go!`);
} else {
  conv.close('Transaction failed.');
}
자바
Argument transactionCheckResult = request
    .getArgument("TRANSACTION_REQUIREMENTS_CHECK_RESULT");
boolean result = false;
if (transactionCheckResult != null) {
  Map<String, Object> map = transactionCheckResult.getExtension();
  if (map != null) {
    String resultType = (String) map.get("resultType");
    result = resultType != null && resultType.equals("CAN_TRANSACT");
  }
}
ResponseBuilder responseBuilder = getResponseBuilder(request);
if (result) {
  responseBuilder.add("Looks like you're good to go! Now say 'confirm transaction'");
} else {
  responseBuilder.add("Transaction failed");
}
return responseBuilder.build();
Dialogflow JSON

아래 JSON은 웹훅 요청을 설명합니다.

{
  "responseId": "",
  "queryResult": {
    "queryText": "",
    "action": "",
    "parameters": {},
    "allRequiredParamsPresent": true,
    "fulfillmentText": "",
    "fulfillmentMessages": [],
    "outputContexts": [],
    "intent": {
      "name": "reservation_transaction_check_complete_df",
      "displayName": "reservation_transaction_check_complete_df"
    },
    "intentDetectionConfidence": 1,
    "diagnosticInfo": {},
    "languageCode": ""
  },
  "originalDetectIntentRequest": {
    "source": "google",
    "version": "2",
    "payload": {
      "isInSandbox": true,
      "surface": {
        "capabilities": [
          {
            "name": "actions.capability.SCREEN_OUTPUT"
          },
          {
            "name": "actions.capability.AUDIO_OUTPUT"
          },
          {
            "name": "actions.capability.MEDIA_RESPONSE_AUDIO"
          },
          {
            "name": "actions.capability.WEB_BROWSER"
          }
        ]
      },
      "inputs": [
        {
          "rawInputs": [],
          "intent": "",
          "arguments": [
            {
              "extension": {
                "@type": "type.googleapis.com/google.transactions.v3.TransactionRequirementsCheckResult",
                "resultType": "CAN_TRANSACT"
              },
              "name": "TRANSACTION_REQUIREMENTS_CHECK_RESULT"
            }
          ]
        }
      ],
      "user": {},
      "conversation": {},
      "availableSurfaces": [
        {
          "capabilities": [
            {
              "name": "actions.capability.SCREEN_OUTPUT"
            },
            {
              "name": "actions.capability.AUDIO_OUTPUT"
            },
            {
              "name": "actions.capability.MEDIA_RESPONSE_AUDIO"
            },
            {
              "name": "actions.capability.WEB_BROWSER"
            }
          ]
        }
      ]
    }
  },
  "session": ""
}
Actions SDK JSON

아래 JSON은 웹훅 요청을 설명합니다.

{
  "user": {},
  "device": {},
  "surface": {
    "capabilities": [
      {
        "name": "actions.capability.SCREEN_OUTPUT"
      },
      {
        "name": "actions.capability.AUDIO_OUTPUT"
      },
      {
        "name": "actions.capability.MEDIA_RESPONSE_AUDIO"
      },
      {
        "name": "actions.capability.WEB_BROWSER"
      }
    ]
  },
  "conversation": {},
  "inputs": [
    {
      "rawInputs": [],
      "intent": "reservation_transaction_check_complete_asdk",
      "arguments": [
        {
          "extension": {
            "@type": "type.googleapis.com/google.transactions.v3.TransactionRequirementsCheckResult",
            "resultType": "CAN_TRANSACT"
          },
          "name": "TRANSACTION_REQUIREMENTS_CHECK_RESULT"
        }
      ]
    }
  ],
  "availableSurfaces": [
    {
      "capabilities": [
        {
          "name": "actions.capability.SCREEN_OUTPUT"
        },
        {
          "name": "actions.capability.AUDIO_OUTPUT"
        },
        {
          "name": "actions.capability.MEDIA_RESPONSE_AUDIO"
        },
        {
          "name": "actions.capability.WEB_BROWSER"
        }
      ]
    }
  ]
}

2. 주문 만들기

사용자 환경

필요한 사용자 정보를 확보하면 사용자가 예약을 작성하도록 안내하는 '장바구니 어셈블리' 환경을 빌드합니다. 모든 작업은 서비스에 따라 장바구니 어셈블리 흐름이 약간 다릅니다.

기본 장바구니 조립 환경에서는 사용자가 목록에서 옵션을 선택하여 예약에 추가할 수 있지만, 개발자는 사용자 환경을 간소화하도록 대화를 설계할 수 있습니다. 예를 들어 사용자가 간단한 예 또는 아니요 질문을 통해 월별 예약을 예약할 수 있는 장바구니 조립 환경을 빌드할 수 있습니다. '권장' 예약의 캐러셀이나 목록 카드를 사용자에게 표시할 수도 있습니다.

다양한 응답을 사용하여 사용자의 옵션을 시각적으로 표시하는 것이 좋지만 사용자가 음성만으로 장바구니를 만들 수 있도록 대화를 설계하는 것이 좋습니다. 장바구니 조립 환경의 일부 권장사항과 예는 트랜잭션 디자인 가이드라인을 참고하세요.

주문 처리

대화 전반에 걸쳐 사용자가 구매하려는 예약 세부정보를 수집한 다음 Order 객체를 구성합니다.

Order에는 최소한 다음이 포함되어야 합니다.

  • buyerInfo - 예약을 예약하는 사용자에 관한 정보입니다.
  • transactionMerchant: 예약을 지원하는 판매자에 관한 정보입니다.
  • contents - lineItems로 나열되는 예약의 실제 세부정보입니다.

장바구니를 구성하려면 Order 응답 문서를 참조하세요. 예약에 따라 다른 필드를 포함해야 할 수도 있습니다.

아래 샘플 코드는 선택적 필드를 포함한 전체 예약 순서를 보여줍니다.

Node.js
app.intent('build_reservation_df', (conv) => {
  const now = new Date().toISOString();
  const order = {
    createTime: now,
    lastUpdateTime: now,
    merchantOrderId: 'UNIQUE_ORDER_ID',
    userVisibleOrderId: 'USER_VISIBLE_ORDER_ID',
    transactionMerchant: {
      id: 'https://www.example.com',
      name: 'Example Merchant',
    },
    contents: {
      lineItems: [
        {
          id: 'LINE_ITEM_ID',
          name: 'Dinner reservation',
          description: 'A world of flavors all in one destination.',
          reservation: {
            status: 'PENDING',
            userVisibleStatusLabel: 'Reservation is pending.',
            type: 'RESTAURANT',
            reservationTime: {
              timeIso8601: '2020-01-16T01:30:15.01Z',
            },
            userAcceptableTimeRange: {
              timeIso8601: '2020-01-15/2020-01-17',
            },
            partySize: 6,
            staffFacilitators: [
              {
                name: 'John Smith',
              },
            ],
            location: {
              zipCode: '94086',
              city: 'Sunnyvale',
              postalAddress: {
                regionCode: 'US',
                postalCode: '94086',
                administrativeArea: 'CA',
                locality: 'Sunnyvale',
                addressLines: [
                  '222, Some other Street',
                ],
              },
            },
          },
        },
      ],
    },
    buyerInfo: {
      email: 'janedoe@gmail.com',
      firstName: 'Jane',
      lastName: 'Doe',
      displayName: 'Jane Doe',
    },
    followUpActions: [
      {
        type: 'VIEW_DETAILS',
        title: 'View details',
        openUrlAction: {
          url: 'https://example.com',
        },
      },
      {
        type: 'CALL',
        title: 'Call us',
        openUrlAction: {
          url: 'tel:+16501112222',
        },
      },
      {
        type: 'EMAIL',
        title: 'Email us',
        openUrlAction: {
          url: 'mailto:person@example.com',
        },
      },
    ],
    termsOfServiceUrl: 'https://www.example.com',
  };

자바
private static OrderV3 createOrder() {
  // Transaction Merchant
  MerchantV3 transactionMerchant = new MerchantV3()
      .setId("http://www.example.com")
      .setName("Example Merchant");

  // Line Item

  // Reservation Item Extension
  ReservationItemExtension reservationItemExtension = new ReservationItemExtension()
      .setStatus("PENDING")
      .setUserVisibleStatusLabel("Reservation pending.")
      .setType("RESTAURANT")
      .setReservationTime(new TimeV3()
          .setTimeIso8601("2020-01-16T01:30:15.01Z"))
      .setUserAcceptableTimeRange(new TimeV3()
          .setTimeIso8601("2020-01-15/2020-01-17"))
      .setPartySize(6)
      .setStaffFacilitators(Collections.singletonList(new StaffFacilitator()
          .setName("John Smith")))
      .setLocation(new Location()
          .setZipCode("94086")
          .setCity("Sunnyvale")
          .setPostalAddress(new PostalAddress()
              .setRegionCode("US")
              .setPostalCode("94086")
              .setAdministrativeArea("CA")
              .setLocality("Sunnyvale")
              .setAddressLines(
                  Collections.singletonList("222, Some other Street"))));

  LineItemV3 lineItem = new LineItemV3()
      .setId("LINE_ITEM_ID")
      .setName("Dinner reservation")
      .setDescription("A world of flavors all in one destination.")
      .setReservation(reservationItemExtension);

  // Order Contents
  OrderContents contents = new OrderContents()
      .setLineItems(Collections.singletonList(lineItem));

  // User Info
  UserInfo buyerInfo = new UserInfo()
      .setEmail("janedoe@gmail.com")
      .setFirstName("Jane")
      .setLastName("Doe")
      .setDisplayName("Jane Doe");

  // Follow up actions
  Action viewDetails = new Action()
      .setType("VIEW_DETAILS")
      .setTitle("View details")
      .setOpenUrlAction(new OpenUrlAction()
          .setUrl("https://example.com"));

  Action call = new Action()
      .setType("CALL")
      .setTitle("Call us")
      .setOpenUrlAction(new OpenUrlAction()
          .setUrl("tel:+16501112222"));

  Action email = new Action()
      .setType("EMAIL")
      .setTitle("Email us")
      .setOpenUrlAction(new OpenUrlAction()
          .setUrl("mailto:person@example.com"));

  // Terms of service and order note
  String termsOfServiceUrl = "https://example.com";

  String now = Instant.now().toString();

  OrderV3 order = new OrderV3()
      .setCreateTime(now)
      .setLastUpdateTime(now)
      .setMerchantOrderId("UNIQUE_ORDER_ID")
      .setUserVisibleOrderId("UNIQUE_USER_VISIBLE_ORDER_ID")
      .setTransactionMerchant(transactionMerchant)
      .setContents(contents)
      .setBuyerInfo(buyerInfo)
      .setFollowUpActions(Arrays.asList(
          viewDetails,
          call,
          email
      ))
      .setTermsOfServiceUrl(termsOfServiceUrl);

  return order;
}
JSON

아래의 JSON은 웹훅 응답을 설명합니다.

{
  "payload": {
    "google": {
      "expectUserResponse": true,
      "systemIntent": {
        "intent": "actions.intent.TRANSACTION_DECISION",
        "data": {
          "@type": "type.googleapis.com/google.actions.transactions.v3.TransactionDecisionValueSpec",
          "order": {
            "createTime": "2019-07-17T18:25:30.182Z",
            "lastUpdateTime": "2019-07-17T18:25:30.182Z",
            "merchantOrderId": "UNIQUE_ORDER_ID",
            "userVisibleOrderId": "USER_VISIBLE_ORDER_ID",
            "transactionMerchant": {
              "id": "https://www.example.com",
              "name": "Example Merchant"
            },
            "contents": {
              "lineItems": [
                {
                  "id": "LINE_ITEM_ID",
                  "name": "Dinner reservation",
                  "description": "A world of flavors all in one destination.",
                  "reservation": {
                    "status": "PENDING",
                    "userVisibleStatusLabel": "Reservation is pending.",
                    "type": "RESTAURANT",
                    "reservationTime": {
                      "timeIso8601": "2020-01-16T01:30:15.01Z"
                    },
                    "userAcceptableTimeRange": {
                      "timeIso8601": "2020-01-15/2020-01-17"
                    },
                    "partySize": 6,
                    "staffFacilitators": [
                      {
                        "name": "John Smith"
                      }
                    ],
                    "location": {
                      "zipCode": "94086",
                      "city": "Sunnyvale",
                      "postalAddress": {
                        "regionCode": "US",
                        "postalCode": "94086",
                        "administrativeArea": "CA",
                        "locality": "Sunnyvale",
                        "addressLines": [
                          "222, Some other Street"
                        ]
                      }
                    }
                  }
                }
              ]
            },
            "buyerInfo": {
              "email": "janedoe@gmail.com",
              "firstName": "Jane",
              "lastName": "Doe",
              "displayName": "Jane Doe"
            },
            "followUpActions": [
              {
                "type": "VIEW_DETAILS",
                "title": "View details",
                "openUrlAction": {
                  "url": "https://example.com"
                }
              },
              {
                "type": "CALL",
                "title": "Call us",
                "openUrlAction": {
                  "url": "tel:+16501112222"
                }
              },
              {
                "type": "EMAIL",
                "title": "Email us",
                "openUrlAction": {
                  "url": "mailto:person@example.com"
                }
              }
            ],
            "termsOfServiceUrl": "https://www.example.com"
          },
          "orderOptions": {
            "requestDeliveryAddress": false,
            "userInfoOptions": {
              "userInfoProperties": [
                "EMAIL"
              ]
            }
          },
          "presentationOptions": {
            "actionDisplayName": "RESERVE"
          }
        }
      }
    }
  }
}

3. 주문 제안

확인 또는 거부할 수 있도록 사용자에게 예약 주문을 제시합니다. actions.intent.TRANSACTION_DECISION 인텐트를 요청하고 빌드한 Order를 제공합니다.

사용자 경험

actions.intent.TRANSACTION_DECISION 인텐트를 요청하면 어시스턴트는 Order가 '장바구니 미리보기 카드'에 직접 렌더링되는 내장 환경을 시작합니다. 사용자는 '예약 예약'이라고 말하거나, 트랜잭션을 거부하거나, 예약 세부정보 변경을 요청할 수 있습니다.

이때 사용자는 주문 변경을 요청할 수도 있습니다. 이 경우 장바구니 조립 환경을 완료한 후 처리에서 주문 변경 요청을 처리할 수 있는지 확인해야 합니다.

주문 처리

actions.intent.TRANSACTION_DECISION 인텐트를 요청할 때 OrderorderOptions가 포함된 TransactionDecision를 만듭니다.

다음 코드는 주문의 TransactionsDecision 예를 보여줍니다.

Node.js
conv.ask(new TransactionDecision({
  orderOptions: {
    requestDeliveryAddress: 'false',
  },
  presentationOptions: {
    actionDisplayName: 'RESERVE',
  },
  order: order,
}));
자바
// Create order options
OrderOptionsV3 orderOptions = new OrderOptionsV3()
    .setRequestDeliveryAddress(false)
    .setUserInfoOptions(new UserInfoOptions()
        .setUserInfoProperties(Collections.singletonList("EMAIL")));

// Create presentation options
PresentationOptionsV3 presentationOptions = new PresentationOptionsV3()
    .setActionDisplayName("RESERVE");

// Ask for transaction decision
return getResponseBuilder(request)
    .add("Placeholder for transaction decision text")
    .add(new TransactionDecision()
        .setOrder(order)
        .setOrderOptions(orderOptions)
        .setPresentationOptions(presentationOptions)
    )
    .build();
Dialogflow JSON

아래의 JSON은 웹훅 응답을 설명합니다.

{
  "payload": {
    "google": {
      "expectUserResponse": true,
      "systemIntent": {
        "intent": "actions.intent.TRANSACTION_DECISION",
        "data": {
          "@type": "type.googleapis.com/google.actions.transactions.v3.TransactionDecisionValueSpec",
          "orderOptions": {
            "requestDeliveryAddress": "false"
          },
          "presentationOptions": {
            "actionDisplayName": "RESERVE"
          },
          "order": {
            "createTime": "2019-07-17T18:25:30.184Z",
            "lastUpdateTime": "2019-07-17T18:25:30.184Z",
            "merchantOrderId": "UNIQUE_ORDER_ID",
            "userVisibleOrderId": "USER_VISIBLE_ORDER_ID",
            "transactionMerchant": {
              "id": "https://www.example.com",
              "name": "Example Merchant"
            },
            "contents": {
              "lineItems": [
                {
                  "id": "LINE_ITEM_ID",
                  "name": "Dinner reservation",
                  "description": "A world of flavors all in one destination.",
                  "reservation": {
                    "status": "PENDING",
                    "userVisibleStatusLabel": "Reservation is pending.",
                    "type": "RESTAURANT",
                    "reservationTime": {
                      "timeIso8601": "2020-01-16T01:30:15.01Z"
                    },
                    "userAcceptableTimeRange": {
                      "timeIso8601": "2020-01-15/2020-01-17"
                    },
                    "partySize": 6,
                    "staffFacilitators": [
                      {
                        "name": "John Smith"
                      }
                    ],
                    "location": {
                      "zipCode": "94086",
                      "city": "Sunnyvale",
                      "postalAddress": {
                        "regionCode": "US",
                        "postalCode": "94086",
                        "administrativeArea": "CA",
                        "locality": "Sunnyvale",
                        "addressLines": [
                          "222, Some other Street"
                        ]
                      }
                    }
                  }
                }
              ]
            },
            "buyerInfo": {
              "email": "janedoe@gmail.com",
              "firstName": "Jane",
              "lastName": "Doe",
              "displayName": "Jane Doe"
            },
            "followUpActions": [
              {
                "type": "VIEW_DETAILS",
                "title": "View details",
                "openUrlAction": {
                  "url": "https://example.com"
                }
              },
              {
                "type": "CALL",
                "title": "Call us",
                "openUrlAction": {
                  "url": "tel:+16501112222"
                }
              },
              {
                "type": "EMAIL",
                "title": "Email us",
                "openUrlAction": {
                  "url": "mailto:person@example.com"
                }
              }
            ],
            "termsOfServiceUrl": "https://www.example.com"
          }
        }
      }
    }
  }
}
Actions SDK JSON

아래의 JSON은 웹훅 응답을 설명합니다.

{
  "expectUserResponse": true,
  "expectedInputs": [
    {
      "possibleIntents": [
        {
          "intent": "actions.intent.TRANSACTION_DECISION",
          "inputValueData": {
            "@type": "type.googleapis.com/google.actions.transactions.v3.TransactionDecisionValueSpec",
            "orderOptions": {
              "requestDeliveryAddress": "false"
            },
            "presentationOptions": {
              "actionDisplayName": "RESERVE"
            },
            "order": {
              "createTime": "2019-07-17T18:25:30.057Z",
              "lastUpdateTime": "2019-07-17T18:25:30.057Z",
              "merchantOrderId": "UNIQUE_ORDER_ID",
              "userVisibleOrderId": "USER_VISIBLE_ORDER_ID",
              "transactionMerchant": {
                "id": "https://www.example.com",
                "name": "Example Merchant"
              },
              "contents": {
                "lineItems": [
                  {
                    "id": "LINE_ITEM_ID",
                    "name": "Dinner reservation",
                    "description": "A world of flavors all in one destination.",
                    "reservation": {
                      "status": "PENDING",
                      "userVisibleStatusLabel": "Reservation is pending.",
                      "type": "RESTAURANT",
                      "reservationTime": {
                        "timeIso8601": "2020-01-16T01:30:15.01Z"
                      },
                      "userAcceptableTimeRange": {
                        "timeIso8601": "2020-01-15/2020-01-17"
                      },
                      "partySize": 6,
                      "staffFacilitators": [
                        {
                          "name": "John Smith"
                        }
                      ],
                      "location": {
                        "zipCode": "94086",
                        "city": "Sunnyvale",
                        "postalAddress": {
                          "regionCode": "US",
                          "postalCode": "94086",
                          "administrativeArea": "CA",
                          "locality": "Sunnyvale",
                          "addressLines": [
                            "222, Some other Street"
                          ]
                        }
                      }
                    }
                  }
                ]
              },
              "buyerInfo": {
                "email": "janedoe@gmail.com",
                "firstName": "Jane",
                "lastName": "Doe",
                "displayName": "Jane Doe"
              },
              "followUpActions": [
                {
                  "type": "VIEW_DETAILS",
                  "title": "View details",
                  "openUrlAction": {
                    "url": "https://example.com"
                  }
                },
                {
                  "type": "CALL",
                  "title": "Call us",
                  "openUrlAction": {
                    "url": "tel:+16501112222"
                  }
                },
                {
                  "type": "EMAIL",
                  "title": "Email us",
                  "openUrlAction": {
                    "url": "mailto:person@example.com"
                  }
                }
              ],
              "termsOfServiceUrl": "https://www.example.com"
            }
          }
        }
      ]
    }
  ]
}
사용자의 결정 처리

사용자가 제안된 주문에 응답하면 처리는 TransactionDecisionValue가 포함된 인수와 함께 actions_intent_TRANSACTION_DECISION 인텐트를 수신합니다. 이 값에는 다음이 포함됩니다.

  • transactionDecision - 제안된 주문과 관련된 사용자의 결정입니다. 가능한 값은 ORDER_ACCEPTED, ORDER_REJECTED, CART_CHANGE_REQUESTED, USER_CANNOT_TRANSACT입니다.

이 요청을 처리하려면 actions_intent_TRANSACTION_DECISION 이벤트에 의해 트리거되는 Dialogflow 인텐트를 선언합니다. 처리에서 이 인텐트를 처리합니다.

Node.js
const arg = conv.arguments.get('TRANSACTION_DECISION_VALUE');
if (arg && arg.transactionDecision === 'ORDER_ACCEPTED') {
  console.log('order accepted');
  const order = arg.order;
}
자바
Argument transactionDecisionValue = request
    .getArgument("TRANSACTION_DECISION_VALUE");
Map<String, Object> extension = null;
if (transactionDecisionValue != null) {
  extension = transactionDecisionValue.getExtension();
}

String transactionDecision = null;
if (extension != null) {
  transactionDecision = (String) extension.get("transactionDecision");
}
if ((transactionDecision != null && transactionDecision.equals("ORDER_ACCEPTED"))) {
  OrderV3 order = ((OrderV3) extension.get("order"));
}
Dialogflow JSON

아래 JSON은 웹훅 요청을 설명합니다.

{
  "responseId": "",
  "queryResult": {
    "queryText": "",
    "action": "",
    "parameters": {},
    "allRequiredParamsPresent": true,
    "fulfillmentText": "",
    "fulfillmentMessages": [],
    "outputContexts": [],
    "intent": {
      "name": "reservation_get_transaction_decision_df",
      "displayName": "reservation_get_transaction_decision_df"
    },
    "intentDetectionConfidence": 1,
    "diagnosticInfo": {},
    "languageCode": ""
  },
  "originalDetectIntentRequest": {
    "source": "google",
    "version": "2",
    "payload": {
      "isInSandbox": true,
      "surface": {
        "capabilities": [
          {
            "name": "actions.capability.SCREEN_OUTPUT"
          },
          {
            "name": "actions.capability.AUDIO_OUTPUT"
          },
          {
            "name": "actions.capability.MEDIA_RESPONSE_AUDIO"
          },
          {
            "name": "actions.capability.WEB_BROWSER"
          }
        ]
      },
      "inputs": [
        {
          "rawInputs": [],
          "intent": "",
          "arguments": []
        }
      ],
      "user": {},
      "conversation": {},
      "availableSurfaces": [
        {
          "capabilities": [
            {
              "name": "actions.capability.SCREEN_OUTPUT"
            },
            {
              "name": "actions.capability.AUDIO_OUTPUT"
            },
            {
              "name": "actions.capability.MEDIA_RESPONSE_AUDIO"
            },
            {
              "name": "actions.capability.WEB_BROWSER"
            }
          ]
        }
      ]
    }
  },
  "session": ""
}
Actions SDK JSON

아래 JSON은 웹훅 요청을 설명합니다.

{
  "user": {},
  "device": {},
  "surface": {
    "capabilities": [
      {
        "name": "actions.capability.SCREEN_OUTPUT"
      },
      {
        "name": "actions.capability.AUDIO_OUTPUT"
      },
      {
        "name": "actions.capability.MEDIA_RESPONSE_AUDIO"
      },
      {
        "name": "actions.capability.WEB_BROWSER"
      }
    ]
  },
  "conversation": {},
  "inputs": [
    {
      "rawInputs": [],
      "intent": "reservation_get_transaction_decision_asdk",
      "arguments": []
    }
  ],
  "availableSurfaces": [
    {
      "capabilities": [
        {
          "name": "actions.capability.SCREEN_OUTPUT"
        },
        {
          "name": "actions.capability.AUDIO_OUTPUT"
        },
        {
          "name": "actions.capability.MEDIA_RESPONSE_AUDIO"
        },
        {
          "name": "actions.capability.WEB_BROWSER"
        }
      ]
    }
  ]
}

4. 예약을 완료하고 영수증 보내기

actions.intent.TRANSACTION_DECISION 인텐트가 ORDER_ACCEPTEDtransactionDecision를 반환하면 예약을 예약하는 데 필요한 처리를 수행합니다 (예: 자체 데이터베이스에 예약 유지).

간단한 응답을 보내 대화를 이어가세요. 사용자는 응답과 함께 '접힌 영수증 카드'를 받게 됩니다.

주문 처리

Node.js
// Set lastUpdateTime and update status of reservation
order.lastUpdateTime = new Date().toISOString();
order.reservation.status = 'CONFIRMED';
order.reservation.userVisibleStatusLabel = 'Reservation confirmed';
order.reservation.confirmationCode = '123ABCDEFGXYZ';

// Send synchronous order update
conv.ask(`Transaction completed! You're all set!`);
conv.ask(new OrderUpdate({
  type: 'SNAPSHOT',
  reason: 'Reason string',
  order: order,
}));
자바
ResponseBuilder responseBuilder = getResponseBuilder(request);
order.setLastUpdateTime(Instant.now().toString());

// Set reservation status to confirmed and provide confirmation code
LineItemV3 lineItem = order.getContents().getLineItems().get(0);
ReservationItemExtension reservationItemExtension = lineItem.getReservation();
reservationItemExtension.setStatus("CONFIRMED");
reservationItemExtension.setUserVisibleStatusLabel("Reservation confirmed.");
reservationItemExtension.setConfirmationCode("123ABCDEFGXYZ");
lineItem.setReservation(reservationItemExtension);
order.getContents().getLineItems().set(0, lineItem);

// Order update
OrderUpdateV3 orderUpdate = new OrderUpdateV3()
    .setType("SNAPSHOT")
    .setReason("Reason string")
    .setOrder(order);

responseBuilder
    .add("Transaction completed! You're all set! Would you like to do anything else?")
    .add(new StructuredResponse().setOrderUpdateV3(orderUpdate));
return responseBuilder.build();
Dialogflow JSON

아래의 JSON은 웹훅 응답을 설명합니다.

{
  "payload": {
    "google": {
      "expectUserResponse": true,
      "richResponse": {
        "items": [
          {
            "simpleResponse": {
              "textToSpeech": "Transaction completed! You're all set!"
            }
          },
          {
            "structuredResponse": {
              "orderUpdateV3": {
                "type": "SNAPSHOT",
                "reason": "Reason string",
                "order": {
                  "merchantOrderId": "UNIQUE_ORDER_ID",
                  "reservation": {
                    "status": "CONFIRMED",
                    "userVisibleStatusLabel": "Reservation confirmed",
                    "confirmationCode": "123ABCDEFGXYZ"
                  },
                  "lastUpdateTime": "2019-07-17T18:25:30.187Z"
                }
              }
            }
          }
        ]
      }
    }
  }
}
Actions SDK JSON

아래의 JSON은 웹훅 응답을 설명합니다.

{
  "expectUserResponse": true,
  "expectedInputs": [
    {
      "possibleIntents": [
        {
          "intent": "actions.intent.TEXT"
        }
      ],
      "inputPrompt": {
        "richInitialPrompt": {
          "items": [
            {
              "simpleResponse": {
                "textToSpeech": "Transaction completed! You're all set!"
              }
            },
            {
              "structuredResponse": {
                "orderUpdateV3": {
                  "type": "SNAPSHOT",
                  "reason": "Reason string",
                  "order": {
                    "merchantOrderId": "UNIQUE_ORDER_ID",
                    "reservation": {
                      "status": "CONFIRMED",
                      "userVisibleStatusLabel": "Reservation confirmed",
                      "confirmationCode": "123ABCDEFGXYZ"
                    },
                    "lastUpdateTime": "2019-07-17T18:25:30.059Z"
                  }
                }
              }
            }
          ]
        }
      }
    }
  ]
}

5. 주문 업데이트 보내기

예약 상태는 전체 기간 동안 변경됩니다. 주문 상태와 세부정보가 포함된 HTTP PATCH 요청을 포함하는 사용자 예약 주문 업데이트를 Orders API로 전송합니다.

Orders API에 대한 비동기 요청 설정

Orders API에 대한 주문 업데이트 요청은 액세스 토큰으로 승인됩니다. Orders API에 주문 업데이트를 PATCH하려면 Actions Console 프로젝트와 연결된 JSON 서비스 계정 키를 다운로드한 다음 서비스 계정 키를 HTTP 요청의 Authorization 헤더에 전달할 수 있는 Bearer 토큰으로 교환합니다.

서비스 계정 키를 검색하려면 다음 단계를 수행합니다.

  1. Google Cloud 콘솔에서 메뉴 ⇧ > API 및 서비스 > 사용자 인증 정보 > 사용자 인증 정보 만들기 > 서비스 계정 키로 이동합니다.
  2. 서비스 계정에서 새 서비스 계정을 선택합니다.
  3. 서비스 계정을 service-account로 설정합니다.
  4. 역할프로젝트 > 소유자로 설정합니다.
  5. 키 유형을 JSON으로 설정합니다.
  6. 만들기를 선택합니다.
  7. 비공개 JSON 서비스 계정 키가 로컬 머신에 다운로드됩니다.

주문 업데이트 코드에서 Google API 클라이언트 라이브러리와 "https://www.googleapis.com/auth/actions.order.developer" 범위를 사용하여 서비스 키를 Bearer 토큰으로 교환합니다. API 클라이언트 라이브러리 GitHub 페이지에서 설치 단계 및 예시를 확인할 수 있습니다.

키 교환 예는 Node.js자바 샘플에서 order-update.js를 참조하세요.

주문 업데이트 보내기

서비스 계정 키를 OAuth Bearer 토큰으로 교환한 후 주문 업데이트를 승인된 PATCH 요청으로 Orders API에 전송합니다.

Order API URL: PATCH https://actions.googleapis.com/v3/orders/${orderId}

요청에 다음 헤더를 제공합니다.

  • "Authorization: Bearer token"를 서비스 계정 키를 교환한 OAuth Bearer 토큰으로 바꿉니다.
  • "Content-Type: application/json".

PATCH 요청은 다음 형식의 JSON 본문을 사용해야 합니다.

{ "orderUpdate": OrderUpdate }

OrderUpdate 객체는 다음과 같은 최상위 필드로 구성됩니다.

  • updateMask - 업데이트하는 주문의 필드입니다. 예약 상태를 업데이트하려면 값을 reservation.status, reservation.userVisibleStatusLabel로 설정합니다.
  • order - 업데이트 콘텐츠입니다. 예약의 콘텐츠를 업데이트하려면 업데이트된 Order 객체로 값을 설정합니다. 예약 상태만 업데이트하는 경우 (예: "PENDING"에서 "FULFILLED"로) 객체에는 다음 필드가 포함됩니다.

    • merchantOrderId: Order 객체에서 설정한 것과 동일한 ID입니다.
    • lastUpdateTime - 이 업데이트의 타임스탬프입니다.
    • purchase - 다음을 포함하는 객체입니다.
      • status - 주문 상태를 ReservationStatus로 나타냅니다(예: 'CONFIRMED' 또는 'CANCELLED').
      • userVisibleStatusLabel - '예약이 확인되었습니다'와 같이 주문 상태의 세부정보를 제공하는 사용자 대상 라벨입니다.
  • userNotification 객체. 이 객체를 포함한다고 해서 알림이 사용자 기기에 표시되는 것은 아닙니다.

다음 샘플 코드는 예약 주문의 상태를 FULFILLED로 업데이트하는 OrderUpdate 예시를 보여줍니다.

Node.js
// Import the 'googleapis' module for authorizing the request.
const {google} = require('googleapis');
// Import the 'request' module for sending an HTTP POST request.
const request = require('request');
// Import the OrderUpdate class from the Actions on Google client library.
const {OrderUpdate} = require('actions-on-google');
// Import the service account key used to authorize the request. Replace the string path with a path to your service account key.
const key = require('./service-account.json');
// Create a new JWT client for the Actions API using credentials from the service account key.
let jwtClient = new google.auth.JWT(
    key.client_email,
    null,
    key.private_key,
    ['https://www.googleapis.com/auth/actions.order.developer'],
    null
);
// Authorize the client asynchronously, passing in a callback to run upon authorization.
jwtClient.authorize((err, tokens) => {
    if (err) {
        console.log(err);
        return;
    }
    // Declare the ID of the order to update.
    const orderId = '<UNIQUE_MERCHANT_ORDER_ID>';

    const orderUpdateJson = new OrderUpdate({
        updateMask: [
          'lastUpdateTime',
          'contents.lineItems.reservation.status',
          'contents.lineItems.reservation.userVisibleStatusLabel',
      ].join(','),
        order: {
          merchantOrderId: orderId,
          lastUpdateTime: new Date().toISOString(),
          contents: {
            lineItems: [
              {
                reservation: {
                  status: 'FULFILLED',
                  userVisibleStatusLabel: 'Reservation fulfilled',
                },
              }
            ]
          }
        },
        reason: 'Reservation status was updated to fulfilled.',
    });

    // Set up the PATCH request header and body, including the authorized token
    // and order update.
    const bearer = 'Bearer ' + tokens.access_token;
    const options = {
        method: 'PATCH',
        url: `https://actions.googleapis.com/v3/orders/${orderId}`,
        headers: {
          'Authorization': bearer,
        },
        body: {
          header: {
            'isInSandbox': true,
          },
          orderUpdate: orderUpdateJson,
        },
        json: true,
      };
    // Send the PATCH request to the Orders API.
    request.patch(options, (err, httpResponse, body) => {
        if (err) {
            console.log('There was an error...');
            console.log(err);
            return;
        }
    });
});
자바
// Create order update
FieldMask fieldMask = FieldMask.newBuilder().addAllPaths(Arrays.asList(
    "lastUpdateTime",
    "contents.lineItems.reservation.status",
    "contents.lineItems.reservation.userVisibleStatusLabel"))
    .build();

OrderUpdateV3 orderUpdate = new OrderUpdateV3()
    .setOrder(new OrderV3()
        .setMerchantOrderId(orderId)
        .setLastUpdateTime(Instant.now().toString())
        .setContents(new OrderContents()
        .setLineItems(Collections.singletonList(new LineItemV3()
            .setReservation(new ReservationItemExtension()
                .setStatus("FULFILLED")
                .setUserVisibleStatusLabel("Reservation fulfilled."))))))
    .setUpdateMask(FieldMaskUtil.toString(fieldMask))
    .setReason("Reservation status was updated to fulfilled.");

// Setup JSON body containing order update
JsonParser parser = new JsonParser();
JsonObject orderUpdateJson =
    parser.parse(new Gson().toJson(orderUpdate)).getAsJsonObject();
JsonObject body = new JsonObject();
body.add("orderUpdate", orderUpdateJson);
JsonObject header = new JsonObject();
header.addProperty("isInSandbox", true);
body.add("header", header);
StringEntity entity = new StringEntity(body.toString());
entity.setContentType(ContentType.APPLICATION_JSON.getMimeType());
request.setEntity(entity);

// Make request
HttpClient httpClient = HttpClientBuilder.create().build();
HttpResponse response = httpClient.execute(request);
예약 상태 설정

주문 업데이트의 ReservationStatus는 주문의 현재 상태를 설명해야 합니다. 업데이트의 order.ReservationStatus 필드에서 다음 값 중 하나를 사용합니다.

  • PENDING - 작업에서 예약이 '생성'되었지만 백엔드에서 추가 처리가 필요합니다.
  • CONFIRMED - 예약 백엔드에서 예약이 확인됩니다.
  • CANCELLED - 사용자가 예약을 취소했습니다.
  • FULFILLED - 서비스에서 사용자의 예약이 처리되었습니다.
  • CHANGE_REQUESTED - 사용자가 예약 변경을 요청했으며 변경사항을 처리하는 중입니다.
  • REJECTED - 예약을 처리하거나 확인할 수 없는 경우

예약과 관련된 각 상태에 대한 주문 업데이트를 전송합니다. 예를 들어 예약이 요청된 후 예약을 확인하기 위해 수동 처리가 필요한 경우 추가 처리가 완료될 때까지 PENDING 주문 업데이트를 전송합니다. 모든 예약에 모든 상태 값이 필요한 것은 아닙니다.

문제 해결

테스트 중에 문제가 발생하면 트랜잭션의 문제 해결 단계를 읽어보세요.