6.4 KiB
add-payment-provider-adyen — sibling adapter for the slim payment-providers core
Package: @suaveplan/payment-provider-adyen
Status: PROPOSED
Type: sibling-package landing (follow-up to 2026-05-17-slim-payment-providers)
- Spec delta: ./specs/payment-provider-adyen/spec.md
Why
2026-05-17-slim-payment-providers reshaped @suaveplan/payment-providers
into a foundation-only package and removed five concrete adapter classes
(Stripe, PayPal, Square, Mollie, Adyen). The slim-down's archival proposal
explicitly names the follow-up wave: each removed adapter re-lands as a
sibling @suaveplan/payment-provider-* package depending on the slim
core. This change ships the Adyen sibling.
Adyen is the merchant-account-context adapter — its constructor takes an
apiKey plus a merchantAccount string that is injected into every
modification request body (captures, cancels, refunds) per Adyen's
Checkout API v71 contract. It also generates a default reference value
via an injectable RandomIdFn when the caller omits idempotencyKey,
keeping the package universal-tier (no crypto.randomUUID() at module
scope, no node:crypto).
What changes
ADDED
- New package
@suaveplan/payment-provider-adyenatpackages/universal/payment-provider-adyen/, version0.1.0, universal tier,sideEffects: false. - Exports
class AdyenProvider implements PaymentProviderwithname: "adyen", plus theAdyenConfigtype (extendsMerchantKeyConfigfrom the slim core). - Re-uses the slim core's
httpRequest,composeAbortSignal,MerchantKeyConfig,RandomIdFn,PaymentProviderError,PAYMENT_PROVIDER_CODES,httpError, andparseErrorsurfaces — no duplication. - Subpath exports:
.(root),./adyen/adyen,./types/adyen. - 100% test coverage + sibling
.mdper implementation file + rootREADME.md.
MODIFIED
- None — pure additive landing.
REMOVED
- None — the removal happened in the slim-down change.
Source mapping
| Surface | Source | Status |
|---|---|---|
AdyenProvider class |
../suaveplan/packages/universal/payment-providers/src/adyen.ts |
ported |
AdyenConfig (extends MerchantKeyConfig) |
../suaveplan/packages/universal/payment-providers/src/types/provider.types.ts (MerchantKeyConfig) |
re-typed as a sibling-local extension |
| Status / refund / payment-method mapping | ../suaveplan/packages/universal/payment-providers/src/adyen.ts L31-L86 |
ported verbatim |
crypto.randomUUID() default reference |
upstream L113 | replaced with injectable RandomIdFn (universal-tier; defaults to crypto.randomUUID() only when globalThis.crypto?.randomUUID exists, otherwise required) |
| Test suite (24 cases) | ../suaveplan/packages/universal/payment-providers/src/adyen.test.ts |
ported; vi.spyOn(globalThis, "fetch") rewritten to a real recording FetchLike closure (rule: no mocks) |
| Co-located docs | ../suaveplan/packages/universal/payment-providers/src/adyen.md |
rewritten to meet genesis §4 (Purpose ≥2 sentences, Features, Basic + Advanced usage, API Reference, Implementation Notes — ≥200 words excl. code) |
Parity Checklist
| Capability | Upstream | Migrated | Notes |
|---|---|---|---|
createIntent POST /payments with merchantAccount + reference + captureDelayHours |
✅ | ✅ | reference auto-generated via injectable randomId when idempotencyKey is omitted |
confirmIntent POST /payments/{id}/details |
✅ | ✅ | |
captureIntent POST /payments/{id}/captures |
✅ | ✅ | merchantAccount in body |
cancelIntent POST /payments/{id}/cancels |
✅ | ✅ | merchantAccount in body |
refund POST /payments/{id}/refunds (optional amount, optional idempotency key) |
✅ | ✅ | |
getIntent GET /payments/{id} |
✅ | ✅ | |
X-API-Key header on every call |
✅ | ✅ | |
Idempotency-Key header on createIntent/refund when supplied |
✅ | ✅ | |
| Status mapping (10 result codes + unknown → failed) | ✅ | ✅ | |
Refund status mapping (received → pending, else failed) |
✅ | ✅ | |
Payment-method mapping (scheme → card, else wallet) |
✅ | ✅ | |
mock / vi.spyOn test scaffolding |
✅ | ⬆️ | rewritten to use a real recording FetchLike closure — @suaveplan/payment-providers already exposes the seam, so no mocks are needed |
crypto.randomUUID() at module scope |
✅ | ⬆️ | replaced with injectable RandomIdFn — keeps the package universal-tier and deterministic in tests |
Sibling .md (≥200 words, mandatory sections) |
🚫 (upstream only had 20 lines) | ⬆️ | rewritten to satisfy genesis §4 |
| Subpath exports per feature module | 🚫 (single barrel) | ✅ | added ./adyen/adyen and ./types/adyen |
Test-runner imports via @suaveplan/testing/runner |
🚫 (upstream used @suaveplan/testing) |
✅ | rule 11 |
PaymentProviderError raised on every failure path |
✅ | ✅ | via httpRequest from the slim core |
Out of scope
- Webhook signature verification (
webhookHmacKeyonMerchantKeyConfigis plumbed through but verification logic stays in@suaveplan/payment'sWebhookVerifier— same as every other sibling). - OpenTelemetry spans / counters — deferred until
@suaveplan/paymentitself wires OTel (matches the slim core deferral). - Direct Storybook stories — the package has no React surface.
Impact
Affected dependents
Run bunx turbo ls --filter=...@suaveplan/payment-provider-adyen to enumerate.
No dependents (verified by bunx turbo ls --filter=...@suaveplan/payment-provider-adyen).
This is a new sibling-leaf package; consumers opt in by adding it to their
own package.json.
Verification approach
src/adyen/adyen.test.ts— 24 cases ported from upstream, run against a real recordingFetchLikeclosure. Every assertion targets either a request the closure recorded (URL, method, headers, body) or the typedPaymentIntent/Refundshape returned from the provider.src/types/adyen.test.ts— type-only smoke test confirmingAdyenConfigextendsMerchantKeyConfigand accepts the documented options.tests/dep-graph.test.ts— asserts nonode:,bun:,fs,path,stream,net,http, orcryptoimports anywhere insrc/.- §13 gate set in full: lint + typecheck + test + coverage (100/100/100/100)
- build + banned-pattern scan + barrel-shape scan + runner-import scan
- missing-
.mdscan + README check + bare-version check + subpath-export count + security scan.