Drupal Commerce: Afterbuy Shop Schnittstelle manuell einrichten und nutzen

Afterbuy ist eines dieser Tools, die man ernsthaft in Betracht ziehen sollte, auch wenns einem beim ersten Kontakt kalt den Rücken runterläuft. Denn kaum eine Anwendung ist so hässlich und grausam unintuitiv wie Afterbuy. Ich würde jetzt sagen das Preis-Leistungs-Verhältnis ist gut, aber wem es bei Leistung nicht nur auf Funktionen, sondern auch auf Bedienbarkeit ankommt, dem sei geraten weiterzusuchen. Da ich kürzlich in den zweifelhaften Genuss kam, die Afterbuy Schnittstelle in eine Drupal-Commerce-Instanz einzubauen, schreibe ich hier einen kleinen Leitfaden, wie's funktioniert.

Als erstes muss die Schnittstelle bestellt werden. 10 € kostet die im Monat. Das geht nach dem Login unter Tools -> Schnittstellen zu Afterbuy. Warum auch immer bekommt man dort aber nicht seine bereits gekauften Schnittstellen angezeigt, sondern nur ein Bestellformular, wo man neue Schnittstellen kaufen kann. Dafür dass es nur 2 verschiedende gibt (Shop und XML) ist das ziemlich albern. Wenn man seine bestehenden Schnittstellen sehen möchte, muss man dazu auf Einstellungen -> Tarif & Rechnung -> Tarif-Informationen klicken. Da kann man dan ganz nach unten scrollen und unter "Bestellte Schnittstellen" seine Schnittstellen inkl. Zugangsdaten einsehen. Daran sieht man, dass allein die Navigation auf Afterbuy ein eigenes Tutorial wert ist. Das Problem ist, wenn man in Afterbuy nach langem Suchen endlich mal eine Information gefunden hat, ist man darüber nicht nicht froh (wie das bei anderen komplexen Tools wie z.B. Photoshop der Fall ist), sondern man ist einfach nur sauer, dass es mal wieder so lange gedauert hat.

Aber machen wir es kurz. Wer in Verbindung mit Drupal Commerce seine fertigen Bestellungen an Afterbuy übermitteln möchte, um diese dort weiterzuverarbeiten der kann das über ein eigenes Modul recht einfach tun. Vorausgesetzt man kennt die Stolpersteine von Afterbuy, die ich hier an den jeweiligen Stellen durch Kommentierung kurz erläutern werde.

Die Doku für die Shop-Schnittstelle findet man unter http://shopdoku.afterbuy.de

//hook_commerce_checkout_complete nutzen um bei abgeschlossenem Kaufprozess die Bestellung an Afterbuy zu übermitteln
function MYMODULE_commerce_checkout_complete($order) {
  MYMODULE_export_to_afterbuy($order);
}

