7.0 KiB
Capability Spec Delta: payment-provider-adyen (add-payment-provider-adyen)
Package: @suaveplan/payment-provider-adyen
Status: SHIPPED
Every requirement below carries a
**Verified by:**line at draft time. This is a fresh capability — only## ADDED Requirementsis used.
ADDED Requirements
Requirement: AdyenProvider implements PaymentProvider with merchant-account context
The package SHALL export an AdyenProvider class implementing the
structural PaymentProvider interface from @suaveplan/payment. The
class SHALL accept an AdyenConfig constructor argument that extends
the slim-core MerchantKeyConfig and carries apiKey,
merchantAccount, optional baseUrl, timeoutMs, signal, fetch,
webhookHmacKey, and an optional randomId: RandomIdFn for default
reference generation. The provider SHALL:
- POST
${baseUrl}/paymentsforcreateIntent, includingmerchantAccount,amount,reference(caller-suppliedidempotencyKeyor the result ofrandomId()), optionalcaptureDelayHours: 0whencaptureMethod === "manual",shopperStatement,returnUrl,paymentMethod.storedPaymentMethodId, andmetadata. - POST
${baseUrl}/payments/{id}/{details,captures,cancels,refunds}forconfirmIntent/captureIntent/cancelIntent/refund. - GET
${baseUrl}/payments/{id}forgetIntent. - Set
X-API-Key: ${apiKey}on every call and forwardIdempotency-Key: ${idempotencyKey}oncreateIntent/refundwhen the caller supplies one. - Inject
merchantAccount: "<configured-value>"into the request body for every modification (captures,cancels,refunds). - Map result
Authorised→authorized;Refused/Error→failed;Cancelled→canceled;Pending/Received/RedirectShopper/IdentifyShopper/ChallengeShopper/PresentToShopper→pending; every other code →failed. - Map refund status
received→pending; every other status →failed. - Map payment-method type
scheme→card; every other type →wallet. - Raise
PaymentProviderError(carrying one of thePAYMENT_PROVIDER_CODESfrom@suaveplan/payment-providers) on every HTTP, timeout, or parse failure — the package SHALL NOT throw plainErrorfrom any public method.
AdyenProvider.name SHALL equal "adyen".
Scenario: merchant-account context forwarded on capture / cancel / refund
- WHEN
captureIntent,cancelIntent, orrefundis invoked - THEN the recording
FetchLikeobserves a request body containingmerchantAccount: "<configured-value>"
Scenario: caller-supplied idempotency key forwarded as both reference and header
- WHEN
createIntentis invoked withidempotencyKey: "idem_1" - THEN the recording
FetchLikeobservesIdempotency-Key: "idem_1"on the request headers AND the request body'sreferencefield equals"idem_1"
Scenario: injectable randomId generates reference when idempotencyKey absent
- WHEN
createIntentis invoked without anidempotencyKeyand the constructor was supplied with a deterministicrandomIdreturning"gen_REF_1" - THEN the recording
FetchLikeobserves a request body whosereferencefield equals"gen_REF_1"
Verified by: src/adyen/adyen.test.ts::"has correct name" AND src/adyen/adyen.test.ts::"creates a payment" AND src/adyen/adyen.test.ts::"uses automatic capture by default" AND src/adyen/adyen.test.ts::"submits payment details on confirm" AND src/adyen/adyen.test.ts::"captures a payment" AND src/adyen/adyen.test.ts::"cancels a payment" AND src/adyen/adyen.test.ts::"creates a refund" AND src/adyen/adyen.test.ts::"refunds without amount" AND src/adyen/adyen.test.ts::"fetches a payment" AND src/adyen/adyen.test.ts::"maps result Authorised to authorized" AND src/adyen/adyen.test.ts::"maps result Refused to failed" AND src/adyen/adyen.test.ts::"maps result Cancelled to canceled" AND src/adyen/adyen.test.ts::"maps result Error to failed" AND src/adyen/adyen.test.ts::"maps result Pending to pending" AND src/adyen/adyen.test.ts::"maps result Received to pending" AND src/adyen/adyen.test.ts::"maps result RedirectShopper to pending" AND src/adyen/adyen.test.ts::"maps result IdentifyShopper to pending" AND src/adyen/adyen.test.ts::"maps result ChallengeShopper to pending" AND src/adyen/adyen.test.ts::"maps result PresentToShopper to pending" AND src/adyen/adyen.test.ts::"maps unknown result to failed" AND src/adyen/adyen.test.ts::"maps payment without paymentMethod" AND src/adyen/adyen.test.ts::"maps non-scheme payment type as wallet" AND src/adyen/adyen.test.ts::"maps refund received to pending" AND src/adyen/adyen.test.ts::"maps other refund statuses to failed" AND src/adyen/adyen.test.ts::"uses injected randomId to generate reference when idempotencyKey is absent"
Requirement: AdyenConfig extends the slim-core MerchantKeyConfig
The package SHALL export an AdyenConfig type that extends
MerchantKeyConfig from @suaveplan/payment-providers. The type
SHALL preserve every field of MerchantKeyConfig (apiKey,
merchantAccount, optional baseUrl, webhookHmacKey, randomId,
fetch, timeoutMs, signal) so consumers can use either name
interchangeably.
Scenario: AdyenConfig is structurally compatible with MerchantKeyConfig
- WHEN a value typed as
MerchantKeyConfigis passed whereAdyenConfigis expected - THEN TypeScript SHALL accept the assignment without a structural mismatch
Verified by: src/types/adyen.test.ts::"accepts a MerchantKeyConfig value as AdyenConfig"
Requirement: Universal-tier purity
The package SHALL contain zero imports from node:*, bun:*, fs,
path, stream, net, http, or crypto. The package SHALL run
unchanged on Node, Bun, browsers, and Web Workers. The default
reference generation SHALL go through an injectable RandomIdFn
(falling back to globalThis.crypto?.randomUUID only when present at
call time) so the package never imports node:crypto.
Scenario: dep-graph guard rejects banned imports
- WHEN
tests/dep-graph.test.tswalks every.tsfile undersrc/ - THEN zero offenders SHALL be reported
Verified by: tests/dep-graph.test.ts::"src contains no node/bun/fs/path/stream/net/http/crypto imports"
Requirement: Subpath exports per feature module
The package's package.json#exports map SHALL expose . (root barrel),
./adyen/adyen (provider class), and ./types/adyen (config type) so
consumers can tree-shake to a single feature. The root barrel SHALL
use explicit named re-exports (rule 6a); src/adyen/index.ts and
src/types/index.ts SHALL likewise use explicit named re-exports.
Scenario: subpath-export count matches feature-module count
- WHEN
Object.keys(pkg.exports).filter(k => k.startsWith("./") && k !== "." && k !== "./package.json")is computed - THEN the resulting array SHALL contain exactly
["./adyen/adyen", "./types/adyen"]
Verified by: tests/dep-graph.test.ts::"package.json exposes exactly the feature subpaths"