📜  我如何在 laravel 网站上实现区块链支付 - PHP (1)

📅  最后修改于: 2023-12-03 15:09:57.399000             🧑  作者: Mango

我如何在 Laravel 网站上实现区块链支付 - PHP

区块链作为一种去中心化、不可篡改的技术,越来越被人们广泛应用到支付领域。本文将介绍如何在 Laravel 网站上实现区块链支付。

前提条件

在开始实现之前,需要确保已经安装并配置好以下工具和环境:

  • Laravel >= 5.5
  • PHP >= 7.1
  • Composer
  • MySQL
安装必要的依赖

首先,我们需要安装一些必要的依赖,包括:

  • laravelcollective/html - 用于生成表单
  • bcmath - 用于高精度计算
  • bitwasp/bitcoin - 用于生成比特币地址、私钥和交易

安装方法如下:

$ composer require "laravelcollective/html" "bitwasp/bitcoin"
$ sudo apt-get install php-bcmath # 或者使用你的操作系统对应的安装工具,如 yum 或 brew
创建数据库表

我们需要创建两张数据表,一张用于存储用户余额,另一张用于记录用户的充值和提现记录。

CREATE TABLE `balances` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `user_id` int(10) unsigned NOT NULL,
  `balance` decimal(18,2) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `balances_user_id_unique` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE `transactions` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `user_id` int(10) unsigned NOT NULL,
  `type` enum('deposit','withdrawal') NOT NULL,
  `amount` decimal(18,2) NOT NULL,
  `txid` varchar(64) DEFAULT NULL,
  `status` enum('pending','success','failed') NOT NULL DEFAULT 'pending',
  `created_at` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
生成比特币地址

在用户注册时,我们需要为其生成一个比特币地址,并将其保存在数据库中。

use BitWasp\Bitcoin\Address\AddressCreator;
use BitWasp\Bitcoin\Key\Factory\PrivateKeyFactory;

class UserController extends Controller
{
    public function register(Request $request)
    {
        $privateKeyFactory = new PrivateKeyFactory();
        $addressCreator = new AddressCreator();

        $privateKey = $privateKeyFactory->generateCompressed();
        $address = $addressCreator->fromKey($privateKey)->getAddress();

        // 将 $address 和 $privateKey 存入数据库中
    }
}
提现

当用户提交提现申请时,我们需要将其余额减去提现金额,生成比特币交易并广播到区块链网络中。

use BitWasp\Bitcoin\Address\AddressCreator;
use BitWasp\Bitcoin\Key\Factory\PrivateKeyFactory;
use BitWasp\Bitcoin\Network\NetworkFactory;
use BitWasp\Bitcoin\Script\ScriptFactory;
use BitWasp\Bitcoin\Transaction\Factory\TxBuilder;
use BitWasp\Bitcoin\Transaction\TransactionFactory;
use BitWasp\Bitcoin\Transaction\TransactionOutput;
use BitWasp\Bitcoin\Transaction\TransactionOutputCollection;
use BitWasp\Bitcoin\Transaction\TransactionOutputInterface;

class UserController extends Controller
{
    public function withdraw(Request $request)
    {
        $privateKeyFactory = new PrivateKeyFactory();
        $addressCreator = new AddressCreator();
        $network = NetworkFactory::bitcoin();

        $privateKey = $privateKeyFactory->fromHex($user->private_key_hex);
        $address = $addressCreator->fromString($user->address, $network);
        $balance = $user->balance;

        $amount = $request->input('amount');

        if ($balance < $amount) {
            // 余额不足
            return response()->json([
                'message' => 'Insufficient balance',
                'data' => [],
            ]);
        }

        $toAddress = $request->input('address');
        $fee = 10000; // 手续费

        $utxos = $this->getUnspentTransactions($address->getAddress()->getAddress());

        $txBuilder = new TxBuilder();
        $txBuilder->spendOutput($utxos[0]);

        $totalAmount = $amount + $fee;
        $changeAmount = $balance - $totalAmount;

        $toOutput = new TransactionOutput($amount, $address->getScriptPubKey());
        $changeOutput = new TransactionOutput($changeAmount, $address->getScriptPubKey());

        $txBuilder->outputs(new TransactionOutputCollection([$toOutput, $changeOutput]));

        $transaction = $txBuilder->get();

        $transaction = $this->signTransaction($transaction, $privateKey, $utxos);

        // 广播交易
        $client = new Client('https://blockchain.info/');
        $result = $client->sendrawtransaction($transaction->getHex());

        if ($result === true) {
            // 更新数据库中的余额和提现记录
        }
    }

