
Habré( one 、 two )にはすでに優れた出版物があり、スマートコントラクトがどのように作成され、ブロックチェーンに注がれるかを詳細に検討したため、すぐにコードに戻ります。
すべてのアクションは、Rinkebyテストネットワークで実行されます。
スマートコントラクト
支払いシステムの中核はスマートな契約です。それから始めます。
契約機能:
- ユーザーからの支払いを受け取る
- 管理者の撤回
- 管理者の払い戻し
- ユーザー権限の制御
- 管理者の変更
- 支払リストの保管
- 請求書の返済をブロックする
- 払い戻しのブロック
- 契約アドレスに自動払い戻し
- 支払い通知の作成、返金、管理者の変更
コメント付きのスマート契約コード
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をインストールする必要があります。 メタマスクは、ユーザーをRPCサーバーに自動的に接続します。
支払フォームコード
<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>

また、管理者がスマートコントラクトと対話するためのページを作成します。
管理ページのコード
<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>

通知
自動注文処理の場合、ほとんどの支払いシステムには、支払いのステータスを追跡したり、受け取った支払いを通知するためのAPIが用意されています。 ブロックチェーンは支払いの通知を送信しませんが、ブロックを読み取り、契約によって作成されたイベントのリストを取得できます。
ブロックにアクセスするには、RPC-HTTPサーバーをオンにしてGethを使用します。
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を使用しました 。
イベントのリストを取得するコード
<?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>"; } } ?>

私のスクリプトでは、ゼロから最後のブロックまでeth_getFilterLogsメソッドを使用しました。これは当然、最速で最も効果的なオプションではありません。 ブロック数でeth_getFilterLogsを制限するか、新しいブロックからのみイベントを返すeth_getFilterChangesメソッドを使用することをお勧めします。
JSON-RPCメソッドの詳細な説明は、 ドキュメントに記載されています 。
おわりに
したがって、独立した支払いシステムを取得し、それを変更してサイトに接続するのは非常に簡単です。 この例で、多くの人にとって、ブロックチェーンの使用方法と場所がより明確になると思います。
rinkeby.etherscan.ioでのスマート契約
GitHubリポジトリ