Fakturoid
The Fakturoid gem is Ruby library for API communication with web based invoicing service www.fakturoid.cz.
Fakturoid API documentation.
Installation
Add this line to your application's Gemfile:
gem "fakturoid"
And then run:
bundle
Gem Versions
Gem version | Fakturoid API | Supported Ruby |
---|---|---|
1.x |
API v3 | >=2.7.0 |
0.x |
API v2 | >=2.7.0 |
Configuration
Authorization with 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
(you can place it in an intializer config/initializers/fakturoid.rb
):
Fakturoid.configure do |config|
config.email = "yourfakturoid@email.com"
config.account = "{fakturoid-account-slug}" # You can also set `account` dynamically later.
config.user_agent = "Name of your app (your@email.com)"
config.client_id = "{fakturoid-client-id}"
config.client_secret = "{fakturoid-client-secret}"
config.redirect_uri = "{your-redirect-uri}"
config.oauth_flow = "authorization_code"
end
Create a client and let the user come to our OAuth login page:
client = Fakturoid.client
# To be rendered on a web page. State is optional.
link_to client.authorization_uri, "Enable Fakturoid Integration"
link_to client.authorization_uri(state: "abcd1234"), "Enable Fakturoid Integration"
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:
client.authorize(code: params[:code])
Credentials are now established in the object instance and we can send queries to the Fakturoid API.
pp client.credentials.as_json
Credentials can also be set manually (eg. loaded from a database):
client.credentials = {
access_token: "1db22484a6d6256e7942158d216157d075ab6e7b583bd16416181ca6c4ac180167acd8d599bd123d", # Example
refresh_token: "5682a4bc6254d85934a03931ed5e235e0f81bca64aef054fa0049d8c953eab919ba67bd8ceb532d7",
expires_at: "2024-03-01T12:42:40+01:00", # This also accepts `Time` or `DateTime` object.
token_type: "Bearer"
}
Don't forget to update your credentials after an access token refresh:
client.credentials_updated_callback do |credentials|
# Store new credentials into database.
pp client.credentials.as_json
end
You may need to set account slug dynamically:
client.account = client.users.current.body["accounts"].first["slug"]
And if you need to create a separate client for a different account:
client = Fakturoid::Client.new(account: "{another-fakturoid-account-slug}")
Revoke access altogether (works in both flows):
client.revoke_access
Client Credentials Flow
Fakturoid.configure do |config|
config.email = "yourfakturoid@email.com"
config.account = "{fakturoid-account-slug}"
config.user_agent = "Name of your app (your@email.com)"
config.client_id = "{fakturoid-client-id}"
config.client_secret = "{fakturoid-client-secret}"
config.oauth_flow = "client_credentials"
end
Credentials can be set and stored the same way as above just without a refresh token.
Usage
Almost all resources that return a list of things are paginated by 40 per page. You can specify the page number
by passing a page
parameter: client.some_resource.all(page: 2)
.
Get current user information along with a list of accounts he/she has access to
response = client.users.current
response.status_code # Returns response HTTP code.
response.body # Contains hash with returned body (JSON is parsed automatically).
Accessing content of returned body:
response.body["name"] # Return name of your company.
response.name # Alternative way of getting the name of your company.
Get a list of all account users:
response = client.users.all
Get Fakturoid account information:
response = client.account.current
Get a list of bank accounts for current account:
response = client.bank_accounts.all
Get a list of invoice number formats for current account:
response = client.number_formats.invoices
Get a list of subjects:
response = client.subjects.all(page: 2)
Fulltext search:
response = client.subjects.search(query: "Client name")
Get a specific subject:
response = client.subjects.find(subject_id)
Create a new subject:
response = client.subjects.create(name: "New client")
Update a subject:
response = client.subjects.update(subject_id, name: "Updated client")
Delete a subject:
client.subjects.delete(subject_id)
Get a list of invoices:
response = client.invoices.all
Fulltext search:
response = client.invoices.search(query: "Client name")
response = client.invoices.search(tags: "Housing")
response = client.invoices.search(tags: ["Housing", "Rent"])
response = client.invoices.search(query: "Client name", tags: ["Housing"])
Get invoice details:
response = client.invoices.find(invoice_id)
Download invoice in PDF format:
response = client.invoices.download_pdf(invoice_id)
File.open("/path/to/file.pdf", "wb") do |f|
f.write(response.body)
end
Download an attachment:
response = client.invoices.download_attachment(invoice_id, attachment_id)
File.open("/path/to/attachment.pdf", "wb") do |f|
f.write(response.body)
end
Invoice actions (eg. lock invoice, cancel, etc., full list is in the API documentation):
response = client.invoices.fire(invoice_id, "lock")
Create an invoice:
data = {
subject_id: 123,
lines: [
{
quantity: 5,
unit_name: "kg",
name: "Sand",
unit_price: "100",
vat_rate: 21
}
]
}
response = client.invoices.create(data)
Update an invoice:
response = client.invoices.update(invoice_id, number: "2015-0015")
Delete an invoice:
response = client.invoices.delete(invoice_id)
Create an invoice payment:
response = client.invoice_payments.create(invoice_id, paid_on: Date.today)
response = client.invoice_payments.create(invoice_id, amount: "500")
Create a tax document for a payment:
response = client.invoice_payments.create_tax_document(invoice_id, payment_id)
tax_document_response = client.invoices.find(response.tax_document_id)
Delete a payment:
response = client.invoice_payments.delete(invoice_id, payment_id)
Send a message to the client (you can use more variables in the message
, full list is in the API documentation):
data = {
email: "testemail@testemail.cz",
email_copy: "some@emailcopy.cz",
subject: "I have an invoice for you",
message: "Hi,\n\nyou can find invoice no. #no# on the following page #link#\n\nHave a nice day"
}
response = client.invoice_messages.create(invoice_id, data)
Get a list of expenses:
response = client.expenses.all
Fulltext search:
response = client.expenses.search(query: "Supplier name")
response = client.expenses.search(tags: "Housing")
response = client.expenses.search(tags: ["Housing", "Rent"])
response = client.expenses.search(query: "Supplier name", tags: ["Housing"])
Get expense details:
response = client.expenses.find(expense_id)
Download an attachment:
response = client.expenses.download_attachment(expense_id, attachment_id)
File.open("/path/to/attachment.pdf", "wb") do |f|
f.write(response.body)
end
Expense actions (eg. lock expense etc., full list is in the API documentation):
response = client.expenses.fire(expense_id, "lock")
Create an expense:
data = {
subject_id: 123,
lines: [
{
quantity: 5,
unit_name: "kg",
name: "Sand",
unit_price: "100",
vat_rate: 21
}
]
}
response = client.expenses.create(data)
Update an expense:
response = client.expenses.update(expense_id, number: "N20240201")
Delete an expense:
response = client.expenses.delete(expense_id)
Create an expense payment:
response = client.expense_payments.create(expense_id, paid_on: Date.today)
response = client.expense_payments.create(expense_id, amount: "500")
Delete a payment:
response = client.expense_payments.delete(expense_id, payment_id)
Get a list of inbox files:
response = client.inbox_files.all
Create an inbox file:
require "base64"
client.inbox_files.create(
attachment: "data:application/pdf;base64,#{Base64.urlsafe_encode64(File.read("some-file.pdf"))}",
filename: "some-file.pdf", # This is optional and defaults to `attachment.{extension}`.
send_to_ocr: true # Also optional
)
Send a file to OCR (data extraction service):
client.inbox_files.send_to_ocr(inbox_file_id)
Download a file:
filename = client.inbox_files.find(inbox_file_id).filename
response = client.inbox_files.download(inbox_file_id)
File.open("/path/to/file.pdf", "wb") do |f|
f.write(response.body)
end
Delete a file:
response = client.inbox_files.delete(inbox_file_id)
Get a list of inventory items:
response = client.inventory_items.all
response = client.inventory_items.all(sku: "SKU1234") # Filter by SKU code
Get a list of archived inventory items:
response = client.inventory_items.archived
Get a list of inventory items that are running low on quantity:
response = client.inventory_items.low_quantity
Search inventory items (searches in name
, article_number
and sku
):
response = client.inventory_items.search(query: "Item name")
Get a single inventory item:
response = client.inventory_items.find(inventory_item_id)
Create an inventory item:
data = {
name: "Item name",
sku: "SKU1234",
track_quantity: true,
quantity: 100,
native_purchase_price: 500,
native_retail_price: 1000
}
response = client.inventory_items.create(data)
Update an inventory item:
response = client.inventory_items.update(inventory_item_id, name: "Another name")
Delete an inventory item:
response = client.inventory_items.delete(inventory_item_id)
Archive an inventory item:
response = client.inventory_items.archive(inventory_item_id)
Unarchive an inventory item:
response = client.inventory_items.unarchive(inventory_item_id)
Get a list of inventory moves across all inventory items:
response = client.inventory_moves.all
Get a list of inventory moves for a single inventory item:
response = client.inventory_moves.all(inventory_item_id: inventory_item_id)
Get a single inventory move:
response = client.inventory_moves.find(inventory_item_id, inventory_move_id)
Create a stock-in inventory move:
response = client.inventory_moves.create(
inventory_item_id,
direction: "in",
moved_on: Date.today,
quantity_change: 5,
purchase_price: "249.99",
purchase_currency: "CZK",
private_note: "Bought with discount"
)
Create a stock-out inventory move:
response = client.inventory_moves.create(
inventory_item_id,
direction: "out",
moved_on: Date.today,
quantity_change: "1.5",
retail_price: 50,
retail_currency: "EUR",
native_retail_price: "1250"
)
Update an inventory move:
data = {
private_note: "Text"
# Plus other fields if necessary
}
response = client.inventory_moves.update(inventory_item_id, inventory_move_id, data)
Delete an inventory move:
response = client.inventory_moves.delete(inventory_item_id, inventory_move_id)
Get a list of generators:
response = client.generators.all
Get generator details:
response = client.generators.find(generator_id)
Create a generator:
data = {
name: "Workshop",
subject_id: 123,
lines: [
{
quantity: 5,
unit_name: "kg",
name: "Sand",
unit_price: "100",
vat_rate: 21
}
]
}
response = client.generators.create(data)
Update an generator:
response = client.generators.update(generator_id, name: "Another name")
Delete an generator:
response = client.generators.delete(generator_id)
Get a list of recurring generators:
response = client.recurring_generators.all
Get recurring generator details:
response = client.recurring_generators.find(recurring_generator_id)
Create a recurring generator:
data = {
name: "Workshop",
subject_id: subject_id,
start_date: Date.today,
months_period: 1,
lines: [
{
quantity: 5,
unit_name: "kg",
name: "Sand",
unit_price: "100",
vat_rate: 21
}
]
}
response = client.recurring_generators.create(data)
Update a recurring generator:
response = client.recurring_generators.update(recurring_generator_id, name: "Another name")
Delete a recurring generator:
response = client.recurring_generators.delete(recurring_generator_id)
Get a list of all events:
response = client.events.all
Get a list of document-paid events:
response = client.events.paid
Get a list of all todos:
response = client.todos.all
Toggle a todo completion:
response = client.todos.toggle_completion(todo_id)
Get a list of all webhooks:
response = client.webhooks.all
Get a single webhook:
response = client.webhooks.find(webhook_id)
Create a webhook:
response = client.webhooks.create(webhook_url: "https://example.com/webhook", events: %w[invoice_created])
Update a webhook:
response = client.webhooks.update(webhook_id, webhook_url: "https://example.com/webhook")
Delete a webhook:
response = client.webhooks.delete(webhook_id)
Handling Errors
The Fakturoid gem raises exceptions if server responds with an error.
All exceptions except ConfigurationError
contain following attributes:
-
message
: Error description -
response_code
: HTTP code of error (only number) -
response_body
: Response body parsed as a hash
Error class | Response code | Description |
---|---|---|
ConfigurationError | N/A | Important configuration is missing. |
OauthError | 400 Bad Request | OAuth request fails. |
AuthenticationError | 401 Unauthorized | Authentication fails due to client credentials error or access token expired. |
ClientError | 4XX | User-sent data are invalid. |
ServerError | 5XX | An exception happened while processing a request. |
Contributing
- Make a pull request with requested changes.
- Make sure the pull request is as small as possible. If you encounter something that should be changed but is unrelated, please make a separate pull request for that.
- Do not change the version number inside the pull request – this will be done after it is merged.
Development
git clone <repo-url>
cd fakturoid-ruby
bundle
bundle exec rake # Run tests
bundle exec rake build # Test the package building process