Ulica Świdnicka we Wrocławiu nocą

Uber API: Jak napisać aplikację, która zamówi za nas kurs?

Zasada działania Ubera z punktu widzenia użytkownika smartfona jest stosunkowo nieskomplikowana. Uruchamiamy aplikację, wciskamy jeden przycisk i czekamy na kierowcę. Proste? Proste. Ale pójdźmy o krok dalej i zastanówmy się nad nieco bardziej wymagającym użytkownikiem, który chciałby dostać się Uberem w pewne miejsce o zaplanowanej wcześniej godzinie. Po najniższym koszcie. A i żeby sam się zamówił 🙂 Poznajmy Uber API.

Czym jest Uber API?

Uber udostępnił programistom możliwość korzytania we własnych aplikacjach z pełnej funkcjonalności przewozów. Rozmiar API jest dosyć pokaźny, bowiem oprócz funkcji zamawiania umożliwia także szacowanie kosztu kursu, czasu jego trwania, czy ustawienie przypomnienia o planowanym przejeździe. Z technicznego punktu widzenia, jest to w dużej mierze RESTful API, więc nie ma znaczenia z jakiego języka korzystamy przy tworzeniu aplikacji. Przykłady, które pojawią się w tym wpisie będą napisane w języku C#.

Jak zacząć?

1. Do korzystania z Uber API nie potrzebujemy specjalnie rejestrować się jako deweloperzy. Wystarczy do tego klasyczne konto używane do przejazdów.

Jeżeli niniejszy wpis zachęcił Ciebie do zabawy z API, a nie masz jeszcze Uberowego konta, możesz go założyć przy pomocy mojego linku. Dostaniesz wtedy 25 zł do wykorzystania na swój pierwszy przejazd. A po jego realizacji 25 zł dostanę także ja 🙂

2. Przygodę zaczynamy od odwiedzenia strony https://developer.uber.com, gdzie po zalogowaniu zostaniemy przekierowani do okna rejestracji aplikacji. Wpisujemy wymyśloną nazwę, opis i akceptujemy regulamin.

3. Po rejestracji aplikacji spisujemy do późniejszego wykorzystania ID KLIENTA, KLUCZ TAJNY KLIENTA oraz TOKEN SERWERA.

4. W zakładce “Uprawnienia” w polu “Przekieruj Url” wpisujemy “http://localhost”.

5. To w zasadzie wszystko, co jest nam potrzebne, aby zacząć używać Uber API w podstawowym zakresie. W przypadku chęci opublikowania naszej aplikacji i udostępnienia możliwości korzystania z niej przez innych użytkowników – trzeba ustawić jeszcze odpowiednie uprawnienia i złożyć specjalny wniosek o akceptację takiej aplikacji. W tym momencie nie jest to konieczne.

Pytamy o cenę

Jedną z prostszych czynności, jaką możemy wykonać przy użyciu Uber API, jest zapytanie o szacowaną cenę kursu. Do wykonania zapytania będziemy potrzebować zapisany wcześniej TOKEN SERWERA oraz współrzędne geograficzne początku i końca trasy. Pozostaje nam spisać współrzędne z Google Maps, aczkolwiek nic nie stoi na przeszkodzie, aby zaprzęgnąć do tego celu odpowiednie API 🙂 Odsyłam do wpisu Piotra Zielińskiego na ten temat.

Dla ułatwienia analizy przykładów implementacji, w poniższej tabeli znajdują się wartości, które otrzymałem po rejestracji aplikacji.

ID KLIENTA
atrz29YkWQW0x0D_bgcusMEfS0XPuWPC
KLUCZ TAJNY KLIENTA
fWdGq0DLQ9p1n9zVEpTFZ94XSdhTFVmMeCkM8gn5
TOKEN SERWERA
FXMRhfZrWe08yD-mKj7yJMj5ZEv-pUxq6VRfMhdh

Zapytajmy Ubera, ile kosztowałby kurs spod Dworca Głównego we Wrocławiu (51.098865, 17.036895) do Portu Lotniczego (51.110073, 16.880632).

Adres API pod którym dostaniemy odpowiedź, to:

