x-laravel / payline-handyapi-bin-lookup
HandyAPI BIN lookup provider for x-laravel/payline
Package info
github.com/x-laravel/payline-handyapi-bin-lookup
pkg:composer/x-laravel/payline-handyapi-bin-lookup
Requires
- php: ^8.3
- illuminate/http: ^12.0|^13.0
- illuminate/support: ^12.0|^13.0
- x-laravel/payline: dev-master
Requires (Dev)
- orchestra/testbench: ^10.0|^11.0
- phpunit/phpunit: ^11.0|^12.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-09-28 22:49:56 UTC
README
HandyAPI BIN lookup provider for x-laravel/payline.
HandyAPI answers the scheme, the funding type, the issuing bank, the issuing country and
the card tier behind the first eight digits of a card number, for cards issued anywhere
including Turkey. It names no card family, so commission rates keyed on bonus or
maximum will not match a profile resolved here.
Requirements
- PHP ^8.3
- Laravel ^12.0 | ^13.0
- x-laravel/payline
Installation
composer require x-laravel/payline-handyapi-bin-lookup
Configuration
Name handyapi as the BIN lookup driver in config/payline.php:
'bin_lookup' => [ 'providers' => ['handyapi'], 'drivers' => [ 'handyapi' => [ 'api_key' => env('HANDYAPI_KEY'), ], ], ],
HandyAPI names no card family, so a shop routing on Turkish loyalty programs lists a provider that does before it and lets Payline merge the two answers:
'providers' => ['hoppa', 'handyapi'],
| Key | Default | Meaning |
|---|---|---|
api_key |
null |
Sent as x-api-key when set |
base_url |
https://data.handyapi.com |
Address to query |
cache_ttl |
7776000 |
Seconds a resolved profile is kept; 0 turns caching off |
cache_store |
null |
Cache store name; the application default when absent |
timeout |
5 |
Seconds to wait for an answer |
The endpoint answers without a key at a rate limited by address. Register for one before
relying on it in earnest; the header goes out only when api_key is set.
There is one address for everyone, so payline.test_mode does not apply here.
Failure Is Quiet
Every failure resolves to null rather than an exception: a BIN the service does not
hold, a refused key, a timeout and an unreachable host alike. A BIN lookup only improves
gateway selection, so it must never be the reason a payment fails. Routing falls back to
the default gateway, exactly as it does when no lookup is configured.
A BIN that is not held comes back as HTTP 200 with {"Status":"NOT FOUND"}, so the
provider reads Status rather than the status code.
A resolved profile is cached for 90 days. The fields this service fills are the durable ones: the country and the scheme belong to the range by assignment and do not move, unlike an issuer's trading name or a loyalty program, which change when banks merge or leave a scheme. A provider that answers those deserves a shorter life than this one.
An answer that resolves nothing is not cached, so a newly issued range works the next time it is asked about.
What is stored is HandyAPI's own payload, not the profile built from it. The profile is
rebuilt on every read, so a correction to this mapping or a new field on CardProfile
takes effect immediately instead of waiting the cache out.
What HandyAPI Fills
CardProfile |
HandyAPI | Note |
|---|---|---|
bin |
the queried digits | |
scheme |
Scheme |
|
type |
Type |
|
category |
CardTier |
only when the tier names one, such as CORPORATE |
productType |
CardTier |
such as CLASSIC, STANDARD, DANKORT |
issuer |
Issuer |
|
issuerCountry |
Country.A2 |
ISO 3166-1 alpha-2 |
source |
always handyapi |
|
raw |
the whole payload | the Luhn flag and the country's other codes live here |
family, issuerCode, currency, prepaid, numberLength and localSchemes stay
null.
The tier fills two fields because HandyAPI folds two ideas into one: CORPORATE and
BUSINESS say the card is commercial, while CLASSIC, STANDARD and PLATINUM only
name a product level and leave the category open.
Usage
Payline calls the provider on its own while routing a payment. To ask directly:
use XLaravel\Payline\BinLookupManager; $profile = app(BinLookupManager::class)->lookup('4571736012345678'); $profile?->issuerCountry; // 'DK' $profile?->issuedOutside('TR'); // true $profile?->issuer; // 'JYSKE BANK'
A routing policy can then keep foreign cards on one gateway:
class ForeignCardsGoToQnb implements GatewayRoutingPolicy { public function allows(Gateway $gateway, PaymentRequest $request, TransactionType $operation): bool { $profile = $request->card?->profile ?? $request->cardProfile; if ($profile === null || ! $profile->issuedOutside(config('payline.country'))) { return true; } return $gateway->getName() === 'qnb'; } }
A profile that names no country answers false to both issuedIn() and
issuedOutside(), so a policy written this way steps aside rather than rejecting a card
it knows nothing about.
Testing
composer test
docker compose --profile php84 up --build
License
MIT