mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-07 11:34:29 -04:00
Merge pull request #9584 from beganovich/1403-gocardless
GoCardless: OAuth
This commit is contained in:
commit
47a7d97e4f
@ -65,4 +65,7 @@ APPLE_REDIRECT_URI=
|
||||
NORDIGEN_SECRET_ID=
|
||||
NORDIGEN_SECRET_KEY=
|
||||
|
||||
GOCARDLESS_CLIENT_ID=
|
||||
GOCARDLESS_CLIENT_SECRET=
|
||||
|
||||
OPENEXCHANGE_APP_ID=
|
||||
|
109
app/Http/Controllers/Gateways/GoCardlessOAuthController.php
Normal file
109
app/Http/Controllers/Gateways/GoCardlessOAuthController.php
Normal file
@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
|
||||
namespace App\Http\Controllers\Gateways;
|
||||
|
||||
use App\DataMapper\FeesAndLimits;
|
||||
use App\Factory\CompanyGatewayFactory;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\GoCardless\OAuthConnectConfirmRequest;
|
||||
use App\Http\Requests\GoCardless\OAuthConnectRequest;
|
||||
use App\Models\CompanyGateway;
|
||||
use App\Models\GatewayType;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class GoCardlessOAuthController extends Controller
|
||||
{
|
||||
public function connect(OAuthConnectRequest $request): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
/** @var \App\Models\Company $company */
|
||||
$company = $request->getCompany();
|
||||
|
||||
$params = [
|
||||
'client_id' => config('services.gocardless.client_id'),
|
||||
'redirect_uri' => config('services.gocardless.redirect_uri'),
|
||||
'scope' => 'read_write',
|
||||
'response_type' => 'code',
|
||||
'state' => $company->company_key,
|
||||
'prefill[email]' => $company->settings->email,
|
||||
'prefill[organisation_name]' => $company->settings->name,
|
||||
'prefill[country_code]' => $company->country()->iso_3166_2,
|
||||
];
|
||||
|
||||
$url = config('services.gocardless.environment') === 'production'
|
||||
? 'https://connect.gocardless.com/oauth/authorize?%s'
|
||||
: 'https://connect-sandbox.gocardless.com/oauth/authorize?%s';
|
||||
|
||||
|
||||
return redirect()->to(
|
||||
sprintf($url, http_build_query($params))
|
||||
);
|
||||
}
|
||||
|
||||
public function confirm(OAuthConnectConfirmRequest $request): \Illuminate\Http\RedirectResponse|\Illuminate\View\View
|
||||
{
|
||||
/** @var \App\Models\Company $company */
|
||||
$company = $request->getCompany();
|
||||
|
||||
// LBo0v_561xgFGnFUae6uEQEfrWoSEMnZ&state=5O2O85C8dPv1Gp1UPVq0xs4FVTZdq5dO
|
||||
// https://invoicing.co/gocardless/oauth/connect/confirm?code=sH55_xb-2s1JtuEw-j7W0hT0Z1sFkM7l
|
||||
|
||||
$url = config('services.gocardless.environment') === 'production'
|
||||
? 'https://connect.gocardless.com/oauth/access_token'
|
||||
: 'https://connect-sandbox.gocardless.com/oauth/access_token';
|
||||
|
||||
$response = Http::post($url, [
|
||||
'client_id' => config('services.gocardless.client_id'),
|
||||
'client_secret' => config('services.gocardless.client_secret'),
|
||||
'grant_type' => 'authorization_code',
|
||||
'code' => $request->query('code'),
|
||||
'redirect_uri' => config('services.gocardless.redirect_uri'),
|
||||
]);
|
||||
|
||||
if ($response->failed()) {
|
||||
return view('auth.gocardless_connect.access_denied');
|
||||
}
|
||||
|
||||
$company_gateway = CompanyGateway::query()
|
||||
->where('gateway_key', 'b9886f9257f0c6ee7c302f1c74475f6c')
|
||||
->where('company_id', $company->id)
|
||||
->first();
|
||||
|
||||
if ($company_gateway === null) {
|
||||
$company_gateway = CompanyGatewayFactory::create($company->id, $company->owner()->id);
|
||||
$fees_and_limits = new \stdClass();
|
||||
$fees_and_limits->{GatewayType::INSTANT_BANK_PAY} = new FeesAndLimits();
|
||||
$company_gateway->gateway_key = 'b9886f9257f0c6ee7c302f1c74475f6c';
|
||||
$company_gateway->fees_and_limits = $fees_and_limits;
|
||||
$company_gateway->setConfig([]);
|
||||
}
|
||||
|
||||
$response = $response->json();
|
||||
|
||||
$payload = [
|
||||
'__current' => $company_gateway->getConfig(),
|
||||
'account_id' => $response['organisation_id'],
|
||||
'token_type' => $response['token_type'],
|
||||
'scope' => $response['scope'],
|
||||
'active' => $response['active'],
|
||||
'accessToken' => $response['access_token'],
|
||||
'testMode' => $company_gateway->getConfigField('testMode'),
|
||||
'oauth2' => true,
|
||||
];
|
||||
|
||||
$company_gateway->setConfig($payload);
|
||||
$company_gateway->save();
|
||||
|
||||
return view('auth.gocardless_connect.completed');
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\Gateways;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\GoCardless\WebhookRequest;
|
||||
use App\Models\CompanyGateway;
|
||||
use App\Repositories\CompanyRepository;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class GoCardlessOAuthWebhookController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
protected CompanyRepository $company_repository,
|
||||
) {
|
||||
}
|
||||
|
||||
public function __invoke(WebhookRequest $request)
|
||||
{
|
||||
foreach ($request->events as $event) {
|
||||
nlog($event['action']);
|
||||
|
||||
$e = Arr::dot($event);
|
||||
|
||||
if ($event['action'] === 'disconnected') {
|
||||
|
||||
/** @var \App\Models\CompanyGateway $company_gateway */
|
||||
$company_gateway = CompanyGateway::query()
|
||||
->whereJsonContains('config->account_id', $e['links.organisation'])
|
||||
->firstOrFail();
|
||||
|
||||
$current = $company_gateway->getConfig('__current');
|
||||
|
||||
if ($current) {
|
||||
$company_gateway->setConfig($current);
|
||||
$company_gateway->save();
|
||||
}
|
||||
|
||||
$this->company_repository->archive($company_gateway);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
47
app/Http/Requests/GoCardless/OAuthConnectConfirmRequest.php
Normal file
47
app/Http/Requests/GoCardless/OAuthConnectConfirmRequest.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Requests\GoCardless;
|
||||
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Company;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class OAuthConnectConfirmRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'state' => ['required', 'string'],
|
||||
'code' => ['required','string'],
|
||||
];
|
||||
}
|
||||
|
||||
public function getCompany(): \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Builder|\App\Models\BaseModel
|
||||
{
|
||||
MultiDB::findAndSetDbByCompanyKey(
|
||||
$this->query('state'),
|
||||
);
|
||||
|
||||
return Company::query()
|
||||
->where('company_key', $this->query('state'))
|
||||
->firstOrFail();
|
||||
}
|
||||
}
|
51
app/Http/Requests/GoCardless/OAuthConnectRequest.php
Normal file
51
app/Http/Requests/GoCardless/OAuthConnectRequest.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Requests\GoCardless;
|
||||
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Company;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class OAuthConnectRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public function getCompany(): \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Builder|\App\Models\BaseModel
|
||||
{
|
||||
$data = Cache::get(
|
||||
key: $this->token,
|
||||
);
|
||||
|
||||
MultiDB::findAndSetDbByCompanyKey(
|
||||
company_key: $data['company_key'],
|
||||
);
|
||||
|
||||
return Company::query()
|
||||
->where('company_key', $data['company_key'])
|
||||
->firstOrFail();
|
||||
}
|
||||
}
|
34
app/Http/Requests/GoCardless/WebhookRequest.php
Normal file
34
app/Http/Requests/GoCardless/WebhookRequest.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Requests\GoCardless;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class WebhookRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'events' => ['required', 'array'],
|
||||
'events.*.resource_type' => ['required', 'string'],
|
||||
];
|
||||
}
|
||||
}
|
@ -131,6 +131,12 @@ return [
|
||||
'client_id' => env('CHORUS_CLIENT_ID', false),
|
||||
'secret' => env('CHORUS_SECRET', false),
|
||||
],
|
||||
'gocardless' => [
|
||||
'client_id' => env('GOCARDLESS_CLIENT_ID', null),
|
||||
'client_secret' => env('GOCARDLESS_CLIENT_SECRET', null),
|
||||
'environment' => env('GOCARDLESS_ENVIRONMENT', 'production'),
|
||||
'redirect_uri' => env('GOCARDLESS_REDIRECT_URI', 'https://invoicing.co/gocardless/oauth/connect/confirm'),
|
||||
],
|
||||
'quickbooks' => [
|
||||
'client_id' => env('QUICKBOOKS_CLIENT_ID', false),
|
||||
'client_secret' => env('QUICKBOOKS_CLIENT_SECRET', false),
|
||||
@ -139,4 +145,4 @@ return [
|
||||
'quickbooks_webhook' => [
|
||||
'verifier_token' => env('QUICKBOOKS_VERIFIER_TOKEN', false),
|
||||
],
|
||||
];
|
||||
];
|
||||
|
@ -0,0 +1,30 @@
|
||||
@extends('layouts.ninja')
|
||||
@section('meta_title', ctrans('texts.error_title'))
|
||||
|
||||
@section('body')
|
||||
<div class="flex flex-col justify-center items-center mt-10">
|
||||
<div class="mb-4">
|
||||
<svg height="60" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
x="0px" y="0px" viewBox="0 0 1000 1000" style="enable-background:new 0 0 1000 1000;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0 {
|
||||
fill: #F1F252;
|
||||
}
|
||||
|
||||
.st1 {
|
||||
fill: #1C1B18;
|
||||
}
|
||||
</style>
|
||||
<circle class="st0" cx="500" cy="500" r="500" />
|
||||
<path class="st1" d="M507.9,242.1c55.2,0,86.2,9,86.2,9l91.7,187.2l-0.8,0.8l-118-70.4c-68.4-40.7-118-62.1-158.4-60.5
|
||||
c-42.7,0.8-68.3,35.2-68.3,85c1.5,127.5,122.7,284.5,243.1,284.5c49.1,0,74.7-15.8,89.7-34.9L494.8,447.3v-0.8h244.8
|
||||
c3.3,17.5,5.2,35.3,5.4,53.1c0,143.1-109.5,258.3-244.6,258.3C364.7,757.9,255,642.7,255,499.6C254.8,357.3,364.3,242.1,507.9,242.1
|
||||
z" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<p>We were unable to connect to GoCardless as access was denied.</p>
|
||||
<span>Click <a class="font-semibold hover:underline" href="{{ url('/#/settings/company_gateways') }}">here</a> to
|
||||
continue.</span>
|
||||
</div>
|
||||
@endsection
|
31
resources/views/auth/gocardless_connect/completed.blade.php
Normal file
31
resources/views/auth/gocardless_connect/completed.blade.php
Normal file
@ -0,0 +1,31 @@
|
||||
@extends('layouts.ninja')
|
||||
@section('meta_title', ctrans('texts.success'))
|
||||
|
||||
@section('body')
|
||||
<div class="flex flex-col justify-center items-center mt-10">
|
||||
<div class="mb-4">
|
||||
<svg height="60" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 1000 1000"
|
||||
style="enable-background:new 0 0 1000 1000;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0 {
|
||||
fill: #F1F252;
|
||||
}
|
||||
|
||||
.st1 {
|
||||
fill: #1C1B18;
|
||||
}
|
||||
</style>
|
||||
<circle class="st0" cx="500" cy="500" r="500" />
|
||||
<path class="st1" d="M507.9,242.1c55.2,0,86.2,9,86.2,9l91.7,187.2l-0.8,0.8l-118-70.4c-68.4-40.7-118-62.1-158.4-60.5
|
||||
c-42.7,0.8-68.3,35.2-68.3,85c1.5,127.5,122.7,284.5,243.1,284.5c49.1,0,74.7-15.8,89.7-34.9L494.8,447.3v-0.8h244.8
|
||||
c3.3,17.5,5.2,35.3,5.4,53.1c0,143.1-109.5,258.3-244.6,258.3C364.7,757.9,255,642.7,255,499.6C254.8,357.3,364.3,242.1,507.9,242.1
|
||||
z" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<p>Connecting your account using GoCardless has been successfully completed.</p>
|
||||
<span>Click <a class="font-semibold hover:underline" href="{{ url('/#/settings/company_gateways') }}">here</a> to
|
||||
continue.</span>
|
||||
</div>
|
||||
@endsection
|
@ -10,6 +10,8 @@ use App\Http\Controllers\ClientPortal\ApplePayDomainController;
|
||||
use App\Http\Controllers\EInvoice\SelfhostController;
|
||||
use App\Http\Controllers\Gateways\Checkout3dsController;
|
||||
use App\Http\Controllers\Gateways\GoCardlessController;
|
||||
use App\Http\Controllers\Gateways\GoCardlessOAuthController;
|
||||
use App\Http\Controllers\Gateways\GoCardlessOAuthWebhookController;
|
||||
use App\Http\Controllers\Gateways\Mollie3dsController;
|
||||
use App\Http\Controllers\SetupController;
|
||||
use App\Http\Controllers\StripeConnectController;
|
||||
@ -51,6 +53,10 @@ Route::get('mollie/3ds_redirect/{company_key}/{company_gateway_id}/{hash}', [Mol
|
||||
Route::get('gocardless/ibp_redirect/{company_key}/{company_gateway_id}/{hash}', [GoCardlessController::class, 'ibpRedirect'])->middleware('domain_db')->name('gocardless.ibp_redirect');
|
||||
Route::get('.well-known/apple-developer-merchantid-domain-association', [ApplePayDomainController::class, 'showAppleMerchantId']);
|
||||
|
||||
Route::get('gocardless/oauth/connect/confirm', [GoCardlessOAuthController::class, 'confirm'])->name('gocardless.oauth.confirm');
|
||||
Route::post('gocardless/oauth/connect/webhook', GoCardlessOAuthWebhookController::class)->name('gocardless.oauth.webhook');
|
||||
Route::get('gocardless/oauth/connect/{token}', [GoCardlessOAuthController::class, 'connect']);
|
||||
|
||||
Route::get('einvoice/beta', [SelfhostController::class, 'index'])->name('einvoice.beta');
|
||||
|
||||
\Illuminate\Support\Facades\Broadcast::routes(['middleware' => ['token_auth']]);
|
||||
|
Loading…
x
Reference in New Issue
Block a user