This package was developed to give you a quick start to the Bexio API.
Bexio is a cloud-based simple business software for the self-employed, small businesses and startups.
Package | PHP | Laravel |
---|---|---|
v13.0.0 (alpha) | ^8.3 - ^8.5 | 13.x |
v12.0.0 | ^8.2 - ^8.4 | 12.x |
v11.0.0 | ^8.2 - ^8.3 | 11.x |
v1.0.0 | ^8.2 | 10.x |
The package supports multiple authentication methods for both single-tenant and multi-tenant applications:
Method | Supported |
---|---|
API token | ✅ |
OAuth | ✅ |
You can install the package via composer:
composer require codebar-ag/laravel-bexio
Optionally, you can publish the config file with:
php artisan vendor:publish --provider="CodebarAg\Bexio\BexioServiceProvider" --tag="bexio-config"
Add the following environment variables to your .env
file:
# For Token Authentication
BEXIO_API_TOKEN=your_api_token_here
# For OAuth Authentication
BEXIO_OAUTH_CLIENT_ID=your_client_id_here
BEXIO_OAUTH_CLIENT_SECRET=your_client_secret_here
BEXIO_OAUTH_SCOPES=accounting,contact_show
# Optional: Custom cache store for OAuth tokens
BEXIO_CACHE_STORE=redis
# Optional: Redirect URL after successful OAuth authentication (will redirect to / by default)
BEXIO_REDIRECT_URL=/dashboard
You can retrieve your API token or OAuth credentials from your Bexio Developer Dashboard.
For OAuth credentials, you'll need to register your application in the Bexio Developer Portal.
For applications that only need to authenticate with one Bexio account:
use CodebarAg\Bexio\BexioConnector;
use CodebarAg\Bexio\Dto\OAuthConfiguration\ConnectWithToken;
// Using environment configuration
$connector = new BexioConnector(new ConnectWithToken());
// Or with explicit token
$connector = new BexioConnector(new ConnectWithToken(token: 'your-specific-token'));
use CodebarAg\Bexio\BexioConnector;
use CodebarAg\Bexio\Dto\OAuthConfiguration\ConnectWithOAuth;
// Using environment configuration
$connector = new BexioConnector(new ConnectWithOAuth());
// Or with explicit configuration
$connector = new BexioConnector(new ConnectWithOAuth(
client_id: 'your_client_id',
client_secret: 'your_client_secret',
redirect_uri: 'https://yourapp.com/bexio/callback',
scopes: ['openid', 'profile', 'email', 'accounting']
));
The package provides built-in routes for OAuth authentication:
- Redirect to Bexio:
/bexio/redirect
- OAuth Callback:
/bexio/callback
You can customize the route prefix and middleware in your config file:
// config/bexio.php
'route_prefix' => 'custom-bexio-prefix',
// Add custom middleware to OAuth routes (in addition to 'web' middleware)
'route_middleware' => ['auth', 'verified'],
After the OAuth callback is processed, the user will be redirected to the URL specified in your configuration (config('bexio.redirect_url')
or /
by default) with flash session data indicating the result:
Success Response:
// When OAuth authentication is successful
session()->get('bexio_oauth_success'); // true
session()->get('bexio_oauth_message'); // 'Successfully authenticated with Bexio.'
Error Responses:
// When user rejects authorization or OAuth returns an error
session()->get('bexio_oauth_success'); // false
session()->get('bexio_oauth_message'); // 'OAuth authorization failed: access_denied'
// When required parameters (code or state) are missing
session()->get('bexio_oauth_success'); // false
session()->get('bexio_oauth_message'); // 'Missing required parameters: code or state.'
Handling the callback in your controller:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class DashboardController extends Controller
{
public function index(Request $request)
{
if (session()->has('bexio_oauth_success')) {
$success = session()->get('bexio_oauth_success');
$message = session()->get('bexio_oauth_message');
if ($success) {
// OAuth authentication was successful
// You can now use the BexioConnector to make API calls
return view('dashboard.index')->with('success', $message);
} else {
// OAuth authentication failed
return view('dashboard.index')->with('error', $message);
}
}
return view('dashboard.index');
}
}
For applications that need to authenticate with multiple Bexio accounts:
use CodebarAg\Bexio\BexioConnector;
use CodebarAg\Bexio\Dto\OAuthConfiguration\ConnectWithToken;
// Different token for each tenant
$tenantToken = $currentUser->bexio_token; // Retrieved from your database
$connector = new BexioConnector(new ConnectWithToken(token: $tenantToken));
For multi-tenant OAuth, you need to create custom resolvers and bind them to Laravel's service container.
this example is with the auth user as a tenant, but you can easily modify it to another model of your choice.
<?php
namespace App\Support\Bexio;
use CodebarAg\Bexio\Contracts\BexioOAuthConfigResolver as BexioOAuthConfigResolverContract;
use CodebarAg\Bexio\Dto\OAuthConfiguration\ConnectWithOAuth;
use Illuminate\Support\Facades\Auth;
class BexioOAuthConfigResolver implements BexioOAuthConfigResolverContract
{
public function resolve(): ConnectWithOAuth
{
$user = Auth::user();
return new ConnectWithOAuth(
client_id: $user->bexio_client_id,
client_secret: $user->bexio_client_secret,
scopes: $user->bexio_scopes ?? []
);
}
}
this example is with the auth user as a tenant, but you can easily modify it to another model of your choice. you are also not required to use cache, you could store the authenticator on the model's database entry by updating a column on the model rather than using the Cache facade.
<?php
namespace App\Support\Bexio;
use CodebarAg\Bexio\BexioConnector;
use CodebarAg\Bexio\Contracts\BexioOAuthAuthenticationStoreResolver as BexioOAuthAuthenticationStoreResolverContract;
use CodebarAg\Bexio\Contracts\BexioOAuthConfigResolver;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Crypt;
use Saloon\Http\Auth\AccessTokenAuthenticator;
class BexioOAuthAuthenticationStoreResolver implements BexioOAuthAuthenticationStoreResolverContract
{
protected string $cacheKey = 'bexio_oauth_authenticator';
public function get(): ?AccessTokenAuthenticator
{
$userId = Auth::id();
$cacheStore = Cache::store(config('bexio.cache_store', config('cache.default')));
$cacheKey = $this->cacheKey . ':' . $userId;
if (! $cacheStore->has($cacheKey)) {
return null;
}
try {
$serialized = Crypt::decrypt($cacheStore->get($cacheKey));
$authenticator = AccessTokenAuthenticator::unserialize($serialized);
if ($authenticator->hasExpired()) {
// Refresh the access token
$resolver = App::make(BexioOAuthConfigResolver::class);
$connector = new BexioConnector($resolver->resolve());
$authenticator = $connector->refreshAccessToken($authenticator);
$this->put($authenticator);
}
return $authenticator;
} catch (\Throwable $e) {
return null;
}
}
public function put(AccessTokenAuthenticator $authenticator): void
{
$userId = Auth::id();
$cacheStore = Cache::store(config('bexio.cache_store', config('cache.default')));
$cacheKey = $this->cacheKey . ':' . $userId;
$serialized = $authenticator->serialize();
$encrypted = Crypt::encrypt($serialized);
$cacheStore->put($cacheKey, $encrypted);
}
public function forget(): void
{
$userId = Auth::id();
$cacheStore = Cache::store(config('bexio.cache_store', config('cache.default')));
$cacheKey = $this->cacheKey . ':' . $userId;
$cacheStore->forget($cacheKey);
}
}
In your AppServiceProvider
or a dedicated service provider:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class BexioServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->bind(
\CodebarAg\Bexio\Contracts\BexioOAuthConfigResolver::class,
\App\Support\Bexio\BexioOAuthConfigResolver::class
);
$this->app->bind(
\CodebarAg\Bexio\Contracts\BexioOAuthAuthenticationStoreResolver::class,
\App\Support\Bexio\BexioOAuthAuthenticationStoreResolver::class
);
}
}
use CodebarAg\Bexio\BexioConnector;
use CodebarAg\Bexio\Dto\OAuthConfiguration\ConnectWithOAuth;
// The connector will:
// 1. Use your custom config resolver to get user-specific OAuth config
// 2. Use your custom auth store resolver to manage tokens per user
// 3. Automatically handle token refresh when needed
$connector = new BexioConnector();
// If you prefer, you can still provide the config yourself
$connector = new BexioConnector(new \CodebarAg\Bexio\Dto\OAuthConfiguration\ConnectWithOAuth(
client_id: Auth::user()->bexio_client_id,
client_secret: Auth::user()->bexio_client_secret,
));
// Or you can manually use your custom resolver
$configuration = App::make(\CodebarAg\Bexio\Contracts\BexioOAuthConfigResolver::class)->resolve();
$connector = new BexioConnector($configuration);
You can implement custom validation logic that runs before the OAuth authenticator is stored. This is useful for:
- Validating user permissions with API calls
- Checking company/organization restrictions
- Implementing custom business logic
- Adding custom error handling with redirects
This feature is not limited to multi-tenant setups; it can be used in single-tenant applications as well.
Create Custom Validation Resolver:
<?php
namespace App\Support\Bexio;
use CodebarAg\Bexio\BexioConnector;
use CodebarAg\Bexio\Contracts\BexioOAuthAuthenticationValidateResolver as BexioOAuthAuthenticationValidateResolverContract;
use CodebarAg\Bexio\Dto\OAuthConfiguration\BexioOAuthAuthenticationValidationResult;
use CodebarAg\Bexio\Requests\OpenID\FetchUserInfoRequest;
use CodebarAg\Bexio\Requests\CompanyProfiles\FetchACompanyProfileRequest;
use Illuminate\Support\Facades\Redirect;
class BexioOAuthAuthenticationValidateResolver implements BexioOAuthAuthenticationValidateResolverContract
{
public function resolve(BexioConnector $connector): BexioOAuthAuthenticationValidationResult
{
try {
// Example 1: Validate user info
$userInfo = $connector->send(new FetchUserInfoRequest());
$userData = $userInfo->json();
if (!$this->isValidUser($userData)) {
return BexioOAuthAuthenticationValidationResult::failed(
Redirect::to('/unauthorized')
->with('error', 'User not authorized for this application')
->with('user_email', $userData['email'])
);
}
// Example 2: Validate company permissions
$companyProfile = $connector->send(new FetchACompanyProfileRequest());
$companyData = $companyProfile->json();
if (!$this->isAllowedCompany($companyData['id'])) {
return BexioOAuthAuthenticationValidationResult::failed(
Redirect::to('/company-not-allowed')
->with('error', 'Your company is not authorized to use this application')
->with('company_name', $companyData['name'])
);
}
// All validations passed
return BexioOAuthAuthenticationValidationResult::success();
} catch (\Exception $e) {
// Handle API errors during validation
return BexioOAuthAuthenticationValidationResult::failed(
Redirect::to('/validation-error')
->with('error', 'Unable to validate OAuth permissions: ' . $e->getMessage())
);
}
}
private function isValidUser(array $userData): bool
{
// Your custom user validation logic
return in_array($userData['email'], [
'allowed@example.com',
'admin@mycompany.com'
]);
}
private function isAllowedCompany(int $companyId): bool
{
// Your custom company validation logic
$allowedCompanies = [12345, 67890];
return in_array($companyId, $allowedCompanies);
}
}
Register the Custom Validator:
Add to your service provider:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class BexioServiceProvider extends ServiceProvider
{
public function register(): void
{
// ... existing bindings ...
$this->app->bind(
\CodebarAg\Bexio\Contracts\BexioOAuthAuthenticationValidateResolver::class,
\App\Support\Bexio\BexioOAuthAuthenticationValidateResolver::class
);
}
}
Validation Result Types:
use CodebarAg\Bexio\Dto\OAuthConfiguration\BexioOAuthAuthenticationValidationResult;
use Illuminate\Support\Facades\Redirect;
// Success - proceed with storing the authenticator
BexioOAuthAuthenticationValidationResult::success();
// Failed - use default error redirect (configured redirect_url with error message)
BexioOAuthAuthenticationValidationResult::failed();
// Failed - use custom redirect with your own error handling
BexioOAuthAuthenticationValidationResult::failed(
Redirect::to('/custom-error-page')
->with('error', 'Custom validation message')
->with('additional_data', 'any extra data you need')
);
Error Handling:
When validation fails, users will be redirected with these session variables:
// For default failed validation
session()->get('bexio_oauth_success'); // false
session()->get('bexio_oauth_message'); // 'Authentication validation failed.'
// For custom redirects, you control the session data
session()->get('error'); // Your custom error message
session()->get('user_email'); // Any additional data you passed
The package automatically applies default OAuth scopes for OpenID Connect authentication. These default scopes are:
openid
- Required for OpenID Connect authenticationprofile
- Access to basic profile informationemail
- Access to email addresscompany_profile
- Access to company profile informationoffline_access
- Enables refresh token functionality for long-term access
These default scopes are automatically included in all OAuth requests and cannot be removed. You can add additional scopes as needed for your application.
The package provides enums for OAuth scopes:
use CodebarAg\Bexio\Enums\OAuthConfiguration\OAuthOpenIDConnectScope;
use CodebarAg\Bexio\Enums\OAuthConfiguration\OAuthApiScope;
// OpenID Connect scopes
OAuthOpenIDConnectScope::OPENID->value; // 'openid'
OAuthOpenIDConnectScope::PROFILE->value; // 'profile'
OAuthOpenIDConnectScope::EMAIL->value; // 'email'
OAuthOpenIDConnectScope::COMPANY_PROFILE->value; // 'company_profile'
OAuthOpenIDConnectScope::OFFLINE_ACCESS->value; // 'offline_access'
// API scopes
OAuthApiScope::ACCOUNTING->value; // 'accounting'
OAuthApiScope::CONTACT_SHOW->value; // 'contact_show'
OAuthApiScope::CONTACT_EDIT->value; // 'contact_edit'
// ... and many more
You can specify a custom cache store for OAuth token storage:
// config/bexio.php
'cache_store' => 'redis', // or any other configured cache store
// config/bexio.php
'route_prefix' => 'api/bexio', // Custom route prefix
'redirect_url' => '/dashboard', // Where to redirect after OAuth Callback
// Add custom middleware to OAuth routes (in addition to 'web' middleware)
'route_middleware' => ['auth', 'verified'],
The OAuth routes (/bexio/redirect
and /bexio/callback
) automatically include the web
middleware group by default. You can add additional middleware using the route_middleware
configuration:
Examples:
// Require authentication for OAuth routes
'route_middleware' => ['auth'],
// Require authentication and email verification
'route_middleware' => ['auth', 'verified'],
// Add custom middleware
'route_middleware' => ['auth', 'custom-middleware'],
// Multiple middleware with parameters
'route_middleware' => ['auth:api', 'throttle:60,1'],
Common Use Cases:
- Authentication Required: Use
['auth']
to ensure only authenticated users can initiate OAuth flow - Email Verification: Use
['auth', 'verified']
for applications requiring email verification - Rate Limiting: Use
['throttle:10,1']
to limit OAuth attempts - Custom Authorization: Add your own middleware to control who can access OAuth routes
The middleware will be applied to both OAuth routes:
GET /bexio/redirect
- Initiates OAuth flowGET /bexio/callback
- Handles OAuth callback from Bexio
After setting up authentication, create a connector instance:
use CodebarAg\Bexio\BexioConnector;
// For single tenant (uses default resolvers)
$connector = new BexioConnector();
// For specific authentication
$connector = new BexioConnector(new ConnectWithToken(token: 'your-token'));
$connector = new BexioConnector(new ConnectWithOAuth(/* config */));
The following responses are currently supported for retrieving the response body:
Response Methods | Description | Supported |
---|---|---|
body | Returns the HTTP body as a string | ✅ |
json | Retrieves a JSON response body and json_decodes it into an array. | ✅ |
object | Retrieves a JSON response body and json_decodes it into an object. | ✅ |
collect | Retrieves a JSON response body and json_decodes it into a Laravel collection. Requires illuminate/collections to be installed. | ✅ |
dto | Converts the response into a data-transfer object. You must define your DTO first | ✅ |
See https://docs.saloon.dev/the-basics/responses for more information.
We provide enums for the following values:
Enum | Values |
---|---|
Accounts: SearchFieldEnum | ACCOUNT_NO(), self FIBU_ACCOUNT_GROUP_ID(), NAME(), ACCOUNT_TYPE() |
Accounts: AccountTypeEnum | EARNINGS(), EXPENDITURES(), ACTIVE_ACCOUNTS(), PASSIVE_ACCOUNTS(), COMPLETE_ACCOUNTS() |
AdditionalAddresses: AddSearchTypeEnum | ID(), ID_ASC(), ID_DESC(), NAME(), NAME_ASC(), NAME_DESC() |
CalendarYears: VatAccountingMethodEnum | EFFECTIVE(), NET_TAX() |
CalendarYears: VatAccountingTypeEnum | AGREED(), COLLECTED() |
ContactGroups: OrderByEnum | ID(), ID_ASC(), ID_DESC(), NAME(), NAME_ASC(), NAME_DESC() |
ContactRelations: OrderByEnum | ID(), ID_ASC(), ID_DESC(), CONTACT_ID(), CONTACT_ID_ASC(), CONTACT_ID_DESC(), CONTACT_SUB_ID(), CONTACT_SUB_ID_ASC(), CONTACT_SUB_ID_DESC(), UPDATED_AT(), UPDATED_AT_ASC(), UPDATED_AT_DESC() |
Contacts: OrderByEnum | ID(), ID_ASC(), ID_DESC(), NR(), NR_ASC(), NR_DESC(), NAME_1(), NAME_1_ASC(), NAME_1_DESC(), UPDATED_AT(), UPDATED_AT_ASC(), UPDATED_AT_DESC() |
ContactSectors: OrderByEnum | ID(), ID_ASC(), ID_DESC(), NAME(), NAME_ASC(), NAME_DESC() |
IbanPayments: AllowanceTypeEnum | FEE_PAID_BY_SENDER(), FEE_PAID_BY_RECIPIENT(), FEE_SPLIT(), NO_FEE() |
IbanPayments: StatusEnum | OPEN(), TRANSFERRED(), DOWNLOADED(), ERROR(), CANCELLED() |
ManualEntries: TypeEnum | MANUAL_SINGLE_ENTRY(), MANUAL_GROUP_ENTRY(), MANUAL_COMPOUND_ENTRY() |
QrPayments: AllowanceTypeEnum | FEE_PAID_BY_SENDER(), FEE_PAID_BY_RECIPIENT(), FEE_SPLIT(), NO_FEE() |
QrPayments: StatusEnum | OPEN(), TRANSFERRED(), DOWNLOADED(), ERROR(), CANCELLED() |
Taxes: ScopeEnum | ACTIVE(), INACTIVE() |
Taxes: TypeEnum | SALES_TAX(), PRE_TAX() |
Titles: OrderByEnum | ID(), ID_ASC(), ID_DESC(), NAME(), NAME_ASC(), NAME_DESC() |
SearchCriteriaEnum | EQUALS(), DOUBLE_EQUALS(), EQUAL(), NOT_EQUALS(), GREATER_THAN_SYMBOL(), GREATER_THAN(), GREATER_EQUAL_SYMBOL(), GREATER_EQUAL(), LESS_THAN_SYMBOL(), LESS_THAN(), LESS_EQUAL_SYMBOL(), LESS_EQUAL(), LIKE(), NOT_LIKE(), IS_NULL(), NOT_NULL(), IN(), NOT_IN() |
Note: When using the dto method on a response, the enum values will be converted to their respective enum class.
We provide DTOs for the following:
DTO |
---|
AccountGroupDTO |
AccountDTO |
BankAccountDTO |
AdditionalAddressDTO |
BankAccountDTO |
BusinessActivityDTO |
BusinessYearDTO |
CalendarYearDTO |
CompanyProfileDTO |
ContactAdditionalAddressDTO |
ContactGroupDTO |
ContactRelationDTO |
ContactDTO |
CreateEditContactDTO |
ContactSectorDTO |
CurrencyDTO |
CreateCurrencyDTO |
EditCurrencyDTO |
ExchangeCurrencyDTO |
DocumentSettingDTO |
FileDTO |
EditFileDTO |
FileUsageDTO |
InvoiceDTO |
InvoicePositionDTO |
InvoiceTaxDTO |
PdfDTO |
LanguageDTO |
AddFileDTO |
EntryDTO |
FileDTO |
ManualEntryDTO |
NoteDTO |
PaymentDTO |
PaymentTypeDTO |
ProjectDTO |
JournalDTO |
SalutationDTO |
TaxDTO |
TitleDTO |
UnitDTO |
UserDTO |
VatPeriodDTO |
In addition to the above, we also provide DTOs to be used for create and edit request for the following:
DTO |
---|
CreateCalendarYearDTO |
CreateEditAdditionalAddressDTO |
CreateEditContactAdditionalAddressDTO |
CreateEditContactGroupDTO |
CreateEditContactRelationDTO |
CreateEditContactDTO |
CreateCurrencyDTO |
EditCurrencyDTO |
EditFileDTO |
AddFileDTO |
CreateEditIbanPaymentDTO |
CreateEntryDTO |
CreateManualEntryDTO |
CreateEditNoteDTO |
CreateEditQrPaymentDTO |
CreateEditSalutationDTO |
CreateEditTitleDTO |
Note: This is the preferred method of interfacing with Requests and Responses however you can still use the json, object and collect methods. and pass arrays to the requests.
Here are some examples of how to use the package with different authentication methods:
use CodebarAg\Bexio\BexioConnector;
use CodebarAg\Bexio\Dto\OAuthConfiguration\ConnectWithToken;
use CodebarAg\Bexio\Dto\OAuthConfiguration\ConnectWithOAuth;
// Token authentication using environment configuration
$connector = new BexioConnector(new ConnectWithToken());
// Token authentication with explicit token
$connector = new BexioConnector(new ConnectWithToken(token: 'your-specific-token'));
// OAuth authentication using environment configuration
$connector = new BexioConnector(new ConnectWithOAuth());
// OAuth authentication with explicit configuration
$connector = new BexioConnector(new ConnectWithOAuth(
client_id: 'your_client_id',
client_secret: 'your_client_secret',
redirect_uri: 'https://yourapp.com/bexio/callback',
scopes: ['openid', 'profile', 'email', 'accounting']
));
// Multi-tenant with custom resolvers (using default connector)
$connector = new BexioConnector();
/**
* Fetch A List Of Account Groups
*/
$accountGroups = $connector->send(new FetchAListOfAccountGroupsRequest())->dto();
/**
* Fetch A List Of Accounts
*/
$accounts = $connector->send(new FetchAListOfAccountsRequest())->dto();
/**
* Search Accounts
*/
$accounts = $connector->send(new SearchAccountsRequest(
searchField: 'Name',
searchTerm: 'Something'
))->dto();
/**
* Fetch A List Of Addresses
*/
$addresses = $connector->send(new FetchAListOfAddressesRequest())->dto();
/**
* Fetch An Address
*/
$address = $connector->send(new FetchAnAddressRequest(
id: 1
))->dto();
/**
* Search Addresses
*/
$addresses = $connector->send(new SearchAddressesRequest(
searchField: 'Name',
searchTerm: 'Something'
))->dto();
/**
* Create Address
*/
$address = $connector->send(new CreateAddressRequest(
data: new CreateEditAddressDTO(
name: 'Test',
subject: 'Test Subject',
description: 'This is a test',
address: 'Test Address',
postcode: '1234',
city: 'Test City',
)
));
/**
* Edit Address
*/
$address = $connector->send(new EditAnAddressRequest(
id: 1,
data: new CreateEditAddressDTO(
name: 'Test Edit',
subject: 'Test Subject Edit',
description: 'This is a test edit',
address: 'Test Address Edit',
postcode: '4567',
city: 'Test City Edit',
)
));
/**
* Delete Address
*/
$address = $connector->send(new DeleteAnAddressRequest(
id: 1
));
/**
* Fetch A List Of Bank Accounts
*/
$bankAccounts = $connector->send(new FetchAListOfBankAccountsRequest())->dto();
/**
* Fetch A Single Bank Account
*/
$bankAccount = $connector->send(new FetchASingleBankAccountRequest(
id: 1
))->dto();
/**
* Fetch A List Of Business Years
*/
$businessYears = $connector->send(new FetchAListOfBusinessYearsRequest())->dto();
/**
* Fetch A Business Year
*/
$businessYear = $connector->send(new FetchABusinessYearRequest(
id: 1
))->dto();
/**
* Fetch A List Of Calendar Years
*/
$calendarYears = $connector->send(new FetchAListOfCalendarYearsRequest())->dto();
/**
* Fetch A Calendar Year
*/
$calendarYear = $connector->send(new FetchACalendarYearRequest(
id: 1
))->dto();
/**
* Fetch A List Of Company Profiles
*/
$companyProfiles = $connector->send(new FetchAListOfCompanyProfilesRequest())->dto();
/**
* Fetch A Company Profile
*/
$companyProfile = $connector->send(new FetchACompanyProfileRequest(
id: 1
))->dto();
/**
* Fetch A List Of Contact Additional Addresses
*/
$contactAdditionalAddresses = $connector->send(new FetchAListOfContactAdditionalAddressesRequest(
contactId: 1
))->dto();
/**
* Fetch A Contact Additional Address
*/
$contactAdditionalAddress = $connector->send(new FetchAContactAdditionalAddressRequest(
contactId: 1,
id: 1
))->dto();
/**
* Search Contact Additional Address
*/
$contactAdditionalAddresses = $connector->send(new SearchContactAdditionalAddressesRequest(
contactId: 1,
searchField: 'Name',
searchTerm: 'Something'
))->dto();
/**
* Create Contact Additional Address
*/
$contactAdditionalAddress = $connector->send(new CreateContactAdditionalAddressRequest(
contactId: 1,
data: new CreateEditContactAdditionalAddressDTO(
name: 'Test',
subject: 'Test Subject',
description: 'This is a test',
address: 'Test Address',
postcode: '1234',
city: 'Test City',
)
));
/**
* Edit Contact Additional Address
*/
$contactAdditionalAddress = $connector->send(new EditAContactAdditionalAddressRequest(
contactId: 1,
id: 9,
data: new CreateEditContactAdditionalAddressDTO(
name: 'Test Edit',
subject: 'Test Subject Edit',
description: 'This is a test edit',
address: 'Test Address Edit',
postcode: '4567',
city: 'Test City Edit',
)
));
/**
* Delete Contact Additional Address
*/
$contactAdditionalAddress = $connector->send(new DeleteAContactAdditionalAddressRequest(
contactId: 1,
id: 9,
));
/**
* Fetch A List Of Contact Groups
*/
$contactGroups = $connector->send(new FetchAListOfContactGroupsRequest())->dto();
/**
* Fetch A Contact Group
*/
$contactGroup = $connector->send(new FetchAContactGroupRequest(
id: 1
))->dto();
/**
* Search Contact Groups
*/
$contactGroups = $connector->send(new SearchContactGroupsRequest(
searchField: 'Name',
searchTerm: 'Something'
))->dto();
/**
* Create Contact Group
*/
$contactGroup = $connector->send(new CreateContactGroupRequest(
data: new CreateEditContactGroupDTO(
name: 'Name'
)
));
/**
* Edit Contact Group
*/
$contactGroup = $connector->send(new EditAContactGroupRequest(
id: 1,
data: new CreateEditContactGroupDTO(
name: 'Name'
)
));
/**
* Delete Contact Group
*/
$contactGroup = $connector->send(new DeleteAContactGroupRequest(
id: 1
));
/**
* Fetch A List Of Contact Relations
*/
$contactRelations = $connector->send(new FetchAListOfContactRelationsRequest())->dto();
/**
* Fetch A Contact Relation
*/
$contactRelation = $connector->send(new FetchAContactRelationRequest(
id: 1
))->dto();
/**
* Search Contact Relations
*/
$contactRelations = $connector->send(new SearchContactRelationsRequest(
searchField: 'Name',
searchTerm: 'Something'
))->dto();
/**
* Create Contact Relation
*/
$contactRelation = $connector->send(new CreateContactRelationRequest(
data: new CreateEditContactRelationDTO(
contact_id: 1,
contact_sub_id: 2,
description: 'Something',
)
));
/**
* Edit Contact Relation
*/
$contactRelation = $connector->send(new EditAContactRelationRequest(
id: 1,
data: new CreateEditContactRelationDTO(
contact_id: 1,
contact_sub_id: 2,
description: 'Something',
)
));
/**
* Delete Contact Relation
*/
$contactRelation = $connector->send(new DeleteAContactRelationRequest(
id: 1
));
/**
* Fetch A List Of Contacts
*/
$contacts = $connector->send(new FetchAListOfContactsRequest())->dto();
/**
* Fetch A Contact
*/
$contact = $connector->send(new FetchAContactRequest(
id: 1
))->dto();
/**
* Search Contacts
*/
$contacts = $connector->send(new SearchContactsRequest(
searchField: 'Name',
searchTerm: 'Something'
))->dto();
/**
* Create Contact
*/
$contact = $connector->send(new CreateContactRequest(
data: new CreateEditContactDTO(
user_id: 1,
owner_id: 1,
contact_type_id: 1,
name_1: 'Name'
)
));
/**
* Bulk Create Contacts
*/
$contact = $connector->send(new BulkCreateContactsRequest(
data: [
new CreateEditContactDTO(
user_id: 1,
owner_id: 1,
contact_type_id: 1,
name_1: 'Name'
),
new CreateEditContactDTO(
user_id: 1,
owner_id: 1,
contact_type_id: 1,
name_1: 'Name 2'
)
]
));
/**
* Edit Contact
*/
$contact = $connector->send(new EditAContactRequest(
id: 1,
data: new CreateEditContactDTO(
user_id: 1,
owner_id: 1,
contact_type_id: 1,
name_1: 'Name'
)
));
/**
* Delete Contact
*/
$contact = $connector->send(new DeleteAContactRequest(
id: 1
));
/**
* Restore Contact
*/
$contact = $connector->send(new RestoreAContactRequest(
id: 1
));
/**
* Fetch A List Of Contact Sectors
*/
$contactSectors = $connector->send(new FetchAListOfContactSectorsRequest())->dto();
/**
* Search Contact Sectors
*/
$contactSectors = $connector->send(new SearchContactSectorsRequest(
searchField: 'Name',
searchTerm: 'Something'
))->dto();
/**
* Fetch A List Of Currencies
*/
$currencies = $connector->send(new FetchAListOfCurrenciesRequest())->dto();
/**
* Fetch A Currency
*/
$currency = $connector->send(new FetchACurrencyRequest(
id: 1
))->dto();
/**
* Create Currency
*/
$currency = $connector->send(new CreateCurrencyRequest(
data: new CreateCurrencyDTO(
name: 'JPY',
round_factor: 0.05,
)
));
/**
* Edit Currency
*/
$currency = $connector->send(new EditACurrencyRequest(
id: 1,
data: new EditCurrencyDTO(
round_factor: 0.05,
)
));
/**
* Delete Currency
*/
$currency = $connector->send(new DeleteACurrencyRequest(
id: 1
));
/**
* Fetch All Possible Currency Codes
*/
$currencyCodes = $connector->send(new FetchAllPossibleCurrencyCodesRequest())->dto();
/**
* Fetch Exchange Rates For Currencies
*/
$exchangeRates = $connector->send(new FetchExchangeRatesForCurrenciesRequest(
currencyId: 1
))->dto();
/**
* Fetch A List Of Files
*/
$files = $connector->send(new FetchAListOfFilesRequest())->dto();
/**
* Get A Single File
*/
$file = $connector->send(new GetASingleFileRequest(
id: 1
))->dto();
/**
* Show A File Usage
*/
$fileUsage = $connector->send(new ShowAFileUsageRequest(
id: 1
))->dto();
/**
* Get A File Preview
*/
$filePreview = $connector->send(new GetAFilePreviewRequest(
id: 1
))->stream();
/**
* Download File Download
*/
$fileDownload = $connector->send(new DownloadFileDownloadRequest(
id: 1
))->stream();
/**
* Create A File
*/
$file = $connector->send(new CreateAFileRequest(
data: [
new MultipartValue(
name: 'picture',
value: fopen(__DIR__ . 'image.png', 'r'),
)
],
));
/**
* Edit A File
*/
$file = $connector->send(new EditAFileRequest(
id: 1,
data: new EditFileDTO(
name: 'Test name edited',
is_archived: false,
source_type: 'web',
)
));
/**
* Delete A File
*/
$file = $connector->send(new DeleteAFileRequest(
id: 1
));
/**
* Fetch An Iban Payment
*/
$payment = $connector->send(new GetIbanPaymentRequest(
bank_account_id: 1,
payment_id: 3
))->dto();
/**
* Create Iban Payment
*/
$payment = $connector->send(new CreateIbanPaymentRequest(
bank_account_id: 1,
data: new CreateEditIbanPaymentDTO(
instructed_amount: [
'currency' => 'CHF',
'amount' => 100,
],
recipient: [
'name' => 'Müller GmbH',
'street' => 'Sonnenstrasse',
'zip' => 8005,
'city' => 'Zürich',
'country_code' => 'CH',
'house_number' => 36,
],
iban: 'CH8100700110005554634',
execution_date: '2024-01-08',
is_salary_payment: false,
is_editing_restricted: false,
message: 'Rechnung 1234',
allowance_type: 'no_fee',
)
))->dto();
/**
* Update Iban Payment
*
* NOTE: THE PAYMENT MUST HAVE A STATUS OF OPEN TO BE UPDATED
*/
$payment = $connector->send(new EditIbanPaymentRequest(
bank_account_id: 1,
payment_id: 3,
iban: 'CH8100700110005554634',
id: 3,
data: new CreateEditIbanPaymentDTO(
instructed_amount: [
'currency' => 'CHF',
'amount' => 100,
],
recipient: [
'name' => 'Müller GmbH',
'street' => 'Colchester Place',
'zip' => 8005,
'city' => 'Zürich',
'country_code' => 'CH',
'house_number' => 36,
],
iban: 'CH8100700110005554634',
execution_date: '2024-01-08',
is_salary_payment: false,
is_editing_restricted: false,
message: 'Rechnung 1234',
allowance_type: 'no_fee',
)
))->dto();
/**
* Fetch A List Of Invoices
*/
$invoices = $connector->send(new FetchAListOfInvoicesRequest())->dto();
/**
* Fetch An Invoice
*/
$invoice = $connector->send(new FetchAnInvoiceRequest(
invoice_id: 1
))->dto();
/**
* Create An Invoice
*/
$contacts = $connector->send(new FetchAListOfContactsRequest);
$user = $connector->send(new FetchAuthenticatedUserRequest);
$languages = $connector->send(new FetchAListOfLanguagesRequest);
$banks = $connector->send(new FetchAListOfBankAccountsRequest);
$currencies = $connector->send(new FetchAListOfCurrenciesRequest);
$paymentTypes = $connector->send(new FetchAListOfPaymentTypesRequest);
$units = $connector->send(new FetchAListOfUnitsRequest);
$accounts = $connector->send(new FetchAListOfAccountsRequest);
$taxes = $connector->send(new FetchAListOfTaxesRequest(scope: 'active', types: 'sales_tax'));
$newInvoice = InvoiceDTO::fromArray([
'title' => 'Test',
'contact_id' => $contacts->dto()->first()->id,
'user_id' => $user->dto()->id,
'pr_project_id' => null,
'language_id' => $languages->dto()->first()->id,
'bank_account_id' => $banks->dto()->first()->id,
'currency_id' => $currencies->dto()->first()->id,
'payment_type_id' => $paymentTypes->dto()->first()->id,
'mwst_type' => 1,
'mwst_is_net' => true,
'show_position_taxes' => true,
'is_valid_from' => now()->format('Y-m-d h:m:s'),
'is_valid_to' => now()->addDays(5)->format('Y-m-d h:m:s'),
'api_reference' => Str::uuid(),
'positions' => [
InvoicePositionDTO::fromArray([
'type' => 'KbPositionText',
'show_pos_nr' => true,
'text' => Str::uuid(),
]),
InvoicePositionDTO::fromArray([
'type' => 'KbPositionCustom',
'amount' => 1,
'unit_id' => $units->dto()->first()->id,
'account_id' => $accounts->dto()->filter(fn ($account) => $account->account_type_enum === AccountTypeEnum::ACTIVE_ACCOUNTS())->first()->id,
'tax_id' => $taxes->dto()->first()->id,
'text' => Str::uuid(),
'unit_price' => 100,
'discount_in_percent' => '0',
]),
],
]);
$invoice = $connector->send(new CreateAnInvoiceRequest(invoice: $newInvoice))->dto();
/**
* Edit An Invoice
*/
$editInvoice = $connector->send(new FetchAnInvoiceRequest(invoice_id: 1))->dto();
$editInvoice->title = 'Test Invoice';
$invoice = $connector->send(new EditAnInvoiceRequest(invoice_id: 1, invoice: $editInvoice));
/**
* Delete An Invoice
*/
$response = $connector->send(new DeleteAnInvoiceRequest(
invoice_id: 1
));
/**
* Cancel An Invoice
*/
$response = $connector->send(new CancelAnInvoiceRequest(
invoice_id: 1
));
/**
* Create A Default Position For An Invoice
*/
$units = $connector->send(new FetchAListOfUnitsRequest);
$accounts = $connector->send(new FetchAListOfAccountsRequest);
$taxes = $connector->send(new FetchAListOfTaxesRequest(scope: 'active', types: 'sales_tax'));
$position = InvoicePositionDTO::fromArray([
'type' => 'KbPositionCustom',
'amount' => 1,
'unit_id' => $units->dto()->first()->id,
'account_id' => $accounts->dto()->filter(fn ($account) => $account->account_type === 1)->first()->id,
'tax_id' => $taxes->dto()->first()->id,
'text' => Str::uuid(),
'unit_price' => 100,
'discount_in_percent' => '0',
]);
$response = $connector->send(new CreateADefaultPositionRequest(
kb_document_type: 'kb_invoice',
invoice_id: 1,
position: $position,
));
/**
* Create A Sub Position For An Invoice
*/
$position = InvoicePositionDTO::fromArray([
'type' => 'KbSubPosition',
'text' => Str::uuid(),
'show_pos_nr' => true,
]);
$response = $connector->send(new CreateASubPositionRequest(
kb_document_type: 'kb_invoice',
invoice_id: 1,
position: $position,
));
/**
* Show PDF
*/
$pdf = $connector->send(new ShowPdfRequest(
invoice_id: 1
))->dto();
/**
* Saving PDF from response
*/
Storage::disk('local')->put('your/directory/'. $pdf->name, base64_decode($pdf->content));
/**
* Download PDF from response
*/
return response(base64_decode($pdf->content))
->header('Content-Type', $pdf->mime)
->header('Content-Disposition', 'attachment; filename="'.$pdf->name.'"')
->header('Content-Length', $pdf->size);
/**
* Fetch A List Of Languages
*/
$languages = $connector->send(new FetchAListOfLanguagesRequest())->dto();
/**
* Fetch A List Of Manual Entries
*/
$manualEntries = $connector->send(new FetchAListOfManualEntriesRequest())->dto();
/**
* Fetch Files Of Accounting Entry
*/
$files = $connector->send(new FetchFilesOfAccountingEntryRequest(
manual_entry_id: 1,
entry_id: 1
))->dto();
/**
* Fetch File Of Accounting Entry Line
*/
$file = $connector->send(new FetchFileOfAccountingEntryLineRequest(
manual_entry_id: 1,
entry_id: 1,
line_id: 1
))->dto();
/**
* Create Manual Entry
*/
$manualEntry = $connector->send(new CreateManualEntryRequest(
data: new CreateManualEntryDTO(
type: 'manual_single_entry',
date: '2023-12-13',
reference_nr: '1234',
entries: collect([
new CreateEntryDTO(
debit_account_id: 89,
credit_account_id: 90,
tax_id: 10,
tax_account_id: 89,
description: 'Something',
amount: 100,
currency_id: 1,
currency_factor: 1,
),
]),
)
));
/**
* Add File To Accounting Entry Line
*/
$manualEntry = $connector->send(new AddFileToAccountingEntryLineRequest(
manual_entry_id: 1,
entry_id: 1,
data: new AddFileDTO(
name: 'fileName',
absolute_file_path_or_stream: fopen('image.png', 'r'),
filename: 'image.png',
)
));
/**
* Get Next Reference Number
*/
$referenceNumber = $connector->send(new GetNextReferenceNumberRequest())->dto();
/**
* Fetch A List Of Notes
*/
$notes = $connector->send(new FetchAListOfNotesRequest())->dto();
/**
* Fetch A Note
*/
$note = $connector->send(new FetchANoteRequest(
id: 1
))->dto();
/**
* Search Notes
*/
$notes = $connector->send(new SearchNotesRequest(
searchField: 'Name',
searchTerm: 'Something'
))->dto();
/**
* Create Note
*/
$note = $connector->send(new CreateNoteRequest(
data: new CreateEditNoteDTO(
title: 'Test',
content: 'Test Content',
is_public: true,
)
));
/**
* Edit Note
*/
$note = $connector->send(new EditANoteRequest(
id: 1,
data: new CreateEditNoteDTO(
title: 'Test Edit',
content: 'Test Content Edit',
is_public: true,
)
));
/**
* Delete Note
*/
$note = $connector->send(new DeleteANoteRequest(
id: 1
));
/**
* Fetch A List Of Payments
*/
$payments = $connector->send(new FetchAListOfPaymentsRequest())->dto();
/**
* Cancel A Payment
*/
$payment = $connector->send(new CancelAPaymentRequest(
payment_id: 1
))->dto();
/**
* Delete A Payment
*/
$payment = $connector->send(new DeleteAPaymentRequest(
payment_id: 1
))->json();
/**
* Fetch A Qr Payment
*/
$payment = $connector->send(new GetQrPaymentRequest(
bank_account_id: 1,
payment_id: 4
))->dto();
/**
* Create A Qr Payment
*/
$connector->send(new CreateQrPaymentRequest(
bank_account_id: 1,
data: new CreateEditQrPaymentDTO(
instructed_amount: [
'currency' => 'CHF',
'amount' => 100,
],
recipient: [
'name' => 'Müller GmbH',
'street' => 'Sonnenstrasse',
'zip' => 8005,
'city' => 'Zürich',
'country_code' => 'CH',
'house_number' => 36,
],
execution_date: '2024-01-08',
iban: 'CH8100700110005554634',
qr_reference_nr: null,
additional_information: null,
is_editing_restricted: false,
)
))->dto();
/**
* Update A Qr Payment
*
* NOTE: THE PAYMENT MUST HAVE A STATUS OF OPEN TO BE UPDATED
*/
$payment = $connector->send(new EditQrPaymentRequest(
bank_account_id: 1,
payment_id: 4,
iban: '8100700110005554634',
id: 4,
data: new CreateEditQrPaymentDTO(
instructed_amount: [
'currency' => 'CHF',
'amount' => 100,
],
recipient: [
'name' => 'Müller GmbH',
'street' => 'Colchester Place',
'zip' => 8005,
'city' => 'Zürich',
'country_code' => 'CH',
'house_number' => 36,
],
execution_date: '2024-01-08',
iban: 'CH8100700110005554634',
)
))->dto();
/**
* Journal
*/
$journals = $connector->send(new JournalRequest())->dto();
/**
* Fetch A List Of Salutations
*/
$salutations = $connector->send(new FetchAListOfSalutationsRequest())->dto();
/**
* Fetch A Salutation
*/
$salutation = $connector->send(new FetchASalutationRequest(
id: 1
))->dto();
/**
* Search Salutations
*/
$salutations = $connector->send(new SearchSalutationsRequest(
searchField: 'Name',
searchTerm: 'Something'
))->dto();
/**
* Create Salutation
*/
$salutation = $connector->send(new CreateSalutationRequest(
data: new CreateEditSalutationDTO(
name: 'Test',
is_archived: false,
)
));
/**
* Edit Salutation
*/
$salutation = $connector->send(new EditASalutationRequest(
id: 1,
data: new CreateEditSalutationDTO(
name: 'Test Edit',
is_archived: false,
)
));
/**
* Delete Salutation
*/
$salutation = $connector->send(new DeleteASalutationRequest(
id: 1
));
/**
* Fetch A List Of Taxes
*/
$taxes = $connector->send(new FetchAListOfTaxesRequest())->dto();
/**
* Fetch A Tax
*/
$tax = $connector->send(new FetchATaxRequest(
id: 1
))->dto();
/**
* Delete A Tax
*/
$tax = $connector->send(new DeleteATaxRequest(
id: 1
));
/**
* Fetch A List Of Titles
*/
$titles = $connector->send(new FetchAListOfTitlesRequest())->dto();
/**
* Fetch A Title
*/
$title = $connector->send(new FetchATitleRequest(
id: 1
))->dto();
/**
* Search Titles
*/
$titles = $connector->send(new SearchTitlesRequest(
searchField: 'Name',
searchTerm: 'Something'
))->dto();
/**
* Create Title
*/
$title = $connector->send(new CreateTitleRequest(
data: new CreateEditTitleDTO(
name: 'Test',
is_archived: false,
)
));
/**
* Edit Title
*/
$title = $connector->send(new EditATitleRequest(
id: 1,
data: new CreateEditTitleDTO(
name: 'Test Edit',
is_archived: false,
)
));
/**
* Delete Title
*/
$title = $connector->send(new DeleteATitleRequest(
id: 1
));
/**
* Fetch A List Of VAT Periods
*/
$vatPeriods = $connector->send(new FetchAListOfVatPeriodsRequest())->dto();
/**
* Fetch A VAT Period
*/
$vatPeriod = $connector->send(new FetchAVatPeriodRequest(
id: 1
))->dto();
Copy your own phpunit.xml-file.
cp phpunit.xml.dist phpunit.xml
Run the tests:
./vendor/bin/pest
Please see CHANGELOG for recent changes.
Please see CONTRIBUTING for details.
composer test
./vendor/bin/pint
Please review our security policy on reporting security vulnerabilities.
- Rhys Lees
- Sebastian Fix
- Kasper Nowak
- All Contributors
- Skeleton Repository from Spatie
- Laravel Package Training from Spatie
- Laravel Saloon by Sam Carré
The MIT License (MIT). Please have a look at License File for more information.