Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Crypto Payment Gateway Laravel Package

victorybiz/laravel-crypto-payment-gateway

Add cryptocurrency checkout to Laravel apps with this configurable payment gateway. It supports multiple coins, payment address generation, confirmation tracking, and callback or webhook notifications, making it easy to accept crypto orders alongside traditional payments.

View on GitHub
Deep Wiki
Context7

Getting Started

Start by getting a GoUrl.io merchant account and grabbing both keys — the public key is safe to embed in the browser box, but the private key is only used for server-side IPN verification and must never leak into HTML or JS.

  1. Install and publish

    composer require victorybiz/laravel-crypto-payment-gateway
    php artisan vendor:publish --provider="Victorybiz\LaravelCryptoPaymentGateway\LaravelCryptoPaymentGatewayServiceProvider"
    php artisan migrate
    

    The package publishes a config file (typically config/crypto-payment-gateway.php) and a migration that creates a crypto_payments table.

  2. Configure the environment

    CRYPTO_PAYMENT_GATEWAY_PUBLIC_KEY=pk_xxx
    CRYPTO_PAYMENT_GATEWAY_PRIVATE_KEY=sk_xxx
    
  3. Look at these first, in order:

    • config/crypto-payment-gateway.php — keys, box style, allowed coin types, callback URL.
    • The package's route file / service provider — to find the callback route it automatically registers (usually something like /crypto-payment/callback).
    • The crypto_payments migration — this is your source of truth for the fields the package writes on each IPN (order id, status, txid, amount, address, confirmed timestamp).
  4. First use case — render a payment box on a checkout page. The façade exposes a getPaymentBox($amount, $currency, $options = []) helper that returns HTML (a form/iframe plus the GoUrl script). You must output it raw:

    {!! \Victorybiz\LaravelCryptoPaymentGateway\LaravelCryptoPaymentGateway::getPaymentBox(
        0.0015, 'LTC', [
            'callbackUrl' => route('crypto.callback'),
            'redirectUrl' => route('orders.show', $order),
            'orderId'     => $order->id,
            'userID'      => auth()->id(),
        ])
    !!}
    

    The user gets a coin picker (or a fixed coin, depending on the options you pass), pays, GoUrl fires an IPN to callbackUrl, and a row in crypto_payments is created/updated. From that point your order state machine should key off crypto_payments.status.

Implementation Patterns

Order-first, then box. Create an application-level Order/invoice record before rendering the box, with a stable orderId. GoUrl and the package use orderId to correlate the IPN back to your record, and reusing one will make reconciliation ambiguous. Pass that ID into getPaymentBox and store it in your own orders table alongside user and amount.

Listen on the table, not on the request. The cleanest integration point is to treat crypto_payments as an event log:

  • A background job or scheduled task (everyMinute) queries rows where status = 'confirmed' and fulfilled_at IS NULL, then marks the order paid and sets fulfilled_at.
  • Or, if the package fires model events on the payment record, hook a listener (CryptoPaymentUpdated) and do the same. Either way, fulfillment should be idempotent — check fulfilled_at / your own paid_at column inside a transaction before granting access or credit.

