Klient PHP

Dla ułatwienia integracji z Connect API udostępniliśmy klasę klienta. Klasa ta posiada odpowiedniki wszystkich metod ConnectAPI, a dodatkowo implementuje funcje logowania i przechowywania tokena autoryzacyjnego.

Klasa ConnectClient do używa:

Moduł shmop jest używany do przechowywania tokena autoryzacyjnego w pamięci współdzielonej serwera. Jeśli środowisko nie udostępnia tego modułu, klasa klienta może przechowywać token w pliku na serwerze, wskazanym w parametrze $tokenPath konstruktora klasy ConnectClient. Ze względów wydajnościowych zaleca się użycie shmop, jeśli jest dostępny (nie podany parametr $tokenPath).

↧ Pobierz klienta
<?php
namespace ConnectAPI;

/**
 * Klient interfejsu Connect API systemu Klub Klienta
 */
class ConnectClient {
  private const CONNECT_URL = 'https://connect.klubklienta.pl';
  private const SHM_SIZE = 512;
  private $_uid;
  private $_key;
  private $_token;
  private $_tokenTimeout;
  private $_tokenPath;
  private $_error;
  
  /**
   * Konstruktor klasy
   * 
   * @var string $uid - identyfikator użytkownika Connect API
   * @var string $key - klucz logowania użytkownika Connect API
   * @var string $tokenPath - ścieżka do katalogu, w którym będą zapisywane tokeny dostępowe do Connect API.
   *  Jeśli ścieżka nie zostanie podana, do przechowywania tokenów będzie używany mechanizm shared memory (opcja zalecana, serwer musi obsługiwać shmop)
   */
  public function __construct($uid, $key, $tokenPath = null) {
    $this->_uid = $uid;
    $this->_key = $key;
    $this->_tokenPath = $tokenPath;
    $this->_token = null;
  }

  /**
   * Zwraca komunikat ostatniego błędu
   * @return string komunikat błędu
   */
  public function error() {
    return $this->_error;
  }
  
  protected function call($url) {
    $curl = curl_init(self::CONNECT_URL . $url);
    if ($curl) {
      $options = [
        CURLOPT_POST => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER => [],
      ];
      if ($url != '/auth/login') {
        $options[CURLOPT_HTTPHEADER][] = "Authorization: Bearer {$this->_token}";
      }
      curl_setopt_array($curl, $options);
      $response = curl_exec($curl);
      $status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
      if ($status >= 200 && $status <= 299) {
        $data = json_decode($response, true);
        if ($response && !$data) $data = $response;
        $this->_error = null;
      }
      else {
        $data = $response;
        $this->_error = $response;
      }
      curl_close($curl);
      return $data;
    }
  }
  
  private function shmKey() {
    return hexdec(hash('crc32', "#ConnectAPI:{$this->_uid}"));
  }

  private function readTokenData() {
    $this->_error = null;
    $shmKey = $this->shmKey();
    if ($this->_tokenPath) { // storing token in file
      $fname = "{$this->_tokenPath}/{$shmKey}";
      if (file_exists($fname)) {
        return json_decode(file_get_contents($fname), true);
      }
    }
    elseif (function_exists('shmop_open')) { // storing token in shared memory
      $shmop = @shmop_open($shmKey, 'c', 0644, self::SHM_SIZE);
      if ($shmop) {
        $size = shmop_read($shmop, 0, 4);
        $json = shmop_read($shmop, 4, (int)$size);
        $tokenData = json_decode($json, true);
        if (phpversion() < '8') shmop_close($shmop);
        return $tokenData;
      }
      else return null;
    }
    else {
      $this->_error = 'No shmop module nor token path available';
      return null;
    }
    return null;
  }

  private function writeTokenData($tokenData) {
    $this->_error = null;
    $shmKey = $this->shmKey();
    if ($this->_tokenPath) { // storing token in file
      $fname = "{$this->_tokenPath}/{$shmKey}";
      if (!file_exists($this->_tokenPath)) {
        mkdir($this->_tokenPath, 0777, true);
      }
      file_put_contents($fname, json_encode($tokenData));
      return true;
    }
    elseif (function_exists('shmop_open')) {
      $shmop = shmop_open($shmKey, 'c', 0644, self::SHM_SIZE);
      $json = json_encode($tokenData);
      shmop_write($shmop, sprintf('%04d', strlen($json)), 0);
      shmop_write($shmop, "{$json}", 4);
      if (phpversion() < '8') shmop_close($shmop);
      return true;
    }
    else {
      $this->_error = 'No shmop module nor token path available';
      return false;
    }
  }
  
