Adding a country
- 1. Implement the driver
- 2. Parse whatever your API returns - you are in control
- 3. Register it
- 4. Score it (optionally per country)
- 5. Certify it
Adding a country (or any registry) is one driver + one registration. Nothing in the core, the checks, or the framework bridges changes.
1. Implement the driver
A RegistryDriver declares what it can answer and how to look it up. The easiest base is
AbstractRegistryDriver, which derives supports() from your declarations:
use Gawrys\Counterparty\Registry\AbstractRegistryDriver;
use Gawrys\Counterparty\Registry\{LookupRequest, LookupResult};
use Gawrys\Counterparty\Enum\RegistryCapability;
use Gawrys\Counterparty\Http\JsonHttpClient;
use Gawrys\Counterparty\Support\ArrayReader;
final readonly class GermanRegistryDriver extends AbstractRegistryDriver
{
public function __construct(private JsonHttpClient $http) {}
public function capabilities(): array
{
return [RegistryCapability::LegalEntityData, RegistryCapability::BusinessRegistration];
}
public function countries(): array
{
return ['DE'];
}
public function lookup(LookupRequest $request): LookupResult
{
// ... see below
}
}
2. Parse whatever your API returns - you are in control
lookup() is your code. Call any API, map its fields however you like, and return a
LookupResult. You decide what goes into data (exposed to checks and risk rules),
proofId (kept as due-diligence proof), and sourceUrl (grounding / audit link).
public function lookup(LookupRequest $request): LookupResult
{
$nip = $request->counterparty->nip;
if ($nip === null) {
return LookupResult::notFound('https://handelsregister.de/');
}
// Suppose the API returns:
// { "status": "ACTIVE", "company": { "legalName": "Muster GmbH", "regId": "HRB 12345" } }
$json = $this->http->getJson("https://my-registry.example/v2/company?taxId={$nip}");
$r = ArrayReader::of($json);
if ($r->string('status') !== 'ACTIVE') {
return LookupResult::notFound('https://handelsregister.de/');
}
$company = $r->nested('company');
return LookupResult::found(
data: [ // anything you want downstream
'legalName' => $company->string('legalName'),
'status' => $r->string('status'),
],
proofId: $company->string('regId'), // -> CheckResult::$proofId
sourceUrl: 'https://handelsregister.de/', // grounding / audit link
);
}
The ArrayReader helper
Decoded JSON is array<string, mixed>. ArrayReader extracts values type-safely so you
stay strict (PHPStan max / Psalm level 1) without scattering is_* checks:
| Method | Returns |
|---|---|
string($key) |
?string |
bool($key) |
?bool |
float($key) |
?float (accepts numeric strings) |
nested($key) |
a child ArrayReader (empty if absent) |
stringList($key) |
list<string> |
each($key) |
list<ArrayReader> (for arrays of objects) |
has($key) |
bool |
You are not required to use it - parse with plain PHP if you prefer.
Transport, 404, and empty responses
JsonHttpClientthrowsHttpRequestFailedon a non-2xx status or invalid JSON; theVerifierconverts an unexpected throw into aninconclusiveresult, so a flaky upstream never crashes verification.- A
2xxwith an empty body (e.g. HTTP 204) is treated as no content and decodes to[], so “not in this registry” becomes a clean not-found rather than an error. - Model “found but negative” explicitly: return
LookupResult::found(...)with astatusfield your risk rule can inspect, rather thannotFound().
3. Register it
Plain PHP:
$registries->extend('de', fn () => new GermanRegistryDriver($http));
Laravel (in a service provider or via the facade):
Counterparty::extendRegistry('de', fn ($cfg) => new GermanRegistryDriver($http));
Symfony - tag a service; a compiler pass collects it into the shared manager:
services:
App\Registry\GermanRegistryDriver:
arguments: ['@Gawrys\Counterparty\Http\JsonHttpClient']
tags:
- { name: counterparty.registry_driver, alias: de }
That is all. A RegistryCheck for LegalEntityData will now route DE counterparties to
your driver, and the result’s data is available to risk rules.
4. Score it (optionally per country)
The data you returned lands in CheckResult::$raw, and risk rules receive the country, so
you can score German results differently from Polish ones - see
Country-specific scoring.
5. Certify it
Make your driver pass the shipped contract test - this is what keeps the ecosystem trustworthy.