如何將自定義加密支付方法集成到在線商店
每天 分享 最新 軟件 開發(fā) ,Devops,敏捷 ,測(cè)試 以及 項(xiàng)目 管理 最新 ,最熱門 的 文章 ,每天 花 3分鐘 學(xué)習(xí) 何樂(lè)而不為 ,希望 大家 點(diǎn)贊 ,加 關(guān)注 ,你的 支持 是我 最大 的 動(dòng)力 。
電子商務(wù)店面在向客戶提供加密支付方式方面進(jìn)展緩慢。加密支付插件或支付網(wǎng)關(guān)集成通常不可用,或者它們依賴第三方托管人收集、交換和分發(fā)資金??紤]到加密貨幣持有率和實(shí)驗(yàn)比率的不斷增長(zhǎng),“用加密貨幣支付”按鈕可以極大地推動(dòng)銷售。
本文演示如何在不依賴第三方服務(wù)的情況下將自定義、安全的加密支付方法集成到任何在線商店中。編碼和維護(hù)智能合同需要相當(dāng)繁重的工作,我們正在把這項(xiàng)工作移交給塊環(huán)鏈建設(shè)者常用的工具鏈Truffle套件。為了在開發(fā)期間和應(yīng)用程序后端提供對(duì)區(qū)塊鏈節(jié)點(diǎn)的訪問(wèn),我們依賴于 Infura 節(jié)點(diǎn),這些節(jié)點(diǎn)以慷慨的免費(fèi)層提供對(duì)以太網(wǎng)絡(luò)的訪問(wèn)。一起使用這些工具將使開發(fā)過(guò)程更加容易。
場(chǎng)景: Amethon 書店
我們的目標(biāo)是為可下載的電子書建立一個(gè)店面,接受以太區(qū)塊鏈的本地貨幣(“以太”)和 ERC20穩(wěn)定幣(以美元掛鉤的支付令牌)作為一種支付方法。從現(xiàn)在開始,我們稱之為“ Amethon”。完整的實(shí)現(xiàn)可以在附帶的 GitHub monorepo 中找到。所有的代碼都用Typescript 編寫 并且能夠被yarn build 或yarn dev 命令編譯。
我們將一步一步地指導(dǎo)您完成這個(gè)過(guò)程,但是熟悉智能契約、以太坊以及 Solidy 編程語(yǔ)言的最低知識(shí)可能有助于您一起閱讀。我們建議您首先閱讀一些基礎(chǔ)知識(shí),以熟悉生態(tài)系統(tǒng)的基本概念。
Application Structure
存儲(chǔ)后端是作為一個(gè) CRUD API 構(gòu)建的,它本身不連接到任何區(qū)塊鏈。它的前端觸發(fā)對(duì)該 API 的支付請(qǐng)求,客戶使用他們的加密錢包完成。
Amethon 被設(shè)計(jì)成一個(gè)“傳統(tǒng)的”電子商務(wù)應(yīng)用程序,它負(fù)責(zé)業(yè)務(wù)邏輯,除了支付本身之外不依賴任何上鏈數(shù)據(jù)。在結(jié)帳過(guò)程中,后端發(fā)出 PaymentRequest 對(duì)象,這些對(duì)象攜帶一個(gè)唯一標(biāo)識(shí)符(例如“發(fā)票號(hào)碼”) ,用戶將其附加到支付交易中。
后臺(tái)守護(hù)進(jìn)程偵聽各自的契約事件,并在檢測(cè)到付款時(shí)更新商店的數(shù)據(jù)庫(kù)。
Amethon 的支付結(jié)算
收款人合約
在 Amethon 中心,PaymentReceiversmart 合同代表店面所有者接受和托管付款。
每次用戶向 PaymentReceiver 合同發(fā)送資金時(shí),都會(huì)發(fā)送一個(gè) PaymentReceivedevent,其中包含有關(guān)支付的來(lái)源(客戶的 Etherum 帳戶)、其總價(jià)值、所使用的 ERC20令牌合同地址以及指向后端數(shù)據(jù)庫(kù)條目的 paymentID 的信息。
TypeScript-JSX
event PaymentReceived( address indexed buyer, uint256 value, address token, bytes32 paymentId );
以太合同的作用類似于基于用戶(即“外部擁有”/EOA)的帳戶,并在部署時(shí)獲得自己的帳戶地址。接收本地以太貨幣需要實(shí)現(xiàn)接收和回退功能,這些功能在某人將以太資金轉(zhuǎn)移到合同時(shí)被調(diào)用,并且沒(méi)有其他功能簽名與調(diào)用匹配:
TypeScript-JSX
receive() external payable { emit PaymentReceived(msg.sender, msg.value, ETH_ADDRESS, bytes32(0)); } fallback() external payable { emit PaymentReceived( msg.sender, msg.value, ETH_ADDRESS, bytes32(msg.data)); }
官方的 Soliity 文檔指出了這些函數(shù)之間的細(xì)微差別: 當(dāng)傳入的事務(wù)不包含額外數(shù)據(jù)時(shí),就會(huì)調(diào)用 Receiveis,否則就會(huì)調(diào)用回退。以太本身的本地貨幣不是 ERC20令牌,除了作為計(jì)數(shù)單位外沒(méi)有其他用途。然而,它有一個(gè)可識(shí)別的地址(0xEeeeeEeeEeeEeeEeeEeeEeeEeeEeeeeeeeeeeEEEE) ,我們使用信號(hào)以太支付在我們的 PaymentReceivedevents。
然而,以太傳輸有一個(gè)主要的缺點(diǎn): 接收時(shí)允許的計(jì)算量非常低??蛻羲蛠?lái)的天然氣只能讓我們發(fā)出一個(gè)事件,但不能將資金重定向到店主的原始地址。因此,接收者契約保留所有傳入的以太,并允許商店所有者在任何時(shí)候?qū)⑺鼈冡尫诺阶约旱膸糁?
TypeScript-JSX
function getBalance() public view returns (uint256) { return address(this).balance;}function release() external onlyOwner { (bool ok, ) = _owner.call{value: getBalance()}(“”); require(ok, “Failed to release Eth”);}
由于歷史原因,接受 ERC20令牌作為支付稍微有點(diǎn)困難。在2015年,最初規(guī)范的作者無(wú)法預(yù)測(cè)即將到來(lái)的需求,因此使 ERC20標(biāo)準(zhǔn)的接口盡可能簡(jiǎn)單。最值得注意的是,ERC20合同不能保證通知收件人有關(guān)轉(zhuǎn)移的信息,所以當(dāng) ERC20令牌被轉(zhuǎn)移到我們的 PaymentReceiver 時(shí),它無(wú)法執(zhí)行代碼。
ERC20生態(tài)系統(tǒng)已經(jīng)發(fā)生了演變,現(xiàn)在包括額外的規(guī)格。例如,EIP1363標(biāo)準(zhǔn)解決了這個(gè)問(wèn)題。不幸的是,你不能依靠主要的穩(wěn)定幣平臺(tái)來(lái)實(shí)現(xiàn)它。
因此,Amethon 必須以“經(jīng)典”的方式接受 ERC20象征性付款。契約不會(huì)在不知情的情況下“丟棄”令牌,而是代表客戶負(fù)責(zé)傳輸。這就要求用戶首先允許合同處理一定數(shù)額的資金。這不方便地要求用戶在與實(shí)際支付方法交互之前首先將 Approval 事務(wù)傳輸?shù)?ERC20令牌合同。EIP-2612可能會(huì)改善這種情況,但是,我們必須按照舊的規(guī)則玩暫時(shí)。
TypeScript-JSX
function payWithErc20( IERC20 erc20, uint256 amount, uint256 paymentId ) external { erc20.transferFrom(msg.sender, _owner, amount); emit PaymentReceived( msg.sender, amount, address(erc20), bytes32(paymentId)
編譯、部署和可變安全性
有幾個(gè)工具鏈允許開發(fā)人員編譯、部署和與 Etherum 智能契約進(jìn)行交互,但最先進(jìn)的工具鏈之一是 Truffle Suite。它帶有一個(gè)基于 Ganache 的內(nèi)置開發(fā)塊鏈和一個(gè)遷移概念,允許您自動(dòng)化并安全地運(yùn)行契約部署。
在“真正的”區(qū)塊鏈基礎(chǔ)設(shè)施上部署合同,比如以太網(wǎng)測(cè)試網(wǎng),需要兩件事: 一個(gè)連接到區(qū)塊鏈節(jié)點(diǎn)的以太網(wǎng)提供商和一個(gè)賬戶的私鑰/錢包助記符或者一個(gè)可以代表一個(gè)賬戶簽署交易的錢包連接。該帳戶還需要有一些(測(cè)試網(wǎng))以太在它支付天然氣費(fèi)用期間部署。
MetaMask 就是干這個(gè)的。創(chuàng)建一個(gè)新的帳戶,你不會(huì)使用其他任何東西,但部署(它將成為“所有者”的合同) ,并與一些以太使用你首選的測(cè)試網(wǎng)的水龍頭(我們推薦 Paradigm)。通常情況下,你現(xiàn)在可以導(dǎo)出賬戶的私鑰(“ Account Details”> “ Export Private Key”)并將其與你的開發(fā)環(huán)境連接起來(lái),但是為了規(guī)避這個(gè)工作流程所隱含的所有安全問(wèn)題,Truffle 提供了一個(gè)專用的儀表板網(wǎng)絡(luò)和網(wǎng)絡(luò)應(yīng)用程序,可以用來(lái)在瀏覽器中使用 Metamask 簽署合同部署之類的事務(wù)。要啟動(dòng)它,在一個(gè)新的終端窗口中執(zhí)行 truffle dashboard,并使用一個(gè)帶有活動(dòng) Metamask 擴(kuò)展的瀏覽器訪問(wèn) http://localhost:24012/。
使用 truffle 的儀表板在不暴露私鑰的情況下對(duì)事務(wù)進(jìn)行簽名
Amethon 項(xiàng)目還依賴于各種秘密設(shè)置。注意,由于 dotenv-flow 的工作方式,。Envfiles 包含示例或公開可見的設(shè)置,這些設(shè)置被 gitignored.env.localfiles 覆蓋。收到。并覆蓋它們的值。
若要將本地環(huán)境連接到以太網(wǎng)絡(luò),請(qǐng)?jiān)L問(wèn)同步塊鏈節(jié)點(diǎn)。當(dāng)然,你可以下載其中一個(gè)客戶端,然后等待它在你的機(jī)器上同步,但是將你的應(yīng)用程序連接到 Ethereum 的節(jié)點(diǎn)上要方便得多,這些節(jié)點(diǎn)是以服務(wù)的形式提供的,其中最著名的是 Infura。他們的免費(fèi)層為您提供三種不同的訪問(wèn)密鑰和每月10萬(wàn)的 RPC 請(qǐng)求,支持范圍廣泛的以太網(wǎng)絡(luò)。
注冊(cè)之后,注意你的 INFURA 密鑰并把它放在你的合同中. env.localas INFURA _ KEY。
如果你想與合同互動(dòng),例如在 Kovan 網(wǎng)絡(luò)上,只需在所有的 truffle 命令中添加相應(yīng)的 truffle 配置和 -network Kovan 選項(xiàng)。您甚至可以啟動(dòng)一個(gè)交互式控制臺(tái): 紗松露控制臺(tái)——網(wǎng)絡(luò) Kovan。在本地測(cè)試契約不需要任何特殊的設(shè)置過(guò)程。為了讓我們的生活變得簡(jiǎn)單,我們使用 Metamask 通過(guò)truffle儀表板提供者注入的提供者和簽名者。
改變?yōu)閏ontracts 文件夾 并運(yùn)行yarn truffle develop.這將啟動(dòng)一個(gè)本地區(qū)塊鏈與預(yù)先資助的帳戶,并打開一個(gè)連接的控制臺(tái)上。要將你的 Metamask 錢包連接到開發(fā)網(wǎng)絡(luò),使用 http://localhost:9545作為其 RPC 端點(diǎn)創(chuàng)建一個(gè)新的網(wǎng)絡(luò)。當(dāng)鏈條開始時(shí),請(qǐng)注意列出的帳戶: 您可以導(dǎo)入他們的私人密鑰到您的 Metamask 錢包發(fā)送交易代表您的本地區(qū)塊鏈。
輸入compile來(lái)一次編譯所有契約,并使用migrate將它們部署到本地鏈中。 你可以通過(guò)請(qǐng)求它們當(dāng)前部署的實(shí)例來(lái)與契約交互,并像這樣調(diào)用它的函數(shù):
TypeScript-JSX
pr = await PaymentReceiver.deployed()balance = await pr.getBalance()
一旦您對(duì)結(jié)果感到滿意,您就可以將它們部署到公共測(cè)試網(wǎng)(或 mainnet)上,以及:
Shell
yarn truffle migrate –interactive –network dashboard
The Backend
存儲(chǔ) API/CRUD
我們的后端提供了一個(gè) JSON API 來(lái)與高層次的支付實(shí)體進(jìn)行交互。我們決定使用 TypeORM 和本地 SQLite 數(shù)據(jù)庫(kù)來(lái)支持 Books 和 PaymentRequest 的實(shí)體。書籍代表我們商店的主要實(shí)體,有一個(gè)零售價(jià)格,以美分表示。為了最初在數(shù)據(jù)庫(kù)中添加書籍,可以使用 companyingSeed.ts 文件。編譯完文件后,可以通過(guò) invokingnode build/Seed.js 執(zhí)行它。
TypeScript
//backend/src/entities/Book.tsimport { Entity, Column, PrimaryColumn, OneToMany } from “typeorm”;import { PaymentRequest } from “./PaymentRequest”;@Entity()export class Book { @PrimaryColumn() ISBN: string; @Column() title: string;
注意: 在任何計(jì)算機(jī)系統(tǒng)上都強(qiáng)烈建議不要將貨幣值存儲(chǔ)為浮動(dòng)值,因?yàn)閷?duì)浮動(dòng)值進(jìn)行操作肯定會(huì)引入精度錯(cuò)誤。這也是為什么所有的加密令牌都使用18位十進(jìn)制數(shù),而 Soliity 甚至沒(méi)有 float 數(shù)據(jù)類型的原因。1以太實(shí)際上代表“10000000000000000000”為最小的以太單位。
對(duì)于打算從 Amethon 購(gòu)買書籍的用戶,首先通過(guò)調(diào)用/books/: isbn/orderpath 為他們的商品創(chuàng)建一個(gè)個(gè)人支付請(qǐng)求。這將創(chuàng)建一個(gè)新的唯一標(biāo)識(shí)符,必須隨每個(gè)請(qǐng)求一起發(fā)送。
我們?cè)谶@里使用的是普通整數(shù),但是,對(duì)于實(shí)際的用例,您將使用更復(fù)雜的東西。唯一的限制是 id 的二進(jìn)制長(zhǎng)度必須適合32字節(jié)(uint256)。EachPaymentRequest 繼承書籍的零售價(jià)值(以美分為單位) ,并記錄客戶的地址,在購(gòu)買過(guò)程中確定 Hash 和 paidUSDCentwill。
TypeScript
//backend/src/entities/PaymentRequest.ts@Entity()export class PaymentRequest { @PrimaryGeneratedColumn() id: number; @Column(“varchar”, { nullable: true }) fulfilledHash: string | null; @Column() address: string; @Column() priceInUSDCent: number; @Column(“mediumint”, { nullable: true }) paidUSDCent: number; @ManyToOne(() => Book, (book) => book.payments) book: Book;}
創(chuàng)建 PaymentRequesttity 的初始訂單請(qǐng)求如下:
TypeScript
POST http://localhost:3001/books/978-0060850524/orderContent-Type: application/json{ “address”: “0xceeca1AFA5FfF2Fe43ebE1F5b82ca9Deb6DE3E42”}—>{ “paymentRequest”: { “book”: { “ISBN”: “978-0060850524”, “title”: “Brave New World”, “retailUSDCent”: 1034 }, “address”: “0xceeca1AFA5FfF2Fe43ebE1F5b82ca9Deb6DE3E42”, “priceInUSDCent”: 1034, “fulfilledHash”: null, “paidUSDCent”: null, “id”: 6 }, “receiver”: “0x7A08b6002bec4B52907B4Ac26f321Dfe279B63E9”}
區(qū)塊鏈監(jiān)聽器后臺(tái)服務(wù)
查詢區(qū)塊鏈的狀態(tài)樹不會(huì)花費(fèi)客戶機(jī)任何氣體,但節(jié)點(diǎn)仍然需要計(jì)算。當(dāng)這些操作變得計(jì)算量太大時(shí),它們可能會(huì)超時(shí)。對(duì)于實(shí)時(shí)交互,強(qiáng)烈建議不要輪詢鏈狀態(tài),而是監(jiān)聽事務(wù)發(fā)出的事件。這需要使用支持 WebSocket 的提供程序,因此一定要使用以 wss://as URL 方案開頭的 Infura 端點(diǎn),用于后端的 PROVIDER _ RPC 環(huán)境變量。然后,您可以啟動(dòng)后端的 daemon.tsscript 并在任何鏈上偵聽 PaymentReceivedevents:
TypeScript
//backend/src/daemon.ts const web3 = new Web3(process.env.PROVIDER_RPC as string); const paymentReceiver = new web3.eth.Contract( paymentReceiverAbi as AbiItem[], process.env.PAYMENT_RECEIVER_CONTRACT as string ); const emitter = paymentReceiver.events.PaymentReceived({ fromBlock: “0”, });
注意我們是如何用一個(gè)應(yīng)用二進(jìn)制接口實(shí)例化契約實(shí)例的。Soliity 編譯器生成 ABI,并為 RPC 客戶機(jī)提供有關(guān)如何編碼事務(wù)以調(diào)用和解碼智能契約上的函數(shù)、事件或參數(shù)的信息。
一旦實(shí)例化,您就可以在契約的 PaymentRecected 日志(從塊0開始)上鉤住一個(gè)偵聽器,并在收到后處理它們。
因?yàn)?Amethon 支持 Ether 和 stablecoin (“ US”)支付,所以守護(hù)進(jìn)程的 handlePaymentEventmethod 首先檢查用戶支付中使用了哪個(gè)令牌,并在需要時(shí)計(jì)算其美元價(jià)值:
TypeScript
//backend/src/daemon.tsconst ETH_USD_CENT = 2_200 * 100;const ACCEPTED_USD_TOKENS = (process.env.STABLECOINS as string).split(“,”);const NATIVE_ETH = “0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE”;const handlePaymentEvent = async (event: PaymentReceivedEvent) => { const args = event.returnValues; const paymentId = web3.utils.hexToNumber(args.paymentId); const decimalValue = web3.utils.fromWei(args.value); const payment = await paymentRepo.findOne({ where: { id: paymentId } }); let valInUSDCents; if (args.token === NATIVE_ETH) { valInUSDCents = parseFloat(decimalValue) * ETH_USD_CENT; } else { if (!ACCEPTED_USD_TOKENS.includes(args.token)) { return console.error(“payments of that token are not supported”); } valInUSDCents = parseFloat(decimalValue) * 100; } if (valInUSDCents < payment.priceInUSDCent) { return console.error(`payment [${paymentId}] not sufficient`); } payment.paidUSDCent = valInUSDCents; payment.fulfilledHash = event.transactionHash; await paymentRepo.save(payment);};
The Frontend
我們書店的前端是建立在官方的創(chuàng)建反應(yīng)應(yīng)用程序模板與類型支持,并使用 Tailwind 的基本樣式。它支持所有已知的 CRA 腳本,因此您可以在創(chuàng)建自己的腳本之后通過(guò)紗線啟動(dòng)本地腳本。本地文件,包含之前創(chuàng)建的支付接收方和 stablecoin 合同地址。
注意: CRA5將他們的 webpack 依賴關(guān)系轉(zhuǎn)移到了一個(gè)不再支持瀏覽器中節(jié)點(diǎn)填充的版本。這打破了今天幾乎所有與以太坊相關(guān)的項(xiàng)目的構(gòu)建。避免彈出的一個(gè)常見解決方案是掛鉤到 CRA 構(gòu)建過(guò)程中。我們正在使用 response-app-rewire,但是你可以簡(jiǎn)單地呆在 CRA4,直到社區(qū)提出一個(gè)更好的解決方案。
連接 WEB3錢包
任何 Dapp 的關(guān)鍵部分都是連接到用戶的錢包。您可以嘗試按照正式的 MetaMask 文檔手動(dòng)連接該進(jìn)程,但我們強(qiáng)烈建議使用適當(dāng)?shù)?React 庫(kù)。我們發(fā)現(xiàn) Noah Zinsmeister 的 web3反應(yīng)是最好的。檢測(cè)和連接 web3客戶端歸結(jié)為以下代碼(ConnectButton.tsx) :
TypeScript
//frontend/src/components/ConnectButton.tsimport { useWeb3React } from “@web3-react/core”;import { InjectedConnector } from “@web3-react/injected-connector”;import React from “react”;import Web3 from “web3”;export const injectedConnector = new InjectedConnector({ supportedChainIds: [42, 1337, 31337], //Kovan, Truffle, Hardhat});export const ConnectButton = () => { const { activate, account, active } = useWeb3React(); const connect = () => { activate(injectedConnector, console.error); }; return active ? ( connected as: {account} ) : ( Connect );};
通過(guò)將應(yīng)用程序的代碼封裝在 上下文中,您可以從任何組件使用 useWeb3Reacthook 訪問(wèn) web3提供程序、帳戶和連接狀態(tài)。由于 Web3React 與所使用的 web3庫(kù)(Web3.js 或 ethers.js)是不可知的,因此您必須提供一個(gè)回調(diào),以生成一個(gè)連接的“庫(kù)”:
TypeScript-JSX
//frontend/src/App.tsximport Web3 from “web3”;function getWeb3Library(provider: any) { return new Web3(provider);}
支付流程
從 Amethon 后端加載可用的圖書后, 組件首先檢查該用戶的付款是否已經(jīng)處理,然后顯示打包在 組件中的所有支持的付款選項(xiàng)。
Paying With ETH
負(fù)責(zé)啟動(dòng)對(duì) PaymentReceiveragreement 的直接以太傳輸。由于這些調(diào)用并不直接與契約接口交互,我們甚至不需要初始化契約實(shí)例:
TypeScript-JSX
//frontend/src/components/PayButton.tsxconst weiPrice = usdInEth(paymentRequest.priceInUSDCent);const tx = web3.eth.sendTransaction({ from: account, //the current user to: paymentRequest.receiver.options.address, //the PaymentReceiver contract address value: weiPrice, //the eth price in wei (10**18) data: paymentRequest.idUint256, //the paymentRequest’s id, converted to a uint256 hex string});const receipt = await tx;onConfirmed(receipt);
如前所述,由于新的事務(wù)帶有 msg.data 字段,Soliity 的約定觸發(fā) PaymentReceiver 的回退()外部支付函數(shù),該函數(shù)發(fā)出一個(gè)帶有 Ether 令牌地址的 PaymentReceivedevent。這由被守護(hù)的鏈監(jiān)聽器接收,該監(jiān)聽器相應(yīng)地更新后端的數(shù)據(jù)庫(kù)狀態(tài)。
靜態(tài)助手函數(shù)負(fù)責(zé)將當(dāng)前的美元價(jià)格轉(zhuǎn)換為以太值。在一個(gè)真實(shí)的場(chǎng)景中,從值得信賴的第三方(如 Coingecko)或像 Uniswap 這樣的 DEX 查詢匯率。這樣做允許您擴(kuò)展 Amethon 以接受任意令牌作為支付。
TypeScript
//frontend/src/modules/index.tsconst ETH_USD_CENT = 2_200 * 100;export const usdInEth = (usdCent: number) => { const eth = (usdCent / ETH_USD_CENT).toString(); const wei = Web3.utils.toWei(eth, “ether”); return wei;};
用 ERC20穩(wěn)定幣支付
由于前面提到的原因,從用戶的角度來(lái)看,ERC20令牌中的支付稍微復(fù)雜一些,因?yàn)椴荒芎?jiǎn)單地刪除契約中的令牌。像幾乎所有具有類似用例的人一樣,我們必須首先請(qǐng)求用戶允許我們的 PaymentReceiver 合同轉(zhuǎn)移他們的資金,并調(diào)用代表用戶轉(zhuǎn)移請(qǐng)求資金的實(shí)際 payWithEerc20方法。
下面是 PayWithStableButton 對(duì)選定的 ERC20令牌授予權(quán)限的代碼:
TypeScript
//frontend/src/components/PayWithStableButton.tsxconst contract = new web3.eth.Contract( IERC20ABI as AbiItem[], process.env.REACT_APP_STABLECOINS);const appr = await coin.methods .approve( paymentRequest.receiver.options.address, //receiver contract’s address price // USD value in wei precision (1$ = 10^18wei) ) .send({ from: account, });
請(qǐng)注意,設(shè)置 ERC20令牌的契約實(shí)例所需的 ABI 接收一般的 IERC20 ABI。我們使用從 OpenZeppelin 的官方庫(kù)中生成的 ABI,但是任何其他生成的 ABI 都可以完成這項(xiàng)工作。在批準(zhǔn)轉(zhuǎn)讓后,我們可以開始付款:
TypeScript-JSX
//frontend/src/components/PayWithStableButton.tsxconst contract = new web3.eth.Contract( PaymentReceiverAbi as AbiItem[], paymentRequest.receiver.options.address);const tx = await contract.methods .payWithErc20( process.env.REACT_APP_STABLECOINS, //identifies the ERC20 contract weiPrice, //price in USD (it’s a stablecoin) paymentRequest.idUint256 //the paymentRequest’s id as uint256 )
簽署下載請(qǐng)求
最后,我們的客戶可以下載他們的電子書。但是有一個(gè)問(wèn)題: 既然我們沒(méi)有“登錄”用戶,我們?nèi)绾未_保只有真正為內(nèi)容付費(fèi)的用戶才能調(diào)用我們的下載路徑?答案是加密簽名。在將用戶重定向到我們的后端之前, 組件允許用戶簽署一個(gè)獨(dú)特的消息,該消息被提交作為帳戶控制的證明:
TypeScript-JSX
//frontend/src/components/DownloadButton.tsxconst download = async () => { const url = `${process.env.REACT_APP_BOOK_SERVER}/books/${book.ISBN}/download`; const nonce = Web3.utils.randomHex(32); const dataToSign = Web3.utils.keccak256(`${account}${book.ISBN}${nonce}`); const signature = await web3.eth.personal.sign(dataToSign, account, “”); const resp = await ( await axios.post(
后端的下載路由可以恢復(fù)簽名者的地址,方法是按照與用戶以前相同的方式組合消息,并使用消息和提供的簽名調(diào)用加密套件的 ecRecovery 方法。如果恢復(fù)的地址與我們數(shù)據(jù)庫(kù)中已完成的 PaymentRequest 匹配,我們知道我們可以允許訪問(wèn)請(qǐng)求的電子書資源:
TypeScript
//backend/src/server.tsapp.post( “/books/:isbn/download”, async (req: DownloadBookRequest, res: Response) => { const { signature, address, nonce } = req.body; //rebuild the message the user created on their frontend const signedMessage = Web3.utils.keccak256( `${address}${req.params.isbn}${nonce}` );
這里提供的帳戶所有權(quán)證明仍然不是完全正確的。任何知道所購(gòu)物品的有效簽名的人都可以成功地調(diào)用下載路由。最后的修復(fù)方法是首先在后端上創(chuàng)建隨機(jī)消息,然后讓客戶簽名并批準(zhǔn)它。由于用戶無(wú)法理解他們應(yīng)該簽署的混亂的十六進(jìn)制代碼,他們不會(huì)知道我們是否會(huì)欺騙他們簽署另一個(gè)有效的交易,這可能會(huì)危及他們的帳戶。
雖然我們已經(jīng)通過(guò)使用 web3的 eth.Personal.signmethod 避免了這種攻擊向量,但是最好以人性化的方式顯示要簽名的消息。這就是 EIP-712達(dá)到的目標(biāo)ーー MetaMask 已經(jīng)支持這一標(biāo)準(zhǔn)。
結(jié)論及下一步
對(duì)于開發(fā)者來(lái)說(shuō),接受電子商務(wù)網(wǎng)站的付款從來(lái)都不是一件容易的事情。盡管 web3生態(tài)系統(tǒng)允許店面接受數(shù)字貨幣,但獨(dú)立于服務(wù)的插件解決方案的可用性不足。本文演示了一種安全、簡(jiǎn)單和自定義的方法來(lái)請(qǐng)求和接收加密支付。
還有進(jìn)一步發(fā)展的空間。以太網(wǎng)上 ERC20傳輸?shù)奶烊粴獬杀具h(yuǎn)遠(yuǎn)超過(guò)我們的賬面價(jià)格。低價(jià)物品的加密支付在天然氣友好的環(huán)境中是有意義的,比如 Gnosis Chain (他們的“本地”以太貨幣是 DAI,所以你甚至不用擔(dān)心穩(wěn)定幣的轉(zhuǎn)移)或者 Arbitrum。您還可以使用購(gòu)物車簽出來(lái)擴(kuò)展后端,或者使用 DEXes 將任何傳入的 ERC20令牌交換到您首選的貨幣。
畢竟,web3的承諾是允許沒(méi)有中間商的直接貨幣交易,并為希望吸引懂加密技術(shù)的客戶的在線商店增加重大價(jià)值。