A production-grade, zero-external-dependency Go client for the Adyen payment platform, built with Domain-Driven Design (DDD) principles and the latest Go 1.22 idioms.
| Capability | Details |
|---|---|
| DDD Architecture | Separate packages per bounded context: checkout, management, platforms, classic, terminal, webhooks |
| All major Adyen APIs | Checkout v72, Management v3, Balance Platform v2, Classic PAL, Terminal API, Webhook notifications |
| Resilience | Exponential-backoff retry · Token-bucket rate limiter · Three-state circuit breaker |
| Observability | Telemetry event bus (start/stop hooks per call, timing, HTTP status) |
| PCI safety | Automatic redaction of sensitive card fields from logs/telemetry |
| Idempotency | Auto-generated UUIDv4 idempotency keys on every POST |
| HMAC validation | Constant-time HMAC-SHA256 webhook signature verification |
| Type safety | Generics-based HTTP helpers, typed error hierarchy, strongly typed event codes |
| Zero external deps | Entire package uses only the Go standard library |
go get github.com/iamkanishka/adyen-goRequires Go 1.22+.
package main
import (
"context"
"fmt"
"log"
adyen "github.com/iamkanishka/adyen-go"
"github.com/iamkanishka/adyen-go/config"
"github.com/iamkanishka/adyen-go/domain"
"github.com/iamkanishka/adyen-go/domain/checkout"
)
func main() {
// 1. Build configuration
cfg, err := config.New(
"AQEy...your-api-key...",
config.WithEnvironment(config.EnvironmentTest),
config.WithMerchantAccount("YourMerchantECOM"),
config.WithWebhookHMACKey("your-hmac-key-hex"),
config.WithMaxRetries(3),
)
if err != nil {
log.Fatal(err)
}
// 2. Create the unified client
client, err := adyen.New(cfg)
if err != nil {
log.Fatal(err)
}
// 3. Call the Checkout API
ctx := context.Background()
session, err := client.Checkout.CreateSession(ctx, checkout.CreateSessionRequest{
Amount: domain.Amount{Currency: "EUR", Value: 1000}, // €10.00
MerchantAccount: cfg.MerchantAccount,
Reference: "order-12345",
ReturnURL: "https://yourshop.com/return",
CountryCode: "NL",
ShopperLocale: "nl-NL",
})
if err != nil {
log.Fatal(err)
}
fmt.Println("Session ID: ", session.ID)
fmt.Println("Session data:", session.SessionData)
}adyen-go/
├── adyen.go ← Top-level facade (New, MustNew)
├── application/
│ └── client.go ← AdyenClient: wires all domain services
├── config/
│ └── config.go ← AdyenConfig value object + URL resolution
├── domain/
│ ├── types.go ← Shared value objects (Amount, Address, LineItem, Split…)
│ ├── checkout/ ← Checkout API v72
│ │ └── service.go
│ ├── management/ ← Management API v3
│ │ └── service.go
│ ├── platforms/ ← Balance Platform API v2
│ │ └── service.go
│ ├── classic/ ← Classic PAL API
│ │ └── service.go
│ ├── terminal/ ← Terminal API (LTC + Cloud + SoftPOS)
│ │ └── service.go
│ └── webhooks/ ← Webhook parsing, HMAC, dispatcher
│ └── service.go
├── infrastructure/
│ └── http/
│ └── client.go ← Transport: retry, circuit breaker, rate limiter, telemetry
├── pkg/
│ └── errors/
│ └── errors.go ← Typed AdyenError hierarchy
└── adyen_test.go ← 71 tests covering all layers
// Drop-in / Components session
session, err := client.Checkout.CreateSession(ctx, checkout.CreateSessionRequest{...})
// Direct payment (server-side)
resp, err := client.Checkout.CreatePayment(ctx, checkout.CreatePaymentRequest{
Amount: domain.Amount{Currency: "EUR", Value: 2000},
MerchantAccount: "YourMerchant",
Reference: "order-99",
ReturnURL: "https://shop.com/return",
PaymentMethod: domain.CardPaymentMethod{
Type: "scheme",
EncryptedCardNumber: "enc:4111...",
EncryptedSecurityCode: "enc:737...",
EncryptedExpiryMonth: "enc:03...",
EncryptedExpiryYear: "enc:2030...",
},
})
// 3DS2 detail submission
resp, err := client.Checkout.SubmitDetails(ctx, checkout.SubmitDetailsRequest{
Details: map[string]any{"redirectResult": "eyJ..."},
PaymentData: "eyJ...paymentdata",
})
// Payment modifications
resp, err := client.Checkout.CapturePayment(ctx, pspRef, checkout.ModificationRequest{...})
resp, err := client.Checkout.RefundPayment(ctx, pspRef, checkout.ModificationRequest{...})
resp, err := client.Checkout.CancelPayment(ctx, pspRef, checkout.ModificationRequest{...})
resp, err := client.Checkout.ReversePayment(ctx, pspRef, checkout.ModificationRequest{...})
// Payment links
link, err := client.Checkout.CreatePaymentLink(ctx, checkout.CreatePaymentLinkRequest{...})
link, err := client.Checkout.GetPaymentLink(ctx, linkID)
link, err := client.Checkout.UpdatePaymentLink(ctx, linkID, checkout.UpdatePaymentLinkRequest{Status: "expired"})
// Stored tokens (recurring)
list, err := client.Checkout.ListStoredTokens(ctx, merchantAccount, shopperReference)
err = client.Checkout.DeleteToken(ctx, recurringID)
// Gift card / partial payments
balance, err := client.Checkout.GetBalance(ctx, checkout.GetBalanceRequest{...})
order, err := client.Checkout.CreateOrder(ctx, checkout.CreateOrderRequest{...})
cancel, err := client.Checkout.CancelOrder(ctx, checkout.CancelOrderRequest{...})
// Donations
campaigns, err := client.Checkout.ListDonationCampaigns(ctx, checkout.DonationCampaignRequest{...})
donation, err := client.Checkout.CreateDonation(ctx, checkout.CreateDonationRequest{...})
// Utility
keys, err := client.Checkout.CreateOriginKeys(ctx, checkout.OriginKeysRequest{...})
session, err := client.Checkout.GetApplePaySession(ctx, checkout.ApplePaySessionRequest{...})// Merchants
merchants, err := client.Management.ListMerchants(ctx, management.ListParams{PageSize: 25})
merchant, err := client.Management.GetMerchant(ctx, merchantID)
created, err := client.Management.CreateMerchant(ctx, management.CreateMerchantRequest{...})
updated, err := client.Management.UpdateMerchant(ctx, merchantID, management.UpdateMerchantRequest{...})
// Stores
stores, err := client.Management.ListStores(ctx, merchantID, management.ListParams{})
store, err := client.Management.CreateStore(ctx, merchantID, management.CreateStoreRequest{...})
// API Credentials
cred, err := client.Management.CreateAPICredential(ctx, merchantID, management.CreateAPICredentialRequest{...})
apiKey, err := client.Management.GenerateAPIKey(ctx, merchantID, credentialID)
origins, err := client.Management.ListAllowedOrigins(ctx, merchantID, credentialID)
origin, err := client.Management.CreateAllowedOrigin(ctx, merchantID, credentialID, management.CreateOriginRequest{Domain: "https://shop.com"})
// Webhooks
webhook, err := client.Management.CreateWebhook(ctx, merchantID, management.CreateWebhookRequest{
Type: "standard",
URL: "https://shop.com/webhooks",
Active: true,
CommunicationFormat: "json",
})
hmac, err := client.Management.GenerateWebhookHMACKey(ctx, merchantID, webhookID)
result, err := client.Management.TestWebhook(ctx, merchantID, webhookID, management.TestWebhookRequest{...})
// Terminal orders
order, err := client.Management.CreateTerminalOrder(ctx, merchantID, management.CreateTerminalOrderRequest{...})// Account holders
holder, err := client.Platforms.CreateAccountHolder(ctx, platforms.CreateAccountHolderRequest{LegalEntityID: "LE-001"})
holder, err := client.Platforms.GetAccountHolder(ctx, holderID)
accounts,err := client.Platforms.GetAllBalanceAccountsForHolder(ctx, holderID, platforms.ListParams{})
// Balance accounts
account, err := client.Platforms.CreateBalanceAccount(ctx, platforms.CreateBalanceAccountRequest{AccountHolderID: holderID})
account, err := client.Platforms.GetBalanceAccount(ctx, accountID)
// Payment instruments (cards)
card, err := client.Platforms.CreatePaymentInstrument(ctx, platforms.CreatePaymentInstrumentRequest{
Type: "card",
BalanceAccountID: accountID,
IssuingCountry: "NL",
})
err = client.Platforms.UpdatePaymentInstrument(ctx, instrumentID, platforms.UpdatePaymentInstrumentRequest{Status: "suspended"})
// Transfers
transfer, err := client.Platforms.CreateTransfer(ctx, platforms.CreateTransferRequest{
Amount: domain.Amount{Currency: "EUR", Value: 10000},
Category: "internal",
Counterparty: platforms.Counterparty{BalanceAccountID: "BA-002"},
})
// Transactions
txns, err := client.Platforms.ListTransactions(ctx, platforms.TransactionListParams{
BalanceAccountID: accountID,
CreatedSince: "2026-01-01T00:00:00Z",
Limit: 50,
})
// Transaction rules (spending controls)
rule, err := client.Platforms.CreateTransactionRule(ctx, platforms.TransactionRule{
Type: "velocity",
Interval: &platforms.RuleInterval{Type: "daily"},
RuleRestrictions: &platforms.RuleRestrictions{
Amount: &platforms.RuleAmount{Value: 100000, Currency: "EUR"},
},
})// Authorise
resp, err := client.Classic.Authorise(ctx, classic.AuthoriseRequest{
Amount: domain.Amount{Currency: "GBP", Value: 3000},
MerchantAccount: "YourMerchant",
Reference: "ref-1",
Card: &classic.Card{
Number: "4111111111111111",
ExpiryMonth: "03",
ExpiryYear: "2030",
CVC: "737",
},
})
// 3DS
resp, err := client.Classic.Authorise3DS(ctx, classic.Authorise3DSRequest{MD: "...", PaResponse: "..."})
resp, err := client.Classic.Authorise3DS2(ctx, classic.Authorise3DS2Request{...})
// Modifications
resp, err := client.Classic.Capture(ctx, classic.ModificationRequest{OriginalReference: pspRef})
resp, err := client.Classic.Refund(ctx, classic.ModificationRequest{OriginalReference: pspRef})
resp, err := client.Classic.Cancel(ctx, classic.ModificationRequest{OriginalReference: pspRef})
resp, err := client.Classic.CancelOrRefund(ctx, classic.ModificationRequest{OriginalReference: pspRef})
resp, err := client.Classic.AdjustAuthorisation(ctx, classic.ModificationRequest{...})
// Recurring
details, err := client.Classic.ListRecurringDetails(ctx, classic.ListRecurringDetailsRequest{
MerchantAccount: "YourMerchant",
ShopperReference: "shopper-42",
})
resp, err := client.Classic.DisableRecurring(ctx, classic.DisableRecurringRequest{
MerchantAccount: "YourMerchant",
ShopperReference: "shopper-42",
RecurringDetailReference: "RECURRING-001",
})
// Payout
resp, err := client.Classic.Payout(ctx, classic.AuthoriseRequest{...})
resp, err := client.Classic.ConfirmThirdParty(ctx, classic.ConfirmThirdPartyRequest{...})
// BIN lookup
info, err := client.Classic.GetBINLookup(ctx, classic.BINLookupRequest{CardNumber: "411111"})// Synchronous (Local Terminal Communication)
resp, err := client.Terminal.SyncPayment(ctx, terminal.TerminalAPIRequest{
SaleToPOIRequest: terminal.SaleToPOIRequest{
MessageHeader: terminal.MessageHeader{
MessageCategory: "Payment",
MessageClass: "Service",
MessageType: "Request",
POIID: "V400m-123456789",
SaleID: "POS-001",
ServiceID: "svc-001",
ProtocolVersion: "3.0",
},
PaymentRequest: &terminal.PaymentRequest{
SaleData: terminal.SaleData{
SaleTransactionID: terminal.SaleTransactionID{
TransactionID: "txn-001",
TimeStamp: "2026-01-01T12:00:00Z",
},
},
PaymentTransaction: terminal.PaymentTransaction{
AmountsReq: terminal.AmountRequest{
Currency: "EUR",
RequestedAmount: 10.00,
},
},
},
},
})
if resp.SaleToPOIResponse.PaymentResponse.Response.IsSuccess() {
fmt.Println("Terminal payment approved!")
}
// Cloud (async via Adyen)
resp, err := client.Terminal.CloudPayment(ctx, terminalID, req)// Parse & validate in one step (requires WebhookHMACKey in config)
notifications, err := client.Webhooks.ParseAndValidate(r.Body)
if err != nil {
http.Error(w, "invalid webhook", 400)
return
}
// Dispatcher pattern
dispatcher := webhooks.NewDispatcher()
dispatcher.On([]webhooks.EventCode{webhooks.EventAuthorisation}, func(ctx context.Context, n webhooks.ParsedNotification) error {
auth, _ := n.AsAuthorisation()
fmt.Printf("Authorised PSP: %s amount: %s %d\n",
auth.PSPReference, auth.Amount.Currency, auth.Amount.Value)
return nil
})
dispatcher.On([]webhooks.EventCode{webhooks.EventChargeback, webhooks.EventSecondChargeback},
func(ctx context.Context, n webhooks.ParsedNotification) error {
cb, _ := n.AsChargeback()
fmt.Printf("Chargeback received: %s reason: %s\n", cb.PSPReference, cb.Reason)
return nil
})
dispatcher.OnAny(func(ctx context.Context, n webhooks.ParsedNotification) error {
fmt.Printf("Unhandled event: %s\n", n.EventCode)
return nil
})
errs := dispatcher.Dispatch(r.Context(), notifications)
w.WriteHeader(200)
w.Write([]byte("[accepted]"))All API errors are returned as *errors.AdyenError:
import adyenerrors "github.com/iamkanishka/adyen-go/pkg/errors"
resp, err := client.Checkout.CreatePayment(ctx, req)
if err != nil {
var ae *adyenerrors.AdyenError
if errors.As(err, &ae) {
switch ae.Type {
case adyenerrors.ErrorTypeAuth:
// Check your API key
case adyenerrors.ErrorTypeValidation:
// Fix request fields; ae.ErrorCode has the Adyen error code
case adyenerrors.ErrorTypeRateLimited:
// Back off; ae.Retryable == true
case adyenerrors.ErrorTypeCircuitOpen:
// Circuit breaker is open; the downstream is unhealthy
}
fmt.Println("HTTP status:", ae.HTTPStatus)
fmt.Println("Error code: ", ae.ErrorCode)
fmt.Println("PSP ref: ", ae.PSPReference)
fmt.Println("Retryable: ", ae.Retryable)
}
}ErrorType |
HTTP status | Retryable | Description |
|---|---|---|---|
ErrorTypeAuth |
401, 403 | No | Invalid/expired API key or insufficient permissions |
ErrorTypeValidation |
422 | No | Request field validation failure |
ErrorTypeNotFound |
404 | No | Resource not found |
ErrorTypeRateLimited |
429 | Yes | Rate limit exceeded |
ErrorTypeServer |
5xx | Yes | Adyen server error |
ErrorTypeNetwork |
— | Yes | Network / TCP / TLS failure |
ErrorTypeTimeout |
— | Yes | Read or connect timeout |
ErrorTypeWebhookValidation |
— | No | HMAC mismatch or missing signature |
ErrorTypeCircuitOpen |
— | No | Circuit breaker has tripped |
ErrorTypeConfig |
— | No | Invalid/missing configuration |
Subscribe to observe every API call's outcome:
unsubscribe := client.Telemetry.Subscribe(func(ev httpclient.TelemetryEvent) {
if ev.Type == httpclient.TelemetryRequestStop {
log.Printf("[adyen] %s %s → %d (%dms) ok=%v",
ev.Method, ev.URL, ev.HTTPStatus, ev.DurationMs, ev.OK)
}
})
defer unsubscribe()Integrate with Prometheus, Datadog, OpenTelemetry, or any metrics backend.
cfg, err := config.New(
"AQEy...apikey", // required
config.WithEnvironment(config.EnvironmentLive), // default: EnvironmentTest
config.WithMerchantAccount("MyMerchant"),
config.WithWebhookHMACKey("hex-encoded-hmac-key"),
config.WithTimeout(30*time.Second), // HTTP read timeout (default 30s)
config.WithConnectTimeout(10*time.Second), // TCP connect timeout (default 10s)
config.WithMaxRetries(3), // retries on 5xx/429 (default 3)
config.WithRetryDelay(500*time.Millisecond), // initial backoff (doubles per retry)
config.WithCheckoutAPIVersion("72"), // override individual API versions
)The built-in circuit breaker protects your application from cascading failures:
- Closed → normal operation
- Open → after 5 consecutive failures; all calls return
ErrorTypeCircuitOpenimmediately - Half-open → after 30 seconds; one probe call is allowed through
fmt.Println(client.CircuitBreakerState()) // "closed", "open", or "half-open"The following fields are automatically redacted in telemetry and log-safe structures:
cardNumber, cvv, cvc, number, holderName, expiryMonth, expiryYear,
encryptedCardNumber, encryptedSecurityCode, encryptedExpiryMonth, encryptedExpiryYear
Use httpclient.Redact(payload) to sanitize any map before logging.
go test ./... -v -count=1The test suite covers:
- Config validation (empty key, invalid environment, all URL builders)
- Error types (classification, retryability, error string)
- HTTP client (GET/POST, 4xx/5xx, retries, circuit breaker, idempotency, telemetry, PCI redaction, context cancellation)
- Checkout (sessions, payments, 3DS, modifications, links, balance, orders)
- Management (merchants, stores, credentials, webhooks)
- Balance Platform (account holders, transfers, payment instruments)
- Classic PAL (authorise, modifications, recurring)
- Terminal API (sync payment, response parsing)
- Webhooks (HMAC validation, HMAC tampering, dispatcher, typed event accessors)
- Top-level facade (New, MustNew, nil config, circuit breaker state)
| API | Version | Package |
|---|---|---|
| Checkout API | v72 | domain/checkout |
| Management API | v3 | domain/management |
| Balance Platform API | v2 | domain/platforms |
| Transfers API | v4 | domain/platforms |
| Classic Payment API (PAL) | v68 | domain/classic |
| Classic Recurring API | v68 | domain/classic |
| Classic Payout API | v68 | domain/classic |
| BIN Lookup API | v54 | domain/classic |
| Terminal API (Sync/Cloud) | v1 | domain/terminal |
| SoftPOS API | v3 | domain/terminal |
| Webhook Notifications | — | domain/webhooks |
| Legal Entity API | v4 | config URL only |
| Capital API | v1 | config URL only |
MIT — see LICENSE.