openreceive / laravel
Accept Bitcoin Lightning payments in Laravel with your own wallet and existing database. Optional USDT, USDC, SOL and ETH swaps settle as BTC over Lightning.
Requires
- php: ^8.2
- illuminate/console: ^11.0 || ^12.0
- illuminate/contracts: ^11.0 || ^12.0
- illuminate/database: ^11.0 || ^12.0
- illuminate/http: ^11.0 || ^12.0
- illuminate/routing: ^11.0 || ^12.0
- illuminate/support: ^11.0 || ^12.0
- nyholm/psr7: ^1.8
- openreceive/openreceive: ~0.4.19
- symfony/psr-http-message-bridge: ^7.0
Requires (Dev)
- orchestra/testbench: ^9.0 || ^10.0
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^11.5 || ^12.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Add Bitcoin Lightning checkout to your Laravel app and receive payments directly into a wallet you control. OpenReceive handles payment attempts and settlement reconciliation in your existing database, while your app keeps its orders, users, prices, and fulfillment.
This adapter connects the openreceive/openreceive PHP engine to Laravel's
routes, service container, migrations, and Artisan commands. Its install
command creates the configuration and three application hooks.
OpenReceive supports optional swaps from USDT, USDC, SOL, and ETH through a configured swap provider. The provider converts the payment to BTC over Lightning, which settles into the merchant's connected wallet. Available assets and networks depend on the provider; swaps are optional.
Install
Requires PHP 8.2 or later with ext-gmp, sodium, mbstring, and a PDO
driver, plus Laravel 11 or 12.
composer require openreceive/laravel php artisan openreceive:install php artisan migrate
openreceive:install writes three files:
config/openreceive.php— settings. Hooks are named by CLASS, not closures, sophp artisan config:cacheworks;app/OpenReceive/Host.php— the three hooks (authorize,amountFor,onPaid), with the two generated placeholders wired and the fulfillment note as comments. The engine warns at every boot until both placeholders are replaced;database/migrations/*_create_openreceive_tables.php— one migration foropenreceive_paymentsandopenreceive_meta, in your database, run byphp artisan migratelike any other. Its DDL comes from the engine'sPaymentsSchema::statements($driver); PostgreSQL, MySQL/MariaDB and SQLite.
Set NWC_URI in .env, fill in Host.php against your order model, and
render <openreceive-checkout reference="{{ $order->id }}"> with
@openreceive/elements through Vite. The whole walk-through is the
Laravel quickstart.
What the provider does
- Binds
OpenReceive\Hostfromconfig('openreceive.host'), theSqlPaymentRepositoryoverDB::connection(config('openreceive.connection'))'s PDO, andOpenReceive\Server\ServicefromNWC_URI(with swap providers auto-built fromLSC_URI_PRIMARY/LSC_URI_BACKUP), all lazily. - Mounts one catch-all route under
config('openreceive.route_prefix')(/openreceive) andconfig('openreceive.middleware')(['web']): the engine's PSR-15 handler decides 404/405 inside the prefix, reads the body under its own cap and content-type gate, and refusesSec-Fetch-Site: cross-site. Thewebgroup adds Laravel's session and CSRF check —VerifyCsrfTokenreadsX-CSRF-TOKEN, which the checkout client sends from<meta name="csrf-token">with no attribute needed. - Hands your
authorizethe Illuminate request ($context->request), with its session and cookies, the way Rails passesActionDispatch::Request. The client IP for the optional per-IP rate limit is$request->ip(), soTrustProxiesdecides who the payer is. - Warns at boot while a placeholder trait is still in
Host.php; in production runs the receive-only wallet preflight eagerly when a web process boots (a bad or spend-capableNWC_URIstops the deploy), skipping the known secretless build commands (config:cache,migrate, …).
Container seams a host or a test may bind before the engine is resolved:
OpenReceive\Nwc\ReceiveNwcClient (the wallet client, in place of NWC_URI),
OpenReceive\Rates\PriceProvider, and openreceive.swap_providers (a list; []
disables swaps). The engine's Testing\FakeWallet and Testing\FakeSwapProvider
fit those seams, which is how the tests here and the Buy a Button demo's
DEMO_WALLET=testkit mode run with no wallet and no network.
Commands
| Command | What it does |
|---|---|
php artisan openreceive:install [--force] |
Scaffold the config, the Host and the migration. |
php artisan openreceive:doctor [--no-wallet] |
Credentials as set/unset (never a value), the host and its placeholders, the route mount, the wallet preflight. |
php artisan openreceive:reconcile |
One reconciliation pass over pending attempts. |
php artisan openreceive:notifications |
The optional long-running worker: NWC-02 payment_received listener plus a periodic pass every OPENRECEIVE_NOTIFICATIONS_RECONCILE_INTERVAL_SECONDS (default 15), resubscribing with backoff. One per deployment. |
Settlement discovery needs no worker: every mounted OpenReceive payment route
first runs one reconcile pass through the durable openreceive_meta gate, so
pending attempts settle on any later request. openreceive:notifications only
makes it faster.
Development
composer install # resolves ../openreceive through the path repository vendor/bin/phpunit # Orchestra Testbench: routes, CSRF, authorize, migration, config:cache, doctor, preflight vendor/bin/phpstan # level 8 OPENRECEIVE_TEST_PGSQL_DSN='pgsql:host=127.0.0.1;port=5432;dbname=x' OPENRECEIVE_TEST_PGSQL_USER=… vendor/bin/phpunit --filter Migration
The path repository in composer.json is for this monorepo; Packagist
ignores it, and the published constraint on openreceive/openreceive is the
lockstep ~X.Y.Z of the same release.
Guides
Agent skills
Run php artisan openreceive:skills from your application to install the
offline skills bundled in the openreceive/openreceive dependency.
See agent setup. The bundled installers write
to .agents/skills/; use --dir .claude/skills for Claude Code. They replace
only integrate-openreceive and debug-openreceive-payment, preserving
unrelated skills.