This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
minfraud-api-php is MaxMind's official PHP client library for the minFraud web services:
- minFraud Score, Insights, and Factors: Fraud detection services that analyze transaction data and return risk scores
- Report Transaction API: Feedback mechanism to report fraudulent transactions for continuous model improvement
The library provides an immutable, fluent interface for building requests using ->with*() methods, which return new objects rather than modifying existing ones.
Key Technologies:
- PHP 8.1+ (uses modern PHP features like readonly properties and strict types)
- GeoIP2-php for IP geolocation data
- MaxMind Web Service Common for HTTP client functionality
- PHPUnit for testing
- php-cs-fixer, phpcs, and phpstan for code quality
MaxMind/
├── MinFraud.php # Main client for Score/Insights/Factors
├── MinFraud/
│ ├── Model/ # Response models (Score, Insights, Factors, etc.)
│ ├── ServiceClient # Base HTTP client functionality
│ ├── ReportTransaction # Client for Report Transaction API
│ └── Util # Helper utilities (email hashing, etc.)
└── Exception/ # Custom exceptions (InvalidInputException, etc.)
The MinFraud class is immutable. All ->with*() methods return a new cloned object:
$mf = new MinFraud(1, 'LICENSE_KEY');
// Each with* call returns a NEW object
$request = $mf->withDevice(['ip_address' => '1.1.1.1'])
->withEmail(['domain' => 'example.com'])
->withBilling(['country' => 'US']);
// Original $mf is unchangedKey Points:
- Always assign the return value when chaining methods
- Use
clone $thiswhen creating modified objects - Never modify
$this->contentdirectly; always work on$new
All model classes use PHP 8.1+ readonly properties:
class Score implements \JsonSerializable
{
public readonly float $riskScore;
public readonly string $id;
public readonly array $warnings;
}Key Points:
- Properties are set once in the constructor and cannot be modified
- Use
readonlykeyword for all public properties - Nullable properties use
?Typesyntax
Models follow clear inheritance patterns:
Score→ base response with risk score, warnings, dispositionInsightsextendsScore→ adds detailed data (IP address, credit card, email, billing/shipping addresses, device)FactorsextendsInsights→ adds risk score reasons and subscores (deprecated)
All model classes implement \JsonSerializable:
public function jsonSerialize(): array
{
$js = parent::jsonSerialize();
if ($this->fieldName !== null) {
$js['field_name'] = $this->fieldName;
}
return $js;
}- Only include non-null/non-empty values in JSON output
- Use snake_case for JSON keys (matching API format)
- Properties use camelCase in PHP
The library validates input by default (can be disabled with validateInput: false option):
private function verifyCountryCode(string $country): void
{
if (!preg_match('/^[A-Z]{2}$/', $country)) {
$this->maybeThrowInvalidInputException("...");
}
}- Validation methods throw
InvalidInputExceptionwhen enabled maybeThrowInvalidInputException()respects thevalidateInputoption- Common validations: country codes, region codes, phone codes, email addresses, IP addresses
Most ->with*() methods support both array and named arguments:
// Array style (snake_case keys)
$mf->withDevice(['ip_address' => '1.1.1.1', 'session_age' => 3600]);
// Named arguments (camelCase)
$mf->withDevice(ipAddress: '1.1.1.1', sessionAge: 3600);
// Cannot mix both - throws InvalidArgumentExceptionThe implementation uses func_num_args() to detect mixing and $this->remove() to extract values from the array.
# Install dependencies
composer install
# Run all tests
vendor/bin/phpunit
# Run specific test class
vendor/bin/phpunit tests/MaxMind/Test/MinFraud/Model/FactorsTest.php
# Run with coverage (if xdebug installed)
vendor/bin/phpunit --coverage-html coverage/# PHP-CS-Fixer (code style)
vendor/bin/php-cs-fixer fix --verbose --diff --dry-run
# Apply fixes
vendor/bin/php-cs-fixer fix
# PHPCS (PSR-2 compliance)
vendor/bin/phpcs -p --standard=PSR2 src/
# PHPStan (static analysis)
vendor/bin/phpstan analyze
# Validate composer.json
composer validateTests are organized by component:
tests/MaxMind/Test/MinFraud/Model/- Response model teststests/MaxMind/Test/MinFraud/ReportTransaction/- Report Transaction API tests
When adding new fields to models:
- Update the test method to include the new field in the
$responsearray - Add assertions to verify the field is properly populated
- Test both presence and absence of the field (null/empty handling)
- Verify JSON serialization includes the field correctly
Example:
public function testFull(): void
{
$response = [
'risk_score' => 42.5,
'id' => '12345678-1234-1234-1234-123456789012',
'funds_remaining' => 100.50,
'queries_remaining' => 5000,
];
$model = new Score($response);
$this->assertSame(42.5, $model->riskScore);
$this->assertSame('12345678-1234-1234-1234-123456789012', $model->id);
}When adding a new input field to a ->with*() method:
-
Add the named parameter to the method signature:
public function withEvent( array $values = [], ?string $newField = null, ): self {
-
Extract from array if using array style:
if (\count($values) !== 0) { $newField = $this->remove($values, 'new_field'); }
-
Validate the input if needed:
if ($newField !== null) { if (!\in_array($newField, ['valid1', 'valid2'], true)) { $this->maybeThrowInvalidInputException("..."); } $values['new_field'] = $newField; }
-
Update the request content:
$new = clone $this; $new->content['event'] = $values; return $new;
-
Update PHPDoc with full documentation
-
Add tests for the new field
-
Update CHANGELOG.md
When adding a new field to a response model:
-
Add the readonly property with proper type hints and PHPDoc:
/** * @var string|null description of the field */ public readonly ?string $fieldName;
-
Update the constructor to set the field from the response array:
$this->fieldName = $response['field_name'] ?? null;
-
Update
jsonSerialize()to include the field:if ($this->fieldName !== null) { $js['field_name'] = $this->fieldName; }
-
Add comprehensive PHPDoc describing the field
-
Update tests to include the new field in test data and assertions
-
Update CHANGELOG.md with the change
When creating a new model class:
- Determine the appropriate base class (Score, Insights, or standalone)
- Use
readonlyproperties for all public fields - Implement
\JsonSerializableinterface - Follow the constructor pattern: accept
array $responseand optionalarray $locales - Provide comprehensive PHPDoc for all properties
- Add corresponding tests with full coverage
When adding input validation:
-
Create a validation method following the pattern:
private function verifyFieldName(string $value): void { if (!preg_match('/pattern/', $value)) { $this->maybeThrowInvalidInputException("error message"); } }
-
Call it from the
->with*()method before setting the value -
Use
maybeThrowInvalidInputException()to respect thevalidateInputoption -
Add tests for both valid and invalid inputs
Always update CHANGELOG.md for user-facing changes.
Important: Do not add a date to changelog entries until release time.
- If there's an existing version entry without a date, add your changes there
- If creating a new version entry, do not include a date
- The release date will be added when the version is released
3.5.0
------------------
* A new `fieldName` input has been added to the `/event` object.
* Added `new_processor` to the payment processor validation.Attempting to modify $this directly breaks immutability.
Solution: Always clone before modifying:
$new = clone $this;
$new->content['device'] = $values;
return $new;Users cannot use both array and named arguments simultaneously.
Solution: Check argument count and throw clear exception:
if (\count($values) !== 0) {
if (\func_num_args() !== 1) {
throw new \InvalidArgumentException(
'You may only provide the $values array or named arguments, not both.'
);
}
}Adding new enums (event types, payment methods, etc.) requires updating validation lists.
Solution: When the API adds new values, update the validation arrays in the corresponding ->with*() methods and document in CHANGELOG.md.
New fields not appearing in JSON output.
Solution: Always update jsonSerialize() to include new fields:
- Check if the value is not null/empty before adding
- Use snake_case for JSON keys to match API format
- Call parent's
jsonSerialize()first if extending
- PSR-2 compliance enforced by phpcs
- PHP-CS-Fixer rules defined in
.php-cs-fixer.php - Strict types (
declare(strict_types=1)) in all files - Yoda style disabled - use normal comparison order (
$var === $value) - Strict comparison required (
===and!==instead of==and!=) - No trailing whitespace
- Unix line endings (LF)
composer install# Run all checks
vendor/bin/php-cs-fixer fix
vendor/bin/phpcs -p --standard=PSR2 src/
vendor/bin/phpstan analyze
vendor/bin/phpunit- PHP 8.1+ required
- Uses modern PHP features (readonly, named arguments, etc.)
- Target compatibility should match current supported PHP versions (8.1-8.4)