Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Php Api Library Laravel Package

adyen/php-api-library

Official Adyen PHP API library for server-side integration with Adyen payments, subscriptions, payouts, and management APIs. Includes a typed client, request/response models, and helpers for configuring credentials, handling webhooks, and building secure payment flows in PHP applications.

View on GitHub
Deep Wiki
Context7

Getting Started

Install with Composer and keep credentials out of code:

composer require adyen/php-api-library
// config/adyen.php
return [
    'api_key'       => env('ADYEN_API_KEY'),
    'client_key'    => env('ADYEN_CLIENT_KEY'),   // frontend Drop-in/Components only
    'hmac_key'      => env('ADYEN_HMAC_KEY'),     // webhook verification only
    'environment'   => env('ADYEN_ENV', 'test'),  // 'test' | 'live'
    'live_prefix'   => env('ADYEN_LIVE_PREFIX'),  // required in live
    'merchant'      => env('ADYEN_MERCHANT_ACCOUNT'),
    'region'        => env('ADYEN_TERMINAL_REGION'), // Terminal API cloud only, v30.1+
    'timeout'       => 15,
    'connect_timeout' => 5,
];

Bind the client once in AppServiceProvider (the Client is stateful — build it from config, don't scatter new Client() around):

use Adyen\Client;
use Adyen\Environment;

$this->app->singleton(Client::class, function () {
    $client = new Client();
    $client->setXApiKey(config('adyen.api_key'));
    $client->setEnvironment(
        config('adyen.environment') === 'live' ? Environment::LIVE : Environment::TEST
    );
    $client->setTimeout(config('adyen.timeout'));
    $client->setConnectionTimeout(config('adyen.connect_timeout'));

    if ($prefix = config('adyen.live_prefix')) {
        $client->getConfig()->set('liveEndpointUrlPrefix', $prefix);
    }

    if ($region = config('adyen.region')) {
        $client->setRegion($region); // v30.1+: regional Terminal API cloud endpoint in live
    }

    $client->setLogger(Log::channel('adyen')->getLogger()); // PSR-3
    return $client;
});

Where to look first, in order:

  1. src/Adyen/Client.php — the only class you configure; everything else hangs off it.
  2. src/Adyen/Service/ — one class per API (Checkout, Management, Recurring, BinLookup, Platforms, Transfer, ForeignExchange\RatesApi (v30.1+), …). Method names map to OpenAPI operations.
  3. src/Adyen/Model/ — typed request/response objects (v13+).
  4. README.md for the canonical snippets, CHANGELOG.md/release notes before every minor bump (API versions are baked into generated paths), and note that most of Service/ and Model/ is generated — treat it as read-only.

First use case: a hosted Checkout session, or /paymentMethods → /payments:

use Adyen\Service\Checkout;
use Adyen\Model\Checkout\Amount;
use Adyen\Model\Checkout\PaymentRequest;

$checkout = new Checkout(app(Client::class));

$request = (new PaymentRequest())
    ->setMerchantAccount(config('adyen.merchant'))
    ->setReference($order->number)
    ->setAmount((new Amount())->setCurrency('EUR')->setValue(1999))
    ->setPaymentMethod(['type' => 'scheme', 'encryptedCardNumber' => '...'])
    ->setReturnUrl(route('checkout.return'));

$result = $checkout->payments($request); // ->getResultCode(), ->getPspReference()

Implementation Patterns

Wrap the client, not the library. Put a thin AdyenGateway/PaymentService class in app/Services that receives the configured Adyen\Client and exposes only the four or five operations your app actually needs (pay, details, capture, refund, paymentMethods, and — if you do multi-currency — quoteFx). Controllers then never touch Adyen\Service\* directly, and tests swap the gateway or the HTTP client.

Typed models vs. raw arrays. Both work; arrays are quicker for prototypes, models give IDE autocompletion and catch typos before you hit the API. Pick one convention per codebase — mixing them makes diffs noisy.

Resolve services per call, not per property. new Checkout($client) is cheap; treat Service\* as stateless and instantiate on demand (or bind them as scoped if you prefer injection).

Classic payment flow (3DS / redirects). /payments returning resultCode: IdentifyShopper|ChallengeShopper|RedirectShopper|Pending means you are not done — persist paymentData and pspReference, then finish via $checkout->paymentsDetails($request) on return, or wait for the webhook (usually both; the webhook is the source of truth).

Currency conversion (v30.1+, ForeignExchange\RatesApi). Use it as a quote, not a promise — fetch the rate for display, store the quote alongside the order, and re-quote immediately before you authorise if the amount is binding. Never derive a settlement amount from a cached rate:

use Adyen\Service\ForeignExchange\RatesApi;

$rates = (new RatesApi(app(Client::class)))->calculate($request);
// persist sourceAmount/targetAmount + quote timestamp; show an expiry in the UI

Keep FX calls off the hot path: cache per (source, target, day) with a short TTL, and prefer charging in the presentment currency with a disclosed converted amount over doing arithmetic yourself.

Terminal API cloud (v30.1+): set the region once, at boot. Client::setRegion() is client state like setEnvironment(); set it in the container binding from config, never per payment. In live, an unsupported region throws AdyenException when PosPayment builds endpointTerminalCloud — so validate the configured region at deploy time (a config-cache smoke test is enough) rather than discovering it at the till. Client::retrieveCloudEndpoint() is deprecated — remove any calls to it.

Webhooks: verify, then queue, then answer fast.

// routes/web.php — excluded from CSRF
Route::post('/adyen/webhook', [AdyenWebhookController::class, 'handle'])
    ->withoutMiddleware(\App\Http\Middleware\VerifyCsrfToken::class);

public function handle(Request $request)
{
    $notification = $request->input('notificationItems.0.NotificationRequestItem');

    $hmac = new \Adyen\Util\HmacSignature();
    abort_unless(
        $hmac->isValidNotificationHMAC(config('adyen.hmac_key'), $notification),
        401
    );

    ProcessAdyenNotification::dispatch($notification);

    return response('[accepted]', 200); // exact body Adyen expects
}

Inside the job, use the notification factory for readable branching instead of raw arrays:

$n = \Adyen\Webhook\Notification::create($payload);   // AuthorisationNotification, etc.
if ($n->isSuccess() && $n->getEventCode() === 'AUTHORISATION') { ... }

Treat transfer/payment webhook status as an open set: v30.1 removed adviceAuthorised/adviceRefused from the Transfer Webhooks models and added reversalReceived, fxSell/fxBuy. Map statuses through a match with a default branch that logs and parks the event, rather than exhaustive switch that silently swallows new values.

Idempotency for every money-moving call. Generate a UUID per payment attempt, store it on the order, and pass it as the request option so a retry (or a re-dispatched job) can't double-charge:

$checkout->payments($request, ['idempotencyKey' => $order->adyen_idempotency_key]);

Multi-tenant / multi-merchant. If different stores use different API keys, use $this->app->scoped(Client::class, ...) or a factory keyed by merchant. A singleton whose key you mutate mid-request is a production incident waiting to happen — especially under Octane, Horizon or queue workers. Same rule applies to setRegion() if you ever serve terminals in more than one region.

Custom HTTP client for tests. Implement Adyen\HttpClient\ClientInterface and inject a recorder/stub, or use Mockery on the service class. Http::fake() will not help you (see Gotchas).

Testing discipline. Unit-test your gateway with a fake ClientInterface; keep a small set of integration tests against the Adyen TEST environment using their test cards, and gate them behind an env flag so CI doesn't hit the network. Bump-driven model regeneration means your assertions should target the fields you read, not whole-object equality — new properties appear in minor releases.

Refunds vs. captures. With Checkout API use $checkout->refunds(...) / $checkout->captures(...) with an Amount; the legacy Modification service with originalReference still exists for older flows — don't mix the two for the same payment.

Gotchas and Tips

  • Amounts are minor units. 19.99 EUR is 1999, but JPY has no decimals and some currencies have three. Always build the integer through \Adyen\Util\Util::formatAmount($amount, $currency) rather than * 100 — and never store Adyen amounts as floats.
  • liveEndpointUrlPrefix is mandatory in live. Without it you get confusing endpoint/404 errors that look like auth failures. It's the prefix from your Customer Area, e.g. 1797a841fbb37ca7-AdyenDemo.
  • Http::fake() does not intercept this library. It uses its own cURL client, so Http::preventStrayRequests() gives you no safety net and tests will silently hit the network. Inject Adyen\HttpClient\ClientInterface instead.
  • The client is mutable and long-lived. setEnvironment, setXApiKey, setTimeout, and now setRegion change state on the instance. Under Octane/queues, a per-tenant mutation leaks into the next request. Use scoped bindings.
  • Verify HMAC on the raw notification item, before casting. Don't run it through resources/(int) casts or array_filter first — the signature is over the exact field set Adyen sent. Also: hmac_key (webhook signing) ≠ api_key (server calls) ≠ client_key (browser Drop-in).
  • Return [accepted] with HTTP 200. Adyen retries non-200s on its own schedule; a slow handler causes duplicate notifications. Queue the work and return immediately.
  • Notifications arrive out of order and more than once. Dedupe on pspReference + eventCode (unique index helps) and order by eventDate, not arrival time. AUTHORISATION can land after CAPTURE.
  • Upgrading to v30.1: adviceAuthorised/adviceRefused are gone from Transfer Webhooks models — if you branch on them, those paths are now dead code and any enum-ish validation will reject nothing but will mis-bucket real events. Client::retrieveCloudEndpoint() is deprecated; switch to setRegion() + config. Everything else in this release is additive (FX API, transactionLinkId, subMerchantData, networkVariant, transfer tracing), so it's a safe minor bump — but re-run integration tests, because generated Service paths may move to newer API versions.
  • setRegion() fails loudly, not silently. In live, an unsupported region throws AdyenException at PosPayment time. Pin the region in config per environment and assert it during deploy, not at the point of sale.
  • Don't treat an FX quote as a guarantee. RatesApi::calculate() reflects a moment in time; rates move and quotes expire. Store the quote with its timestamp, re-quote before authorising, and never re-use yesterday's rate for today's settlement.
  • Don't retry payments blindly. Retries without an idempotency key duplicate charges. Idempotency keys are single-use per payload — reuse only for the same request.
  • Exceptions: failures raise Adyen\AdyenException (check getStatus(), getErrorCode(), getErrorType(), getPspReference()). Catch it in the gateway, translate to your domain exception, and log the pspReference — support will ask for it. Don't let raw Adyen errors bubble to users as 500s.
  • The API version is baked into the generated service paths (e.g. /v71/payments). A minor library bump can move you to a newer API version; read the release notes and re-run integration tests.
  • Never edit src/Adyen/Service/* or src/Adyen/Model/*. They are regenerated from the OpenAPI specs by adyen-sdk-automation and will be overwritten. If you need a change, patch the templates/automation repo and regenerate (./gradlew :php:checkout), not the PHP file.
  • Timeouts: defaults are generous. Tighten connection/read timeouts for user-facing calls and let webhooks resolve slow outcomes, so a stalled Adyen call doesn't pin PHP-FPM workers.
  • Payment method payloads are type-dependent (scheme, ideal, paypal, applepay…). Don't hand-roll them — take the object from /paymentMethods and pass it back, adjusting only what the shopper chose.
  • Nothing card-related may touch your server if you're using Drop-in/Components with client_key; the api_key must never reach the browser.
  • Debugging: attach Laravel's Monolog instance via $client->setLogger(Log::channel('adyen')->getLogger()) and make sure your log processor masks paymentMethod.encrypted* and any holderName fields before they land in storage.
  • Requirements: check the package's PHP and ext-curl/ext-json constraints in composer.json before upgrading; the library tracks modern PHP and may outpace a legacy Laravel app.
  • v30.1.0 security note: the CVE fix (CVE-2026-67434) is in the dev-only squizlabs/php_codesniffer dependency, so it doesn't affect production runtime — bump anyway to keep CI/lint tooling off the advisory list.
  • Working on the library itself? Tests live behind tests/config/test.ini credentials; run vendor/bin/phpunit --testsuite unit. Generation requires cloning adyen-sdk-automation and symlinking php/repo — a day-long detour you should not take on a feature ticket.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle