📜 ⬆️ ⬇️

Payment system in 50 lines of code, really?

Recently, technological solutions on the blockchain are increasingly penetrating our daily lives. The technology is new, so not everyone understands how and where to apply it. I tried to create a payment system based on the Ethereum smart contract and the result surprised me. The smart contract that performs the functions of a full-fledged payment system turned out to be only 50 lines of code. All interested in how it works please under the cat.

image

On Habré there were already good publications ( one , two ) in which it was discussed in detail how the smmatr contract is created and poured into the blockchain, so we will immediately move on to the code.

All actions are carried out in the test network Rinkeby.
')

Smart contract


The core of our payment system is a smart contract, with which we will begin.

Contract Functions:

  1. accept payments from users
  2. admin money withdrawal
  3. admin payment refund
  4. control of user permissions
  5. administrator change
  6. storage of the list of payments
  7. block re-invoice payment
  8. blocking the re-invoice
  9. automatic refund sent to the address of the contract
  10. creation of notifications about payment, refund, change of administrator

Smart contract code with comments
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; } } 


The source code is verified on rinkeby.etherscan.io and as seen on the tab “Contract Source” takes only 50 lines.

User interface


Of course, you can ask users to make payments through Myetherwallet or Mist, but this is inconvenient, therefore it is better to make a payment method on the site. For the payment form to work, the user must install Metamask . Metamask automatically connects users to their RPC servers.

Payment Form Code
  <body> <div class="main_section"> <h3 class="section_title">Pay</h3> <div class="edit"><input type="text" class="myedit" id="edit_id" placeholder="id"></div> <div class="edit"><input type="text" class="myedit" id="edit_value" placeholder="value (ETH)"></div> <div id="button_pay" class="mybutton">pay</div> <div id="message_pay" class="message"></div> <script> // //----------------------------------------------------------------- var button_pay = document.querySelector('#button_pay'); button_pay.addEventListener('click', function() { var pay_id = document.getElementById("edit_id").value; var pay_value = web3.toWei(parseFloat(document.getElementById("edit_value").value), 'ether') var user_adress = web3.eth.accounts[0]; if (!web3.isAddress(user_adress)) { write_wessage("#message_pay", "error: MetaMask not open"); return; } if (pay_id.length==0) { write_wessage("#message_pay", "error: not id"); return; } if (pay_value==0) { write_wessage("#message_pay", "error: volume 0"); return; } contract.paymentOrder( pay_id, {from: user_adress, value: pay_value, gasPrice: 41000000000}, function (err, transaction_hash) { if (err) { write_wessage("#message_pay", "error"); console.log(err); } else { write_wessage("#message_pay", "transaction hash: "+transaction_hash); } }); }); </script> </div> </body> <script> function write_wessage(element, message) { document.querySelector(element).innerHTML = message; } if (typeof web3 === 'undefined') { document.getElementsByTagName("body")[0].innerHTML = 'You need to install MetaMask'; } else { //  var contract_adress='0x3b4a22858093B9942514eE42eD1B4BF177632ba3'; var abi=[ { "constant": true, "inputs": [], "name": "owner", "outputs": [ { "name": "", "type": "address" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [ { "name": "", "type": "uint256" } ], "name": "orders", "outputs": [ { "name": "payer", "type": "address" }, { "name": "value", "type": "uint256" }, { "name": "revert", "type": "bool" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "anonymous": false, "inputs": [ { "indexed": true, "name": "previousOwner", "type": "address" }, { "indexed": true, "name": "newOwner", "type": "address" } ], "name": "OwnershipTransferred", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "name": "id", "type": "uint256" }, { "indexed": false, "name": "payer", "type": "address" }, { "indexed": false, "name": "value", "type": "uint256" } ], "name": "PaymentOrder", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "name": "id", "type": "uint256" }, { "indexed": false, "name": "payer", "type": "address" }, { "indexed": false, "name": "value", "type": "uint256" } ], "name": "RevertOrder", "type": "event" }, { "constant": false, "inputs": [ { "name": "_to", "type": "address" }, { "name": "_value", "type": "uint256" } ], "name": "outputMoney", "outputs": [ { "name": "", "type": "bool" } ], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": false, "inputs": [ { "name": "_id", "type": "uint256" } ], "name": "paymentOrder", "outputs": [ { "name": "", "type": "bool" } ], "payable": true, "stateMutability": "payable", "type": "function" }, { "constant": false, "inputs": [ { "name": "_id", "type": "uint256" } ], "name": "revertOrder", "outputs": [ { "name": "", "type": "bool" } ], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": false, "inputs": [ { "name": "newOwner", "type": "address" } ], "name": "transferOwnership", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "payable": true, "stateMutability": "payable", "type": "fallback" } ]; var contract = web3.eth.contract(abi).at(contract_adress); } </script> 


image
We will also create a page for the administrator to interact with the smart contract.

