preloader
#happyselling :-)

Billbee bietet derzeit zwei verschiedene Möglichkeiten an, um mit nicht unterstützten Systemen zu sprechen. Zum einen gibt es die Billbee API, welche per ReST-Calls angesprochen werden kann, und zum anderen die Custom Shop API, mit der du dein System als Shopverbindung in Billbee hinzufügen kannst. Einmal angelegt, behandelt Billbee die Shopverbindung wie jede andere Verbindung und ruft diese im hinterlegten Intervall ab.

In diesem Artikel zeigen wir die Integration eines Custom Shops. Es wird PHP und Composer als Paketmanager verwendet. Zudem wird das von Billbee bereitgestellte Custom Shop SDK für PHP verwendet. Außerdem findest du den Quellcode aus diesem Artikel ebenfalls auf GitHub.

Voraussetzungen

Hinweise

Da es unzählige Systeme gibt, wobei jedes davon Daten unterschiedlich handhabt, werden in diesem Artikel aus Gründen der Einfachheit, Daten statisch in der Programmierung hinterlegt. In deinem Fall wird sehr wahrscheinlich noch eine Datenbank im Hintergrund hängen, welche Bestellungen, Artikeldaten und vieles andere hält. Im Verlauf des Artikels werden wir weiter darauf eingehen, wie du bei dir vorgehen könntest.

Im Beispiel verwenden wir Composer für Autoloading. Der Code befindet sich im Billbee\CustomShopApiExample Namespace. Falls das bei dir abweicht, musst du das entsprechend in den Codebeispielen berücksichtigen. Darüber hinaus wird http://localhost:1337/ als lokalen Endpunkt verwendet.

Projekt anlegen

Falls du schon ein Composer Projekt hast, in welchem du arbeiten möchtest, kannst du die Abhängigkeit billbee/custom-shop-api über Composer installieren und zum nächsten Abschnitt übergehen.

Erzeuge zunächst ein neues Verzeichnis für das Projekt. In diesem führst du mit der Kommandozeile den Befehl composer init aus und folgst den Anweisungen. Bei der Frage "Would you like to define your dependencies (require) interactively" drückst du Enter und gibst anschließend billbee/custom-shop-api ein. Die übrigen Fragen kannst du einfach mit Enter bestätigen.

Anschließend legst du noch in diesem Verzeichnis eine Datei mit dem Namen index.php sowie ein Verzeichnis src an.

Die index.php ist der Endpunkt, welcher von Billbee später aufgerufen wird, um mit deinem System zu sprechen. Im Verzeichnis src landet der komplette Quellcode.

Zuletzt musst du noch die composer.json öffnen, und das Quellcode Verzeichnis für den Autoloader registrieren. Füge dazu einfach den folgenden Abschnitt vor dem letzten } hinzu.

,
"autoload": {
   "psr-4": {
       "Billbee\\CustomShopApiExample\\": "src/"
   }
}


Anschließend aktualisierst du mit dem Befehl composer update noch den Autoloader.

Endpunkt programmieren

Du wirst im Verlauf merken, dass das SDK dir schon eine Menge Arbeit abnehmen wird.

Der Allgemeine "Request-Flow" mit dem SDK ist wie folgt:

  1. Es wird von Billbee ein HTTP-Call an die hinterlegte URL (der Endpunkt in deinem System) geschickt.
  2. Der Endpunkt...
    1. erzeugt einen RequestHandlerPool, welcher die Anfragen verarbeitet,
    2. erzeugt aus der aktuellen HTTP-Anfrage ein PSR-7 konformes Request Objekt,
    3. übergibt das Request-Objekt an die RequestHandlerPool::handle($request) Methode, welche...
    4. den Benutzer authentifiziert,
    5. die Anfrage verarbeitet und
    6. ein PSR-7 konformes Response Objekt erzeugt.
  3. Das Response Objekt wird anschließend an den Client zurückgegeben.

Zur Veranschaulichung noch mal als vereinfachte Grafik:

In der einfachsten Implementation musst du lediglich den Endpunkt programmieren und das OrdersRepositoryInterface implementieren.

Das OrdersRepositoryInterface umfasst derzeit vier Methoden:

  • getOrder: Eine einzelne Bestellung abrufen,
  • getOrders: Die Liste der Bestellungen abrufen,
  • acknowledgeOrder: Eine Bestellung markieren, welche erfolgreich an Billbee übertragen wurde,
  • setOrderState: Den Bestellstatus einer Bestellung setzen.

Von diesen vier Methoden muss mindestens die Methode getOrders implementiert werden. Falls du ein *RepositoryInterface nur zum Teil implementierst, musst du in den Methoden, welche du nicht implementierst eine Billbee\CustomShopApi\Exception\NotImplementedException erzeugen.

Soweit zum Theoretischen, weiter geht es nun mit etwas Code. Der Inhalt der index.php ist recht überschaubar:

<?php
// index.php

require_once __DIR__ . '/vendor/autoload.php';

use Billbee\CustomShopApi\Http\Request;
use Billbee\CustomShopApi\Http\RequestHandlerPool;

