Download the PHP package snipershady/amilon without Composer
On this page you can find all versions of the php package snipershady/amilon. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download snipershady/amilon
More information about snipershady/amilon
Files in snipershady/amilon
Package amilon
Short Description PHP client library for the Amilon API: order, manage and distribute digital gift cards at scale
License GPL-2.0-only
Homepage https://www.spinfo.it
Informations about the package amilon
amilon
PHP client library for the Amilon B2B Web API — the platform welfare agencies, resellers and partner companies use to integrate and automate the ordering, management and distribution of digital gift cards at scale.
Unofficial integration. This is a community-maintained, open-source client. It is not affiliated with, endorsed by, or supported by Amilon S.r.l.; all product and company names are the property of their respective owners. Use it at your own risk against your own Amilon contract.
Contributions are always welcome — open an issue or a pull request. See Development for the local setup and the quality gate every change must pass.
Status
Early development. The client covers authentication and the first resource
operations — catalogue denominations (with a V1-compatible flat getProducts()
view), retailers, order creation, order read-back and contract balance. The
public surface is stable; more operations will be added the same way.
Requirements
- PHP 8.3+ (CI runs 8.3 and 8.4)
symfony/http-client^7.4— transport for the HTTP callssymfony/dotenv^7.1— load credentials from.envfilessnipershady/typeidentifier^2.0— typed reads of the decoded API responses
Installation
Namespace: Amilon\ → src/ (PSR-4).
Quick start
Credentials
The library never reads the environment on its own. The caller decides where
credentials live (a secrets manager, a Symfony parameter bag, $_ENV, a database
row), packs one environment's worth into an immutable CredentialDto tagged with
an Environment, and hands it to AmilonClientFactory::create() (a static
method). CredentialDto does no validation — every value is trimmed, checked and
normalised inside create(), which throws
Amilon\Exception\InvalidConfigurationException on anything missing, blank, not a
valid absolute URL, or (for the contract id) not a UUID.
CredentialDto field |
Notes |
|---|---|
username / password |
resource-owner credentials |
clientId / clientSecret |
OAuth client |
authDomain |
SSO host, normalised to one trailing / |
webDomain |
V1 Web API base (keep the /v1/ segment), normalised to one trailing / — still required, kept for rollback |
webDomainV2 |
V2 Web API base (keep the /v2/ segment), normalised to one trailing / — what the client actually talks to |
contractId |
contract UUID, lower-cased |
environment |
Environment::STAGING or Environment::PRODUCTION — labels the client; $client->isProduction() gates money-moving calls |
STAGING and PRODUCTION are isolated Amilon deployments with their own hosts, credentials and contract ids.
Reading credentials from .env
symfony/dotenv can load the AMILON_* variables from .env (committed,
placeholders) overridden by .env.local (git-ignored, real values):
Amilon\Configuration\Configuration::fromEnvironment() reads $_SERVER + $_ENV
and Configuration::fromArray() takes an explicit map, if you want to validate
that set on its own; to build a client, map it into a CredentialDto as above.
Operations
Every method is version-less: it returns a DTO from Amilon\Dto\Response\ that is
the same regardless of which API revision answered. The client currently speaks
v2 (ApiVersion::latest()).
AmilonClient method |
HTTP | Returns |
|---|---|---|
getToken() |
POST {authDomain}connect/token |
AccessTokenDto |
getDenominations(CountryEnum) |
GET contracts/{id}/{culture}/denominations |
MerchantDenominationCollectionDto |
getDenominationsComplete(CountryEnum) |
GET contracts/{id}/{culture}/denominations/complete |
MerchantDenominationCollectionDto |
getProducts(CountryEnum) |
GET contracts/{id}/{culture}/denominations (reshaped) |
ProductCollectionDto |
getRetailers(CountryEnum) |
GET contracts/{id}/{culture}/retailers |
RetailerCollectionDto |
getRetailerCategories(?string $categoryId, ?string $categoryName) |
GET retailers/categories |
RetailerCategoryCollectionDto |
makeOrder(CreateOrderRequestDto) |
POST orders/create/{id} |
OrderDto |
makeOrderPostponed(CreateOrderRequestDto, DateTimeImmutable $codeValidityStartDate) |
POST orders/createpostponed/{id} |
OrderDto |
getOrderInfo(string $externalOrderId) |
GET orders/{externalOrderId} |
OrderDto |
getOrderInfoComplete(string $externalOrderId) |
GET orders/{externalOrderId}/complete |
OrderDto |
getContractInfo() |
GET contracts/{id} |
ContractInfoDto |
Each operation below shows a minimal call and the DTO it returns. The response
blocks are symfony/var-dumper dumps of the actual objects (+ public, -
private property); string values are truncated for readability.
getToken()
The OAuth access token for the configured credentials, fetched on first use and reused until it is near expiry. Resource calls acquire it automatically — call this only when you need the raw token (e.g. to call Amilon yourself).
Response — AccessTokenDto
getDenominations(CountryEnum)
The merchants and their gift-card denominations the contract can sell in a
country (CountryEnum — IT, ES, DE, DK, FR, GB, NL, NO, PL,
PT, SE; the case is the country, the value the API language-COUNTRY
culture tag). The result is iterable and
countable — ->all(), ->count(), ->isEmpty(). Each merchant code is what
you pass to makeOrder() as the retailer id.
Response — MerchantDenominationCollectionDto
A DenominationDto comes in one of three shapes — read them with the predicates,
never by guessing from the raw fields:
getDenominationsComplete(CountryEnum)
Identical to getDenominations(), but every MerchantDenominationsDto also
carries its editorial ->extendedContent block (long copy, extra logo sizes,
category ids) and every DenominationDto fills in its five image* artwork
sizes.
Response — MerchantDenominationCollectionDto (same as above, plus)
getProducts(CountryEnum)
A backward-compatibility view of getDenominations() for integrations
written against the pre-v2 surface: it makes the same call and flattens the
merchant → denomination → price tree into the old flat ProductCollectionDto
(iterable/countable) of ProductDto. One row per price point; a variable
(open-range) denomination becomes a single row priced at its rangeMin with the
range carried across; a denomination with neither prices nor range is dropped.
active / visible are always true (v2 has no such flags), productType is a
constant "Voucher", art100 is always false, and name is synthesised
"{merchant} - {amount} {symbol}". Every row also carries the parent merchant
block V1's product row had (merchant*, rebateTypeName, vatValue*), copied
off the owning denomination merchant. New code should use getDenominations().
Response — ProductCollectionDto
getRetailers(CountryEnum)
The retailers (brands) available to the contract in a country.
Response — RetailerCollectionDto
getRetailerCategories(?string $categoryId = null, ?string $categoryName = null)
The platform-wide list of brand categories and their translated names — useful to
build a category filter over getRetailers() / getDenominations(). Not
contract-scoped; pass $categoryId and/or $categoryName to narrow it.
Response — RetailerCategoryCollectionDto
makeOrder(CreateOrderRequestDto)
Place an order with immediate fulfilment. The request is self-contained: your
own externalOrderId plus one OrderLineDto (retailerId, quantity, price)
per merchant. v2 identifies the item by retailer id and face value, so a line
with no price is rejected (InvalidOrderRequestException) before any HTTP call.
Spends real money on a Environment::PRODUCTION client — gate on
$client->isProduction().
Response — OrderDto
voucherscan be[]right after the call while Amilon is still issuing them — read the order back withgetOrderInfoComplete().orderDateand the voucher validity dates arenullwhen Amilon omits them or sends something unparseable.
makeOrderPostponed(CreateOrderRequestDto, DateTimeImmutable $codeValidityStartDate)
Same order rows and same OrderDto back as makeOrder(), but fulfilment is
deferred: Amilon registers the order now and issues the vouchers
asynchronously, valid from $codeValidityStartDate. That date is mandatory
and Amilon only accepts it when it is in the future and at most one month out — a
date outside that window is rejected with InvalidOrderRequestException before
any HTTP call. vouchers is normally empty on the confirmation; collect them
later with getOrderInfoComplete().
Response — OrderDto (vouchers normally empty)
getOrderInfo(string $externalOrderId)
Order summary for an order you placed, keyed by the externalOrderId you
chose: status and totals, no vouchers (GET orders/{externalOrderId}). Use
getOrderInfoComplete() when you need the issued vouchers too.
Response — OrderDto (vouchers is always [] from this view)
getOrderInfoComplete(string $externalOrderId)
The full order — status, totals and every issued voucher
(GET orders/{externalOrderId}/complete). This is how you pick up the vouchers of
a makeOrderPostponed() order once it has been fulfilled.
Response — OrderDto (same shape as makeOrder(); vouchers populated once fulfilled)
getContractInfo()
The configured contract's identity, validity window, currency and balances.
Response — ContractInfoDto
startDate / endDate / lastUpdate are null when Amilon omits or sends an
unparseable timestamp.
Errors
Every exception the library throws implements
Amilon\Exception\AmilonExceptionInterface, so one catch covers the
integration:
| Exception | When |
|---|---|
InvalidConfigurationException |
a credential is missing, blank or malformed (thrown by create(), before any HTTP) |
InvalidOrderRequestException |
an order request is malformed — blank id, no lines, blank retailer id, quantity below 1, a v2 line with no price, or a postponed codeValidityStartDate that is past / more than a month out (thrown when building the DTO or the request body, before any HTTP) |
AuthenticationException |
the SSO endpoint is unreachable, rejects the credentials, or returns an unusable token |
ApiRequestException |
a resource call fails — transport error, non-2xx status, or a non-JSON body |
On a non-2xx status ApiRequestException parses the error body
({"ErrorCode": …, "Message": …}, plus CreateOrder's ModelErrors) and
exposes it:
| Property / method | |
|---|---|
->httpStatus |
the HTTP status code (int), or null for a transport failure |
->rawErrorCode |
the ErrorCode string verbatim ("0105"), or null — the documented set is not exhaustive |
->errorCode |
Amilon\Enum\AmilonErrorCode when the code is one the client models, else null |
->validationErrors |
list<string> — one "Property: message; message" line per ModelErrors entry |
->isTransient() |
true for 0000 / 0500 — the same call can be retried |
How it works
- Version-less surface, internal versioning.
AmilonClientforwards each call to an implementation ofAmilon\Api\AmilonApiInterfacechosen byAmilon\Api\ApiVersion::latest(). A future API revision is a new implementation underAmilon\Api\V{n}\plus one enum line — callers and DTOs do not change. - Shared response DTOs. Each revision's mapper absorbs its own wire quirks
(PascalCase keys, numbers as strings,
0/1booleans) and produces the sameAmilon\Dto\Response\types, so callers never see a version-specific shape. - Backward-compatible shims stay in the surface. When v2 reshaped the
catalogue (
getProducts()→ the merchant-groupedgetDenominations()),getProducts()was kept as an adapter that reprojects the new response into the old flat DTO, so an existing integration upgrades without code changes. - No shared state. Each
create()call returns an independent client with its own HTTP transports; the OAuth token lives in memory for that client's lifetime only — there is no external cache.
Using it from Symfony
The library has no bundle and touches no DI container itself — by design (see
Credentials), it never reaches for $_ENV or a container on its
own. Wire it up with a small factory service that turns your app's own
configuration into a CredentialDto and calls the library's factory; register
the result under the library's own class so it can be type-hinted anywhere.
The
.env/.env.localfiles at the root of this repository only feed its own test suite (see Tests) — they are not read by a host application. A Symfony app that depends on this library keeps its ownAMILON_*configuration (its.env, a vault, whatever it already uses for secrets).
AmilonClient is now an ordinary autowireable service — Symfony builds one per
request (its default, non-shared-across-requests service lifetime), which
matches the library's own "one client, one transport, one in-memory token"
design: each request gets a fresh token lifecycle, nothing leaks between requests.
Development
Clone the repo and run composer install. Every change must keep all of the
following green:
| Tool | Configuration | Command |
|---|---|---|
| PHPUnit | failOnWarning / failOnRisky / failOnNotice / failOnDeprecation |
composer test |
| PHPStan | level: max, strict-rules, bleeding edge, zero suppressions in src/ |
composer phpstan |
| PHP-CS-Fixer | @Symfony + @Symfony:risky + strict-types rules |
composer cs-check |
| Rector | dead code, code quality, coding style, type declarations, privatization, naming, early return, PHPUnit sets — PHP 8.3 target | composer rector-dry |
| Script | Description |
|---|---|
composer test |
run the whole PHPUnit suite |
composer test:unit |
unit suite only — no network, always runnable |
composer test:integration |
integration suite against the Amilon sandbox — skips itself while AMILON_* are unset |
composer quality |
apply the Rector + PHP-CS-Fixer autofixes |
composer quality-check |
dry-run Rector + PHP-CS-Fixer + PHPStan |
Tests
- Unit tests cover the pure logic (mappers, DTOs, validation) with no I/O.
- Integration tests hit the real Amilon STAGING API and skip themselves until
the
AMILON_*credentials are present in the environment. - Integration tests that need more than the standard credential set — they place a
real sandbox order, or need a known order id — are in the
opt-ingroup, excluded from the CI run. Run them deliberately:
License
GPL-2.0-only. See LICENSE.
All versions of amilon with dependencies
snipershady/typeidentifier Version ^2.0
symfony/dotenv Version ^7.1
symfony/http-client Version ^7.4