Download the PHP package qbitflow/qbitflow-php without Composer
On this page you can find all versions of the php package qbitflow/qbitflow-php. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download qbitflow/qbitflow-php
More information about qbitflow/qbitflow-php
Files in qbitflow/qbitflow-php
Package qbitflow-php
Short Description Official PHP SDK for QBitFlow - Next Generation Crypto Payment Processing
License MPL-2.0
Homepage https://qbitflow.app
Informations about the package qbitflow-php
QBitFlow PHP SDK
Official PHP SDK for QBitFlow โ non-custodial cryptocurrency payment processing and on-chain subscriptions. Feature-equivalent to the Python and JavaScript SDKs.
Works in any PHP project, with first-class Laravel integration.
Features
- ๐ Typed โ readonly DTOs and backed enums for every request and response
- ๐งฉ Framework-agnostic โ PSR-18 / PSR-17, so it runs anywhere
- ๐ ป Laravel-ready โ auto-discovered service provider, facade, config and webhook routes
- ๐ Automatic retries โ with backoff on server and network failures
- ๐ณ One-time payments โ accept crypto with a single call
- โป๏ธ Recurring subscriptions โ on-chain, with trials and minimum terms
- ๐ Webhooks โ verification, plus typed Laravel events
- ๐ฅ Customers, products and users โ full CRUD
- ๐ฐ Refunds โ query and track refund entries
- ๐ Accounting export โ JSON or CSV
- ๐ Account claims โ invite users and settle what you owe them
- ๐ญ Act on behalf of a user โ one org key, every user
Table of Contents
- Requirements
- Installation
- Quick Start
- Configuration
- Laravel Integration
- Acting on Behalf of a User
- One-Time Payments
- Subscriptions
- Transaction Status
- Webhooks
- Customers
- Products
- Users
- API Keys
- Currencies
- Refunds
- Accounting Export
- Claims
- Pagination
- Error Handling
- Coming from the Python or JavaScript SDK
- Examples
- Testing
- License
Requirements
- PHP 8.2 or newer
- A PSR-18 HTTP client and PSR-17 factories โ every Laravel application already has these, via Guzzle
- Laravel 10, 11 or 12 for the optional framework integration
Installation
Laravel
That's the whole install. Laravel's HTTP client depends on Guzzle, which satisfies the
PSR-18 and PSR-17 requirements, so nothing else is pulled in โ the SDK is the only package
added. The service provider and QBitFlow facade register themselves through package
discovery; there is nothing to add to config/app.php or bootstrap/providers.php.
Then run the installer and check your key:
Everywhere else
The SDK speaks PSR-18, so it uses whatever HTTP client your project already has. If you don't have one, install Guzzle alongside it:
composer.json requires psr/http-client-implementation and
psr/http-factory-implementation, so if your project has no implementation at all Composer
refuses the install and names what's missing โ rather than letting it fail later on the
first API call. Guzzle, Symfony HTTP Client, Nyholm, Laminas Diactoros and Slim PSR-7 are
all detected automatically.
Quick Start
1. Get your API key
Sign up at QBitFlow and copy your API key from the dashboard. A test key keeps every action on blockchain testnets, with data kept entirely separate from live mode.
2. Create a client
In Laravel, skip this โ set QBITFLOW_API_KEY in .env and resolve the client from the
container or the facade instead. See Laravel Integration.
3. Take a payment
Webhook URLs are configured in the dashboard, under Settings โ Webhooks, with separate Test and Live endpoints. They can no longer be set per session. See Webhooks.
4. Start a subscription
5. Check where a transaction stands
Configuration
| Argument | Type | Default | Description |
|---|---|---|---|
$apiKey |
string |
(required) | Your QBitFlow API key |
$baseUrl |
string |
https://api.qbitflow.app/v1 |
API base URL; also read from QBITFLOW_BASE_URL |
$timeout |
float |
30 |
Request timeout in seconds |
$maxRetries |
int |
3 |
Retry attempts for server and network failures |
$httpClient |
ClientInterface |
auto-discovered | Your own PSR-18 client |
โ ๏ธ
timeoutis in seconds, unlike the JavaScript SDK, which uses milliseconds. PHP HTTP clients work in seconds, so this one does too.
Bring your own HTTP client
Pass any PSR-18 client and the SDK will use it as-is. Configure the timeout on that client
โ the SDK's $timeout only applies to a client it builds itself.
Set 'http_errors' => false on a Guzzle client you supply: the SDK classifies non-2xx
responses itself and needs to see them.
Laravel Integration
The service provider and facade register themselves through package discovery โ there is
nothing to add to config/app.php or bootstrap/providers.php.
Set up
This publishes config/qbitflow.php, adds a QBITFLOW_API_KEY placeholder to .env and
.env.example if they don't have one, and prints what to do next. It never overwrites a
key you have already set, so it is safe to re-run.
You can also do it by hand โ the only thing the SDK truly needs is the key:
Check it works
It calls GET /user and reports who the key belongs to, its role, and your organization โ
so you find out now, rather than on your first checkout. It distinguishes a rejected key
from an unreachable API, and warns you if the key is user-level (which means onBehalfOf()
will 403). Handy after rotating keys, and in CI.
Use it
Inject the client wherever you need it:
Or use the facade:
Every service is reachable both as a property ($client->products) and as a method
($client->products()); the facade forwards to the method form.
Supplying your own HTTP client
Bind a PSR-18 client in the container and the SDK uses it instead of auto-detecting one โ useful for an outbound proxy, request logging, or your own retry middleware:
PSR-17 factories are picked up from the container the same way
(RequestFactoryInterface, StreamFactoryInterface). Bind nothing and the SDK detects
Guzzle on its own.
Testing
Swap the client for one backed by a mock transport, and no test touches the network:
Acting on Behalf of a User
With an organization (admin or owner) API key you can run any request as one of your users, without storing a key per user. Funds route to that user's wallet and your platform fee is applied, exactly as if you had used their own key.
Every service exposes onBehalfOf(). It returns a scoped copy โ your base client keeps
operating at the organization level.
Requires an admin- or owner-level key. A regular user key gets a
403(ForbiddenException).
One-Time Payments
Create a session
Identify the product in one of three ways:
Use your own identifiers
Rather than storing QBitFlow's UUIDs, pass your own. Set reference to your order or
invoice ID; use productReference and customerReference to select an existing product or
customer by your identifier. If no customer matches, one is created during checkout.
reference comes back on the resulting Payment and in webhook payloads.
Read payments
Subscriptions
A subscription always bills against an existing product, so supply either productId or
productReference.
Duration accepts seconds, minutes, hours, days, weeks, months and years,
via named constructors (Duration::months(1)) or directly
(new Duration(1, DurationUnit::MONTHS)).
Manage subscriptions
Transaction Status
The API also exposes a status WebSocket. This SDK does not wrap it โ a long-lived socket has no place in a typical PHP request. Use webhooks, which are the recommended way to learn that a payment settled, or poll the method above.
Webhooks
Verifying a webhook signature
Every webhook QBitFlow sends carries three headers:
| Header | Meaning |
|---|---|
X-Webhook-Signature-256 |
HMAC signature, formatted sha256=<hex> |
X-Webhook-Timestamp |
Send time, in unix seconds |
X-Webhook-Id |
Transaction id, e.g. pay@<uuid> |
There are two ways to check a webhook is genuine, and you can use either:
| Needs the secret | Network call | Use when | |
|---|---|---|---|
| Local | yes | none | Default. Faster, and keeps working if the API is unreachable. |
| Remote | no | one per webhook | You would rather not hold the secret at all. |
Local verification performs the same three checks the server does: the timestamp is within a replay window (5 minutes by default), the HMAC matches, and the comparison is constant-time so a timing side channel cannot be used to guess the signature.
Why the signature covers a canonical rendering, not the raw bytes. JSON object key
order is not significant, and proxies, frameworks and logging layers routinely re-serialize
a body and reorder keys. Signing raw bytes would reject a payload that is in fact
untouched. So both sides sign <timestamp>.<canonical-json>, where canonical means keys
sorted at every level and no insignificant whitespace. You do not have to do anything for
this โ pass the body you received and the SDK handles it.
Get your webhook secret from the QBitFlow dashboard. Treat it like a password: keep it in your environment or secret manager, never in source control.
extractHeaders() takes any array of headers, so it works with $_SERVER (where PHP
exposes them as HTTP_X_WEBHOOK_*), getallheaders(), a PSR-7 $request->getHeaders(), or
Laravel's $request->headers->all(). Lookup is case-insensitive and tolerates the HTTP_
prefix and underscore spelling.
To widen or narrow the replay window (it must match the server's setting), pass it as the fifth argument:
To verify through the API instead, with no secret in your process:
Configure your endpoints in the dashboard under Settings โ Webhooks. There are two, each with separate Test and Live URLs:
- Transaction webhook โ a checkout you created was completed by the customer
- Subscription webhook โ an existing subscription changed status, or was billed
Every delivery carries X-Webhook-Signature-256, X-Webhook-Timestamp and
X-Webhook-ID. Always verify before acting, and always answer 200 โ anything else
makes QBitFlow retry.
In Laravel
Register the routes, and everything above is handled for you:
Put these in routes/api.php. If you prefer routes/web.php, exclude the paths from CSRF
protection โ QBitFlow does not send a CSRF token.
Each route verifies the signature, answers the dashboard's "Test the endpoint" probe automatically, and dispatches a typed event. Listen for the ones you care about:
Queue your listeners. The route answers 200 as soon as the event is dispatched, so slow work in a synchronous listener risks a timeout and a redelivery.
To verify on a route of your own, apply the middleware directly:
Outside Laravel
Pass the raw body to
verify(). Decoding and re-encoding it reorders keys and invalidates the signature.
verify()returnsfalseonly when QBitFlow rejects the signature. A network or server failure is rethrown instead, so an outage is never mistaken for a forgery โ let it bubble, answer non-200, and the delivery is retried.
Customers
Products
Prices are in USD; QBitFlow converts to crypto at checkout using live rates.
Users
Most of these require an admin or owner key.
No password is set at creation โ users set their own through the claim flow.
API Keys
Read-only. Creating and deleting keys requires a dashboard session and cannot be done with an API key; manage them in the dashboard.
Currencies
Public endpoints. Use them to resolve the currency IDs in availableCurrencies on a
session, and in currencyId on payments and subscriptions.
Refunds
Accounting Export
export($from, $to, $format) matches the other SDKs and returns either shape;
exportJson() and exportCsv() are the same call with a single, definite return type.
Token amounts (grossAmount, netAmount, fees) are decimal strings so no precision is
lost; the matching โฆUsd fields are floats.
Claims
Create users whose earnings your organization holds initially. When you are ready, raise a claim request: the user follows the link, sets a password, connects a wallet, and their funds become transferable.
Pagination
List endpoints that can grow return a CursorData page, which is countable and iterable.
Error Handling
Every exception extends QBitFlowException, so one catch covers the SDK.
| Exception | Raised on |
|---|---|
ValidationException |
400, and local validation before a request is sent |
UnauthorizedException |
401 โ invalid or missing API key |
ForbiddenException |
403 โ key not allowed to perform the request |
NotFoundException |
404 |
RateLimitException |
429 โ see getRetryAfter() |
ServerException |
5xx, once retries are exhausted |
NetworkException |
connection failures, DNS errors, timeouts |
Retries. Server (5xx) and network failures are retried up to maxRetries times, with
the delay growing on each attempt (1s, 2s, 3sโฆ). Client errors (4xx) are never retried โ
repeating them would not help. Rate limits are not retried automatically either; back off
on your own schedule using getRetryAfter().
Coming from the Python or JavaScript SDK
The surface is the same; these are the deliberate differences.
- Naming follows the JavaScript SDK (
camelCase), which is also PHP's convention:getByReference(),oneTimePayments,claims. timeoutis in seconds, not milliseconds.- Services are both properties and methods โ
$client->productsand$client->products()are the same object. The facade needs the method form. - Timestamps are
DateTimeImmutable. Decimal strings (allowance,amountMinUnits, accounting token amounts) stay strings, to preserve precision. - Unknown enum values do not throw. If the API adds a status this SDK has not seen, the field falls back to a sensible default rather than failing the whole response.
claims->triggerTestClaimFunds()sends the user ID as a path segment (/user/claim/funds/test-trigger/{userID}), as the REST reference specifies. The JavaScript SDK sends it as a?userID=query parameter, which does not match the route.- Subscription sessions require a stored product (
productIdorproductReference), matching the REST reference and the Python SDK. The JavaScript SDK's shared validator also accepts an inline product here, which the endpoint does not. webhooks->verify()distinguishes a rejected signature from an outage. It returnsfalseonly for the former and rethrows the latter, following the Python SDK. The JavaScript SDK returnsfalsefor both.- No WebSocket transaction status. Like the Python SDK, this one omits it; use webhooks
or poll
transactionStatus->get(). - No pay-as-you-go service. PAYG session creation is disabled on the API, so it is not
exposed.
PaygSubscriptionSessionstill hydrates if you read an existing PAYG session.
Examples
Runnable examples live in examples/:
client.phpโ a tour of the SDK outside any frameworkwebhook-server.phpโ a webhook endpoint in plain PHPlaravel/โ checkout controller, routes and queued webhook listeners
Testing
The suite runs against a mock PSR-18 client, so nothing touches the network. To test your own code against the SDK, inject a mock client the same way:
License
TRADEMARKS.md for brand usage, COMPLIANCE.md for compliance posture.
Support
- ๐ Documentation ยท API reference
- ๐ Issues
- โ๏ธ [email protected]
All versions of qbitflow-php with dependencies
ext-json Version *
psr/http-client Version ^1.0
psr/http-client-implementation Version ^1.0
psr/http-factory Version ^1.0
psr/http-factory-implementation Version ^1.0
psr/http-message Version ^1.1 || ^2.0