エージェンティックコマースについて、日本で一番早く、詳しく、深く情報を発信するメディアRSS
AGENTIC COMMERCE LAB — エージェンティックコマースラボ
検証・実装2026.09.25 公開制約あり

決済規格 AP2 の中身を見る — 「AI が勝手に買う」を防ぐ電子署名のしくみ

Google が公開し、FIDO Alliance で標準化が進む AP2。UCP・ACP や実決済との関係、承認を記録する Mandate、電子署名が無断購入を防ぐしくみを、靴の購入例と TypeScript の検証で読み解きます。

青木 亮磨青木 亮磨株式会社 STRACT / ソフトウェアエンジニアXB!f
検証・実装本ラボ作成(AI 生成)

「指定の店で、赤い靴を1足、1万円以内で買っておいて」。AI エージェントに買い物を任せるとき、店はその依頼が本当に利用者の許可を得ているか確かめる必要があります。AP2 は、許可された購入内容を電子署名付きのデータで伝え、店や支払いに関わる事業者が検証するための規格です。

利用者の承認がどう記録され、AI エージェントが作った注文とどう結び付くのか。靴の購入例を通して、AP2 の役割と電子署名のしくみを追います。後半では TypeScript の検証ソースコードを使い、正常な購入と、予算や商品が承認の範囲を外れる購入を比べます。

3行まとめTL;DR
  • AP2 は、AI エージェントに任せた購入が、利用者の承認の範囲内かを確かめるための規格です。
  • 購入内容と支払いの承認を Mandate に記録し、電子署名で注文と結び付けます。
  • 署名による改ざん検知に加え、購入条件の判定と、同じ承認による重複購入を防ぐ管理も必要です。

AP2 は、AI エージェントに与えた購入権限を確かめる規格

AP2 は Agent Payments Protocol の略です。Google が決済・技術企業と開発し、公開しました。AI エージェントが利用者に代わって買い物をするとき、何を許可されたかを関係者の間で確認できるようにします。

Google は FIDO Alliance への寄贈と v0.2 を発表しました。FIDO Alliance は認証技術の標準化を進める業界団体です。同団体の AP2 に関する説明でも、寄贈された AP2 の標準化を進める方針が示されています。

普通の買い物なら、購入画面で人が商品と金額を確認します。AI エージェントに「条件を満たしたら買って」と任せる場合には、その瞬間に本人が画面を見ているとは限りません。そこで、あらかじめ承認した条件を、後から検証できる形で残します。

購入の場で利用者が確定注文を確認する方式を Human Present、事前に条件を承認して任せる方式を Human Not Present と呼びます。どちらにも利用者の承認が必要です。靴の例では、後者の流れを扱います。

本記事は、執筆時点(2026年9月)の公開資料に基づきます。技術説明と検証ソースコードは、AP2 v0.2 の仕様を参照しています。

UCP・ACP と AP2 の役割を比べる

AI エージェントと店が商品や数量をやり取りして注文を作る処理と、その注文を利用者が許可したか確かめる処理には、それぞれ役割があります。AP2 は後者の承認確認を、購入手続きに組み込む設計です。

規格・仕組み主に扱うことAP2 との関係
UCP (Universal Commerce Protocol)AI エージェントと店の購入手続きや決済連携購入手続きに AP2 の署名付き承認を載せる公式拡張があります
ACP (Agentic Commerce Protocol)AI エージェントと店の購入手続き、支払情報の安全な受け渡しAP2 が購入の承認を検証するのに対し、ACP は購入手続きの連携を定めます
AP2 (Agent Payments Protocol)利用者の承認と、AI エージェントに任せた権限の検証店や支払いに関わる事業者が、要求された購入を許可してよいか確かめます
決済事業者・カード網など支払いの処理や資金の移動承認を確認した購入を、対応する決済手段で処理します

UCP の AP2 拡張では、店と購入を仲介するサービスが対応を確認して利用を有効にします。店が提示する注文と、利用者が承認する内容を署名で結び付けます。UCP の導入だけで、すべての購入に AP2 が適用されるわけではありません。

ACP は、OpenAI と Stripe が開発した、AP2 とは別の規格です。AI エージェントと店の購入手続きを連携させ、カード番号などを直接渡さずに支払情報を共有する仕組みが用意されています。

カード決済では、承認を確かめてから支払情報を渡す