  /**
   * Logowanie do Connect API
   * Logowanie następuje z użyciem UID i KEY przekazanych w konstruktorze klasy
   *
   * @return bool rezultat logowania, w przypadku niepowodzenia błąd można pobrać metodą error()
   */
  public function authorize() {
    if ($this->_token && time() < $this->_tokenTimeout) { // token is valid
      return true;
    }
    $tokenData = $this->readTokenData();
    if ($this->_error) return false;
    if ($tokenData) { // token taken from file or shared memory
      if (time() < $tokenData['timeout']) {
        $this->_token = $tokenData['token'];
        $this->_tokenTimeout = $tokenData['timeout'];
        return true;
      }
    }
    // call Connect login
    $tokenData = $this->call("/auth/login/{$this->_uid}/{$this->_key}");
    if ($this->_error) return false;
    if ($tokenData) {
      $this->_token = $tokenData['token'];
      $this->_tokenTimeout = time() + $tokenData['time'] - 300;
      $this->writeTokenData([
        'token' => $this->_token,
        'timeout' => $this->_tokenTimeout,
      ]);
      return !$this->_error;
    }
    else {
      if (!$this->_error) $this->_error = 'Login failed';
      return false;
    }
  }

  /**
   * Pobranie listy programów lojalnościowych
   * 
   * @return Program[] tablica obiektów klasy Program
   */
  public function programs() {
    if (!$this->authorize()) return null;
    $data = $this->call('/seller/programs');
    if ($data === null) return null;
    $result = [];
    foreach ($data as $progData) {
      $result[] = new Program($progData);
    }
    return $result;
  }

  /**
   * Pobranie informacji o programie lojalnościowym
   * 
   * @param int $programId identyfikator programu
   * @return Program definicja programu lojalnościowego
   */
  public function program($programId) {
    if (!$this->authorize()) return null;
    $data = $this->call('/seller/program/' . (int)$programId);
    if ($data === null) return null;
    return new Program($data);
  }

  /**
   * Pobranie listy definicji punktów za zakupy
   * 
   * @param int $programId identyfikator programu
   * @return Point[] lista definicji punktów
   */
  public function points($programId) {
    if (!$this->authorize()) return null;
    $data = $this->call('/seller/points/' . (int)$programId);
    if ($data === null) return null;
    $result = [];
    foreach ($data as $pointData) {
      $result[] = new Point($pointData);
    }
    return $result;
  }

  /**
   * Pobranie pojedynczej definicji punktów za zakupy
   * 
   * @param int $programId identyfikator programu
   * @param string $tag kod definicji punktów
   * @return Point definicja punktów
   */
  public function point($programId, $tag) {
    if (!$this->authorize()) return null;
    $data = $this->call('/seller/point/' . (int)$programId . "/{$tag}");
    if ($data === null) return null;
    return new Point($data);
  }

  /**
   * Pobranie listy nagród
   * 
   * @param int $programId identyfikator programu
   * @return Prize[] lista nagród
   */
  public function prizes($programId) {
    if (!$this->authorize()) return null;
    $data = $this->call('/seller/prizes/' . (int)$programId);
    if ($data === null) return null;
    $result = [];
    foreach ($data as $prizeData) {
      $result[] = new Prize($prizeData);
    }
    return $result;
  }

  /**
   * Pobranie nagrody
   * 
   * @param int $programId identyfikator programu
   * @param int $tag kod nagrody
   * @return Prize nagroda
   */
  public function prize($programId, $tag) {
    if (!$this->authorize()) return null;
    $data = $this->call('/seller/prize/' . (int)$programId . "/{$tag}");
    if ($data === null) return null;
    return new Prize($data);
  }

