diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..8f0de65 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,18 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false + +[*.{yml,yaml}] +indent_size = 2 + +[docker-compose.yml] +indent_size = 4 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..58f394d --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/vendor/ +.DS_Store diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..91d2ba2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,16 @@ +Copyright 2021 David Villa (david@kfoobar.se) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..54fab21 --- /dev/null +++ b/README.md @@ -0,0 +1,68 @@ +# Fortnox API Client for Laravel + +Simplifies integration with the Fortnox API. + +*Please notice! This package does not yet support all available resources in the Fortnox API* + +## Requirements + +- Laravel 6 or higher +- PHP 8.0 or higher +- Valid client id (from Fortnox) +- Valid client secret (from Fortnox) +- Valid refresh token (from Fortnox) + +## Installation + +You can install the package via Composer: + +` +composer require kfoobar/laravel-fortnox +` + +## Settings for .env + +``` +FORTNOX_CLIENT_ID= +FORTNOX_CLIENT_SECRET= +FORTNOX_REFRESH_TOKEN= +``` + +## Fortnox authorization + +In order to use the Fortnox API, you need a valid refresh token. +To get a refresh token, you need to grant access to the integration via Fortnox's OAuth2 +authorization code flow. In that process, you are assigned an *authorization code* +that you can use to exchange for a pair of token: + +The **access token** - which is used to authenticate all API request - is only valid +for 1 hour and needs to be refreshed using a refresh token. This package handles +that process as long you have a valid refresh token. + +The **refresh token** - which is used to renew the access token - is valid for 30 days. +If it expires, you need to redo the OAuth2 authorization flow. + +Every time you renew you access token, you will be assigned a new refresh token that +is valid for another 30 days. It is therefore important that you regularly renew +the tokens so that the refresh token does not expire. + +You can renew your tokens with the following command: + +``` +php artisan fortnox:refresh +``` + +*Both the access token and the refresh token are stored in your application's cache. +Keep this in mind when you purge the cache or changes cache driver.* + +## Instructions + +Coming soon... + +## Contributing + +Contributions are welcome! + +## License + +The MIT License (MIT). Please see [License File](LICENSE) for more information. diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..7526573 --- /dev/null +++ b/composer.json @@ -0,0 +1,34 @@ +{ + "name": "kfoobar/laravel-fortnox", + "description": "Fortnox API Client for Laravel", + "keywords": ["laravel", "fortnox", "bookkeep", "api"], + "license": "MIT", + "type": "project", + "require": { + "php": "^8", + "ext-json": "*", + "illuminate/console": ">=6", + "illuminate/support": ">=6" + }, + "autoload": { + "psr-4": { + "KFoobar\\Fortnox\\": "src/" + } + }, + "authors": [ + { + "name": "David Villa", + "email": "david@kfoobar.se" + } + ], + "extra": { + "laravel": { + "providers": [ + "KFoobar\\Fortnox\\FortnoxServiceProvider" + ], + "aliases": { + "Fortnox": "KFoobar\\Fortnox\\Facades\\Fortnox" + } + } + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..79ad90d --- /dev/null +++ b/composer.lock @@ -0,0 +1,2180 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "a8547ee4fc0080a111aadcba836e4ead", + "packages": [ + { + "name": "doctrine/inflector", + "version": "2.0.6", + "source": { + "type": "git", + "url": "https://github.com/doctrine/inflector.git", + "reference": "d9d313a36c872fd6ee06d9a6cbcf713eaa40f024" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/d9d313a36c872fd6ee06d9a6cbcf713eaa40f024", + "reference": "d9d313a36c872fd6ee06d9a6cbcf713eaa40f024", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^10", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpstan/phpstan-strict-rules": "^1.3", + "phpunit/phpunit": "^8.5 || ^9.5", + "vimeo/psalm": "^4.25" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", + "homepage": "https://www.doctrine-project.org/projects/inflector.html", + "keywords": [ + "inflection", + "inflector", + "lowercase", + "manipulation", + "php", + "plural", + "singular", + "strings", + "uppercase", + "words" + ], + "support": { + "issues": "https://github.com/doctrine/inflector/issues", + "source": "https://github.com/doctrine/inflector/tree/2.0.6" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finflector", + "type": "tidelift" + } + ], + "time": "2022-10-20T09:10:12+00:00" + }, + { + "name": "illuminate/bus", + "version": "v9.45.1", + "source": { + "type": "git", + "url": "https://github.com/illuminate/bus.git", + "reference": "c7f09872054f2b361f8ed9e9e988b3c9be06c596" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/bus/zipball/c7f09872054f2b361f8ed9e9e988b3c9be06c596", + "reference": "c7f09872054f2b361f8ed9e9e988b3c9be06c596", + "shasum": "" + }, + "require": { + "illuminate/collections": "^9.0", + "illuminate/contracts": "^9.0", + "illuminate/pipeline": "^9.0", + "illuminate/support": "^9.0", + "php": "^8.0.2" + }, + "suggest": { + "illuminate/queue": "Required to use closures when chaining jobs (^7.0)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Bus\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Bus package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2022-11-25T07:56:47+00:00" + }, + { + "name": "illuminate/collections", + "version": "v9.45.1", + "source": { + "type": "git", + "url": "https://github.com/illuminate/collections.git", + "reference": "7a8afa0875d7de162f30865d9fae33c8fb235fa2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/collections/zipball/7a8afa0875d7de162f30865d9fae33c8fb235fa2", + "reference": "7a8afa0875d7de162f30865d9fae33c8fb235fa2", + "shasum": "" + }, + "require": { + "illuminate/conditionable": "^9.0", + "illuminate/contracts": "^9.0", + "illuminate/macroable": "^9.0", + "php": "^8.0.2" + }, + "suggest": { + "symfony/var-dumper": "Required to use the dump method (^6.0)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.x-dev" + } + }, + "autoload": { + "files": [ + "helpers.php" + ], + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Collections package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2022-12-02T18:48:05+00:00" + }, + { + "name": "illuminate/conditionable", + "version": "v9.45.1", + "source": { + "type": "git", + "url": "https://github.com/illuminate/conditionable.git", + "reference": "5b40f51ccb07e0e7b1ec5559d8db9e0e2dc51883" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/conditionable/zipball/5b40f51ccb07e0e7b1ec5559d8db9e0e2dc51883", + "reference": "5b40f51ccb07e0e7b1ec5559d8db9e0e2dc51883", + "shasum": "" + }, + "require": { + "php": "^8.0.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Conditionable package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2022-07-29T19:44:19+00:00" + }, + { + "name": "illuminate/console", + "version": "v9.45.1", + "source": { + "type": "git", + "url": "https://github.com/illuminate/console.git", + "reference": "152e203af3dc19350b335c633d6b5c0cd06c40fa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/console/zipball/152e203af3dc19350b335c633d6b5c0cd06c40fa", + "reference": "152e203af3dc19350b335c633d6b5c0cd06c40fa", + "shasum": "" + }, + "require": { + "illuminate/collections": "^9.0", + "illuminate/contracts": "^9.0", + "illuminate/macroable": "^9.0", + "illuminate/support": "^9.0", + "illuminate/view": "^9.0", + "nunomaduro/termwind": "^1.13", + "php": "^8.0.2", + "symfony/console": "^6.0.9", + "symfony/process": "^6.0" + }, + "suggest": { + "dragonmantank/cron-expression": "Required to use scheduler (^3.3.2).", + "guzzlehttp/guzzle": "Required to use the ping methods on schedules (^7.5).", + "illuminate/bus": "Required to use the scheduled job dispatcher (^9.0).", + "illuminate/container": "Required to use the scheduler (^9.0).", + "illuminate/filesystem": "Required to use the generator command (^9.0).", + "illuminate/queue": "Required to use closures for scheduled jobs (^9.0)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Console\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Console package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2022-12-16T16:52:48+00:00" + }, + { + "name": "illuminate/container", + "version": "v9.45.1", + "source": { + "type": "git", + "url": "https://github.com/illuminate/container.git", + "reference": "8ca3036459e26dc7cdedaf0f882b625757cc341e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/container/zipball/8ca3036459e26dc7cdedaf0f882b625757cc341e", + "reference": "8ca3036459e26dc7cdedaf0f882b625757cc341e", + "shasum": "" + }, + "require": { + "illuminate/contracts": "^9.0", + "php": "^8.0.2", + "psr/container": "^1.1.1|^2.0.1" + }, + "provide": { + "psr/container-implementation": "1.1|2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Container\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Container package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2022-09-05T15:58:42+00:00" + }, + { + "name": "illuminate/contracts", + "version": "v9.45.1", + "source": { + "type": "git", + "url": "https://github.com/illuminate/contracts.git", + "reference": "c7cc6e6198cac6dfdead111f9758de25413188b7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/contracts/zipball/c7cc6e6198cac6dfdead111f9758de25413188b7", + "reference": "c7cc6e6198cac6dfdead111f9758de25413188b7", + "shasum": "" + }, + "require": { + "php": "^8.0.2", + "psr/container": "^1.1.1|^2.0.1", + "psr/simple-cache": "^1.0|^2.0|^3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Contracts\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Contracts package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2022-10-31T22:25:40+00:00" + }, + { + "name": "illuminate/events", + "version": "v9.45.1", + "source": { + "type": "git", + "url": "https://github.com/illuminate/events.git", + "reference": "8e534676bac23bc17925f5c74c128f9c09b98f69" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/events/zipball/8e534676bac23bc17925f5c74c128f9c09b98f69", + "reference": "8e534676bac23bc17925f5c74c128f9c09b98f69", + "shasum": "" + }, + "require": { + "illuminate/bus": "^9.0", + "illuminate/collections": "^9.0", + "illuminate/container": "^9.0", + "illuminate/contracts": "^9.0", + "illuminate/macroable": "^9.0", + "illuminate/support": "^9.0", + "php": "^8.0.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.x-dev" + } + }, + "autoload": { + "files": [ + "functions.php" + ], + "psr-4": { + "Illuminate\\Events\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Events package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2022-09-15T13:14:12+00:00" + }, + { + "name": "illuminate/filesystem", + "version": "v9.45.1", + "source": { + "type": "git", + "url": "https://github.com/illuminate/filesystem.git", + "reference": "9923cb717f5505b84200fb78feba1c2f2fe9fe83" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/filesystem/zipball/9923cb717f5505b84200fb78feba1c2f2fe9fe83", + "reference": "9923cb717f5505b84200fb78feba1c2f2fe9fe83", + "shasum": "" + }, + "require": { + "illuminate/collections": "^9.0", + "illuminate/contracts": "^9.0", + "illuminate/macroable": "^9.0", + "illuminate/support": "^9.0", + "php": "^8.0.2", + "symfony/finder": "^6.0" + }, + "suggest": { + "ext-ftp": "Required to use the Flysystem FTP driver.", + "illuminate/http": "Required for handling uploaded files (^7.0).", + "league/flysystem": "Required to use the Flysystem local driver (^3.0.16).", + "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^3.0).", + "league/flysystem-ftp": "Required to use the Flysystem FTP driver (^3.0).", + "league/flysystem-sftp-v3": "Required to use the Flysystem SFTP driver (^3.0).", + "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).", + "symfony/filesystem": "Required to enable support for relative symbolic links (^6.0).", + "symfony/mime": "Required to enable support for guessing extensions (^6.0)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Filesystem\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Filesystem package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2022-12-08T16:55:54+00:00" + }, + { + "name": "illuminate/macroable", + "version": "v9.45.1", + "source": { + "type": "git", + "url": "https://github.com/illuminate/macroable.git", + "reference": "e3bfaf6401742a9c6abca61b9b10e998e5b6449a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/macroable/zipball/e3bfaf6401742a9c6abca61b9b10e998e5b6449a", + "reference": "e3bfaf6401742a9c6abca61b9b10e998e5b6449a", + "shasum": "" + }, + "require": { + "php": "^8.0.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Macroable package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2022-08-09T13:29:29+00:00" + }, + { + "name": "illuminate/pipeline", + "version": "v9.45.1", + "source": { + "type": "git", + "url": "https://github.com/illuminate/pipeline.git", + "reference": "e0be3f3f79f8235ad7334919ca4094d5074e02f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/pipeline/zipball/e0be3f3f79f8235ad7334919ca4094d5074e02f6", + "reference": "e0be3f3f79f8235ad7334919ca4094d5074e02f6", + "shasum": "" + }, + "require": { + "illuminate/contracts": "^9.0", + "illuminate/support": "^9.0", + "php": "^8.0.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Pipeline\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Pipeline package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2022-06-09T14:13:53+00:00" + }, + { + "name": "illuminate/support", + "version": "v9.45.1", + "source": { + "type": "git", + "url": "https://github.com/illuminate/support.git", + "reference": "d7f7c07e35a2c09cbeeddc0168826cf05a2eeb84" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/support/zipball/d7f7c07e35a2c09cbeeddc0168826cf05a2eeb84", + "reference": "d7f7c07e35a2c09cbeeddc0168826cf05a2eeb84", + "shasum": "" + }, + "require": { + "doctrine/inflector": "^2.0", + "ext-json": "*", + "ext-mbstring": "*", + "illuminate/collections": "^9.0", + "illuminate/conditionable": "^9.0", + "illuminate/contracts": "^9.0", + "illuminate/macroable": "^9.0", + "nesbot/carbon": "^2.62.1", + "php": "^8.0.2", + "voku/portable-ascii": "^2.0" + }, + "conflict": { + "tightenco/collect": "<5.5.33" + }, + "suggest": { + "illuminate/filesystem": "Required to use the composer class (^9.0).", + "league/commonmark": "Required to use Str::markdown() and Stringable::markdown() (^2.0.2).", + "ramsey/uuid": "Required to use Str::uuid() (^4.7).", + "symfony/process": "Required to use the composer class (^6.0).", + "symfony/uid": "Required to use Str::ulid() (^6.0).", + "symfony/var-dumper": "Required to use the dd function (^6.0).", + "vlucas/phpdotenv": "Required to use the Env class and env helper (^5.4.1)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.x-dev" + } + }, + "autoload": { + "files": [ + "helpers.php" + ], + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Support package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2022-12-20T14:03:34+00:00" + }, + { + "name": "illuminate/view", + "version": "v9.45.1", + "source": { + "type": "git", + "url": "https://github.com/illuminate/view.git", + "reference": "ceb08678d07b1d6092f3ef1d9154db5d4f155eb4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/view/zipball/ceb08678d07b1d6092f3ef1d9154db5d4f155eb4", + "reference": "ceb08678d07b1d6092f3ef1d9154db5d4f155eb4", + "shasum": "" + }, + "require": { + "ext-json": "*", + "illuminate/collections": "^9.0", + "illuminate/container": "^9.0", + "illuminate/contracts": "^9.0", + "illuminate/events": "^9.0", + "illuminate/filesystem": "^9.0", + "illuminate/macroable": "^9.0", + "illuminate/support": "^9.0", + "php": "^8.0.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\View\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate View package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2022-12-16T19:07:05+00:00" + }, + { + "name": "nesbot/carbon", + "version": "2.64.0", + "source": { + "type": "git", + "url": "https://github.com/briannesbitt/Carbon.git", + "reference": "889546413c97de2d05063b8cb7b193c2531ea211" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/889546413c97de2d05063b8cb7b193c2531ea211", + "reference": "889546413c97de2d05063b8cb7b193c2531ea211", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^7.1.8 || ^8.0", + "symfony/polyfill-mbstring": "^1.0", + "symfony/polyfill-php80": "^1.16", + "symfony/translation": "^3.4 || ^4.0 || ^5.0 || ^6.0" + }, + "require-dev": { + "doctrine/dbal": "^2.0 || ^3.1.4", + "doctrine/orm": "^2.7", + "friendsofphp/php-cs-fixer": "^3.0", + "kylekatarnls/multi-tester": "^2.0", + "ondrejmirtes/better-reflection": "*", + "phpmd/phpmd": "^2.9", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12.99 || ^1.7.14", + "phpunit/php-file-iterator": "^2.0.5 || ^3.0.6", + "phpunit/phpunit": "^7.5.20 || ^8.5.26 || ^9.5.20", + "squizlabs/php_codesniffer": "^3.4" + }, + "bin": [ + "bin/carbon" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-3.x": "3.x-dev", + "dev-master": "2.x-dev" + }, + "laravel": { + "providers": [ + "Carbon\\Laravel\\ServiceProvider" + ] + }, + "phpstan": { + "includes": [ + "extension.neon" + ] + } + }, + "autoload": { + "psr-4": { + "Carbon\\": "src/Carbon/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Nesbitt", + "email": "brian@nesbot.com", + "homepage": "https://markido.com" + }, + { + "name": "kylekatarnls", + "homepage": "https://github.com/kylekatarnls" + } + ], + "description": "An API extension for DateTime that supports 281 different languages.", + "homepage": "https://carbon.nesbot.com", + "keywords": [ + "date", + "datetime", + "time" + ], + "support": { + "docs": "https://carbon.nesbot.com/docs", + "issues": "https://github.com/briannesbitt/Carbon/issues", + "source": "https://github.com/briannesbitt/Carbon" + }, + "funding": [ + { + "url": "https://github.com/sponsors/kylekatarnls", + "type": "github" + }, + { + "url": "https://opencollective.com/Carbon#sponsor", + "type": "opencollective" + }, + { + "url": "https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=readme", + "type": "tidelift" + } + ], + "time": "2022-11-26T17:36:00+00:00" + }, + { + "name": "nunomaduro/termwind", + "version": "v1.15.0", + "source": { + "type": "git", + "url": "https://github.com/nunomaduro/termwind.git", + "reference": "594ab862396c16ead000de0c3c38f4a5cbe1938d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/594ab862396c16ead000de0c3c38f4a5cbe1938d", + "reference": "594ab862396c16ead000de0c3c38f4a5cbe1938d", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^8.0", + "symfony/console": "^5.3.0|^6.0.0" + }, + "require-dev": { + "ergebnis/phpstan-rules": "^1.0.", + "illuminate/console": "^8.0|^9.0", + "illuminate/support": "^8.0|^9.0", + "laravel/pint": "^1.0.0", + "pestphp/pest": "^1.21.0", + "pestphp/pest-plugin-mock": "^1.0", + "phpstan/phpstan": "^1.4.6", + "phpstan/phpstan-strict-rules": "^1.1.0", + "symfony/var-dumper": "^5.2.7|^6.0.0", + "thecodingmachine/phpstan-strict-rules": "^1.0.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Termwind\\Laravel\\TermwindServiceProvider" + ] + } + }, + "autoload": { + "files": [ + "src/Functions.php" + ], + "psr-4": { + "Termwind\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "Its like Tailwind CSS, but for the console.", + "keywords": [ + "cli", + "console", + "css", + "package", + "php", + "style" + ], + "support": { + "issues": "https://github.com/nunomaduro/termwind/issues", + "source": "https://github.com/nunomaduro/termwind/tree/v1.15.0" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + }, + { + "url": "https://github.com/xiCO2k", + "type": "github" + } + ], + "time": "2022-12-20T19:00:15+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/simple-cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "support": { + "source": "https://github.com/php-fig/simple-cache/tree/3.0.0" + }, + "time": "2021-10-29T13:26:27+00:00" + }, + { + "name": "symfony/console", + "version": "v6.2.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "5a9bd5c543f00157c55face973c149957467db31" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/5a9bd5c543f00157c55face973c149957467db31", + "reference": "5a9bd5c543f00157c55face973c149957467db31", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/string": "^5.4|^6.0" + }, + "conflict": { + "symfony/dependency-injection": "<5.4", + "symfony/dotenv": "<5.4", + "symfony/event-dispatcher": "<5.4", + "symfony/lock": "<5.4", + "symfony/process": "<5.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/event-dispatcher": "^5.4|^6.0", + "symfony/lock": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/var-dumper": "^5.4|^6.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v6.2.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-12-16T15:08:36+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "1ee04c65529dea5d8744774d474e7cbd2f1206d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/1ee04c65529dea5d8744774d474e7cbd2f1206d3", + "reference": "1ee04c65529dea5d8744774d474e7cbd2f1206d3", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.3-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-25T10:21:52+00:00" + }, + { + "name": "symfony/finder", + "version": "v6.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "eb2355f69519e4ef33f1835bca4c935f5d42e570" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/eb2355f69519e4ef33f1835bca4c935f5d42e570", + "reference": "eb2355f69519e4ef33f1835bca4c935f5d42e570", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "symfony/filesystem": "^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v6.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-10-09T08:55:40+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "5bbc823adecdae860bb64756d639ecfec17b050a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a", + "reference": "5bbc823adecdae860bb64756d639ecfec17b050a", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "511a08c03c1960e08a883f4cffcacd219b758354" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/511a08c03c1960e08a883f4cffcacd219b758354", + "reference": "511a08c03c1960e08a883f4cffcacd219b758354", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/19bd1e4fcd5b91116f14d8533c57831ed00571b6", + "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534", + "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/process", + "version": "v6.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "ba6e55359f8f755fe996c58a81e00eaa67a35877" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/ba6e55359f8f755fe996c58a81e00eaa67a35877", + "reference": "ba6e55359f8f755fe996c58a81e00eaa67a35877", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v6.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-02T09:08:04+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "aac98028c69df04ee77eb69b96b86ee51fbf4b75" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/aac98028c69df04ee77eb69b96b86ee51fbf4b75", + "reference": "aac98028c69df04ee77eb69b96b86ee51fbf4b75", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^2.0" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.3-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-25T10:21:52+00:00" + }, + { + "name": "symfony/string", + "version": "v6.2.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "863219fd713fa41cbcd285a79723f94672faff4d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/863219fd713fa41cbcd285a79723f94672faff4d", + "reference": "863219fd713fa41cbcd285a79723f94672faff4d", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.0" + }, + "require-dev": { + "symfony/error-handler": "^5.4|^6.0", + "symfony/http-client": "^5.4|^6.0", + "symfony/intl": "^6.2", + "symfony/translation-contracts": "^2.0|^3.0", + "symfony/var-exporter": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v6.2.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-12-14T16:11:27+00:00" + }, + { + "name": "symfony/translation", + "version": "v6.2.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "3294288c335b6267eab14964bf2c46015663d93f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/3294288c335b6267eab14964bf2c46015663d93f", + "reference": "3294288c335b6267eab14964bf2c46015663d93f", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-mbstring": "~1.0", + "symfony/translation-contracts": "^2.3|^3.0" + }, + "conflict": { + "symfony/config": "<5.4", + "symfony/console": "<5.4", + "symfony/dependency-injection": "<5.4", + "symfony/http-kernel": "<5.4", + "symfony/twig-bundle": "<5.4", + "symfony/yaml": "<5.4" + }, + "provide": { + "symfony/translation-implementation": "2.3|3.0" + }, + "require-dev": { + "nikic/php-parser": "^4.13", + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0", + "symfony/console": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/finder": "^5.4|^6.0", + "symfony/http-client-contracts": "^1.1|^2.0|^3.0", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/intl": "^5.4|^6.0", + "symfony/polyfill-intl-icu": "^1.21", + "symfony/routing": "^5.4|^6.0", + "symfony/service-contracts": "^1.1.2|^2|^3", + "symfony/yaml": "^5.4|^6.0" + }, + "suggest": { + "nikic/php-parser": "To use PhpAstExtractor", + "psr/log-implementation": "To use logging capability in translator", + "symfony/config": "", + "symfony/yaml": "" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to internationalize your application", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/translation/tree/v6.2.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-12-13T18:04:17+00:00" + }, + { + "name": "symfony/translation-contracts", + "version": "v3.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "68cce71402305a015f8c1589bfada1280dc64fe7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/68cce71402305a015f8c1589bfada1280dc64fe7", + "reference": "68cce71402305a015f8c1589bfada1280dc64fe7", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "suggest": { + "symfony/translation-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.3-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to translation", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/translation-contracts/tree/v3.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-25T10:21:52+00:00" + }, + { + "name": "voku/portable-ascii", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/voku/portable-ascii.git", + "reference": "b56450eed252f6801410d810c8e1727224ae0743" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/voku/portable-ascii/zipball/b56450eed252f6801410d810c8e1727224ae0743", + "reference": "b56450eed252f6801410d810c8e1727224ae0743", + "shasum": "" + }, + "require": { + "php": ">=7.0.0" + }, + "require-dev": { + "phpunit/phpunit": "~6.0 || ~7.0 || ~9.0" + }, + "suggest": { + "ext-intl": "Use Intl for transliterator_transliterate() support" + }, + "type": "library", + "autoload": { + "psr-4": { + "voku\\": "src/voku/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Lars Moelleken", + "homepage": "http://www.moelleken.org/" + } + ], + "description": "Portable ASCII library - performance optimized (ascii) string functions for php.", + "homepage": "https://github.com/voku/portable-ascii", + "keywords": [ + "ascii", + "clean", + "php" + ], + "support": { + "issues": "https://github.com/voku/portable-ascii/issues", + "source": "https://github.com/voku/portable-ascii/tree/2.0.1" + }, + "funding": [ + { + "url": "https://www.paypal.me/moelleken", + "type": "custom" + }, + { + "url": "https://github.com/voku", + "type": "github" + }, + { + "url": "https://opencollective.com/portable-ascii", + "type": "open_collective" + }, + { + "url": "https://www.patreon.com/voku", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/voku/portable-ascii", + "type": "tidelift" + } + ], + "time": "2022-03-08T17:03:00+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.0.2", + "ext-json": "*" + }, + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/config/fortnox.php b/config/fortnox.php new file mode 100644 index 0000000..e34d923 --- /dev/null +++ b/config/fortnox.php @@ -0,0 +1,11 @@ + env('FORTNOX_HOST', 'https://api.fortnox.se/3/'), + 'client_id' => env('FORTNOX_CLIENT_ID', ''), + 'client_secret' => env('FORTNOX_CLIENT_SECRET', ''), + 'refresh_token' => env('FORTNOX_REFRESH_TOKEN', ''), + 'timeout' => env('FORTNOX_TIMEOUT', 5), + +]; diff --git a/database/migrations/.gitkeep b/database/migrations/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/Commands/DisplayTokensCommand.php b/src/Commands/DisplayTokensCommand.php new file mode 100644 index 0000000..0f9126b --- /dev/null +++ b/src/Commands/DisplayTokensCommand.php @@ -0,0 +1,41 @@ +components->info( + 'Access token: ' . Cache::get('fortnox-access-token', 'Missing') + ); + + $this->components->info( + 'Refresh token: ' . Cache::get('fortnox-refresh-token', 'Missing') + ); + + return Command::SUCCESS; + } +} diff --git a/src/Commands/PurgeTokensCommand.php b/src/Commands/PurgeTokensCommand.php new file mode 100644 index 0000000..6c0b78d --- /dev/null +++ b/src/Commands/PurgeTokensCommand.php @@ -0,0 +1,62 @@ +confirmAction()) { + $this->components->info('Aborting...'); + + return Command::SUCCESS; + } + + try { + Cache::forget('fortnox-access-token'); + Cache::forget('fortnox-refresh-token'); + } catch (\Exception) { + $this->components->error('Failed to cached tokens!'); + + return Command::FAILURE; + } + + $this->components->info('Successfully cleared cached tokens!'); + + return Command::SUCCESS; + } + + /** + * Prompts the user for confirmation. + * + * @return bool + */ + protected function confirmAction(): bool + { + $this->components->warn('This will clear all your cached tokens! Make sure you add a valid refresh token to your .env file to continue using the Fortnox API.'); + + return $this->confirm('Do you want to continue?'); + } +} diff --git a/src/Commands/RefreshTokensCommand.php b/src/Commands/RefreshTokensCommand.php new file mode 100644 index 0000000..cbae52f --- /dev/null +++ b/src/Commands/RefreshTokensCommand.php @@ -0,0 +1,43 @@ +get('ping'); + } catch (\Exception) { + $this->components->error('Failed to refresh tokens!'); + + return Command::FAILURE; + } + + $this->components->info('Successfully refreshed tokens!'); + + return Command::SUCCESS; + } +} diff --git a/src/Exceptions/FortnoxException.php b/src/Exceptions/FortnoxException.php new file mode 100644 index 0000000..7b4d21a --- /dev/null +++ b/src/Exceptions/FortnoxException.php @@ -0,0 +1,8 @@ +mergeConfigFrom( + __DIR__.'/../config/fortnox.php', 'fortnox' + ); + } + + /** + * Bootstrap services. + * + * @return void + */ + public function boot() + { + $this->publishes([ + __DIR__.'/../config/fortnox.php' => config_path('fortnox.php'), + ], 'fortnox-config'); + + if ($this->app->runningInConsole()) { + $this->commands([ + DisplayTokensCommand::class, + PurgeTokensCommand::class, + RefreshTokensCommand::class, + ]); + } + } +} diff --git a/src/Interfaces/ClientInterface.php b/src/Interfaces/ClientInterface.php new file mode 100644 index 0000000..fb9a39f --- /dev/null +++ b/src/Interfaces/ClientInterface.php @@ -0,0 +1,8 @@ + $this->getFilter(), + 'sortby' => $this->getSortBy(), + 'sortorder' => $this->getSortOrder(), + 'limit' => $this->getLimit(), + 'offset' => $this->getOffset(), + 'page' => $this->getPage(), + ]; + + $query = array_filter( + array_merge($query, $this->getSearch()) + ); + + $query = array_map('mb_strtolower', $query); + + return $query; + } + + /** + * @return mixed + */ + public function getSearch() + { + return $this->search; + } + + /** + * @param string $key + * @param string $value + * + * @return self + */ + public function setSearch(string $key, string $value) + { + $this->search[$key] = $search; + + return $this; + } + + /** + * @return mixed + */ + public function getFilter() + { + return $this->filter; + } + + /** + * @param string $filter + * + * @return self + */ + public function setFilter(string $filter) + { + $this->filter = $filter; + + return $this; + } + + /** + * @return mixed + */ + public function getSortBy() + { + return $this->sortBy; + } + + /** + * @param string $sortBy + * + * @return self + */ + public function setSortBy(string $sortBy) + { + $this->sortBy = $sortBy; + + return $this; + } + + /** + * @return mixed + */ + public function getSortOrder() + { + return $this->sortOrder; + } + + /** + * @param string $sortOrder + * + * @return self + */ + public function setSortOrder(string $sortOrder) + { + if (in_array($sortOrder, ['ascending', 'descending'])) { + throw new FortnoxException('Sort order must be ascending or descending.'); + } + + $this->sortOrder = $sortOrder; + + return $this; + } + + /** + * @return self + */ + public function sortAsc() + { + $this->sortOrder = 'ascending'; + + return $this; + } + + /** + * @return self + */ + public function sortDesc() + { + $this->sortOrder = 'descending'; + + return $this; + } + + /** + * @return mixed + */ + public function getLimit() + { + return $this->limit; + } + + /** + * @param int $limit + * + * @return self + */ + public function setLimit(int $limit) + { + if ($limit > 500) { + throw new FortnoxException('Limit can not be higher then 500.'); + } + + $this->limit = $limit; + + return $this; + } + + /** + * @return mixed + */ + public function getOffset() + { + return $this->offset; + } + + /** + * @param int $offset + * + * @return self + */ + public function setOffset(int $offset) + { + $this->offset = $offset; + + return $this; + } + + /** + * @return mixed + */ + public function getPage() + { + return $this->page; + } + + /** + * @param int $page + * + * @return self + */ + public function setPage(int $page) + { + $this->page = $page; + + return $this; + } +} diff --git a/src/Resources/Account/Accounts.php b/src/Resources/Account/Accounts.php new file mode 100644 index 0000000..fc6ed47 --- /dev/null +++ b/src/Resources/Account/Accounts.php @@ -0,0 +1,29 @@ +client = $client; + } +} diff --git a/src/Resources/Archive/.gitkeep b/src/Resources/Archive/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/Resources/Article/.gitkeep b/src/Resources/Article/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/Resources/Company/.gitkeep b/src/Resources/Company/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/Resources/Contract/.gitkeep b/src/Resources/Contract/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/Resources/CostCenter/CostCenters.php b/src/Resources/CostCenter/CostCenters.php new file mode 100644 index 0000000..3b540bf --- /dev/null +++ b/src/Resources/CostCenter/CostCenters.php @@ -0,0 +1,30 @@ +client = $client; + } +} diff --git a/src/Resources/Currency/Currencies.php b/src/Resources/Currency/Currencies.php new file mode 100644 index 0000000..29e766a --- /dev/null +++ b/src/Resources/Currency/Currencies.php @@ -0,0 +1,30 @@ +client = $client; + } +} diff --git a/src/Resources/Customer/Customers.php b/src/Resources/Customer/Customers.php new file mode 100644 index 0000000..8b66cd7 --- /dev/null +++ b/src/Resources/Customer/Customers.php @@ -0,0 +1,30 @@ +client = $client; + } +} diff --git a/src/Resources/Employee/.gitkeep b/src/Resources/Employee/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/Resources/Expense/.gitkeep b/src/Resources/Expense/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/Resources/FinancialYear/FinancialYears.php b/src/Resources/FinancialYear/FinancialYears.php new file mode 100644 index 0000000..79cc03c --- /dev/null +++ b/src/Resources/FinancialYear/FinancialYears.php @@ -0,0 +1,28 @@ +client = $client; + } +} diff --git a/src/Resources/Invoice/Invoices.php b/src/Resources/Invoice/Invoices.php new file mode 100644 index 0000000..d840223 --- /dev/null +++ b/src/Resources/Invoice/Invoices.php @@ -0,0 +1,183 @@ +client = $client; + } + + /** + * Bookkeep given invoice. + * + * @param mixed $id + * + * @return mixed + */ + public function bookkeep(mixed $id): mixed + { + $endpoint = sprintf('%s/%s/bookkeep', $this->endpoint, $id); + + return $this->client->put($endpoint); + } + + /** + * Cancels given invoice. + * + * @param mixed $id + * + * @return mixed + */ + public function cancel(mixed $id): mixed + { + $endpoint = sprintf('%s/%s/cancel', $this->endpoint, $id); + + return $this->client->put($endpoint); + } + + /** + * Credit given invoice. + * + * @param mixed $id + * + * @return mixed + */ + public function credit(mixed $id): mixed + { + $endpoint = sprintf('%s/%s/credit', $this->endpoint, $id); + + return $this->client->put($endpoint); + } + + /** + * Set given invoice as sent. + * + * @param mixed $id + * + * @return mixed + */ + public function sent(mixed $id): mixed + { + $endpoint = sprintf('%s/%s/externalprint', $this->endpoint, $id); + + return $this->client->put($endpoint); + } + + /** + * Set given invoice as done. + * + * @param mixed $id + * + * @return mixed + */ + public function done(mixed $id): mixed + { + $endpoint = sprintf('%s/%s/warehouseready', $this->endpoint, $id); + + return $this->client->put($endpoint); + } + + /** + * Prints given invoice. + * + * @param mixed $id + * + * @return mixed + */ + public function print(mixed $id): mixed + { + $endpoint = sprintf('%s/%s/print', $this->endpoint, $id); + + return $this->client->get($endpoint); + } + + /** + * Sends given invoice as email. + * + * @param mixed $id + * + * @return mixed + */ + public function email(mixed $id): mixed + { + $endpoint = sprintf('%s/%s/email', $this->endpoint, $id); + + return $this->client->get($endpoint); + } + + /** + * Prints given invoice as reminder. + * + * @param mixed $id + * + * @return mixed + */ + public function reminder(mixed $id): mixed + { + $endpoint = sprintf('%s/%s/printreminder', $this->endpoint, $id); + + return $this->client->get($endpoint); + } + + /** + * Preview given invoice. + * + * @param mixed $id + * + * @return mixed + */ + public function preview(mixed $id): mixed + { + $endpoint = sprintf('%s/%s/preview', $this->endpoint, $id); + + return $this->client->get($endpoint); + } + + /** + * Sends given invoice as e-print. + * + * @param mixed $id + * + * @return mixed + */ + public function eprint(mixed $id): mixed + { + $endpoint = sprintf('%s/%s/eprint', $this->endpoint, $id); + + return $this->client->get($endpoint); + } + + /** + * Sends given invoice as e-invoice. + * + * @param mixed $id + * + * @return mixed + */ + public function einvoice(mixed $id): mixed + { + $endpoint = sprintf('%s/%s/einvoice', $this->endpoint, $id); + + return $this->client->get($endpoint); + } +} diff --git a/src/Resources/Offer/.gitkeep b/src/Resources/Offer/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/Resources/Order/.gitkeep b/src/Resources/Order/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/Resources/Price/PriceLists.php b/src/Resources/Price/PriceLists.php new file mode 100644 index 0000000..f3b3138 --- /dev/null +++ b/src/Resources/Price/PriceLists.php @@ -0,0 +1,29 @@ +client = $client; + } +} diff --git a/src/Resources/Project/Projects.php b/src/Resources/Project/Projects.php new file mode 100644 index 0000000..ee50987 --- /dev/null +++ b/src/Resources/Project/Projects.php @@ -0,0 +1,30 @@ +client = $client; + } +} diff --git a/src/Resources/Supplier/SupplierInvoices.php b/src/Resources/Supplier/SupplierInvoices.php new file mode 100644 index 0000000..8e30292 --- /dev/null +++ b/src/Resources/Supplier/SupplierInvoices.php @@ -0,0 +1,85 @@ +client = $client; + } + + /** + * Bookkeep given supplier invoice. + * + * @param mixed $id + * + * @return mixed + */ + public function bookkeep(mixed $id): mixed + { + $endpoint = sprintf('%s/%s/bookkeep', $this->endpoint, $id); + + return $this->client->put($endpoint); + } + + /** + * Cancels given supplier invoice. + * + * @param mixed $id + * + * @return mixed + */ + public function cancel(mixed $id): mixed + { + $endpoint = sprintf('%s/%s/cancel', $this->endpoint, $id); + + return $this->client->put($endpoint); + } + + /** + * Credit given supplier invoice. + * + * @param mixed $id + * + * @return mixed + */ + public function credit(mixed $id): mixed + { + $endpoint = sprintf('%s/%s/credit', $this->endpoint, $id); + + return $this->client->put($endpoint); + } + + /** + * Approval of payment of given supplier invoice. + * + * @param mixed $id + * + * @return mixed + */ + public function approve(mixed $id): mixed + { + $endpoint = sprintf('%s/%s/approvalpayment', $this->endpoint, $id); + + return $this->client->put($endpoint); + } +} diff --git a/src/Resources/Supplier/Suppliers.php b/src/Resources/Supplier/Suppliers.php new file mode 100644 index 0000000..04fead5 --- /dev/null +++ b/src/Resources/Supplier/Suppliers.php @@ -0,0 +1,29 @@ +client = $client; + } +} diff --git a/src/Resources/Template/PrintTemplates.php b/src/Resources/Template/PrintTemplates.php new file mode 100644 index 0000000..f29c748 --- /dev/null +++ b/src/Resources/Template/PrintTemplates.php @@ -0,0 +1,41 @@ +client = $client; + } + + /** + * Retrieve a single print template. + * + * @param mixed $id + * @throws \KFoobar\Fortnox\Exceptions\FortnoxException + * + * @return mixed + */ + public function get(mixed $id): mixed + { + throw new FortnoxException('Print templates do not support this method.'); + } +} diff --git a/src/Resources/Terms/TermsOfDeliveries.php b/src/Resources/Terms/TermsOfDeliveries.php new file mode 100644 index 0000000..3207f9e --- /dev/null +++ b/src/Resources/Terms/TermsOfDeliveries.php @@ -0,0 +1,29 @@ +client = $client; + } +} diff --git a/src/Resources/Terms/TermsOfPayments.php b/src/Resources/Terms/TermsOfPayments.php new file mode 100644 index 0000000..d7f5cf0 --- /dev/null +++ b/src/Resources/Terms/TermsOfPayments.php @@ -0,0 +1,30 @@ +client = $client; + } +} diff --git a/src/Resources/Unit/Units.php b/src/Resources/Unit/Units.php new file mode 100644 index 0000000..b1693ad --- /dev/null +++ b/src/Resources/Unit/Units.php @@ -0,0 +1,30 @@ +client = $client; + } +} diff --git a/src/Resources/Voucher/VoucherSeries.php b/src/Resources/Voucher/VoucherSeries.php new file mode 100644 index 0000000..2d43808 --- /dev/null +++ b/src/Resources/Voucher/VoucherSeries.php @@ -0,0 +1,29 @@ +client = $client; + } +} diff --git a/src/Resources/Voucher/Vouchers.php b/src/Resources/Voucher/Vouchers.php new file mode 100644 index 0000000..020ceeb --- /dev/null +++ b/src/Resources/Voucher/Vouchers.php @@ -0,0 +1,60 @@ +client = $client; + } + + /** + * Retrieve a specific voucher. + * + * @param mixed $series + * @param mixed $number + * @param \KFoobar\Fortnox\Services\QueryObject $query + * + * @return mixed + */ + public function get(mixed $series, mixed $number, QueryObject $query = null): mixed + { + $endpoint = sprintf('%s/%s/%s', $this->endpoint, $series, $number); + + return $this->client->get($endpoint, $query->toArray() ?? []); + } + + /** + * Retrieve a list of vouchers for a specific series. + * + * @param mixed $series + * @param \KFoobar\Fortnox\Services\QueryObject $query + * + * @return mixed + */ + public function sublist(mixed $series, QueryObject $query = null): mixed + { + $endpoint = sprintf('%s/%s', $this->endpoint, $series); + + return $this->client->get($endpoint, $query->toArray() ?? []); + } +} diff --git a/src/Services/Client.php b/src/Services/Client.php new file mode 100644 index 0000000..0fffdec --- /dev/null +++ b/src/Services/Client.php @@ -0,0 +1,201 @@ +client = Http::baseUrl($this->getHost()) + ->timeout($this->getTimeout()) + ->withToken($this->getAccessToken()) + ->asJson() + ->acceptJson(); + } + + /** + * Sends GET request. + * + * @param string $endpoint + * @param array $data + * @param array $filter + * + * @return mixed + */ + public function get(string $endpoint, array $data = [], array $filter = []): mixed + { + return $this->client->get($endpoint, $data); + } + + /** + * Sends PUT request. + * + * @param string $endpoint + * @param array $data + * + * @return mixed + */ + public function put(string $endpoint, array $data = []): mixed + { + $response = $this->client->put($endpoint, $data); + + if ($response->failed()) { + ray($response->json()); + } + + return $response; + } + + /** + * Sends POST request. + * + * @param string $endpoint + * @param array $data + * @param array $filter + * + * @return mixed + */ + public function post(string $endpoint, array $data = [], array $filter = []): mixed + { + $response = $this->client->post($endpoint, $data); + + if ($response->failed()) { + ray($response->json()); + } + + return $response; + } + + /** + * Send DELETE requests. + * + * @param string $endpoint + * @param array $data + * + * @return mixed + */ + public function delete(string $endpoint, array $data = []): mixed + { + $response = $this->client->delete($endpoint, $data); + + if ($response->failed()) { + ray($response->json()); + } + + return $response; + } + + /** + * Gets the host. + * + * @return null|string + */ + protected function getHost(): ?string + { + return config('fortnox.host'); + } + + /** + * Gets the client id. + * + * @return null|string + */ + protected function getClientId(): ?string + { + return config('fortnox.client_id'); + } + + /** + * Gets the client secret. + * + * @return null|string + */ + protected function getClientSecret(): ?string + { + return config('fortnox.client_secret'); + } + + /** + * Gets the access token. + * + * @return null|string + */ + protected function getAccessToken(): ?string + { + return Cache::remember('fortnox-access-token', 3500, function () { + return $this->refreshAccessToken(); + }); + } + + /** + * Gets the refresh token. + * + * @throws \KFoobar\Fortnox\Exceptions\FortnoxException + * + * @return string + */ + protected function getRefreshToken(): string + { + if (Cache::has('fortnox-refresh-token')) { + return Cache::get('fortnox-refresh-token'); + } + + if (!empty(config('fortnox.refresh_token'))) { + return config('fortnox.refresh_token'); + } + + throw new FortnoxException('Refresh token not found or not valid'); + } + + /** + * Gets the timeout. + * + * @return null|string + */ + protected function getTimeout(): ?string + { + return config('fortnox.timeout'); + } + + /** + * Refreshes the access and refresh token. + * + * @throws \KFoobar\Fortnox\Exceptions\FortnoxException + * + * @return string + */ + protected function refreshAccessToken(): string + { + $response = Http::withBasicAuth($this->getClientId(), $this->getClientSecret()) + ->timeout($this->getTimeout()) + ->asForm() + ->post('https://apps.fortnox.se/oauth-v1/token', [ + 'grant_type' => 'refresh_token', + 'refresh_token' => $this->getRefreshToken(), + ]); + + if ($response->failed()) { + throw new FortnoxException('Failed to refresh token.'); + } + + if (empty($response->json('access_token'))) { + throw new FortnoxException('Failed to retrieve access token from response.'); + } + + if (empty($response->json('refresh_token'))) { + throw new FortnoxException('Failed to retrieve refresh token from response.'); + } + + Cache::put('fortnox-refresh-token', $response->json('refresh_token'), 2160000); // 25 days + + return $response->json('access_token'); + } +} diff --git a/src/Services/Fortnox.php b/src/Services/Fortnox.php new file mode 100644 index 0000000..1a1ea6a --- /dev/null +++ b/src/Services/Fortnox.php @@ -0,0 +1,192 @@ +client = new Client; + } + + /** + * Returns the accounts resource. + * + * @return \KFoobar\Fortnox\Resources\Account\Accounts + */ + public function accounts() + { + return new Accounts($this->client); + } + + /** + * Returns the cost centers resource. + * + * @return \KFoobar\Fortnox\Resources\CostCenter\CostCenters + */ + public function costCenters() + { + return new CostCenters($this->client); + } + + /** + * Returns the currencies resource. + * + * @return \KFoobar\Fortnox\Resources\Currency\Currencies + */ + public function currencies() + { + return new Currencies($this->client); + } + + /** + * Returns the customers resource. + * + * @return \KFoobar\Fortnox\Resources\Customer\Customers + */ + public function customers() + { + return new Customers($this->client); + } + + /** + * Returns the financial years resource. + * + * @return \KFoobar\Fortnox\Resources\FinancialYear\FinancialYears + */ + public function financialYears() + { + return new FinancialYears($this->client); + } + + /** + * Returns the invoices resource. + * + * @return \KFoobar\Fortnox\Resources\Invoice\Invoices + */ + public function invoices() + { + return new Invoices($this->client); + } + + /** + * Returns the price lists resource. + * + * @return \KFoobar\Fortnox\Resources\Price\PriceLists + */ + public function priceLists() + { + return new PriceLists($this->client); + } + + /** + * Returns the projects resource. + * + * @return \KFoobar\Fortnox\Resources\Account\Projects + */ + public function projects() + { + return new Projects($this->client); + } + + /** + * Returns the supplier invoices resource. + * + * @return \KFoobar\Fortnox\Resources\Supplier\SupplierInvoices + */ + public function supplierInvoices() + { + return new SupplierInvoices($this->client); + } + + /** + * Returns the suppliers resource. + * + * @return \KFoobar\Fortnox\Resources\Supplier\Suppliers + */ + public function suppliers() + { + return new Suppliers($this->client); + } + + /** + * Returns the print templates resource. + * + * @return \KFoobar\Fortnox\Resources\Template\PrintTemplates + */ + public function printTemplates() + { + return new PrintTemplates($this->client); + } + + /** + * Returns the terms of deliveries resource. + * + * @return \KFoobar\Fortnox\Resources\Terms\TermsOfDeliveries + */ + public function termsOfDeliveries() + { + return new TermsOfDeliveries($this->client); + } + + /** + * Returns the terms of payments resource. + * + * @return \KFoobar\Fortnox\Resources\Terms\TermsOfPayments + */ + public function termsOfPayments() + { + return new TermsOfPayments($this->client); + } + + /** + * Returns the units resource. + * + * @return \KFoobar\Fortnox\Resources\Unit\Units + */ + public function units() + { + return new Units($this->client); + } + + /** + * Returns the vouchers resource. + * + * @return \KFoobar\Fortnox\Resources\Voucher\Vouchers + */ + public function vouchers() + { + return new Vouchers($this->client); + } + + /** + * Returns the voucher series resource. + * + * @return \KFoobar\Fortnox\Resources\Voucher\VoucherSeries + */ + public function voucherSeries() + { + return new VoucherSeries($this->client); + } +} diff --git a/src/Traits/HasCreate.php b/src/Traits/HasCreate.php new file mode 100644 index 0000000..3f2c76f --- /dev/null +++ b/src/Traits/HasCreate.php @@ -0,0 +1,18 @@ +client->post($endpoint); + } +} diff --git a/src/Traits/HasDelete.php b/src/Traits/HasDelete.php new file mode 100644 index 0000000..0ee927d --- /dev/null +++ b/src/Traits/HasDelete.php @@ -0,0 +1,20 @@ +endpoint, $id); + + return $this->client->delete($endpoint); + } +} diff --git a/src/Traits/HasRetrieve.php b/src/Traits/HasRetrieve.php new file mode 100644 index 0000000..1d86d43 --- /dev/null +++ b/src/Traits/HasRetrieve.php @@ -0,0 +1,39 @@ +endpoint, $id); + + $response = $this->client->get($endpoint, $query?->toArray() ?? []); + + return $response->json(); + } + + /** + * Sends an get request for all items. + * + * @param \KFoobar\Fortnox\Services\QueryObject $query + * + * @return mixed + */ + public function all(QueryObject $query = null): mixed + { + $response = $this->client->get($this->endpoint, $query?->toArray() ?? []); + + return $response->json(); + } +} diff --git a/src/Traits/HasUpdate.php b/src/Traits/HasUpdate.php new file mode 100644 index 0000000..4238bd8 --- /dev/null +++ b/src/Traits/HasUpdate.php @@ -0,0 +1,21 @@ +endpoint, $id); + + return $this->client->put($endpoint, $data); + } +}