AP2 は、店に加え、支払情報の提供者と店の決済を処理する事業者にも検証の役割を定めています。支払情報の提供者は Credential Provider と呼ばれ、利用者のウォレットなどが担います。店の決済を処理する事業者は Merchant Payment Processor です。

次の図は、公式の自律購入フローをカード決済の例として整理したものです。AP2 の承認確認を通った後にも、実際の決済処理が続きます。

承認データを渡し、検証してから決済する

上から下へ時間が進みます。矢印は情報を渡す向き、枠は各担当の処理です。

利用者の承認からカード決済までの情報の流れ利用者が条件を承認し、AI エージェントが承認データを受け取ります。AI エージェントは店と注文を作り、店が署名した注文に承認を結び付けます。支払情報の提供者は AI エージェントから支払いの承認を受け取り、検証後に支払用トークンを返します。AI エージェントは購入の承認とトークンを店に渡します。店が購入の承認を検証して決済事業者へ決済を依頼し、決済事業者が支払いの承認と注文の一致を検証してから決済します。利用者確認画面で同意AI エージェント支払情報の提供者ウォレットなど店店の決済事業者決済処理を担当① 条件を承認承認データを渡す② 条件に合う注文を作る店が署名した注文③ 支払いの承認④ 支払用トークン⑤ 購入の承認 + 支払用トークン⑥ 決済を依頼トークン・注文の参照注文と承認を結び付ける支払いの承認を検証署名・委任・条件購入の承認を検証署名・委任・条件支払いの承認と注文の一致を検証決済を実行カード網などを利用

成功時の流れを簡略化しています。承認データへの署名の発行処理と、結果記録(Receipt)の返却は省略しています。

出典: AP2 v0.2 Flows / Implementation Considerations を基に本ラボ整理

支払用トークンは、支払いに使う情報の代わりとして受け渡す値です。利用範囲を限定し、今回の注文への支払いであることを店の決済事業者も確認します。AP2 が定める承認の確認だけで、カードの引き落としまで完了するわけではありません。

処理後には、店と店の決済事業者がそれぞれ署名付きの結果記録を返します。これを Receipt と呼びます。購入前の許可と処理結果を結び付け、後から承認の範囲や結果を確かめるために使います。

Mandate に「何を買い、どう支払ってよいか」を記録する

利用者が AI エージェントに許可した内容を記録した、電子署名付きの承認データが Mandate です。「買ってよい」という一言に加え、対象の商品や支払いの条件を、受け取り側が検証できる形で表します。

AP2 v0.2 では、商品を買う許可を Checkout Mandate、その購入への支払いの許可を Payment Mandate に分けています。店は商品の条件を、支払情報の提供者や店の決済事業者は金額や支払手段の条件を確認するためです。

タイミング名称記録する承認内容
事前承認Open Checkout Mandate指定の店で、赤い靴を1足買ってよい
事前承認Open Payment Mandate指定の支払手段で、上限1万円まで払ってよい
確定注文への承認Closed Checkout Mandate店が提示した「赤い靴1足」の注文を承認する
確定注文への承認Closed Payment Mandateその注文に対する9,800円の支払いを承認する

事前承認には購入条件を、確定注文への承認には特定の注文を記録します。購入内容の承認と支払いの承認のどちらにも、この区分があります。Checkout MandateとPayment Mandateの仕様で、それぞれ定義されています。

靴の自律購入では、利用者が Open Checkout Mandate と Open Payment Mandate の条件に同意します。AI エージェントはその範囲内で Closed Checkout Mandate と Closed Payment Mandate を作り、事前承認と一緒に渡します。

Human Present では、利用者がその場で確定注文を確認します。Closed Checkout Mandate と Closed Payment Mandate を直接承認する流れになります。

利用者に条件を示して同意を取得する、信頼された確認画面を Trusted Surface と呼びます。自然言語の「赤い靴」が正しい商品に、予算が正しい数字になっているかを、ここで確かめます。

例: 「上限1万円」の Mandate

次は、後半の検証ソースコードで Open Payment Mandate に入れる金額条件の抜粋です。currency は通貨、min と max は金額の範囲を表します。金額は最小通貨単位の整数なので、JPY の 10000 は1万円です。

JSON
{
  "type": "payment.amount_range",
  "currency": "JPY",
  "min": 0,
  "max": 10000
}

Open Checkout Mandate には、次の商品条件を入れます。quantity が数量、acceptable_items が許可する商品の候補です。以下は商品条件1件の抜粋で、Mandate 全体ではありません。

