fakturoid / fakturoid-php
Fakturoid PHP library
Installs: 235 506
Dependents: 4
Suggesters: 0
Security: 0
Stars: 29
Watchers: 17
Forks: 16
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
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.
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( new ClientInterface(), // PSR-18 client '{fakturoid-client-id}', '{fakturoid-client-secret}', 'PHPlib <your@email.cz>', 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( new ClientInterface(), // PSR-18 client '{fakturoid-client-id}', '{fakturoid-client-secret}', 'PHPlib <your@email.cz>' ); $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( new ClientInterface(), // PSR-18 client '{fakturoid-client-id}', '{fakturoid-client-secret}', 'PHPlib <your@email.cz>' ); // 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( new ClientInterface(), // PSR-18 client '{fakturoid-client-id}', '{fakturoid-client-secret}', 'PHPlib <your@email.cz>' '{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( new ClientInterface(), // PSR-18 client '{fakturoid-client-id}', '{fakturoid-client-secret}', 'PHPlib <your@email.cz>' ); $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