lemonway/php-sdk
PHP SDK for Lemon Way that wraps the payment API in convenient classes, covering authentication, wallet management, SEPA transfers, payment operations, and transaction status calls. Designed to simplify Lemon Way integration in Laravel and other PHP apps.
Despite the lemonway/php-sdk name, this is php-client-directkit-xml — a sample integration, not a maintained SDK. Six stars, zero dependents, no Composer manifest you can rely on, no tests. Your first move is to treat it as documentation you can read and fork, not as a composer require.
Day-one checklist:
Clone and read before you install. Open the repo and identify three things:
https://sandbox-api.lemonway.fr/mb/{account}/directkitxml/ (sandbox) vs. the production host.wlLogin, wlPass, walletIp, walletUid, version, lang, plus the method-specific block.SimpleXMLElement or string handling — assume no DTOs).Get sandbox credentials from the Lemonway dashboard: account number (goes in the URL path), wlLogin, wlPass. Whitelist your server IP — Lemonway gates by IP, sandbox included. This bites people before they write a single line of business logic.
Configure it the Laravel way in config/services.php:
'lemonway' => [
'endpoint' => env('LEMONWAY_ENDPOINT'),
'account' => env('LEMONWAY_ACCOUNT'),
'login' => env('LEMONWAY_LOGIN'),
'password' => env('LEMONWAY_PASSWORD'),
'lang' => 'en',
'version' => '1.8',
],
First call: read-only. Hit GetWalletDetails or GetWalletStatus for a wallet you created manually in the dashboard. This proves credentials, endpoint, IP whitelist, and response parsing in one shot.
Do not open with MoneyIn, SendPayment, or RegisterCard — those are irreversible and produce noisy failures against real money.
Bind it behind an interface — the sample is not a dependency, it's a protocol reference. The single highest-leverage decision is an adapter:
interface PaymentGateway {
public function createWallet(CreateWalletRequest $req): WalletId;
public function getWallet(WalletId $id): WalletDetails;
public function registerCard(RegisterCardRequest $req): CardId;
public function initiateMoneyIn(MoneyInRequest $req): MoneyInRedirect;
public function payout(PayoutRequest $req): PayoutResult;
}
Implement LemonwayClient implements PaymentGateway in app/Infrastructure/Payments/Lemonway/. This is what makes a future swap to DirectKit JSON — or to Mangopay/Adyen — a config change instead of a rewrite.
Replace the sample's cURL with Laravel's Http client immediately. You get timeouts, backoff, Http::fake() in tests, request/response events, and centralized logging for free. The sample's raw cURL gives you none of that, and a money-moving call with no timeout is a support ticket waiting to happen.
Build the XML envelope in one place. A single XmlEnvelopeBuilder that escapes values (the sample string-concatenates XML, which is an injection vector) and takes a method name plus payload:
$xml = $this->envelope->build('GetWalletDetails', ['wallet' => $walletId]);
$response = Http::timeout(15)
->withBody($xml, 'text/xml')
->post($this->endpoint);
Map XML to DTOs at the boundary. Parse with simplexml_load_string, then immediately hydrate value objects (Wallet, MoneyInResult, KycDocumentStatus). Never let SimpleXMLElement leak into a controller, Livewire component, or model.
Model the async edges explicitly. MoneyInWebInit returns a redirect URL; terminal state arrives via return URL and/or server callback. The pattern:
payment_intents table) with your reference mapped to Lemonway's wlRef before redirecting.Queue the money path. Any call that moves funds goes through a job with an idempotency key stored on the intent row. Retries with exponential backoff are fine on 5xx and network errors; never retry a non-idempotent call without a stored reference.
Secrets stay out of config files. wlPass in services.php pulled from env() is fine for the spike; for anything real, use Vault/SSM and plan for rotation, because password-based auth means rotating means redeploying.
No Composer version means no upgrade path. Fork it into your own VCS with a real composer.json, a version tag, PSR-4 autoloading, and tests around the XML serialization and error mapping. Then it's a controlled asset you can upgrade deliberately.
XML is on a deprecation runway. Lemonway has been steering integrators toward DirectKit JSON with OAuth2. Confirm the XML timeline with them before you build breadth. If it's short, build against JSON from day one and keep the port interface stable.
HTML error pages will be parsed as XML. Provider maintenance, WAF blocks, and 5xx pages return non-XML. Make your parser fail loudly into a known LemonwayMalformedResponse error type rather than returning half-null DTOs.
Error classification is your job. The sample doesn't map Lemonway error codes to a taxonomy. Build one: InsufficientFunds, WalletNotFound, CardDeclined, KycRequired, DuplicateReference, RateLimited. Everything else is LemonwayUnknown — log the raw XML for those.
PCI scope is a fork in the road. If the sample posts raw PAN to your server, adopting it as-is puts you in PCI DSS scope and drags in PSD2/SCA and 3-D Secure obligations. If Lemonway offers a hosted/iframe card entry, use that and never let a PAN touch your infrastructure.
IP whitelisting is non-negotiable. Calls from an unwhitelisted IP fail with a response that looks like an auth error. If you're debugging "credentials are right but it won't work," check the whitelist before anything else.
wlRef/reference is your idempotency anchor. Store it on every write. It's what lets you reconcile, retry safely, and answer "did this actually go through?" when a response was lost.
Watch for PHP 5.x/7.x idioms if you vendor the sample — no namespaces, no type declarations, no dependency injection. Nothing is mockable until you rewrite it, which means no meaningful tests until then.
Multi-step flows leave orphans. Wallet created but card registration failed is a real state. Either compensate on failure or have a reconciliation job that sweeps for dangling wallets against the Lemonway account.
Log with PII redaction. KYC documents, IBANs, and card data will land in your logs if you're careless with request/response logging. Redact at the Http client middleware layer, not per-call.
Ramp-up is domain, not code. The code is trivial. Understanding wallets vs. accounts, fee/commission models, IBAN/BIC validation, SEPA direct debit, KYC document types, and PSD2/SCA rules is where the 1–2 weeks actually go. Someone on the team must own payments domain knowledge — refunds, disputes, and reconciliation are where these integrations fail.
How can I help you explore Laravel packages today?