JSON
{
  "id": "one-pair-of-shoes",
  "quantity": 1,
  "acceptable_items": [
    { "id": "shoe-red", "title": "赤い靴" }
  ]
}

実際のデータには、このほか店や支払手段の条件、委任先の公開鍵などが入ります。AI エージェントが1足9,800円の注文を作ると、受け取り側はこの承認記録に照らして、商品と支払いの両方を判定します。

電子署名で、承認内容と AI エージェントの購入要求を結び付ける

電子署名には、署名を作る秘密鍵と、その署名を確かめる公開鍵を使います。秘密鍵を持つ側が承認データに署名し、受け取り側が公開鍵で検証します。データを書き換えると、元の署名では整合性を確認できなくなります。

AP2 の認可モデルには、利用者の本人確認に使う資格情報を基に署名する方式があります。もう一つは、AI エージェントを提供する事業者が、利用者の同意を取得して署名する方式です。以下では、後者を使います。

例えば、「赤い靴を探して買って」と頼める買い物アプリを考えます。そのアプリを提供し、利用者のアカウントや購入条件の確認画面を管理する会社を、この記事では「AI エージェントの提供会社」と呼びます。

同じ会社の買い物アプリの中でも、利用者の承認を受け付ける処理と、商品を探して注文する AI エージェントの処理を分けます。前者は「何を買ってよいか」を記録し、後者はその範囲で「実際に何を買うか」を決める役割です。

この例では、AI エージェントの提供会社が確認画面で利用者の同意を得て、自社の秘密鍵で承認データに署名します。店やウォレットは、その同意取得と署名の仕組みを信頼して承認を受け付けます。

AI エージェントの提供会社は、利用者が同意した Open Checkout Mandate と Open Payment Mandate に署名します。どちらも、購入を任せる AI エージェントの公開鍵を含めたデータです。

AI エージェントには別の秘密鍵を持たせ、それで Closed Checkout Mandate と Closed Payment Mandate に署名させます。

AI エージェントの提供会社の秘密鍵まで自由に使えると、AI エージェントが自分で「上限10万円まで承認済み」というデータに署名できてしまいます。そこで、提供会社の秘密鍵を保護し、利用者の同意なしに承認データへ署名できないようにします。

そのため、AI エージェントが自分の鍵で「上限10万円」という承認を作っても、AI エージェントの提供会社の承認としては検証を通りません。購入を受け付ける側が、署名と委任先を確認することで拒否します。

購入要求を受け取った店やウォレットは、署名した相手に合わせて公開鍵を使い分けます。AI エージェントの公開鍵だけは、AI エージェントの提供会社の署名を検証した事前承認から取り出します。

確かめる署名検証に使う公開鍵
利用者が同意した事前承認あらかじめ信頼した AI エージェントの提供会社の公開鍵
店が提示した注文あらかじめ信頼した店の公開鍵
AI エージェントが作った確定注文への承認事前承認に記録された AI エージェントの公開鍵

知らない相手が作った鍵でも、その相手の署名の計算は確認できます。しかし、それだけで利用者の承認とは扱えません。誰の公開鍵を信頼するかを決める必要があります。検証ソースコードでは、AI エージェントの提供会社と店のテスト用公開鍵を、信頼済みの設定として渡しています。

署名が正しくても、購入条件の検証は必要です。10,001円の注文への購入要求に AI エージェントが正しい鍵で署名しても、上限1万円を超えています。店や支払情報の提供者は、承認された条件と照合して購入を拒否します。

改ざんと注文の差し替えを検知するしくみ

検証ソースコードでは、必要なデータを選んで相手に渡せる SD-JWT (Selective Disclosure JWT) という形式を使います。開示するデータからハッシュ(内容に応じて変わる照合用の値)を計算し、その参照を含むデータに署名します。受け取り側は、署名と、開示データから再計算したハッシュの両方を確認します。

上限を1万円から10万円に書き換えると、開示データのハッシュが変わり、署名で保護された参照と合わなくなります。参照も書き換えて有効な承認を作り直すには、AI エージェントの提供会社の秘密鍵による新しい署名が必要です。AI エージェント自身の鍵では代用できません。

事前承認には購入を任せる AI エージェントの公開鍵を記録し、確定注文への承認には元の承認と店の注文への参照を含めます。別の署名済み注文に差し替えても、参照が一致しなければ拒否します。ソースコードでは、この関係をどのように作るのでしょうか。

