Recipe: add a German registry (end to end)
Goal: verify DE counterparties against a Handelsregister-style API, then score the result with a DE-only rule.
1. The driver
use Gawrys\Counterparty\Registry\{AbstractRegistryDriver, 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, private string $apiKey) {}
public function capabilities(): array { return [RegistryCapability::LegalEntityData]; }
public function countries(): array { return ['DE']; }
public function lookup(LookupRequest $request): LookupResult
{
$taxId = $request->counterparty->euVat ?? $request->counterparty->nip;
if ($taxId === null) {
return LookupResult::notFound('https://handelsregister.de/');
}
$json = $this->http->getJson(
"https://handelsregister.example/v1/company?vat={$taxId}",
['Authorization' => 'Bearer ' . $this->apiKey],
);
$r = ArrayReader::of($json);
if (!$r->has('company')) {
return LookupResult::notFound('https://handelsregister.de/');
}
$c = $r->nested('company');
return LookupResult::found(
data: ['legalName' => $c->string('name'), 'status' => $c->string('status')],
proofId: $c->string('hrb'),
sourceUrl: 'https://handelsregister.de/',
);
}
}
2. Register it + add a check
use Gawrys\Counterparty\Registry\RegistryManager;
use Gawrys\Counterparty\Check\RegistryCheck;
$registries = new RegistryManager();
$registries->extend('de', fn () => new GermanRegistryDriver($http, $deKey));
$checks = [
// ... White List / VIES / sanctions ...
new RegistryCheck($registries, RegistryCapability::LegalEntityData, $clock, 'de.handelsregister', 'DE Handelsregister'),
];
3. Score DE differently
use Gawrys\Counterparty\Risk\{RiskRule, RiskContext, RiskSignal, Evidence};
final class GermanDissolvedRule implements RiskRule
{
public function evaluate(RiskContext $ctx): iterable
{
if ($ctx->counterparty->country !== 'DE') {
return;
}
foreach ($ctx->report->fromSource('de.handelsregister') as $r) {
if (($r->raw['status'] ?? null) === 'DISSOLVED') {
yield new RiskSignal('de.dissolved', 0.8, adverse: true,
evidence: Evidence::grounded('Company dissolved per Handelsregister.', $r->proofId ?? 'https://handelsregister.de/', 0.9));
}
}
}
}
4. Wire the verifier
$verifier = new Verifier(
$checks,
new RuleBasedRiskStrategy([
new GermanDissolvedRule(),
...iterator_to_array((function () { yield from [
new \Gawrys\Counterparty\Risk\Rule\SanctionsHitRule(),
new \Gawrys\Counterparty\Risk\Rule\InconclusiveCoverageRule(),
]; })()),
]),
$clock,
);
5. Certify
Add a GermanRegistryDriverTest extends RegistryDriverContractTestCase (see
Contract tests) wired to a mocked HTTP client. Done - DE is a
first-class country with its own scoring, and nothing in the core changed.