Skip to content
Payfim - Secure. Simple. Yours.
PHP / Laravel · v3.0.0

PHP & Laravel Crypto Payment SDK

A dependency-free PHP library that connects your own application to a self-hosted Payfim Gateway: create crypto invoices, read their status and verify signed webhooks in a few lines. A Laravel bridge with a facade, middleware, route macro and events is included.

  • Lifetime license, 0% fees
  • Pays straight to your wallet
  • 16 coins incl. USDT TRC20/ERC20
  • Illustrated PDF guide

Compatibility: PHP 7.4 - 8.4 · Laravel 8+ (optional) · no dependencies

Payfim crypto checkout page opened from a custom PHP application through the SDK

Everything you need to accept crypto on PHP / Laravel

Typed client

ping, createInvoice, getInvoice and getCoins.

Webhook verification

Webhook::verify() checks HMAC and timestamp and returns a typed event.

Laravel bridge

Auto-discovered provider, facade, middleware and events.

No dependencies

Secure built-in cURL transport; PSR-18 optional.

Examples included

Plain PHP and Laravel examples plus a test suite.

For applications that no plugin covers

Ready-made plugins are great when your store runs on a known platform. Many businesses do not: a SaaS with its own billing, a membership site, a booking system, a game server shop, or a cart an agency wrote years ago. The Payfim SDK is for those projects.

It wraps the gateway API in a typed client (Payfim\Client), turns responses into an immutable Invoice object, and verifies webhooks for you. The heavy lifting - rates, unique amounts, blockchain monitoring, confirmations - stays in your Payfim Gateway, so your code never talks to a blockchain directly. Coins go to your own wallets, and Payfim charges no transaction fees.

  • No dependencies: only ext-json, ext-hash and ext-curl for the default transport.
  • PSR-4, PSR-12, MIT licensed Composer package payfim/payfim-php.
  • Optional PSR-18 transport if you prefer Guzzle or Symfony HttpClient.

Create an invoice and redirect the customer

Create the client with the Gateway URL and API key from the gateway's Integrations page. Keep both in environment variables on the server - never in JavaScript or a mobile app.

$payfim = new Payfim\Client(getenv('PAYFIM_URL'), getenv('PAYFIM_API_KEY'));

Then create an invoice from your own order record. Amount and currency come from your database, not from the browser:

$invoice = $payfim->createInvoice([

  'order_id' => (string) $order->id,

  'amount' => $order->total, // "49.90"

  'currency' => $order->currency, // "USD"

  'webhook_url' => 'https://shop.example.com/payfim/webhook',

  'return_url' => 'https://shop.example.com/orders/' . $order->id,

]);

header('Location: ' . $invoice->getCheckoutUrl(), true, 303);

Store $invoice->getId() on the order so you can match webhooks later. createInvoice() is idempotent: calling it again for the same open order returns the same invoice, so a double click on "Pay" is harmless. If the amount changed, the gateway replaces the old open invoice. Bad input throws InvalidArgumentException before any request is sent.

Verify webhooks the safe way

Your gateway sends a signed POST to webhook_url on every status change (invoice.confirming, invoice.paid, invoice.underpaid, invoice.expired, invoice.cancelled) and retries until it gets a 2xx, for up to 24 hours. The header looks like X-Payfim-Signature: t=<unix>,v1=<hex>, an HMAC-SHA256 of the timestamp and the raw body.

$event = $payfim->constructEvent(file_get_contents('php://input'), $_SERVER['HTTP_X_PAYFIM_SIGNATURE'] ?? '');

$invoice = $payfim->fetchEventInvoice($event);

if ($invoice->isPaid() && $order->status !== 'paid' && $invoice->matchesAmount($order->total, $order->currency)) {

  $order->markPaid($invoice->getTransactionReference());

}

constructEvent() checks the signature and a 300-second timestamp window and throws SignatureVerificationException on failure (answer 401). fetchEventInvoice() re-reads the invoice from the gateway and cross-checks its ID, order and source, so you act on trusted data rather than on the payload. getTransactionReference() returns the transaction hash, or a payfim-<id> reference when no hash is available. In a queue worker without a client, Payfim\Webhook::verify($raw, $header, $apiKey) does the same check statically.

The SDK's built-in checklist is short and worth following: create invoices server-side, verify the raw body, act on the re-fetched status, make "mark paid" idempotent, treat underpaid as "needs review", and never mark an order paid just because the customer reached your return page.

Laravel: a facade, a route and events

In Laravel the service provider and the Payfim facade are auto-discovered. Add PAYFIM_URL and PAYFIM_API_KEY to .env, then register the webhook route:

Route::payfimWebhook(); // POST /payfim/webhook