Mandate を生成・署名し、購入の許可と拒否を確かめる

「赤い靴1足・上限1万円」を承認データにし、9,800円の注文と結び付けます。ソースコードでは、AI エージェントの提供会社・店・AI エージェントに別々の鍵を持たせ、電子署名を実際に生成・検証します。利用者の同意取得や買い物は模擬で、AI モデルの実行や外部への注文・課金はありません。

検証でたどる、承認から購入判定までの流れ

登場人物ごとの処理を上から順に再現します。まず赤い靴9,800円の購入を試し、次に金額や商品を変えて、判定がどう変わるかを見ます。

検証の流れ:利用者の承認、提供会社・店・AI エージェントの署名、購入の許可と拒否利用者が指定の店で赤い靴1足、上限1万円を承認したという前提で始めます。AI エージェントの提供会社が承認内容と委任先の公開鍵に署名します。店が赤い靴9,800円の注文に署名し、AI エージェントが事前承認と注文を結び付けた購入要求に署名します。店とウォレットの検証を1つの関数で再現し、署名、購入条件、使用済み状態を調べます。正常な購入は ALLOW、10,001円や青い靴の購入は DENY になることを試します。利用者購入条件を決める人① 「指定の店で赤い靴1足、上限1万円」を承認検証では同意済みの条件をデータとして用意AI エージェントの提供会社確認画面と署名を管理② 承認内容と委任先の公開鍵に署名するOpen Checkout Mandate / Open Payment MandateAI エージェントの提供会社の秘密鍵を使用店商品と金額を提示③ 赤い靴9,800円の注文に、店の秘密鍵で署名する商品・数量・金額が確定した注文AI エージェント買い物を代行する処理④ AI エージェントの秘密鍵で購入要求に署名するClosed Checkout Mandate / Closed Payment Mandate②の事前承認と③の店の注文を参照する店・ウォレット購入要求を検証する⑤ 署名・購入条件・使用済み状態を検証する店とウォレットの判定を1つの関数で再現通れば ALLOW、条件を満たさなければ DENY

最初に試す赤い靴・9,800円ALLOW(許可)

金額を変える赤い靴・10,001円DENY(拒否)

商品を変える青い靴・9,800円DENY(拒否)

この後、同じ承認の再利用や、署名前に購入上限を取り違えた場合も試します。

出典: AP2 v0.2 Agent Authorization Framework / Flows と本ラボの検証ソースコードを基に作成

(検証ソースコードのリポジトリは後日公開予定です。公開後、ここにリンクを掲載します)

Node.js 22 以上を用意し、リポジトリからソースコードを取得します。取得したソースコードの package.json があるディレクトリへ移動して、次を実行します。

npm ci で必要なライブラリをインストールし、npm run demo で検証を実行します。インストール後はネットワークや API キー、カード情報は不要で、ローカルで試せます。

Shell
npm ci
npm run demo

実行を開始するエントリーポイントは demo.ts です。以下では、図の①〜⑤に沿って、承認の準備から購入の許可・拒否までを追います。

① 利用者が承認した購入条件を用意する

利用者が「指定の店で赤い靴1足、上限1万円」を承認した前提で進めます。確認画面は省略し、同意済みの条件をソースコードに設定しています。

②(1)購入条件と、購入を任せる AI エージェントの公開鍵を入れる

ソースコードの provider は AI エージェントの提供会社、merchant は商品を売る店(Merchant)です。利用者の同意を確認して事前承認に署名するのは、AI エージェントの提供会社です。

ここでは、支払いの条件を記録する Open Payment Mandate を作ります。maximum は上限の 10000、shop は指定した店、instrument はテスト用の支払手段です。ソースコードの型名 OpenPayment は Open Payment Mandate を表します。

TypeScript
const openPayment: OpenPayment = {
  vct: 'mandate.payment.open.1',
  cnf,
  ...validity,
  constraints: [
    { type: 'payment.amount_range', currency: 'JPY', min: 0, max: maximum },
    { type: 'payment.allowed_payees', allowed: [shop] },
    { type: 'payment.allowed_payment_instruments', allowed: [instrument] },
    // 別の Open Checkout Mandate と組み合わせられないように結び付ける。
    { type: 'payment.reference', conditional_transaction_id: hash(checkout) },
  ],
};