https://api.uber.com/v1/estimates/price

W przypadku tego zapytania, wystarczy, że wykonamy je metodą GET, dodając do nagłówka TOKEN SERWERA. Oto przykładowy kod, który realizuje to zadanie:


/*...*/
  
  var client = new HttpClient();

  client.DefaultRequestHeaders.Add(
    "Authorization",
    "Token FXMRhfZrWe08yD-mKj7yJMj5ZEv-pUxq6VRfMhdh");

  var queryString = HttpUtility.ParseQueryString(string.Empty);

  const string uri = "https://api.uber.com/v1/estimates/price";

  queryString["start_latitude"] = "51.098865";
  queryString["start_longitude"] = "17.036895";

  queryString["end_latitude"] = "51.110073";
  queryString["end_longitude"] = "16.880632";

  var priceEstimation = 
    client.GetStringAsync($"{uri}?{queryString}").Result;

/*...*/

W efekcie dostajemy JSONa, z którego dowiadujemy się, że na tej trasie w chwili zapytania zapłacimy w granicach 25-33 zł i pokonamy ją w 1620 sekund:

{
  "prices": [{
    "localized_display_name": "uberPOP",
    "high_estimate": 33,
    "minimum": 8,
    "duration": 1620,
    "estimate": "PLN25-33",
    "distance": 8.23,
    "display_name": "uberPOP",
    "product_id": "2bc02557-7c84-4a21-88bd-261dd5b3a816",
    "low_estimate": 25,
    "surge_multiplier": 1.0,
    "currency_code": "PLN"
  }]
}

Nie jest to coś, czego nie jesteśmy w stanie sami sprawdzić korzystając z aplikacji na smartfona. Jednak przy pomocy API możemy pytać i pytać i pytać w nieskończoność, co pozwoli na zbadanie pewnych prawidłowości na tej trasie i zaplanowanie w przyszłości kursu w taki sposób, aby uniknąć pory, w której jest drożej.

Zapytajmy jeszcze kilka razy

Pozwoliłem sobie popytać Ubera kilka tysięcy razy przez całą dobę, ile kosztowałby powyższy kurs (request wysyłany średnio raz na 3 sekundy). Na pierwszy rzut oka można wyciągnąć pewne wnioski, a już na pewno będzie ich więcej, jeśli przebadamy daną trasę np. przez cały miesiąc dzień w dzień. Jak widać, są takie pory, w których bardziej może opłacić się zamówić klasyczną taksówkę, ale również takie, w których to Uber będzie zdecydowanie tańszy. Poniżej wykres przedstawiający jak cena przejazdu z Dworca Głównego we Wrocławiu do Portu Lotniczego kształtowała się w sobotę 2016-07-23 od 00:00 do 23:59. Jeżeli ktoś planuje szarpnąć się na codzienne dojazdy do pracy Uberem, warto zrobić sobie taką analizę i dobrać godziny dojazdów tak, aby najbardziej zoptymalizować ten koszt 🙂 Nie do końca wiem jak wygląda to z punktu widzenia kierowców, ale przeanalizowanie miasta pod kątem najbardziej dochodowych godzin i miejsc wydaje się być sensowne.

Szacowany koszt kursu Uberem z Dworca Głównego we Wrocławiu na Port Lotniczy o różnych godzinach
Szacowany koszt kursu Uberem z Dworca Głównego we Wrocławiu na Port Lotniczy o różnych godzinach

Zanim zamówimy przejazd

Zamawianie przejazdów w imieniu użytkownika (a nawet w swoim imieniu) wymaga uzykania odpowiednich uprawnień. API Ubera używa do tego celu standardu autoryzacji OAuth 2.0. W naszym przypadku sprowadza się to do wygenerowania odpowiedniego tokena, który będzie dołączany do każdego nagłówka zapytania.

W celu wygenerowania tokena użytkownika, należy wykonać poniższe kroki:

1. Akceptujemy zamawianie przejazdów w naszym imieniu

Aby uzyskać odpowiednie uprawnienia, musimy przygotować specjalne zapytanie bazując na:

https://login.uber.com/oauth/v2/authorize

Do powyższego adresu należy dodać trzy parametry:

