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

Sdk Laravel Package

bitpay/sdk

Official BitPay PHP SDK for integrating Bitcoin and crypto payments into Laravel/PHP apps. Create and manage invoices, handle payment notifications, and access merchant APIs with a clean, Composer-installable client.

View on GitHub
Deep Wiki
Context7

Getting Started

composer require bitpay/sdk

There is no service provider, facade, or publishable config — you wire it yourself. First stop after install is vendor/bitpay/sdk/examples/, not the README: it contains a runnable script per endpoint (CreateInvoice.php, GetInvoice.php, RefundInvoice.php, CreatePayout.php, …) that shows the exact bootstrap for your pinned version. The second stop is src/Client.php to confirm the method names, because the v2/v3 and v4 bootstraps differ:

// v2/v3 style
$client = new \BitPaySDK\Client();
$client->init($privateKeyPath, \BitPaySDK\Env::TEST, $merchantToken);

// v4 style (fluent builder)
$client = \BitPaySDK\Client::create()
    ->withPrivateKeyFromFile($privateKeyPath, \BitPaySDK\Env::TEST)
    ->withMerchantToken($merchantToken);

Prerequisites on the BitPay side:

  1. A merchant account on test.bitpay.com (never start on prod).
  2. A merchant token (created in the dashboard, tied to one environment).
  3. A private key — the SDK ships key utilities; generate one and upload the public half to BitPay. The .pem is a signing key, not an API secret, and it never travels over the wire.

Register it once in the container:

// config/services.php
'bitpay' => [
    'base_url'       => env('BITPAY_BASE_URL', 'https://test.bitpay.com'),
    'private_key'    => env('BITPAY_PRIVATE_KEY_PATH', storage_path('app/bitpay/private_key.pem')),
    'merchant_token' => env('BITPAY_MERCHANT_TOKEN'),
    'payout_token'   => env('BITPAY_PAYOUT_TOKEN'),
],

// AppServiceProvider::register()
$this->app->singleton(\BitPaySDK\Client::class, fn () => (new \BitPaySDK\Client())->init(
    config('services.bitpay.private_key'),
    config('services.bitpay.base_url'),
    config('services.bitpay.merchant_token'),
));

First use case: create an invoice when an order is placed, persist the invoice ID, and redirect the customer to BitPay's hosted checkout.

$invoice = new \BitPaySDK\Model\Invoice\Invoice(49.99, 'USD');
$invoice->setOrderId((string) $order->id);
$invoice->setItemDesc("Order #{$order->id}");
$invoice->setNotificationURL(route('webhooks.bitpay'));
$invoice->setRedirectURL(route('checkout.return', $order));

$created = app(\BitPaySDK\Client::class)->createInvoice($invoice);

$order->update(['bitpay_invoice_id' => $created->getId()]);

return redirect()->away($created->getUrl());

Implementation Patterns

Two-phase checkout (hosted). Never trust the redirectURL as proof of payment — the customer controls the browser. Treat the redirect as "come back and poll", and treat the webhook as the source of truth. On return, call getInvoice($id) and check getStatus() before showing a success page.

Webhook endpoint. Fulfillment belongs in a queued job; the HTTP handler should verify and dispatch in under a second, because BitPay retries non-2xx responses and will hammer a slow endpoint.

Route::post('/webhooks/bitpay', BitPayWebhook::class)
    ->withoutMiddleware([\App\Http\Middleware\VerifyCsrfToken::class]);

class BitPayWebhook
{
    public function __invoke(Request $request)
    {
        $raw = $request->getContent(); // raw body only — see gotchas

        if (! $this->ipn->verify($raw, $request->header('X-Signature'), $request->header('X-Identity'))) {
            abort(403);
        }

        ProcessBitPayEvent::dispatch(json_decode($raw, true))->onQueue('webhooks');

        return response()->json(['status' => 'success']);
    }
}