vct は Verifiable Credential Type の略で、証明データの種類を示す項目です。ここでは Open Payment Mandate を識別します。

cnf は Confirmation の略で、鍵の所持を確かめるための情報を入れる項目です。この例では、次のように AI エージェントの公開鍵を入れ、店やウォレットが「この AI エージェントに購入を任せたのか」を検証する際に使います。

TypeScript
const cnf = { jwk: agent.publicKey.export({ format: 'jwk' }) };

jwk は JSON Web Key の略で、鍵を JSON で表す形式です。ここでは公開鍵だけを渡し、秘密鍵は AI エージェントが保持します。

validity には発行時刻と有効期限を入れています。checkout は、赤い靴1足の条件を記録して先に署名した Open Checkout Mandate です。最後の payment.reference でそのハッシュを指定し、別の購入内容への承認と組み合わせられないようにしています。

この時点の openPayment は、条件と公開鍵を入れたオブジェクトです。issueMandate(openPayment, provider.privateKey) に渡して AI エージェントの提供会社の秘密鍵で署名します。この関数の中で、何を署名しているのかを見ます。

②(2)SD-JWT にして、AI エージェントの提供会社の秘密鍵で署名する

ここでは、JSON データに署名を付けて文字列にする JWT (JSON Web Token) を使います。SD-JWT はその仕組みを使い、相手に渡すデータを選びつつ、署名との整合性を検証できる形式です。

この実装では Mandate を「開示データ」として分離し、そのハッシュを含む JWT に署名します。検証者には JWT と開示データを一緒に渡します。

@sd-jwt/core は、この分離と組み立てを担当します。src/sd-jwt.ts では、ライブラリに署名・検証・ハッシュ計算の関数を渡しています。signing が true のときは秘密鍵で発行し、検証時は公開鍵を渡します。

TypeScript
function instance(key: KeyObject, signing = false) {
  return new SDJwtInstance<Record<string, unknown>>({
    signAlg: 'ES256',
    hashAlg: 'sha-256',
    allowedDisclosureHashAlgorithms: ['sha-256'],
    saltGenerator: async () => randomBytes(16).toString('base64url'),
    hasher: async (input, algorithm) => {
      requireValid(algorithm === 'sha-256', 'Only sha-256 is supported');
      return new Uint8Array(Buffer.from(hash(input), 'base64url'));
    },
    signer: signing ? async (input) => signature(input, key) : undefined,
    verifier: async (input, signed) => signatureMatches(input, signed, key),
  });
}

ES256 は電子署名の方式、sha-256 はハッシュの方式です。saltGenerator が返す乱数は、開示データのハッシュを作るときに使います。signature と signatureMatches は、Node.js の暗号機能を呼ぶ関数です。

秘密鍵で署名する処理を見る

src/crypto.ts の signature は次の実装です。sign は node:crypto から読み込みます。ieee-p1363 は、ES256 の署名を JWT が要求するバイト形式で出力する指定です。

TypeScript
export function signature(input: string, privateKey: KeyObject): string {
  return sign('sha256', Buffer.from(input), {
    key: privateKey,
    dsaEncoding: 'ieee-p1363',
  }).toString('base64url');
}

Mandate を発行する issueMandate は、先ほどの openPayment を mandate、AI エージェントの提供会社の秘密鍵を key として受け取ります。AI エージェントの提供会社の署名では binding を指定せず、次のようにライブラリの issue を呼びます。

TypeScript
return instance(key, true).issue(
  // AP2 の承認内容を格納する。binding は AI エージェントの署名を前段の承認と結び付ける値。
  { delegate_payload: [mandate], ...binding },
  // 配列の0番目にある Mandate を開示データへ分離し、そのハッシュを署名対象にする。
  { delegate_payload: { _sd: [0] } },
  { header: { typ: binding ? 'kb+sd-jwt' : 'example+sd-jwt' } },
);

delegate_payload は承認内容を入れる配列です。_sd: [0] は、この配列の0番目にある Mandate を開示データとして分離する指定です。利用者の承認内容を JWT にそのまま埋め込む代わりに、内容を照合するハッシュを署名で保護します。

typ は署名付きデータの種類を示します。事前承認には、公式の Mandate 記載例と同じ検証用の型名 example+sd-jwt を指定します。AI エージェントが作る確定注文への承認には kb+sd-jwt を指定します。

