paysera/lib-api-bundle
A Symfony/PHP bundle for integrating Paysera API services into your application. It streamlines configuration, dependency injection, and API request handling, making it easier to connect payment flows, callbacks, and other Paysera endpoints with less boilerplate.
This is a query/pagination layer for API endpoints: it takes request parameters (sort, filters, pagination) and turns them into a shaped, paginated response, with errors reported as machine-readable codes (invalid_parameters, internal_server_error). If you're on Laravel, install it as you would any other package, publish whatever config it ships, and look first at:
config/*.php for default page size, allowed sort fields, and parameter naming (sort vs sort[], etc.).First use case: expose a paginated, sortable index endpoint for one Eloquent model, e.g. GET /api/posts?sort=-created_at&page[number]=2. Verify three things before writing anything else:
# happy path
curl -s 'https://app.test/api/posts?sort=-created_at' | jq
# unsupported sort field -> should now be 400, not 500
curl -s -o /dev/null -w '%{http_code}\n' 'https://app.test/api/posts?sort=does_not_exist'
# confirm the body carries the error code, not just the status
curl -s 'https://app.test/api/posts?sort=does_not_exist' | jq '.errors[0].code'
That third check is the whole point of 1.8.2 — previously an unknown sort field on a paginated endpoint blew up as 500 internal_server_error; it now reports 400 invalid_parameters. Once you see invalid_parameters coming back, you know the package is wired in correctly and you can stop debugging the integration.
Allow-list sort fields explicitly. Don't let the parser infer sortable columns from the schema. Keep a per-endpoint (or per-model) whitelist so an unsupported field fails fast and predictably:
// app/Http/Controllers/Api/PostController.php
public function index(PostIndexRequest $request)
{
return PostResource::collection(
$this->query->paginate(Post::query(), $request->validated())
);
}
With the allow-list in place, ?sort=does_not_exist becomes a routable 400 rather than a database-level surprise.
Treat invalid_parameters as a validation channel, not an exception. The most useful pattern is to let the package's 400 responses flow through your normal API error envelope so clients see one consistent shape:
// app/Exceptions/Handler.php
public function register(): void
{
$this->renderable(function (\YourPackage\Exceptions\InvalidParameters $e) {
return response()->json([
'errors' => [[
'status' => '400',
'code' => 'invalid_parameters',
'detail' => $e->getMessage(),
]],
], 400);
});
}
This keeps sort/filter/pagination errors distinguishable from genuine 500s on the client side — which is exactly the distinction 1.8.2 fixed.
Wire sorting and pagination as middleware or a form request, not in controllers. A PostIndexRequest that validates sort, filter.*, and page.* before the controller runs gives you one place to add fields and one place to test. The controller then stays a three-liner and every endpoint behaves the same way.
Feature-test the contract, not the implementation. Assert status and error code, because that pair is what clients branch on:
public function test_unsupported_sort_returns_400(): void
{
$this->getJson('/api/posts?sort=does_not_exist')
->assertStatus(400)
->assertJsonPath('errors.0.code', 'invalid_parameters');
}
Add this test whenever you touch the sortable allow-list — it's the cheapest regression guard against a silent return to 500s.
Keep an eye on PHP/Composer constraints. 1.8.2 ships CI jobs against Symfony 3.4 and 4.4 with Composer 2.10, which means the package's own constraint set is broad. In a Laravel app that's harmless, but if you're also installing it into a legacy Symfony service (or a package that pins Symfony 3.4), verify with composer why-not before blaming Laravel.
The 400 change is a contract change for anyone who coded around the old 500. If you had client-side retry logic or alerting keyed on internal_server_error from paginated endpoints, it will go quiet. Grep your monitoring and front-end error handling for internal_server_error before deploying 1.8.2.
Don't assume all bad sort input is now 400. 1.8.2 specifically covers the unsupported field case on paginated endpoints. Malformed values on non-paginated endpoints, bad filter syntax, or an unsupported sort on a collection endpoint may still take a different path. Write one test per endpoint shape rather than one global assertion.
invalid_parameters is a 400, so it bypasses $dontReport reasoning in the wrong direction. If your exception handler reports every 4xx to Sentry/Bugsnag, you'll get noisy alerts the moment a client sends a typo'd sort field. Add InvalidParameters to $dontReport (or the equivalent shouldntReport) — this matters more now that these surface cleanly rather than as 500s.
Sort semantics need normalising at the edge. -created_at vs created_at:desc vs sort=-created_at is a decision the package may or may not make for you. Pick one syntax, document it, and validate it in the form request; don't let two client teams invent two conventions that both half-work.
The CI change tells you something about Composer 2.10. The package now lets dependencies with known security advisories install so old Symfony jobs survive. That's a build-time concession, not a runtime guarantee — running composer audit against your own lockfile still matters, especially if you inherit Symfony 3.4-era transitive dependencies through this package.
Debug by isolating the parser. When a sort silently doesn't apply, hit the endpoint with the package's config caching disabled and log the parsed parameters before the query runs. Most "sort doesn't work" reports are an allow-list miss or a parameter-name mismatch (sort vs sort[] vs order_by), not a package bug.
Extension point worth knowing: the sort-field allow-list and the error-code mapping are usually overridable via config or a bound interface. If you need a custom error payload, override that before you consider subclassing the paginator — the latter breaks on minor releases.
How can I help you explore Laravel packages today?