slowbeardigger / xmr-pay-laravel
Accept Monero in Laravel. Non-custodial, view-key only, no monero-wallet-rpc, no server. A thin adapter over slowbeardigger/xmr-pay.
Requires
- php: >=8.0
- illuminate/support: ^9.0 || ^10.0 || ^11.0
- slowbeardigger/xmr-pay: ^0.1
README
Accept Monero in a Laravel app. The money goes straight to your wallet. There is no company in the middle, no account to open, no fee paid to anyone, and nobody can freeze, see, or hold your funds but you.
This is a thin adapter over the xmr-pay engine: a
config file, a service, and a facade. The engine does the work in pure PHP, holding only your
view key, against a Monero node you choose. No monero-wallet-rpc, no daemon on your box, no
backend service.
Install
composer require slowbeardigger/xmr-pay-laravel
php artisan vendor:publish --tag=xmr-pay-config
Then set your wallet in .env. Only the view key is sensitive, and on stagenet the coins are
free test XMR:
XMRPAY_ADDRESS=4...your primary address...
XMRPAY_VIEW_KEY=...your private view key...
XMRPAY_NETWORK=stagenet
XMRPAY_NODES=http://node.monerodevs.org:38089
XMRPAY_MIN_CONFIRMATIONS=10
Use it
Give each order a unique receiving subaddress, show the buyer a QR, and check the chain when you poll or run a job. The facade resolves the configured service.
use XmrPay\Laravel\Facades\XmrPay; // when an order is created — derive its own subaddress from your address + view key $index = $order->id; // any unique integer per order $address = XmrPay::subaddressFor($index); $uri = XmrPay::paymentUri($address, '0.25', "Order #{$order->id}"); // store the index and the current height on the order, then render $uri as a QR $order->update(['xmr_index' => $index, 'xmr_from' => XmrPay::tipHeight()]); // later — in a scheduled command, a queued job, or a poll route — ask if it is paid $r = XmrPay::checkOrder($order->xmr_index, '0.25', $order->xmr_from); if (! empty($r['paid'])) { $order->markPaid(); // funds are already in your wallet }
checkOrder() sums every confirmed payment to the order's subaddress, so a partial payment or a
top-up settles once the total adds up. The scan is bounded; for a long-open order, call it again
from the returned scanned_to + 1 on the next poll.
If you would rather have the buyer paste a transaction id ("I've paid"), verify a single one:
$r = XmrPay::verifyPayment($txid, $address); // $r['found'], $r['commitment_ok'], $r['confirmations'], $r['amount_atomic']
The truths (please read before taking real money)
Monero is irreversible and the sender is hidden, so there are no automatic refunds. If you need to refund someone you send them XMR back by hand. You are trusting the node you point it at. A public node is fine for tips; for real revenue run your own, or list two nodes so a lagging one can only delay a payment, never confirm it early. Few confirmations is fast but reversible. Raise
XMRPAY_MIN_CONFIRMATIONSfor higher-value orders. That is your risk dial. The browser is never trusted. Release goods only aftercheckOrder()/verifyPayment()confirmed a real payment on-chain.
How it works
Nothing always-on is required. checkOrder() reads public chain data from the node and does the
Monero math in PHP with your view key: it derives the order subaddress, detects the output, verifies
the RingCT amount commitment, and counts confirmations. The same engine powers
xmr-pay for WooCommerce; the verification is
cross-checked against the reference Monero library on real stagenet payments.
Status
New. The engine underneath is the same one the WooCommerce plugin runs in production, and this adapter is verified end to end against real stagenet payments. It has not yet seen wide Laravel production use. Test on stagenet first.