nti/payliance-bundle
Integrate Payliance payment services into your PHP application with this bundle. It provides configuration and service wiring for connecting to Payliance APIs, helping streamline payment workflows, reduce boilerplate setup, and keep payment integration organized within your existing stack.
The first thing to internalize: nti/payliance-bundle is a Symfony bundle, not a Laravel package. There is no PaylianceServiceProvider, no config/payliance.php to publish, no facade. composer require nti/payliance-bundle in a Laravel app will install files but nothing will boot. With 1 star, 0 dependents, and a last release in August 2020, treat it as reference source code, not a maintained dependency.
Where to look first, in order:
composer.json — confirm "type": "symfony-bundle", the PHP version constraint, and which symfony/* components it drags in.src/DependencyInjection/*Extension.php and Resources/config/services.yml — this is where the real configuration schema and constructor arguments live. This tells you exactly what the API client needs.src/Api/Client.php or similar) — the actual Payliance HTTP calls, endpoints, and auth scheme.src/Resources/config/ — the bundle's default config keys (base URL, credentials, merchant ID), which you will translate to Laravel config manually.Pick an integration route before writing any code:
app/Services/Payliance/. No Symfony dependency, full control, Laravel-idiomatic.symfony/dependency-injection and friends for zero benefit. Avoid.Minimal extracted setup:
// config/services.php
'payliance' => [
'base_url' => env('PAYLIANCE_BASE_URL', 'https://sandbox.payliance.com'),
'username' => env('PAYLIANCE_USERNAME'),
'password' => env('PAYLIANCE_PASSWORD'),
'merchant_id' => env('PAYLIANCE_MERCHANT_ID'),
],
// app/Providers/AppServiceProvider.php
$this->app->singleton(PaylianceClient::class, fn ($app) =>
new PaylianceClient($app['config']['services.payliance'])
);
First use case to prove the wiring: a read-only call (list merchants, fetch a transaction, or whatever "ping"/lookup endpoint the client exposes) executed from php artisan tinker against sandbox credentials. Do not start with a charge.
Wrap the client, don't call it from controllers. Define your own interface (App\Contracts\PaymentGateway) and have the extracted PaylianceClient implement it. This is the single highest-value decision, because it lets you swap to Payliance's current API or another processor without touching business logic.
Normalize the transport. Once extracted, replace Guzzle calls with Http::withBasicAuth(...)->baseUrl(...) so timeouts, retries, logging, and Http::fake() tests come free from Laravel. Keep the original Guzzle code in a git tag for reference on request shapes.
Every money movement goes through a queued job. ACH submissions and card authorizations should never block a request. Pattern:
final class SubmitAchDebit implements ShouldQueue
{
public int $tries = 3;
public array $backoff = [10, 60, 300];
public function handle(PaylianceClient $client): void
{
$result = $client->achDebit($this->payment->toGatewayPayload());
$this->payment->recordGatewayResponse($result);
}
}
Idempotency is mandatory. Add a unique idempotency_key (UUID) to your payments table and pass it to the gateway if supported. If the old client doesn't support it, enforce it locally: a submitted_at column and a guard clause that refuses a second submission for the same payment row.
Model the lifecycle locally with a state machine. draft → submitted → settled | returned | failed. Never derive state from a single API response — ACH returns and chargebacks arrive days later via webhook or reconciliation.
Webhook/return-URL handling. Register a route outside auth middleware, exclude it from CSRF, respond 200 immediately, and push payload processing to a job. Verify whatever signature scheme Payliance offers (the 2020 client likely doesn't implement one — check the docs; if none exists, restrict by IP allowlist and reconcile amounts against your DB rather than trusting the body).
Nightly reconciliation command. php artisan payliance:reconcile --date=yesterday that pulls transaction statuses and diffs against local rows. Schedule it in routes/console.php. This catches dropped webhooks, which are inevitable.
Testing seam. Because you own the interface, tests use a fake implementation for unit tests and Http::fake() with recorded sandbox fixtures for integration tests. Keep a tests/Fixtures/payliance/*.json directory — the real API responses are your documentation since the package's docs are stale.
Psr\Log\LoggerInterface, possibly an EventDispatcherInterface, and a config array. If you instantiate manually you must supply these — use app('log') for the logger and null or app('events') for the dispatcher, then verify nothing calls dispatch() on a null.%env(PAYLIANCE_USERNAME)% syntax, which Symfony's container resolves and Laravel does not. Strip these and map to config/services.php with real env() calls. Only use env() inside config files so php artisan config:cache doesn't silently break you.200 OK with an error code and message in the JSON body. A status-only check will mark failed charges as successful. Always inspect a success/response_code style field and map it to your state machine.PAYLIANCE_BASE_URL per environment, and add a boot-time guard in AppServiceProvider that throws if app()->isProduction() is false but the base URL contains sandbox while credentials look live (or vice versa). Mixing these is the classic way to move real money in a demo.$tries will happily resubmit an ACH debit on timeout. If the request reached Payliance but the response was lost, a naive retry double-charges. Only retry on connection failures and 5xx; treat timeout as "unknown — reconcile before retrying."tap on the client that masks anything matching \d{9,17} before it hits your log channel. Store only the last four digits in your database; tokenize where the API allows it.PaylianceGateway implements PaymentGateway) around it rather than extending the vendored class directly; vendored code changes are then isolated to one file.request/response to storage/logs/payliance.log when APP_DEBUG=true, and keep the raw payloads as fixtures. Without this you'll be guessing at request shapes, because the bundle's documentation (if any) is from 2020 and will not match the current API.How can I help you explore Laravel packages today?