写点什么

鲜衣怒马散尽千金,Vue3.0+Tornado6 前后端分离集成 Web3.0 之 Metamask 钱包区块链虚拟货币三方支付功能

  • 2022 年 7 月 27 日
  • 本文字数:5713 字

    阅读完需:约 19 分钟

鲜衣怒马散尽千金,Vue3.0+Tornado6前后端分离集成Web3.0之Metamask钱包区块链虚拟货币三方支付功能

原文转载自「刘悦的技术博客」https://v3u.cn/a_id_219


不得不承认,大多数人并不拥有或者曾经拥有加密货币。是的,Web3.0、加密货币、区块链,对于大多数的互联网用户来说,其实是一些过于轻佻的词汇。如果你是为了追求暴利投机而研究区块链和加密货币,那你多半会失望,因为盐在哪里都是咸的;而如果你是为了摆脱知识桎梏而学习区块链,那你几乎一定能满足,因为懵懂决不是编程界的常态。


对于支付系统来说,加密货币的主要好处之一是去中心化,这意味着它由许多团队或团体控制和管理,而不是一个单一的中心化机构,暗箱操作在这里并不存在,这为系统带来了透明度。加密货币的另一个好处是它是一个点对点系统,因为它可以发送给世界上任何人,消除了微信、支付宝等第三方支付寡头的干预,这使得它们具有成本效益。


本次,就让我们来为支付系统添上加密货币支付的这一笔,通过 Vue3.0+Tornado6 的前后端分离系统,一睹区块链加持下去中心化支付逻辑的风采。

前期准备

首先,我们当然需要一个加密货币钱包,关于系统集成 MetaMask 钱包的逻辑,请参见之前的一篇:青山不遮,毕竟东流,集成Web3.0身份钱包MetaMask以太坊一键登录(Tornado6+Vue.js3)


其后,我们需要为接下来的支付行为领取一些“测试加密货币”,领取测试币的网站被称为水龙头(faucet),测试币被称为水,所以领取测试币的过程也被叫做领水。


我们以 Rinkeby 的测试币领取为例讲解过程,其他测试网的测试币领取方式类似,如果愿意,大家可以把几个测试网的水都领一些。


第一步,打开钱包插件,选择一个钱包,点击设置:



随后,选择高级,然后启用测试网络:



接着,将网络切换到 Rinkeby 测试网络,网络中还可以看到 Ropsten、Kovan 和 Goerli 等其他三个测试网络,四个测试网络各有特点, Ropsten 采用 POW 机制,可以自己搭建节点挖测试币,但是稳定性较差,偶尔还会遇到区块回滚的情况,很多实验性测试,比如 “区块阻塞攻击” 实验会放到这个测试网来测试; Kovan 、Rinkeby 和 Goerli 是采用 POA 机制,这几个测试网络不能通过挖矿的方式获取测试币,只能通过水龙头领取,我们以 Rinkeby 为例讲解领取过程, Kovan 和 Goerli 类似,但领取条件更为严格,大家可以根据需要领取。


切换好 Rinkeby 测试网络后,访问水龙头网站:https://faucets.chain.link/rinkeby 通过钱包进行链接登录,然后将钱包地址填入领取表单,即可领取 0.1 的 eth 货币:



领取交易确认之后,查看钱包余额:



至此,前期准备工作就完成了。

钱包支付加签

前端在 Vue3.0 项目中安装区块链模块和异步请求模块:


npm install --save ethers
npm install --save axios
复制代码


随后在组件中导入区块链模块:


import {ethers} from 'ethers';
复制代码


并且对 axios 进行简单封装:


const myaxios = function(url,type,data={}){      return new      Promise((resolve) => {          //判断      if(type==="get" || type === "delete"){            axios({            method:type,          url:url,          params:data        }).then((result) => {              resolve(result.data);          });          }else{            axios({            method:type,          url:url,          data:qs.stringify(data)        }).then((result) => {              resolve(result.data);          });            }        });        }    const app = createApp(App)  app.config.globalProperties.myaxios = myaxios;
复制代码


接着,当页面首次加载时,我们希望检查用户是否已经将钱包连接到站点。为此,我们需要使用 eth_accounts 方法获取用户的帐户。如果没有返回帐户,这意味着用户没有连接:


checkIfWalletConnected:function() {          window.ethereum.request({ method: 'eth_accounts' }).then(function (accounts) {          if (accounts.length > 0) {                  console.log(accounts[0]);            } else {                          alert("应用未链接钱包");          }        });  }
复制代码


随后,在初始化方法内对当前用户进行检测:


created(){          this.checkIfWalletConnected();        }
复制代码


如果用户钱包链接没问题,那么构建支付表单:


<input type="text" v-model="amount" />    <a-button type="primary" @click="create_sign">点击支付</a-button>
复制代码


这里用户点击支付按钮后,进入加签逻辑:


create_sign:function(){              var provider = new ethers.providers.Web3Provider(web3.currentProvider);            //获取签名对象          var signer = provider.getSigner();            //时间戳          var rightnow = (Date.now()/1000).toFixed(0);            var sortanow = rightnow - (rightnow % 600);            //生成签名          signer.signMessage("Trade with "+document.domain+" at "+sortanow+" for "+this.amount,this.accountaddress,"test").then((signature) => {                  this.check_sign(signature);              });        }
复制代码


这里通过 Web3Provider 获取到签名实例,随后根据时间戳+域名+支付金额生成签名,签名生成后,立刻调用 check_sign 方法向后台发起异步请求进行验签操作:


check_sign:function(signature){          this.myaxios(this.weburl+"/sign/","post",{"signature":signature,"accountaddress":this.accountaddress,"amount":this.amount}).then(data =>{                  if(data.errcode == 0){                  this.makePaymentRequest(data.data.selleraddress,data.data.amount);                                }              });          }
复制代码


这里将签名和钱包地址发送给后端,在客户端与钱包请求交互的过程中,请求的数据很容易被拦截并篡改,所以加签环节必不可少:


后端验签并创建交易

后端需要 web3 模块的加持:


pip3 install web3
复制代码


随后创建验签方法:


from web3.auto import w3  # 反编译方法  from eth_account.messages import defunct_hash_message  import time    class CheckSign(BaseHandler):        async def post(self):        signature = self.get_argument("signature",None)        accountaddress = self.get_argument("accountaddress",None)        amount = self.get_argument("amount",None)        selleraddress = "0x95f57Bf3837325FE99a611EFacff6b1d70C7731A"          # 获取当前域名      domain = self.request.host        if ":" in domain:          domain = domain[0:domain.index(":")]        # 时间戳      now = int(time.time())        sortanow = now - now % 600        # 生成签名      message = "Trade with {} at {} for {}".format(domain,sortanow,amount)        print(message)        # 反编译      message_hash = defunct_hash_message(text=message)        # 获取签名对象      signer = w3.eth.account.recoverHash(message_hash,signature=signature)        print(accountaddress,signer)        if accountaddress == signer.lower():          res = {"errcode":0,"msg":"ok","data":{"selleraddress":selleraddress,"amount":str(w3.toWei(amount,'ether'))}}        else:          res = {"errcode":1,"msg":"failed"}        self.finish(res)
复制代码


这里后端通过同样的算法对签名进行验证,如果验签通过,后端将会返回商户的钱包地址,也就是用户转账的钱包地址,同时会将付款金额通过 w3.toWei 方法进行转换,以太币的最小单位为 wei,1 个以太币相当于 10 的 8 次方 wei。通常,大家也使用 Gwei 作为展示单位。比较常用的就是 eth,Gwei 和 wei。


但为了统一标准,支付表单汇总显示的是 eth 最大单位,所以通过 toWei 方法,将最大单位转换为最小单位,即 0.001eth=100000000000000wei,注意转换后需要以字符串的形式返回到前端。


随后后端将商户钱包地址和转换后的支付金额返回给前端。

创建交易

回到前端,验签通过后,前端获取支付钱包地址和金额,旋即通过钱包创建支付:


makePaymentRequest:function(sellerAddress,amount) {          window.ethereum.request({ method:'eth_sendTransaction', params: [{ from:this.accountaddress, to:sellerAddress,value:amount}] })      .then(response => {          console.log("交易号:"+response);          var orderid = response;      })      .catch(error => {          console.log(error);      });    }
复制代码


通过 eth_sendTransaction 方法发起交易,当用户同意支付请求时,将会返回此笔交易的 TransactionHash,也就是交易哈希号:



确认支付交易后,获取 TransactionHash:


交易号:0xe937c66e337322cf3b83788b495af2da35ff9635aaaa20f156c74c7f7fddad26 
复制代码


事实上,每一笔支付交易都会产生另一笔“燃料费”,交易燃料费将归属于挖出区跨链中本次交易区块的矿工。当矿工挖矿时,他需要决定哪些交易放入到区块中,可以随机选择交易, 也可以不包含任何交易。为了鼓励让矿工将你的交易放入区块,相应地,你必须付出一部分“小费”。

支付查询

支付确认之后,我们可以利用 Rinkeby 网络站点通过输入交易哈希号来查询这一笔交易:https://rinkeby.etherscan.io/tx/0xe937c66e337322cf3b83788b495af2da35ff9635aaaa20f156c74c7f7fddad26



当然了,让用户自己在“水龙头”上查询支付结果显然不怎么讲究,后端肯定需要记录交易哈希号并且查询交易明细,这里我们需要一个“上链”服务,让我们的后端也接上区块链网络,访问 https://infura.io/


Infura 是一种 IaaS 产品,目的是为了降低访问以太坊数据的门槛。 对于开发者来说,Infura 是一个可以让你的 dApp 快速接入以太坊的平台,不需要本地运行以太坊节点。 Infura 背后是负载均衡的 API 节点集群。 有针对以太坊 Infura 有一系列的开发套件。


注册后,创建链接项目:



随后,复制 Rinkeby 节点链接:



接着,创建订单查询脚本 checkorder.py:


from web3 import Web3  w3 = Web3(Web3.HTTPProvider("https://rinkeby.infura.io/v3/32ff12c27a9c485db9a7b61b0a7f3f61"))    print(w3.isConnected())    print(w3.eth.getTransactionReceipt("0xe937c66e337322cf3b83788b495af2da35ff9635aaaa20f156c74c7f7fddad26"))
复制代码


这里一旦“上链”成功,就可以根据交易哈希号来查询交易的明细,系统返回:


True  AttributeDict({'blockHash': HexBytes('0x4ede42c4bd15c7ce1736523ae1f84284c7bbdc17388cfbae0df2897bf19f287c'), 'blockNumber': 11098045, 'contractAddress': None, 'cumulativeGasUsed': 13409523, 'effectiveGasPrice': 1500000017, 'from': '0x3B14DdBa7FFF887ED3CCF01fCa0b84501Fd7a711', 'gasUsed': 21000, 'logs': [], 'logsBloom': HexBytes('0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'), 'status': 1, 'to': '0x95f57Bf3837325FE99a611EFacff6b1d70C7731A', 'transactionHash': HexBytes('0xe937c66e337322cf3b83788b495af2da35ff9635aaaa20f156c74c7f7fddad26'), 'transactionIndex': 16, 'type': '0x2'})
复制代码


至此,完成的加密货币支付逻辑就完成了,大体流程如下:


1. 应用会载入并自动检查 Metamask 钱包是否已连接。如果没有,将会提示用户安装钱包插件并且链接。


2. 交易加签操作。


3. 后端验签,并且返回商户钱包地址以及转换金额。


4. 钱包创建交易。


5. 用户审核并确认付款。


6. 用户确认交易,生成交易号,用户和应用都会收到付款确认。

退款

很遗憾,用户在向钱包地址发送加密货币时必须非常小心,如果有人将加密货币发送到任何错误的地址,用户将无法取消交易或提出任何投诉以获得退款,是的,deal is deal,当交易行为已经被写入区块,那么是无法被撤销的。


但这并不意味的用户就会因此和平台商户产生纠纷,如果沟通之后,达成了退款协议,加密货币也可以直接从后台进行转账操作:


from web3 import Web3  import os    w3 = Web3(Web3.HTTPProvider("https://rinkeby.infura.io/v3/32ff12c27a9c485db9a7b61b0a7f3f61"))    print(w3.isConnected())    address1 = w3.toChecksumAddress('selleraddress')  address2 = w3.toChecksumAddress ('accountaddress')  private_key = os.getenv('PRIVATE_KEY')  # in this case, the nonce is the amount of transactions on accounti  nonce = w3.eth.getTransactionCount(address1)  # setup the transaction  tx ={  'nonce':nonce,  "to":address2,  'value':w3.toWei("0.0001","ether"),  "gas": 21000,  "gasPrice":w3.toWei(40,'gwei'),  }  signed_tx = w3.eth.account.signTransaction(tx, private_key)    tx_hash = w3.sendRawTransaction(signed_tx.rawTransaction)
复制代码


这里商户只需要将钱包私钥导入环境变量,随后创建交易并通过私钥加签,最后确认交易,并且获取到交易哈希号。

结语

毫无疑问,加密货币会损害一部分传统行业既得利益者的利益,但也不能不承认,加密货币更是二十一世纪的一记响雷,就像洪荒时代孑余的一头恐龙、大戈壁中枝叶扶疏的一株胡杨、兵马俑阵中一个脉搏跳动体温犹存的肉身、死寂的山谷中凭空乍响的一声洪钟。所谓衣不如新,人不如故,不入春园,怎知春色几许?所谓技术的本质就是最大额度地收获创新,你同意吗?


原文转载自「刘悦的技术博客」 https://v3u.cn/a_id_219

发布于: 刚刚阅读数: 3
用户头像

专注技术,凝聚意志,解决问题 v3u.cn 2020.12.21 加入

还未添加个人简介

评论

发布
暂无评论
鲜衣怒马散尽千金,Vue3.0+Tornado6前后端分离集成Web3.0之Metamask钱包区块链虚拟货币三方支付功能_Python_刘悦的技术博客_InfoQ写作社区