client_id – który już wcześniej poznaliśmy
scope=request – parametr oznaczający, że prosimy o uprawnienia do zamawiania przewozów w naszym imieniu
response_type=code – bez tego parametru nie działa, a nie może on też przyjąć innej wartości. Jest bo jest 🙂

Po uwzględnieniu powyższch punktów, adres, który należy odwiedzić przy pomocy przeglądarki wygląda następująco:

https://login.uber.com/oauth/v2/authorize?client_id=atrz29YkWQW0x0D_bgcusMEfS0XPuWPC&scope=request&response_type=code

Po zalogowaniu i zaakceptowaniu uprawnień do zamawiania przejazdów zostaniemy przekierowani na adres zawierający odpowiedni kod. W tym też celu przy konfiguracji aplikacji wpisywaliśmy adres przekierowania. Właśnie w tym momencie adres ten jest wykorzystywany.

W moim przypadku zaakceptowanie uprawnień przekierowało mnie na stronę:

http://localhost/?code=wFab2iFcVpkVp1U0Lr7gRvtxDzxwK6#_

To jeszcze nie koniec całego procesu, bowiem dopiero otrzymany kod możemy zamienić na token 🙂

2. Wymieniamy kod na token

Na tym etapie, gdy już mamy kod, do wygenerowania tokena posłużymy się poniższym przykładem:


/*...*/

  var client = new HttpClient();

  const string uri = "https://login.uber.com/oauth/v2/token";

  var content = new List<KeyValuePair<string, string>>
  {
    new KeyValuePair<string, string>(
      "client_secret", "fWdGq0DLQ9p1n9zVEpTFZ94XSdhTFVmMeCkM8gn5"),
    new KeyValuePair<string, string>(
      "client_id", "atrz29YkWQW0x0D_bgcusMEfS0XPuWPC"),
    new KeyValuePair<string, string>(
      "grant_type", "authorization_code"),
    new KeyValuePair<string, string>(
      "redirect_uri", "http://localhost"),
    new KeyValuePair<string, string>(
      "code", "wFab2iFcVpkVp1U0Lr7gRvtxDzxwK6#_")
  };

  var requestContent = new FormUrlEncodedContent(content);            

  var requestResult = client.PostAsync(uri, requestContent).Result;

  var resultContent = 
    requestResult.Content.ReadAsStringAsync().Result;

/*...*/

Samo zapytanie polega na wysłaniu pod adres https://login.uber.com/oauth/v2/token metodą POST kod, który otrzymaliśmy w wyniku autoryzacji i reszty danych identyfikujących naszą aplikację.

Po ciężkich bojach 🙂 w odpowiedzi przychodzi do nas JSON zawierający wszystko co potrzebne, aby zacząć zamawiać kurs:

{
  "last_authenticated": 1469560074,
  "access_token": "BARDZO_DLUGI_ACCESS_TOKEN",
  "expires_in": 2592000,
  "token_type": "Bearer",
  "scope": "request",
  "refresh_token": "LsQwzAsXyJt4QfVGshFhhQm8JgxbX7"
}

Wygenerowanym tokenem możemy posługiwać się przez 30 dni. Traci ważność w momencie, w którym wygenerujemy użytkownikowi ponownie token dla danej aplikacji lub go odświeżymy przy pomocy tokenu odświeżającego “refresh_token”.

Zamawiamy UBERa!

Po przekopaniu podstawowych zasad rządzących Uber API jesteśmy gotowi do zamówienia kursu przy pomocy naszej aplikacji. Zanim będziemy w pełni gotowi do wykonywania rzeczywistych zamówień – aby nie ponieść niepotrzebnych kosztów – możemy skorzystać ze środowiska testowego. Adres API w przypadku środowiska testowego ma adres https://sandbox-api.uber.com. Wszystkie wygenerowane do tej pory tokeny działają zarówno w środowisku produkcyjnym jak i testowym.

Adres Uber API, który służy do składania zamówień to:

https://api.uber.com/v1/requests