function MYMODULE_export_to_afterbuy($order) {
  $url = variable_get('afterbuy_interface', 'https://api.afterbuy.de/afterbuy/ShopInterface.aspx');
  //variable_get kann man nutzen um die URL über ein simples system_settings_form()-Formular änderbar zu machen (nicht Teil dieses Tutorials)
  //Bestellnummer wird in eigene Variable geschrieben
  $order_id = $order->order_number;

  //In diesem Beispiel werden Flat Rate Versandkosten berechnet.
  //Die Schleife zieht die Versandkosten und den Namen der Versand-Pauschale aus dem Bestellobjekt
  //und schreibt diese in $shipping_cost und $shipping_method_title
  foreach($order->commerce_order_total[LANGUAGE_NONE][0]['data']['components'] as $compkeys => $compvals) {
    if (strpos($compvals['name'],'flat_rate_') !== false) {
      $shipping_cost = $compvals['price']['amount'] ;
      $servicename = str_replace('flat_rate_', '', $compvals['name']);
      $shipping_method_title = commerce_shipping_service_get_title($servicename, 'display_title'); 
    }
  }

  //Liefer- und Rechnungsadresse werden ermittelt
  $customer_profile_shipping = commerce_customer_profile_load($order->commerce_customer_shipping['und'][0]['profile_id']);
  $customer_profile_billing = commerce_customer_profile_load($order->commerce_customer_billing['und'][0]['profile_id']);

  //Es wird geprüft ob Liefer- und Rechnungsadresse identisch sind.
  //Wenn ja, erhält die Variable $differentdeladdr den Wert 1
  $differentdeladdr = 0;
  $differences = array_diff($customer_profile_shipping->commerce_customer_address['und'][0], $customer_profile_billing->commerce_customer_address['und'][0]);
  if(!empty($differences))
    $differentdeladdr = 1;
  }

  //Da der Shop in diesem Beispiel Vorkasse anbietet muss bei einer Vorkasse-Bestellung
  //Afterbuy gemeldet werden, dass die Bestellung noch nicht bezahlt ist. Bei Zahlung
  //per Zahlungsanbieter (z.B. PayPal) kann an Afterbuy bereits eine eingegangene Zahlung
  //gemeldet werden. $setpay erhält den Wert 1, wenn die Zahlung bereits erfolgt ist
  $setpay = 1;
  if (strpos($order->data['payment_method'],'bank_transfer') !== false) {
    $setpay = 0; 
  }

  //Der nächste Abschnitt ist mal wieder eine Afterbuy-Eigenart. Anstelle von ISO-Länderkürzeln, benutzt
  //Afterbuy die Länderkürzel von KFZ-Kennzeichen... Es muss also zuerst eine Konvertierung für die
  //vom Shop unterstützten Länder stattfinden :(
  $country_license_plate_billing = $customer_profile_billing->commerce_customer_address['und'][0]['country'];
  $country_license_plate_shipping = $customer_profile_shipping->commerce_customer_address['und'][0]['country'];
  switch($customer_profile_billing->commerce_customer_address['und'][0]['country']) {
    case 'DE': $country_license_plate_billing = 'D'; break;
    case 'BE': $country_license_plate_billing = 'B'; break;
    case 'FR': $country_license_plate_billing = 'F'; break;
    case 'IT': $country_license_plate_billing = 'I'; break;
    case 'LU': $country_license_plate_billing = 'L'; break;
    case 'SI': $country_license_plate_billing = 'SLO'; break;
    case 'ES': $country_license_plate_billing = 'E'; break;
    case 'HU': $country_license_plate_billing = 'H'; break;
    case 'AT': $country_license_plate_billing = 'A'; break;
  }
  switch($customer_profile_shipping->commerce_customer_address['und'][0]['country']) {
    case 'DE': $country_license_plate_shipping = 'D'; break;
    case 'BE': $country_license_plate_shipping = 'B'; break;
    case 'FR': $country_license_plate_shipping = 'F'; break;
    case 'IT': $country_license_plate_shipping = 'I'; break;
    case 'LU': $country_license_plate_shipping = 'L'; break;
    case 'SI': $country_license_plate_shipping = 'SLO'; break;
    case 'ES': $country_license_plate_shipping = 'E'; break;
    case 'HU': $country_license_plate_shipping = 'H'; break;
    case 'AT': $country_license_plate_shipping = 'A'; break;
  }

  //Hier wird die Beschreibung der Bezahlart ermittelt
  $tmp = explode('|', $order->data['payment_method']);
  $payment_method_title = commerce_payment_method_get_title('display_title', $tmp[0]);

  //Im Folgenden werden alle ermittelten Daten für die Übertragung an Afterbuy vorbereitet
  //indem sie in einem Array $data gesammelt werden. Die Benamung der Schlüssel und die
  //Formatierung der Werte kann der Afterbuy Shop-Schnittstellen-Dokumentation entnommen werden
  $data = array(
    'Action' => 'new',
    'PartnerID' => variable_get('afterbuy_partnerid', 123456),
    'PartnerPass' => variable_get('afterbuy_partnerpass', 'jkhkjhjkhkjlhkjhkjhkjhklh'),
    'UserID' => 'meineuserid',
    'Kbenutzername' => $order->mail,
    'KVorname' => $customer_profile_billing->commerce_customer_address['und'][0]['first_name'],
    'KNachname' => $customer_profile_billing->commerce_customer_address['und'][0]['last_name'],
    'KStrasse' => $customer_profile_billing->commerce_customer_address['und'][0]['thoroughfare'],
    'KStrasse2' => $customer_profile_billing->commerce_customer_address['und'][0]['premise'],
    'KPLZ' => $customer_profile_billing->commerce_customer_address['und'][0]['postal_code'],
    'KOrt' => $customer_profile_billing->commerce_customer_address['und'][0]['locality'],
    'Ktelefon' => $customer_profile_billing->field_phonenumber['und'][0]['value'],
    'Kemail' => $order->mail,
    'KLand' => $country_license_plate_billing,
    'Lieferanschrift' => $differentdeladdr,
    'BuyDate' => date('d.m.Y H:i:s'),
    'Versandart' => $shipping_method_title,
    'Versandkosten' => number_format($shipping_cost * 1.19 / 100, 2, ",", ""),
    'VID' => $order_id,
    'SetPay' => $setpay,
    'CheckVID' => 1,
    //'Kommentar' => 'Testkommentar',
    'Zahlart' => $payment_method_title,
  );

  //Wenn Liefer- und Rechnungsadresse nicht identisch sind, wird die Lieferadresse zusätzlich in $data geschrieben
  if($differentdeladdr) {
    $data['KLVorname'] = $customer_profile_shipping->commerce_customer_address['und'][0]['first_name'];
    $data['KLNachname'] = $customer_profile_shipping->commerce_customer_address['und'][0]['last_name'];
    $data['KLStrasse'] = $customer_profile_shipping->commerce_customer_address['und'][0]['thoroughfare'];
    $data['KLStrasse2'] = $customer_profile_shipping->commerce_customer_address['und'][0]['premise'];
    $data['KLPLZ'] = $customer_profile_shipping->commerce_customer_address['und'][0]['postal_code'];
    $data['KLOrt'] = $customer_profile_shipping->commerce_customer_address['und'][0]['locality'];
    $data['KLTelefon'] = $customer_profile_shipping->field_phonenumber['und'][0]['value'];
    $data['KLLand'] = $country_license_plate_shipping;
  }

  //Im Folgenden werden die einzelnen Artikel in $data geschrieben.
  $posanz = 0;
  foreach($order->commerce_line_items[LANGUAGE_NONE] as $line_item) {
    $line_item_object = commerce_line_item_load($line_item['line_item_id']);
    //Da Line Items nicht nur Produkte, sondern auch Versandkosten o.a. Elemente einer Bestellung sein könne
    //darf der folgende Code nur ausgeführt werden, wenn es sich um ein Line Item vom Typ 'product' handelt.
    if($line_item_object->type != 'product') continue;
    $product_object = commerce_product_load($line_item_object->commerce_product[LANGUAGE_NONE][0]['product_id']);
    $posanz++;
    $data['Artikelnr_' . $posanz] = $product_object->product_id;
    $data['AlternArtikelNr1_' . $posanz] = $product_object->sku;
    $data['AlternArtikelNr2_' . $posanz] = $line_item_object->order_id;
    $data['Artikelname_' . $posanz] = $product_object->title;
    $quantity = number_format($line_item_object->quantity, 0);
    $data['ArtikelEpreis_' . $posanz] = number_format($line_item_object->commerce_total[LANGUAGE_NONE][0]['amount']/$quantity/100, 2, ",", "");
    $data['ArtikelMwSt_' . $posanz] = 19;
    $data['ArtikelMenge_' . $posanz] = $quantity;
  }
  //Die Anzahl der Produkte wird ebenfalls als Wert übergeben
  $data['PosAnz'] = $posanz;

  //Und wieder muss mit einer Afterbuy Eigenart gekämpft werden:
  //Die Drupal-Funktion drupal_http_request() führt für ihre Parameter eine URL-Kodierung durch, wenn man diese
  //nicht direkt über die URL, sondern als query-Parameter übergibt.
  //Das erfolgt standard-konform über die PHP-Funktion rawurlencode(). In der Afterbuy-Dokumentation wird
  //nur darauf hingewiesen, dass die Parameter URL-kodiert werden müssen. Mehr nicht. Mit UTF-8 kann Afterbuy
  // - laut Dokumentation - umgehen. Dem ist aber nicht so. Lässt man UTF8-Daten durch rawurlencode laufen und übergibt
  //diese an Afterbuy, sind die Umlaute im Afterbuy-Admin-Interface zerstört.
  //Die einzige Möglichkeit ist, für alle Werte zuerst einen utf8_decode durchzuführen, dann selbst per urlencode zu kodieren
  //und dann nicht als query-Parameter, sondern als Teil der URL an drupal_http_request zu übergeben.
  $tempdata = array();
  foreach($data as $dkey=>$dval) {
    $tempdata[] = $dkey . '=' . urlencode(utf8_decode($dval));
  }
  $implodedquery = implode('&', $tempdata);
  $full_url = $url . '?' . $implodedquery;

  //Zu Debugging-Zwecken wird die URL ins Log geschrieben
  watchdog('afterbuy', print_r($full_url, TRUE));

  //Die Daten werden an Afterbuy übermittelt
  $result = drupal_http_request($full_url);

  //Zu Debugging-Zwecken wird die Antwort des HTTP-Requests ins Log geschrieben
  watchdog('afterbuy', print_r($result, TRUE));
}