Pull to refresh

Подключаем Facebook Credits для интернет-магазинов

Lumber room
Привет, хабр. Не так давно мы написали модуль, который подключает магазины на 1С-Битрикс к социальной сети Facebook. Хочу поделиться опытом, а также особенностями настройки приема Facebook Credits в вашем магазине — не суть важно, на какой CMS он реализован. Помчались!


Историческая справка


Facebook Credits — внутренняя валюта социальной сети Facebook, впервые появившаяся в 2009 году, и ставшая единственной разрешенной, по правилам Facebook, с 1 июня этого года. Долгое время подключить российский банк для вывода заработанных кредитов было невозможно — Россия просто отсутствовала в списке стран для подключения вывода Facebook Credits.

Единственными возможными способами вывести заработанные кредиты был либо вывод через PayPal (легального, белого и пушистого способа вывод из PayPal в Россию на данный момент нет), либо открытие счета в иностранном банке. Однако 27 июня Россия появилась в форме подключения платежей, хотя в официальном списке ее еще нет. Но мы очень надеемся, и уже готовы :-)

Подключаем платежи к приложению


Будем исходить из того, что у вас уже есть рабочее приложение для Facebook, и ваша задача — подключить прием кредитов. Итак, идем в настройки вашего приложения
и жмем «Редактировать». Переходим в раздел «On Facebook» -> Credits. Идем по ссылке регистрации новой компании. Аккуратно заполняем поля на свою организацию. Самая интересная вкладка — третья. Это настройка банковского счета.


Сходу несколько советов. Форма несколько подглючивает, и если вы ее неправильно заполнили — стирает часть полей. Поэтому рекомендую не кликать на кнопку «ОК», а нажимать «Открыть в отдельном окне» — так даже в случае возникновения ошибок вам не придется перебивать данные заново. Заполняйте все поля на английском языке. Поле SWIFT вводите без пробелов.


После регистрации организации вы сможете добавить ее для приема кредитов в своем приложении.

На заметку


По правилам Facebook вы можете продавать за Facebook Credits исключительно виртуальные товары. Лично мне было бы интересно узнать, является ли продажа QR-кодов виртуальным товаром (если, допустим, по этому QR-коду можно сходить в кино, или заказать пиццу :-)).
Комиссия Facebook составляет 30% (это очень много, но меньше, чем, например, в Одноклассниках). В общем, есть нюансы, которые нужно учитывать при планировании бизнес-стратегии продаж в социальных сетях.

Общая схема работы Facebook Credits


Итак, давайте посмотрим, что происходит в вашем приложении в тот момент, когда пользователь решается чего-нибудь оплатить и кликает на заветную кнопку.


Ваше приложение с помощью JavaScript-вызова Facebook Api создает диалоговое окно оплаты товара:

    FB.init({appId: <?=$facebookAppID?>, status: true, cookie: true});

    function placeOrder() {
    // Вызываем диалог оплаты   
    var obj = {
            method: 'pay',
            order_info: {"order_id": "<?=$orderId?>"},
            purchase_type: 'item'
        };

        FB.ui(obj, callback);
    }

    var callback = function(data) {
        
        if (data['order_id']) {
            $("#payment").hide();
            $("#result-success").show();

        } else if ((data['error_code']) && (data['error_message'].indexOf("User canceled", 0) == -1)) {
            $("#payment").hide();
            $("#result-failure").show();

        } else if ((data['error_code']) && (data['error_message'].indexOf("User canceled", 0) != -1)) {
            $("#result-cancel").show();

        } else {
            $("#result-failure").show();
        }
    };




Facebook посылает запрос на серверную часть вашего приложения для получения деталей о заказе:
  • item_id — идентификатор товара;
  • title — название товара;
  • description — описание;
  • image_url — картинка;
  • product_url — ссылка на страницу с товаром;
  • price — цена;
  • data — дополнительные данные.

Как вы заметили, Facebook предполагает, что за одну транзакцию пользователь может купить только один товар. Но ничто не мешает вам формировать все содержимое корзины в виде товара.

Клиентский callback будет вызван сразу после закрытия окна оплаты. В нем вы должны обработать код ошибки/отмены, если такие возникли, либо поздравить пользователя с успешной покупкой.

Принимаем заказ