Całość zamówienia sprowadza się do wywołania powyższego adresu metodą POST z tokenem w nagłówku oraz lokalizacją źródłową i docelową jako argumenty zapytania (tym razem dane, które wysyłamy muszą być reprezentowane JSONem). I możemy ruszać w podróż 🙂

Pozostaje jeszcze sprawa sprawdzenia statusu zamówienia. W tym celu metodą GET z tokenem w nagłówku wywołujemy poniższy adres:

https://api.uber.com/v1/requests/current

Implementacja zamawiania pojazdu może wyglądać następująco:


/*...*/

  var client = new HttpClient();

  client.DefaultRequestHeaders.Add(
    "Authorization",
    "Bearer BARDZO_DLUGI_ACCESS_TOKEN");

  const string uri = "https://api.uber.com/v1/requests";

  dynamic content =
    new
    {
      start_latitude = "51.098865",
      start_longitude = "17.036895",
      end_latitude = "51.110073",
      end_longitude = "16.880632"
    };

    var jsonContent = JsonConvert.SerializeObject(content);
    var requestContent = new StringContent(jsonContent);

    requestContent.Headers.ContentType = 
      new MediaTypeHeaderValue("application/json");

    var requestResult = client.PostAsync(uri, requestContent).Result;

    var resultContent = 
      requestResult.Content.ReadAsStringAsync().Result;

/*...*/

Zaś sprawdzania statusu aktualnie odbywanego przejazdu w sposób poniższy:


/*...*/

  var client = new HttpClient();

  client.DefaultRequestHeaders.Add(
    "Authorization",
    "Bearer BARDZO_DLUGI_ACCESS_TOKEN");

  const string uri = "https://api.uber.com/v1/requests/current";

  var status = client.GetStringAsync(uri).Result;

/*...*/

Testujemy!

Aby nie pozostać jedynie przy teorii całego procesu, postanowiłem sprawdzić działanie Uber API na żywym organiźmie. W tym celu, po przejściu całej procedury uzyskiwania tokena, złożyłem zamówienie, ustawiłem sprawdzanie statusu co 5 sekund i udałem się na wyznaczone przeze mnie miejsce odbioru. Chwilę niepewności rozwiała wiadomość sms, którą po chwili otrzymałem:

Your Uber is on the way.
Edward (4.9 stars) will arrive in 6 minutes.

Poniżej fragmenty statusów, jakie zalogowała aplikacja podczas całego procesu mojego przejazdu:


22:09:42 {"status":"processing" /*...*/ }
22:09:53 {"status":"accepted","destination":{ "eta":12 /*...*/ }}
/*...*/
22:15:54 {"status":"in_progress","destination":{ "eta":7 /*...*/ }}
/*...*/
22:23:15 {"status":"in_progress","destination":{ "eta":1 /*...*/ }}
22:23:21 {"status":"in_progress","destination":{ "eta":0 /*...*/ }}

Co ciekawe, zaraz po realizacji kursu, status przestaje być dostępny, a serwer w odpowiedzi zwraca kod 404:


Response status code does not indicate success: 404 (no_current_trip).

Tytułem zakończenia

Uber API daje nam ciekawe możliwości, których nie oferuje klasyczna aplikacja dostępna na smartfona. Możemy pokusić się o analizę danych, czy zamawianie przejazdu według ustalonego harmonogramu – wiele pomysłów na wykorzystanie API nasuwa się na myśl. Przedstawione tutaj przykłady to jedynie garstka z dostępnego publicznie API. Zachęcam do próbowania i dzielenia się pomysłami na jego wykorzystanie 🙂

Jeżeli niniejszy wpis zachęcił Ciebie do zabawy z API, a nie masz jeszcze Uberowego konta, możesz go założyć przy pomocy mojego linku. Dostaniesz wtedy 25 zł do wykorzystania na swój pierwszy przejazd. A po jego realizacji 25 zł dostanę także ja 🙂

P.S. Pan Edward nie poparł pomysłu polowania na czas i miejsce podwyższonego mnożnika – podobno większość klientów czeka, aż mnożnik spadnie, podczas gdy kierowcy Ubera stoją na postojach.


Przygotowując wpis, korzystałem z dokumentacji Uber API dostępnej pod adresem https://developer.uber.com/docs/rides