Store interfaces (so-called "importers") can be created customer-specific or overloaded. In the process, data can be modified or enriched in the import process, and the more complex conversion of data structures is also possible.
A major topic at xentral is the connection and automatic processing of orders in online shops. Although many online stores already have a high degree of automation, new store systems and their connection always present us with challenges. Although almost every store has an interface, it often turns out that the interfaces to be connected are not compatible.
The importer acts as so-called middleware and translates the order data from the store into a format from which xentral can then create orders. Conversely, the importer is also responsible for transferring the order status, i.e. the information as to whether the order has been processed and shipped, as well as article data and stock figures to the store - a compact all-rounder, one might say.
The process within the importer always follows a fixed pattern. For example, if orders are to be retrieved from the store, the system first checks whether there is a connection to the store interface. Then it is determined whether, or how many, orders are to be picked up in the store. If there are orders to be picked up, they are imported and then marked as "In process" in the store. If items are imported from the store or exported to the store, the scheme looks similar. First, a check is made to see if there is a connection between xentral and the store. Then the data is transferred.
You do not need expert knowledge to write your own importer. Basic PHP knowledge is sufficient if you stick to the given structure. However, some things have to be clarified in advance:
-
Name: The name of the importer does not really matter. By convention, however, the class name is composed of "Shopimporter_XXX". In this example we will develop a special importer for Shopware, which is why the class is named Shopimporter_Shopwarespecial. The file itself is named like the class name in lower case and is stored in the www/pages/ directory of the Xentral installation, for example
/var/home/www/html/xentral/www/pages/shopimporter_shopwarespecial.php.
-
Database entry: If a new importer is added to the system via the interface, a new entry is created in the database in the "Shopexport" table. Since this importer is a special production, in this case the name of the importer must be entered without further ado into this table. It is important that the module name corresponds to the name of the PHP file.
-
ShopimporterBase All internal importers are based on the "ShopimporterBase" class. The class contains system-relevant functions that ensure the execution of the importer. In principle, every importer should be derived from the ShopimporterBase base class to ensure that these functions are actually present:
class Shopimporter_Shopwarespecial extends ShopimporterBase {
-
Boilerplate: Two variables are relevant for using the importers. $app contains the application xentrals itself and is passed in the constructor. This allows the importer to use the core functionality of xentral without having to do anything additional. The $internal variable does not indicate whether it is an internal importer, but whether the action handlers for the object need to be initialized. This allows the object to be instantiated and used elsewhere without conflict.
/** @var bool */ public $intern = false; /** @var Application */ public $app;
-
Constructor: Two parameters are transferred in the constructor, $app and $internal are stored directly in the object to be addressed later if needed. Then the first logic in the module takes place: It is checked if action handlers should be registered for the module. There are several action handlers that can be registered - but for this example we will use the basic functions that every store importer should have. It is recommended to use the function names as given to ensure compatibility. A list of the functions as well as the role, which is taken over in the importer, follows below in detail.
public function __construct($app, $intern = false) { $this->app=$app; $this->intern = true; if($intern) { return; } $this->app->ActionHandlerInit($this); $this->app->ActionHandler('auth','ImportAuth'); $this->app->ActionHandler('sendlist','ImportSendList'); $this->app->ActionHandler('sendlistlager','ImportSendListLager'); $this->app->ActionHandler('getauftraegeanzahl','ImportGetAuftraegeAnzahl'); $this->app->ActionHandler('getauftrag','ImportGetAuftrag'); $this->app->ActionHandler('deleteauftrag','ImportDeleteAuftrag'); $this->app->ActionHandler('updateauftrag','ImportUpdateAuftrag'); $this->app->DefaultActionHandler('list'); $this->app->ActionHandlerListen($app); }
-
SettingsStructure: Since every store interface has its very own setting options, it is necessary to give the importer exactly these setting options along the way. This happens in the function SettingsStructure(). Fields passed here are displayed in the interface form of the importer. If required, additional fields can be created - for this example, however, the data required for the registration should be sufficient.
public function EinstellungenStruktur() { return array( 'felder'=>array( 'APIUser'=>array('typ'=>'text','bezeichnung'=>'{|API User:','size'=>40), 'APIKey'=>array('typ'=>'text','bezeichnung'=>'{|API Key|}:','size'=>40), 'APIUrl'=>array('typ'=>'text','bezeichnung'=>'{|API Url|}:','size'=>40), )); }
-
getKonfig: The last function before it really starts is getKonfig(). In this function the data from the settings and the ID of the store importer are passed to the object. The parameter $data is a leftover from the time of the external importers. In this parameter instructions for the individual functions are kept ready. This function is called by the system when the object is initialized.
public function getKonfig($shopid, $data) { $this->shopid = $shopid; $this->data = $data; $einstellungen = $this->app->DB->Select("SELECT einstellungen_json FROM shopexport WHERE id = '$shopid' LIMIT 1"); if($einstellungen){ $einstellungen = json_decode($einstellungen,true); $this->apiUser = $einstellungen['felder']['APIUser']; $this->apiKey = $einstellungen['felder']['APIKey']; $this->apiUrl = $einstellungen['felder']['APIUrl']; } }
After the basics for the importer are done, it now comes to the actual connection to Shopware. Of course, it is possible to outsource the complete communication to a separate class or, in the case of Shopware, to copy the class from the documentation. For the sake of simplicity, however, a short function that returns the response of the API is sufficient for this example:
protected function call($endpoint, $method, $data=array(),$params=array()){ $queryString = ''; if (!empty($params)) { $queryString = '?'.http_build_query($params); } $url = $this->apiUrl.$endpoint.$queryString; $dataString = json_encode($data); $curl = curl_init(); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); curl_setopt($curl, CURLOPT_FOLLOWLOCATION, false); curl_setopt($curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); curl_setopt($curl, CURLOPT_USERPWD, $this->apiUser . ':' . $this->apiKey); curl_setopt($curl, CURLOPT_HTTPHEADER, ['Content-Type: application/json; charset=utf-8']); curl_setopt($curl, CURLOPT_URL, $url); curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method); curl_setopt($curl, CURLOPT_POSTFIELDS, $dataString); return curl_exec($curl); }
After all the requirements for the importer to function are met, it can be filled with life. The first step in this direction is the ImportAuth() function. In external importers this function is necessary for authentication from xentral to the importer, in internal importers the function checks the connection call to the API. Since this function is called every time the importer is to take action, there is usually a request or a mechanism to create a token. The important thing here is to return success to indicate that the importer is ready to accept instructions and pass them on to the store API.
public function ImportAuth() { $params = ['limit' => 1]; $responseJson =$this->call('articles','GET',null, $params); $response = json_decode($responseJson,true); if($response['success']){ return 'success'; } return ''; }
Next, the ImportSendListLager() function is added. If the option for transferring stock numbers is activated in the import settings, the function ensures that the stocks in the store are synchronized with the stocks in xentral. In this example, this is done via the item number. Note that for this function, the data parameter of the Importer object must contain the data for the item to be updated.
public function ImportSendListLager() { $updatedArticles = 0; $tmp = $this->CatchRemoteCommand('data'); foreach ($tmp as $article){ $nummer = $article['nummer']; $lageranzahl = $article['anzahl_lager'];
$updateInStock = [
'mainDetail' => [
'inStock' => $lageranzahl
]
];
$params = [
'useNumberAsId'=>true
];
$result = $this->call('articles/'.$nummer, 'PUT',$updateInStock, $params);
if($result['success']){
$updatedArticles++;
}
} return $updatedArticles; }
The ImportSendList() function is basically an enhanced version of ImportSendListLager(). The function transfers items to the store - respectively updates their data. Which data and especially how they can be transferred depends on the respective store interface. This way, item properties or pictures can be transferred to the store. You should pay attention to the separation of the two functions, because ImportSendList is only called in two cases:
-
Manual export of the item
-
Export of the item via the item transfer in the importer
$articleData = [ 'name' => $name, 'lastStock' => $laststock, 'tax' => $tax, // alternativ 'taxId' => 1, 'supplier' => $supplier, // alternativ 'supplierId' => 2, 'description' => $description, 'descriptionLong' => $description_long, 'keywords' => $keywords, 'metaTitle' => $metatitle, 'highlight' => $topseller, 'mainDetail' => [ 'number' => $articleNumber, 'active' => $active, 'ean' => $ean, 'weight' => $weight, 'width' => $width, 'len' => $length, 'height' => $height, 'supplierNumber' => $supplierNumber, 'inStock' => $lageranzahl, 'prices' => $prices ] ];
This function determines how many open orders are available in the store and need to be picked up. It is conceivable to pick up via the status, from a certain order number or also via the order date. Depending on the store interface, not all options can be implemented.
public function ImportGetAuftraegeAnzahl() { $result = $this->getOrders($this->data); return count($result['data']); }
The most important basic function of all importers is to fetch and translate the orders from the store to the shopping cart format of xentral. After the next order is fetched from the store API, the first step is to transfer the customer's address data to the shopping cart:
$cart['anrede'] = 'herr'; $cart['strasse'] = $order['data']['billing']['street']; $cart['plz'] = $order['data']['billing']['zipCode']; $cart['ort'] = $order['data']['billing']['city']; $cart['land'] = $order['data']['billing']['country']['iso']; $cart['email'] = $order['data']['customer']['email']; $cart['name'] = $order['data']['billing']['firstName'] . ' ' . $order['data']['billing']['lastName'];
If there is a different delivery address, this must logically also be transferred. In addition, the field different delivery address must be filled with true so that the order arrives at the correct recipient. Afterwards there is a whole series of important data, which are important for the further processing.
$cart['auftrag'] = $orderFromCollection['id']; $cart['gesamtsumme'] = $orderFromCollection['invoiceAmount']; $cart['transaktionsnummer'] = $orderFromCollection['transactionId']; $cart['onlinebestellnummer'] = $orderFromCollection['number']; ... $cart['zahlungsweise'] = $order['data']['payment']['name']; $cart['lieferung'] = $order['data']['dispatch']['name']; $cart['bestelldatum'] = substr($order['data']['orderTime'],0,10); $cart['versandkostennetto'] = $order['data']['invoiceShippingNet'];
-
Total: The total sum of the order serves as a safeguard against an incorrectly composed shopping cart. If item data was not submitted correctly or there is a problem with taxation, the order total differs from the cart total and the auto-shipping option is disabled for the order. This ensures that no shipment leaves the warehouse that does not match the order.
-
Transaction number: The transaction number is relevant for the automation of accounting and helps to assign paid orders.
-
Order: In this field the unique identification of the order for the store is transferred. Not every store interface allows to change the shipping status of an order via the order number. Occasionally an internal ID is required, which can be passed at this point. The value passed here will be referenced later when a change to the order status is transmitted from the store.
-
Online order number: This field contains the order number that the customer also sees. This facilitates the assignment and in case of a query, an order from the store can be directly assigned to an order in xentral.
-
Payment method: Transferring the payment method allows settings such as autoship, fast lane, or invoice creation to be enabled or disabled specifically for certain payment methods in xentral.
-
Delivery: Similar to the payment method, matching can be done in xentral when the delivery method is transferred.
-
Shipping costs gross: Generally, the shipping costs can be transferred with an order like an item in the shopping cart. By using this special field, however, the shipping costs are immediately posted to the postage item set in the importer and transferred to the order.
-
Order date: The order date shows the date on which the order was received.
foreach ($order['data']['details'] as $article) { $articlearray[] = [ 'articleid' => $article['articleNumber'], 'name' => $article['articleName'], 'price' => $article['price'], 'quantity' => $article['quantity'] ]; } ... $orderData =[ 'id' => $cart['auftrag'], 'warenkorb' => base64_encode(serialize($cart)), 'warenkorbjson' => base64_encode(json_encode($cart)) ];
When the required basic order data has been transferred to the shopping cart, it is time to add the individual items to the shopping cart. The item number is transferred in the articleid field. The other fields contain their respective equivalent from the order item. After the shopping cart has been filled with all the data to be passed from the order, the function returns the data. Functions ImportDeleteOrder / ImportUpdateOrder The ImportDeleteOrder function, which for historical reasons has an inconvenient name, is not used to delete orders from the store. Instead, it is responsible for marking an open order as "In process". The partner function ImportUpdateAuftrag marks the order as "Shipped".
public function ImportDeleteAuftrag() { $orderId = $this->data['auftrag']; $update = [ 'orderStatusId' => 1 ]; $this->call('orders/'.$orderId, 'PUT',$update); return true; }