118 lines
6.4 KiB
Markdown
118 lines
6.4 KiB
Markdown
# 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.
|