Search by

slowbeardigger / xmr-pay-laravel

SlowBearDigger

Laravel service, facade and configuration for the XMRPay PHP payment engine.

Package info

github.com/SlowBearDigger/xmr-pay-laravel

pkg:composer/slowbeardigger/xmr-pay-laravel

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 0

v0.1.1 2026-07-15 20:42 UTC

This package is auto-updated.

Last update: 2026-09-22 03:06:03 UTC


README

Accept Monero in a Laravel application using the PHP engine. This package provides configuration, a service and a facade. Verification runs in PHP with a private view key and configured Monero nodes. It does not hold a spend key.

Requires PHP 8.0+, GMP, BCMath and a supported Laravel version from composer.json (Laravel 9 through 13). Your application owns payment persistence and fulfillment. For new installations, use a Laravel version covered by the framework security support policy.

Install

composer require slowbeardigger/xmr-pay-laravel
php artisan vendor:publish --tag=xmr-pay-config

Then set your wallet in .env. The view key and any node password are sensitive. Stagenet coins are free test XMR:

XMRPAY_ADDRESS=5...your stagenet primary address...
XMRPAY_VIEW_KEY=...your private view key...
XMRPAY_NETWORK=stagenet
XMRPAY_NODES=http://node.monerodevs.org:38089
XMRPAY_MIN_CONFIRMATIONS=10

For authenticated nodes, use structured JSON instead of putting credentials in the URL. JSON takes precedence over the legacy XMRPAY_NODES list:

XMRPAY_NODES_JSON='[{"url":"https://node.example:18081","auth":"digest","username":"merchant","password":"replace-me","allow_insecure_http":false}]'

Supported authentication modes are none, basic, and digest. Authenticated plain HTTP is blocked unless that node sets allow_insecure_http to true; use the opt-in only for a trusted private network. Keep the JSON in .env, never in version control.

Laravel resolves these values into bootstrap/cache/config.php after php artisan config:cache. Protect that file with the same server permissions as .env and clear/rebuild the cache when node credentials change.

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}");

// Do not present a payable order until its birthday height is known and stored.
$height = XmrPay::tipHeight();
if ($height === null) {
    throw new \RuntimeException('Payment node unavailable');
}
$order->update(['xmr_index' => $index, 'xmr_from' => $height]);

// 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() summarizes only the bounded range scanned during that call. It does not retain matches between calls. Defaults limit a call to 200 blocks and eight seconds. Reusing the birthday rechecks that range; advancing to scanned_to + 1 without retaining matches loses earlier partial payments and their confirmation updates.

For long-lived orders, use scanner()->scan_all() with durable matches and checkpoints, or the shared adapter core's Settler. Preserve earlier payments, rescan recent blocks for reorganisations, and update confirmations before summing. The snippet above is a bounded verification example, not a complete settlement worker. Make markPaid() atomic and idempotent in your application's order store.

If you would rather have the buyer paste a transaction id ("I've paid"), verify a single one:

$r = XmrPay::verifyPayment($txid, $address);
// Evidence only: compare amount and enforce confirmations, locks and replay protection.
// A found transaction alone does not authorize fulfillment.

Operational limits

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 configure independent nodes. All configured nodes must answer and agree; agreement is not a substitute for consensus validation. Few confirmations is fast but reversible. Raise XMRPAY_MIN_CONFIRMATIONS for higher-value orders. That is your risk dial. The browser is never trusted. Release goods only after your server applies the order's amount, confirmation, lock and replay policy to verified evidence.

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.

Validation

Test the configured Laravel application on stagenet, including partial payments across scan batches, delayed confirmations and duplicate fulfillment attempts. Engine tests do not validate application-owned storage or release of goods.

To check package discovery, configuration and facade bindings in an installed application:

XMRPAY_LARAVEL_APP=/path/to/app php tests/framework.test.php

Run it before and after php artisan config:cache. Laravel 12 and 13 were exercised with PHP 8.5, SQLite persistence and stagenet payments. Application-specific checkout, database locking and fulfillment still need their own validation.