PHPackages                             mfadul24/kount-commerce - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [HTTP &amp; Networking](/categories/http)
4. /
5. mfadul24/kount-commerce

ActiveLibrary[HTTP &amp; Networking](/categories/http)

mfadul24/kount-commerce
=======================

PHP SDK for the Kount 360 Commerce APIs (Payments Fraud 2.0) — PSR-18/PSR-17 based, OAuth2 client-credentials with optional PSR-6 token caching, plus a legacy RIS migration facade.

v0.1.0(1mo ago)05MITPHPPHP &gt;=8.3CI passing

Since Jul 8Pushed 1mo agoCompare

[ Source](https://github.com/mfadul24/kount-commerce-api-sdk)[ Packagist](https://packagist.org/packages/mfadul24/kount-commerce)[ GitHub Sponsors](https://github.com/mfadul24)[ RSS](/packages/mfadul24-kount-commerce/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (1)Dependencies (22)Versions (2)Used By (0)

mfadul24/kount-commerce
=======================

[](#mfadul24kount-commerce)

[![Sponsor](https://camo.githubusercontent.com/905c0628741c74fd2dc51df9409ed09e4624cfed6a16787d8c2102a17f4e8506/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f53706f6e736f722d2545322539442541342d6561346161613f6c6f676f3d67697468756273706f6e736f7273266c6f676f436f6c6f723d7768697465)](https://github.com/sponsors/mfadul24)

Modern, PSR-first PHP SDK for the **Kount 360 Commerce APIs (Payments Fraud 2.0)** — evaluate orders for fraud risk and receive a typed Approve / Decline / Review decision.

- **PSR-18 / PSR-17 transport** — bring your own HTTP client; nothing concrete is forced on you. [`php-http/discovery`](https://github.com/php-http/discovery) finds an installed client automatically.
- **OAuth2 client-credentials built in** — with an optional **PSR-6 cache** so the ~20-minute bearer token is reused across requests and processes.
- **Typed, generated-from-OpenAPI models** hydrated by `symfony/serializer` — enums, nested objects and dates, no raw arrays.
- **One exception marker interface** (`Kount\Exception\KountException`) covering every failure mode.
- **Legacy RIS migration facade** for consumers of the old `Kount/kount-ris-php-sdk`.

Installation
------------

[](#installation)

The SDK requires a PSR-18 client and PSR-17 factories at runtime (declared as virtual packages). If your project doesn't already ship one:

```
composer require mfadul24/kount-commerce symfony/http-client nyholm/psr7
```

Any other PSR-18/PSR-17 implementation (Guzzle 7, httplug adapters, ...) works exactly the same.

Quickstart
----------

[](#quickstart)

```
use Kount\KountCommerceApi;
use Kount\Models\Components\OrderPostBody;
use Kount\Models\Components\Item;

$kount = KountCommerceApi::builder()
    ->setOAuth('your-client-id', 'your-client-secret')
    ->build();

$result = $kount->orders->evaluate(
    OrderPostBody::for('ORDER-4711')
        ->withDeviceSession($deviceSessionId) // unique for >= 30 days
        ->addItem(new Item(name: 'Widget', quantity: 2)),
    riskInquiry: true,
);

$decision = $result->order?->riskInquiry?->decision;   // enum: Approve | Decline | Review
$omniscore = $result->order?->riskInquiry?->omniscore; // float 0-99
```

Available operations on `$kount->orders`:

MethodEndpoint`evaluate(OrderPostBody $body, ?bool $riskInquiry, ?bool $excludeDevice, ?bool $excludeFromPaymentsModel)``POST /v2/orders``get(string $orderId)``GET /v2/orders/{orderId}``update(string $orderId, OrderPatchBody $body)``PATCH /v2/orders/{orderId}``batchUpdateReversals(BatchUpdateReversalsRequest $request)``PATCH /v2/orders:batchUpdateReversals`Credentials
-----------

[](#credentials)

Kount issues the OAuth2 client-credentials secret in one of two shapes, and the builder has a method for each:

You were givenUseHeader the SDK sendsa discrete **client id + client secret**`setOAuth($clientId, $clientSecret)``Basic base64("$clientId:$clientSecret")`a single pre-encoded **API key** (the ready-made base64 of `clientId:clientSecret`)`setApiKey($apiKey)``Basic $apiKey` — used verbatimSome tenants — for example those onboarded via Digistore24 — never receive a separate id/secret pair, only the encoded API key. Pass it straight through; the SDK does **not** decode or split it:

```
$kount = KountCommerceApi::builder()
    ->setApiKey('bXktY2xpZW50LWlkOm15LWNsaWVudC1zZWNyZXQ=')
    ->build();
```

`setApiKey()` behaves exactly like `setOAuth()` otherwise — same token endpoint, the same `401 → refresh → retry`, and the same optional PSR-6 cache and `tokenUrl:` override:

```
$kount = KountCommerceApi::builder()
    ->setApiKey($apiKey, tokenCache: new FilesystemAdapter())
    ->useSandbox()
    ->build();
```

Under the hood both route to `Kount\Auth\ClientCredentialsTokenProvider`, which stores only the encoded Basic credential:

```
use Kount\Auth\ClientCredentialsTokenProvider;

ClientCredentialsTokenProvider::fromApiKey($apiKey, $tokenUrl);                    // pre-encoded key
ClientCredentialsTokenProvider::fromClientCredentials($id, $secret, $tokenUrl);   // id + secret pair
```

Token caching (PSR-6)
---------------------

[](#token-caching-psr-6)

Without a cache the bearer token is memoized per process only — under PHP-FPM that means one token exchange per worker. Inject any PSR-6 pool to share the token (TTL is `expires_in − 60s` safety skew). The example uses `FilesystemAdapter`, which requires `composer require symfony/cache`:

```
use Symfony\Component\Cache\Adapter\FilesystemAdapter;

$kount = KountCommerceApi::builder()
    ->setOAuth('your-client-id', 'your-client-secret', tokenCache: new FilesystemAdapter())
    ->build();
```

On a `401` the SDK invalidates the cached token, re-authenticates and retries the request **exactly once**; a second `401` throws `AuthenticationException`.

Bringing your own HTTP client
-----------------------------

[](#bringing-your-own-http-client)

Retry and timeout policy intentionally belong to *your* client, not the SDK:

```
use Symfony\Component\HttpClient\Psr18Client;
use Symfony\Component\HttpClient\HttpClient;

$kount = KountCommerceApi::builder()
    ->setClient(new Psr18Client(HttpClient::create(['timeout' => 10])))
    ->setOAuth('your-client-id', 'your-client-secret')
    ->build();
```

`setRequestFactory()` / `setStreamFactory()` accept custom PSR-17 factories; `setLogger()` accepts any PSR-3 logger.

Tracing outbound requests
-------------------------

[](#tracing-outbound-requests)

Need per-request traceability — duration, request body, response body — for every external call the SDK makes? Decorate your PSR-18 client. **Every outbound call flows through the one client you inject**, so a single decorator captures both the OAuth token `POST` and the `/v2/orders` calls (tell them apart by URL). Timing and traceability policy belong to the transport layer, exactly like retries.

The SDK ships `Kount\Http\SensitiveDataRedactor` so your trace never records a live credential: it masks the whole `Authorization` header (`Basic` on the token call, `Bearer` on order calls) and any JSON body key whose name contains `token` (the token endpoint's `access_token`). Everything else — order payloads, error documents, the token request's `grant_type`/`scope` form body — is left verbatim.

```
use Kount\Http\SensitiveDataRedactor;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

final class TracingClient implements ClientInterface
{
    public function __construct(
        private readonly ClientInterface $inner,
        private readonly YourTraceRecorder $recorder,
        private readonly SensitiveDataRedactor $redactor = new SensitiveDataRedactor(),
    ) {}

    public function sendRequest(RequestInterface $request): ResponseInterface
    {
        $requestBody = (string) $request->getBody(); // (string) rewinds a seekable stream

        $start = microtime(true);
        $response = $this->inner->sendRequest($request);
        $durationSeconds = microtime(true) - $start;

        $this->recorder->record(
            method: $request->getMethod(),
            uri: (string) $request->getUri(),
            requestHeaders: $this->redactor->redactHeaders($request->getHeaders()),
            requestBody: $this->redactor->redactBody($requestBody),
            statusCode: $response->getStatusCode(),
            responseBody: $this->redactor->redactBody((string) $response->getBody()),
            durationSeconds: $durationSeconds,
        );

        return $response;
    }
}

$kount = KountCommerceApi::builder()
    ->setClient(new TracingClient($inner, $recorder))
    ->setOAuth('your-client-id', 'your-client-secret')
    ->build();
```

Two things to keep in mind:

- **Seekable streams.** Reading a body consumes its stream. The `(string)` cast above seeks back to the start first (PSR-7 `__toString`), and the SDK re-reads the response the same way — so as long as your PSR-18 client returns *seekable* streams (guzzle and nyholm do), reading for the trace doesn't starve the SDK. With a non-seekable stream you must buffer the body yourself.
- **Redaction is yours to keep.** Only the values above are masked. If you also want to keep order PII (email, payment fields) out of traces, extend the redactor: `new SensitiveDataRedactor(sensitiveKeySubstrings: ['token', 'email', 'pan'])`, or pass extra header names via the first argument.

Sandbox
-------

[](#sandbox)

```
$kount = KountCommerceApi::builder()
    ->setOAuth('sandbox-client-id', 'sandbox-client-secret')
    ->useSandbox()
    ->build();
```

Warning

The default production/sandbox hosts (`https://api.kount.com/commerce`, `https://api-sandbox.kount.com/commerce`) and the OAuth token URL are taken from the OpenAPI spec plus Kount's public documentation but have **not yet been verified against live credentials**. If your Kount onboarding material lists different endpoints, override them with `setServerUrl()` and the `tokenUrl:` argument of `setOAuth()` / `useSandbox()`. Running the gated live smoke test (below) settles this.

Error handling
--------------

[](#error-handling)

Every exception the SDK throws implements `Kount\Exception\KountException`:

ExceptionMeaning`AuthenticationException`Credentials rejected by the token endpoint, or persistent `401` after a forced refresh`TransportException`The PSR-18 client failed (network, DNS, timeout) — including token endpoint calls; original exception in `getPrevious()``ApiException`Non-2xx API answer; carries `statusCode`, raw `body`, `response` and the typed error document: `errorDetails` (POST 400/500) or `rpcStatus` (all other error bodies)`MalformedResponseException`2xx answer that could not be decoded into the expected model`UnsupportedFeatureException`Legacy RIS feature without a 2.0 equivalent (facade only)`InvalidArgumentException`Pre-send validation (empty order id, missing credentials) — never reaches the network; extends `\InvalidArgumentException````
use Kount\Exception\ApiException;
use Kount\Exception\KountException;

try {
    $result = $kount->orders->evaluate($body);
} catch (ApiException $e) {
    $log->warning('Kount rejected the call', [
        'status' => $e->statusCode,
        'correlationId' => $e->errorDetails?->correlationId,
        'rpcMessage' => $e->rpcStatus?->message,
    ]);
} catch (KountException $e) {
    // transport / auth / decoding problems
}
```

The SDK parses responses strictly against the OpenAPI contract and fails closed: an unknown enum value or a number where the spec promises a string (e.g. the uint64-as-string amount fields) raises `MalformedResponseException` rather than degrading silently.

### Auditability

[](#auditability)

Kount's cross-system trace id arrives as the `X-Correlation-Id` response header and is not part of any success model. After a call, `$kount->orders->lastCorrelationId()` returns the header of the most recent response on that instance (null when absent). It refers to the last call only and is not concurrency-safe across a shared instance — read it right after the call whose trace you want.

Migrating from the legacy RIS SDK
---------------------------------

[](#migrating-from-the-legacy-ris-sdk)

`Kount\Legacy\RisClient` offers familiar `Inquiry` / `Response` semantics on top of the 2.0 pipeline — see [docs/MIGRATION.md](docs/MIGRATION.md) for the full field mapping.

> Upgrading between versions of this SDK? See [docs/UPGRADING.md](docs/UPGRADING.md) for breaking changes and migration steps.

```
use Kount\Legacy\Inquiry;
use Kount\Legacy\RisClient;

$ris = new RisClient($kount);

$response = $ris->process(
    (new Inquiry())
        ->setSessionId($deviceSessionId)
        ->setOrderNumber('ORDER-4711')
        ->setEmail('customer@example.com')
        ->setTotal(1999)          // cents, as in legacy RIS
        ->setCurrency('EUR'),
);

$response->getDecision();  // 'A' | 'D' | 'R'
$response->getOmniscore(); // float
```

Legacy-only features (Khash payment encoding, masked payment tokens) throw `UnsupportedFeatureException` instead of silently doing nothing.

Development
-----------

[](#development)

```
composer install
composer lint   # ECS code style
composer stan   # PHPStan, level max (zero baseline)
composer test   # unit + mocked integration tests
```

The live sandbox smoke test never runs in CI; it is gated on env vars:

```
KOUNT_SANDBOX_CLIENT_ID=... KOUNT_SANDBOX_CLIENT_SECRET=... composer test:live
```

Optional: `KOUNT_SANDBOX_SERVER_URL` / `KOUNT_SANDBOX_TOKEN_URL` to override the unverified default endpoints.

About this codebase
-------------------

[](#about-this-codebase)

The typed models were originally bootstrapped from [`docs/swagger.json`](docs/swagger.json) with a code generator; the SDK is hand-maintained since and has no generator toolchain dependency. `docs/swagger.json` remains as the API contract reference.

Sponsoring
----------

[](#sponsoring)

If this SDK saves you time, consider supporting its maintenance by [sponsoring @mfadul24 on GitHub](https://github.com/sponsors/mfadul24). ❤️

License
-------

[](#license)

[MIT](LICENSE)

###  Health Score

36

—

LowBetter than 79% of packages

Maintenance90

Actively maintained with recent releases

Popularity4

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity38

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

48d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/86224001?v=4)[Marcos Fadul](/maintainers/mfadul24)[@mfadul24](https://github.com/mfadul24)

---

Top Contributors

[![mfadul24](https://avatars.githubusercontent.com/u/86224001?v=4)](https://github.com/mfadul24 "mfadul24 (10 commits)")

---

Tags

sdkpsr-18paymentsfraudkountkount 360risk

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan, Rector

Code StyleECS

Type Coverage Yes

### Embed Badge

![Health badge](/badges/mfadul24-kount-commerce/health.svg)

```
[![Health](https://phpackages.com/badges/mfadul24-kount-commerce/health.svg)](https://phpackages.com/packages/mfadul24-kount-commerce)
```

###  Alternatives

[telnyx/telnyx-php

Official Telnyx PHP SDK — APIs for Voice, SMS, MMS, WhatsApp, Fax, SIP Trunking, Wireless IoT, Call Control, and more. Build global communications on Telnyx's private carrier-grade network.

36826.2k2](/packages/telnyx-telnyx-php)[sylius/sylius

E-Commerce platform for PHP, based on Symfony framework.

8.5k6.0M780](/packages/sylius-sylius)[shopware/core

Shopware platform is the core for all Shopware ecommerce products.

595.8M684](/packages/shopware-core)[typo3/cms

TYPO3 CMS is a free open source Content Management Framework initially created by Kasper Skaarhoj and licensed under GNU/GPL.

1.2k1.9M122](/packages/typo3-cms)[tempest/framework

The PHP framework that gets out of your way.

2.3k37.6k21](/packages/tempest-framework)[shopware/platform

The Shopware e-commerce core

3.4k1.5M3](/packages/shopware-platform)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
