fakturoid / fakturoid-php
Fakturoid PHP library
Installs: 266 845
Dependents: 5
Suggesters: 0
Security: 0
Stars: 31
Watchers: 16
Forks: 18
Open Issues: 0
Requires
- php: >=8.2
- ext-json: *
- nyholm/psr7: ^1.8
- psr/http-client: ^1.0
Requires (Dev)
- ext-dom: *
- ext-mbstring: *
- guzzlehttp/guzzle: ^7.9
- phpstan/phpstan: ^1.11
- phpunit/phpunit: ^11.4
- rector/rector: ^1.2
- squizlabs/php_codesniffer: ^3.10
Suggests
- guzzlehttp/guzzle: for usage with guzzle
- symfony/http-client: for usage with symfony
README
PHP library for Fakturoid.cz. Please see API for more documentation. New account just for testing API and using separate user (created via "Settings > User account") for production usage is highly recommended.
Content
Versions
Lib. version | Fakturoid API | PHP |
---|---|---|
4.x |
v3 |
>=8.2 |
3.x |
v3 |
>=8.2 |
2.x |
v3 |
>=8.1 |
1.x |
v2 |
>=5.3.0 |
Installation
The recommended way to install is through Composer:
composer require fakturoid/fakturoid-php
Library requires PHP 8.2 (or later) and ext-json
, nyholm/psr7
and psr/http-client
extensions.
User agent and HTTP client
You need to create your own client that implements Psr\Http\Client\ClientInterface
or you can use symfony/http-client
, guzzlehttp/guzzle
or other. And you also need to set a default header value for this client, where you need to specify User-Agent.
Creating a client using Guzzle
new \GuzzleHttp\Client(['headers' => ['User-Agent' => 'Bar']])
Create a client using Symfony
In Symfony app you can define in configuration
(new \Symfony\Component\HttpClient\Psr18Client())->withOptions([['headers' => ['User-Agent' => 'Bar']]))
Authorization by OAuth 2.0
Authorization Code Flow
Authorization using OAuth takes place in several steps. We use data obtained from the developer portal as client ID and client secret (Settings → Connect other apps → OAuth 2 for app developers).
First, we offer the user a URL address where he enters his login information. We obtain this using the following method:
$fManager = new \Fakturoid\FakturoidManager( \Psr\Http\Client\ClientInterface, // see User agent and HTTP client above '{fakturoid-client-id}', '{fakturoid-client-secret}', null, '{your-redirect-uri}' ); echo '<a href="' . $fManager->getAuthenticationUrl() . '">Link</a>';
After entering the login data, the user is redirected to the specified redirect URI and with the code with which we obtain his credentials. We process the code as follows:
$fManager->requestCredentials($_GET['code']);
Credentials are now established in the object instance and we can send queries to the Fakturoid api. Credentials can be obtained in 2 ways. Obtaining credentials directly from the object:
$credentials = $fManager->getCredentials(); echo $credentials->toJson();
Client Credentials Flow
$fManager = new \Fakturoid\FakturoidManager( \Psr\Http\Client\ClientInterface, // see User agent and HTTP client above '{fakturoid-client-id}', '{fakturoid-client-secret}' ); $fManager->authClientCredentials();
Processing credentials using the credentials callback:
The way callback works is that the library calls the callback function whenever the credentials are changed. This is useful because the token is automatically refreshed after its expiration.
$fManager->setCredentialsCallback(new class implements \Fakturoid\Auth\CredentialCallback { public function __invoke(?\Fakturoid\Auth\Credentials $credentials = null): void { // Save credentials to database or another storage } });
Usage
Set credentials to the Fakturoid manager
If you run a multi-tenant application or an application that processes documents in parallel, you need to set Credentials correctly. Each time a new access token is obtained, the previous one is invalidated. For these needs there is AuthProvider::setCredentials()
and also CredentialCallback
.
$fManager = new \Fakturoid\FakturoidManager( \Psr\Http\Client\ClientInterface, // see User agent and HTTP client above '{fakturoid-client-id}', '{fakturoid-client-secret}' ); // restore credentials from storage $credentials = new \Fakturoid\Auth\Credentials( 'refreshToken', 'accessToken', (new DateTimeImmutable())->modify('-2 minutes'), \Fakturoid\Enum\AuthTypeEnum::AUTHORIZATION_CODE_FLOW // or \Fakturoid\Enum\AuthTypeEnum:CLIENT_CREDENTIALS_CODE_FLOW ); $fManager->getAuthProvider()->setCredentials($credentials); $fManager->setCredentialsCallback(new class implements \Fakturoid\Auth\CredentialCallback { public function __invoke(?\Fakturoid\Auth\Credentials $credentials = null): void { // Save credentials to database or another storage } });
Switch account
$fManager = new \Fakturoid\FakturoidManager( \Psr\Http\Client\ClientInterface, // see User agent and HTTP client above '{fakturoid-client-id}', '{fakturoid-client-secret}', '{fakturoid-account-slug}', ); $fManager->authClientCredentials(); $fManager->getBankAccountsProvider()->list(); // switch account and company $fManager->setAccountSlug('{fakturoid-account-slug-another}'); $fManager->getBankAccountsProvider()->list();
Basic usage
require __DIR__ . '/vendor/autoload.php'; $fManager = new \Fakturoid\FakturoidManager( \Psr\Http\Client\ClientInterface, // see User agent and HTTP client above '{fakturoid-client-id}', '{fakturoid-client-secret}' ); $fManager->authClientCredentials(); // get current user $user = $fManager->getUsersProvider()->getCurrentUser(); $fManager->setAccountSlug($user->getBody()->accounts[0]->slug); // or you can set account slug manually $fManager->setAccountSlug('{fakturoid-account-slug}'); // create subject $response = $fManager->getSubjectsProvider()->create(['name' => 'Firma s.r.o.', 'email' => 'aloha@pokus.cz']); $subject = $response->getBody(); // create invoice with lines $lines = [['name' => 'Big sale', 'quantity' => 1, 'unit_price' => 1000]]; $response = $fManager->getInvoicesProvider()->create(['subject_id' => $subject->id, 'lines' => $lines]); $invoice = $response->getBody(); // send by mail $fManager->getInvoicesProvider()->createMessage($invoice->id, ['email' => 'aloha@pokus.cz']); // to mark invoice as paid and send thank you email $fManager->getInvoicesProvider()->createPayment($invoice->id, ['paid_on' => (new \DateTime())->format('Y-m-d'), 'send_thank_you_email' => true]); // lock invoice (other fire actions are described in the API documentation) $fManager->getInvoicesProvider()->fireAction($invoice->id, 'lock');
Downloading an invoice PDF
$invoiceId = 123; $response = $fManager->getInvoicesProvider()->getPdf($invoiceId); $data = $response->getBody(); file_put_contents("{$invoiceId}.pdf", $data);
If you call $fManager->getInvoicesProvider()->getPdf()
right after creating an invoice, you'll get
a status code 204
(No Content
) with empty body, this means the invoice PDF
hasn't yet been generated and you should try again a second or two later.
More info in API docs.
$invoiceId = 123; // This is just an example, you may want to do this in a background job and be more defensive. while (true) { $response = $fManager->getInvoicesProvider()->getPdf($invoiceId); if ($response->getStatusCode() == 200) { $data = $response->getBody(); file_put_contents("{$invoiceId}.pdf", $data); break; } sleep(1); }
Using custom_id
You can use custom_id
attribute to store your application record ID into our record.
Invoices and subjects can be filtered to find a particular record:
$response = $fManager->getSubjectsProvider()->list(['custom_id' => '10']); $subjects = $response->getBody(); $subject = null; if (count($subjects) > 0) { $subject = $subjects[0]; }
As for subjects, Fakturoid won't let you create two records with the same custom_id
so you don't have to worry about multiple results.
Also note that the field always returns a string.
InventoryItem resource
To get all inventory items:
$fManager->getInventoryItemsProvider()->list();
To filter inventory items by certain SKU code or article number:
$fManager->getInventoryItemsProvider()->list(['sku' => 'SKU1234']); $fManager->getInventoryItemsProvider()->list(['article_number' => 'IAN321']);
To search inventory items (searches in name
, article_number
and sku
):
$fManager->getInventoryItemsProvider()->listArchived(['query' => 'Item name']);
To get all archived inventory items:
$fManager->getInventoryItemsProvider()->listArchived();
To get a single inventory item:
$fManager->getInventoryItemsProvider()->get($inventoryItemId);
To create an inventory item:
$data = [ 'name' => 'Item name', 'sku' => 'SKU12345', 'track_quantity' => true, 'quantity' => 100, 'native_purchase_price' => 500, 'native_retail_price' => 1000 ]; $fManager->getInventoryItemsProvider()->create($data)
To update an inventory item:
$fManager->getInventoryItemsProvider()->update($inventoryItemId, ['name' => 'Another name']);
To archive an inventory item:
$fManager->getInventoryItemsProvider()->archive($inventoryItemId);
To unarchive an inventory item:
$fManager->getInventoryItemsProvider()->unArchive($inventoryItemId);
To delete an inventory item:
$fManager->getInventoryItemsProvider()->delete($inventoryItemId);
InventoryMove resource
To get get all inventory moves across all inventory items:
$fManager->getInventoryMovesProvider()->list()
To get inventory moves for a single inventory item:
$fManager->getInventoryMovesProvider()->list(['inventory_item_id' => $inventoryItemId]);
To get a single inventory move:
$fManager->getInventoryMovesProvider()->get($inventoryItemId, $inventoryMoveId);
To create a stock-in inventory move:
$fManager->getInventoryMovesProvider()->create( $inventoryItemId, [ 'direction' => 'in', 'moved_on' => '2023-01-12', 'quantity_change' => 5, 'purchase_price' => '249.99', 'purchase_currency' => 'CZK', 'private_note' => 'Bought with discount' ] )
To create a stock-out inventory move:
$fManager->getInventoryMovesProvider()->create( $inventoryItemId, [ 'direction' => 'out', 'moved_on' => '2023-01-12', 'quantity_change' => '1.5', 'retail_price' => 50, 'retail_currency' => 'EUR', 'native_retail_price' => '1250' ] );
To update an inventory move:
$fManager->getInventoryMovesProvider()->update($inventoryItemId, $inventoryMoveId, ['moved_on' => '2023-01-11']);
To delete an inventory move:
$fManager->getInventoryMovesProvider()->update($inventoryItemId, $inventoryMoveId);
Handling errors
Library raises Fakturoid\Exception\ClientErrorException
for 4xx
and Fakturoid\Exception\ServerErrorException
for 5xx
status. You can get response code and response body by calling getCode()
or getResponse()->getBody()
.
try { $response = $fManager->getSubjectsProvider()->create(['name' => '', 'email' => 'aloha@pokus.cz']); $subject = $response->getBody(); } catch (\Fakturoid\Exception\ClientErrorException $e) { $e->getCode(); // 422 $e->getMessage(); // Unprocessable entity $e->getResponse()->getBody()->getContents(); // '{"errors":{"name":["je povinná položka","je příliš krátký/á/é (min. 2 znaků)"]}}' } catch (\Fakturoid\Exception\ServerErrorException $e) { $e->getCode(); // 503 $e->getMessage(); // Fakturoid is in read only state }
Common problems
- In case of problem please contact our invoicing robot on podpora@fakturoid.cz.
Development
- To run tests, PHPUnit requires
ext-dom
extension (typically aphp-xml
package on Debian) andext-mbstring
extension (php-mbstring
package). - If you wish to generate code coverage (and have more intelligent stack traces), you will need Xdebug
(
php-xdebug
package), it will hook itself into PHPUnit automatically.
Docker
$ docker-compose up -d $ docker-compose exec php composer install $ docker-compose exec php bash
Testing
Both commands do the same but the second version is a bit faster.
$ docker-compose exec php composer test:phpunit $ docker-compose exec php composer coverage:phpunit # or locally $ composer test:phpunit $ composer coverage:phpunit
Code-Style Check
Both commands do the same but the second version seems to have a more intelligent output.
$ docker-compose exec php composer check:cs # or locally $ composer check:cs
Check all requires for PR
$ docker-compose exec php composer check:all # or locally $ composer check:all
Or you can fix CS and Rector issues automatically:
$ docker-compose exec php composer fix:all # or locally $ composer fix:all