Blockchain
In a recent project, I constructed a Bitcoin payment solution for an E-Commerce site using Laravel 5.6.
Upon clicking a Pay with Bitcoin button in the E-Commerce site, the user will be transferred to our site for payment.
The user will be asked to pay to a bitcoin address from his/her desktop or mobile wallet.
When viewed on a mobile device, the user will be shown a Pay button, with a bitcoin://
URL. Clicking the button would open native mobile dialog suggesting to open one of the wallets on the device that registered to the Bitcoin URL scheme. We discovered that desktop wallets, without special setup at the operating system level, do not open when clicking a bitcoin URL.
At a later time, we would like to pay the merchant from the E-Commerce site the Bitcoin amount pod by the user.
These capabilities:
This is a Laravel project, so we need a Laravel package with the power of bitcoin-cli. We used the excellent Bitcoin Bitcoin JSON-RPC Service Provider for Laravel.
This package provides camel cased functions corresponding to each of Bitcoin Core functions. One can access these functions with the bitcoind
helper, as:
bitcoind()
We would like to hold the payment in escrow until the goods are delivered to the user. Hence, we request the user to pay to a multi-signature address (multisig):
$multisig_address = bitcoind()->createMultiSig(2, $public_keys_array);
From which we extract the P2SH and redeem script:
$p2sh_address = $multisig->result()['address'];
$redeem_script = $multisig->result()['redeemScript'];
A Laravel job would run every minute to check for payments. It used ListUnspent to watch for payment, by adding the target address as a Watch-Only Address using ImportMulti. We would match an unspent transaction on the address based on amount and timestamp. We ask for 0 and more confirmations.
Upon payment, we would update the web view using socket.io using the Laravel Echo Server. We update the web view immediately when 0 or more confirmations are detected.
As is common, we considered a payment to be finalized with 6 confirmations. We save in the database the txid of the payment transaction.
$import_result = bitcoind()->importMulti(
[
[
'redeemscript' => $redeem_script,
'scriptPubKey' => [ 'address' => $p2sh_address ],
'timestamp' => $time,
'watchonly' => true,
'internal' => false,
]
],
[
'rescan' => false,
]
);
$unspent_transactions = bitcoind()->listUnspent(0, 999999, array($address));
We split the payment between us the merchant. So we define our $targets as an array mapping addresses to amounts,
array(
$merchant_address => $amount - $commission,
$payment_provider => $commission
)
$raw_payment_transaction = $this->get_raw_transaction($payment_txid);
$vout = $raw_payment_transaction->result()['vout'];
// check if we need to search over vout
$our_input = array_filter($vout, function($v, $k) use($input_amount) {
return $v['value'] == $input_amount;
}, ARRAY_FILTER_USE_BOTH);
$used_output = head($our_input);
$vout_index = $used_output['n'];
$hex = $used_output['scriptPubKey']['hex'];
// create raw transaction
$raw_transaction = $this->create_raw_transaction($payment_txid, $vout_index, $targets);
$output_details = $this->compose_output_from_transaction($payment_txid, $vout_index, $hex, $redeem_script);
// sign raw transation
$composed_transaction_hex = $this->sign_raw_transaction($raw_transaction, $output_details, $private_keys);
Let us break it down.
First, we retrieve the raw payment transaction using the txid using GetRawTransaction.
$txInfo = bitcoind()->getRawTransaction($txid, 1);
From the raw transaction, we extract the vout to be used. For simplicity the code above just checks for amounts, but it should check for time stamps also. In case there are multiple payments.
$rawTxInfo = bitcoind()->createRawTransaction(
array(array('txid' => $input_txid, 'vout' => $input_vout)), $targets
);
$composed_output = array(
array(
'txid' => $txid,
'vout' => $vout,
'scriptPubKey' => $script_pub_key, 'redeemScript' => $redeem_script
)
);
$signed_transaction = bitcoind()->signRawTransaction(
$raw_transaction, $output_details, $private_keys
);
$hex = $signed_transaction->result()['hex'];
If we have all the private keys of the multisig, we could send the transaction,
$txid = bitcoind()->sendRawTransaction($raw_transaction);
But we really do not most of the time, so we give the raw transaction to the merchant. The merchant signs it his/her private key and broadcasts it to the network. For example using, coinb.in.
Yoram Kornatzky