Files
2026-05-27 23:29:53 +01:00

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-adyen at packages/universal/payment-provider-adyen/, version 0.1.0, universal tier, sideEffects: false.
  • Exports class AdyenProvider implements PaymentProvider with name: "adyen", plus the AdyenConfig type (extends MerchantKeyConfig from the slim core).
  • Re-uses the slim core's httpRequest, composeAbortSignal, MerchantKeyConfig, RandomIdFn, PaymentProviderError, PAYMENT_PROVIDER_CODES, httpError, and parseError surfaces — no duplication.
  • Subpath exports: . (root), ./adyen/adyen, ./types/adyen.
  • 100% test coverage + sibling .md per implementation file + root README.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 (webhookHmacKey on MerchantKeyConfig is plumbed through but verification logic stays in @suaveplan/payment's WebhookVerifier — same as every other sibling).
  • OpenTelemetry spans / counters — deferred until @suaveplan/payment itself 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 recording FetchLike closure. Every assertion targets either a request the closure recorded (URL, method, headers, body) or the typed PaymentIntent / Refund shape returned from the provider.
  • src/types/adyen.test.ts — type-only smoke test confirming AdyenConfig extends MerchantKeyConfig and accepts the documented options.
  • tests/dep-graph.test.ts — asserts no node:, bun:, fs, path, stream, net, http, or crypto imports anywhere in src/.
  • §13 gate set in full: lint + typecheck + test + coverage (100/100/100/100)
    • build + banned-pattern scan + barrel-shape scan + runner-import scan
    • missing-.md scan + README check + bare-version check + subpath-export count + security scan.