Skip to content

feat(acp): Agentic Commerce Protocol as a master-native plugin - #775

Open
pozylon wants to merge 5 commits into
masterfrom
pozylon/acp-checkout-master
Open

feat(acp): Agentic Commerce Protocol as a master-native plugin#775
pozylon wants to merge 5 commits into
masterfrom
pozylon/acp-checkout-master

Conversation

@pozylon

@pozylon pozylon commented Sep 2, 2026

Copy link
Copy Markdown
Member

Summary

Adds the Agentic Commerce Protocol (ACP 2026-04-17) checkout integration to master as a self-contained plugin — the checkout-session surface, product feed, .well-known discovery doc, signed outbound order webhooks, and a Stripe Shared Payment Token (SPT) delegated-charge path with agent-payment evidence capture.

The integration was originally built on v4.8.x. Because master diverged from v4.8.x back at v4.6.1 (211 commits) and re-architected the plugin system, this is a re-implementation against master's model, not a port of the old files.

Architecture (master-native)

  • New packages/plugins/src/acp/ — a standalone IPlugin. Endpoints are declarative WHATWG routes (RequestResponse, mounted once for both Express and Fastify via the @whatwg-node bridge); outbound order webhooks wire in onRegister.
  • It registers no payment adapter — it drives services.orders.checkoutOrder({ paymentContext }) through whichever provider is configured. Nothing in the ACP layer assumes Stripe: the delegated token flows generically as paymentContext.{acpToken, acpHandlerId}, and PSP-specific handling lives in the adapter. A config-driven allowlist (ACP_PAYMENT_ADAPTER_KEYS) + handler advertisement (ACP_PAYMENT_HANDLER_*) make adding PayPal/Adyen a pure adapter add.
  • Stripe adapter extended (payment/stripe/adapter.ts + stripe.ts) with the SPT charge + an evidence trail (delegated-token reference, resulting charge id, API version, timestamp) persisted on the order payment info for merchant-of-record dispute defense. SPT is pinned to Stripe 2026-04-22.preview; the base client stays on master's stable 2026-08-26.dahlia.
  • Registered in presets/all.ts — self-gates via onRegister, so it stays inert until UNCHAINED_ACP_API_KEY is set.

Endpoints

POST /acp/checkout_sessions · GET|POST /acp/checkout_sessions/:id · POST /acp/checkout_sessions/:id/{complete,cancel} · GET /acp/feed.jsonl · GET /.well-known/acp.json

Every request requires Authorization: Bearer + API-Version: 2026-04-17; every POST also requires Idempotency-Key.

Validation

  • tsc --build green (from a confirmed-green clean-master baseline)
  • ESLint clean
  • 7 ACP unit tests pass — including proofs that the payment layer is genuinely PSP-agnostic (a non-Stripe adapter/handler configured purely via env is accepted, with Stripe given no special treatment)
  • Full plugins suite (115 tests) passes — the Stripe adapter change introduces no regression

Deferred (intentional)

  • Durable idempotency + webhook dedupe. Both are process-local (single-instance) for now — flagged in the plugin README. A shared persistent store + durable delivery queue is tracked as a payment-system-wide follow-up, not scoped to this PR.
  • DB-backed integration test. Validation here is build + unit + full-suite regression; an end-to-end ACP checkout test (guest cart → SPT charge via Stripe test-mode granted token → order) can follow.

Notes

  • SPT is still Preview/Beta at Stripe (no GA as of 2026-09) — reflected in the README/config guidance.
  • See packages/plugins/src/acp/README.md for the full config surface.

Port the ACP checkout integration (originally built on v4.8.x) onto master's IPlugin/PluginRegistry model.

- New packages/plugins/src/acp/ standalone IPlugin: checkout-session, product-feed and .well-known endpoints as declarative WHATWG routes; outbound order webhooks wired in onRegister; PSP-agnostic handler + adapter allowlist. Registers no payment adapter — it drives services.orders.checkoutOrder through the configured provider.

- Extend the Stripe adapter (adapter.ts/stripe.ts) with the ACP Shared Payment Token charge + agent-payment evidence capture (persisted on the order payment info). SPT stays pinned to Stripe 2026-04-22.preview; the base client stays on stable 2026-08-26.dahlia.

- Register ACPPlugin in presets/all.ts (self-gates when UNCHAINED_ACP_API_KEY is unset).

Idempotency + webhook dedupe remain in-process (durable cluster-wide storage is a payment-system-wide follow-up). Build + lint green; ACP unit tests (7) + full plugins suite (115) pass.
Exercise the ACP plugin end-to-end against the live engine (HTTP -> guest cart -> order -> serializer): well-known discovery, auth (bearer/API-Version/Idempotency-Key), session create/get/update/cancel + terminal state, idempotent replay + conflict, complete validation, and the product feed.

Enables ACP in .env.tests and points the provider at the already-registered cryptopay GENERIC adapter — the SPT charge needs real Stripe creds, but the session lifecycle is adapter-agnostic (this also exercises the PSP-agnostic path).
Guarded on STRIPE_SECRET (like plugins-stripe.test.js): mints a real test SPT via Stripe's test-helper (/v1/test_helpers/shared_payment/granted_tokens, 2026-04-22.preview), completes an ACP checkout with it, and asserts the order confirms with the payment PAID (pi_ transaction) and the agent-payment evidence trail (token id + SPT api version) persisted on the order payment. Inert in the hermetic CI default (STRIPE_SECRET empty).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant