lesson-4_購入機能を実装しよう
👀 ロード時に購入済かどうかを確認する
それでは、order.json
の「データベース」を上手く利用して、ストア に購入機能を実装していきましょう!
これを行うためのフローはapi.js
で使用したaddOrder
とよく似ています。
まず、lib/api.js
のさいごに以下のコードを追加します。
// api.js
// 指定された公開鍵が過去に商品を購入していた場合はtrueを返します。
export const hasPurchased = async (publicKey, itemID) => {
// 公開鍵をパラメータとしてGETリクエストを送信します。
const response = await fetch(`../api/orders?buyer=${publicKey.toString()}`);
// レスポンスコードが200の場合の処理です。
if (response.status === 200) {
const json = await response.json();
console.log("Current wallet's orders are:", json);
// 注文が存在した場合の処理です。
if (json.length > 0) {
// この購入者とアイテムIDのレコードがあるかどうかを確認します。
const order = json.find(
(order) =>
order.buyer === publicKey.toString() && order.itemID === itemID
);
if (order) {
return true;
}
}
}
return false;
};
ここではエンドポイントとやり取りし、指定されたアドレスが特定のアイテムを購入したかどうかを確認しています。
この機能を実装するにあたって、Buy.js
で以下の2つのことを行う必要があります。
1. インポートにhasPurchased
を含める。
2. useEffectでページの読み込み時にhasPurchased
チェックを実行する。
それぞれの変更を加えるために、以下のとおり更新していきましょう。
// Buy.js
import { findReference, FindReferenceError } from "@solana/pay";
import { useConnection, useWallet } from "@solana/wallet-adapter-react";
import { Keypair, Transaction } from "@solana/web3.js";
import { useEffect, useMemo, useState } from "react";
import { InfinitySpin } from "react-loader-spinner";
import { addOrder, hasPurchased } from "../lib/api";
import IPFSDownload from "./IpfsDownload";
const STATUS = {
Initial: "Initial",
Submitted: "Submitted",
Paid: "Paid",
};
export default function Buy({ itemID }) {
const { connection } = useConnection();
const { publicKey, sendTransaction } = useWallet();
const orderID = useMemo(() => Keypair.generate().publicKey, []); // 注文を識別するために使用する公開鍵を用意します。
const [loading, setLoading] = useState(false);
const [status, setStatus] = useState(STATUS.Initial); // トランザクションステータス追跡用のstateを定義します。
const order = useMemo(
() => ({
buyer: publicKey.toString(),
orderID: orderID.toString(),
itemID: itemID,
}),
[publicKey, orderID, itemID]
);
const processTransaction = async () => {
setLoading(true);
const txResponse = await fetch("../api/createTransaction", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(order),
});
const txData = await txResponse.json();
const tx = Transaction.from(Buffer.from(txData.transaction, "base64"));
console.log("Tx data is", tx);
try {
const txHash = await sendTransaction(tx, connection);
console.log(
`Transaction sent: https://solscan.io/tx/${txHash}?cluster=devnet`
);
setStatus(STATUS.Submitted);
} catch (error) {
console.error(error);
} finally {
setLoading(false);
}
};
useEffect(() => {
// このアドレスがすでに対象 の商品を購入しているか確認し、購入している場合は商品データを取得してPaidをtrueに設定します。
async function checkPurchased() {
const purchased = await hasPurchased(publicKey, itemID);
if (purchased) {
setStatus(STATUS.Paid);
console.log("Address has already purchased this item!");
}
}
checkPurchased();
}, [publicKey, itemID]);
useEffect(() => {
if (status === STATUS.Submitted) {
setLoading(true);
const interval = setInterval(async () => {
try {
const result = await findReference(connection, orderID);
console.log("Finding tx reference", result.confirmationStatus);
if (
result.confirmationStatus === "confirmed" ||
result.confirmationStatus === "finalized"
) {
clearInterval(interval);
setStatus(STATUS.Paid);
setLoading(false);
addOrder(order);
alert("Thank you for your purchase!");
}
} catch (e) {
if (e instanceof FindReferenceError) {
return null;
}
console.error("Unknown error", e);
} finally {
setLoading(false);
}
}, 1000);
return () => {
clearInterval(interval);
};
}
}, [status]);
if (!publicKey) {
return (
<div>
<p>You need to connect your wallet to make transactions</p>
</div>
);
}
if (loading) {
return <InfinitySpin color="gray" />;
}
return (
<div>
{status === STATUS.Paid ? (
<IPFSDownload
filename="anya"
hash="QmcJPLeiXBwA17WASSXs5GPWJs1n1HEmEmrtcmDgWjApjm"
cta="Download goods"
/>
) : (
<button
disabled={loading}
className="buy-button"
onClick={processTransaction}
>
Buy now →
</button>
)}
</div>
);
}
以下が今回追加した部分になります。
コメントのとおり動作します。
useEffect(() => {
// このアドレスがすでに対象の商品を購入しているか確認し、購入している場合は商品データを取得してPaidをtrueに設定します。
async function checkPurchased() {
const purchased = await hasPurchased(publicKey, itemID);
if (purchased) {
setStatus(STATUS.Paid);
console.log("Address has already purchased this item!");
}
}
checkPurchased();
}, [publicKey, itemID]);