発行される文字列は 署名付きJWT~開示データ~ という形で、末尾の ~ も区切り記号です。この実装では、購入条件を含む Mandate 全体を開示します。データを読めなくする暗号化ではありません。

③ 店の注文に署名する

次に、店が赤い靴1足・9,800円の注文を提示します。src/scenario.ts では、注文を店の秘密鍵で署名します。

TypeScript
const orderJwt = signJwt(order, merchant.privateKey);

signJwt は、注文を JWT にして店の鍵で署名する処理です。

④ AI エージェントが事前承認と注文を結び付けて署名する

③の署名付き注文からハッシュを計算し、Closed Checkout Mandate に記録します。ソースコードの型名 ClosedCheckout は Closed Checkout Mandate を表します。

TypeScript
const checkoutHash = hash(orderJwt);
const closedCheckout: ClosedCheckout = {
  vct: 'mandate.checkout.1',
  checkout_jwt: orderJwt,
  checkout_hash: checkoutHash,
  ...validity,
};

Closed Payment Mandate の transaction_id にも同じ checkoutHash を入れるため、購入内容と支払いが同じ注文を参照します。

AI エージェントは Closed Checkout Mandate と Closed Payment Mandate に、自分の秘密鍵で署名します。presentMandate は、それぞれの事前承認のハッシュも署名対象に含めます。

購入内容なら Open Checkout Mandate、支払いなら Open Payment Mandate のハッシュです。

TypeScript
const leaf = await issueMandate(closed, agentKey, {
  // 元の承認を差し替えられないように、事前承認の署名と開示データ全体を参照する。
  sd_hash: hash(open),
  aud: audience,
  nonce,
  iat: closed.iat,
});
// dSD-JWT は ~ が2つ続く位置で各段を区切る。事前承認の末尾 ~ は1つ省く。
return `${open.slice(0, -1)}~~${leaf}`;

sd_hash が事前承認への参照です。aud は宛先、nonce は検証者が今回の要求に対して発行したランダムな値です。これらも AI エージェントの署名に含めるので、承認データだけを別のものに差し替えたり、宛先を書き換えたりすると検証を通りません。

末尾では、事前承認と確定注文への承認を ~~ で連結しています。こうして、AI エージェントの提供会社が署名した事前承認と、AI エージェントが署名した確定注文への承認を一緒に渡します。AP2 では、委任を表すためにこの形式を使います。

⑤(1)公開鍵で署名を確かめ、9,800円の購入を許可する

受信時は、発行時と同じ SDJwtInstance に公開鍵を渡します。src/sd-jwt.ts の verifySdJwt で行う、ライブラリの呼び出しは次のとおりです。

TypeScript
return await instance(key).verify(token, { currentDate: now, skewSeconds: 0 });

verify は署名に加え、開示データのハッシュが署名付き JWT の参照と一致するかを検証します。中身をデコードして読めるだけでは、承認が正しいとは扱いません。

委任の検証を担う verifyChain は、AI エージェントの提供会社の公開鍵で事前承認を検証します。その cnf.jwk を AI エージェントの公開鍵として使い、確定注文への承認を検証します。

購入内容では Open Checkout Mandate と Closed Checkout Mandate を検証します。支払いでは Open Payment Mandate と Closed Payment Mandate を検証します。事前承認への参照や宛先も verifyChain で照合します。

店の署名や購入条件、使用済み状態は src/verifier.ts で検証します。SD-JWT ライブラリによる署名検証と、これらの判定を組み合わせると、正常な注文は次の結果になりました。

検証メモ:正常な購入の実行結果
Text
正常な9,800円の購入: ALLOW (complete)

赤い靴1足で、9,800円は上限1万円以内です。署名と条件の検証を通ったため、購入要求が許可されました。実際の決済は行っていません。

⑤(2)正しい署名でも、10,001円の購入は拒否される

次は、同じ「上限1万円」の承認に対して、注文額だけを10,001円にします。demo.ts の実験で変えるのは、次の注文データです。

TypeScript
const overBudgetOrder = scenario.createOrder(10001, 'shoe-red');

この注文にも店が署名し、AI エージェントが Closed Checkout Mandate と Closed Payment Mandate を作って署名します。

この実験では検証関数を新しく作り、使用済みの記録が空の状態から始めます。先ほどの購入で承認が使用済みになった影響を受けずに、金額条件による拒否を試すためです。

実行結果は次のとおりです。