    private function getUnspentTransactions(string $address)
    {
        $client = new Client('https://blockchain.info/');
        $response = $client->fetch_unspent($address);

        $utxos = [];
        foreach ($response->getOutputs() as $output) {
            $utxos[] = new TransactionOutputInterface($output->getValue(), ScriptFactory::fromHex($output->getScript()), $output->getTxId(), $output->getVout());
        }

        return $utxos;
    }

    private function signTransaction(TransactionInterface $transaction, PrivateKeyInterface $privateKey, array $utxos)
    {
        $signer = new Signer($transaction, Bitcoin::getEcAdapter());
        foreach ($utxos as $i => $utxo) {
            $input = $transaction->getInput($i);
            $input->setScript(ScriptFactory::create()->push($privateKey->getPublicKey()->getBuffer())->op('OP_CHECKSIG'));
            $signer->sign($i, $privateKey, $utxo->getScript());
        }
        return TransactionFactory::fromHex($signer->get()->getHex());
    }
}
充值

当用户通过比特币向系统账户充值时,我们需要监听比特币网路上的交易,如果发现收到了该用户的充值交易,则将其余额增加相应的金额,并记录充值记录。

use BitWasp\Bitcoin\Address\AddressCreator;
use BitWasp\Bitcoin\Address\PayToPubKeyHashAddress;
use BitWasp\Bitcoin\Network\NetworkFactory;
use BitWasp\Bitcoin\Script\ScriptFactory;
use BitWasp\Bitcoin\Transaction\Factory\TxBuilder;
use BitWasp\Bitcoin\Transaction\TransactionFactory;
use BitWasp\Bitcoin\Transaction\TransactionOutputCollection;
use BitWasp\Bitcoin\Transaction\TransactionOutputInterface;
use Symfony\Component\Process\Process;

class TransactionListener
{
    public function listen()
    {
        $client = new Client('https://blockchain.info/');
        $latestBlockHeight = $client->getblockcount();

        while (true) {
            $process = new Process("bitcoin-cli -testnet getblockhash $latestBlockHeight");
            $process->run();
            $blockHash = trim($process->getOutput());

            $process = new Process("bitcoin-cli -testnet getblock \"$blockHash\"");
            $process->run();
            $blockData = json_decode($process->getOutput());

            if (isset($blockData->tx)) {
                foreach ($blockData->tx as $txid) {
                    $transaction = $client->gettransaction($txid);

                    if (count($transaction->getInputs()) !== 1 || count($transaction->getOutputs()) !== 2) {
                        continue;
                    }

                    $fromAddress = $transaction->getInputs()[0]->getScript()->getScriptParser()->getHumanReadable();
                    if (!startsWith($fromAddress, 'n')) {
                        continue;
                    }

                    $toAddress = $transaction->getOutputs()[1]->getScript()->getScriptParser()->getHumanReadable();
                    if (!startsWith($toAddress, 'm')) {
                        continue;
                    }

                    $amount = $transaction->getOutputs()[1]->getValue();

                    $user = User::where('address', $fromAddress)->first();
                    if (!isset($user)) {
                        continue;
                    }

                    $user->balance += $amount;
                    $user->save();

                    Transaction::create([
                        'user_id' => $user->id,
                        'type' => 'deposit',
                        'amount' => $amount,
                        'txid' => $txid,
                        'status' => 'success',
                    ]);
                }
            }

            $latestBlockHeight++;
            sleep(10);
        }
    }
}
总结

本文基于 Laravel 框架实现了一个简单的区块链支付系统,并介绍了比特币地址生成、比特币交易签名、交易监听等相关知识点。在实际应用时,还需要注意安全性和效率等问题。