// Authentifizierung kommt später, also erst einmal auf null setzen
$authenticator = null;

// Jetzt wird der RequestHandlerPool erzeugt.
// Als ersten Parameter nimmt dieser den Authenticator und als zweiten Parameter ein Array von Repositories
$handler = new RequestHandlerPool($authenticator, [
]);

// Im nächsten Schritt erzeugen wir aus der aktuellen HTTP-Anfrage ein Request Objekt
// und lassen dieses vom RequestHandlerPool verarbeiten
$request = Request::createFromGlobals();
$response = $handler->handle($request);

// Zuletzt wird die Response an den client gesendet
$response->send();

Wenn du jetzt über eine Kommandozeile im Projektverzeichnis php -S localhost:1337 ausführst, kannst du mit Postman die Orders/GetOrders Abfrage durchführen.
Dabei kommt ein "Diese Aktion ist nicht implementiert." zurück.

OrderRepositoryInterface implementieren

Dass, was im Moment vom Endpunkt geliefert wird, zeigt zwar, dass der Endpunkt funktioniert, jedoch können wir damit noch nichts anfangen. Deswegen wollen wir jetzt das OrderRepositoryInterface implementieren.

Erzeuge dazu einfach das Verzeichnis src/Repository und darin die Datei OrderRepository.php. Diese füllst du initial mit diesem Inhalt:

<pre>
<?php

namespace Billbee\CustomShopApiExample\Repository;

use Billbee\CustomShopApi\Exception\NotImplementedException;
use Billbee\CustomShopApi\Model\PagedData;
use Billbee\CustomShopApi\Repository\OrdersRepositoryInterface;
use DateTime;

class OrderRepository implements OrdersRepositoryInterface
{
   /** @inheritDoc */
   public function getOrder($orderId)
   {
       throw new NotImplementedException();
   }

   /** @inheritDoc */
   public function getOrders($page, $pageSize, DateTime $modifiedSince)
   {
       return new PagedData([], 0);
   }

   /** @inheritDoc */
   public function acknowledgeOrder($orderId)
   {
       throw new NotImplementedException();
   }

   /** @inheritDoc */
   public function setOrderState($orderId, $newStateId, $comment)
   {
       throw new NotImplementedException();
   }
}

</pre>

Damit der RequestHandlerPool, als zentrale Stelle, auch weiß, dass er Abfragen zum Thema Bestellungen verarbeiten kann, musst du das OrderRepository noch entsprechend registrieren. Wechsele dazu einfach zurück in die index.php und übergebe dem RequestHandlerPool dein OrderRepository:

<pre>

<?php

// ...

$handler = new RequestHandlerPool($authenticator, [
   new \Billbee\CustomShopApiExample\Repository\OrderRepository(), // neu
]);

Nach einem erneuten Abruf mit Postman bekommst du nun folgendes Ergebnis

{
   "paging": {
       "page": 1,
       "totalCount": 0,
       "totalPages": 0
   },
   "orders": []
}

</pre>

Nun wechselst du zurück in das OrderRepository.

Wie schon eingangs erwähnt, werden wir mit statischen Daten arbeiten. Wir haben 50 Bestellungen mit Fake Daten erzeugt, welche du im Konstruktor auf eine private Instanzvariable zuweist. In der getOrders Methode werden dann die Bestellungen mit dem angefragten Minimum-Datum gefiltert und dann die Bestellungen für die aktuell angefragte Seite in einem PagedData Objekt zurückgegeben.

Da der Code aufgrund der statischen Daten ziemlich groß ist, habe ich diesen auf GitHub ausgelagert: https://gist.github.com/devtronic/bfdabcce0e9f1eb952aa41add0246457

Wenn du in Postman nun als StartDate 2020-01-01T00:00:00 einträgst, solltest du bei dem Beispieldatensatz 50 Bestellungen zurück bekommen. Das siehst du im Paging

<pre>

"paging": {
   "page": 1,
   "totalCount": 50,
   "totalPages": 1
}

Wenn du stattdessen 2020-01-09T00:00:00 eingibst, dürften es nur noch 6 Bestellungen sein:

"paging": {
   "page": 1,
   "totalCount": 6,
   "totalPages": 1
}

</pre>

Somit hast du erfolgreich - wenn auch mit statischen Daten - die GetOrders Methode implementiert.

Vorgehensweise mit einer anderen Datenquelle

Wenn du einen Shop anbindest, arbeitest du in der Regel nicht mit einer statischen Liste von Bestellungen 🙂.

In den meisten Fällen hat dein Shop ein Datenbank Objekt oder ein ORM mit welchem du mit deiner Datenbank sprichst. Dieses kannst du z.B. über den Konstruktor deines Repository an dein Repository geben und dann in den jeweiligen Methoden verwenden.

Andere Repositories

Derzeit bietet das SDK eine 100-Prozentige Abdeckung der zur Verfügung stehenden Methoden. Daher bietet das SDK zusätzlich zum OrderRepositoryInterface noch folgende Schnittstellen an:

  • ProductsRepositoryInterface: Dient zum Importieren deiner Shop-Artikel in Billbee 
  • ShippingProfileRepositoryInterface: Dient zum Abrufen der Versandprofile in der Shopverbindung
  • StockSyncRepositoryInterface: Wird für den Bestandsabgleich verwendet

