monext/payline-sdk
A PHP SDK for integrating Monext Payline payment services into your application. It provides client classes and helpers for calling Payline web services, managing payment transactions, and connecting PHP projects to the Payline API.
Install the SDK first, then verify its constraints before writing application code:
composer require monext/payline-sdk
composer show monext/payline-sdk # read the declared PHP + ext-* requirements
ls vendor/monext/payline-sdk/src # namespace layout, entry points
Look at the SDK's PaylineClient / Payline classes and the WebService/Object folders first — that's where Payment, Transaction, Order, Wallet, DoWebPayment, and DoAuthorization live. Read the vendor docs alongside the source; the two drift.
Prerequisites you must have in hand before a single line of code:
ext-curl, ext-json, and — on legacy code paths — ext-soap are present in your container image. SOAP is the silent blocker.Your first use case should be hosted payment (DoWebPayment), not direct card capture. Hosted keeps you in SAQ A-EP territory, gives you a redirect flow you can ship in days, and forces you to solve the two things everything else depends on: order state persistence and the return-URL handler.
Wire it up minimally:
// config/payline.php
return [
'merchant_id' => env('PAYLINE_MERCHANT_ID'),
'access_key' => env('PAYLINE_ACCESS_KEY'),
'contract_number' => env('PAYLINE_CONTRACT_NUMBER'),
'environment' => env('PAYLINE_ENV', 'homologation'), // or 'production'
'return_url' => env('PAYLINE_RETURN_URL'),
'cancel_url' => env('PAYLINE_CANCEL_URL'),
];
// app/Providers/PaylineServiceProvider.php
public function register(): void
{
$this->app->singleton(PaylineClient::class, fn () => new PaylineClient(
config('payline.merchant_id'),
config('payline.access_key'),
config('payline.contract_number'),
config('payline.environment'),
));
}
Secrets come from Vault / AWS Secrets Manager in deployed environments, not .env baked into images.
The happy path: create an Order with your merchant reference → call DoWebPayment with returnURL/cancelURL → redirect the user → on return, call getWebPaymentDetails with the returned token and reconcile the result against the order you stored before redirecting.
Never call the SDK from a controller. Put an anti-corruption layer in front of it: an internal PaymentGateway interface with authorize(), capture(), refund(), status(). The SDK's vocabulary (contractNumber, DoAuthorization, Payment/Transaction objects) must not leak into controllers, models, or domain events. When you add a second PSP, you swap the adapter — the app doesn't know.
interface PaymentGateway
{
public function authorize(AuthorizeRequest $r): AuthorizeResult;
public function capture(string $ref, Money $amount): CaptureResult;
public function refund(string $ref, Money $amount): RefundResult;
public function status(string $ref): PaymentStatus;
}
Every payment attempt gets a client-generated merchant reference and idempotency key, persisted on your orders row before you call the SDK. Payline's Order.ref (merchant reference) is your reconciliation key — you will need it to find orphaned transactions after timeouts.
Model the lifecycle as a state machine, not booleans. created → redirected → authorized → captured → refunded, with explicit failed, cancelled, unknown branches. unknown (post-timeout, no response) is a real state and must not be conflated with failed. 3DS challenges, hosted-page abandonment, and partial refunds all require resumable state — treat the return-URL callback as "resume the flow," not "fire and forget."
Webhook endpoint pattern:
Route::post('/webhooks/payline', PaylineWebhookController::class)
->withoutMiddleware([VerifyCsrfToken::class]);
Accept the webhook as an event, not as the source of truth: verify the signature (see Gotchas — verify Payline's documented scheme rather than guessing, or fall back to IP allowlist + a follow-up getTransactionDetails), persist the event ID, and reply 200 fast. Do the state transition on the follow-up query, not on the payload. Idempotency: store webhook IDs and reject duplicates.
Reconciliation job — build this first, not last. A scheduled command that pages Payline's transaction report for the last 24–48 hours and diffs against your orders. It resolves unknown states, catches lost webhooks, and prevents duplicate charges when you retry after a timeout. Without it, every timeout becomes either a duplicate charge or silent revenue loss.
Test against the sandbox, not mocks. Write contract tests that exercise: successful authorize, decline, timeout, malformed response, 3DS challenge, 3DS abandonment. Mocks will lie to you about the payload shapes and error codes.
Wire domain events (PaymentAuthorized, PaymentFailed, PaymentRefunded) out of the adapter, so notifications, order fulfillment, and analytics subscribe to your domain rather than to Payline.
Timeout discipline: set explicit connect/read timeouts on whatever transport the SDK uses (5s connect / 15s read is a reasonable baseline). Payline adds 300–1500ms plus redirect time for 3DS — do not block a synchronous web request for the full round-trip if you can avoid it.
Stage the rollout: auth/config → authorize/capture → webhook receipt → refunds → recurring/wallets → 3DS rules. Each is independently shippable and revertible.
retryable vs terminal, and declined vs unknown. A declined is a business outcome; unknown is a reconciliation task.ext-soap. Older Payline code paths use SOAP; a minimal PHP-FPM image without it will fail at runtime, not install time. Grep the vendor source for SoapClient if you can't find it in composer.json requirements.amount handling for EUR (and any non-EUR currencies) against Payline's expectation of integer minor units. French-market defaults can mask EUR-only assumptions that break when you add another currency."monext/payline-sdk": "x.y.z", not ^x). Subscribe to the release feed and budget a few hours per quarter — the bigger maintenance cost is Payline endpoint version deprecations, not the library itself.PaymentGateway so a future transport swap doesn't touch call sites.How can I help you explore Laravel packages today?