Платёжная система в 50 строк кода, реально?

В последнее время технологические решения на блокчейне всё больше проникают в нашу повседневную жизнь. Технология новая, поэтому не все понимают, как и где её применять. Я попробовал создать платежную систему на базе смарт-контракта Ethereum и результат меня удивил. Смарт-контракт выполняющий функции полноценной платёжной системы получился всего в 50 строк кода. Всех заинтересовавшихся как он работает прошу под кат.

image

На Хабре уже были хорошие публикации (один, два), в которых подробно рассматривалось, как создаётся и заливается в блокчейн сматр-контракт, поэтому сразу перейдём к коду.

Все действия проводятся в тестовой сети Rinkeby.

Смарт-контракт


Ядро нашей платёжной системы смарт-контракт, с него мы и начнём.

Функции контракта:

  1. приём платежей от пользователей
  2. вывод денег администратором
  3. возврат платежа администратором
  4. контроль пользовательских разрешений
  5. смена администратора
  6. хранение списка платежей
  7. блокирование повторной оплаты счета
  8. блокирование повторного возврата счета
  9. автоматический возврат средств отправленных на адрес контракта
  10. создание уведомлений об оплате, возврате денег, смене администратора


Код смарт-контракта с комментариями
pragma solidity ^0.4.18;
//version:4

/**
 * @title Ownable
 * @dev The Ownable contract has an owner address, and provides basic authorization control
 * functions, this simplifies the implementation of "user permissions".
 */
contract Ownable {
  address public owner;


  event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);


  /**
   * @dev The Ownable constructor sets the original `owner` of the contract to the sender
   * account.
   */
  function Ownable() public {
    owner = msg.sender;
  }


  /**
   * @dev Throws if called by any account other than the owner.
   */
  modifier onlyOwner() {
    require(msg.sender == owner);
    _;
  }


  /**
   * @dev Allows the current owner to transfer control of the contract to a newOwner.
   * @param newOwner The address to transfer ownership to.
   */
  function transferOwnership(address newOwner) onlyOwner public {
    require(newOwner != address(0));
    OwnershipTransferred(owner, newOwner);
    owner = newOwner;
  }

}

contract PaymentSystem is Ownable {

    struct order {
        address payer;
        uint256 value;
        bool revert;
    }

    //база ордеров
    mapping(uint256 => order) public orders;

    //возврат денег при попытке отправить деньги на контракт
    function () public payable {
        revert();
    }

    event PaymentOrder(uint256 indexed id, address payer, uint256 value);

    //оплата ордера
    function paymentOrder(uint256 _id) public payable returns(bool) {
        require(orders[_id].value==0 && msg.value>0);

        orders[_id].payer=msg.sender;
        orders[_id].value=msg.value;
        orders[_id].revert=false;

        //создать евент
        PaymentOrder(_id, msg.sender, msg.value);

        return true;
    }

    event RevertOrder(uint256 indexed id, address payer, uint256 value);

    //возврат платежа администратором
    function revertOrder(uint256 _id) public onlyOwner returns(bool)  {
        require(orders[_id].value>0 && orders[_id].revert==false);

        orders[_id].revert=true;
        orders[_id].payer.transfer(orders[_id].value);

        RevertOrder(_id, orders[_id].payer, orders[_id].value);

        return true;
    }

    //вывод денег администратором
    function outputMoney(address _from, uint256 _value) public onlyOwner returns(bool) {
        require(this.balance>=_value);

        _from.transfer(_value);

        return true;
    }

}


Исходный код верифицирован на rinkeby.etherscan.io и как видно на вкладке «Contract Source» занимает всего 50 строк.

Пользовательский интерфейс


Конечно, можно попросить пользователей совершать платежи через Myetherwallet или Mist но, это неудобно поэтому лучше сделать на сайте форму оплаты. Для работы формы оплаты пользователь должен установить Metamask. Metamask автоматически подключает пользователя к своим RPC серверам.

Код платёжной формы
  

    

Pay

pay


image
Также создадим страницу для взаимодействия администратора со смарт-контрактом.

Код страницы администратора
  

    

Info

Output money

output

Revert order

revert


image

Уведомления


Для автоматической обработки ордеров большинство платежных систем предоставляют API для отслеживания статуса платежа или уведомления о поступивших платежах. Блокчейн не отправляет уведомления о совершении платежей, но мы можем прочитать блоки и получить список событий созданных контрактом.

Для доступа к блокам будем использовать Geth с включенным RPC-HTTP сервером

geth --rinkeby --datadir "D:/eth/blockchain_rinkeby" --rpc --rpcaddr "0.0.0.0" --rpcapi "admin,debug,miner,shh,txpool,personal,eth,net,web3" console


Подключение к Geth я реализовал на php, но подойдет любая платформа, из которой можно выполнить POST запрос.

Для ускорения разработки я использовал ethereum-php.

Код для получения списка событий
eth_newFilter($filter);

//получаем список events
$logs=$ethereum->eth_getFilterLogs($result_filter);

foreach ($logs as $key => $value) {

  /*
  сравниваем первый элемент масива topics, в нем хранится хэш имени события и списка типов переменных
  строка: PaymentOrder(uint256,address,uint256) тип хэштрования: Keccak-256 (для получения хэша я воспользовался онлайн сервисом)
  в остальнх элементах topics хранятся проиндексированные параметры события
  */

  if (strcasecmp($value->{'topics'}[0], "0x"."c84883193d3a69d991d82f61928c06e179b647e413da4c20be80d8c0314c2e1b") == 0) {
    echo "Payment order id:".hexdec($value->{'topics'}[1]);

    /*
    в элементе data хранятся остальные параметры события
    склеенные по 32 байта
    */

    $data=str_split(substr($value->{'data'}, 2),64);

    echo " volume:".hexdec($data[1])*$rate." ETH";

    echo "
"; } } ?>


image

В своём скрипте я использовал метод eth_getFilterLogs с нулевого до последнего блока, естественно это не самый быстрый и эффективный вариант. Лучше ограничить eth_getFilterLogs по количеству блоков или использовать метод eth_getFilterChanges, который вернёт события только из новых блоков.

Полное описание методов JSON-RPC можно посмотреть в документации.

Заключение


Вот так довольно просто можно получить независимую платёжную систему и доработав подключить её к сайту. Думаю, на этом примере для многих станет понятнее, как и где можно применить блокчейн.

Смарт-контракт на rinkeby.etherscan.io
Репозиторий на GitHub

© Habrahabr.ru