PHP skeleton for Booking Server API v3
diff --git a/.htaccess b/.htaccess
new file mode 100644
index 0000000..6b6f978
--- /dev/null
+++ b/.htaccess
@@ -0,0 +1,34 @@
+# Copyright 2019, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+<IfModule mod_rewrite.c>
+Options +FollowSymLinks
+RewriteEngine On
+RewriteRule ^v3/(.*) bookingserver.php?route=$1 [NC]
+</IfModule>
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..c5bc859
--- /dev/null
+++ b/README.md
@@ -0,0 +1,53 @@
+# Booking Server Skeleton for PHP
+
+This is a reference implementation of
+[API v3 Booking Server](https://developers.google.com/maps-booking/guides/end-to-end-integration/implement-booking-server)
+based on PHP
+
+### Prerequisites
+
+Requires an installation of
+
+*   [Apache](https://httpd.apache.org/)
+*   [PHP](https://secure.php.net/)
+
+## Getting Started
+
+The Booking Server is implemented using PHP and Apache. To properly route the
+requests it makes use of the
+[.htaccess file](https://httpd.apache.org/docs/current/howto/htaccess.html) so
+make sure that
+[AllowOverride](https://httpd.apache.org/docs/current/mod/core.html#allowoverride)
+is enabled for the directory.
+
+It is required that you implement an SSL certificate and have all requests
+served over https. If your server does not already have an SSL certificate setup
+you can review the
+[Apache SSL/TLS documentation](https://httpd.apache.org/docs/2.4/ssl/ssl_howto.html).
+
+The implementation is also not using protocol buffer libraries, but instead
+relies on simple JSON serialization methods.
+
+To download the project execute the following command:
+
+    git clone https://maps-booking.googlesource.com/php-maps-booking-rest-server-v3-skeleton
+
+The entire code base consists of only three files:
+
+*   .htaccess instructs apache to route all of the /v3/ requests through
+    bookingserver.php
+*   bookingserver.php handles the request logic, including authentication
+*   apiv3methods.php has the methods implementing API v3 interface
+
+After you downloaded the files you should place them in a servable directory.
+Note that whichever directory you put them in will automatically be the parent
+to the /v3/ folder (which you do not need to create).
+
+## Testing your Booking Server
+
+For instructions on how to test your booking server refer to the
+[booking server testing](https://developers.google.com/maps-booking/guides/end-to-end-integration/test-booking-server)
+section of our documentation. It is reccomended that you download and run the
+[booking test utility](https://maps-booking.googlesource.com/maps-booking-v3/).
+To install it, follow the provided installation instructions in its README page.
+
diff --git a/apiv3methods.php b/apiv3methods.php
new file mode 100644
index 0000000..8ad6db2
--- /dev/null
+++ b/apiv3methods.php
@@ -0,0 +1,209 @@
+<?php
+/*
+ * Copyright 2019, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * HealthCheck method
+ * https://developers.google.com/maps-booking/reference/rest-api-v3/healthcheck-method
+ * @param {string} requestBody - HTTP request body
+ * @return {string} HTTP response body
+ */
+function healthCheck() {
+  // TO-DO: add any additional server checks, e.g. database status
+  // Return a response similar to gRPC Health Check
+  // https://github.com/grpc/grpc/blob/master/doc/health-checking.md
+  $res['status'] = 'serving';
+  return json_encode($res);
+}
+/**
+ * CheckAvailability method
+ * https://developers.google.com/maps-booking/reference/rest-api-v3/checkavailability-method
+ * @param {string} requestBody - HTTP request body
+ * @return {string} HTTP response body
+ */
+
+function checkAvailability($request) {
+  // CheckAvailabilityRequest
+  $req = json_decode($request, true);
+  // TO-DO: validate req, e.g.
+  //   (req.slot !== null && req.slot.merchant_id !== null)
+  // TO-DO: add code to verify the provided slot availability
+  // CheckAvailabilityResponse
+  $resp = [
+    'slot' => $req['slot'],
+    'count_available' => 1,
+    'duration_requirement' => 'DURATION_REQUIREMENT_UNSPECIFIED'
+    // TO-DO: populate proper values and other fields, such as
+    // availability_update
+  ];
+  return json_encode($resp);
+}
+/**
+ * CreateBooking method
+ * https://developers.google.com/maps-booking/reference/rest-api-v3/createbooking-method
+ * @param {string} requestBody - HTTP request body
+ * @return {string} HTTP response body
+ */
+
+function createBooking($request) {
+  // CreateBookingRequest
+  $req = json_decode($request, true);
+
+  // TO-DO: validate req, e.g. (req.user_information !== null)
+  // TO-DO: add code to create a booking
+  // CreateBookingResponse
+  $resp['booking'] = [
+    'booking_id' => '1234',
+    'slot' => $req['slot'],
+    'user_information' => ['user_id' => $req['user_information']['user_id']],
+    'payment_information' => $req['payment_information'],
+    'status' => 'CONFIRMED'
+  ];
+  return json_encode($resp);
+}
+/**
+ * UpdateBooking method
+ * https://developers.google.com/maps-booking/reference/rest-api-v3/updatebooking-method
+ * @param {string} requestBody - HTTP request body
+ * @return {string} HTTP response body
+ */
+
+function updateBooking($request) {
+  // UpdateBookingRequest
+  $req = json_decode($request, true);
+  // TO-DO: validate req, e.g.
+  //   (req.booking !== null && req.booking.booking_id !== null)
+  // TO-DO: add code to update the provided booking
+  // UpdateBookingResponse
+  $resp['booking'] = [
+      'booking_id' => $req['booking']['booking_id'],
+      'status' => $req['booking']['status']
+  ];
+  return json_encode($resp);
+}
+/**
+ * GetBookingStatus method
+ * https://developers.google.com/maps-booking/reference/rest-api-v3/getbookingstatus-method
+ * @param {string} requestBody - HTTP request body
+ * @return {string} HTTP response body
+ */
+
+function getBookingStatus($request) {
+  // GetBookingStatusRequest
+  $req = json_decode($request, true);
+  // TO-DO: validate req, e.g. (req.booking_id !== null)
+  // TO-DO: add code to retrieve the booking status
+  // GetBookingStatusResponse
+  $resp = [
+    'booking_id' => $req['booking_id'],
+    'booking_status' => 'BOOKING_STATUS_UNSPECIFIED'
+  ];
+  return json_encode($resp);
+}
+/**
+ * ListBookings method
+ * https://developers.google.com/maps-booking/reference/rest-api-v3/listbookings-method
+ * @param {string} requestBody - HTTP request body
+ * @return {string} HTTP response body
+ */
+
+function listBookings($request) {
+  // ListBookingsRequest
+  $req = json_decode($request, true);
+  // TO-DO: validate req, e.g. (req.user_id !== null)
+  // TO-DO: add code to fetch all bookings for the user_id
+  // ListBookingsResponse
+  $resp = [
+    'booking_id' => $req['booking_id'],
+    'booking_status' => 'BOOKING_STATUS_UNSPECIFIED'
+  ];
+  return json_encode($resp);
+}
+/**
+ * CheckOrderFulfillability method (Order-based Booking Server only)
+ * https://developers.google.com/maps-booking/reference/rest-api-v3/checkorderfulfillability-method
+ * @param {string} requestBody - HTTP request body
+ * @return {string} HTTP response body
+ */
+function checkOrderFulfillability($request) {
+  // CheckOrderFulfillabilityRequest
+  // TO-DO: validate req, e.g. (req.merchant_id !== null)
+  // TO-DO: add code to validate individual items and calculate the total price
+  $req = json_decode($request, true);
+  // CheckOrderFulfillabilityResponse
+  $resp = [
+    'fulfillability' => [
+      'result' => 'CAN_FULFILL',
+      'item_fulfillability' => []
+    ],
+    'fees_and_taxes' => ['price_micros' => 1000000, 'currency_code' => 'USD']
+  ];
+
+  return json_encode($resp);
+}
+/**
+ * CreateOrder method (Order-based Booking Server only)
+ * https://developers.google.com/maps-booking/reference/rest-api-v3/createorder-method
+ * @param {string} requestBody - HTTP request body
+ * @return {string} HTTP response body
+ */
+function createOrder($request) {
+  // CreateOrderRequest
+  $req = json_decode($request, true);
+  // TO-DO: validate req, e.g. (req.user_information !== null)
+  // TO-DO: check for req.idempotency_token uniqueness
+  // TO-DO: create and process the order
+  // CreateOrderResponse
+  $resp['order'] = [
+    'order_id' => '1234',
+    'merchant_id' => $req['order']['merchant_id'],
+    'item' => []
+  ];
+  return json_encode($resp);
+}
+/**
+ * ListOrders method (Order-based Booking Server only)
+ * https://developers.google.com/maps-booking/reference/rest-api-v3/listorders-method
+ * @param {string} requestBody - HTTP request body
+ * @return {string} HTTP response body
+ */
+function listOrders($request) {
+  // ListOrdersRequest
+  $req = json_decode($request, true);
+  // TO-DO: validate req, e.g. if ("user_id" in req || "order_ids" in req)
+  // TO-DO: fetch orders for req.user_id or a list of req.order_ids
+  // ListOrdersResponse
+  $resp = [
+    'order' => []
+  ];
+  return json_encode($resp);
+}
+
diff --git a/bookingserver.php b/bookingserver.php
new file mode 100644
index 0000000..901fb83
--- /dev/null
+++ b/bookingserver.php
@@ -0,0 +1,188 @@
+<?php
+/*
+ * Copyright 2019, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+$route = $_GET['route'];
+$method = $_SERVER['REQUEST_METHOD'];
+
+//Ensure all requests are made over https
+if(empty($_SERVER['HTTPS']) || $_SERVER['HTTPS'] !== "on") {
+  header('HTTP/1.0 403 Forbidden');
+  echo 'All requests must be made over https';
+  die();
+}
+
+//Implements basic auth for all routes
+//Be sure to change the username and password
+if($_SERVER['PHP_AUTH_USER'] != 'username'
+   || $_SERVER['PHP_AUTH_PW'] != 'password') {
+  header('WWW-Authenticate: Basic');
+  header('HTTP/1.0 401 Unauthorized');
+  die('Unauthorized');
+}
+
+require 'apiv3methods.php';
+
+//loads the posted data of the request
+$request = file_get_contents('php://input');
+
+if($method === 'GET') {
+  //GET /v3/HealthCheck
+  if($route === 'HealthCheck') {
+    try {
+      header('Content-type:application/json');
+      echo healthCheck();
+    }
+    catch(Exception $e) {
+      //TO-DO: add specific error handling
+      echo $e->getMessage();
+    }
+  }
+  else {
+    //an unkown GET request
+    header('HTTP/1.0 404 Not Found');
+    echo '404<br>';
+    echo 'Method: ' . $method;
+  }
+}
+else if($method === 'POST') {
+  switch($route) {
+    // POST /v3/CheckAvailability
+    case 'CheckAvailability':
+      try {
+        header('Content-type:application/json');
+        echo checkAvailability($request);
+      }
+      catch(Exception $e) {
+        //TO-DO: add specific error handling
+        echo $e->getMessage();
+        die();
+      }
+      break;
+    //POST /v3/CreateBooking
+    case 'CreateBooking':
+      try {
+        header('Content-type:application/json');
+        echo createBooking($request);
+      }
+      catch(Exception $e) {
+        //TO-DO: add specific error handling
+        echo $e->getMessage();
+        die();
+      }
+      break;
+    // POST /v3/UpdateBooking
+    case 'UpdateBooking':
+      try {
+        header('Content-type:application/json');
+        echo updateBooking($request);
+      }
+      catch(Exception $e) {
+        //TO-DO: add specific error handling
+        echo $e->getMessage();
+        die();
+      }
+      break;
+    //POST /v3/GetBookingStatus
+    case 'GetBookingStatus':
+      try {
+        header('Content-type:application/json');
+        echo getBookingStatus($request);
+      }
+      catch(Exception $e) {
+        //TO-DO: add specific error handling
+        echo $e->getMessage();
+        die();
+      }
+      break;
+    //POST /v3/ListBookings
+    case 'ListBookings':
+      try {
+        header('Content-type:application/json');
+        echo listBookings($request);
+      }
+      catch(Exception $e) {
+        //TO-DO: add specific error handling
+        echo $e->getMessage();
+        die();
+      }
+      break;
+    //POST /v3/CheckOrderFulfillability
+    case 'CheckOrderFulfillability':
+      try {
+        header('Content-type:application/json');
+        echo checkOrderFulfillability($request);
+      }
+      catch(Exception $e) {
+        //TO-DO: add specific error handling
+        echo $e->getMessage();
+        die();
+      }
+      break;
+    //POST /v3/CreateOrder
+    case 'CreateOrder':
+      try {
+        header('Content-type:application/json');
+        echo createOrder($request);
+      }
+      catch(Exception $e) {
+        //TO-DO: add specific error handling
+        echo $e->getMessage();
+        die();
+      }
+      break;
+    //POST /v3/ListOrders
+    case 'ListOrders':
+      try {
+        header('Content-type:application/json');
+        echo listOrders($request);
+      }
+      catch(Exception $e) {
+        //TO-DO: add specific error handling
+        echo $e->getMessage();
+        die();
+      }
+      break;
+    //An unkown post request
+    default:
+      header('HTTP/1.0 404 Not Found');
+      echo '404<br>';
+      echo 'Method: ' . $method;
+  }
+}
+//A request with a non post or get method
+else {
+  header('HTTP/1.0 404 Not Found');
+  echo '404<br>';
+  echo 'Method: ' . $method;
+}
+