Agentic Commerce Protocol (ACP) の対応
- Dominant language
- PHP
- Stars
- 788
- Forks
- 719
- Avg merge
- 4d 4h
- Merged PRs (30d)
- 39
Description
### 概要 (Overview)
OpenAI と Stripe が共同策定する **Agentic Commerce Protocol (ACP)** への対応を行う。AI エージェント (ChatGPT、Claude 他) が EC-CUBE 上の商品をエージェント経由で購入完了できるようにする。
関連 issue: #6574 (UCP 対応)、**#6777 (ACP/UCP 共通基盤・前提依存)**、**#6794 (Product Feed / Catalog 配信基盤・論理的前段)**。両プロトコル共通の `CheckoutSession`・住所マッピング・OAuth 2.0 認証基盤は #6777 で整備する。OAuth 2.0 認可サーバーは [eccube-api4](https://github.com/EC-CUBE/eccube-api4) (`ec-cube/api42`) を前提依存とする。
### 期待する内容 (Expect) / 要望 (Requirement)
- AI エージェント (まずは ChatGPT Instant Checkout) から ACP 経由で購入完了できる
- 決済は `stripe-payment-plugin` を通じた Shared Payment Token のリディームで完了する
- 既存の `PurchaseFlow` (在庫・税・送料・ポイント) を再利用し、エージェント経由購入でも整合性を保つ
### 参照仕様
- **ACP リポジトリ: https://github.com/agentic-commerce-protocol/agentic-commerce-protocol** — 最新安定版 `2026-04-17` (RFC + OpenAPI + JSON Schema、Apache 2.0、Status: beta)
- Checkout API: `spec/2026-04-17/openapi/openapi.agentic_checkout.yaml`
- Webhook: `spec/2026-04-17/openapi/openapi.agentic_checkout_webhook.yaml`
- Delegate Payment: `spec/2026-04-17/openapi/openapi.delegate_payment.yaml`
- JSON Schema: `spec/2026-04-17/json-schema/schema.agentic_checkout.json`
- [agenticcommerce.dev](https://agenticcommerce.dev/) — 概念とエコシステム
- [Stripe Agentic Commerce Documentation](https://docs.stripe.com/agentic-commerce) — Shared Payment Token、決済ハンドラ
### 対象ブランチ
**`4.4` (Symfony 7 対応版 / 現行メイン開発ブランチ)**
---
## `stripe-payment-plugin` との棲み分け
| 責務 | 配置 |
|------|------|
| ACP REST エンドポイント (`/checkout_sessions` 等) | 本体 |
| ACP データモデル (Cart / Order / Address) と EC-CUBE エンティティのマッピング | 本体 |
| `CheckoutSession` エンティティ・リポジトリ | 本体 (UCP #6574 と共通化、#6777) |
| `PurchaseFlow` 連携 (税・送料・在庫) | 本体 |
| ACP 認証 (OAuth 2.0 + 署名) | 本体 + **eccube-api4** (OAuth2 認可サーバー) を前提依存。共通基盤 #6777 経由で利用 |
| **決済ハンドラ抽象 (`AcpPaymentHandlerInterface` 等)** | **本体** |
| **Shared Payment Token のリディーム (`PaymentIntent` 生成)** | **`stripe-payment-plugin`** |
| Stripe Webhook ハンドリング (注文ステータス更新) | `stripe-payment-plugin` |
→ 将来 PayPay 等が ACP の決済ハンドラを実装することを想定し、本体は決済プロバイダ非依存に保つ。ただし ACP の **Shared Payment Token は現時点で Stripe 固有実装**であり、他プロバイダ対応は各 PSP 自身が ACP token プロバイダ機能を実装することが前提 (国内 PSP の ACP 対応は未確認)。
---
## 優先スコープ
### Phase 1: Checkout コア (最優先)
ACP Checkout API は create / update / **retrieve (GET)** / complete / **cancel** の **5 エンドポイント**で構成される (正準パスは `/checkout_sessions`。EC-CUBE 側のベースパス例 `/agentic-commerce` は任意で、エージェントは discovery の `api_base_url` から解決する。パスをハードコードせず `RequestContext` から生成する):
- [ ] `CheckoutSession` エンティティ (#6777 共通設計を利用)
- [ ] `POST /checkout_sessions` — セッション作成 (`createCheckoutSession`)
- [ ] `POST /checkout_sessions/{id}` — セッション更新 (`updateCheckoutSession`、配送先変更等で税・送料再計算)
- [ ] `GET /checkout_sessions/{id}` — セッション取得 (`getCheckoutSession`)
- [ ] `POST /checkout_sessions/{id}/complete` — 注文確定 (`completeCheckoutSession`)。**単発の同期確定ではなく「中断→再開」する状態機械**。EMV-3DS が必要な場合は HTTP 200 + `status: authentication_required` + `authentication_metadata` を返し (エラーではない)、エージェントが 3DS を完了して `authentication_result` を付けて再度 complete を呼ぶ。状態遷移・在庫引当の保持/回収・トランザクション境界は共通基盤 #6777 の `AgentCheckoutCompletionService` へ委譲する
- [ ] `POST /checkout_sessions/{id}/cancel` — セッション取消 (`cancelCheckoutSession`、完了/取消済でなければ取消)
- [ ] ACP Cart / Order / LineItem ↔ EC-CUBE `Cart` / `Order` / `OrderItem` マッピング
- [ ] 住所マッピング (#6777 共通化、姓名分割、ISO 3166-1 numeric→alpha-2 等)
- [ ] 金額単位変換 (#6777 `MinorUnitConverter`、JPY ゼロデシマル、bcmath)
- [ ] `PurchaseFlow` を介した税・送料・在庫検証
- [ ] `AcpPaymentHandlerInterface` の抽象定義 (#6777 の `AgentCheckoutPaymentHandlerInterface` を継承し `redeemSharedPaymentToken()` を追加)。**ハンドラの `authorize()` / `capture()` は `void` でなく `PaymentOutcome` (COMPLETED / REQUIRES_ACTION / PENDING / FAILED) を返す** (#6777 改訂)。`authorize()` は状態機械の入口で、初回 (Shared Payment Token のリディーム) と再開 (`authentication_result` で 3DS 完了) の双方から呼ばれる。REQUIRES_ACTION 時の `actionData` に `authentication_metadata` 相当を載せる
- [ ] **エラーレスポンス (2 系統)**: 下記参照
#### エラーモデル (2 系統)
ACP は UCP 同様に **2 系統のエラー**を持つ (`schema.agentic_checkout.json`)。両方を実装する:
| 種類 | HTTP | レスポンス | 例 |
|---|---|---|---|
| **プロトコルエラー** | 4xx / 5xx | `Error` オブジェクト | 不正な item ID (`error_400_invalid_item`)、認証失敗 |
| **ビジネスロジックエラー** | **200** | CheckoutSession 応答内の `messages[]` (`MessageError` / `MessageWarning` / `MessageInfo`、各々 `type` / `code` / `severity` / `content_type` / `content` 等) | 在庫切れ (`out_of_stock`)、決済拒否 (`payment_declined`)、価格不一致 |
→ 「在庫切れで HTTP 400」ではなく「**HTTP 200 + `messages[]` に `MessageError`**」が正しい。
#### 追加認証 (EMV-3DS) はエラーではなく中間状態 (2026-06-17 追記)
**EMV-3DS / 追加認証は上記いずれの「エラー」にも該当しない。** complete が返す**正常な中間状態**であり、`status: authentication_required` (HTTP 200) + `authentication_metadata` (acquirer_details / directory_server / flow_preference) で表現する (`openapi.agentic_checkout.yaml` の status enum / AuthenticationMetadata)。エージェントは `authentication_metadata` を使って browser-native 3DS2 を実行し、`authentication_result` (outcome / three_ds_cryptogram 等) を付けて **再度 complete** を呼ぶ。複数回呼ばれる状態機械である点が要 (詳細は共通基盤 #6777 「7. PaymentHandler 抽象 + complete 状態機械」)。
- **例外**: `authentication_required` のまま `authentication_result` なしで確定要求した場合のみ HTTP **400** `code: requires_3ds` (= プロトコルエラー)。この 400 判定・生成は本 issue (プロトコル層) の責務で、共通エラーコード enum には追加しない (`rfc.agentic_checkout.md:159-164`)。
- **正規化ステータスのマッピング**: ACP `authentication_required` → 共通基盤 #6777 の正規化 `requires_action`、ACP `complete_in_progress` / `in_progress` → `in_progress`。プロトコル固有の status 原文は `metadata` に保持する。
- **非同期決済 (in_progress)**: PSP が即時確定しない場合は `complete_in_progress` を返し、**Stripe Webhook** で確定通知を受けて `complete()` の再開経路で commit する (受注番号で Order を引くセッションレス IPN・`sample-payment-plugin::receiveComplete` と同型)。
### Phase 2: Authentication / Signing
OAuth 2.0 認可基盤は eccube-api4 を前提依存とし、共通基盤 #6777 の `AgentCommerceOAuth2Authenticator` 経由でトークン検証を行う。
- [ ] eccube-api4 に ACP 用 scope (`acp:checkout`) を登録 (eccube-api4 リポジトリ側で別 issue)
- [ ] 共通基盤 #6777 の OAuth2 認証ミドルウェアを ACP の inbound エンドポイントに適用
- [ ] `AcpMessageSigner` 実装。**ACP の署名は面ごとに方式が異なる** (一次仕様で確認済):
- **API 認証**: `Authorization: Bearer `
- **Webhook 検証**: **`Merchant-Signature` ヘッダ単独** = `t=,v1=<64_hex>`、**HMAC-SHA256(`timestamp + "." + raw_body`, 共有シークレット)**。不正/リプレイ/検証失敗は 401
- **Delegate payment / authentication**: `Signature` (detached JSON signature) + `Timestamp` を**別パラメータ**で送付。**署名アルゴリズムは out-of-band 告知**
- 公開鍵 (JWK) 広告と正式な request signing 仕様は ACP 側で **deferred (未策定)**。共通基盤 #6777 の `AgentCommerceMessageSigner` を**アルゴリズム差し替え可能**な形で実装する (UCP の EC P-256 JWK 固定とは異なる)
- **共有シークレット (HMAC) の保管**: Webhook 検証用の共有シークレットは `BaseInfo`/DB カラムに置かず、共通の鍵保管ディレクトリ `app/keystore/agent-commerce/` に保管する (環境変数でパス上書き可・Secrets Manager / Key Vault 連携。eccube-api4 の OAuth2 鍵と同方式)。鍵保管ディレクトリの新設・保護・`KeyProvider` 抽象は **#6797** で共通化
- [ ] マーチャント (OAuth2 client) の管理導線を eccube-api4 既存 UI に統合
### Phase 3 (別 issue 化済み / 検討)
- **Product Feed** → **#6794**。ACP の Feed は **加盟店 → OpenAI への push モデル**であり、本 issue では扱わない (詳細は #6794)
- Webhook (注文ステータスをエージェントへ通知): ACP `agentic_checkout_webhook` 準拠
- 管理画面: ACP 有効化、エージェント許可リスト、トランザクション一覧
#### ACP の discovery について (補足・一次仕様で確認済)
ACP の discovery は UCP と非対称で、二層に分かれる (`rfcs/rfc.discovery.md`):
- **商品 discovery = Product Feed を OpenAI へ push する中央集権モデル** (#6794)。merchant がホストする商品 well-known は存在しない。
- **プロトコル/capability discovery = `/.well-known/acp.json`** だが **Status: Proposal / unreleased** (リリース版 `2026-04-17` には未収録)。内容は `protocol` (version/supported_versions) / `api_base_url` / `transports` / `capabilities` のみで、**鍵・トークン・商品カタログは含めない**。Seller Platform ホスティング時は `merchant_id` を露出してはならない (列挙防止)。実装は forward-looking 扱い (released 化後に対応)。
- well-known は **RFC 8615 によりオリジンルート固定・1 オリジン 1 枚**。設置形態による制約は **共通基盤 #6777 「10. 設置形態と discovery 配信」** に準拠。
### スコープ外 (別タスクで進行中)
- **MCP transport (Native MCP)** — EC-CUBE MCP Server 実装は別タスクで進行中。統合点のみ設計時に考慮。
---
## 配送モデルのマッピング仕様 (ACP)
EC-CUBE 配送モデルと ACP `fulfillment_*` モデルの差異は **共通基盤 #6777 の「配送モデルのマッピング仕様」に準拠**。本 issue では ACP 固有のフィールド対応のみ記述する。
### Phase 1 機能制限 (共通基盤 #6777 と同じ)
| EC-CUBE 機能 | エージェント経由での扱い |
|---|---|
| 複数お届け先 | **非対応** (1 CheckoutSession = 1 `Shipping`) |
| 時間帯指定 | **非対応** |
| お届け希望日 | **非対応** (`delivery_estimate_min_days/max_days` のみ) |
| 着日不可日 / 温度帯フィルタ | 対象外 (EC-CUBE 標準未提供) |
### ACP `fulfillment_*` フィールドのマッピング
| ACP フィールド | EC-CUBE 側マッピング |
|---|---|
| `fulfillment_address` | 共通基盤 `AddressMappingService` 経由で `Shipping` 1 件作成 |
| `fulfillment_options[]` | 共通基盤 `FulfillmentOptionMapperInterface` で `(Delivery × DeliveryFee)` を展開 |
| `fulfillment_options[].id` | 内部 `Delivery.id` のシリアライズ (改ざん検知用 signed token 推奨) |
| `fulfillment_options[].name` | `Delivery.name` |
| `fulfillment_options[].carrier` | `Delivery.service_name` (任意) |
| `fulfillment_options[].cost` | `DeliveryFee.fee` を共通基盤 `MinorUnitConverter` で minor unit 変換 |
| `fulfillment_options[].delivery_estimate_min_days / max_days` | 注文明細の `ProductClass` が参照する **`DeliveryDuration.getDuration()` の最大値**から算出 |
| `selected_fulfillment_option` | 内部 `Delivery` に解決し `Shipping.Delivery` に設定 |
| 代引手数料 | **`PaymentOption` (`delivery_id`×`payment_id`) 経由で解決した `Payment.charge`** を ACP `totals` の手数料カテゴリに分離 (EC-CUBE の `Delivery` に手数料カラムは無い) |
### 別 issue 化候補 (Phase 1 スコープ外)
- 複数お届け先 (multi-shipping) / 時間帯指定 / お届け希望日
---
## 技術的考慮事項
1. **UCP との共通化**: `CheckoutSession`、住所マッピング、金額変換、PurchaseFlow 連携層は #6777 で共通モジュール化
2. **冪等性**: ACP は HTTP リトライ前提のため `Idempotency-Key` 対応必須 (特に二重 complete)。実体は**共通基盤 #6777 の DB 一意制約ベース store** (`dtb_agent_checkout_idempotency` の `unique(idempotency_key, subject)`) を consume する。Symfony Lock / cache に非依存で、マルチインスタンス (AWS 等) でも単一の共有 DB だけで越境・並行の二重実行を防ぐ
3. **セッション TTL とクリーンアップ**: 期限切れ `CheckoutSession` の定期削除コマンド (開発者向けオプション)
4. **メール通知**: エージェント経由購入時の購入者メール送信フロー設計
5. **app/Customize での拡張ポイント**: ACP マッピングをサイト独自属性で拡張できるように
6. **多通貨**: JPY 以外のゼロデシマル/小数通貨にも将来対応できる金額変換ユーティリティ
7. **PCI 範囲**: Shared Payment Token は加盟店側に生カード情報を渡さない設計だが、ログ出力時のマスキングを徹底
8. **MCP Server との統合点**: 別タスクの MCP Server 実装が固まり次第、ACP Native MCP transport を検討
9. **eccube-api4 前提依存**: inbound エンドポイントは eccube-api4 の OAuth2 認可サーバーを利用。未インストール時は 503 / 機能フラグ。詳細は #6777 参照
---
## テスト計画
レイヤーごとに「GA 待ち不要な層」と「Stripe sandbox が必要な層」が分かれる。
### Layer 0: 仕様適合性 (Spec Conformance / 要件トレーサビリティ)
`specifications/agentic-commerce-protocol/` 配下の一次仕様 (ACP `2026-04-17` / RFC は repo `main`) の各**規範要件 (MUST / MUST NOT / SHALL / REQUIRED)** を検証テストケースに紐付け、**「どの仕様要件を満たし、実装済みか」を追跡可能**にするためのレイヤー。Layer 1〜6 の機能テストとは独立した観点で、横断的に要件の充足状況をスナップショットする。
**方針:**
- **1 テストケース = 1 規範要件**。テストメソッドの docblock に**仕様書の GitHub URL** (該当行アンカー付き) を記載する。
- **assertion の第3引数 (失敗メッセージ) に仕様要件文をそのまま記載**する (例: `'MUST NOT re-execute side effects on replay'`)。テスト失敗時に**どの MUST 違反か**がメッセージから直接読め、レビュー時も「コード ↔ 仕様」を 1:1 で確認できる。
- **未実装の要件は `$this->markTestIncomplete()` で `incomplete` とする**。CI 上で「未充足要件」として可視化し、実装完了時に incomplete を外す運用とする。
- **要件カタログ (全 MUST 一覧) の網羅的な管理は別 issue** とし、本 issue にはテスト計画と**サンプル数本のみ**を示す。本テスト群はそのカタログに対する**実装状況の生きたスナップショット**として機能する。
- URL は版固定の参照とする (spec は `2026-04-17`、RFC はテキスト文面を正とする)。行番号は仕様更新で前後しうるため、参照は**要件文の文面**を正とする。
> **対象スコープ**: 本 issue は ACP Checkout / Webhook / delegate payment 認証が対象。Product Feed の規範要件は #6794、共通基盤 (`CheckoutSession` 正規化ステータス・住所/金額変換) の要件は #6777 のトレーサビリティに委ねる。
#### サンプルテスト
> 以下はパターンを示す**サンプル**。実装済みの要件はそのまま assert し、未実装は `markTestIncomplete()` とする。
**ACP Checkout 適合性 (冪等性・金額・注文確定・2 系統エラー):**
```php
// tests/Eccube/Tests/Service/AgentCommerce/Conformance/AcpCheckoutConformanceTest.php
/**
* ACP Agentic Checkout conformance.
*
* @see https://github.com/agentic-commerce-protocol/agentic-commerce-protocol/blob/main/rfcs/rfc.agentic_checkout.md
* (spec 2026-04-17; §6 Idempotency, §3 Conventions)
*/
class AcpCheckoutConformanceTest extends AbstractWebTestCase
{
public function testPostWithoutIdempotencyKeyIsRejected(): void
{
// §6.1: "A POST request without an Idempotency-Key header MUST be rejected"
// (400 / idempotency_key_required)
// @see https://github.com/agentic-commerce-protocol/agentic-commerce-protocol/blob/main/rfcs/rfc.agentic_checkout.md#L241
$client = $this->createClientWithScope('acp:checkout');
$client->request('POST', '/checkout_sessions', server: [], content: '{}'); // Idempotency-Key 無し
self::assertSame(400, $client->getResponse()->getStatusCode(),
'MUST: A POST request without an Idempotency-Key header MUST be rejected (idempotency_key_required).');
}
public function testReplayDoesNotReExecuteSideEffects(): void
{
// §6: "Servers MUST NOT re-execute side effects (e.g., payment capture,
// inventory reservation) on replay."
// @see https://github.com/agentic-commerce-protocol/agentic-commerce-protocol/blob/main/rfcs/rfc.agentic_checkout.md#L269
$this->markTestIncomplete('未実装: Idempotency-Key リプレイ時の副作用抑止 (二重 capture/在庫引当回避)');
}
public function testAmountsAreIntegerMinorUnits(): void
{
// §3: "Amounts MUST be integers in minor units."
// @see https://github.com/agentic-commerce-protocol/agentic-commerce-protocol/blob/main/rfcs/rfc.agentic_checkout.md#L59
$session = $this->mapper->toAcpSession($this->createOrder());
foreach ($session['totals'] as $total) {
self::assertIsInt($total['amount'],
'MUST: Amounts MUST be integers in minor units.');
}
}
public function testCompleteCreatesOrderWithRequiredFields(): void
{
// §4 / §5.x: complete "MUST create an order"; response MUST include
// status: completed and an order with id, checkout_session_id, permalink_url.
// @see https://github.com/agentic-commerce-protocol/agentic-commerce-protocol/blob/main/rfcs/rfc.agentic_checkout.md#L156
$this->markTestIncomplete('未実装: POST /checkout_sessions/{id}/complete の注文生成');
}
public function testOutOfStockReturnsHttp200WithMessageError(): void
{
// 2 系統エラー: ビジネスロジックエラー (在庫切れ) は HTTP 200 + messages[] の MessageError。
// プロトコルエラー (4xx/5xx + Error) にしてはならない。
// @see https://github.com/agentic-commerce-protocol/agentic-commerce-protocol/blob/main/spec/2026-04-17/json-schema/schema.agentic_checkout.json
$this->markTestIncomplete('未実装: 在庫切れ時の HTTP 200 + messages[] (MessageError)');
}
public function testServerRejectsInboundMarkdownWithRawHtml(): void
{
// §5: "Servers MUST validate inbound markdown and reject content
// containing raw HTML." (CommonMark, raw HTML 禁止)
// @see https://github.com/agentic-commerce-protocol/agentic-commerce-protocol/blob/main/rfcs/rfc.agentic_checkout.md#L213
$this->markTestIncomplete('未実装: inbound markdown の raw HTML 拒否');
}
}
```
**ACP Webhook 署名適合性:**
```php
// tests/Eccube/Tests/Service/AgentCommerce/Conformance/AcpWebhookConformanceTest.php
public function testWebhookVerifiesMerchantSignatureHmac(): void
{
// "Verify HMAC (Merchant-Signature) on webhook calls."
// Merchant-Signature 単独 = t=,v1=、HMAC-SHA256(timestamp + "." + raw_body)
// @see https://github.com/agentic-commerce-protocol/agentic-commerce-protocol/blob/main/rfcs/rfc.agentic_checkout.md#L327
// @see https://github.com/agentic-commerce-protocol/agentic-commerce-protocol/blob/main/spec/2026-04-17/openapi/openapi.agentic_checkout_webhook.yaml
$this->markTestIncomplete('未実装: Webhook の Merchant-Signature (HMAC-SHA256) 検証');
}
```
### Layer 1: ACP スキーマ契約テスト (外部接続なし)
- ACP リポジトリの OpenAPI / JSON Schema を `tests/fixtures/acp/` にベンダリング (Apache 2.0)、**`2026-04-17` を pin**
- `justinrainbow/json-schema` で 5 エンドポイントの request/response を validate
- **2 系統エラー** (`Error` / `messages[]` の `MessageError`) の双方を契約テストで網羅
- 仕様 bump 時のみ手動更新 (drift 検知)
### Layer 2: エンドポイント単体 (PHPUnit、認証/決済モック)
| エンドポイント | 検証ケース |
|---|---|
| `POST /checkout_sessions` | 正常系、必須欠落、不正通貨、不正商品 ID (→ HTTP 4xx の `Error`) |
| `POST /.../{id}` (更新) | 配送先変更による税・送料再計算、状態遷移バリデーション |
| `GET /.../{id}` (取得) | 存在確認、未存在で 404、他マーチャントのセッション遮断 |
| `POST /.../{id}/complete` | 注文確定、**Idempotency-Key** 重複、二重 complete の冪等性、`Order.agent_protocol=acp` 記録、**3DS challenge → `authentication_required` (HTTP 200) → `authentication_result` 付き再開 complete → completed**、**`authentication_result` 欠落で 400 `requires_3ds`**、**非同期 `complete_in_progress` → Webhook で確定** |
| `POST /.../{id}/cancel` | 取消、完了済/取消済での 4xx (Not cancelable) |
| 在庫切れ / 決済拒否 | **HTTP 200 + `messages[]` の `MessageError`** (プロトコルエラーにしない) |
`AcpPaymentHandlerInterface` を `InMemoryAcpPaymentHandler` でモック。
### Layer 3: マッピング層 (PHPUnit)
- ACP Cart/Order/LineItem ↔ EC-CUBE、共通基盤 `AddressMappingService` / `MinorUnitConverter` 経由
- 代引手数料の `PaymentOption`→`Payment.charge` 解決、配送日数の `DeliveryDuration.duration` max
### Layer 4: 認証・署名層 (eccube-api4 統合)
- `acp:checkout` scope での認可、scope 不足 → 403
- `AcpMessageSigner` 単体: **Webhook の `Merchant-Signature`** (`t=,v1=`、HMAC-SHA256) 検証、**delegate auth の detached `Signature`+`Timestamp`** (アルゴリズム out-of-band) のラウンドトリップ
- 不正署名・clock-skew 外・リプレイの reject
### Layer 5: Stripe sandbox 結合 (Stripe テストアカウント、nightly)
- Stripe **テストモード API キー** で Shared Payment Token 発行 → リディーム → `PaymentIntent` 確認
- `stripe-payment-plugin` にテスト用ハンドラスタブを実装し、本体テストはそれを経由
- 失敗系: token 期限切れ、金額不一致、3DS 認証要求の伝播
- API キーは stripe-payment-plugin の既存 DB 保存 (`plg_stripe_payment_config`) に合わせ、`.env` / CI Secrets は fixture 投入の一時受け渡しのみ
### Layer 6: モックエージェント E2E (Codeception)
- ACP リポジトリ提供のサンプルペイロードを fixture 化し、5 エンドポイントを順次実行
- `Order` 生成 + `agent_protocol=acp` 記録を確認
- **ChatGPT Instant Checkout 実機接続は GA 後の別タスク**
### CI 統合
- Layer 0 (仕様適合性): PR ごと。`incomplete` 件数を集計し「未充足要件」として可視化 (件数の増加は要 review)
- Layer 1〜4: PR ごと
- Layer 5 (Stripe): nightly (`workflow_dispatch` + cron)
- Layer 6: PR ごと smoke
## ロールバック方針
- `BaseInfo.acp_checkout_enabled` を `false` で全 checkout エンドポイント無効化 (#6777 で定義、default false。discovery / feed は別系統で常時公開/認証情報ガード)
- ルーティングを ConfigurableExtension で条件登録 (無効時 404)
---
## 追記 (2026-06-11): 実装からの確定事項
### インバウンド認証は Bearer / OAuth2(UCP と非対称)
- ACP のエージェント→merchant 認証は **merchant 発行の Bearer / api key(OAuth2)**。→ eccube-api4 [#188](https://github.com/EC-CUBE/eccube-api4/issues/188)(`client_credentials` 有効化 + `acp:*` scope 登録)が前提。
- 共通基盤の `AgentCommerceOAuth2Authenticator`(Symfony `AccessTokenHandlerInterface` 経由・api4 具象非依存・未導入時 503)を ACP firewall に配線する。
- 参考: **UCP は RFC 9421 署名ベースで api4 非依存**(#6574)。ACP/UCP で認証モデルが異なる点に注意。delegate payment は `Signature`(detached)+`Timestamp`+`Bearer`、Webhook は `Merchant-Signature` 単独(面別・既述)。
### 区分値マスタ化・共通基盤の再利用
- `Order.agent_protocol` / `CheckoutSession.status` 等は文字列でなく **マスタ + INTEGER FK**(`mtb_agent_protocol` / `mtb_checkout_session_status`)。#6777(PR #6825)で landing 済み。
- CheckoutSession 中核(shopping flow 再利用・`Cart.agent_owned` 隔離・ゲスト基準線・`messages[]` 系統)を再利用。ACP マッパー(`AcpCheckoutSessionMapper`)で ACP ⇔ 中立 DTO を変換。
---
## 追記 (2026-06-17): complete を「中断→再開」状態機械として再設計(#6777 追随)
共通基盤 #6777 で、追加認証 (EMV-3DS) はエラーでなく complete が返す正常な中間状態であり、complete は状態機械であることを確定した(#6777「7. PaymentHandler 抽象 + complete 状態機械」)。本 issue (ACP) は以下を前提に実装する:
- **complete は状態機械**。`status: authentication_required` (HTTP 200) + `authentication_metadata` を返し、エージェントが delegate_authentication で browser-native 3DS2 を実行 → `authentication_result` 付きで再開 complete → `completed`。状態遷移・在庫引当の保持/回収・トランザクション境界は #6777 `AgentCheckoutCompletionService` へ委譲する。
- **決済ハンドラの戻り値は `PaymentOutcome`** (COMPLETED / REQUIRES_ACTION / PENDING / FAILED)。`void` では「3DS が必要・authentication_metadata・pending」を表現できず Stripe が EMV-3DS を実装できないため。`AcpPaymentHandlerInterface` は #6777 基底を継承し `redeemSharedPaymentToken()` を追加。
- **正規化ステータス対応**: ACP `authentication_required` → `requires_action`、`complete_in_progress`/`in_progress` → `in_progress`(#6777 で `mtb_checkout_session_status` に `requires_action`/`in_progress` を追加)。差異 (`authentication_metadata`) は `actionData`/`metadata` で表現。
- **`requires_3ds` 400**: `authentication_result` 欠落のまま確定要求した場合のみ HTTP 400 (プロトコルエラー)。本 issue (controller 層) が判定・生成し、共通エラーコードには追加しない。
- **非同期確定**: `complete_in_progress` は Stripe Webhook で確定通知を受け `complete()` 再開経路で commit(セッションレス IPN・`OrderStateMachine` ガード)。GET ポーリングは不採用。
- **在庫確保期限**: 3DS 待ち中は在庫を引き当てたまま保持し、`eccube.yaml` の `eccube_agent_checkout_escalation_expire`(既定 15 分・EMV-3DS タイムアウト 10 分より大)超過で `findExpired` → rollback。
Contributor guide
Assessment
This issue has not been assessed yet.