Skip to content

feat: PaymentClient + buildPaymentHeaders (Phase 2)#144

Merged
badjer merged 7 commits intomainfrom
feat/payment-client
Mar 31, 2026
Merged

feat: PaymentClient + buildPaymentHeaders (Phase 2)#144
badjer merged 7 commits intomainfrom
feat/payment-client

Conversation

@badjer
Copy link
Copy Markdown
Contributor

@badjer badjer commented Mar 31, 2026

Summary

Adds PaymentClient class and buildPaymentHeaders() utility for programmatic payment authorization via the meta-API. Refactors protocol handlers to use them.

New exports from @atxp/client:

  • PaymentClient — calls /authorize/{protocol} on accounts, resolves protocol via protocolFlag, returns opaque credential
  • buildPaymentHeaders(result, originalHeaders?) — builds protocol-specific headers from credential (X-PAYMENT for x402, Authorization: Payment for mpp)
  • AuthorizeResult{ protocol, credential } interface

Handler refactor:

  • X402ProtocolHandler and MPPProtocolHandler now use PaymentClient.authorize() instead of inline fetch calls
  • Both use buildPaymentHeaders() instead of private buildRetryHeaders()
  • Removes duplicated auth header building and request body construction

Why: Phase 2 (LLM settlement over protocol rails) needs LLM to call authorize + settle separately. LLM uses PaymentClient for the authorize step and ProtocolSettlement from @atxp/server for the settle step. The same PaymentClient is also used internally by the fetcher's protocol handlers.

Test plan

  • 49 tests pass (34 handler + 15 PaymentClient)
  • LLM integration with PaymentClient + ProtocolSettlement

🤖 Generated with Claude Code

badjer and others added 7 commits March 31, 2026 11:25
Add PaymentClient class with authorize() method that centralizes the
/authorize/{protocol} call pattern. Resolves protocol via protocolFlag,
builds connection token auth headers, constructs protocol-specific
request bodies, returns opaque credential.

Add buildPaymentHeaders() that takes an AuthorizeResult and builds
protocol-specific headers (X-PAYMENT for x402, Authorization: Payment
for mpp). Extracted from protocol handlers so it can be used by
other callers (e.g. LLM service).

Refactor X402ProtocolHandler and MPPProtocolHandler to use PaymentClient
and buildPaymentHeaders instead of inline authorize logic. Removes
duplicated auth header building and fetch call patterns.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add authorize(params: AuthorizeParams) as a required method on the
Account interface. Each account implementation handles authorization
according to its capabilities:

- ATXPAccount: calls /authorize/{protocol} on accounts service via HTTP.
  For ATXP, injects connection token client-side into credential.
  For X402, returns paymentHeader. For MPP, returns credential.

- BaseAccount: X402 signs Permit2 locally via x402/client. ATXP executes
  payment via makePayment() and returns tx evidence as credential.

- SolanaAccount, PolygonServerAccount, PolygonBrowserAccount,
  BaseAppAccount, WorldchainAccount: ATXP executes payment and returns
  evidence. Other protocols throw unsupported.

- TempoAccount: MPP executes payment and returns evidence. Other
  protocols throw unsupported.

PaymentClient simplified to a thin protocol resolver that delegates
to account.authorize(). All HTTP/signing logic now lives in the account
implementations where it belongs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
account.authorize() now handles signing per account type — ATXPAccount
calls the accounts service, BaseAccount signs locally. The handler
no longer needs its own fallback path, getLocalSigner(), or
ensureCurrencyIfNeeded(). This prevents ATXPAccount from incorrectly
attempting local signing (it has no local key).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add AuthorizationError class to @atxp/common with statusCode and
errorCode fields. ATXPAccount.authorize() now throws AuthorizationError
with the error code from the accounts service response (e.g.
INSUFFICIENT_BALANCE, SPEND_LIMIT_EXCEEDED).

MPP handler uses instanceof AuthorizationError instead of fragile
string matching on error messages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ationAccountId, credential parsing

- PaymentClient: mark accountsServer and fetchFn as deprecated (no
  longer used since authorization delegates to account.authorize())
- ProtocolSettlement: accept destinationAccountId in constructor for
  ATXP settle (the server/LLM's own account ID)
- ProtocolSettlement: parse self-contained ATXP credential JSON to
  extract sourceAccountId, sourceAccountToken, and options instead
  of requiring external SettlementContext
- Export AuthorizationError from @atxp/common index

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… in base

- serverTestHelpers mock Account was missing the new authorize method
- BaseAccount.authorize() x402 path needs explicit null check and cast
  for paymentRequirements parameter
- Add x402-client.d.ts type shim to atxp-base (same as atxp-client)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@badjer badjer merged commit 1c63f53 into main Mar 31, 2026
1 check passed
@badjer badjer deleted the feat/payment-client branch March 31, 2026 22:36
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