決済・サービス連携
ウォレット連携とx402を使い、利用と支払いをつなぐ。
← 実装パターン一覧設備のレポートAPIを、支払い後に利用できるようにする
設備の稼働レポートを取得するときに、APIがx402形式で支払い条件を返し、利用者が確認・支払い後にレポートを取得する構成です。商品・注文・閲覧権限は自社サービスで管理し、ウォレットでの支払いをhazBaseへ接続します。
「レポートを選ぶ → 金額を確認する → 支払う → 閲覧する」の流れを構成できます。購入者には同じ注文からの再取得も提供できます。
システム構成と役割
レポートAPI・注文管理
価格・資産・支払先・有効期限と、対象レポートを紐づけます。
Merchant backend / Order DB支払い条件の読取
対応するチェーン・資産を選び、利用者に金額と宛先を表示します。
@hazbase/kit/x402認証・支払い
セッションと操作承認を使って、支払いを送信します。
@hazbase/kit/wallet / @hazbase/auth支払確認・閲覧権限
サーバーで支払いと注文を照合し、閲覧権限を一度だけ発行します。
Payment verification / Access DB
データモデル例
業務DBに保存する情報と、チェーンやAPIで管理する情報を対応づけます。フィールド名とサンプル値は、この構成で使うアプリケーション側の設計例です。
| 項目・値の例 | 管理する場所 | 役割・対応関係 |
|---|---|---|
orderId / resourceUrlORDER-001 / report URL | 自社サーバー | 注文と取得対象の正規URL。購入者のセッションと紐づけます。 |
paymentRequestIdissued request ID | 支払い要求 | 接続先の支払いサービスが発行したIDを使用します。任意の文字列を支払いAPIに渡す構成ではありません。 |
network / asset / payToeip155:… / 0x… / 0x… | 注文・支払い要求 | 許可するチェーン、トークン、加盟店の受取先。自社の設定と照合します。 |
amountAtomic1000000 | 注文・支払い要求 | 最小単位で表した金額。小数桁6なら表示上1単位です。円などの法定通貨の金額とは区別します。 |
paymentAttemptId / statusattempt ID / settled | 支払いAPI+DB | 送信後の試行IDを保存し、同じ試行の完了状態を確認します。 |
accessGrantorderId + memberId | 自社DB | 注文に対する閲覧権限。一意制約で二重発行を防ぎます。 |
処理の流れ
注文に紐づく支払い要求を用意する
レポートの取得要求に対して、サーバーが支払い条件を返します。支払い要求の作成や加盟店の設定は接続先サービスの契約に合わせます。ここで使用するWallet SDKは、任意の注文を登録するAPIではありません。
Merchant API → HTTP 402 / paymentRequestId条件を確認して表示する
x402レスポンスを読み、チェーン・資産・支払先・対象URL・金額の上限を自社ポリシーと照合します。利用者に対象レポートと金額を表示し、支払い意思を確認します。
summarizeX402Request / isX402RequestExpired認証済みウォレットから支払う
emailSession、smartAccountAddress、deviceBindingId、highTrustTokenを認証フローから取得します。承認した金額と宛先をexpectedAmountAtomic・expectedPayToとして送信します。
payX402WithHazbaseWallet(input)同じ試行の完了状態を確認する
応答が処理中ならpaymentAttemptIdを保存し、status APIで照会します。送信済み、verified、settledなどの状態を区別し、通信が遅いだけで新たな支払いを作らないようにします。
getX402HazbaseWalletPaymentStatus({ emailSession, paymentRequestId, paymentAttemptId })サーバーで照合して閲覧を許可する
加盟店サーバーが信頼できる支払い情報・証明を検証し、注文・購入者・金額・資産・宛先との一致を確認します。DBトランザクションと一意制約で閲覧権限を保存し、対象レポートを返します。
Server payment verification → accessGrant → report response
コードを利用する前の準備
- 接続先で発行された有効なpaymentRequestIdと、対応するチェーン・資産・加盟店受取先を用意します。
- 支払い確認画面と、Wallet APIで使用するセッション・端末登録・操作承認を組み込みます。
- 認証トークンをログ、URL、公開設定に保存しません。サンプル関数のauthには、その利用者の認証フローで得た値を渡します。
npm install --save-exact @hazbase/kit@0.9.0 ethers@6.16.0以下はアプリケーションから呼び出すTypeScriptの関数です。接続先・権限・対象データを引数として渡します。UI、DBへの保存、処理の再開は、上記の構成に沿って業務側へ組み込めます。
支払い条件を読み取り、承認した内容で送信する
inspectReportPaymentで支払い条件を取り出し、表示・確認後にpayApprovedReportを呼び出します。payloadは支払いサービスから受け取ったJSON、policyは自社サービスで定めた条件です。
import { summarizeX402Request, isX402RequestExpired, type HazbaseX402Request } from '@hazbase/kit/x402';
import { createHazbaseWalletClient, type PayX402WithHazbaseWalletInput } from '@hazbase/kit/wallet';
import { getAddress } from 'ethers';
export function inspectReportPayment(
payload: Record<string, unknown>,
resourceUrl: string,
policy: { network: string; asset: string; payTo: string; maxAmountAtomic: bigint },
) {
const request = summarizeX402Request(payload, { sourceUrl: resourceUrl }, {
networks: [policy.network],
assets: [{ address: policy.asset }],
requirePayTo: true,
});
if (!request) throw new Error('Unsupported payment request.');
const terms = request.requirement;
if (terms.resource !== resourceUrl || getAddress(terms.payTo) !== getAddress(policy.payTo)) {
throw new Error('Payment does not match the requested service.');
}
const amount = BigInt(terms.amountAtomic);
if (amount <= 0n || amount > policy.maxAmountAtomic) throw new Error('Amount is outside policy.');
if (isX402RequestExpired(request, Date.now())) throw new Error('Payment request expired.');
return request;
}
export async function payApprovedReport(
request: HazbaseX402Request,
auth: Pick<PayX402WithHazbaseWalletInput,
'emailSession' | 'smartAccountAddress' | 'deviceBindingId' | 'highTrustToken'>,
) {
// Call only after the user confirms the displayed resource and payment terms.
if (isX402RequestExpired(request, Date.now())) throw new Error('Payment request expired.');
const wallet = createHazbaseWalletClient();
return wallet.payX402WithHazbaseWallet({
...auth,
paymentRequestId: request.paymentRequestId,
expectedAmountAtomic: request.requirement.amountAtomic,
expectedPayTo: request.requirement.payTo,
waitForReceipt: false,
});
}
実行結果と、アプリケーションへの反映
応答のpaymentRequestId・paymentAttemptIdを保持して状態を追跡します。フロントエンドから送られたpaid: trueだけでは閲覧権限を与えず、加盟店サーバーの検証結果で確定します。
実装で押さえておきたい点
金額の表示と有効期限
支払い条件の表示値だけでなく最小単位の金額を照合します。SDKの期限確認は受信時刻に基づく補助判定なので、注文の絶対的な有効期限もサーバーで検証します。
決済と閲覧権限の二重処理
タイムアウト時は同じ試行を照会し、注文ごとに支払状態を保持します。支払い完了の通知が複数回来ても、同じ閲覧権限を重複発行しない構成にします。
支払先や対象の差し替え
決済対象のURL・資産・チェーン・宛先を注文の正と照合します。ログイン済みの購入者と要求IDの関連も、サーバー側で確認します。
用途に合わせた拡張
従量課金・継続利用
1回のレポート購入から始め、利用枠や期限付きアクセスへ拡張できます。どの支払いが何回・いつまでの利用を許可するかは、サービス側の権限モデルで管理します。
ウォレット拡張との連携
x402要求をページ上で公開し、対応ウォレットへ受け渡す構成も選べます。支払いUIを委ねる場合も、注文との照合とサービス提供の判定は加盟店サーバーが担います。