Repository navigation
v0.7.0
What's new
-
Async settlement path on
SettleResultandPaymentEnforcer. NewSettleResult::pending(tracker: …)factory andisPending()predicate, plus a constructor invariant that rejects the contradictory shape (success: truetogether with a non-empty tracker).PaymentEnforcernow returns 202 Accepted with a pendingPAYMENT-RESPONSEreceipt body and skips the inner handler when the facilitator returns a pending result — delivering paid content for an unsettled payment defeats x402. The path is opt-in from the facilitator side: the bundled synchronous facilitators (CoinbaseFacilitator,FakeFacilitator) keep emittingsuccess: true/success: falseand the existing 2xx / 402 flow is byte-identical. -
X402\Webhook\*primitives. The upstream surfaces required to verify the inbound notification that confirms an async settlement:WebhookEvent— value object carryingid,tracker,status,transaction,network,payer,amount, rawmeta, andsignedAt.WebhookStatus— string-backed enum with the canonical statuses plus anUnknownfallback (forward-compatible — adopters never crash on a status the facilitator added later).SignatureVerifier— Stripe-stylet=<unix>,v1=<hmac>header verification withhash_equals, strict-integer timestamp parse, and a configurable clock-skew window. Rejects malformed headers, mismatched secrets, tampered bodies, and replays outside the skew window.WebhookDedupStore— interface for the "claim webhook id once" guarantee. Concrete Redis / cache-backed implementations ship downstream insandermuller/laravel-x402alongside the inbound route and Laravel-event dispatch.InvalidWebhookException+InvalidWebhookSignatureExceptionfor adopters to branch on at the handler boundary.
-
PaymentOutcomeKind::SettlePending+DispatchingFacilitatorrouting. Outcome closures see the new kind when the wrapped facilitator returns a pending result.SettlePendingtakes precedence overSettleFailedwhen the tracker is non-empty, so the audit row reflects the actual disposition rather than a misleading failure.PaymentRowBuilder::pendingRow()produces aSTATUS_PENDINGrow withtransaction: nulland the tracker populated; thetrackercolumn is added uniformly to settled / rejected / pending rows. -
PaymentResponseCacheis pending-aware. ReadsPaymentResponse::isPending()directly off the wire and skips caching the 202 response — replaying a pending receipt would surface an unsettled payment as paid. Lost-receipt tradeoff (a dropped 202 in flight cannot be replayed from the cache) is documented inline. Synchronous 200s with non-pending receipts are unaffected. -
X402\Client\HdWallet— BIP-32 hierarchical-deterministic wallet. A per-process root key derives unlimited child wallets along the standard EVM pathm/44'/60'/0'/0/N(or any custom path). Signs EIP-712 typed data exactly likePrivateKeyWallet— produced signatures recover to the HD-derived address, byte-for-byte identical to a freshPrivateKeyWalletinitialised with the same derived key. Verified against the BIP-32 published test vectors (8 entries pinned intests/Fixtures/bip32-vectors.json).Use case: per-tenant signing keys in framework adapters. The HD root sits in env / KMS; child wallets are derived deterministically per tenant id, so adopters never persist a per-tenant key. The Laravel-side
TenantHdWalletResolverwiring lands insandermuller/laravel-x402next.Rejects malformed input: seeds shorter than 128 bits or longer than 512 bits, odd-length hex, non-hex chars, empty / malformed paths (
m/,m//, non-numeric components, mixed-apostrophe placement, child index ≥ 2^31).xprv/xpubimport/export is deferred to a follow-up minor on adopter pull.
Maintainability pass
-
PaymentResponseCacheconstructor — optional knobs moved intoPaymentResponseCacheOptions. See "Breaking changes" below. The required arguments are unchanged; only callers that passed any of the six optional knobs (version,ttl,prefix,logger,responseHeadersAllowList,resourceResolver) need to update. -
CoinbaseFacilitatorhygiene. Three duplicateddefaultHeadersforeach loops collapse intoapplyDefaultHeaders(). The 33-line, 3-level-nested discovery item parser extracts intoparseDiscoveryItem(), leavingdiscoverResources()six lines. -
38 new tests for previously-untested public API.
PayingClient(11 cases — pass-through, v1 / v2 wire negotiation precedence, error paths, EIP-712 domain resolution and fallback) andPaymentIdentifier(27 cases — generate / isValid / extractId / isRequired / wire envelopes). Also explicit per-method tests for the refactoredCoinbaseFacilitator::discoverResources()andsupported()so the extracted helpers stop relying on transitive coverage. -
X402\Support\Hexconsolidates four duplicatedhex2binStrictcopies across the scheme classes into one helper. No behaviour change. -
FakeFacilitator::verifyResults()/settleResults(). Recording arrays indexed in lockstep withverifyCalls()/settleCalls(). Useful for tests that togglerejectVerify('reason-A')mid-flight and need to confirm the right reason landed on the right call, or for verifying tracker propagation through the pending path.
Breaking changes
-
PaymentResponseCacheconstructor — six optional knobs moved into aPaymentResponseCacheOptionsDTO. The 10-parameter constructor was unwieldy and adding any further knob was itself a breaking change for positional and named-arg callers. Required arguments (cache,responseFactory,streamFactory,schemes) are unchanged. Migration:use X402\Server\PaymentResponseCache; +use X402\Server\PaymentResponseCacheOptions; $cache = new PaymentResponseCache( cache: $psr16, responseFactory: $psr17, streamFactory: $psr17, schemes: ['exact' => new ExactScheme()], - ttl: 7200, - prefix: 'app:idem:', - logger: $logger, + options: new PaymentResponseCacheOptions( + ttl: 7200, + prefix: 'app:idem:', + logger: $logger, + ), );
Full migration steps in
UPGRADING.md. -
PaymentEnforcerreturns 202 onSettleResult::pending(...)and skips the inner handler. Only affects adopters whoseFacilitatorClientreturns a pending result; synchronous facilitators see the existing 2xx / 402 flow unchanged. Adopters wiring async settlement should add aSettlePendingarm to anymatch ($outcome->kind) { ... }overPaymentOutcomeKind(or rely on thedefaultarm the 0.6.0 notes recommended for forward-compat).
Full Changelog: 0.6.0...0.7.0