Admin Page Code
  <body> <div class="main_section"> <h3 class="section_title">Info</h3> <div id="message_balance" class="message"></div> <div id="message_owner" class="message"></div> </div> <div class="main_section"> <h3 class="section_title">Output money</h3> <div class="edit"><input type="text" class="myedit" id="edit_adress" placeholder="adress"></div> <div class="edit"><input type="text" class="myedit" id="edit_value" placeholder="value (ETH)"></div> <div id="button_output" class="mybutton">output</div> <div id="message_output" class="message"></div> <script> //  //----------------------------------------------------------------- var button_output = document.querySelector('#button_output'); button_output.addEventListener('click', function() { var pay_value = web3.toWei(parseFloat(document.getElementById("edit_value").value), 'ether') var to_adress = document.getElementById("edit_adress").value; var user_adress = web3.eth.accounts[0]; if (!web3.isAddress(user_adress)) { write_wessage("#message_output", "error: MetaMask not open"); return; } if (!web3.isAddress(to_adress)) { write_wessage("#message_output", "error: adress not valid"); return; } if (pay_value==0) { write_wessage("#message_output", "error: volume 0"); return; } contract.outputMoney( to_adress, pay_value, {from: user_adress, gasPrice: 41000000000}, function (err, transaction_hash) { if (err) { write_wessage("#message_output", "error"); console.log(err); } else { write_wessage("#message_output", "transaction hash: "+transaction_hash); } }); }); </script> </div> <div class="main_section"> <h3 class="section_title">Revert order</h3> <div class="edit"><input type="text" class="myedit" id="edit_id" placeholder="id"></div> <div id="button_revert" class="mybutton">revert</div> <div id="message_revert" class="message"></div> <script> //  //----------------------------------------------------------------- var button_revert = document.querySelector('#button_revert'); button_revert.addEventListener('click', function() { var pay_id = document.getElementById("edit_id").value; var user_adress = web3.eth.accounts[0]; if (!web3.isAddress(user_adress)) { write_wessage("#message_revert", "error: MetaMask not open"); return; } if (pay_id.length==0) { write_wessage("#message_revert", "error: not id"); return; } contract.revertOrder( pay_id, {from: user_adress, gasPrice: 41000000000}, function (err, transaction_hash) { if (err) { write_wessage("#message_revert", "error"); console.log(err); } else { write_wessage("#message_revert", "transaction hash: "+transaction_hash); } }); }); </script> </div> </body> <script> function write_wessage(element, message) { document.querySelector(element).innerHTML = message; } if (typeof web3 === 'undefined') { document.getElementsByTagName("body")[0].innerHTML = 'You need to install MetaMask'; } else { //  var contract_adress='0x3b4a22858093B9942514eE42eD1B4BF177632ba3'; var abi=[ { "constant": true, "inputs": [], "name": "owner", "outputs": [ { "name": "", "type": "address" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [ { "name": "", "type": "uint256" } ], "name": "orders", "outputs": [ { "name": "payer", "type": "address" }, { "name": "value", "type": "uint256" }, { "name": "revert", "type": "bool" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "anonymous": false, "inputs": [ { "indexed": true, "name": "previousOwner", "type": "address" }, { "indexed": true, "name": "newOwner", "type": "address" } ], "name": "OwnershipTransferred", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "name": "id", "type": "uint256" }, { "indexed": false, "name": "payer", "type": "address" }, { "indexed": false, "name": "value", "type": "uint256" } ], "name": "PaymentOrder", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "name": "id", "type": "uint256" }, { "indexed": false, "name": "payer", "type": "address" }, { "indexed": false, "name": "value", "type": "uint256" } ], "name": "RevertOrder", "type": "event" }, { "constant": false, "inputs": [ { "name": "_to", "type": "address" }, { "name": "_value", "type": "uint256" } ], "name": "outputMoney", "outputs": [ { "name": "", "type": "bool" } ], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": false, "inputs": [ { "name": "_id", "type": "uint256" } ], "name": "paymentOrder", "outputs": [ { "name": "", "type": "bool" } ], "payable": true, "stateMutability": "payable", "type": "function" }, { "constant": false, "inputs": [ { "name": "_id", "type": "uint256" } ], "name": "revertOrder", "outputs": [ { "name": "", "type": "bool" } ], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": false, "inputs": [ { "name": "newOwner", "type": "address" } ], "name": "transferOwnership", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "payable": true, "stateMutability": "payable", "type": "fallback" } ]; var contract = web3.eth.contract(abi).at(contract_adress); //  web3.eth.getBalance(contract_adress.toString(), function (err, result) { write_wessage("#message_balance", "contract balance: "+web3.fromWei(result, 'ether')+" ETH"); }); // owner contract.owner(function(err, data) { if (err) { write_wessage("#message_owner", "error"); } else { write_wessage("#message_owner", "owner: "+data); } }); } </script> 


image

Notifications


For automatic order processing, most payment systems provide an API for tracking payment status or notification of incoming payments. The blockchain does not send payment notifications, but we can read the blocks and get a list of events created by the contract.

To access the blocks, we will use Geth with the RPC-HTTP server enabled.

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

I have connected to Geth in php, but any platform from which you can execute a POST request will do.

To speed up development, I used ethereum-php .

Code to get a list of events
 <?php require 'ethereum-php-master/ethereum.php'; $rate=0.000000000000000001; //   $ethereum = new Ethereum('192.168.56.1', 8545); //   $filter = new Ethereum_Filter('0x0', 'latest', '0x3b4a22858093B9942514eE42eD1B4BF177632ba3', []); //    $result_filter=$ethereum->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 "<br>"; } } ?> 


image

In my script, I used the eth_getFilterLogs method from zero to the last block; naturally, this is not the fastest and most efficient option. It is better to limit eth_getFilterLogs by the number of blocks or use the eth_getFilterChanges method, which returns events only from new blocks.

A full description of JSON-RPC methods can be found in the documentation .

Conclusion


So it's pretty easy to get an independent payment system and modify it to connect to the site. I think this example will make it easier for many to understand how and where to use the blockchain.

Smart contract for rinkeby.etherscan.io
GitHub Repository

Source: https://habr.com/ru/post/348876/


All Articles