mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-07 18:27:16 -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_ID=
|
||||||
NORDIGEN_SECRET_KEY=
|
NORDIGEN_SECRET_KEY=
|
||||||
|
|
||||||
|
GOCARDLESS_CLIENT_ID=
|
||||||
|
GOCARDLESS_CLIENT_SECRET=
|
||||||
|
|
||||||
OPENEXCHANGE_APP_ID=
|
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),
|
'client_id' => env('CHORUS_CLIENT_ID', false),
|
||||||
'secret' => env('CHORUS_SECRET', 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' => [
|
'quickbooks' => [
|
||||||
'client_id' => env('QUICKBOOKS_CLIENT_ID', false),
|
'client_id' => env('QUICKBOOKS_CLIENT_ID', false),
|
||||||
'client_secret' => env('QUICKBOOKS_CLIENT_SECRET', false),
|
'client_secret' => env('QUICKBOOKS_CLIENT_SECRET', 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\EInvoice\SelfhostController;
|
||||||
use App\Http\Controllers\Gateways\Checkout3dsController;
|
use App\Http\Controllers\Gateways\Checkout3dsController;
|
||||||
use App\Http\Controllers\Gateways\GoCardlessController;
|
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\Gateways\Mollie3dsController;
|
||||||
use App\Http\Controllers\SetupController;
|
use App\Http\Controllers\SetupController;
|
||||||
use App\Http\Controllers\StripeConnectController;
|
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('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('.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');
|
Route::get('einvoice/beta', [SelfhostController::class, 'index'])->name('einvoice.beta');
|
||||||
|
|
||||||
\Illuminate\Support\Facades\Broadcast::routes(['middleware' => ['token_auth']]);
|
\Illuminate\Support\Facades\Broadcast::routes(['middleware' => ['token_auth']]);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user