Die Vorgehensweise ist bei diesen Repositories immer gleich:

  1. Das jeweilige Interface in einer Klasse implementieren,
  2. Ein neues Objekt der Klasse instanziieren,
  3. Das Objekt an das Repository übergeben.

Sicherheit

Aktuell kann jede:r einfach auf den Endpunkt zugreifen. Allein aus Sicht des Datenschutzes ist das schon unvernünftig. Um dieses Problem zu lösen, gibt es wieder verschiedene Ansätze. Die einfachste, und in den meisten Fällen ausreichende Lösung ist es, mit einem geheimen Schlüssel zu arbeiten. Hierzu bietet das SDK eine fertige Klasse an, nämlich den \Billbee\CustomShopApi\Security\KeyAuthenticator. Dieser erwartet im Konstruktor den geheimen Schlüssel.

Dieser muss mit dem in Billbee hinterlegten Schlüssel übereinstimmen:

Angaben für die API Verbindung

Billbee hasht bei jeder Anfrage das aktuelle Datum mit dem Schlüssel und sendet diesen Hash dann zur Prüfung mit.

Falls du nicht die Postman collection verwendest, kannst du während der Entwicklungsphase mit dem folgenden Script einen Hash erzeugen (php script.php "Geheimer Schlüssel"):

<?php
ini_set('date.timezone', 'Europe/Berlin');

$key = $argv[0];

$shortenedTimestamp = substr(time(), 0, 7);
$hash = hash_hmac('sha256', utf8_encode($key), utf8_encode($shortenedTimestamp));
$encodedHash = base64_encode($hash);

$missingDigits = strlen(time()) - strlen($shortenedTimestamp);
$validFrom = str_pad($shortenedTimestamp, strlen($shortenedTimestamp) + $missingDigits, '0');
$validUntil = str_pad($shortenedTimestamp, strlen($shortenedTimestamp) + $missingDigits, '9');

echo sprintf("Gueltig von: %s Uhr\nGueltig bis: %s Uhr\n\n", date('d.m.Y, H:i:s', $validFrom), date('d.m.Y, H:i:s', $validUntil));
echo str_replace(['=', '/', '+'], '', $encodedHash);

Die Postman collection selber berechnet bei jeder Abfrage den Hash automatisch.

Um einen KeyAuthenticator zu verwenden, wechsele einfach wieder in die index.php und ändere die $authenticator Variable:

$authenticator = null; // alt
$authenticator = new KeyAuthenticator('MySecretKey'); // neu

Ab sofort müssen alle Abfragen einen gültige Hash beinhalten, ansonsten werden diese mit 401 Unauthorized abgelehnt.

Nutzerbasierte Authentifizierung

In bestimmten Fällen, zum Beispiel falls du einen Marktplatz betreibst, ist es notwendig, dass Nutzer:innen sich mit einem Benutzernamen und Passwort anmelden muss. In einem solchen Fall musst du einen eigenen Authenticator programmieren.

Das klingt erst einmal schwieriger als es tatsächlich ist. Implementiere einfach das AuthenticatorInterface und daraus die isAuthorized Methode. Diese Methode erhält das RequestInterface Objekt, welches die aktuelle HTTP Anfrage abbildet und muss ein true bzw. false zurückgeben, je nachdem ob die Zugangsdaten gültig sind (true), oder nicht (false). In einer sehr einfachen Ausführung (wieder mit statischen Zugangsdaten) würde das Ganze so aussehen:

class BasicAuthAuthenticator implements AuthenticatorInterface
{
   public function isAuthorized(RequestInterface $request)
   {
       $userInfo = trim($request->getUri()->getUserInfo());
       if (empty($userInfo)) {
           return false;
       }

       list($username, $password) = explode(':', $userInfo, 2);
       if ($username != 'admin_user' && $password != 'admin_password') {
           return false;
       }

       return true;
   }
}

Ein ausführlicheres Beispiel, welches auch beschreibt, wie du aktuelle Nutzer:innen in das Repository bekommst, findest du in der Dokumentation des SDK. Die entsprechenden BasicAuth-Zugangsdaten, müssen an der gleichen Stelle wie der Sicherheitsschlüssel in Billbee hinterlegt werden.

Fazit

Wie schon eingangs erwähnt, übernimmt das SDK schon jede Menge Arbeit für dich.  Grob geschätzt ist es für eine:n erfahrene:n Programmierer:in möglich, in weniger als einer Stunde ein bestehendes Shopsystem an Billbee zu koppelnm um zumindest Aufträge von Billbee abholen zu lassen. Weitere Features, insbesondere die Marktplatz Authentifizierung, sind natürlich umfangreicher und dauern entsprechend länger.

Titelbild von RealToughCandy.com auf Pexels

20.000+ E-Commerce Brands nutzen Billbee
... und wickeln über 85 Mio. Bestellungen pro Jahr ab
Was kostet das Ganze?
Zu den Preisen