Как только пользователь подтверждает оплату, на ваш сервер отправляется повторный запрос об изменении статуса заказа. Если все ок, ваша задача — реализовать обработчик заказов. Например, отправлять владельцу магазина уведомление о заказе, или выставлять статус “оплачено” в базе данных.


<?require($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/prolog_before.php");

if (!CModule::IncludeModule("sale") || !CModule::IncludeModule("iblock")) {
    echo("Модуль интернет-магазина не установлен!");
    require($_SERVER["DOCUMENT_ROOT"] . "/bitrix/modules/main/include/epilog_after.php");
    die();
}


// Получим настройки модуля
$frontendPath = COption::GetOptionString("sibirix.freshshop", "frontend");
$catalogIblockId = COption::GetOptionString("sibirix.freshshop", "catalogIblockId");
$fbcExchange = COption::GetOptionString("sibirix.freshshop", "exchange");
$api_key = COption::GetOptionString("sibirix.freshshop", "facebook_appid");
$secret  = COption::GetOptionString("sibirix.freshshop", "facebook_appsecret");

require_once('facebook.php');

// prepare the return data array
$data = array('content' => array());
$request = parse_signed_request($_REQUEST['signed_request'], $secret);
if ($request == null) {
    // handle an unauthenticated request here
    die("empty request\n");
}

// при получении фейсбуковский order_id преобразуется из строки во float и это скорее всего может стать огромной проблемой (потеряется пара последних знаков в номере заказа, который нам возвращает FB). Выдернем его руками
$payloadData = explode('.', $_REQUEST['signed_request'], 2);
$payloadData = base64_url_decode($payloadData[1]);
preg_match('/\"order_id\"\:([0-9]*)/', $payloadData, $matches);
$stringOrderId = $matches[1];

$payload = $request['credits'];

// retrieve all params passed in
$func = $_REQUEST['method'];
$order_info = json_decode($payload['order_info']);
if (!empty($order_info) && isset($order_info->order_id)) {
    $orderId = (int)$order_info->order_id;
}

if (empty($orderId)) {
    // Повторный запрос (оплата прошла), в нем нашего внутреннего order_id уже нет. Надо взять заказ по фейсбуковскому order_id
    $orderRes  = CSaleOrder::GetList(array(), array("COMMENTS" => $stringOrderId, "PAYED" => "N"), false, false, array("*"));
    $orderData = $orderRes->GetNext();
    $orderId = $orderData['ID'];

} else {
    // Взять из БД заказ по идентификатору
    $orderData = CSaleOrder::GetByID($orderId);
}

$basketRes = CSaleBasket::GetList(array(), array( "LID" => SITE_ID, "ORDER_ID" => $orderId), false, false, array("*")); 
$basket = array();
$description = array();

$itemIds = array();
while ($item = $basketRes->GetNext()) { 
    $basket[] = $item;
    $description[] = iconv(SITE_CHARSET, "UTF-8", $item['~NAME']) . " - " . number_format($item['QUANTITY'], 0) . "шт.";
    if (!empty($item['PRODUCT_ID'])) $itemIds[] = $item['PRODUCT_ID'];
}

if (count($itemIds)) {
    $res = CIBlockElement::GetList(array(), array("IBLOCK_ID" => $catalogIblockId, "ID" => $itemIds), false, false, array("DETAIL_PICTURE", "PREVIEW_PICTURE"));
    
    while ($item = $res->GetNext()) {
        if (!empty($item['DETAIL_PICTURE'])) {
            $pictureId = $item['DETAIL_PICTURE'];
            break;
        }
        if (!empty($item['PREVIEW_PICTURE'])) {
            $pictureId = $item['PREVIEW_PICTURE'];
            break;
        }
    }
}
if (isset($pictureId)) {
    $uploadDir = "/" . COption::GetOptionString("main", "upload_dir", "upload") . "/";
    $resFile = CFile::GetList(array(), array("ID" => $pictureId));
    $ifile = $resFile->Fetch();
    $picture = $ifile;
    $picture['SRC'] = $uploadDir . $ifile["SUBDIR"] . "/" . $ifile['FILE_NAME'];
}
$picture = CFacebookShop::getThumbImage($picture, CFacebookShop::PRODUCT_PREVIEW_IMG, SITE_TEMPLATE_PATH.'/images');

if ($orderData['PAYED'] == "Y") {
    // Заказ уже был оплачен
    $data['content']['status'] = 'settled';

} elseif ($func == 'payments_status_update') {
    // FB говорит нам, что пользователь оплатил заказ
    $status = $payload['status'];

    // write your logic here, determine the state you wanna move to
    if ($status == 'placed') {
        $next_state = 'settled';
        $data['content']['status'] = $next_state;

        // Обновить заказ
        $ret = CSaleOrder::PayOrder($orderId, "Y");
    }
    // compose returning data array_change_key_case
    $data['content']['order_id'] = $orderId;

} else if ($func == 'payments_get_items') {
    // FB запрашивает у нас описание заказа
    $item['title'] = 'Заказ №' . $orderId . " в интернет магазине " . COption::GetOptionString("main", "site_name");
    $item['price'] = ceil($orderData['PRICE'] / $fbcExchange);
    $item['description'] = implode(", ", $description);
    $item['image_url']   = "http://" . COption::GetOptionString("main", "server_name") . $picture;
    $item['product_url'] = "http://" . COption::GetOptionString("main", "server_name") . $picture;
    $item['order_id'] = $orderId;

    CSaleOrder::Update($orderId, array("COMMENTS" => $stringOrderId));

    $data['content'] = array($item);
}

// required by api_fetch_response()
$data['method'] = $func;

// send data back
echo json_encode($data);

// you can find the following functions and more details
// on http://developers.facebook.com/docs/authentication/canvas
function parse_signed_request($signed_request, $secret) {
    list($encoded_sig, $payload) = explode('.', $signed_request, 2);

    $sig = base64_url_decode($encoded_sig);
    $data = json_decode(base64_url_decode($payload), true);

    if (strtoupper($data['algorithm']) !== 'HMAC-SHA256') {
        error_log('Unknown algorithm. Expected HMAC-SHA256');
        return null;
    }

    // check signature
    $expected_sig = hash_hmac('sha256', $payload, $secret, $raw = true);
    if ($sig !== $expected_sig) {
        error_log('Bad Signed JSON signature!');
        return null;
    }

    return $data;
}

function base64_url_decode($input) {
    return base64_decode(strtr($input, '-_', '+/'));
}
require($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/epilog_after.php");?>


Регистрируем платежную систему




Мы писали оплату именно для заказов через 1С-Битрикс, поэтому остается мелочь: зарегистрировать платежную систему в списке платежных систем 1С-Битрикс при инсталляции модуля:


    // Устанавливаем платежную систему Facebook Credits
    function InstallPaysystem() {
        if (!CModule::IncludeModule("sale") || !CModule::IncludeModule("catalog")) {
            throw new Exception("Can't include sale and catalog modules");
        }

        $paysystemRes = CSalePaySystem::GetList(array(), array("NAME" => "Facebook Credits"));
        $paysystem = $paysystemRes->GetNext();

        if (!empty($paysystem)) {
            // Уже установлена, просто сохраним ID
            COption::SetOptionString($this->MODULE_ID, "paysystemId", $paysystem['ID']);
            return true;

        } else {
            $paysystemId = CSalePaySystem::Add(array(
                "LID"       => SITE_ID,
                "CURRENCY"  => CCurrency::GetBaseCurrency(),
                "NAME"      => "Facebook Credits",
                "ACTIVE"    => "N",
                "DESCRIPTION" => GetMessage("SHOPBOOK_INSTALL_FBC_DESCR")
            ));
            COption::SetOptionString($this->MODULE_ID, "paysystemId", $paysystem['ID']);
        }

        return true;
    }


Вуаля! Наш магазин готов к приему виртуальных денег от Facebook Credits, ч.т.д.
Tags:FacebookFacebook CreditsБитриксэлектронная коммерцияинтернет-магазиныsibirix
Hubs: Lumber room
Total votes 40: ↑27 and ↓13 +14
Views3K

Popular right now

Руководитель отдела web разработки
from 180,000 ₽ТопКомпьютерМосква
Программист 1С-Битрикс
to 150,000 ₽SeoVenRemote job
Интернет-маркетолог/Таргетолог
from 120,000 to 200,000 ₽Golden GooseМосква
Разработчик Битрикс
from 70,000 to 120,000 ₽iTrackRemote job
Senior Product Manager
from 250,000 to 350,000 ₽meyvnRemote job