Skip to content

Commit 769c3c7

Browse files
JamesFreemanhailwood
authored andcommitted
First step into documentation handling
1 parent 944963e commit 769c3c7

File tree

8 files changed

+288
-268
lines changed

8 files changed

+288
-268
lines changed

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
build
22
composer.lock
3-
docs
43
vendor
54
coverage
65
.phpunit.result.cache

README.md

Lines changed: 0 additions & 267 deletions
Original file line numberDiff line numberDiff line change
@@ -14,273 +14,6 @@ You can install this package via composer using the following command:
1414
composer require webfox/laravel-xero-oauth2
1515
```
1616

17-
The package will automatically register itself.
18-
19-
You should add your Xero keys to your `.env` file using the following keys:
20-
21-
```
22-
XERO_CLIENT_ID=
23-
XERO_CLIENT_SECRET=
24-
```
25-
26-
(on [Xero developer portal](https://developer.xero.com/app/manage)): ***IMPORTANT*** When setting up the application in Xero ensure your redirect url is:
27-
28-
```
29-
https://{your-domain}/xero/auth/callback
30-
```
31-
32-
*(The flow is xero/auth/callback performs the oAuth handshake and stores your token, then redirects you over to your success callback)*
33-
34-
You can publish the configuration file with:
35-
36-
```
37-
php artisan vendor:publish --provider="Webfox\Xero\XeroServiceProvider" --tag="config"
38-
```
39-
40-
## Scopes
41-
42-
You'll want to set the scopes required for your application in the config file.
43-
44-
The default set of scopes are `openid`, `email`, `profile`, `offline_access`, and `accounting.settings`.
45-
You can see all available scopes on [the official Xero documentation](https://developer.xero.com/documentation/oauth2/scopes).
46-
47-
## Using the Package
48-
49-
This package registers two bindings into the service container you'll be interested in:
50-
51-
* `\XeroAPI\XeroPHP\Api\AccountingApi::class` this is the main api for Xero - see the [xeroapi/xero-php-oauth2 docs](https://github.com/XeroAPI/xero-php-oauth2/tree/master/docs) for usage.
52-
When you first resolve this dependency if the stored credentials are expired it will automatically refresh the token.
53-
* `Webfox\Xero\OauthCredentialManager` this is the credential manager - The Accounting API requires we pass through a tenant ID on each request, this class is how you'd access that.
54-
This is also where we can get information about the authenticating user. See below for an example.
55-
56-
*app\Http\Controllers\XeroController.php*
57-
58-
```php
59-
<?php
60-
61-
namespace App\Http\Controllers;
62-
63-
use Illuminate\Http\Request;
64-
use App\Http\Controllers\Controller;
65-
use Webfox\Xero\OauthCredentialManager;
66-
67-
class XeroController extends Controller
68-
{
69-
70-
public function index(Request $request, OauthCredentialManager $xeroCredentials)
71-
{
72-
try {
73-
// Check if we've got any stored credentials
74-
if ($xeroCredentials->exists()) {
75-
/*
76-
* We have stored credentials so we can resolve the AccountingApi,
77-
* If we were sure we already had some stored credentials then we could just resolve this through the controller
78-
* But since we use this route for the initial authentication we cannot be sure!
79-
*/
80-
$xero = resolve(\XeroAPI\XeroPHP\Api\AccountingApi::class);
81-
$organisationName = $xero->getOrganisations($xeroCredentials->getTenantId())->getOrganisations()[0]->getName();
82-
$user = $xeroCredentials->getUser();
83-
$username = "{$user['given_name']} {$user['family_name']} ({$user['username']})";
84-
}
85-
} catch (\throwable $e) {
86-
// This can happen if the credentials have been revoked or there is an error with the organisation (e.g. it's expired)
87-
$error = $e->getMessage();
88-
}
89-
90-
return view('xero', [
91-
'connected' => $xeroCredentials->exists(),
92-
'error' => $error ?? null,
93-
'organisationName' => $organisationName ?? null,
94-
'username' => $username ?? null
95-
]);
96-
}
97-
98-
}
99-
```
100-
101-
*resources\views\xero.blade.php*
102-
103-
```
104-
@extends('_layouts.main')
105-
106-
@section('content')
107-
@if($error)
108-
<h1>Your connection to Xero failed</h1>
109-
<p>{{ $error }}</p>
110-
<a href="{{ route('xero.auth.authorize') }}" class="btn btn-primary btn-large mt-4">
111-
Reconnect to Xero
112-
</a>
113-
@elseif($connected)
114-
<h1>You are connected to Xero</h1>
115-
<p>{{ $organisationName }} via {{ $username }}</p>
116-
<a href="{{ route('xero.auth.authorize') }}" class="btn btn-primary btn-large mt-4">
117-
Reconnect to Xero
118-
</a>
119-
@else
120-
<h1>You are not connected to Xero</h1>
121-
<a href="{{ route('xero.auth.authorize') }}" class="btn btn-primary btn-large mt-4">
122-
Connect to Xero
123-
</a>
124-
@endif
125-
@endsection
126-
```
127-
128-
*routes/web.php*
129-
130-
```php
131-
/*
132-
* We name this route xero.auth.success as by default the config looks for a route with this name to redirect back to
133-
* after authentication has succeeded. The name of this route can be changed in the config file.
134-
*/
135-
Route::get('/manage/xero', [\App\Http\Controllers\XeroController::class, 'index'])->name('xero.auth.success');
136-
```
137-
138-
### Error Handling
139-
140-
In the event that a user denies access on the Xero Authorisation page, the package will throw a `OAuthException` from the [AuthorizationCallbackController](src/Controllers/AuthorizationCallbackController.php). This can be caught and acted upon however you prefer.
141-
142-
#### Laravel 11
143-
144-
To do this in Laravel 11, bind a custom exception renderer in `bootstrap/app.php`:
145-
146-
```php
147-
return Application::configure(basePath: dirname(__DIR__))
148-
->withRouting(
149-
web: __DIR__ . '/../routes/web.php',
150-
commands: __DIR__ . '/../routes/console.php',
151-
health: '/up',
152-
)
153-
->withMiddleware(function (Middleware $middleware) {
154-
//
155-
})
156-
->withExceptions(function (Exceptions $exceptions) {
157-
// Handle when the user clicks cancel on the Xero authorization screen
158-
$exceptions->render(function (OAuthException $e, Request $request) {
159-
return redirect('/my/xero/connect/page')->with('errorMessage', $e->getMessage());
160-
});
161-
})->create();
162-
```
163-
164-
#### Laravel 8-10
165-
166-
Use the `reportable` method in the `App\Exceptions\Handler` class:
167-
168-
```php
169-
public function register()
170-
{
171-
$this->reportable(function (OAuthException $e) {
172-
// Handle when the user clicks cancel on the Xero authorization screen
173-
return redirect('/my/xero/connect/page')->with('errorMessage', $e->getMessage());
174-
});
175-
}
176-
```
177-
178-
## Credential Storage
179-
180-
Credentials are stored in a JSON file using the default disk on the Laravel Filesystem, with visibility set to private. This allows credential sharing across multiple servers using a shared disk such as S3, regardless of which server conducted the OAuth flow.
181-
182-
To use a different disk, change the `xero.credential_disk` config item to another disk defined in `config/filesystem.php`.
183-
184-
You can switch out the credential store (e.g. for your own `UserStore` if you wanted to store
185-
the credentials against your user) in one of two ways:
186-
187-
1. If it's a simple store and Laravel can automatically resolve your bindings, simply change the `xero.credential_store` config
188-
key to point to your new implementation.
189-
2. If it requires more advanced logic (e.g. using the current user to retrieve the credentials) then you can rebind this
190-
in your `AppServiceProvider` or a Middleware
191-
e.g.
192-
193-
```php
194-
$this->app->bind(OauthCredentialManager::class, function(Application $app) {
195-
return new UserStorageProvider(
196-
\Auth::user(), // Storage Mechanism
197-
$app->make('session.store'), // Used for storing/retrieving oauth 2 "state" for redirects
198-
$app->make(\Webfox\Xero\Oauth2Provider::class) // Used for getting redirect url and refreshing token
199-
);
200-
});
201-
```
202-
203-
An example UserStorageProvider [can been found here](https://github.com/webfox/laravel-xero-oauth2/issues/45#issuecomment-757552563)
204-
205-
## Using Webhooks
206-
207-
On your application in the Xero developer portal create a webhook to get your webhook key.
208-
209-
You can then add this to your `.env` file as
210-
211-
```
212-
XERO_WEBHOOK_KEY=...
213-
```
214-
215-
You can then setup a controller to handle your webhook and inject `\Webfox\Xero\Webhook` e.g.
216-
217-
```php
218-
<?php
219-
220-
namespace App\Http\Controllers;
221-
222-
use Webfox\Xero\Webhook;
223-
use Illuminate\Http\Request;
224-
use Illuminate\Http\Response;
225-
use XeroApi\XeroPHP\Models\Accounting\Contact;
226-
use XeroApi\XeroPHP\Models\Accounting\Invoice;
227-
228-
class XeroWebhookController extends Controller
229-
{
230-
public function __invoke(Request $request, Webhook $webhook)
231-
{
232-
233-
// The following lines are required for Xero's 'itent to receive' validation
234-
if (!$webhook->validate($request->header('x-xero-signature'))) {
235-
// We can't use abort here, since Xero expects no response body
236-
return response('', Response::HTTP_UNAUTHORIZED);
237-
}
238-
239-
// A single webhook trigger can contain multiple events, so we must loop over them
240-
foreach ($webhook->getEvents() as $event) {
241-
if ($event->getEventType() === 'CREATE' && $event->getEventCategory() === 'INVOICE') {
242-
$this->invoiceCreated($request, $event->getResource());
243-
} elseif ($event->getEventType() === 'CREATE' && $event->getEventCategory() === 'CONTACT') {
244-
$this->contactCreated($request, $event->getResource());
245-
} elseif ($event->getEventType() === 'UPDATE' && $event->getEventCategory() === 'INVOICE') {
246-
$this->invoiceUpdated($request, $event->getResource());
247-
} elseif ($event->getEventType() === 'UPDATE' && $event->getEventCategory() === 'CONTACT') {
248-
$this->contactUpdated($request, $event->getResource());
249-
}
250-
}
251-
252-
return response('', Response::HTTP_OK);
253-
}
254-
255-
protected function invoiceCreated(Request $request, Invoice $invoice)
256-
{
257-
}
258-
259-
protected function contactCreated(Request $request, Contact $contact)
260-
{
261-
}
262-
263-
protected function invoiceUpdated(Request $request, Invoice $invoice)
264-
{
265-
}
266-
267-
protected function contactUpdated(Request $request, Contact $contact)
268-
{
269-
}
270-
271-
}
272-
```
273-
274-
## Example calls
275-
276-
This package is simply a bridge so you don't have to deal with the Oauth2 gymnastics in Laravel.
277-
278-
Once you've have an instance of \XeroAPI\XeroPHP\Api\AccountingApi::class you're dealing directly with Xero's api library.
279-
280-
The XeroAPI PHP Oauth2 App repository has this list of examples of implementing calls to the API: e.g. invoice creation etc.
281-
282-
<https://github.com/XeroAPI/xero-php-oauth2-app/blob/master/example.php>
283-
28417
## License
28518

28619
The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

docs/_config.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
theme: jekyll-theme-slate

docs/credential-storage.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
## Credential Storage
2+
3+
Credentials are stored in a JSON file using the default disk on the Laravel Filesystem, with visibility set to private. This allows credential sharing across multiple servers using a shared disk such as S3, regardless of which server conducted the OAuth flow.
4+
5+
To use a different disk, change the `xero.credential_disk` config item to another disk defined in `config/filesystem.php`.
6+
7+
You can switch out the credential store (e.g. for your own `UserStore` if you wanted to store
8+
the credentials against your user) in one of two ways:
9+
10+
1. If it's a simple store and Laravel can automatically resolve your bindings, simply change the `xero.credential_store` config
11+
key to point to your new implementation.
12+
2. If it requires more advanced logic (e.g. using the current user to retrieve the credentials) then you can rebind this
13+
in your `AppServiceProvider` or a Middleware
14+
e.g.
15+
16+
```php
17+
$this->app->bind(OauthCredentialManager::class, function(Application $app) {
18+
return new UserStorageProvider(
19+
\Auth::user(), // Storage Mechanism
20+
$app->make('session.store'), // Used for storing/retrieving oauth 2 "state" for redirects
21+
$app->make(\Webfox\Xero\Oauth2Provider::class) // Used for getting redirect url and refreshing token
22+
);
23+
});
24+
```
25+
26+
An example UserStorageProvider [can be found here](https://github.com/webfox/laravel-xero-oauth2/issues/45#issuecomment-757552563)

docs/examples.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
*app\Http\Controllers\XeroController.php*
2+
3+
```php
4+
<?php
5+
6+
namespace App\Http\Controllers;
7+
8+
use Illuminate\Http\Request;
9+
use App\Http\Controllers\Controller;
10+
use Webfox\Xero\OauthCredentialManager;
11+
12+
class XeroController extends Controller
13+
{
14+
15+
public function index(Request $request, OauthCredentialManager $xeroCredentials)
16+
{
17+
try {
18+
// Check if we've got any stored credentials
19+
if ($xeroCredentials->exists()) {
20+
/*
21+
* We have stored credentials so we can resolve the AccountingApi,
22+
* If we were sure we already had some stored credentials then we could just resolve this through the controller
23+
* But since we use this route for the initial authentication we cannot be sure!
24+
*/
25+
$xero = resolve(\XeroAPI\XeroPHP\Api\AccountingApi::class);
26+
$organisationName = $xero->getOrganisations($xeroCredentials->getTenantId())->getOrganisations()[0]->getName();
27+
$user = $xeroCredentials->getUser();
28+
$username = "{$user['given_name']} {$user['family_name']} ({$user['username']})";
29+
}
30+
} catch (\throwable $e) {
31+
// This can happen if the credentials have been revoked or there is an error with the organisation (e.g. it's expired)
32+
$error = $e->getMessage();
33+
}
34+
35+
return view('xero', [
36+
'connected' => $xeroCredentials->exists(),
37+
'error' => $error ?? null,
38+
'organisationName' => $organisationName ?? null,
39+
'username' => $username ?? null
40+
]);
41+
}
42+
43+
}
44+
```
45+
46+
*resources\views\xero.blade.php*
47+
48+
```
49+
@extends('_layouts.main')
50+
51+
@section('content')
52+
@if($error)
53+
<h1>Your connection to Xero failed</h1>
54+
<p>{{ $error }}</p>
55+
<a href="{{ route('xero.auth.authorize') }}" class="btn btn-primary btn-large mt-4">
56+
Reconnect to Xero
57+
</a>
58+
@elseif($connected)
59+
<h1>You are connected to Xero</h1>
60+
<p>{{ $organisationName }} via {{ $username }}</p>
61+
<a href="{{ route('xero.auth.authorize') }}" class="btn btn-primary btn-large mt-4">
62+
Reconnect to Xero
63+
</a>
64+
@else
65+
<h1>You are not connected to Xero</h1>
66+
<a href="{{ route('xero.auth.authorize') }}" class="btn btn-primary btn-large mt-4">
67+
Connect to Xero
68+
</a>
69+
@endif
70+
@endsection
71+
```
72+
73+
*routes/web.php*
74+
75+
```php
76+
/*
77+
* We name this route xero.auth.success as by default the config looks for a route with this name to redirect back to
78+
* after authentication has succeeded. The name of this route can be changed in the config file.
79+
*/
80+
Route::get('/manage/xero', [\App\Http\Controllers\XeroController::class, 'index'])->name('xero.auth.success');
81+
```

0 commit comments

Comments
 (0)