Map GoUrl statuses explicitly. GoUrl emits statuses such as waiting, confirming, confirmed, cancelled/expired. Treat only confirmed (or the package's equivalent constant) as paid. Anything else is "don't ship yet." Build a small enum/map in your app so you never string-compare in Blades.

Two common rendering patterns.

  • Fixed coin, fixed amount (e.g. a specific LTC/USD price you quote yourself): pass the coin and amount, and store the quote in your order so you can compare the confirmed amount.
  • Fiat amount with a coin picker: pass the fiat currency (USD/EUR) and let GoUrl's box show available coins and convert. Users see live conversion; you just need to know the rate at confirmation time if you're crediting fiat-equivalent value.

Attach payments to users. Add a hasMany(CryptoPayment::class) to your User model and pass userID in the box options. This makes dashboards, support lookups, and "my payments" screens trivial and avoids storing raw addresses on the user row.

Callback as the source of truth (not the redirect). The redirectUrl is where the user's browser goes after paying; it is not proof of payment. Anything showing "Thanks, you paid" should be driven by a check against crypto_payments for that order — otherwise a user can spoof the success page by simply hitting the redirect URL.

Keep the box rendering in one Blade partial. Wrap getPaymentBox() in a component (<x-crypto-payment-box :order="$order" />) so you centralize the option array, the orderId, and any CSP nonce handling. This is also where you'd swap in a "test mode" coin if you want a sandbox flow.

Custom callback route. If you don't want the package's default route (or need to add auth/logging), set callbackUrl to your own controller and forward the request through the package's handler, or let the package handle it and just observe the DB — the DB-observer pattern is less invasive and easier to test.

Testing. Fake the payment by inserting a crypto_payments row directly in tests (status = 'confirmed'), then assert your fulfillment job picks it up. Use Http::fake() if your own code posts to GoUrl; use Bus::fake() to assert the fulfillment job dispatches.

Gotchas and Tips**

Localhost will never work. GoUrl's IPN is a server-to-server POST from their infrastructure, so it can only reach a publicly reachable URL. Use ngrok/expose/Laravel Valet share or a staging server. If your callback URL is http://127.0.0.1, the box will render but the payment will sit in waiting forever and you'll spend an afternoon debugging nothing.

Exclude the callback from CSRF and auth middleware. IPNs arrive as a POST with no session and no CSRF token. If the package's route is under web middleware, add the callback path to VerifyCsrfToken::$except and make sure nothing in your route group requires auth. Verify in php artisan route:list which middleware stack the callback actually has.

Use strings, not floats, for crypto amounts. BTC/LTC amounts have 8 decimals; PHP floats will round and can throw off amount-match validation. Store amounts as DECIMAL(18,8) or strings, and compare with a tolerance (e.g. ±1 satoshi) when reconciling.

Unique order_id. GoUrl dedupes on the order ID you pass. If a customer abandons a box and you re-render with the same orderId, you may get stale state or a rejected second IPN. Either reuse the same payment row intentionally (recommended) or generate a new order ID and void the old row.

Only the public key belongs in the rendered box. If you ever see the private key in a page source, config dump, or error report, rotate it immediately in the GoUrl dashboard — the private key is what authenticates the IPN, so a leak means an attacker can forge "confirmed" callbacks.

Config cache after env changes. php artisan config:cache will freeze CRYPTO_PAYMENT_GATEWAY_* values; if you add keys to .env and the box still fails, clear the cached config. Same story for queue workers — restart them.

Status constant drift. The exact status strings the package writes are defined by the upstream GoUrl cryptobox script, not by Laravel. Inspect a real row in crypto_payments in staging before writing your fulfillment logic — don't trust a blog post or a README example from an older release.

The box is client-side JS. It injects a GoUrl script and (depending on config) an iframe. Adblockers, strict Content-Security-Policy (script-src/frame-src), and consent managers regularly break it. If the box renders blank in production but works in staging, check CSP, adblock, and whether the public key is being served over HTTPS. Give the box a visible fallback (raw address + QR) so users on hostile networks aren't dead-ended.

Logging for support. GoUrl's dashboard has an IPN log per transaction; correlate that with storage/logs/laravel.log and your own crypto_payments.order_id. When a "customer says they paid" ticket arrives, those three sources resolve 95% of cases in minutes.

Extension points when the package falls short. If you need richer events, multi-currency accounting, or custom retry logic, the lowest-friction approach is to not fork the package: keep it as the box renderer + IPN writer, and put all business logic in your own listener/job against crypto_payments. That keeps you upgrade-safe when a new release ships (last release was 2024-04-01, so expect infrequent updates — pinning the version and owning the wrapper pays off).

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle