четверг, 29 октября 2009 г.

Продажа контента за SMS

Сейчас очень распространенным стала оплата услуг и товаров через СМС. Но не многие знают как это все организовано.
Я бы хотел рассказать об одном из простых способов.

Рассмотрим пример оплаты и прокомментируем каждый этап исходным кодом на PHP.

0. Размещению на сайте оплаты через смс предшествует заключение договора с компанией предоставляющей эти услуги.
1. Пользователь заходит на страничку, где ему предлагается скачать приложение, предварительно отослав смс с текстом на указанный номер.
Реализация: Выводится статичные текст с текстом и телефоном, который предоставила на предыдущем этапе организация
2. Клиент отправляет СМС. Ему приходит ответная с уникальным кодом на доступ
Реализация: Сервер смс компании, получает сообщение с нужным тектом на номер. Генерируется событие, вызывающее произвольную (настраиваемую) страницу на нашем сайте. На это странице мы произвольны сделать, что угодно.
Генерируем код, и записываем в БД:
function getCOde() {
  $code = sprintf("%06x", rand(1000000, 10000000)); //рандомное число, преобразованное в hex
  //проверяем на повторения
  $res = db_query("SELECT COUNT(*) AS cnt FROM {sms_pay} WHERE code = '%s'", $code);
  $codes = db_fetch_array($res);
  if($codes['cnt'] > 0) {
    getCode(); //генерируем еще раз
  }
  db_query("INSERT INTO {sms_pay} (code) VALUES('%s')", rand(), $code); //записываем код в БД
  return $code;
}
Сгенерированный код, вываливается через echo в браузер, а сервер весь сгенерированый текст отправляет в виде смс обратно клиенту.
На данном этапе код еще не привязан ни к одному материалу и его можно активировать на любом.
3. Клиент получил код, вводит его, чтобы получить доступ к материалу
Реализация: Сервер сайт получает код, проверяет его наличие в БД. Если код верный, то в БД вносятся данные о файле к которому был получен доступ, также для защиты от передачи кода третьим лицам в бд заносится ip адрес клиента и ставится кука.
Файл может быть скачан не сразу, а по частям. В этом случае пользователю дается несколько часов на скачку файла: в бд заносим также время первого обращения к файлу.
После этого пользователю файл стримится прямо в браузер с утановкой необходимых хеадреов, чтобы у клиента не было прямого доступа к файлу (Об этом можно почитать отдельно, я пользуюсь стандартными функциями Drupal)
4. Клиент повторно обращается к этому же файлу.
Реализация: При повторном обращении к файлу, снова проверять код не обязательно. Необходимо сверить лишь куки и ip пользователя, а также id файла к которому идет обращение.
5. Если пользователь пытается обратится к другому файлу с этим же кодом, то это ему не удастся, т.к. он не пройдет проверку по id файла.
6. Код был передан третьим лицам. Пользователь не пройдет проверку по ip и куке.
Примерный код для предыдущих пунктов можно видеть в листинге: (php/Drupal, точка входа функция sms_pay_file_download)
function checkNumber($code, $fid) {
  //сверяемся с БД
  $res = db_query("SELECT COUNT(*) AS cnt FROM {sms_pay} WHERE code = '%s' AND (fid = %d OR fid = 0)", $code, $fid);
  $sms = db_fetch_array($res);
  if($sms['cnt'] > 0) return true;
  return false;
}

/* Проверка прав на скачку файла */
function isAllowDownload($file) {
  $node = getNodeFile($file);
  $hash = $_COOKIE['game_hash_'.$node['fid']];

  $result = db_query("SELECT * FROM {sms_pay} WHERE hash = '%s' AND ip = '%s'", $hash, ip_address());
  $file = db_fetch_array($result);
  if(!empty($file['id'])) {
    if($file['dl_time'] < time() - variable_get('sms_time', 3) * 3600) {
      db_query("DELETE FROM {sms_pay} WHERE hash = '%s' AND ip = '%s'", $hash, ip_address());
      echo theme('page', t("Время доступа к файлу вышло. Отправьте СМС заново. У Вас было !hours часов с !date",
                           array('!hours' => variable_get('sms_time', 3), '!date' => format_date($file['dl_time']))));
      exit;
    }
    return true;
  }

  return false;
}

function sms_pay_genCode($nid, $fid) {
  return substr(sprintf("%x", $nid.$fid), 0, 6);
}

/* Уникальная строка для пользователя */
function getHash() {
  return md5($node['fid'].ip_address().time().rand());
}

/* Получить данные о ноде по файлу */
function getNodeFile($file) {
  $result = db_query("SELECT n.nid nid, n.title game, u.fid fid, a.dst path ".
                     "FROM {node} n ".
                     "INNER JOIN {upload} u ".
                     "ON u.nid = n.nid ".
                     "INNER JOIN {files} f ".
                     "ON(f.fid = u.fid)".
                     "LEFT JOIN {url_alias} a ".
                     "ON a.src =  CONCAT('node/',n.nid) ".
                     "WHERE f.filepath = '%s'", "sites/default/files/".$file);
  return db_fetch_array($result);
}

/* Hook File Download */
function sms_pay_file_download($filepath) {
  $mime = substr($filepath, -3);
  if(in_array($mime, explode(" ", variable_get('pay_ext', 'exe zip swf rar')))) {
    $node = getNodeFile($filepath);
    if(!isAllowDownload($filepath)) {
      if(!empty($_POST['code'])) {
        //ввели какойто код
        if(checkNumber($_POST['code'], $node['fid'])) { //он верный
          setAllowSmsPay($filepath);
        } else {
          echo theme('page', "
Вы ввели не верный код скачки или уже активировали его на другой игре.
"); exit; } } } $res = db_query("SELECT totalcount FROM node_counter WHERE nid = %d", $node['fid']); $cnt = db_fetch_array($res); //увеличим счетчик скачивания if($cnt['totalcount'] > 0 ) { db_query("UPDATE node_counter SET totalcount = totalcount + 1 WHERE nid = %d", $node['fid']); } else { db_query("INSERT INTO node_counter (nid, totalcount) VALUES(%d, 1)", $node['fid']); } } } /* Установка возможности скачки файла для текущего пользователя */ function setAllowSmsPay($file) { $node = getNodeFile(basename($file)); $hash = getHash(); setcookie("game_hash_".$node['fid'], $hash, time() + variable_get('sms_time', 3) * 3600, "/"); db_query("UPDATE {sms_pay} SET dl_time = %d, hash = '%s', ip = '%s', fid = %d", time(), $hash, ip_address(), $node['fid']); }

Метод имеет недостаток: нет взаимосвязи между отсылаем сообщение и пришедшим кодом, т.к. на странице генерации кода мы еще не знаем для какого файла будет применен код. Нельзя будет сделать бонусные или привязанные к файлу коды.

Пример реализации можно видеть на сайте mobipad.ru