區塊相隔雖一線,俱在支付同冶熔,Vue3.0+Tornado6前後端分離整合Web3.0之Metamask區塊鏈虛擬三方支付功能

劉悅的技術部落格發表於2022-08-31

最近幾年區塊鏈技術的使用外延持續擴充套件,去中心化的節點認證機制可以大幅度改進傳統的支付結算模式的經營效率,降低交易者的成本並提高收益。但不能否認的是,區塊鏈技術也存在著極大的風險,所謂身懷利器,殺心自起,業內應當謹慎使用與推廣區塊鏈技術。

本次,就讓我們來為支付系統添上區塊鏈支付功能,透過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)

這裡商戶只需要將錢包私鑰匯入環境變數,隨後建立交易並透過私鑰加簽,最後確認交易,並且獲取到交易雜湊號。

結語

區塊鏈技術是二十一世紀的一記響雷,就像洪荒時代孑餘的一頭恐龍、大戈壁中枝葉扶疏的一株胡楊、兵馬俑陣中一個脈搏跳動體溫猶存的肉身、死寂的山谷中憑空乍響的一聲洪鐘。所謂衣不如新,人不如故,不入春園,怎知春色幾許?所謂技術的本質就是最大額度地收穫創新,你同意嗎?

相關文章