鮮衣怒馬散盡千金,Vue3.0+Tornado6前後端分離整合Web3.0之Metamask錢包區塊鏈虛擬貨幣三方支付功能

劉悅的技術部落格發表於2022-07-28

原文轉載自「劉悅的技術部落格」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

相關文章