検証メモ:上限を超えた購入の実行結果
Text
署名は正しいが10,001円: DENY (payment_conditions)

DENY は拒否、payment_conditions は支払条件の判定です。npm run demo -- --trace で途中経過を見ると、承認の署名・委任と店の署名、注文の参照は通過し、金額条件で止まっています。

金額を判定する src/conditions.ts には、次の処理があります。amount.amount は注文に対する支払額、constraint.min と constraint.max は承認された金額の下限と上限です。

TypeScript
requireValid(
  amount.amount >= constraint.min && amount.amount <= constraint.max,
  'Amount exceeds approved range',
);

requireValid は条件が満たされなければ検証を中断します。10,001円は上限の10,000円を超えるため、署名が正しくても購入は拒否されます。

⑤(3)予算内でも、青い靴の購入は拒否される

続いて、金額を9,800円に戻し、商品だけを青い靴に変えます。注文を作る行は次のとおりです。こちらも使用済みの記録が空の状態から、同じ上限と商品条件に照らして判定します。

TypeScript
const wrongProductOrder = scenario.createOrder(9800, 'shoe-blue');
検証メモ:承認された商品と違う購入の実行結果
Text
署名は正しいが青い靴: DENY (checkout_conditions)

金額は予算内でも、承認したのは赤い靴です。商品条件を調べる checkout_conditions で拒否されました。署名による改ざん検知と、購入条件の判定を組み合わせて、承認外の購入を止めています。

⑤(4)同じ承認の再利用と、署名前の誤承認も試す

同じ実行の後半では、正常な購入で使った検証関数にもう一度、赤い靴9,800円の購入を要求します。新しい署名を作っても、使用済み状態を調べる usage で拒否されます。

Text
同じ承認で再購入: DENY (usage)

一方、利用者が意図した1万円を、署名前に10万円と取り違えると、5万円の購入が許可されました。検証側には「利用者は1万円のつもりだった」という情報が届いていないためです。

Text
誤って10万円を承認し5万円で購入: ALLOW (complete)

承認後の改ざんや条件違反は検出できても、署名前の取り違えまでは分かりません。利用者が確認画面で承認する内容の正しさも必要です。

実運用に向けて必要な実装

この検証では、1つの承認で1回だけ購入する方針で再利用を防いでいます。AP2 のすべての承認を1回限りとする意味ではありません。使用済み記録はメモリ内にあるため、再起動すると失われます。実運用では複数サーバー間の共有と、決済の再試行時にも二重課金を防ぐ設計が必要です。

今回の検証ソースコードは商品1種類と、AI エージェントへの1段の委任に限定し、店と支払情報の提供者を1つの検証関数にまとめています。確認画面、実際の鍵の配布・保管・失効、支払用トークンの発行、Receipt は実装していません。公式の購入フローの完走や、実カード決済も未検証です。

導入を検討する店舗は、利用する購入サービスと決済事業者に、AP2 への対応状況を確認するところから始められます。その際は、利用者がどの画面で何を承認するか、店・支払情報の提供者・決済事業者がそれぞれどの条件を検証するか、再試行時の二重課金をどう防ぐかまで確かめる必要があります。

執筆後記WRITER'S NOTE
青木 亮磨

AP2 を調べていて、気になったことが二つあります。

一つは、承認が取れても、そこから実際に決済するには、また別のハードルがあることです。例えば、クレジットカードなら3Dセキュアをどうするのか。購入するタイミングで利用者がいないとなると、使える決済手段が限られてきそうです。

もう一つは、利用者が承認したことを忘れてしまうケースです。Trusted Surface でちゃんと承認していても、数日後に購入されたら「そんなの頼んでないんだけど」となることもありそうです。

これは AP2 だけの話ではありませんが、「ここで承認しています」と記録を見せれば納得してもらえるとも限りません。何を任せたかを思い出させたり、購入前に通知したり、その時点で取り消せるようにしたり。そういう使い勝手まで含めて考えるのが大事だと感じました。

青木 亮磨
全記事に執筆後記を掲載しています(編集方針)
出典・参照SOURCES
青木 亮磨
青木 亮磨 Ryoma Aoki
株式会社 STRACT / ソフトウェアエンジニア

カードイシュイングシステム開発と SRE の経歴を持ち、決済規格とカード業界の記事に一次知識で当たる。決済ラインを担当。

青木 亮磨の記事一覧 →