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

    Привет, хабр. Не так давно мы написали модуль, который подключает магазины на 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, ч.т.д.

    Similar posts

    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 7

      0
      что насчёт продажи реальных товаров через фейсбук? не проще ли внедрить существующие платёжные системы/аггрегаторы платежей в приложение? профит в виде отсутствия 30% комиссии очевиден.
        –1
        > Facebook Credits — внутренняя валюта социальной сети Facebook,
        > впервые появившаяся в 2009 году, и ставшая единственной разрешенной,
        > по правилам Facebook, с 1 июня этого года

        Я ответил на вопрос? :)
          0
          Во-первых с 1 июля, а во-вторых не ответили. Читайте термз оф юз кредитов повнимательнее. Особенно обращая внимание на пункты 1.1 и 2.6
            0
            FB Credits обязательны только для игр. Обычные магазины по прежнему могут принимать заказы через любой другой удобный способ оплаты.
            0
            Для нашего модуля при оплате реальных товаров мы перенаправляем пользователя на сайт, где уже привязаны другие системы оплаты и доставки. Вроде как все легально.
            0
            Как человек, реализовавший десятки интеграций с различными платежными системами, могу сказать, что статья очень хорошая: все по делу, кратко и с нагрядными иллюстрациями. Молодец!
              0
              Сразу же возникает вопрос по поводу валютного контроля. Фейсбук бумажного договора не дает, а без него нормально принимать валюту невозможно

              Only users with full accounts can post comments. Log in, please.