API-Key einsehen. * * @var string */ private $api = ''; /** * look n days into the past, max 30 days allowed! * * @var int */ private $days = 5; /** * Als Benutzername verwenden Sie bitte Ihre Kundennummer (bspw. 99999) * als Passwort Ihren API-Key (bspw. a12b34cd567890123e456f7890123456) * * required credentials in array $zugangsdaten: * array ( * 'API_USER' => 'customer id' * 'API_KEY' => 'API key' * ) * * optional: 'API_DAYS' * * @param array $credentials * @param string csv * * @throws Exception * @throws RuntimeException * @throws ResponseException * @throws MissingArgumentException * * @return string */ public function Import(array $credentials) { $credentials = array_change_key_case($credentials, CASE_UPPER); if (!array_key_exists('API_USER', $credentials)) { throw new MissingArgumentException('API_USER fehlt'); } $this->customerID = $credentials['API_USER']; if (!array_key_exists('API_KEY', $credentials)) { throw new MissingArgumentException('API_KEY fehlt'); } $this->api = $credentials['API_KEY']; if (array_key_exists('API_DAYS', $credentials)) { $days = $credentials['API_DAYS']; if (is_numeric($days)) { $days = (int) $days; $this->days = $days; } } $csv = $this->importLoop(); $csv = implode(self::ROW_DIVIDER, $csv); $csv = utf8_decode($csv); return $csv; } /** * copied and modified from 'ImportKontoauszug' in 'class.erpapi' * used case "stripe" in switch statement * * @param string $csv the csv 'file' to import * @param int $konto the konto id * @param $app * @return array($inserted, $duplicate); */ public function ImportKontoauszug($csv, $konto, $app) { $inserted = 0; $duplicate = 0; $db_array = preg_split("/(\r\n)+|(\n|\r)+/", $csv); if (empty($db_array)) { // empty db_array return array($inserted, $duplicate); } // fix values $gegenkonto = ''; $stamp = time(); $userName = $app->User->GetName(); $userName = mysqli_real_escape_string($app->DB->connection, $userName); // skip first row -> 'header' line $count = count($db_array); for ($i = 1; $i < $count; $i++) { // explode row $row = $db_array[$i]; $row = str_replace('"','', $row); $row = explode(self::COL_DIVIDER, $row); // $csv="date;description;amount;currency\r\n"; // 0 date // 1 description // 2 amount // 3 currency // extract the values list($buchung, $transaction, $vorgang, $projektID, $betrag, $waehrung,$gebuehr) = $row; $buchung = mysqli_real_escape_string($app->DB->connection, $buchung); $buchung = str_replace('"','', $buchung); $vorgang = utf8_encode($vorgang ); $vorgang = mysqli_real_escape_string($app->DB->connection, $vorgang); $vorgang = str_replace('"','',$vorgang); $betrag = mysqli_real_escape_string($app->DB->connection, $betrag); $waehrung = mysqli_real_escape_string($app->DB->connection, $waehrung); $gebuehr = mysqli_real_escape_string($app->DB->connection, $gebuehr); // haben vs. soll list($haben, $soll) = $betrag > 0 ? [$betrag, ''] : ['', $betrag]; // hash over some values $pruefsumme = md5(serialize([$buchung, $vorgang, $soll, $haben, $waehrung])); $check = $app->DB->Select("SELECT id FROM kontoauszuege WHERE buchung='$buchung' AND konto='$konto' AND pruefsumme='$pruefsumme' LIMIT 1"); if($check > 0) { $duplicate++; continue; } $sql = "INSERT INTO kontoauszuege ( konto, buchung, vorgang, soll, haben, gebuehr, waehrung, fertig, bearbeiter, pruefsumme, importgroup, originalbuchung, originalvorgang, originalsoll, originalhaben, originalgebuehr, originalwaehrung, gegenkonto ) VALUE ( '$konto', '$buchung', '$vorgang', '$soll', '$haben', '$gebuehr', '$waehrung', 0, '".$userName."', '$pruefsumme', '$stamp', '$buchung', '$vorgang', '$soll', '$haben', '$gebuehr', '$waehrung', '$gegenkonto')"; $app->DB->Insert($sql); $newid = $app->DB->GetInsertID(); $app->DB->Update("UPDATE kontoauszuege SET sort='$newid' WHERE id='$newid' LIMIT 1"); $inserted++; } return [$inserted, $duplicate]; } /** * perform curl request * protected to override via child class, if necessary * * Sie müssen die korrekte URL aufrufen und dabei HTTPS als Protokoll verwenden. * Sie müssen die korrekten Authentifizierungsinformationen übermitteln. Zur Authentifizierung * wird die Basic-HTTP-Authentication (RFC 2617) verwendet. * * Sie müssen die korrekten Content-Type Header angeben. * Ihre Daten müssen korrekt als XML formatiert sein (RFC 3023, siehe Parameterübersicht) und per HTTP POST * verschickt werden. * * @param string $xml * @return string * @throws Exception */ protected function curl($xml) { $header = [ 'Content-Type' => self::CONTENT_TYPE, 'Accept:' => self::CONTENT_TYPE, ]; $url = self::URL; $ch = curl_init($url); curl_setopt_array($ch, array( CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSLVERSION => 6, CURLOPT_HTTPAUTH => CURLAUTH_BASIC, CURLOPT_USERPWD => $this->getAuthString(), CURLOPT_POSTFIELDS => $xml, CURLOPT_HTTPHEADER => $header )); $result = curl_exec($ch); $info = curl_getinfo($ch); curl_close($ch); if (isset($info['http_code']) && $info['http_code'] !== 200) { $code = $info['http_code']; throw new RuntimeException(sprintf( 'Verbindung fehlgeschlagen - konnte \'%s\' nicht erreichen (Statuscode: \'%s\')', $url, $code )); } return $result; } /** * perform the imports * * @throws Exception * @throws RuntimeException * * @return array */ private function importLoop() { $page = 1; $number = 20; $end = $this->getDate(); $start = $this->getDate($this->days); $csv = array('date'.self::COL_DIVIDER.'description'.self::COL_DIVIDER.'amount'.self::COL_DIVIDER.'currency'); do { /** * - product: * Produkt, auf das die ausgegebenen Transaktionen eingeschränkt werden soll * ("paycode": SOFORT Überweisung Paycode, "payment": SOFORT Überweisung) * - status: * Status, auf den die ausgegebenen Transaktionen eingeschränkt werden sollen * * status, reason, meaning * loss not_credited Das Geld ist nicht eingegangen. * pending not_credited_yet Das Geld ist noch nicht eingegangen. * received credited Das Geld ist eingegangen. * refunded compensation Das Geld wurde zurückerstattet (Teilrückbuchung). * refunded refunded Das Geld wurde zurückerstattet (komplette Rückbuchung des Gesamtbetrags). * */ $request = [ 'from_time' => $start, 'to_time' => $end, 'number' => $number, 'page' => $page, 'product' => 'payment', //'status' => 'received', ]; $xml = $this->generateXML('transaction_request version="2"', $request); $response = $this->curl($xml); if (empty($response)) { break; } $xml = simplexml_load_string($response); $childs = $xml->count(); $name = $xml->getName(); if ($name !== 'transactions') { /* * no 'transactions' response -> may an error or a warning?! * * * * * 1000 * Invalid request. * * */ throw new ResponseException(sprintf( 'Expected \'transactions\' response, got \'%s\'\n%s', $name, $response )); } if (isset($xml->transaction_details)) { // transaction_details found -> loop foreach ($xml->transaction_details as $transaction) { $csv[] = $this->extractData($transaction); } } // loop as long as the received childs // are the max. number of requested child // so step to the next page $page = $page + 1; } while($childs === $number); return $csv; } /** * extract some data from the given transaction * extract * - date * - amount * - currency * - description (transaction id, paycode, project id) * * @param SimpleXMLElement $xml * @return string */ private function extractData(SimpleXMLElement $xml) { $transaction = (string) $xml->transaction; $reasons = ''; foreach ($xml->reasons->reason as $index => $reason){ $reasons .= ' '.$reason; } $reasons = trim($reasons); $date = (string) $xml->time; $date = substr($date, 0, 10); $amount = (float) $xml->amount; $currency = (string) $xml->currency_code; $paycode = (string) $xml->paycode->code; $project = (string) $xml->project_id; $costs = (float) $xml->costs->fees; /* * amount_refunded; [0,1]; Decimal (8.2); Zurück überwiesener Betrag * [0,1] = optionaler Parameter, es kann max. ein Wert übergeben werden */ if (isset($xml->amount_refunded)) { $amount_refunded = (float) $xml->amount_refunded; if ($amount_refunded != 0) { /* * Make sure, the refunded amount is negative and replace the amount. */ $amount = -1 * abs($amount_refunded); } } $line = [ $date, $transaction, $reasons, $paycode.' '.$project, $amount, $currency, $costs, ]; $line = implode(self::COL_DIVIDER, $line); return $line; } /** * generate the xml request from given array & tag * * e.g. * $tag = 'transaction_request version="2"' * $request = array( * 'from_time' => '2013-04-01', * 'to_time' => '2013-04-30', * 'number' => 10, * 'page' => 2, * 'product' => 'paycode' * ); * * converts to * * * * 2013-04-01 * 2013-04-30 * paycode * 10 * 2 * * * * @param string $tag * @param array $request * @return string */ private function generateXML($tag, array $request) { // remove surrounding '< -- >' from tag $tag = ltrim($tag, '<'); $tag = rtrim($tag, '>'); // explode the tag -> use first part as end tag (cut's version=2) // e.g. $tag = 'transaction_request version="2"' // converts to $end = transaction_request $end = explode(' ', $tag)[0]; $xml = array(); $xml[] = ''; $xml[] = "<{$tag}>"; foreach ($request as $k => $v){ $xml[] = "\t<{$k}>{$v}"; } $xml[] = ""; $xml = implode("\n", $xml); return $xml; } /** * get the necessary Date * if daysAgo is '0', return today's date * else the date, n days ago * * @param int $daysAgo note: the max value is 29 * * @throws RuntimeException * * @return string */ public function getDate($daysAgo = 0) { $daysAgo = (int)$daysAgo; $daysAgo = (int) abs($daysAgo); // allow max. 29 days $daysAgo = min(29, $daysAgo); if ($daysAgo === 0) { $time = time(); } else { $tmp = sprintf('-%s days', $daysAgo); $time = strtotime($tmp); } // generate formatted date string from time $date = date(self::DATE_FORMAT, $time); if ($date === false) { throw new RuntimeException(sprintf( 'Cannot create formatted date for %s day(s) ago.', $daysAgo )); } return $date; } /** * Zur Authentifizierung wird die Basic-HTTP-Authentication (RFC 2617) verwendet. * Als Benutzername verwenden Sie bitte Ihre Kundennummer (bspw. 99999) und als Passwort * Ihren API-Key (bspw. a12b34cd567890123e456f7890123456), die Sie durch ":" getrennt * aneinander fügen und mit Base64 codieren (base64(99999:a12b34cd567890123e456f7890123456)). * * @return string */ private function getAuthString() { return $this->customerID.':'.$this->api; } }