The macro attaches the VerifyPayfimSignature middleware, and the built-in controller re-fetches the invoice and dispatches events based on the re-fetched status: InvoicePaid, InvoiceUnderpaid, InvoiceConfirming, InvoiceExpired and InvoiceCancelled, plus InvoiceWebhookReceived for every verified call. A listener only needs the business logic:

public function handle(InvoicePaid $e): void

{

  $order = Order::where('payfim_invoice_id', $e->invoice->getId())->first();

  if (!$order || $order->status === 'paid') { return; }

  if ($e->invoice->matchesAmount($order->total, $order->currency)) { $order->update(['status' => 'paid']); }

}

Remember to exclude payfim/webhook from CSRF protection. If the gateway cannot be reached for the re-check, the controller answers 502 so the gateway tries again later. php artisan vendor:publish --tag=payfim-config publishes the config file if you want to adjust tolerance, paths or timeouts.

Where the SDK sits in your architecture

Three parts talk to each other. Your application holds orders and customers. The Payfim Gateway, usually on its own subdomain such as pay.example.com, holds invoices, wallets and blockchain monitoring. The SDK is the thin, typed layer between them: every request carries your API key in the X-Payfim-Api-Key header, and every response is either a success object or an error the SDK converts into an exception.

An invoice moves through a small set of states, available as constants in Payfim\InvoiceStatus: new (no coin chosen yet), pending, confirming and paid, or the exits underpaid, expired and cancelled. On your return page you may call getInvoice() to show a fresher status, but let the webhook decide when an order is paid.

Errors, transports and tests

Every exception extends Payfim\Exception\PayfimException. ApiException carries the HTTP status and an error code such as unauthorized, not_found, validation or rate_limited; TransportException covers DNS, TLS and timeouts. Show customers a generic "temporarily unavailable" message and log the details. Note that the gateway rate-limits API calls after 20 failed key checks in 15 minutes, so fix a wrong key before retrying in a loop.

The default cURL transport verifies TLS peer and host, never follows redirects and uses short timeouts. The package ships with plain-PHP and Laravel examples and a test suite that runs with php tests/run.php - no PHPUnit needed - and can optionally run live tests against your own gateway.

Requirements

PHP7.4 - 8.4 with ext-json, ext-hash (ext-curl for the default transport)
LaravelOptional; the bridge supports Laravel 8 - 13
DependenciesNone (PSR-18 packages only if you choose that transport)
InstallComposer path repository or a four-line autoloader
GatewayPayfim Gateway 3.0+ on HTTPS (included in your download)

The SDK itself is MIT licensed, so it can ship inside commercial and closed-source projects.

How to install the PHP / Laravel crypto payment SDK

  1. Install the gateway. Deploy the Payfim Gateway on your server and add your receiving wallets. See Install the Payfim gateway.
  2. Add the package. Unzip payfim-php.zip into your project (for example packages/payfim-php/), add it as a Composer path repository, require payfim/payfim-php ^3.0 and run composer update payfim/payfim-php. Without Composer, use the small autoloader shown in README.md.
  3. Configure credentials. Copy the Gateway URL and API key from the gateway's Integrations page into environment variables, create a Payfim\Client and call ping() to check the connection and list your active coins.
  4. Build checkout and webhook. Call createInvoice() when the customer chooses crypto, redirect to getCheckoutUrl(), and handle the webhook with constructEvent() and fetchEventInvoice() (or Route::payfimWebhook() in Laravel).
  5. Test end to end. Run php tests/run.php, then make a small real payment through your integration and watch the order update. The SDK guide covers each step in more detail.

PHP / Laravel crypto payments FAQ

Do I need Laravel to use the SDK?

No. The core library is plain PHP and works in any framework or none. The Laravel bridge is an optional extra that is auto-discovered when Laravel is present.

Which PHP versions are supported?

PHP 7.4 through 8.4. The library is written without PHP 8-only syntax, which keeps it usable on older hosting plans.

Can I trust the data in the webhook body?

Only after verification, and even then the SDK recommends acting on the invoice re-fetched with fetchEventInvoice(), which also checks that the invoice belongs to the order.

What if the same webhook arrives twice?

That is expected with retries. Check the stored order state before changing it, as in the examples, and answer 2xx so the gateway stops retrying.

How do I handle an underpaid invoice?

Treat it as "needs review": keep the order unfulfilled and use getAmountReceived() and getAmountCrypto() to show how much arrived. Our underpayment guide covers the business side.

Can several apps share one gateway?

Yes. Pass a source label in the client options (the fourth constructor argument) so each app's invoices are labelled and can be looked up with getInvoiceByOrder().

Can I use Guzzle instead of cURL?

Yes. Psr18Transport accepts any PSR-18 client and PSR-17 factories, or you can implement TransportInterface yourself.