  /**
   * Wygenerowanie kodu zaproszenia do programu
   * 
   * @param int $programId identyfikator programu
   * @return Code wygenerowany kod
   */
  public function invite($programId) {
    if (!$this->authorize()) return null;
    $data = $this->call('/seller/invite/' . (int)$programId);
    if ($this->_error) return null;
    return new Code($data);
  }

  /**
   * Wygenerowanie kodu punktów za zakupy o okresie ważności 5 minut
   * 
   * @param int $programId identyfikator programu
   * @param string $tag kod definicji punktów
   * @param int $number podstawa wyliczenia punktów za zakupy: liczba zakupionych produktów (dla type = Point::TypeNumber) lub kwota zakupu (dla type = Point::TypeAmount)
   * @return Code wygenerowany kod
   */
  public function givePoints($programId, $tag, $number) {
    if (!$this->authorize()) return null;
    $data = $this->call('/seller/givePoints/' . (int)$programId . "/{$tag}/" . (int)$number);
    if ($this->_error) return null;
    return new Code($data);
  }

  /**
   * Wygenerowanie kodu przekazania nagrody o okresie ważności 5 minut
   * 
   * @param int $programId identyfikator programu
   * @param string $tag kod nagrody
   * @return Code wygenerowany kod
   */
  public function givePrize($programId, $tag) {
    if (!$this->authorize()) return null;
    $data = $this->call('/seller/givePrize/' . (int)$programId . "/{$tag}");
    if ($this->_error) return null;
    return new Code($data);
  }

  /**
   * Wydłużenie okresu ważności kodu
   * 
   * @param string $code kod, który ma zostać przedłużony
   * @return Code wygenerowany kod
   */
  public function extendCode($code) {
    if (!$this->authorize()) return null;
    $data = $this->call("/seller/extendCode/{$code}");
    if ($this->_error) return null;
    return new Code($data);
  }

  /**
   * Wysłanie e-mailem kodu wraz instrukcjami jego użycia
   * 
   * @param string $code kod, który ma zostać wysłany
   * @param string $recipient adres, na który ma zostać wysłany e-mail
   * @return Result potwierdzenie wykonania operacji
   */
  public function sendCode($code, $recipient) {
    if (!$this->authorize()) return null;
    $data = $this->call("/seller/sendCode/{$code}/{$recipient}");
    if ($data === null) return null;
    return new Result($data);
  }

  /**
   * Pobranie informacji o kodzie realizacji nagrody
   *
   * @param string $code kod realizacji nagrody wygenerowany w aplikacji klienta
   * @return CheckResult informacje o kodzie
   */
  public function checkCode($code) {
    if (!$this->authorize()) return null;
    $data = $this->call("/seller/checkCode/{$code}");
    if ($data === null) return null;
    return new CheckResult($data);
  }

  /**
   * Potwierdzenie kodu realizacji nagrody
   * 
   * @param string $code kod realizacji nagrody wygenerowany w aplikacji klienta
   * @return Result potwierdzenie wykonania operacji
   */
  public function confirmCode($code) {
    if (!$this->authorize()) return null;
    $data = $this->call("/seller/confirmCode/{$code}");
    if ($data === null) return null;
    return new Result($data);
  }
}

abstract class Initializable {
  protected function init($data) {
    if ($data && is_array($data)) {
      $vars = get_class_vars(get_class($this));
      foreach ($vars as $var => $foo) {
        if (key_exists($var, $data)) {
          $this->{$var} = $data[$var];
        }
      }
    }
  }
}

/**
 * Definicja programu lojalnościowego
 */
class Program extends Initializable {
  /** @var int identyfikator programu lojalnościowego */
  public $id;
  /** @var string nazwa programu */
  public $name;
  /** @var string nazwa organizatora (string) */
  public $companyName;
  
  /** @var string opis programu lub slogan reklamowy */
  public $descr;
  /** @var string opis nagrody powitalnej lub punktów powitalnych */
  public $startPrize;
  /** @var int liczba punktów za polecenie programu */
  public $recommendPoints;
  /** @var string data zakończenia programu w formacie YYYY-MM-DD */
  public $collectPointsTo;
  /** @var string data zakończenia odbioru nagród w formacie YYYY-MM-DD */
  public $receivePrizesTo;