The X-Signature header is an ECDSA signature over the raw body, verifiable with the public key belonging to X-Identity. The SDK includes IPN helpers (src/Util/, plus an example); use them rather than hand-rolling verification.

Status state machine. Model it explicitly and only transition forward:

  • new → invoice created, awaiting payment
  • paid → seen on-chain, unconfirmed
  • confirmed → on-chain confirmed, not yet settled
  • complete → settled and credited; safe to fulfill
  • expired / invalid → stop polling, release inventory

Fulfil on complete unless you deliberately accept the settlement risk BitPay documents for confirmed.

Idempotency. Webhooks are at-least-once. Make ProcessBitPayEvent idempotent by guarding on bitpay_invoice_id + status, or wrap the handler in Cache::lock("bitpay:{$invoiceId}"). Also guard invoice creation — a retried checkout request will happily create a second invoice; check for an existing open invoice first.

Payouts. A separate token with payout scope, a separate client instance, and a Recipient that must exist and be approved before you can pay it. Payouts are also the place where the private key matters most (request signing), so the key file must be readable by the queue worker, not just the web process. Model payouts as their own table with a local state machine mirroring BitPay's (new → funded → processing → complete).

Reconciliation. Don't build a reconciliation report from webhooks alone — they get missed. A nightly job over getInvoices (date range) plus getLedgerEntries catches everything, and getExchangeRates / getCurrencies are the endpoints for any currency picker or display logic.

Gotchas and Tips

  • Namespace trap. v1 snippets (BitPay\Client, new BitPay\Invoice) are everywhere online and will not work. This package is BitPaySDK\. Copy from vendor/bitpay/sdk/examples/, not from blog posts.
  • Version drift. The v4 bootstrap is a fluent builder; v2/v3 is new Client(); $client->init(...). Pin the version in composer.json and re-read the examples on upgrade — this package has churned its public surface across majors.
  • Env/token mismatch. A production token against test.bitpay.com (or vice versa) produces a confusing auth error, not a clear "wrong environment". Keep the token and the base URL in the same env block and never let them be set independently.
  • Raw body for signature verification. Laravel parses application/json into $request->all(), and re-encoding reorders keys and breaks the signature. Always $request->getContent().
  • Key file newlines. Reading a PEM from an env var usually yields literal \n sequences and OpenSSL rejects it. Use a file path under storage/app/ with restrictive permissions, or str_replace('\n', "\n", $value).
  • notificationURL must be publicly reachable HTTPS. Localhost silently receives nothing; use ngrok/Expose during development and set the URL per invoice rather than relying on token-level defaults, so it's obvious in code review.
  • Amount types. Use setPrice() + setCurrency(), not floating-point arithmetic you round yourself; 0.1 + 0.2 issues become API validation errors. Store money as integer minor units and convert at the boundary.
  • Expiration defaults. Invoices default to a ~15 minute window. Set setExpirationTime() deliberately for high-ticket items, and don't leave stale "pending" orders open forever.
  • Exceptions are coarse. Catch BitPaySDK\Exceptions\BitPayException (and its BitPayApiException / BitPayValidationException subclasses), then log getMessage() plus the API code — the message is often a JSON blob and the useful bit is buried in it.
  • No PSR-3 logging. The SDK has no logger injection; wrap every call site in try/catch and log to your own channel if you want request/response traces.
  • No retry logic. Transient 5xx/network failures bubble up as exceptions. Add a small retry with backoff for reads only; never auto-retry createInvoice or createPayout without an idempotency guard.
  • Clock skew. Signatures and expiry windows break if the host clock drifts; keep NTP running in containers.
  • Test mode isn't a simulation of your integration. test.bitpay.com still issues real (testnet) invoices with real webhook flows, which is what you want — but test-mode checkout has its own payment simulator, so "I paid it" in test tells you nothing about real settlement timing.
  • Zero dependents, 42 stars. Assume little community support and budget time for reading source; it's a thin, readable wrapper over Guzzle, so src/ is a viable debugging tool when the API rejects something.
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.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console