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.
Ich werde in diesem Artikel auf die Integration eines Custom Shops eingehen. Ich verwende PHP und Composer als Paketmanager. Zudem verwende ich das von uns bereitgestellte Custom Shop SDK für PHP.
Außerdem findest du den Quellcode aus diesem Artikel ebenfalls auf GitHub.
Voraussetzungen
- Editor/IDE deiner Wahl
- Eine lokale PHP Installation
- Composer
- Postman und die Billbee Custom Shop Collection
- Am Ende natürlich noch einen Webserver auf dem du den Code hochladen kannst :-)
Hinweise
Da es unzählige Systeme gibt, wobei jedes davon Daten unterschiedlich handhabt, werde ich in diesem Artikel aus Gründen der Einfachheit, Daten statisch in der Programmierung hinterlegen. In deinem Fall wird sehr wahrscheinlich noch eine Datenbank im Hintergrund hängen, welche Bestellungen, Artikel-Daten und vieles Andere hält. Ich werde im Verlauf des Artikels weiter darauf eingehen wie du bei dir vorgehen könntest.
Im Beispiel verwende ich 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 verwende ich http://localhost:1337/ als lokalen Endpunkt.
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. Die 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:
- Es wird von Billbee ein HTTP-Call an die hinterlegte URL (der Endpunkt in deinem System) geschickt.
- Der Endpunkt...
- erzeugt einen RequestHandlerPool, welcher die Anfragen verarbeitet,
- erzeugt aus der aktuellen HTTP-Anfrage ein PSR-7 konformes Request Objekt,
- übergibt das Request-Objekt an die RequestHandlerPool::handle($request) Methode, welche...
- den Benutzer authentifiziert,
- die Anfrage verarbeitet und
- ein PSR-7 konformes Response Objekt erzeugt.
- 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, werde ich mit statischen Daten arbeiten. Ich habe 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:
- Das jeweilige Interface in einer Klasse implementieren,
- Ein neues Objekt der Klasse instanziieren,
- Das Objekt an das Repository übergeben.
Sicherheit
Aktuell kann jeder 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:

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 der Nutzer 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 den aktuellen Nutzer 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 einen. Grob geschätzt ist es für einen erfahrenen Programmierer möglich, in weniger als einer Stunde ein bestehendes Shop-System an Billbee zu koppeln um zumindest Aufträge von Billbee abholen zu lassen. Weitere Features, insbesondere die Marktplatz Authentifizierung sind natürlich umfangreicher und dauern entsprechend länger.
Danke für's Lesen. Fragen, Lob und Kritik gerne in die Kommentare.
Photo by Markus Spiske on Unsplash