  public function __construct($data = null) {
    $this->init($data);
  }
}

/**
 * Definicja punktów za zakupy
 */
class Point extends Initializable {
  /** @var int punkty przypisane do zakupu */
  const TypeNumber = 1;
  /** @var int punkty za wielokrotność kwoty zakupu */
  const TypeAmount = 2;

  /** @var string kod definicji punktów */
  public $tag;
  /** @var string nazwa definicji punktów */
  public $name;
  /** @var string typ definicji:
   *  Point::TypeNumber (1) - przypisane do zakupu;
   *  Point::TypeAmount (2) - zależne od kwoty zakupów
   */
  public $type;
  /** @var int liczba punktów za zakupy */
  public $points;
  /** @var int w przypadku, gdy type = Point::TypeAmount (2), oznacza kwotę, której wielokrotność uprawnia do otrzymania punktów */
  public $amount;
  /** @var string jednostka zakupu, np. minutę, sztukę, seans, usługę */
  public $subject;
  /** @var string opis definicji punktów */
  public $descr;
  /** @var string opis definicji punktów przyjazny dla klienta; wygenerowany automatycznie na podstawie właściwości: name, type, points, amount i subject */
  public $label;

  public function __construct($data = null) {
    $this->init($data);
  }
}

/**
 * Definicja nagrody
 */
class Prize extends Initializable {
  /** @var int zniżka procentowa */
  const TypePercentDiscount = 1;
  /** @var int zniżka kwotowa */
  const TypeAmountDiscount = 2;
  /** @var int gratis */
  const TypeFree = 3;
  /** @var int prezent */
  const TypeGift = 4;

  /** @var string kod nagrody */
  public $tag;
  /** @var string name - nazwa nagrody */
  public $name;
  /** @var int typ nagrody:
   *  Prize::TypePercentDiscount (1) - zniżka procentowa,
   *  Prize::TypeAmountDiscount (2) - rabat kwotowy,
   *  Prize::TypeFree (3) - gratis,
   *  Prize::TypeGift (4) - prezent
   */
  public $type;
  /** @var int wartość punktowa nagrody */
  public $points;
  /** @var int liczba dostępnych sztuk nagrody (null -  nielimitowana) */
  public $availableCnt;
  /** @var bool dostępna tylko dla sprzedawcy */
  public $onlyForSeller;
  /** @var string opis nagrody */
  public $descr;
  /** @var string opis nagrody przyjazny dla klienta; wygenerowany automatycznie na podstawie właściwości: name, type, points */
  public $label;

  public function __construct($data = null) {
    $this->init($data);
  }
}

/**
 * Kod dla aplikacji mobilnej kienta
 */
class Code extends Initializable {
  /** @var string kod w postaci cyfrowej */
  public $code;
  /** @var string adres kierujący użytkownika do użycia kodu */
  public $url;
  /** @var string kod QR jako obraz image/png w postaci data inline */
  public $qr;
  /** @var string tytuł kodu */
  public $title;
  /** @var string nazwa kodu */
  public $descr;
  /** @var string opis czasu aktywności kodu */
  public $time;
  /** @var int czas aktywności kodu w sekundach */
  public $timeout;

  public function __construct($data = null) {
    $this->init($data);
  }
}

/**
 * Rezultat operacji
 */
class Result extends Initializable {
  /** @var bool flaga powodzenia operacji */
  public $success;
  /** @var string tytuł rezultatu potwierdzenia lub nazwa błędu */
  public $title;
  /** @var string opis rezultatu potwierdzenia lub komunikat błędu */
  public $message;
  
  public function __construct($data = null) {
    $this->init($data);
  }
}

/**
 * Rezultat sprawdzenia kodu odbioru nagrody
 */
class CheckResult extends Initializable {
  /** @var string tytuł sprawdzonego kodu */
  public $title;
  /** @var string opis sprawdzonego kodu */
  public $descr;
  /** @var int liczba sekund pozostała do upłynięcia ważności kodu */
  public $timeout;
  /** @var int identyfikator programu, w ramach którego wygenerowano kod */
  public $programId;
  /** @var int kod nagrody */
  public $tag;
  
  public function __construct($data = null) {
    $this->init($data);
  }
}