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

Bitpay Laravel Package

omnipay/bitpay

View on GitHub
Deep Wiki
Context7

Getting Started

Start by checking composer.json before you write any code. This package was last released in April 2016 and was built against the Omnipay 2.x generation (omnipay/common: ~2.0), not the PSR-7 based 3.x line. That single fact determines your whole integration strategy: on a modern Laravel app you will either pin the older stack or port the driver.

composer require omnipay/bitpay:^1.0
# verify what it pulled in
composer show omnipay/common omnipay/bitpay

Then read four files in this order — they define the entire contract:

  1. src/Gateway.php — getName(), defaultParameters, and which parameters (apiKey, testMode, endpoint) are configurable.
  2. src/Message/PurchaseRequest.php — how Omnipay params map to BitPay's invoice payload (price, currency, posData, notificationURL, redirectURL, transactionSpeed).
  3. src/Message/PurchaseResponse.php — what getRedirectUrl()/getTransactionReference() return (BitPay returns an invoice id and a hosted url).
  4. src/Message/CompletePurchaseRequest.php — whether it fetches the invoice from BitPay or merely parses the POST body. This is the security-critical decision.

The first use case to get working end-to-end is a single hosted-invoice checkout: create an invoice, redirect the customer to BitPay, receive the webhook, and mark the order paid.

use Omnipay\Omnipay;

$gateway = Omnipay::create('BitPay');
$gateway->initialize([
    'apiKey'   => config('services.bitpay.api_key'),
    'testMode' => app()->environment('local'),
]);

$response = $gateway->purchase([
    'amount'          => '49.99',        // fiat amount, decimal string
    'currency'        => 'USD',
    'description'     => 'Order #1042',
    'transactionId'   => '1042',         // your reference
    'returnUrl'       => route('bitpay.return'),
    'notifyUrl'       => route('bitpay.webhook'),
    'posData'         => json_encode(['order_id' => 1042]),
])->send();

if (! $response->isRedirect()) {
    throw new \RuntimeException($response->getMessage());
}

$order->update(['bitpay_invoice_id' => $response->getTransactionReference()]);
return $response->redirect(); // or redirect()->away($response->getRedirectUrl())

Implementation Patterns

Bind the gateway once. Don't call Omnipay::create scattered through controllers. Register a singleton or a small factory in a service provider so API keys, testMode, and the HTTP client are configured in one place and swappable in tests.

Persist the invoice before redirecting. The redirect URL does not reliably carry your order id back, and BitPay's return page can be closed. Store bitpay_invoice_id, amount, currency, and status on the order before the redirect, then use posData as a secondary correlation key. Treat the invoice id as the primary key for every subsequent operation.

Split the return and webhook routes. The browser return is a UX event; the webhook is the source of truth. Both should funnel into the same verifier:

public function webhook(Request $request)
{
    $invoiceId = $request->input('id');           // BitPay's invoice id
    $order = Order::where('bitpay_invoice_id', $invoiceId)->firstOrFail();

    // Always re-fetch from BitPay rather than trusting the POST body.
    $response = $this->gateway->completePurchase([
        'transactionReference' => $invoiceId,
        'amount'               => $order->total,
        'currency'             => $order->currency,
    ])->send();

    if ($response->isSuccessful() && $order->status !== 'paid') {
        $order->markPaid($response->getTransactionReference());
    }

    return response('ok', 200);
}

Map BitPay statuses explicitly. Expect new, paid, confirmed, complete, expired, invalid. paid means seen in the mempool; confirmed/complete means it met the confirmation threshold. Decide per-product whether to fulfil on paid (digital) or complete (physical), and store the raw status so you can change your mind later.

Reconcile stuck invoices on a schedule. Users close tabs, webhooks get lost. A queued job that polls any order in new/paid older than ~30 minutes and re-fetches the invoice will save you a support queue. Invoice expiry defaults to roughly 15 minutes on BitPay, after which the quoted BTC amount is void.

Wrap it behind your own interface. The whole point of Omnipay is that purchase()/completePurchase() look identical across gateways. Type-hint a PaymentGateway contract of your own so a future migration to BitPay's API v2 (or to the official bitpay/bitpay-php SDK) is a one-class change rather than a search-and-replace.

Gotchas and Tips

  • This is effectively an unmaintained driver. A 2016 release predates BitPay's API v2 token model (pos/merchant tokens). BitPay has been retiring legacy merchant endpoints for years; confirm the endpoint your driver targets still answers in production before building a checkout on it. Test against the sandbox (testMode → test.bitpay.com) with testnet coins first.
  • Omnipay version mismatch is the most common install failure. Omnipay 3.x gateways use a different AbstractGateway/message API. If you install omnipay/common: ^3.0 alongside this package you will get class-not-found or signature errors. Pin accordingly, or fork the driver and port it.
  • Never trust the IPN body. The legacy BitPay notification is an unsigned POST. Anyone can hit your webhook URL with a plausible invoice id. Always confirm by fetching the invoice server-side with your API key, and verify the amount and currency match the order.
  • Make webhook handling idempotent. BitPay retries, and fullNotifications may deliver several events per invoice. Use the invoice id as a unique constraint or an atomic where('status', '!=', 'paid') guard; return 200 quickly and do work in a job.
  • Amounts are decimally-stringified and priced in fiat. amount/currency describe the price, not the BTC received. Don't pass a BTC quantity with currency => 'USD'. Omnipay will happily let you create a self-consistent-looking invoice that BitPay rejects.
  • Underpayment and rate drift are real. The BTC amount is quoted at invoice time. BitPay flags exceptionStatus for underpaid/late invoices; surface that field and treat it as a manual-review state, not an automatic failure.
  • No refunds, voids, or captures. This is a redirect-only purchase flow. Refunds go through BitPay's dashboard or require token-based API calls outside this package — don't promise refund() in your abstraction just because Omnipay's base classes mention it.
  • Your notifyUrl must be public HTTPS. Localhost and *.test domains silently receive nothing, which makes "the webhook never fires" the most common false bug report. Use a tunnel for local development.
  • Log the raw request and response objects. The driver's exception messages are terse; enable Guzzle request logging (or a debug subscriber on the Omnipay HTTP client) so you can see the exact JSON BitPay rejected.
  • Extension points: subclass AbstractGateway to override the endpoint or add parameters (transactionSpeed, fullNotifications, physical), and extend CompletePurchaseRequest if you need richer verification — for example, comparing btcPrice against a tolerated slippage window, or writing a small API-v2 client alongside the driver and using it for verification only.
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.
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names