PHPackages                             jarrin/wefact-php-sdk - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [API Development](/categories/api)
4. /
5. jarrin/wefact-php-sdk

ActiveLibrary[API Development](/categories/api)

jarrin/wefact-php-sdk
=====================

Modern, strongly-typed, PSR-compliant PHP client for the WeFact v2 API.

v0.0.1(1mo ago)02MITPHPPHP &gt;=8.3

Since Jul 15Pushed 1mo agoCompare

[ Source](https://github.com/jarrin/wefact-php-sdk)[ Packagist](https://packagist.org/packages/jarrin/wefact-php-sdk)[ Docs](https://github.com/jarrin/wefact-php-sdk)[ RSS](/packages/jarrin-wefact-php-sdk/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependencies (20)Versions (2)Used By (0)

jarrin/wefact-php-sdk
=====================

[](#jarrinwefact-php-sdk)

A modern, strongly-typed, PSR-compliant PHP 8.3+ client for the [WeFact v2 API](https://api.mijnwefact.nl/) — the Dutch invoicing / billing / bookkeeping SaaS. Works **standalone** and under **Laravel**.

- **Typed end-to-end.** Every controller is a resource, every response object a readonly DTO, every documented code a backed enum. No associative-array guessing, full IDE autocompletion, PHPStan-max clean.
- **One obvious way to call it.** `$wefact->debtors()->get(code: 'DB10000')` — a lazy accessor per controller, a typed method per action.
- **Hand-written from the official reference and verified against the live API.** Not generated; every endpoint is confirmed by an integration test.

```
use Jarrin\WeFactApiClient\WeFact;

$wefact = new WeFact('your-api-key');

$debtor  = $wefact->debtors()->get(code: 'DB10000');
$invoice = $wefact->invoices()->create([
    'DebtorCode'   => $debtor->code,
    'InvoiceLines' => [
        ['ProductCode' => 'P0001', 'Number' => 2],
        ['Description' => 'Consultancy', 'PriceExcl' => 95.0, 'Number' => 3, 'TaxCode' => 'V21'],
    ],
]);

echo $invoice->code, ' — €', $invoice->amountIncl;
```

Requirements
------------

[](#requirements)

- PHP **8.3+**
- A WeFact v2 **API key** (WeFact control panel → *Settings → API*)
- The calling server's **IP must be whitelisted** for that key (a non-whitelisted IP returns HTTP 403)

Installation
------------

[](#installation)

```
composer require jarrin/wefact-php-sdk
```

Quick start (standalone)
------------------------

[](#quick-start-standalone)

Construct with an API key, or with a `Config` for full control:

```
use Jarrin\WeFactApiClient\WeFact;
use Jarrin\WeFactApiClient\Config;

$wefact = new WeFact('your-api-key');

// or:
$wefact = new WeFact(new Config(
    apiKey: 'your-api-key',
    timeout: 15,
));
```

Then reach the API through one accessor per controller:

```
// Fetch, addressed by id OR code (never both).
$debtor = $wefact->debtors()->get(id: 42);
$debtor = $wefact->debtors()->get(code: 'DB10000');

// List with filtering, sorting and paging.
$page = $wefact->debtors()->list(searchAt: 'EmailAddress', searchFor: 'a@b.nl', limit: 20);
foreach ($page as $debtor) {
    echo $debtor->companyName, PHP_EOL;
}
echo $page->totalResults; // total matching records, not just this page

// Create — pass a plain array or a DTO.
$product = $wefact->products()->create([
    'ProductName' => 'Hosting',
    'PriceExcl'   => 10.0,
    'TaxCode'     => 'V21',
]);

// Update, identified by id or code.
$wefact->products()->update(['PriceExcl' => 12.5], code: $product->code);

// Delete.
$wefact->products()->delete(code: $product->code);
```

The call pattern
----------------

[](#the-call-pattern)

```
$wefact->{resource}()->{action}(named args…): DTO | Collection | void

```

- **`{resource}()`** — a lazy, memoised accessor per WeFact controller (`debtors()`, `invoices()`, `products()`, …). See the [resource map](docs/resources/README.md).
- **`{action}()`** — a typed method per API action (`get`, `list`, `create`, `update`, `delete`, plus lifecycle actions like `invoices()->credit()` or `invoices()->markAsPaid()`).
- Reads return a **readonly DTO** (or a `Collection` for lists); mutations return the updated DTO or `void`.

### Addressing records: `id:` or `code:`

[](#addressing-records-id-or-code)

Every identifiable record is addressed by **either** its numeric `id:` **or** its human-readable `code:` (`DebtorCode`, `InvoiceCode`, …) — never both, never neither. Passing both or neither throws a `ValidationException` *before* any request is sent. Some records (groups, subscriptions, tasks, …) have no code and take only `id:`.

```
$wefact->invoices()->get(id: 10);            // ok
$wefact->invoices()->get(code: 'F2024-0001'); // ok
$wefact->invoices()->get();                   // ValidationException — missing identifier
$wefact->invoices()->get(id: 10, code: 'F…'); // ValidationException — ambiguous
```

Error handling
--------------

[](#error-handling)

Every failure is a typed exception extending `Jarrin\WeFactApiClient\Exceptions\WeFactException`:

ExceptionWhen`ValidationException`Bad arguments caught locally (ambiguous/missing identifier) — no request sent`AuthenticationException`HTTP 403 — blocked key or non-whitelisted IP`ApiException`The API returned an `error` envelope (carries `->errors`, `->apiController`, `->apiAction`)`TransportException`Network failure or an undecodable/unexpected response```
use Jarrin\WeFactApiClient\Exceptions\ApiException;
use Jarrin\WeFactApiClient\Exceptions\WeFactException;

try {
    $wefact->debtors()->get(code: 'DB-does-not-exist');
} catch (ApiException $e) {
    report($e->errors);        // list of API messages
} catch (WeFactException $e) {
    // any other client failure
}
```

See [docs/error-handling.md](docs/error-handling.md).

Laravel
-------

[](#laravel)

The package auto-registers a service provider and a `WeFact` facade — no manual wiring.

```
php artisan vendor:publish --tag=wefact-config
```

```
// config/wefact.php reads WEFACT_API_KEY / WEFACT_BASE_URI / WEFACT_TIMEOUT from .env
use Jarrin\WeFactApiClient\WeFact;
use WeFact as WeFactFacade; // the registered facade alias

public function handle(WeFact $wefact) // resolved from the container as a singleton
{
    $wefact->invoices()->list(limit: 10);
}

WeFactFacade::debtors()->get(code: 'DB10000'); // or via the facade
```

The standalone client never loads Laravel; the Laravel layer is optional and dev-only in this package's own test suite. See [docs/configuration.md](docs/configuration.md).

CLI: seeding a test administration
----------------------------------

[](#cli-seeding-a-test-administration)

The package ships a small Symfony Console binary for populating a **dedicated test administration** with a known, reversible fixture set (all through the typed client):

```
vendor/bin/wefact seed          # find-or-create the fixtures (idempotent)
vendor/bin/wefact seed --wipe   # remove the deletable ones again
vendor/bin/wefact seed --fresh  # wipe, then re-seed
```

It reads `WEFACT_API_KEY` (falling back to `API_SECRET`) from the environment. See [docs/testing.md](docs/testing.md).

Development
-----------

[](#development)

The entire toolchain runs in **one pinned PHP 8.3 Docker image**, so contributing needs no PHP on your host. The `bin/dev` wrapper runs any command in it:

```
bin/dev composer test        # offline suite
bin/dev composer stan        # PHPStan (max)
bin/dev composer docs:api    # regenerate the API reference (phpDocumentor)
bin/dev bin/wefact seed      # seed a test administration
```

The pre-commit hook (`composer hooks:install`) runs the full gate in that image. See [docs/development.md](docs/development.md) and [docs/testing.md](docs/testing.md).

Documentation
-------------

[](#documentation)

- [Getting started](docs/getting-started.md)
- [Configuration](docs/configuration.md)
- [Resources &amp; the call pattern](docs/resources/README.md)
- [DTOs &amp; enums](docs/dtos-and-enums.md)
- [Error handling](docs/error-handling.md)
- [Testing, Docker &amp; seeding](docs/testing.md)
- [Development &amp; extending the client](docs/development.md)
- [Generated API reference](docs/reference/Home.md) — per-class, produced by `composer docs:api`

License
-------

[](#license)

MIT.

###  Health Score

35

—

LowBetter than 77% of packages

Maintenance90

Actively maintained with recent releases

Popularity2

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity38

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

46d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/5e56683517f4e06d37ac06ca61e62d245853c1a0528e81c8483580bd109844e8?d=identicon)[jarrin](/maintainers/jarrin)

---

Top Contributors

[![jarrin](https://avatars.githubusercontent.com/u/1340732?v=4)](https://github.com/jarrin "jarrin (1 commits)")

---

Tags

apiclientlaravelbillinginvoicingwefact

###  Code Quality

TestsPest

Static AnalysisPHPStan, Rector

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/jarrin-wefact-php-sdk/health.svg)

```
[![Health](https://phpackages.com/badges/jarrin-wefact-php-sdk/health.svg)](https://phpackages.com/packages/jarrin-wefact-php-sdk)
```

###  Alternatives

[tempest/framework

The PHP framework that gets out of your way.

2.3k42.4k21](/packages/tempest-framework)[typo3/cms

TYPO3 CMS is a free open source Content Management Framework initially created by Kasper Skaarhoj and licensed under GNU/GPL.

1.2k1.9M122](/packages/typo3-cms)[flow-php/flow

PHP ETL - Extract Transform Load - Data processing framework

86538.6k](/packages/flow-php-flow)[typo3/cms-core

TYPO3 CMS Core

3714.0M5.8k](/packages/typo3-cms-core)[shopware/platform

The Shopware e-commerce core

3.4k1.5M3](/packages/shopware-platform)[shopware/core

Shopware platform is the core for all Shopware ecommerce products.

605.9M709](/packages/shopware-core)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
